Read DeepEP from token routing to GPU and RDMA transport: V2's ElasticBuffer, NCCL Gin, hybrid topology, deterministic handles, low precision, and the legacy V1 boundary.
Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
A mixture-of-experts (MoE) layer can route one token to two experts on another GPU. The neural-network math is small; moving that token, its routing metadata, and its return value across a cluster is not. If communication burns too many streaming multiprocessors (SMs), it steals cycles from the expert matrix multiply. If the route is wrong, the model trains on the wrong token even when every kernel reports success.
DeepEP (DeepEveryParallel) is DeepSeek's communication library for that boundary. Its current V2 API turns expert-parallel dispatch and combine into an ElasticBuffer contract backed by NCCL Gin, NVIDIA Collective Communications Library's device-side network interface. This lesson starts with four tokens and two ranks, then follows the same metadata through the NVLink GPU interconnect, remote direct memory access (RDMA), a fused kernel, and a reverse combine. It keeps V2 separate from the archived NVIDIA SHMEM (NVSHMEM)-based V1 path, because the two versions have different interfaces, resource costs, and failure modes.[1][2]
An MoE architecture replaces one dense feed-forward block with many expert blocks. A router chooses top-k experts for each token. Expert parallelism (EP) places different experts on different ranks, so each rank owns only a slice of the expert weights. That reduces per-rank model memory, but it turns each MoE layer into an all-to-all exchange.
For one local rank, write the hidden states as a matrix X with one row per token. The router returns topk_idx[t, j] and topk_weight[t, j]. The dispatch operation expands this logical table into rows grouped by destination expert. Local rows can stay on the current GPU; remote rows need a transport path. Each expert then runs its matrix multiplications. Combine reverses the route and applies gate weights while reducing duplicate expert results into the original token rows.
The operation is not the same as an ordinary all_to_all of a dense rectangular tensor. Token counts differ by expert, top-k can duplicate a token for several experts, and a rank may receive no rows for one expert but many for another. A complete communication layer therefore owns four jobs:
| Job | Question the implementation must answer |
|---|---|
| Layout | Which source token goes to which destination expert slot? |
| Transport | Which local or remote path carries each slot? |
| Synchronization | When can a general matrix multiplication (GEMM) read received rows? |
| Inversion | How does combine restore source order and gate weights? |
DeepEP doesn't implement the router or expert GEMMs. It accepts their token rows and routing tensors, provides a layout handle, executes the exchange, and returns data in a layout that the next kernel can consume. The boundary is deliberately narrow enough to embed in a training or inference runtime.[1]
Start with two ranks and four tokens. Rank 0 owns tokens a and b; rank 1 owns c and d. There are four experts, two per rank. Each token selects two experts:
| Token | Source rank | First expert | Second expert | Gate weights |
|---|---|---|---|---|
a | 0 | 0 (rank 0) | 3 (rank 1) | 0.70, 0.30 |
b | 0 | 2 (rank 1) | 1 (rank 0) | 0.40, 0.60 |
c | 1 | 1 (rank 0) | 3 (rank 1) | 0.55, 0.45 |
d | 1 | 0 (rank 0) | 2 (rank 1) | 0.25, 0.75 |
Rank 0 must send one copy of a to expert 3 and one copy of b to expert 2. It also receives c for expert 1 and d for expert 0. Rank 1 performs the mirror exchange. The number of rows sent by one rank is not necessarily the number received by another rank.
Use two coordinate systems:
(source_rank, token_index, topk_slot) identify the original row and expert choice.(destination_rank, local_expert, expert_slot) identify where a receiving GEMM expects the row.Dispatch creates the mapping. The returned EPHandle stores enough metadata to map expert coordinates back to source coordinates. Combine consumes that handle after expert computation. If the handle is discarded, an implementation would need to reconstruct source positions and weights, which is slower and prone to mistakes.
DeepEP V2 replaces separate high-throughput and low-latency expert-parallel interfaces with one ElasticBuffer. Its constructor can receive explicit bytes or calculate a size from maximum tokens per rank, hidden dimension, top-k, 8-bit floating-point (FP8) dispatch, topology, and reduction settings. Allocation is aligned to 2 MiB, and the buffer owns a symmetric memory window registered with NCCL.
The word elastic describes the planned memory abstraction, not automatic infinite growth. Current V2 allocates GPU-backed storage and may reserve CPU bytes for experimental Engram use. The README warns that V2 consumes more buffer space than V1. It also says elastic GPU and CPU backing is still on the roadmap, so production code should size the buffer from a real worst-case envelope instead of assuming it will grow on demand.[1]
The public path looks like this:
1required = ElasticBuffer.get_buffer_size_hint(
2 group,
3 num_max_tokens_per_rank=4096,
4 hidden=7168,
5 num_topk=8,
6 use_fp8_dispatch=True,
7)
8buffer = ElasticBuffer(
9 group,
10 num_max_tokens_per_rank=4096,
11 hidden=7168,
12 num_topk=8,
13 use_fp8_dispatch=True,
14)
15recv_x, recv_idx, recv_w, handle, event = buffer.dispatch(
16 x,
17 topk_idx=topk_idx,
18 topk_weights=topk_weights,
19 num_experts=128,
20 async_with_compute_stream=True,
21)The example leaves num_sms and num_qps at zero, asking V2 to compute them. dispatch returns an EventOverlap object because communication may still be in flight when Python regains control. A caller can launch independent expert-side work, then wait on the event before reading recv_x. combine receives the expert outputs and the same handle.
| ElasticBuffer concern | V2 contract |
|---|---|
| Capacity | Set bytes directly or use get_buffer_size_hint; size is a planning input |
| Topology | Detect physical ranks and choose direct or hybrid logical domains |
| Layout | Return EPHandle with prefixes, slots, and source metadata |
| Compute overlap | Return EventOverlap and keep allocation on the communication stream when requested |
| Precision | Accept BF16 tensors or FP8 data plus scale factors |
| Reuse | Accept a cached handle when routing layout is unchanged |
The buffer is a resource and a protocol. Reusing it across layers can save allocation and communicator setup, but a stale handle is only valid when the dimensions, expert layout, and routing assumptions still match. The Python API asserts those invariants when a cached handle is passed.
V2 dispatch has three conceptual stages even though kernels can overlap them:
topk_idx has shape [num_tokens, num_topk]. topk_weights has the same shape and uses float values. The hidden-state input is [num_tokens, hidden] in BF16, or a tuple containing FP8 values and scale factors. -1 expert indices represent unused selections. V2 returns received indices and weights alongside data so an expert implementation can preserve routing metadata.
The receive layout can be non-expanded or expanded. In non-expanded mode, one received row represents a source token with top-k metadata. In expanded mode, a token can occupy one slot per selected expert, and each local expert's segment is aligned for its GEMM. Alignment padding makes the expert matrix shapes regular, but padding must not become a trainable or combined row. The do_zero_padding option exists for callers that need those gaps cleared.
The copy epilogue is where metadata becomes a contiguous expert input. EPHandle.psum_num_recv_tokens_per_expert stores prefix sums with alignment rules. num_unaligned_recv_tokens_per_expert records actual counts in expanded mode. num_recv_tokens_per_expert_list is a CPU-side list used to launch one GEMM per local expert. These fields are not redundant bookkeeping: they connect irregular network arrival to regular compute tiles.
The C++ launch path selects dispatch for one scale-out domain and hybrid_dispatch when ranks span multiple scale-out groups. It passes the NCCL device communicator, symmetric window, buffer pointers, prefix arrays, slot metadata, timeout cycles, SM count, QP count, and topology indices into a just-in-time (JIT)-generated kernel. Reading csrc/kernels/elastic/dispatch.hpp beside deep_ep/buffers/elastic.py makes the Python-to-kernel boundary concrete.
After each receiving rank runs its local experts, combine returns rows to source ranks. It consumes handle.recv_src_metadata, per-scale-up prefix sums, optional hybrid channel metadata, and gate weights. The kernel reduces duplicates that originated from one source token. Local bypasses can avoid a network transfer, but they still participate in the same logical reduction.
The API exposes allow_multiple_reduction. When enabled, a hybrid path can perform more than one reduction stage. When disabled, the final combine epilogue performs one reduction for better precision, potentially moving more data. This is a bandwidth and numerical-contract tradeoff, not a universal "fast" flag.
Backward duality provides a compact consistency check:
| Forward operation | Reverse-mode operation |
|---|---|
| Dispatch source rows to experts | Combine expert gradients to source rows |
| Expert GEMM consumes received rows | Expert GEMM produces gradients in received layout |
| Combine weighted outputs to source tokens | Dispatch source gradients to expert slots |
DeepEP's README example calls the backward of dispatch a combine, and the backward of combine a dispatch. This follows the same route metadata, not a claim that automatic differentiation is hidden inside the communication library. The surrounding training framework still computes matrix gradients and gate gradients.
topk_weights can be a two-dimensional tensor in non-expanded mode or a one-dimensional tensor in expanded mode. bias supports zero, one, or two BF16 bias tensors for the combine epilogue. The receiving output is BF16. If the caller uses a different layout or dtype, the failure should happen at the contract boundary rather than after a silent reinterpretation.
A GPU cluster has at least two transport scales. Scale-up means GPUs inside one node, normally connected by NVLink. Scale-out means GPUs in different nodes, normally connected through RDMA network interfaces. A route that is fast on NVLink can be a poor route across a network interface card (NIC), and a route that minimizes SM work for RDMA can add unnecessary synchronization inside a node.
V2 derives physical domains from NCCL. With hybrid mode enabled, num_scaleout_ranks represents RDMA groups and num_scaleup_ranks represents the local NVLink domain. With hybrid mode disabled, DeepEP treats the whole communicator as one logical scale-up domain and uses a direct path. This logical view lets the kernel choose hierarchy while retaining the original global rank IDs.
| Mode | Logical shape | Primary path | Use when | Main risk |
|---|---|---|---|---|
| Direct | One scale-up domain | NCCL Gin full connection or local symmetric memory | Network fabric or topology favors one flat exchange | Every rank sees the largest domain and queue-pair (QP) pressure |
| Hybrid | Scale-out × scale-up | RDMA rail between nodes plus NVLink within node | Multi-node EP with usable NVLink islands | More metadata, channels, and topology assumptions |
Hybrid dispatch uses channel linked lists and per-channel token metadata. It can forward rows through a local scale-up group after the RDMA phase. The code computes warps for scale-out and forward work separately, then launches a clustered kernel to overlap communication with nearby compute. That extra machinery is why a flat all-to-all comparison misses important costs.
When a network has multiple rails, NCCL Gin's railed connection type matters. The V2 backend asks NCCL for a NCCL_GIN_CONNECTION_RAIL context in hybrid mode and NCCL_GIN_CONNECTION_FULL in direct mode. If Gin isn't available for the selected topology, initialization asserts instead of quietly using an incompatible path unless EP_DISABLE_GIN explicitly changes behavior. A deployment should treat this as a topology validation step.
DeepEP's README calls its performance numbers logical bandwidth. Logical bytes count the communication contract seen by the algorithm, including local-rank traffic. Physical link counters measure bytes that crossed NVLink or a NIC. Those values answer different questions.
Suppose four ranks exchange 100 MiB of token payload according to an EP layout. If half the rows stay on source GPUs, the logical operation still moves 100 MiB in its model, while the RDMA port may see only the remote half. If the same payload crosses an NVLink hop during a hybrid forward, a physical NVLink counter can count copies that logical bandwidth reports once. Comparing the numbers without naming the denominator produces a misleading speedup.
Use the README tables as shape-specific project reports: V2's stated configuration is 8K tokens per rank, hidden size 7168, top-8 routing, FP8 dispatch, and BF16 combine. It reports examples for SM90 and SM100, ConnectX-7 (CX7) RDMA, EP 8×2, EP 8×4, and EP 8. The headline comparison says up to 1.3× peak performance and up to 4× fewer SMs than V1, but those are not universal promises. Record topology, token count, hidden size, top-k, dtype, SM count, QP count, and whether bandwidth is logical before comparing runs.[1]
| Measurement | Include in a benchmark receipt |
|---|---|
| Logical payload | Tokens, hidden dimension, top-k, dtype, local-bypass convention |
| Physical links | NVLink topology, NIC model, rail count, RDMA speed, congestion state |
| Kernel budget | SM count, channel count, QP count, shared-memory setting |
| Shape | Token distribution per rank and per expert, padding/alignment |
| Timing | Dispatch, combine, end-to-end MoE layer, warm-up and JIT compile time |
| Correctness | Route checksum, expert counts, deterministic mode, output tolerance |
NCCL Gin is a device-side communication interface for issuing network operations from GPU code. DeepEP V2 reuses an existing NCCL communicator instead of requiring an application to construct a separate transport world. The C++ backend queries communicator properties, requests Gin contexts and signals, registers a symmetric memory window, and stores a device communicator pointer for JIT kernels.
The V2 backend's initialization path is worth reading in order:
The signal count includes rank barriers and custom communication notifications. EP_OVERRIDE_RDMA_SL can place V2 traffic on a chosen InfiniBand service level. NCCL_GIN_CROSS_NIC=0 changes symmetric-memory handle reuse behavior for multi-plane systems. These are cluster integration knobs, not per-request model settings.
The important boundary is ownership. DeepEP owns layout, kernels, and its window lifecycle. NCCL owns communicator and network primitives. A model runtime still owns process-group formation, rank assignment, and shutdown order. Aborting a communicator while an EventOverlap hook or JIT kernel still references its window is a lifecycle bug, not a routing bug.
EPHandle is returned by dispatch and consumed by combine. Its fields expose the route in a form that bridges device kernels, CPU scheduling, and cached inference:
| Handle field | Meaning |
|---|---|
topk_idx | Cloned routing choices, unless caller opts out of copying |
recv_src_metadata | Source token and destination slot mapping |
dst_buffer_slot_idx | Receive-buffer slots used for cached dispatch |
psum_num_recv_tokens_per_scaleup_rank | Prefix sum for received rows per scale-up peer |
psum_num_recv_tokens_per_expert | Alignment-aware local expert offsets |
num_unaligned_recv_tokens_per_expert | Actual expanded-mode counts |
token_metadata_at_forward | Per-channel metadata for hybrid combine |
channel_linked_list | Hybrid channel forwarding links |
num_sms | Dispatch SM choice reused by combine |
For decode, a cached handle can skip layout recomputation when expert assignments remain unchanged. The cached path doesn't perform a CPU synchronization to discover new counts and must reuse matching dimensions. This can help stable decoding patterns, but it changes the correctness proof: test that the gate layout is reusable, and invalidate the handle when routing or capacity changes.
V2's deterministic mode sorts received rows after the kernel. Non-expanded mode sorts data, weights, indices, and source metadata. Expanded mode sorts rows within each expert and updates slot pointers without permuting the metadata table in the same way. The sort key uses source-global order and expert identity, so two runs with the same inputs can present the same expert order even when arrival order differs.
Determinism has a cost. It adds sorting and metadata work and can constrain asynchronous behavior. Enable it for correctness tests, reproducible training slices, or debugging route drift. Measure it separately from the non-deterministic throughput path.
CUDA streams let communication and compute overlap, but a Python return doesn't make an output tensor safe to read. EventOverlap wraps a CUDA event and can register a hook to run after the event is waited on. The common pattern is:
1recv_x, recv_idx, recv_w, handle, event = buffer.dispatch(
2 x,
3 topk_idx=topk_idx,
4 topk_weights=topk_weights,
5 num_experts=num_experts,
6 async_with_compute_stream=True,
7)
8
9launch_independent_work()
10event.current_stream_wait()
11launch_expert_gemm(recv_x, recv_idx)
12
13combined_x, _, combine_event = buffer.combine(
14 expert_output,
15 handle=handle,
16 async_with_compute_stream=True,
17)
18combine_event.current_stream_wait()previous_event can make a communication kernel wait for an upstream CUDA event. allocate_on_comm_stream controls ownership of newly allocated tensors when that dependency is used. If the caller combines a stream event with an ordinary host synchronization, it can erase the overlap it intended to gain. Profile stream waits along with kernel durations.
The same contract appears in V2's barrier and experimental PP, CP, and Engram APIs. Reusing one event wrapper across unrelated buffers is unsafe because event readiness says nothing about which allocation or route it protects.
DeepEP accepts brain floating point 16 (BF16) hidden states for the baseline path. In FP8 dispatch mode, the input is a tuple of FP8 values and scale factors. The transport moves the low-precision payload, while scale factors describe how to recover a value for the receiving expert computation. Combine returns BF16 and applies gate-weight reductions there.
This split targets bandwidth and memory pressure without forcing the final reduction to accumulate in FP8. It doesn't remove the need to validate numerical error. Scale-factor layout, hidden dimension alignment, and expert GEMM expectations must agree. A kernel can move bytes correctly while a mismatched scale stride corrupts activations.
use_tma_aligned_col_major_sf selects an optional scale-factor layout suitable for Tensor Memory Accelerator (TMA) paths. Read the model's quantization and GEMM contract before enabling it. DeepEP's source handles scale metadata, but it doesn't decide which quantization scheme an entire model should use.
For post-training or RL on large sparse models, this boundary matters twice. Rollout inference may favor low-latency FP8 dispatch, while training may need BF16 or a different accumulator policy. Keep dtype and scale metadata in the experiment receipt. Comparing reward curves across a dtype change without recording it confounds communication changes with model numerics.
V2's get_theoretical_num_sms estimates communication SMs from topology bandwidth and a balanced gate model. It computes expected top-k destinations, read and write work, NVLink traffic, RDMA traffic, and the bounded link. It then chooses an even SM count, applies a margin, and clamps to the device's multiprocessor count. If overlap is preferred, it can stay near the minimum needed; otherwise it may choose at least 64 SMs.
This is a model, not a tuner oracle. The implementation explicitly assumes balanced routing and says group-limited gates need different treatment. DeepSeek-style routing can be asymmetric by design, so inspect the actual token distribution before trusting the estimate. Passing num_sms manually is appropriate when measured topology or an application-level overlap budget beats the balanced approximation.
QP sizing follows a related rule. Direct mode encourages roughly one QP per communication SM, capped at nine in the current helper. Hybrid mode encourages num_sms * 16 + 1 QPs to give channels and notification work independent queues, capped by allocated QPs. More QPs consume resources and can ring database or network state more aggressively. Use the theoretical count as a starting point, then measure queue pressure and tail latency.
DeepEP V2 compiles kernels at runtime. The Python or C++ launch code assembles template parameters such as topology, expansion mode, hidden bytes, expert alignment, number of warps, SM count, and QP count. The JIT compiler parses required headers, invokes NVCC or the configured compiler, caches the resulting module, and launches it with a generated configuration.
JIT avoids compiling every possible hardware and shape combination during package installation. It also moves a failure boundary into first use. A missing CUDA compiler, wrong TORCH_CUDA_ARCH_LIST, stale cache, incompatible NCCL headers, or unsupported PTX instruction can surface when the first dispatch runs. Warm-up time must be separated from steady-state communication benchmarks.
Relevant environment switches include:
| Variable | Purpose |
|---|---|
EP_JIT_CACHE_DIR | Choose a persistent kernel cache directory |
EP_JIT_DEBUG | Print JIT diagnostics |
EP_JIT_DUMP_PTX / EP_JIT_DUMP_SASS | Save generated assembly for inspection |
EP_JIT_PTXAS_CHECK | Assert no local-memory use in compiled kernels |
TORCH_CUDA_ARCH_LIST | Select CUDA architecture targets |
EP_NCCL_ROOT_DIR | Point build and JIT lookup at NCCL headers and libraries |
For a production launch, pre-warm every required shape on every worker or include compilation time in startup readiness. A cache mounted from one host may contain code for a different device capability. Treat cache keys and compiler versions as part of the deployment artifact.
DeepEP V1 is archived documentation, not a hidden compatibility mode for the V2 API. V1 exposes a Buffer, uses NVSHMEM for internode and low-latency methods, asks callers to provide separate NVLink and RDMA sizes, and has distinct normal and low-latency kernels. Its low-latency path can use a hook to defer an RDMA receive without occupying SMs. V2 uses ElasticBuffer, NCCL Gin, unified high-throughput and low-latency APIs, and larger scale-up and scale-out domains.[2]
| Boundary | V1 | V2 |
|---|---|---|
| Main Python object | Buffer | ElasticBuffer |
| Primary network backend | NVSHMEM | NCCL Gin |
| Buffer sizing | Separate NVLink/RDMA hints | Unified elastic buffer hint |
| Low-latency EP | Pure RDMA method | Removed 0-SM RDMA low-latency mode |
| SM selection | Caller-selected or tuned configs | Analytical helper, manual override available |
| Scale domain | Normal and low-latency variants | Direct and hybrid logical domains |
| Documentation status | Archived | Current README and source |
V1 source remains relevant when reading old DeepSeek-V3 integrations or reproducing a historical benchmark. That history doesn't show that a current V2 deployment supports the same CUDA, PyTorch, NVSHMEM, or low-latency assumptions. V1 performance tables use H800 and CX7 setups, which should not be compared to V2's SM90 and SM100 tables without matching shapes and links.[2][3]
Migration order matters: port the route contract first, then port the transport. Replace Buffer layout calls with ElasticBuffer handles, verify NCCL Gin availability, resize memory, and rerun deterministic correctness tests before measuring throughput. Don't infer compatibility from a successful import alone.
The current repository includes experimental primitives beyond EP:
These APIs share allocation and event machinery with ElasticBuffer, which makes them relevant systems experiments. They aren't proof that the whole feature set has the same production maturity as V2 EP. The README labels Engram, PP, and CP experimental and calls 0-SM PP and Engram features available with RDMA.[1]
For a large RL system, keep these boundaries explicit. A PP send can be healthy while EP RDMA is congested. An Engram fetch can consume the same QP or memory budget that dispatch sizing assumed was free. A CP copy can change stream dependencies. Share a buffer only when its maximum concurrent sessions and lifetimes are accounted for.
The current README lists Hopper (SM90) GPUs or architectures with SM90 Parallel Thread Execution (PTX) support, CUDA 12.3 or newer for SM90, PyTorch 2.10 or newer, and NCCL 2.30.4 or newer. NVLink is expected for intranode communication and RDMA for internode communication. The repository reports full InfiniBand testing and theoretical compatibility with RDMA over Converged Ethernet (RoCE).[1]
Run these checks before starting a multi-node job:
| Check | Why it matters | Failure symptom |
|---|---|---|
| GPU capability and PTX target | JIT kernels need supported instructions | Compile or launch error |
| CUDA, PyTorch, NCCL versions | Headers and runtime device APIs must agree | Import, communicator, or JIT failure |
| NVLink peer map | Scale-up domain must match NCCL local symmetric access (LSA) | Slow or invalid hybrid path |
| RDMA device and rail map | Gin needs usable network contexts | Gin unavailable assertion |
| QP allocation | Hybrid channels need enough queues | Initialization or timeout |
| Symmetric memory capacity | Buffer and workspace must fit | Allocation or window registration failure |
| Service-level and traffic isolation | Congestion can distort route latency | Tail spikes and cross-job interference |
Don't run pip install on a login node and assume the worker image matches. The source's JIT cache and NCCL library path are runtime dependencies. Include their versions, topology dump, and EP_BUFFER_DEBUG=1 initialization output in a launch receipt, while redacting credentials and private host details.
DeepEP is especially relevant when a policy or teacher is a sparse MoE model. During online RL, every sampled trajectory can trigger many MoE layers. The rollout service needs fast expert routing; the trainer needs exact route metadata and reproducible gradients. A slow all-to-all multiplies across generated tokens, while an incorrect or stale handle can corrupt every subsequent update.
Use DeepEP to reason about three placement patterns:
| Post-training pattern | DeepEP role | Measurement |
|---|---|---|
| Separate rollout and trainer clusters | Serve MoE layers during rollout; trainer uses its own EP group | Tokens per second, sync cost, route version |
| Colocated actor and rollout | Reuse GPU memory and communicator when phases alternate | Peak buffer bytes, pause time, overlap |
| Teacher or specialist distillation | Run many expert policies or a large teacher with repeatable routing | Deterministic output, FP8 error, aggregate bandwidth |
The communication library doesn't solve policy staleness, reward hacking, or checkpoint synchronization. It can, however, become the lowest-level source of latency and numerical mismatch in those systems. Pair route metrics with rollout weight versions, expert-load histograms, and reward receipts. If reward falls after a topology change, first separate transport delay, dtype error, and algorithm behavior.
The following standard-library example models the metadata transformation without CUDA. It doesn't claim to emulate DeepEP's kernels or bandwidth. It checks the invariant that dispatch creates expert slots and combine reconstructs source rows with gate weights. The leetllm:test marker makes the snippet eligible for the repository's runnable-example checks.
1from collections import defaultdict
2
3tokens = ["a", "b", "c", "d"]
4routes = {
5 "a": [(0, 0.70), (3, 0.30)],
6 "b": [(2, 0.40), (1, 0.60)],
7 "c": [(1, 0.55), (3, 0.45)],
8 "d": [(0, 0.25), (2, 0.75)],
9}
10expert_output = {
11 (token, expert): f"{token}->e{expert}"
12 for token in tokens
13 for expert, _weight in routes[token]
14}
15
16dispatch = defaultdict(list)
17for source_index, token in enumerate(tokens):
18 for topk_slot, (expert, weight) in enumerate(routes[token]):
19 dispatch[expert].append(
20 {
21 "source_index": source_index,
22 "topk_slot": topk_slot,
23 "token": token,
24 "weight": weight,
25 }
26 )
27
28combined = defaultdict(list)
29for expert, rows in dispatch.items():
30 for row in rows:
31 key = (row["token"], expert)
32 combined[row["source_index"]].append(
33 (expert_output[key], row["weight"])
34 )
35
36for source_index, token in enumerate(tokens):
37 pieces = combined[source_index]
38 assert len(pieces) == 2
39 weight_sum = sum(weight for _value, weight in pieces)
40 assert abs(weight_sum - 1.0) < 1e-9
41 print(token, "<-", ", ".join(value for value, _weight in pieces))Expected output is:
1a <- a->e0, a->e3
2b <- b->e2, b->e1
3c <- c->e1, c->e3
4d <- d->e0, d->e2The dictionary groups rows by expert, standing in for a receive layout. source_index, topk_slot, and weight stand in for handle metadata. A real combine reduces vectors, not strings, and a real dispatch can cross ranks. Keep this tiny invariant visible when reviewing a more complex route.
DeepEP's strength is focus. It exposes a compact EP API while specializing kernels for modern GPU and RDMA paths. V2's unified buffer, analytical resource estimates, NCCL communicator reuse, deterministic option, and JIT compilation make the transport boundary inspectable. Its limits are equally concrete.
| Strength | Why it helps | Limitation |
|---|---|---|
| Unified V2 API | One handle model for throughput and latency paths | V2 buffer sizing is larger than V1 |
| NCCL Gin integration | Reuses application communicators and network setup | Requires recent NCCL and compatible Gin topology |
| Direct plus hybrid modes | Fits flat or NVLink-island clusters | Hybrid metadata and tuning are harder to debug |
| Analytical SM/QP sizing | Good starting point without exhaustive autotuning | Balanced-gate assumptions miss skewed routes |
| FP8 dispatch and BF16 combine | Cuts payload while retaining BF16 reduction | Scale layout and numerical error remain caller concerns |
| Deterministic sorting | Reproducible route order for tests and training | Sorting adds work and can lower throughput |
| Runtime JIT | Avoids compiling every shape during install | First-use compile and cache failures are runtime risks |
| MIT source license | Permissive source reuse with notice obligations | NCCL, NVSHMEM, models, and data have separate terms |
Weakness isn't the same as a bug. Larger V2 buffers may trade memory for broader topology support, while minimum SM counts can favor overlap over standalone benchmarks. Failed Gin assertions can expose network deployments that don't satisfy the chosen mode. Diagnose against the contract before patching a kernel.
The repository is published under the DeepSeek organization. Its citation lists Chenggang Zhao, Shangyan Zhou, Liyue Zhang, Chengqi Deng, Zhean Xu, Yuxuan Liu, Kuai Yu, Jiashi Li, and Liang Zhao. The README also thanks NCCL contributors and the NCCL team for V2 Gin support. These names establish the source snapshot's credited contributors, not a permanent ranking of current maintainers.[1]
| Field | What the pinned source says |
|---|---|
| Organization | DeepSeek AI repository, focused on expert-parallel communication |
| Credited contributors | Chenggang Zhao, Shangyan Zhou, Liyue Zhang, Chengqi Deng, Zhean Xu, Yuxuan Liu, Kuai Yu, Jiashi Li, Liang Zhao |
| Upstream dependency | NVIDIA NCCL Gin backend and NCCL device communication APIs |
| Source license | MIT, copyright notice for DeepSeek, per LICENSE[4] |
| Legacy dependency | V1 documentation and code use NVSHMEM, which has its own NVIDIA license terms[5] |
| Asset boundary | CUDA, NCCL, NVSHMEM, model weights, datasets, and cluster software keep their own terms |
The source's MIT license permits use, modification, and distribution with the required notice. It doesn't grant a license to NCCL or NVSHMEM binaries, model checkpoints, benchmark datasets, or network firmware. A production image should inventory those dependencies separately. The V2 repository says NVSHMEM remains needed for legacy methods, so removing every NVSHMEM package from an image can break a V1 compatibility path even when V2 EP itself uses NCCL Gin.[4][5]
There is no peer-reviewed DeepEP system paper in this pinned repository. The README contains a project citation and benchmark tables. Use the DeepSeek-V3 report to explain why expert-parallel communication and group-limited routing matter, but don't cite it as proof that every V2 kernel or result came from that paper.[3]
Before trusting a number, capture a receipt with:
Common failures map to specific boundaries:
| Symptom | Boundary to inspect | First check |
|---|---|---|
| Gin unavailable assertion | NCCL communicator and topology | NCCL version, Gin properties, direct/hybrid mode |
| JIT compile failure | Compiler and cache | CUDA home, architecture list, cache permissions |
| Receive count mismatch | Layout metadata | EPHandle prefixes, expert alignment, CPU sync mode |
| Wrong output order | Deterministic and source slots | recv_src_metadata, cached-handle validity |
| Timeout at scale | RDMA and QP pressure | Rail map, service level, QP count, congestion |
| Out of memory (OOM) during initialization | Buffer sizing | V2 hint, CPU bytes, concurrent PP/Engram sessions |
| Reward regression after dtype change | Numeric contract | FP8 scales, BF16 combine, expert GEMM accumulator |
| Slow second iteration | Stream dependency | EventOverlap waits and stale handles |
The most dangerous failure is a plausible tensor with wrong provenance. Add route checksums, per-expert counts, and deterministic small-cluster tests before launching a long RL job. Communication success is not model correctness.
Read one vertical slice instead of opening every kernel at once:
| Order | File or directory | Question |
|---|---|---|
| 1 | README.md | What is V2, which claims are project-reported, and what remains experimental? |
| 2 | deep_ep/buffers/elastic.py | How do Python inputs, handles, sizing, and events fit together? |
| 3 | deep_ep/utils/event.py | Which stream waits and hooks make asynchronous output safe? |
| 4 | csrc/kernels/backend/nccl.cu | How does Gin capability, rail mode, and symmetric memory initialize? |
| 5 | csrc/kernels/elastic/dispatch.hpp | Which launch arguments define dispatch layout and topology? |
| 6 | csrc/kernels/elastic/combine.hpp | How does the reverse reduction consume source metadata? |
| 7 | csrc/jit/compiler.hpp and csrc/jit/cache.hpp | When are kernels generated, compiled, and reused? |
| 8 | docs/legacy.md | Which V1 assumptions should not leak into V2 guidance? |
| 9 | docs/nvshmem.md | Which binary and hardware terms apply only to legacy methods? |
At each boundary, write down the authoritative state. Router output owns expert choices. EPHandle owns source-to-slot metadata. NCCL owns communicator and window state. The event owns readiness. The expert GEMM owns its local output. Combine should never guess any of those values from decoded text or a second route calculation.
ElasticBuffer unifies dispatch and combine around a reusable EPHandle and asynchronous EventOverlap.Answer every question, then check your score. Score 75% or higher to mark this lesson complete.
8 questions remaining.
Questions and insights from fellow learners.