Repository navigation
Conversation
apply_mask_to_padding_states (and the inline mixer copies in Bamba and FalconH1) zeros padding positions before the depthwise conv and the SSM / gated-delta recurrence so padding cannot leak into a real token's state. It was gated on attention_mask.shape[1] > 1 and attention_mask.shape[0] > 1, so a single padded sequence (batch size 1) was never masked and its padding tokens leaked through the conv and recurrence into the real tokens, corrupting their logits. The batch-size gate cannot simply be dropped. During cached multi-token decode (for example assisted / speculative decoding) attention_mask spans the whole sequence while hidden_states holds only the new tokens, so the elementwise multiply would shape-mismatch. Replace the batch-size gate with a check that the mask is aligned with the current hidden states (attention_mask.shape[1] == hidden_states.shape[1]); this is true exactly in the prefill / full forward where masking is needed and false during cached decode. Batch size 1 prefill is now masked correctly while cached decode is untouched. Signed-off-by: Ting Sun <suntcrick@gmail.com>
|
[For maintainers] Suggested jobs to run (before merge) run-slow: bamba, falcon_h1, granitemoehybrid, lfm2, lfm2_moe, mamba2, olmo_hybrid, qwen3_5, qwen3_5_moe, qwen3_next |
|
CI Dashboard: View test results in Grafana |
| if attention_mask is not None and attention_mask.shape[1] > 1 and attention_mask.shape[0] > 1: | ||
| if ( | ||
| attention_mask is not None | ||
| and attention_mask.shape[1] > 1 | ||
| and attention_mask.shape[1] == hidden_states.shape[1] | ||
| ): |
There was a problem hiding this comment.
I guess the intuition was to skip non-padded inputs, and usually an input of bs==1 or seq-len==1 are not padded
Interesting that you have padding in a single input
There was a problem hiding this comment.
Right, the original attention_mask.shape[0] > 1 was a proxy for "is this input padded", assuming a single sequence is never padded. But left-padding a single prompt is a legitimate, common case (e.g. tokenizer(..., padding="max_length"), or a batch=1 request in a serving setup), and there it silently corrupts the real tokens because the pad positions are never zeroed before the conv/SSM recurrence.
I replaced the batch-size proxy with attention_mask.shape[1] == hidden_states.shape[1]: apply the mask exactly when the 2D mask aligns with the current hidden states (the padded prefill), which also naturally skips the cached decode step where the mask spans past+new tokens and is longer than the single-step hidden states.
There was a problem hiding this comment.
hmm, padded single sequences feel like a weird edge case though I get the reason ppl might be using it (static-shaped traced model ig?)
Let me trigger slow integration CI
|
run-slow: bamba, falcon_h1, granitemoehybrid, lfm2, lfm2_moe, mamba2, olmo_hybrid, qwen3_5, qwen3_5_moe, qwen3_next |
|
This comment contains models: ["models/bamba", "models/falcon_h1", "models/granitemoehybrid", "models/lfm2", "models/lfm2_moe", "models/mamba2", "models/olmo_hybrid", "models/qwen3_5", "models/qwen3_5_moe", "models/qwen3_next"] |
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
CI ResultsCommit Info
Model CI Report❌ 1 new failed tests from this PR 😭
|
|
@zucchini-nlp Slow CI is clean: every failure also fails on base ( On the edge case: it's not only traced/compiled models. A batch-of-1 request through a padded collator and |
What does this PR do?
Follow-up to #41790.
apply_mask_to_padding_states(and the inline mixer copies in Bamba and FalconH1) zeros padding positions before the depthwise conv and the SSM / gated-delta recurrence. It was gated onattention_mask.shape[1] > 1 and attention_mask.shape[0] > 1, so a batch-size-1 padded sequence was never masked: its padding flowed through the conv and recurrence into the real tokens and corrupted their logits. Batch size > 1 (the mask ran) and right padding (it follows the real tokens, which a causal model ignores) were unaffected.The
attention_mask.shape[0] > 1gate cannot just be dropped. During cached multi-token decode (for example assisted / speculative decoding)attention_maskspans the whole sequence whilehidden_statesholds only the new tokens, so the multiply would shape-mismatch and raise; the batch-size gate happened to hide that for batch size 1. This replaces it with an alignment check,attention_mask.shape[1] == hidden_states.shape[1], which is true exactly in the prefill / full forward where the padding needs zeroing and false during cached decode. #41790 already noted the intent was to keep only the prefill guard ("we only make sure it is applied in prefill stage"); this makes that guard robust.On
Qwen/Qwen3.5-0.8Bin float32, the last-token logits of a left-padded batch-size-1 sequence diverge from the unpadded sequence by ~5.7 onmainand ~1e-5 with this change. AddedQwen3_5TextModelTest::test_left_padding_matches_unpadded_batch_size_one(fails before, passes after); the existingLfm2ModelTest::test_assisted_decoding_*tests crash on a naive removal of the gate and pass with the alignment check.All ten linear-attention models sharing this code are updated: Bamba, FalconH1, GraniteMoeHybrid, LFM2, LFM2-MoE, Mamba2, Olmo-hybrid, Qwen3.5, Qwen3.5-MoE, Qwen3-Next. The modeling files are regenerated from the modular sources, so
make fix-repoandcheck_modular_conversionstay clean.Reproduction, evidence, and environment
Real-last-token logits, unpadded vs the same sequence padded, max abs diff (right padding compares the last real token, which precedes the pad; left padding compares position -1):
float32 isolates the bug from precision noise: batch-size-1 left padding jumps from ~1e-5 to 5.7 and the fix restores it. In bfloat16 the corruption is the same magnitude (5.7) and the fix brings batch size 1 back to the ~0.19 floor that batch size > 1 already sits at. Per layer the divergence originates at the first gated-delta-net layer.
Environment: transformers
main(5.13.0.dev0), torch 2.8.0+cu128, Python 3.12.3, Linux. The float32 numbers reproduce on CPU (torch fallback); the bfloat16 column was measured on an RTX 4090.Code Agent Policy
Who can review?
@zucchini-nlp