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),所以一次磁盘读取可以拆出生成链最常用的三个组件。
第一步:由路径注册表解析文件
节点输入只接收 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。
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() 大致分两步:
- 从 key 和 tensor shapes 生成一份
unet_config; - 遍历
supported_models.models,寻找matches(unet_config, state_dict)的模型配置类。
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())注意“构造 model”和“让全部权重常驻 GPU”不是同一步。initial load device 可能是 CPU;Patcher 后续按预算加载。
第八步:拆 VAE
模型配置给出一组 VAE key prefixes。加载器:
- 只筛选这些 key;
- 去掉前缀;
- 让 model config 做 state dict 转换;
- 构造
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 开关。
某些 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 file | diffusion prefix / config 识别失败 |
| 大量 missing / unexpected keys | state dict 转换与目标模型不匹配 |
| no CLIP weights | checkpoint 本身不含 text encoder 或 prefix 规则不匹配 |
| 模型加载成功、采样 shape error | MODEL 与 CLIP/latent 组合不兼容,schema 类型无法表达细粒度架构 |
| 一加载就 OOM | initial 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 运算”通常是错的。
可迁移的设计方法
- 用内容结构与 metadata 识别格式,不信任文件名。
- 把“格式检测/转换”封装为配置策略,把公共装载管线保持稳定。
- 构造逻辑对象与选择物理驻留设备分离。
- 多组件 bundle 应显式拆成类型不同的运行对象。
- 降级路径提供占位错误对象时,要在真正使用处 fail loudly。