| EN | 中文 |
本文说明通过 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 |
典型顺序(实现要点):
- 加载
state_dict - 调用
NunchakuZImage.get_model(patched_sd, "", torch_dtype=...) super().get_model(...)构建model_base.Lumina2→ 构造 NextDiT(各Linear上权重可能尚未就绪)patch_model(diffusion_model, ...)将例如JointAttention替换为ComfyNunchakuZImageAttention- 在该构造器内执行
SVDQW4A4Linear.from_linear(orig_attn.qkv, kwargs) - 之后(视加载器流程而定)
load_model_weights等才会填入权重
因此若 「量化模块被 patch 的时机」 与 「ComfyUI 的 Linear 获得真实权重」 不一致,则在 patch 时刻 weight 仍为 None 是可能的。
在 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 的预期行为。
SVDQW4A4Linear.from_linear 概念上应:
- 从已有
nn.Linear读取 入/出特征、bias、dtype、device - 构建同形状的量化占位
SVDQW4A4Linear(训练权重稍后经state_dict到达)
若 隐含假设 linear.weight 永远非 None,则与 ComfyUI 的延迟 Linear 冲突。
宜理解为 复合问题,而非单一根因。
问题行(修复前 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。
即便修复层 A,linear.weight is None 仍留下 dtype / device 从何处取得 的问题。
- ComfyUI-nunchaku 常在
get_model内patch_model,再load_model_weights - 在
patch_model瞬间,延迟 ComfyUILinear仍可能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 等文档中的务实模式一致。 |
本仓库已在 __init__.py 启动时对 patches/nunchaku_patch.py 打补丁:
- Nunchaku Qwen: 对
NunchakuQwenImageTransformerBlock.forward的 Manual Planar Injection(LoRA) - 对脆弱 import 路径的
sys.modules扫描
本次变更是 同一 apply_nunchaku_patch() 伞盖下 的兼容扩展。
- 手改
site-packages/nunchaku→ 升级即丢失,难复现 - 修补 ComfyUI 核心 → 更新冲突,向他人解释成本高
- 固定 ComfyUI-nunchaku fork → 维护成本
已安装 LoRA 节点包的用户已信任本包 —— 在此集中修复 可降低运维成本。
文件夹名字顺序可能导致 本包先于 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(避免与上游漂移)。 |
以下为 说明用途;行号可能漂移 —— 以仓库源码为准。
作用: 当 linear.weight is None 时,推断 device。
- 优先
comfy.model_management.get_torch_device()(与 ComfyUI 所选推理设备一致) - 失败时(测试等)简单回退
cuda/cpu
原因: 即使稍后 load_model_weights 会填张量,构造阶段 设备一致 对量化 Parameter 更安全。
修复:
- 停止使用错误的
pop;用"torch_dtype" in kwargs分支 → 已提供torch_dtype时不触碰linear.weight weight is None时要求kwargs中必须有torch_dtype,否则抛出明确TypeError(可调试性)device同样模式;缺失则用_torch_device_fallback()
绑定为 classmethod: 原 from_linear 为类方法,故 SVDQW4A4Linear.from_linear = classmethod(_patched_svdqw4a4_from_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相同的安全分支
- 成功导入
nunchaku.models.linear.SVDQW4A4Linear后 覆盖from_linear _svdq_from_linear_patched标志 防止重复应用(重载 / 重复执行韧性)
模块识别启发式(减少误伤):
- 含
fuse_to_svdquant_linear - 含
ComfyNunchakuZImageAttention(Z-Image nunchaku patch 模块指纹) - 含
add_comfy_cast_weights_attr(同文件辅助函数)
已 patch 的函数带 _qwen_lora_loader_lazy_linear_patch 以跳过重复 patch。
问题: 若本包 先 导入(例如按字母序),启动时 zimage 可能尚未加载。
缓解:
- 短延迟后 重试
apply_nunchaku_zimage_fuse_lazy_linear_patch() - 最长约 6 秒(0.25s × 24),待 ComfyUI-nunchaku 加载后再替换
fuse
权衡: 后台 threading.Timer。非 daemon 但 短生命周期且有界,不应表现为永久泄漏。
惰性 Linear 修复与既有 Manual Planar Injection 一并运行(同函数内先 lazy 路径再 planar)。
返回值:
True:Planar patch 成功 或from_linearpatch 成功False:二者皆否(例如未安装 nunchaku)
成功日志明确提及 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 可能因 延迟重试 而 数秒后才出现。
- 若
kwargs缺少torch_dtype且weight is None,本 patch 有意 抛出TypeError。- 倾向 显式失败 而非 静默损坏。
- 只要 ComfyUI-nunchaku 的 Z-Image 加载器持续传入
torch_dtype=,正常使用不应触发。
- Monkey-patch 一般性: 若上游改动相同入口(
SVDQW4A4Linear.from_linear、ComfyUI-nunchaku 的fuse_to_svdquant_linear),可能出现 非预期行为漂移。见下文 §4。 sys.modules启发式: 理论上其他模块也可能满足检查;风险低,且由 强 Z-Image 指纹(ComfyNunchakuZImageAttention)缓解。
本缓解在启动时 替换 已安装 nunchaku 的 classmethod 与 ComfyUI-nunchaku 的 模块级函数。若上游 并入正式修复(与其它文档「副作用」第 6 条同类),行为可整理如下。
- 可能。
apply_nunchaku_patch()仍会在可行时替换from_linear,与上游版本无关。 - 若 意图一致(去掉
pop误用、对weight is None分支),害处通常很小:本 patch 先执行,再与正确上游路径对齐 —— 极少产生结果级冲突。 - 若上游 方案不同(例如
from_linearAPI 变更、迁至其他类),此处陈旧替换 可能导致 仅一层生效 / 不同异常。
- 例如:
SVDQW4A4Linear.from_linear(cls, linear, **kwargs)增加参数、不再是classmethod、返回类型变更、委托给其他辅助函数等。 - 则 钉死在旧契约上的
_patched_svdqw4a4_from_linear可能 启动通过但运行失败 或 静默偏离。 - 缓解: 更新本 patch,或在上游已足够处 移除 / 禁用 lazy-
Linearpatch,或 按 nunchaku / ComfyUI-nunchaku 版本门控 并在新 release 上跳过 patch。
- 当发行说明 / issue 写明 「lazy Linear +
from_linear」已在上游修复 时,请 重新评估该版本起是否仍需要 本 patch。 - 移除时清理
patches/nunchaku_patch.py、apply_nunchaku_patch调用点、schedule_nunchaku_zimage_fuse_patch_retries,若仅需 Planar Injection 则保留后者。 - 在本文档 变更记录 中记下:「上游吸收修复后已移除 ○○」供后续读者查阅。
- 常见情况:仅更新上游而本包较旧 仍 可工作,因本 patch 在加载时 覆盖。
- 若异常,怀疑 nunchaku / ComfyUI-nunchaku / 本包组合;更新本包 或 暂时禁用 以仅复现上游。
| 文档 | 关系 |
|---|---|
| COMFYUI_0.4.0_MODEL_MANAGEMENT_ERRORS.md | 相同 前提:核心修复滞后;用户环境先坏 |
| QWEN_IMAGE_CONTROLNET_AND_GETATTR_FIX.md | 相同 策略:包装 / patch 以满足上游契约 |
| 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 条扩展)