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才走更重路径。
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, ...) 的流程:
- 取得目标 parameter / tensor;
- 首次修改时把原值和原 device 存入 backup;
- 按目标设备/dtype 取得可计算权重;
- 调
comfy.lora.calculate_weight()叠加该 key 的所有 patch; - 必要时 stochastic rounding / cast;
- 原地或替换写回模型属性;
- 可选择返回 patch 后权重。
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。
对称性是关键不变量:任一异常路径都必须让当前变体可被卸下,否则下一次 clone 可能从已经污染的 W’ 再叠 patch。
load():按模块预算权重
经典 Patcher 遍历模块和参数,估计每个 module 的内存,把它分为:
- 可常驻 load device 的模块;
- 需要 offload 的模块;
- low-VRAM 下通过 weight/bias function 按需转换的模块;
- 可 pin 在 CPU 的权重;
- 必须预先应用或可动态应用 patch 的权重。
加载结束记录 model_loaded_weight_memory 等状态,使 model management 知道“总模型多大”和“目前加载多少”不是同一个数。
部分卸载与再加载
partially_unload(device_to, memory_to_free) 尝试从后向前卸载模块,直到释放目标内存;partially_load(device_to, extra_memory) 用新预算把更多模块搬回。
因此 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 管理它们的使用标志和生命周期。
这比“扫描所有节点类型找 Loader”可靠:模型可能来自 cache、运行时子图或自定义节点,真正沿数据边流过的对象才是事实。
ModelPatcherDynamic
本快照在能力满足时用 ModelPatcherDynamic 作为 CoreModelPatcher。它继承同一外部协议,但把权重、patch 和内存子集交给 AIMDO 动态管理,支持更细粒度的按需驻留与回收。
共同语义
- 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 model | RAM/VRAM 成倍增长,加载慢 |
| 直接原地改权重不备份 | 下一个 Prompt 继承污染 |
| clone 共享可变 patch list | 分支间相互改配置 |
| 把“同一 model object”当“同一权重” | 错误复用已加载 clone |
节点自己 .cuda() | 绕过全局预算、offload 和多设备策略 |
| 只记录总模型大小 | 无法做 partial unload/load |
可迁移的设计方法
- 大对象的变体用 copy-on-write 逻辑视图,不做全量复制。
- 所有原地 mutation 必须拥有可验证的反向恢复路径。
- “补丁描述”与“补丁物化”分开,延迟到资源策略已知时。
- 用变体 fingerprint 判断物理状态等价,不只看对象身份。
- 统一资源管理器观察真实数据流中的模型,而不是猜哪些节点会产生模型。