From 7b5e4c8e043f4107bf3b6413e25c6984437631ca Mon Sep 17 00:00:00 2001 From: javierdejesusda Date: Mon, 18 May 2026 17:44:45 +0200 Subject: [PATCH] fix(quantization): accept QuantizeAlgorithmConfig in get_modelike_from_algo_cfg get_modelike_from_algo_cfg raised ValueError for a QuantizeAlgorithmConfig instance even though QuantizeAlgoCfgType lists it as a valid type. Since ModeloptBaseConfig is a Mapping, widen the type cascade's dict check to Mapping and materialize the resolved config with dict(). Add unit and end-to-end regression tests plus a CHANGELOG entry. Fixes #201 Signed-off-by: javierdejesusda --- CHANGELOG.rst | 1 + modelopt/torch/quantization/mode.py | 6 +++-- tests/unit/torch/quantization/test_mode.py | 24 ++++++++++++++++++- .../torch/quantization/test_quantize_cpu.py | 14 ++++++++++- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index be2210a33f2..8c6e553f7f2 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -31,6 +31,7 @@ Changelog **Bug Fixes** - Fix Megatron-Core HF importer to load fused ``TELayerNormColumnParallelLinear.layer_norm_weight`` from HF for GPT-family models (Qwen3 etc.) under ``--export-default-te-spec``. Importer now prefers per-context keys ``fused_input_layernorm`` / ``fused_pre_mlp_layernorm`` (fallback ``fused_norm`` for Nemotron-H backward compatibility); ``mcore_qwen.py`` provides the new rules. Without this fix, post-prune MMLU sat at chance. +- Fix ``QuantizeAlgorithmConfig`` instances being rejected with ``ValueError`` when passed as the ``algorithm`` in a quantization config (e.g. ``cfg["algorithm"] = mtq.MaxCalibConfig(...)``). Such instances are now accepted, consistent with the ``algorithm`` field's type annotation. 0.44 (2026-05-14) ^^^^^^^^^^^^^^^^^ diff --git a/modelopt/torch/quantization/mode.py b/modelopt/torch/quantization/mode.py index c6cb07fbfb8..a177a095e55 100644 --- a/modelopt/torch/quantization/mode.py +++ b/modelopt/torch/quantization/mode.py @@ -16,7 +16,7 @@ """This module contains the mode descriptor for the quantization mode.""" from abc import abstractmethod -from collections.abc import Callable +from collections.abc import Callable, Mapping from modelopt.torch.opt.config import ModeloptBaseConfig from modelopt.torch.opt.conversion import ModelLikeModule @@ -374,7 +374,9 @@ def get_modelike_from_algo_cfg(algo_cfg: QuantizeAlgoCfgType) -> ModeConfigList: return [get_modelike_from_algo_cfg(c)[0] for c in algo_cfg] if algo_cfg is None or isinstance(algo_cfg, str): algo_name, algo_cfg = algo_cfg, {} - elif isinstance(algo_cfg, dict): + elif isinstance(algo_cfg, Mapping): + # Normalize any mapping (incl. ModeloptBaseConfig instances) to a plain dict. + algo_cfg = dict(algo_cfg) algo_name = algo_cfg["method"] else: raise ValueError(f"Invalid config type: {type(algo_cfg)}") diff --git a/tests/unit/torch/quantization/test_mode.py b/tests/unit/torch/quantization/test_mode.py index 6ed8c661712..5b0cd1b3948 100644 --- a/tests/unit/torch/quantization/test_mode.py +++ b/tests/unit/torch/quantization/test_mode.py @@ -19,11 +19,16 @@ from modelopt.torch.opt.config import ModeloptField from modelopt.torch.opt.mode import _ModeRegistryCls -from modelopt.torch.quantization.config import QuantizeAlgorithmConfig +from modelopt.torch.quantization.config import ( + MaxCalibConfig, + QuantizeAlgorithmConfig, + SmoothQuantCalibConfig, +) from modelopt.torch.quantization.mode import ( BaseCalibrateModeDescriptor, CalibrateModeRegistry, QuantizeModeRegistry, + get_modelike_from_algo_cfg, ) @@ -60,3 +65,20 @@ def config_class(self) -> QuantizeAlgorithmConfig: @CalibrateModeRegistry.register_mode class TestIncorrectCalibrateModeDescriptor: pass + + +def test_get_modelike_from_algo_cfg_accepts_config_instance(): + """Regression test for GitHub issue #201. + + A ``QuantizeAlgorithmConfig`` instance passed as the ``algorithm`` config is + normalized to a dict and accepted, instead of raising ``ValueError``. + """ + mode_name, algo_cfg = get_modelike_from_algo_cfg(MaxCalibConfig(distributed_sync=False))[0] + assert mode_name == "max_calibrate" + assert isinstance(algo_cfg, dict) + assert algo_cfg["distributed_sync"] is False + MaxCalibConfig(**algo_cfg) + + modes = get_modelike_from_algo_cfg([MaxCalibConfig(), SmoothQuantCalibConfig()]) + assert [name for name, _ in modes] == ["max_calibrate", "smoothquant_calibrate"] + assert all(isinstance(cfg, dict) for _, cfg in modes) diff --git a/tests/unit/torch/quantization/test_quantize_cpu.py b/tests/unit/torch/quantization/test_quantize_cpu.py index 301f4cdab1e..37fb441b952 100644 --- a/tests/unit/torch/quantization/test_quantize_cpu.py +++ b/tests/unit/torch/quantization/test_quantize_cpu.py @@ -32,7 +32,7 @@ import modelopt.torch.opt as mto import modelopt.torch.quantization as mtq from modelopt.torch.quantization.calib import MaxCalibrator -from modelopt.torch.quantization.config import QuantizerAttributeConfig +from modelopt.torch.quantization.config import MaxCalibConfig, QuantizerAttributeConfig from modelopt.torch.quantization.conversion import set_quantizer_attributes_full from modelopt.torch.quantization.nn.modules.tensor_quantizer import ( SequentialQuantizer, @@ -167,6 +167,18 @@ def forward_loop(): mtq.quantize(model, mtq.INT8_DEFAULT_CFG, forward_loop=forward_loop) +def test_quantize_accepts_algo_config_instance_end_to_end(): + """Regression test for GitHub issue #201. + + A ``QuantizeAlgorithmConfig`` instance set as ``config["algorithm"]`` must not + raise ``ValueError`` through the full quantize/calibrate flow. + """ + quant_config = copy.deepcopy(mtq.INT8_DEFAULT_CFG) + quant_config["algorithm"] = MaxCalibConfig(distributed_sync=False) + model = SimpleLinear() + quantize_model_and_forward(model, quant_config, [model.get_input() for _ in range(2)]) + + def test_custom_calib_config(): model_ref = SimpleLinear() model_ref = mtq.quantize(