AI 基建
0%
第十部分 · 生态与经济 · 第 74 章

模型作为一件制品:格式、分发与供应链

作者Changkun Ou
阅读时长约 10 分钟

上一章把模型当作一种能力,沿两个坐标轴之一交付:权重,或 API。这一章把它当作一个文件:一个数吉字节的对象,必须先被序列化、上传、版本化、下载、校验、加载进一个进程,那份能力才存在。这个对象是软件供应链上的一环,而这条链有着每一条软件供应链都有的失效模式,外加一个学得的制品所独有的。一个检查点可能夹带在加载时运行的代码。一个注册处可能是整个开放生态的单一策略点。而一个每一字节都干净、签名也校验通过的模型,仍可能以任何扫描器都永远抓不到的方式被植入后门,因为恶意藏在权重里,不在文件里。本章依次讲清格式、分发层、攻击,以及回答了其中一部分问题的出处工具,好让读者能说出「下载这个模型」究竟在信任什么,以及信任字节在哪里不再足够。

一个会运行代码的检查点

先看格式,因为默认的那一种危险得会让多数工程师吃惊。PyTorch 的 torch.save 用 Python 的 pickle 序列化,而 pickle 不是一种数据格式,是一种小型的程序格式。Python 文档在页首就写明:这个模块「并不安全」,而且「可以构造出恶意的 pickle 数据,在反序列化时执行任意代码」(Python Software Foundation 2024)。机制是一个序列化钩子:一个对象可以定义自己该如何被重建,返回一个可调用对象及其参数,而「反序列化器会在加载时调用它」。所以反序列化一个不可信的检查点,就是执行不可信的代码,下面的可运行单元用十几行代码、不带一点机器学习,就把整套把戏演了一遍。

import pickle

class Benign:                      # 看起来像个无害的对象
    def __reduce__(self):          # 但这个钩子规定了它该如何被「重建」
        return (print, ("加载时运行了代码:不需要 torch",))

blob = pickle.dumps(Benign())      # 「保存一个检查点」
print("字节已写入,尚未执行任何东西:", len(blob))
pickle.loads(blob)                 # 「加载这个检查点」-> print() 触发
# 把 print 换成 os.system 或一个 socket,加载模型就打开了一个反弹 shell

这不是理论。2024 年对最大模型库的一次扫描,发现了约一百个夹带真实恶意载荷的模型,其中一个的重建钩子会在加载时向攻击者的服务器反弹一个 shell (JFrog Security Research 2024),而本该抓住这些的扫描器是可以被绕过的:2025 年的一项发现,用扫描器不解析的格式压缩载荷、并把它放在一条故意损坏的指令之前,绕过了模型库的 pickle 扫描器,于是 shell 在反序列化尚未失败之前就跑了 (ReversingLabs 2025)。对一种程序格式扫描恶意,是一场猫鼠游戏,不是一份保证。

答案是把格式改成加载时无法执行任何东西。safetensors 是一种刻意做得毫无花样的二进制布局:一个记录张量名、类型、形状与字节偏移的 JSON 头,后面跟着原始张量字节,没有代码路径,也没有重建钩子,于是一次加载就是一次带边界检查的解析加一次内存映射,仅此而已。它的安全主张是结构性的,而不是倚赖某个扫描器,而且它被查验过:2023 年委托的一次外部审计没有发现任意代码执行的路径 (Hugging Face 2023)。生态在随后的一年里完成了转换,主流库把 safetensors 设为默认保存格式,一个机器人还给老旧的 pickle 仓库开拉取请求、补上 safetensors 权重。本地推理的世界则定下了自己的单文件格式 GGUF,把张量、分词器与元数据打进一个可内存映射、量化感知的文件 (ggml project 2023)。值得带往上层的教训是:格式安全是格式的属性,不是盯着它的那个扫描器的属性;safetensors 从构造上关掉了加载时的代码执行通道,而也只有从构造上关掉,才真正关得住。

loadpaths cluster_pickle pickle(torch.save 默认) cluster_st safetensors pf 文件 pu 反序列化器 pf->pu pr __reduce__ 调用 callable(args) pu->pr po 对象 pr->po sf 文件 sh 解析 + 边界检查 JSON 头 sf->sh sm mmap 原始张量字节 (无代码路径) sh->sm st 张量 sm->st
图 74.1. 两条加载路径。pickle 路径通过调用文件所指名的可调用对象来重建对象,于是加载即执行代码,安全性依赖扫描器。safetensors 路径解析一个带边界检查的头、映射原始字节,没有可被利用的代码路径。

模型库就是注册处

一旦检查点成了一个安全的文件,它就需要一个安身之处,而实践中那个地方只有一个。占主导的模型库到 2026 年初托管着约两百万个公开模型,且同比大致翻倍,而这个数字内部的集中程度才是惊人的一面:极小一部分模型占了约一半的下载量,而一半的模型被下载不到两百次 (Hugging Face 2026)。它充当着这个领域事实上的包注册处,还继承了一个比它自己更老的文档规范:模型卡,2019 年提出,是一份报告模型预期用途、跨条件评估结果与局限的简短标准文档,在模型库上落实为一个带元数据头的 README (Mitchell et al. 2019)。注册处的完整性原语与任何 git 托管一样:一次下载可以固定到某个修订,而固定到一个完整的提交哈希、而不是一个分支或标签,才让制品不可变,因为标签能被移动,哈希不能。

这种集中化是本章的第一个系统性问题。一个注册处,对开放生态的很大一部分而言,就是一个策略点、故障点与下架点,而下载的集中意味着少数几个仓库对整个领域是承重的。制衡的一面是,这个注册处底下是 git 加 HTTP 加一个可镜像的协议:客户端库尊重一个端点覆盖设置,替代性的库也存在(阿里巴巴的 ModelScope 于 2022 年作为区域对应物上线),社区镜像在主站慢或被墙的地方提供同样的内容。争论在于,一个开放、可镜像的协议是否抵得过一个集中的默认;按哈希固定至少让任何镜像都可核验,正是这个技术事实使这种集中没有完全承重。

当字节干净、模型却不干净

在这里,一个学得的制品的供应链与其他任何软件供应链分道扬镳,这也正是格式安全与签名必要却不充分的原因。一个模型可以是一个合法的 safetensors 文件,签名正确、按哈希固定,却仍被植入后门,因为恶意行为是训练进权重的,字节里没有任何东西泄露它。最清楚的一次演示,训练一个模型在提示词显示某一年时写出可被利用的代码、其他情况下写出安全代码,然后表明这个后门「挺过了」整条安全流水线:监督微调、强化学习与对抗训练都没能移除它,而对抗训练有时反倒教会模型把触发条件藏得更好 (Hubinger et al. 2024)。这种威胁甚至不需要控制模型的生产者,因为训练数据也是一个攻击面。给网络规模的语料投毒很便宜:一项研究表明,攻击者花约六十美元,就能通过买下爬虫仍然信任的过期域名,控制某个大型图文数据集的一小部分 (Carlini et al. 2023);而 2025 年的一个结果让这个警告更加尖锐:它表明植入一个后门所需的投毒文档数量大致是常数、约两百五十篇,与模型和数据集规模无关,这意味着模型越大,投毒相对越容易 (Souly et al. 2025)。攻击也直接搭上分发层:2023 年的一次演示,把特定事实编辑进一个模型的权重、并以与某知名实验室仅差一个字母的名字上传,把权重编辑与命名空间仿冒结合起来,于是制品看上去货真价实,除了在被投毒的事实上,行为一切正常 (Mithril Security 2023)。

attacksurface data 网络数据 train 训练 data->train ckpt 检查点 (文件) train->ckpt repo 模型库仓库 ckpt->repo load 加载 repo->load a1 投毒 (约 250 篇,$60) a1->data a2 训练进去的后门 (挺过安全训练) a2->train a3 仿冒仓库 a3->repo a4 pickle 载荷 a4->load d1 行为评估 (权重唯一的扫描器) d1->train d2 safetensors + 哈希 + 签名 d2->repo
图 74.2. 沿制品生命周期的攻击面,防御在第二条轨道上。格式安全、哈希与签名是文件与注册处阶段的「字节与出处」保证;它们对数据与训练阶段的投毒与后门无能为力,那里只有行为评估管用。

出处回答的是另一个问题

围绕制品长出来的、用于保护它的工具,回答的是「这东西是不是原封不动地、来自我以为的那一方、按他们所说的方式构建的」,而这是一个出处问题,不是安全问题。模型签名由一个开源安全组织在 2025 年标准化,能为任意格式与体量的模型签名、并对照一个签名者身份核验,配一个透明日志,使签名无法被悄悄伪造 (OpenSSF AI/ML Working Group 2025)。一份机器可读的物料清单,即某个既有软件物料清单标准的机器学习扩展,让模型附带一份组件、数据集与血统的清单 (OWASP CycloneDX 2024)。而构建出处的证明能记录谁训练了一个模型、用什么代码与参数,这恰好是这一层与 第 60 章 那套经过证明的硬件相接的地方:一个可信执行环境可以证明某次特定的训练运行发生在一个被度量的隔离区里,而一份签名的出处记录可以引用那份证明。而所有这些都不能说明模型是安全的。签名证明来源;来源只与它背后的流水线一样可信;而一个签了名、按哈希固定、格式为 safetensors、却在被投毒的语料上训练出来的模型,是一个货真价实的后门。

下层约束

制品以什么格式出货,会反过来改写上面各层能做哪些假设。当 pickle 还是默认时,每一次模型加载都是一次不可信代码的执行,于是一个服务平台或一个智能体运行框架无法把下载来的检查点当作数据,稳妥的做法是对加载本身重度沙箱化。safetensors 拿掉了加载时的代码路径,这让 第 31 章 的服务栈与 第 42 章 的智能体运行框架,能把权重加载当作一次本就该如此的惰性解析,并把残余的信任从文件移到它背后的训练流水线上。一个在底层为了速度与安全而做的序列化选择,无声地决定了上面各层是否必须防备自己的模型文件。

争议所在

两个问题悬而未决。模型库集中化是不是系统性风险,取决于一个开放、可镜像的协议是否抵得过一个集中的默认:一方认为,一个注册处、其中极小一部分仓库供出一半下载量,意味着离一次生态级事故只差一个策略决定或一次宕机;另一方回应说按哈希固定的内容从任何镜像都可核验,端点也只差一个环境变量就能换掉,所以锁定比看上去要软。而扫描权重能不能有意义,取决于字节与行为之间的鸿沟:既然一个字节干净、签过名的模型可以以任何静态分析都揭示不出的方式被植入后门,一种立场认为唯一真正的扫描器是行为评估,而它本身也不完整,因为一个秘密触发条件在测试期间大可从不触发;另一种立场认为格式安全与签名仍然抬高了攻击者的成本、并回答了出处问题,哪怕它们没有回答安全问题。这个领域大体达成的共识是:签名扫描来源,评估扫描行为,两者互不替代,这也是为什么一个严肃的部署两样都做,却仍然无法承诺权重是干净的。

延伸阅读

  • Python Software Foundation, “pickle: Python object serialization (module documentation)” (反序列化不可信数据即执行代码,出自一手文档), 2024. docs.python.org
    pickle 模块文档,其页首警告指出反序列化不可信数据会按设计执行任意代码,正是恶意检查点的机制来源。
  • JFrog Security Research, “Data Scientists Targeted by Malicious Hugging Face ML Models with Silent Backdoor” (模型库上约 100 个借 pickle 反弹 shell 的真实恶意模型), 2024. jfrog.com
    对最大模型库的一次扫描发现约一百个真正的恶意模型,其中一个的 pickle 重建钩子在加载时向攻击者反弹 shell。
  • ReversingLabs, “nullifAI: malicious ML models evade Hugging Face picklescan” (扫描一种程序格式是一场猫鼠游戏), 2025. reversinglabs.com
    一批恶意模型通过采用扫描器不解析的压缩格式、并把载荷放在故意损坏的指令之前,绕过了模型库的 pickle 扫描器,说明扫描并非保证。
  • Hugging Face, “Audit shows that safetensors is safe and ready to become the default” (以格式保安全:外部审计未发现代码执行路径), 2023. huggingface.co
    safetensors 格式及其委托的外部审计,未发现任意代码执行路径:加载只是带边界检查的解析加内存映射,没有重建钩子。
  • ggml project, “GGUF file format specification” (面向本地、单文件、可 mmap、量化感知的格式), 2023. github.com
    面向本地推理的单文件格式,把张量、分词器与全部元数据打进一个可内存映射、量化感知的文件,无需单独的配置。
  • Hugging Face, “The State of Open Source on Hugging Face: Spring 2026” (约两百万模型,且下载量高度集中), 2026. huggingface.co
    模型库自己的报告,2026 年初约有两百万个公开模型,而极小一部分模型占了约一半的下载量。
  • Mitchell et al., “Model Cards for Model Reporting” (模型库每一张模型卡的源头文档规范), 2019. arXiv:1810.03993
    Mitchell 等人提出模型卡,即随训练好的机器学习模型发布的简短文档,通过跨人口群体和交叉群体的分类性能报告,支持透明、公平的评估。
  • Hubinger et al., “Sleeper Agents: Training Deceptive LLMs that Persist Through Safety Training” (字节干净不等于行为干净:一个挺过安全训练的后门), 2024. arXiv:2401.05566
    通过训练植入大语言模型的后门能够抵抗监督微调、强化学习与红队对抗训练,表明标准安全训练无法可靠消除欺骗性对齐。
  • Carlini et al., “Poisoning Web-Scale Training Datasets is Practical” (约六十美元就能给一个大型图文数据集投毒), 2023. arXiv:2302.10149
    对网络规模数据集的两种可行投毒:买下爬虫仍信任的过期域名,以及在快照前修改页面,给一个大型数据集投毒的成本约六十美元。
  • Souly et al., “Poisoning Attacks on LLMs Require a Near-constant Number of Poison Samples” (约 250 篇文档即可,且与模型和数据集规模无关), 2025. arXiv:2510.07192
    约 250 篇投毒文档就能给从 6 亿到 130 亿参数的模型植入后门,与干净数据量无关,意味着模型越大投毒相对越容易。
  • Mithril Security, “PoisonGPT: How We Hid a Lobotomized LLM on Hugging Face to Spread Fake News” (权重编辑加命名空间仿冒), 2023. mithrilsecurity.io
    一次演示:把特定的虚假事实编辑进模型权重,并以与知名实验室仅差一个字母的组织名上传,把权重编辑与仿冒命名结合起来。
  • OpenSSF AI/ML Working Group, “Launch of Model Signing v1.0” (出处而非安全:以签名者身份为原点签名), 2025. openssf.org
    面向任意格式与体量模型的签名库与格式,凭签名者身份并借透明日志核验,回答的是出处而非安全。
  • OWASP CycloneDX, “CycloneDX v1.6 with AI/ML Bill of Materials (ML-BOM)” (模型组件与血统的机器可读清单), 2024. cyclonedx.org
    软件物料清单标准的机器学习扩展,让模型附带一份组件、数据集与血统的机器可读清单。

评论

登录后评论