Files
vLLM/recipes/models/deepseek-ai/DeepSeek-V4.1-Flash.yaml
T
2026-10-17 09:14:36 +00:00

847 lines
42 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DeepSeek-V4.1-Flash — serves /deepseek-ai/DeepSeek-V4.1-Flash. Validate: node scripts/build-recipes-api.mjs
meta:
title: "DeepSeek-V4.1-Flash"
slug: "deepseek-v4.1-flash"
provider: "DeepSeek"
description: "DeepSeek V4.1 Flash vision-language MoE (552B backbone; 8B active per prompt token, 16B per output token) combining sliding-window plus compressed sparse attention with a two-level indexer, engram n-gram memory, hyper-connections, and a DSpark multi-token draft head."
date_added: 2026-09-09
date_updated: 2026-10-13
tasks:
- text
- multimodal
performance_headline: "1M context at 8-16B active parameters, with engram n-gram memory"
related_recipes:
- "deepseek-ai/DeepSeek-V4-Flash"
- "deepseek-ai/DeepSeek-V4-Pro"
default_hardware: gb200
hardware:
h100: verified
h200: verified
b200: verified
gb200: verified
gb300: verified
b300: verified
mi300x: verified
mi325x: verified
mi350x: verified
mi355x: verified
dgx_station_gb300: verified
model:
model_id: "deepseek-ai/DeepSeek-V4.1-Flash"
# Opt in to SMG gRPC PD; parsing runs on the router, not vLLM.
smg_grpc:
reasoning_parser: deepseek_v41
tool_call_parser: deepseek_v41
min_vllm_version: "0.30.0"
# 0.30.0 has not shipped; the architecture lives on main (vllm-project/vllm#56228),
# so NVIDIA renders `vllm/vllm-openai:nightly` instead of the launch-day
# `deepseekv41-flash-0909` tag, which is now behind main.
nightly_required: true
default_frontend: rust
docker_image:
# ROCm 10.0 nightly channel; includes the AITER mHC delayed-pre path from
# vllm-project/vllm#56503.
amd: "vllm/vllm-openai-rocm:nightly-rocm100"
architecture: moe
parameter_count: "552B"
active_parameters: "8-16B"
context_length: 1048576
base_args:
- "--tokenizer-mode"
- "deepseek_v41"
base_env:
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
install:
docker:
note: "Serve from the image for your GPU vendor — no pip wheel carries the DeepSeek-V4.1 architecture. Both vendors track the nightly image; the launch-day 0909 tag is superseded (see Prerequisites)."
pip: false
dependencies: []
features:
tool_calling:
description: "Enable tool calling. V4.1 emits DSML tag blocks rather than JSON fences."
args:
- "--tool-call-parser"
- "deepseek_v41"
- "--enable-auto-tool-choice"
reasoning:
description: "Enable thinking mode with the DeepSeek V4.1 reasoning parser."
args:
- "--reasoning-parser"
- "deepseek_v41"
spec_decoding:
description: "DSpark multi-token drafting, 5 tokens per round (the trained block size). On NVIDIA, adaptive verification scores each (request, position) slot and only high-scoring slots are verified, up to a startup-profiled compute budget. On AMD, vLLM currently refuses that flag, so the generated command sets enable_adaptive_verification:false and verifies the full 5-token block. Remove the AMD override only after a serve with the true flag actually starts on ROCm."
args:
- "--speculative-config"
- '{"method":"dspark","num_speculative_tokens":5,"draft_sample_method":"probabilistic","rejection_sample_method":"block","enable_adaptive_verification":true}'
hardware_overrides:
# Adaptive verification writes query_start_loc on device and captures
# varlen decode CUDA graphs. maybe_create_adaptive_verification_manager
# therefore requires both supports_device_cpu_query_lens_mismatch()
# and AttentionCGSupport.ALWAYS on every target attention builder.
# On ROCm, DeepseekV41IndexerBackend fails the first check because
# the helpers it inherits are CUDA plus DeepGEMM only. Forcing that
# helper True still dies: DeepseekV41ROCMAiterSparseSWABackend reports
# UNIFORM_BATCH. Features emit last in resolveCommand, so this
# replacement must live here rather than in
# hardware_overrides.amd.extra_args. Remove this override only when
# a vLLM serve with enable_adaptive_verification:true starts on AMD.
amd:
args:
- "--speculative-config"
- '{"method":"dspark","num_speculative_tokens":5,"draft_sample_method":"probabilistic","rejection_sample_method":"block","enable_adaptive_verification":false}'
# Offloaded experts make deep drafts expensive at high batch: k=5 to 4 seqs, k=1 above.
dgx_station_gb300:
args:
- "--speculative-config"
- '{"method":"dspark","num_speculative_tokens":5,"num_speculative_tokens_per_batch_size":[[1,4,5],[5,24,1]]}'
text_only:
description: "Skip loading the vision encoder for text-only workloads — frees VRAM for KV cache. Mutually exclusive with encoder_parallel."
args:
- "--language-model-only"
encoder_parallel:
description: "Run the vision encoder in data-parallel mode — avoids TP comm overhead on the small encoder. Mutually exclusive with text_only."
args:
- "--mm-encoder-tp-mode"
- "data"
opt_in_features:
- spec_decoding
- text_only
variants:
default:
precision: fp8
vram_minimum_gb: 614
description: "Native mixed checkpoint: MXFP4 MoE experts, MXFP8 block-quantized dense weights, UE8M0 scales throughout"
hardware_overrides:
# 8x80 GB is the smallest NVIDIA node that serves this checkpoint, and
# only with the memory levers the InferenceX H100 arm settled on
# (SemiAnalysisAI/InferenceX#2974, validated on the 2026-09-15 nightly in
# SemiAnalysisAI/InferenceX#3135). Engram tables (183 GiB FP8) move to
# pinned host DRAM behind UVA, leaving ~36 GiB/GPU of resident weights at
# TP8. The sparse-attention indexer then allocates a
# [max-num-batched-tokens, max-model-len] logits buffer at startup:
# 8192 x 1M x 2 B is exactly 16 GiB and does not fit next to the weights,
# so batched tokens are capped at 4096 (8 GiB) rather than the context
# length, which would push the 256k-plus agentic traces off the model.
# The V2 model runner and expandable segments keep the KV pool from
# fragmenting around that buffer.
h100:
extra_args:
- "--engram-config"
- '{"cpu_offload":true}'
- "--max-num-batched-tokens"
- "4096"
- "--gpu-memory-utilization"
- "0.92"
extra_env:
VLLM_USE_V2_MODEL_RUNNER: "1"
PYTORCH_CUDA_ALLOC_CONF: "expandable_segments:True"
# Single GB300 + Grace: Engram tables and 60 GiB of experts in host memory via UVA.
# vram_minimum_gb is the GPU-resident fit budget (0.97 x 252); taxonomy vram_gb stays physical HBM.
# 60 GiB is the lowest verified offload at 1M context with DSpark on; 55 fails the minimum-KV check.
dgx_station_gb300:
vram_minimum_gb: 244
extra_args:
- "--offload-backend"
- "uva"
- "--cpu-offload-gb"
- "60"
- "--cpu-offload-params"
- "routed_experts.w13_weight"
- "routed_experts.w2_weight"
- "--engram-config"
- '{"cpu_offload":true}'
- "--gpu-memory-utilization"
- "0.97"
- "--max-model-len"
- "1048576"
- "--max-num-batched-tokens"
- "8192"
- "--long-prefill-token-threshold"
- "6144"
- "--compilation-config"
- '{"cudagraph_capture_sizes":[1,2,4,6,8,12,16,18,24,32,40,48,64,96,128]}'
compatible_strategies:
- single_node_tp
- single_node_tep
- single_node_dep
- multi_node_tp
- multi_node_tep
- multi_node_dep
- pd_cluster
kv_offload_support:
offloading_cpu: verified
offloading_fs: verified
strategy_hardware:
# Single GPU; only TP1 is measured.
single_node_tep:
dgx_station_gb300: unsupported
single_node_dep:
dgx_station_gb300: unsupported
hardware_overrides:
amd:
extra_args:
- "--gpu-memory-utilization"
- "0.9"
# Allow CK a8w4 expert selection instead of pinning Triton W4A16.
- "--moe-backend"
- "aiter"
extra_env:
VLLM_ROCM_USE_AITER: "1"
VLLM_ROCM_USE_AITER_MOE: "1"
# Suppress repeated Gluon-unavailable warnings on gfx950.
AITER_TRITON_LOG_LEVEL: "ERROR"
# DeepseekV41ForCausalLM does not support torch.compile. The ROCm sparse
# SWA backend only reports UNIFORM_BATCH, so default FULL_AND_PIECEWISE
# cannot start unless breakable CUDA graphs are on.
VLLM_USE_BREAKABLE_CUDAGRAPH: "1"
strategy_overrides:
single_node_tp:
# TP4 baseline (one GB200 NVL4 tray; 4 GPUs on MI350X or MI355X). H100
# spans the whole 8x80 GB node: 4 cards hold 320 GB against a 614 GB
# minimum, and even TP8 needs the H100 override above.
tp:
default: 4
h100: 8
b200: 2
b300: 2
gb200: 2
gb300: 2
mi355x: 2
dgx_station_gb300: 1
hardware_overrides:
# 288 GiB per card. The Engram tables cost 47.2 GiB of device memory per
# rank at TP4 and 94.4 GiB at TP2, which does not fit beside half the
# checkpoint, so TP2 offloads them to pinned host memory. TP4 leaves them
# resident. vllm-project/vllm#57491 widened two is_cuda() gates to
# is_cuda_alike(), which is what makes the offload resolve on gfx950; on
# builds carrying it cpu_offload also defaults on via VLLM_PLE_CPU_OFFLOAD,
# so set it explicitly rather than inheriting the default.
# 256 GB per card (gfx942). MI325X takes the TP4 default rather than the
# MI355X TP2: at TP2 the resident weights are ~145 GiB per rank, which
# leaves the smallest KV pool of any arm here, while TP4 lands at ~81 GiB
# per rank with room to spare. The Engram tables are offloaded to pinned
# host memory at TP4 as well -- gfx942 has no FP4 MFMA, so the MXFP4
# experts run through a Triton kernel and the device memory is better
# spent on KV. Same vllm-project/vllm#57491 gate as MI355X applies.
#
# gfx942 also differs from gfx950 on graph capture: every worker
# segfaults during piecewise capture, so breakable CUDA graphs are off
# and only full decode graphs are captured. Prefill runs eagerly.
# 192 GB per card, the smallest gfx942 part here. Same TP4 default and the
# same two gfx942 workarounds as MI325X below. TP2 is NOT offered on this
# SKU: at TP2 the offloaded weights are ~145 GiB per rank, and a measured
# run reported "Available KV cache memory: -13.51 GiB" -- the engine could
# not start even with the sparse-attention indexer buffer halved. TP4
# lands at ~81 GiB per rank and leaves a healthy KV pool.
mi300x:
extra_args:
- "--engram-config"
- '{"cpu_offload":true}'
- "--compilation-config"
- '{"cudagraph_mode":"FULL_DECODE_ONLY"}'
extra_env:
VLLM_USE_BREAKABLE_CUDAGRAPH: "0"
mi325x:
extra_args:
- "--engram-config"
- '{"cpu_offload":true}'
- "--compilation-config"
- '{"cudagraph_mode":"FULL_DECODE_ONLY"}'
extra_env:
VLLM_USE_BREAKABLE_CUDAGRAPH: "0"
mi355x:
extra_args:
- "--engram-config"
- '{"cpu_offload":true}'
# ROCm paged MXFP4 sparse indexer (vllm-project/vllm#58671).
- "--attention-config"
- '{"indexer_kv_dtype":"mxfp4","indexer_sparse_logits":true}'
# The fp4 indexer prefers block-size 128; auto-resolution
# otherwise falls back to 64.
- "--block-size"
- "128"
# Mono decode kernels (vllm-project/vllm#60397, #61082): two persistent
# launches a layer on all 40 backbone layers for decode steps of up to
# 48 rows, i.e. up to concurrency 8 with five DSpark drafts. Larger
# steps take the regular path, so the gain fades as concurrency rises.
# Replaces the VLLM_ROCM_MONO_DECODE environment variable.
- "--monokernel"
extra_env:
# Aiter's Gluon sparse-MLA kernel, gfx950 only
# (vllm-project/vllm#53492).
VLLM_ROCM_USE_AITER_TRITON_SPARSE_MLA: "1"
# INT4 quantization for faster all-reduce
VLLM_ROCM_QUICK_REDUCE_QUANTIZATION: "INT4"
blackwell:
extra_args:
- "--attention-config"
- '{"backend":"FLASHINFER_MLA_SPARSE_DSV41","indexer_kv_dtype":"mxfp4","indexer_sparse_logits":true}'
- "--kv-cache-dtype"
- "fp8"
- "--max-num-seqs"
- "128"
# TP2 halves the replica GPU count, so each rank holds the full
# non-expert weights plus half the experts. Moving the Engram tables to
# pinned host DRAM keeps that resident footprint inside HBM on these
# SKUs; the lookups read the tables through UVA and the output is
# unchanged.
# B200 TP2 serves the low-concurrency (c1) agentic setting: THP-backed
# Engram tables, 4096 batched tokens, and graphs captured up to 4092 tokens.
b200:
extra_args:
- "--engram-config"
- '{"cpu_offload":true,"use_thp":true}'
- "--max-num-seqs"
- "8"
- "--max-num-batched-tokens"
- "4096"
- "--compilation-config"
- '{"mode":"VLLM_COMPILE","cudagraph_mode":"FULL_AND_PIECEWISE","cudagraph_capture_sizes":[6,12,18,24,30,36,48,60,72,96,120,144,192,240,288,384,480,576,768,1020,1536,2046,3072,4092]}'
- "--max-cudagraph-capture-size"
- "4092"
gb200:
extra_args:
- "--engram-config"
- '{"cpu_offload":true}'
gb300:
extra_args:
- "--engram-config"
- '{"cpu_offload":true}'
# Exact-GPU override, after the shared Blackwell block so it wins last-wins dedupe.
dgx_station_gb300:
extra_args:
- "--attention-config"
- '{"backend":"FLASHMLA_MEGA_ATTN_DSV41"}'
- "--kv-cache-dtype"
- "fp8_ds_mla"
- "--max-num-seqs"
- "24"
b300:
extra_args:
- "--engram-config"
- '{"cpu_offload":true}'
- "--compilation-config"
- '{"mode":"VLLM_COMPILE","cudagraph_mode":"FULL_AND_PIECEWISE","cudagraph_capture_sizes":[6,12,18,24,30,36,48,60,72,96,120,144,192,240,288,384,480,576,768,1020,1536,2046,3072,4092,6144,8190]}'
- "--max-cudagraph-capture-size"
- "8190"
- "--max-num-batched-tokens"
- "8192"
- "--max-num-seqs"
- "256"
# H200 takes the TP4 default on 8x141 GB. Moving the Engram tables to
# pinned host DRAM (23.6 GiB per rank, 94 GiB across the replica) drops
# resident weights to 81.2 GiB per GPU and leaves 38.5 GiB of KV per GPU,
# about 20M tokens, measured on the InferenceX AgentX sweep. Without the
# offload the tables stay row-sharded in HBM and the KV pool collapses.
h200:
extra_args:
- "--engram-config"
- '{"cpu_offload":true}'
single_node_dep:
hardware_overrides:
blackwell:
extra_args:
- "--kernel-config"
- '{"moe_backend":"deep_gemm_mega_moe"}'
- "--attention-config"
- '{"backend":"FLASHMLA_MEGA_ATTN_DSV41","indexer_kv_dtype":"mxfp4","indexer_sparse_logits":true}'
- "--kv-cache-dtype"
- "fp8"
- "--max-num-seqs"
- "128"
- "--engram-config"
- '{"embedding_across_dp":true}'
# B200 DEP2 at c32: piecewise graphs extend to 4096 tokens so prefill-bearing
# steps replay graphs, and new prefills are admitted every 64 engine steps,
# aligned across DP ranks. The vLLM default of 1 admits on every step.
b200:
extra_args:
- "--max-num-seqs"
- "32"
- "--max-num-batched-tokens"
- "4096"
- "--prefill-schedule-interval"
- "64"
- "--compilation-config"
- '{"mode":"VLLM_COMPILE","cudagraph_mode":"FULL_AND_PIECEWISE","cudagraph_capture_sizes":[6,12,18,24,30,36,48,60,72,96,120,144,192,240,288,384,480,576,640,768,896,1024,1280,1536,1792,2048,2304,2560,2816,3072,3328,3584,3840,4096]}'
- "--max-cudagraph-capture-size"
- "4096"
pd_cluster:
# Verified 1P1D on GB200 NVL4: one tray per role, TP4 each, NIXL transfer.
env:
VLLM_DEEP_GEMM_WARMUP: "skip"
prefill:
nodes: 1
parallelism: tp
vllm_args:
- "--max-num-seqs"
- "32"
decode:
nodes: 1
parallelism: tp
vllm_args:
- "--max-num-seqs"
- "32"
hardware_notes:
dgx_station_gb300:
title: "Weights in Grace memory (UVA offload)"
body: "Single GB300 with both Engram tables and 60 GiB of routed experts in pinned Grace memory (UVA offload). Nightly image only. The first boot on a new flag set runs a 75-90 min FlashInfer autotune; keep /root/.cache/vllm on a persistent volume."
guide: |
## Overview
DeepSeek-V4.1 is a vision-language Mixture-of-Experts model: **552B backbone parameters
plus 196B of Engram memory, 8B active per prompt token and 16B per output token**, 40
transformer layers at hidden size 5120, with a 32-layer ViT and aligner in front of the
text stack. Six things distinguish it from V4:
- **Causal encoder-decoder.** The 40 layers split into a 20-layer encoder and a
20-layer decoder, and the decoder's global KV is projected from the encoder's final
hidden state instead of being computed layer by layer. Prefill therefore only runs
the encoder half, which is where the 8B-prefill / 16B-decode split comes from.
- **Two-tier sparse attention.** Every layer attends over a 128-token sliding window.
Layers that carry a compression ratio add compressed KV latents reaching further
back, pooled by a learned softmax gate. A small side attention — the *indexer*,
inherited from V3.2-Exp — scores those latents and keeps the best 512 per query,
pre-filtered by a candidate stage that selects 2048 blocks of 8. Only four layers
(2, 8, 14, 20) actually compress their own KV; the rest read that cache. V4.1 uses
only compression ratios 1 and 2, where V4 used 4 and 128.
- **Engram n-gram memory.** Layers 1 and 14 each own a hash table of ~384M rows x 256
dims, looked up by 2-, 3- and 4-gram hashes of the input and written into the residual
stream through a learned gate. These two tables alone are **196.6B parameters (~183 GiB)** —
plan capacity for them, they dominate everything except the experts.
- **Hyper-Connections.** The residual stream is carried as 4 parallel copies; each
sublayer derives its own pre/post/combine coefficients from the stream, with the
combine matrix made doubly stochastic by 20 Sinkhorn iterations.
- **DSpark draft head.** Three stages (128 routed experts each, 3 activated) draft a
block of 5 tokens, reading the attention input of layers 37-39, with a Markov bias
head and a confidence head on the last stage.
- **Mixed MXFP4/MXFP8 checkpoint.** Routed expert weights are MXFP4; everything else is
MXFP8 block-quantized, UE8M0 scales throughout. Embedding and LM head are BF16.
Routing is 6 of 384 experts per token plus one shared expert, scored with
`sqrtsoftplus` and a `noaux_tc` bias — with a **separate routing bias for tokens
inside an image span**, so vision and text tokens do not compete for the same experts.
## Context length
1,048,576 tokens, reached by YaRN with factor 16 over a 65,536-token training window.
Compressed KV rotates at its own RoPE theta (160,000) because one latent stands for
several tokens, so its positions are further apart than the raw stream's.
## Images
Images enter the prompt as `<|deepseek_image|>` spans; every position in the span
carries the image token id, and the per-position role (start / newline / end / image)
comes from the processor. The vision tower is a 32-layer ViT at hidden size 1024, patch
14, with a 3x downsampling aligner and a cap of 1024 tokens per image (minimum 295,936
pixels). There is **no limit on images per prompt**. Merged embeddings enter the text
model as `inputs_embeds`, before the hyper-connection stream expansion, while raw token
ids still flow through so the router can apply the image routing bias.
Tick **Encoder parallel** to run the ViT data-parallel (`--mm-encoder-tp-mode data`)
instead of tensor-parallel: at 32 layers / hidden 1024 the encoder is small enough
that TP communication costs more than it saves, which can significantly reduce TTFT
for multi-image requests. It is mutually exclusive with **Text only**.
## Reasoning and tool calling
Two thinking modes and a **numeric reasoning budget** rather than discrete tiers.
Send them as `chat_template_kwargs`:
| key | values |
|---|---|
| `thinking` / `enable_thinking` | boolean; if you send both they must agree |
| `reasoning_effort` | `low` (25), `high` (50), `xhigh` (75), `max` (100), **or an integer 1-100** |
The top-level OpenAI `reasoning_effort` field also works, where `"none"` turns
thinking off. `"minimal"` and `"medium"` are rejected — they are not part of this
model's set.
Note that the DeepSeek API maps its `low` / `high` / `max` tiers to 50 / 75 / 100,
while the open-source prompt encoder (and therefore vLLM) maps `low` / `high` /
`xhigh` / `max` to 25 / 50 / 75 / 100. A request tuned at `high` on the API runs at
effort 50 here; pass an integer if you need parity.
> **With both keys unset, thinking is ON at effort 50.** The do-nothing config is
> the most verbose one, so a request with a small `max_tokens` spends its budget on
> the trace and returns empty `content` with `finish_reason=length`. That reads as a
> broken model and is not. Either pin `thinking: false` or give the budget room.
In thinking mode the budget is rendered into the prompt as a `Reasoning Effort: N`
prefix on the first turn only.
```python
resp = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V4.1-Flash",
messages=[{"role": "user", "content": "What is 17*19?"}],
extra_body={"chat_template_kwargs": {"thinking": True, "reasoning_effort": 25}},
)
```
DeepSeek's published numbers use `temperature=1.0`, `top_p=0.95` and effort 100 with
`max_tokens` of at least 256K. The tech report notes that efforts of 60-80 recover most
of that accuracy at well under half the tokens, so reserve `max` for hard tasks.
Tool calls are wrapped in DSML tag blocks rather than JSON fences, and tool output
comes back in `<tool_result>` tags.
## Speculative decoding
DSpark is the only speculative method for this checkpoint. V4.1 dropped the MTP module
that V3 and V4 trained alongside the backbone. **Speculative decoding** enables a 5-token
block. On NVIDIA it also turns on adaptive verification (see the feature description).
On AMD, vLLM currently refuses adaptive verification. The generated command sets
`enable_adaptive_verification:false` and verifies the full block. Two checks in
`maybe_create_adaptive_verification_manager` fail on ROCm: the indexer helper
`supports_device_cpu_query_lens_mismatch()` is False, and
`DeepseekV41ROCMAiterSparseSWABackend` reports `AttentionCGSupport.UNIFORM_BATCH`
rather than `ALWAYS`. Remove the AMD override only after a serve with the true flag
starts. Acceptance depends on the workload, so measure it on your own traffic before
sizing a deployment around it.
The drafter's experts add roughly 14B parameters to the load.
## Serving text-only
Tick **Text only** to add `--language-model-only`, which skips the vision encoder
entirely. Worth it whenever the workload is text: it drops the ViT and aligner from
the load and frees that VRAM for KV cache. It is mutually exclusive with
`encoder_parallel`. The verified GB200 runs (TP4 and 1P1D) were text-only.
## Prefill/Decode disaggregation
The **Prefill/Decode Disaggregation** strategy is the verified 1P1D layout on GB200
NVL4: one tray (4 GPUs) per role, TP4 in each pool, KV handed over through NIXL,
fronted by `vllm-router --vllm-pd-disaggregation`. Both pools disable FlashInfer
autotune plus JIT and CuTeDSL warmup via `--kernel-config`, skip the DeepGEMM warmup
(`VLLM_DEEP_GEMM_WARMUP=skip`), and cap `--max-num-seqs` at 32. On 8-GPU nodes the
same layout becomes TP8 per role.
With **Speculative decoding** on, DSpark runs in both pools so the transferred KV
stays compatible.
## Memory
The checkpoint is roughly **511 GB on disk** (476 GiB), which breaks down as:
| Component | Entries | Stored |
|---|---:|---:|
| Routed + DSpark experts (MXFP4) | 557.2B | 259.5 GiB |
| Engram tables (FP8) | 196.6B | 183.1 GiB |
| Attention, dense projections, routers (FP8) | 7.4B | 6.9 GiB |
| Embedding + LM head (BF16), norms (FP32) | 2.0B | 3.9 GiB |
| UE8M0 block scales | 23.6B | 21.9 GiB |
DeepSeek's 552B backbone figure covers the routed experts, attention and embeddings;
the Engram tables, the ~14B DSpark drafter and the block scales sit outside it.
`vram_minimum_gb: 614` is that total times the schema's 1.2 headroom factor. It fits
one GB200 NVL4 tray (768 GB) at TP4, or one 8-GPU H200 node (1128 GB) with room for
KV cache.
KV cache is a small share of that budget. The compressed latents are shared across
layers and trained to be stored in FP4, and DeepSeek puts the resulting global KV at
**890 bytes per token**, about a quarter of V4-Flash; by that accounting a full 1M-token
prompt holds under 1 GB of global KV, plus a fixed 128-token sliding window per layer.
Weights and batch size, not cache, set the capacity limit — still, measure before
assuming a given context/batch fits.
## Prerequisites
- An image (vLLM 0.30.0+); no pip wheel serves this architecture, so the Install block
only offers Docker. On NVIDIA that is `vllm/vllm-openai:nightly`: the architecture
landed on `main` in [vllm-project/vllm#56228](https://github.com/vllm-project/vllm/pull/56228),
so any nightly from 2026-09-10 on serves it, and the launch-day
`deepseekv41-flash-0909` tag is no longer needed. On AMD it is
`vllm/vllm-openai-rocm:nightly-rocm100`, the ROCm 10.0 nightly, which also carries
[vllm-project/vllm#56503](https://github.com/vllm-project/vllm/pull/56503) — the change
that moves the mHC delayed pre block off the eager Torch reference and onto AITER
and postdates the 0909 tag.
- The Rust OpenAI frontend is selected by default in the command builder. Switch
to Python if you encounter unsupported features or compatibility issues.
- Expect a long first load: `VLLM_ENGINE_READY_TIMEOUT_S=3600` is set for that reason.
- On AMD, the generated command sets `VLLM_USE_BREAKABLE_CUDAGRAPH=1`. DeepSeek-V4.1-Flash does not support `torch.compile`, and the ROCm sparse SWA backend only supports uniform-batch CUDA graphs. Without breakable CUDA graphs, default `FULL_AND_PIECEWISE` dies at capture.
- On AMD, the generated command passes `--moe-backend aiter` rather than naming a kernel. Naming `aiter_triton_mxfp4_bf16` pins the Triton W4A16 `_moe_gemm_a16w4` kernel; the plain name lets vLLM select the Composable Kernel a8w4 experts, used in the MI355X benchmark below. It also sets `AITER_TRITON_LOG_LEVEL=ERROR` to drop AITER's per-call "Gluon unavailable" warning, which is constant on gfx950 and dominated the server log.
- On MI355X, **Tensor Parallel** inherits vLLM's scheduler limits. Tune `--max-num-seqs` for your traffic and memory budget if needed. The benchmark configuration below records an explicit limit for reproducibility.
- On AMD, ticking **Speculative decoding** also sets `enable_adaptive_verification:false`. vLLM currently refuses the true flag: `DeepseekV41IndexerBackend.supports_device_cpu_query_lens_mismatch()` is False, and `DeepseekV41ROCMAiterSparseSWABackend` reports `UNIFORM_BATCH` rather than `ALWAYS`. DSpark still drafts 5 tokens per round.
## Verifying
Serve, then send one text request and one image request — the vision tower is a
separate path and a text-only smoke test will not exercise it:
```bash
curl http://localhost:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"deepseek-ai/DeepSeek-V4.1-Flash",
"messages":[{"role":"user","content":"What is 17*19? Return only the integer."}]}'
```
A correct answer is `323`.
## Performance tuning
Start with vLLM's defaults and tune one parameter at a time for your workload:
- `--max-num-seqs`: raise the running-sequence limit if it causes queueing and
memory permits; lower it if larger batches hurt latency or memory use.
- `--max-num-batched-tokens`: balance prefill throughput against decode latency
and memory use by adjusting the token budget per scheduling iteration.
- `--max-cudagraph-capture-size`: cover expected decode batches, including DSpark
draft tokens, while accounting for graph memory and startup cost. The benchmark
below rounds `128 * (1 + 5) = 768` up to 1024.
- `--max-model-len`: size the context limit for your prompt plus output lengths.
### MI355X benchmark server
This runnable command matches the server arguments and execution environment in
[InferenceX #3058](https://github.com/SemiAnalysisAI/InferenceX/pull/3058) at
[commit `559ef756`](https://github.com/SemiAnalysisAI/InferenceX/blob/559ef7560a74d0fb8da64fdfc0c357ad2fc33c4f/benchmarks/single_node/agentic/dsv41flash_fp4_mi355x_vllm_mtp.sh).
It uses four MI355X GPUs and the pinned image. Set `ROCR_VISIBLE_DEVICES` and `PORT`
for your host, and `HF_HOME` to an existing writable Hugging Face cache if needed.
Throughput mode uses synthetic acceptance for benchmark measurement only. Run
`export EVAL_ONLY=true` before this command for real block rejection, as used by
the InferenceX accuracy eval; use that mode for ordinary serving too.
```bash
docker run --rm -i --network host --ipc host \
--device /dev/kfd --device /dev/dri --group-add video \
--security-opt seccomp=unconfined --workdir /tmp \
-v "${HF_HOME:-$HOME/.cache/huggingface}:/root/.cache/huggingface" \
-e ROCR_VISIBLE_DEVICES="${ROCR_VISIBLE_DEVICES:-0,1,2,3}" \
-e PORT="${PORT:-8000}" -e EVAL_ONLY="${EVAL_ONLY:-false}" \
--entrypoint /bin/bash \
vllm/vllm-openai-rocm:nightly-eed1f3d0c6043bd494424a22443ee198dd56f657 -s <<'BASH'
set -eo pipefail
export HIP_VISIBLE_DEVICES="$ROCR_VISIBLE_DEVICES"
export VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_MOE=1
export AITER_TRITON_LOG_LEVEL=ERROR VLLM_USE_BREAKABLE_CUDAGRAPH=1
export OMP_NUM_THREADS=1 VLLM_ENGINE_READY_TIMEOUT_S=3600
export VLLM_USE_RUST_FRONTEND=1 PYTHONUNBUFFERED=1 GPU_COUNT=4
MODEL=deepseek-ai/DeepSeek-V4.1-Flash
hf download "$MODEL"
if [[ "$EVAL_ONLY" == true ]]; then
SPEC_CONFIG='{"method":"dspark","num_speculative_tokens":5,"draft_sample_method":"probabilistic","rejection_sample_method":"block","enable_adaptive_verification":false}'
else
SPEC_CONFIG='{"method":"dspark","num_speculative_tokens":5,"draft_sample_method":"probabilistic","rejection_sample_method":"synthetic","synthetic_acceptance_length":3.51,"enable_adaptive_verification":false}'
fi
exec vllm serve "$MODEL" --served-model-name "$MODEL" \
--host 0.0.0.0 --port "$PORT" --tensor-parallel-size 4 \
--language-model-only \
--tokenizer-mode deepseek_v41 \
--tool-call-parser deepseek_v41 --enable-auto-tool-choice \
--reasoning-parser deepseek_v41 \
--moe-backend aiter \
--gpu-memory-utilization 0.9 \
--speculative-config "$SPEC_CONFIG" \
--max-model-len 1048576 \
--max-num-seqs 128 \
--max-cudagraph-capture-size 1024 \
--max-num-batched-tokens 16384 \
--disable-uvicorn-access-log
BASH
```
The AgentX sweep uses task concurrencies 1, 2, 4, 8, 16 and 32, GPU-resident KV,
and `WEKA_LOADER_OVERRIDE=semianalysis_cc_traces_weka_062126`. Five-token DSpark
uses golden acceptance length 3.51. For replay setup and results, see
[InferenceX #3058](https://github.com/SemiAnalysisAI/InferenceX/pull/3058) and its
[pinned master config](https://github.com/SemiAnalysisAI/InferenceX/blob/559ef7560a74d0fb8da64fdfc0c357ad2fc33c4f/configs/amd-master.yaml#L1761).
The command above starts the server; the InferenceX harness supplies the replay
client. Validation evidence applies to that pinned benchmark configuration, not
every future `:nightly` image or the general command's inherited scheduler limits.
## MI355X Tensor Parallel
On MI355X, **Tensor Parallel** defaults to **TP2** with Engram CPU offload
(`--engram-config '{"cpu_offload":true}'`). The Engram tables cost 47.2 GiB of
device memory per rank at TP4 and 94.4 GiB at TP2; on a 288 GiB card the TP2
figure does not fit beside half the checkpoint, so the tables move to pinned
host memory. TP2 halves the GPUs per server and so doubles the servers per
node. For TP4, pass `--tensor-parallel-size 4` and
`--engram-config '{"cpu_offload":false}'` to keep the tables resident.
The offload resolves on gfx950 only on builds carrying
[vllm-project/vllm#57491](https://github.com/vllm-project/vllm/pull/57491),
which widened two `is_cuda()` gates to `is_cuda_alike()`. Those builds also
default `cpu_offload` on through `VLLM_PLE_CPU_OFFLOAD`, so set it explicitly
rather than inheriting the default.
`--monokernel` selects the mono decode layer from
[vllm-project/vllm#60397](https://github.com/vllm-project/vllm/pull/60397),
which [vllm-project/vllm#61082](https://github.com/vllm-project/vllm/pull/61082)
extended to all 40 backbone layers and moved from the
`VLLM_ROCM_MONO_DECODE` environment variable to this flag. Two persistent
launches cover a layer for decode steps of up to 48 rows, which with five
DSpark drafts is concurrency 8; larger steps fall back to the regular path, so
the gain is largest at low concurrency and fades as concurrency rises.
Decoder SWA bounded replay
([vllm-project/vllm#59039](https://github.com/vllm-project/vllm/pull/59039))
is on by default on ROCm, so the recipe no longer passes
`--no-swa-bounded-replay`. Use a nightly from 2026-10-13 or later; earlier
ROCm builds lack the replay window clamp and fault at the first prefix hit.
## MI300X Tensor Parallel
On MI300X, **Tensor Parallel** takes the recipe default of **TP4** with the
Engram tables offloaded to pinned host memory
(`--engram-config '{"cpu_offload":true}'`). At TP4 the resident weights are
roughly 81 GiB per rank, which leaves a healthy KV pool on a 192 GB card.
**TP2 does not fit on this SKU.** At TP2 the offloaded weights rise to roughly
145 GiB per rank, and a measured run reported
`Available KV cache memory: -13.51 GiB`, so the engine refused to start with
`No available memory for the cache blocks`. That was already with the
sparse-attention indexer buffer halved to 4096 batched tokens. Raising
`--gpu-memory-utilization` and halving the buffer again recover roughly 13 GiB
between them, which still leaves no usable KV for a 1M-context model, so TP2
is not offered here. MI325X runs the same TP2 configuration successfully on
its larger 256 GB card.
MI300X is gfx942 and needs the same two workarounds as MI325X: no FP4 MFMA, so
the MXFP4 experts run through a Triton kernel and device memory is better spent
on KV than on resident Engram tables; and piecewise graph capture segfaults
every worker, so this recipe sets `VLLM_USE_BREAKABLE_CUDAGRAPH=0` and captures
only full decode graphs
(`--compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}'`), with prefill
eager.
The offload resolves on gfx942 only on builds carrying
[vllm-project/vllm#57491](https://github.com/vllm-project/vllm/pull/57491),
the same gate MI325X and MI355X need. Those builds also default `cpu_offload`
on through `VLLM_PLE_CPU_OFFLOAD`, so set it explicitly rather than inheriting
the default.
## MI325X Tensor Parallel
On MI325X, **Tensor Parallel** takes the recipe default of **TP4**, with the
Engram tables offloaded to pinned host memory
(`--engram-config '{"cpu_offload":true}'`). A 256 GB card leaves roughly
81 GiB per rank of resident weights at TP4 and roughly 145 GiB at TP2; TP2
fits but leaves the smallest KV pool of any arm in this recipe, so TP4 is the
default here rather than the TP2 that MI355X uses on its larger 288 GiB card.
For TP2, pass `--tensor-parallel-size 2`.
MI325X is gfx942, which differs from gfx950 in two ways that matter. It has no
FP4 MFMA, so the MXFP4 experts run through a Triton kernel and device memory is
better spent on KV than on resident Engram tables. It also segfaults in every
worker during piecewise graph capture, so this recipe sets
`VLLM_USE_BREAKABLE_CUDAGRAPH=0` and captures only full decode graphs
(`--compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}'`); prefill runs
eagerly.
The offload resolves on gfx942 only on builds carrying
[vllm-project/vllm#57491](https://github.com/vllm-project/vllm/pull/57491),
the same gate MI355X needs. Those builds also default `cpu_offload` on through
`VLLM_PLE_CPU_OFFLOAD`, so set it explicitly rather than inheriting the
default.
## H200 Tensor Parallel
On H200, **Tensor Parallel** uses the TP4 default with Engram CPU offload
(`--engram-config '{"cpu_offload":true}'`). The Engram tables move to pinned
host DRAM and are read through UVA, so the output is unchanged. On 141 GB
cards that offload is what makes TP4 practical: it moves 23.6 GiB per rank off
the GPU, leaving 81.2 GiB of resident weights and 38.5 GiB of KV per GPU
(about 20M tokens). No scheduler caps are needed at this replica size.
## Blackwell TP and DEP
On B200, B300, GB200 and GB300, **Tensor Parallel** uses **TP2** with
`FLASHINFER_MLA_SPARSE_DSV41` and Engram CPU offload
(`--engram-config '{"cpu_offload":true}'`), which moves the Engram tables to
pinned host DRAM and reads them through UVA; the output is unchanged.
**Data + Expert Parallel** uses the hardware's
GPU count: DEP4 on GB200/GB300 trays, DEP8 on B200/B300 nodes. It enables
`--enable-expert-parallel`, `FLASHMLA_MEGA_ATTN_DSV41`,
`--kernel-config '{"moe_backend":"deep_gemm_mega_moe"}'`, and
`--engram-config '{"embedding_across_dp":true}'`.
Both configurations set `indexer_kv_dtype` to `mxfp4`, `indexer_sparse_logits`
to `true`, `--kv-cache-dtype fp8`, and `--max-num-seqs 128`. Reasoning uses
`--reasoning-parser deepseek_v41`.
Each Blackwell SKU carries an exact-GPU override that takes precedence over
the shared Blackwell defaults. B300 keeps its existing CUDA Graph capture
configuration (`--max-cudagraph-capture-size 8190`),
`--max-num-batched-tokens 8192` and `--max-num-seqs 256`; B200, GB200 and
GB300 add Engram CPU offload and otherwise inherit the shared Blackwell
settings. Scheduler limits are workload-dependent at TP2, so size
`--max-num-batched-tokens` and `--max-cudagraph-capture-size` for your own
serving profile. For high interactivity on any of them, use
`--tensor-parallel-size 4`.
## B200 AgentX tuning
On B200, TP2 at concurrency 1 uses `--engram-config '{"cpu_offload":true,"use_thp":true}'`,
`--max-num-seqs 8` and `--max-num-batched-tokens 4096`. DEP2 (TP1 x DP2 with expert
parallelism) serves concurrency 8 to 32; at c8 and c16 graph capture stops at 576 tokens and
`--prefill-schedule-interval` stays at the vLLM default of 1, while c32 extends piecewise
graphs to 4096 tokens and sets `--prefill-schedule-interval 64`. DEP4 sets
`--prefill-schedule-interval 48` at c128 and keeps the default of 1 at c64.
For DEP2 at c128, add vLLM's native CPU KV offloading with
`--kv-offloading-backend native --kv-offloading-size 32` (GiB per DP rank, 64 GiB per
server), `--gpu-memory-utilization 0.955` and `--max-num-seqs 48`.
## DGX Station (GB300) Tensor Parallel
One GB300 exposes 252 GB of HBM next to 496 GB of Grace LPDDR5X, and the
476 GiB checkpoint does not fit the HBM. The Station row serves TP1 with
both Engram tables (189 GiB) and 60 GiB of routed experts in pinned host
memory, read through UVA. At `--gpu-memory-utilization 0.97` the KV pool
holds about 3.09M tokens at full 1M context. 60 GiB is the lowest
verified offload at 1M context with speculative decoding on (55 fails the
minimum-KV check). For more concurrent 1M contexts, raise
`--cpu-offload-gb` to 70 and accept ~10% lower single-stream decode.
Measured on `vllm/vllm-openai:nightly-dee37d89`:
| | 1 stream | 2 | 4 | 8 | 12 | 16 |
|---|--:|--:|--:|--:|--:|--:|
| aggregate tok/s, prose | 90.5 | 124 | 168 | 302 | 378 | 416–423 |
- **Autotune.** The first boot on a new flag set or image runs a 75–90
minute FlashInfer autotune. Mount `/root/.cache/vllm` on a persistent
volume; later boots start in about 7 minutes.
- **Host memory.** With the server loaded, ~430 of the 494 GiB Grace pool
is in use (pinned weights plus loader shared memory).
`VLLM_WEIGHT_OFFLOADING_DISABLE_PIN_MEMORY=1` frees ~66 GB at about 7%
lower single-stream decode.
Launch scripts and full measurements:
[J-M-Recipes/recipes](https://github.com/J-M-Recipes/recipes/tree/main/recipes/dgx-station-gb300/deepseek-v4.1-flash-vllm-uva-dspark).
## KV cache offloading
Offloading requires a build containing [vllm-project/vllm#57145](https://github.com/vllm-project/vllm/pull/57145).
Validation used [deepseek-ai/DeepSeek-V4.1-Flash](https://huggingface.co/deepseek-ai/DeepSeek-V4.1-Flash) on 4×H200 (TP4),
vLLM commit `d1b4028d7e`, the Python frontend, `--enforce-eager`,
`--max-model-len 16384`, `--max-num-seqs 16` and `--max-num-batched-tokens 8192`.
DeepSeek validation used `--gpu-memory-utilization 0.95`.
Validation used `TieringOffloadingSpec`: 8 GiB total CPU tier, `blocks_per_chunk: 1`,
a persistent filesystem `root_dir`, eight read/write threads and `PYTHONHASHSEED=0`.
Three CPU-tier reloads and three filesystem reloads after restarting the server passed.
Reloaded responses matched the cold output, including the reasoning field.
The five-shot GSM8K diagnostic scored 31/32; this is a small sample, not the full benchmark.
These results cover this text-only configuration, not the builder's larger tier/chunk defaults or other variants.
## References
- [Model card](https://huggingface.co/deepseek-ai/DeepSeek-V4.1-Flash)
- [Technical report](https://huggingface.co/deepseek-ai/DeepSeek-V4.1-Flash/blob/main/DeepSeek_V41_Tech_Report.pdf)
- [DeepSeek-V4-Flash recipe](/deepseek-ai/DeepSeek-V4-Flash) — the V4 sibling this diverges from
- [DeepSeek-V3.2-Exp recipe](/deepseek-ai/DeepSeek-V3.2-Exp) — where the indexer first appeared