Skip to content

fix(kimi-k3): correct MFU accounting (FLOPs formula, config defaults) - #3792

Merged
HuiyingLi merged 2 commits into
NVIDIA-NeMo:mainfrom
yisongbetter:yisongbetter/fix/k3-mfu-accounting
Sep 2, 2026
Merged

fix(kimi-k3): correct MFU accounting (FLOPs formula, config defaults)#3792
HuiyingLi merged 2 commits into
NVIDIA-NeMo:mainfrom
yisongbetter:yisongbetter/fix/k3-mfu-accounting

Conversation

@yisongbetter

Copy link
Copy Markdown
Contributor

What does this PR do ?

Makes Automodel's Kimi K3 MFU accounting correct. Two independent defects made the reported MFU wrong for K3:

  1. No FLOPs formula for K3. get_flops_formula_for_hf_config had no entry for KimiK3TextConfig, so K3 fell through to the generic dense transformer_flops, whose MoE branch probes num_experts_per_tok while the K3 config spells it num_experts_per_token. The 2.78T hybrid MoE was costed as a dense 93-layer transformer at ~0.44× of its real FLOPs (a 64-node run that measures 13.7% MFU wall-clock printed ~6.0%).
  2. from_config built a lighter model than the checkpoint. KimiK3TextConfig defaulted to num_attention_heads=56 and intermediate_size=18432; the released moonshotai/Kimi-K3 has 96 heads and a 33792-wide dense FFN (config.json, safetensors shapes, tech report Table 1). from_pretrained was unaffected (config.json overrides), but the benchmark recipes and random-init smoke configs built ~101.5B instead of ~104B active matmul parameters per token, so their throughput numbers were not numbers for Kimi K3.

Two commits, one per defect; they are independent (the formula's tests pass explicit config values).

Changelog

  • flops_utils.kimi_k3_flops: hybrid KDA / gated-MLA attention (layer pattern read from linear_attn_config, MLA output gate), latent MoE (routed_expert_hidden_size down/up projections around the top-k experts) + SiTU shared expert, dense layer(s) per first_k_dense_replace, LM head. KDA chunked kernel costed with the Kimi Linear paper's count (6 T d_h² + 3 T C d_h + T C² per head, arXiv:2510.26692). _kda_attention_per_layer_flops helper mirrors _gdn_attention_per_layer_flops.
  • Registry entries for KimiK3TextConfig and the multimodal KimiK3Config wrapper (text_config is used; the vision tower is not counted, as for the other VL entries).
  • KimiK3TextConfig defaults: num_attention_heads 56 → 96, intermediate_size 18432 → 33792; docstring states the defaults reproduce the released checkpoint.
  • Tests: tests/unit_tests/utils/test_kimi_k3_flops.py (layer pattern, model-card parameter counts, precomputed values for the checkpoint and the pre-fix shapes, batch linearity, >2× correction over the dense fallback, exact cost of each optional block); tests/unit_tests/models/kimi_k3/test_config_defaults.py (pins the released text config, the linear-attention block, the 69 KDA / 24 MLA pattern, the MLA projection widths implied by the head count, the multimodal wrapper); a registration case in test_flops_utils_new_models.py.

Verification

Cross-checked against the released checkpoint (moonshotai/Kimi-K3 config.json + modeling_kimi_linear.py + safetensors headers: q_b_proj [18432, 1536] = 96 × 192, kv_b_proj [24576, 512], layers.0.mlp.gate_proj [33792, 7168]), the model card, and the K3 tech report (Table 1: 96 heads, 104.2B activated / 2.78T total):

  • With the corrected defaults from_config builds the checkpoint's shape, and the formula counts 104.0B active matmul parameters per token, 2.78T total (within 0.2% of Table 1; the formula counts GEMM weights only — no router, norms or gate biases). Layer pattern 69 KDA + 24 gated MLA, 1 dense layer, 16/896 experts, 2 shared — all read from the config.
  • FLOPs/token at seq 1024 / 2048 = 1.004× / 1.007× of the wall-clock basis 6 × 104e9 used for every published K3 number (the excess is the MLA attention BMMs and the KDA kernel).
  • The report's single MTP layer is not in the released checkpoint (num_nextn_predict_layers: 0) and not modelled by Automodel, so it is not counted.
  • Tests run locally (CPU, torch 2.14 / transformers 5.16): tests/unit_tests/utils/ 247 passed (1 pre-existing environment failure in test_yaml_utils.py: needs /home/TestData); tests/unit_tests/models/kimi_k3/ 65 passed (1 environment failure: FLA kernels not installed); tests/unit_tests/recipes/llm/test_benchmark.py 33 passed. ruff format --check / ruff check clean on the changed files.
  • End-to-end (2 nodes × 4 GB200, nemo_automodel/recipes/llm/benchmark.py --config …, Kimi-K3 reduced to 24 layers / 64 experts, seq 4096, GBS 32, random init, the formula commit applied on upstream main): TFLOPs/GPU: 21323.249904 printed at setup = kimi_k3_flops(cfg, gbs=32, seq_len=4096) exactly (the dense fallback would have given 0.485× of that). Steady iterations 4.95 s → printed MFU: 23.93%; the wall-clock basis at the same iteration time (tokens/s/GPU × 6 × active params / 2.25e15) gives 23.74%, ratio 1.008. Average MFU: 23.17% over the 9 post-warmup iterations (two GC-slowed).

Additional Information

… MFU

`get_flops_formula_for_hf_config` had no entry for `KimiK3TextConfig`, so
Kimi K3 fell through to the generic dense `transformer_flops`, whose MoE
branch probes `num_experts_per_tok` while the K3 config spells it
`num_experts_per_token`. The 2.78T hybrid MoE was therefore costed as a
dense 93-layer transformer and the recipe printed MFU at well under half of
the wall-clock value (13.7% measured -> ~6.0% printed at 64 nodes).

Add `kimi_k3_flops`, derived from the K3 config and the released modeling
code: 69 KDA + 24 gated-MLA layers read from `linear_attn_config`, the MLA
output gate, latent MoE (shared down/up projections around top-k experts in
`routed_expert_hidden_size`) plus the SiTU shared expert, one dense layer,
and the LM head. The KDA chunked kernel uses the Kimi Linear paper's own
count (6 T d_h^2 + 3 T C d_h + T C^2 per head, arXiv:2510.26692). With the
checkpoint's config.json values the formula gives 104.0B active matmul
parameters per token and 2.78T total, matching the model card ("104B
activated / 2.8T") and the tech report's Table 1 (104.2B / 2.78T).

Register both `KimiK3TextConfig` and the multimodal `KimiK3Config` wrapper
(text_config is used; the vision tower is not counted, as for other VL
entries). Tests pin the layer pattern, the model-card parameter counts, a
precomputed value, batch linearity, the >2x correction over the dense
fallback, and the exact cost of each optional block.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>
…point

`KimiK3TextConfig` defaulted to `num_attention_heads=56` and
`intermediate_size=18432`, but the released `moonshotai/Kimi-K3` checkpoint
(config.json text_config, tech report Table 1, and the safetensors shapes:
`q_b_proj [18432, 1536]` = 96 heads x 192, `layers.0.mlp.gate_proj
[33792, 7168]`) has 96 attention heads and a 33792-wide dense FFN. Every
other default already matched.

`from_pretrained` was unaffected because config.json overrides the
defaults, but `from_config` -- used by the benchmark recipes and by
random-init smoke configs -- built a lighter model (24 MLA layers with 56
heads, half-width dense layer: ~101.5B instead of ~104B active matmul
parameters per token), so throughput numbers measured with it were not
numbers for Kimi K3.

Set the two defaults to the checkpoint values and pin the whole released
text config (plus the implied MLA projection widths and the 69 KDA / 24 MLA
layer pattern) in a unit test so the class cannot drift from the model it
names again.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>
@yisongbetter
yisongbetter requested a review from a team as a code owner September 2, 2026 19:03
@copy-pr-bot

copy-pr-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@HuiyingLi

Copy link
Copy Markdown
Contributor

/claude review

@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

LGTM

@HuiyingLi

Copy link
Copy Markdown
Contributor

/ok to test 1613a9f

@HuiyingLi
HuiyingLi enabled auto-merge (squash) September 2, 2026 20:01
@HuiyingLi
HuiyingLi merged commit 429967f into NVIDIA-NeMo:main Sep 2, 2026
119 checks passed
yisongbetter added a commit to yisongbetter/Automodel that referenced this pull request Sep 3, 2026
The 13.7% MFU figure was measured before NVIDIA-NeMo#3792 aligned the KimiK3TextConfig
defaults with the released checkpoint (56-head MLA / 18432-wide dense FFN,
~101.5B active matmul parameters instead of 104.0B). Say so in the header,
and point at the recipes/llm/benchmark.py entry that prints the recipe's own
MFU, until a re-measurement on the aligned shape replaces the numbers.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>
yisongbetter added a commit to yisongbetter/Automodel that referenced this pull request Sep 3, 2026
The 17.1% MFU figure predates NVIDIA-NeMo#3792's KimiK3TextConfig default alignment
(56-head MLA / 18432-wide dense FFN, ~101.5B active matmul parameters
instead of 104.0B). Record that in the header and point at the
recipes/llm/benchmark.py entry that prints the recipe's own MFU, until a
re-measurement on the aligned shape replaces the numbers.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>
yisongbetter added a commit to yisongbetter/Automodel that referenced this pull request Sep 3, 2026
The 13.7% MFU figure was measured before NVIDIA-NeMo#3792 aligned the KimiK3TextConfig
defaults with the released checkpoint (56-head MLA / 18432-wide dense FFN,
~101.5B active matmul parameters instead of 104.0B). Say so in the header,
and point at the recipes/llm/benchmark.py entry that prints the recipe's own
MFU, until a re-measurement on the aligned shape replaces the numbers.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>
yisongbetter added a commit to yisongbetter/Automodel that referenced this pull request Sep 3, 2026
The 17.1% MFU figure predates NVIDIA-NeMo#3792's KimiK3TextConfig default alignment
(56-head MLA / 18432-wide dense FFN, ~101.5B active matmul parameters
instead of 104.0B). Record that in the header and point at the
recipes/llm/benchmark.py entry that prints the recipe's own MFU, until a
re-measurement on the aligned shape replaces the numbers.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>
HuiyingLi pushed a commit that referenced this pull request Sep 3, 2026
…nc pins (#3779)

* perf(kimi-k3): RMSNorm compile island and benchmark static-routing sync pins

Two launch-tax reductions for the Kimi-K3 GB200 benchmark, measured at
256 GPUs (64x GB200 NVL72, pp8 x EP32, 1k rows x GBS 4096):

1. BackendConfig.compile_norm (default False): torch.compile the fp32
   RMSNorm chain once per process, same lazy pattern as the merged
   compile_situ. At 256 GPUs this is worth +11% end-to-end (launch-bound
   regime: fusing the per-token norm chain pays in both busy time and
   inter-kernel gaps); the same change gains only +5.3% on a 2-node mini.

2. BackendConfig.benchmark_static_routing (default False, validated to
   require fake_balanced_gate with zero noise): under forced-balanced
   routing the per-microbatch routing metadata is constant, so the
   recurring device-to-host syncs on it are skipped after the first
   microbatch: tokens_per_expert.tolist() in GroupedExpertsTE,
   count_nonzero + the CPU tokens_per_expert copy in GroupedExpertsDeepEP,
   and the HybridEP num_permuted_tokens reset in the flex dispatcher
   (config field moe_benchmark_static_routing). Worth +2.4% together with
   the mask fixes below.

Plus two unconditional fixes on the same hot path:
- _update_linear_attn_mask returns early when attention_mask is None,
  skipping a per-microbatch D2H sync on cache_position[0] (both branches
  returned None for that input).
- _make_causal_mask caches one upper-triangular mask per (dtype, device),
  grown on demand and sliced per call, instead of rebuilding the [S, S]
  mask every microbatch.

The benchmark YAML enables both flags; its documented throughput moves
from ~441 tok/s/GPU (12.2% MFU) to ~496 tok/s/GPU (13.7% MFU), measured
wall-clock with the same formula as before.

Mini-scale gates: loss parity across all steps with flags on/off; RMSNorm
compile parity is covered by unit tests (allclose forward and backward).

Signed-off-by: Yisong Li <yisongbetter@gmail.com>

* test(moe): behavioral coverage for the static-routing m_splits cache

Address the review bot's two observations: extract the TE-path split-size
resolution into _resolve_m_splits (unchanged logic, now directly unit-testable
on CPU) with tests asserting cache reuse under static routing and
re-materialization without it; and read BackendConfig.benchmark_static_routing
directly instead of a defensive getattr on the typed field.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>

* docs(kimi-k3): state the measured model shape in the 1k benchmark header

The 13.7% MFU figure was measured before #3792 aligned the KimiK3TextConfig
defaults with the released checkpoint (56-head MLA / 18432-wide dense FFN,
~101.5B active matmul parameters instead of 104.0B). Say so in the header,
and point at the recipes/llm/benchmark.py entry that prints the recipe's own
MFU, until a re-measurement on the aligned shape replaces the numbers.

Signed-off-by: Yisong Li <yisongbetter@gmail.com>

---------

Signed-off-by: Yisong Li <yisongbetter@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants