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>
This commit is contained in:
liminfei-amd 2026-06-11 12:25:31 +08:00
parent bda19b2604
commit 8d53b96264

View File

@ -165,6 +165,23 @@ def low_vram_patch_estimate_vram(model, key):
return weight.numel() * model_dtype.itemsize * LOWVRAM_PATCH_ESTIMATE_MATH_FACTOR 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): def get_key_weight(model, key):
set_func = None set_func = None
convert_func = None convert_func = None
@ -817,6 +834,14 @@ class ModelPatcher:
if filter_prefix is not None: if filter_prefix is not None:
if not k.startswith(filter_prefix): if not k.startswith(filter_prefix):
continue 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) bk = self.backup.get(k, None)
hbk = self.hook_backup.get(k, None) hbk = self.hook_backup.get(k, None)
weight, set_func, convert_func = get_key_weight(self.model, k) weight, set_func, convert_func = get_key_weight(self.model, k)