Skip to content

Fix several quantizer config bugs (AutoRound override, ModelOpt JSON crash, single-file pre_quantized, config mutation, Nunchaku group_size, dataclass eq/repr) - #15121

Draft
OnePunchMonk wants to merge 8 commits into
huggingface:mainfrom
OnePunchMonk:fix/quantizer-config-bugs
Draft

OnePunchMonk wants to merge 8 commits into
huggingface:mainfrom
OnePunchMonk:fix/quantizer-config-bugs

Conversation

@OnePunchMonk

@OnePunchMonk OnePunchMonk commented Oct 24, 2026 •

Copy link
Copy Markdown

What does this PR do?

Fixes #15120, #15122, #15123, #15124, #15125, #15126.

Six independent correctness bugs in src/diffusers/quantizers/, each its own commit:

Test plan

Verified on Modal CPU (no GPU used), pre-diff vs. post-diff, for the config-only logic that doesn't need a backend installed:

pip install -e . --no-deps -q
python -m pytest -v --no-header tests/quantization/test_quantization_config.py
  • Post-diff: all 7 tests pass.
  • Pre-diff (source reverted, tests kept): 6/7 fail with the exact predicted errors (group_size 128 != 32, forward_loop leaking into to_dict(), GGUF configs comparing equal / empty repr, QuantoConfig has no to_diff_dict, Nunchaku accepting group_size=128). The 7th is a valid-input case that correctly passes either way.

The bnb (tests/quantization/bnb/test_mixed_int8.py) and TorchAO (tests/quantization/torchao/test_torchao.py) mutation-fix tests are gated behind @require_torch_accelerator by those existing test classes' own convention, so they weren't run here — see self-review notes below.

Self-review notes (per the self-review skill, run against this diff):

  • Checked review-rules.md's "ephemeral context" rule against my own added comments — trimmed two that referenced "the fix" or restated the test name instead of standing alone.
  • Checked whether removing @dataclass from the config classes breaks anything relying on dataclasses.fields() elsewhere in the codebase — nothing does.
  • Left for the actual review: the bnb/TorchAO mutation-fix tests are gated behind @require_torch_accelerator by the existing test classes' own convention, so they haven't been executed here (no GPU used for this PR) — only verified by reading the code path. Happy to run them on GPU if a maintainer wants that confirmed before merge.
  • tests/quantization/test_quantization_config.py is a new file because AutoRound, ModelOpt, Nunchaku, and the base config/dataclass logic have no existing test coverage anywhere in the repo to extend instead.

Before submitting

Who can review?

Anyone in the community is free to review. Tagging @sayakpaul and @DN6 per the template's guide for general functionalities / loading.

@OnePunchMonk
OnePunchMonk marked this pull request as ready for review October 24, 2026 16:57
@OnePunchMonk
OnePunchMonk force-pushed the fix/quantizer-config-bugs branch from 26b7ca7 to ba77b98 Compare October 24, 2026 17:07
merge_quantization_configs looped over every field in the user-passed
AutoRoundConfig and overrode any field differing from the checkpoint's,
not just backend. The documented AutoRoundConfig(backend="auto") call
was silently rewriting a checkpoint's bits/group_size/sym to dataclass
defaults. Only backend is a runtime-only override; the rest describe
how the checkpoint was quantized.

Towards huggingface#15120.
forward_loop is a documented NVIDIAModelOptConfig parameter, but
to_dict() had no override and inherited the base deepcopy-of-__dict__
implementation, so the raw callable flowed into modeling_utils.py's
json.dumps(...) telemetry call and raised TypeError on every
from_pretrained/save_pretrained.

Towards huggingface#15120.
…ackends

DiffusersAutoQuantizer.from_config() defaults pre_quantized to True
when not passed, and the single-file loader never passed it. GGUF and
BFL set pre_quantized themselves from their config so were unaffected,
but bitsandbytes (4-bit/8-bit), TorchAO, and Quanto need to quantize on
load rather than assume already-quantized weights, so single-file
loading broke or silently no-opped for them.

No dedicated test: exercising this needs a real single-file checkpoint
load, which isn't practical as a CPU-only unit test.

Towards huggingface#15120.
bnb (4-bit + 8-bit), TorchAO, Quanto, and ModelOpt quantizers aliased
modules_to_not_convert-style lists directly off the quantization_config
object and then .extend()-ed them in place. Reusing the same config
across two from_pretrained calls leaked state between loads (e.g.
offloaded module names accumulating on the shared config and getting
written into config.json). Copy the list before mutating it.

Towards huggingface#15120.
Config validation for the Nunchaku AWQ path only checked
precision == "int4"; the actual AWQ kernel call site hardcodes
group_size 64. A checkpoint with group_size=128 loaded without error
and would silently produce wrong output. The SVDQ path in the same
config already validates its group size, so apply the same pattern
here.

Towards huggingface#15120.
…ng()

Every quant config subclass is @dataclass-decorated but declares no
dataclass fields beyond the inherited quant_method, so the generated
__eq__/__repr__ only see that one field. Two GGUFQuantizationConfig
instances with different compute_dtype compared equal, repr() hid
every real parameter, and the configs were unhashable. Remove the
decorator from subclasses with a custom __init__ and no declared
fields.

Separately, to_json_string() calls self.to_diff_dict(), which only
BitsAndBytesConfig defined; every other config raised AttributeError.
Add a base to_diff_dict (no comparable "default" instance exists for
these configs in general, so it's just to_dict()).

Removing @DataClass exposed GGUFQuantizationConfig/BFLQuantizationConfig's
raw torch.dtype compute_dtype to the now-working diff path in
to_json_string(), which would have been a new JSON-serialization crash,
so both get a to_dict() override that stringifies it.

Towards huggingface#15120.
_validate_init_kwargs_in_backends compared init_kwargs_transformers
(None when transformers isn't involved for a given backend) against
init_kwargs_diffusers (a set), which is always unequal, so the
signature-mismatch error fired unconditionally.

No dedicated test: this needs no existing test file (no coverage of
PipelineQuantizationConfig anywhere in the repo) and the fix is a
one-line guard with no side effects to assert on beyond "doesn't raise
when it shouldn't", which overlaps the normal pipeline-quantization
flow already covered elsewhere.

Towards huggingface#15120.
- Two log lines were missing an f-string prefix, so {current_device}
  and {target_dtype} printed literally instead of interpolating.
- check_quantized_param_shape's error message had expected and actual
  shapes swapped.
- The SCB/weight_format key lookups used param_name.replace("weight",
  "SCB"), which corrupts the key for any module name containing
  "weight" elsewhere than the trailing ".weight" param (e.g.
  "weight_proj.weight" -> "weight_proj.SCB" would actually become
  "SCB_proj.weight"). Switch to suffix-safe slicing, matching the
  pattern this file already uses elsewhere.

Towards huggingface#15120.
@OnePunchMonk
OnePunchMonk force-pushed the fix/quantizer-config-bugs branch from ba77b98 to fee4c69 Compare October 24, 2026 17:18
@OnePunchMonk
OnePunchMonk marked this pull request as draft October 24, 2026 17:21
@OnePunchMonk

OnePunchMonk commented Oct 24, 2026 •

Copy link
Copy Markdown
Author
  • I read the AI-assisted coding guide and the contributor guidelines.
  • I've split the umbrella issue into smaller issues, one per bug
  • I'll keep this PR in draft until a maintainer confirms the bug reports.
  • After that, I'll break it into separate PRs. Until then, the current diff can serve as a reference for a possible fix.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AutoRound: user-passed quantization_config silently overwrites checkpoint's quant settings

1 participant