docs: add v2.6.8 Chinese release notes (zhmd)
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#e5e7eb" width="88" height="36"><a href="https://github.com/ussoewwin/ComfyUI-QwenImageLoraLoader/releases/tag/v2.6.8"><font color="#4b5563"><b>EN</b></font></a></td>
|
||||
<td align="center" bgcolor="#3478ca" width="88" height="36"><font color="#ffffff"><b>中文</b></font></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 1. 变更内容与原因
|
||||
|
||||
### 1.1 背景:ComfyUI v0.44.0 通过 cast 契约供给权重
|
||||
|
||||
自 ComfyUI v0.44.0 起(dynamic VRAM / aimdo 默认开启),模块 `forward` 执行时层权重不再保证位于计算设备上。权重保持文件形态(CPU / 虚拟内存后端),直到该模块加入核心的权重供给契约:
|
||||
|
||||
```python
|
||||
# comfy/ops.py (v0.44.0, disable_weight_init.Linear.forward)
|
||||
def forward(self, *args, **kwargs):
|
||||
run_every_op()
|
||||
if self.comfy_cast_weights or len(self.weight_function) > 0 or len(self.bias_function) > 0:
|
||||
return self.forward_comfy_cast_weights(*args, **kwargs) # 契约路径
|
||||
else:
|
||||
return super().forward(*args, **kwargs) # 直接读 self.weight
|
||||
```
|
||||
|
||||
契约路径(`forward_comfy_cast_weights` → `CastBiasWeightContext` → `cast_bias_weight`)负责把权重送到输入设备:vbar 管理的层走 `comfy/ops.py:396-414`,普通张量走 `comfy/ops.py:417-462`。未加入契约的模块直接读 `self.weight`,拿到的是权重当时碰巧所在的设备。
|
||||
|
||||
经由 `ModelPatcher` 创建并加载的模块由核心完成 arm。问题出在这条路径之外。
|
||||
|
||||
### 1.2 症状
|
||||
|
||||
Qwen Image ControlNet 不经过 wrapper 的 `forward` 调用扩散模型,而是直接伸手到 base model(`comfy/ldm/qwen_image/controlnet.py:151-162`):
|
||||
|
||||
```python
|
||||
hidden_states = base_model.img_in(hidden_states)
|
||||
encoder_hidden_states = base_model.txt_norm(context)
|
||||
encoder_hidden_states = base_model.txt_in(encoder_hidden_states)
|
||||
temb = base_model.time_text_embed(timesteps, hidden_states)
|
||||
```
|
||||
|
||||
这里的 `base_model` 是本包的 `ComfyQwenImageWrapper`,其 `__getattr__` 把未知属性转发给内层 nunchaku 模型。借来的 `img_in` 是一个 `comfy_cast_weights = False` 的普通 `disable_weight_init.Linear` —— 从未加入契约。首次失败形如:
|
||||
|
||||
```
|
||||
File "comfy/samplers.py", line 330, in _calc_cond_batch
|
||||
c['control'] = control.get_control(input_x, timestep_, c, len(cond_or_uncond), transformer_options)
|
||||
File "comfy/controlnet.py", line 320, in get_control
|
||||
control = self.control_model(x=x_noisy.to(dtype), hint=self.cond_hint, ...)
|
||||
File "comfy/ldm/qwen_image/controlnet.py", line 151, in forward
|
||||
hidden_states = base_model.img_in(hidden_states)
|
||||
File "comfy/ops.py", line 641, in forward
|
||||
return super().forward(*args, **kwargs)
|
||||
RuntimeError: Expected all tensors to be on the same device, but got mat1 is on cuda:0,
|
||||
different from other tensors on cpu (when checking argument in method wrapper_CUDA_addmm)
|
||||
```
|
||||
|
||||
### 1.3 为什么第一次成功、第二次失败
|
||||
|
||||
在受影响的工作流上连续实测 10 次(ComfyUI v0.44.0,torch 2.15.0+cu134):
|
||||
|
||||
| 次序 | ControlNet loader 是否重新执行 | 结果 | 耗时 |
|
||||
|---|---|---|---|
|
||||
| 1 | 是 | 成功 | 106.06 s |
|
||||
| 2 | 否(节点缓存) | 失败 | 31.56 s |
|
||||
| 3 | 是 | 成功 | 68.24 s |
|
||||
| 4-10 | 否(节点缓存) | 失败 | 5.5 - 9.5 s |
|
||||
|
||||
- loader 重新执行时,ControlNet 的 state dict 被重新读取(`Found quantization metadata version 1`),权重在采样前经加载路径到达 GPU。
|
||||
- ComfyUI 的节点输出缓存返回缓存的 `CONTROL_NET` / `MODEL` 对象时,loader 不会再次执行。工作流末端的 purge 节点此前已经 unload 了模型,于是缓存对象携带的权重已被放回 CPU / 文件形态——而被借用的模块仍然直接读取它们。
|
||||
|
||||
因此这是一个生命周期缝隙:缓存复用 + purge + 一条位于核心契约之外的直接属性借用。当 v0.44.0 改变了默认权重放置方式后,它才变成致命问题。
|
||||
|
||||
## 2. 修复:把借用的模块接入核心契约
|
||||
|
||||
`wrappers/qwenimage.py` —— `ComfyQwenImageWrapper.__init__` 现在调用一个静态辅助方法:
|
||||
|
||||
```python
|
||||
# wrappers/qwenimage.py (v2.6.8)
|
||||
_CONTROLNET_BORROWED_MODULES = (
|
||||
"img_in",
|
||||
"txt_in",
|
||||
"txt_norm",
|
||||
"time_text_embed",
|
||||
"pe_embedder",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _enable_weight_supply_contract(model):
|
||||
"""Opt the modules ControlNet borrows into ComfyUI's cast contract."""
|
||||
for attribute in _CONTROLNET_BORROWED_MODULES:
|
||||
root = getattr(model, attribute, None)
|
||||
if root is None:
|
||||
continue
|
||||
seen = set()
|
||||
stack = [root]
|
||||
while stack:
|
||||
module = stack.pop()
|
||||
if id(module) in seen:
|
||||
continue
|
||||
seen.add(id(module))
|
||||
if hasattr(module, "comfy_cast_weights") and not module.comfy_cast_weights:
|
||||
module.comfy_cast_weights = True
|
||||
stack.extend(module.children())
|
||||
|
||||
# ComfyQwenImageWrapper.__init__ 的最后一行:
|
||||
self._enable_weight_supply_contract(model)
|
||||
```
|
||||
|
||||
性质:
|
||||
|
||||
- **范围限定**:只触碰 ControlNet 实际访问 `base_model` 的 5 个属性(`img_in`、`txt_in`、`txt_norm`、`time_text_embed`、`pe_embedder`)及其子模块;模型其余部分保持构造时的原样(下方实测)。
|
||||
- **幂等**:已加入契约的模块直接跳过。
|
||||
- **追随核心而不是对抗核心**:dynamic VRAM 保持开启;不需要启动参数、不改 ComfyUI 本体、不改 nunchaku、不改工作流。核心移动权重时,`cast_bias_weight` 在每次 forward 把权重供给到输入设备——包括 ControlNet 这条路径。
|
||||
|
||||
## 3. 本版本发布后的仓库状态
|
||||
|
||||
| 路径 | 变更 | 说明 |
|
||||
|---|---|---|
|
||||
| `wrappers/qwenimage.py` | +38 / −0 | `ComfyQwenImageWrapper` 把借用模块接入权重供给契约 |
|
||||
| ComfyUI 本体 / nunchaku 包 / 工作流 | 无改动 | 本包之外的文件零修改 |
|
||||
| `RELEASE_NOTES/changelog.md` / `zhmd/changelogzh.md` | changelog | v2.6.8 条目(合计 +16 / −2) |
|
||||
| `pyproject.toml` / `__init__.py` | 版本号 | 2.6.7 → 2.6.8 |
|
||||
|
||||
## 4. 实测验证
|
||||
|
||||
* 契约关闭复现:权重在 CPU、`comfy_cast_weights = False` 时,`F.linear` 抛出与上文完全相同的 `RuntimeError`(python_embeded,torch 2.15.0+cu134,cuda:0)。
|
||||
* 契约开启:同一层返回 CUDA 结果;对单位权重,输出与输入一致(`maxdiff` 0.0)。
|
||||
* 范围:`img_in` / `txt_in` 由 `False` 翻转为 `True`;5 个借用属性之外的同级 Linear 保持 `False`。
|
||||
* 部署副本:与仓库文件逐字节一致(SHA256 `20061EBC58DBFCC1BDCAF33EC8628F6AA86F7339B5229BDEBBF9328F55903262`),`py_compile` 通过。
|
||||
* 真实工作流:受影响的 i2i 工作流(ControlNet + Nunchaku Qwen Image + LoRA 栈 + EasyCache)在 ComfyUI v0.44.0 上连续执行正常——无 `RuntimeError`、无 fallback latent 输出、每次都出图。
|
||||
|
||||
## 5. 需要用户做什么
|
||||
|
||||
* 通过 ComfyUI Manager / git pull 更新本包到 v2.6.8,重启 ComfyUI。
|
||||
* 无需修改工作流、无需改 ComfyUI 本体、无需改 nunchaku、无需任何启动参数。
|
||||
|
||||
## 6. 备注
|
||||
|
||||
* `_CONTROLNET_BORROWED_MODULES` 中的 5 个属性是 Qwen Image ControlNet 当下访问 `base_model` 的模块属性全集的实测范围(`comfy/ldm/qwen_image/controlnet.py:143-162`;`process_img` 是方法调用,不需要契约)。若上游将来增加借用属性,把它们加进同一元组即可。
|
||||
* 工作流末端的 purge 节点与 ComfyUI 的节点输出缓存保持原样;契约到位后,它们对这条路径不再构成影响。
|
||||
|
||||
## 7. 相关链接
|
||||
|
||||
* 变更提交:https://github.com/ussoewwin/ComfyUI-QwenImageLoraLoader/commit/b8cd1df
|
||||
* English version: https://github.com/ussoewwin/ComfyUI-QwenImageLoraLoader/releases/tag/v2.6.8
|
||||
Reference in New Issue
Block a user