A sibling engine for Kimi K3
(2.8T parameters, 104B active, 93 layers), following the one-engine-per-family
pattern (colibri.c = GLM-5.2, olmoe.c, inkling.c). It shares st.h,
json.h, tok.h, quant.h and touches nothing in the GLM engine.
make kimi_k3
./kimi_k3 <model_dir> "prompt" --ngen 64
<model_dir> is either the plain HF snapshot (config.json +
model-*-of-000096.safetensors) or a repacked container (below). Text-only:
the vision tower (shards 95–96) is never read.
K3 is not a DeepSeek-shaped model; four pieces are new relative to everything else in this repo:
- Hybrid attention, NoPE. 69 KDA (Kimi Delta Attention) layers + 24 gated
MLA layers (every 4th, plus the final layer). There is no positional
encoding anywhere — position lives in KDA's convolution and decay. The MLA
block has DeepSeek dims (q_lora 1536, kv_lora 512, nope 128 + 64 uncached
extra dims, v 128, 96 heads) and both attention types multiply their output
by a full-rank sigmoid gate
σ(W_g x)beforeo_proj. - KDA per head (dim 128, 96 heads):
q,k,v = SiLU(causal-conv4(W x)), q/k L2-normalized (ε=1e-6 inside the sqrt), q scaled by 128^-1/2; per-channel decaygk = -5·σ(exp(A_log[h])·(W_fb W_fa x + dt_bias)); the delta-rule state updateS = (I − βkkᵀ)·Diag(e^gk)·S + βkvᵀ; outputW_o[σ(W_g x) ⊙ RMSNorm_head(Sᵀq)]. The checkpoint storesA_logas[128]: that is the per-head[96]parameter zero-padded (verified — entries 96..127 are exactly 0). Decode state: 96×128×128 f32 per layer. - AttnRes replaces the residual stream. Each layer keeps a running
prefix_sum; at layers 0, 12, 24, …, 84 it is snapshotted into a block list. Twice per layer (before attention, before the MLP) and once at the end, the hidden state is REPLACED by a softmax mix over[snapshots…, prefix_sum], scored by(v · (res_norm.w ⊙ res_proj.w)) / rms(v)and mixing the raw (un-normalized) entries in fp32. - Stable LatentMoE. Sigmoid router
[896, 7168]+ score-correction bias, top-16 selected on biased scores, weights are the raw scores renormalized. The token is projected 7168→3584, the 16 experts run in that latent space (GLU, inter 3072), the aggregate is RMSNorm-ed and projected back 3584→7168; two fused full-width shared experts (inter 6144) are added. Activation is SiTU-GLU:4·tanh(g/4)·σ(g) · 25·tanh(u/25).
Only the routed experts (experts.N.w{1,2,3}) are quantized in the
checkpoint — MXFP4 (compressed-tensors mxfp4-pack-quantized,
quantization-aware trained): e2m1 nibbles packed two per byte (low nibble =
even column, bit 3 = sign) with a ue8m0 power-of-two scale per 32 columns,
w = v · 2^(scale-127). 17.55 MB per expert, 82,432 experts ≈ 1.45 TB.
quant.h gained matmul_mxfp4 (scalar + AVX2) that computes on this layout
directly — expert bytes are never converted; QAT weights are exactly the
trained values and any re-encode only adds error.
Everything else is BF16 and is quantized into RAM at load time (int8 per-row / int4-g64), or read pre-quantized from a repacked container.
Experts are streamed straight from the safetensors shards through a per-layer
LRU (K3_EXPERT_GB). Measured layout facts the engine exploits: the six
tensors of an expert are stored back-to-back (one expert = one pread), and
experts are not id-ordered inside a shard, so loads are issued in disk-offset
order. A token's misses are read in parallel (OMP over working-set slots)
and by default with O_DIRECT (K3_DIRECT=0 for buffered): the resident
weights leave little page-cache headroom, and flat routing means cached reads
mostly cannot be reused anyway — measured on the 93-layer model this took
expert reads from ~1.8 to ~6.3 GB/s (drive ceiling 7.1) and decode from ~21
to ~9.4 s/token. The LRU slot floor is 1 (experts are consumed one at a time),
so K3_EXPERT_GB is honored even at tiny budgets. On top of that, K3_PIPE
(default on) runs the reads on loader threads so expert j's matmuls overlap
expert j+1's pread, K3_IDOT (default on) computes the expert matmuls with
per-32-group int8-quantized activations via integer dots (the e2m1 doubled
values are exact int8 — same trick as dot_i4i8), and K3_TOPP optionally
drops the low-weight tail of the top-16 (renormalized; quality-gate any
setting with a K3_LOGITS A/B first). Note that K3's router was trained with Quantile Balancing (deliberately
flat expert usage), so LRU hit rates are structurally lower than on models
with skewed routing — expect the expert tier to be bandwidth-bound.
python3 tools/k3_repack.py <hf_src> <dst> --bits 8 [--mla-bits 8] [--head-bits 8]
One streaming read of the source (so a disk-to-disk copy can be the
conversion): experts pass through byte-identical (id-ordered, single-pread
layout), the big BF16 matrices are quantized with exactly the engine's
load-time algorithm and stored as U8 + <name>.qs f32 scales, the small /
sensitive tensors (norms, router, conv taps, dt_bias, A_log, f_a/f_b,
b_proj, embeddings) pass through, vision is dropped. Output is spec-valid
safetensors (model-XXXXX-of-000094.safetensors) plus a regenerated index;
interrupted runs resume per shard. On every run, including disjoint --shards
passes, the index is rebuilt atomically from all completed output shards in the
destination; its total_size therefore includes both new and previously
converted data. Duplicate tensor names abort the index update instead of
publishing an ambiguous artifact. --verify-full re-reads every expert byte
and compares against the source.
The engine auto-detects container tensors (dtype U8 + .qs sidecar) and skips
load-time quantization. Setting K3_BITS=4 explicitly on an int8 container
downcasts the int8 matrices to int4-g64 at load (~35 vs ~57 GiB resident on
the 93-layer model — fits next to a desktop session; the int8 grid is 16x
finer than int4, so the double-quant noise ~ direct int4). Unset K3_BITS
keeps the container's own bits. Startup: measured on two layers, init drops 30.4 s → 0.6 s;
on the full model this removes a 10–15 minute quantization pass. Quantized
values are bit-identical to the load-time path (verified by hidden-state
trace), so the two formats are numerically interchangeable.
K3_MMAP=1 is an experimental CPU-only residency option for a fully repacked
container. It maps each prepared U8 matrix and its F32 scale sidecar read-only
instead of copying both into private heap. The mapped pages remain file-backed
and reclaimable; this changes commit/accounting, not the active working set, so
memory pressure can become page faults during inference. On Linux, process
monitors may still show the touched mapping (about 33.8 GB for the full model)
in RSS; it is clean file-backed memory, not private heap, so use /proc smaps
accounting to distinguish file-backed RSS from private anonymous memory. The
option has no fallback: tensors that need load-time conversion, non-F32 scale
sidecars, and enabled Vulkan/CUDA backends are refused: K3_MMAP=1 runs with
COLI_VULKAN unset (or K3_VK=0) and K3_CUDA off.
Sizes: source 1.56 TB → ≈1.50 TB (--bits 8) / ≈1.48 TB (--bits 4). The
experts (93 % of bytes) are already at 4.25 bits/weight and cannot shrink
losslessly; sub-4-bit expert re-encodes (fmt=5/6) would be double quantization
of QAT weights and are deliberately not offered here.
The HF repo ships only a raw tiktoken vocab (tiktoken.model).
tools/k3_tokenizer.py writes a tokenizer.json for it, and tok.h gained:
- a kimi pre-tokenizer family (sniffed via
\p{Han}in the Split regex): the o200k case-aware rules plus a leading Han-run rule, Han excluded from the letter classes, and no/tail in the punctuation rule; - a rank-BPE mode, used when
model.mergesis empty: merge the adjacent pair whose concatenation has the lowest vocab id — which is tiktoken's own algorithm, so encoding is exact by construction. (Merge-list recovery from ranks is provably lossy: the standard reconstruction mis-merges e.g. "newlines"; that is why the generator emits no merges.)
tools/k3_tokenizer.py --ctest tests/test_tok_kimi cross-checks the C
tokenizer against tiktoken; it matches on all 18 corpus cases (Chinese,
kanji-vs-kana, Korean, CRLF, contractions, emoji, mixed-script boundaries).
tools/k3_ref.py is an independent numpy implementation of the full layer
stack (KDA recurrence, gated MLA, AttnRes, LatentMoE with MXFP4 dequant). With
injected inputs (K3_X0, bypassing the embedding) and f32 weights
(K3_BITS=32), the C engine matches it on real checkpoint weights across all
four layer types to rel-L2 ≤ 2.2e-6 (float32 noise).
One property worth knowing: the AttnRes mix logits sit close together (margins ~0.02), so the mix leverages weight noise strongly even though it does not amplify input perturbations (a 1e-4 input perturbation stays 1e-4 through the stack). Load-time int8 produces a hidden-state drift growing to ~14 % over four layers on synthetic inputs — per-tensor quantization SNR is a uniform ~1 %, so this is architectural sensitivity, not an outlier problem. Judge quantization choices on real-text logits, not synthetic-vector norms.
| var | default | meaning |
|---|---|---|
K3_BITS |
4 | load-time bits for KDA/latent/shared/dense mats (4, 8, 32=f32) |
K3_MLA_BITS |
8 | load-time bits for MLA projections |
K3_HEAD_BITS |
8 | load-time bits for lm_head |
K3_MMAP |
0 | map fully prepared U8/F32 weights read-only (experimental, CPU-only, no conversion fallback) |
K3_EXPERT_GB |
8 | routed-expert LRU budget |
COLI_VULKAN |
0 | make VK=1 kimi_k3: routed experts on the shared Vulkan expert tier, shared experts where COLI_VK_DENSE puts the dense matrices (see Vulkan) |
K3_VK |
unset | old switch, an alias: 1 = COLI_VULKAN=1, 0 = never open the device |
K3_VK_GB |
unset | old switch, read as COLI_VK_TIER_GB when that is unset |
K3_VK_UP |
unset | old switch, a number read as COLI_VK_TIER_RATE when that is unset; auto does nothing |
K3_DIRECT |
1 | O_DIRECT expert reads (0 = buffered + WILLNEED) |
K3_IDOT |
1 | int8-activation expert matmuls (0 = exact-float kernel) |
K3_PIPE |
1 | overlap expert loads with compute (loader threads) |
K3_LOAD_THREADS |
4 | loader threads for K3_PIPE |
K3_TOPP |
0 | keep routed experts to cumulative weight p (0 = off) |
K3_CHUNK |
32 | prefill chunk size (1 = token-at-a-time; forced 1 under K3_TRACE) |
K3_THINK |
1 | chat mode: open the structural think channel (0 = response-only) |
K3_DIRS |
— | extra shard directories (multi-drive split, no duplication) |
K3_MAXT |
prompt+ngen | context capacity |
K3_LAYERS |
all | truncate the stack (validation) |
K3_TRACE |
— | dump f32 hidden state after every layer (validation) |
K3_X0 |
— | inject input rows, bypass embedding (validation) |
COLI_TEMP |
0 | 0 = greedy, else softmax temperature |
Prefill processes the prompt in chunks of K3_CHUNK tokens, layer-major:
every dense matmul batches over the chunk (each weight matrix streams from
RAM once per chunk instead of once per token), the MoE loads each unique
expert of the chunk once (measured at C=32 on real text: 2.7x dedup —
neighbouring tokens share experts far more than QB-flat routing suggests —
9.6 instead of 25.8 GB/token), and the lm_head runs only on the chunk's last
token. Sequential state (KDA recurrence, MLA cache, AttnRes bookkeeping)
advances per token inside each layer in the original order, so chunked
results are bit-identical to token-at-a-time (verified: 125-position
teacher-forced logit streams at C=32 vs C=1 match exactly). Measured prefill:
~5.3 -> 2.0 s/token at C=32. The KDA state-update sweeps are AVX2
(scalar fallback for head dims not divisible by 8).
# standalone one-turn diagnostic
./kimi_k3 <model_dir> --chat "your question" [--system "..."] --ngen 300
# first-class multi-turn interfaces (config.json auto-detects Kimi K3)
COLI_MODEL=<model_dir> ./coli chat
COLI_MODEL=<model_dir> ./coli serve
COLI_MODEL=<model_dir> ./coli webK3's chat format ("XTML", from the checkpoint's own encoding_k3.py) uses
only four special tokens — <|open|>, <|close|>, <|sep|>,
<|end_of_msg|> — around ordinary-text tag names and attributes:
<|open|>message role="user"<|sep|>TEXT<|close|>message<|sep|><|end_of_msg|>
<|open|>message role="assistant"<|sep|><|open|>think<|sep|> <- generation prompt
The assistant's thinking is a structural channel and is preserved when an
OpenAI-compatible client sends prior reasoning_content; enable_thinking=false
opens <response> directly. The gateway does not flatten XTML into a string:
it sends length-framed messages to the C engine, which builds every structural
and ordinary-text segment at the tokenizer boundary required by K3's rank-BPE.
We checked our text handling against Moonshot's own: their encoding_k3.py
turns a conversation into the numbers the model actually reads, and ours
produced identical numbers on all 77 test conversations — multi-turn
histories with system, user and assistant messages, including non-ASCII text.
So the engine is not subtly mangling anything before the model sees it.
coli chat starts a private local server for Kimi and keeps the 2.8T model
loaded for the whole terminal session. coli serve exposes streaming and
non-streaming /v1/chat/completions; coli web uses that same API. Reasoning
is returned as reasoning_content, response text as content, and
<|end_of_msg|> remains the model-owned stop token. STOP and CANCEL are
honoured between generated tokens. Long prefill also polls CANCEL between
layers and drops the unpublished partial state before serving another request.
Long agent sessions can opt into recurrent-state checkpoints (COLI_K3_CKPT=N
slots in RAM, or parked on disk with COLI_K3_CKPT_DIR): an edited or follow-up
prompt restores the deepest surviving checkpoint and re-prefills only the tail,
instead of replaying the whole conversation through the SSM layers.
Run with COLI_VULKAN=1, a VK=1 build puts the routed experts on the shared Vulkan
expert tier (vulkan.md): the shards'
MXFP4 bytes as they sit in RAM (e2m1 nibbles, the ue8m0 group exponents widened to
the f32 scales the shader reads, never re-encoded; the fmt 7 kernel against
matmul_mxfp4: rel_l2 2.2e-07 on an RX 9070/RADV, 2.6e-07 on llvmpipe,
tests/test_vk_mxfp4.c), gate, SiTU-GLU and down on the device, in the latent
space (the tier's hidden is the latent, 3584, its intermediate moe_inter, 3072).
- What is resident adapts. At startup the tier fills its budget from the expert
history (
<snap>/.coli_usage, the hottest first, read in parallel); after that every expert the CPU computes is a candidate, and a hotter one displaces the coldest resident. The engine's own tier before this one filled once and never let go. - The device and the CPU work at once, prefill included. Each MoE step sends the (position, rank) pairs whose expert is resident to the device as one batch; the CPU reads and computes the other experts of the chunk's union and the shared experts meanwhile. An expert the device took entirely is not read from disk.
- The sum keeps the CPU run's order. Every pair joins its position in the union's disk-offset order, the order a CPU-only run adds them, the device's rows and the CPU's alike: which experts were resident never changes the order, and an expert computed on the CPU keeps the CPU run's bits.
- The shared experts go to the device where
COLI_VK_DENSEputs the dense matrices (int8, int4-g64 or f32, asK3_BITSmade them; one row at a time, so decode): on a discrete GPU the device, on an integrated GPU or Lavapipe the CPU while the tier runs. KDA, MLA, the latent projections, the router and the head stay on the CPU. WithCOLI_VK_TIER=0the shared experts alone go to the device.
The device computes with f32 activations: its experts match K3_IDOT=0, the
configuration of the vendor oracle, while the CPU's default kernel rounds
activations to int8 per 32. With K3_IDOT=0 a run with the tier gives the CPU's
tokens on the tiny fixture and passes Moonshot's oracle, the eviction and warm-start
cases included (tests/vulkan_engines.sh kimi). A build with both backends runs on
Vulkan when CUDA was asked for (K3_CUDA=1, which coli sets for COLI_CUDA=1) and
neither COLI_VULKAN nor K3_VK was set: the CUDA path uploads every routed expert's
weights at every call (an MXFP4 expert of 7168 x 2048, 2.46 ms on an RTX PRO 4000, against
72 us for the tier's resident ones) and keeps the dense matrices on the CPU. With
COLI_VULKAN=0 and K3_CUDA=1, CUDA wins and the Vulkan tier stays off. Each run and serve turn ends with
[VK] kimi_k3: N matmuls on the GPU and the tier's [VK] tier kimi_k3 line.
The old switches still work, read as the shared tier's (an explicit shared one
wins): K3_VK=1 opens the device as COLI_VULKAN=1 does and K3_VK=0 keeps it
closed, K3_VK_GB is the tier's budget (COLI_VK_TIER_GB), a numeric K3_VK_UP
its promotions per token (COLI_VK_TIER_RATE); K3_VK_UP=auto and
K3_VK_FILL_FRAC do nothing, since the shared tier uploads on a thread of its own
and never holds a step. One default changed: a VK=1 build used to open the device
without being asked (K3_VK defaulted to 1); it now waits for COLI_VULKAN=1 or
K3_VK=1, like every engine, and without either it is the CPU build's bytes.
K3's Quantile-Balancing-flat routing caps what any cache tier can do — the tier's value scales with how long the server lives and with the measured short-term reuse (temporal locality), not with marginal expert heat.
- Decode is single-token (no speculative decoding — K3 has no MTP head).
- Tool declarations/calls and image content are not exposed through the shared gateway yet; unsupported requests fail explicitly.
- CPU + optional Vulkan (the shared expert tier; no CUDA/Metal).
- The protocol, tokenizer, gateway, TUI, and Web client paths are locally testable without the 1.5 TB checkpoint. A release claim still requires one full-model multi-turn TUI/Web run on a host that owns the complete snapshot.