Read DeepGEMM as a GPU-kernel case study: tiled GEMMs, low-bit scaling, runtime JIT, the DeepSeek indexer, and Mega MoE communication-compute overlap.
Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
When a language model serves one token, it may execute thousands of matrix multiply instructions before a user sees the next word. A good kernel can save a few nanoseconds per tile, then repeat that saving across every layer, request, and GPU. DeepGEMM is a useful project to study because it exposes that work instead of hiding it behind a general-purpose framework.
The repository is a small CUDA library for tensor-core operations used by modern large language models. It includes dense and grouped GEMMs, low-bit formats, multi-query attention (MQA) scoring for the DeepSeek-V3.2 lightning indexer, and Mega MoE, which fuses expert-parallel communication with expert computation.[1] Its runtime just-in-time (JIT) compilation specializes kernels for a concrete shape. This lesson reads the code as an engineering argument: match data layout and schedule to a fixed hardware contract, then specialize only where measured shapes justify it.
The project isn't a model server. It doesn't own request admission, token budgets, or a public HTTP API. It supplies fast device kernels and a Python interface that other model and serving systems can call. That boundary is its first lesson: a kernel library wins at one layer, while the surrounding runtime decides whether that win improves end-to-end latency.
DeepGEMM is maintained in the DeepSeek AI open-source ecosystem. Its README describes a unified tensor-core kernel library and lists DeepSeek contributors. The Mega MoE release notes name a larger group of contributors because communication kernels, layouts, scheduling, and benchmarking all cross subsystem boundaries.[1][2]
| Field | Current project fact |
|---|---|
| Origin and steward | DeepSeek AI publishes and maintains DeepGEMM under the deepseek-ai organization.[1] |
| Named contributors | The repository citation names Chenggang Zhao, Zhean Xu, Liang Zhao, Jiashi Li, Chenhao Xu, Anyi Xu, Shengyu Liu, Kexing Zhou, and Kuai Yu.[1] |
| Contributor model | Development happens through the public repository and focused kernel pull requests. The current snapshot doesn't declare a foundation, TSC, or formal committer ladder.[1][2] |
| Source license | MIT, with DeepSeek copyright, for the pinned source snapshot.[3] |
| Commercial boundary | DeepGEMM is a kernel library, not the DeepSeek API or a model release. Its own Mega MoE announcement separates kernel development from internal model releases.[2] |
| Asset boundary | DeepSeek model versions, weights, tokenizers, and training data can use terms different from the kernel library. Check the exact model card and version. |
The project sits beside, not above, libraries such as CUTLASS and CuTe. DeepGEMM borrows ideas from those projects while keeping a smaller set of public kernel functions. That smaller surface makes it easier to inspect the generated path for one shape, but it also means callers must prepare layouts and scaling factors correctly. There is no general-purpose fallback that can make every malformed tensor fast.
| Layer | DeepGEMM owns | Caller still owns |
|---|---|---|
| Tensor math | GEMM, grouped GEMM, MQA logits, fused Mega MoE | Model graph and layer ordering |
| Device schedule | Tile sizes, persistent task order, barriers, ring buffers | Request batching and token admission |
| Data representation | Kernel-required FP8, FP4, BF16, and scale layouts | Casting, transposition, padding, and source quantization |
| Compilation | Shape and architecture-specific JIT modules | CUDA toolchain, process launch, cache lifecycle |
| Measurement | Correctness tests and shape-specific benchmarks | Product SLOs, traffic mix, and end-to-end comparison |
There are four main application paths:
DeepSeek-V3 is a clear motivation for the MoE paths. Its technical report describes 671B total parameters with roughly 37B active per token, 256 routed experts, and 8 routed experts selected per token.[5] Active compute is smaller than total storage, but every expert still has to live somewhere and routed tokens still have to move between ranks. That gap is exactly where grouped kernels and Mega MoE spend their complexity budget.
DeepGEMM doesn't ship one standalone research paper that explains every kernel. Its source tree and release pull requests are the primary engineering record. The papers explain why the kernels matter: DeepSeek-V3 motivates sparse expert routing and expert-parallel placement, while DeepSeek-V3.2 describes the lightning indexer that needs weighted MQA logits.[5][4]
Use the sources in four layers:
| Layer | Source | Question it answers |
|---|---|---|
| Model architecture | DeepSeek-V3 technical report | Why total expert storage and active-token compute diverge |
| Indexer behavior | DeepSeek-V3.2 paper | Why MQA scoring needs range-aware, low-bit logits |
| Kernel implementation | DeepGEMM repository | Which layouts, dtypes, architectures, and APIs are supported |
| Fused distributed path | Mega MoE release and benchmark PRs | How overlap is implemented and how one harness measured it |
This distinction keeps the evidence honest. A paper can motivate a workload without proving a kernel speedup. A benchmark can show a speedup without proving model quality. Read each claim against the source that actually owns it.
General Matrix-Matrix Multiplication, or GEMM, computes a matrix product and optionally adds an existing output:
For the common non-transposed A, transposed B layout, has shape , has shape , and has shape . Each output element is a dot product of one row from A and one row from B. The operation count is approximately floating-point operations because each multiply is paired with an add.
Consider a tile with , , and . It carries multiply-accumulate pairs, or floating-point operations when multiply and add each count as one. If a kernel finishes in microseconds, its arithmetic rate is about teraFLOPS. That number is a derived rate for that shape, not a promise that every request will run at that rate.
The hardware sees more than arithmetic. It sees global-memory loads, shared-memory staging, scale-factor loads, register or tensor-memory pressure, barriers, and output stores. A kernel can have high arithmetic intensity and still lose if a layout forces extra transposes or if a small M dimension leaves tensor cores idle.
DeepGEMM's low-bit paths trade input precision for bandwidth and tensor-core throughput. FP8 is an 8-bit floating-point format used for activations or weights. FP4 stores two 4-bit values per byte and needs scale metadata to recover a useful numeric range. BF16 is a 16-bit format commonly used for accumulators and outputs because its exponent range is close to FP32.
The kernel doesn't cast a tensor and hope for the best. It consumes scale factors in a specific layout. For the current APIs, SM90 expects FP32 (32-bit floating point) scale factors, while SM100 expects packed UE8M0 scale values in an integer tensor. UE8M0 is a power-of-two style scale encoding with eight exponent bits and no mantissa bits. The format is compact and hardware-friendly, but it makes layout and packing part of the API contract.[1]
For a per-32 K-group scale, a row of K values is split into groups of 32. Each group gets one scale. The caller must align the scale tensor so the tensor-memory accelerator (TMA) can issue the expected multidimensional copy. A correct numerical tensor with the wrong stride can still fail an assertion or trigger a slow preparation path.
| Input path | Typical value format | Scale representation | What to watch |
|---|---|---|---|
| SM90 FP8 GEMM | FP8 A and B | FP32 scales | NT layout and TMA alignment |
| SM100 FP8 x FP4 GEMM | FP8 A, packed FP4 B | Packed UE8M0 integer scales | K-group granularity and major layout |
| BF16 GEMM | BF16 A and B | No low-bit scale tensor | Accumulator type and tile shape |
| Mega MoE, FP8 × FP4 path | FP8 activations, FP4 weights | Per-32 UE8M0 scales | Symmetric buffer and expert alignment; BF16 path also exists |
The practical rule is to treat casts and layout transforms as a pipeline stage before the kernel. DeepGEMM provides helpers such as transform_sf_into_required_layout, but the README warns that input transposition and FP8 casting may be slower utility operations. In a production path, fuse those operations into an earlier model kernel when profiling shows they dominate.
The repository supports NVIDIA SM90 and SM100 architectures. SM90 and SM100 are architecture families, not interchangeable names for a single GPU. A kernel selected for one family may use different scale types, layouts, instructions, and compiler flags on the other.
The dispatch layer checks the device architecture, input major layout, scale dtype, and output shape before selecting an implementation. On SM90, the current FP8 path uses FP32 scales and supports the NT layout for the primary GEMM interface. On SM100, the FP8 x FP4 path can select NT, TN, NN, or TT variants and uses packed UE8M0 scales.[1]
This split is deliberate. A single portable kernel would need to hide the differences behind extra branches or generalized iterators. DeepGEMM instead keeps architecture-specific implementations visible, then uses a thin API layer to check the contract and route to the right implementation.
The biggest mistake at this boundary is treating an architecture mismatch as a tuning issue. If a SM100-only FP4 path is sent to SM90, no tile-size tweak can fix it. Check torch.cuda.get_device_capability(), CUDA version, scale dtype, and the major layout before comparing benchmarks.
TMA, the Tensor Memory Accelerator, copies multidimensional tensor regions between global memory and shared memory using a descriptor. A descriptor carries shape and stride information so the copy engine can move a tile without every thread computing addresses.
TMA reduces address instructions and stages a copy asynchronously while a previous tile is being consumed. That lets a kernel overlap global-memory movement with tensor-core math, provided barriers and shared-memory stages are sized correctly.
The catch is alignment. DeepGEMM's API checks TMA-aligned strides for input and scale tensors. A caller who transposes a tensor with a view may produce mathematically correct values but a stride pattern that can't satisfy the descriptor. The safe path is to create the required major layout explicitly, then validate both shape and stride.
TMEM, or tensor memory, is the SM100 accumulator storage used by the tcgen05 instruction family. It differs from ordinary registers and shared memory in both access pattern and synchronization rules. The kernel allocates TMEM slices, issues matrix multiply-accumulate operations, then synchronizes before reading results back for an epilogue.
This split changes how a programmer thinks about a tile. Global memory holds model tensors. Shared memory stages TMA input. TMEM holds the matrix accumulator. The data path has three separate lifetimes and each has its own barrier or fence.
The SM100 implementation calls tcgen05 matrix multiply-accumulate instructions through CUTLASS and local PTX helpers. One instruction consumes a selected tile from shared memory and accumulates into TMEM. The epilogue then applies output conversion, activation-specific work, or a store.
The important invariant is ownership. Warp groups that issue tcgen05 work follow different access rules from ordinary CUDA threads reading and writing shared registers. DeepGEMM's implementation places thread synchronization around TMEM allocation, TMA completion, commit, and load operations. Removing one fence may produce a race that appears only under a particular shape or rank count.
| Resource | Role in one tile | Typical failure symptom |
|---|---|---|
| Global memory | Source A, B, C, and scale tensors | Low bandwidth or wrong stride |
| Shared memory | TMA staging and epilogue buffers | TMA barrier timeout or overwritten stage |
| TMEM | tcgen05 accumulator | Race, invalid read, or incorrect output |
| Tensor core | Multiply-accumulate execution | Low utilization on bad tile shape |
| Host JIT cache | Compiled cubin and metadata | Recompile latency or stale artifact |
DeepGEMM compiles kernels at runtime instead of requiring a complete CUDA build for every possible shape. The C++ JIT compiler creates source for a selected implementation, builds a compiled GPU binary (CUBIN) with the NVIDIA CUDA Compiler (NVCC) or NVIDIA Runtime Compilation (NVRTC), and loads it through a runtime handle. Installation can therefore skip a giant matrix of prebuilt kernels.[1]
The cache key includes the kernel name, compiler signature, compiler flags, and generated code. A digest maps that signature to a directory under $HOME/.deep_gemm by default, or under DG_JIT_CACHE_DIR when configured. Compilation writes into a temporary directory, flushes files, and atomically renames the completed directory. That atomic publish step matters when multiple distributed ranks try to compile the same shape on a shared filesystem.
1kernel signature = name + compiler + flags + generated source
2cache key = digest(kernel signature)
3cache hit = load existing cubin
4cache miss = compile temporary cubin, publish atomicallyThe JIT can specialize block sizes, scale granularity, architecture, and optional features. Traffic with many new shapes exposes the corresponding weakness: a model serving system with unbounded sequence lengths can produce a long compile tail unless it warms likely signatures or constrains shapes.
The signature should include every value that changes generated code or the memory contract: architecture, M/N/K tile choices, scale granularity, grouped versus dense mode, activation, and feature flags such as Programmatic Dependent Launch (PDL). If a value affects code but is missing from the key, a cached cubin can be reused for an incompatible layout.
DG_JIT_DEBUG, DG_JIT_PRINT_COMPILER_COMMAND, DG_JIT_DUMP_PTX, and DG_JIT_DUMP_SASS expose the compilation path. DG_JIT_PTXAS_CHECK=1 asks the build to reject local-memory usage. These switches are useful during kernel bring-up, but production launchers should control them deliberately because logs and compilation artifacts can be large.
An MoE layer routes each token to a small set of experts. Each expert sees a different number of tokens, so a naïve implementation launches one GEMM per expert or pads every expert to the same M. Both choices waste work when routing is uneven.
DeepGEMM's contiguous grouped API concatenates expert token segments into one M axis. N and K stay fixed because experts share the same weight dimensions. A compact layout tensor describes each expert's segment. The kernel can then schedule multiple expert blocks while reusing one compiled implementation.
The segment boundaries need alignment. The README's API requires each expert segment to meet the M block alignment returned by get_mk_alignment_for_contiguous_layout(). Padding belongs in the caller's token packing, and the resulting output must be unpacked to the original token order.
| Grouped mode | Known at launch? | Layout | Intended workload |
|---|---|---|---|
| Contiguous | Expert token counts known | Concatenated M segments | Training and prefill |
| Masked | Counts hidden from CPU | Fixed M plus validity mask | Decode with CUDA graphs |
| K-grouped backward | K segments known | Fixed M/N, grouped K | MoE weight gradients |
Masked grouped GEMM solves a different problem. During decode, the CPU may not know how many tokens each expert receives, especially when a CUDA graph fixes tensor shapes. A mask marks valid rows, so the GPU launches one shape while skipping invalid portions. This avoids a host synchronization, but it may execute less efficiently than a perfectly packed contiguous layout.
Suppose four experts receive token counts [64, 192, 32, 128], and the kernel's M alignment is 128. The contiguous pack reserves aligned capacities such as [128, 256, 128, 128], while separate cumulative valid ends preserve each expert's true row boundary. Exact padding can be larger under the selected heuristic. The weights keep one shared N and K.
The padding isn't free. It increases memory traffic and can lower arithmetic utilization. Yet it can still win over four separate launches because one persistent kernel amortizes launch overhead and keeps tiles in flight. Measure both total kernel time and useful-token throughput; a low microsecond number with high padding can mislead.
Multi-Query Attention (MQA) uses many query heads with one shared key/value representation. DeepGEMM's indexer kernel computes a weighted ReLU score for each query token against a selected range of key/value tokens. For query i and key/value token j, the README gives this computation:
The key/value vector is dequantized with its scale, each head computes a dot product, ReLU clips negative scores, and the per-head weights sum the result into a token-to-token logit matrix. Callers can request a full matrix or a compressed range with per-query bounds.
DeepGEMM offers non-paged scoring for prefill and paged scoring for decode. Paged mode reads key/value blocks through a block table, which matches a serving runtime's fragmented cache. The indexer can therefore sit beside, not inside, the main attention kernel: it selects useful context positions before the model performs a larger attention operation.[4]
The MQA path shows why a kernel library needs model-specific features. Generic GEMM can express a dot product, but it doesn't automatically fuse dequantization, ReLU, head weighting, range masking, and cache paging. The specialized path saves intermediate writes and keeps the score reduction close to the input tiles.
Mega MoE is DeepGEMM's most ambitious kernel. It fuses expert-parallel dispatch, linear 1, SwiGLU, linear 2, and expert-parallel combine into one SM100 kernel. It uses PyTorch symmetric memory so ranks can address each other's buffers and overlap NVIDIA's NVLink GPU interconnect traffic with tensor-core work.[2]
The computation still follows ordinary MoE semantics:
Schedule and storage provide the core mechanism. A symmetric ring buffer holds token data, scale data, routing indices, weights, and intermediate expert activations. Persistent schedulers claim blocks, move remote data into reusable slots, run L1 and L2 work, and release slots only after all consumers finish.
Persistent scheduling means a resident set of cooperative thread arrays repeatedly claims work instead of launching a new grid for every expert block. This makes communication and compute phases interleave, but it also places more state in registers and shared memory. The source comments call out warmup waves, ring-block reuse, and ordering rules that prevent an L2 task from consuming an unfinished L1 block.
The ring buffer is bounded. A new dispatch can't overwrite a slot until the previous consumer signals that all N blocks are complete. That rule is a correctness invariant, not a performance hint. A missing release or an early reuse can produce data races that look like occasional model quality regressions rather than a clean CUDA error.
The Python API allocates a SymmBuffer for a distributed process group. For one rank it can use a regular CUDA allocation; for multiple ranks it uses PyTorch's symmetric-memory rendezvous. The buffer exposes typed views for input tokens, scale factors, top-k indices, top-k weights, and intermediate activations.
The current Mega MoE path requires PyTorch 2.9 or newer, FP8 x FP4 weights, SwiGLU activation, SM100 hardware, and dimensions that satisfy alignment checks.[2] The operation also expects a multi-process launch when expert parallelism spans ranks. A single-process smoke test can validate API shape, but it doesn't prove remote buffer synchronization or NVLink overlap.
1# Partial API sketch. Allocate and fill SymmBuffer before this call.
2buffer = deep_gemm.get_symm_buffer_for_mega_moe(
3 group, num_experts, max_tokens_per_rank, topk, hidden, intermediate_hidden
4)
5transformed_l1, transformed_l2 = deep_gemm.transform_weights_for_mega_moe(
6 l1_weights, l2_weights
7)
8deep_gemm.fp8_fp4_mega_moe(
9 y, transformed_l1, transformed_l2, buffer, recipe=(1, 1, 32)
10)This snippet is intentionally a partial API sketch, not a copy-runnable lab. The caller must cast activations, pack scales, initialize the process group, copy routing inputs into the symmetric buffer, and size output tensors. Keeping those steps explicit prevents a common mistake: assuming the fused kernel will perform every model-graph conversion for you.
DeepGEMM's benchmark pull request reports Mega MoE under eight-way expert parallelism, with values averaged across eight ranks. It compares a fused kernel with a legacy dispatch, GEMM, activation, and combine path. The results are shape-specific and use two named DeepSeek-V4 configurations, so they should be read as evidence for that harness rather than a universal speedup.[6]
The reported Flash shape has 256 experts, top-k 6, hidden size 4096, and intermediate size 2048. In the pull request's labels, batch size means tokens per rank.
| Tokens per rank | Mega MoE time | Compute | Global memory | Interconnect | Speedup vs legacy |
|---|---|---|---|---|---|
| 1 | 56.5 µs | 5 TFLOPS | 1311 GB/s | 1 GB/s | 1.96x |
| 512 | 146.5 µs | 1056 TFLOPS | 3192 GB/s | 266 GB/s | 1.73x |
| 8192 | 1283.1 µs | 1928 TFLOPS | 998 GB/s | 499 GB/s | 1.56x |
| 32768 | 4855.5 µs | 2038 TFLOPS | 794 GB/s | 529 GB/s | 1.62x |
The Pro shape has 384 experts, top-k 6, hidden size 7168, and intermediate size 3072. Larger dimensions change both tensor-core occupancy and communication volume.
| Tokens per rank | Mega MoE time | Compute | Global memory | Interconnect | Speedup vs legacy |
|---|---|---|---|---|---|
| 1 | 108.1 µs | 7 TFLOPS | 1758 GB/s | 1 GB/s | 1.61x |
| 512 | 369.6 µs | 1098 TFLOPS | 4619 GB/s | 182 GB/s | 1.54x |
| 8192 | 2818.5 µs | 2304 TFLOPS | 1094 GB/s | 393 GB/s | 1.50x |
| 32768 | 10655.2 µs | 2438 TFLOPS | 692 GB/s | 417 GB/s | 1.54x |
The table gives useful questions to ask. Why does one token show tiny interconnect throughput? Because elapsed time is short and the routed payload is small, not because the network is unimportant. Why does speedup vary with token count? Tile occupancy, buffer reuse, and the ratio of communication to compute change with M. A product benchmark should add model version, GPU type, CUDA version, rank topology, warmup count, correctness check, and p50/p99 latency before making a deployment decision.
{fmt}, NVCC or NVRTC, and PyTorch versions all affect the supported path.DeepGEMM chooses explicit contracts and measured specialization over broad portability. That choice fits a model team that controls hardware, CUDA images, and tensor layouts, but fits less well as a drop-in library for arbitrary user tensors.
The README lists Python 3.8 or newer, C++20-capable compilers, PyTorch 2.1 or newer for the base library, CUTLASS 4.0 or newer, and CUDA 12.3 or newer for SM90. CUDA 12.9 or newer is recommended for best performance and required by current SM100 support.[1] Mega MoE adds a PyTorch 2.9 requirement.
Before running a benchmark, record:
For a first local check, start with a dense BF16 or FP8 GEMM shape from the repository tests. Enable DG_JIT_DEBUG=1 only while tracing compilation. Use DG_JIT_CACHE_DIR on fast local storage, not a path with unreliable distributed-file semantics. For multi-rank Mega MoE, run the repository's distributed test harness and compare output to the non-fused baseline before reading timing output.
Make constraints part of your model runtime's validation layer. Check that hidden and intermediate dimensions meet the required multiples, expert count is divisible by rank count, token counts fit the symmetric buffer, and scale tensors have the required packed width. Reject a request with a clear error before it reaches a CUDA graph or a collective barrier.
| Constraint | Why it exists | Guardrail |
|---|---|---|
hidden % 128 == 0 on low-bit Mega paths | Scale and tile packing | Validate model config at startup |
intermediate_hidden % 128 == 0 | Expert weight and scale layout | Check every expert block |
| Expert count divisible by rank count | Even local ownership | Assert before process-group launch |
| Contiguous grouped M alignment | Stable tile boundaries | Pad and preserve segment map |
recipe=(1, 1, 32) for FP8 x FP4 Mega MoE | Supported scale granularity | Keep recipe in config and logs |
| SM90 or SM100 device | Architecture-specific implementation | Route unsupported devices to fallback |
Start with scale packing, tensor major order, and output accumulation dtype. Compare a small shape against the PyTorch reference after dequantization. If the error appears only on SM100, inspect UE8M0 packing and TMA strides. If it appears only in grouped mode, print segment offsets and padded M counts.
Check whether the JIT cache missed. Log kernel name, signature, compile duration, and cache directory. Warm the expected dimensions during process startup or route the first request to a fallback while compilation completes. Don't hide compile time inside p99 inference latency.
Treat a hang as a synchronization or collective contract failure until proven otherwise. Confirm every rank entered the same process-group call, buffer sizes match, top-k indices are valid, and symmetric memory rendezvous completed. Enable communication-kernel debug only in a controlled reproduction because it zeros the buffer and requires callers to recopy inputs before each invocation.
Check whether casts, transposes, token packing, or network transport sit outside the timed kernel. Then compare useful-token throughput against padded-token throughput. A fused kernel can be faster while the full graph regresses if preparation dominates.
Repeat after warmup and pin rank placement. A benchmark that runs eight ranks on a different NVLink topology from production can report a misleading interconnect rate. Record clocks and power policy when comparing machines.
Use this order when studying the clone:
README.md defines public APIs, supported architectures, scale formats, and examples.deep_gemm/__init__.py exposes Python functions and utility transforms.csrc/apis/gemm.hpp checks shapes and dispatches to SM90 or SM100 implementations.csrc/jit/compiler.hpp shows signatures, compiler flags, cache publication, and NVCC or NVRTC paths.deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_gemm_1d1d.cuh traces TMA, TMEM, and tcgen05 stages for dense GEMM.deep_gemm/include/deep_gemm/scheduler/mega_moe.cuh explains expert task order, warmup, and ring capacity.deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe.cuh connects remote loads, grouped GEMMs, barriers, and stores.tests/test_attention.py and tests/test_mega_moe.py show reference calculations and shape sweeps.Read one data structure at a time. First find the shape and stride checks. Then find where a scale tensor is transformed. Finally trace who writes each buffer and who releases its barrier. This route is more productive than starting with a 1,000-line CUDA kernel and guessing the invariants.
Answer every question, then check your score. Score 75% or higher to mark this lesson complete.
7 questions remaining.
DeepGEMM
DeepSeek AI · 2026
Mega MoE: Fusing Expert Parallel Communication with Computation
DeepSeek AI · 2026
DeepGEMM MIT License
DeepSeek AI · 2026
DeepSeek-V3.2: Pushing the Frontier of Open Large Language Models
DeepSeek AI · 2025 · arXiv
DeepSeek-V3 Technical Report.
DeepSeek-AI · 2024 · arXiv preprint
Mega MoE Benchmark Results
DeepSeek AI · 2026
Questions and insights from fellow learners.