docs: fix cross-reference link from English to Chinese in zhmd/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#d4465e" width="88" height="36"><font color="#ffffff"><b>EN</b></font></td>
|
||||
<td align="center" bgcolor="#e5e7eb" width="88" height="36"><a href="https://github.com/ussoewwin/ComfyUI-QwenImageLoraLoader/blob/main/zhmd/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md"><font color="#4b5563"><b>中文</b></font></a></td>
|
||||
<td align="center" bgcolor="#e5e7eb" width="88" height="36"><font color="#4b5563"><b>EN</b></font></td>
|
||||
<td align="center" bgcolor="#3478ca" width="88" height="36"><a href="https://github.com/ussoewwin/ComfyUI-QwenImageLoraLoader/blob/main/zhmd/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md"><font color="#ffffff"><b>中文</b></font></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
# 高速化計画
|
||||
|
||||
`wrappers/qwenimage.py` と `nunchaku_code/lora_qwen.py` を実際に読んだ上で作成。推測は入れない。
|
||||
|
||||
---
|
||||
|
||||
## 1. `_load_lora_state_dict_robust` にファイルキャッシュを追加
|
||||
|
||||
### 現状の事実
|
||||
|
||||
```python
|
||||
def _load_lora_state_dict_robust(path_or_dict: Union[str, Dict]) -> Dict:
|
||||
if isinstance(path_or_dict, dict):
|
||||
return path_or_dict
|
||||
path = str(path_or_dict)
|
||||
if os.path.exists(path):
|
||||
return comfy.utils.load_torch_file(path)
|
||||
```
|
||||
|
||||
`compose_loras_v2` のループ内で各 `lora_configs` 要素ごとに呼ばれる。同一ファイルパスが複数回含まれていても、毎回 `comfy.utils.load_torch_file(path)` を呼ぶ。辞書キャッシュは存在しない。
|
||||
|
||||
### 高速化案
|
||||
|
||||
`_load_lora_state_dict_robust` に `functools.lru_cache(maxsize=None)` を付与し、同一パスの2回目以降のファイル読み込みを省略する。
|
||||
|
||||
```python
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _load_lora_state_dict_robust(path_or_dict: Union[str, Dict]) -> Dict:
|
||||
...
|
||||
```
|
||||
|
||||
`path_or_dict` が `dict` の場合はキャッシュ不要だが、文字列/Path の場合はキャッシュが有効。`lru_cache` は `hashable` な引数のみ受け付けるため、文字列パスに限定する。
|
||||
|
||||
### 影響範囲
|
||||
|
||||
- `compose_loras_v2` 内の `for idx, (lora_path_or_dict, strength) in enumerate(lora_configs):` ループ
|
||||
- 同一 LoRA ファイルが複数回指定された場合、2回目以降はメモリから取得
|
||||
|
||||
---
|
||||
|
||||
## 2. デバイス転送のバッチ化
|
||||
|
||||
### 現状の事実
|
||||
|
||||
```python
|
||||
all_A.append(A.to(dtype=target_dtype, device=target_device))
|
||||
all_B_scaled.append((B * scale).to(dtype=target_dtype, device=target_device))
|
||||
```
|
||||
|
||||
各 LoRA の各 weight(A, B)ごとに個別の `.to(device=...)` を発行。`torch.cat` は転送後に行われる。
|
||||
|
||||
### 高速化案
|
||||
|
||||
CPU 上で `torch.cat` または `torch.stack` してから、1回の `.to(device)` を発行する。
|
||||
|
||||
```python
|
||||
# 現状
|
||||
all_A.append(A.to(dtype=target_dtype, device=target_device))
|
||||
# ...
|
||||
final_A = torch.cat(all_A, dim=0)
|
||||
|
||||
# 変更後
|
||||
all_A_cpu.append(A.to(dtype=target_dtype, device="cpu"))
|
||||
# ...
|
||||
final_A_cpu = torch.cat(all_A_cpu, dim=0)
|
||||
final_A = final_A_cpu.to(device=target_device)
|
||||
```
|
||||
|
||||
### ただし
|
||||
|
||||
- `target_device` は `module.qweight.device` または `module.proj_down.device` で、モジュールごとに異なる可能性がある
|
||||
- 同一モジュール内の `all_A` は同一デバイスに転送されるため、モジュール単位でバッチ化は有効
|
||||
|
||||
---
|
||||
|
||||
## 3. `_awq_lora_forward` 内の `.to()` 削除または事前転送
|
||||
|
||||
### 現状の事実
|
||||
|
||||
```python
|
||||
for (A_local, B_local) in module._nunchaku_lora_bundle:
|
||||
A_local = A_local.to(device=device, dtype=dtype)
|
||||
B_local = B_local.to(device=device, dtype=dtype)
|
||||
lora_mid = x_flat @ A_local.transpose(0, 1)
|
||||
lora_term = lora_mid @ B_local.transpose(0, 1)
|
||||
```
|
||||
|
||||
`_apply_lora_to_module` で `A_local`, `B_local` をモジュールにアタッチする際、既に `target_device` に転送している:
|
||||
|
||||
```python
|
||||
all_A.append(A.to(dtype=target_dtype, device=target_device))
|
||||
# ...
|
||||
mod._nunchaku_lora_bundle.append((A, B))
|
||||
```
|
||||
|
||||
しかし `_awq_lora_forward` では毎回 `.to(device=device, dtype=dtype)` を発行しており、すでに同じデバイス/dtype にあるテンソルに対する無駄なコピーが発生している可能性がある。
|
||||
|
||||
### 高速化案
|
||||
|
||||
`_nunchaku_lora_bundle` に格納する時点で、既に `target_device` / `target_dtype` に変換済みのテンソルを格納する。`_awq_lora_forward` 内の `.to()` を削除または、デバイス/dtype が一致している場合はスキップする。
|
||||
|
||||
```python
|
||||
# _awq_lora_forward 内
|
||||
for (A_local, B_local) in module._nunchaku_lora_bundle:
|
||||
# 既に同じデバイス/dtypeならスキップ
|
||||
if A_local.device != device or A_local.dtype != dtype:
|
||||
A_local = A_local.to(device=device, dtype=dtype)
|
||||
if B_local.device != device or B_local.dtype != dtype:
|
||||
B_local = B_local.to(device=device, dtype=dtype)
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. `_get_module_by_name` のパスキャッシュ
|
||||
|
||||
### 現状の事実
|
||||
|
||||
```python
|
||||
def _get_module_by_name(model: nn.Module, name: str) -> Optional[nn.Module]:
|
||||
module = model
|
||||
for part in name.split("."):
|
||||
if hasattr(module, part):
|
||||
module = getattr(module, part)
|
||||
elif part.isdigit() and _is_indexable_module(module):
|
||||
module = module[int(part)]
|
||||
else:
|
||||
return None
|
||||
return module
|
||||
```
|
||||
|
||||
`compose_loras_v2` の `for module_name_key, weight_list in aggregated_weights.items():` ループ内で、毎回 `_resolve_module_name` → `_get_module_by_name` を呼ぶ。同一 `module_name_key` が複数回現れる場合、毎回ツリーを走査する。
|
||||
|
||||
### 高速化案
|
||||
|
||||
`compose_loras_v2` 内にローカルキャッシュ `{name: module}` を設け、2回目以降はキャッシュを使用する。
|
||||
|
||||
```python
|
||||
_module_cache = {}
|
||||
# ...
|
||||
for module_name_key, weight_list in aggregated_weights.items():
|
||||
if module_name_key in _module_cache:
|
||||
resolved_name, module = _module_cache[module_name_key]
|
||||
else:
|
||||
resolved_name, module = _resolve_module_name(model, module_name_key)
|
||||
_module_cache[module_name_key] = (resolved_name, module)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. `all_A` / `all_B_scaled` の事前確保
|
||||
|
||||
### 現状の事実
|
||||
|
||||
```python
|
||||
all_A = []
|
||||
all_B_scaled = []
|
||||
for w in weight_list:
|
||||
# ...
|
||||
all_A.append(A.to(...))
|
||||
all_B_scaled.append((B * scale).to(...))
|
||||
final_A = torch.cat(all_A, dim=0)
|
||||
final_B = torch.cat(all_B_scaled, dim=1)
|
||||
```
|
||||
|
||||
`append` 後に `torch.cat` を行う。`cat` は新しいテンソルを割り当てるため、`append` された各テンソルのメモリは断片化する。
|
||||
|
||||
### 高速化案
|
||||
|
||||
`weight_list` のサイズが事前にわかっているため、`torch.empty` で事前に確保し、`copy_` で埋める。ただし、各 A/B のサイズ(rank)が異なるため、連続したメモリ確保は困難。`torch.cat` の代替として `torch.zeros` で事前確保する方法もあるが、複雑さが増す。
|
||||
|
||||
---
|
||||
|
||||
## 結論
|
||||
|
||||
| 最適化 | 期待効果 | 実装難易度 |
|
||||
|--------|----------|------------|
|
||||
| `_load_lora_state_dict_robust` の `lru_cache` | 中(同一ファイルの2回目以降省略) | 低 |
|
||||
| デバイス転送のバッチ化(モジュール単位) | 小〜中(`.to()` の回数削減) | 中 |
|
||||
| `_awq_lora_forward` の `.to()` 条件スキップ | 小(無駄なコピー排除) | 低 |
|
||||
| `_get_module_by_name` のローカルキャッシュ | 小(走査コスト削減) | 低 |
|
||||
| `all_A` / `all_B_scaled` の事前確保 | 小(メモリ断片化軽減) | 高(rank 可変のため) |
|
||||
|
||||
**最も効果が高く実装が容易なのは 1(ファイルキャッシュ)と 3(AWQ forward の `.to()` スキップ)と 4(モジュールパスキャッシュ)です。**
|
||||
@@ -0,0 +1 @@
|
||||
GFM: remove blank lines between table rows (GitHub rendering)
|
||||
@@ -52,4 +52,4 @@ def __init__(self, model, load_device, offload_device, size=0, weight_inplace_up
|
||||
- **建议**:
|
||||
1. 将 Nunchaku 库与 ComfyUI-Nunchaku 节点更新到最新版本(可缓解 `model_config` 相关问题)
|
||||
2. 考虑对 ComfyUI 核心的 `model_management.py` 应用 None 检查类修复(可能从根因上缓解)
|
||||
- **说明**:这是第一层支持措施。我们已在环境中对 ComfyUI 核心的 `model_management.py` 所应用的修复技术细节已单独整理发布。详见 [COMFYUI_0.4.0_UPDATE_ERROR_FIXES.md](../md/COMFYUI_0.4.0_UPDATE_ERROR_FIXES.md)。请注意,这些修复是在特定环境中实施的,未必适用于所有环境;也可能同时缓解 `copy.deepcopy` 与 `pinned` 属性相关错误。
|
||||
- **说明**:这是第一层支持措施。我们已在环境中对 ComfyUI 核心的 `model_management.py` 所应用的修复技术细节已单独整理发布。详见 [COMFYUI_0.4.0_UPDATE_ERROR_FIXES.md](../zhmd/COMFYUI_0.4.0_UPDATE_ERROR_FIXES.md)。请注意,这些修复是在特定环境中实施的,未必适用于所有环境;也可能同时缓解 `copy.deepcopy` 与 `pinned` 属性相关错误。
|
||||
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
<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 条扩展)
|
||||
Reference in New Issue
Block a user