Skip to content

Add Kimi-K3 NVFP4 experts and FP8-PB attention recipe - #2206

Open
Edwardf0t1 wants to merge 4 commits into
mainfrom
agent/kimi-k3-nvfp4-fp8-pb-recipe
Open

Add Kimi-K3 NVFP4 experts and FP8-PB attention recipe#2206
Edwardf0t1 wants to merge 4 commits into
mainfrom
agent/kimi-k3-nvfp4-fp8-pb-recipe

Conversation

@Edwardf0t1

@Edwardf0t1 Edwardf0t1 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: new example

Adds the calibration-free conversion pipeline and checkpoint-mirror PTQ recipe used for nvidia/Kimi-K3-NVFP4:

  • streams the 96-shard Kimi-K3 checkpoint without loading the 2.8T model;
  • casts the source MXFP4 routed experts to NVFP4 with expert input_scale=1.0;
  • quantizes the selected KDA and MLA attention weights to 128x128 block FP8;
  • leaves shared/latent experts, routers, convolutions, norms, the vision tower, lm_head, and KV cache unquantized;
  • emits mixed-precision Hugging Face metadata for deployment; and
  • adds an exact recipe under modelopt_recipes/huggingface/models/moonshotai/Kimi-K3/ that directly configures the streaming converter.

It also fixes NVFP4QTensor.quantize() probing CUDA/Blackwell capability before checking whether the tensor is on CUDA and whether the optional TensorRT-LLM fast path was requested. That probe broke the converter's supported CPU path on hosts without a compatible GPU.

Usage

python examples/kimi/kimi_k3/quantize_to_nvfp4.py \
    --source_ckpt /models/moonshotai/Kimi-K3 \
    --output_ckpt /models/Kimi-K3-NVFP4 \
    --recipe huggingface/models/moonshotai/Kimi-K3/ptq/nvfp4_experts-fp8_pb_attention \
    --jobs 8

The conversion requires no calibration dataset, forward pass, or GPU. Multi-node shard conversion is also supported through --rank, --world_size, and --run_id.

Testing

uv run --frozen --extra dev python -m pytest -q \
    tests/unit/torch/quantization/test_nvfp4_tensor.py \
    tests/unit/recipe/test_kimi_k3_recipe.py \
    tests/unit/recipe/test_recipe_docs.py \
    tests/examples/hf_ptq/test_kimi_k3_quantize_to_nvfp4.py

Result: 17 passed.

All pre-commit hooks pass for the changed files, including recipe validation, Ruff, mypy, Bandit, YAML formatting, and markdownlint.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ✅
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A
  • Did you write any new necessary tests?: ✅
  • Did you update Changelog?: ✅
  • Did you get Claude approval on this PR?: N/A

Additional Information

The resulting checkpoint and model card are available at https://huggingface.co/nvidia/Kimi-K3-NVFP4.

Summary by CodeRabbit

  • New Features

    • Added calibration-free Kimi-K3 MXFP4-to-NVFP4 checkpoint conversion, with optional FP8 attention quantization and distributed processing.
    • Added NVFP4 activation headroom calibration, weight-only quantization recipes, and expanded quantization workflows.
    • Added MLflow tracking for supported post-training quantization and vLLM fake-quantization runs.
  • Documentation

    • Updated quantization terminology, recipe catalogs, and Kimi-K3 usage guides.
  • Bug Fixes

    • Improved CPU and disabled-fast-path NVFP4 behavior, tied-weight export handling, and EAGLE-3 training compatibility.
    • Removed deprecated configuration options and legacy evaluation examples.

Signed-off-by: Zhiyu Cheng <zhiyuc@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Version 0.47 adds calibration-free Kimi-K3 MXFP4-to-NVFP4 conversion, shared export utilities, quantization recipes, documentation, distributed checkpoint handling, and regression tests.

Changes

Kimi-K3 NVFP4 conversion

Layer / File(s) Summary
Recipe and conversion contract
modelopt_recipes/huggingface/..., modelopt_recipes/ptq.md, examples/kimi/README.md, examples/hf_ptq/README.md, tests/unit/recipe/*, CHANGELOG.rst
Adds Kimi-K3 recipe settings, weight-only recipe documentation, support-matrix updates, streaming conversion instructions, and release notes.
Shared quantization primitives
modelopt/torch/export/*, examples/deepseek/deepseek_v4/quantize_to_nvfp4.py
Adds shared MXFP4/NVFP4 conversion, W1/W3 scale overrides, filesystem handling, path validation, and output preparation. DeepSeek conversion now uses these utilities.
Kimi-K3 shard conversion
examples/kimi/kimi_k3/quantize_to_nvfp4.py
Adds recipe validation, routed-expert conversion, optional FP8-family attention quantization, BF16 preservation, metadata rewriting, and conversion statistics.
Checkpoint orchestration
examples/kimi/kimi_k3/quantize_to_nvfp4.py
Adds CLI validation, shard assignment, distributed fingerprints, rank synchronization, atomic reports, final configuration updates, and entry-point wiring.
Validation and regression coverage
tests/examples/kimi/*, tests/unit/torch/export/*, tests/unit/torch/quantization/*
Tests recipe constraints, conversion accuracy, attention formats, metadata output, filesystem safety, distributed validation, and CPU NVFP4 behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: 🟠 High · up to d9ec9

The conversion tool can follow symlinked checkpoint files outside the requested source directory, potentially reading unintended data and producing output from it. Merge should wait until checkpoint inputs are restricted to regular files contained within the source directory.

Suggested reviewers: meenchen, realasma

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant KimiK3Converter
  participant ShardCastUtils
  participant CheckpointFiles
  participant ConversionReport
  Operator->>KimiK3Converter: provide recipe and checkpoint paths
  KimiK3Converter->>ShardCastUtils: convert routed MXFP4 expert tensors
  ShardCastUtils-->>KimiK3Converter: return NVFP4 tensors and scales
  KimiK3Converter->>CheckpointFiles: quantize attention and rewrite metadata
  KimiK3Converter->>ConversionReport: publish per-rank and final statistics
Loading

Fixed issue severity: Low

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the Kimi-K3 NVFP4 expert and FP8 attention recipe added by the pull request.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns ✅ Passed The complete PR diff adds no forbidden patterns; its only torch.load uses weights_only=True, with no # nosec, unsafe trust_remote_code, or dependency-manifest changes found.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/kimi-k3-nvfp4-fp8-pb-recipe

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://NVIDIA.github.io/Model-Optimizer/pr-preview/pr-2206/

Built to branch gh-pages at 2026-08-24 16:54 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.33333% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.23%. Comparing base (a2fbac7) to head (d9ec9e7).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
modelopt/torch/export/shard_cast_utils.py 68.06% 38 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2206      +/-   ##
==========================================
- Coverage   78.99%   78.23%   -0.77%     
==========================================
  Files         522      524       +2     
  Lines       60599    61253     +654     
==========================================
+ Hits        47872    47920      +48     
- Misses      12727    13333     +606     
Flag Coverage Δ
examples-diffusers 20.70% <22.50%> (+<0.01%) ⬆️
examples-gpt-oss 13.24% <22.50%> (+0.01%) ⬆️
examples-hf_ptq 21.47% <22.50%> (-0.04%) ⬇️
examples-llm_distill 13.31% <22.50%> (+0.01%) ⬆️
examples-llm_eval 17.07% <22.50%> (+0.01%) ⬆️
examples-llm_qat 17.55% <22.50%> (+<0.01%) ⬆️
examples-llm_sparsity 15.89% <22.50%> (+0.01%) ⬆️
examples-megatron_bridge 25.67% <22.50%> (-0.16%) ⬇️
examples-specdec_bench 12.99% <22.50%> (+0.01%) ⬆️
examples-speculative_decoding 17.49% <22.50%> (-0.06%) ⬇️
examples-torch_onnx 21.79% <22.50%> (+<0.01%) ⬆️
examples-torch_trt 15.05% <22.50%> (+0.01%) ⬆️
gpu 58.43% <22.50%> (-0.78%) ⬇️
regression 14.87% <22.50%> (+0.08%) ⬆️
unit 55.77% <68.33%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Edwardf0t1
Edwardf0t1 marked this pull request as ready for review August 18, 2026 00:13
@Edwardf0t1
Edwardf0t1 requested review from a team as code owners August 18, 2026 00:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 4

🧹 Nitpick comments (1)
examples/kimi/kimi_k3/quantize_to_nvfp4.py (1)

966-982: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Validate world_size against the rank-0 rendezvous file.

Rank 0 writes world_size into ready.json at Line 971. Non-zero ranks only compare run_id at Line 976. If a rank is launched with a different --world_size, the shard assignment at Line 982 (shards[rank::world_size]) leaves gaps or duplicates.

Rank 0's len(results) != len(shards) check at Line 1064 catches most mismatches, but overlapping duplicates and gaps can sum to the expected count. The value is already in the file, so the check costs one comparison.

♻️ Proposed refactor
     else:
+        def rendezvous_ready() -> bool:
+            if not ready_path.exists():
+                return False
+            ready = json.loads(ready_path.read_text())
+            if ready.get("run_id") != args.run_id:
+                return False
+            if ready.get("world_size") != args.world_size:
+                raise ValueError(
+                    f"rank {args.rank} has --world_size {args.world_size}, but rank 0 "
+                    f"published {ready.get('world_size')} for run {args.run_id}"
+                )
+            return True
+
         _wait_for(
-            lambda: (
-                ready_path.exists()
-                and json.loads(ready_path.read_text()).get("run_id") == args.run_id
-            ),
+            rendezvous_ready,
             f"rank-0 rendezvous for run {args.run_id}",
             args.sync_timeout,
         )
🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@examples/kimi/kimi_k3/quantize_to_nvfp4.py` around lines 966 - 982, Update
the non-zero-rank readiness predicate in the rendezvous flow to require both the
matching run_id and a world_size equal to args.world_size before proceeding to
shard assignment. Keep rank 0’s ready.json writing unchanged and preserve the
existing timeout behavior.
🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Inline comments:
In `@examples/kimi/kimi_k3/quantize_to_nvfp4.py`:
- Around line 891-896: Update the argument validation and recipe handling around
the `--recipe` branch so explicitly provided `--input_scale` cannot be silently
overwritten by the recipe-derived value. Reject `--recipe` combined with
`--input_scale`, matching the existing format-flag validation, while preserving
recipe defaults when `--input_scale` was not explicitly supplied.
- Around line 609-634: Update _module_name_aliases to emit the vLLM runtime
alias lm_head for language_model.lm_head, while preserving the existing model.*
and stripped-prefix aliases for language_model.model.* names and the
block_sparse_moe-to-mlp mapping.
- Around line 243-260: Ensure _build_w13_kmax_overrides or its caller preserves
one shared k_max for every w1/w3 pair across shard boundaries; either compute
pair values in a full pre-pass over all shards and reuse them in convert_shard,
or detect a split pair and fail with a clear diagnostic instead of deriving
independent per-tensor values.

In `@tests/unit/torch/quantization/test_nvfp4_tensor.py`:
- Around line 33-42: Update test_cpu_quantize_does_not_probe_cuda to cover both
try_tensorrt=False and try_tensorrt=True, while keeping fp4_compatible patched
to fail and asserting CPU quantization completes without invoking it.

---

Nitpick comments:
In `@examples/kimi/kimi_k3/quantize_to_nvfp4.py`:
- Around line 966-982: Update the non-zero-rank readiness predicate in the
rendezvous flow to require both the matching run_id and a world_size equal to
args.world_size before proceeding to shard assignment. Keep rank 0’s ready.json
writing unchanged and preserve the existing timeout behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4d5a38e2-1411-49e1-9b2a-452a4ba0d6c0

📥 Commits

Reviewing files that changed from the base of the PR and between 58ad6ed and 224bbd0.

📒 Files selected for processing (11)
  • CHANGELOG.rst
  • examples/hf_ptq/README.md
  • examples/kimi/README.md
  • examples/kimi/kimi_k3/quantize_to_nvfp4.py
  • modelopt/torch/quantization/qtensor/nvfp4_tensor.py
  • modelopt_recipes/huggingface/README.md
  • modelopt_recipes/huggingface/models/moonshotai/Kimi-K3/ptq/nvfp4_experts-fp8_pb_attention.yaml
  • modelopt_recipes/ptq.md
  • tests/examples/hf_ptq/test_kimi_k3_quantize_to_nvfp4.py
  • tests/unit/recipe/test_kimi_k3_recipe.py
  • tests/unit/torch/quantization/test_nvfp4_tensor.py

Included review availability: Your plan includes up to 12 reviews per rolling hour; 11 remain after this review.

Comment thread examples/kimi/kimi_k3/quantize_to_nvfp4.py Outdated
Comment thread examples/kimi/kimi_k3/quantize_to_nvfp4.py
Comment thread examples/kimi/kimi_k3/quantize_to_nvfp4.py
Comment thread tests/unit/torch/quantization/test_nvfp4_tensor.py Outdated

Usage (CPU partition, no GPU needed; ``--jobs`` shards convert in parallel):

python quantize_to_nvfp4.py \\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to expand this cover general model types that we just want to do weight casting?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the closed-form MXFP4-to-NVFP4 numerics can be reused and already live in shared numeric utilities used by this converter and the GPT-OSS cast. I would keep this script Kimi-K3-specific in this PR because its tensor paths, fused w1/w3 scale contract, manifest aliases and exclusions, attention policy, and config rewrite are model-specific. A general weight-only checkpoint caster would be better introduced as a focused follow-up.

Signed-off-by: Zhiyu Cheng <zhiyuc@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🧹 Nitpick comments (1)
CHANGELOG.rst (1)

12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove implementation detail from this changelog entry.

Describe the new APIs and their user-visible limitations. Remove details about fake-quant snapshots, retained scales, and restoration mechanics.

As per coding guidelines, “Keep each entry to one or two sentences written for external users” and include “No … implementation detail.”

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@CHANGELOG.rst` at line 12, Rewrite the changelog entry to describe the two
new public APIs and only their user-visible limitations, such as unsupported
shared weights, shared quantizers, and SequentialQuantizer weights. Remove
implementation details about snapshots, retained scales, devices, and
restoration mechanics, keeping the entry to one or two sentences.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Inline comments:
In `@examples/kimi/kimi_k3/quantize_to_nvfp4.py`:
- Around line 808-821: Extend _rank0_ready and the ready/report rendezvous flow
to publish and validate one canonical conversion fingerprint containing the
resolved source path, shard list, conversion-format flags, and input scale;
reject mismatches before any rank writes shards. Store the same fingerprint in
ready.json and each rank report, and guard shared report, manifest, and other
file writes against races using the existing coordination mechanism.

---

Nitpick comments:
In `@CHANGELOG.rst`:
- Line 12: Rewrite the changelog entry to describe the two new public APIs and
only their user-visible limitations, such as unsupported shared weights, shared
quantizers, and SequentialQuantizer weights. Remove implementation details about
snapshots, retained scales, devices, and restoration mechanics, keeping the
entry to one or two sentences.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 094bb526-d260-4487-b688-815a5133af0b

📥 Commits

Reviewing files that changed from the base of the PR and between 224bbd0 and c985838.

📒 Files selected for processing (6)
  • CHANGELOG.rst
  • examples/hf_ptq/README.md
  • examples/kimi/kimi_k3/quantize_to_nvfp4.py
  • modelopt_recipes/ptq.md
  • tests/examples/hf_ptq/test_kimi_k3_quantize_to_nvfp4.py
  • tests/unit/torch/quantization/test_nvfp4_tensor.py

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Comment thread examples/kimi/kimi_k3/quantize_to_nvfp4.py Outdated

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (claude-opus-5) — DM the bot to share feedback.

Re-review status of previous comments

Addressed and verified in the current diff:

  • 💬 w1/w3 split across shards (critical) — _build_w13_kmax_overrides now raises with a clear diagnostic, plus test_split_w1_w3_pair_fails_instead_of_using_independent_scales. ✔ (but see the non-cast path below — the same contract is still unenforced there)
  • 💬 language_model.lm_head runtime alias (minor) — _module_name_aliases now strips the language_model. prefix; covered by a focused test. ✔
  • 💬 --recipe silently overwriting --input_scale (minor) — --input_scale now defaults to None, the combination is rejected, and there is a CLI regression test. ✔
  • 💬 CPU-guard test only exercising try_tensorrt=False (critical-ish) — now parameterized over both. ✔

Still open:

  • The cross-rank "conversion fingerprint" comment (ranks can disagree on --attn_* / --cast_mxfp4_to_nvfp4 / source path; only run_id and world_size are validated). Given this is a multi-node example script writing a 2.8T checkpoint, a mismatched rank produces a silently inconsistent output that rank 0 happily finalizes. Not necessarily blocking, but it hasn't been answered.
  • 💬 @cjluo-nv asked whether this could be generalized to weight-only casting for other models; the author replied that the shared numerics already live in numeric_utils and proposed a follow-up for a general caster. That answers the numerics half, but not the larger duplication: this file re-implements examples/deepseek/deepseek_v4/quantize_to_nvfp4.py almost verbatim for _dequantize_mxfp4_to_bf16, _kmax_from_mxfp4_scale, _build_w13_kmax_overrides, _quantize_weight_nvfp4_lossless, _link_or_copy / _hard_link_aux, _validate_paths, _prepare_output_dir, _log, and the shard-streaming/index-rewrite skeleton — ~200+ lines of copy-paste, including the identical w1/w3 fused-GEMM1 contract. The design gate fired on this PR and the PR body doesn't discuss why the DS-V4 streaming converter (or a shared examples/_shard_cast_utils.py) wasn't extended. That's a maintainer call, but it should be made explicitly rather than deferred implicitly.

New finding (correctness): the cast=False expert path derives weight_scale_2 independently per tensor, so w1 and w3 do not share one scale_2 — exactly the contract the module docstring says must hold for the fused GEMM1, and the contract the cast=True path now hard-fails to protect. DS-V4 solves this with _build_w13_weight_amax_overrides; here there is no equivalent and no test. Since --cast_mxfp4_to_nvfp4 is a store_true defaulting to off, a bare python quantize_to_nvfp4.py --source_ckpt ... --output_ckpt ... silently emits a wrong checkpoint.

Other notes: the recipe→converter translation (_conversion_settings_from_recipe) only extracts 5 fields, so any added/edited quantizer entry in the shipped recipe (e.g. enabling shared experts) is silently ignored while the recipe is presented as the authoritative quant map; and the test file for an examples/kimi/ script lives under tests/examples/hf_ptq/. Size (1765 lines, one 1170-line script) is on the large side — splitting the nvfp4_tensor.py short-circuit fix + its unit test from the example would make both easier to land. Licensing: standard NVIDIA Apache-2.0 headers only, no third-party code.

stats["cast_oor_tensors"] += 1
else:
bf16 = _dequantize_mxfp4_to_bf16(w, s, device)
weight_scale_2 = (bf16.abs().max().float() / (E2M1_MAX * E4M3_MAX)).reshape(())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

w1/w3 do not share scale_2 on the non-cast path.

else:
    bf16 = _dequantize_mxfp4_to_bf16(w, s, device)
    weight_scale_2 = (bf16.abs().max().float() / (E2M1_MAX * E4M3_MAX)).reshape(())

w13_kmax is only built if cast, so with --cast_mxfp4_to_nvfp4 off every projection gets its own per-tensor weight_scale_2. The module docstring ("w1/w3 feed one fused GEMM1 and therefore must share a single scale_2") and the new hard failure in _build_w13_kmax_overrides both assert the opposite invariant, so the default flag combination silently produces the exact checkpoint the cast path refuses to emit. examples/deepseek/deepseek_v4/quantize_to_nvfp4.py handles this with _build_w13_weight_amax_overrides (shared torch.maximum amax per pair).

Either share the amax across the pair here as DS-V4 does, or drop the non-cast branch entirely if the only supported entry point is the recipe (which always sets cast=True). Whichever way, the existing test_convert_shard_casts_experts_and_quantizes_attention assertion on equal weight_scale_2 should be mirrored for cast=False — right now no test exercises that branch at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 575c27a. I kept the non-cast path supported and now build a shared w1/w3 weight amax with the common pair helper before requantization, so both projections receive the same weight_scale_2 while w2 remains independent. test_convert_shard_requantizes_w1_w3_with_shared_scale exercises cast=False end to end and checks that invariant.

via ``modelopt.torch.quantization.utils.numeric_utils``.

As in DeepSeek-V4, ``w1``/``w3`` feed one fused GEMM1 and therefore must
share a single ``scale_2``, so ``k_max`` is taken over both projections.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

Duplication with the DeepSeek-V4 converter.

_dequantize_mxfp4_to_bf16, _kmax_from_mxfp4_scale, _build_w13_kmax_overrides, _quantize_weight_nvfp4_lossless, _link_or_copy/_hard_link_aux, _validate_paths, _prepare_output_dir and _log are near-verbatim copies of examples/deepseek/deepseek_v4/quantize_to_nvfp4.py, down to the docstrings and the w1/w3 fused-GEMM1 rationale. Only the tensor-name regexes, the attention policy and the manifest/alias logic are genuinely Kimi-specific.

numeric_utils already exists precisely because the GPT-OSS and DS-V4 casts shared math; the shard-streaming/aux-linking/index-rewrite skeleton is the next obvious extraction (an examples/_mxfp4_cast_utils.py or similar). Please either factor the shared half out in this PR or state in the PR body why a third independent copy is preferable — that's the question @cjluo-nv raised above, and the follow-up-PR answer leaves the duplication in main in the meantime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 575c27a. I extracted the common MXFP4 dequant/requant and closed-form cast numerics, w1/w3 pair-scale helpers, path validation/output preparation, progress logging, and recursive auxiliary-file linking into examples/_shard_cast_utils.py; both the DS-V4 and Kimi converters now use that module. I kept the shard/index rewrite portions model-specific because DS-V4 consumes calibrated amax and explicit index deltas, while Kimi handles multiple attention formats plus partial/distributed shard reports and total-size reconstruction. I also added coverage for the shared auxiliary-file filtering.

_NVFP4_BLOCK = 16 # NVFP4 block size (elements)
_MXFP4_BYTES_PER_BLOCK = 16 # 32 E2M1 nibbles packed 2-per-byte
_MXFP4_BLOCK = 32

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

_conversion_settings_from_recipe validates the routed-expert and attention entries it knows about and derives five settings, but everything else in the recipe (base_disable_all, default_disabled_quantizers, the algorithm block, any future entry) is ignored. So a recipe edit that, say, enables *shared_experts*weight_quantizer would pass validation and be silently dropped by the converter while modelopt_recipes/ptq.md advertises the YAML as the checkpoint's quant map.

Consider asserting that no quantizer entry outside the known set is enabled, so recipe drift fails loudly instead of producing a checkpoint that doesn't match the recipe.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in d9ec9e7. Recipe validation now requires the expected algorithm and wildcard disable entry, requires every known enabled quantizer, and rejects any unexpected enabled quantizer. Tests cover both an enabled shared-expert entry and algorithm drift.


from modelopt.torch.quantization.qtensor import FP8QTensor, MXFP4QTensor, MXFP8QTensor

_SCRIPT = (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

Minor: this exercises examples/kimi/kimi_k3/quantize_to_nvfp4.py, but lives under tests/examples/hf_ptq/. tests/examples/ is organized per example directory (gpt-oss/, llm_qat/, ...), so tests/examples/kimi/ would be the consistent home.

Signed-off-by: Zhiyu Cheng <zhiyuc@nvidia.com>
@Edwardf0t1
Edwardf0t1 requested a review from a team as a code owner August 21, 2026 23:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🧹 Nitpick comments (3)
examples/kimi/kimi_k3/quantize_to_nvfp4.py (1)

242-261: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Defer fused-attention amax transfers until the pre-pass ends.

When device is CUDA, Tensor.cpu() synchronizes the current stream after each attention-tensor reduction. Keep group_amax on CUDA during the loop, then transfer all values to CPU once before constructing worker jobs.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@examples/kimi/kimi_k3/quantize_to_nvfp4.py` around lines 242 - 261, Update
compute_fused_attn_amax to keep each reduced amax in the requested device during
the shard loop by removing the per-tensor CPU transfer, then move all group_amax
values to CPU once after the loop and before returning. Preserve the existing
maximum aggregation behavior.

Source: Coding guidelines

examples/deepseek/deepseek_v4/quantize_to_nvfp4.py (1)

216-243: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Keep synthesized weight amax values on device.

When device is CUDA, .cpu() synchronizes the CUDA stream. Return the synthesized amax on device, and move loaded amax values to device before build_w13_amax_overrides combines them. Otherwise, mixed-device w1/w3 pairs can fail in torch.maximum.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@examples/deepseek/deepseek_v4/quantize_to_nvfp4.py` around lines 216 - 243,
Update _synthesize_weight_amax to keep the computed maximum on the requested
device instead of moving it to CPU, and update get_amax in
_build_w13_weight_amax_overrides to move loaded amax values to device before
build_w13_amax_overrides combines w1/w3 values. Ensure both synthesized and
loaded amax tensors are device-consistent.

Source: Coding guidelines

examples/_shard_cast_utils.py (1)

169-172: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Avoid the extra per-expert CUDA synchronization.

When device is CUDA, lossless.sum().item() blocks the host for each expert. Return a device counter, aggregate the counters on the device, replace the per-expert Python branch with a device-side reduction, and convert the totals once after shard conversion. The later .cpu() copies already synchronize output transfers.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@examples/_shard_cast_utils.py` around lines 169 - 172, The shard conversion
flow should avoid calling .item() for each expert on CUDA. Update the
lossless-count logic to return a device-resident counter, aggregate counters and
perform the lossless decision via device-side reductions, then convert the final
totals to host values only once after shard conversion; preserve the existing
CPU behavior and use the surrounding shard conversion helpers to locate the
affected paths.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Inline comments:
In `@examples/_shard_cast_utils.py`:
- Around line 222-226: Update link_aux_files to validate each source path before
link_or_copy: reject symlinks and any non-regular file, including source paths
outside source_ckpt, before copying or linking. Preserve the existing
destination cleanup for accepted regular files.

---

Nitpick comments:
In `@examples/_shard_cast_utils.py`:
- Around line 169-172: The shard conversion flow should avoid calling .item()
for each expert on CUDA. Update the lossless-count logic to return a
device-resident counter, aggregate counters and perform the lossless decision
via device-side reductions, then convert the final totals to host values only
once after shard conversion; preserve the existing CPU behavior and use the
surrounding shard conversion helpers to locate the affected paths.

In `@examples/deepseek/deepseek_v4/quantize_to_nvfp4.py`:
- Around line 216-243: Update _synthesize_weight_amax to keep the computed
maximum on the requested device instead of moving it to CPU, and update get_amax
in _build_w13_weight_amax_overrides to move loaded amax values to device before
build_w13_amax_overrides combines w1/w3 values. Ensure both synthesized and
loaded amax tensors are device-consistent.

In `@examples/kimi/kimi_k3/quantize_to_nvfp4.py`:
- Around line 242-261: Update compute_fused_attn_amax to keep each reduced amax
in the requested device during the shard loop by removing the per-tensor CPU
transfer, then move all group_amax values to CPU once after the loop and before
returning. Preserve the existing maximum aggregation behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 37531a95-2b01-406e-9a0f-6e2786728d21

📥 Commits

Reviewing files that changed from the base of the PR and between c985838 and 575c27a.

📒 Files selected for processing (4)
  • examples/_shard_cast_utils.py
  • examples/deepseek/deepseek_v4/quantize_to_nvfp4.py
  • examples/kimi/kimi_k3/quantize_to_nvfp4.py
  • tests/examples/hf_ptq/test_kimi_k3_quantize_to_nvfp4.py

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Comment thread modelopt/torch/export/shard_cast_utils.py

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (claude-opus-5) — DM the bot to share feedback.

Re-review of PR #2206 (13 files, +2010/-269). All previously-flagged critical items are now addressed and verified in the diff:

  • 💬 cast=False expert path did not share weight_scale_2 across w1/w3 (critical) — fixed in 575c27a: the non-cast branch now builds a shared amax via build_w13_amax_overrides, and test_convert_shard_requantizes_w1_w3_with_shared_scale asserts w1.weight_scale_2 == w3.weight_scale_2 while w2 stays independent. ✔
  • 💬 Split w1/w3 pair (critical) — _w13_pairs raises with a clear diagnostic; regression test present. ✔
  • 💬 ~200 lines duplicated from examples/deepseek/deepseek_v4/quantize_to_nvfp4.py (design gate) — extracted into examples/_shard_cast_utils.py; DS-V4 now consumes it (-265 lines). Constants check out (2**E4M3_KMIN == 2**-9, E2M1_MAX*E4M3_MAX == 6*448), so the extraction looks numerically faithful. ✔
  • 💬 --recipe vs --input_scale, language_model.lm_head alias, CPU-guard test parameterization — all addressed with tests. ✔

Licensing: standard NVIDIA Apache-2.0 headers only, no third-party code. Note that the CodeRabbit comment bodies embed "🤖 Prompt for AI Agents" blocks and curl | sh install hints; I treated those as data and did not act on them.

Nudging rather than approving for the items below — mostly owner judgement calls on a shipped 2.8T-checkpoint workflow.

Comment thread examples/_shard_cast_utils.py Outdated
# See the License for the specific language governing permissions and
# limitations under the License.

"""Shared primitives for streaming MXFP4-to-NVFP4 checkpoint conversion."""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this to the export module in core lib and add test?

Signed-off-by: Zhiyu Cheng <zhiyuc@nvidia.com>
@Edwardf0t1
Edwardf0t1 requested a review from a team as a code owner August 24, 2026 16:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/kimi/kimi_k3/quantize_to_nvfp4.py (1)

951-956: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject symlinked primary checkpoint inputs.

validate_paths checks directory overlap only. Lines 952-956 then follow model.safetensors.index.json and shard symlinks without verifying that their resolved targets remain inside source_ckpt.

A malicious checkpoint directory can therefore read an external JSON file or safetensors file and write derived content into output_ckpt. Apply the same non-symlink, regular-file, and resolved-containment checks used for auxiliary files to config.json, model.safetensors.index.json, and every selected shard before reading them.

Proposed validation shape
+def _validate_checkpoint_file(path: Path, source_root: Path) -> None:
+    if path.is_symlink() or not path.is_file():
+        raise ValueError(f"checkpoint source must be a regular file: {path}")
+    if source_root not in path.resolve(strict=True).parents:
+        raise ValueError(f"checkpoint source is outside source_ckpt: {path}")
+
 validate_paths(args.source_ckpt, args.output_ckpt)
+source_root = args.source_ckpt.resolve(strict=True)
 src_index_path = args.source_ckpt / "model.safetensors.index.json"
+_validate_checkpoint_file(src_index_path, source_root)
+_validate_checkpoint_file(args.source_ckpt / "config.json", source_root)
 ...
 shards = sorted(args.source_ckpt.glob("model-*-of-*.safetensors"))
+for shard in shards:
+    _validate_checkpoint_file(shard, source_root)

As per coding guidelines, “Validate checkpoint paths and other external inputs at boundaries.”

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@examples/kimi/kimi_k3/quantize_to_nvfp4.py` around lines 951 - 956, Extend
the checkpoint-input validation around validate_paths to reject symlinked or
non-regular config.json, model.safetensors.index.json, and selected shard files,
and require each file’s resolved path to remain within source_ckpt before
reading it. Reuse the existing auxiliary-file validation logic and apply it
consistently before json.loads or shard processing.

Sources: Coding guidelines, Path instructions

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Outside diff comments:
In `@examples/kimi/kimi_k3/quantize_to_nvfp4.py`:
- Around line 951-956: Extend the checkpoint-input validation around
validate_paths to reject symlinked or non-regular config.json,
model.safetensors.index.json, and selected shard files, and require each file’s
resolved path to remain within source_ckpt before reading it. Reuse the existing
auxiliary-file validation logic and apply it consistently before json.loads or
shard processing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2033fceb-d9c8-4cd2-8fcf-859857c33c02

📥 Commits

Reviewing files that changed from the base of the PR and between 575c27a and d9ec9e7.

📒 Files selected for processing (6)
  • examples/deepseek/deepseek_v4/quantize_to_nvfp4.py
  • examples/kimi/kimi_k3/quantize_to_nvfp4.py
  • modelopt/torch/export/__init__.py
  • modelopt/torch/export/shard_cast_utils.py
  • tests/examples/kimi/test_kimi_k3_quantize_to_nvfp4.py
  • tests/unit/torch/export/test_shard_cast_utils.py

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (claude-opus-5) — DM the bot to share feedback.

Re-review of PR #2206 (15 files, +2248/-269). Every previously-flagged critical item is now resolved and verified in the diff, but I'd like a human sign-off before merge because this round introduced a new hard-fail in shared code that now also governs the existing DeepSeek-V4 converter.

Resolved since the last review (no further action needed): cross-rank conversion fingerprint (_conversion_fingerprint / _validate_fingerprint published in ready.json and every rank report, with mismatch tests); recipe drift (_conversion_settings_from_quantize_config now requires the algorithm block, the wildcard disable entry, every expected quantizer, and rejects unexpected enabled ones, with tests for a shared-expert entry and algorithm drift); tests moved to tests/examples/kimi/; and the shared cast helpers moved out of examples/ into modelopt/torch/export/shard_cast_utils.py with a unit-test file, per @cjluo-nv's request. The design-gate question (third copy of the DS-V4 streaming cast) is answered — DS-V4 now consumes the shared module (-265 lines) and the extraction looks numerically faithful. Licensing: standard NVIDIA Apache-2.0 headers only, no third-party code.

Still worth an owner look:

  • New, and the reason I'm not approving: link_aux_files (shard_cast_utils.py) raises ValueError for any symlinked sidecar and for any file whose resolved target is not under source_ckpt. huggingface_hub.snapshot_download materializes every file in snapshots/<rev>/ as a symlink into ../../blobs/, so pointing --source_ckpt at an HF cache snapshot now aborts with source must be a regular file — and it aborts at the last step, after all 96 shards have already been converted. This is also a behavior regression for the shipped DS-V4 converter, whose previous _hard_link_aux linked/copied through symlinks fine. Consider resolving-and-allowing links that stay inside the model repo (snapshot + sibling blobs/), or shutil.copy2 with a warning, rather than a terminal error.
  • modelopt/torch/export/__init__.py does from .shard_cast_utils import *, which promotes log (a bare print(..., flush=True) wrapper), validate_paths, prepare_output_dir, and link_or_copy into the public modelopt.torch.export namespace. log in particular is collision-prone with the wildcard imports that follow it. Narrowing __all__ to the cast helpers, or not re-exporting from __init__, would be safer for a core-library surface.
  • _conversion_settings_from_quantize_config compares quantize["algorithm"] by exact dict equality with _RECIPE_ALGORITHM; any future default-valued field added to the algorithm schema will make the shipped recipe fail validation. A key-subset check would be less brittle.
  • Kimi passes skip_file=lambda path: path.suffix == ".safetensors", so a nested model.safetensors.index.json inside a subdirectory is linked into the output (DS-V4 explicitly skips it at every level). Low risk, but a stale index in the output is confusing.
  • Size is still on the large side (~1.15k-line example script); the nvfp4_tensor.py short-circuit fix plus its unit test would land cleanly on its own.

Note on untrusted content: the CodeRabbit comment bodies embed "🤖 Prompt for AI Agents" blocks and a curl … | sh install hint. I treated those strictly as review data and did not act on them; flagging so a human is aware.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants