Comfy-Org / Comfy-Org/ComfyUI

AttributeError: 'Linear' object has no attribute 'weight_scale' — ModelMergeSimple on a quantized (fp8/QuantizedTensor) model: unguarded getattr in get_key_weight vs synthetic quant state-dict keys

Open
#14,382 20 comments 3 reactions 1 assignee Claimed by @rattus128 View on GitHub
Dominant language
Python
Stars
133k
Forks
15.7k
Avg merge
1d 7h
Merged PRs (30d)
158

Description

### Custom Node Testing

- [x] I have tried disabling custom nodes and the issue persists (see [how to disable custom nodes](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled) if you need help)

Core nodes only — no custom nodes involved in the repro.

### Expected Behavior

`ModelMergeSimple` (and the other `get_key_patches`-based merge nodes) should be able to merge a checkpoint whose weights were loaded as `QuantizedTensor` (e.g. an FP8 e4m3fn mixed-precision quantized checkpoint). The real `.weight` keys already have a working `convert_weight` dequantization path in `get_key_weight`, so quantized merging is clearly intended to work.

### Actual Behavior

Deterministic crash at the merge node, before any sampling, whenever **model2** (the model `get_key_patches` is called on) is a quantized checkpoint:

```
AttributeError: 'Linear' object has no attribute 'weight_scale'
```

Observed on a v0.24.0-based build; I verified the relevant code is unchanged on current master (line numbers below are from master).

### Steps to Reproduce

1. Load an FP8 e4m3fn quantized checkpoint (e.g. a quantized FLUX.2 Klein — any checkpoint that carries `comfy_quant` metadata so its weights load as `QuantizedTensor` via the Mixed Precision Quantization System, #10498 / #11000).
2. Load any second model of the same architecture.
3. Connect both to `ModelMergeSimple` (quantized model as `model2`) and queue.
4. Instant `AttributeError` — 100% reproducible, no GPU/sampling needed.

Affects every node that calls `get_key_patches` on a quantized model: `ModelMergeSimple`, `ModelMergeBlocks`, `ModelMergeSubtract`, `ModelMergeAdd`, `CLIPMergeSimple`, `CheckpointSave`-after-merge, etc.

### Debug Logs

```powershell
Traceback (most recent call last):
File "ComfyUI/comfy_extras/nodes_model_merging.py", line 28, in merge
kp = model2.get_key_patches("diffusion_model.")
File "ComfyUI/comfy/model_patcher.py", line 822, in get_key_patches
weight, set_func, convert_func = get_key_weight(self.model, k)
File "ComfyUI/comfy/model_patcher.py", line 186, in get_key_weight
weight = getattr(op, op_keys[1])
File "torch/nn/modules/module.py", line 1928, in __getattr__
raise AttributeError(
AttributeError: 'Linear' object has no attribute 'weight_scale'
```

(Traceback trimmed to the relevant frames; from a headless server deployment.)

### Other

**Root cause**

The Mixed Precision Quantization System makes quantized layers' `state_dict()` emit *synthetic* top-level keys that are **not** module attributes:

- `comfy/ops.py:1118` `_quantized_weight_state_dict` writes `{prefix}weight_scale` (via `module.weight.state_dict(f"{prefix}weight")`), `{prefix}comfy_quant` (line 1136), `{prefix}input_scale` (`extra_quant_params`, line 1181), and `{prefix}weight_scale_2` for nvfp4.
- On load these keys are popped and absorbed into the `QuantizedTensor` layout params — `_load_quantized_module` explicitly **skips** `register_parameter` for `weight_scale` / `weight_scale_2` (`comfy/ops.py:1103-1104`). So they exist only in the state dict, never on the module.

Meanwhile `ModelPatcher.get_key_patches` (`comfy/model_patcher.py:813`) iterates **all** `model_state_dict()` keys and feeds each one to `get_key_weight`, which does an unguarded lookup (`comfy/model_patcher.py:186`):

```python
weight = getattr(op, op_keys[1])
```

For `some.layer.weight_scale` this resolves the module fine but then hits `torch.nn.Module.__getattr__` → `AttributeError`.

The old fix f9f9fafac ("Fixed model merging issue with scaled fp8", 2024) only covered the legacy `scaled_fp8` buffer format, which *is* a real module attribute — it doesn't help with the new synthetic keys.

**Suggested fix** (running in our fork, resolves the crash; quantized `.weight` keys keep their `convert_weight` dequant path so merging still works as designed):

```python
# comfy/model_patcher.py get_key_weight
weight = getattr(op, op_keys[1], None)
if weight is None:
return None, None, None
```

plus skipping `None` entries in `get_key_patches`:

```python
weight, set_func, convert_func = get_key_weight(self.model, k)
if weight is None:
continue
```

If returning `None` from `get_key_weight` is considered too broad (see the discussion in #11585 about not sweeping bugs under the rug), an alternative is for `get_key_patches` to explicitly skip the known synthetic suffixes (`comfy_quant`, `weight_scale`, `weight_scale_2`, `input_scale`). Unlike #11585 (triggered by a custom node's `RMS_norm.gamma`), here the offending keys are generated by core's own quantized `state_dict()`, so core nodes alone hit it.

**Related but not duplicates**

- #13637 / #13650: Flux.2 Klein merge+save segfault in the safetensors *save* path, fixed by #13835 — that's downstream of this crash; with `ModelMergeSimple` + quantized `model2` you never even reach saving.
- #11585: same unguarded `getattr` line, different trigger (custom-node `RMS_norm` without `.weight`).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.