From 8ade4e560656a43cc2ef9b6a3e3e85b0cec01cb3 Mon Sep 17 00:00:00 2001
From: ussoewwin <136552381+ussoewwin@users.noreply.github.com>
Date: Thu, 21 May 2026 13:08:53 +0900
Subject: [PATCH] docs: fix cross-reference link from English to Chinese in
zhmd/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md
---
md/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md | 4 +-
md/PERFORMANCE_OPTIMIZATION_PLAN.md | 184 ++++++++++
release/_gh_notes_gfm.txt | 1 +
zhmd/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md | 2 +-
zhmd_github.md | 333 ++++++++++++++++++
5 files changed, 521 insertions(+), 3 deletions(-)
create mode 100644 md/PERFORMANCE_OPTIMIZATION_PLAN.md
create mode 100644 release/_gh_notes_gfm.txt
create mode 100644 zhmd_github.md
diff --git a/md/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md b/md/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md
index d5a487a..b75de98 100644
--- a/md/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md
+++ b/md/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md
@@ -2,8 +2,8 @@
diff --git a/md/PERFORMANCE_OPTIMIZATION_PLAN.md b/md/PERFORMANCE_OPTIMIZATION_PLAN.md
new file mode 100644
index 0000000..abef37a
--- /dev/null
+++ b/md/PERFORMANCE_OPTIMIZATION_PLAN.md
@@ -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(モジュールパスキャッシュ)です。**
diff --git a/release/_gh_notes_gfm.txt b/release/_gh_notes_gfm.txt
new file mode 100644
index 0000000..8aba030
--- /dev/null
+++ b/release/_gh_notes_gfm.txt
@@ -0,0 +1 @@
+GFM: remove blank lines between table rows (GitHub rendering)
diff --git a/zhmd/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md b/zhmd/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md
index 107f528..1f8669d 100644
--- a/zhmd/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md
+++ b/zhmd/COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md
@@ -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` 属性相关错误。
diff --git a/zhmd_github.md b/zhmd_github.md
new file mode 100644
index 0000000..ee2739d
--- /dev/null
+++ b/zhmd_github.md
@@ -0,0 +1,333 @@
+
+
+## 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 条扩展)
\ No newline at end of file