Skip to content

Commit 8a29f31

Browse files
authored
[tests] standardize model-level quant tests (#14332)
standardize model-level quant tests
1 parent af0e6e4 commit 8a29f31

1 file changed

Lines changed: 86 additions & 82 deletions

File tree

tests/models/testing_utils/quantization.py

Lines changed: 86 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -244,16 +244,70 @@ def _test_quantization_lora_inference(self, config_kwargs):
244244
assert not torch.isnan(output).any(), "Model output contains NaN with LoRA"
245245

246246
@torch.no_grad()
247-
def _test_quantization_serialization(self, config_kwargs, tmp_path):
247+
def _test_quantization_serialization(self, config_kwargs, tmp_path, max_shard_size=None):
248+
"""
249+
Test that a quantized model can be saved and reloaded without changing its outputs.
250+
251+
Args:
252+
config_kwargs: Quantization config parameters
253+
tmp_path: Directory the model is serialized into
254+
max_shard_size: When set, the checkpoint is sharded and the shard/index files are checked
255+
"""
248256
model = self._create_quantized_model(config_kwargs)
257+
model.to(torch_device)
249258

250-
model.save_pretrained(str(tmp_path), safe_serialization=True)
259+
inputs = self.get_dummy_inputs()
260+
expected_output = model(**inputs, return_dict=False)[0].detach().cpu()
251261

252-
model_loaded = self.model_class.from_pretrained(str(tmp_path))
262+
save_kwargs = {"safe_serialization": True}
263+
if max_shard_size is not None:
264+
save_kwargs["max_shard_size"] = max_shard_size
265+
model.save_pretrained(str(tmp_path), **save_kwargs)
253266

254-
inputs = self.get_dummy_inputs()
255-
output = model_loaded(**inputs, return_dict=False)[0]
256-
assert not torch.isnan(output).any(), "Loaded model output contains NaN"
267+
del model
268+
gc.collect()
269+
backend_empty_cache(torch_device)
270+
271+
if max_shard_size is not None:
272+
assert len(list(tmp_path.glob("*.safetensors"))) > 1, "Expected a sharded safe-serialization checkpoint."
273+
assert any(path.name.endswith(".index.json") for path in tmp_path.iterdir()), (
274+
"Expected an index file for sharded safe checkpoint."
275+
)
276+
277+
model_loaded = self.model_class.from_pretrained(str(tmp_path), device_map=str(torch_device))
278+
279+
output = model_loaded(**inputs, return_dict=False)[0].detach().cpu()
280+
assert_tensors_close(output, expected_output, rtol=1e-3, atol=1e-3)
281+
282+
def _test_quantization_config_serialization(self, config_kwargs):
283+
"""
284+
Test that the quantization config attached to a quantized model is serializable.
285+
286+
Args:
287+
config_kwargs: Quantization config parameters
288+
"""
289+
model = self._create_quantized_model(config_kwargs)
290+
291+
assert "quantization_config" in model.config, "Missing quantization_config"
292+
_ = model.config["quantization_config"].to_dict()
293+
_ = model.config["quantization_config"].to_diff_dict()
294+
_ = model.config["quantization_config"].to_json_string()
295+
296+
def _test_original_dtype(self, config_kwargs):
297+
"""
298+
Test that the dtype the model had before quantization is recorded on its config.
299+
300+
Args:
301+
config_kwargs: Quantization config parameters
302+
"""
303+
model = self._create_quantized_model(config_kwargs)
304+
305+
assert "_pre_quantization_dtype" in model.config, "Missing _pre_quantization_dtype"
306+
assert model.config["_pre_quantization_dtype"] in [
307+
torch.float16,
308+
torch.float32,
309+
torch.bfloat16,
310+
], f"Unexpected dtype: {model.config['_pre_quantization_dtype']}"
257311

258312
def _test_quantized_layers(self, config_kwargs):
259313
model_fp = self._load_unquantized_model()
@@ -374,6 +428,21 @@ def _test_quantization_device_map(self, config_kwargs):
374428
assert output is not None, "Model output is None"
375429
assert not torch.isnan(output).any(), "Model output contains NaN"
376430

431+
def _test_quantization_cpu_device_map(self, config_kwargs):
432+
"""
433+
Test that quantized models are placed on the CPU with device_map="cpu".
434+
435+
Args:
436+
config_kwargs: Base quantization config kwargs
437+
"""
438+
model_quantized = self._create_quantized_model(config_kwargs, device_map="cpu")
439+
440+
assert hasattr(model_quantized, "hf_device_map"), "Model should have hf_device_map attribute"
441+
assert model_quantized.hf_device_map is not None, "hf_device_map should not be None"
442+
assert model_quantized.device == torch.device("cpu"), (
443+
f"Model should be on CPU, but is on {model_quantized.device}"
444+
)
445+
377446
@torch.no_grad()
378447
def _test_dequantize(self, config_kwargs):
379448
"""
@@ -582,25 +651,10 @@ def test_bnb_quantized_layers(self, config_name):
582651
ids=list(BitsAndBytesConfigMixin.BNB_CONFIGS.keys()),
583652
)
584653
def test_bnb_quantization_config_serialization(self, config_name):
585-
model = self._create_quantized_model(BitsAndBytesConfigMixin.BNB_CONFIGS[config_name])
586-
587-
assert "quantization_config" in model.config, "Missing quantization_config"
588-
_ = model.config["quantization_config"].to_dict()
589-
_ = model.config["quantization_config"].to_diff_dict()
590-
_ = model.config["quantization_config"].to_json_string()
654+
self._test_quantization_config_serialization(BitsAndBytesConfigMixin.BNB_CONFIGS[config_name])
591655

592656
def test_bnb_original_dtype(self):
593-
config_name = list(BitsAndBytesConfigMixin.BNB_CONFIGS.keys())[0]
594-
config_kwargs = BitsAndBytesConfigMixin.BNB_CONFIGS[config_name]
595-
596-
model = self._create_quantized_model(config_kwargs)
597-
598-
assert "_pre_quantization_dtype" in model.config, "Missing _pre_quantization_dtype"
599-
assert model.config["_pre_quantization_dtype"] in [
600-
torch.float16,
601-
torch.float32,
602-
torch.bfloat16,
603-
], f"Unexpected dtype: {model.config['_pre_quantization_dtype']}"
657+
self._test_original_dtype(BitsAndBytesConfigMixin.BNB_CONFIGS["4bit_nf4"])
604658

605659
def test_bnb_keep_modules_in_fp32(self):
606660
self._test_keep_modules_in_fp32(BitsAndBytesConfigMixin.BNB_CONFIGS["4bit_nf4"])
@@ -633,15 +687,9 @@ def test_bnb_training(self):
633687
list(BitsAndBytesConfigMixin.BNB_CONFIGS.keys()),
634688
ids=list(BitsAndBytesConfigMixin.BNB_CONFIGS.keys()),
635689
)
636-
def test_cpu_device_map(self, config_name):
637-
config_kwargs = BitsAndBytesConfigMixin.BNB_CONFIGS[config_name]
638-
model_quantized = self._create_quantized_model(config_kwargs, device_map="cpu")
639-
640-
assert hasattr(model_quantized, "hf_device_map"), "Model should have hf_device_map attribute"
641-
assert model_quantized.hf_device_map is not None, "hf_device_map should not be None"
642-
assert model_quantized.device == torch.device("cpu"), (
643-
f"Model should be on CPU, but is on {model_quantized.device}"
644-
)
690+
def test_bnb_cpu_device_map(self, config_name):
691+
"""Test that device_map='cpu' works correctly with quantization."""
692+
self._test_quantization_cpu_device_map(BitsAndBytesConfigMixin.BNB_CONFIGS[config_name])
645693

646694

647695
@is_quantization
@@ -922,57 +970,15 @@ def test_torchao_quantization_lora_inference(self, quant_type):
922970
@pytest.mark.parametrize("quant_type", ["int8wo"], ids=["int8wo"])
923971
@require_torchao_version_greater_or_equal("0.16.0")
924972
def test_torchao_quantization_serialization(self, quant_type, tmp_path):
925-
config_kwargs = TorchAoConfigMixin.TORCHAO_QUANT_TYPES[quant_type]
926-
model = self._create_quantized_model(config_kwargs)
927-
inputs = self.get_dummy_inputs()
928-
929-
with torch.no_grad():
930-
expected_output = model(**inputs, return_dict=False)[0].detach().cpu()
931-
932-
model.save_pretrained(str(tmp_path), safe_serialization=True)
933-
del model
934-
gc.collect()
935-
backend_empty_cache(torch_device)
936-
937-
model_loaded = self.model_class.from_pretrained(
938-
str(tmp_path), device_map=str(torch_device), use_safetensors=True
939-
)
940-
941-
with torch.no_grad():
942-
output = model_loaded(**inputs, return_dict=False)[0].detach().cpu()
943-
944-
assert_tensors_close(output, expected_output, rtol=1e-3, atol=1e-3)
973+
self._test_quantization_serialization(TorchAoConfigMixin.TORCHAO_QUANT_TYPES[quant_type], tmp_path)
945974

946975
@pytest.mark.parametrize("quant_type", ["int8dq"], ids=["int8dq"])
947976
@require_torchao_version_greater_or_equal("0.16.0")
948977
def test_torchao_quantization_sharded_serialization(self, quant_type, tmp_path):
949-
config_kwargs = TorchAoConfigMixin.TORCHAO_QUANT_TYPES[quant_type]
950-
model = self._create_quantized_model(config_kwargs)
951-
inputs = self.get_dummy_inputs()
952-
953-
with torch.no_grad():
954-
expected_output = model(**inputs, return_dict=False)[0].detach().cpu()
955-
956-
model.save_pretrained(str(tmp_path), safe_serialization=True, max_shard_size="16KB")
957-
del model
958-
gc.collect()
959-
backend_empty_cache(torch_device)
960-
961-
shard_files = list(tmp_path.glob("*.safetensors"))
962-
assert len(shard_files) > 1, "Expected a sharded safe-serialization checkpoint."
963-
assert any(path.name.endswith(".index.json") for path in tmp_path.iterdir()), (
964-
"Expected an index file for sharded safe checkpoint."
978+
self._test_quantization_serialization(
979+
TorchAoConfigMixin.TORCHAO_QUANT_TYPES[quant_type], tmp_path, max_shard_size="16KB"
965980
)
966981

967-
model_loaded = self.model_class.from_pretrained(
968-
str(tmp_path), device_map=str(torch_device), use_safetensors=True
969-
)
970-
971-
with torch.no_grad():
972-
output = model_loaded(**inputs, return_dict=False)[0].detach().cpu()
973-
974-
assert_tensors_close(output, expected_output, rtol=1e-3, atol=1e-3)
975-
976982
def test_torchao_modules_to_not_convert(self):
977983
"""Test that modules_to_not_convert parameter works correctly."""
978984
modules_to_exclude = getattr(self, "modules_to_not_convert_for_test", None)
@@ -1354,12 +1360,14 @@ def _test_torch_compile(self, config_kwargs, fullgraph=True, error_on_recompile=
13541360
13551361
Args:
13561362
config_kwargs: Quantization config parameters
1363+
fullgraph: Whether the compiled model is required to compile without graph breaks
1364+
error_on_recompile: Whether a recompilation during the forward pass should fail the test
13571365
"""
13581366
model = self._create_quantized_model(config_kwargs)
13591367
model.to(torch_device)
13601368
model.eval()
13611369

1362-
model.compile(fullgraph=True)
1370+
model.compile(fullgraph=fullgraph)
13631371

13641372
with torch._dynamo.config.patch(error_on_recompile=error_on_recompile):
13651373
inputs = self.get_dummy_inputs()
@@ -1695,10 +1703,6 @@ class AutoRoundConfigMixin:
16951703

16961704
config_dict = {"backend": "auto"}
16971705

1698-
def _load_unquantized_model(self):
1699-
kwargs = getattr(self, "pretrained_model_kwargs", {})
1700-
return self.model_class.from_pretrained(self.pretrained_model_name_or_path, **kwargs)
1701-
17021706
def _create_quantized_model(self, config_kwargs, **extra_kwargs):
17031707
config = AutoRoundConfig(**config_kwargs)
17041708
kwargs = getattr(self, "pretrained_model_kwargs", {}).copy()

0 commit comments

Comments
 (0)