diff --git a/.github/workflows/nightly_tests.yml b/.github/workflows/nightly_tests.yml index c1542e9a04aa..e72189186a49 100644 --- a/.github/workflows/nightly_tests.yml +++ b/.github/workflows/nightly_tests.yml @@ -350,9 +350,6 @@ jobs: - backend: "torchao" marker: "torchao" additional_deps: ["mslk"] - - backend: "optimum_quanto" - marker: "quanto" - additional_deps: [] - backend: "nvidia_modelopt" marker: "modelopt" additional_deps: [] @@ -428,7 +425,7 @@ jobs: run: | printf 'torch==2.10.0\ntorchvision==0.25.0\ntorchaudio==2.10.0\n' > "$UV_OVERRIDE" uv pip install -e ".[quality]" - uv pip install -U bitsandbytes optimum_quanto + uv pip install -U bitsandbytes uv pip uninstall transformers huggingface_hub && UV_PRERELEASE=allow uv pip install -U transformers@git+https://github.com/huggingface/transformers.git uv pip install pytest-reportlog - name: Environment diff --git a/docs/source/en/quantization/quanto.md b/docs/source/en/quantization/quanto.md index 1dd4c05e1cbe..b0bd13f8677c 100644 --- a/docs/source/en/quantization/quanto.md +++ b/docs/source/en/quantization/quanto.md @@ -13,6 +13,10 @@ specific language governing permissions and limitations under the License. # Quanto +> [!WARNING] +> The Quanto backend is deprecated and will be removed in version 1.0.0. Consider switching to one of the other +> supported [quantization backends](./overview), such as [bitsandbytes](./bitsandbytes) or [torchao](./torchao). + [Quanto](https://github.com/huggingface/optimum-quanto) is a PyTorch quantization backend for [Optimum](https://huggingface.co/docs/optimum/en/index). It has been designed with versatility and simplicity in mind: - All features are available in eager mode (works with non-traceable models) diff --git a/src/diffusers/quantizers/quantization_config.py b/src/diffusers/quantizers/quantization_config.py index ea78b5f7ff53..30e89f53f906 100644 --- a/src/diffusers/quantizers/quantization_config.py +++ b/src/diffusers/quantizers/quantization_config.py @@ -653,6 +653,13 @@ class QuantoConfig(QuantizationConfigMixin): This is a wrapper class about all possible attributes and features that you can play with a model that has been loaded using `quanto`. + + + `QuantoConfig` is deprecated and will be removed in version 1.0.0. Consider switching to one of the other supported + quantization backends, such as [`BitsAndBytesConfig`] or [`TorchAoConfig`]. + + + Args: weights_dtype (`str`, *optional*, defaults to `"int8"`): The target dtype for the weights after quantization. Supported values are ("float8","int8","int4","int2") diff --git a/src/diffusers/quantizers/quanto/quanto_quantizer.py b/src/diffusers/quantizers/quanto/quanto_quantizer.py index 9a04291c883a..92bbecbb1b53 100644 --- a/src/diffusers/quantizers/quanto/quanto_quantizer.py +++ b/src/diffusers/quantizers/quanto/quanto_quantizer.py @@ -33,6 +33,13 @@ class QuantoQuantizer(DiffusersQuantizer): r""" Diffusers Quantizer for Optimum Quanto + + + + The Quanto backend is deprecated and will be removed in version 1.0.0. Consider switching to one of the other + supported quantization backends, such as `bitsandbytes` or `torchao`. + + """ use_keep_in_fp32_modules = True diff --git a/tests/conftest.py b/tests/conftest.py index b6f8c18183c7..d0401281e37d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -43,7 +43,6 @@ def pytest_configure(config): config.addinivalue_line("markers", "single_file: marks tests for single file checkpoint loading") config.addinivalue_line("markers", "quantization: marks tests for quantization functionality") config.addinivalue_line("markers", "bitsandbytes: marks tests for BitsAndBytes quantization functionality") - config.addinivalue_line("markers", "quanto: marks tests for Quanto quantization functionality") config.addinivalue_line("markers", "torchao: marks tests for TorchAO quantization functionality") config.addinivalue_line("markers", "gguf: marks tests for GGUF quantization functionality") config.addinivalue_line("markers", "modelopt: marks tests for NVIDIA ModelOpt quantization functionality") diff --git a/tests/models/testing_utils/__init__.py b/tests/models/testing_utils/__init__.py index 0e9b7ebb4aa0..760a3fac04e0 100644 --- a/tests/models/testing_utils/__init__.py +++ b/tests/models/testing_utils/__init__.py @@ -40,9 +40,6 @@ NunchakuLiteTesterMixin, QuantizationCompileTesterMixin, QuantizationTesterMixin, - QuantoCompileTesterMixin, - QuantoConfigMixin, - QuantoTesterMixin, SDNQCompileTesterMixin, SDNQConfigMixin, SDNQTesterMixin, @@ -97,9 +94,6 @@ "TaylorSeerCacheTesterMixin", "QuantizationCompileTesterMixin", "QuantizationTesterMixin", - "QuantoCompileTesterMixin", - "QuantoConfigMixin", - "QuantoTesterMixin", "run_nondeterministic", "SDNQCompileTesterMixin", "SDNQConfigMixin", diff --git a/tests/models/testing_utils/quantization.py b/tests/models/testing_utils/quantization.py index 918126fe3f13..8285704ec2a5 100644 --- a/tests/models/testing_utils/quantization.py +++ b/tests/models/testing_utils/quantization.py @@ -25,7 +25,6 @@ GGUFQuantizationConfig, NunchakuLiteQuantizationConfig, NVIDIAModelOptConfig, - QuantoConfig, SDNQConfig, TorchAoConfig, ) @@ -34,7 +33,6 @@ is_gguf_available, is_kernels_available, is_nvidia_modelopt_available, - is_optimum_quanto_available, is_peft_available, is_torchao_available, ) @@ -42,14 +40,11 @@ from ...testing_utils import ( assert_tensors_close, backend_empty_cache, - backend_max_memory_allocated, - backend_reset_peak_memory_stats, is_autoround, is_bitsandbytes, is_gguf, is_modelopt, is_quantization, - is_quanto, is_sdnq, is_torch_compile, is_torchao, @@ -59,7 +54,6 @@ require_bitsandbytes_version_greater, require_gguf_version_greater_or_equal, require_modelopt_version_greater_or_equal, - require_quanto, require_sdnq, require_torchao_version_greater_or_equal, torch_device, @@ -77,9 +71,6 @@ if is_bitsandbytes_available(): import bitsandbytes as bnb -if is_optimum_quanto_available(): - from optimum.quanto import QLinear - if is_gguf_available(): pass @@ -119,7 +110,7 @@ class QuantizationTesterMixin: Backend-specific mixins should: 1. Implement _create_quantized_model(config_kwargs) 2. Implement _verify_if_layer_quantized(name, module, config_kwargs) - 3. Define their config dict (e.g., BNB_CONFIGS, QUANTO_WEIGHT_TYPES, etc.) + 3. Define their config dict (e.g., BNB_CONFIGS, TORCHAO_CONFIGS, etc.) 4. Use @pytest.mark.parametrize to create tests that call the common test methods below Expected class attributes: @@ -777,153 +768,6 @@ def test_bnb_cpu_device_map(self, config_name): self._test_quantization_cpu_device_map(BitsAndBytesConfigMixin.BNB_CONFIGS[config_name]) -@is_quantization -@is_quanto -@require_quanto -@require_accelerate -@require_accelerator -class QuantoConfigMixin: - """ - Base mixin providing Quanto quantization config and model creation. - - Expected class attributes: - - model_class: The model class to test - - pretrained_model_name_or_path: Hub repository ID for the pretrained model - - pretrained_model_kwargs: (Optional) Dict of kwargs to pass to from_pretrained - """ - - QUANTO_WEIGHT_TYPES = { - "float8": {"weights_dtype": "float8"}, - "int8": {"weights_dtype": "int8"}, - "int4": {"weights_dtype": "int4"}, - "int2": {"weights_dtype": "int2"}, - } - - QUANTO_EXPECTED_MEMORY_REDUCTIONS = { - "float8": 1.5, - "int8": 1.5, - "int4": 3.0, - "int2": 7.0, - } - - def _create_quantized_model(self, config_kwargs, **extra_kwargs): - config = QuantoConfig(**config_kwargs) - kwargs = getattr(self, "pretrained_model_kwargs", {}).copy() - kwargs["quantization_config"] = config - kwargs.update(extra_kwargs) - return self.model_class.from_pretrained(self.pretrained_model_name_or_path, **kwargs) - - def _verify_if_layer_quantized(self, name, module, config_kwargs): - assert isinstance(module, QLinear), f"Layer {name} is not QLinear, got {type(module)}" - - def _test_quantization_memory_footprint(self, config_kwargs, expected_memory_reduction=1.2): - """Override to use max_memory_allocated for Quanto (get_memory_footprint doesn't reflect quantized _data).""" - # Measure unquantized model memory - backend_reset_peak_memory_stats(torch_device) - backend_empty_cache(torch_device) - - model = self._load_unquantized_model() - model.to(torch_device) - mem = backend_max_memory_allocated(torch_device) - - del model - gc.collect() - backend_empty_cache(torch_device) - - # Measure quantized model memory - backend_reset_peak_memory_stats(torch_device) - - model_quantized = self._create_quantized_model(config_kwargs) - model_quantized.to(torch_device) - mem_quantized = backend_max_memory_allocated(torch_device) - - ratio = mem / mem_quantized - assert ratio >= expected_memory_reduction, ( - f"Memory ratio {ratio:.2f} is less than expected ({expected_memory_reduction}x). unquantized={mem}, quantized={mem_quantized}" - ) - - -@is_quanto -@require_quanto -@require_accelerate -@require_accelerator -class QuantoTesterMixin(QuantoConfigMixin, QuantizationTesterMixin): - """ - Mixin class for testing Quanto quantization on models. - - Expected class attributes: - - model_class: The model class to test - - pretrained_model_name_or_path: Hub repository ID for the pretrained model - - pretrained_model_kwargs: (Optional) Dict of kwargs to pass to from_pretrained (e.g., {"subfolder": "transformer"}) - - Expected methods to be implemented by subclasses: - - get_dummy_inputs(): Returns dict of inputs to pass to the model forward pass - - Optional class attributes: - - QUANTO_WEIGHT_TYPES: Dict of weight_type_name -> qtype - - Pytest mark: quanto - Use `pytest -m "not quanto"` to skip these tests - """ - - @pytest.mark.parametrize( - "weight_type_name", - list(QuantoConfigMixin.QUANTO_WEIGHT_TYPES.keys()), - ids=list(QuantoConfigMixin.QUANTO_WEIGHT_TYPES.keys()), - ) - def test_quanto_quantization_num_parameters(self, weight_type_name): - self._test_quantization_num_parameters(QuantoConfigMixin.QUANTO_WEIGHT_TYPES[weight_type_name]) - - @pytest.mark.parametrize( - "weight_type_name", - list(QuantoConfigMixin.QUANTO_WEIGHT_TYPES.keys()), - ids=list(QuantoConfigMixin.QUANTO_WEIGHT_TYPES.keys()), - ) - def test_quanto_quantization_memory_footprint(self, weight_type_name): - expected = QuantoConfigMixin.QUANTO_EXPECTED_MEMORY_REDUCTIONS.get(weight_type_name, 1.2) - self._test_quantization_memory_footprint( - QuantoConfigMixin.QUANTO_WEIGHT_TYPES[weight_type_name], expected_memory_reduction=expected - ) - - @pytest.mark.parametrize( - "weight_type_name", - list(QuantoConfigMixin.QUANTO_WEIGHT_TYPES.keys()), - ids=list(QuantoConfigMixin.QUANTO_WEIGHT_TYPES.keys()), - ) - def test_quanto_quantization_inference(self, weight_type_name): - self._test_quantization_inference(QuantoConfigMixin.QUANTO_WEIGHT_TYPES[weight_type_name]) - - @pytest.mark.parametrize("weight_type_name", ["int8"], ids=["int8"]) - def test_quanto_quantized_layers(self, weight_type_name): - self._test_quantized_layers(QuantoConfigMixin.QUANTO_WEIGHT_TYPES[weight_type_name]) - - @pytest.mark.parametrize("weight_type_name", ["int8"], ids=["int8"]) - def test_quanto_quantization_lora_inference(self, weight_type_name): - self._test_quantization_lora_inference(QuantoConfigMixin.QUANTO_WEIGHT_TYPES[weight_type_name]) - - @pytest.mark.parametrize("weight_type_name", ["int8"], ids=["int8"]) - def test_quanto_quantization_serialization(self, weight_type_name, tmp_path): - self._test_quantization_serialization(QuantoConfigMixin.QUANTO_WEIGHT_TYPES[weight_type_name], tmp_path) - - def test_quanto_modules_to_not_convert(self): - """Test that modules_to_not_convert parameter works correctly.""" - modules_to_exclude = getattr(self, "modules_to_not_convert_for_test", None) - if modules_to_exclude is None: - pytest.skip("modules_to_not_convert_for_test not defined for this model") - - self._test_quantization_modules_to_not_convert( - QuantoConfigMixin.QUANTO_WEIGHT_TYPES["int8"], modules_to_exclude - ) - - def test_quanto_device_map(self): - """Test that device_map='auto' works correctly with quantization.""" - self._test_quantization_device_map(QuantoConfigMixin.QUANTO_WEIGHT_TYPES["int8"]) - - def test_quanto_dequantize(self): - """Test that dequantize() works correctly.""" - self._test_dequantize(QuantoConfigMixin.QUANTO_WEIGHT_TYPES["int8"]) - - @is_quantization @is_torchao @require_accelerator @@ -1599,35 +1443,6 @@ def test_bnb_torch_compile_with_group_offload(self, config_name): self._test_torch_compile_with_group_offload(BitsAndBytesConfigMixin.BNB_CONFIGS[config_name]) -@is_quanto -@require_quanto -@require_accelerate -@require_accelerator -class QuantoCompileTesterMixin(QuantoConfigMixin, QuantizationCompileTesterMixin): - """ - Mixin class for testing torch.compile with Quanto quantized models. - - Expected class attributes: - - model_class: The model class to test - - pretrained_model_name_or_path: Hub repository ID for the pretrained model - - pretrained_model_kwargs: (Optional) Dict of kwargs to pass to from_pretrained - - Expected methods to be implemented by subclasses: - - get_dummy_inputs(): Returns dict of inputs to pass to the model forward pass - - Pytest mark: quanto - Use `pytest -m "not quanto"` to skip these tests - """ - - @pytest.mark.parametrize("weight_type_name", ["int8"], ids=["int8"]) - def test_quanto_torch_compile(self, weight_type_name): - self._test_torch_compile(QuantoConfigMixin.QUANTO_WEIGHT_TYPES[weight_type_name]) - - @pytest.mark.parametrize("weight_type_name", ["int8"], ids=["int8"]) - def test_quanto_torch_compile_with_group_offload(self, weight_type_name): - self._test_torch_compile_with_group_offload(QuantoConfigMixin.QUANTO_WEIGHT_TYPES[weight_type_name]) - - @is_torchao @require_accelerator @require_torchao_version_greater_or_equal("0.7.0") diff --git a/tests/models/transformers/test_models_transformer_flux.py b/tests/models/transformers/test_models_transformer_flux.py index d60e34e2ea3f..53af9eedc50c 100644 --- a/tests/models/transformers/test_models_transformer_flux.py +++ b/tests/models/transformers/test_models_transformer_flux.py @@ -48,8 +48,6 @@ ModelOptCompileTesterMixin, ModelOptTesterMixin, ModelTesterMixin, - QuantoCompileTesterMixin, - QuantoTesterMixin, SDNQCompileTesterMixin, SDNQTesterMixin, SingleFileTesterMixin, @@ -406,18 +404,6 @@ def get_dummy_inputs(self): return {k: v.to(self.torch_dtype) if torch.is_floating_point(v) else v for k, v in inputs.items()} -class TestFluxTransformerQuanto(FluxTransformerTesterConfig, QuantoTesterMixin): - """Quanto quantization tests for Flux Transformer.""" - - @property - def pretrained_model_name_or_path(self): - return "hf-internal-testing/tiny-flux-transformer" - - @property - def pretrained_model_kwargs(self): - return {} - - class TestFluxTransformerTorchAo(FluxTransformerTesterConfig, TorchAoTesterMixin): """TorchAO quantization tests for Flux Transformer.""" @@ -473,10 +459,6 @@ def test_loading_gguf_diffusers_format(self): model(**self.get_dummy_inputs()) -class TestFluxTransformerQuantoCompile(FluxTransformerTesterConfig, QuantoCompileTesterMixin): - """Quanto + compile tests for Flux Transformer.""" - - class TestFluxTransformerTorchAoCompile(FluxTransformerTesterConfig, TorchAoCompileTesterMixin): """TorchAO + compile tests for Flux Transformer.""" diff --git a/tests/pipelines/testing_utils/quantization.py b/tests/pipelines/testing_utils/quantization.py index 547751b9972f..813d1228647e 100644 --- a/tests/pipelines/testing_utils/quantization.py +++ b/tests/pipelines/testing_utils/quantization.py @@ -47,7 +47,6 @@ FluxTransformer2DModel, GGUFQuantizationConfig, NVIDIAModelOptConfig, - QuantoConfig, SD3Transformer2DModel, StableDiffusion3Pipeline, TorchAoConfig, @@ -80,7 +79,6 @@ require_modelopt_version_greater_or_equal, require_peft_backend, require_peft_version_greater, - require_quanto, require_torch, require_torch_accelerator, require_torch_version_greater, @@ -173,7 +171,7 @@ def test_torch_compile_with_cpu_offload(self): @is_quantization @require_bitsandbytes_version_greater("0.43.2") -@require_quanto +@require_torchao_version_greater_or_equal("0.16.0") @require_accelerate @require_torch @require_torch_accelerator @@ -212,7 +210,7 @@ def test_quant_config_set_correctly_through_kwargs(self): def test_quant_config_set_correctly_through_granular(self): quant_config = PipelineQuantizationConfig( quant_mapping={ - "transformer": QuantoConfig(weights_dtype="int8"), + "transformer": TorchAoConfig(Int8WeightOnlyConfig(version=2)), "text_encoder_2": TranBitsAndBytesConfig(load_in_4bit=True, compute_dtype=torch.bfloat16), } ) @@ -231,7 +229,7 @@ def test_quant_config_set_correctly_through_granular(self): assert quantization_config.load_in_4bit assert quantization_config.quant_method == "bitsandbytes" else: - assert quantization_config.quant_method == "quanto" + assert quantization_config.quant_method == "torchao" _ = pipe(self.prompt, num_inference_steps=self.num_inference_steps) @@ -239,7 +237,7 @@ def test_raises_error_for_invalid_config(self): with pytest.raises(ValueError) as err_context: _ = PipelineQuantizationConfig( quant_mapping={ - "transformer": QuantoConfig(weights_dtype="int8"), + "transformer": TorchAoConfig(Int8WeightOnlyConfig(version=2)), "text_encoder_2": TranBitsAndBytesConfig(load_in_4bit=True, compute_dtype=torch.bfloat16), }, quant_backend="bitsandbytes_4bit", @@ -253,8 +251,8 @@ def test_validation_for_kwargs(self): components_to_quantize = ["transformer", "text_encoder_2"] with pytest.raises(ValueError) as err_context: _ = PipelineQuantizationConfig( - quant_backend="quanto", - quant_kwargs={"weights_dtype": "int8"}, + quant_backend="torchao", + quant_kwargs={"quant_type": Int8WeightOnlyConfig(version=2)}, components_to_quantize=components_to_quantize, ) @@ -262,7 +260,7 @@ def test_validation_for_kwargs(self): def test_raises_error_for_wrong_config_class(self): quant_config = { - "transformer": QuantoConfig(weights_dtype="int8"), + "transformer": TorchAoConfig(Int8WeightOnlyConfig(version=2)), "text_encoder_2": TranBitsAndBytesConfig(load_in_4bit=True, compute_dtype=torch.bfloat16), } with pytest.raises(ValueError) as err_context: @@ -287,7 +285,7 @@ def test_validation_for_mapping(self): def test_saving_loading(self): quant_config = PipelineQuantizationConfig( quant_mapping={ - "transformer": QuantoConfig(weights_dtype="int8"), + "transformer": TorchAoConfig(Int8WeightOnlyConfig(version=2)), "text_encoder_2": TranBitsAndBytesConfig(load_in_4bit=True, compute_dtype=torch.bfloat16), } ) @@ -313,7 +311,7 @@ def test_saving_loading(self): assert quantization_config.load_in_4bit assert quantization_config.quant_method == "bitsandbytes" else: - assert quantization_config.quant_method == "quanto" + assert quantization_config.quant_method == "torchao" output_2 = loaded_pipe(**pipe_inputs, generator=torch.manual_seed(self.seed)).images @@ -332,7 +330,7 @@ def test_warn_invalid_component(self, method): else: quant_config = PipelineQuantizationConfig( quant_mapping={ - "transformer": QuantoConfig("int8"), + "transformer": TorchAoConfig(Int8WeightOnlyConfig(version=2)), invalid_component: TranBitsAndBytesConfig(load_in_8bit=True), } ) @@ -1021,9 +1019,6 @@ def test_torch_compile_with_cpu_offload(self): super()._test_torch_compile_with_cpu_offload(torch_dtype=torch.float16) -# ======================== Quanto ======================== - - # ======================== TorchAO ======================== diff --git a/tests/testing_utils.py b/tests/testing_utils.py index 27d5c0bb3662..c35f975285c4 100644 --- a/tests/testing_utils.py +++ b/tests/testing_utils.py @@ -42,7 +42,6 @@ is_nvidia_modelopt_version, is_onnx_available, is_opencv_available, - is_optimum_quanto_available, is_peft_available, is_sdnq_available, is_timm_available, @@ -429,14 +428,6 @@ def is_bitsandbytes(test_case): return pytest.mark.bitsandbytes(test_case) -def is_quanto(test_case): - """ - Decorator marking a test as a Quanto quantization test. These tests can be filtered using: - pytest -m "not quanto" to skip pytest -m quanto to run only these tests - """ - return pytest.mark.quanto(test_case) - - def is_torchao(test_case): """ Decorator marking a test as a TorchAO quantization test. These tests can be filtered using: @@ -762,13 +753,6 @@ def require_bitsandbytes(test_case): return pytest.mark.skipif(not is_bitsandbytes_available(), reason="test requires bitsandbytes")(test_case) -def require_quanto(test_case): - """ - Decorator marking a test that requires quanto. These tests are skipped when quanto isn't installed. - """ - return pytest.mark.skipif(not is_optimum_quanto_available(), reason="test requires quanto")(test_case) - - def require_sdnq(test_case): """ Decorator marking a test that requires sdnq. These tests are skipped when sdnq isn't installed. diff --git a/utils/generate_model_tests.py b/utils/generate_model_tests.py index 67ea5612ac12..c006a71580db 100644 --- a/utils/generate_model_tests.py +++ b/utils/generate_model_tests.py @@ -55,13 +55,11 @@ OPTIONAL_TESTERS = [ # Quantization testers ("BitsAndBytesTesterMixin", "bnb"), - ("QuantoTesterMixin", "quanto"), ("TorchAoTesterMixin", "torchao"), ("GGUFTesterMixin", "gguf"), ("ModelOptTesterMixin", "modelopt"), # Quantization compile testers ("BitsAndBytesCompileTesterMixin", "bnb_compile"), - ("QuantoCompileTesterMixin", "quanto_compile"), ("TorchAoCompileTesterMixin", "torchao_compile"), ("GGUFCompileTesterMixin", "gguf_compile"), ("ModelOptCompileTesterMixin", "modelopt_compile"), @@ -373,7 +371,7 @@ def generate_test_class(model_name: str, config_class: str, tester: str) -> str: " return {}", ] ) - elif tester in ["BitsAndBytesTesterMixin", "QuantoTesterMixin", "TorchAoTesterMixin", "ModelOptTesterMixin"]: + elif tester in ["BitsAndBytesTesterMixin", "TorchAoTesterMixin", "ModelOptTesterMixin"]: lines.extend( [ " def get_dummy_inputs(self) -> dict[str, torch.Tensor]:", @@ -383,7 +381,6 @@ def generate_test_class(model_name: str, config_class: str, tester: str) -> str: ) elif tester in [ "BitsAndBytesCompileTesterMixin", - "QuantoCompileTesterMixin", "TorchAoCompileTesterMixin", "ModelOptCompileTesterMixin", ]: @@ -525,12 +522,10 @@ def main(): default=[], choices=[ "bnb", - "quanto", "torchao", "gguf", "modelopt", "bnb_compile", - "quanto_compile", "torchao_compile", "gguf_compile", "modelopt_compile",