Skip to content

Commit 5aa4da9

Browse files
WindChimeRanrahul-tulishanjiaz
authored
test(mtp): pin packed-batch attention to document-local (#621) (#627)
## Purpose Regression test for #621. Full investigation in the issue thread. MTP builds its mask on the dense `create_causal_mask` path, where transformers auto-derives the block-diagonal packed mask from the per-document `position_ids` the collate emits (positions reset per document, attention_mask=None). So MTP training attention is already document-local — implicitly via position_ids, not via an explicit document_ids mask. This test pins that invariant: a packed batch with per-document position_ids must yield a causal and same-document mask in the MTP forward. It fails if positions stop carrying segment boundaries or packed-mask detection changes — the silent failure mode where loss still decreases. ## Tests This PR is test only. ## Checklist I have filled in: - [x] The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)". - [x] The test plan/results, such as providing test command and pasting the results. - [ ] (Optional) The necessary documentation update. - [x] I (a human) have written or reviewed the code in this pr to the best of my ability. --------- Signed-off-by: Ranran Haoran Zhang <ranzhang@redhat.com> Co-authored-by: Rahul Tuli <rtuli@redhat.com> Co-authored-by: shanjiaz <zsjwpianpian@gmail.com>
1 parent 0451356 commit 5aa4da9

1 file changed

Lines changed: 54 additions & 0 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""MTP packed-batch attention must stay document-local (#621).
2+
3+
When several documents are packed into one training row, no token may attend
4+
across a document boundary. MTP builds its mask on the dense
5+
``create_causal_mask`` path, where transformers derives the block-diagonal
6+
packed mask from the per-document ``position_ids`` the collate produces (each
7+
document's positions restart at 0, ``attention_mask`` is None). This test pins
8+
that invariant so it can't silently regress (e.g. if positions stop resetting
9+
or packed-mask detection changes) -- a failure mode where loss still decreases.
10+
"""
11+
12+
import torch
13+
14+
import speculators.models.mtp.core as mtp_core
15+
16+
17+
def test_packed_batch_attention_is_document_local(mtp_model, monkeypatch):
18+
captured = {}
19+
real = mtp_core.create_causal_mask
20+
21+
def spy(*args, **kwargs):
22+
captured["mask"] = real(*args, **kwargs)
23+
return captured["mask"]
24+
25+
monkeypatch.setattr(mtp_core, "create_causal_mask", spy)
26+
27+
lengths = [1, 3, 2] # three packed documents
28+
n = sum(lengths)
29+
# size the row so valid_len (= seq_len - num_steps - 1) covers all documents
30+
seq_len = n + mtp_model.config.num_speculative_steps + 1
31+
# per-document position_ids restart at 0 (as the training collate produces)
32+
position_ids = torch.zeros(1, seq_len, dtype=torch.long)
33+
position_ids[0, :n] = torch.cat([torch.arange(length) for length in lengths])
34+
document_ids = torch.cat(
35+
[torch.full((length,), i) for i, length in enumerate(lengths)]
36+
)
37+
38+
with torch.no_grad():
39+
mtp_model(
40+
input_ids=torch.randint(0, mtp_model.config.vocab_size, (1, seq_len)),
41+
hidden_states=torch.randn(1, seq_len, mtp_model.config.hidden_size),
42+
position_ids=position_ids,
43+
)
44+
45+
assert "mask" in captured, "create_causal_mask was never called"
46+
mask = captured["mask"]
47+
assert isinstance(mask, torch.Tensor) # dense mask path, not BlockMask/None
48+
# [batch, 1, q, kv]: broadcast over heads, so head 0 is authoritative
49+
assert mask.shape == (1, 1, n, n)
50+
allow = mask[0, 0] == 0 # additive mask -> "may attend"
51+
idx = torch.arange(n)
52+
doc = document_ids
53+
expected = (idx[:, None] >= idx[None, :]) & (doc[:, None] == doc[None, :])
54+
assert torch.equal(allow, expected)

0 commit comments

Comments
 (0)