DeepInfer Explained field guide

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.

In one paragraph A trained AI model is just a huge pile of numbers. It cannot do anything by itself. Something has to load those numbers onto chips, take your text, feed it through the numbers, pick the next word, and do that for thousands of users at once without wasting a cent of electricity. That "something" is an inference engine. DeepInfer is DeepSeek's inference engine for their DeepSeek-V4.1-Flash model. It is the thing that makes the model fast and cheap, not the thing that makes it smart.
Oct 20, 2026
Released publicly, Apache-2.0
1 model
DeepSeek-V4.1-Flash only
2 chips
NVIDIA SM100 · Huawei Ascend 950
~342 files
Python engine + C++ core + kernels
155T
tokens/day on one production cluster
US$38
revenue per GPU-hour (valley pricing)
3 gaps
KV store, P/D split, load balancers not shipped
0 servers
No HTTP API — offline engine only

What this guide covers

Three things that are easy to confuse

LayerWhat it isIs 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.
Read this before you plan a deployment DeepInfer is an offline inference engine. You import it as a Python class, hand it a list of prompts, and get tokens back. There is no HTTP server, no OpenAI-compatible endpoint, no KV-cache cluster, and no load balancer in the box. The README itself lists those three as "required for a complete inference service (not included)". If you need an API this week, a general framework like vLLM or SGLang is the lower-risk path.

Who should read which part

"I just want to understand it."

Read sections 2 and 3, then skim the concept cards in section 6. That is the whole story in about 20 minutes.

"I'm going to read the code."

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.

"I'm evaluating it against vLLM/SGLang."

Sections 14, 16 and 17: the published numbers, the missing pieces, and the comparison write-ups with their caveats.

"I run the hardware."

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:

① PREFILL — read the prompt All prompt tokens go through the model at once …128k One big parallel matrix job → the GPU's math units saturate. Bottleneck: raw compute (FLOPs) Cost grows with prompt length. Output: the KV cache — the model's "notes" on your prompt. Metric that matters: time to first token (TTFT). ② DECODE — write the answer One token per step, each needing the one before it …one at a time Tiny math, but the whole model must be re-read from memory each step. Bottleneck: memory bandwidth Cost is roughly constant per step, ~1000× more steps. Needs: big batches, so one memory read serves many users. Metric that matters: time between tokens (TBT/TPOT). Because the two phases want opposite hardware, DeepSeek runs them on separate machines in production ("P/D disaggregation").
The single most important diagram: prefill is compute-bound and parallel; decode is memory-bound and serial.
Analogy Prefill is a student reading a 300-page case file and taking notes — slow, heavy, but they can read many pages in parallel. Decode is the same student then writing the answer one word at a time, re-checking all the notes before each word. Reading is throughput work; writing is latency work. The entire engineering effort is: make the reading cheap, keep the notes small, and never let the writer sit idle.

Where every DeepInfer feature fits

FeatureAttacksThe idea in one sentence
FlashMLA / DeepGEMMbothHand-written chip instructions so each math operation runs at ~95% of what the silicon physically allows.
MoE + Wide-EPdecodeOnly a few "experts" of the model run per token; spread them over 64–128 GPUs so each GPU reads less memory per step.
EPLBdecodeSome experts are more popular; copy the hot ones to extra GPUs so no single GPU becomes the straggler.
DSpark / MTPdecodeA small draft model guesses 5 tokens ahead; the big model checks them all in one step, so one memory read yields several tokens.
Confidence headdecodePredict which guesses are likely right, and only pay to verify those.
Async scheduling (ZOS)decodeDo the CPU bookkeeping for step N+1 while the GPU is still busy with step N, so the GPU never idles.
CUDA graphsdecodeRecord the whole step once and replay it, removing thousands of tiny CPU launch calls.
Paged KV cachebothStore the notes in fixed-size blocks like OS virtual memory, so memory never fragments.
Prefix caching + 3FSprefillIf someone already read this prompt prefix, reuse their notes from a 100-PiB SSD cluster instead of re-reading.
SWA bounded replay / YoCoprefillWhen only the recent-window notes are missing, rebuild just the last 128 tokens instead of the whole window.
FP8 / FP4 quantizationbothStore and multiply numbers in 8 or 4 bits instead of 16, halving or quartering memory traffic.
Sparse indexer (DSA)bothFor long contexts, only attend to the few hundred most relevant past blocks instead of all of them.
Chunked prefillprefillSlice a 128k prompt into pieces so one huge request can't freeze everyone else.
Batch invarianceneitherNot 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:

Entrypoint — deepinfer/entrypoints/llm.py Python API: LLM(...).generate(prompts, SamplingParams) → text Engine — engine/llm_engine.py + csrc/engine/scheduler.hpp Who runs this step? Python drives the loop; C++ does the per-token bookkeeping, block allocation, preemption and the variable-length draft schedule. Worker & model — worker/ + model_executor/models/ One process per GPU. Loads weights, owns the KV cache, builds the input tensors, runs forward + sample, replays CUDA graphs. Kernels — tilelang_ops/ + vendored FlashMLA · DeepGEMM · DeepEP · DeepSelect · TileKernels The actual machine code that runs on NVIDIA SM100 GPUs / Huawei Ascend 950 NPUs.
Four layers. The top two decide what to compute; the bottom two decide how fast.

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.

User request (JSON) DeepSeek Recipe protocol ⇄ tokens (Rust) tokens + sampling params Entrypoint Vision Encoder LB Prefill Load Balancer Decode Load Balancer Vision Encoder 1 GPU per instance images → embeddings Prefill pool 8-GPU machines · EP2–8 / DP2–8 Expert-Parallel Load Balancer compute-bound · reads the prompt Decode pool 64–128-GPU supernodes · EP64–128 (Wide-EP) Expert-Parallel Load Balancer bandwidth-bound · writes the answer KV cache handoff embeddings External KV Cache Storage — 3FS cluster (~100 PiB SSD) 10+2 erasure coding · 3-day TTL agents / 7-day chatbot · GC worker per node Black arrows = request flow. Dashed violet = KV cache movement. Everything below the Entrypoint except the engine itself is NOT in the open-source release.
The production E/P/D disaggregated architecture. DeepInfer open-sources the engines inside the prefill and decode pools; the balancers, the 3FS KV store and the entrypoint are not included.
Why split the pools?

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.

Why a separate storage cluster?

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.

Smallest sensible unit

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.

PYTHON — orchestration C++ (deepinfer._C) — per-token bookkeeping LLMentrypoints/llm.py LLMEngineengine/llm_engine.py · the step() loop Tokenizer / HF loaderhugging_face/ Multimodal processorve/ — images → patches Scheduler csrc/engine/scheduler.hpp picks this step's batch BlockManager block_manager.hpp free-list of KV blocks MTPVarlen mtp_varlen.hpp · how many drafts to verify prepare_tensor_input builds pinned CPU tensors for the GPU step Sequence · SequenceMetadata · RequestOutputPool · FlopsEstimator · ZOS object pools so a 60-request decode step allocates almost nothing schedule() Worker (one process per GPU) — worker/worker.py, worker/base.py Forward(N) → post-process(N−1) → Sample(N). Driver worker runs in-process; CP ranks are spawned children. Owns: CacheEngine (KV + state memory), ModelRunner, CudaGraphRunner, DFlashCudaGraphRunner. Model — model_executor/models/ deepseek_vl → deepseek.py → decoder_layer.py → v4_modules/{attn, mlp(MoE), hc, engram, index_attn, compress} + mtp/dflash.py (draft model) · vision_encoder/vit.py (images) · layers/sampler.py (picks the token) Parallelism helpers: parallel_utils/{parallel_state, cp, userbuffer} · eplb/ (expert balancing) In-tree kernels — layers/tilelang_ops/ ~45 kernels × {cuda/, asc/} backends, chosen at import by ops.py KV index, paged load/store, norms, quant, sampling, metadata Vendored kernels — vendor/ (git submodules) FlashMLA (attention) · DeepGEMM (matmul/MoE) · DeepEP (all-to-all) DeepSelect (top-k) · TileKernels (pip) · Ascend variants of each
The engine. Python drives, C++ books, the worker executes, kernels do the arithmetic.

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.

setup.py
# 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)
Design tell 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.

342 files · ~60k lines · 9 top-level directories

The ten-second version

DirectorySizeWhat lives there
deepinfer/223 filesThe engine. Python: entrypoint, engine loop, workers, the V4.1 model, ~45 TileLang kernels ×2 backends, EPLB, structured output, multimodal.
csrc/33 filesThe C++ core. Scheduler (1 329 lines), block manager, sequence state, input-tensor packing, sampler-input packing, zero-overhead scheduling, graph timers.
vendor/6 submodulesThe kernels. Git submodules: FlashMLA, DeepGEMM, DeepEP, DeepSelect (+ the Ascend ports of DeepEP and DeepGEMM).
examples/18 filesRunnable scripts: offline inference, the benchmark harness, Pareto sweeps, YoCo equivalence checks, structured-output demo.
eval/17 filesAccuracy harness for AIME 2026, GPQA Diamond and HLE, with LLM-judge and rule-based scoring.
tests/20 filespytest suite: batch invariance, sampler, EPLB policy, KL divergence, state-cache ops, CUDA sync guards.
deep_cluster/8 filesHardware discovery without importing torch — NVIDIA via NVML/procfs, Ascend via sysfs, plus NUMA binding.
doc/5 filesThe deployment Q&A (312 lines — the most valuable document in the repo) and the benchmark/architecture images.
scripts/4 filesBuild, 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

examples/offline_inference.py (condensed)
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

1 · LLM.generate() entrypoints/llm.pytokenize, one _add_request per prompt 2 · LLMEngine.add_request() engine/llm_engine.pybuild Sequence, compile SO grammar async, enqueue to waiting 3 · Scheduler.schedule() C++ · prefill or decode this step? csrc/engine/scheduler.hpp NORMAL engine votes across DP ranks (ALL every 70 steps, ANY every 100) allocates KV blocks, preempts by LCFS, sizes the chunk 4 · MTPVarlen.schedule() csrc/engine/mtp_varlen.hpp how many draft tokens per request to verify (throughput-optimal) 5 · prepare_tensor_input_v4 csrc/engine/input_metadata/ pack block tables, positions, slot mapping into pinned CPU tensors 6 · Worker.execute_model_step() worker/worker.py Forward(N) → post-process(N−1) → Sample(N) — the ZOS round 7 · ModelRunner.execute_main_model worker/model_runner.py H2D copy, pick a CUDA graph by batch size, or run eager 8 · DeepseekModel.forward() per layer: mHC → attn → MoE models/deepseek.py + decoder_layer.py MLA + sparse indexer + SWA (FlashMLA) · experts (DeepGEMM + DeepEP) Engram memory lookups · YoCo consumer-layer truncation 9 · Sampler.sample() model_executor/layers/sampler.py logits → SO mask → temp/top-k/top-p → verify drafts → new drafts 10 · post_process (deferred) llm_engine.post_process runs one step late so it overlaps the next forward 11 · build_request_outputs csrc/engine/outputs.hpp pooled RequestOutput objects — released automatically next step 12 · detokenize → your text loop back to 3 until every sequence hits EOS or max_tokens
One decode step. Steps 3–11 repeat for every token of every request, roughly 200 times a second.

The loop, in real code

deepinfer/engine/llm_engine.py — LLMEngine.step() (condensed)
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:

deepinfer/worker/worker.py — Worker.execute_model_step() (condensed)
# 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
The consequence Because the CPU is always one step behind the GPU, the CPU does not yet know how many draft tokens were accepted when it schedules step N+1. DeepInfer solves this by having the CPU fix only the total token budget, and letting a GPU kernel (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:

deepinfer/utils.py · used in worker/model_runner.py
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.

Why it matters here: DeepInfer's deployment Q&A admits that the public FlashMLA lacks one hand-tuned SASS (assembly) optimisation that DeepSeek's internal build has, making the public decoding kernel 5–10% slower — but end-to-end the gap is at most 5%.
In the repo
vendor/FlashMLAThe submodule itself.
layers/attention_backend.pyDeepInfer's thin wrapper: prefill_attn_varlen_sparse() and decode_mla_mqa_sparse(), plus head-count padding to shapes FlashMLA supports.
tilelang_ops/flash_mla_utils.pyconvert_kv_index — turns logical top-k token indices into physical paged-cache slots for FlashMLA.
models/v4_modules/attn.py1 008 lines: the call sites, cross-layer KV sharing, YoCo paths.
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.

Mega MoE is the piece DeepInfer leans on hardest. The Q&A says it is the general solution within a scale-up domain: regardless of EP size or batch size, Mega MoE is significantly faster than an unfused MoE, because it overlaps the expert matrix multiplies with the all-to-all communication instead of doing them in sequence.
In the repo
vendor/DeepGEMMThe submodule (and DeepGEMM-Ascend for NPUs).
v4_modules/mlp.py616 lines — the MoE runtime: grouped GEMM, Mega-MoE weight repacking, EPLB-aware routing.
layers/linear.pybf16_gemm_nt, FP8 cast granularity helpers.
worker/runtime_config.pydeep_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.

In the repo
vendor/DeepEPSubmodule (+ DeepEP-Ascend).
eplb/shuffle.pyMoves expert weights between ranks — NCCL batched P2P, or symmetric-memory IPC within one node.
parallel_utils/userbuffer.pyOwns every process-wide symmetric communication buffer for the loaded model.
worker/runtime_config.pySets 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.

In the repo
vendor/DeepSelectSubmodule, built first by build_vendor.sh.
npu_ops/deep_select.pyThe Ascend shim with a torch.topk fallback.
v4_modules/index_attn.pyThe indexer that consumes it — 575 lines of MQA scoring and candidate-block selection.
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.

tilelang_ops/ops.py — the whole backend-selection mechanism
_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
In the repo
tilelang_ops/ops.pyThe dispatcher above, plus KernelUnavailableError so tests skip kernels a backend lacks.
tilelang_ops/cuda/33 CUDA kernel implementations.
tilelang_ops/asc/31 Ascend implementations of the same interfaces.
TileKernelsThe external library, pinned at 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.

It is not a server. You still supply the HTTP transport, auth, tool execution and the inference backend. Defaults it does supply: max_tokens of 8 192 outside thinking mode, 65 536 in thinking mode, 131 072 in thinking mode at max effort.
Related
hugging_face/tokenizer.pyWhat DeepInfer uses instead for offline runs — loads the HF tokenizer and pins BOS/PAD and multimodal token IDs.
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.

Related
deepseek-ai/DeepSpecTraining/eval for draft models.
models/mtp/dflash.pyThe runtime side: the 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.

None of this is in DeepInfer. The repo's README lists "a KV cache system" as required but not included, and the Q&A explains why: that code is tightly coupled to DeepSeek's internal infrastructure.
Related
deepseek-ai/3fsThe file system (separate repo, MIT).
doc/inference_deployment_qa.mdThe "KV cache storage" section — medium, replication, TTL, hit policy.

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.

In the repo
v4_modules/mla_attn.pyParameter containers: ColumnParallelLora (the A→norm→B chain), SoftmaxAPEGatedColumnParallelLora, MQAIndexer.
v4_modules/attn.pyThe runtime: Q/KV projection, RoPE, KV storage, prefill and decode paths.
v4_modules/compress.pyCompressor — 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.

Vocabulary you'll meet in the code logical expert = one the gate can choose (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.
In the repo
v4_modules/moe.pyParameter containers: FusedExperts, TopKGate, ReplicatedSharedExpert — and the expert-counting rules above.
v4_modules/mlp.py616 lines of runtime: routing, dispatch/combine, grouped GEMM, Mega-MoE repacking.
model_loader.py_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.

In the repo
v4_modules/index_attn.py575 lines — MQA, the indexer runtime, prefill/decode metadata, candidate-block restriction.
v4_modules/cross_layer.pySharedLayerTopology — the share-kv / share-index / share-index-k rules, with a long docstring on the validation.
tilelang_ops/candidate_blocks.pycandidate_block_max and index restoration kernels.
tilelang_ops/get_kv_index.py518 lines of index-building kernels for compressed, SWA and DFlash paths.
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.

In the repo
worker/cache_engine.pyStateBlock — the per-sequence layout, with a long docstring on stored vs. dropped regions.
tilelang_ops/swa_index.pyexpand_swa_index — turns two KV segments per token into dense prefill indices.
tilelang_ops/state_cache.pyselect_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/environ.py
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.
In the repo
v4_modules/yoco.pyThe predicates and the boundary compaction operation.
input_metadata/yoco.pyThe metadata mixin — compact view vs. mask view.
examples/yoco/yoco_equiv.py1 057 lines of equivalence verification between the two modes.
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.

In the repo
v4_modules/engram.pyRuntime: hash n-grams, gather sharded tables across ranks, gate the fused projection.
v4_modules/engram_metadata.pyCompressedVocabulary, EngramLayout, NgramHashMapping.
tilelang_ops/gather_engram_tokens_zos.pyThe decode-time n-gram gather, branching fresh/previous tokens inside the kernel.
hugging_face/weights.py_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.

In the repo
v4_modules/hc.pyHCLayerMixin (the pre/post mixing pair) and HCHead.
v4_modules/mhc_ops.pyTensor ops: prenorm GEMM, split mixes, sinkhorn_normalize.
v4_modules/hc_lazy.pyDependency-free bundle passed between the pre and post halves.
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.

In the repo
vision_encoder/vit.pyThe ViT itself: patch embed, rotary embedding, attention, SwiGLU blocks.
ve/processor/image_processor.pyResize geometry planning — "every rounding here is load-bearing: it decides tokens per image".
ve/multimodal_processor.pyThe worker-process pool that does CPU preprocessing asynchronously.
tilelang_ops/image_embed_ops.pyrgb_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.

In the repo
tilelang_ops/cache_common.pyKVCacheFormat, ScaleFactorFormat, TMA-aligned scale layouts.
tilelang_ops/kv_quant.pyThe quantisation kernels (Ascend variant shown; CUDA path in quant.py).
hugging_face/parameters.pyset_ue8m0_sf — packs scales for ordinary 2-D E4M3 parameters at load time.
model_metadata_utils.pyderive_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.

csrc/engine/block_manager.hpp
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; }
};
In the repo
csrc/engine/block_manager.hpp242 lines — the free list, ref counts, can_allocate, fork/clone support.
input_metadata/block_load_plan.cppTurns a block table into compact contiguous load segments for the cache kernels.
tilelang_ops/load_paged_kv_cache.pyReads those segments back out of the paged cache.
tilelang_ops/store_kv_cache.pyWrites new tokens' KV via the slot mapping.
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.

deepinfer/worker/cache_engine.py — StateBlock layout
# 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
In the repo
worker/cache_engine.py978 lines — KVCache, StateBlock, KVCacheManager, GPUCacheInterface, CacheEngine.
tests/worker/test_state_cache_ops.py736 lines of tests — the largest test file in the repo, which tells you how subtle this is.
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.

Exact reconstruction — what you'd have to do without bounded replay recompute last 2 560 tokens × 20 decoder layers cost grows with the model Bounded replay — what DeepInfer actually does cached prefix — reused as-is, never recomputed or overwritten 128 tok non-cached suffix ↑ replayed: rebuilds SWA only ↑ computed normally Hit policy: block cache gives hit length L1 (a multiple of 128). If an SWA state exists in [L1−128, L1], hit = L1; otherwise back off one block to L1−128 and replay. Max hit for input length L is L − 134 (128 window + 6 backtracking).
Two places it is used. Encoder bounded replay handles a missing SWA cache on a prefix hit. Decoder bounded replay goes further: because CED projects the decoder layers' block KV from the last encoder hidden state, each prefill only recomputes the last 128 tokens in the decoder layers and the result is used for decode only, never entering the prefix cache — which nearly halves total prefill computation.
In the repo
doc/inference_deployment_qa.md"KV cache hits and recomputation" — the authoritative description, including the exact hit policy.
input_metadata/yoco.pyThe truncation metadata that implements the window narrowing.
worker/cache_engine.pyget_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).

Serial — the GPU idles twice per token CPUGPU sched(N) post-proc(N) sched(N+1) forward(N) sample GPU IDLE — the bubble forward(N+1) Overlapped (ZOS) — the CPU runs one step behind, in the shadow of the GPU CPUGPU sched(N+1) launch fwd post-proc(N−1) launch sample forward(N) sample(N) forward(N+1) sample no gaps
The cost of the trick. Scheduling step N+1 before step N's result is known means the CPU's idea of each sequence's length is stale. DeepInfer fixes this on the GPU: 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.
In the repo
csrc/engine/zos.cpp307 lines — builds the remap and pinned CPU inputs.
worker/zos_status.pyWorkerStepState and MetadataZOS — the state carried one step forward.
input_metadata/prepare_tensor_input_zos.py382 lines of pinned-buffer preparation for ZOS decode.
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.

In the repo
worker/cuda_graph_runner.pyCapture, select(total_q), replay, and the batch-size ladder.
worker/dflash_cuda_graph_runner.pyThe same for the draft model.
worker/decode_perf_cache.pyPersists the timing curve, keyed by a hash of model + parallel config, with a TTL.
worker/model_runner.pyprofile_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.

csrc/engine/scheduler.hpp — the DP voting logic
// 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.
In the repo
csrc/engine/scheduler.hppThe whole thing: _schedule, _schedule_prefill, _schedule_decode, preemption, chunk sizing, image boundaries.
csrc/engine/sequence.hpp419 lines — Sequence, SequenceMetadata, MtpState, status enum, stopping criteria.
csrc/engine/flops_estimator.hppEstimates FP8-equivalent FLOPs per step so the logger can report MFU; deliberately excludes draft-model work.
engine/engine_type.pyNORMAL (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.

In the repo
models/mtp/dflash.py392 lines — the DFlash module and ConfidenceHead.
csrc/engine/mtp_varlen.hpp275 lines — the throughput-maximising batch-size search and per-request draft allocation.
tilelang_ops/schedule_mtp_varlen.pyThe GPU-side redistribution used under ZOS, when the CPU doesn't yet know the confidences.
layers/mtp_info_pool.pyStable GPU storage for draft proposals, indexed by scheduler slot; slot 0 reserved for padding.
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.

A small but careful detail Rejection sampling can, in theory, fail to produce any token when the residual distribution is degenerate. The kernel applies a tiny penalty 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.
In the repo
layers/sampler.py683 lines, with a docstring that walks the whole prefill/decode/MTP flow.
tilelang_ops/fused_sample.py832 lines — temperature, top-p/top-k, log-softmax and sampling in one kernel.
tilelang_ops/random_sampling.pySampling that is deterministic in (seq_seed, position) and independent of batch size or row order.
csrc/engine/prepare_sample.cppPacks the per-request sampling parameters into the kernel's input structs.
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.

deepinfer/worker/runtime_config.py
if envs.DEEPINFER_BATCH_INVARIANT or PLATFORM.is_npu:
    deep_gemm.use_deterministic_algorithms(True)
    tile_kernels.use_deterministic_algorithms(True)
In the repo
tests/models/test_batch_invariant.pyProves results match across batch compositions.
tests/models/test_ve_batch_invariant.pyThe same for the vision encoder.
sampling_params.pyseq_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.

examples/_structured_output.py — all three schema types
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.
In the repo
structure_output/matcher.py347 lines — the xgrammar matcher, KMP trigger matching, async futures.
structure_output/so_matcher_builder.pyThe CPU-pinned compile pool, built lazily so SO-free deployments don't pay for it.
tilelang_ops/structure_output.pyapply_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.

This is why 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.
In the repo
csrc/engine/outputs.hppRequestOutput and RequestOutputPool.
csrc/utils/object_pool.hppThe generic pool base.
engine/llm_engine.pyDeferredRelease — 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.

In the repo
deepinfer/timer.py295 lines — all three timers.
csrc/graph_timer/graph_timer.hppEvent-record nodes inside an instantiated CUDA graph.
engine/stats_logger.py477 lines of windowed aggregation and logging.
engine/profile_server.pyThe per-engine HTTP profiling endpoint (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.

Why Wide-EP helps decode specifically Decode is memory-bandwidth-bound and the expert weights dominate the traffic. Spreading 384 experts over 64 GPUs instead of 8 means each GPU reads one-eighth as much expert weight per step, so steps get faster — provided the extra all-to-all communication (DeepEP's job) and the load imbalance (EPLB's job) don't eat the gain. The Q&A ships an EP8-vs-EP64 chart showing the difference.
In the repo
parallel_utils/parallel_state.pyBuilds and caches every process group: DP, EP, CP, engram, CPU variants.
parallel_utils/cp.pyCPManager — all-gather sessions with reused buffers.
tilelang_ops/prepare_cp_mirror.pyMirror-CP index preparation, including the YoCo CP boundary.
config.pyParallelConfig — 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:

StageRedundant experts/GPUStats windowReassign everyMeasured moe_lb
Prefill4 (start)100 steps1 000 steps1.074 (EP4DP4) / 1.045 (EP2DP2)
Decode1 (start)1 000 steps10 000 steps1.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.

In the repo
eplb/__init__.py434 lines — ExpertLoadBalancer, per-layer state, the manager, resolve_num_extra_experts.
eplb/policy.pyrebalance_experts_global — assign replicas to hot experts, then greedily balance all replicas across ranks.
eplb/shuffle.pyThe in-place weight migration: NCCL batched P2P, or symmetric-memory IPC inside a node.
tests/models/test_eplb_policy.pyPolicy tests; 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.

How much do they buy? The Q&A's own answer: production reaches roughly 80% of a perfectly balanced offline benchmark. Which also means the published Pareto charts are an upper bound, not a prediction of what a real cluster delivers.
Where to read about it
doc/inference_deployment_qa.md"Data-parallel load balancing" — the only description that exists.
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.

Where to read about it
doc/inference_deployment_qa.md"KV cache storage" — the medium/replication table and the overlap strategy.
worker/cache_engine.pyselect_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.

Where to read about it
doc/inference_deployment_qa.md"Autoscaling & fault tolerance".
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.

In the repo
examples/sweep/run_sweep.shThree modes: decode (sweeps requests/rank), prefill (sweeps chunk size), encoder (sweeps images/batch).
examples/benchmark.py588 lines — decoupled prefill/decode staging, steady-state windows, profiler sections.
examples/README.mdThe measurement methodology, in unusual detail — read this before quoting any number.

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

LibraryDelivered asOwnsHeadline performance
FlashMLAsubmoduleAttention: sparse prefill, sparse decode with FP8/FP4 KV, the fused norm→RoPE→attn→RoPE→cast kernel, dense MHA1 460 TFLOPS prefill / 950 TFLOPS decode on B200 (fused kernel); 410 / 360 TFLOPS on Ascend 950 (95% / 83% of peak)
DeepGEMMsubmoduleAll matrix multiplies: FP8/FP4/BF16 GEMM, Mega MoE with overlapped comms, MQA indexer scoring, HC operatorsUp to 1 550 TFLOPS on H800; runtime-compiled by DeepJIT, so no CUDA build at install
DeepEPsubmoduleMoE all-to-all dispatch/combine (incl. FP8 dispatch), NVLink weight exchange for redundant experts, experimental PP/CP/DP collectivesAscend: 313–375 GB/s dispatch from EP8 to EP128; ~90–95% of physical payload bandwidth up to EP32
DeepSelectsubmoduleTop-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
TileKernelspip (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 repoEngine 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
Honest admission in the Q&A Asked whether DeepSeek has better internal versions of DeepGEMM/FlashMLA, the answer is: broadly the same; the indexer and mega series differ somewhat; public FlashMLA lacks one hand-tuned SASS optimisation making its decoding kernel 5–10% slower — but end-to-end performance is at most 5% apart. That is an unusually specific and unflattering disclosure to publish.

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.

GroupKernelsWhat they do
KV indexingget_kv_index, kv_index_cat, swa_index, candidate_blocks, flash_mla_utilsTurn 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/Oload_paged_kv_cache, store_kv_cache, cache_common, state_cache, kv_quantRead and write the quantised paged cache and the circular state cache, including scale-factor layouts (UE8M0, TMA-aligned).
Normalisation & transformsnorm, fused_transform, rstd_1ulp, compressRMSNorm with fused residual-add and FP8 cast; the big fused norm→RoPE→Hadamard→cast→store kernel; compression state updates.
Samplingfused_sample, sampler, random_sampling, sum_smaller_probs, structure_outputTemperature/top-k/top-p/log-softmax in one pass, draft verification metadata, batch-order-independent random sampling, grammar bitmask application.
Speculative decodingdflash, dflash_copy, store_mtp_info, schedule_mtp_varlenFused confidence-head sigmoid, draft input packing for CUDA graphs, MTP pool writes, and the GPU-side draft-length redistribution.
ZOS correctionscompute_seqlens_k_zos, gather_input_ids_zos, gather_engram_tokens_zosFix the stale CPU view of sequence lengths and input IDs using the previous step's GPU results.
Parallelismprepare_cp_mirror, prepare_v4_decode_metadata, gatherMirror-CP index preparation, whole-decode metadata computed on the GPU, row gather with zero-fill.
Multimodalimage_embed_opsrgb_pad_to_patches, replace_input_embeds, batch_downsample.
Debugoutlier_detectionQueues 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_ops/cuda/sum_smaller_probs.py
@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
Why this is a nice codebase to learn from Every kernel module ships (a) the TileLang implementation, (b) a plain-PyTorch reference called *_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

WhatLives onReplicationManaged by
In-flight block KV + state KVInference GPU HBM—The prefill / decode engines themselves (this repo)
SWA KV cacheLocal host memorySingle copy, never on 3FSEngines; cross-machine hits pull over a 400G VLAN using an in-memory directory sharded across entrypoints
Paged block KV (persistent)Dedicated 3FS clusterNone at app level — relies on 3FS's 10+2 erasure codingkvcache-store (not open-sourced)
~100 PiB
SSD for KV cache on 3FS
14.4 TB
logical KV per GPU, in practice
20 × 30.73 TB
SSDs per recommended storage server
40 / 5 GB/s
min read / write per storage server
10+2
erasure coding — survives 2 node failures
3 / 7 days
TTL for agent / chatbot traffic
400 Gbps
interconnect budget per 8 GPUs
1.41 MB
SWA state persisted per sequence

Inside one GPU

Model weights — FP8 / FP4, loaded once from safetensors hugging_face/weights.py Paged block KV cache — grows with context … thousands of 128-token blocks … Block table per sequence maps logical position → physical block. Free list is O(1). Block 0 = null sentinel. State cache — one fixed-size StateBlock slot per sequence STORED: SWA ring buffers (first 20 layers in V4.1) DROPPED: YoCo consumer SWA compression state state_size_to_disk = just the stored region → persisting is a length truncation, not a gather. Slot count = max_num_seqs × DEEPINFER_NUM_STATE_SLOTS_RATIO (1.2) + 1 CUDA graph buffers + userbuffers (symmetric memory for EP all-to-all and expert shuffling)
GPU memory, top to bottom. 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:

deepinfer/engine/llm_engine.py — _init_cache()
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
If you enable CUDA graphs and hit 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

Decode writes to disk even though it never reads from it

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.

Store SWA only on 128-token boundaries

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.

No TTL logic in the cache store at all

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.

Shorter TTL has a measurable price

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.

EP8 — eight GPUs, 48 experts each GPU 048 experts GPU 148 experts GPU 248 experts … ×8 Each GPU reads 48 experts' weights every step → high memory traffic, slow decode steps. But: little communication, simple failure domain. EP64 (Wide-EP) — sixty-four GPUs, ~6–7 experts each GPU 06 exp GPU 16 exp GPU 26 exp GPU 36 exp GPU 47 exp … ×64 Each GPU reads ~⅛ as much → much faster steps. Cost: a full all-to-all every layer (DeepEP), and one dead GPU kills the whole 64-GPU instance. ↑ holds a redundant copy of a hot expert (EPLB) Counts are rounded so total physical experts divide evenly by ep_size. At EP64 that lands on 7 per GPU after rounding, excluding the shared expert. The gate still chooses from the same 384 logical experts either way.
Why decode wants to be wide and prefill does not.

DeepSeek's recommended settings

StageParallelismHardwareRule of thumb
PrefillEP2–8 / DP2–8ordinary 8-GPU machinesUse the smallest EP/DP that HBM allows. Prefill is compute-bound; spreading experts only adds communication.
DecodeEP64–128 / DP64–12864–128-GPU supernodesUse the largest EP/DP the GPUs allow. This is Wide-EP, and it is where the throughput comes from.
Vision encoder1 GPU per instancecheaper GPUs recommendedPure single-device compute; no parallelism needed.
TP / CPnot used—Neither tensor nor context parallelism is used in production today, though CP exists in the code and the examples.

Running it yourself

examples/ — the three parallelism shapes
# 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
How the processes are laid out One engine per data-parallel rank, spawned by 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.

FileDoes
deep_cluster/hardware/nvidia.pyDiscovers NVIDIA devices via procfs/sysfs; reads compute capability through NVML without creating a CUDA context.
deep_cluster/hardware/ascend.pyThe same for Ascend, via device files, ubcore sysfs and npu-smi for NUMA nodes.
deep_cluster/hardware/device.pyThe neutral API: accelerator_kind(), visible_devices(), get_bf16_tflops_limit(), is_sm_major_10().
deep_cluster/topology/bind.pyNUMA 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.

Fixed-length verification (nextn = 5) req A 3 accepted, 2 wasted req B 1 accepted, 4 wasted — but it cost a full slot anyway Every request always pays for 5 verification slots, however unlikely its drafts are. DSpark — confidence-scheduled, variable length req A .94 .88 .71 .40 → gets 4 slots req B .66 .21 → gets 2 slots; the other 3 go to requests likelier to use them The confidence head predicts each draft's acceptance probability. The scheduler sorts all drafts across all requests by that probability and picks the prefix subset that maximises expected throughput against the profiled timing curve. Result: ~30% fewer draft tokens verified per step, with essentially unchanged accepted length.
DSpark spends verification budget where it will pay off.

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 lenDynamic verified lenFixed-5 acceptedDynamic acceptedFixed-5 tok/s/rankDynamic tok/s/rankGain
165.004.443.323.244 8914 816−1.5%
325.003.653.253.178 9649 111+1.6%
485.003.943.303.2612 03212 467+3.6%
60 ← production point5.003.583.303.2113 73014 466+5.4%
725.003.243.293.1214 96215 961+6.7%
965.003.243.303.1516 81718 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?"

csrc/engine/mtp_varlen.hpp — MTPVarlen::schedule (condensed)
// 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;
The timing model is self-tuning _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.
The round-counting subtlety Under async scheduling the confidence from step N hasn't come back when step N+1 is scheduled. So the code tracks 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

environment
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.

EngineTypeDoesAsync scheduling defaultNotes
NORMALPrefill and decodeonThe default; must vote with DP peers each step to stay in lockstep.
PROMPTPrefill onlyoffAlways returns a prefill result, even an empty one. Asserts it never decodes.
COMPLETIONDecode onlyonGC 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:

Each prefill step gets a time budget 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:

reproducible inference
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)
Batch invariance alone

Gives you identical logits regardless of who shares your batch. Sampling is still random.

+ fixed seq_seed

Gives you identical tokens. The sampling kernel is deterministic in (seed, position), independent of batch size and row order.

+ MTP varlen off

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.

With YoCo truncation enabled, the code explicitly notes that preemption is allowed to change results — because the replayed suffix after a restart may differ. If you need bit-exact reproducibility, you need headroom so preemption never happens.

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

RequirementNVIDIAHuawei Ascend
AcceleratorSM100 GPUs (Blackwell-class)Ascend 950 NPUs
ToolkitCUDA 13CANN 9.2
OS / compilerLinux · GCC ≥ 13.0 · Python 3.12 (exactly — the project pins it)
PyTorchtorch==2.13.0+cu132torch==2.13.0+cpu + torch-npu==2.13.0rc1
EnvironmentA clean conda env or virtualenv — setup.py refuses to build without CONDA_PREFIX or VIRTUAL_ENV set
Neither an H100 nor an RTX 4090 is on the list. This is Blackwell-generation data-centre hardware or Ascend 950, and nothing else. There is no CPU fallback and no consumer-GPU path.

Build

from a clean Python 3.12 environment
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:

  1. pip install -r requirements-nvidia.txt (or -ascend) — pulls torch, tilelang, tile-kernels, xgrammar, transformers, flash-attn-4 and friends.
  2. On NVIDIA only: force-reinstalls nvidia-nccl-cu13==2.32.3. DeepEP needs that exact NCCL.
  3. 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.
  4. scripts/build.sh build — compiles the deepinfer._C extension into build/<accelerator>-<arch>/, symlinks the .so into deepinfer/_so/<tag>/, and byte-compiles the package.
Nice touch Build artefacts are stored per platform tag (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

Makefile
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

offline inference
# 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

examples/README.md — NPU notes
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.

VariableDefaultWhat it does
DEEPINFER_ACCELERATORnvidiaPlatform selector: nvidia or ascend. Must match the build and stay exported at runtime.
DEEPINFER_ZERO_OVERHEAD_SCHEDULINGper engine typeAsync scheduling. On by default for NORMAL and COMPLETION engines, off for PROMPT.
DEEPINFER_ENFORCE_EAGER0Disable CUDA graphs entirely. Slower, but far easier to debug.
DEEPINFER_CUDAGRAPH_BATCH_SIZE_INTERVAL8Spacing 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_INTERVAL0 · 8The same two knobs for the draft model's separate graph runner.
DEEPINFER_DECODE_PERF_CACHE_DIR · _TTL_SECunset · 3600Where to persist the startup decode-timing curve, and how long it stays valid. Saves profiling time on restart.
DEEPINFER_PREFILL_ATTEMPT_INTERVAL70Decode steps between "ALL" prefill votes — every DP rank must have prefill work for the group to switch.
DEEPINFER_PREFILL_ANY_ATTEMPT_INTERVAL100"ANY" backstop interval: one rank with work forces a group prefill, so an isolated rank can't starve.
DEEPINFER_PREFILL_MAX_SUM_SEQLEN_KunlimitedCaps the total K length in a prefill batch. Unset means no limit (passed to C++ as 2³⁰).
DEEPINFER_JIT_CACHE_DIRunsetRoot 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_SMS32 · 0How many streaming multiprocessors communication may consume. Trade-off against compute.
DEEPINFER_GLOO_TIMEOUT14 400 sCPU process-group timeout. Four hours, because weight loading across many nodes is slow.
DEEPINFER_GC_INTERVAL_STEP1000Manual garbage collection cadence (GC is disabled outright in workers). ×10 for completion engines.
DEEPINFER_OVERRIDE_NUM_EXTRA_EXPERTS0Lower bound on redundant experts. The engine rounds total experts up to divide by ep_size; examples set 32.
DEEPINFER_EXPERT_COUNT_WINDOW_SIZE100How many steps of per-expert token counts to keep. Smaller saves GPU memory when EPLB is off.
DEEPINFER_EXPERT_REARRANGEMENT_STEP_INTERVAL0 (off)Steps between EPLB replacements. Production: 1 000 for prefill, 10 000 for decode.
DEEPINFER_REARRANGE_AFTER_PREFILL_IN_NORMAL_ENGINE0Rebalance experts right after a prefill step in a NORMAL engine.
DEEPINFER_MOE_LB_STAT_INTERVAL128How 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_BALANCE0Synthetic imbalance / forced perfect balance — for testing EPLB, not production.
DEEPINFER_SHOW_EPLB_DETAIL0Verbose per-expert placement logging.
DEEPINFER_REARRANGE_P2P_GROUPS1How many P2P groups the expert-weight shuffle splits into.
DEEPINFER_USE_NEXTN_DECODE5Maximum draft length. 0 disables speculative decoding entirely.
DEEPINFER_ENABLE_MTP_VARLEN0Turns on DSpark: confidence-scheduled variable-length verification. Must be off for deterministic sampling.
DEEPINFER_MAX_BACKTRACKING_TOKENSnextn+1How far a rejected draft may rewind. Forced to at least nextn + 1.
DEEPINFER_DFLASH_CONFIDENCE_HEAD_TEMPERATURE1.0Calibrates the confidence head. Accepts a comma list — one value per draft slot — which must match nextn.
DEEPINFER_YOCO_PREFILL_TRUNCATION_MODEoffoff · mask (narrow SWA visibility, keep all queries) · compact (actually drop truncated queries, saving compute).
DEEPINFER_YOCO_PREFILL_KEEP_TOKENS0Suffix length kept when truncating. Must be ≥ window + backtracking. V4.1 default is 140.
DEEPINFER_NUM_STATE_SLOTS_RATIO1.2State slots = max_num_seqs × ratio + 1. Headroom for forks and in-flight sequences.
DEEPINFER_PREFILL_STORE_EXTRA_STATE · _SIZE0 · 128Store extra SWA beyond the inference window, so a consumer can attach at a different boundary.
DEEPINFER_INDEX_ATTN_MEMORY_LIMIT10 GiBMemory cap for sparse-indexer intermediates.
DEEPINFER_ENGRAM_PAD_TOKEN · DEEPINFER_ENGRAM_FORCE_GPU_STORAGE2 · autoEngram padding token; force the n-gram tables into GPU memory (always true on Ascend).
DEEPINFER_SAMPLER_FORCE_TOPK0Restrict softmax/sampling to the top-k logits. Examples use 4 096; benchmark uses 1 024. Big saving on a 129k vocabulary.
DEEPINFER_DECODE_TOKEN_BLACKLISTemptyToken IDs that may never be emitted, as a 0..3,7,10..12 range string.
DEEPINFER_COMPUTE_SUM_SMALLER_PROBS0Also compute the inclusive CDF at the sampled token — useful for analysis, costs a kernel.
DEEPINFER_XGRAMMAR_TIMEOUT · _CACHE_LIMIT_BYTES · _DISABLE_CACHE · _DEBUG20 s · 10 GiB · 0 · 0Structured-output grammar compilation: time limit, compiled-grammar cache size, disable, debug.
DEEPINFER_SO_BUILDER_CPUS4Worker threads for async grammar compilation, pinned to idle CPUs.
DEEPINFER_BATCH_INVARIANT0Deterministic reduction order so results don't depend on batch composition. NVIDIA only.
DEEPINFER_DISABLE_SYNC_GUARD0Turn off the "no CPU-GPU sync inside forward" guard — needed if you add a print to the forward pass.
DEEPINFER_LOGGING_INTERVAL_STEP100Steps between stats log lines. 0 silences it (the benchmark does this during measurement windows).
DEEPINFER_PROFILE_HTTP_ENABLE · _PORT_BASE0 · 6070Per-engine HTTP endpoint to capture a torch profiler trace on demand. Port = base + driver GPU id.
DEEPINFER_TORCH_PROFILER_DIR/tmpWhere traces land.
DEEPINFER_PROFILE_TORCH_CUDA_MEMORY · DEEPINFER_ENABLE_MEMORY_TRACINGunset · 0Record CUDA memory history (100k entries) for leak hunting.
DEEPINFER_NCCL_MEMORY_SAMPLE_INTERVAL_STEPS1000How often to snapshot NCCL communicator memory.
DEEPINFER_RINGBUFFER_WARNING_INTERVAL60 sRate limit on shared-memory ring-buffer congestion warnings.
DEEPINFER_USE_MULTIMODAL_POOL · _POOL_SIZE_GB · DEEPINFER_MULTI_MODAL_BUFFER_SIZE · _WORKER_TIMEOUTper type · 8 · 512 · 1800The shared-memory pool that carries preprocessed images from worker processes into the engine.
DEEPINFER_HF_MODEL_DIRunsetPoints 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:

ArgumentDefaultEffect
expert_parallel_size1EP degree. Must divide model_parallel_size × data_parallel_size.
data_parallel_size00 means derive it as max(1, ep // cp).
context_parallel_size1Split one sequence across this many devices.
gpu_memory_utilization0.90Fraction of HBM for weights + activations + KV. Lower it if CUDA graphs OOM.
max_num_seqs256Max concurrent sequences. Also sizes the state cache and the CUDA-graph ladder.
max_num_batched_tokensderivedPrefill chunk budget. Must be ≥ max_num_seqs × (1 + nextn).
max_model_len4096Prompt + output cap. The runtime length adds scheduler and draft overhead on top.
chunked_prefillFalseWithout it, max_num_batched_tokens must be ≥ max_model_len or construction fails.
lookahead_tokens0Speculative depth from the cache's point of view — reserves KV room for draft tokens.
engine_typeNORMALNORMAL / PROMPT / COMPLETION.
load_formatautodummy 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

Prefill chart
  • 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
Decode chart
  • 64 × SM100 GPUs, EP64DP64
  • 384 + 64 experts
  • 131 072-token context
  • nextn = 3 fixed, 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:

ChartPointThroughput (approx.)Latency / per-user speed (approx.)
Prefill4 096 batch~60k tok/s/GPU~68 ms/step
Prefill16 384 batch~81k tok/s/GPU~200 ms/step
Prefill65 536 batch~89k tok/s/GPU~730 ms/step
Decode8 req/rank~2.6k tok/s/GPU~325 tok/s/user
Decode64 req/rank~15.5k tok/s/GPU~248 tok/s/user
Decode256 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

BenchmarkTasksSamples/taskPass@1How it's scored
AIME 2026303299.9%Mean accuracy over 32 samples. Rule judge extracts the integer in \boxed{}; agreed with the LLM judge on all 960 rollouts.
GPQA Diamond198893.4%Mean over 8 samples. Rule judge takes the letter after ANSWER:.
HLE-Full2 500 (2 158 text + 342 image)142.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.
running an evaluation
# 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
A Pass@1 figure is only valid when 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

Not a comparison with other engines

There is no published head-to-head against vLLM or SGLang on the same hardware and workload. Each project benchmarks its own setup.

Not Ascend data

Both charts are SM100. Ascend 950 is a supported target with no published throughput at all — you'd have to validate it yourself.

Not a cost model

Tokens/s/GPU is not dollars per million tokens until you add utilisation, power, depreciation, networking, storage and failure overhead.

Not TTFT

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.

Offline ≠ online

The Q&A says production load balancing reaches about 80% of a perfectly balanced offline benchmark. These curves are an upper bound.

The profiler lies

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.

The production claim, separately One cluster serving 155 trillion tokens per day at US$38 revenue per GPU-hour (valley pricing). That is a scale statement, not a benchmark: no cluster size, GPU mix or measurement window is given, and revenue is not profit, so it cannot be converted into a cost per token.

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.

QuestionAnswer
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.

1 · No KV cache system

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.

2 · No P/D disaggregation

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.

3 · No load balancing

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

MissingWhat it means for you
Any HTTP serverNo 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 modelsThe model registry has exactly one entry: DeepseekV41ForCausalLM. Nothing else loads.
Older / consumer hardwareSM100 and Ascend 950 only. No H100, no consumer GPUs, no CPU path.
Ascend performance dataAscend is a listed, supported target with zero published throughput or stability numbers. You validate it yourself.
Third-party reproductionAs of the public snapshot, nobody outside DeepSeek had reproduced the Pareto charts.
Production parity guaranteeDeepSeek says DeepInfer powers its production service; the release is a subset, so parity can't be verified externally.
ObservabilityThere are rich internal timers and a stats logger, but no metrics export, tracing, canary tooling or regression harness for latency and cost.
A release tagAt 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…ThenBecause
Need a served V4.1 API this quarterUse a general frameworkvLLM 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 950DeepInferIt is the official Ascend path, with first-party Ascend kernels across the whole stack. Budget time to validate performance yourself.
Need deterministic, reproducible inferenceDeepInferBatch-invariant mode plus deterministic sampling is explicit and documented here. Rare elsewhere.
Have SM100 capacity and only serve V4.1Evaluate bothDeepInfer is tuned specifically for this architecture, but you own the service layer.
Want to learn how frontier inference worksRead DeepInferThe 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 modelsNot applicableNeither 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.

Provenance matters here deepinfer.net describes itself as an independent, fan-made reference site, not affiliated with DeepSeek, Huawei, NVIDIA or the DeepInfer project. It states that it did no hands-on testing and carries sponsored placements and a paid partner directory. Its analysis is careful and well-sourced, and I have summarised it faithfully below — but treat it as a thoughtful secondary reading, not as documentation. Where it disagrees with the repo, the repo wins.

DeepSeek's own writing

DocumentWhat'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:

PageThe 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
DimensionDeepInfervLLM
TypeDeepSeek-specific offline engineGeneral inference and serving framework
ModelsV4.1-Flash only200+ architectures
HardwareSM100, Ascend 950NVIDIA, AMD, Intel GPUs, CPUs, plugins (incl. Ascend)
Online APINoneOpenAI-compatible, Anthropic Messages, gRPC
KV cacheIn-GPU paging onlyPagedAttention, prefix caching, chunked prefill
P/D disaggregationEngine hooks onlyIncluded
Speculative decodingDSparkn-gram, suffix, EAGLE, DFlash
MaturityEarly code dropLarge, 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 withReason
Need a served V4.1 API nowSGLang (preview)API, PD, HiCache and router already wired up
Run Ascend 950DeepInferThe official Ascend path
Need deterministic inferenceDeepInferThe post says SGLang's V4.1 backend currently refuses deterministic inference
Want a mature general frameworkSGLangCommunity and production history
Want DeepSeek's own engine for researchDeepInferKernels, 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
PostThe angle
vs TensorRT-LLMDeepSeek's V4.1-specific engine against NVIDIA's general inference stack — focused on hardware lock-in and build complexity.
vs LMDeployInternLM's TurboMind stack, which already carries DeepSeek optimisations and its own Ascend path.
vs TGIHugging Face's serving toolkit is in maintenance mode; the post weighs what that means for deployment risk.
vs XinferenceA multi-model platform (LLMs, embeddings, rerankers, multimodal) versus a single-model engine.
vs NVIDIA DynamoDynamo is an orchestration layer above vLLM/SGLang/TensorRT-LLM — arguably the closest thing to the missing service layer.
vs llama.cppExplicitly framed as an unlike pairing: a local C/C++ GGUF runtime against a 64-GPU cluster engine.
vs OllamaAsks whether the easy local runner can handle the model at all, versus a data-centre engine.
vs MLC-LLMA 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.

TermMeaning
Batch invarianceA 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 tablePer-sequence list mapping logical token ranges to physical KV cache blocks — the page table of the paged cache.
CACompressed/context attention — the dense-but-compressed attention path, as opposed to the sliding-window and sparse-indexed paths.
Candidate blocksA 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.
CANNHuawei's Compute Architecture for Neural Networks — the Ascend equivalent of CUDA. DeepInfer requires 9.2.
CEDCompress-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 prefillSplitting a long prompt's prefill across several steps so it cannot monopolise the engine.
Continuous batchingAdding and removing sequences from the running batch every iteration, instead of running fixed batches to completion.
CPContext parallelism — splitting a single sequence across GPUs. DeepInfer implements a "mirror CP" variant. Not used in DeepSeek production.
CUDA graphA recorded sequence of kernel launches replayed as one call, removing per-kernel CPU launch overhead.
DeepEPDeepSeek's expert-parallel communication library: MoE all-to-all dispatch/combine, FP8 dispatch, NVLink expert-weight exchange.
DeepGEMMDeepSeek's matrix-multiply kernel library: FP8/FP4/BF16 GEMM, Mega MoE, MQA indexer scoring, HC operators, JIT-compiled.
DeepJITThe runtime kernel compiler used by DeepGEMM and DeepEP, so installation requires no CUDA compilation.
DeepSelectDeepSeek's specialised top-k kernel, 2–20× faster than torch.topk for the indexer and sampler shapes.
DFlashDeepSeek's draft-model implementation for speculative decoding, with its own CUDA graph runner and a confidence head.
DPData parallelism — independent replicas handling different requests. In V4.1, attention is data-parallel with #DP = #EP.
DSADeepSeek Sparse Attention — attending only to the top-scoring blocks chosen by a lightweight indexer, instead of the full context.
DSparkThe confidence-scheduled, variable-length verification layer on top of speculative decoding. The novel part.
E4M3 / E2M1FP8 and FP4 formats named for their exponent and mantissa bit counts.
EngramAn external n-gram memory: huge hash-indexed embedding tables, sharded across ranks, gathered and gated into the hidden state.
EPExpert parallelism — splitting MoE experts across GPUs so each holds a slice.
EPLBExpert-Parallel Load Balancing — redundant copies of hot experts plus periodic re-placement, measured by moe_lb.
EPDEncoder / Prefill / Decode disaggregation — running the three phases on separate pools of machines.
FlashMLADeepSeek's attention kernel library: sparse prefill and decode with FP8/FP4 KV, plus the fused norm→RoPE→attn→RoPE→cast kernel.
FTPFirst token position — the index of a request's first packed row in a flattened batch. A negative value marks CUDA-graph padding.
GDRGPUDirect 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 tokensForced 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.
LCFSLast-come-first-served — the preemption order; the shortest-output sequences are evicted first so the most-invested requests survive.
Mega MoEDeepGEMM's fused MoE path with overlapped communication. The general choice inside a scale-up domain.
MFUModel FLOPs Utilisation — achieved FLOPs over hardware peak. DeepInfer's estimator deliberately excludes draft-model FLOPs.
MLAMulti-head Latent Attention — compressing K and V into a shared low-rank latent so the KV cache shrinks dramatically.
moe_lbThe MoE load-balance statistic: slowest rank's load ÷ mean. 1.0 is perfect; production sees 1.045–1.090.
MQAMulti-Query Attention — many query heads sharing one key/value head. Used by the lightning indexer because it is cheap.
MTPMulti-Token Prediction — predicting several future tokens at once; the basis of speculative decoding here. Depth set by DEEPINFER_USE_NEXTN_DECODE.
MXFP44-bit floating point with per-32 UE8M0 scales. Used for indexer KV and MoE routed-expert weights.
nextnShorthand for the speculative draft depth. Default 5; the published decode chart uses 3.
Paged KV cacheStoring attention KV in fixed-size blocks with a per-sequence block table, eliminating fragmentation and enabling prefix sharing.
Pass@1Accuracy of a single sampled attempt, averaged over samples and tasks. The eval harness's headline metric.
PDLProgrammatic Dependent Launch — a CUDA feature letting a kernel begin setup before its predecessor fully drains. Enabled for DeepGEMM and TileKernels.
Prefill / DecodeThe two phases: reading the prompt (compute-bound, parallel) and generating tokens (bandwidth-bound, serial).
PreemptionEvicting a running sequence when KV blocks run out, freeing its memory.
RoPE / YaRNRotary position embedding, and the YaRN interpolation scheme that extends it to longer contexts than training used.
SASSNVIDIA's native GPU assembly. The Q&A notes public FlashMLA lacks one hand-tuned SASS optimisation.
Slot mappingA flat array telling the store kernel the physical cache slot for each newly-computed token.
SM100NVIDIA's Blackwell-generation streaming multiprocessor architecture — the only supported NVIDIA target.
SOStructured Output — constraining generation to a regex, JSON Schema or EBNF grammar via a per-step token bitmask.
State cache / StateBlockThe second cache: one fixed-size slot per sequence holding SWA ring buffers and compression accumulators, split into stored and dropped regions.
SWASliding Window Attention — attending only to a fixed window of recent tokens (128 in V4.1), making those layers' cost constant.
SWA bounded replayRebuilding a missing sliding-window cache by replaying only the last 128 tokens with a truncated window, at constant cost.
Symmetric memoryMemory mapped at matching addresses across ranks, letting a GPU read a peer's buffer directly. Used to shuffle expert weights within a node.
TBT / TPOTTime Between Tokens / Time Per Output Token — the decode latency metric users feel.
TileLangA multi-backend DSL for GPU kernels. How DeepInfer supports NVIDIA and Ascend from (mostly) one source style.
TMATensor Memory Accelerator — NVIDIA's async bulk-copy engine. Scale-factor layouts are TMA-aligned to use it.
TPTensor parallelism — splitting individual weight matrices across GPUs. Notably not used in DeepSeek production.
TTFTTime To First Token — the prefill-dominated latency metric.
UE8M0A 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.
UserbufferProcess-wide symmetric communication buffers used for EP all-to-all, CP all-gather and expert shuffling.
Wide-EPExpert parallelism at 64–128 GPUs, the decode configuration. Cuts per-GPU expert memory traffic at the cost of communication and a larger failure domain.
xgrammarThe 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).
ZOSZero-Overhead Scheduling — overlapping CPU scheduling and post-processing with GPU execution so the GPU never idles.
3FSFire-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

Primary — the sibling repositories

Secondary — independent write-ups

About this page Built by reading the repository and its sibling projects directly, cross-checked against the published documents above. Where DeepSeek's own documents and a secondary source conflict, I have followed DeepSeek. Numbers quoted without a hedge come from the repository or the Q&A; estimated or third-party figures are labelled as such. Nothing here was run on hardware — I had no SM100 GPUs or Ascend NPUs — so every performance claim is a reported figure, not a measurement I made.