vLLM/Recipes
Qwen

Qwen/Qwen3.8-2.4T-A95B

2.4T-parameter hybrid-attention MoE (~95B active) with linear attention on 69 of 92 layers, 512 routed experts, a built-in MTP draft head, 262K native context window and extensible to 1M context

NVFP4 W4A4 on 8 Blackwell GPUs: 304 output tok/s per user with MTP-3, up to 4300 total tok/s per GPU at DEP16

moe2.4T / 95B262,144 ctxvLLM nightly+text
Guide

Overview

Qwen3.8-2.4T-A95B is a 2.4-trillion-parameter Mixture-of-Experts model with roughly 95B parameters active per token — 512 routed experts with 10 active, plus one shared expert, over a 92-layer hybrid-attention backbone.

The layer mix is the interesting part. Only 23 of the 92 layers run full attention (full_attention_interval: 4); the other 69 run linear attention with a constant recurrent state. This is a text-only causal LM: the architecture is Qwen3_5MoeForCausalLM, config.json has no vision_config, and the weight index contains zero vision tensors.

Prerequisites

  • vLLM: nightly. for the best performance
  • transformers >= 5.4.0, per the version recorded in config.json.
uv venv && source .venv/bin/activate
uv pip install -U vllm --extra-index-url https://wheels.vllm.ai/nightly
uv pip install -U "transformers>=5.4.0"

Choosing a variant

Counted in GPUs, not nodes — a "node" is 8 GPUs on B300/MI355X/H200 but only 4 on the GB200/GB300 NVL4 trays, and that difference decides whether a variant fits.

VariantWeightsSizedB300 (268 GB)MI355X (288 GB)H200 (141 GB)GB300 tray (4 GPU)
BF164.45 TiB5871 GB24 GPUs24 GPUs48 GPUs6 trays
FP82.27 TiB2996 GB16 GPUs16 GPUs32 GPUs4 trays (TP16)
MXFP4 (AMD)1.45 TiB1917 GB8 GPUs16 GPUs
NVFP4 W4A4 (NVIDIA)1.32 TiB1737 GB8 GPUs16 GPUs2 trays (TP8)
NVFP4-REAP-25 (RedHatAI)1.04 TiB1373 GB8 GPUs16 GPUs2 trays (TP8)

TP must divide the 64 attention heads, so only 1/2/4/8/16/32 are legal. This rounds some layouts up: FP8 needs 2325 GiB, which is 3 GB300 trays by capacity, but TP12 is invalid — so FP8 is a 16-GPU / 4-tray deployment on GB300, not 12.

Launch commands

Low latency

NVFP4 W4A4, TP8 (one 8xB300 node, or two GB300 trays):

vllm serve Inferact/Qwen3.8-2.4T-A95B-NVFP4 \
  --tensor-parallel-size 8 \
  --max-model-len 262144 \
  --kv-cache-dtype fp8 \
  --reasoning-parser qwen3 \
  --enable-auto-tool-choice --tool-call-parser qwen3_coder

FP8, TP16 across two 8-GPU nodes. Same command on both, changing only --node-rank; rank > 0 additionally takes --headless (it must not start an API server).

vllm serve Qwen/Qwen3.8-2.4T-A95B-FP8 \
  --tensor-parallel-size 16 \
  --nnodes 2 --node-rank 0 --master-addr $HEAD_ADDR \
  --max-model-len 262144 \
  --kv-cache-dtype fp8 \
  --reasoning-parser qwen3

Add --speculative-config '{"method":"mtp","num_speculative_tokens":3}' for the latency numbers below — MTP-3 is worth roughly 2.3x on per-user output rate.

MXFP4 on 8xMI355X — swap the model id and drop --kv-cache-dtype fp8 unless you have confirmed the ROCm path supports it in your build.

High throughput

Expert parallelism, not plain TP, and a one-sided NVLink all2all. The recipe sets --all2all-backend flashinfer_nvlink_one_sided automatically on the EP strategies when the hardware is Blackwell. The MoE kernel differs by precision, so set it explicitly:

  • FP8, TP4·DP4 + EP16 (multi_node_tp_dp, 4 GB300 trays): add --moe-backend flashinfer_trtllm --enable-expert-parallel. See https://github.com/vllm-project/vllm/pull/51924.
  • NVFP4, DEP16 (multi_node_dep, 4 trays): add --moe-backend flashinfer_cutedsl.

These MoE backends are deliberately NOT pinned in the variant args: they are measured for the EP layouts above, and the plain-TP path has not been measured with them. vLLM auto-selects FLASHINFER_TRTLLM for NVFP4 under TP, which is what the TP8 numbers below were taken with.

Pipeline parallelism

multi_node_tp_pp works on this architecture — verified 2026-08-13 on GB300 with TP4 x PP3 across 3 trays on the FP8 checkpoint: it loads, captures full CUDA graphs (51 piecewise + 35 full-decode), and generates correctly. Earlier revisions of this recipe warned that hybrid linear-attention models had historically lagged on PP support; that caveat is resolved. It remains a fallback rather than a default — PP exists for GPU counts that pure TP cannot express (12 GPUs is 3 trays but TP12 is illegal), and TP or EP is faster whenever the GPU count is a power of two.

Performance

Two operating points, because they want different layouts. Latency wants TP and speculative decoding; throughput wants expert parallelism.

Low latency — output tokens/s per user:

VariantLayoutno MTPMTP-3with kernel opts
FP8TP16130307150 / 360 (target)
NVFP4TP8133304

High throughput — total (input+output) tokens/s per GPU:

VariantLayoutBackendstotal TPS/GPU
FP8TP4·DP4 + EP16one-sided all2all + trtllm-gen MoE + activation optup to 3200
NVFP4DEP16one-sided all2all + cute-dsl MoEup to 4300

The pending kernel work behind the "target" column is gdn decode fusion, split-k GEMM, and all-reduce fusion.

Verification run (what earned gb300: verified) — NVFP4 W4A4, TP8 across 2 trays, 8k in / 1k out, vLLM 0.1.dev19754+g3a0914114, no EP, default MoE backend:

ConcurrencyTPS/usertotal TPS/GPUTPOT
11011089.9 ms
3241.9133823.9 ms
6426.9171537.2 ms
12818.3217054.7 ms

That 2170 against 4300 is the cost of plain TP with the default all2all — it is the measurement the EP + one-sided-NVLink defaults above are based on.

Three things that moved the numbers materially:

  • Use MTP depth 3, not 1. MTP-1 was measured at 64.8% acceptance and is not worth it: +3.4% at concurrency 1 and a loss above it (−9% at 128, −23% at 256), because the draft pass displaces real work once the batch is compute-bound. Depth 3 is where speculation pays (~2.3x per-user).
  • --load-format fastsafetensors --safetensors-load-strategy lazy cut weight load from 545 s to 306 s (−44%) on a 1.32 TiB checkpoint over shared storage.
  • Size --max-model-len to the workload. At 262144 the engine reserved KV for 25 concurrent requests; at 9240 (8k in + 1k out) the same 70 GiB of KV serves 506.

Client usage

generation_config.json ships temperature: 1.0, top_p: 0.95, top_k: 20.

from openai import OpenAI

client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1", timeout=3600)

resp = client.chat.completions.create(
    model="Qwen/Qwen3.8-2.4T-A95B",
    messages=[{"role": "user", "content": "Give me three primes above 100."}],
    temperature=1.0, top_p=0.95, max_tokens=2048,
)
print(resp.choices[0].message.content)

Processing Ultra-Long Texts

The model has 262k native context length and it can be extended to 1M with --max-model-len flag. The best value can be picked based on the use case and GPU ram usage tradeoff.

For example, to enable full 1M context length on this model:

VLLM_ALLOW_LONG_MAX_MODEL_LEN=1 vllm serve Qwen/Qwen3.8-2.4T-A95B \
  --max-model-len 1010000 \
  --hf-overrides '{"max_position_embeddings": 1010000}' \
  ...

Troubleshooting

CUDA graph / recurrent-state cache size error. On hybrid models an assert num_cache_lines >= batch failure means the CUDA-graph capture size exceeds the state cache. Reduce --max-cudagraph-capture-size (default 512), or fall back to --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' if full capture misbehaves on this architecture. See https://github.com/vllm-project/vllm/pull/34571.

Engine start times out. Raise VLLM_ENGINE_READY_TIMEOUT_S (set to 3600 by this recipe) and prefer a real /v1/chat/completions probe over /health for readiness — loading terabytes plus kernel JIT takes minutes.

The linear backend feature "--linear-backend flashinfer_cutedsl" is Docker only until FlashInfer version upgrade in the vllm build. The NVFP4 variant sets that flag, so on a pip install either use the Docker image pinned above or drop the flag.

References