Repository navigation
Continuous batching for linear attention layers - #50350
qgallouedec wants to merge 10 commits into
Conversation
|
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. Pages changed in this PR: |
|
Found a bug in this PR, fixed in 2d67d9b: under sdpa, the attention layers of Qwen3-Next and Qwen3.5 got no mask at all. The engine collapsed its mask dict to its last entry, which is the linear layers' |
A linear attention layer keeps a state of fixed size per request (the last inputs of its causal convolution and its recurrent state) instead of a cache that grows with the tokens. A new LinearAttentionCacheAllocator stores that state in one page per layer: a request owns one block for its whole life, allocated with its first tokens and overwritten in place by every forward pass, read from the zeroed trash block while it has no past, copied on fork and never shared. Its indices hold one entry per sequence rather than per token, and its block is sized to a multiple of the other allocators' blocks so the sectors stay small. The gated delta net layers of Qwen3-Next and Qwen3.5 take the engine's cache from the kwargs, like the attention layers do, and run the packed batch as one row: a varlen causal convolution continues every sequence from its own conv state, and the fla kernels take the per-sequence recurrent states through cu_seqlens. The chunked kernel syncs with the host, so accelerator graphs stay off on the varlen path for these models; the decode path keeps them. Fixes #48305
…inputs run_one_warmup prepared the next warmup size while the previous forward could still be running on the compute stream, so its kernels read inputs being rewritten. The KV paths were short enough to never show it; the linear attention layers read cu_seq_lens in the first layer and a real model's forward is long enough to lose the race (Qwen3.5-0.8B failed every warmup with an illegal address, the tiny test model never did). Also exclude the linear attention allocator from the decode fast path length: it holds one block per request whatever its length, so it must not cap the eligible request length at max_blocks_per_request tokens.
…eps for them The allocator read the shapes of the conv and recurrent states from the Qwen3-Next config fields, which tied the engine to one family and made every other linear attention model fail at cache init with an AttributeError on those fields. The gated delta net now declares its per-request states (shape and dtype) through linear_attention_states, the engine collects them from the model's modules when it builds the cache, and the allocator holds any number of declared states per page, each on a 128-byte boundary. A linear attention layer that declares no states, or layers that declare different ones, are refused with a readable error.
The sync after each warmup forward fixes a race that any model can hit, so it goes to main on its own; this branch picks it up when it rebases.
…views ty refused state_views on a CacheAllocator; layer_to_allocator maps the linear layers to the linear allocator, which is the one that has it.
…to keep per request Mamba layers will declare theirs the same way, and the per-token rows get a sibling declaration next, so the name says what the method returns rather than which layer type returns it.
A model with one attention type besides its linear attention layers has a single entry in cumulative_seqlens_k, so the masks were collapsed to the last entry of the dict, which is the linear layers' None. Under sdpa, the attention layers of Qwen3-Next and Qwen3.5 then built their own mask from the new tokens alone: none at all on a packed batch, so they attended across sequences and to later tokens. The tiny model's greedy parity did not see it. The masks are now collapsed only when the cache has a single allocator, and a test checks the attention layers get the engine's mask over all the keys they read; it fails without this change.
cfc6210 to
f66c64b
Compare
# Conflicts: # src/transformers/models/qwen3_5/modeling_qwen3_5.py # src/transformers/models/qwen3_5/modular_qwen3_5.py # src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py # src/transformers/models/qwen3_next/modeling_qwen3_next.py # src/transformers/models/qwen3_next/modular_qwen3_next.py # src/transformers/models/qwen4_exp/modeling_qwen4_exp.py # tests/generation/test_continuous_batching.py
…ich already wrote the new states
|
[For maintainers] Suggested jobs to run (before merge) run-slow: qwen3_5, qwen3_5_moe, qwen3_next, qwen4_exp |
What does this PR do?
Continuous batching for the linear attention layers of hybrid models (Qwen3-Next, Qwen3.5, Qwen3.5-MoE). The layers declare their per-request states (
linear_attention_states: conv and recurrent), and aLinearAttentionCacheAllocatorkeeps them in one block of the paged cache per request, allocated with its first tokens and overwritten in place. The gated delta net runs the packed batch as one row from those states, through a varlen convolution and the fla kernels'cu_seqlens. Greedygenerate_batchmatchesgenerateon Qwen3.5-0.8B under flash, the decode fast path, graphs and compile.Fixes #48305