Skip to Content
Ⅲ · 模型与采样10 · ModelPatcher 与权重视图

10 · ModelPatcher 与权重视图

一份 12 GB checkpoint 加三组 LoRA,不应复制出四份完整模型。ComfyUI 用 ModelPatcher 表达“共享基础模型上的一个逻辑变体”,同时负责把补丁落实到权重、把模型移入/移出设备,并在不同变体切换时恢复状态。

核心分离: model 是基础网络对象;ModelPatcher 是该网络在一组 patches、hooks、options 与设备策略下的可执行视图。

它包装了什么

comfy/model_patcher.pyL340-429ModelPatcher 初始化与核心状态。

概念上:

ModelPatcher ├── model 基础 torch module ├── load_device 执行设备 ├── offload_device 卸载设备 ├── patches 权重 key → patch 列表 ├── backup 被改写权重的原值/设备 ├── object_patches 非 tensor 对象替换 ├── model_options sampler / transformer 行为选项 ├── model_keys 本 patcher 触碰的 keys ├── hooks / callbacks 执行阶段扩展 ├── parent 动态/派生视图父级 └── patches_uuid 补丁集合身份

它同时横跨“逻辑配置”和“物理资源”。这不是最纯粹的单一职责,但能保证 patch 应用与设备移动遵循同一生命周期。

clone:廉价模型分支

clone() 默认:

  • 共享同一个底层 model;
  • 复制 patch / option / hook 等容器;
  • 共享或协调 backup 信息;
  • 创建新的 patcher 身份与父关系;
  • 可选择禁用 dynamic 或指定 model override;
  • 只有显式 force_deepcopy 才走更重路径。
comfy/model_patcher.pyL430-504共享模型、复制视图状态的 clone。

clone 之后修改 B 的 patch list 不应污染 C 的逻辑配置;但它们不能同时把共享权重改成两种状态。模型管理器在加载 clone 时检测同源变体,卸下/切换旧状态。

clone_has_same_weights

两个 patcher 是否共享 model object 不足以判断实际权重等价。方法还比较 patches UUID、object patches、model options 和其他影响权重/执行的状态。

它用于避免不必要重载,也防止把“同一个底座、不同 LoRA”误当成已加载目标。

patches 的记录格式

add_patches(patches, strength_patch, strength_model) 遍历外部解析好的 patch:

patches[weight_key].append( strength_patch, patch_value, strength_model, optional_offset, optional_function )

只接受模型中存在的 key,返回实际应用的 key 集合,并刷新 patches UUID。

comfy/model_patcher.pyL834-856补丁登记,不在此时复制完整权重。

这里登记的是“如何修改”,不是立刻把所有权重算一遍。延迟应用让多个 LoRA 节点可以继续组合,也让设备与 dtype 决策留到加载时。

一块权重怎样被 patch

patch_weight_to_device(key, ...) 的流程:

  1. 取得目标 parameter / tensor;
  2. 首次修改时把原值和原 device 存入 backup;
  3. 按目标设备/dtype 取得可计算权重;
  4. 调 comfy.lora.calculate_weight() 叠加该 key 的所有 patch;
  5. 必要时 stochastic rounding / cast;
  6. 原地或替换写回模型属性;
  7. 可选择返回 patch 后权重。
comfy/model_patcher.pyL891-920单权重的备份、LoRA 合成与写回。

strength_model 与 strength_patch

抽象地看:

W' = strength_model · W + strength_patch · ΔW

具体 patch 类型可能是 LoRA low-rank 对、LoHa、LoKr、直接 diff 或带 offset 的局部权重,calculate_weight() 负责分派。Patcher 只统一生命周期与组合顺序。

offset patch

补丁可只作用于权重切片。它适合多个逻辑模块打包进一个 tensor 的架构;backup 仍需保证卸载时能恢复完整原始语义。

patch_model 与 unpatch_model

patch_model

先应用 object patches 和 model options,再根据 load_weights 调 load()。返回基础 model,供 sampler 真正 forward。

unpatch_model

恢复 object patches;若 unpatch_weights,从 backup 恢复原权重并清空相关状态;最后按要求把模型移到 offload device。

comfy/model_patcher.pyL1105-1161patch / unpatch 的对称生命周期。

对称性是关键不变量:任一异常路径都必须让当前变体可被卸下,否则下一次 clone 可能从已经污染的 W’ 再叠 patch。

load():按模块预算权重

经典 Patcher 遍历模块和参数,估计每个 module 的内存,把它分为:

  • 可常驻 load device 的模块;
  • 需要 offload 的模块;
  • low-VRAM 下通过 weight/bias function 按需转换的模块;
  • 可 pin 在 CPU 的权重;
  • 必须预先应用或可动态应用 patch 的权重。
comfy/model_patcher.pyL974-1104完整/低显存装载、module 预算和 patch 实施。

加载结束记录 model_loaded_weight_memory 等状态,使 model management 知道“总模型多大”和“目前加载多少”不是同一个数。

部分卸载与再加载

partially_unload(device_to, memory_to_free) 尝试从后向前卸载模块,直到释放目标内存;partially_load(device_to, extra_memory) 用新预算把更多模块搬回。

comfy/model_patcher.pyL1163-1274在不摧毁逻辑模型的情况下伸缩设备驻留。

因此 LoadedModel 可以回答:

model_size() 总权重大小 loaded_size() 当前执行设备已加载大小 offloaded 二者差值

这比“model 在 cuda 还是 cpu”的二元状态更贴合低显存推理。

model_options:不改权重也能改变模型

Patcher 还承载 sampler / transformer options,例如:

  • conditioning / CFG 相关 function;
  • attention 替换;
  • transformer block patches;
  • wrapper callbacks;
  • dtype / cast 行为;
  • seed 与 batch 上下文。

这些选项可能改变 forward 的可观察结果,所以 clone、缓存和权重等价判断不能只比较 LoRA key。

object patches

并非所有扩展都能表示成 tensor delta。Patcher 可替换模型对象的属性或子模块,并记录原对象以恢复。它与 weight backup 一样需要严格对称。

风险也更高:第三方补丁若替换共享对象后没有正确恢复,可能跨 Prompt 泄漏。优先使用受支持的 patch API,而不是直接 monkey-patch 底层 model。

PromptModelTracker

执行器创建 PromptModelTracker,ExecutionList 在缓存 link 首次看到 Patcher 输出时调用 add()。它跟踪本 Prompt 实际引用的模型视图,并在 prompt start/end 管理它们的使用标志和生命周期。

comfy/model_patcher.pyL50-96Prompt 级模型引用跟踪。

这比“扫描所有节点类型找 Loader”可靠:模型可能来自 cache、运行时子图或自定义节点,真正沿数据边流过的对象才是事实。

ModelPatcherDynamic

本快照在能力满足时用 ModelPatcherDynamic 作为 CoreModelPatcher。它继承同一外部协议,但把权重、patch 和内存子集交给 AIMDO 动态管理,支持更细粒度的按需驻留与回收。

comfy/model_patcher.pyL1741-1844Dynamic 实现的状态与内存管理接入。

共同语义

  • clone / patches / options 仍存在;
  • load、patch_model、unpatch_model、partial load/unload 仍是边界;
  • 模型节点不需要知道当前具体实现。

不同实现

  • 经典实现直接遍历 module/weight 并维护 backup;
  • dynamic 实现可把 weights-loaded、patches-loaded、weights、patches 作为不同内存子集管理;
  • dynamic 模型之间的 free 策略避免为另一个 dynamic model 粗暴整模卸载。

这就是为什么前一章强调 CoreModelPatcher 是启动期策略注入点。

LoRA 工作流的生命周期

错误设计的后果

反模式后果
每个 LoRA deepcopy modelRAM/VRAM 成倍增长,加载慢
直接原地改权重不备份下一个 Prompt 继承污染
clone 共享可变 patch list分支间相互改配置
把“同一 model object”当“同一权重”错误复用已加载 clone
节点自己 .cuda()绕过全局预算、offload 和多设备策略
只记录总模型大小无法做 partial unload/load

可迁移的设计方法

  1. 大对象的变体用 copy-on-write 逻辑视图,不做全量复制。
  2. 所有原地 mutation 必须拥有可验证的反向恢复路径。
  3. “补丁描述”与“补丁物化”分开,延迟到资源策略已知时。
  4. 用变体 fingerprint 判断物理状态等价,不只看对象身份。
  5. 统一资源管理器观察真实数据流中的模型,而不是猜哪些节点会产生模型。
Last updated on