Skip to content

Continuous batching for linear attention layers - #50350

Open
qgallouedec wants to merge 10 commits into
mainfrom
cb-linear-attention
Open

qgallouedec wants to merge 10 commits into
mainfrom
cb-linear-attention

Conversation

@qgallouedec

@qgallouedec qgallouedec commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

🤗 Transformers CI

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 a LinearAttentionCacheAllocator keeps 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. Greedy generate_batch matches generate on Qwen3.5-0.8B under flash, the decode fast path, graphs and compile.

Fixes #48305

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

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:

@qgallouedec

Copy link
Copy Markdown
Member Author

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' None, so on a packed batch they attended across sequences and to later tokens. The tiny model's greedy parity did not notice. A new test checks the attention layers get the engine's mask over all the keys they read, and it fails without the fix. Flash was not affected.

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.
# 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
@github-actions

Copy link
Copy Markdown
Contributor

[For maintainers] Suggested jobs to run (before merge)

run-slow: qwen3_5, qwen3_5_moe, qwen3_next, qwen4_exp

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Continuous batching does not support hybrid linear-attention models (all Qwen3.5/3.6/3.8)

2 participants