Add Qwen3.5 hybrid linear-attention MoE support (122B-A10B-FP8, 397B-A17B-FP8) - #1
Add Qwen3.5 hybrid linear-attention MoE support (122B-A10B-FP8, 397B-A17B-FP8)#1yuxuandexter wants to merge 6 commits into
Conversation
Qwen3.5 interleaves gated DeltaNet with full attention at 3:1 — 122B-A10B is 48 layers (36 GDN, 12 full) and 397B-A17B is 60 (45, 15) — so it needs a KV pool that holds recurrent state and paged KV side by side, which `kvcache/hybrid_pool.py` provides. The GDN decode path lands in `kernel/qwen35_gdn.py`: a causal conv1d that holds its taps in scalar registers through a KERNEL_WIDTH cascade, and a recurrent gated-delta kernel whose grid puts the value block on the fastest- varying axis so consecutive CTAs walk one request's state contiguously. The gated RMSNorm is compiled rather than eager, which is how vLLM gets the same arithmetic as a single inductor kernel. 397B routes 512 experts at top-k 10 against 122B's 256 at 8, so MegaMoE routing, weight loading and the AFD expert plans all had to stop assuming the smaller shape. `models/weight.py` and `models/config.py` carry most of that, including ModelOpt NVFP4 checkpoints, which route to the FlashInfer NVFP4 runner added under `layers/moe/moe_runner/`. The AFD workers gain a configurable step-ahead window. Both loops previously retired the oldest handle once `len(pending) > 2`; that constant decides how far cudaGraphLaunch runs ahead of the CPU wait on the previous step's D2H copy, and the right depth differs by model and topology, so it is now `--afd-async-pending-steps` threaded from ServerArgs through the coordinator. `--afd-force-multi-mb-graph-overlap` is the explicit opt-in for shapes where multi-microbatch graph capture would otherwise auto-serialise. Verified on two GB200 nodes, attention DP4 / TP1 and MLP EP4 / TP1, against a vLLM reference on the same checkpoints.
`init_capture_graph` sized the captured page table at `max_seq_len // page_size`
while `_get_page_offsets` built replay tables with a ceiling division. Whenever
`max_seq_len` is not a multiple of `page_size` the two disagree by one column, and
the replay copy fails on a sequence long enough to need that last page:
RuntimeError: The expanded size of the tensor (132) must match the existing
size (133) at non-singleton dimension 1
The floor is simply wrong, not merely inconsistent: a 8505-token sequence occupies
133 pages of 64, and the engine will serve sequences up to `max_seq_len`. Route
both sites through one `_num_pages` helper so a future change cannot reintroduce
the skew, and reject an over-wide replay table explicitly — slicing it to fit
would silently narrow the destination and drop the pages that overflow.
Reachable on any model whose maximum sequence length is not page-aligned, which
for Qwen3.5 is the ordinary case: 8192 input plus a 313-token allowance.
Three gaps kept the shell entrypoints from expressing what a Qwen3.5 alignment run needs. `fastafd_server.sh` had no way to reach the AFD overlap controls, so `--afd-async-pending-steps`, `--afd-force-multi-mb-graph-overlap` and `--afd-disable-overlap` were unreachable from a script even though the server accepts them. The first is the one that matters in practice: it sets how far graph launches may run ahead of the CPU wait on the previous step's device-to-host copy, and the useful depth differs by model and topology. `fastafd_vllm_alignment.sh` assumed one conda environment and one model path for both engines. Qwen3.5 needs neither: FastAFD and vLLM live in separate environments here, and a reference may need its own view of a checkpoint. It also hardcoded 600 s readiness and scoring timeouts, which a 397B cold start on two nodes does not fit — those now read `AFD_READY_TIMEOUT`, `AFD_SAMPLE_TIMEOUT`, `VLLM_READY_TIMEOUT` and `VLLM_SCORE_TIMEOUT`. `--afd-num-mb` is threaded through so one preset can run both the mb1 baseline and the mb2 overlap case. All three serve scripts resolve CUDA the same way when the toolkit comes from a CUDA 13 conda package: headers and binaries sit at the prefix while target libraries are exposed through `$CONDA_PREFIX/lib64`, so pointing `CUDA_HOME` at `targets/sbsa-linux` — which the previous fallback effectively did — makes a cold FlashInfer JIT link against a lib64 that does not exist. The new branch is guarded on that layout actually being present, so environments without it are unaffected.
Two presets pin a checkpoint revision and hand everything else to a shared driver, matching how the Qwen3-30B presets are laid out. Both compare FastAFD against a vLLM reference on the same weights: FastAFD on AG-TP4 plus EG-TP/EP4 across two nodes, vLLM at TP4 in its own conda environment, prompt logprobs at top-10 over the pinned prompt set. The reference deliberately runs with `--enforce-eager`, `--language-model-only` and allreduce-RMS fusion off. A fused or graph-captured reference turns any mismatch into a question about which pass changed the arithmetic; an eager one points at the kernel under test. The driver refuses to run without MODEL_REPO, MODEL_REVISION and MODEL_DISPLAY_NAME rather than defaulting to a checkpoint. A default there would quietly download and align a model the caller did not ask for, and the pinned revision is the part that makes a result reproducible. 397B differs from 122B only in identity and timeouts: 379 GB of weights loaded on both engines does not fit the 1800 s readiness window that suits 122B.
One preset per model over a shared no-TP driver: attention DP4/TP1 and expert DP4/TP1/EP4 across two nodes, MegaMoE M:N transport, 8k prompts replayed from the pinned pool with Nsight capturing a decode window. The driver refuses three things rather than guessing them. Tensor parallelism is rejected on both roles, so a retained decode window contains no TP all-reduce kernels and a transport measurement is not contaminated by one. `AFD_NUM_MB` has no default, because 1 is the serialized baseline and 2 is the overlap case and picking either silently turns a throughput comparison into a coin toss. And the Nsight stop step is checked against the minimum reachable total step: overshoot it and the worker sessions never finalize, leaving only coordinator traces — which looks like a profiling bug rather than a window that was too wide. Batch is named per attention GPU, as the 235B presets do: 256 for 122B and 320 for 397B. 397B is 60 layers to 122B's 48 and routes 512 experts at top-k 10, so the same eight GPUs hold fewer requests. Prefill duration is bracketed rather than point-estimated. Dividing prompt tokens by the token cap assumes no decode contention and is a lower bound; requests that finish prefill early consume a decode slot on every later iteration, shrinking the per-step prefill budget toward (cap - active), which gives the upper bound. The capture window spans the bracket and the analyser isolates the pure-decode tail by kernel timestamp. Verified by comparing each preset's derived configuration against the driver the measurements were taken with: all 22 echoed values match at both batch sizes.
Adds Qwen3.5 to Supported Models, a correctness-alignment block naming both presets, and a row per model in the large-scale experiment table. The topology note is the part a reader needs before running anything. Every other preset in that table sizes itself from the cluster through `AFD_TOTAL_NODES`; the Qwen3.5 ones do not, and refuse to start on anything but four attention and four expert workers. Without saying so, the obvious move — copying the 235B command line and its `AFD_TOTAL_NODES=4` — fails with a worker-count error that reads like a cluster problem. Batch is stated per attention GPU, matching the existing rows: 256 for 122B and 320 for 397B. No throughput figures, since none have been published.
|
Thanks for the work. I have a question about pipeline balance for hybrid-attention models such as Qwen3.5. Linear-attention and full-attention layers can have quite different compute times on the attention group (AG), so the balance between AG work and expert-group (EG) MoE work is inherently layer-dependent. For example, suppose the microbatch size and topology make the AG computation of a linear-attention layer roughly balanced with its corresponding MoE computation on the EG. At a full-attention layer, the attention-side time and the FFN/expert-side time may differ substantially, leaving one side idle and introducing pipeline bubbles. This could waste resources and reduce the throughput benefit of AFD. How do you view or address this layer-wise imbalance? Does the current implementation provide any mitigation, such as per-layer scheduling or microbatching, different overlap depths, or workload-aware partitioning? If not, have you measured how much the full-attention layers contribute to pipeline bubbles in the 122B or 397B runs, and is this something you plan to address in future work? |
Adds support for the Qwen3.5 hybrid linear-attention MoE models — 122B-A10B-FP8 and
397B-A17B-FP8 — together with the presets needed to reproduce a correctness alignment
and a two-node AFD throughput run. FP8 only; the NVFP4 checkpoints are still being
worked on and are deliberately left out.
Six commits, ordered so each stands on its own:
Add Qwen3.5 hybrid linear-attention MoE supportSize the TRT-LLM capture page table with a ceiling divisionLet the launcher scripts drive Qwen3.5 alignment runsAdd Qwen3.5 FP8 correctness alignment presetsAdd Qwen3.5 two-node AFD throughput presetsDocument running Qwen3.5 122B and 397BWhat the model needed
Qwen3.5 interleaves gated DeltaNet with full attention at 3:1, so it needs a KV pool
holding recurrent state and paged KV side by side. The GDN decode path is a causal
conv1d that keeps its taps in scalar registers through a
KERNEL_WIDTHcascade, plus arecurrent gated-delta kernel whose grid puts the value block on the fastest-varying axis
so consecutive CTAs walk one request's state contiguously. 397B routes 512 experts at
top-k 10 against 122B's 256 at 8, which is what forced MegaMoE routing, weight loading
and the AFD expert plans to stop assuming the smaller shape.
The AFD workers gain
--afd-async-pending-steps. Both loops previously retired theoldest handle at a hardcoded
len(pending) > 2; that constant decides how farcudaGraphLaunchruns ahead of the CPU wait on the previous step's D2H copy, and theright depth differs by model and topology.
The page-table fix (commit 2)
init_capture_graphsized the captured page table atmax_seq_len // page_sizewhilereplay built its table with a ceiling division. Whenever
max_seq_lenis not a multipleof
page_sizethe two disagree by one column, and replay fails on a sequence longenough to need that last page:
The floor is wrong rather than merely inconsistent — an 8505-token sequence occupies 133
pages of 64. Both sites now route through one
_num_pageshelper, and an over-widereplay table is rejected explicitly instead of being silently narrowed. This is reachable
on any model whose maximum sequence length is not page-aligned, Qwen3.5 or not.
Reproducing
Correctness, against a vLLM reference on the same weights:
Throughput, two nodes:
Unlike the 235B and MiniMax presets, the Qwen3.5 ones fix their topology instead of
sizing it from the cluster: four attention workers (DP 4, no tensor parallelism) and four
expert workers (DP 4, EP 4). They ignore
AFD_TOTAL_NODESand refuse to start on anyother worker count, so that a retained decode window contains no tensor-parallel
all-reduce traffic.
AFD_NUM_MBhas no default in the driver — 1 is the serializedbaseline and 2 is the ping-pong overlap, and silently picking either makes a throughput
comparison meaningless.
Verification
python -m compileall python/minisglpasses.one, into a driver named for an NVFP4 checkpoint). Proven behaviour-identical by
stubbing the validator and diffing the final argv and environment of the old and new
chains: 79 lines match exactly, per model.
with, via its
QWEN35_DRY_RUN=1path: all 22 derived configuration values match atboth batch sizes, including the prompt-pool SHA-256, the prefill-step bracket, the
auto-raised token count and the Nsight window.
README.mdandscripts/README.mdexists on the branch.Measured on two GB200 nodes, attention DP4/TP1 and MLP EP4/TP1, against a vLLM reference
on the same checkpoints. No throughput figures are stated in the README, since none have
been published yet.
Not included
Tests, the
baseline/study harness, NVFP4 presets, and development-only validators —they depend on paths and checkpoints outside this repository.