333 lines
16 KiB
Markdown
333 lines
16 KiB
Markdown
<table align="center">
|
||
<tr>
|
||
<td align="center" bgcolor="#e5e7eb" width="88" height="36"><a href="https://github.com/ussoewwin/ComfyUI-QwenImageLoraLoader/releases/tag/v2.4.3"><font color="#4b5563"><b>EN</b></font></a></td>
|
||
<td align="center" bgcolor="#d4465e" width="88" height="36"><font color="#ffffff"><b>中文</b></font></td>
|
||
</tr>
|
||
</table>
|
||
|
||
## Z-Image / SVDQ:`NoneType` 上的 `.dtype`——ComfyUI 延迟(惰性)`Linear`、`pop` 默认值求值与本节点包的缓解措施(详解)
|
||
|
||
本文说明通过 **ComfyUI-nunchaku** 加载 Nunchaku 量化 Z-Image(Lumina2 / NextDiT)时可能出现的崩溃:**错误是什么、为何发生、根因分层、为何由 ComfyUI-QwenImageLoraLoader 吸收修复、改动了哪些文件、新增代码各自做什么**。
|
||
|
||
---
|
||
|
||
## 目标错误(典型用户报告)
|
||
|
||
### 异常信息
|
||
|
||
```
|
||
AttributeError: 'NoneType' object has no attribute 'dtype'
|
||
|
||
```
|
||
|
||
### 堆栈要点(代表性)
|
||
|
||
执行从 ComfyUI 节点管线进入,在 **ComfyUI-nunchaku Z-Image 模型构建 → Nunchaku `SVDQW4A4Linear.from_linear`** 处失败。
|
||
|
||
```
|
||
File ".../ComfyUI/execution.py", line 525, in execute
|
||
...
|
||
File ".../ComfyUI-nunchaku/nodes/models/zimage.py", line 215, in load_model
|
||
model = _load(sd, metadata=metadata)
|
||
File ".../ComfyUI-nunchaku/nodes/models/zimage.py", line 148, in _load
|
||
model = model_config.get_model(patched_sd, "", torch_dtype=torch_dtype)
|
||
File ".../ComfyUI-nunchaku/model_configs/zimage.py", line 71, in get_model
|
||
patch_model(out.diffusion_model, ...)
|
||
File ".../ComfyUI-nunchaku/models/zimage.py", line 320, in patch_model
|
||
_patch_transformer_block(diffusion_model.layers)
|
||
File ".../ComfyUI-nunchaku/models/zimage.py", line 317, in _patch_transformer_block
|
||
block.attention = ComfyNunchakuZImageAttention(block.attention, **kwargs)
|
||
File ".../ComfyUI-nunchaku/models/zimage.py", line 133, in __init__
|
||
self.qkv = SVDQW4A4Linear.from_linear(orig_attn.qkv, **kwargs)
|
||
File ".../site-packages/nunchaku/models/linear.py", line 152, in from_linear
|
||
torch_dtype = kwargs.pop("torch_dtype", linear.weight.dtype)
|
||
^^^^^^^^^^^^^^^^^^^
|
||
AttributeError: 'NoneType' object has no attribute 'dtype'
|
||
|
||
```
|
||
|
||
**实际断裂点:** `linear.weight` 为 `None`,但代码读取了 `linear.weight.dtype`。
|
||
|
||
---
|
||
|
||
## 症状摘要
|
||
|
||
| 项目 | 详情 |
|
||
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| **何时** | Z-Image(Nunchaku DiT)加载器构建模型且 `patch_model` 将 Attention / FF 换为 SVDQ 层时 |
|
||
| **何处** | `nunchaku.models.linear.SVDQW4A4Linear.from_linear`(以及 ComfyUI-nunchaku 中 `fuse_to_svdquant_linear` 的同类写法) |
|
||
| **环境** | 在 **Windows** 且 ComfyUI **AIMDO(内存优化)** 开启时使用 **`disable_weight_init.Linear`** —— 在应用 `state_dict` 之前权重保持为 **`None`** |
|
||
| **现象** | 工作流执行期间出现上述 `AttributeError` |
|
||
---
|
||
|
||
## 调用路径(逻辑流)
|
||
|
||
### 1. ComfyUI-nunchaku Z-Image 加载(概要)
|
||
|
||
典型顺序(实现要点):
|
||
|
||
1. 加载 `state_dict`
|
||
2. 调用 `NunchakuZImage.get_model(patched_sd, "", torch_dtype=...)`
|
||
3. **`super().get_model(...)`** 构建 `model_base.Lumina2` → **构造 NextDiT**(各 `Linear` 上权重**可能尚未就绪**)
|
||
4. **`patch_model(diffusion_model, ...)`** 将例如 `JointAttention` 替换为 **`ComfyNunchakuZImageAttention`**
|
||
5. 在该构造器内执行 **`SVDQW4A4Linear.from_linear(orig_attn.qkv, kwargs)`**
|
||
6. **之后**(视加载器流程而定)`load_model_weights` 等才会填入权重
|
||
|
||
因此若 **「量化模块被 patch 的时机」** 与 **「ComfyUI 的 `Linear` 获得真实权重」** 不一致,则在 patch 时刻 **`weight` 仍为 `None`** 是可能的。
|
||
|
||
### 2. ComfyUI 核心:`disable_weight_init.Linear` 的含义
|
||
|
||
在 `comfy/ops.py` 中,**`disable_weight_init.Linear`** 在 **Windows 且 `aimdo_enabled`** 时大致行为:
|
||
|
||
* 跳过常规 `torch.nn.Linear` 的 `super().__init__(...)`;仅 `torch.nn.Module.__init__`
|
||
* 初始 **`self.weight = None`**、**`self.bias = None`**
|
||
* 真实 **`Parameter`** 在 **`_load_from_state_dict`** 中赋值
|
||
|
||
**设计意图(摘要):** 用 `torch.empty` 分配占位权重会在 Windows 上急剧推高 commit charge 并导致不稳定。ComfyUI 改为延迟 / 从 `state_dict` 零拷贝。
|
||
|
||
因此在上述条件下 **`weight is None` 并非 bug** —— 而是 **ComfyUI 的预期行为**。
|
||
|
||
### 3. Nunchaku:`from_linear` 的隐含假设
|
||
|
||
`SVDQW4A4Linear.from_linear` 概念上应:
|
||
|
||
* 从已有 `nn.Linear` 读取 **入/出特征、bias、dtype、device**
|
||
* 构建同形状的量化占位 `SVDQW4A4Linear`(训练权重稍后经 `state_dict` 到达)
|
||
|
||
若 **隐含假设 `linear.weight` 永远非 `None`**,则与 ComfyUI 的延迟 `Linear` **冲突**。
|
||
|
||
---
|
||
|
||
## 真正的问题(两层)
|
||
|
||
宜理解为 **复合问题**,而非单一根因。
|
||
|
||
### 层 A:Python 语义 —— `dict.pop(key, default)` 总会对 default 求值
|
||
|
||
问题行(修复前 nunchaku 的典型形态):
|
||
|
||
```
|
||
torch_dtype = kwargs.pop("torch_dtype", linear.weight.dtype)
|
||
```
|
||
|
||
许多开发者直觉认为:
|
||
|
||
> 「若 `torch_dtype` 已在 `kwargs` 中,则不会用到 default,故不会求值 `linear.weight.dtype`。」
|
||
|
||
**在 Python 中这是错的。** **所有实参表达式都会在调用前求值。** 因此:
|
||
|
||
* 即使 **`"torch_dtype": torch.bfloat16` 已在 `kwargs` 中**
|
||
* **`linear.weight.dtype` 仍会被求值**
|
||
|
||
于是:
|
||
|
||
* 当 **`linear.weight is None`** → **`None.dtype` → `AttributeError`**
|
||
* 即使调用方 **正确传入 `torch_dtype=`** → **仍会同一 `AttributeError`**(即「明明传了 kwargs 仍崩」的真实原因)
|
||
|
||
**语言层面结论:** 把 `pop` 的 default 当作「惰性回退」是 **错误的**。若回退代价高、有副作用或可能失败,应使用 **`if "k" in kwargs`** 或 **`try/except KeyError`**。
|
||
|
||
### 层 B:顺序 —— ComfyUI 延迟 `Linear` 与 ComfyUI-nunchaku「先 patch」
|
||
|
||
即便修复层 A,**`linear.weight is None`** 仍留下 **dtype / device 从何处取得** 的问题。
|
||
|
||
* ComfyUI-nunchaku 常在 **`get_model` 内 `patch_model`,再 `load_model_weights`**
|
||
* 在 **`patch_model` 瞬间**,延迟 ComfyUI `Linear` 仍可能 **`weight is None`**
|
||
|
||
**集成结论:** ComfyUI **「patch 时 Linear 可无权重」** 与 Nunchaku 从 **weight 读取 dtype/device** 的假设不匹配。
|
||
再叠加 **层 A 的 `pop` bug**,导致 **调用方传入 `torch_dtype` 仍会崩溃**。
|
||
|
||
---
|
||
|
||
## 责任划分(上游「理应」修哪里)
|
||
|
||
| 领域 | 最接近责任方 | 说明 |
|
||
| ------------------------------------------------ | ---------------------- | --------------------------------------------------------------------- |
|
||
| Windows + AIMDO 下的延迟 Linear | **ComfyUI 核心** | 有意优化;行为应作为规格文档化 |
|
||
| `from_linear` 使用 `pop(..., linear.weight.dtype)` | **Nunchaku(库)** | 不符合 Python 语义;在 weight 为 `None` 时可安全改写 |
|
||
| `patch_model` 与 `load_model_weights` 的顺序 | **ComfyUI-nunchaku** | 设计选择:无权重 patch vs 重排 |
|
||
| 各用户环境立即可用的修复 | **无人单独保证** | 等 PR / release / review 会长期阻塞用户 |
|
||
**在本节点包内吸收修复** 与 `md/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md` 等文档中的务实模式一致。
|
||
|
||
---
|
||
|
||
## 为何由 ComfyUI-QwenImageLoraLoader 吸收
|
||
|
||
### 1. 与既有架构一致
|
||
|
||
本仓库已在 **`__init__.py` 启动时对 `patches/nunchaku_patch.py` 打补丁**:
|
||
|
||
* **Nunchaku Qwen:** 对 `NunchakuQwenImageTransformerBlock.forward` 的 **Manual Planar Injection**(LoRA)
|
||
* 对脆弱 import 路径的 **`sys.modules` 扫描**
|
||
|
||
本次变更是 **同一 `apply_nunchaku_patch()` 伞盖下** 的兼容扩展。
|
||
|
||
### 2. 用户侧改动面更少
|
||
|
||
* 手改 **`site-packages/nunchaku`** → 升级即丢失,难复现
|
||
* 修补 **ComfyUI 核心** → 更新冲突,向他人解释成本高
|
||
* 固定 **ComfyUI-nunchaku fork** → 维护成本
|
||
|
||
已安装 **LoRA 节点包的用户已信任本包** —— **在此集中修复** 可降低运维成本。
|
||
|
||
### 3. 加载顺序问题本就存在
|
||
|
||
文件夹名字顺序可能导致 **本包先于 ComfyUI-nunchaku** 加载。
|
||
则启动时 **`models.zimage` 可能尚未进入 `sys.modules`**。
|
||
|
||
**一次性 patch 不够**;需要 **延迟重试** —— 与本仓库已处理的「环境相关」模式相同。
|
||
|
||
---
|
||
|
||
## 变更文件
|
||
|
||
| 文件 | 变更 |
|
||
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| **ComfyUI-QwenImageLoraLoader/patches/nunchaku_patch.py** | 替换 `SVDQW4A4Linear.from_linear`、动态替换 `fuse_to_svdquant_linear`、重试调度器、从 `apply_nunchaku_patch` 接入 |
|
||
| **ComfyUI-QwenImageLoraLoader/__init__.py** | 成功日志文案更新,提及 lazy-Linear 缓解 |
|
||
**说明:** 本仓库 **不** 在树内修改 ComfyUI-nunchaku 或 nunchaku(避免与上游漂移)。
|
||
|
||
---
|
||
|
||
## 新增 / 变更代码及含义(按小节)
|
||
|
||
以下为 **说明用途**;行号可能漂移 —— 以仓库源码为准。
|
||
|
||
### A. `_torch_device_fallback`
|
||
|
||
**作用:** 当 **`linear.weight is None`** 时,**推断 `device`**。
|
||
|
||
1. 优先 **`comfy.model_management.get_torch_device()`**(与 ComfyUI 所选推理设备一致)
|
||
2. 失败时(测试等)简单回退 **`cuda` / `cpu`**
|
||
|
||
**原因:** 即使稍后 `load_model_weights` 会填张量,构造阶段 **设备一致** 对量化 `Parameter` 更安全。
|
||
|
||
### B. `_patched_svdqw4a4_from_linear`(`SVDQW4A4Linear.from_linear` 的替换)
|
||
|
||
**修复:**
|
||
|
||
1. 停止使用错误的 **`pop`**;用 **`"torch_dtype" in kwargs`** 分支 → **已提供 `torch_dtype` 时不触碰 `linear.weight`**
|
||
2. **`weight is None`** 时要求 **`kwargs` 中必须有 `torch_dtype`**,否则抛出明确 **`TypeError`**(可调试性)
|
||
3. **`device`** 同样模式;缺失则用 **`_torch_device_fallback()`**
|
||
|
||
**绑定为 `classmethod`:** 原 `from_linear` 为类方法,故 `SVDQW4A4Linear.from_linear = classmethod(_patched_svdqw4a4_from_linear)`。
|
||
|
||
### C. `_make_patched_fuse_to_svdquant_linear` 及替换
|
||
|
||
ComfyUI-nunchaku 的 **`fuse_to_svdquant_linear`**(Z-Image 的 FF w1/w3 融合)可能使用 **相同的 `pop(..., weight.dtype)` 模式**。
|
||
仅修 Attention 的 `qkv` / `out` 仍可能在 **下一 FF 块** 失败。
|
||
|
||
**做法:**
|
||
|
||
* 从目标模块取出 **`add_comfy_cast_weights_attr`** 并保留原行为(offload 兼容用的占位属性)
|
||
* 仅内层逻辑使用 **与 `from_linear` 相同的安全分支**
|
||
|
||
### D. `apply_svdqw4a4_lazy_linear_patch`
|
||
|
||
* 成功导入 **`nunchaku.models.linear.SVDQW4A4Linear`** 后 **覆盖 `from_linear`**
|
||
* **`_svdq_from_linear_patched` 标志** 防止重复应用(重载 / 重复执行韧性)
|
||
|
||
### E. `apply_nunchaku_zimage_fuse_lazy_linear_patch`
|
||
|
||
**模块识别启发式**(减少误伤):
|
||
|
||
* 含 **`fuse_to_svdquant_linear`**
|
||
* 含 **`ComfyNunchakuZImageAttention`**(Z-Image nunchaku patch 模块指纹)
|
||
* 含 **`add_comfy_cast_weights_attr`**(同文件辅助函数)
|
||
|
||
已 patch 的函数带 **`_qwen_lora_loader_lazy_linear_patch`** 以跳过重复 patch。
|
||
|
||
### F. `schedule_nunchaku_zimage_fuse_patch_retries`
|
||
|
||
**问题:** 若本包 **先** 导入(例如按字母序),启动时 **`zimage` 可能尚未加载**。
|
||
|
||
**缓解:**
|
||
|
||
* 短延迟后 **重试 `apply_nunchaku_zimage_fuse_lazy_linear_patch()`**
|
||
* 最长约 **6 秒**(0.25s × 24),待 **ComfyUI-nunchaku** 加载后再替换 **`fuse`**
|
||
|
||
**权衡:** 后台 **`threading.Timer`**。**非 daemon** 但 **短生命周期且有界**,不应表现为永久泄漏。
|
||
|
||
### G. `apply_nunchaku_patch` 集成
|
||
|
||
惰性 `Linear` 修复与既有 **Manual Planar Injection** **一并**运行(同函数内先 lazy 路径再 planar)。
|
||
|
||
**返回值:**
|
||
|
||
* **`True`**:**Planar patch** 成功 **或** **`from_linear` patch** 成功
|
||
* **`False`**:二者皆否(例如未安装 nunchaku)
|
||
|
||
### H. `__init__.py` 日志
|
||
|
||
成功日志明确提及 **Z-Image / SVDQ / ComfyUI 延迟 `Linear` 权重**,便于从启动日志判断 **何种缓解已激活**。
|
||
|
||
---
|
||
|
||
## 如何在启动日志中确认
|
||
|
||
若 ComfyUI 启动日志出现类似行,则 **lazy-`Linear` patch 很可能已生效**:
|
||
|
||
* `Patched SVDQW4A4Linear.from_linear for ComfyUI lazy Linear ...`
|
||
* `Patched fuse_to_svdquant_linear in ...`
|
||
|
||
**`fuse`** patch 可能因 **延迟重试** 而 **数秒后才出现**。
|
||
|
||
---
|
||
|
||
## 残留限制 / 注意事项
|
||
|
||
1. 若 **`kwargs` 缺少 `torch_dtype` 且 `weight is None`**,本 patch **有意** 抛出 **`TypeError`**。
|
||
* 倾向 **显式失败** 而非 **静默损坏**。
|
||
* 只要 ComfyUI-nunchaku 的 Z-Image 加载器持续传入 **`torch_dtype=`**,正常使用不应触发。
|
||
2. **Monkey-patch 一般性:** 若上游改动相同入口(`SVDQW4A4Linear.from_linear`、ComfyUI-nunchaku 的 `fuse_to_svdquant_linear`),可能出现 **非预期行为漂移**。见下文 **§4**。
|
||
3. **`sys.modules` 启发式:** 理论上其他模块也可能满足检查;风险低,且由 **强 Z-Image 指纹**(`ComfyNunchakuZImageAttention`)缓解。
|
||
|
||
### 4. 若上游(Nunchaku / ComfyUI-nunchaku)并入等效修复
|
||
|
||
本缓解在启动时 **替换** 已安装 nunchaku 的 **classmethod** 与 ComfyUI-nunchaku 的 **模块级函数**。若上游 **并入正式修复**(与其它文档「副作用」第 **6** 条同类),行为可整理如下。
|
||
|
||
#### 4.1 双重 patch?
|
||
|
||
* **可能。**`apply_nunchaku_patch()` **仍会在可行时替换 `from_linear`**,与上游版本无关。
|
||
* 若 **意图一致**(去掉 `pop` 误用、对 `weight is None` 分支),**害处通常很小**:本 patch 先执行,再与正确上游路径对齐 —— **极少产生结果级冲突**。
|
||
* 若上游 **方案不同**(例如 `from_linear` API 变更、迁至其他类),**此处陈旧替换** 可能导致 **仅一层生效 / 不同异常**。
|
||
|
||
#### 4.2 上游 API / 签名变更
|
||
|
||
* 例如:`SVDQW4A4Linear.from_linear(cls, linear, **kwargs)` 增加参数、不再是 `classmethod`、返回类型变更、委托给其他辅助函数等。
|
||
* 则 **钉死在旧契约上的 `_patched_svdqw4a4_from_linear`** 可能 **启动通过但运行失败** 或 **静默偏离**。
|
||
* **缓解:** **更新本 patch**,或在上游已足够处 **移除 / 禁用** lazy-`Linear` patch,或 **按 nunchaku / ComfyUI-nunchaku 版本门控** 并在新 release 上跳过 patch。
|
||
|
||
#### 4.3 维护建议
|
||
|
||
* 当发行说明 / issue 写明 **「lazy Linear + `from_linear`」已在上游修复** 时,请 **重新评估该版本起是否仍需要** 本 patch。
|
||
* 移除时清理 **`patches/nunchaku_patch.py`**、**`apply_nunchaku_patch` 调用点**、**`schedule_nunchaku_zimage_fuse_patch_retries`**,若仅需 **Planar Injection** 则保留后者。
|
||
* 在本文档 **变更记录** 中记下:「上游吸收修复后已移除 ○○」供后续读者查阅。
|
||
|
||
#### 4.4 用户可见现象
|
||
|
||
* 常见情况:**仅更新上游而本包较旧** 仍 **可工作**,因本 patch 在加载时 **覆盖**。
|
||
* 若异常,怀疑 **nunchaku / ComfyUI-nunchaku / 本包组合**;**更新本包** 或 **暂时禁用** 以仅复现上游。
|
||
|
||
---
|
||
|
||
## 与其它文档的关系
|
||
|
||
| 文档 | 关系 |
|
||
| --------------------------------------------- | ----------------------------------------------------------------------- |
|
||
| [COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md](COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md) | 相同 **前提**:核心修复滞后;**用户环境先坏** |
|
||
| [QWEN_IMAGE_CONTROLNET_AND_GETATTR_FIX.md](../md/QWEN_IMAGE_CONTROLNET_AND_GETATTR_FIX.md) | 相同 **策略**:**包装 / patch** 以满足上游契约 |
|
||
| [ZIMAGETURBO_CONTROLNET_FIX.md](../md/ZIMAGETURBO_CONTROLNET_FIX.md) | Z-Image 系:**transformer_options** 等,**补管线缺口** |
|
||
本文在 **Z-Image × Nunchaku SVDQ × ComfyUI `Linear` × Python `pop`** 的交叉点上增加一层 **兼容层**。
|
||
|
||
---
|
||
|
||
## 一句话总结
|
||
|
||
**ComfyUI 在部分条件下可有意保持 `Linear.weight` 为 `None`**,而 **Nunchaku 的 `from_linear` 误用了 `pop` 的 default**,共同导致 **`None.dtype`**;**ComfyUI-nunchaku 的 FF 融合可共享同一脆弱性**;**本仓库扩展 `apply_nunchaku_patch`,以 monkey-patch 与延迟重试使用户不必苦等上游发布。**
|
||
|
||
---
|
||
|
||
## 文档变更记录
|
||
|
||
* 初稿:说明惰性 `Linear`、`pop` 默认值求值及在本仓库中的吸收方式
|
||
* 增补:**§4** —— 双重 patch、API 漂移及上游并入同修复后的维护(由副作用第 6 条扩展) |