Skip to content

Latest commit

 

History

History
333 lines (210 loc) · 16.5 KB

File metadata and controls

333 lines (210 loc) · 16.5 KB
EN 中文

Z-Image / SVDQ:NoneType 上的 .dtype——ComfyUI 延迟(惰性)Linearpop 默认值求值与本节点包的缓解措施(详解)

本文说明通过 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.weightNone,但代码读取了 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.LinearWindows 且 aimdo_enabled 时大致行为:

  • 跳过常规 torch.nn.Linearsuper().__init__(...);仅 torch.nn.Module.__init__
  • 初始 self.weight = Noneself.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 NoneNone.dtypeAttributeError
  • 即使调用方 正确传入 torch_dtype=仍会同一 AttributeError(即「明明传了 kwargs 仍崩」的真实原因)

语言层面结论:pop 的 default 当作「惰性回退」是 错误的。若回退代价高、有副作用或可能失败,应使用 if "k" in kwargstry/except KeyError

层 B:顺序 —— ComfyUI 延迟 Linear 与 ComfyUI-nunchaku「先 patch」

即便修复层 A,linear.weight is None 仍留下 dtype / device 从何处取得 的问题。

  • ComfyUI-nunchaku 常在 get_modelpatch_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_modelload_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.forwardManual 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_linearSVDQW4A4Linear.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()

绑定为 classmethodfrom_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)。

返回值:

  • TruePlanar 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_dtypeweight 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.pyapply_nunchaku_patch 调用点schedule_nunchaku_zimage_fuse_patch_retries,若仅需 Planar Injection 则保留后者。
  • 在本文档 变更记录 中记下:「上游吸收修复后已移除 ○○」供后续读者查阅。

4.4 用户可见现象

  • 常见情况:仅更新上游而本包较旧可工作,因本 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.weightNone,而 Nunchaku 的 from_linear 误用了 pop 的 default,共同导致 None.dtypeComfyUI-nunchaku 的 FF 融合可共享同一脆弱性本仓库扩展 apply_nunchaku_patch,以 monkey-patch 与延迟重试使用户不必苦等上游发布。


文档变更记录

  • 初稿:说明惰性 Linearpop 默认值求值及在本仓库中的吸收方式
  • 增补:§4 —— 双重 patch、API 漂移及上游并入同修复后的维护(由副作用第 6 条扩展)