Skip to content

[OMNIML-5570] 2/2 Compose GEMM and KV-cache AutoQuant workflows - #2273

Open
meenchen wants to merge 2 commits into
agent/kv-cache-autoquant-corefrom
agent/kv-autoquant-composition
Open

[OMNIML-5570] 2/2 Compose GEMM and KV-cache AutoQuant workflows#2273
meenchen wants to merge 2 commits into
agent/kv-cache-autoquant-corefrom
agent/kv-autoquant-composition

Conversation

@meenchen

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: new feature.

Stacked on #2272. Adds composition of existing GEMM quantization with the standalone KV-cache AutoQuant API:

  • fixed FP8 GEMM PTQ followed by mixed-KV AutoQuantize;
  • gradient-based NVFP4/FP8 GEMM AutoQuantize followed by independent mixed-KV AutoQuantize;
  • an optional kv_auto_quantize recipe stage with independent method, constraints, candidates, and checkpoint path;
  • ordered hf_ptq.py orchestration that keeps the selected weight/activation QDQ behavior active while freezing its calibration state during KV scoring;
  • fail-closed validation of the actual converted model when a preceding stage leaves K/V quantizers enabled; and
  • unified export of a uniform-weight or mixed-weight checkpoint together with the selected per-layer KV map.

The two stages remain independent searches and reuse the public APIs from #2272. This PR does not change either solver, scoring protocol, or checkpoint schema.

Usage

Fixed FP8 GEMM PTQ followed by KV AutoQuantize:

python examples/hf_ptq/hf_ptq.py \
  --pyt_ckpt_path Qwen/Qwen3-8B \
  --recipe general/auto_quantize/fp8_ptq_then_kv_fp8_nvfp4_cast_kl_div_at_5p4bits \
  --kv_auto_quantize_checkpoint /path/to/kv_autoquant.pth \
  --export_path /path/to/qwen3-8b-fp8-and-mixed-kv

Weight AutoQuantize followed by KV AutoQuantize:

python examples/hf_ptq/hf_ptq.py \
  --pyt_ckpt_path Qwen/Qwen3-8B \
  --recipe general/auto_quantize/nvfp4_fp8_gradient_then_kv_fp8_nvfp4_cast_kl_div_at_5p4bits \
  --auto_quantize_checkpoint /path/to/weight_autoquant.pth \
  --kv_auto_quantize_checkpoint /path/to/kv_autoquant.pth \
  --export_path /path/to/qwen3-8b-autoquant-and-mixed-kv

Testing

  • 378 focused tests passed across both composed recipes, KV AutoQuant, recipe loading, and unified export.
  • All three shipped standalone/composed recipe paths invoke the actual public KV AutoQuant API on tiny offline Qwen fixtures.
  • Changed-file pre-commit hooks passed. The isolated recipe validator was rerun successfully after its initial network-only dependency bootstrap failure.
  • The complete tracked tree is byte-identical to the preserved combined ground-truth head f94d74a98 (tree 2564939297095db02c0ffea5848a0c1f99b18d6d).

Before your PR is "Ready for review"

Additional Information

  • This PR intentionally targets agent/kv-cache-autoquant-core so reviewers see only the nine composition-specific files.
  • --auto_quantize_checkpoint and --kv_auto_quantize_checkpoint are intentionally separate. Changing the preceding GEMM configuration requires a fresh KV checkpoint path.
  • Downstream deployment of uniform-weight plus mixed-KV metadata remains gated until the runtime's uniform-weight ModelOpt configuration consumes kv_cache_quantized_layers.

Signed-off-by: weimingc <17592131+meenchen@users.noreply.github.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 27, 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 27, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (3)
  • main
  • release/.*
  • feature/.*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d41e280a-a997-4488-bee5-c5fadb668418

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@meenchen meenchen changed the title Compose GEMM and KV-cache AutoQuant workflows [OMNIML-5570] 2/2 Compose GEMM and KV-cache AutoQuant workflows Sep 1, 2026
@meenchen
meenchen marked this pull request as ready for review September 1, 2026 18:31
@meenchen
meenchen requested review from a team as code owners September 1, 2026 18:31
@meenchen
meenchen requested review from cjluo-nv, juhi10071998, kevalmorabia97, realAsma and sychen52 and removed request for a team September 1, 2026 18:31
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 6.25000% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.84%. Comparing base (e75d06e) to head (15e6b4d).

Files with missing lines Patch % Lines
modelopt/recipe/config.py 10.00% 9 Missing ⚠️
modelopt/torch/export/quant_utils.py 0.00% 6 Missing ⚠️
Additional details and impacted files
@@                        Coverage Diff                        @@
##           agent/kv-cache-autoquant-core    #2273      +/-   ##
=================================================================
- Coverage                          70.49%   69.84%   -0.65%     
=================================================================
  Files                                526      526              
  Lines                              61588    61551      -37     
=================================================================
- Hits                               43417    42992     -425     
- Misses                             18171    18559     +388     
Flag Coverage Δ
examples-diffusers 20.58% <0.00%> (-0.01%) ⬇️
examples-gpt-oss 13.21% <0.00%> (-0.01%) ⬇️
examples-llm_distill 13.28% <0.00%> (-0.01%) ⬇️
examples-llm_eval 17.03% <6.25%> (-0.01%) ⬇️
examples-llm_qat 17.50% <6.25%> (-0.01%) ⬇️
examples-llm_sparsity 15.82% <0.00%> (-0.01%) ⬇️
examples-megatron_bridge 25.68% <6.25%> (-0.01%) ⬇️
examples-specdec_bench 12.96% <0.00%> (-0.01%) ⬇️
examples-speculative_decoding 17.44% <6.25%> (-0.01%) ⬇️
examples-torch_onnx 21.67% <6.25%> (-0.01%) ⬇️
examples-torch_trt 15.00% <6.25%> (-0.01%) ⬇️
gpu 31.96% <0.00%> (-0.01%) ⬇️
regression 14.85% <6.25%> (-0.01%) ⬇️

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.

@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.

Stacked composition PR (targets agent/kv-cache-autoquant-core, not main). The orchestration itself is small and readable, and the happy paths for both new shipped recipes are covered by tests. Three things I'd want resolved before this lands:

1. README now contradicts the shipped recipes and the new test. The diff rewrites the KV-AutoQuant paragraph to say "The shipped canary recipe searches calibrated FP8 K/V ... Each candidate uses max calibration so its persistent K/V scales are present in the unified HF checkpoint." All three shipped KV recipes (kv_fp8_nvfp4_cast_…, fp8_ptq_then_kv_…, nvfp4_fp8_gradient_then_kv_…) still use algorithm: (None) + constant_amax: 448.0, i.e. calibration-free cast candidates — which is exactly what the new loader test test_builtin_kv_autoquantize_recipes_use_calibration_free_cast_candidates asserts, and what kv_cache_auto_quant._validate_deployable_candidate treats as the constant-amax branch. Either revert this doc edit or change the recipes.

2. get_quant_config fail-closed guard removed with no replacement signal. The NotImplementedError for "uniform quantized weights + mixed-precision KV map" is dropped so the fixed-FP8-PTQ→mixed-KV flow can export. But the PR body itself says this combination "remains gated until the runtime's uniform-weight ModelOpt configuration consumes kv_cache_quantized_layers" — so the export now silently produces a checkpoint no released runtime can load. At minimum emit a warn() on that branch. Note this also affects non-AutoQuant flows: any uniform-weight PTQ run where only some KV-eligible layers are quantized (e.g. MTP layers excluded) now falls into the elif and exports kv_cache_quant_algo: MIXED_PRECISION + a per-layer map instead of the plain uniform algo; that path has no test.

3. Design gate (5 directories) is unaddressed in the PR body. The recipe schema already expresses stages as quantize (fixed PTQ) + auto_quantize; this adds a third special-cased field kv_auto_quantize plus three new cross-field validators, a second --kv_auto_quantize_checkpoint CLI flag, and a primary_is_kv / primary_uses_kv_checkpoint branch in the runner. The obvious in-repo alternative — a single ordered stage list (auto_quantize: list[AutoQuantizeConfig], or a generic stages:) with one checkpoint path per stage index — would collapse the validators and the checkpoint-attr branching and generalizes past two stages. The PR body explains what was built but not why the existing two-field shape couldn't be generalized. Please state the "why not a stage list / why not extend mtq.auto_quantize to both domains" rationale in the body.

Also: no negative tests for the three new ModelOptAutoQuantizeRecipe validators, and the checkpoint_attr string-indirection is worth simplifying (details inline).
[{"file": "examples/hf_ptq/README.md", "line": 457, "body": "This contradicts every shipped KV recipe and the new test. kv_fp8_nvfp4_cast_kl_div_at_5p4bits, fp8_ptq_then_kv_… and nvfp4_fp8_gradient_then_kv_… all specify algorithm: (None) with constant_amax: 448.0 — no calibration pass at all — and test_builtin_kv_autoquantize_recipes_use_calibration_free_cast_candidates asserts exactly that (candidate.algorithm is None). The previous wording ("Each candidate uses an explicit constant scale, avoiding an additional calibration pass") was correct. Same for the line above: "calibrated FP8 K/V" should stay "FP8-cast K/V", matching the recipe filenames."}, {"file": "modelopt/torch/export/quant_utils.py", "line": 1777, "body": "Two concerns with dropping the fail-closed guard here:\n\n1. Per the PR body, uniform-weight + mixed-KV checkpoints are still undeployable ("gated until the runtime's uniform-weight ModelOpt configuration consumes kv_cache_quantized_layers"). Previously that raised; now it exports silently. Please emit a warn() on this branch so a user doesn't discover it at deploy time.\n2. This branch is also reached by plain PTQ runs where only some KV-eligible layers are quantized (e.g. MTP/attention layers excluded by the recipe): len(kv_cache_formats) == 1 but all_kv_layers_quantized is False. Those exports now flip from a uniform kv_cache_quant_algo to MIXED_PRECISION + a per-layer map. That's a deployment-visible metadata change for existing recipes and there's no test for it — worth one covering "uniform weight format, uniform KV format, partial KV coverage"."}, {"file": "examples/hf_ptq/hf_ptq.py", "line": 481, "body": "checkpoint_attr: str + getattr(args, checkpoint_attr, None) is stringly-typed indirection that also silently yields None when the attribute is missing (see test_composed_kv_autoquantize_rejects_enabled_actual_kv_quantizers, whose SimpleNamespace has no kv_auto_quantize_checkpoint). Passing the resolved value — checkpoint: str | None = None, computed once in _run_auto_quantize_recipe — is simpler, keeps auto_quantize independent of argparse attribute names, and makes a missing flag a real error rather than a silent no-checkpoint run."}, {"file": "examples/hf_ptq/hf_ptq.py", "line": 935, "body": "The checkpoint flag a user must pass for the same KV search depends on whether a fixed quantize block precedes it: standalone KV recipe → --auto_quantize_checkpoint, fixed-PTQ-then-KV → --kv_auto_quantize_checkpoint. That's surprising and only discoverable from the README. Consider making any kv_effective_bits stage always use --kv_auto_quantize_checkpoint (with a one-release fallback to the old flag), or at least add a comment here explaining the rule.\n\nAlso: _assert_kv_autoquantize_input_is_clean only fires after the fixed PTQ stage has run a full calibration pass. Since the offending config is knowable from the recipe (a quantize block that enables *[kv]_bmm_quantizer under a KV-primary recipe), a cheap pre-check at recipe load/stage start would fail in seconds instead of after calibration."}, {"file": "modelopt/recipe/config.py", "line": 358, "body": "None of the three new error paths (kv_auto_quantize after a KV-domain primary, kv_auto_quantize without kv_effective_bits, auto_quantize.kv_cache + kv_auto_quantize) has a negative test — tests/unit/recipe/test_loader.py only adds happy-path cases. Please add pytest.raises(ValidationError, match=...) coverage for each, consistent with the existing test_load_recipe_autoquantize_fixed_baseline_requires_explicit_search style.\n\nSeparately: primary_is_kv now short-circuits the three pre-existing fixed-baseline checks. That means a KV-primary recipe with both a quantize baseline and module_search_spaces is silently accepted even though _run_auto_quantize_recipe drops fixed_quantize_config after mono_quantize and AutoQuantizeConfig._has_search_space already rejects module_search_spaces for KV. Worth an explicit error rather than relying on the other validator."}]

Signed-off-by: weimingc <17592131+meenchen@users.noreply.github.com>

@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 the composition PR (still stacked on #2272, targets agent/kv-cache-autoquant-core). Most of the previous round is genuinely fixed:

Resolved

  1. README contradicted the shipped recipes (critical) — fixed. The paragraph is back to "The shipped canary recipe searches FP8-cast K/V …" / "Each candidate uses an explicit constant scale, avoiding an additional calibration pass", which matches algorithm: None + constant_amax: 448.0 in all three recipes and test_builtin_kv_autoquantize_recipes_use_calibration_free_cast_candidates.
  2. Fail-closed guard dropped with no signal (critical) — fixed. get_quant_config now warn()s on the uniform-weight + mixed-KV branch, and both new export tests (…exports_mixed_kv_cache_map, …partial_kv_map_with_warning) cover the branch, including the partial-coverage case I asked for.
  3. checkpoint_attr string indirection (minor) — fixed: auto_quantize(..., checkpoint: str | None) is resolved once by the caller.
  4. Flag inconsistency + late clean-input check (minor/critical mix) — fixed: _resolve_kv_auto_quantize_checkpoint gives one rule for every KV-domain search with a deprecated fallback, and _quantize_config_explicitly_enables_kv now fails before mono_quantize runs (test asserts mono_quantize must not start).
  5. No negative tests for the new validators (critical) — fixed: four pytest.raises(ValidationError, …) cases, including the new explicit "KV-primary + fixed baseline must not define module_search_spaces" error.

Still open

  • Design gate (blocker #3) is unchanged. The PR body still explains what was built (an extra kv_auto_quantize field, three cross-field validators, a second CLI checkpoint flag, a primary_is_kv branch in the runner) but never says why the obvious generalization — one ordered stage list (stages: / auto_quantize: list[AutoQuantizeConfig]) with a checkpoint path per stage — was rejected, nor why mtq.auto_quantize/auto_quantize_kv_cache couldn't be composed at the API level instead of in hf_ptq.py. Please add that rationale to the body; per the design protocol I can't approve while it's unaddressed.
  • Two small residuals inline (doc/behavior mismatch on the deprecated-flag scope; dead fallback in the KV pre-check), plus one thing worth a human eye: with all_kv_layers_quantized (from #2272) plus this PR's warn() branch, an existing plain-PTQ recipe that excludes some KV-eligible layers (e.g. *mtp*) now exports kv_cache_quant_algo: MIXED_PRECISION + a per-layer map instead of the uniform algo. It's now tested and warned, but it's a deployment-visible metadata change for shipped recipes and the CHANGELOG is marked N/A here — make sure #2272's entry actually calls it out.

Comment thread modelopt/recipe/config.py
)

kv_auto_quantize: AutoQuantizeConfig | None = ModeloptField(
default=None,

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.

Design gate, unchanged from the last round: kv_auto_quantize is a third special-cased stage field alongside quantize and auto_quantize, and it brings three new cross-field validators plus a primary_is_kv short-circuit through the pre-existing fixed-baseline checks. The recipe schema already expresses "fixed PTQ then search"; the natural generalization is a single ordered stage list (stages: / auto_quantize: list[AutoQuantizeConfig]) with one checkpoint path per stage index, which collapses all three validators and the checkpoint-attr branching in the runner and generalizes past two stages. Please state in the PR body why that (and/or composing the two searches inside mtq.auto_quantize* rather than in hf_ptq.py) was rejected — the body currently documents the shape but not the choice.

Comment thread examples/hf_ptq/hf_ptq.py
help=(
"Path for saving/restoring any KV-cache AutoQuantize search checkpoint. Use a new "
"path whenever the preceding weight/activation quantization stage changes. Standalone "
"KV recipes temporarily accept --auto_quantize_checkpoint as a deprecated fallback."

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.

The help text (and the matching README sentence) says "Standalone KV recipes temporarily accept --auto_quantize_checkpoint as a deprecated fallback", but _run_auto_quantize_recipe calls _resolve_kv_auto_quantize_checkpoint for any KV-primary recipe — including the composed fp8_ptq_then_kv_…, which is not standalone. The behavior is the right one (one rule: every KV-domain search prefers the dedicated flag); it's the wording that's narrower than the code. Suggest "KV-primary recipes" in both places.

Comment thread examples/hf_ptq/hf_ptq.py
continue
suffix = pattern.rsplit(".", 1)[-1]
for name in enabled:
if fnmatch(name, suffix):

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.

entry.get("enable", entry.get("cfg") is not None): QuantizerCfgEntry.enable is bool with default=True, and this function is fed model_dump() output, so "enable" is always present and the fallback is dead. Not a bug (the dumped default True gives the same fail-closed answer), but the expression reads as if an absent enable were possible — entry["enable"] is clearer, or add a comment if you want to keep it tolerant of hand-built dicts.

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