Fix `StaticCache` for Mllama and enable `torch.compile` (#48141)
* Fix StaticCache allocation for Mllama cross-attention layers
`generate(cache_implementation="static")` crashed on Mllama, both eagerly
(`index_copy_(): index out of bounds`) and under `torch.compile`
(`expand: attempting to expand a dimension of length 6404 -> 47!`).
`_prepare_static_cache` sizes every layer of the `StaticCache` from
`max_cache_len`, which counts text tokens. Mllama interleaves cross-attention
layers into the same layer list, and those cache the vision tower states
instead, whose length is `num_images * max_num_tiles * num_patches` (6404 for
the 11B checkpoint, against a `max_cache_len` of 47 in the repro).
Re-allocate the cross-attention layers with that length, mirroring the
`is_encoder_decoder` branch of `_prepare_static_cache`, which likewise sizes
the cross-attention cache from the encoder output rather than from
`max_cache_len`. Mllama cannot reuse `EncoderDecoderCache` directly because it
has a single interleaved stack instead of two separate ones.
The resulting cache is fully static, so `Cache.is_compileable` is `True` and
`generate` would auto-compile. Mllama appends a row to `cross_attention_mask`
at every decoding step, so `forward` sees a new input shape each step and
dynamo recompiles the whole graph, measured ~17x slower than eager on the 11B
checkpoint. `_can_compile_fullgraph = False` was meant to prevent exactly this
but is never read by `generate`, so honour it in `_valid_auto_compile_criteria`.
Benchmarked on Llama-3.2-11B-Vision-Instruct (A100, bf16, sdpa, 64 tokens,
median of 5): dynamic 2.014s/31.78 tok/s (unchanged from main, identical
tokens), static 2.491s/25.69 tok/s (crashed on main). Static and dynamic differ
slightly in output, as expected from the different KV layout.
* Scope the auto-compile override to `MllamaForConditionalGeneration`
`MllamaPreTrainedModel` is reused by `blt` through modular, so an override
placed there leaks into `BltPreTrainedModel` and breaks
`utils/check_modular_conversion.py`. Only `MllamaForConditionalGeneration`
builds the cross-attention static cache, so put the override there.
Also derive the cross-attention cache length from `self.model.vision_model`
(`max_num_tiles`, `num_patches`) instead of recomputing it from the config, so
it stays in sync with the length the vision tower actually produces.
* Enable torch.compile for Mllama with a static cache
Now that the cross-attention layers are sized correctly, the static cache is
compileable. Three things kept the traced graph from being static:
- `MllamaTextCrossAttention.forward` and `MllamaTextModel.forward` branched on
`past_key_values.get_seq_length(...)`, which is a 0-dim tensor for compileable
layers. Both only need to know whether the cross-attention layer is populated,
so use `layer.is_initialized` (a python bool) through a small helper.
- `cross_attention_mask` grows by one row per decoded token and was passed to
`forward` in full, so dynamo recompiled at every step. Slice it in
`prepare_inputs_for_generation`, like the generic implementation does for
`attention_mask`, including the `clone` that keeps the stride consistent.
- Drop the now-incorrect `_can_compile_fullgraph = False` from
`MllamaPreTrainedModel` and set it on `MllamaForConditionalGeneration`.
Llama-3.2-11B-Vision-Instruct, bf16, sdpa, 64 new tokens: 42.70 tok/s compiled
against 32.13 tok/s with the dynamic cache, with one graph, no graph breaks and
no recompiles.
Also update `_check_attentions_for_generate` to account for static caches, and
add a test asserting the compiled output matches eager with a single graph.
* Shorten comments
* Shorten comment
* Drop PR reference from comment
* Don't touch blt, and skip the compile test without an accelerator
`MllamaForCausalLM` already sets `_can_compile_fullgraph = True` explicitly, so
the `False` on `MllamaPreTrainedModel` was only ever read by
`MllamaForConditionalGeneration`. Overriding it there instead of removing it
leaves `blt`, which inherits from `MllamaPreTrainedModel` through modular,
completely untouched.
`generate` only auto-compiles on an accelerator, so `test_generate_compile_matches_eager`
counted 0 graphs on the CPU runners.
* Move the redundant `_can_compile_fullgraph` down to blt
`MllamaForCausalLM` and `MllamaForConditionalGeneration` both declare the flag
themselves, so the `False` on `MllamaPreTrainedModel` was unreachable, and its
comment ("static cache cannot have different shapes for each layer") is exactly
what this PR disproves.
`blt` inherits that base class through modular, so the line moves into
`modular_blt.py` verbatim: every `Blt*` class still resolves to `False`
(`BltForCausalLM` sets it explicitly, the rest now inherit the same default from
`PreTrainedModel`), and `tests/models/blt/` gives an identical result before and
after.
* Revert "Move the redundant `_can_compile_fullgraph` down to blt"
This reverts commit 3b6fa6fd45012718fe390969f95368afc9501dd9.
* Reapply "Move the redundant `_can_compile_fullgraph` down to blt"
This reverts commit 386a4dd54c269b3981e13ec4364ce8e7ecd7e016.
* Shorten the `_can_compile_fullgraph` comment
* Address review: reuse next_sequence_length, handle early-initialized caches, drop dead cross-attention branch
* Read the cross-attention cache length from the config instead of the vision module