fix: Hessian auto-quant crash and qconfig_dict mutation bugs - #29
Merged
Adithya-Thonse merged 2 commits intoAug 5, 2026
Merged
Conversation
Two bugs in Hessian-based auto-quantization:
1. run_auto_quantization() crashed with a bare TypeError instead of failing
gracefully. optimal_bitwidth stays None whenever no calibration_dataloader
is supplied, or compute_hessian_sensitivity can't compute sensitivities
(returns ({}, {}) when inputs/targets/criterion aren't available) -- a
path callers like audio_classification/train.py already anticipate and
try to degrade gracefully from, logging "Could not obtain sample data...
Proceeding without it". That graceful-degradation intent was defeated a
few lines later by `total_bit_budget = optimal_bitwidth * total_params`,
raising TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'.
Fixed by returning the caller's already-built qconfig_mapping unchanged
when optimal_bitwidth is None -- this is the qconfig_mapping the caller
constructed from the manually-specified uniform bitwidth before auto-
quantization was attempted, making it the correct, non-crashing fallback
(not a forced all-float32 fallback, and not a crash).
2. apply_mixed_precision() wrote qconfig_dict['weight']['bitwidth'] /
['activation']['bitwidth'] directly onto whatever dict it was handed.
Two of its four call sites (qconfig_types.py's manual mixed-precision
path, and auto_quantization.py's default Hessian auto-quantization path
-- i.e. essentially every successful quantized run) passed the original,
un-copied qconfig_dict, which is frequently the same dict object retained
as self.qconfig_type on the live model wrapper (quant_base.py). Any
downstream logging, checkpoint metadata, or reuse of that config
silently observed whichever bitwidth tier this function processed last,
not the model's actual/intended default. Two other call sites already
worked around this by building a throwaway copy before calling in --
confirming the mutation was an oversight, not intended behavior.
Fixed in the function itself (not just the two unsafe call sites): builds
a local copy per bit-width tier instead of mutating the caller's dict,
so every caller is protected regardless of whether it already built its
own defensive copy.
Verified: 3 of 4 added tests fail on the unmodified code (both crash tests
reproduce the exact TypeError; the mutation test observes the caller's dict
change from 8 to 4) and pass after the fix. The 4th test confirms the
mutation fix doesn't break the actual mixed-precision assignment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ewrote
Both CodeRabbit and the earlier Opus review flagged that
test_apply_mixed_precision_still_applies_the_requested_bitwidth_per_layer
used {32: ["fc"]}, which takes the bit_width == 32 "disable quantization"
path -- a branch the mutation fix never touched -- so nothing verified
the rewritten bw_qconfig_dict construction actually produces a qconfig at
the requested bitwidth. Now uses {4: ["fc"]} and asserts the produced
qconfig's real quantization ranges (signed 4-bit weights: quant_max 7;
unsigned 4-bit activations: quant_max 15, values confirmed empirically
against get_default_qconfig before asserting). The 32-bit disable path
keeps its own separate test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Summary
Two bugs in Hessian-based auto-quantization (
auto_quantization.py/qconfig_types.py), both real, reproducible on the code as it stands.Bare
TypeErrorinstead of graceful degradationrun_auto_quantization()'soptimal_bitwidthstaysNonewhenever nocalibration_dataloaderis supplied, orcompute_hessian_sensitivity()can't compute sensitivities (it returns({}, {})when sampleinputs/targets/criterionaren't available) — a path callers likeaudio_classification/train.pyalready anticipate and try to degrade gracefully from, logging "Could not obtain sample data... Proceeding without it". That graceful-degradation intent was defeated a few lines later:TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'.Fixed by returning the caller's already-built
qconfig_mappingunchanged whenoptimal_bitwidth is None— this is the mapping the caller constructed from the manually-specified uniform bitwidth before auto-quantization was attempted, making it the semantically correct fallback (not a forced all-float32 fallback, and not a crash).apply_mixed_precision()mutates the caller's live config dictTwo of its four call sites —
qconfig_types.py's manual mixed-precision path, andauto_quantization.py's default Hessian auto-quantization path (i.e. essentially every successful quantized run) — pass the original, un-copiedqconfig_dict, which is frequently the same dict object retained asself.qconfig_typeon the live model wrapper (quant_base.py). Any downstream logging, checkpoint metadata, or reuse of that config silently observed whichever bitwidth tier this function processed last, not the model's actual/intended default. Two other call sites already build a throwaway copy before calling in — confirming the mutation was an oversight, not intended behavior.Fixed in the function itself, not just the two unsafe call sites: builds a local copy per bit-width tier instead of mutating the caller's dict, so every caller is protected regardless of whether it already defended itself.
Verification
3 of 4 added tests fail on the unmodified code — both crash tests reproduce the exact
TypeError, and the mutation test observes the caller's dict actually change from8to4— and pass after the fix. The 4th test confirms the mutation fix doesn't break the actual mixed-precision assignment. Also verified this merges cleanly alongsidepr/dataloader-pinning-fix(#20), which touches a sibling file (quant_base.py) in the same directory.🤖 Generated with Claude Code