当你在Hugging Face上下载一个预训练模型时,你有没有想过这个文件里究竟包含了什么?当你用torch.load()加载一个.pt文件时,你的计算机正在执行什么操作?为什么Hugging Face现在强烈推荐使用.safetensors格式而不是传统的PyTorch格式?这些看似简单的文件格式问题,实际上牵涉到深度学习领域最核心的安全与技术权衡。
序列化:模型从内存到磁盘的必经之路
训练一个深度学习模型需要数小时、数天甚至数周的时间,消耗大量的计算资源和电力。当训练完成后,模型的参数——那些经过反复迭代优化得到的权重矩阵——需要被保存到磁盘上,以便将来加载使用、分享给他人或部署到生产环境。这个将内存中的Python对象转换为磁盘上字节流的过程,称为序列化(Serialization)。
PyTorch和TensorFlow等框架提供了便捷的API来完成这一任务。在PyTorch中,你可以用torch.save(model.state_dict(), 'model.pt')保存模型权重,用torch.load('model.pt')加载它们。这看起来简单直接,但底层的实现机制却隐藏着深刻的安全隐患。
graph LR
subgraph 模型生命周期
A[训练阶段] -->|保存| B[序列化]
B -->|存储| C[磁盘文件]
C -->|加载| D[反序列化]
D -->|使用| E[推理/微调]
end
Pickle:灵活与危险并存
Python的标准库pickle模块是最常用的序列化工具之一。它能将几乎任何Python对象——包括自定义类实例、函数引用、嵌套数据结构——转换为字节流,然后在需要时完美还原。这种强大的灵活性正是它的致命弱点。
Pickle的工作原理是定义了一套虚拟机指令集(opcodes)。当序列化一个对象时,pickle将其转换为一连串的操作码;反序列化时,pickle虚拟机依次执行这些操作码来重建对象。问题在于,pickle的设计允许在反序列化过程中执行任意Python代码。
# 一个恶意的pickle载荷示例
import pickle
import os
class Malicious:
def __reduce__(self):
return (os.system, ('whoami',))
# 序列化这个对象
payload = pickle.dumps(Malicious())
# 当任何人加载这个文件时...
pickle.loads(payload) # 执行 os.system('whoami')
__reduce__方法是Python序列化协议的核心。当pickle序列化一个对象时,它会检查对象是否定义了这个方法。如果定义了,__reduce__返回一个元组:第一个元素是可调用对象(通常是类或函数),第二个元素是传给它的参数。反序列化时,pickle会执行callable(*args)。这个设计初衷是为了支持复杂对象的重建,但它也成为了攻击者的完美武器。
graph TB
subgraph Pickle攻击流程
A[恶意模型文件] -->|torch.load| B[Pickle反序列化]
B -->|执行__reduce__| C[调用os.system]
C -->|执行命令| D[受害者系统]
end
subgraph 攻击载荷
E["class Evil:
def __reduce__(self):
return (os.system, ('rm -rf /',))"]
end
从理论到现实:针对ML模型的攻击
Sleepy Pickle攻击
2024年,Trail of Bits安全团队披露了一种名为"Sleepy Pickle"的新型攻击技术。与传统攻击不同,Sleepy Pickle不直接攻击目标系统,而是通过篡改模型本身来实现持久化控制。
攻击者可以创建一个恶意pickle文件,在反序列化时修改模型权重或劫持模型的方法。例如,攻击者可以在GPT-2-XL模型中注入有害的医疗建议:“喝漂白剂可以治愈流感”。这种修改只涉及极少数权重参数,文件大小增加不到0.1%,但影响却是深远的。
graph LR
subgraph Sleepy Pickle攻击链
A[正常模型] -->|注入恶意代码| B[被污染的模型]
B -->|分发| C[受害者下载]
C -->|加载| D[条件触发检查]
D -->|满足条件| E[执行恶意行为]
D -->|不满足| F[正常运行]
end
更隐蔽的是,攻击载荷可以检查运行环境的时区、日期或其他条件,只在特定情况下激活。这使得检测变得极其困难——模型在测试环境中表现完全正常,但在攻击者指定的目标环境中才会暴露恶意行为。
Hugging Face上的恶意模型
2025年2月,ReversingLabs研究人员在Hugging Face平台上发现了使用"破碎pickle"技术绕过安全扫描的恶意模型。这些模型利用了pickle扫描工具的漏洞,将恶意代码隐藏在被破坏的pickle文件中,从而逃避检测。
这不是Hugging Face第一次遭遇此类事件。2024年,JFrog研究人员就发现了多个包含静默后门的恶意模型。攻击者利用开发者对开源模型生态系统的信任,将恶意代码注入到流行模型中。
这些事件的共同特点是:
- 攻击者不需要直接访问目标系统
- 恶意载荷隐藏在看似正常的模型文件中
- 传统安全工具(如防火墙、DLP)难以检测
- 影响范围取决于模型的传播广度
Safetensors:安全与性能的新标准
面对pickle的安全风险,Hugging Face推出了Safetensors格式。这个格式的设计哲学可以用三个词概括:安全、快速、简单。
技术规范
Safetensors文件的结构非常清晰:
graph TB
subgraph Safetensors文件结构
A["8 bytes: Header长度 (N)"] --> B["N bytes: JSON Header"]
B --> C["张量1元数据"]
B --> D["张量2元数据"]
B --> E["..."]
A --> F["Data Buffer"]
F --> G["张量1数据"]
F --> H["张量2数据"]
F --> I["..."]
end
Header是一个JSON对象,描述了文件中所有张量的元信息:
{
"weight1": {
"dtype": "F16",
"shape": [1024, 1024],
"data_offsets": [0, 2097152]
},
"weight2": {
"dtype": "F32",
"shape": [512, 512],
"data_offsets": [2097152, 3145728]
},
"__metadata__": {
"format": "pt"
}
}
关键设计决策:
-
无代码执行:Header是纯数据,不包含任何可执行内容。反序列化过程只是解析JSON和读取字节,不会执行任何Python代码。
-
零拷贝加载:张量数据直接通过操作系统级别的内存映射访问,无需额外的拷贝操作。
-
格式约束:Header必须以
{字符开头,防止创建多语言文件。数据区域必须被完全索引,不能有空洞,防止隐藏数据。 -
头部大小限制:默认限制Header大小为100MB,防止通过超大JSON实施DoS攻击。
内存映射与零拷贝
Safetensors的核心优势之一是内存映射支持。传统的pickle加载需要将整个文件读入内存,解析opcode,然后创建Python对象。这个过程需要额外的内存缓冲区,加载速度也受限于Python解释器的性能。
graph LR
subgraph Pickle加载流程
A[磁盘文件] -->|完整读取| B[内存缓冲区]
B -->|解析opcode| C[Python解释器]
C -->|创建对象| D[Python对象]
end
subgraph Safetensors加载流程
E[磁盘文件] -->|mmap系统调用| F[虚拟内存映射]
F -->|直接访问| G[张量数据]
end
内存映射利用操作系统的虚拟内存管理机制,将文件内容映射到进程的地址空间。当程序访问某个内存地址时,操作系统会按需将对应的文件内容加载到物理内存。这意味着:
- 快速启动:不需要等待整个文件加载,可以立即开始计算
- 低内存占用:只有实际使用的张量才会被加载到内存
- 跨进程共享:多个进程可以共享同一份内存映射,减少内存消耗
对于分布式训练场景,这个特性尤为重要。BLOOM模型在使用safetensors格式后,8卡GPU加载时间从10分钟缩短到45秒,提升了超过10倍。
性能基准测试
根据Hugging Face官方文档和社区测试数据:
| 格式 | 加载速度(相对) | 内存峰值 | 安全性 | 懒加载 |
|---|---|---|---|---|
| Pickle (.pt) | 基准 | 高 | 低 | 否 |
| Safetensors | 快80x+ | 低 | 高 | 是 |
| H5 | 中等 | 中 | 高 | 是 |
| ONNX | 快 | 中 | 高 | 否 |
在具体测试中,加载BERT-base模型(约440MB参数),safetensors比pickle快约3倍;对于更大的模型如GPT-2-XL,差距更加明显。
多元化的格式生态
GGUF:量化和边缘部署
GGUF(GPT-Generated Unified Format)是llama.cpp项目开发的专用格式,主要用于在资源受限的环境中部署大语言模型。
与safetensors只存储张量数据不同,GGUF将模型权重、tokenizer配置和元数据打包在一个文件中:
graph TB
subgraph GGUF文件结构
A[GGUF Header] --> B[键值对元数据]
B --> C[Tokenizer配置]
B --> D[模型架构信息]
A --> E[张量信息表]
E --> F[量化张量数据]
end
GGUF的核心优势是内置量化支持:
| 量化类型 | 比特数/权重 | 内存占用(70B模型) | 质量损失 |
|---|---|---|---|
| F16 | 16 | 140GB | 无 |
| Q8_0 | 8 | 70GB | 极小 |
| Q4_K | 4.5 | 40GB | 小 |
| Q2_K | 2.625 | 24GB | 中等 |
量化后的模型可以直接在CPU上运行,这对于没有GPU的用户或边缘部署场景非常重要。GGUF还支持多种量化级别,用户可以根据硬件资源在精度和效率之间权衡。
ONNX:跨框架互操作性
ONNX(Open Neural Network Exchange)由Microsoft和Facebook联合开发,目标是为不同深度学习框架提供一个通用的模型表示格式。
graph LR
A[PyTorch模型] -->|导出| D[ONNX格式]
B[TensorFlow模型] -->|导出| D
C[其他框架] -->|导出| D
D -->|导入| E[ONNX Runtime]
D -->|导入| F[TensorRT]
D -->|导入| G[OpenVINO]
ONNX的核心特点:
-
计算图表示:模型被表示为有向无环图(DAG),节点是操作符,边是张量流动。
-
标准化算子:定义了一套标准的神经网络操作符,不同框架只需要支持这些算子即可互操作。
-
硬件优化:ONNX Runtime针对不同硬件(CPU、GPU、TPU)提供了优化的执行引擎。
然而,ONNX也有一些限制:
- 并非所有PyTorch操作都支持导出到ONNX
- 自定义算子需要额外处理
- 文件大小有2GB的限制(Protocol Buffers的限制)
格式选择决策框架
选择合适的模型格式需要考虑多个维度:
graph TB
A[选择模型格式] --> B{需要跨框架?}
B -->|是| C[ONNX]
B -->|否| D{边缘部署?}
D -->|是| E[GGUF]
D -->|否| F{需要安全分享?}
F -->|是| G[Safetensors]
F -->|否| H{快速原型?}
H -->|是| I[PyTorch .pt]
H -->|否| G
安全性考量
如果你的模型会与他人分享,或者你会加载来自他人的模型,安全性是首要考量:
- 高风险场景:从公开渠道下载的模型、模型分享平台
- 推荐格式:Safetensors(首选)、ONNX
- 应避免:Pickle格式的.pt/.pth文件(除非来源可信)
性能需求
| 场景 | 推荐格式 | 原因 |
|---|---|---|
| 快速原型开发 | .pt (PyTorch) | 原生支持,无需转换 |
| 生产环境部署 | Safetensors / ONNX | 加载快,安全 |
| 边缘设备推理 | GGUF (.gguf) | 量化支持,CPU优化 |
| GPU推理优化 | .engine (TensorRT) | 硬件特定优化 |
| 跨平台部署 | ONNX | 框架无关 |
模型大小与内存限制
对于超大规模模型,加载时间和内存占用是关键瓶颈:
- 内存映射友好:Safetensors、GGUF
- 懒加载支持:Safetensors(可只加载部分张量)
- 量化必需:GGUF(可选多种量化级别)
框架兼容性
- PyTorch原生:.pt/.pth/.safetensors
- TensorFlow原生:.ckpt/.h5/SavedModel
- 跨框架:ONNX
安全最佳实践
对于模型使用者
-
优先使用Safetensors格式
# 安全的方式 from safetensors import safe_open with safe_open("model.safetensors", framework="pt") as f: weights = {k: f.get_tensor(k) for k in f.keys()} -
如果必须使用Pickle格式
# PyTorch 2.6+ 默认启用 torch.load("model.pt", weights_only=True) # 对于旧版本 torch.load("model.pt", map_location='cpu', pickle_module=pickle, weights_only=True) -
验证模型来源
- 只从可信来源下载模型
- 检查模型的commit hash或签名
- 使用Hugging Face的"verified"标签过滤
-
沙箱环境加载
- 在隔离的容器或虚拟机中加载未知模型
- 限制网络访问
- 监控异常进程行为
对于模型提供者
-
默认发布Safetensors格式
-
提供校验信息
import hashlib with open("model.safetensors", "rb") as f: sha256 = hashlib.sha256(f.read()).hexdigest() print(f"SHA256: {sha256}") -
考虑签名机制
- 使用GPG或Sigstore对模型文件签名
- 发布公钥和签名验证方法
格式转换工具
在实际工作中,你可能需要在不同格式之间转换:
PyTorch → Safetensors
from safetensors.torch import save_file
import torch
# 加载PyTorch模型
state_dict = torch.load("model.pt", weights_only=True)
# 保存为safetensors
save_file(state_dict, "model.safetensors")
PyTorch → GGUF
需要使用llama.cpp的转换脚本:
python convert-hf-to-gguf.py /path/to/model \
--outfile model-f16.gguf \
--outtype f16
# 量化
./llama-quantize model-f16.gguf model-q4_k.gguf Q4_K
PyTorch → ONNX
import torch
import torch.onnx
model = YourModel()
model.load_state_dict(torch.load("model.pt", weights_only=True))
model.eval()
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
model,
dummy_input,
"model.onnx",
input_names=['input'],
output_names=['output'],
dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}}
)
graph LR
subgraph 格式转换生态
A[PyTorch .pt] -->|safetensors库| B[Safetensors]
A -->|torch.onnx.export| C[ONNX]
A -->|convert-hf-to-gguf| D[GGUF]
B -->|llama.cpp| D
C -->|TensorRT| E[.engine]
end
PyTorch的安全演进
PyTorch社区也在积极应对pickle的安全问题。从PyTorch 1.10开始,torch.load()引入了weights_only参数。当设置为True时,只加载张量数据,不执行任意代码。
# 安全模式(PyTorch 1.10+)
torch.load("model.pt", weights_only=True)
# PyTorch 2.6+ 默认行为
torch.load("model.pt") # 等同于 weights_only=True
然而,研究人员在2025年发现,即使是weights_only=True模式,在某些特定条件下仍可能被绕过(CVE-2025-32434)。这提醒我们,基于黑名单的安全机制总是存在被绕过的风险。
更安全的做法是直接使用safetensors格式,因为它从根本上杜绝了代码执行的可能性——safetensors的反序列化过程根本不涉及任何Python对象重建或代码执行。
总结与展望
模型权重文件格式不仅仅是技术细节,它关系到AI系统的安全性、性能和可移植性。
Pickle格式的灵活性使其成为Python生态的默认选择,但这种灵活性也带来了不可忽视的安全风险。__reduce__方法允许在反序列化时执行任意代码,这使得从不可信来源加载模型成为危险行为。
Safetensors通过严格限制文件格式——只允许纯数据存储,不支持任何形式的代码执行——从根本上解决了这个问题。配合内存映射技术,它还提供了更快的加载速度和更低的内存占用。
GGUF为边缘部署场景提供了完整的解决方案,集成了量化和tokenizer配置。
ONNX则专注于跨框架互操作性,为模型在不同推理引擎之间迁移提供桥梁。
在实际工作中,建议:
- 新项目默认使用Safetensors格式
- 部署到生产环境时根据硬件选择Safetensors或GGUF
- 永远不要加载来自不可信来源的Pickle格式模型
- 为公开分享的模型提供校验和签名
随着AI技术的普及,模型文件格式的安全问题会变得越来越重要。选择正确的格式,不仅是对自己负责,也是对使用你模型的人负责。
参考文献
-
Hugging Face. “Safetensors Documentation.” https://huggingface.co/docs/safetensors/index
-
Trail of Bits. “Exploiting ML models with pickle file attacks: Part 1.” https://blog.trailofbits.com/2024/06/11/exploiting-ml-models-with-pickle-file-attacks-part-1/
-
ReversingLabs. “Malicious ML models discovered on Hugging Face platform.” https://www.reversinglabs.com/blog/rl-identifies-malware-ml-model-hosted-on-hugging-face
-
Python Documentation. “pickle — Python object serialization.” https://docs.python.org/3/library/pickle.html
-
PyTorch Documentation. “Saving and Loading Models.” https://docs.pytorch.org/tutorials/beginner/saving_loading_models.html
-
LearnOpenCV. “Model Weights File Formats in Machine Learning.” https://learnopencv.com/model-weights-file-formats-in-machine-learning/
-
Kaitchup. “Safe, Fast, and Memory Efficient Loading of LLMs with Safetensors.” https://kaitchup.substack.com/p/safe-fast-and-memory-efficient-loading
-
JFrog. “Data scientists targeted by malicious Hugging Face ML models with silent backdoor.” https://jfrog.com/blog/data-scientists-targeted-by-malicious-hugging-face-ml-models-with-silent-backdoor/
-
Hugging Face Blog. “Common AI Model Formats.” https://huggingface.co/blog/ngxson/common-ai-model-formats
-
NCSC UK. “Principles for security of Machine learning.” https://www.ncsc.gov.uk/collection/machine-learning-principles
-
ArXiv. “Secure Deserialization of Pickle-based Machine Learning Models.” https://arxiv.org/html/2508.15987v1
-
ArXiv. “Speeding up Model Loading with fastsafetensors.” https://arxiv.org/html/2505.23072v1
-
Snyk. “Python Pickle Poisoning and Backdooring Pth Files.” https://snyk.io/articles/python-pickle-poisoning-and-backdooring-pth-files/
-
Dark Reading. “‘Sleepy Pickle’ Exploit Subtly Poisons ML Models.” https://www.darkreading.com/threat-intelligence/sleepy-pickle-exploit-subtly-poisons-ml-models
-
CSO Online. “Attackers hide malicious code in Hugging Face AI model Pickle files.” https://www.csoonline.com/article/3819920/attackers-hide-malicious-code-in-hugging-face-ai-model-pickle-files.html
-
Hugging Face GitHub. “safetensors - Simple, safe way to store and distribute tensors.” https://github.com/huggingface/safetensors
-
PyTorch Developer Discussion. “torch.load is being flipped to use weights_only=True by default.” https://dev-discuss.pytorch.org/t/bc-breaking-change-torch-load-is-being-flipped-to-use-weights-only-true-by-default-in-the-nightlies-after-137602/2573
-
Stack Overflow. “How to implement code execution via reduce for pickling?” https://stackoverflow.com/questions/57099631/how-to-implement-code-execution-via-reduce-for-pickling
-
Medium. “pickle vs safetensors vs GGUF — with conversion code & recipes.” https://medium.com/@ankitw497/model-saving-formats-101-pickle-vs-safetensors-vs-gguf-with-conversion-code-recipes-71e825c29ceb
-
The Hacker News. “Malicious ML Models on Hugging Face Leverage Broken Pickle Files.” https://thehackernews.com/2025/02/malicious-ml-models-found-on-hugging.html