当你在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"
    }
}

关键设计决策:

  1. 无代码执行:Header是纯数据,不包含任何可执行内容。反序列化过程只是解析JSON和读取字节,不会执行任何Python代码。

  2. 零拷贝加载:张量数据直接通过操作系统级别的内存映射访问,无需额外的拷贝操作。

  3. 格式约束:Header必须以{字符开头,防止创建多语言文件。数据区域必须被完全索引,不能有空洞,防止隐藏数据。

  4. 头部大小限制:默认限制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的核心特点:

  1. 计算图表示:模型被表示为有向无环图(DAG),节点是操作符,边是张量流动。

  2. 标准化算子:定义了一套标准的神经网络操作符,不同框架只需要支持这些算子即可互操作。

  3. 硬件优化: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

安全最佳实践

对于模型使用者

  1. 优先使用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()}
    
  2. 如果必须使用Pickle格式

    # PyTorch 2.6+ 默认启用
    torch.load("model.pt", weights_only=True)
    
    # 对于旧版本
    torch.load("model.pt", map_location='cpu', 
               pickle_module=pickle, weights_only=True)
    
  3. 验证模型来源

    • 只从可信来源下载模型
    • 检查模型的commit hash或签名
    • 使用Hugging Face的"verified"标签过滤
  4. 沙箱环境加载

    • 在隔离的容器或虚拟机中加载未知模型
    • 限制网络访问
    • 监控异常进程行为

对于模型提供者

  1. 默认发布Safetensors格式

  2. 提供校验信息

    import hashlib
    with open("model.safetensors", "rb") as f:
        sha256 = hashlib.sha256(f.read()).hexdigest()
    print(f"SHA256: {sha256}")
    
  3. 考虑签名机制

    • 使用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技术的普及,模型文件格式的安全问题会变得越来越重要。选择正确的格式,不仅是对自己负责,也是对使用你模型的人负责。


参考文献

  1. Hugging Face. “Safetensors Documentation.” https://huggingface.co/docs/safetensors/index

  2. 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/

  3. ReversingLabs. “Malicious ML models discovered on Hugging Face platform.” https://www.reversinglabs.com/blog/rl-identifies-malware-ml-model-hosted-on-hugging-face

  4. Python Documentation. “pickle — Python object serialization.” https://docs.python.org/3/library/pickle.html

  5. PyTorch Documentation. “Saving and Loading Models.” https://docs.pytorch.org/tutorials/beginner/saving_loading_models.html

  6. LearnOpenCV. “Model Weights File Formats in Machine Learning.” https://learnopencv.com/model-weights-file-formats-in-machine-learning/

  7. Kaitchup. “Safe, Fast, and Memory Efficient Loading of LLMs with Safetensors.” https://kaitchup.substack.com/p/safe-fast-and-memory-efficient-loading

  8. 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/

  9. Hugging Face Blog. “Common AI Model Formats.” https://huggingface.co/blog/ngxson/common-ai-model-formats

  10. NCSC UK. “Principles for security of Machine learning.” https://www.ncsc.gov.uk/collection/machine-learning-principles

  11. ArXiv. “Secure Deserialization of Pickle-based Machine Learning Models.” https://arxiv.org/html/2508.15987v1

  12. ArXiv. “Speeding up Model Loading with fastsafetensors.” https://arxiv.org/html/2505.23072v1

  13. Snyk. “Python Pickle Poisoning and Backdooring Pth Files.” https://snyk.io/articles/python-pickle-poisoning-and-backdooring-pth-files/

  14. Dark Reading. “‘Sleepy Pickle’ Exploit Subtly Poisons ML Models.” https://www.darkreading.com/threat-intelligence/sleepy-pickle-exploit-subtly-poisons-ml-models

  15. 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

  16. Hugging Face GitHub. “safetensors - Simple, safe way to store and distribute tensors.” https://github.com/huggingface/safetensors

  17. 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

  18. 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

  19. 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

  20. 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