---
base_model: moonshotai/Kimi-K3
base_model_relation: quantized
library_name: transformers
pipeline_tag: text-generation
tags:
- nvfp4
- fp4
- quantized
- moe
- modelopt
- sglang
---
# Kimi-K3-NVFP4
NVFP4 conversion of [`moonshotai/Kimi-K3`](https://huggingface.co/moonshotai/Kimi-K3),
produced by a **closed-form, bit-exact MXFP4 → NVFP4 cast** with
[NVIDIA TensorRT Model Optimizer](https://github.com/NVIDIA/Model-Optimizer).
Tooling: [`patronus-ai/kimi-k3-nvfp4`](https://github.com/patronus-ai/kimi-k3-nvfp4)
| | |
| --- | --- |
| Cast fidelity | **85,085,650,944 / 85,085,650,944 blocks bit-exact (100.0000%)**, `max_abs_err 0.0` |
| Layers cast | 247,296 (routed MoE experts) |
| **GPQA Diamond** | **92.96%** (pass@1 avg-of-16) |
| **IFBench** | **75.27%** (pass@1 avg-of-5, prompt_loose) |
| **SciCode** | **23.08%** problem / **57.29%** subtask |
| Verified on | SGLang `kimi-k3` branch, 2 nodes × 8×B200, TP16 |
| Size | ~1.6 TB, 96 shards |
## What this is
| | Source (Kimi-K3) | This checkpoint |
| --- | --- | --- |
| Weight format | MXFP4 (E2M1, block 32, E8M0 scale) | **NVFP4** (E2M1, block 16, E4M3 scale + FP32 per-tensor scale) |
| Weight values | — | **bit-identical to the source** |
| Scope | routed MoE experts (`w1`/`w2`/`w3`) | routed MoE experts (`w1`/`w2`/`w3`) |
Attention, shared experts, dense MLP projections, `lm_head` and vision modules are
carried through unquantized.
## Bit-exactness
The cast is **data-free**: NVFP4 scales are derived from the source E8M0 exponents
rather than re-estimated from data. E8M0 has no mantissa, so every source scale is
already an exact power of two. With `m = k_max - 8`:
```
weight_scale_2 = 2^m (per tensor, FP32)
per_block_scale = 2^(k_j - m) (per 16 elements, E4M3)
=> product = 2^k_j exactly
```
E4M3 represents `2^k` exactly for `k ∈ [-9, 8]`, so any block within 17 binades of
the tensor max reconstructs bit-for-bit. On Kimi-K3 that covered **every** block.
No calibration data was used, and none is needed.
> **Note on precision.** Because the per-block scale is always pinned to an
> exact power of two, this cast deliberately uses none of NVFP4's precision
> advantages at the weight level — neither the finer block-16 granularity nor
> E4M3's 3 mantissa bits.
> That is the cost of bit-exactness, and it is the right trade: quantization is
> irreversible, so a higher-resolution container cannot recover information MXFP4
> already discarded. NVFP4's accuracy edge is only reachable by re-quantizing from
> BF16, which for 2.8T params means materializing ~5.6 TB. What the conversion buys
> is access to the NVFP4 kernel and tooling ecosystem.
## Deployment (SGLang)
Verified on the SGLang **`kimi-k3` branch** (not `main`), 2 nodes × 8×B200, TP16.
Run the identical command on every node, varying only `--node-rank`.
```bash
# --- per-node environment ---
export SGLANG_HOST_IP=$(ip -o -4 addr show ens1 | awk '{print $4}' | cut -d/ -f1)
export GLOO_SOCKET_IFNAME=ens1 NCCL_SOCKET_IFNAME=ens1
export NCCL_IB_HCA=mlx5_0,mlx5_1,mlx5_2,mlx5_3,mlx5_4,mlx5_9,mlx5_12,mlx5_13
export NCCL_IB_GID_INDEX=7 NCCL_IB_DISABLE=0 NCCL_NVLS_ENABLE=0 NCCL_CROSS_NIC=0
export PATH=/path/to/venv/bin:$PATH # ninja must resolve from SHARED storage
python -m sglang.launch_server \
--model-path /path/to/kimi-k3-nvfp4 \
--trust-remote-code \
--tp-size 16 --nnodes 2 --node-rank ${NODE_RANK} \
--dist-init-addr ${HEAD_IP}:20000 \
--moe-runner-backend marlin \
--mem-fraction-static 0.85 \
--mamba-full-memory-ratio 7.21 \
--disable-flashinfer-autotune \
--watchdog-timeout 3600 --dist-timeout 3600 \
--reasoning-parser kimi_k3 --tool-call-parser kimi_k3 \
--model-loader-extra-config '{"enable_multithread_load": true}' \
--host 0.0.0.0 --port 30000
```
`SGLANG_HOST_IP` is mandatory for multi-node — without it the ranks never rendezvous
and startup hangs with no error.
### Five non-obvious requirements
Each of these independently prevents the model from serving:
1. **`--moe-runner-backend marlin`.** Kimi-K3 uses the `situ` (SiTuGlu) activation,
which is **not implemented** in SGLang's NVFP4 FlashInfer runner
(`_SUPPORTED_FP4_ACTIVATIONS = {silu, relu2, gelu}`). Marlin supports gated
`{silu, situ}` and forwards both SiTu parameters
(`gemm1_alpha`=β=4.0, `clamp_limit`=linear_β=25.0).
2. **Patch `modelopt_quant.py`**: `_SUPPORTED_ACT_STRS += ("situ",)`. The assert at
`:2552` runs *before* the per-backend dispatch at `:2556`, so it rejects `situ`
even when the selected backend supports it. Every backend already applies its own
stricter check afterwards.
3. **Raise `UNBALANCED_MODEL_LOADING_TIMEOUT_S`** (`load_model_utils.py`, default
480 s, no CLI flag). 1.6 TB over network storage means per-node load skew
routinely exceeds 8 minutes and aborts an otherwise healthy run.
4. **`--dist-timeout 3600`.** Marlin's `prepare_moe_nvfp4_layer_for_marlin` repacks
247k expert weights, exceeding NCCL's 600 s default; the first rank to finish then
times out alone at `SeqNum=1 _ALLGATHER_BASE`.
5. **Omit `--dcp-size`** unless FlashInfer is new enough — `cutedsl_mla_backend`
passes `enable_dcp`/`cp_world` to `trtllm_batch_decode_with_kv_cache_mla`, which
flashinfer 0.6.15 does not accept.
Patches 2–3 are scripted in
[`serving/patch_situ_nvfp4.py`](https://github.com/patronus-ai/kimi-k3-nvfp4/blob/main/serving/patch_situ_nvfp4.py).
### Querying
K3 is a reasoning model — it emits thinking tokens before the answer. Use a generous
`max_tokens` (≥512) and read `choices[0].message.content`; the thinking is in
`reasoning_content`. `reasoning_effort` accepts `low` / `high` / `max`.
```bash
curl $URL/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "k3",
"messages": [{"role":"user","content":"What is 17 * 23? Reply with only the number."}],
"max_tokens": 1200, "temperature": 0, "reasoning_effort": "low"}'
```
With too small a budget you get `content: ""` and `finish_reason: "length"` — that is
truncated thinking, not a broken model.
### This is a W4A16 deployment
Marlin dequantizes the 4-bit weights to BF16 **inside the GEMM**, so weights stay
4-bit in HBM (the memory and bandwidth win is kept) but the math runs at BF16
tensor-core rate. Activations are never quantized.
Consequences: no activation calibration is needed or used and this deployment setup
does not deliver FP4 compute throughput. Accuracy should be at least that of native
MXFP4, since the weights are bit-identical and the activations are *higher* precision
than the source's MXFP8.
## Evaluation
All three are judge-free and scored with the official harnesses.
| Benchmark | Score | Setup |
| --- | --- | --- |
| **GPQA Diamond** | **92.96%** | pass@1 avg-of-16, sd 1.24; 198 questions |
| **IFBench** | **75.27%** | pass@1 avg-of-5 `prompt_loose`, sd 0.86; 300 prompts |
| **SciCode** | **23.08%** / 57.29% | problem / subtask; 65 problems, 288 subtasks |
GPQA per-seed: 96.0 / 93.4 / 91.9 / 91.4 / 92.9 / 93.4 / 90.9 / 93.9 /
92.4 / 91.4 / 93.9 / 92.4 / 92.9 / 93.9 / 93.4 / 92.9.
IFBench: `prompt_loose` counts a response only if every constraint on it is met;
`prompt_strict` was 71.07% and instruction-level loose 78.20%.
SciCode: AA test split, `prompt_config=eval/scicode/background`, greedy.
Harness: nemo-skills with prompt `eval/aai/mcq-4choices` and
`++eval_type=multichoice`, matching the
[Artificial Analysis methodology](https://artificialanalysis.ai/methodology/intelligence-benchmarking);
198 Diamond questions, greedy for seed 0 and `temperature=0.6, top_p=0.95` thereafter.
`tokens_to_generate=65536` is load-bearing: at 16384 the model exhausted its budget
inside `reasoning` on ~10% of questions and emitted an empty answer, scoring as wrong
for a purely harness reason — worth ~8.6 points on the greedy pass (87.4% → 96.0%).
Truncation fell from 9.6% to 0.58%.
## Layout
Per quantized module ``:
```
.weight uint8 (out, in/2) NVFP4-packed E2M1 nibbles
.weight_scale E4M3 (out, in/16) per-block scale
.weight_scale_2 fp32 scalar per-tensor scale
```
`config.json` reports `format: nvfp4-pack-quantized`, `quant_method: modelopt`,
`quant_algo: NVFP4`, `group_size: 16`, and an `exclude_modules` list of 55 globs
derived from the modules that actually kept an unpacked 2-D float `.weight`.
> **Branches.** `main` is weight-only (`input_activations: null`). `w4a4` adds
> `input_scale` placeholders of 1.0; they are unused by the marlin path.
## Bundled fix
`modeling_kimi_linear.py` here includes a fix to `KimiDeltaAttention`: `A_log` is
stored at `head_dim` (128) but the upstream release builds it at `num_heads` (96), so
`from_pretrained` fails to load its own weights.
The checkpoint holds **96 trained decay values zero-padded to 128** — verified across
all 69 `A_log` tensors here: elements `[96:128]` are exactly zero in every one. The
FLA kernel indexes by `num_heads`, so the padding is inert.
The bundled file follows
[discussion #150](https://huggingface.co/moonshotai/Kimi-K3/discussions/150): it sizes
`A_log` at `num_heads` and narrows the checkpoint tensor in `_load_from_state_dict`,
raising if the discarded tail is ever non-zero.
An earlier version of this file followed our own
[discussion #144](https://huggingface.co/moonshotai/Kimi-K3/discussions/144), which
enlarges the parameter to `head_dim` instead. That approach loads but cannot run:
`fla/ops/kda/gate.py` computes `A_log.view(H, 1)` with `H = g.shape[-2]` (the head
count), so a 128-element parameter raises
`shape '[96, 1]' is invalid for input of size 128` on the first forward. If you pulled
this checkpoint before 2026-07-31, re-fetch `modeling_kimi_linear.py`.
This matters only for `transformers`-based loading — SGLang narrows to the first
`num_heads` elements independently, which the zero-padding makes lossless.
## Known limitation: the FP4 fast path
The trtllm-gen cubin pool ships SiTu kernels at NVFP4's 16-element scale block
(57 × BF16-activation, 45 × E2M1-activation), but they are unreachable from SGLang's
NVFP4 runner, which resolves activations via FlashInfer's `ActivationType` (no SiTu
member) and reads a different cubin store. Wiring the dispatch to
`sglang.kernels.ops.moe.trtllm_gen_moe` reaches the JIT build stage and then fails on
CUDA 12.8 with `namespace "cuda" has no member "maximum"` — `cuda::maximum` requires
CUDA 13's CCCL. **Untested beyond that point.**
## Reproducing
```bash
python cast_mxfp4_to_nvfp4_offline.py \
--source_ckpt /path/to/Kimi-K3 \
--output_ckpt /path/to/Kimi-K3-NVFP4 --verify
```
Runs on CPU without instantiating the model (~7 h, bounded by shard I/O).
## License
Derivative of `moonshotai/Kimi-K3`; the upstream model's license and terms apply.