Skip to content

Wan 2.2 T2V A14B diffusers PTQ: two-expert NVFP4-SVDQuant HF checkpoint export - #1957

Open
jingyu-ml wants to merge 5 commits into
mainfrom
jingyux/wan22-svdquant-export
Open

Wan 2.2 T2V A14B diffusers PTQ: two-expert NVFP4-SVDQuant HF checkpoint export#1957
jingyu-ml wants to merge 5 commits into
mainfrom
jingyux/wan22-svdquant-export

Conversation

@jingyu-ml

@jingyu-ml jingyu-ml commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: New example feature

Add Wan 2.2 T2V A14B NVFP4-SVDQuant HuggingFace checkpoint export to the diffusers quantization example, mirroring the Qwen-Image SVDQuant mechanics (#1706):

  • --model wan2.2-t2v-14b now quantizes both experts by default — transformer (high-noise) first, then transformer_2 (low-noise), so the low-noise expert calibrates against the already-quantized high-noise expert, matching deployment.
  • The default Wan recipe (filter_func_wan_video: exclude the first 3 / last 3 of the 40 blocks, plus everything outside blocks) is now applied before calibration via the block_range mechanism. This is required for SVDQuant: the algorithm folds AWQ smoothing scales and subtracts the low-rank residual from every enabled linear during calibration, so post-hoc disabling (the previous filter-func-only path) would leave "excluded" weights silently mutated. With this PR they stay bit-identical to the source.
  • VAE backbones (--backbone vae) skip the transformer block-range recipe and keep their dedicated Conv3D recipe.
  • No svdquant_skip_layers for Wan: every quantized linear (self-attn, cross-attn incl. to_k/to_v, FFN) carries the full SVDQuant recipe (pre_quant_scale + rank-r svdquant_lora_a/b).
  • ONNX export paths untouched.

Usage

python examples/diffusers/quantization/quantize.py \
    --model wan2.2-t2v-14b \
    --model-dtype BFloat16 --trt-high-precision-dtype BFloat16 \
    --format fp4 --quant-algo svdquant --lowrank 32 \
    --batch-size 1 --calib-size 16 --n-steps 20 \
    --hf-ckpt-dir ./hf_ckpt

Testing

  • New wan22_14b_nvfp4_svdquant case in tests/examples/diffusers/test_export_diffusers_hf_ckpt.py asserting per expert (transformer and transformer_2): NVFP4_SVD quant config + lora_rank; only blocks {3, 4} of the 8-block tiny fixture quantized and nothing outside blocks; svdquant_lora_a/b + pre_quant_scale present on all 10 block linears with rank-consistent shapes; NVFP4 weight_scale_2; no live-quantizer keys leaked.
  • Tiny Wan fixture updated: num_layers 2→8 (the block-range recipe needs ≥ 8 blocks), heads 2→4 (hidden 48, divisible by the NVFP4 block size 16; head_dim stays 12 so the RoPE axis split stays even).
  • All 11 affected example tests pass on GB200 (sm100), 653 s total: 3 Wan HF-export cases (int8 / fp8 / new svdquant), 7 Wan quantize+restore cases incl. --backbone vae fp8/fp4 (verifying the VAE gate), and the qwen_nvfp4_svdquant regression.
  • Real-model smoke: Wan2.2-T2V-A14B rank-32/64/128 SVDQuant exports with a structural verify gate (both experts NVFP4_SVD; blocks 3–36 quantized; excluded blocks and non-block modules bit-identical to the source BF16; full pipeline dirs). Rank-32 in progress; results will be posted on this PR.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ❌ — --model wan2.2-t2v-14b previously quantized only transformer by default; it now quantizes both experts (a single-expert checkpoint of the two-expert pipeline is half-quantized). Pass --backbone transformer to restore the old behavior. The tiny Wan test fixture also grew from 2 to 8 blocks / hidden 24 to 48.
  • 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?: ❌ (draft)

Additional Information

Draft until the real-model rank-32/64/128 smoke exports and verify gates complete.

🤖 Generated with Claude Code

Update: AWQ-Lite search removed from the SVDQuant path

SVDQuant calibration is now SmoothQuant-style fixed migration only (SVDQuantConfig.alpha, default 1.0 = migrate outliers fully to the weights; the diffusers example reuses --alpha). The AWQ-Lite search is gone from svdquant: two forward-loop passes instead of three, no per-alpha quantized GEMM sweep. (The transient --svdquant-alpha opt-in from the second commit was superseded in the fourth commit.)

Why: the search optimized pre-SVD, weight-only output MSE with activations unquantized — a mismatched objective twice over for W4A4 SVDQuant, where the SVD low-rank branch absorbs the migrated outliers and activation flatness is the point of the migration (SVDQuant paper design).

Validation on Wan2.2-T2V-A14B (rank 32, calib 1 x 20 steps @ 720x1280x81f, GB200):

AWQ-Lite search SmoothQuant alpha=1.0
End-to-end quantize+export 1:32:48 1:12:07
Structural verify gate PASSED PASSED

Gate checks per expert: NVFP4_SVD + lora_rank=32; only blocks 3-36 quantized; 340/340 linears with svdquant_lora_a/b + pre_quant_scale; excluded region bit-identical in export precision (the Wan-AI repo ships FP32 shards, so excluded tensors are pure BF16 downcasts - 0 value changes). Unit: 16/16 calib/mode tests incl. new test_svdquant_smoothquant_calibration (asserts 2 forward loops + the SmoothQuant scale formula). Example: Wan + Qwen SVDQuant export tests pass on the new path.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added fixed-strength SmoothQuant-style calibration for SVDQuant with configurable migration strength.
    • Added NVFP4-SVDQuant export support for Wan 2.2 T2V models.
    • Added MiniMax-H3 quantization and calibration support.
    • Added modular Diffusers pipeline detection and export support.
    • Expanded quantization and deployment capabilities across supported workflows.
  • Bug Fixes

    • Improved quantization, block selection, and multi-backbone recipe handling.
    • Refined Diffusers and Hugging Face checkpoint export behavior.
  • Documentation

    • Added Wan 2.2 quantization guidance, configuration details, and command-line examples.
    • Documented AutoQuantize recipe migration and recent feature updates.

…nt export

- Quantize both experts by default for --model wan2.2-t2v-14b
  (transformer first, so transformer_2 calibrates against the quantized
  high-noise expert), producing a deployable two-expert checkpoint.
- Apply the default Wan recipe (first-3/last-3 of 40 blocks, nothing
  outside blocks) BEFORE calibration via the block-range mechanism so
  SVDQuant leaves excluded weights bit-identical; VAE backbones keep
  their dedicated recipe.
- Full SVDQuant on every quantized linear (self/cross-attn, FFN); no
  svdquant_skip_layers for Wan.
- Tiny Wan fixture: 8 blocks (block-range needs >= 8) and hidden 48
  (NVFP4 block-size divisible; head_dim stays 12 for even RoPE splits).
- New wan22_14b_nvfp4_svdquant export test mirroring the Qwen SVDQuant
  structural assertions, per expert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 9, 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.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change replaces AWQ-Lite search with fixed-alpha SVDQuant smoothing, adds Wan 2.2 and MiniMax-H3 Diffusers quantization workflows, and extends unified HF export for modular pipelines.

Changes

SVDQuant calibration and Wan 2.2 export

Layer / File(s) Summary
Fixed-alpha SVDQuant calibration
modelopt/torch/quantization/..., examples/diffusers/quantization/config.py, tests/unit/torch/quantization/test_calib.py
SVDQuant adds validated alpha configuration and fixed-alpha SmoothQuant-style calibration. Tests verify scaling, calibration passes, and LoRA initialization.
Wan 2.2 quantization integration
examples/diffusers/quantization/..., examples/diffusers/README.md, CHANGELOG.rst
The workflow supports named backbones and adds the two-expert Wan 2.2 NVFP4-SVDQuant recipe.
Wan 2.2 export validation
tests/_test_utils/torch/diffusers_models.py, tests/examples/diffusers/test_export_diffusers_hf_ckpt.py
Tests validate both experts, selected blocks, SVDQuant tensors, LoRA ranks, exclusions, and NVFP4 scales.

MiniMax-H3 quantization support

Layer / File(s) Summary
Model configuration and filtering
examples/diffusers/quantization/models_utils.py, examples/diffusers/quantization/utils.py, examples/diffusers/quantization/quantize.py
MiniMax-H3 receives model defaults, block filtering, SVDQuant alpha handling, and configuration validation.
Modular pipeline and calibration
examples/diffusers/quantization/pipeline_manager.py, examples/diffusers/quantization/calibration.py
The workflow loads the modular T2VA pipeline, supports local paths and offloading, and runs deterministic single-prompt calibration.

Modular Diffusers export

Layer / File(s) Summary
Pipeline detection and export
modelopt/torch/export/diffusers_utils.py, modelopt/torch/export/unified_export_hf.py
Export detection supports modular pipelines and writes modular indexes with local component locations and partial-export filtering.
Export validation
tests/unit/torch/export/test_export_diffusers.py
Tests cover modular component export, index replacement, loading keys, location rewriting, and inactive-component omission.

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

Sequence Diagram(s)

sequenceDiagram
  participant QuantizeCLI
  participant PipelineManager
  participant MiniMaxPipeline
  participant Calibrator
  QuantizeCLI->>PipelineManager: create MiniMax-H3 modular pipeline
  PipelineManager->>MiniMaxPipeline: load T2VA components and configure offloading
  QuantizeCLI->>Calibrator: dispatch calibration batch
  Calibrator->>MiniMaxPipeline: run deterministic prompt-only T2V calibration
Loading

Possibly related PRs

Suggested reviewers: ajrasane, edwardf0t1, meenchen

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: two-expert Wan 2.2 T2V A14B NVFP4-SVDQuant Hugging Face checkpoint export.
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 PR diff adds no unsafe torch.load, pickle loading, trust_remote_code=True, built-in eval/exec, or # nosec patterns, and changes no dependency manifests.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch jingyux/wan22-svdquant-export
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jingyux/wan22-svdquant-export

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

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.39535% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.76%. Comparing base (e911c3b) to head (1e44cbf).
⚠️ Report is 96 commits behind head on main.

Files with missing lines Patch % Lines
modelopt/torch/quantization/model_calib.py 80.95% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1957      +/-   ##
==========================================
- Coverage   77.76%   77.76%   -0.01%     
==========================================
  Files         519      519              
  Lines       57955    58081     +126     
==========================================
+ Hits        45067    45164      +97     
- Misses      12888    12917      +29     
Flag Coverage Δ
unit 55.24% <81.39%> (+0.04%) ⬆️

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.

jingyu-ml and others added 2 commits July 9, 2026 15:22
… search)

- SVDQuantConfig.alpha (None = keep the AWQ-Lite search): when set, one
  SmoothQuant-style per-channel act-amax stats pass replaces AWQ-Lite's
  cache + 11-candidate search passes. The search optimizes pre-SVD,
  weight-only output MSE with activations unquantized -- a mismatched
  objective once the SVD low-rank branch absorbs the migrated outliers.
  alpha=1.0 migrates outliers fully to the weights (flat activations),
  per the SVDQuant paper.
- Extract smoothquant's per-module smoothing into _smoothquant_postprocess
  and reuse it for the fixed-alpha pass (any quantizer format, not only
  int8; smoothquant() behavior unchanged).
- diffusers example: --svdquant-alpha flag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
@github-actions

github-actions Bot commented Jul 9, 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-1957/

Built to branch gh-pages at 2026-07-09 23:34 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

…gration

SVDQuant calibration is now a single per-channel act-amax stats pass at a
fixed migration strength (SVDQuantConfig.alpha, default 1.0 = migrate
outliers fully to the weights, which the SVD low-rank branch absorbs, per
the SVDQuant paper). The AWQ-Lite alpha search is removed from this path:
its objective -- pre-SVD, weight-only output MSE with activations
unquantized -- does not match the shipped decomposition, and it cost a
full extra forward-loop pass with one quantized GEMM per alpha candidate
per linear. SVDQuant now runs two forward-loop passes instead of three.
Checkpoint format is unchanged; scale values change (paper-aligned).

The diffusers example reuses --alpha for the strength (the transient
--svdquant-alpha flag from the previous commit is removed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
@jingyu-ml

Copy link
Copy Markdown
Contributor Author

Offline A/B, rank 32, Wan2.2-T2V-A14B (searched AWQ vs SmoothQuant α=1.0):

metric (per expert avg) AWQ-Lite search SmoothQuant α=1.0
weight recon rel-err mean 0.097 0.110
weight recon rel-err p95 0.104 0.145
weight recon rel-err max 0.165 0.177
pre_quant_scale max/min spread (median) 9.4–11.8 34.7–41.5
end-to-end quantize+export 1:32:48 1:12:07

Reading: weight-reconstruction error is structurally biased toward the search (it is the search's objective and ignores activation quantization entirely — the search runs with input quantizers disabled). The 3–4× larger pre_quant_scale spread shows α=1.0 actually flattening activations, which is the point of the migration for W4A4. Worst weight-side regressions concentrate in ffn.net.0.proj and early-block attn1 q/v (largest activation outliers, beyond what rank 32 absorbs). Decisive comparison needs a forward-pass/E2E quality eval; per-layer JSON kept alongside the ckpts.

🤖 Generated with Claude Code

@jingyu-ml

jingyu-ml commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Rank ladder complete — all three Wan2.2-T2V-A14B SVDQuant exports on the final (SmoothQuant-only) path, each passing the full structural verify gate (both experts NVFP4_SVD, blocks 3–36 only, lora/pre_quant_scale on all 340 linears per expert, excluded region bit-identical in export precision, complete pipeline dirs):

ckpt lora_rank size quantize+export gate
r32 32 33 G 1:12:07 PASSED
r64 64 34 G 1:12:02 PASSED
r128 128 35 G 1:12:03 PASSED

(calib 1 × 20 steps @ 720×1280×81f smoke calibration — pipe-clean scope; quality-grade calibration is a knob rerun.)

🤖 Generated with Claude Code

@jingyu-ml
jingyu-ml marked this pull request as ready for review August 12, 2026 06:39
@jingyu-ml
jingyu-ml requested review from a team as code owners August 12, 2026 06:39
@jingyu-ml
jingyu-ml requested a review from ajrasane August 12, 2026 06:39

@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

🤖 Prompt for all review comments with AI agents
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/diffusers/README.md`:
- Around line 127-130: Update the `pre_quant_scale` description in the SVDQuant
documentation to identify it as SmoothQuant-style rather than an AWQ artifact,
while preserving the existing explanation of the quantization recipe.

In `@modelopt/torch/quantization/model_calib.py`:
- Around line 1150-1155: Update the scale calculation around scale_a and
act_amax to create a squeezed zero-activation mask before division, then apply
it unconditionally with torch.where so masked channels receive the neutral scale
of 1. Remove the tensor-value-based Python conditional and ensure zero-over-zero
cases cannot leave NaN values before the existing clamp.

In `@tests/examples/diffusers/test_export_diffusers_hf_ckpt.py`:
- Around line 459-468: Extend the export validation around the existing
weight_scale assertions to load excluded block tensors from tiny_wan22_path for
both experts and compare them with the exported tensors using torch.equal. Cover
every excluded first-3 and last-3 block weight, ensuring the test fails if
smoothing or SVDQuant mutates their full-precision values while preserving the
existing quantized-block checks.
- Line 180: Move import re from the test/function scope at
tests/examples/diffusers/test_export_diffusers_hf_ckpt.py:180-180 to the module
import section. Also move from safetensors import safe_open from
tests/examples/diffusers/test_export_diffusers_hf_ckpt.py:427-427 to module
scope, or explicitly document safetensors as optional and apply an explicit
dependency-based skip; do not leave either import inside a test or function.
🪄 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: 47c54b39-b7bc-4dca-b05b-19e750956610

📥 Commits

Reviewing files that changed from the base of the PR and between 089c06e and 1e44cbf.

📒 Files selected for processing (11)
  • CHANGELOG.rst
  • examples/diffusers/README.md
  • examples/diffusers/quantization/config.py
  • examples/diffusers/quantization/models_utils.py
  • examples/diffusers/quantization/quantize.py
  • examples/diffusers/quantization/quantize_config.py
  • modelopt/torch/quantization/config.py
  • modelopt/torch/quantization/model_calib.py
  • tests/_test_utils/torch/diffusers_models.py
  • tests/examples/diffusers/test_export_diffusers_hf_ckpt.py
  • tests/unit/torch/quantization/test_calib.py

Comment on lines +127 to +130
precision. The exclusion is applied **before calibration** so that for SVDQuant
the excluded blocks' weights stay bit-identical to the original. Every quantized
linear keeps the full SVDQuant recipe (AWQ `pre_quant_scale` + low-rank
`svdquant_lora_a/b`).

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the pre_quant_scale description.

Line 129 calls pre_quant_scale an AWQ artifact. This PR replaces the AWQ-Lite search with fixed SmoothQuant-style migration. Describe it as a SmoothQuant-style pre_quant_scale.

🤖 Prompt for AI Agents
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/diffusers/README.md` around lines 127 - 130, Update the
`pre_quant_scale` description in the SVDQuant documentation to identify it as
SmoothQuant-style rather than an AWQ artifact, while preserving the existing
explanation of the quantization recipe.

Comment on lines +1150 to +1155
# Some channel could have 0 amax which causes scale_a to overflow. Explicitly mask them out here
epsilon = 1.0 / (1 << 31)
if scale_a.min() <= epsilon:
zero_mask = act_amax <= epsilon
scale_a[zero_mask] = 1
scale_a = scale_a.clamp(min=1e-4, max=1e4)

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mask zero activation channels before scale calculation.

Line 1152 does not detect a zero act_amax when it produces inf. Line 1155 then clamps that channel to 1e4 instead of the intended neutral scale of 1. A 0 / 0 scale can also remain NaN.

Create a squeezed zero mask before the division. Apply it with torch.where unconditionally.

Proposed fix
-    scale_a = (weight_scale.pow(1 - alpha) / act_amax.pow(alpha)).squeeze()
+    zero_mask = act_amax.squeeze() <= epsilon
+    safe_act_amax = act_amax.clamp_min(epsilon)
+    scale_a = (weight_scale.pow(1 - alpha) / safe_act_amax.pow(alpha)).squeeze()
 ...
-    if scale_a.min() <= epsilon:
-        zero_mask = act_amax <= epsilon
-        scale_a[zero_mask] = 1
-    scale_a = scale_a.clamp(min=1e-4, max=1e4)
+    scale_a = torch.where(zero_mask, torch.ones_like(scale_a), scale_a)
+    scale_a = scale_a.clamp(min=1e-4, max=1e4)

As per coding guidelines, “Avoid tensor-value-based Python branching when it can break CUDA graphs.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/quantization/model_calib.py` around lines 1150 - 1155, Update
the scale calculation around scale_a and act_amax to create a squeezed
zero-activation mask before division, then apply it unconditionally with
torch.where so masked channels receive the neutral scale of 1. Remove the
tensor-value-based Python conditional and ensure zero-over-zero cases cannot
leave NaN values before the existing clamp.

Source: Coding guidelines

"""transformer_blocks indices referenced by a set of module prefixes."""
def _block_indices(prefixes: set[str], block_re: str = r"transformer_blocks\.(\d+)\.") -> set[int]:
"""Block indices referenced by a set of module prefixes."""
import re

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move test imports to module scope.

Both imports can fail after test collection. Move them to the module import section. If safetensors is intentionally optional, add a brief comment and use an explicit optional-dependency skip.

  • tests/examples/diffusers/test_export_diffusers_hf_ckpt.py#L180-L180: move import re to module scope.
  • tests/examples/diffusers/test_export_diffusers_hf_ckpt.py#L427-L427: move from safetensors import safe_open to module scope, or document the optional-dependency exception.

As per path instructions, “Imports inside functions or test methods without explicit justification” are IMPORTANT issues.

📍 Affects 1 file
  • tests/examples/diffusers/test_export_diffusers_hf_ckpt.py#L180-L180 (this comment)
  • tests/examples/diffusers/test_export_diffusers_hf_ckpt.py#L427-L427
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/examples/diffusers/test_export_diffusers_hf_ckpt.py` at line 180, Move
import re from the test/function scope at
tests/examples/diffusers/test_export_diffusers_hf_ckpt.py:180-180 to the module
import section. Also move from safetensors import safe_open from
tests/examples/diffusers/test_export_diffusers_hf_ckpt.py:427-427 to module
scope, or explicitly document safetensors as optional and apply an explicit
dependency-based skip; do not leave either import inside a test or function.

Source: Path instructions

Comment on lines +459 to +468
# Recipe: only the middle `blocks` are quantized — first-3/last-3 are
# excluded, and nothing outside `blocks`.
weight_scale_prefixes = _module_prefixes(keys, ".weight_scale")
assert weight_scale_prefixes, f"{expert}: no quantized linears found in export"
assert all(p.startswith("blocks.") for p in weight_scale_prefixes), (
f"{expert}: a non-blocks module was quantized: {weight_scale_prefixes}"
)
assert _block_indices(weight_scale_prefixes, _WAN22_BLOCK_RE) == _WAN22_QUANTIZED_BLOCKS, (
f"{expert}: expected only blocks {_WAN22_QUANTIZED_BLOCKS} quantized"
)

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Compare excluded weights with the source fixture.

These checks prove that excluded blocks have no weight_scale. They do not prove that SVDQuant left their full-precision weights bit-identical.

Load the excluded block tensors from tiny_wan22_path for both experts. Compare each exported tensor with torch.equal. This test must detect an accidental smoothing or SVD mutation of excluded blocks.

As per coding guidelines, “Tests must exercise the behavior they claim to validate.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/examples/diffusers/test_export_diffusers_hf_ckpt.py` around lines 459 -
468, Extend the export validation around the existing weight_scale assertions to
load excluded block tensors from tiny_wan22_path for both experts and compare
them with the exported tensors using torch.equal. Cover every excluded first-3
and last-3 block weight, ensuring the test fails if smoothing or SVDQuant
mutates their full-precision values while preserving the existing
quantized-block checks.

Source: Coding guidelines

@jingyu-ml
jingyu-ml requested a review from a team as a code owner August 12, 2026 18:53
@jingyu-ml
jingyu-ml requested a review from Edwardf0t1 August 12, 2026 18:53

@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 (2)
examples/diffusers/quantization/pipeline_manager.py (1)

212-218: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the justified local import.

The local import handles an optional Diffusers API. Add a brief comment before the import that states why it must remain local. The ImportError message does not satisfy the required source-level justification.

Proposed change
     def _create_minimax_h3_pipeline(self) -> Any:
         """Load only MiniMax-H3's prompt-only T2V workflow."""
         try:
+            # ModularPipeline is optional in released Diffusers versions.
             from diffusers import ComponentsManager, ModularPipeline

As per coding guidelines: “Keep imports at the top of Python source and test files; use local imports only for justified circular dependencies, optional dependencies, or unusually heavy imports, with a brief explanatory comment.”

🤖 Prompt for AI Agents
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/diffusers/quantization/pipeline_manager.py` around lines 212 - 218,
Before the local import of ComponentsManager and ModularPipeline, add a brief
comment explaining that it must remain local because these Diffusers APIs are
optional and may be unavailable in standard releases. Keep the existing
ImportError handling unchanged.

Source: Coding guidelines

examples/diffusers/quantization/utils.py (1)

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

Define the module public API.

examples/diffusers/quantization/utils.py has no __all__ declaration. Add one that includes filter_func_minimax_h3 and the other intended public utilities.

🤖 Prompt for AI Agents
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/diffusers/quantization/utils.py` around lines 125 - 130, Add a
module-level __all__ declaration in utils.py listing filter_func_minimax_h3 and
every other intentionally public utility defined by the module, while excluding
private helpers and implementation details. Keep the existing utility
implementations unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
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 `@modelopt/torch/export/unified_export_hf.py`:
- Around line 1407-1410: Update the pipeline index handling near the
is_diffusers_pipe export logic in modelopt/torch/export/unified_export_hf.py to
delete the alternate index before writing the selected one: remove
model_index.json for modular exports and modular_model_index.json for standard
exports. In tests/unit/torch/export/test_export_diffusers.py lines 175-199, seed
a stale model_index.json and assert that a modular export removes it.

---

Nitpick comments:
In `@examples/diffusers/quantization/pipeline_manager.py`:
- Around line 212-218: Before the local import of ComponentsManager and
ModularPipeline, add a brief comment explaining that it must remain local
because these Diffusers APIs are optional and may be unavailable in standard
releases. Keep the existing ImportError handling unchanged.

In `@examples/diffusers/quantization/utils.py`:
- Around line 125-130: Add a module-level __all__ declaration in utils.py
listing filter_func_minimax_h3 and every other intentionally public utility
defined by the module, while excluding private helpers and implementation
details. Keep the existing utility implementations unchanged.
🪄 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: c7e5216e-dae4-4eb5-bde8-9b23d79528fd

📥 Commits

Reviewing files that changed from the base of the PR and between 1e44cbf and 6c04b1e.

📒 Files selected for processing (8)
  • examples/diffusers/quantization/calibration.py
  • examples/diffusers/quantization/models_utils.py
  • examples/diffusers/quantization/pipeline_manager.py
  • examples/diffusers/quantization/quantize.py
  • examples/diffusers/quantization/utils.py
  • modelopt/torch/export/diffusers_utils.py
  • modelopt/torch/export/unified_export_hf.py
  • tests/unit/torch/export/test_export_diffusers.py

Comment on lines +1407 to +1410
# For pipelines, also save the pipeline index.
if is_diffusers_pipe:
model_index_path = export_dir / "model_index.json"
model_index_filename = "modular_model_index.json" if is_modular_pipe else "model_index.json"
model_index_path = export_dir / model_index_filename

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove the alternate pipeline index during retry.

A modular export can reuse a directory that contains model_index.json from a previous standard export. The code writes modular_model_index.json but leaves the standard index in place. The output then contains incompatible pipeline indexes.

  • modelopt/torch/export/unified_export_hf.py#L1407-L1410: Remove the alternate index before writing the selected index. Remove model_index.json for modular exports and modular_model_index.json for standard exports.
  • tests/unit/torch/export/test_export_diffusers.py#L175-L199: Seed a stale model_index.json and assert that modular export removes it.
📍 Affects 2 files
  • modelopt/torch/export/unified_export_hf.py#L1407-L1410 (this comment)
  • tests/unit/torch/export/test_export_diffusers.py#L175-L199
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/torch/export/unified_export_hf.py` around lines 1407 - 1410, Update
the pipeline index handling near the is_diffusers_pipe export logic in
modelopt/torch/export/unified_export_hf.py to delete the alternate index before
writing the selected one: remove model_index.json for modular exports and
modular_model_index.json for standard exports. In
tests/unit/torch/export/test_export_diffusers.py lines 175-199, seed a stale
model_index.json and assert that a modular export removes it.

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