-
Notifications
You must be signed in to change notification settings - Fork 7.3k
[torchao]: fix how FqnConfig is resolved and handled #14686
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 all commits
a2b9a5f
8d221d1
cc6fbbe
0de9609
d5aa1ba
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 |
|---|---|---|
|
|
@@ -51,31 +51,8 @@ | |
| import torch | ||
| import torch.nn as nn | ||
|
|
||
| if is_torch_version(">=", "2.5"): | ||
| SUPPORTED_TORCH_DTYPES_FOR_QUANTIZATION = ( | ||
| # At the moment, only int8 is supported for integer quantization dtypes. | ||
| # In Torch 2.6, int1-int7 will be introduced, so this can be visited in the future | ||
| # to support more quantization methods, such as intx_weight_only. | ||
| torch.int8, | ||
| torch.float8_e4m3fn, | ||
| torch.float8_e5m2, | ||
| torch.uint1, | ||
| torch.uint2, | ||
| torch.uint3, | ||
| torch.uint4, | ||
| torch.uint5, | ||
| torch.uint6, | ||
| torch.uint7, | ||
| ) | ||
| else: | ||
| SUPPORTED_TORCH_DTYPES_FOR_QUANTIZATION = ( | ||
| torch.int8, | ||
| torch.float8_e4m3fn, | ||
| torch.float8_e5m2, | ||
| ) | ||
|
|
||
| if is_torchao_available(): | ||
| from torchao.quantization import quantize_ | ||
| from torchao.quantization import FqnToConfig, quantize_ | ||
|
|
||
| if is_torchao_version(">=", "0.16.0"): | ||
| from torchao.prototype.safetensors.safetensors_support import ( | ||
|
|
@@ -145,6 +122,41 @@ def fuzzy_match_size(config_name: str) -> str | None: | |
| return None | ||
|
|
||
|
|
||
| def _fqn_to_config_weight_sizes(config: "FqnToConfig") -> tuple[set[str | None], bool]: | ||
|
Member
Author
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. Needed to determine CUDA warmup factor from |
||
| """ | ||
| Summarize the configs an `FqnToConfig` holds, for the memory estimates that assume one weight size model-wide. | ||
|
|
||
| Returns the size digits (as `fuzzy_match_size` reports them) of every config it maps to, along with whether the | ||
| config leaves modules unquantized -- either mapped to `None`, or unmatched with no `_default` to fall back on. | ||
| """ | ||
| fqn_to_config = config.fqn_to_config | ||
| size_digits = {fuzzy_match_size(type(c).__name__) for c in fqn_to_config.values() if c is not None} | ||
| leaves_modules_unquantized = "_default" not in fqn_to_config or any(c is None for c in fqn_to_config.values()) | ||
| return size_digits, leaves_modules_unquantized | ||
|
|
||
|
|
||
| def _resolve_fqn_to_config(config: "FqnToConfig", module_fqn: str, param_fqn: str): | ||
|
Member
Author
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. Needed so that we can properly resolve config for a single linear layer because that is how we create the quantized params, i.e., iterating over the modules and calling |
||
| """ | ||
| Pick the config an `FqnToConfig` assigns to a single parameter. | ||
|
|
||
| `create_quantized_param` quantizes one module at a time, so `quantize_` only ever sees a lone `nn.Linear` whose fqn | ||
| is `""` and whose parameters are named `weight`/`bias`. Full-path patterns therefore never match on their own, and | ||
| the config silently falls through to `_default`. | ||
| """ | ||
| fqn_to_config = config.fqn_to_config | ||
|
|
||
| for fqn in (param_fqn, module_fqn): | ||
| if fqn in fqn_to_config: | ||
| return fqn_to_config[fqn] | ||
|
|
||
| for fqn in (param_fqn, module_fqn): | ||
| for pattern, pattern_config in fqn_to_config.items(): | ||
| if pattern.startswith("re:") and re.fullmatch(pattern[3:], fqn): | ||
| return pattern_config | ||
|
|
||
| return fqn_to_config.get("_default", None) | ||
|
sayakpaul marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def _linear_extra_repr(self): | ||
| from torchao.utils import TorchAOBaseTensor | ||
|
|
||
|
|
@@ -228,6 +240,15 @@ def adjust_target_dtype(self, target_dtype: "torch.dtype") -> "torch.dtype": | |
| from accelerate.utils import CustomDtype | ||
|
|
||
| quant_type = self.quantization_config.quant_type | ||
| if isinstance(quant_type, FqnToConfig): | ||
| size_digits, leaves_modules_unquantized = _fqn_to_config_weight_sizes(quant_type) | ||
| if leaves_modules_unquantized: | ||
| # Modules the config skips keep `target_dtype`, so shrinking the estimate for every parameter would | ||
| # under-estimate the model and let `infer_auto_device_map` overfill a device. | ||
| return target_dtype | ||
| # Only claim int4 when nothing the config maps to is wider than that. | ||
| return CustomDtype.INT4 if size_digits == {"4"} else torch.int8 | ||
|
|
||
| config_name = quant_type.__class__.__name__ | ||
| size_digit = fuzzy_match_size(config_name) | ||
|
|
||
|
|
@@ -236,18 +257,6 @@ def adjust_target_dtype(self, target_dtype: "torch.dtype") -> "torch.dtype": | |
| else: | ||
| return torch.int8 | ||
|
|
||
| if isinstance(target_dtype, SUPPORTED_TORCH_DTYPES_FOR_QUANTIZATION): | ||
| return target_dtype | ||
|
|
||
| # We need one of the supported dtypes to be selected in order for accelerate to determine | ||
| # the total size of modules/parameters for auto device placement. | ||
| possible_device_maps = ["auto", "balanced", "balanced_low_0", "sequential"] | ||
| raise ValueError( | ||
| f"You have set `device_map` as one of {possible_device_maps} on a TorchAO quantized model but a suitable target dtype " | ||
| f"could not be inferred. The supported target_dtypes are: {SUPPORTED_TORCH_DTYPES_FOR_QUANTIZATION}. If you think the " | ||
| f"dtype you are using should be supported, please open an issue at https://github.com/huggingface/diffusers/issues." | ||
| ) | ||
|
Comment on lines
-239
to
-249
Member
Author
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. Deadcode. |
||
|
|
||
| def adjust_max_memory(self, max_memory: dict[str, int | str]) -> dict[str, int | str]: | ||
| max_memory = {key: val * 0.9 for key, val in max_memory.items()} | ||
| return max_memory | ||
|
|
@@ -372,8 +381,25 @@ def create_quantized_param( | |
| module.extra_repr = types.MethodType(_linear_extra_repr, module) | ||
| else: | ||
| # As we perform quantization here, the repr of linear layers is set by TorchAO, so we don't have to do it ourselves | ||
| module._parameters[tensor_name] = torch.nn.Parameter(param_value).to(device=target_device) | ||
| quantize_(module, self.quantization_config.get_apply_tensor_subclass()) | ||
| module._parameters[tensor_name] = torch.nn.Parameter(param_value.to(device=target_device)) | ||
|
Member
Author
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. The path for pre-quantized checkpoints also does the same. |
||
|
|
||
| retrieved_config = self.quantization_config.get_apply_tensor_subclass() | ||
| if isinstance(retrieved_config, FqnToConfig): | ||
| module_fqn = param_name.rsplit(".", 1)[0] if "." in param_name else "" | ||
| retrieved_config = _resolve_fqn_to_config(retrieved_config, module_fqn, param_name) | ||
| if retrieved_config is None: | ||
| # This module is either explicitly excluded or unmatched with no `_default`, so it stays unquantized. | ||
| return | ||
| if isinstance(retrieved_config, FqnToConfig): | ||
| # `quantize_` matches an `FqnToConfig` against the fqns of the module it is handed, which here is a | ||
| # lone `nn.Linear`. The resolution above is what keeps fqn targeting working, so a config that is | ||
| # still an `FqnToConfig` at this point would quantize nothing at all rather than erroring. | ||
| raise ValueError( | ||
| f"Nested `FqnToConfig` entries are not supported (resolved from `{param_name}`). Map each fqn " | ||
| f"to a quantization config, or to `None` to leave the matching modules unquantized." | ||
| ) | ||
| # `retrieved_config` is a plain config by this point, so `quantize_` can use its default `filter_fn`. | ||
| quantize_(module, retrieved_config) | ||
|
|
||
| def get_cuda_warm_up_factor(self): | ||
| """ | ||
|
|
@@ -391,6 +417,12 @@ def get_cuda_warm_up_factor(self): | |
| - Use a division factor of 4 for int8 weights | ||
| """ | ||
| quant_type = self.quantization_config.quant_type | ||
| if isinstance(quant_type, FqnToConfig): | ||
| # Pre-allocating more than the model ends up using can OOM the warmup itself, so assume the narrowest | ||
| # weights this config can produce. Modules it leaves unquantized only make the estimate safer. | ||
| size_digits, _ = _fqn_to_config_weight_sizes(quant_type) | ||
| return 8 if "4" in size_digits else 4 | ||
|
|
||
| config_name = quant_type.__class__.__name__ | ||
| size_digit = fuzzy_match_size(config_name) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deadcode not needed.