|
24 | 24 |
|
25 | 25 | import modelopt.torch.opt as mto |
26 | 26 | import modelopt.torch.quantization as mtq |
| 27 | +import modelopt.torch.quantization.model_quant as model_quant |
27 | 28 | from modelopt.torch.quantization._auto_quantize_cost import ( |
28 | 29 | EXCLUDED_MODULE_NAME_PATTERNS_KEY, |
29 | 30 | _get_module_weight_numel, |
|
39 | 40 | estimate_quant_compression, |
40 | 41 | ) |
41 | 42 | from modelopt.torch.quantization.config import _base_disable_all, _default_disabled_quantizer_cfg |
42 | | -from modelopt.torch.utils import safe_load |
| 43 | +from modelopt.torch.utils import safe_load, safe_save |
43 | 44 | from modelopt.torch.utils.distributed import DistributedProcessGroup |
44 | 45 |
|
45 | 46 |
|
@@ -366,9 +367,51 @@ def test_auto_quantize_module_search_spaces_keep_fixed_routed_experts_costed(): |
366 | 367 | assert routed_hparam.active == int4_recipe |
367 | 368 |
|
368 | 369 |
|
369 | | -def test_auto_quantize_fixed_module_isolated_from_unrelated_calibration(monkeypatch): |
370 | | - import modelopt.torch.quantization.model_quant as model_quant |
| 370 | +@pytest.mark.parametrize("formats", ["FP8_DEFAULT_CFG", mtq.FP8_DEFAULT_CFG, ()]) |
| 371 | +def test_auto_quantize_rejects_non_list_global_formats(formats): |
| 372 | + with pytest.raises(TypeError, match="`quantization_formats` must be a list"): |
| 373 | + mtq.auto_quantize(TransformerBlock(), quantization_formats=formats) |
| 374 | + |
| 375 | + |
| 376 | +@pytest.mark.parametrize("formats", [[], [None]]) |
| 377 | +def test_auto_quantize_rejects_empty_global_formats(formats): |
| 378 | + with pytest.raises(ValueError, match="`quantization_formats` must"): |
| 379 | + mtq.auto_quantize(TransformerBlock(), quantization_formats=formats) |
| 380 | + |
| 381 | + |
| 382 | +@pytest.mark.parametrize("formats", ["FP8_DEFAULT_CFG", mtq.FP8_DEFAULT_CFG, ()]) |
| 383 | +def test_auto_quantize_rejects_non_list_module_formats(formats): |
| 384 | + with pytest.raises( |
| 385 | + TypeError, match=r"module_search_spaces\.quantization_formats must be a list" |
| 386 | + ): |
| 387 | + mtq.auto_quantize( |
| 388 | + TransformerBlock(), |
| 389 | + quantization_formats=[mtq.INT8_DEFAULT_CFG], |
| 390 | + module_search_spaces=[ |
| 391 | + { |
| 392 | + "module_name_patterns": ["*mlp*"], |
| 393 | + "quantization_formats": formats, |
| 394 | + } |
| 395 | + ], |
| 396 | + ) |
| 397 | + |
| 398 | + |
| 399 | +@pytest.mark.parametrize("formats", [[], [None]]) |
| 400 | +def test_auto_quantize_rejects_empty_module_formats(formats): |
| 401 | + with pytest.raises(ValueError, match=r"module_search_spaces\.quantization_formats must"): |
| 402 | + mtq.auto_quantize( |
| 403 | + TransformerBlock(), |
| 404 | + quantization_formats=[mtq.INT8_DEFAULT_CFG], |
| 405 | + module_search_spaces=[ |
| 406 | + { |
| 407 | + "module_name_patterns": ["*mlp*"], |
| 408 | + "quantization_formats": formats, |
| 409 | + } |
| 410 | + ], |
| 411 | + ) |
371 | 412 |
|
| 413 | + |
| 414 | +def test_auto_quantize_fixed_module_isolated_from_unrelated_calibration(monkeypatch): |
372 | 415 | model = TransformerBlock() |
373 | 416 | calibration_states = [] |
374 | 417 | original_calibrate = model_quant.calibrate |
@@ -1011,6 +1054,82 @@ def interrupt_after_calibration(self): |
1011 | 1054 | ) |
1012 | 1055 |
|
1013 | 1056 |
|
| 1057 | +def test_auto_quantize_calibration_only_checkpoint_validates_global_formats_and_legacy( |
| 1058 | + tmp_path, monkeypatch |
| 1059 | +): |
| 1060 | + checkpoint_path = str(tmp_path / "autoquant_calibration_only_checkpoint.pth") |
| 1061 | + legacy_checkpoint_path = str(tmp_path / "autoquant_legacy_calibration_only_checkpoint.pth") |
| 1062 | + original_estimate_scores = AutoQuantizeGradientSearcher.estimate_sensitivity_scores |
| 1063 | + |
| 1064 | + def interrupt_after_calibration(self): |
| 1065 | + raise RuntimeError("interrupt after calibration") |
| 1066 | + |
| 1067 | + monkeypatch.setattr( |
| 1068 | + AutoQuantizeGradientSearcher, |
| 1069 | + "estimate_sensitivity_scores", |
| 1070 | + interrupt_after_calibration, |
| 1071 | + ) |
| 1072 | + model = TransformerBlock() |
| 1073 | + with pytest.raises(RuntimeError, match="interrupt after calibration"): |
| 1074 | + mtq.auto_quantize( |
| 1075 | + model, |
| 1076 | + constraints={"effective_bits": 6.0}, |
| 1077 | + quantization_formats=[ |
| 1078 | + mtq.INT4_BLOCKWISE_WEIGHT_ONLY_CFG, |
| 1079 | + mtq.INT8_DEFAULT_CFG, |
| 1080 | + ], |
| 1081 | + data_loader=[model.get_input()], |
| 1082 | + forward_step=lambda model, batch: model(batch), |
| 1083 | + loss_func=lambda output, data: output.sum(), |
| 1084 | + num_calib_steps=1, |
| 1085 | + num_score_steps=1, |
| 1086 | + checkpoint=checkpoint_path, |
| 1087 | + ) |
| 1088 | + |
| 1089 | + saved = safe_load(checkpoint_path) |
| 1090 | + assert saved["quantizer_states"] |
| 1091 | + assert saved["quantization_formats_signature"] |
| 1092 | + legacy_saved = dict(saved) |
| 1093 | + legacy_saved.pop("quantization_formats_signature") |
| 1094 | + safe_save(legacy_saved, legacy_checkpoint_path) |
| 1095 | + |
| 1096 | + monkeypatch.setattr( |
| 1097 | + AutoQuantizeGradientSearcher, |
| 1098 | + "estimate_sensitivity_scores", |
| 1099 | + original_estimate_scores, |
| 1100 | + ) |
| 1101 | + mismatched_model = TransformerBlock() |
| 1102 | + with pytest.raises(ValueError, match="quantization_formats do not match"): |
| 1103 | + mtq.auto_quantize( |
| 1104 | + mismatched_model, |
| 1105 | + constraints={"effective_bits": 6.0}, |
| 1106 | + quantization_formats=[mtq.INT4_BLOCKWISE_WEIGHT_ONLY_CFG, mtq.FP8_DEFAULT_CFG], |
| 1107 | + data_loader=[mismatched_model.get_input()], |
| 1108 | + forward_step=lambda model, batch: model(batch), |
| 1109 | + loss_func=lambda output, data: output.sum(), |
| 1110 | + num_calib_steps=1, |
| 1111 | + num_score_steps=1, |
| 1112 | + checkpoint=checkpoint_path, |
| 1113 | + ) |
| 1114 | + |
| 1115 | + legacy_model = TransformerBlock() |
| 1116 | + with pytest.raises(ValueError, match="does not record its quantization_formats signature"): |
| 1117 | + mtq.auto_quantize( |
| 1118 | + legacy_model, |
| 1119 | + constraints={"effective_bits": 6.0}, |
| 1120 | + quantization_formats=[ |
| 1121 | + mtq.INT4_BLOCKWISE_WEIGHT_ONLY_CFG, |
| 1122 | + mtq.INT8_DEFAULT_CFG, |
| 1123 | + ], |
| 1124 | + data_loader=[legacy_model.get_input()], |
| 1125 | + forward_step=lambda model, batch: model(batch), |
| 1126 | + loss_func=lambda output, data: output.sum(), |
| 1127 | + num_calib_steps=1, |
| 1128 | + num_score_steps=1, |
| 1129 | + checkpoint=legacy_checkpoint_path, |
| 1130 | + ) |
| 1131 | + |
| 1132 | + |
1014 | 1133 | @pytest.mark.parametrize("method", ["gradient", "kl_div"]) |
1015 | 1134 | def test_get_auto_quantize_config(method): |
1016 | 1135 | model = TransformerBlock() |
|
0 commit comments