-
Notifications
You must be signed in to change notification settings - Fork 7.3k
[Quantization] Add support for Comfy-Kitchen / Comfy-Quants #14747
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 11 commits
2dd589f
78054ff
eb4811c
b185a13
a8b2e12
0317874
65587a0
0c97d8d
e38bf7f
2452aa8
e9bca91
97c216d
6e450d5
2b6f56e
5eb91a7
c680d23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| <!--Copyright 2026 The HuggingFace Team. All rights reserved. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
| the License. You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
| an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
| specific language governing permissions and limitations under the License. | ||
|
|
||
| --> | ||
|
|
||
| # Comfy Quant | ||
|
|
||
| The [Comfy Quant](https://github.com/Comfy-Org/comfy-quants) toolkit provides state-of-the-art quantization techniques. While `comfy-quants` is used for exporting and quantizing models, Diffusers natively supports running inference on these models using the [comfy-kitchen](https://github.com/Comfy-Org/comfy-kitchen) library. | ||
|
|
||
| `comfy-kitchen` provides highly optimized GPU kernels that allow you to seamlessly run quantized layers. By passing a `ComfyQuantConfig` to Diffusers, the library will dynamically intercept parameters and wrap them in a `QuantizedTensor` that maps directly to the optimized `comfy-kitchen` layouts. | ||
|
|
||
| Before starting, please install `comfy-kitchen` in your environment: | ||
|
|
||
| ```shell | ||
| pip install comfy-kitchen | ||
| ``` | ||
|
|
||
| ## Loading a Comfy Quant Model | ||
|
|
||
| To load a model prequantized with Comfy Quant, use the [`~FromSingleFileMixin.from_single_file`] method and pass in the [`ComfyQuantConfig`]. | ||
|
|
||
| The configuration requires you to specify the `quant_format` that the model was quantized in, and the `compute_dtype` for active inference calculations. | ||
|
|
||
| The following example demonstrates how to load a quantized FLUX transformer: | ||
|
|
||
| ```python | ||
| import torch | ||
| from diffusers import FluxPipeline, FluxTransformer2DModel, ComfyQuantConfig | ||
|
|
||
| ckpt_path = "path/to/comfy_quant_checkpoint.safetensors" | ||
|
|
||
| # Initialize the config with your desired format and compute dtype | ||
| quantization_config = ComfyQuantConfig( | ||
| quant_format="fp8", | ||
| compute_dtype=torch.bfloat16 | ||
| ) | ||
|
|
||
| # Load the transformer directly from the safetensors file | ||
| transformer = FluxTransformer2DModel.from_single_file( | ||
| ckpt_path, | ||
| quantization_config=quantization_config, | ||
| dtype=torch.bfloat16, | ||
| ) | ||
|
|
||
| # Pass the quantized transformer into the pipeline | ||
| pipe = FluxPipeline.from_pretrained( | ||
| "black-forest-labs/FLUX.1-dev", | ||
| transformer=transformer, | ||
| dtype=torch.bfloat16, | ||
| ) | ||
| pipe.enable_model_cpu_offload() | ||
|
|
||
| prompt = "A cat holding a sign that says hello world" | ||
| image = pipe(prompt, generator=torch.manual_seed(0)).images[0] | ||
| image.save("flux-comfy-quant.png") | ||
| ``` | ||
|
|
||
| ## Supported Quantization Formats | ||
|
|
||
| Diffusers currently maps the following Comfy Quant formats to `comfy-kitchen` layouts: | ||
|
|
||
| - **FP8** (`fp8`): Maps to `TensorCoreFP8Layout` (E4M3/E5M2) | ||
| - **INT8** (`int8`): Maps to `TensorCoreInt8Layout` (W8A8, tensorwise) | ||
| - **MXFP8** (`mxfp8`): Maps to `TensorCoreMXFP8Layout` | ||
| - **NVFP4** (`nvfp4`): Maps to `TensorCoreNVFP4Layout` | ||
| - **INT4 SVD** (`int4_svd`): Maps to `SVDQuantW4A4Layout` (SVDQuant W4A4) | ||
| - **INT4 AWQ** (`int4_awq`): Maps to `AWQW4A16Layout` (AWQ W4A16) | ||
|
|
||
| When using optimized layouts, `comfy-kitchen` automatically dispatches the operations to the best available backend (HIP, CUDA, Triton, or Eager). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from .comfy_quantizer import ComfyQuantizer |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| from typing import TYPE_CHECKING, Any | ||
|
|
||
| from ...utils import ( | ||
| get_module_from_name, | ||
| is_torch_available, | ||
| logging, | ||
| ) | ||
| from ..base import DiffusersQuantizer | ||
|
|
||
|
|
||
| if TYPE_CHECKING: | ||
| from ...models.modeling_utils import ModelMixin | ||
|
|
||
| if is_torch_available(): | ||
| import torch | ||
|
|
||
|
|
||
| logger = logging.get_logger(__name__) | ||
|
|
||
|
|
||
| class ComfyQuantizer(DiffusersQuantizer): | ||
| """ | ||
| Quantizer for comfy-kitchen formats (FP8, INT8, etc.). | ||
| """ | ||
|
|
||
| use_keep_in_fp32_modules = True | ||
| requires_calibration = False | ||
| required_packages = ["comfy_kitchen"] | ||
|
|
||
| def __init__(self, quantization_config, **kwargs): | ||
| super().__init__(quantization_config, **kwargs) | ||
| self.quant_format = getattr(quantization_config, "quant_format", "fp8") | ||
| self.compute_dtype = quantization_config.compute_dtype | ||
| self.modules_to_not_convert = quantization_config.modules_to_not_convert or [] | ||
| if not isinstance(self.modules_to_not_convert, list): | ||
| self.modules_to_not_convert = [self.modules_to_not_convert] | ||
|
|
||
| def validate_environment(self, *args, **kwargs): | ||
| from ...utils.import_utils import is_comfy_kitchen_available | ||
|
|
||
| if not is_comfy_kitchen_available(): | ||
| raise ImportError( | ||
| "Loading Comfy Quant weights requires `comfy-kitchen`. " | ||
| "Please install it with: `pip install comfy-kitchen`." | ||
| ) | ||
|
|
||
| def check_if_quantized_param( | ||
| self, | ||
| model: "ModelMixin", | ||
| param_value: "torch.Tensor", | ||
| param_name: str, | ||
| state_dict: dict[str, Any], | ||
| **kwargs, | ||
| ) -> bool: | ||
| # Based on comfy_kitchen, we will likely wrap tensors based on some config layout. | ||
| # For now, we assume all linear weights that aren't excluded are quantized. | ||
| # This will be refined based on comfy-kitchen's actual detection logic. | ||
| if any(m in param_name.split(".") for m in self.modules_to_not_convert): | ||
| return False | ||
| return True | ||
|
|
||
| def create_quantized_param( | ||
| self, | ||
| model: "ModelMixin", | ||
| param_value: "torch.Tensor", | ||
| param_name: str, | ||
| target_device: "torch.device", | ||
| state_dict: dict[str, Any] | None = None, | ||
| unexpected_keys: list[str] | None = None, | ||
| **kwargs, | ||
| ): | ||
| module, tensor_name = get_module_from_name(model, param_name) | ||
|
|
||
| import comfy_kitchen.tensor as ck_tensor | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can run a |
||
|
|
||
| layout_map = { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a few of these are not real layout objects. Please check. |
||
| "fp8": getattr(ck_tensor, "TensorCoreFP8Layout", None), | ||
| "nvfp4": getattr(ck_tensor, "TensorCoreNVFP4Layout", None), | ||
| "mxfp8": getattr(ck_tensor, "TensorCoreMXFP8Layout", None), | ||
| "int8": getattr(ck_tensor, "Int8Layout", None), | ||
| "int4_svd": getattr(ck_tensor, "SVDQuantW4A4Layout", None), | ||
| "int4_awq": getattr(ck_tensor, "AWQW4A16Layout", None), | ||
| } | ||
|
|
||
| # Check if it's already a QuantizedTensor (e.g., if loaded directly from a custom loader) | ||
| if isinstance(param_value, ck_tensor.QuantizedTensor): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wouldn't work since the state dict would contain just |
||
| quantized_weight = param_value | ||
| else: | ||
| layout = layout_map.get(self.quant_format.lower()) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this is a constant for a given quant format? Can't we set it once on init? |
||
| if layout is None: | ||
| raise ValueError(f"The layout for '{self.quant_format}' was not found in `comfy_kitchen`.") | ||
|
|
||
| # comfy-kitchen natively handles wrapping standard float tensors via from_float | ||
| # If the tensor is pre-quantized raw bytes, comfy-kitchen exposes `.from_quantized(...)` or similar internally, | ||
| # but `.from_float` guarantees we intercept float weights (e.g. standard safetensors float weights). | ||
| quantized_weight = ck_tensor.QuantizedTensor.from_float(param_value.to(target_device), layout.__name__) | ||
|
|
||
| if tensor_name in module._parameters: | ||
| module._parameters[tensor_name] = quantized_weight.to(target_device) | ||
| if tensor_name in module._buffers: | ||
| module._buffers[tensor_name] = quantized_weight.to(target_device) | ||
|
|
||
| def update_torch_dtype(self, torch_dtype: "torch.dtype") -> "torch.dtype": | ||
| if torch_dtype is None: | ||
| torch_dtype = self.compute_dtype | ||
| return torch_dtype | ||
|
|
||
| def _process_model_before_weight_loading( | ||
| self, | ||
| model: "ModelMixin", | ||
| device_map, | ||
| keep_in_fp32_modules: list[str] = [], | ||
| **kwargs, | ||
| ): | ||
| pass | ||
|
|
||
| def _process_model_after_weight_loading(self, model, **kwargs): | ||
| pass | ||
|
|
||
| @property | ||
| def is_serializable(self): | ||
| return False | ||
|
|
||
| @property | ||
| def is_trainable(self): | ||
| return False | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,7 @@ class QuantizationMethod(str, Enum): | |
| MODELOPT = "modelopt" | ||
| AUTOROUND = "auto-round" | ||
| SDNQ = "sdnq" | ||
| COMFY_QUANT = "comfy_quant" | ||
|
|
||
|
|
||
| @dataclass | ||
|
|
@@ -994,3 +995,32 @@ def __new__(cls, *args, **kwargs): | |
| from sdnq import SDNQConfig as SDNQLibConfig | ||
|
|
||
| return SDNQLibConfig(*args, **kwargs) | ||
|
|
||
|
|
||
| @dataclass | ||
| class ComfyQuantConfig(QuantizationConfigMixin): | ||
| """ | ||
| This is a wrapper class about all possible attributes and features that you can play with for a model that has been | ||
| quantized using comfy-kitchen. | ||
|
|
||
| Args: | ||
| quant_format (`str`, *optional*, defaults to `"fp8"`): | ||
| The quantization format. Supported values include `"fp8"`, `"int8"`, `"mxfp8"`, `"nvfp4"`, `"int4_svd"`, | ||
| and `"int4_awq"`. | ||
| compute_dtype (`torch.dtype`, *optional*): | ||
| The target dtype for the compute operations. | ||
| modules_to_not_convert (`list[str]`, *optional*, defaults to `None`): | ||
| The list of modules to skip during quantization. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| quant_format: str = "fp8", | ||
| compute_dtype: Any = None, | ||
| modules_to_not_convert: list[str] | None = None, | ||
| **kwargs, | ||
| ): | ||
| self.quant_method = QuantizationMethod.COMFY_QUANT | ||
| self.quant_format = quant_format | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to raise when an unsupported format is passed here. |
||
| self.compute_dtype = compute_dtype | ||
| self.modules_to_not_convert = modules_to_not_convert | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # This file is autogenerated by the command `make fix-copies`, do not edit. | ||
| from ..utils import DummyObject, requires_backends | ||
|
|
||
|
|
||
| class Com(metaclass=DummyObject): | ||
|
PrakshaaleJain marked this conversation as resolved.
Outdated
|
||
| _backends = ["comfy_kitchen"] | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| requires_backends(self, ["comfy_kitchen"]) | ||
|
|
||
| @classmethod | ||
| def from_config(cls, *args, **kwargs): | ||
| requires_backends(cls, ["comfy_kitchen"]) | ||
|
|
||
| @classmethod | ||
| def from_pretrained(cls, *args, **kwargs): | ||
| requires_backends(cls, ["comfy_kitchen"]) | ||
Uh oh!
There was an error while loading. Please reload this page.