Skip to content

Add Qwen-Image DMD2 QAT and PEFT-backed SVDQuant - #2068

Closed
jingyu-ml wants to merge 26 commits into
NVIDIA:mainfrom
jingyu-ml:feature/qwen-image-dmd2-qat
Closed

Add Qwen-Image DMD2 QAT and PEFT-backed SVDQuant#2068
jingyu-ml wants to merge 26 commits into
NVIDIA:mainfrom
jingyu-ml:feature/qwen-image-dmd2-qat

Conversation

@jingyu-ml

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: new feature

This draft currently brings the Qwen-Image DMD2/QAT branch up to date with main. It will also make SVDQuant's low-rank residual a structural Hugging Face PEFT adapter owned and restored by the generic ModelOpt lifecycle.

The SVDQuant portion will ensure that only layers actually processed by SVDQuant receive PEFT A/B parameters, while ModelOpt continues to own the residualized quantized base weights and quantizer state. Full mto.save/mto.restore and modelopt_state plus state_dict workflows will reconstruct the PEFT topology and preserve its trainability status.

Usage

model = mtq.quantize(model, quant_config, forward_loop)
mtq.set_svdquant_peft_adapter_trainable(model, True)
mto.save(model, "svdquant.pt")
restored = mto.restore(clean_model, "svdquant.pt")

Testing

Focused CPU SVDQuant/PEFT lifecycle tests and relevant code-quality hooks will be listed here before the draft is marked ready.

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?: In progress
  • Did you update CHANGELOG.rst?: In progress
  • Did you get Claude approval on this PR?: N/A while draft

Additional Information

This is intentionally a draft while the generic SVDQuant/HF-PEFT implementation and focused tests are added as a separate signed commit.

jingyu-ml and others added 26 commits June 11, 2026 16:50
Register Qwen/Qwen-Image as a supported model in the diffusers
quantization example:
- ModelType.QWEN_IMAGE and lazy-imported QwenImagePipeline (so the
  example still imports on older diffusers).
- MODEL_REGISTRY / MODEL_PIPELINE / MODEL_DEFAULTS entries
  (backbone="transformer", text-to-image calibration dataset).
- An actionable ImportError when the installed diffusers lacks Qwen
  classes, instead of an opaque failure.
- filter_func_qwen_image: quantize only transformer_blocks, keeping the
  first two and last two of the 60 blocks (and everything outside
  transformer_blocks) in original precision.

Enables the plain FP8/NVFP4 export path for Qwen-Image. Core SVDQuant
code is unchanged. (Qwen-Image SVDQuant checkpoint work, RLCR round 0 / M1.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…harness

Implements the Qwen-Image NVFP4/FP8/SVDQuant diffusers quantization feature
(RLCR round 0 / M2-M5), keeping core SVDQuant code unchanged:

M2 (recipe): build_block_range_quant_cfg() emits ordered quant_cfg rules
(disable-all -> enable *.transformer_blocks.* -> disable first/last-N), applied
pre-calibration in Quantizer.get_quant_config so SVDQuant never mutates the
excluded blocks. Driven by a MODEL_DEFAULTS["block_range"] entry for Qwen-Image
(exclude first 2 / last 2; n derived from the model; n>=first+last+1 enforced).

M3 (export): _export_diffusers_checkpoint now promotes quantizer-owned tensors
to clean module-level safetensors keys before hide_quantizers_from_state_dict
(diffusers path only; the transformers path keeps its postprocess_state_dict
rename): input_quantizer._pre_quant_scale -> <module>.pre_quant_scale (AWQ key),
weight_quantizer.svdquant_lora_a/b -> <module>.svdquant_lora_a/b. Adds an
NVFP4_SVD branch to convert_hf_config (modeled on nvfp4_awq: pre_quant_scale +
lora_rank), and process_layer_quant_config now flags SVDQuant with
pre_quant_scale=True. This also resolves the diffusers pre_quant_scale TODO for
AWQ-style exports.

M4 (tests): unit tests for the block-range recipe (first/last-2 exclusion,
n>=6 validation) and the NVFP4_SVD HF config conversion.

M5 (harness): quantize.py --sanity-image-path (in-memory quantized-inference
image, pre-export) + examples/diffusers/quantization/qwen_image_svdquant/
{run_qwen_image_quantization.sh, README.md} (parameterized container/model/
export flow for FP8/NVFP4/SVDQuant).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
… tests

Addresses the round-0 Codex review (RLCR round 1):

Blocking fixes:
- convert_hf_config: NVFP4_SVD config groups now keep `has_zero_point: False`
  (both convert_hf_quant_config_format and _quant_algo_to_group_config); asserted
  in the unit test.
- build_block_range_quant_cfg: minimum is now first+last+2 (>=2 quantized middle
  blocks; n>=6 for the 2+2 Qwen recipe); recipe test rejects 5/4/3-block models.
- quantize.py --sanity-image-path failures are now fatal (re-raise -> non-zero
  exit) so the harness cannot report success without the image; the harness also
  verifies sanity.png + safetensors + config.json exist per format.

Qwen export enablement:
- diffusers_utils.generate_diffusion_dummy_inputs: add a QwenImageTransformer2DModel
  branch (packed latents [B,(H//2)(W//2),C], encoder_hidden_states_mask, img_shapes,
  txt_seq_lens, optional guidance, continuous timestep).
- unified_export_hf._fuse_qkv_linears_diffusion gains strict=; Qwen QKV fusion now
  fails hard instead of silently skipping. Promotion buffers now overwrite on
  re-export. create_pipeline_from gives the same actionable Qwen import error.

Tests:
- New tests/unit/torch/quantization/test_svdquant_forward_fold.py: LoRA stays on
  weight_quantizer, forward includes a nonzero residual, fold_weight folds it and
  drops the buffers (existing test_svdquant_lora_weights left unmodified).

Deferred to Round 2 / cluster: tiny Qwen2_5_VL fixture + full diffusers e2e export
test (needs a Qwen-capable diffusers + GPU); the actual AC-7 checkpoint run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…minology

Round 2 (addresses round-1 Codex review: the round-1 code had no direct test
coverage). Adds tests/unit/torch/export/test_diffusers_qwen_export.py:
- Qwen dummy inputs: generate_diffusion_dummy_inputs builds the expected keys for
  a real tiny QwenImageTransformer2DModel, and the generated dummy forward runs
  on it (this is what catches any wrong shape/kwarg in the dummy-input builder).
- Strict fusion: _fuse_qkv_linears_diffusion(strict=True) re-raises on a failing
  dummy forward; strict=False does not.
- Structural export: _promote_quantizer_tensors_to_module promotes SVDQuant LoRA
  + pre_quant_scale to clean module keys that survive hide_quantizers_from_state_dict
  (promoted <module>.svdquant_lora_a/b + <module>.pre_quant_scale present;
  weight_quantizer / input_quantizer keys absent), on a calibrated tiny SVDQuant MLP.

Also removes plan/workflow terminology (DEC-5, "pre-calibration") from source and
test comments per the plan code-style note.

Still pending (Round 3 / cluster): the full tiny Qwen pipeline fixture + e2e
subprocess export test (needs diffusers' tokenizer/text-encoder construction and
a GPU) and the AC-7 cluster run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
Round 3 (addresses round-2 Codex review):

- Fix the tiny Qwen-Image pipeline fixture (tests/_test_utils/torch/diffusers_models.py):
  build the Qwen2.5-VL text encoder inline from a tiny Qwen2_5_VLConfig (no Hub
  model load; the previous hf-internal-testing/...Qwen2_5_VL id does not exist),
  load the tokenizer from the tiny ...Qwen2VL id diffusers' own fast test uses,
  build the transformer with num_layers=6 (so the corrected first-2/last-2
  block-range recipe, which needs >=6 blocks, is valid) and joint_attention_dim=16
  matching the text encoder hidden_size, and a z_dim=4 VAE. Mirrors diffusers'
  QwenImagePipelineFastTests.get_dummy_components.

- Add Qwen FP8 / NVFP4 / NVFP4-SVDQuant cases to test_export_diffusers_hf_ckpt.py
  using the tiny fixture. The test opens transformer/config.json and the exported
  safetensors and asserts: quant_method=modelopt; no weight_quantizer /
  input_quantizer._amax keys; for SVDQuant, promoted <module>.svdquant_lora_a/b +
  <module>.pre_quant_scale keys, config group pre_quant_scale/has_zero_point/
  lora_rank, and non-empty ignore (excluded blocks); for plain formats, weight_scale.
  GPU/diffusers skip-guarded.

- Drop remaining workflow terminology (Step 4.5, before-calibration) from the
  comments I introduced.

Still cluster-only (no GPU here): executing these tests and the AC-7 harness run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…ep comments

Round 4 (addresses round-3 Codex review):

- Offline tiny Qwen tokenizer: _build_local_qwen2_tokenizer builds a deterministic
  byte-level Qwen2 tokenizer locally (GPT-2 byte->unicode vocab + Qwen specials,
  empty merges) instead of a Hub load; removes the tokenizer-unavailable skip path.

- Strengthen test_qwen_image_hf_ckpt_export: assert equal module-prefix sets for
  .svdquant_lora_a/.svdquant_lora_b/.pre_quant_scale; promoted linears are a subset
  of weight-scaled linears; only the middle blocks {2,3} of 6 are quantized (first-2/
  last-2 excluded); lora_a=[rank,in]/lora_b=[out,rank] with rank == --lowrank (8);
  NVFP4 weight_scale_2 present; exact config (quant_algo=NVFP4_SVD, lora_rank=8,
  pre_quant_scale=True, has_zero_point=False, non-empty ignore).

- Remove the remaining "Step N:" workflow comments from unified_export_hf.py
  (the round-3 "grep clean" claim was wrong; verified clean across the whole file).

Still cluster-only (no GPU/torch/diffusers here): executing these tests and the
AC-7 harness run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…port test

Round 5 (addresses round-4 Codex review, which found a regression I introduced):

- The round-4 edit inserted the _module_prefixes/_block_indices helpers between
  @pytest.mark.parametrize("qwen_model", ...) and test_qwen_image_hf_ckpt_export,
  so the decorator was attached to the helper and the test would request an
  undefined qwen_model fixture. Moved the helpers/constants above the decorator so
  it directly decorates the test (verified via ast: the test now carries the
  qwen_model parametrization and the helper is undecorated).

- Tightened SVDQuant assertions: require a_prefixes == b_prefixes == pqs_prefixes
  == weight_scale_prefixes (every quantized linear is promoted, no gaps), and
  assert every quantized prefix is under transformer_blocks (nothing outside is
  quantized), in addition to the {2,3}-only block check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
Round 6 (round-5 review found no code blocker; only the queued docstring nit):
the create_tiny_qwen_image_pipeline_dir docstring still said the tokenizer was
fetched from the Hub, but Round 4 switched it to a local offline build
(_build_local_qwen2_tokenizer). Updated the wording to "fully offline".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…tale docs

Round 7 (addresses round-6 Codex review's two missing-coverage items):

- AC-2.2 SVDQuant immutability test (test_qwen_block_range_recipe.py): builds a
  6-block backbone, snapshots the excluded first/last block linear weights, runs
  SVDQuant via build_block_range_quant_cfg, and asserts the excluded blocks'
  weights are bit-identical (never calibrated) with no LoRA, while the middle
  blocks {2,3} receive LoRA and have their weights modified.
- AC-1 negative-loading tests (new test_qwen_pipeline_loading.py): monkeypatch
  MODEL_PIPELINE[QWEN_IMAGE]=None and assert the actionable ImportError; a fake
  pipeline asserts create_pipeline does not pass trust_remote_code.

Stale-doc cleanups: the resolved pre_quant_scale TODO wording in
unified_export_hf.py; the build_block_range_quant_cfg docstring (first+last+1 ->
+2); the conftest "SKETCH" wording (the fixture is now a working offline build).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…e-gate

Round 8 (addresses round-7 Codex review, which verified against the diffusers
source that QwenImageTransformer2DModel.forward has no txt_seq_lens parameter):

- _qwen_inputs no longer passes txt_seq_lens (the real forward signature is
  hidden_states, encoder_hidden_states, encoder_hidden_states_mask, timestep,
  img_shapes, guidance, return_dict). Passing txt_seq_lens would have raised an
  unexpected-keyword error and, because Qwen export uses strict QKV fusion,
  hard-failed the export.
- Signature-gate the dummy inputs: filter to the kwargs the installed model's
  forward actually accepts (via inspect.signature), so diffusers-version drift
  cannot hard-fail strict fusion either.
- Update test_diffusers_qwen_export.py: no longer require txt_seq_lens.
- Remove AC- plan terminology from two test docstrings (code-style note).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…after export

Round 9 (clears the last queued code item from Codex; no code blockers remain):
_promote_quantizer_tensors_to_module left the temporary <module>.svdquant_lora_a/b
+ <module>.pre_quant_scale buffers on the live module after export. Add
_remove_promoted_quantizer_tensors and call it after each quantized diffusers
component is saved, so the live module is unchanged post-export (repeated export /
module reuse stay correct). The quantizer-owned tensors are untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…dquant)

Validated end-to-end on GB200 against the real Qwen/Qwen-Image: all three
formats export correct HF checkpoints (only transformer_blocks 2..57; nothing
outside), no quantizer-state leak, and the focused tests pass.

- models_utils: build_block_range_quant_cfg now uses the top-level enable
  QuantizerCfgEntry field (a None cfg retains the base preset's params) instead
  of nesting cfg.enable, which the QuantizerAttributeConfig validator
  rejects/mis-applies (the old form left every block quantized).
- quantize.py: import onnx_utils.export lazily (only needed for --onnx-dir;
  avoids a hard onnx_graphsurgeon dependency), and pass max_shard_size so the
  ~20B transformer saves as a single safetensors -- the unified export's
  layerwise-metadata post-processing does not support sharded files.
- diffusers_utils: hide_quantizers_from_state_dict strips quantizer submodules
  from all modules, not only is_quantlinear, so enabled input quantizers on
  norm layers no longer leak input_quantizer._amax into the checkpoint.
- tests: the tiny QwenImageTransformer2DModel fixture signature-gates its
  kwargs (diffusers 0.38 removed pooled_projection_dim from the constructor);
  the recipe test asserts the corrected top-level enable schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
run_qwen_image_quantization.sh and its README are cluster-specific
experiment/operator scripts (hard-coded /lustre paths) that do not belong in
the upstream diffusers example. The feature itself (model registration,
block-range recipe, FP8/NVFP4/SVDQuant export) is covered by the committed
tests. The scripts are kept locally outside the repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
Remove the standalone Qwen test files. The fp8/nvfp4/svdquant cases in
test_export_diffusers_hf_ckpt.py already cover the block-range recipe
(only transformer_blocks 2..57 quantized), the promoted SVDQuant keys +
pre_quant_scale, the NVFP4_SVD quantization_config, and the no-leak check
-- matching how SDXL/Flux/Wan are tested in the same file. Core SVDQuant
forward/fold is unchanged and remains covered by existing upstream tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…motion

Covers svdquant calibration -> _promote_quantizer_tensors_to_module ->
clean module-level keys (svdquant_lora_a/b, pre_quant_scale) with the
quantizers hidden, plus the post-export cleanup. Runs on CPU in <1s
(INT8_SMOOTHQUANT + svdquant on a tiny linear stack). The full NVFP4
end-to-end check remains test_qwen_image_hf_ckpt_export[qwen_nvfp4_svdquant];
svdquant calibration is already covered by test_calib.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…nk check, imports)

- unified_export_hf: wrap promote -> save -> post-process -> config-update in
  try/finally so the temporary promoted export buffers are always removed even
  if an exception occurs, keeping the live module reusable for a repeated export.
- unified_export_hf: _detect_svdquant_rank collects the unique SVDQuant ranks
  across modules and raises on a mismatch instead of silently recording the
  first module's rank as the single config-level lora_rank.
- Hoist deferred imports to module level (inspect in diffusers_utils and the
  tiny Qwen fixture; copy/torch.nn/mtq/hide_quantizers in the export-promotion
  test) per the test coding guidelines.

Verified on GB200: ruff clean; the fast CPU export-promotion test and the three
Qwen e2e export cases (fp8/nvfp4/svdquant) pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
Resolve tests/unit/torch/export/test_export_diffusers.py: keep both the SVDQuant
export-promotion test and main's NVIDIA#1794 ComfyUI post-processing tests (union),
and merge the imports.

Drop the max_shard_size=200GB workaround in the diffusers export harness: NVIDIA#1794
makes the ComfyUI layerwise-metadata post-processing a no-op unless explicitly
opted in, so a default (sharded) export no longer hits the unsupported-sharded
path and the workaround is unnecessary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
This PR had added a Qwen-Image-specific ImportError in both
PipelineManager.create_pipeline_from and .create_pipeline, raised when
MODEL_PIPELINE[model_type] is None. That over-specialized the message: the
same None condition also covers Flux2 (whose pipeline import is version-gated)
and any future version-gated pipeline, which fell through to the misleading
generic "does not use diffusers pipelines."

Replace both blocks with a single ValueError stating the model type is not
supported by the installed diffusers version, keeping the actionable "upgrade
diffusers" hint. This matches the existing "Raises: ValueError" docstrings; no
caller or test depends on the previous ImportError type (create_pipeline
re-raises through a generic except Exception).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
Remove the --sanity-image-path flag and the in-memory sanity-image generation
block from the diffusers quantization example. It was an optional developer
convenience (render one image from the fake-quantized pipeline before export)
that no test exercises and that the regular quantize/export flow does not need.
Dropping it simplifies the example's main path.

No functional impact on export: pipe, MODEL_DEFAULTS, and Path remain used
elsewhere, and the Qwen e2e never invoked the flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
Restore the top-level `from onnx_utils.export import generate_fp8_scales,
modelopt_export_sd` instead of importing it lazily inside export_onnx, matching
main's placement and keeping the import style consistent with the rest of the
example rather than special-casing this one import.

This restores main's contract that the diffusers example depends on the modelopt
`[onnx]` extra (onnx-graphsurgeon, onnxruntime, ...) to import quantize.py. The
HF-export logic is unchanged; the module is ruff/isort clean and compiles. The
top-level import path is the same one main's SDXL/Flux/Wan export tests already
exercise in CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
The code-quality job (pre-commit) failed on three hooks; all addressed:

- mypy: convert_hf_config.py — annotate the shared `config_group_details`
  (first/FP8 branch) as `dict[str, Any]`. The NVFP4_SVD branch adds bool flags
  (`has_zero_point`, `pre_quant_scale`) that broke mypy's inferred
  `dict[str, Collection[str]]` (dict-item errors on the two bool entries).
- ruff format: models_utils.py — wrap the over-length `rules.append(...)` line
  in build_block_range_quant_cfg.
- ruff check: test_export_diffusers_hf_ckpt.py — UP037 (drop quotes on the
  `object` annotation), PIE810 (merge the two `endswith` calls into one tuple
  call), and `# noqa: SIM118` on the `safe_open(...).keys()` loop (safe_open is
  not directly iterable; matches the convention used elsewhere in the repo).

No runtime behavior changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…weights

Two related changes to the diffusers quantization example.

1) Weight-free quantizer-state checkpoint (all models)
The torch checkpoint written by `--quantized-torch-ckpt-save-path` now stores
ONLY ModelOpt's quantization state -- the recipe plus the quantizer buffers
(amax, pre_quant_scale, ...) -- and NOT the model weights. The weights live in
the base HF/diffusers checkpoint and are reloaded there on restore. This uses
ModelOpt's own idiom (mirrors plugins/transformers_trainer.py):

    save:    modelopt_state = mto.modelopt_state(model)
             modelopt_state["modelopt_state_weights"] = get_quantizer_state_dict(model)
             torch.save(modelopt_state, path)
    restore: modelopt_state = mto.load_modelopt_state(path)
             weights = modelopt_state.pop("modelopt_state_weights", None)
             mto.restore_from_modelopt_state(model, modelopt_state)
             set_quantizer_state_dict(model, weights)

Wrapped as utils.save_quantizer_state / restore_quantizer_state and wired into
ExportManager.save_checkpoint / restore_checkpoint. Restore auto-applies on top
of the freshly-loaded base weights (the pipeline is created before restore).
Effect: the artifact drops from a full-model checkpoint to KBs-MBs (a 60-layer
Qwen-Image student: 40.8 GB -> 2.0 MB) while amax round-trips bit-identically.

2) qwen-image-dmd2 model type (DMD2 few-step Qwen-Image students)
For students distilled by examples/diffusers/fastgen. Reuses the existing
Qwen-Image quantization stack from the base branch (filter_func_qwen_image, the
block-range recipe, QwenImagePipeline registration) and adds only what differs:

- pipeline_manager: load the consolidated student transformer (+ optional EMA)
  and swap it into the base QwenImagePipeline; stash the few-step sampler config
  (defaults to the canonical 4-step shift=3 [1.0, 0.9, 0.75, 0.5, 0.0] ODE
  schedule, guidance_scale=1.0).
- calibration: drive the few-step DMD sampler instead of the standard denoising
  loop, so collected amax matches how the student is actually run.
- qwen_image_dmd2_sampler.py (new): vendored compact DMD unroll, bit-aligned with
  fastgen/inference_dmd2_qwen_image.py; transformer forwards only for calibration
  (decode=False), optional VAE decode for sanity inference (decode=True).
- sanity_check_dmd2.py (new): restore the quantizer-state checkpoint and run one
  few-step inference to validate the round trip.
- quantize.py: import the ONNX export tooling lazily so the calibration + save
  path runs without onnx_graphsurgeon (e.g. the diffusers/fastgen container).

Validated end-to-end (FP8, single GB200): calibrate -> save (2.0 MB) ->
restore_quantizer_state reproduces amax bit-identically (e.g. block 30
attn.to_q input amax 1.26e+03) and the restored quantized student renders a
finite, non-constant 1024x1024 image.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Jingyu Xin <jingyux@nvidia.com>
…d2-qat

Signed-off-by: Jingyu Xin <jingyux@nvidia.com>

# Conflicts:
#	examples/diffusers/quantization/models_utils.py
#	examples/diffusers/quantization/quantize.py
#	tests/_test_utils/torch/diffusers_models.py
#	tests/examples/diffusers/test_export_diffusers_hf_ckpt.py
@copy-pr-bot

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

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

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: 400de53e-4e76-4095-8394-5d082cda416d

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.

@jingyu-ml

Copy link
Copy Markdown
Contributor Author

Superseded by #2069, which uses an official NVIDIA/Model-Optimizer head branch and contains the completed HF-PEFT SVDQuant implementation plus the Qwen-Image-only cleanup.

@jingyu-ml jingyu-ml closed this Aug 5, 2026
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