From 8d53b96264657f97b6fc0f6a76a62ec30b3b03a9 Mon Sep 17 00:00:00 2001 From: liminfei-amd <91481003+liminfei-amd@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:25:31 +0800 Subject: [PATCH] model_patcher: skip synthetic quant keys in get_key_patches get_key_patches() iterates model_state_dict() and calls get_key_weight(), which does getattr(op, op_keys[1]). A quantized (fp8/QuantizedTensor) checkpoint flattens into synthetic state_dict keys (*.weight_scale, *.weight_scale_2, *.input_scale and a comfy_quant marker) that are components of the QuantizedTensor in *.weight, not module attributes, so getattr(linear, "weight_scale") raises AttributeError and every merge node (ModelMergeSimple, ModelMergeBlocks, CLIPMergeSimple, ...) crashes on a quantized model2. Skip the synthetic quant sub-keys, gathered from comfy.quant_ops.QUANT_ALGOS so new quant types are covered automatically. The real *.weight key keeps its convert_weight dequant path, so quantized merging still works. Only the known core-defined suffixes are skipped (not a blanket getattr swallow that would also hide a genuinely missing weight). Fixes #14382 Signed-off-by: liminfei-amd <91481003+liminfei-amd@users.noreply.github.com> --- comfy/model_patcher.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/comfy/model_patcher.py b/comfy/model_patcher.py index d70b42bf8..c01a57a9b 100644 --- a/comfy/model_patcher.py +++ b/comfy/model_patcher.py @@ -165,6 +165,23 @@ def low_vram_patch_estimate_vram(model, key): return weight.numel() * model_dtype.itemsize * LOWVRAM_PATCH_ESTIMATE_MATH_FACTOR +def _collect_quant_synthetic_keys(): + # Synthetic per-parameter suffixes a QuantizedTensor surfaces in state_dict(), gathered from + # the core quant algo table so new quant types are covered automatically. "comfy_quant" is the + # metadata marker key attached alongside quantized weights. + keys = {"comfy_quant"} + try: + import comfy.quant_ops + for algo in getattr(comfy.quant_ops, "QUANT_ALGOS", {}).values(): + keys.update(algo.get("parameters", ())) + except Exception: + keys.update({"weight_scale", "weight_scale_2", "input_scale"}) + return keys + + +_QUANT_SYNTHETIC_KEYS = _collect_quant_synthetic_keys() + + def get_key_weight(model, key): set_func = None convert_func = None @@ -817,6 +834,14 @@ class ModelPatcher: if filter_prefix is not None: if not k.startswith(filter_prefix): continue + # Quantized weights (Mixed Precision Quantization) flatten into synthetic + # state_dict keys (e.g. *.weight_scale / *.input_scale + the comfy_quant marker) + # that are components of the QuantizedTensor in *.weight, not real module + # attributes. The *.weight key carries the convert_weight dequant path, so the + # synthetic sub-keys are skipped here and merging uses *.weight. + op_keys = k.rsplit('.', 1) + if len(op_keys) == 2 and op_keys[1] in _QUANT_SYNTHETIC_KEYS: + continue bk = self.backup.get(k, None) hbk = self.hook_backup.get(k, None) weight, set_func, convert_func = get_key_weight(self.model, k)