ControlNet模型文件承载着控制权重和主干网络参数,目前社区中常见的权重文件主要有两种:.pth和.safetensors。它们虽然都能存储张量数据,但内部布局和加载机制完全不同。理解这两种格式的结构差异,有助于在部署和微调ControlNet时避免加载失败、内存溢出或安全隐患。

.pth格式的内部结构与加载逻辑
先看.pth文件。它本质上是Python的pickle序列化产物。PyTorch的torch.save函数默认使用pickle模块将对象图写入磁盘。在较新的PyTorch版本中,torch.save可以选择使用ZIP文件格式作为外层容器,但内部仍然包含一个由pickle序列化的data.pkl文件。无论是早期纯pickle流还是新版ZIP容器,加载.pth文件时都需要通过pickle反序列化过程,这意味着任何被序列化进去的Python对象都会在加载时被还原。
这种机制带来了一个显著的安全问题。因为pickle支持还原任意Python类实例,恶意构造的.pth文件可以在加载时执行系统命令或写入文件。ControlNet模型文件往往从社区下载,如果来源不可信,直接调用torch.load会带来服务器被入侵的风险。另外,pickle序列化的数据不能进行内存映射,必须完整读入内存再反序列化,对于动辄几个GB的ControlNet模型,加载时间和内存峰值都偏高。
从字节结构上看,一个典型的.pth文件开头通常是pickle协议的操作码,比如PROTO、GLOBAL等,然后是state_dict的键值对。下面这段代码展示了如何加载并查看ControlNet权重中的键名:
import torch
# 加载ControlNet的.pth权重文件
state_dict = torch.load("controlnet.pth", map_location="cpu")
print(type(state_dict))
print(state_dict.keys())
for key, value in state_dict.items():
print(key, value.shape, value.dtype)
上述代码中的torch.load会触发完整的反序列化流程。如果文件带有恶意pickle指令,执行到这里就已经产生风险。因此,在处理来源不明的.pth文件时,应当先在隔离环境中检查,或者直接转换为更安全的格式。
.safetensors格式的内部结构与安全设计
.safetensors是Hugging Face提出的一种张量存储格式,专门解决pickle的安全问题和跨语言兼容性。它的文件布局非常简单:最前面8个字节是一个无符号64位小端整数,表示头部JSON的长度;紧接着是头部JSON文本;之后就是连续排列的原始张量二进制数据。头部JSON中记录了每个张量的名称、数据类型、形状以及数据在文件中的起始偏移和结束偏移。
这种设计的核心优势在于加载时不需要解析任何可执行代码。读取程序只需先读出8字节长度,再读取对应长度的JSON头,然后根据偏移量直接从文件中取出张量数据。整个过程不会触发Python对象反序列化,因此无法执行恶意代码。同时,张量数据可以以内存映射方式读取,只有真正访问某个张量时才将对应部分映射进内存,这对加载大型ControlNet模型非常友好,尤其适合在内存受限的推理服务器上运行。
下面用Python解析一个.safetensors文件的头部,观察它的内部结构:
import json
import struct
with open("controlnet.safetensors", "rb") as f:
header_size_bytes = f.read(8)
header_size = struct.unpack("<Q", header_size_bytes)[0]
header_json = json.loads(f.read(header_size).decode("utf-8"))
print(header_size)
for name, info in header_json.items():
if name == "__metadata__":
continue
print(name, info["dtype"], info["shape"], info["data_offsets"])
代码中的struct.unpack使用了小端格式标记。解析出的JSON头会包含类似controlnet_cond_embedding.weight这样的键,每个键下面有dtype、shape和data_offsets字段。data_offsets是一个二元数组,表示该张量在文件数据区中的起始和结束字节位置。这种结构让读取器可以直接定位并复制或映射所需的张量块,而不必读取整个文件。
两种格式的性能、安全与兼容性对比
从安全角度看,.safetensors的优势是决定性的。.pth格式继承了pickle的任意代码执行能力,而.safetensors只包含纯数据和元数据,没有任何代码路径。对于需要从互联网下载ControlNet模型的用户来说,优先选择.safetensors可以显著降低供应链攻击风险。
在加载性能方面,.safetensors支持零拷贝和内存映射,加载一个包含大量卷积层和Transformer块的ControlNet模型时,通常可以获得更快的启动速度。.pth则需要完整反序列化,不仅CPU开销更高,还可能导致内存峰值达到文件大小加解码临时对象的总和。文件体积方面,两者通常接近,因为底层都存储原始张量,但.pth在ZIP容器方式下可能包含压缩,而.safetensors默认不压缩,不过可以通过插件实现压缩。
兼容性上,.pth由PyTorch生态直接支持,几乎不需要额外依赖;.safetensors需要安装safetensors库,但该库已经支持Python、Rust、C++等多种语言,跨语言读取更方便。下面给出从.pth转换到.safetensors的代码:
import torch
from safetensors.torch import save_file
state_dict = torch.load("controlnet.pth", map_location="cpu")
save_file(state_dict, "controlnet.safetensors")
print("conversion done")
需要注意的是,如果.pth文件本身带有恶意代码,转换过程仍然会执行其中的pickle指令。因此,转换应当在受控的沙箱环境中进行,或者确认文件来源可信后再操作。反之,从.safetensors转回.pth则相对安全,因为读取.safetensors不会执行任何代码。
ControlNet项目中的格式选择建议
在实际的ControlNet部署和使用中,建议遵循以下原则:发布模型时优先提供.safetensors格式,并在文件名或模型卡中写明校验信息;下载模型时优先选择.safetensors版本,避免直接加载来源不明的.pth文件;如果只有.pth版本,可以先用一次性容器或独立Python进程完成转换,再删除原始文件。
对于需要频繁热加载模型的在线服务,.safetensors的内存映射特性能够显著降低多模型切换时的内存压力。例如,使用safetensors库的safe_open接口,可以只加载模型的前几层进行快速预览,而不必一次性把整个文件读入内存。这让ControlNet在处理不同控制条件时能够更灵活地管理权重生命周期。
最后,无论使用哪种格式,都建议在下载后记录文件大小和哈希值,并在代码中做基本校验。虽然.safetensors本身不执行代码,但文件损坏或截断仍可能导致偏移量错误,因此配合版本管理和元数据检查,可以让ControlNet的模型加载流程更稳健。
ControlNet模型.pth格式.safetensors格式修改时间:2026-10-01 04:55:54