|
13 | 13 | from mobius.components._common import ( |
14 | 14 | Embedding, |
15 | 15 | Linear, |
| 16 | + build_packed_token_offset, |
16 | 17 | create_attention_bias, |
17 | 18 | create_padding_mask, |
18 | 19 | create_sliding_window_mask, |
@@ -214,6 +215,65 @@ def test_decode_single_query_is_causal(self): |
214 | 215 | assert bool((out[0, 0, 0] > -1.0).all()) |
215 | 216 |
|
216 | 217 |
|
| 218 | +class TestBuildPackedTokenOffset: |
| 219 | + """``build_packed_token_offset`` must reproduce ORT's GetPaddingOffset. |
| 220 | +
|
| 221 | + ORT's ``PackedMultiHeadAttention`` derives ``batch_size`` from |
| 222 | + ``token_offset.shape[0]`` and requires ``cumulative_sequence_length`` to |
| 223 | + have length ``batch_size + 1``. ``token_offset`` lists the padded-layout |
| 224 | + indices (``b * max_len + s``) of valid tokens (packed order) first, then |
| 225 | + the padding slots. We run the helper through ORT and compare exact values. |
| 226 | + """ |
| 227 | + |
| 228 | + @staticmethod |
| 229 | + def _build(dtype=ir.DataType.INT64): |
| 230 | + b, op, g = create_test_builder() |
| 231 | + cu = create_test_input(b, "cu_seqlens", ["K"], dtype=dtype) |
| 232 | + token_offset = build_packed_token_offset(op, cu) |
| 233 | + token_offset.name = "token_offset" |
| 234 | + g.outputs.append(token_offset) |
| 235 | + return ir.Model(g, ir_version=10) |
| 236 | + |
| 237 | + def _run(self, cu_seqlens, np_dtype=np.int64, ir_dtype=ir.DataType.INT64): |
| 238 | + sess = OnnxModelSession(self._build(ir_dtype), device="cpu") |
| 239 | + return sess.run({"cu_seqlens": np.array(cu_seqlens, dtype=np_dtype)})["token_offset"] |
| 240 | + |
| 241 | + def test_ort_reference_example(self): |
| 242 | + # ORT test data: cu=[0,1,3] (lengths 1,2; max_len=2) -> [[0,2],[3,1]]. |
| 243 | + out = self._run([0, 1, 3]) |
| 244 | + assert out.dtype == np.int32 |
| 245 | + assert np.array_equal(out, np.array([[0, 2], [3, 1]], dtype=np.int32)) |
| 246 | + |
| 247 | + def test_padding_indices_exceed_token_count(self): |
| 248 | + # cu=[0,2,5]: lengths [2,3], max_len=3, token_count=5. |
| 249 | + # Padded grid pos = b*3 + s -> row0 valid cols {0,1} pad col {2}; |
| 250 | + # row1 valid cols {3,4,5}. valid (packed order) = [0,1,3,4,5]; |
| 251 | + # padding slot = [2]. token_offset = [[0,1,3],[4,5,2]]. |
| 252 | + out = self._run([0, 2, 5]) |
| 253 | + assert np.array_equal(out, np.array([[0, 1, 3], [4, 5, 2]], dtype=np.int32)) |
| 254 | + # Padding value (2) is a padded-layout index, here < token_count, but |
| 255 | + # the construction may yield values >= token_count for other shapes. |
| 256 | + |
| 257 | + def test_single_subsequence_is_identity(self): |
| 258 | + # cu=[0,4]: one sub-sequence -> shape (1,4), identity [0,1,2,3]. |
| 259 | + out = self._run([0, 4]) |
| 260 | + assert out.shape == (1, 4) |
| 261 | + assert np.array_equal(out, np.array([[0, 1, 2, 3]], dtype=np.int32)) |
| 262 | + |
| 263 | + def test_uniform_windows(self): |
| 264 | + # Three windows of equal length 2: max_len=2, no padding. |
| 265 | + out = self._run([0, 2, 4, 6]) |
| 266 | + assert out.shape == (3, 2) |
| 267 | + assert np.array_equal(out, np.array([[0, 1], [2, 3], [4, 5]], dtype=np.int32)) |
| 268 | + |
| 269 | + def test_int32_input(self): |
| 270 | + # The helper documents INT32 or INT64 cu_seqlens; INT32 input must |
| 271 | + # produce the same result as the INT64 reference example. |
| 272 | + out = self._run([0, 1, 3], np_dtype=np.int32, ir_dtype=ir.DataType.INT32) |
| 273 | + assert out.dtype == np.int32 |
| 274 | + assert np.array_equal(out, np.array([[0, 2], [3, 1]], dtype=np.int32)) |
| 275 | + |
| 276 | + |
217 | 277 | class TestCreatePaddingMask: |
218 | 278 | def test_creates_bool_mask_with_2d_input_ids(self): |
219 | 279 | """Standard path: input_ids is 2D [batch, q_len].""" |
|
0 commit comments