Kimi K3: Architecture and SGLang Day-0 Support
From MHA to DeltaNet to KDA, a walkthrough of the attention mechanisms; how Kimi K3's hybrid architecture supports 2.8T parameters and a 1M context; and how SGLang runs K3 efficiently with a unified memory pool, chunked PP, and DCP. All diagrams are interactive.
The Kimi K3 architecture
K3 is a 2.8T-parameter MoE model with 104B parameters active per token, and it supports a native 1M-token context. K3 has 93 layers: 69 use KDA linear attention and 24 use gated global MLA. The first layer is a dense FFN; the remaining 92 use Stable LatentMoE. The 93 layers split into 8 blocks of 12, and Attention Residual aggregates across blocks. Click the diagram for the complete structure:
KV cache memory estimation
Start with memory: both MLA and MHA KV caches grow with context. The Kimi K3 day-0 support blog puts K3's compressed MLA KV cache at about 27KB per token per GPU across all 24 MLA layers, which works out to roughly 1.125KB per layer. If all 93 layers used MLA, a 1M-token request would need about 105GB on one GPU (1.125KB × 93 layers × 1M tokens). Uncompressed MHA at the same shape would store 48KB per layer per token (96 heads × 128 dims × K/V × bf16), more than 40× the MLA latent. Bringing that number down is the first problem to solve, which is why K3 cannot keep context-growing softmax attention in most layers.
K3 therefore interleaves 3 KDA + 1 MLA. KDA is a linear-attention variant whose recurrent state has a fixed size and does not grow with context; the mechanism comes later. It takes about 54MB per request per GPU and updates in place each step, while MLA keeps global attention. The model uses no explicit position encoding (NoPE); position comes implicitly from the KDA recurrence. The diagram below contrasts memory use between all-MLA and the KDA+MLA mix:
The next sections walk from classic MHA to KDA and compare what changes at each step, which makes it clear why Kimi K3 picks KDA, a linear-attention variant.
Attention mechanisms in detail: classic MHA
In ordinary MHA, decoding token first appends its own KV to the cache, then reads all entries in three steps:
- Score: compare against every token with . The subscript reads “step 's query against key ”.
- Normalize: compute , then assign attention weight .
- Aggregate: take the weighted sum of values, .
A length- sequence therefore performs about dot products: compute and cache. In the diagram, every token has a line to each of the tokens before it.
Softmax attention buys per-position reads, which gives it the most complete global memory, but the cost is just as clear. Every new token rescans the whole history, so the whole history has to be stored and compute stays at .
Attention mechanisms in detail: linear attention
Linear attention no longer keeps one KV entry per token. It accumulates history into a fixed-size matrix, . S is a fixed-size memory matrix, and every new token writes into it once it is computed: k sets the write direction, meaning where the token points in vector space; v is the content written along that direction; and q reads with . No matter how long the context becomes, each step reads and writes only S.
The diagram below gives both a math view and the same sentence used earlier, so the difference is visible directly:
kₐ=(1/√2)[1,1]ᵀkᵦ=(1/√2)[1,−1]ᵀIn MHA, "it" can point directly to "cat" because every historical token has its own KV position. The linear-attention state S has no token-position axis, so its readout mixes all historical writes in one fixed state. The diagram above shows the simplest form of naive linear attention: plain accumulation.
Attention mechanisms in detail: DeltaNet
Naive linear attention keeps adding to its state and never overwrites an old value. In the simplest case, if the same key direction receives 1 and then 4, both stay in S, and the next read returns . Different contexts usually give the same word different keys, but over a sequence as long as 1M tokens a fixed-capacity state can still fail to keep those writes apart. Say the text first says Zhang San lives in Beijing, so the state records Zhang San → Beijing, and later says Zhang San moved to Shanghai. Asked where Zhang San lives, the readout is likely a blend of Beijing and Shanghai rather than Shanghai cleanly overwriting Beijing.
DeltaNet's answer is an error-directed edit to the state. It introduces a dynamically produced scalar gate that sets how strong the current update is. Before writing, the model reads the prediction the state already holds along the current key, . It then writes the difference between the target value and that prediction instead of accumulating : . For a normalized key, the new readout is .
The coefficient controls rewrite strength: fully replaces the old association, smaller retains more of the old value, and does not rewrite it at all.
kₐ=(1/√2)[1,1]ᵀkᵦ=(1/√2)[1,−1]ᵀThat fixes same-direction rewriting but leaves a capacity question: once unrelated history keeps entering a fixed-size S, what should decay? Even unrelated key vectors are rarely perfectly orthogonal in practice, so a write in one direction projects into another and creates crosstalk. The longer the context, the larger that effect gets.
Math details: A/B in the diagram and the crosstalk term
A and B are two unit directions in key space (), not token slots or individual channels. is shorthand for “the key points along and the value is the scalar 1.” Colors only trace where each write came from; the real S stores their sum.
Suppose S holds two associations, . Reading with gives , where is the angle between the two keys. The second term is B leaking into A, scaled exactly by the inner product .
For a general set of writes the readout is , and interference between writes is governed by the Gram matrix . The readout is crosstalk-free iff all keys are pairwise orthogonal — and admits at most such directions, so once more associations are stored, crosstalk in a fixed-size state cannot be eliminated, only managed.
Attention mechanisms in detail: KDA
KDA's gating works per channel, so first, what a channel is. K3's hidden size is 7168; each of the 96 KDA heads uses its own learned projection to compress the model-wide 7168-d hidden state down to 128 dimensions, and a channel is one dimension of that projected 128-d space. Key channel sets the coefficient with which content is written into row of S:
xₜ ∈ ℝ⁷¹⁶⁸kₜ ∈ ℝ¹²⁸the highlighted dim = one channelA fixed state does not grow with context, which is exactly why it saves memory; the cost is that more and more history has to share one S. The delta rule can rewrite the same or a nearby key direction, but it does not proactively clear irrelevant old signal, which is the crosstalk above. KDA therefore adds channel-wise gating: for every token and head, the model produces a vector from the current hidden state, applies to scale the channels of the old state, and then performs the DeltaNet rewrite.
Math details: how KDA gates and rewrites the same S
The full update is . Term by term: is a vector gate computed from the current hidden state, and scales row of S by — every historical contribution in that row decays together; it neither picks A or B apart nor rotates keys toward orthogonality. is a scalar write strength.
Read one update in two steps. Gate first: . Then delta-rewrite along the current key: with (the decayed state's prediction for this key), . For a unit , the post-update readout along the same key is : replaces fully, keeps everything.
Unrolling the recurrence shows where position comes from: . Earlier writes pass through more gates and rewrites, so decay accumulates with distance — the mechanism that lets K3 drop RoPE. K3 also pins a lower bound on 's log-decay with a scaled sigmoid (, so every step's retention exceeds ): chunkwise computation has to rescale keys by the reciprocal cumulative decay, and bounding that numerical range lets both diagonal and off-diagonal tiles run as dense Tensor Core matmuls, removing the position-pair diagonal path Kimi Linear needed. The output gate moves from Kimi Linear's low-rank parameterization to an input-dependent full-rank projection.
The diagram compresses the real 128-d key space into a 2D teaching slice: and , both spanning and . The gate scales both rows first, then the delta rule writes along the full the residual needed to reach target value 4.
kₐ=(1/√2)[1,1]ᵀkᵦ=(1/√2)[1,−1]ᵀSo Kimi K3 needs no RoPE, because the KDA state update already carries position. An earlier token passes through more decay steps and state transforms before it reaches the current position, while a nearer token passes through fewer, and the model reads order and distance from that. Where RoPE encodes position explicitly with a fixed rotation, KDA models it implicitly through learnable, input-dependent decay. K3 also lower-bounds that decay to keep the chunkwise numerical range in check, so every causal tile in the KDA kernel can run on Tensor Cores; the output gate becomes a full-rank projection.
Attention Residual
An ordinary Transformer's residual stream is like one draft passed from the bottom layer to the top, with every layer editing the same page. This keeps information moving upward, but by layer 90, shallow details are mixed with dozens of later updates; a deep layer cannot reopen an earlier version directly.
Attention Residual keeps a few historical versions on the side. K3 splits its 93 layers into 8 blocks — 12 layers each for the first seven, 9 for the last — and each block sums its own layer outputs into one block representation. No layer has to rely only on the current draft: each one uses its own learned pseudo-query to weight the embedding, every preceding block's summary, and its own block's partial sum so far, and the mixture is that layer's input — the once-accumulating residual is taken over inside a block by that partial sum:
hℓ = hℓ₋₁ + Fℓ(hℓ₋₁)Shallow information must survive every intervening addition; a deep layer cannot retrieve one earlier output on its own.
hℓ = Σᵢ αℓᵢ bᵢA deep layer forms a softmax-weighted mixture of saved summaries rather than selecting one exact layer, creating shorter paths for information and gradients.
Math and cost: why K3 saves one summary every 12 layers
Let be the embedding, the summary of block (the accumulated outputs of its layers), and the partial sum over the first layers of block . Retrieval is done per layer: layer of block has its own learned pseudo-query , and its candidate set is when , plus when . Scoring with a dot-product-style score , the mixture is that layer's input. Note that AttnRes does not add a term on top of a residual; it replaces layer-by-layer accumulation itself. Weight can concentrate on one summary, but the choice is never hard.
Cost: full AttnRes stores every layer output — vectors of width per token, i.e. . The block version stores summaries plus the embedding, i.e. . With and block size 12, K3 keeps summaries, i.e. numbers per token. These summaries also travel with activations through the pipeline, so enters communication volume directly. Note that blocking shrinks the candidate set (93 → 9), not the number of retrievals: all 93 layers still retrieve once each. The kernel just batches the cross-block reads per block and lets each layer merge in its own intra-block partial sum with an online softmax.
Granularity vs. cost: smaller blocks (one layer per block in the limit) give finer depth-wise retrieval but grow state, memory bandwidth, and pipeline traffic linearly in ; larger blocks are cheaper, but layers inside a block are already summed and cannot be retrieved separately. The AttnRes paper reports the cost as: end-to-end inference latency overhead below 2% on typical inference workloads; on the training side, negligible without pipeline parallelism and a measured end-to-end overhead below 4% with it.
LatentMoE
Every FFN except the first uses MoE: each token selects 16 of 896 routed experts, while 2 shared experts are always active. The routed-pool selection rate is therefore ; the separate whole-model ratio is active parameters per token.
Routing still scores the full 7168-d hidden state, but the selected experts compute in a 3584-d latent space and project back to full width. This halves both the 16-way all-to-all dispatch traffic and expert weight volume:
At this point we have seen how KDA, MLA, Attention Residual, and LatentMoE form K3's hybrid architecture. But the model design only answers how to compute. In production, SGLang still has to answer how these different states fit in memory and how prefill is parallelized. The next half starts from those system questions.
SGLang's day-0 adaptations
K3's hybrid architecture creates two inference states with completely different lifecycles:
| MLA (24 layers) | KDA (69 layers) | |
|---|---|---|
| State shape | KV cache, appends per token | Recurrent state, fixed size |
| Growth | ~27KB per token per GPU, append-only | ~54MB per request per GPU (TP=8), overwritten in place each step |
MLA appends KV per token, while KDA reserves a fixed amount per request and overwrites it every step. That difference propagates into memory allocation, prefill parallelism, and decode scaling—the three parts of SGLang's day-0 work.
RadixAttention: prefix caching for KDA state
Traditional RadixAttention can share a prefix across requests because attention KV only appends as tokens arrive and never rewrites history. KDA's recurrent state is different: every token it reads overwrites the state in place. Suppose D and E both hit the prefix ABC. If they shared one , D's next step would turn it into , so E could no longer start from the state after ABC — the mechanism rules prefix caching out. So how do we keep RadixAttention's cache tree?
SGLang's answer is to never let a request mutate the shared state in the radix tree. A KDA state stored in the tree is a read-only checkpoint: after a prefix hit, copy-on-write restores it into the request's private working slot, and the forward pass mutates only that copy. Copying and forking are what keep an overwrite from destroying the prefix. Below, the first view compares how the KDA and MLA radix cache grow under two workload changes; the second steps through copy-on-write, snapshot, donate, and checkpoint eviction:
Every branch shown hits ABC. S(ABC) in the tree is a read-only checkpoint, not a live request's working state.
Memory management for two state types
Because K3 mixes KDA and MLA layers, memory management has to carve out two
kinds of region as well. The old approach preallocates a KDA region and an MLA
region at startup. KDA demand follows concurrency while MLA demand follows
total context length, so once the workload differs from the estimate, one
region fills while the other sits idle. The fix is direct: SGLang puts both in
one pool, with fixed KDA blocks allocated from the left, MLA pages from the
right, and the free space in the middle shared. When a request finishes, aborts
or is retracted and leaves a hole in the middle, a block from the end is moved
into it, so the free region stays contiguous and both ends stay packed — which
is what lets a 54MB KDA block and a 27KB MLA page draw from the same bytes with
no common page size forced on them. Day-0 makes this opt-in with
--enable-unified-memory:
In the baseline, allocations pick free slots inside two statically sized regions. When the MLA region fills, free KDA slots cannot be borrowed. The unified layout instead packs KDA from the left and MLA from the right, so every request keeps running.
Chunked pipeline prefill
Prefill processes many prompt tokens at once, which gives it enough work to cut into chunks and fill a pipeline. TP8 puts all eight GPUs on the same prefill work, but the problem is that every layer needs a lockstep AllReduce before the full result can enter the next layer. Chunked PP8 works differently: PP8 assigns ranges of the 93 layers to G1–G8 and lets different GPUs process different chunks. GPUs pass only the activation from the preceding stage, and those P2P transfers overlap with computation on the next chunk. As the diagram shows, G1–G8 each run a run of complete layers rather than splitting one layer eight ways, so no AllReduce is needed between layers. The cost is that filling and draining the pipeline takes some steps, so it cannot run full from the start; over a long-context prefill that overhead is diluted, so throughput still comes out ahead.
Every layer is split eight ways. After compute, every rank must finish an AllReduce before any rank can enter the next layer.
G1–G8 each execute a run of complete layers. The long prompt becomes multiple chunks; after one chunk, a GPU sends its activation directly to the next GPU.
Decode is another story. It usually has only one new token per step, not
enough work to fill a pipeline as deep as PP8, so TP8 remains the better fit.
A common PD-disaggregated split is PP8 prefill → TP8 / DCP8 decode. As noted
above, PP also pays fill and drain overhead, so it does not win when requests
or chunks are too few.
Decode context parallelism (DCP)
MLA has multiple attention heads, but they all share one compressed KV latent, so unlike MHA there is no clear axis to shard when TP splits by head. Every TP8 rank therefore has to replicate the complete MLA KV latent. That means the same MLA KV latent sits on every GPU, and adding GPUs does not increase logical context capacity. SGLang uses DCP to shard by token position instead: each GPU computes partial attention over its local KV, split as the diagram shows, then one packed all-to-all merges the result exactly:
MLA has multiple attention heads, but they all share the same compressed KV latent, leaving no cache head axis for TP to shard. TP4 therefore replicates this KV latent on all 4 GPUs: more GPUs do not increase logical context capacity.
The small query is replicated to all GPUs; the long, memory-heavy KV is striped round-robin by token position, with each position stored once.
DCP primarily increases capacity and concurrency, not latency for one short request. Keeping more long sessions on the GPU avoids host offload, re-prefill, and throughput collapse. DCP groups are built inside the TP group, so TP8 with DCP8 is still just 8 GPUs: on K3 logical capacity goes from about 1.5M to 12.2M tokens (roughly 7.9×) and reaches 541 tok/s at 48 agent sessions. It costs one all-to-all per MLA layer. The KDA state S, meanwhile, does not grow with context and has no token-position axis, so it stays TP/head-sharded.
Performance numbers and benchmark results
BS=1 decode, aggregate system throughput, and per-user speed are three distinct metrics. Single-request speed first: 15 kernel and communication optimizations raise non-speculative BS=1 decode from 44.3 to 112.5 tok/s, and DSpark's speculative decoding is a separate line at ~423 tok/s:
Cluster metrics are a different question: under PD disaggregation, aggregate throughput trades against per-user speed, and each deployment goal maps to a different parallel layout:
System efficiency (tok/s/GPU)
Per-user speed (tok/s/user)
Acknowledgments
Kimi K3 day-0 support was a collaboration between the SGLang & Miles team at RadixArk and the Moonshot AI team, together with NVIDIA, AMD, Approaching AI, Baseten, and Modal.
- AMD: Wun-guo Huang, Xinyi Song, Hai Xiao, Soga Lin, Duyi Wang, Thomas Wang
- Approaching AI: Huanming Shen, Xiaohao Zhang, Nan Li, Mingxing Zhang
Thanks to DigitalOcean for providing AMD instances for testing. Thanks also to Google Cloud, DigitalOcean, Nebius, fal, RunPod, DeepInfra, and GMI Cloud for serving Kimi K3 on SGLang.
Further reading
- Kimi K3 Technical Report
- LMSYS Blog: Kimi K3 Day-0 Support
- Kimi Linear: An Expressive, Efficient Attention Architecture
- Attention Residuals Technical Report
The visualized tutorial was written by Yichi Zhang and the SGLang Team.
