Skip to content

feat(quant): skip max-calib forward when no activation needs data#1963

Open
Edwardf0t1 wants to merge 1 commit into
mainfrom
feat/skip-forward-loop-constant-amax
Open

feat(quant): skip max-calib forward when no activation needs data#1963
Edwardf0t1 wants to merge 1 commit into
mainfrom
feat/skip-forward-loop-constant-amax

Conversation

@Edwardf0t1

@Edwardf0t1 Edwardf0t1 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: new feature (performance)

Addresses @realAsma's review comment on #1947: when all enabled input/KV activation quantizers use a constant amax, max calibration still runs the full forward_loop, which is wasted — the constant/dynamic quantizers collect no data-driven statistics.

This adds _needs_activation_forward_for_max_calib(model) and gates the forward_loop in max_calibrate():

  • Skips the forward when no enabled quantizer needs data-driven activation stats — i.e. every enabled non-weight quantizer is dynamic, use_constant_amax, or constant_amax. Weight quantizers are still calibrated on the weight tensors directly via weight_only_quantize(), so results are unchanged; only the wasted forward is avoided.
  • Runs the forward whenever any enabled activation quantizer still needs data (normal PTQ → identical behavior).

Safety / scoping. The gate is behind an opt-in flag max_calibrate(..., skip_forward_without_activation_calib=False). max_calibrate has many internal callers — the advanced algorithms that always need activations (MSE, local Hessian, SmoothQuant, SVDQuant, GPTQ) call it directly and keep the default False, so they are unaffected. Only the top-level max path opts in, via MaxCalibConfig.skip_forward_without_activation_calib (default True).

Also simplifies a default-arg lambda in core_utils.sync_moe_expert_amax (lambda m, w=weight: m(w)lambda m: m(weight)) so mypy can infer its type — surfaced by the max_calibrate signature change; the forward_loop is invoked synchronously so closure capture is safe.

Usage

Automatic for the top-level max path — no user action needed. For the nvfp4_experts_only_input_scale1-kv_fp8_cast recipe (all expert inputs + KV pinned to constant amax), calibration now performs weight-only calibration and skips the model forward:

max_calibrate: all enabled activation quantizers use constant/dynamic amax; skipping the calibration forward pass (weight-only calibration).

To force the old behavior: set skip_forward_without_activation_calib: false on the max algorithm config.

Testing

New CPU unit tests in tests/unit/torch/quantization/test_calib.py:

  • test_max_calib_skips_forward_when_all_activations_constant — forward not called; weight quantizer still gets _amax; constant input amax preserved.
  • test_max_calib_runs_forward_when_activation_needs_data — a normal input quantizer still triggers the forward.
  • test_max_calib_default_does_not_skip_forward — direct callers (default flag) keep running the forward.
  • test_needs_activation_forward_ignores_disabled_and_weight_quantizers.
  • test_max_calib_config_enables_skip_by_default.

pytest tests/unit/torch/quantization/test_calib.py → 17 passed; test_quantize_cpu.py + test_tensor_quantizer_cpu.py → 79 passed. pre-commit on all changed files passes (ruff, mypy, bandit, …).

Before your PR is "Ready for review"

  • Is this change backward compatible?: ✅ (opt-in flag; default preserves behavior for direct callers; normal max PTQ unchanged)
  • 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?: ❌ (pending)

Additional Information

Follow-up to #1947 (now merged) per reviewer request. Closes the optimization discussion at #1947 (comment).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Max calibration can now skip the forward pass when no enabled activation quantizer needs runtime statistics.
    • Weight calibration continues to run even when the forward pass is skipped.
    • The behavior is configurable, with skipping enabled by default through the calibration settings.
    • Calibration methods that require activation data continue to run the forward pass as before.
  • Documentation

    • Updated the changelog with details about the new calibration behavior and configuration option.

@Edwardf0t1 Edwardf0t1 requested review from a team as code owners July 10, 2026 22:28
@Edwardf0t1 Edwardf0t1 requested review from chadvoegele and removed request for a team July 10, 2026 22:28
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Max calibration gains configurable detection of activation-statistics requirements. When none are needed, the activation forward_loop can be skipped while weight calibration continues. Tests cover configuration defaults, quantizer types, and forward-loop execution.

Changes

Max calibration forward control

Layer / File(s) Summary
Calibration configuration and API
modelopt/torch/quantization/config.py, modelopt/torch/quantization/model_calib.py
Adds skip_forward_without_activation_calib, defaulting to True in MaxCalibConfig, and exposes the corresponding max_calibrate argument.
Activation requirement detection
modelopt/torch/quantization/model_calib.py
Inspects enabled activation quantizers and excludes disabled, dynamic, constant-amax, and weight quantizers when determining whether forward calibration is required.
Conditional execution and validation
modelopt/torch/quantization/model_calib.py, modelopt/torch/quantization/utils/core_utils.py, tests/unit/torch/quantization/test_calib.py, CHANGELOG.rst
Skips or runs forward_loop based on the detected requirement, preserves weight calibration, updates the MoE calibration lambda, and adds unit tests and release notes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant max_calibrate
  participant TensorQuantizer
  participant forward_loop
  participant WeightCalibration

  max_calibrate->>TensorQuantizer: Inspect enabled activation quantizers
  TensorQuantizer-->>max_calibrate: Report whether activation data is needed
  alt Activation data is needed
    max_calibrate->>forward_loop: Run calibration forward pass
  else No activation data is needed
    max_calibrate->>WeightCalibration: Skip forward pass
  end
  max_calibrate->>WeightCalibration: Calibrate weight tensors
Loading
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 Changed modelopt code only adds a config flag, helper, and tests; no weights_only=False, allow_pickle=True, trust_remote_code=True, eval/exec, or # nosec found.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: skipping max-calib forward when no activation quantizer needs data.
✨ 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 feat/skip-forward-loop-constant-amax

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

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.75000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 69.40%. Comparing base (d641b4a) to head (977b74c).

Files with missing lines Patch % Lines
modelopt/torch/quantization/utils/core_utils.py 0.00% 1 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (d641b4a) and HEAD (977b74c). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (d641b4a) HEAD (977b74c)
gpu 4 3
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1963      +/-   ##
==========================================
- Coverage   76.39%   69.40%   -6.99%     
==========================================
  Files         519      519              
  Lines       58192    58206      +14     
==========================================
- Hits        44453    40397    -4056     
- Misses      13739    17809    +4070     
Flag Coverage Δ
examples 43.32% <68.75%> (+2.21%) ⬆️
gpu 32.05% <87.50%> (-26.54%) ⬇️
regression 14.99% <12.50%> (+0.13%) ⬆️
unit 55.40% <93.75%> (+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.

Base automatically changed from feat/nvfp4-constant-amax-input-scale to main July 10, 2026 23:14
@Edwardf0t1 Edwardf0t1 requested a review from a team as a code owner July 10, 2026 23:14
When all enabled activation quantizers use a constant amax (constant_amax
/ use_constant_amax) or dynamic quantization, max calibration's forward
pass collects no data-driven statistics and is pure overhead. Add
_needs_activation_forward_for_max_calib() and gate the forward_loop in
max_calibrate() behind a new opt-in flag
skip_forward_without_activation_calib.

Weight quantizers are still calibrated on the weight tensors directly via
weight_only_quantize(), so results are unchanged; only the wasted forward
is avoided (e.g. the nvfp4_experts_only_input_scale1 recipe).

The flag defaults to False in max_calibrate() so the advanced algorithms
that call it directly and always need activations (MSE, local Hessian,
SmoothQuant, SVDQuant, GPTQ) are unaffected; it is enabled via
MaxCalibConfig.skip_forward_without_activation_calib (default True) so
only the top-level max path opts in.

Also simplify a default-arg lambda in core_utils.sync_moe_expert_amax so
mypy can infer its type (surfaced by the max_calibrate signature change);
the forward_loop is invoked synchronously so closure capture is safe.

Stacked on #1947 (depends on the constant_amax attribute).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Zhiyu Cheng <zhiyuc@nvidia.com>
@Edwardf0t1 Edwardf0t1 force-pushed the feat/skip-forward-loop-constant-amax branch from be9b904 to 977b74c Compare July 11, 2026 00:33
@github-actions

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-1963/

Built to branch gh-pages at 2026-07-11 00:36 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

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.

1 participant