Repository navigation
Fix RecurrentGemma padded generation by declaring its layer_types - #50577
Conversation
Without `layer_types`, every layer is inferred as `sliding_attention`, so the cache allocates KV slots for the recurrent blocks too. The mask / `get_seq_length` then read the (always empty) slot of layer 0, a recurrent block, which gives a wrong decode mask with padding (CUDA SDPA error or silently wrong outputs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| @unittest.skip( | ||
| "RecurrentGemma's recurrent block has no chunked-continuation support: a cached multi-token forward is " | ||
| "treated as a new prefill, so the split-vs-single comparison cannot match regardless of padding masking." | ||
| ) | ||
| def test_recurrent_layers_mask_padding_on_continued_forward(self): | ||
| pass | ||
|
|
There was a problem hiding this comment.
it was skipped before. I don't want to handle this in the current PR.
mangeshraut712
left a comment
There was a problem hiding this comment.
Declaring layer_types so the generic generate/cache path can prepare a mask dict is the right fix, and reusing a mask that generate already built avoids building a second one. The fallback always reads the sliding_attention entry, which does not match a mixed or recurrent-only stack.
| ) | ||
| # It may already have been prepared by e.g. `generate` | ||
| if isinstance(attention_mask, dict): | ||
| causal_mask = attention_mask["sliding_attention"] |
There was a problem hiding this comment.
layer_types maps recurrent blocks to "linear_attention" and attention blocks to "sliding_attention". When generate has already prepared attention_mask as a dict, this line always takes attention_mask["sliding_attention"] and uses that one tensor for every layer. A RecurrentGemma whose block_types are all "recurrent" (or a generate path that only filled linear_attention) will KeyError here. Even on the usual mixed stack, the linear/recurrent layers are then masked with the sliding-window tensor rather than the linear_attention mask that was prepared for them.
If recurrent blocks ignore the mask and only the attention blocks need the sliding window, please say so in the comment — but I'd still use .get("sliding_attention") and fall back to create_sliding_window_causal_mask(...) when that key is missing. If they do consume the mask, the dict should be passed through (or indexed per layer_types[i]) so the linear-attention entry is not dropped. The new skip on test_recurrent_layers_mask_padding_on_continued_forward is fair given the missing chunked-continuation support, but it means padded continued generation stays untested after this fix.
There was a problem hiding this comment.
the model is so old, that it would require too much effort to fix it up properly imo
some of your initial points are more theoretical issues which are not really important
|
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. |
|
@mangeshraut712 not sure if it's fully agent comments but since I'm seeing you on multiple PRs now, could you keep the comments shorter (and human). Atm it feels very "agent just spammed" but I dont want to judge preemptively Reviewing is hard enough but agents love to bloat comments to the end which makes it hard to extract the core messages at times |
vasqu
left a comment
There was a problem hiding this comment.
Some comments but my biggest point is to avoid introducing yet another layer types property 👀
| return (self.block_types * 100)[: self.num_hidden_layers] | ||
|
|
||
| @property | ||
| def layer_types(self): |
There was a problem hiding this comment.
hmm why not changed it directly in layers block types instead? Id like to avoid having so many layer types living side by side
| ) | ||
| # It may already have been prepared by e.g. `generate` | ||
| if isinstance(attention_mask, dict): | ||
| causal_mask = attention_mask["sliding_attention"] |
There was a problem hiding this comment.
the model is so old, that it would require too much effort to fix it up properly imo
some of your initial points are more theoretical issues which are not really important
| @unittest.skip( | ||
| "RecurrentGemma's recurrent block has no chunked-continuation support: a cached multi-token forward is " | ||
| "treated as a new prefill, so the split-vs-single comparison cannot match regardless of padding masking." | ||
| ) | ||
| def test_recurrent_layers_mask_padding_on_continued_forward(self): | ||
| pass | ||
|
|
…d property Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vasqu
left a comment
There was a problem hiding this comment.
🚨 just to be sure, the model is not worth to fix on a deeper scale imo so good to go
|
run-slow: recurrent_gemma |
CI (Nvidia + AMD)Nvidia · ❌ 2 tests with changed errors 😭 · ⚙️ Workflow · 📊 Dashboard Failures (4 pre-existing)Pre-existing failures (still failing) — 4 total: Error changed (2): - recurrent_gemma: Same error (2): - recurrent_gemma: AMD · ❌ 2 tests with changed errors 😭 · ⚙️ Workflow · 📊 Dashboard Failures (4 pre-existing)Pre-existing failures (still failing) — 4 total: Error changed (2): - recurrent_gemma: Same error (2): - recurrent_gemma: |
Values from the PR's CI run (Nvidia `captured_info.txt`, AMD MI300 failure log). They match the outputs of the daily CI before #48961. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[For maintainers] Suggested jobs to run (before merge) run-slow: recurrent_gemma |
Captured on an A10G with this PR; fp32 and fp16 give the same output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The 2 remaining failing tests ( |
Alternative to #50554, following @vasqu's review: fix the layer types instead of patching the call sites.
Problem
RecurrentGemmaConfighas nolayer_types, so every layer is inferred assliding_attention(sliding_windowis set). The cache then gives the recurrent blocks KV slots too, and the mask /get_seq_length()read layer 0's slot, which belongs to a recurrent block and is always empty. With padding, the decode mask gets the wrong size: on GPU, CUDA SDPA raises(*bias): last dimension must be contiguous. On CPU, generation runs but the outputs are silently wrong. Regression from #48961.Fix
layers_block_typenow returns standard layer types (recurrent →linear_attention, attention →sliding_attention) and is aliased aslayer_typesviaattribute_map. Nothing changes inconfig.json, and the attention computation is unchanged (it readsconfig.sliding_windowdirectly). Only the cache slots of the recurrent blocks change type, and those slots are never used: the recurrent block keeps its state on the module.generatepre-builds oncelayer_typesexists (needed by the static-cache / compile path).test_recurrent_layers_mask_padding_on_continued_forward. It now runs because the config declares a recurrent layer type, but the recurrent block has never supported a cached multi-token continuation (same skip as Mamba / Jamba).Same generated tokens and scores as #50554 (tiny model, padded batch).
Results (A10G, torch 2.15)
maintest_generate_with_and_without_position_idstest_generate_from_random_inputs_embedstests/models/recurrent_gemmaThe slow integration tests' expected values still need updating (same as #50554).
🤖 Generated with Claude Code