01 Start here
DeepInfer is the program DeepSeek runs on its own GPUs to answer your prompts. In October 2026 they opened the source. This page is everything in that repository, the blogs around it, and every acronym it drops — explained from zero.
What this guide covers
Three things that are easy to confuse
| Layer | What it is | Is it in this repo? |
|---|---|---|
| The model DeepSeek-V4.1-Flash |
The weights — the learned numbers. MIT licensed, downloaded from Hugging Face. | ❌ No. DeepInfer runs it. Default checkpoint deepseek-ai/DeepSeek-V4.1-Flash-1017. |
| The engine DeepInfer |
The code that executes the model on hardware: kernels, batching, memory, sampling. | ✅ Yes. This is the repo. |
| The service api.deepseek.com |
HTTP API, auth, billing, routing, the distributed KV cache, load balancers. | ⚠️ Mostly not. Only deepseek-recipe (protocol/tokenizing) is separately open. |
Who should read which part
Read sections 2 and 3, then skim the concept cards in section 6. That is the whole story in about 20 minutes.
Section 4 is a clickable map of every file; section 5
traces one request through all of them in order. Start with entrypoints/llm.py and follow the arrows.
Sections 14, 16 and 17: the published numbers, the missing pieces, and the comparison write-ups with their caveats.
Sections 12, 13 and 15: build commands, every tunable env var, and DeepSeek's own deployment Q&A translated into plain terms.
02 The mental model
Almost every optimization in DeepInfer exists because of one fact: generating text has two completely different phases with opposite bottlenecks. Learn that, and the rest of the repo explains itself.
Prefill vs. Decode
When you send a prompt, the engine does two different jobs:
Where every DeepInfer feature fits
| Feature | Attacks | The idea in one sentence |
|---|---|---|
| FlashMLA / DeepGEMM | both | Hand-written chip instructions so each math operation runs at ~95% of what the silicon physically allows. |
| MoE + Wide-EP | decode | Only a few "experts" of the model run per token; spread them over 64–128 GPUs so each GPU reads less memory per step. |
| EPLB | decode | Some experts are more popular; copy the hot ones to extra GPUs so no single GPU becomes the straggler. |
| DSpark / MTP | decode | A small draft model guesses 5 tokens ahead; the big model checks them all in one step, so one memory read yields several tokens. |
| Confidence head | decode | Predict which guesses are likely right, and only pay to verify those. |
| Async scheduling (ZOS) | decode | Do the CPU bookkeeping for step N+1 while the GPU is still busy with step N, so the GPU never idles. |
| CUDA graphs | decode | Record the whole step once and replay it, removing thousands of tiny CPU launch calls. |
| Paged KV cache | both | Store the notes in fixed-size blocks like OS virtual memory, so memory never fragments. |
| Prefix caching + 3FS | prefill | If someone already read this prompt prefix, reuse their notes from a 100-PiB SSD cluster instead of re-reading. |
| SWA bounded replay / YoCo | prefill | When only the recent-window notes are missing, rebuild just the last 128 tokens instead of the whole window. |
| FP8 / FP4 quantization | both | Store and multiply numbers in 8 or 4 bits instead of 16, halving or quartering memory traffic. |
| Sparse indexer (DSA) | both | For long contexts, only attend to the few hundred most relevant past blocks instead of all of them. |
| Chunked prefill | prefill | Slice a 128k prompt into pieces so one huge request can't freeze everyone else. |
| Batch invariance | neither | Not speed — correctness: make results identical no matter who else is in the batch. |
The four layers of the codebase
The repo is organised as a stack. Each layer only talks to the one below it:
03 Architecture
Two architectures matter: the production service DeepSeek actually runs (most of which is not in the repo), and the engine that is in the repo. Here is both.
3.1 — The production system (DeepSeek's own deployment)
This is the picture in doc/images/architecture_overview.png, redrawn. Requests arrive as JSON, get
turned into tokens, and are routed through three independently-scaled pools of GPUs.
Prefill wants few, fat, compute-dense GPUs. Decode wants many GPUs to spread MoE experts. Separating them lets you scale and tune TTFT and TBT independently — at the cost of shipping the KV cache between them.
So any prefill machine can hit any prefix. Local caching would force routing by prefix affinity, which fights the load balancer. Storage also scales — and fails — independently of compute.
1 prefill + 1 decode engine technically works. In practice the P:D GPU ratio is ~1:1 and decode wants EP64+, so the smallest efficient deployment is about 64 prefill + 64 decode GPUs.
3.2 — The engine (what you actually get)
Inside one engine instance, this is the flow. Note the split brain: Python owns orchestration and the model; a
compiled C++ extension (deepinfer._C) owns the hot per-request bookkeeping.
3.3 — Why a C++ core at all?
In decode, a step can take ~5 ms and involve 60+ sequences. If the Python interpreter spends 3 ms deciding what to run, you have lost half your machine. So everything that is per-request, per-step moved into C++: the scheduler, the block allocator, sequence state, the sampler-input packing, and the output objects (which are pooled, not allocated). Python keeps the parts that run once per step or are inherently PyTorch.
# The C++ extension is compiled into deepinfer._C — these are its translation units COMMON_SOURCES = [ "csrc/pybind.cpp", "csrc/engine/pybind.cpp", "csrc/engine/pybind_sequence.cpp", "csrc/engine/pybind_scheduler.cpp", "csrc/engine/pybind_output.cpp", "csrc/engine/zos.cpp", # zero-overhead scheduling "csrc/engine/input_metadata/block_load_plan.cpp", "csrc/engine/input_metadata/prepare_tensor_input_base.cpp", "csrc/engine/input_metadata/prepare_tensor_input_v4.cpp", # 655 lines of tensor packing "csrc/engine/prepare_sample.cpp", "csrc/graph_timer/pybind.cpp", ] # Then either a CUDAExtension (+ cuda_utils.cpp) or an NpuExtension (+ asc_utils.cpp)
deepinfer/__init__.py opens with a comment forbidding import torch anywhere in that file,
because importing torch initialises CUDA, and once CUDA is initialised you can no longer change
CUDA_VISIBLE_DEVICES for the child worker processes. Small detail, but it tells you the whole package is
built around careful control of process and device setup.
04 Repository map
Every directory and every significant file, with what it does and why it exists. Click a folder to open it; click a filename to open it on GitHub. The search box at the top filters this tree too.
The ten-second version
| Directory | Size | What lives there |
|---|---|---|
deepinfer/ | 223 files | The engine. Python: entrypoint, engine loop, workers, the V4.1 model, ~45 TileLang kernels ×2 backends, EPLB, structured output, multimodal. |
csrc/ | 33 files | The C++ core. Scheduler (1 329 lines), block manager, sequence state, input-tensor packing, sampler-input packing, zero-overhead scheduling, graph timers. |
vendor/ | 6 submodules | The kernels. Git submodules: FlashMLA, DeepGEMM, DeepEP, DeepSelect (+ the Ascend ports of DeepEP and DeepGEMM). |
examples/ | 18 files | Runnable scripts: offline inference, the benchmark harness, Pareto sweeps, YoCo equivalence checks, structured-output demo. |
eval/ | 17 files | Accuracy harness for AIME 2026, GPQA Diamond and HLE, with LLM-judge and rule-based scoring. |
tests/ | 20 files | pytest suite: batch invariance, sampler, EPLB policy, KL divergence, state-cache ops, CUDA sync guards. |
deep_cluster/ | 8 files | Hardware discovery without importing torch — NVIDIA via NVML/procfs, Ascend via sysfs, plus NUMA binding. |
doc/ | 5 files | The deployment Q&A (312 lines — the most valuable document in the repo) and the benchmark/architecture images. |
scripts/ | 4 files | Build, vendor build, stub generation, C++ formatting. |
05 What happens to one request
Follow a single prompt from llm.generate("Hello") to the returned text, naming the exact
file at every hop. This is the fastest way to learn the codebase.
The code you write
from deepinfer.entrypoints.llm import LLM from deepinfer.sampling_params import SamplingParams llm = LLM( model="/path/to/DeepSeek-V4.1-Flash-1017", expert_parallel_size=8, # spread MoE experts over 8 GPUs context_parallel_size=1, # split one sequence across N GPUs (1 = off) data_parallel_size=8, # 8 independent engine replicas max_num_seqs=4, # max concurrent sequences in a batch max_model_len=32768, max_num_batched_tokens=32768, # prefill chunk budget gpu_memory_utilization=0.85, # fraction of HBM for weights + KV chunked_prefill=True, lookahead_tokens=5, # speculative draft depth (nextn) ) outputs = llm.generate( prompts=["Explain MoE routing in two sentences."], sampling_params=SamplingParams(temperature=0.7, top_p=0.95, max_tokens=256), ) # outputs[i] is a list of RequestOutput (one per generated token) or decoded text
The twelve hops
The loop, in real code
def step(self) -> np.ndarray[np.uint64]: with cpu_timer.record("schedule"): # 1. C++ decides this step's batch. Also frees finished sequences. seq_metadata_list, scheduler_outputs = self.scheduler.schedule(self.sequence_metadata_pool) # 2. Forked sequences need their KV state cloned before they diverge if clone_states.size > 0 or clone_blocks.size > 0: to_release = self._run_workers("clone_and_zero_states_and_blocks_on_default_stream", ...) self.deferred_release.collect_clone_unpins(*to_release) # 3. On the last prefill chunk, arm the structured-output grammar matcher if scheduler_outputs.prompt_run: for seq, md in zip(scheduler_outputs.scheduled_seqs, seq_metadata_list, strict=True): if md.is_last_prompt_chunk and md.sampling_params.so_args is not None: md.so_status = 1 # 4. Pull any finished image preprocessing from the multimodal worker pool if self.multi_modal_processor is not None: self.multi_modal_processor.fetch() # 5. Launch the GPU work. Returns last step's outputs under async scheduling. request_outputs = self._execute_model_async_step(seq_metadata_list, scheduler_outputs) self.generate_step += 1 return request_outputs
And the worker side — this is the heart of the zero-overhead design, where the previous step's CPU work is sandwiched between this step's GPU launches:
# 1. Launch this step's forward (non-blocking — just queues kernels) main_model_output = self.model_runner.execute_main_model( seq_metadata_list, self.gpu_cache, metadata_zos=metadata_zos, total_q=total_q) # 2. While the GPU chews on that, do the PREVIOUS step's CPU post-processing request_outputs = self._post_process_step(self.prev_step_state) # 3. Launch this step's sampling (also non-blocking) sampler_output = self.model_runner.execute_sample( seq_metadata_list, input_metadata, model_output, self.gpu_cache) sample_end_evt = torch.cuda.Event(enable_timing=True); sample_end_evt.record() curr_step_state = WorkerStepState(..., sample_end_evt=sample_end_evt) # 4. Prefill steps post-process immediately; ZOS decode steps defer to next step if is_prompt or not envs.DEEPINFER_ZERO_OVERHEAD_SCHEDULING: request_outputs = np.concatenate([request_outputs, self._post_process_step(curr_step_state)]) self.prev_step_state = None else: self.prev_step_state = curr_step_state # ← the whole trick
schedule_mtp_varlen.py) redistribute that budget across sequences using the
confidence scores that are already on the device. Elegant, and the reason
compute_seqlens_k_zos and gather_input_ids_zos exist.
A guard that tells you how serious they are
Any accidental CPU↔GPU synchronisation inside the forward pass would stall the pipeline and silently cost ~30% of throughput. So the forward and sampling launches are wrapped in a context manager that raises if torch detects a sync:
with ( cpu_timer.record("launch_forward", record_name=f"launch_forward_N{num_reqs}_B{total_q}"), cuda_timer.record("device_forward", scale=1), forbid_cuda_sync(), # ← raises on any blocking copy / synchronize() ): if run_cuda_graph: model_output = self.cuda_graph_runner.replay(cuda_graph_used) else: model_output = self.model(kv_caches=kv_caches, input_metadata=input_metadata)
There is a whole test file for this —
tests/test_cuda_sync_guards.py — and an escape hatch, DEEPINFER_DISABLE_SYNC_GUARD=1, for
when you need to add a debug print inside the forward.
06 Every concept, explained
Every name the repo and the blogs drop, in plain language, with where it lives in the code. Use the filters or the top search box to narrow this list.
No concept matches that search.
Vendored kernel libraries (the vendor/ submodules)
These are separate DeepSeek repositories
pulled in as git submodules and compiled by scripts/build_vendor.sh. They are where the raw speed comes
from. Each one is a general-purpose library that happens to be tuned for V4.1.
FlashMLAvendorThe attention kernels
FlashMLA is DeepSeek's library of optimised attention kernels — the single most expensive operation in a transformer. "MLA" is Multi-head Latent Attention, DeepSeek's attention design that compresses keys and values into a small shared latent vector so the KV cache is a fraction of the usual size. This repository contains token-level sparse attention kernels for both prefill and decoding with FP8/FP4 KV caches, plus a famous fused kernel that performs Q-norm, Q-RoPE, the attention itself, the conjugate O-RoPE and the cast to FP8 all in one GPU launch, avoiding the memory round-trips that separate kernels would pay. On a B200 it reaches up to 1 460 TFLOPS in prefill and 950 TFLOPS in decoding; on the Huawei Ascend 950 the sparse kernels hit 410 TFLOPS in prefill (95% of theoretical peak) and 360 TFLOPS in decode (83%). The September 2026 release dropped support for NVIDIA Hopper and for DeepSeek V3/V3.2/V4.0 and changed the FP8/FP4 KV cache format, so it is deliberately not backward-compatible.
prefill_attn_varlen_sparse() and decode_mla_mqa_sparse(), plus head-count padding to shapes FlashMLA supports.convert_kv_index — turns logical top-k token indices into physical paged-cache slots for FlashMLA.DeepGEMMvendorThe matrix-multiply kernels
DeepGEMM is a unified tensor-core kernel library that collects the main computation primitives of a modern LLM into one CUDA codebase: GEMMs (matrix multiplications) in FP8, FP4 and BF16, fused mixture-of-experts with overlapped communication ("Mega MoE"), the MQA scoring kernel for the sparse "lightning indexer", and HyperConnection (HC) operators. Every kernel is compiled at runtime by DeepJIT, so installing the package needs no CUDA compilation and the kernel can be specialised to the exact shapes you use. It borrows ideas from NVIDIA's CUTLASS and CuTe but deliberately avoids their heavy template machinery, so the library stays small enough to read — DeepSeek positions it as a teaching resource for GPU optimisation as much as a production dependency. Despite the lightweight design it matches or beats expert-tuned libraries, reaching 1 550 TFLOPS on H800, and a fully API-compatible Ascend port exists as DeepGEMM-Ascend.
DeepGEMM-Ascend for NPUs).bf16_gemm_nt, FP8 cast granularity helpers.deep_gemm.set_pdl(True) and use_deterministic_algorithms() for batch-invariant mode.DeepEPvendorThe expert-parallel communication library
DeepEP ("DeepEveryParallel") is the communication library that makes mixture-of-experts work across many GPUs. In an MoE model each token is routed to a handful of experts, and those experts live on different GPUs, so every layer requires an all-to-all shuffle: send each token to the GPUs holding its experts (dispatch), then send the results back (combine). DeepEP provides high-throughput, low-latency GPU kernels for exactly that, including FP8 dispatch to halve the bytes on the wire, plus NVLink exchange of weights and gradients for redundant experts — which is precisely what EPLB needs when it shuffles hot experts between GPUs. V2 added a lightweight NCCL "Gin" backend, analytical SM and queue-pair sizing (no more auto-tuning), and experimental primitives for pipeline, context and data parallelism plus remote memory access for Engram. The Ascend port keeps the same buffer API and reports 313–375 GB/s dispatch across EP8–EP128 on Ascend 950DT.
DeepEP-Ascend).EP_AVOID_RECORD_STREAM=1, NCCL tuning, and the JIT cache dirs for deep_ep / deep_gemm / tilelang.DeepSelectvendorA very fast top-k
DeepSelect does one job: find the k largest values in a list, extremely fast. That sounds trivial until you
realise DeepInfer does it twice per token at enormous scale — once in the sparse "lightning indexer" to pick the
512 most relevant past blocks to attend to, and once in the sampler to restrict softmax to the top few thousand of
a 129 280-token vocabulary. A naïve torch.topk is memory-bound and badly tuned for these shapes, so
DeepSelect specialises for exactly two cases — bfloat16 indexer workloads with topk ≤ 4 096 (optimised around
topk=512), and float32 sampling over a ~128K vocabulary — and achieves 2× to 20× speedups over
torch.topk. It supports both CUDA and Ascend, though only the CUDA path takes float32 input. Because
top-k does no floating-point arithmetic, the library is benchmarked in effective memory bandwidth rather than
FLOPS.
build_vendor.sh.torch.topk fallback.TileLang & TileKernelsvendorThe portable kernel language
TileLang is a domain-specific language for writing GPU kernels in Python-like syntax that compiles to multiple
hardware backends — which is how DeepInfer supports both NVIDIA GPUs and Huawei NPUs without two separate
hand-written kernel sets. TileKernels (installed from PyPI as tile-kernels, not a submodule) is
DeepSeek's library of dozens of pre-written TileLang kernels for MoE routing, Engram gating, FP8/FP4 quantisation
and manifold hyper-connection operators, most of which run near the hardware's compute or bandwidth ceiling.
Separately, DeepInfer ships about 45 of its own TileLang kernels in
deepinfer/model_executor/layers/tilelang_ops/, each written twice — once under cuda/ and
once under asc/ — with a dispatcher that picks the backend at import time. These in-tree kernels cover
the engine-specific glue: building KV indices, loading paged caches, RMSNorm, the fused sampler, and the GPU-side
draft-length scheduler.
_pkg = importlib.import_module(f"{__package__}.{PLATFORM.tilelang_ops_backend}") # "cuda" or "asc" for _info in pkgutil.iter_modules(_pkg.__path__): _mod = importlib.import_module(f"{_pkg.__name__}.{_info.name}") for _name, _obj in vars(_mod).items(): if _name.endswith("_tl"): # every kernel factory ends in _tl globals()[_name] = _obj
KernelUnavailableError so tests skip kernels a backend lacks.tile-kernels==2.1.0.deepseek-recipesibling repoProtocol & tokenisation, open-sourced alongside
DeepInfer takes token IDs in and gives token IDs out; it knows nothing about chat formats. deepseek-recipe
is the separate MIT-licensed Rust library (with Python bindings) that fills that gap: it converts Chat Completions,
Responses and Anthropic Messages requests into one internal Conversation format, renders that into the exact prompt
string V4/V4.1 expects, encodes it to token IDs, and parses the model's output stream back into protocol responses
with reasoning content, tool calls and stop sequences. Two details are genuinely clever. First, special-token
injection defence: when building the prompt it records exactly where it inserted control tokens like
<|User|>, so if a user literally types that string it is encoded as ordinary text and cannot
hijack the conversation. Second, segmented parallel tokenisation: because the special-token boundaries are
already known, the ordinary text between them can be BPE-encoded independently and in parallel, then concatenated —
which measurably cuts tokenisation latency on very long prompts. Its behaviour is aligned with the official
DeepSeek API, so a third-party deployment behaves identically.
max_tokens of 8 192 outside thinking mode, 65 536 in
thinking mode, 131 072 in thinking mode at max effort.DeepSpecsibling repoWhere draft models come from
DeepInfer runs a speculative draft model; DeepSpec is the separate MIT-licensed codebase for training and evaluating one. It is a full stack: data preparation (download prompts, regenerate target-model answers, build a target cache — which can run to tens of terabytes), training code for several draft algorithms including DSpark, DFlash and Eagle3, and an evaluation harness that measures acceptance rate on GSM8K, MATH-500, AIME-25, HumanEval, MBPP, LiveCodeBench, MT-Bench, Alpaca and Arena-Hard. Released checkpoints target Qwen3-4B/8B/14B and Gemma-4-12B rather than V4.1, so it is the research vehicle, not the production artefact. You only need it if you want to train your own draft head; DeepInfer uses the DFlash head already baked into the V4.1 checkpoint.
DFlash module and its ConfidenceHead.3FS (Fire-Flyer File System)infraWhere the KV cache actually lives
3FS is DeepSeek's own distributed file system, built for AI workloads on modern SSDs and RDMA networks. It matters here because it is the storage layer underneath the production KV cache: roughly 100 PiB of SSD holding the attention keys and values of past requests so that a repeated prompt prefix never has to be prefilled twice. Data is stored with 10+2 erasure coding, meaning each group tolerates two simultaneous node failures, and a read fires requests to all 11 nodes in the group and decodes from whichever 10 respond first — latency insurance by design. A GC worker on each storage node evicts least-recently-used cache under a watermark, and DeepSeek sets a TTL of 3 days for agent traffic and 7 days for chatbot traffic (chatbot has a heavier long tail of repeat hits, and dropping to ~3 days costs it 0.5–1% of hit rate). Published 3FS cluster figures reach ~6.6 TiB/s aggregate read on 180 storage nodes.
Model architecture (what V4.1-Flash actually is)
DeepInfer supports exactly one model, so the engine and the architecture are co-designed. These concepts explain the shapes the code keeps talking about.
MLA — Multi-head Latent AttentionmodelWhy the KV cache is small
In ordinary attention, every layer stores a full key and value vector for every token and every head; for a
128k-token context that cache becomes larger than the model weights. MLA instead projects keys and values down into
a single shared low-rank "latent" vector per token, caches only that, and projects back up on demand — so the cache
shrinks by roughly an order of magnitude while quality is preserved. In the code this shows up as a chain of
low-rank projections: an A matrix down, an RMSNorm, then a B matrix up, with separate
handling for the RoPE (rotary position) part of the key that cannot be compressed. DeepInfer fuses the up-projection
of values with the output projection (_init_wv_o_proj) so the two never materialise separately. The
V4.1 variant adds gating and a second compression stage on top, which is why you see kv_lora_rank,
qk_rope_head_dim and compress_dim all over the cache-engine code.
ColumnParallelLora (the A→norm→B chain), SoftmaxAPEGatedColumnParallelLora, MQAIndexer.Compressor — gated low-rank compression; returns pre-RoPE latent for the indexer, writes post-RoPE into the cache.MoE — Mixture of ExpertsmodelWhy a huge model is cheap to run
Instead of one giant feed-forward network per layer, an MoE layer holds hundreds of smaller "expert" networks
plus a tiny gate that scores them. For each token the gate picks the top few experts (V4.1 uses 384 routed
experts in the benchmark configs), so only a small fraction of the parameters do work on any given token. The model
can therefore be enormous in total parameters while staying cheap per token. DeepInfer's gate uses sqrt-softplus
scoring plus the noaux_tc top-k selection with a load-correction bias — and interestingly keeps
two FP32 biases, one for text and one for image tokens. A "shared expert" runs for every token regardless of
routing. The whole engine's decode strategy is built around the fact that the expert weights, not the activations,
dominate memory traffic.
n_routed_experts) · extra/redundant = a
duplicate copy of a hot expert (n_redundant_experts) · physical = logical + extra, rounded up
to be divisible by ep_size · local = physical ÷ ep_size, what one GPU holds.FusedExperts, TopKGate, ReplicatedSharedExpert — and the expert-counting rules above._configure_moe_redundancy — resolves physical-expert padding before the model is built.DSA — DeepSeek Sparse Attention & the indexermodelAttending to 512 blocks, not 128 000 tokens
Full attention costs grow with the square of context length, which is unaffordable at 128k+. DSA makes attention
sparse: a cheap scoring network — the "lightning indexer" — estimates how relevant each past block of tokens
is to the current query, and then real attention runs only over the top few hundred blocks. The indexer is a
multi-query attention (MQA) pass computing relu(q @ k) * weights logits, deliberately tiny compared to
the main attention, and the selection uses DeepSelect's fast top-k. DeepInfer adds two refinements: candidate
blocks, where one designated layer produces a restricted candidate set that later layers select within, and
cross-layer index sharing, where consecutive layers reuse the same top-k or the same index keys instead of
recomputing them. These sharing relationships are derived from the model config and validated fail-fast at
startup.
MQA, the indexer runtime, prefill/decode metadata, candidate-block restriction.SharedLayerTopology — the share-kv / share-index / share-index-k rules, with a long docstring on the validation.candidate_block_max and index restoration kernels.SWA — Sliding Window AttentionmodelSome layers only look at the last 128 tokens
Not every layer needs to see the whole context. In V4.1 a set of layers use sliding-window attention: each query
attends only to a fixed window of recent keys — 128 tokens — which makes their cost constant regardless of how long
the conversation is. The engine stores that window in a ring buffer per sequence rather than in the paged
block cache, since it is small, fixed-size and constantly overwritten. In the code the SWA cache lives inside a
StateBlock, one fixed-size slot per sequence, and a dedicated kernel
(get_kv_swa_index_decode) maps logical positions to ring slots. The interaction between SWA and prefix
caching is the single trickiest part of the whole system, and is what SWA bounded replay exists to solve.
StateBlock — the per-sequence layout, with a long docstring on stored vs. dropped regions.expand_swa_index — turns two KV segments per token into dense prefill indices.select_state_slice / load_state_to_cache — reading and writing the circular window.YoCo & the CED splitmodelHalf the layers don't need the full prompt
YoCo ("You Only Cache Once") splits the model into a producer half and a consumer half. The first
n_prefill_layer layers compute real KV caches from the prompt; the layers after that — the "consumer"
or decoder layers — derive their block KV from the last producer layer's hidden state instead of computing it
themselves. In DeepSeek's CED (compress-encode-decode) architecture this means prefill only has to run the encoder
layers fully, and the decoder layers' only per-layer state is their sliding window. DeepInfer exposes two truncation
modes: mask keeps every query row but narrows what the SWA can see, and compact actually
removes the truncated query rows so the consumer layers do less work. The two are numerically equivalent inside the
retained region, and examples/yoco/yoco_equiv.py exists purely to prove that — comparing SWA pivots and
visible keys cheaply (L1), or comparing logprobs element-by-element across 8 GPUs (L2).
DEEPINFER_YOCO_PREFILL_TRUNCATION_MODE = EnvVar.choice(default="off", choices=("off", "mask", "compact")) DEEPINFER_YOCO_PREFILL_KEEP_TOKENS = EnvVar.int() # Constraint enforced at startup: # KEEP_TOKENS >= window_size + DEEPINFER_MAX_BACKTRACKING_TOKENS # V4.1: minimum is 134 = 128 (SWA window) + 6 (backtracking, = nextn + 1). # The README's recommended value is 140, i.e. the minimum plus a little headroom.
EngrammodelA giant lookup table bolted onto the model
Engram is an external memory subsystem: alongside the normal transformer weights, the model carries enormous embedding tables indexed by n-gram hashes of the recent tokens. At certain layers the engine hashes the last N tokens, looks up the corresponding rows, projects them through a shared KV projection, and gates the result into the hidden state — effectively giving the model a vast, cheap associative memory that is read rather than computed. Because the tables are far too big for one GPU they are sharded across ranks and gathered by hash at runtime (DeepEP provides the remote-memory primitive). Engram also brings a compressed tokenizer: raw token IDs are mapped into a smaller compressed vocabulary before hashing, so the tables stay a manageable size. One independent write-up describes V4.1-Flash as a 552B backbone plus 196B Engram parameters, which gives a sense of the scale involved.
CompressedVocabulary, EngramLayout, NgramHashMapping._load_engram_tables — reads this rank's rows in chunks, restoring UE8M0 scales losslessly.mHC — manifold HyperConnectionmodelThe residual stream, multiplied
A standard transformer has one residual stream: each sub-layer reads it, computes something, and adds the result
back. mHC replaces that single stream with hc_mult parallel streams — a "trunk" of shape
[tokens, hc_mult, hidden]. Before each sub-layer the trunk is mixed into that sub-layer's input
with learned coefficients, and afterwards the output is mixed back into the trunk. The mixing coefficients are
normalised with a Sinkhorn iteration, which keeps the mixture well-conditioned. V4.1 also uses a "shifted" variant
where the mix is offset by half a layer, so each sub-layer applies the coefficients computed by the previous one and
the final mix-down happens at the end of the model instead of in a separate head. DeepInfer implements this with a
lazy data structure (HCLazyPostData) so the post-mix of one sub-layer and the pre-mix of the next are
fused into a single kernel call.
HCLayerMixin (the pre/post mixing pair) and HCHead.sinkhorn_normalize.Vision encoder (VE) & multimodalmodelHow images get in
V4.1-Flash accepts interleaved images, so DeepInfer carries a full vision path. An image is resized to a
patch-aligned "anyres" geometry on the CPU by worker processes (off the step critical path), uploaded as raw RGB,
padded into patches on the GPU, run through a Vision Transformer, spatially downsampled, and projected by an MLP
"aligner" into the language model's embedding space — where the resulting vectors replace the image placeholder
tokens in the prompt. The token layout is strict: [IMAGE_START] then, per row,
n_llm_w image tokens plus a newline token, then [IMAGE_END]. The scheduler must never cut
a prefill chunk in the middle of an image, which is why scheduler.hpp carries image-boundary logic.
In production the vision encoder runs on its own single-GPU instances behind their own load balancer.
rgb_pad_to_patches, replace_input_embeds, batch_downsample.FP8, FP4, UE8M0 — low-precision formatsmodelFour bits per weight, and the scales that make it work
Most of V4.1 does not run in 16-bit. Weights and caches are stored in 8-bit (E4M3) or 4-bit formats, with a shared scaling factor per small group of values to preserve dynamic range — this is what "per-16" or "per-32" means in the Q&A. UE8M0 is a scale format with 8 exponent bits and no mantissa, i.e. the scale is always a power of two, which makes applying it a cheap exponent add rather than a multiply; four of them pack into one int32. MXFP4 is the 4-bit format with per-32 UE8M0 scales. DeepSeek's exact production choices are documented: the main KV cache uses per-16 E4M3 (like NVFP4 but without the global scale), the indexer KV uses per-32 UE8M0 MXFP4, and the MoE routed-expert weights also use per-32 UE8M0 MXFP4. SWA KV stores 528 bytes per token — 512 bytes of FP8 data plus 16 bytes of UE8M0 scale.
KVCacheFormat, ScaleFactorFormat, TMA-aligned scale layouts.quant.py).set_ue8m0_sf — packs scales for ordinary 2-D E4M3 parameters at load time.derive_main_kv_cache_num_per_channels — the per-16 vs per-32 rule in code.Engine internals
The machinery that decides what runs, where memory goes, and how the GPU is kept busy. This is the part that is genuinely DeepInfer rather than the model.
Paged KV cache, block tables, slot mappingengineVirtual memory for attention
A sequence's KV cache grows one token at a time and you don't know how long it will get, so reserving a contiguous slab per request wastes enormous amounts of memory. The paged approach — borrowed from operating-system virtual memory — chops GPU memory into fixed-size blocks (V4.1 uses 128 tokens per block) and gives each sequence a block table: a list saying "my tokens 0–127 are in physical block 41, tokens 128–255 in block 9…". Blocks can then be allocated and freed individually with no fragmentation, and two sequences sharing a prompt prefix can point at the same physical blocks. Slot mapping is the companion idea: a flat array telling the store kernel exactly which physical slot each newly-computed token's KV should be written into. In DeepInfer the free list is a C++ intrusive doubly-linked list with reference counts, so allocate and free are O(1) and block 0 is reserved as a null/padding sentinel.
enum class AllocStatus { OK, LATER, NEVER }; // can we fit this sequence now, later, or never? struct Blocks { // NOTE: GPU block 0 is unused, so 0 means "empty" Blocks(int _n) : _blocks(_n), _head(1), _tail(_n - 1), _size(_n - 1) { TORCH_CHECK(_n > 1, "GPU blocks too few: ", _n); for (int i = 1; i < _n; ++i) { _blocks[i].prev = i - 1; _blocks[i].next = i + 1; } _blocks[_n - 1].next = 0; } int _pop_front() { int r = _head; _erase(_head); return r; } // O(1) allocate int num_free_blocks() { return _size; } };
can_allocate, fork/clone support.The state cache & StateBlockengineThe second, separate cache
DeepInfer keeps two caches. The paged block cache holds the compressed main KV, which grows with context
length. The state cache holds everything that is fixed-size per sequence: the sliding-window KV ring buffers
and the compression-boundary accumulators. Each sequence gets one StateBlock slot, and that slot is
deliberately partitioned into a stored region (the SWA layers that must survive across requests — the first
20 layers in V4.1) followed by a dropped region (the YoCo consumer SWA, recoverable by recomputation, plus
compression state that has no cross-boundary dependency). The split exists so that persisting a sequence to disk is
just "write the first state_size_to_disk bytes" — a length truncation, not a gather. The number in the
Q&A: 20 layers × 134 tokens × 528 bytes = 1 415 040 bytes per sequence.
# Layout, low address → high: # [ stored region: SWA ] # [ dropped region: YOCO consumer SWA | compression state ] # # SWA partitioning depends only on n_prefill_layer / n_layer, never on the # truncation mode. Consumer SWA recovery relies on prefill recomputation; # compression state has no dependency across the persistence boundary. self.state_size_to_disk = self.swa_store_total_bytes_inference_pad
KVCache, StateBlock, KVCacheManager, GPUCacheInterface, CacheEngine.SWA bounded replayengineThe trick that makes prefix caching work with sliding windows
Here is the problem. Prefix caching lets you skip prefill when someone sends a prompt you've already seen — but
only if you saved all the state, and the sliding-window KV is expensive to persist (it is per-layer and
constantly rewritten). If you don't save it, a cache hit still forces you to recompute the window exactly, which for
V4.1's decoder would mean pushing the last 2 560 tokens through 20 layers. Bounded replay says: don't rebuild it
exactly — rebuild it approximately, by replaying only the last 128 tokens and truncating the attention window
to within the replayed segment. Replaying from position s, the query at position i sees
only keys in [max(s, i−127), i]. The cost becomes a constant, independent of context length and layer
count. DeepSeek reports the quality loss as negligible, and the win is large: because misses are now cheap, the
persistent cache can store only the global KV and skip SWA KV entirely, shrinking the stored footprint
dramatically.
get_yoco_store_extra_tokens, get_kvstore_swa_block_size.ZOS — zero-overhead / asynchronous schedulingengineNever let the GPU wait for Python
A naïve decode step is schedule(N) → forward(N) → sample(N) → post-process(N). Two of those four
stages are pure CPU work, and you need a GPU→CPU synchronisation before post-processing to learn which token was
sampled. So the GPU sits idle during post-process(N) and schedule(N+1) — a bubble on every single token. Asynchronous
scheduling removes it by pipelining: while the GPU runs forward(N) and sample(N), the CPU concurrently runs
schedule(N+1), launches forward(N+1), post-processes step N−1, and launches sample(N+1). The GPU queue never drains.
DeepSeek credits SGLang and NanoFlow for the idea and enables it by default
(DEEPINFER_ZERO_OVERHEAD_SCHEDULING, on for NORMAL and COMPLETION engines).
prepare_zos_inputs
builds a remap array matching this batch's rows to the previous batch's, and kernels
(compute_seqlens_k_zos, gather_input_ids_zos) correct the lengths and input IDs in place.
Three whole kernels exist only because of this optimisation.WorkerStepState and MetadataZOS — the state carried one step forward.CUDA graphsengineRecord the step once, replay it forever
A decode step launches thousands of tiny kernels, and at a few microseconds of CPU overhead each, the launching
alone can exceed the compute. A CUDA graph captures the entire sequence of launches once and replays it as a single
driver call. The catch is that a graph is frozen: fixed shapes, fixed memory addresses. DeepInfer therefore captures
one graph per batch size on a ladder (default step of 8, via
DEEPINFER_CUDAGRAPH_BATCH_SIZE_INTERVAL), pads the real batch up to the next captured size, and keeps
stable input buffers that it overwrites each step. There is a second, separate runner for the DFlash draft model.
During startup the engine also times every captured graph to build a decode performance curve — (batch size,
milliseconds) pairs — which is cached to disk and later fed to the speculative-decoding scheduler so it can predict
how long a candidate batch size would take.
select(total_q), replay, and the batch-size ladder.profile_decode_perf — warmup, 5 timed replays, trimmed mean, all-reduced across the EP group.The scheduler: batching, chunking, preemptionengine1 329 lines of C++ deciding what runs next
Every iteration, the scheduler answers one question: what goes on the GPU this step? It maintains
waiting (not started), prompted (prefilled, awaiting decode) and running
queues, and a step is homogeneous — either a prefill step or a decode step, never both. Key behaviours:
continuous batching, where finished sequences leave and new ones join every single iteration rather than
waiting for a whole batch to complete; chunked prefill, slicing a 128k prompt into pieces so it cannot
monopolise the engine (with careful logic to never cut inside an image); and preemption, where if KV blocks
run out the scheduler evicts sequences — choosing victims by shortest output length (LCFS, last-come-first-served)
so the most-invested requests survive. A NORMAL engine running both phases must stay in lockstep with its
data-parallel peers, which it achieves by voting.
// Two kinds of attempt point: // ALL point (every prefill_attempt_interval = 70 decode steps): // only prefill if EVERY rank has prefill work — otherwise one rank works // while the others run an empty prompt step. // ANY point (every prefill_any_attempt_interval = 100 decode steps): // prefill if ANY rank has work — a starvation backstop for an isolated rank. bool is_any_point = (decode_step != 0) && (decode_step % prefill_any_attempt_interval == 0); bool is_all_point = (decode_step % prefill_attempt_interval == 0); if (!is_any_point && !is_all_point) return _schedule_decode(acquire); bool want = (get_gpu_cache_usage() <= GPU_CACHE_PREFILL_WATERMARK) && _has_prefill_waiting(); bool do_prefill = is_any_point ? (_dp_allreduce(want ? 0 : 1, ReduceOp::MIN) == 0) // ANY : (_dp_allreduce(want ? 1 : 0, ReduceOp::MIN) == 1); // ALL // Second vote: "want" only checked that the queue was non-empty — _schedule_prefill // can still come up empty (no slots, no KV blocks). If EVERY rank came up empty we // must fall back to decode, or the group spins forever re-voting and re-failing.
_schedule, _schedule_prefill, _schedule_decode, preemption, chunk sizing, image boundaries.Sequence, SequenceMetadata, MtpState, status enum, stopping criteria.NORMAL (both phases) / PROMPT (prefill only) / COMPLETION (decode only) — the hooks for P/D disaggregation.MTP, DFlash, DSpark & the confidence headengineGuess ahead, verify in bulk
Three names for layers of the same idea. MTP (multi-token prediction) is the general technique: a small draft model proposes the next few tokens, and the big model verifies all of them in one forward pass, so a single expensive memory read can yield several accepted tokens instead of one. DFlash is DeepSeek's specific draft implementation, a lightweight head that shares the main model's KV and runs inside its own CUDA graph. DSpark is the scheduling layer on top, and it is the genuinely novel bit: a confidence head predicts the acceptance probability of each individual draft token, and the scheduler then allocates verification compute only to the tokens with the best expected return. Verification is therefore variable-length — a request whose drafts look doubtful gets fewer slots this step. Acceptance correctness is preserved by standard rejection sampling against the target distribution. Full detail and the measured gains are in section 10.
DFlash module and ConfidenceHead.The samplerengineTurning 129 280 numbers into one token
The model outputs a score for every token in the vocabulary; the sampler picks one. In DeepInfer that is far
more than a softmax. The pipeline is: gather the hidden states that need sampling → matrix-multiply against the
embedding to get logits → apply the structured-output bitmask → apply temperature, top-k and top-p → sample → and,
if speculation is on, verify the previous round's drafts by comparing their logprobs under the old and new
distributions, accept a prefix, resample at the first rejection from the residual distribution, then run the draft
head to propose the next round. A notable optimisation is force_topk: rather than softmaxing 129 280
values, restrict to the top few thousand first (default 4 096 in the offline example) and maintain an index
mapping. There is also golden_token_ids for teacher-forcing, used by the evaluation and equivalence
harnesses.
alpha = exp(-5e-5) to the draft probability to guarantee progress, and
tilelang_ops/common.py contains a half-page proof bounding the resulting distribution error to
within (1 ± eps) of the true distribution. That level of rigour in a comment is unusual.
(seq_seed, position) and independent of batch size or row order.Batch invariance & deterministic samplingengineSame input, same output — always
Normally, running the same prompt twice on the same GPU can give different results, because floating-point
addition is not associative and batching changes the order in which partial sums are reduced. Who else happened to
be in your batch silently changes your logits in the last decimal places, which occasionally flips a token. For
evaluation, auditing, debugging and reproducible agent runs this is maddening. Batch-invariant inference
(DEEPINFER_BATCH_INVARIANT=1) forces deterministic reduction orders in the GEMM and attention kernels
so results do not depend on batch composition. Deterministic sampling builds on it: with batch invariance on,
variable-length MTP off (DEEPINFER_ENABLE_MTP_VARLEN=0) and a fixed seq_seed in
SamplingParams, identical input tokens and parameters produce byte-identical output. Both are NVIDIA-only
in this release.
if envs.DEEPINFER_BATCH_INVARIANT or PLATFORM.is_npu: deep_gemm.use_deterministic_algorithms(True) tile_kernels.use_deterministic_algorithms(True)
seq_seed — a uint64 per sequence, randomised unless you pin it.Structured output (SO)engineForcing valid JSON, by construction
Structured output constrains generation so the result cannot violate a format — a regex, a JSON Schema, or
an EBNF grammar. The format is compiled into a grammar automaton (DeepInfer uses xgrammar); at each decode step the
automaton is advanced by the token just emitted, the set of legal next tokens is computed, and a bitmask is applied
to the logits so illegal tokens get −∞ before sampling. Compilation is expensive and inference is latency-critical,
so the two are decoupled: compilation runs asynchronously on a thread pool pinned to idle CPUs (in production, on a
dedicated machine) and a request fetches its compiled matcher before entering the engine. DeepInfer adds two extras
beyond the basics: trigger_token_ids, a token sequence that must appear before the grammar activates
(so the model can think freely first), and bound_token_id, which stops free generation and forces the
trigger sequence through.
cases = [
("…write a long story…", {"schema_type": "regex", "schema": r"[A-Z]{2}-[0-9]{3}"}),
("…ramble in natural language, no JSON…",
{"schema_type": "json", "schema": json.dumps(json_schema)}),
("…write a long poem…", {"schema_type": "ebnf",
"schema": 'root ::= "answer=" ("yes" | "no") ";level=" [1-3]'}),
]
outputs = llm.generate(prompts=[p for p, _ in cases],
sampling_params=[SamplingParams(temperature=0, max_tokens=128, so_args=so) for _, so in cases])
# The prompts deliberately ask for the OPPOSITE of the schema — if the output still
# matches the regex/schema/grammar, the constraint is genuinely binding.apply_token_bitmask_by_step_inplace — the mask applied to logits.Object pools & deferred releaseengineWhy the GC is switched off
At 200 decode steps per second with 60 sequences each, allocating a Python object per token per request would
bury you in garbage collection pauses — and a GC pause at the wrong moment stalls the GPU. DeepInfer's answer is
blunt: the worker calls gc.disable() at startup and runs a manual collection every
DEEPINFER_GC_INTERVAL_STEP (default 1 000, ×10 for completion engines). Everything hot is pooled —
SequenceMetadataPool, RequestOutputPool, MTPInfoPool — and the outputs handed
back to Python are raw uint64 pointers into C++ storage rather than objects. Because those pointers are
recycled on the next step, there is a DeferredRelease class that holds resources for exactly one extra
round so the caller is guaranteed to have read them, and releases them during the next step's
pre_sampler_sync — i.e. overlapped with GPU compute.
llm.generate() calls RequestOutput.clone(raw_output):
if you keep a RequestOutput past the current step without cloning it, you are reading recycled memory.
The docstring on LLMEngine.step() says so explicitly.RequestOutput and RequestOutputPool.DeferredRelease — collect now, release during the next pre-sampler sync.Timers, profiling & MFUengineHow they know where the time goes
DeepInfer ships three timing systems. CPUTimer records wall time around named scopes. CUDATimer
records GPU time with event pairs. And CUDAGraphTimer solves a subtle problem: once a step is captured
in a CUDA graph you can't insert timing calls, so it embeds event-record nodes into the graph itself and
ping-pongs between two event buffers so that reading last replay's timing never stalls this replay. On top of this
sits a StatsLogger with sliding windows that periodically prints throughput, cache usage, preemptions,
MoE load balance and MFU — model FLOPs utilisation, computed from the C++ FlopsEstimator, which
deliberately excludes draft-model FLOPs because DFlash is extra compute spent to accelerate the target model, not
useful work in itself. There is even an optional HTTP endpoint per engine process for capturing a torch profiler
trace on demand.
DEEPINFER_PROFILE_HTTP_ENABLE=1).Parallelism & infrastructure
EP, DP, CP — and why not TPparallelismFour ways to split work, three of them used
EP (expert parallelism) splits the MoE experts across GPUs; each GPU holds a slice of the experts and
tokens are shuffled to wherever their experts live. DP (data parallelism) runs independent replicas, each
handling different requests — in V4.1 the attention part is data-parallel with #DP = #EP.
CP (context parallelism) splits a single long sequence across GPUs, which helps latency on very long
prompts; DeepInfer implements a "mirror CP" variant with its own index-preparation kernels. TP (tensor
parallelism), the classic approach of splitting each weight matrix, is not used in DeepSeek's production
deployment at all. The Q&A's recommended settings: prefill uses EP2–8/DP2–8 (the smallest that HBM allows);
decode uses EP64–128/DP64–128, the largest the GPUs allow — this is what "Wide-EP" means.
CPManager — all-gather sessions with reused buffers.ParallelConfig — the divisibility assertions that relate world size, DP, CP and EP.EPLB — expert-parallel load balancingparallelismBecause some experts are more popular
MoE routing is not uniform: certain experts attract far more tokens than others. Since every GPU must finish
before the layer completes, one overloaded GPU sets the pace for all 64 — and the imbalance is dynamic, drifting
with the traffic mix. EPLB fixes this with two mechanisms. First, each GPU holds a few redundant experts:
extra copies of hot experts, so their load can be split. Second, the engine counts tokens per expert over a sliding
window and, every N steps, recomputes the whole placement and physically shuffles expert weights between GPUs
over NVLink symmetric memory. DeepSeek's measured results use moe_lb — the slowest rank's load divided
by the mean, where 1.0 is perfect:
| Stage | Redundant experts/GPU | Stats window | Reassign every | Measured moe_lb |
|---|---|---|---|---|
| Prefill | 4 (start) | 100 steps | 1 000 steps | 1.074 (EP4DP4) / 1.045 (EP2DP2) |
| Decode | 1 (start) | 1 000 steps | 10 000 steps | 1.090 |
The counts are rounded up so total experts divide evenly by ep_size — at
EP64 that works out to 7 experts per GPU after rounding, excluding the shared expert.
ExpertLoadBalancer, per-layer state, the manager, resolve_num_extra_experts.rebalance_experts_global — assign replicas to hot experts, then greedily balance all replicas across ranks.tests/distributed/test_expert_shuffle.py tests the migration.The four load balancersnot shippedDocumented in detail, absent from the code
Production runs four separate balancers, and the Q&A describes each precisely even though none is in the
release. The Prefill LB buckets requests into four context-length tiers (<16K, 16–64K, 64–256K, ≥256K) and
redistributes engines between tiers based on last round's demand; within a GPU it doesn't use a fixed chunk size but
solves a fitted quadratic cost model cost(q, c) for the largest chunk q that exactly
consumes the step's time budget, capped at 32 768 tokens. The Decode LB balances two things at once: request
count (which sets batch size) and total KV length (which sets attention cost), so it heuristically sends short
requests to GPUs with low concurrency but long KV, and long requests to GPUs with short KV. The Vision Encoder
LB routes at machine granularity, picking the GPU currently processing the fewest image patches, computed
directly from image dimensions. The Expert-Parallel LB is EPLB, above.
Prefix caching & the three-tier KV hierarchynot shippedThe biggest cost saving of all
If a user sends a 100k-token document and asks five questions about it, prefilling it five times is pure waste. Prefix caching keeps the KV from the first pass and reuses it. DeepSeek runs a three-tier hierarchy: in-flight KV in GPU HBM; the SWA KV in local host memory with an in-memory directory sharded across entrypoint machines, so a machine that doesn't hold it can pull it over a 400G VLAN; and the persistent paged block KV on the dedicated 3FS cluster. Hiding the cost of a hit is its own engineering problem: matching is a hash-chain prefix match on metadata only but can't be hidden because the hit length determines routing; fetching from 3FS into host shared memory runs in a separate process; and swap-in from host to GPU runs in a thread, overlapping per-layer host-to-device transfers with the forward pass — using GDR/RDMA rather than the CUDA copy engine, because the copy engine performs poorly on the fine-grained block copies and RDMA saturates PCIe without consuming SMs.
select_state_slice_to_store / load_state_from_store_to_prefill_cache — the engine-side hooks a KV store would call.Autoscaling & fault toleranceoperationsWhat happens when a GPU dies
In a Wide-EP deployment all GPUs of an instance form one communication domain, so a single failed GPU makes the whole instance unusable — a brutal failure mode at EP64. DeepSeek's answer is not to make restarts fast (they take minutes) but to keep redundancy: ready machines held unoccupied as scale-out inventory, and a rule forbidding scale-in whenever quota utilisation is above 0.8. On restart the platform re-derives the EP size from whatever GPUs are currently healthy, so the instance adapts to the GPUs it actually receives. There is also a neat detail for long-tail requests: when a decode engine gets a shutdown notice it enters a one-minute grace period and forwards any still-running requests — along with their KV cache — to other engines in one shot, rather than killing them or waiting for the longest generation to finish. Scaling signals are simple: prefill sizes itself from the number of non-cached tokens, decode from the number of running requests, both rate-limited and bounded.
Pareto curves & the metrics that mattermeasurementWhy there is no single "speed" number
Serving performance is a trade-off curve, not a number. Increase the batch size and you serve more tokens per
second per GPU (good for your costs) but each individual user waits longer between tokens (bad for them). A Pareto
chart plots both axes so you can pick the operating point your service level agreement allows — which is why
DeepSeek published curves rather than headline numbers. The metrics: TTFT (time to first token) is dominated
by prefill; TBT/TPOT (time between output tokens) is dominated by decode; throughput is
tokens/s/GPU. They are not interchangeable, and comparing one project's tokens/s/user against another's
tokens/s/GPU is meaningless. DeepInfer ships the harness to generate these curves yourself:
examples/sweep/run_sweep.sh sweeps one dimension and plot_pareto.py joins the points.
07 The kernel stack
Everything above this layer is bookkeeping. This is where the arithmetic actually happens, and where the performance claims come from.
Who does what
| Library | Delivered as | Owns | Headline performance |
|---|---|---|---|
| FlashMLA | submodule | Attention: sparse prefill, sparse decode with FP8/FP4 KV, the fused norm→RoPE→attn→RoPE→cast kernel, dense MHA | 1 460 TFLOPS prefill / 950 TFLOPS decode on B200 (fused kernel); 410 / 360 TFLOPS on Ascend 950 (95% / 83% of peak) |
| DeepGEMM | submodule | All matrix multiplies: FP8/FP4/BF16 GEMM, Mega MoE with overlapped comms, MQA indexer scoring, HC operators | Up to 1 550 TFLOPS on H800; runtime-compiled by DeepJIT, so no CUDA build at install |
| DeepEP | submodule | MoE all-to-all dispatch/combine (incl. FP8 dispatch), NVLink weight exchange for redundant experts, experimental PP/CP/DP collectives | Ascend: 313–375 GB/s dispatch from EP8 to EP128; ~90–95% of physical payload bandwidth up to EP32 |
| DeepSelect | submodule | Top-k for the sparse indexer (bf16, topk≈512) and for the sampler (fp32, ~128K vocab) | 2×–20× faster than torch.topk; measured in effective bandwidth, since top-k does no FLOPs |
| TileKernels | pip (tile-kernels) | MoE routing, Engram gating, FP8/FP4 quantisation, manifold hyper-connection, RoPE, rand | "Most kernels close to the hardware's compute or bandwidth limits" |
| tilelang_ops/ | in this repo | Engine glue: KV index building, paged cache load/store, norms, the fused sampler, decode metadata, ZOS corrections | ~45 kernels, each written twice (cuda/ and asc/), dispatched at import |
The in-tree kernel catalogue
These live in deepinfer/model_executor/layers/tilelang_ops/. Each name is a module with a Python
reference implementation, pytest tests and benchmarks, plus a cuda/ and an asc/
implementation of the same signature.
| Group | Kernels | What they do |
|---|---|---|
| KV indexing | get_kv_index, kv_index_cat, swa_index, candidate_blocks, flash_mla_utils | Turn logical token positions into physical cache slots: compressed-attention indices, sliding-window ring slots, candidate block sets, and the conversion FlashMLA expects. |
| Paged cache I/O | load_paged_kv_cache, store_kv_cache, cache_common, state_cache, kv_quant | Read and write the quantised paged cache and the circular state cache, including scale-factor layouts (UE8M0, TMA-aligned). |
| Normalisation & transforms | norm, fused_transform, rstd_1ulp, compress | RMSNorm with fused residual-add and FP8 cast; the big fused norm→RoPE→Hadamard→cast→store kernel; compression state updates. |
| Sampling | fused_sample, sampler, random_sampling, sum_smaller_probs, structure_output | Temperature/top-k/top-p/log-softmax in one pass, draft verification metadata, batch-order-independent random sampling, grammar bitmask application. |
| Speculative decoding | dflash, dflash_copy, store_mtp_info, schedule_mtp_varlen | Fused confidence-head sigmoid, draft input packing for CUDA graphs, MTP pool writes, and the GPU-side draft-length redistribution. |
| ZOS corrections | compute_seqlens_k_zos, gather_input_ids_zos, gather_engram_tokens_zos | Fix the stale CPU view of sequence lengths and input IDs using the previous step's GPU results. |
| Parallelism | prepare_cp_mirror, prepare_v4_decode_metadata, gather | Mirror-CP index preparation, whole-decode metadata computed on the GPU, row gather with zero-fill. |
| Multimodal | image_embed_ops | rgb_pad_to_patches, replace_input_embeds, batch_downsample. |
| Debug | outlier_detection | Queues a CUDA assertion if any value is NaN, infinite, or out of range — fires asynchronously on a later op. |
What a TileLang kernel actually looks like
This one computes, per row, the sum of all probabilities less than or equal to the sampled token's — an inclusive CDF used by the sampler. It shows the whole style: a JIT-decorated factory, dynamic shapes, explicit tiling and a reducer.
@tilelang.jit(out_idx=None, target="cuda", pass_configs={ tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True, tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True}) def sum_smaller_probs_tl(vocab_size: int) -> Any: num_tokens = T.dynamic("num_tokens") # shapes resolved at call time logprobs_stride = T.dynamic("logprobs_stride") threads = 512 block_size = 8192 if vocab_size >= 8192 else tilelang.next_power_of_2(vocab_size) @T.prim_func def _sum_smaller_probs(logprobs, sample_results, output) -> None: with T.Kernel(num_tokens, threads=threads) as bx: # one CTA per token T.pdl_sync() # programmatic dependent launch thread_sum = T.alloc_reducer((1,), T.float32, replication="all") logprob_limit = T.alloc_var(T.float32, logprobs[bx, sample_result]) for i0 in T.serial(n_chunks): T.copy(logprobs[bx, i0 * block_size], logprobs_frag) # global → fragment for j in T.Parallel(block_size): if val < logprob_limit or (val == logprob_limit and col <= sample_result): thread_sum[0] += T.exp(val) T.finalize_reducer(thread_sum) return _sum_smaller_probs
*_ref, (c) an allclose correctness test, and (d) a benchmark that records effective
bandwidth or TFLOPS. If you want to learn GPU kernel optimisation, reading the reference next to the kernel is an
unusually good on-ramp.
08 KV cache & memory
Memory, not compute, is what limits a serving cluster. This section is the whole memory story, from a single GPU block to the 100-petabyte SSD cluster.
The hierarchy
| What | Lives on | Replication | Managed by |
|---|---|---|---|
| In-flight block KV + state KV | Inference GPU HBM | — | The prefill / decode engines themselves (this repo) |
| SWA KV cache | Local host memory | Single copy, never on 3FS | Engines; cross-machine hits pull over a 400G VLAN using an in-memory directory sharded across entrypoints |
| Paged block KV (persistent) | Dedicated 3FS cluster | None at app level — relies on 3FS's 10+2 erasure coding | kvcache-store (not open-sourced) |
Inside one GPU
gpu_memory_utilization (default 0.9) caps the whole thing; the
engine profiles a dummy run first, then sizes the block count from what's left.How many blocks do I get?
You don't choose. The engine runs a profiling forward pass with synthetic data, measures peak memory, subtracts it from the budget, and divides by the per-block size:
def _init_cache(self) -> None: """Profile memory usage and initialise the KV cache. The engine first profiles the existing memory usage, then allocates the maximum possible number of GPU blocks with the remaining free memory.""" num_gpu_blocks = self._run_workers("profile_num_available_blocks", ...) logger.info(f"RANK {dist.get_rank()}, # GPU blocks: {num_gpu_blocks}") # cache_config is then updated in place with the real numbers: # num_gpu_blocks · bytes_per_block · num_state_seqs · bytes_of_state_per_seqs
CUDA error: an illegal memory access was encountered, the docstring on LLM.__init__ says the
likely cause is CUDA-graph OOM — lower gpu_memory_utilization and retry.Decisions worth stealing
KV offload is off during decode — decode isn't capacity-bound, so there's nothing to gain. But the KV produced during decode is still written out, purely to raise the hit rate for the next conversation turn.
After prefill, reserve one token at the end and round down to a multiple of 128; if that leaves less than a full unit, store nothing. During decode, a replica region on-device is copied each time a request crosses a boundary. Overshoot by at most 5 tokens is absorbed by 6 extra ring-buffer positions.
Reclamation is entirely 3FS's own garbage collection — one GC worker per storage node, evicting LRU under a disk watermark. Fewer moving parts, and the file system already has to solve it.
Chatbot traffic has a heavy long tail — a small amount of KV read very many times. Dropping from 7 days to ~3 costs 0.5–1% of overall cache hit rate. That is the kind of number you only get from running the thing at scale.
09 Parallelism in practice
The configuration you choose for prefill should be nearly the opposite of the one you choose for decode. Here is the reasoning and the recommended numbers.
DeepSeek's recommended settings
| Stage | Parallelism | Hardware | Rule of thumb |
|---|---|---|---|
| Prefill | EP2–8 / DP2–8 | ordinary 8-GPU machines | Use the smallest EP/DP that HBM allows. Prefill is compute-bound; spreading experts only adds communication. |
| Decode | EP64–128 / DP64–128 | 64–128-GPU supernodes | Use the largest EP/DP the GPUs allow. This is Wide-EP, and it is where the throughput comes from. |
| Vision encoder | 1 GPU per instance | cheaper GPUs recommended | Pure single-device compute; no parallelism needed. |
| TP / CP | not used | — | Neither tensor nor context parallelism is used in production today, though CP exists in the code and the examples. |
Running it yourself
# 8-way expert parallel, DP derived as ep // cp = 8 python examples/offline_inference.py --ep 8 # EP2 with 8 data-parallel replicas (16 GPUs of work across the node group) python examples/offline_inference.py --ep 2 --dp 8 # EP2 with 8-way context parallel: one sequence split across 8 devices python examples/offline_inference.py --ep 2 --cp 8 # Parallel scale actually comes from the environment, not the flags: # ep_size = WORLD_SIZE × len(CUDA_VISIBLE_DEVICES) (WORLD_SIZE = node count) # --cp must divide ep_size export WORLD_SIZE=1 RANK=0 export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
examples/_launch.py with the
spawn start method. Inside an engine, the CP rank 0 worker (the "driver") runs in the engine process
itself; CP ranks 1..n−1 are spawned child processes that sit in an RPC loop. Workers die with their parent via
prctl(PR_SET_PDEATHSIG, SIGKILL) — no orphaned GPU processes.
Hardware discovery without torch
deep_cluster/ exists for one reason: you must know the hardware topology before importing torch,
because importing torch initialises the CUDA/ACL runtime and freezes CUDA_VISIBLE_DEVICES. So it reads
procfs, sysfs, device nodes and NVML directly.
| File | Does |
|---|---|
deep_cluster/hardware/nvidia.py | Discovers NVIDIA devices via procfs/sysfs; reads compute capability through NVML without creating a CUDA context. |
deep_cluster/hardware/ascend.py | The same for Ascend, via device files, ubcore sysfs and npu-smi for NUMA nodes. |
deep_cluster/hardware/device.py | The neutral API: accelerator_kind(), visible_devices(), get_bf16_tflops_limit(), is_sm_major_10(). |
deep_cluster/topology/bind.py | NUMA lookup and process binding through libnuma — pins each worker's CPU threads near its GPU. |
10 Speculative decoding & DSpark
The one place where DeepInfer does something genuinely new rather than doing a known thing very well.
The base idea
Decode is memory-bound: reading the model's weights to produce one token costs almost the same as reading them to produce six. So have a cheap draft model propose the next few tokens, then run the big model once over all of them and check which it agrees with. Accepted drafts are free tokens. Rejection sampling at the first disagreement guarantees the output distribution is unchanged — this is mathematically exact, not an approximation.
The measured result
From the deployment Q&A, computed on EP64/DP64 benchmark data. Note the gain is load-dependent — at low concurrency it is slightly negative:
| Concurrency (seqs/rank) | Fixed-5 verified len | Dynamic verified len | Fixed-5 accepted | Dynamic accepted | Fixed-5 tok/s/rank | Dynamic tok/s/rank | Gain |
|---|---|---|---|---|---|---|---|
| 16 | 5.00 | 4.44 | 3.32 | 3.24 | 4 891 | 4 816 | −1.5% |
| 32 | 5.00 | 3.65 | 3.25 | 3.17 | 8 964 | 9 111 | +1.6% |
| 48 | 5.00 | 3.94 | 3.30 | 3.26 | 12 032 | 12 467 | +3.6% |
| 60 ← production point | 5.00 | 3.58 | 3.30 | 3.21 | 13 730 | 14 466 | +5.4% |
| 72 | 5.00 | 3.24 | 3.29 | 3.12 | 14 962 | 15 961 | +6.7% |
| 96 | 5.00 | 3.24 | 3.30 | 3.15 | 16 817 | 18 660 | +11.0% |
The scheduler's actual search
This is the core of mtp_varlen.hpp: sort every draft token across every request by predicted
acceptance, then walk the list adding one token at a time and ask "does this batch size still improve throughput?"
// Sort all (acceptance_rate, seq_idx) pairs descending std::sort(acc_queue_for_bsz.begin(), acc_queue_for_bsz.end(), by_rate_desc); double best_throughput = 0.0; int best_bsz = 0; float cur_expected = num_seqs; // the guaranteed tokens (one per sequence) int cur_bsz = num_seqs; auto update_best = [&]() { const auto &[graph_size, baseline_ms] = _decode_perf_curve[cur_bsz_idx]; // predicted step time = profiled CUDA-graph baseline // + fitted per-token slope (context-length effect) // + measured sampler time (median of recent steps) const double time_ms = baseline_ms + slope_ms * graph_size + sampler_ms; const double cur_throughput = cur_expected / time_ms; if (cur_throughput > best_throughput) { best_throughput = cur_throughput; best_bsz = cur_bsz; } }; update_best(); // batch of just the golden tokens for (auto &acc : acc_queue_for_bsz) { // then add drafts, best-first cur_expected += acc.first; // expected extra accepted tokens cur_bsz += 1; // definite extra compute update_best(); } // Hand out best_bsz - num_seqs slots to the highest-confidence drafts seqlens_q.assign(num_seqs, 1); for (int i = 0; i < best_bsz - num_seqs; ++i) seqlens_q[acc_queue_for_seqs_q[i].second] += 1;
_decode_perf_curve is the startup CUDA-graph profile. On top of it, ForwardRuntimeEstimator
fits a per-token slope (context length affects step time, which the static curve ignores) and
SamplerRuntimeEstimator tracks a rolling median of sampler time — both using median-of-medians so one
slow step doesn't poison the estimate. The scheduler is therefore continuously re-calibrating against the machine it
is actually running on.
schedule_cnt and process_cnt per sequence; they can differ by at most one, and that tells it
whether the stored accept_rates are from round N−1 or N−2. It deliberately uses the older round
to pick the total batch size (so that confidence for draft i+1 can never influence whether draft i is verified) and
the newer one to distribute slots. That is a careful piece of reasoning hiding in a 30-line comment.
Turning it on and off
export DEEPINFER_USE_NEXTN_DECODE=5 # max draft length (default 5; 0 disables speculation) export DEEPINFER_ENABLE_MTP_VARLEN=1 # turn on DSpark confidence-scheduled verification export DEEPINFER_DFLASH_CONFIDENCE_HEAD_TEMPERATURE=1.0 # or a comma list, one per draft slot export DEEPINFER_MAX_BACKTRACKING_TOKENS=6 # ≥ nextn + 1, forced
11 Scheduling, engine types & determinism
Three engine types — the hook for P/D disaggregation
The engine can be told to do only half the job. This is how a disaggregated deployment is built, and the README notes that "most required engine functionality is already implemented" even though the orchestration isn't shipped.
| EngineType | Does | Async scheduling default | Notes |
|---|---|---|---|
NORMAL | Prefill and decode | on | The default; must vote with DP peers each step to stay in lockstep. |
PROMPT | Prefill only | off | Always returns a prefill result, even an empty one. Asserts it never decodes. |
COMPLETION | Decode only | on | GC interval ×10, multimodal pool off by default, no context parallelism. |
Chunked prefill, and the cost model behind it
A 128k-token prompt processed in one step would block every other user for hundreds of milliseconds. Chunked prefill
slices it. The open-source scheduler uses a token budget (max_num_batched_tokens); the production prefill
balancer goes further and solves for the chunk size:
compute_quota_limit in nanoseconds. A fitted quadratic cost model
cost(q, c) predicts the step duration from q (new tokens) and c (prefix already
computed). The scheduler solves that quadratic for the largest q that stays under 32 768 tokens while
exactly consuming the remaining budget — then the budget itself is reduced by an all-reduce MIN across the DP group,
so no rank runs long and strands the others.
The determinism recipe
Three settings, all required together, NVIDIA only:
export DEEPINFER_BATCH_INVARIANT=1 # deterministic reduction order in GEMM/attention export DEEPINFER_ENABLE_MTP_VARLEN=0 # variable draft length makes the step shape data-dependent # ...and pin the per-sequence seed in Python: SamplingParams(temperature=0.7, top_p=0.95, max_tokens=256, seq_seed=42) # seq_seed is uint64; left unset it is random.getrandbits(64)
Gives you identical logits regardless of who shares your batch. Sampling is still random.
Gives you identical tokens. The sampling kernel is deterministic in (seed, position), independent of batch size and row order.
Needed because variable-length verification changes how many rows are in flight, which changes reduction shapes.
Where the preemption policy bites
When KV blocks run out mid-decode, something has to give. DeepInfer sorts running by output length and
preempts from the shortest end — last-come-first-served — so the requests that have already consumed the most
compute are protected. Preempted sequences either return to the queue or, with
stop_preempted_request=True (the default outside the offline LLM wrapper), are terminated.
12 Build & run
Everything you need to go from a clean machine to generated tokens — assuming you have the right hardware, which is the hard part.
Prerequisites
| Requirement | NVIDIA | Huawei Ascend |
|---|---|---|
| Accelerator | SM100 GPUs (Blackwell-class) | Ascend 950 NPUs |
| Toolkit | CUDA 13 | CANN 9.2 |
| OS / compiler | Linux · GCC ≥ 13.0 · Python 3.12 (exactly — the project pins it) | |
| PyTorch | torch==2.13.0+cu132 | torch==2.13.0+cpu + torch-npu==2.13.0rc1 |
| Environment | A clean conda env or virtualenv — setup.py refuses to build without CONDA_PREFIX or VIRTUAL_ENV set | |
Build
git clone https://github.com/deepseek-ai/DeepInfer.git cd DeepInfer # Use `ascend` instead on the Ascend platform. # Keep this exported wherever you RUN DeepInfer too — it selects the built extension. export DEEPINFER_ACCELERATOR=nvidia ./build.sh
What build.sh actually does, in order:
pip install -r requirements-nvidia.txt(or-ascend) — pulls torch, tilelang, tile-kernels, xgrammar, transformers, flash-attn-4 and friends.- On NVIDIA only: force-reinstalls
nvidia-nccl-cu13==2.32.3. DeepEP needs that exact NCCL. scripts/build_vendor.sh— initialises the submodules and builds wheels for DeepSelect, FlashMLA, DeepEP and DeepGEMM (or their Ascend ports), then installs them with--no-index.scripts/build.sh build— compiles thedeepinfer._Cextension intobuild/<accelerator>-<arch>/, symlinks the.sointodeepinfer/_so/<tag>/, and byte-compiles the package.
nvidia-x86_64, ascend-aarch64 …) and
deepinfer/__init__.py appends the matching directory to __path__ at import. So one checkout
can hold a CUDA build and an Ascend build simultaneously, and the right one loads automatically.
Make targets
make build-nvidia # build the extension for CUDA make build-ascend # build the extension for Ascend make vendor-nvidia # rebuild just the vendored kernel wheels make vendor-ascend make clean # drop build artefacts and __pycache__ make stubgen # regenerate deepinfer/_C/*.pyi type stubs make format # ruff check --fix && ruff format, then clang-format on csrc/
Run
# Downloads deepseek-ai/DeepSeek-V4.1-Flash-1017 into the HF cache on first run. # Point at a local copy instead: export DEEPINFER_HF_MODEL_DIR=/data/DeepSeek-V4.1-Flash-1017 python examples/offline_inference.py # defaults to --ep 8 python examples/offline_inference.py --ep 2 --dp 8 python examples/offline_inference.py --long_context # 16 389 in / 4 096 out python examples/offline_inference.py --check_structured_output # Benchmarks python examples/benchmark.py # 32k context, 64 decode requests python examples/benchmark.py --decode_num_requests 1 # low-latency point python examples/benchmark.py --context_length 131072 --yoco_clip # the 128k nightly config # Pareto sweeps → sweeps/<tag>/results.jsonl + a PNG bash examples/sweep/run_sweep.sh decode ep64-128k 131072 3 0.75 bash examples/sweep/run_sweep.sh prefill pe-128k 131072 bash examples/sweep/run_sweep.sh encoder ve-800 # Tests python -m pytest -v -s -m ci tests # GPU core tests python -m pytest -v -s -m ci_npu tests # NPU core tests python -m pytest -v -s tests # everything, including long-context PPL
Debugging on Ascend
export ASCEND_LAUNCH_BLOCKING=1 # run operators synchronously → readable stack traces export ASCEND_SLOG_PRINT_TO_STDOUT=1 # CANN logs to stdout
13 Every environment variable
All of them, from deepinfer/environ.py. They are resolved lazily on first access and
cached, which is why rearm() exists — the engine re-reads a few after setting defaults. Type the name in
the top search box to jump to one.
| Variable | Default | What it does |
|---|---|---|
DEEPINFER_ACCELERATOR | nvidia | Platform selector: nvidia or ascend. Must match the build and stay exported at runtime. |
DEEPINFER_ZERO_OVERHEAD_SCHEDULING | per engine type | Async scheduling. On by default for NORMAL and COMPLETION engines, off for PROMPT. |
DEEPINFER_ENFORCE_EAGER | 0 | Disable CUDA graphs entirely. Slower, but far easier to debug. |
DEEPINFER_CUDAGRAPH_BATCH_SIZE_INTERVAL | 8 | Spacing of the captured graph batch-size ladder. Smaller = less padding waste, more capture time and memory. |
DEEPINFER_DFLASH_ENFORCE_EAGER · DEEPINFER_DFLASH_CUDAGRAPH_BATCH_SIZE_INTERVAL | 0 · 8 | The same two knobs for the draft model's separate graph runner. |
DEEPINFER_DECODE_PERF_CACHE_DIR · _TTL_SEC | unset · 3600 | Where to persist the startup decode-timing curve, and how long it stays valid. Saves profiling time on restart. |
DEEPINFER_PREFILL_ATTEMPT_INTERVAL | 70 | Decode steps between "ALL" prefill votes — every DP rank must have prefill work for the group to switch. |
DEEPINFER_PREFILL_ANY_ATTEMPT_INTERVAL | 100 | "ANY" backstop interval: one rank with work forces a group prefill, so an isolated rank can't starve. |
DEEPINFER_PREFILL_MAX_SUM_SEQLEN_K | unlimited | Caps the total K length in a prefill batch. Unset means no limit (passed to C++ as 2³⁰). |
DEEPINFER_JIT_CACHE_DIR | unset | Root for the TileLang / DeepGEMM / DeepEP JIT caches. Set it, and image-baked warmup dirs are appended to the search path — big cold-start win. |
DEEPINFER_NCCL_NUM_SMS · DEEPINFER_USERBUFFER_COMM_SMS | 32 · 0 | How many streaming multiprocessors communication may consume. Trade-off against compute. |
DEEPINFER_GLOO_TIMEOUT | 14 400 s | CPU process-group timeout. Four hours, because weight loading across many nodes is slow. |
DEEPINFER_GC_INTERVAL_STEP | 1000 | Manual garbage collection cadence (GC is disabled outright in workers). ×10 for completion engines. |
DEEPINFER_OVERRIDE_NUM_EXTRA_EXPERTS | 0 | Lower bound on redundant experts. The engine rounds total experts up to divide by ep_size; examples set 32. |
DEEPINFER_EXPERT_COUNT_WINDOW_SIZE | 100 | How many steps of per-expert token counts to keep. Smaller saves GPU memory when EPLB is off. |
DEEPINFER_EXPERT_REARRANGEMENT_STEP_INTERVAL | 0 (off) | Steps between EPLB replacements. Production: 1 000 for prefill, 10 000 for decode. |
DEEPINFER_REARRANGE_AFTER_PREFILL_IN_NORMAL_ENGINE | 0 | Rebalance experts right after a prefill step in a NORMAL engine. |
DEEPINFER_MOE_LB_STAT_INTERVAL | 128 | How often the MoE load-balance statistic is all-gathered. The benchmark sets 10 so moe_lb shows up in logs. |
DEEPINFER_FORCE_IMBALANCE_RATIO · DEEPINFER_FORCE_LOAD_BALANCE | 0 | Synthetic imbalance / forced perfect balance — for testing EPLB, not production. |
DEEPINFER_SHOW_EPLB_DETAIL | 0 | Verbose per-expert placement logging. |
DEEPINFER_REARRANGE_P2P_GROUPS | 1 | How many P2P groups the expert-weight shuffle splits into. |
DEEPINFER_USE_NEXTN_DECODE | 5 | Maximum draft length. 0 disables speculative decoding entirely. |
DEEPINFER_ENABLE_MTP_VARLEN | 0 | Turns on DSpark: confidence-scheduled variable-length verification. Must be off for deterministic sampling. |
DEEPINFER_MAX_BACKTRACKING_TOKENS | nextn+1 | How far a rejected draft may rewind. Forced to at least nextn + 1. |
DEEPINFER_DFLASH_CONFIDENCE_HEAD_TEMPERATURE | 1.0 | Calibrates the confidence head. Accepts a comma list — one value per draft slot — which must match nextn. |
DEEPINFER_YOCO_PREFILL_TRUNCATION_MODE | off | off · mask (narrow SWA visibility, keep all queries) · compact (actually drop truncated queries, saving compute). |
DEEPINFER_YOCO_PREFILL_KEEP_TOKENS | 0 | Suffix length kept when truncating. Must be ≥ window + backtracking. V4.1 default is 140. |
DEEPINFER_NUM_STATE_SLOTS_RATIO | 1.2 | State slots = max_num_seqs × ratio + 1. Headroom for forks and in-flight sequences. |
DEEPINFER_PREFILL_STORE_EXTRA_STATE · _SIZE | 0 · 128 | Store extra SWA beyond the inference window, so a consumer can attach at a different boundary. |
DEEPINFER_INDEX_ATTN_MEMORY_LIMIT | 10 GiB | Memory cap for sparse-indexer intermediates. |
DEEPINFER_ENGRAM_PAD_TOKEN · DEEPINFER_ENGRAM_FORCE_GPU_STORAGE | 2 · auto | Engram padding token; force the n-gram tables into GPU memory (always true on Ascend). |
DEEPINFER_SAMPLER_FORCE_TOPK | 0 | Restrict softmax/sampling to the top-k logits. Examples use 4 096; benchmark uses 1 024. Big saving on a 129k vocabulary. |
DEEPINFER_DECODE_TOKEN_BLACKLIST | empty | Token IDs that may never be emitted, as a 0..3,7,10..12 range string. |
DEEPINFER_COMPUTE_SUM_SMALLER_PROBS | 0 | Also compute the inclusive CDF at the sampled token — useful for analysis, costs a kernel. |
DEEPINFER_XGRAMMAR_TIMEOUT · _CACHE_LIMIT_BYTES · _DISABLE_CACHE · _DEBUG | 20 s · 10 GiB · 0 · 0 | Structured-output grammar compilation: time limit, compiled-grammar cache size, disable, debug. |
DEEPINFER_SO_BUILDER_CPUS | 4 | Worker threads for async grammar compilation, pinned to idle CPUs. |
DEEPINFER_BATCH_INVARIANT | 0 | Deterministic reduction order so results don't depend on batch composition. NVIDIA only. |
DEEPINFER_DISABLE_SYNC_GUARD | 0 | Turn off the "no CPU-GPU sync inside forward" guard — needed if you add a print to the forward pass. |
DEEPINFER_LOGGING_INTERVAL_STEP | 100 | Steps between stats log lines. 0 silences it (the benchmark does this during measurement windows). |
DEEPINFER_PROFILE_HTTP_ENABLE · _PORT_BASE | 0 · 6070 | Per-engine HTTP endpoint to capture a torch profiler trace on demand. Port = base + driver GPU id. |
DEEPINFER_TORCH_PROFILER_DIR | /tmp | Where traces land. |
DEEPINFER_PROFILE_TORCH_CUDA_MEMORY · DEEPINFER_ENABLE_MEMORY_TRACING | unset · 0 | Record CUDA memory history (100k entries) for leak hunting. |
DEEPINFER_NCCL_MEMORY_SAMPLE_INTERVAL_STEPS | 1000 | How often to snapshot NCCL communicator memory. |
DEEPINFER_RINGBUFFER_WARNING_INTERVAL | 60 s | Rate limit on shared-memory ring-buffer congestion warnings. |
DEEPINFER_USE_MULTIMODAL_POOL · _POOL_SIZE_GB · DEEPINFER_MULTI_MODAL_BUFFER_SIZE · _WORKER_TIMEOUT | per type · 8 · 512 · 1800 | The shared-memory pool that carries preprocessed images from worker processes into the engine. |
DEEPINFER_HF_MODEL_DIR | unset | Points the default checkpoint at a local directory. Ignored if you pass --model_name explicitly. |
Python-side configuration
Everything not in an env var goes through EngineArgs. The ones that actually matter:
| Argument | Default | Effect |
|---|---|---|
expert_parallel_size | 1 | EP degree. Must divide model_parallel_size × data_parallel_size. |
data_parallel_size | 0 | 0 means derive it as max(1, ep // cp). |
context_parallel_size | 1 | Split one sequence across this many devices. |
gpu_memory_utilization | 0.90 | Fraction of HBM for weights + activations + KV. Lower it if CUDA graphs OOM. |
max_num_seqs | 256 | Max concurrent sequences. Also sizes the state cache and the CUDA-graph ladder. |
max_num_batched_tokens | derived | Prefill chunk budget. Must be ≥ max_num_seqs × (1 + nextn). |
max_model_len | 4096 | Prompt + output cap. The runtime length adds scheduler and draft overhead on top. |
chunked_prefill | False | Without it, max_num_batched_tokens must be ≥ max_model_len or construction fails. |
lookahead_tokens | 0 | Speculative depth from the cache's point of view — reserves KV room for draft tokens. |
engine_type | NORMAL | NORMAL / PROMPT / COMPLETION. |
load_format | auto | dummy builds synthetic weights from the same config — invaluable for profiling without a checkpoint. |
14 Benchmarks & evaluations
What DeepSeek published, what it means, and the long list of things it does not mean.
The two Pareto charts
- 4 × SM100 GPUs, EP4DP4
- 384 + 32 experts
- 131 072-token context
- Decoder bounded replay of 140 tokens
- X = latency per prefill step (ms) · Y = non-cached input throughput (tokens/s/GPU)
- Each point labelled by prefill batch size
- 64 × SM100 GPUs, EP64DP64
- 384 + 64 experts
- 131 072-token context
nextn = 3fixed, 75% assumed per-token acceptance- X = per-user speed (tokens/s/user) · Y = output throughput (tokens/s/GPU)
- Each point labelled by requests per DP rank
The charts carry no value labels. An independent reader estimated the following from the axes; treat them as shape, not procurement data:
| Chart | Point | Throughput (approx.) | Latency / per-user speed (approx.) |
|---|---|---|---|
| Prefill | 4 096 batch | ~60k tok/s/GPU | ~68 ms/step |
| Prefill | 16 384 batch | ~81k tok/s/GPU | ~200 ms/step |
| Prefill | 65 536 batch | ~89k tok/s/GPU | ~730 ms/step |
| Decode | 8 req/rank | ~2.6k tok/s/GPU | ~325 tok/s/user |
| Decode | 64 req/rank | ~15.5k tok/s/GPU | ~248 tok/s/user |
| Decode | 256 req/rank | ~26k tok/s/GPU | ~103 tok/s/user |
The shape is the message: prefill throughput rises with batch size and then flattens; decode throughput rises with concurrency while each user gets slower. There is no free lunch, only a frontier.
Accuracy — the eval harness
| Benchmark | Tasks | Samples/task | Pass@1 | How it's scored |
|---|---|---|---|---|
| AIME 2026 | 30 | 32 | 99.9% | Mean accuracy over 32 samples. Rule judge extracts the integer in \boxed{}; agreed with the LLM judge on all 960 rollouts. |
| GPQA Diamond | 198 | 8 | 93.4% | Mean over 8 samples. Rule judge takes the letter after ANSWER:. |
| HLE-Full | 2 500 (2 158 text + 342 image) | 1 | 42.6% | Single sample. LLM judge only — over half the answers are free-form, so exact matching would understate the score. score.py refuses --judge rule here. |
# Datasets are NOT downloaded for you — place the JSONL yourself # eval/data/aime_2026.jsonl · eval/data/gpqa_diamond.jsonl · eval/data/hle_test.jsonl export EVAL_JUDGE_BASE_URL=https://api.deepseek.com export EVAL_JUDGE_API_KEY=<key> export EVAL_JUDGE_MODEL=deepseek-v4-flash EP=8 N_SAMPLE=4 TAG=smoke bash eval/aime/run.sh # quick smoke test bash eval/gpqa/run.sh # full run # Generation and scoring are separate stages — rescore without regenerating: python eval/score.py --out-dir eval/outputs/aime/<TAG> --judge rule
coverage_complete is true in
summary.json; otherwise some rank or sample is missing and the number is silently wrong.Reading any of these numbers responsibly
There is no published head-to-head against vLLM or SGLang on the same hardware and workload. Each project benchmarks its own setup.
Both charts are SM100. Ascend 950 is a supported target with no published throughput at all — you'd have to validate it yourself.
Tokens/s/GPU is not dollars per million tokens until you add utilisation, power, depreciation, networking, storage and failure overhead.
Prefill latency here is per step, not time to first token. Decode per-user speed is steady-state, not single-call latency. Network, queueing and tokenisation are all excluded.
The Q&A says production load balancing reaches about 80% of a perfectly balanced offline benchmark. These curves are an upper bound.
The examples README warns step times inside a trace
include profiler overhead (up to ~50% inflation at batch 1) and that a residual 7–11% slowdown persists after the
profiler exits. Sweeps therefore run with PROFILE=0.
15 The production Q&A, distilled
doc/inference_deployment_qa.md is 312 lines and is, for my money, the most valuable file
in the repository — a working description of how a frontier inference service is actually run. Here is all of it,
compressed.
| Question | Answer |
|---|---|
| What shape is the system? | Disaggregated E/P/D — vision encoder, prefill and decode pools — plus dedicated storage nodes. |
| Parallelism? | Prefill EP2–8/DP2–8 (smallest HBM allows). Decode EP64–128/DP64–128 (largest GPUs allow). VE: one GPU per instance, cheaper GPUs fine. TP and CP unused. |
| Why Wide-EP for decode? | It substantially reduces per-step memory traffic in the MoE part and, with good EPLB, raises TPS. An EP8-vs-EP64 chart is included. |
| Minimum deployment? | 1P + 1D is deployable. P:D GPU ratio is ~1:1 in practice, and decode wants EP64+, so the smallest efficient unit is 64 + 64 GPUs. |
| Across data centres? | Technically feasible because V4.1 compresses KV so heavily. Budget ~400 Gbps per 8 GPUs. Reliability cost is real — inter-DC fibre gets cut by construction work. DeepSeek uses adjacent DCs. |
| Storage server spec? | 20 × 30.73 TB SSDs per server, ≥40 GB/s read and ≥5 GB/s write, enough capacity for 24 h of data. Scale server count with deployment size. |
| Storage-to-GPU ratio? | ~14.4 TB logical per GPU; physical requirement is higher. |
| 3FS properties? | 10+2 erasure coding, tolerates 2 node failures per group. A read fans out to all 11 nodes and decodes from the first 10 replies. One GC worker per storage node evicts LRU under a watermark. |
| Prefill EPLB? | Start 4 redundant experts/GPU, 100-step window, reassign every 1 000 steps. Measured moe_lb 1.074 (EP4DP4) / 1.045 (EP2DP2). |
| Decode EPLB? | Start 1 redundant expert/GPU (7/GPU after rounding at EP64, excluding the shared expert), 1 000-step window, reassign every 10 000 steps. moe_lb 1.090. |
| Prefill load balancer? | Four context-length tiers; engines per tier redistributed by last round's demand; engines pull by remaining quota; within a GPU, FIFO with a chunk size solved from a quadratic cost model against a per-step nanosecond budget, all-reduce MIN across the DP group. |
| Decode load balancer? | Uniform across all decode GPUs, balancing request count and total KV length simultaneously: short requests to low-concurrency/long-KV GPUs, long requests to short-KV GPUs. |
| Vision encoder balancer? | Machine-granularity random pick, then the GPU currently processing the fewest patches — computed directly as (w/patch) × (h/patch). |
| How much does LB buy? | Roughly 80% of a perfectly balanced offline benchmark. |
| KV offload during decode? | No — decode isn't capacity-bound, so Hi-Sparse isn't used either. Decode KV is still written to disk to raise the future hit rate. |
| Expiry / TTL? | No TTL logic in the cache store; 3FS GC handles it. ~100 PiB SSD, 3-day TTL for agent traffic, 7-day for chatbot. Dropping chatbot to ~3 days costs 0.5–1% hit rate. |
| Hiding cache overhead? | Matching: metadata-only hash-chain prefix match at the entrypoint — can't be hidden, since hit length determines routing. Fetch: async, separate process, 3FS → host shared memory. Swap-in: in-engine thread, per-layer H2D overlapped with forward, implemented with GDR because the CUDA copy engine handles fine-grained block copies poorly. |
| Autoscaling? | Prefill sizes from non-cached token count, decode from running-request count; both rate-limited and bounded. A decode engine given a shutdown notice takes a 1-minute grace period and forwards unfinished requests plus their KV to other engines in one shot. |
| Redundant quota? | Ready machines held in a pool, plus more instances online than computed demand. Scale-in forbidden above 0.8 quota utilisation. |
| EP failover? | One dead GPU kills the instance (single communication domain). On restart the platform re-derives EP size from the healthy GPUs, and the instance adopts whatever count it receives. |
| Where is FP4 used? | Main KV: per-16 E4M3 (NVFP4 without the global scale). Indexer KV: per-32 UE8M0 MXFP4. MoE routed-expert weights: per-32 UE8M0 MXFP4. |
| Mega MoE? | The general solution inside a scale-up domain — significantly faster than unfused MoE regardless of EP size or batch size. |
| Better internal kernels? | Broadly the same. Indexer and mega series differ somewhat. Public FlashMLA lacks one hand-tuned SASS optimisation → decode kernel 5–10% slower. End to end: at most 5% apart. |
| Speculative decoding gain? | Dynamic-length verification cuts verified drafts per step ~30% (5.00 → 3.2–4.4) with essentially unchanged accepted length (3.29 → 3.20). +4–7% throughput at the ~60 seqs/rank production point. |
| Structured output workflow? | Compilation decoupled from inference: a dedicated SO machine compiles the constraint into a grammar automaton; at each decode step the automaton advances, legal tokens are computed, and a mask is applied to the sampling logic. |
16 What's missing
Stated plainly by DeepSeek in the README, and expanded on by independent write-ups. If you are planning anything with this, read this section twice.
You get the in-GPU paged cache and the hooks to store and load state. You do not get: prefix matching, a metadata service, a distributed store, eviction and TTL, cross-machine transfer, fault tolerance, or cache-aware routing. One write-up calls this "a storage team's worth of work", which sounds about right.
The engine types exist and the README says most engine functionality is implemented. The orchestration is not: KV handoff between pools, a directory of cache locations, and routing that weighs both cache placement and decode occupancy.
All four production balancers are documented and none is shipped. EPLB — the intra-instance one — is included; the inter-instance ones are not. A round-robin router will not reach the published 80%-of-ideal figure.
And beyond those three
| Missing | What it means for you |
|---|---|
| Any HTTP server | No OpenAI-compatible endpoint, no streaming API, no auth, quotas, billing or rate limits. deepseek-recipe handles protocol conversion and tokenisation but is a library, not a server. |
| Other models | The model registry has exactly one entry: DeepseekV41ForCausalLM. Nothing else loads. |
| Older / consumer hardware | SM100 and Ascend 950 only. No H100, no consumer GPUs, no CPU path. |
| Ascend performance data | Ascend is a listed, supported target with zero published throughput or stability numbers. You validate it yourself. |
| Third-party reproduction | As of the public snapshot, nobody outside DeepSeek had reproduced the Pareto charts. |
| Production parity guarantee | DeepSeek says DeepInfer powers its production service; the release is a subset, so parity can't be verified externally. |
| Observability | There are rich internal timers and a stats logger, but no metrics export, tracing, canary tooling or regression harness for latency and cost. |
| A release tag | At launch the public history was three commits on one day, with no GitHub release or tag. It is a code drop, not a versioned product. |
Decision guide
| If you… | Then | Because |
|---|---|---|
| Need a served V4.1 API this quarter | Use a general framework | vLLM has a mature serving layer today; SGLang has the most complete V4.1 path (preview images, PD, HiCache, router). Both give you an endpoint; DeepInfer gives you an engine. |
| Run Ascend 950 | DeepInfer | It is the official Ascend path, with first-party Ascend kernels across the whole stack. Budget time to validate performance yourself. |
| Need deterministic, reproducible inference | DeepInfer | Batch-invariant mode plus deterministic sampling is explicit and documented here. Rare elsewhere. |
| Have SM100 capacity and only serve V4.1 | Evaluate both | DeepInfer is tuned specifically for this architecture, but you own the service layer. |
| Want to learn how frontier inference works | Read DeepInfer | The kernels, the scheduler, the EPLB policy and the deployment Q&A together are the most detailed public account of a production LLM serving system. |
| Have consumer GPUs, or serve other models | Not applicable | Neither is supported, at all. |
17 The blogs & comparisons
Two bodies of writing surround this release: DeepSeek's own documents, and an independent field-notes site at deepinfer.net. They are very different things and deserve different trust.
DeepSeek's own writing
| Document | What's in it |
|---|---|
| Inference Deployment Q&A in-repo, 312 lines |
The primary source for everything about the production system. Opens with the three design goals — support multiple accelerator vendors, organise prefill and decode around commodity hardware, retain KV cache for 72+ hours — then answers detailed questions on architecture, storage, EPLB, load balancing, autoscaling, precision, kernels, speculative decoding, async scheduling, structured output and DeepSeek Recipe. Fully distilled in section 15. |
| The Zhihu post linked from the Q&A |
The longer Chinese write-up the Q&A builds on: design goals, overall architecture, and the key implementation of the KV cache management and load-balancing components that are not open-sourced. |
| examples/README.md in-repo, 124 lines |
Unusually rigorous measurement methodology: how prefill and decode stages are decoupled, exactly which steps enter a steady-state window, why intervals colliding with an EPLB statistics flush are discarded, and why profiler-inflated step times must not be used to derive throughput. |
| eval/README.md in-repo, 221 lines |
The accuracy harness: dataset formats, the two judging modes, why HLE refuses rule-based scoring, and the reference Pass@1 figures. |
| Kernel deep-dives in the vendored repos |
FlashMLA ships a technical report on the Ascend prefill kernel and an earlier Hopper FP8 sparse-decoding deep dive; DeepSelect ships an algorithm analysis. These are the best writing on the kernels themselves. |
deepinfer.net — the independent field notes
The site is organised as a reference, not a chronological blog. Its pages:
| Page | The argument it makes |
|---|---|
| Architecture | Separates model / engine / service, walks prefill vs decode, then EPD and Wide-EP, EPLB, DSpark, SWA bounded replay, async scheduling, determinism and structured output. Ends with a novelty assessment arguing that most individual primitives are established, and DeepSeek's real contribution is the integration plus the published tuning — with the genuinely DeepSeek-specific items being the 3FS KV cluster, the Wide-EP-at-64–128 operating point, and the CED + SWA-replay co-design. |
| Release | A forensic look at the drop: Apache-2.0, three commits on 20 Oct 2026, no release tag, MIT model weights, and a precise included/not-included list. Notes the short history reflects a code drop rather than a development record. |
| Benchmarks | Reads the two Pareto charts carefully, publishes estimated values with an explicit warning against false precision, lists six categories of thing the numbers do not mean, and ends with a wish list: third-party reproduction on SM100, any Ascend data, a same-hardware head-to-head, end-to-end serving benchmarks including KV cache, and a cost-per-million-tokens model. |
| Limits & Production gap | The sharpest framing on the site: "the engine is the expensive compute; the service is the expensive operations." Enumerates what a service needs beyond an engine and estimates the build effort for each missing subsystem. |
| FAQ | Short answers to the obvious questions — can I run it on a 4090 (no), is there an OpenAI API (no), is it the same as DeepInfra (no, different company), is it better than vLLM (no public head-to-head exists). |
| Name | Disambiguation: there is also a 2016 medical-imaging DeepInfer (a 3D Slicer toolkit), a 2023 smart-contract DeepInfer (infers function signatures from Ethereum bytecode), and the commercially unrelated DeepInfra. |
The ten comparison posts
All dated 2026-10-20, all explicitly scope-and-fit comparisons rather than speed tests, because no public head-to-head benchmark exists.
DeepInfer vs vLLMThe most useful of the ten
| Dimension | DeepInfer | vLLM |
|---|---|---|
| Type | DeepSeek-specific offline engine | General inference and serving framework |
| Models | V4.1-Flash only | 200+ architectures |
| Hardware | SM100, Ascend 950 | NVIDIA, AMD, Intel GPUs, CPUs, plugins (incl. Ascend) |
| Online API | None | OpenAI-compatible, Anthropic Messages, gRPC |
| KV cache | In-GPU paging only | PagedAttention, prefix caching, chunked prefill |
| P/D disaggregation | Engine hooks only | Included |
| Speculative decoding | DSpark | n-gram, suffix, EAGLE, DFlash |
| Maturity | Early code drop | Large, mature, years in production |
Where DeepInfer wins, per the post: V4.1-specific tuning (the DSpark path, SWA replay, the Wide-EP decode layout), deterministic modes, official Ascend 950 support, and documentation value. Where vLLM wins: an actual serving API, model breadth ("if you serve more than V4.1-Flash the comparison is effectively over"), hardware breadth, production features in the box, and ecosystem.
The post notes vLLM published its own V4.1-Flash optimisation results — a 1.9× speedup at low concurrency and a 5.3× throughput gain under a 150 TPS constraint on the SemiAnalysis AgentX workload — and warns these cannot be compared to DeepInfer's charts: different workload, different hardware, different metrics.
Verdict: vLLM if you need an API soon or multiple models; DeepInfer if you have the hardware, serve only V4.1-Flash, and will build the service layer; read DeepInfer either way if you want to learn the design.
DeepInfer vs SGLangThe closest real competitor for V4.1
SGLang shipped a day-0 V4.1-Flash cookbook covering DSpark, PD disaggregation, HiCache, SWA bounded replay,
Engram, an OpenAI-compatible server and a cache-aware router — but as a preview image, not a stable release,
so pip install sglang cannot serve the model. The post's framing: SGLang offers the most complete
V4.1-Flash serving path available today, at the cost of building on a moving target (pin the image digest).
DeepInfer is a stable code drop, but a very early project.
| If you… | Start with | Reason |
|---|---|---|
| Need a served V4.1 API now | SGLang (preview) | API, PD, HiCache and router already wired up |
| Run Ascend 950 | DeepInfer | The official Ascend path |
| Need deterministic inference | DeepInfer | The post says SGLang's V4.1 backend currently refuses deterministic inference |
| Want a mature general framework | SGLang | Community and production history |
| Want DeepSeek's own engine for research | DeepInfer | Kernels, EPLB, DSpark, SWA replay, first-party |
On performance it quotes SGLang's 873.63 tokens/s at batch size 1 on 4× GB300 with DSpark and a simulated acceptance length of 5.5 — a single-stream decode figure, not serving throughput — and concludes that comparing it to DeepInfer's 64-GPU decode chart is meaningless without normalising model, hardware, context, acceptance rate and concurrency. Its phrase for an unnormalised "faster" claim: a claim without a denominator.
The other eight comparisonsShorter, and some deliberately absurd
| Post | The angle |
|---|---|
| vs TensorRT-LLM | DeepSeek's V4.1-specific engine against NVIDIA's general inference stack — focused on hardware lock-in and build complexity. |
| vs LMDeploy | InternLM's TurboMind stack, which already carries DeepSeek optimisations and its own Ascend path. |
| vs TGI | Hugging Face's serving toolkit is in maintenance mode; the post weighs what that means for deployment risk. |
| vs Xinference | A multi-model platform (LLMs, embeddings, rerankers, multimodal) versus a single-model engine. |
| vs NVIDIA Dynamo | Dynamo is an orchestration layer above vLLM/SGLang/TensorRT-LLM — arguably the closest thing to the missing service layer. |
| vs llama.cpp | Explicitly framed as an unlike pairing: a local C/C++ GGUF runtime against a 64-GPU cluster engine. |
| vs Ollama | Asks whether the easy local runner can handle the model at all, versus a data-centre engine. |
| vs MLC-LLM | A compiler-based runtime targeting many GPU vendors against DeepSeek's fixed SM100/Ascend engine. |
The last three exist mostly to answer the search query. A model of this size on a laptop is not a near-miss; it is a category error.
DeepWiki
deepwiki.com/deepseek-ai/DeepInfer
is an auto-generated code wiki. It is useful as a navigational index — it has pages for the engine lifecycle, the C++
core, input metadata, sampling, structured output, the worker runtime, the model implementations, the TileLang library,
distributed execution, platform abstraction, testing, and a glossary. Being machine-generated, it occasionally cites
file paths that have since moved (for example it lists attention modules under
model_executor/layers/v4_modules/ where the repo actually has
model_executor/models/v4_modules/), so verify paths against the tree in
section 4.
18 Glossary
Every abbreviation, in one place. Filter it with the search box at the top of the page.
No term matches that search.
| Term | Meaning |
|---|---|
| Batch invariance | A guarantee that a request's output does not depend on which other requests shared its batch. Needed because floating-point reductions change with batch shape. DEEPINFER_BATCH_INVARIANT=1, NVIDIA only. |
| Block table | Per-sequence list mapping logical token ranges to physical KV cache blocks — the page table of the paged cache. |
| CA | Compressed/context attention — the dense-but-compressed attention path, as opposed to the sliding-window and sparse-indexed paths. |
| Candidate blocks | A restricted set of KV blocks produced by one designated layer, within which later layers select their top-k. Saves repeated scoring over the whole context. |
| CANN | Huawei's Compute Architecture for Neural Networks — the Ascend equivalent of CUDA. DeepInfer requires 9.2. |
| CED | Compress-Encode-Decode: V4.1's layer structure where decoder layers derive their KV from the last encoder layer's hidden state rather than computing it. Enables decoder SWA bounded replay. |
| Chunked prefill | Splitting a long prompt's prefill across several steps so it cannot monopolise the engine. |
| Continuous batching | Adding and removing sequences from the running batch every iteration, instead of running fixed batches to completion. |
| CP | Context parallelism — splitting a single sequence across GPUs. DeepInfer implements a "mirror CP" variant. Not used in DeepSeek production. |
| CUDA graph | A recorded sequence of kernel launches replayed as one call, removing per-kernel CPU launch overhead. |
| DeepEP | DeepSeek's expert-parallel communication library: MoE all-to-all dispatch/combine, FP8 dispatch, NVLink expert-weight exchange. |
| DeepGEMM | DeepSeek's matrix-multiply kernel library: FP8/FP4/BF16 GEMM, Mega MoE, MQA indexer scoring, HC operators, JIT-compiled. |
| DeepJIT | The runtime kernel compiler used by DeepGEMM and DeepEP, so installation requires no CUDA compilation. |
| DeepSelect | DeepSeek's specialised top-k kernel, 2–20× faster than torch.topk for the indexer and sampler shapes. |
| DFlash | DeepSeek's draft-model implementation for speculative decoding, with its own CUDA graph runner and a confidence head. |
| DP | Data parallelism — independent replicas handling different requests. In V4.1, attention is data-parallel with #DP = #EP. |
| DSA | DeepSeek Sparse Attention — attending only to the top-scoring blocks chosen by a lightweight indexer, instead of the full context. |
| DSpark | The confidence-scheduled, variable-length verification layer on top of speculative decoding. The novel part. |
| E4M3 / E2M1 | FP8 and FP4 formats named for their exponent and mantissa bit counts. |
| Engram | An external n-gram memory: huge hash-indexed embedding tables, sharded across ranks, gathered and gated into the hidden state. |
| EP | Expert parallelism — splitting MoE experts across GPUs so each holds a slice. |
| EPLB | Expert-Parallel Load Balancing — redundant copies of hot experts plus periodic re-placement, measured by moe_lb. |
| EPD | Encoder / Prefill / Decode disaggregation — running the three phases on separate pools of machines. |
| FlashMLA | DeepSeek's attention kernel library: sparse prefill and decode with FP8/FP4 KV, plus the fused norm→RoPE→attn→RoPE→cast kernel. |
| FTP | First token position — the index of a request's first packed row in a flattened batch. A negative value marks CUDA-graph padding. |
| GDR | GPUDirect RDMA — moving data into GPU memory over the network without consuming SMs, used for KV swap-in because the CUDA copy engine handles fine-grained block copies poorly. |
| Golden tokens | Forced token IDs for teacher-forcing, used by the evaluation and YoCo equivalence harnesses. |
| HC / mHC | (manifold) HyperConnection — replacing the single residual stream with hc_mult parallel ones, mixed with Sinkhorn-normalised coefficients. |
| LCFS | Last-come-first-served — the preemption order; the shortest-output sequences are evicted first so the most-invested requests survive. |
| Mega MoE | DeepGEMM's fused MoE path with overlapped communication. The general choice inside a scale-up domain. |
| MFU | Model FLOPs Utilisation — achieved FLOPs over hardware peak. DeepInfer's estimator deliberately excludes draft-model FLOPs. |
| MLA | Multi-head Latent Attention — compressing K and V into a shared low-rank latent so the KV cache shrinks dramatically. |
| moe_lb | The MoE load-balance statistic: slowest rank's load ÷ mean. 1.0 is perfect; production sees 1.045–1.090. |
| MQA | Multi-Query Attention — many query heads sharing one key/value head. Used by the lightning indexer because it is cheap. |
| MTP | Multi-Token Prediction — predicting several future tokens at once; the basis of speculative decoding here. Depth set by DEEPINFER_USE_NEXTN_DECODE. |
| MXFP4 | 4-bit floating point with per-32 UE8M0 scales. Used for indexer KV and MoE routed-expert weights. |
| nextn | Shorthand for the speculative draft depth. Default 5; the published decode chart uses 3. |
| Paged KV cache | Storing attention KV in fixed-size blocks with a per-sequence block table, eliminating fragmentation and enabling prefix sharing. |
| Pass@1 | Accuracy of a single sampled attempt, averaged over samples and tasks. The eval harness's headline metric. |
| PDL | Programmatic Dependent Launch — a CUDA feature letting a kernel begin setup before its predecessor fully drains. Enabled for DeepGEMM and TileKernels. |
| Prefill / Decode | The two phases: reading the prompt (compute-bound, parallel) and generating tokens (bandwidth-bound, serial). |
| Preemption | Evicting a running sequence when KV blocks run out, freeing its memory. |
| RoPE / YaRN | Rotary position embedding, and the YaRN interpolation scheme that extends it to longer contexts than training used. |
| SASS | NVIDIA's native GPU assembly. The Q&A notes public FlashMLA lacks one hand-tuned SASS optimisation. |
| Slot mapping | A flat array telling the store kernel the physical cache slot for each newly-computed token. |
| SM100 | NVIDIA's Blackwell-generation streaming multiprocessor architecture — the only supported NVIDIA target. |
| SO | Structured Output — constraining generation to a regex, JSON Schema or EBNF grammar via a per-step token bitmask. |
| State cache / StateBlock | The second cache: one fixed-size slot per sequence holding SWA ring buffers and compression accumulators, split into stored and dropped regions. |
| SWA | Sliding Window Attention — attending only to a fixed window of recent tokens (128 in V4.1), making those layers' cost constant. |
| SWA bounded replay | Rebuilding a missing sliding-window cache by replaying only the last 128 tokens with a truncated window, at constant cost. |
| Symmetric memory | Memory mapped at matching addresses across ranks, letting a GPU read a peer's buffer directly. Used to shuffle expert weights within a node. |
| TBT / TPOT | Time Between Tokens / Time Per Output Token — the decode latency metric users feel. |
| TileLang | A multi-backend DSL for GPU kernels. How DeepInfer supports NVIDIA and Ascend from (mostly) one source style. |
| TMA | Tensor Memory Accelerator — NVIDIA's async bulk-copy engine. Scale-factor layouts are TMA-aligned to use it. |
| TP | Tensor parallelism — splitting individual weight matrices across GPUs. Notably not used in DeepSeek production. |
| TTFT | Time To First Token — the prefill-dominated latency metric. |
| UE8M0 | A scale format with 8 exponent bits and no mantissa — scales are powers of two, so applying one is an exponent add. Four pack into an int32. |
| Userbuffer | Process-wide symmetric communication buffers used for EP all-to-all, CP all-gather and expert shuffling. |
| Wide-EP | Expert parallelism at 64–128 GPUs, the decode configuration. Cuts per-GPU expert memory traffic at the cost of communication and a larger failure domain. |
| xgrammar | The grammar-automaton library behind structured output. Pinned at 0.2.2. |
| YoCo | "You Only Cache Once" — the producer/consumer layer split plus its prefill truncation modes (mask / compact). |
| ZOS | Zero-Overhead Scheduling — overlapping CPU scheduling and post-processing with GPU execution so the GPU never idles. |
| 3FS | Fire-Flyer File System — DeepSeek's distributed file system; the storage layer under the production KV cache. |
19 Sources
Everything in this page is traceable to one of these. Primary sources are the repository itself, which I cloned and read; secondary sources are marked.
Primary — the code and its documents
- github.com/deepseek-ai/DeepInfer — the repository (read at commit
9bd9cf2) - README.md — features, hardware, build, the "not included" list
- doc/inference_deployment_qa.md — the production deployment Q&A
- examples/README.md — benchmark methodology
- eval/README.md — evaluation harness and reference scores
- examples/yoco/README.md — YoCo equivalence verification
- CONTRIBUTING.md — formatting, test markers, directory conventions
Primary — the sibling repositories
- FlashMLA — attention kernels
- DeepGEMM · DeepGEMM-Ascend — matrix multiply
- DeepEP · DeepEP-Ascend — expert-parallel communication
- DeepSelect — top-k
- TileKernels — the TileLang kernel library
- deepseek-recipe — protocol conversion and tokenisation
- DeepSpec — draft-model training and evaluation
- 3FS — the distributed file system
- DeepSeek-V4.1-Flash-1017 — the model card and benchmark scores
Secondary — independent write-ups
- deepinfer.net — independent, fan-made field notes. Not affiliated with DeepSeek; states it did no hands-on testing; carries sponsored placements. Pages used: architecture, release, benchmarks, limits, FAQ, name, production gap, and the ten comparison posts.
- deepwiki.com/deepseek-ai/DeepInfer — auto-generated code wiki. Useful as an index; some file paths are stale.