Skip to Content
Ⅲ · 模型与采样09 · Checkpoint 与模型构造

09 · Checkpoint 与模型构造

Checkpoint Loader 的名字容易让人以为它只是 torch.load()。实际链路要从一袋没有 Python 类型信息的 state dict 推断模型家族、UNet 前缀、参数规模、权重 dtype、文本编码器与 VAE,再选择加载设备和包装器。

关键结论: ComfyUI 主要依据权重 key、shape 与 metadata 识别模型,不靠文件名决定它是 SD1、SDXL、Flux 或其他架构。

入口链路

CheckpointLoaderSimple.load_checkpoint → folder_paths.get_full_path_or_raise → comfy.sd.load_checkpoint_guess_config → utils.load_torch_file → load_state_dict_guess_config → model_detection.model_config_from_unet → model_config.get_model → CoreModelPatcher

CheckpointLoaderSimple 同时返回 (MODEL, CLIP, VAE),所以一次磁盘读取可以拆出生成链最常用的三个组件。

nodes.pyL555-580CheckpointLoaderSimple 的输入、输出与 load_checkpoint 调用。

第一步:由路径注册表解析文件

节点输入只接收 checkpoint 下拉框里的相对名字。folder_paths 根据 checkpoints 类别解析完整路径,并在找不到时抛错。

这比节点接受任意绝对路径多一层治理:

  • UI 枚举与后端解析共享同一目录注册;
  • extra model paths 可挂载多个模型盘;
  • API 客户端不能通过这个字段任意读取系统文件;
  • 缓存 fingerprint 可围绕规范路径表达外部状态。

第二步:安全读取容器

comfy.utils.load_torch_file() 根据扩展名选择:

  • .safetensors:通过 safetensors API 读取张量与 metadata;
  • 其他 torch checkpoint:使用受限/安全加载选项,按配置控制 pickle 风险;
  • 可指定目标 device,默认通常先落 CPU;
  • 可选择同时返回 metadata。
comfy/utils.pyL122-200safetensors、torch checkpoint 与 metadata 读取。

Safetensors 减少任意对象反序列化风险,但文件仍可巨大或畸形;“安全格式”不等于不需要来源校验和资源限制。

第三步:找到 diffusion model 前缀

一个整包 checkpoint 可能混合:

model.diffusion_model.* diffusion / UNet / transformer first_stage_model.* VAE cond_stage_model.* text encoder ... metadata / legacy / extra weights

不同训练/导出工具使用不同前缀。unet_prefix_from_state_dict() 检查 key 模式,找出主生成模型部分。后续参数数量和 dtype 都只围绕该 prefix 计算。

第四步:先量尺寸与 dtype

load_state_dict_guess_config() 计算:

parameters = diffusion prefix 下的总参数数量 weight_dtype = 从权重张量观察到的存储 dtype load_device = model option 或全局执行设备

参数量参与“能否直接初始放 GPU”的预算,weight dtype 与模型支持 dtype 一起决定推理 dtype / manual cast。

如果没有自定义 operations,旧量化表示会先被转换为当前内部形式;带 quant config 的模型又走自己的 dtype 分支。

comfy/sd.pyL2022-2055前缀、参数、dtype、量化兼容与 model config 识别。

第五步:从 state dict 猜架构

model_detection.model_config_from_unet() 大致分两步:

  1. 从 key 和 tensor shapes 生成一份 unet_config;
  2. 遍历 supported_models.models,寻找 matches(unet_config, state_dict) 的模型配置类。
comfy/model_detection.pyL1247-1331UNet config 到 supported model config 的匹配。

supported_models.py 的注册表不是模型文件列表,而是一组懂得以下行为的配置类:

  • 怎样构造具体 BaseModel 子类;
  • latent format 是什么;
  • 支持哪些推理 dtype;
  • checkpoint key 如何预处理;
  • CLIP target / tokenizer 怎样选择;
  • VAE 与 text encoder 前缀怎样拆;
  • 是否需要特定 model sampling。

为什么不能靠文件名

文件名可随意改,且同一模型可有不同量化或合并版本。权重形状才是构造网络所需的事实。例如输入通道数、block 数、attention dim、context dim 都可从关键 tensor shape 推断。

如果识别失败

代码先警告“可能不是完整 checkpoint”,再尝试按 diffusion-model-only 加载。若仍失败返回 None。成功的纯 diffusion model 会配一个占位 VAE;真正使用该 VAE 时应报错,而不是悄悄产生错误图像。

这是很好的降级策略:允许更窄格式,但让缺失组件在被需求时显式失败。

第六步:选择推理 dtype 与 manual cast

模型配置声明 supported_inference_dtypes。运行时综合:

  • 用户/节点强制的 dtype;
  • checkpoint weight dtype;
  • 设备支持(fp16、bf16、fp8 等);
  • 参数规模与内存;
  • quant config;
  • 自定义 operations。

最终得到:

unet_dtype 模型希望以什么 dtype 推理/驻留 manual_cast_dtype 某些层是否在算子边界手工转换

这两者不总相同。低精度存储可以在不受支持的算子上临时提升,设备能力也可能迫使权重与计算 dtype 分离。

第七步:构造 diffusion model

配置对象通过 get_model(sd, prefix, device=initial_load_device) 创建具体模型骨架;随后选择包装类:

ModelPatcher = ModelPatcher if disable_dynamic else CoreModelPatcher patcher = ModelPatcher(model, load_device=..., offload_device=...) model.load_model_weights(sd, prefix, assign=patcher.is_dynamic())
comfy/sd.pyL2056-2076模型实例、加载/卸载设备与 CoreModelPatcher。

注意“构造 model”和“让全部权重常驻 GPU”不是同一步。initial load device 可能是 CPU;Patcher 后续按预算加载。

第八步:拆 VAE

模型配置给出一组 VAE key prefixes。加载器:

  1. 只筛选这些 key;
  2. 去掉前缀;
  3. 让 model config 做 state dict 转换;
  4. 构造 VAE(sd=..., metadata=..., device=...)。

VAE 有自己的加载设备、dtype、tile encode/decode 与内存估算,不必与 diffusion model 永远同驻 GPU。

第九步:拆 CLIP / 文本编码器

model_config.clip_target(state_dict=sd) 决定文本侧架构;process_clip_state_dict() 提取并转换权重。若确实有权重,则构造 CLIP,并传入 embedding directory、tokenizer data、参数量、options 与 dynamic 开关。

comfy/sd.pyL2085-2115scaled FP8 兼容、CLIP target 与文本编码器构造。

某些 checkpoint 不内含文本编码器,代码会警告而不是伪造对象。工作流可以用独立 CLIP Loader 补充组件。

第十步:处理剩余 key

被模型、VAE、CLIP 消费后仍有 key,debug 日志打印 leftovers。剩余不一定表示失败:checkpoint 可能包含 EMA、训练状态或未使用组件。但大量核心前缀 leftover 常是架构识别或转换不完整的信号。

直接 GPU 加载路径

若 unet_inital_load_device() 根据参数量和空闲内存选择了非 CPU,权重已直接构造在执行设备。加载器随后调用 load_models_gpu(..., force_full_load=True) 登记完整加载状态。

这避免 CPU → GPU 二次复制,但只有预算足够时成立。不能从日志“loaded checkpoint”推断模型一定全在 GPU。

state dict 到运行对象的分解图

为什么 ModelConfig 是关键抽象

如果 load_checkpoint_guess_config 为每种模型写一大串 if key exists,公共加载逻辑会被模型差异淹没。ModelConfig 把变化点收拢:检测、model class、latent、clip、vae、dtype、state-dict conversion。

新增模型通常需要扩充 detection/config/model implementation,而不必重写 Queue、ExecutionList、Patcher 或 Sampler 的控制骨架。

常见失败与证据

表现更可能的层
not a checkpoint filediffusion prefix / config 识别失败
大量 missing / unexpected keysstate dict 转换与目标模型不匹配
no CLIP weightscheckpoint 本身不含 text encoder 或 prefix 规则不匹配
模型加载成功、采样 shape errorMODEL 与 CLIP/latent 组合不兼容,schema 类型无法表达细粒度架构
一加载就 OOMinitial device / dtype / model management 预算
文件名存在但节点找不到folder_paths 类别与 extra model paths,不是 model detection
pickle/security error非 safetensors 加载限制或文件来源问题

不要混淆的四个 dtype

file weight dtype 文件里张量怎样存 model inference dtype 模型主要以什么精度计算/驻留 manual cast dtype 某些 ops 临时转到什么精度 latent / output dtype 节点间张量自身的精度

它们可以相同,也可以不同。把“这是 FP8 checkpoint”直接等价于“所有算子都 FP8 运算”通常是错的。

可迁移的设计方法

  1. 用内容结构与 metadata 识别格式,不信任文件名。
  2. 把“格式检测/转换”封装为配置策略,把公共装载管线保持稳定。
  3. 构造逻辑对象与选择物理驻留设备分离。
  4. 多组件 bundle 应显式拆成类型不同的运行对象。
  5. 降级路径提供占位错误对象时,要在真正使用处 fail loudly。
Last updated on