@@ -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