The Transformers modeling backend serves DeepSeek-R1 at native speed

Community Article
Published August 4, 2026

vLLM PR #48250 (MLA fuser) + PR #48757 (fusion passes)

The MLA fuser maps Hugging Face's Multi-head Latent Attention onto vLLM's optimized MLA layer, so a DeepSeek model served through --model-impl transformers gets the same compressed-latent KV cache as the hand-written native implementation. Measured on DeepSeek-R1-0528 — 671B parameters, 37B active, native FP8 — across 16×H100-80GB under 3D parallelism (TP8 × PP2 × EP8), the Transformers backend runs at 99.6% of native throughput averaged over six workloads, with every point inside 3%.

article_figure_light

Results (% of native vLLM)

Concurrency ladder, 1024-token prompts, 2048-token generations — R1 is a reasoning model, so the workload is a moderate prompt and a long chain of thought.

Client concurrency Native tok/s Transformers tok/s % native Native TPOT Transformers TPOT
1 139 136 98.0% 10.5 ms 10.8 ms
16 941 931 98.9% 24.1 ms 24.4 ms
64 2,378 2,373 99.8% 39.6 ms 39.7 ms
128 3,787 3,762 99.3% 49.7 ms 50.1 ms
unbounded 5,512 5,667 102.8% 98.8 ms 96.4 ms
long-context (8,192-in / 1,024-out, conc. 32) 5,941 5,858 98.6% 44.9 ms 45.8 ms

Mean 99.6% of native (range 98.0–102.8%). Decode latency tracks just as closely: mean TPOT is 100.4% of native across the five concurrency rungs. Time to first token is 98.8% of native on the same mean.

Each point is a single run — no repeats — so sub-percent differences are not signal. The unbounded leg is the largest deviation in either direction; the Transformers backend finishing 2.8% ahead there is not a claimed speedup, only evidence that the gap does not open up under saturation.

Both conditions run identical server flags, identical workloads, and the same transformers library from source; only --model-impl differs (native additionally needs --trust-remote-code, since it reads DeepSeek's config through its remote-code auto_map while the Transformers backend uses the in-library modeling).

What the fuser actually replaced

The engine logs what it matched at load. On the Transformers path:

Using FLASH_ATTN_MLA attention backend
  Fused: gate + experts + shared experts (DeepseekV3MoE) -> MoERunner (internal routing)
  Fused: input_layernorm (DeepseekV3RMSNorm) -> RMSNorm
  Fused: kv_a_layernorm (DeepseekV3RMSNorm) -> RMSNorm
  Fused: post_attention_layernorm (DeepseekV3RMSNorm) -> RMSNorm
  Fused: q_a_layernorm (DeepseekV3RMSNorm) -> RMSNorm
  Fused: self_attn (DeepseekV3Attention) -> MLAAttention
Enabled custom fusions: norm_quant, act_quant, allreduce_rms

Native selects the same FLASH_ATTN_MLA backend and the same three custom fusions. That is the point: after fusing, the two paths are running the same attention, GEMM and MoE kernels, and the remaining difference is bookkeeping.

Topology: 3D parallelism, TP8 × PP2 × EP8

MLA sets num_kv_heads = 1, so tensor parallelism cannot shard the KV cache — TP replicates the latent cache on every rank. The usual R1 recipe is therefore pure data parallel with expert parallelism (TP1 + DP16 + EP), which costs nothing on KV.

That does not fit in 80 GB. DP replicates R1's non-expert weights on every rank, putting weights at 68.4 GiB/GPU and leaving 10.8 GiB where the KV cache wanted 29.2 GiB. This is an H100 constraint, not a fundamental one — on H200s the same shape has headroom to spare.

What fits is all three axes at once: tensor parallel 8, pipeline parallel 2, expert parallel 8 — 8-way TP within each node, the layers split across the two nodes, and R1's 256 experts sharded across the 8 ranks of each pipeline stage. The cost is an 8× replicated MLA latent cache, which is what the 4.31× / 3.87× concurrency figures above already reflect.

Reproduce

Two nodes of 8×H100-80GB. Start the engine on each node — node 0 hosts the API server, node 1 runs headless:

# node 0
vllm serve deepseek-ai/DeepSeek-R1-0528 --model-impl transformers \
  --tensor-parallel-size 8 --pipeline-parallel-size 2 --enable-expert-parallel \
  --all2all-backend deepep_high_throughput \
  --nnodes 2 --node-rank 0 --master-addr $NODE0 --master-port 29501 \
  --no-enable-prefix-caching --gpu-memory-utilization 0.9 --port 8241

# node 1 — same flags, plus --headless
vllm serve deepseek-ai/DeepSeek-R1-0528 --model-impl transformers \
  --tensor-parallel-size 8 --pipeline-parallel-size 2 --enable-expert-parallel \
  --all2all-backend deepep_high_throughput \
  --nnodes 2 --node-rank 1 --master-addr $NODE0 --master-port 29501 \
  --no-enable-prefix-caching --gpu-memory-utilization 0.9 --headless

Then benchmark against it. This is the unbounded row; the other rungs are the same command with --max-concurrency set to 1, 16, 64 or 128, and the long-context leg is --random-input-len 8192 --random-output-len 1024 --max-concurrency 32 --num-prompts 96:

vllm bench serve --model deepseek-ai/DeepSeek-R1-0528 --port 8241 \
  --dataset-name random --random-input-len 1024 --random-output-len 2048 \
  --num-prompts 512 --ignore-eos \
  --percentile-metrics ttft,tpot,itl,e2el --save-result --result-filename tfm.json

For the native reference, swap --model-impl transformers for --model-impl vllm --trust-remote-code and restart the server — native reads DeepSeek's config through its remote-code auto_map, while the Transformers backend uses the in-library modeling. Nothing else changes.

Leave --max-model-len unset so the engine takes R1's own 163,840; capping it would inflate the reported concurrency multiple, which is the stat the MLA comparison quotes.

Raw results (vllm bench output)

native__reason_max.json
{
  "date": "2026-07-28T15:03:06Z",
  "endpoint_type": "openai",
  "backend": "openai",
  "model_id": "deepseek-ai/DeepSeek-R1-0528",
  "num_prompts": 512,
  "request_rate": "inf",
  "burstiness": 1.0,
  "max_concurrency": null,
  "duration": 285.245001591,
  "completed": 512,
  "failed": 0,
  "total_input_tokens": 523683,
  "total_output_tokens": 1048576,
  "request_throughput": 1.794948192410866,
  "input_throughput": 1835.9059653248037,
  "output_throughput": 3676.0538980574534,
  "total_token_throughput": 5511.959863382257,
  "max_output_tokens_per_s": 7296.0,
  "max_concurrent_requests": 512,
  "mean_ttft_ms": 12650.083188916024,
  "median_ttft_ms": 12589.589538999999,
  "std_ttft_ms": 7196.290916923024,
  "p99_ttft_ms": 25139.36458525,
  "mean_tpot_ms": 98.77910820285302,
  "median_tpot_ms": 100.00085775769418,
  "std_tpot_ms": 21.35193265558169,
  "p99_tpot_ms": 127.0218162356668,
  "mean_itl_ms": 98.77910820285261,
  "median_itl_ms": 64.573471,
  "p99_itl_ms": 379.100455,
  "mean_e2el_ms": 214850.9176801562,
  "median_e2el_ms": 217291.41715999998,
  "p99_e2el_ms": 285038.57440309995
}
tfm__reason_max.json
{
  "date": "2026-07-28T15:45:13Z",
  "endpoint_type": "openai",
  "backend": "openai",
  "model_id": "deepseek-ai/DeepSeek-R1-0528",
  "num_prompts": 512,
  "request_rate": "inf",
  "burstiness": 1.0,
  "max_concurrency": null,
  "duration": 277.429484294,
  "completed": 512,
  "failed": 0,
  "total_input_tokens": 523683,
  "total_output_tokens": 1048576,
  "request_throughput": 1.8455140098138194,
  "input_throughput": 1887.6256117213484,
  "output_throughput": 3779.612692098702,
  "total_token_throughput": 5667.2383038200505,
  "max_output_tokens_per_s": 7680.0,
  "max_concurrent_requests": 512,
  "mean_ttft_ms": 12596.19438248046,
  "median_ttft_ms": 12539.140319000002,
  "std_ttft_ms": 7158.228256194002,
  "p99_ttft_ms": 25022.91038557,
  "mean_tpot_ms": 96.35147293843127,
  "median_tpot_ms": 92.69548367024913,
  "std_tpot_ms": 18.672019963976954,
  "p99_tpot_ms": 123.25109050512458,
  "mean_itl_ms": 96.35147293843056,
  "median_itl_ms": 67.474756,
  "p99_itl_ms": 377.44121772999995,
  "mean_e2el_ms": 209827.6594874493,
  "median_e2el_ms": 202286.947066,
  "p99_e2el_ms": 277200.33659341006
}

Every number in the results table above comes from the vllm bench serve JSON for that run.

Community

Sign up or log in to comment