LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 196 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/17
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningTraining Run OperationsReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/31
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Parallel Primitives LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingGPU Runtime Execution LabAI-Generated Kernel EngineeringModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingGPU Failure OperationsDistributed Inference Data PlaneServing Benchmark EngineeringA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnProject Deep DivesDeep Dive - DeepEP
🚀HardInference Optimization

Deep Dive - DeepEP

Read DeepEP from a four-token routing ledger through V2's ElasticBuffer, NCCL Gin, NVLink and RDMA topology, FP8 dispatch, and the archived V1 boundary.

45 min read
Learning path
Step 194 of 196 in the full curriculum
Deep Dive - slimeDeep Dive - Tinker

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

A routed mixture-of-experts (MoE) layer can send one token to two experts on different GPUs. Returning both outputs isn't enough: they must return to the correct source row, with the gate applied exactly once. A route can be wrong while every CUDA kernel reports success, and communication that consumes too many streaming multiprocessors (SMs) leaves fewer resources for the expert matrix multiply.

The previous chapter followed slime's rollout and training loop. That loop still has to move hidden-state rows whenever its policy is a sparse MoE. DeepEP (DeepEveryParallel) is DeepSeek's communication library for that hop. V2 wraps expert-parallel dispatch and combine in an ElasticBuffer backed by NCCL Gin, NVIDIA Collective Communications Library's device-side network interface. Its NVLink and remote direct memory access (RDMA) paths share routing metadata but differ in transport and synchronization. The archived NVIDIA SHMEM (NVSHMEM)-based V1 has a different API and resource model.[1]Reference 1DeepEPhttps://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d[2]Reference 2DeepEP V1 Documentationhttps://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/docs/legacy.md

The code walkthrough is pinned to dd758caf451848bd150e1046af3d0a73e5fff38d, checked on September 2, 2026. The runnable examples below exercise CPU routing and arithmetic only. CUDA snippets show integration contracts, not a completed DeepEP installation or a measured network run.

That reading snapshot isn't a deployment recommendation. At the same check, main was 01dc3aaac82068020353dce2c302e38153c0bfaa, two commits ahead. Those commits add and document a system-scope memory fence before the Gin barrier when a logical scale-up domain spans NVLink and RDMA. A completed transport operation and visibility of preceding writes are different guarantees; the mixed-fabric ordering fix matters even when API shapes stay unchanged.[3]Reference 3DeepEP Mixed-Fabric Gin Barrier Orderinghttps://github.com/deepseek-ai/DeepEP/blob/01dc3aaac82068020353dce2c302e38153c0bfaa/deep_ep/include/deep_ep/common/comm.cuh

Before reading kernels, answer one question: what must cross ranks in an MoE layer?

Answer

Hidden-state rows and enough routing metadata to identify destination experts and restore source positions. Gate values can travel as metadata too, but the caller decides where to apply them. Expert parameter matrices stay on their owning ranks.

Why expert parallelism needs a communication layer

Start from one rank's view. 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 saves per-rank model memory, but every MoE layer now has to exchange token rows.

For one local rank, write hidden states as a matrix X with one row per token. The router returns topk_idx[t, j] and topk_weight[t, j]. Dispatch transports a token once per selected destination rank, even if that rank owns two of its selected experts. An optional expansion then creates one slot per expert. Local destinations still need layout and memory work, but not a remote link. After expert computation, combine reverses the route and sums supplied contributions. The caller must apply gate weights before that sum; passing topk_weights doesn't multiply activation rows automatically.

A fixed-size all-to-all assumes known per-peer counts. Variable-split collectives can handle uneven sizes, but callers must still compute those splits and pack the rows. Sparse routing also creates per-expert expansion and reverse-reduction work. DeepEP specializes that whole exchange rather than introducing the possibility of uneven communication. Four questions remain:

JobQuestion the implementation must answer
LayoutWhich source token goes to which destination expert slot?
TransportWhich local or remote path carries each slot?
SynchronizationWhen can a general matrix multiplication (GEMM) read received rows?
InversionHow does combine restore source order and sum supplied contributions?

DeepEP doesn't implement the router or expert GEMMs. It accepts their rows and routing tensors, builds a layout handle, executes the exchange, and returns data in the layout the next kernel expects. The router, expert GEMMs, and training loop remain in the caller. That ownership boundary will matter when we diagnose a slow route versus a wrong route.[1]Reference 1DeepEPhttps://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d

Diagram showing Source rows and route indices, dispatch, Received rows wait before reading, and Caller runs experts and applies gates.
Source rows and route indices, dispatch, Received rows wait before reading, and Caller runs experts and applies gates.

A four-token routing ledger

Use 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, and each token selects two experts. Here the two selected experts always live on different ranks, so eight expert slots also require eight source-token/destination-rank rows, including local rows.

TokenSource rankFirst expertSecond expertGate weights
a00 (rank 0)3 (rank 1)0.70, 0.30
b02 (rank 1)1 (rank 0)0.40, 0.60
c11 (rank 0)3 (rank 1)0.55, 0.45
d10 (rank 0)2 (rank 1)0.25, 0.75

Rank 0 sends 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 counts happen to balance here; a real gate can send more rows from one rank than that rank receives.

Read each copy in two coordinate systems:

  1. Source coordinates: (source_rank, token_index, topk_slot) identify the original row and expert choice.
  2. Expert coordinates: (destination_rank, local_expert, expert_slot) identify where a receiving GEMM expects the row.

Dispatch creates this mapping. The returned EPHandle retains route indices and reverse-slot metadata, not gate values. Received gate tensors are separate outputs. Keep them aligned with expert rows when applying weights, then use the handle to return the weighted contributions.

Four-token gate matrix beside token a: expert 0 on rank 0 returns 1, weighted to 0.70; expert 3 on rank 1 returns 4, weighted to 1.20. Combine sums those contributions back into source row a as 1.90.
Token `a` stays on rank 0 for expert 0 and crosses to rank 1 for expert 3. The caller weights the expert outputs; combine uses the EPHandle to sum them into source row `a`.

For token b, which rank receives its first dispatched copy, and who applies its gate weight?

Answer

Rank 1 receives the copy for expert 2. The caller must multiply that expert's output by 0.40 before DeepEP combines the contributions for source token b.

V2's single interface: ElasticBuffer

V2 gives high-throughput and latency-oriented expert-parallel dispatch one interface: ElasticBuffer. The caller can pass an explicit byte count, or derive a 2 MiB-aligned size from maximum tokens per rank, hidden dimension, top-k, 8-bit floating-point (FP8) dispatch, topology, and reduction settings. Here 2 MiB means 2,097,152 bytes. The buffer then owns symmetric memory registered with NCCL.

Elastic describes this memory abstraction, not automatic infinite growth. Current V2 uses GPU-backed storage and can reserve CPU bytes for experimental Engram use. The README says V2 consumes more buffer space than V1 and that mixed GPU/CPU backing is still planned. Size the buffer from a real worst-case envelope rather than assuming it will grow on demand.[1]Reference 1DeepEPhttps://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d

Before dispatch can run, a caller needs a capacity estimate. get_buffer_size_hint gives a planning number, not an allocation. This unexecuted integration fragment assumes an initialized NCCL process group, BF16 or correctly scaled FP8 input, and valid routing tensors on a supported GPU:

elastic_buffer_setup.py
1from deep_ep import ElasticBuffer 2 3required = ElasticBuffer.get_buffer_size_hint( 4 group, 5 num_max_tokens_per_rank=4096, 6 hidden=7168, 7 num_topk=8, 8 use_fp8_dispatch=True, 9) 10buffer = ElasticBuffer( 11 group, 12 num_max_tokens_per_rank=4096, 13 hidden=7168, 14 num_topk=8, 15 use_fp8_dispatch=True, 16) 17recv_x, recv_idx, recv_w, handle, event = buffer.dispatch( 18 x, 19 topk_idx=topk_idx, 20 topk_weights=topk_weights, 21 num_experts=128, 22 async_with_compute_stream=True, 23)

If num_sms or num_qps is 0, dispatch asks the analytical helpers for defaults. Pass an integer to override them; the pinned implementation doesn't accept None as the automatic sentinel. EventOverlap marks communication readiness when asynchronous mode is requested. Launch independent work, then wait on the event before reading recv_x. V2 documents domains up to EP2048, which isn't a measured saturation result at that width.[1]Reference 1DeepEPhttps://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d

ElasticBuffer concernV2 contract
CapacitySet bytes directly or use get_buffer_size_hint; size is a planning input
TopologyDetect physical ranks and choose direct or hybrid logical domains
LayoutReturn EPHandle with prefixes, slots, and source metadata
Compute overlapReturn EventOverlap and keep allocation on the communication stream when requested
PrecisionAccept BF16 tensors or FP8 data plus scale factors
ReuseAccept a cached handle when routing layout is unchanged

Reusing a buffer can avoid allocation and communicator setup. Reusing a handle is a stronger promise: the route itself must remain unchanged. The Python API asserts selected dimensions and reuses handle.topk_idx; it doesn't compare a newly computed route because cached dispatch requires topk_idx=None. Equal shapes don't establish equal routing.

Dispatch: layout, move, and epilogue

Ask what dispatch must preserve while rows are moving. V2 has three conceptual stages, even though kernels can overlap them: count and lay out rows by scale-up rank and local expert; move rows through local symmetric memory, NVLink, or NCCL Gin RDMA; then copy or expand rows into the receive layout used by expert GEMM.

The input contract makes those stages concrete. topk_idx has shape [num_tokens, num_topk], and topk_weights has the same shape with float values. Hidden states have shape [num_tokens, hidden] in BF16, or arrive as FP8 values plus scale factors. An expert index of -1 means that selection is unused. V2 returns received indices and weights beside the data so the next kernel can keep routing metadata attached.

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 regularizes expert matrix shapes, but padding isn't a token and must not enter a combined row. Callers can use do_zero_padding when those gaps need to be cleared.

The copy epilogue turns metadata into expert input. In expanded mode, psum_num_recv_tokens_per_expert[i] is the aligned start of expert i plus its actual count, not simply an inclusive sum of padded counts. For counts [3, 1] and alignment 4, starts are [0, 4], these endpoints are [3, 5], and allocated expert segments total 8 slots. num_unaligned_recv_tokens_per_expert retains [3, 1]. Exact CPU counts require the CPU-synchronized path; no-sync callers must respect valid device counts rather than treating capacity as data.

The C++ launch path chooses 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 exposes the Python-to-kernel boundary.

Why does V2 keep both aligned prefix sums and unaligned expert counts?

Answer

Expert GEMM wants aligned offsets, while combine and correctness checks need the actual number of rows. The two values let the kernel use regular storage without treating alignment padding as a real token.

Combine: reverse routing plus reduction

Once each receiving rank runs its local experts, rows are in expert order rather than source-token order. combine consumes handle.recv_src_metadata, per-scale-up prefix sums, and optional hybrid channel metadata to reverse that mapping and sum contributions. Activation reduction and optional return of top-k weights are separate operations in combine_reduce_epilogue.cuh. Local bypasses skip a remote transfer, but still participate in reduction.[1]Reference 1DeepEPhttps://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d

For token a, the caller supplies weighted outputs 0.70 × (1, 0) and 0.30 × (4, 0). Combine should return (1.90, 0). Supplying raw expert outputs returns (5, 0); weighting twice returns (0.85, 0). All three tensors have the same shape. A transport test therefore needs a numerical reference, not just matching counts.

The API exposes allow_multiple_reduction, which controls staging. Reducing within a rank or node can send fewer rows onward, but intermediate BF16 materialization introduces extra rounding. Disabling it avoids those additional reduction stages, potentially moving more data. The general reduction path accumulates in FP32 before casting to BF16; a no-bias path with at most two inputs uses packed BF16 addition. Neither "BF16 output" nor "FP32 accumulation" alone describes every path. Treat staging as a bandwidth/numerical tradeoff.

For a concrete rounding case, consider already-weighted contributions 256, 1, and -256. Accumulating all three in FP32 gives 1, which BF16 can represent. If a local stage first materializes 256 + 1 as BF16, round-to-nearest-even produces 256; adding -256 then gives 0. The pinned CPU reference reproduces this difference when the first two contributions share a local reduction stage. This checks the reference's numerical contract, not a GPU kernel's latency or correctness.

Backward duality provides a compact consistency check:

Forward operationReverse-mode operation
Dispatch source rows to expertsCombine expert gradients to source rows
Expert GEMM consumes received rowsExpert GEMM produces gradients in received layout
Sum supplied outputs back to source tokensCopy source gradients to corresponding expert slots

For the unweighted route operators, dispatch and combine are transposes: copying rows forward becomes summing their gradients backward. The surrounding autograd wrapper must also differentiate expert functions and gate multiplication. It can't omit gate gradients merely because the communication calls reverse each other.

topk_weights can be two-dimensional in non-expanded mode or one-dimensional in expanded mode. bias supports zero, one, or two BF16 bias tensors for the combine epilogue. Its combined activation output is BF16. If a caller supplies a different layout or dtype, failure should happen at the contract boundary rather than after a silent reinterpretation.

Topology: scale-up and scale-out are different roads

The four-token ledger now meets hardware. 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 row that moves quickly over NVLink may wait on a network interface card (NIC), while a path that saves SM work across RDMA can add needless 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. The logical view lets kernels choose a hierarchy while retaining original global rank IDs.

Four-rank transport sketch. Direct mode sends R0 to R3 over a full Gin connection. Hybrid mode sends R0 to R2 over a rail, then forwards to R3 over NVLink. Gin is a device interface, not a central routing server.
This separate four-rank sketch highlights one route. Direct mode uses a flat logical domain; hybrid mode adds local forwarding after the RDMA rail. The physical NVLink islands exist in both modes.
ModeLogical shapePrimary pathUse whenMain risk
DirectOne scale-up domainNCCL Gin full connection or local symmetric memoryNetwork fabric or topology favors one flat exchangeEvery rank sees the largest domain and queue-pair (QP) pressure
HybridScale-out × scale-upRDMA rail between nodes plus NVLink within nodeMulti-node EP with usable NVLink islandsMore 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 budgets CUDA warps, groups of 32 scheduled GPU threads, for scale-out and forward work separately, then launches a clustered kernel to overlap communication with nearby compute. A flat all-to-all comparison misses those extra ownership and synchronization costs.

When a network has multiple rails, NCCL Gin's 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 choosing an incompatible path, unless EP_DISABLE_GIN explicitly changes behavior. Treat that assertion as a topology validation step.

Logical bandwidth is not a NIC counter

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 count bytes that crossed NVLink or a NIC. Those measurements answer different questions, so a profiler needs both labels.

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 accounts for 100 MiB while the RDMA port may see only the remote half. If that payload crosses an NVLink hop during a hybrid forward, a physical NVLink counter can count copies that logical bandwidth reports once. A speedup without its byte denominator is not a reproducible measurement.

For a hypothetical 1 ms interval, 100 MiB corresponds to 104.8576 decimal GB/s; 50 MiB corresponds to 52.4288 GB/s. That difference can be bookkeeping, not speed loss. Real counters also include their own protocol and direction conventions. Use dispatch-plus-expert-plus-combine latency to evaluate layer performance, not a communication-only bandwidth number.

Read the README tables as shape-specific project reports, not generic hardware ceilings. It states 8K tokens per batch, hidden size 7168, top-8 routing, FP8 dispatch, and BF16 combine. For reproduction, also inspect the test harness: its per-rank count can be max(1, args.num_tokens - rank). Don't silently replace the README's batch wording with an exact per-rank claim. These are its reported logical-bandwidth snapshots:[1]Reference 1DeepEPhttps://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d

ArchNICTopologyDispatch bottleneckCombine bottleneckSMs
SM90CX7EP 8×290 GB/s RDMA81 GB/s RDMA12
SM90CX7EP 8×461 GB/s RDMA61 GB/s RDMA6
SM100CX7EP 8×290 GB/s RDMA91 GB/s RDMA12
SM100N/AEP 8726 GB/s NVLink740 GB/s NVLink64 (max perf)
SM100N/AEP 8643 GB/s NVLink675 GB/s NVLink24 (min SM)

The same README reports up to 1.3× peak performance versus V1 while using up to 4× fewer SMs. It also reports a V3-like legacy-training configuration dropping from 24 SMs to 4-6 while keeping equivalent or better performance. These are project-reported envelopes, not a warranty for your topology. Record token count, hidden size, top-k, dtype, SM count, queue-pair count, and bandwidth denominator before comparing runs.

MeasurementInclude in a benchmark receipt
Logical payloadTokens, hidden dimension, top-k, dtype, local-bypass convention
Physical linksNVLink topology, NIC model, rail count, RDMA speed, congestion state
Kernel budgetSM count, channel count, QP count, shared-memory setting
ShapeToken distribution per rank and per expert, padding/alignment
TimingDispatch, combine, end-to-end MoE layer, warm-up and JIT compile time
CorrectnessRoute checksum, expert counts, deterministic mode, output tolerance

An experiment reports 90 GB/s logical RDMA bandwidth, but your NIC counter shows 45 GB/s. Is that automatically a regression?

Answer

No. The logical figure can include local traffic and can use a different byte denominator. Reconcile token counts, local bypass, and measured physical bytes before judging the result.

NCCL Gin V2 backend

NCCL Gin is a device-side communication interface for issuing network operations from GPU code. V2 reuses an existing NCCL communicator instead of asking 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.

Follow initialization as a chain of ownership transfers:

  1. Create or receive the host NCCL communicator.
  2. Query physical rank domains and Gin capability.
  3. Request QP contexts, queue depth, traffic class, and signal count.
  4. Choose rail or full connection based on direct or hybrid mode.
  5. Allocate symmetric GPU and optional CPU memory.
  6. Register a window and obtain NVLink peer pointers.
  7. Build a device communicator that JIT kernels can call.

The signal count includes rank barriers and custom communication notifications. EP_OVERRIDE_RDMA_SL can place V2 traffic on a chosen InfiniBand service level, while 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.

Keep ownership explicit. 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: route metadata with a lifetime

After dispatch, ask what survives the network transfer. 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 fieldMeaning
topk_idxCloned routing choices, unless caller opts out of copying
recv_src_metadataSource token and destination slot mapping
dst_buffer_slot_idxReceive-buffer slots used for cached dispatch
psum_num_recv_tokens_per_scaleup_rankPrefix sum for received rows per scale-up peer
psum_num_recv_tokens_per_expertAlignment-aware local expert offsets
num_unaligned_recv_tokens_per_expertActual expanded-mode counts
token_metadata_at_forwardPer-channel metadata for hybrid combine
channel_linked_listHybrid channel forwarding links
num_smsDispatch SM choice reused by combine

A cached handle can skip layout recomputation for a replay of the same route, such as a corresponding backward operation. A new decode token usually has a newly computed route; equal batch size doesn't make the old handle reusable. The cached path skips CPU count discovery, so the caller must establish route and layout compatibility before using it.

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 equal inputs can present the same expert order even when arrival order differs.

Determinism has a cost: sorting and metadata work 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.

EventOverlap: a stream contract

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. First launch independent work, then make the compute stream wait before it reads received rows:

event_overlap.py
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() 11# Non-expanded layout: caller computes experts, applies each gate once, 12# and sums experts belonging to this destination rank into one BF16 row. 13expert_output = launch_weighted_local_experts(recv_x, recv_idx, recv_w) 14 15combined_x, _, combine_event = buffer.combine( 16 expert_output, 17 handle=handle, 18 async_with_compute_stream=True, 19) 20combine_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. A host synchronization inserted between the event and expert work can erase the overlap the caller intended to gain. Profile stream waits beside kernel durations, and keep readiness separate from route correctness.

This fragment is not an executable CUDA test: launch_weighted_local_experts stands for the caller's expert integration. current_stream_wait() enqueues a dependency, not a host-wide synchronization. In deterministic mode it also triggers a sorting hook on the waiting stream. Releasing the wrapper after one wait doesn't establish readiness on every other consumer stream; keep allocation lifetimes and all consumer dependencies explicit.

Precision: FP8 on the wire, BF16 for the reduction

DeepEP accepts BF16 hidden states for the baseline path. In FP8 dispatch mode, input is a tuple of FP8 values and scale factors. The receiving expert integration consumes that representation according to its GEMM contract. Combine takes and returns BF16 activation rows; gate multiplication belongs in the caller's expert path, not in the transport.

Lower payload precision doesn't remove numerical validation. Scale-factor layout, hidden alignment, and expert GEMM expectations must agree. A kernel can move bytes correctly while a mismatched scale stride corrupts activations. The reference quantizer uses one float scale per 128 values: a 7,168-element FP8 row takes 7,168 payload bytes plus 224 scale bytes, versus 14,336 bytes for BF16. Routing metadata and protocol overhead are additional, so this isn't exactly a 2× wire-byte reduction.

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. Otherwise, a reward-curve change after a dtype switch confounds communication changes with model numerics.

Analytical SM and QP sizing

V2's get_theoretical_num_sms estimates communication SMs from topology bandwidth and a balanced gate model. Before trusting that number, ask what it can observe: expected top-k destinations, read and write work, NVLink traffic, RDMA traffic, and the link bottleneck, but not your future gate histogram.

This is an analytical model, not a runtime-tuner oracle. The helper multiplies the raw bandwidth-derived estimate by 1.25, rounds up to an even count of at least 4, and clamps to the device's multiprocessor count. When compute-communication overlap is preferred, it stays near that calculated minimum; otherwise, it raises the floor to 64 SMs for maximum standalone throughput. The implementation assumes balanced routing, whereas production DeepSeek-style gates can be group-limited and asymmetric. Inspect the actual token distribution before trusting the estimate, and pass num_sms explicitly when your application overlap budget demands tighter bounds.

Queue-pair (QP) sizing follows a related rule. Direct mode encourages roughly one QP per communication SM, capped at nine in the current helper (min(num_sms, 8 + 1)). Hybrid mode encourages num_sms * 16 + 1 QPs so channels and notification work can have independent queues, then caps that value by allocated QPs. More QPs consume resources and can increase RDMA doorbell ringing. Use the theoretical count as a starting point, then measure queue pressure and tail latency.

The balanced-gate model counts distinct destinations, not expert selections. With four experts split evenly across two ranks and two distinct experts sampled uniformly, six expert pairs are possible. Two pairs stay on one destination rank; four touch both. Expected destination count is therefore (2×1+4×2)/6=5/3(2×1 + 4×2)/6 = 5/3(2×1+4×2)/6=5/3, not 2. The helper uses the equivalent formula G[1−(E−E/Gk)/(Ek)]G[1-\binom{E-E/G}{k}/\binom{E}{k}]G[1−(kE−E/G​)/(kE​)], for EEE experts evenly divided among GGG groups.

This standard-library check compares that formula with exhaustive enumeration. It also reproduces the pinned QP helper's arithmetic without claiming that the resulting queue count is optimal on hardware:

destination_and_queue_counts.py
1from itertools import combinations 2from math import comb, isclose 3 4def expected_destinations(experts, groups, topk): 5 if (any(type(v) is not int for v in (experts, groups, topk)) 6 or experts < 1 or groups < 1 or experts % groups 7 or not 0 <= topk <= experts): 8 raise ValueError("Invalid balanced routing model") 9 return groups * (1 - comb(experts - experts // groups, topk) / comb(experts, topk)) 10 11def queue_count(sms, allocated, hybrid): 12 if type(sms) is not int or type(allocated) is not int or min(sms, allocated) < 1: 13 raise ValueError("Expected positive SM and QP counts") 14 requested = sms * 16 + 1 if hybrid else min(sms, 9) 15 return min(requested, allocated) 16 17pairs = list(combinations(range(4), 2)) 18enumerated = sum(len({expert // 2 for expert in pair}) for pair in pairs) / len(pairs) 19assert isclose(enumerated, expected_destinations(4, 2, 2)) 20print(f"expected destination ranks: {enumerated:.6f}") 21print(f"12 SMs, 128 allocated QPs: direct={queue_count(12, 128, False)}, hybrid={queue_count(12, 128, True)}")
Output
1expected destination ranks: 1.666667 212 SMs, 128 allocated QPs: direct=9, hybrid=128

The hybrid request is 193 queues, but only 128 are allocated. The helper caps at 128; it doesn't allocate the missing 65. Group-limited or skewed gates violate the uniform-subset assumption behind the expected-destination formula. The source explicitly warns against using its balanced SM model for V3.0's group-limited gate.

Why can a balanced-gate SM estimate be wrong for a production MoE model?

Answer

The gate distribution can be asymmetric or group-limited. That changes read, write, RDMA, and NVLink traffic, so the modeled bottleneck and chosen SM count no longer match the real route.

JIT compilation is part of the runtime

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.

That choice avoids compiling every shape specialization during installation, but moves a failure boundary into first use. A missing CUDA compiler, incompatible NCCL headers, or unsupported instruction can surface at the first dispatch. The pinned JIT selects its architecture from the active device and compiler support; TORCH_CUDA_ARCH_LIST controls extension/build targets, not every runtime JIT specialization. Separate compile, cold-start, and steady-state timings.

Relevant environment switches include:

VariablePurpose
EP_JIT_CACHE_DIRChoose a persistent kernel cache directory
EP_JIT_DEBUGPrint JIT diagnostics
EP_JIT_DUMP_PTX / EP_JIT_DUMP_SASSSave generated assembly for inspection
EP_JIT_PTXAS_CHECKAssert no local-memory use in compiled kernels
TORCH_CUDA_ARCH_LISTSelect CUDA architecture targets
EP_NCCL_ROOT_DIRPoint build and JIT lookup at NCCL headers and libraries

For a 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.

V2 and V1 are different products

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]Reference 2DeepEP V1 Documentationhttps://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/docs/legacy.md

BoundaryV1V2
Main Python objectBufferElasticBuffer
Primary network backendNVSHMEMNCCL Gin
Buffer sizingSeparate NVLink/RDMA hintsUnified elastic buffer hint
Low-latency EPPure RDMA methodRemoved 0-SM RDMA low-latency mode
SM selectionCaller-selected or tuned configsAnalytical helper, manual override available
Scale domainNormal and low-latency variantsDirect and hybrid logical domains
Documentation statusArchivedCurrent 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 shouldn't be compared with V2's SM90 and SM100 tables until shapes and links match.[2]Reference 2DeepEP V1 Documentationhttps://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/docs/legacy.md[4]Reference 4DeepSeek-V3 Technical Report.https://arxiv.org/abs/2412.19437

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.

Experimental PP, CP, and Engram boundaries

The current repository includes experimental primitives beyond EP. Treat each as a separate boundary rather than as another dispatch mode:

  • Pipeline parallel (PP): send and receive tensors through symmetric memory. The README lists 0-SM PP with RDMA as an experimental path. It doesn't replace Megatron's full pipeline scheduler.
  • Context parallel (CP): use copy-engine-oriented paths for context slices, including a 0-SM copy-engine variant. It doesn't define attention semantics or sequence partitioning policy.
  • Engram: fetch remote key-value entries through RDMA, with optional CPU-backed storage and TMA-aligned scale factors. It doesn't provide a model-level retrieval index.
  • AGRS: experimental all-gather and reduce-scatter sessions over NVLink symmetric memory.

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.[1]Reference 1DeepEPhttps://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d

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.

Requirements and cluster checks

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). These are requirements for the pinned source, not a guarantee that a different revision keeps the same floor.[1]Reference 1DeepEPhttps://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d

Before a multi-node job starts, check the path from hardware to buffer:

CheckWhy it mattersFailure symptom
GPU capability and PTX targetJIT kernels need supported instructionsCompile or launch error
CUDA, PyTorch, NCCL versionsHeaders and runtime device APIs must agreeImport, communicator, or JIT failure
NVLink peer mapScale-up domain must match NCCL local symmetric access (LSA)Slow or invalid hybrid path
RDMA device and rail mapGin needs usable network contextsGin unavailable assertion
QP allocationHybrid channels need enough queuesInitialization or timeout
Symmetric memory capacityBuffer and workspace must fitAllocation or window registration failure
Service-level and traffic isolationCongestion can distort route latencyTail 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. Redact credentials and private host details before sharing it.

Applications in giant-model post-training

DeepEP matters when a policy or teacher is a sparse MoE model. The previous slime study showed that rollout, reward, and training must agree on tokens and weight versions. Those sampled trajectories still pass through MoE layers, so every generated token can trigger another dispatch and combine. Rollout needs fast expert routing; training 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 compare three placement patterns. For each one, ask which phase owns the communicator and which receipt would reveal its cost:

Post-training patternDeepEP roleMeasurement
Separate rollout and trainer clustersServe MoE layers during rollout; trainer uses its own EP groupTokens per second, sync cost, route version
Colocated actor and rolloutReuse GPU memory and communicator when phases alternatePeak buffer bytes, pause time, overlap
Teacher or specialist distillationRun many expert policies or a large teacher with repeatable routingDeterministic output, FP8 error, aggregate bandwidth

The communication library doesn't solve policy staleness, reward hacking, or checkpoint synchronization. It can 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, separate transport delay, dtype error, and algorithm behavior before changing the policy.

A runnable routing ledger

Work token a by hand before touching code. Its hidden state is (1.0, 0.0). Give expert kkk a toy map that multiplies by k+1k + 1k+1, so expert 0 returns (1.0, 0.0) and expert 3 returns (4.0, 0.0). Predict the combined first coordinate before reading the equation: gate 0.70 keeps most of expert 0's value, while gate 0.30 adds a smaller expert 3 contribution. The caller weights each expert output before combine sums the results:

0.70⋅(1.0, 0.0)+0.30⋅(4.0, 0.0)=(1.90, 0.0)0.70\cdot(1.0,\ 0.0) + 0.30\cdot(4.0,\ 0.0) = (1.90,\ 0.0)0.70⋅(1.0, 0.0)+0.30⋅(4.0, 0.0)=(1.90, 0.0)

Expert 0 lives on rank 0, so that copy of a is local. Expert 3 lives on rank 1, so the second copy is remote. The standard-library model below builds expanded receive slots, pads each expert to four rows, computes and weights outputs in expert order, and combines them using the saved inverse map. It never recomputes the original routes during combine. Shuffling the returned rows must not change the answer.

routing_ledger.py
1from dataclasses import dataclass 2import math 3 4HIDDEN = { 5 "a": (1.0, 0.0), 6 "b": (0.0, 1.0), 7 "c": (1.0, 1.0), 8 "d": (2.0, -1.0), 9} 10ROUTES = { 11 "a": [(0, 0.70), (3, 0.30)], 12 "b": [(2, 0.40), (1, 0.60)], 13 "c": [(1, 0.55), (3, 0.45)], 14 "d": [(0, 0.25), (2, 0.75)], 15} 16SOURCE_RANK = {"a": 0, "b": 0, "c": 1, "d": 1} 17EXPERTS_PER_RANK = 2 18 19def owner_rank(expert): 20 return expert // EXPERTS_PER_RANK 21 22def expert_map(expert, hidden): 23 scale = expert + 1 24 return (hidden[0] * scale, hidden[1] * scale) 25 26@dataclass(frozen=True) 27class Row: 28 token: str 29 topk_slot: int 30 hidden: tuple[float, float] 31 weight: float 32 path: str 33 34def dispatch(hidden, routes, sources, alignment=4): 35 if type(alignment) is not int or alignment < 1: 36 raise ValueError("Invalid expert alignment") 37 if set(hidden) != set(routes) or set(hidden) != set(sources): 38 raise ValueError("Token sets differ") 39 recv = {expert: [] for expert in range(4)} 40 inverse = {} 41 rank_rows = set() 42 for token, choices in routes.items(): 43 if sources[token] not in (0, 1): 44 raise ValueError("Invalid source rank") 45 if len(hidden[token]) != 2 or not all(math.isfinite(v) for v in hidden[token]): 46 raise ValueError("Expected a finite two-coordinate row") 47 seen = set() 48 for topk_slot, (expert, weight) in enumerate(choices): 49 if expert == -1: # unused router slot, not expert Python index -1 50 continue 51 if type(expert) is not int or not 0 <= expert < 4 or expert in seen: 52 raise ValueError("Invalid or duplicate expert") 53 if not math.isfinite(weight) or weight < 0: 54 raise ValueError("Invalid gate weight") 55 seen.add(expert) 56 destination = owner_rank(expert) 57 rank_rows.add((token, destination)) 58 slot = len(recv[expert]) 59 inverse[expert, slot] = (token, topk_slot) 60 path = "local" if destination == sources[token] else "remote" 61 recv[expert].append(Row(token, topk_slot, hidden[token], weight, path)) 62 for rows in recv.values(): 63 rows.extend([None] * (-len(rows) % alignment)) 64 return recv, inverse, rank_rows 65 66def run_experts(recv): 67 results = [] 68 for expert, rows in recv.items(): 69 for slot, row in enumerate(rows): 70 if row is None: # padding never acquires a source identity 71 continue 72 raw = expert_map(expert, row.hidden) 73 weighted = tuple(row.weight * value for value in raw) 74 results.append(((expert, slot), weighted)) 75 return results 76 77def combine(results, inverse, source_tokens): 78 combined = {token: [0.0, 0.0] for token in source_tokens} 79 seen = set() 80 for key, value in results: 81 if key not in inverse or key in seen: 82 raise ValueError("Unexpected, padded, or duplicate result slot") 83 if len(value) != 2 or not all(math.isfinite(v) for v in value): 84 raise ValueError("Invalid expert output") 85 seen.add(key) 86 token, _topk_slot = inverse[key] 87 for column in range(2): 88 combined[token][column] += value[column] 89 if seen != set(inverse): 90 raise ValueError("Missing expert results") 91 return {token: tuple(value) for token, value in combined.items()} 92 93recv, inverse, rank_rows = dispatch(HIDDEN, ROUTES, SOURCE_RANK) 94results = run_experts(recv) 95combined = combine(list(reversed(results)), inverse, HIDDEN) 96assert math.isclose(combined["a"][0], 1.90) 97assert len(inverse) == 8 and sum(map(len, recv.values())) == 16 98 99for token in HIDDEN: 100 x, y = combined[token] 101 print(f"{token}: ({x:.2f}, {y:.2f})") 102for expert in range(4): 103 copies = ", ".join(f"{row.token} {row.path}" for row in recv[expert] if row is not None) 104 print(f"E{expert} <- {copies}")
Output
1a: (1.90, 0.00) 2b: (0.00, 2.40) 3c: (2.90, 2.90) 4d: (5.00, -2.50) 5E0 <- a local, d remote 6E1 <- b local, c remote 7E2 <- b remote, d local 8E3 <- a remote, c local

This inverse map is a Python teaching structure, not DeepEP's binary EPHandle layout. Gate values live in the received rows and are applied in run_experts; combine only sums supplied vectors. Missing, duplicated, or padded return slots fail explicitly. DeepEP doesn't promise these expensive Python-style checks on every GPU operation, so retain an independent reference in integration tests.

Now move a's first selection from expert 0 to expert 2. Experts 2 and 3 both live on rank 1: there are still eight expert assignments, but only seven source-token/destination-rank rows. The receiver expands a for the two local experts after transport. A -1 selection contributes neither a transport destination nor an expert slot; don't accidentally treat it as Python's last expert.

Strengths and weaknesses

DeepEP stays small at its boundary. It exposes a compact EP API and specializes kernels for current GPU and RDMA paths. V2's unified buffer, analytical resource estimates, NCCL communicator reuse, deterministic option, and JIT compilation make that boundary inspectable. Each choice also leaves a limit to measure:

StrengthWhy it helpsLimitation
Unified V2 APIOne handle model for throughput and latency pathsV2 buffer sizing is larger than V1
NCCL Gin integrationReuses application communicators and network setupRequires recent NCCL and compatible Gin topology
Direct plus hybrid modesFits flat or NVLink-island clustersHybrid metadata and tuning are harder to debug
Analytical SM/QP sizingGood starting point without exhaustive autotuningBalanced-gate assumptions miss skewed routes
FP8 dispatch and BF16 combineCuts payload while retaining BF16 activation outputScale layout, gate application, and reduction rounding remain caller concerns
Deterministic sortingReproducible route order for tests and trainingSorting adds work and can lower throughput
Runtime JITAvoids compiling every shape during installFirst-use compile and cache failures are runtime risks
MIT source licensePermissive source reuse with notice obligationsNCCL, 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. A failed Gin assertion can expose a network deployment that doesn't satisfy the chosen mode. Diagnose against the contract before patching a kernel.

Team, contributors, governance, and licenses

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 describe the pinned source snapshot's credited contributors, not a permanent ranking of current maintainers.[1]Reference 1DeepEPhttps://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d

FieldWhat the pinned source says
OrganizationDeepSeek AI repository, focused on expert-parallel communication
Credited contributorsChenggang Zhao, Shangyan Zhou, Liyue Zhang, Chengqi Deng, Zhean Xu, Yuxuan Liu, Kuai Yu, Jiashi Li, Liang Zhao
Upstream dependencyNVIDIA NCCL Gin backend and NCCL device communication APIs
Source licenseMIT, copyright notice for DeepSeek, per LICENSE[5]Reference 5DeepEP MIT Licensehttps://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/LICENSE
Legacy dependencyV1 documentation and code use NVSHMEM, which has its own NVIDIA license terms[6]Reference 6DeepEP NVSHMEM Installation Guidehttps://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/docs/nvshmem.md
Asset boundaryCUDA, 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. An 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.[5]Reference 5DeepEP MIT Licensehttps://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/LICENSE[6]Reference 6DeepEP NVSHMEM Installation Guidehttps://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/docs/nvshmem.md

The pinned README supplies a project citation and benchmark tables, not a paper establishing every V2 result. The DeepSeek-V3 report motivates expert-parallel communication and group-limited routing; it doesn't document all of these later V2 kernels.[4]Reference 4DeepSeek-V3 Technical Report.https://arxiv.org/abs/2412.19437

Benchmark and failure-mode checklist

A throughput number is evidence only when another engineer can reconstruct its bytes, route, timing, and correctness check. Before trusting a number, capture a receipt with:

  1. DeepEP commit and JIT/compiler versions.
  2. GPU architecture, GPU count, NVLink map, NIC model, and rail topology.
  3. CUDA, PyTorch, NCCL, and optional NVSHMEM versions.
  4. Tokens per rank, hidden size, top-k, expert count, and expert alignment.
  5. Gate histogram, local-bypass rate, and deterministic setting.
  6. FP8/BF16 scale layout, SM count, QP count, and hybrid/direct mode.
  7. Warm-up policy, JIT time, dispatch time, combine time, and end-to-end layer time.
  8. Logical-byte formula and physical-link counters.

Common failures map to specific boundaries:

SymptomBoundary to inspectFirst check
Gin unavailable assertionNCCL communicator and topologyNCCL version, Gin properties, direct/hybrid mode
JIT compile failureCompiler and cacheCUDA home, architecture list, cache permissions
Receive count mismatchLayout metadataEPHandle prefixes, expert alignment, CPU sync mode
Wrong output orderDeterministic and source slotsrecv_src_metadata, cached-handle validity
Timeout at scaleRDMA and QP pressureRail map, service level, QP count, congestion
Out of memory (OOM) during initializationBuffer sizingV2 hint, CPU bytes, concurrent PP/Engram sessions
Reward regression after dtype changeNumeric contractFP8 scales, BF16 combine, expert GEMM accumulator
Slow second iterationStream dependencyEventOverlap waits and stale handles

The most dangerous failure is a plausible tensor with wrong provenance. A profiler can show kernel time, stream waits, and link traffic, but it can't establish that each row returned to its source token. Add route checksums, per-expert counts, and deterministic small-cluster tests before launching a long RL job. Communication success isn't model correctness.

Source-reading map

Read one vertical slice instead of opening every kernel at once. Begin with the public contract, follow one route into the kernels, then check the legacy boundary:

OrderFile or directoryQuestion
1README.mdWhat is V2, which claims are project-reported, and what remains experimental?
2deep_ep/buffers/elastic.pyHow do Python inputs, handles, sizing, and events fit together?
3deep_ep/utils/event.pyWhich stream waits and hooks make asynchronous output safe?
4csrc/kernels/backend/nccl.cuHow does Gin capability, rail mode, and symmetric memory initialize?
5csrc/kernels/elastic/dispatch.hppWhich launch arguments define dispatch layout and topology?
6csrc/kernels/elastic/combine.hppHow does the reverse reduction consume source metadata?
7csrc/jit/compiler.hpp and csrc/jit/cache.hppWhen are kernels generated, compiled, and reused?
8docs/legacy.mdWhich V1 assumptions should not leak into V2 guidance?
9docs/nvshmem.mdWhich 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. That ownership map is the shortest path from a profiler symptom to the component that can explain it.

Reviewing an integration

Evaluation rubric

  • Foundational: Trace token a through both experts and derive 1.90 after exactly one gate multiplication. Distinguish received gate tensors from the handle's route metadata.
  • Intermediate: Explain destination-rank deduplication, expert padding, cached-route validity, and the stream wait that must precede reading dispatched rows.
  • Advanced: Reconcile logical bytes with physical traffic, qualify balanced-gate resource estimates, and separate CPU reference agreement from GPU memory-ordering and distributed correctness.

Follow-up questions

The route changes from experts [0, 3] to [2, 3] without changing tensor shapes. What must change before dispatch?

Answer

Rebuild the route handle. Both new experts live on rank 1, so the token has one destination-rank row rather than two, followed by two expert slots on rank 1. Reusing the old handle silently preserves the old route; matching shapes aren't a validity check.

An integration agrees with the CPU routing ledger but hangs only when a direct logical domain spans NVLink and RDMA. What remains untested?

Answer

The ledger doesn't execute transport, remote visibility, barriers, event lifetimes, or GPU kernels. Check the deployed commit against the mixed-fabric ordering fix, then reproduce on the actual topology with upstream distributed correctness tests. A CPU match doesn't establish that a missing system-scope fence is harmless.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.After expert GEMMs finish, which object lets DeepEP map expert-slot outputs back to source tokens?

Correct answer: The EPHandle from dispatch, carrying source slots, prefixes, and route metadata.

EPHandle contains source metadata, slots, prefixes, and route indices. Gate values are separate tensors; the caller applies them before combine sums activation contributions.

2.A deployment uses multiple nodes with NVLink inside each node and RDMA between nodes. Which DeepEP mode models those domains explicitly?

Correct answer: Hybrid separates RDMA scale-out groups from NVLink scale-up islands in the topology.

Hybrid mode represents RDMA scale-out groups plus NVLink scale-up islands. Direct mode treats the communicator as one logical flat domain.

3.A benchmark reports 90 GB/s logical bandwidth while the NIC counter reports 45 GB/s. What should be reconciled before calling it a regression?

Correct answer: Reconcile logical and physical byte counters, including local traffic and the topology path.

Logical bandwidth can count algorithm-level traffic, including local paths, while a NIC sees remote physical bytes. The receipts must name both denominators and topology.

4.An expert GEMM reads recv_x immediately after dispatch returns, while communication was launched asynchronously. Which fix preserves the intended overlap?

Correct answer: Use EventOverlap.current_stream_wait() before GEMM, after launching independent work.

A Python return does not imply device readiness. EventOverlap carries the stream dependency, allowing independent work first and expert reads only after the event is satisfied.

5.The theoretical communication-SM estimate assumes balanced gates, but production routing is group-limited and skewed. What should the engineer do?

Correct answer: Re-estimate SMs from observed skew and measure the resulting bottleneck under load.

The helper models balanced token destinations. Skew changes per-expert and per-link work, so its calculated SM floor may not match the actual bottleneck.

6.A team sees V1's Buffer and V2's ElasticBuffer in old integration notes. What compatibility conclusion follows?

Correct answer: V1 and V2 differ in object, transport, sizing, and low-latency semantics.

V1 uses archived Buffer and NVSHMEM-oriented paths, while V2 uses ElasticBuffer and NCCL Gin with different domains and sizing. Matching names or imports do not establish compatibility.

7.A model sends FP8 payloads but combines expert outputs in BF16. Which contract must accompany the low-precision payload?

Correct answer: Pass FP8 values with matching scale factors and expected layout and stride before BF16 combine.

FP8 dispatch moves values plus scale metadata. A mismatched scale layout can corrupt activations even when transport and row counts are correct; combine can still reduce in BF16.

8.A cached EPHandle is reused after top-k routing and expert dimensions change. What should the caller do?

Correct answer: Invalidate and rebuild the handle whenever route, dimensions, or expert layout changes.

Cached handles skip layout work only when route and layout still match. The API checks selected dimensions but doesn't compare new routing choices; the caller must invalidate a stale mapping.

9.Token a has expert outputs 1 and 4, with gates 0.70 and 0.30. What activation contributions should the caller supply to DeepEP combine?

Correct answer: Supply 0.70 and 1.20; combine sums already weighted contributions to 1.90.

Activation reduction and optional return of top-k weight metadata are separate. Applying no gate gives 5; applying the gate twice gives 0.85. Shapes alone cannot distinguish these errors.

10.Expanded local expert counts are [3, 1] with alignment 4. Which starts, endpoint metadata, and total slot allocation follow the pinned EPHandle convention?

Correct answer: Starts [0, 4], endpoints [3, 5], total 8 slots; four slots contain real rows.

Expanded psum metadata stores the aligned segment start plus its unaligned count. Aligning each endpoint yields the next segment boundary. Padding slots must never become returned token contributions.

10 questions remaining.

Next Step
Continue to Deep Dive - Tinker

DeepEP is the in-cluster MoE communication contract: dispatch, combine, NVLink, RDMA, and the handles that keep those copies honest. Tinker is a hosted LoRA service that keeps that cluster off your laptop, so the next chapter is the algorithm and API boundary sitting above kernels like this one.

PreviousDeep Dive - slime
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

DeepEP

DeepSeek AI · 2026

https://github.com/deepseek-ai/DeepEP/tree/dd758caf451848bd150e1046af3d0a73e5fff38d

DeepEP V1 Documentation

DeepSeek AI · 2026

https://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/docs/legacy.md

DeepEP Mixed-Fabric Gin Barrier Ordering

DeepSeek AI · 2026

https://github.com/deepseek-ai/DeepEP/blob/01dc3aaac82068020353dce2c302e38153c0bfaa/deep_ep/include/deep_ep/common/comm.cuh

DeepSeek-V3 Technical Report.

DeepSeek-AI · 2024 · arXiv preprint

https://arxiv.org/abs/2412.19437

DeepEP MIT License

DeepSeek AI · 2026

https://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/LICENSE

DeepEP NVSHMEM Installation Guide

DeepSeek AI · 2026

https://github.com/deepseek-ai/DeepEP/blob/dd758caf451848bd150e1046af3d0a73e5fff38d/docs/nvshmem.md

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.