Add Qwen-Image DMD2 QAT and PEFT-backed SVDQuant - #2068
Closed
jingyu-ml wants to merge 26 commits into
Closed
Conversation
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
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.restoreandmodelopt_stateplusstate_dictworkflows will reconstruct the PEFT topology and preserve its trainability status.Usage
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"
CONTRIBUTING.md: N/ACHANGELOG.rst?: In progressAdditional Information
This is intentionally a draft while the generic SVDQuant/HF-PEFT implementation and focused tests are added as a separate signed commit.