AB
AiBoss
Wiki

什么是 Safetensors?一种安全且快速的张量存储格式

Safetensors 是 Hugging Face 官方文档描述的一种用于存储张量(tensor)的简单格式,它被设计为相比 pickle 更安全,同时保持零拷贝(zero-copy)的读取速度。本文依据官方文档介绍它的定义、出现背景、工作机制、典型用法与已知边界。

Safetensors 是一种用于存储张量(tensor)的简单格式,按照 Hugging Face 官方文档的说法,它的目标是把张量安全地存下来(相对于 pickle 而言),同时依然很快,读取时可以做到零拷贝(zero-copy)。它要解决的问题是:在机器学习模型权重、嵌入矩阵这类大规模数值数据的保存与分发环节,既要避免反序列化带来的安全风险,又不想牺牲加载速度。

为什么重要

在 Safetensors 出现之前,张量数据最常见的落盘方式之一是 Python 的 pickle 及其衍生格式。pickle 的设计目标是序列化任意 Python 对象,因此反序列化过程本质上是在执行一段构造对象的逻辑。这意味着一个来源不明的权重文件在被加载时,可能触发文件作者预设的行为,而使用者往往只是想把权重读进内存。对于需要从公开模型仓库下载权重、在多个框架之间搬运权重的场景,这种「加载即执行」的特性构成了实际的攻击面。

另一条路是使用纯数值容器格式,例如 NumPy 的 .npy 或通用的二进制数组格式。这类格式在安全性上更可控,但往往缺少对多张量字典、张量名称、数据类型与形状元信息的统一描述,也不一定支持按需读取部分张量。当模型被切分到多张 GPU 上、或者只需要读取其中某一层时,整文件加载会带来不必要的内存与时间开销。

官方文档把 Safetensors 的定位概括为两点:安全,以及快。它不试图成为通用的对象序列化方案,而是把范围收窄到张量这一种数据,从而可以在格式层面做出更严格的约束。文档同时提到,Safetensors 正在被多家 AI 企业广泛使用,包括 Hugging Face、EleutherAI 和 StabilityAI;文档还给出了一份非穷尽的采用项目清单,其中包含 huggingface/transformers、ml-explore/mlx、huggingface/candle、AUTOMATIC1111/stable-diffusion-webui、llama-cpp、microsoft/TaskMatrix、hpcaitech/ColossalAI、huggingface/pytorch-image-models、CivitAI、huggingface/diffusers、coreylowman/dfdx、invoke-ai/InvokeAI、oobabooga/text-generation-webui、Sanster/lama-cleaner、PaddlePaddle/PaddleNLP、AIGC-Audio/AudioGPT、brycedrennan/imaginAIry、comfyanonymous/ComfyUI、LianjiaTech/BELLE、alvarobartt/safejax、MaartenGr/BERTopic、rachthree/safestructures、justinchuby/onnx-safetensors 等。需要说明的是,这份清单来自官方文档,属于该文档的陈述,而不是对生态现状的独立统计。

工作机制

官方文档把 Safetensors 描述为一个「新的简单格式」。它的核心做法可以从以下几个层面理解。

第一,只存张量,不存代码。格式本身只描述张量的名称、数据类型、形状和字节数据,不包含可执行的构造逻辑。加载一个 Safetensors 文件时,读取方拿到的是数值缓冲区,而不是一段需要被解释执行的程序。这是它与 pickle 类格式在安全属性上的根本差别。

第二,元信息与数据分离,支持零拷贝。文档称该格式「仍然很快(zero-copy)」。零拷贝的含义是:读取张量时可以直接把文件中的字节区间映射为内存中的张量视图,而不必先把数据完整复制到另一块缓冲区再转换。对于动辄数 GB 的模型权重,这能显著减少加载阶段的内存峰值与耗时。

第三,支持按需读取部分张量。官方文档给出的用法示例中,除了逐个键读取张量之外,还专门演示了「只加载部分张量」的方式:先用 get_slice 取得某个张量的切片对象,再通过 get_shape 拿到它的形状,然后按维度切片取出所需部分。文档把这一能力标注为「在多 GPU 上运行时比较有意思」,因为此时每个进程通常只需要自己负责的那一片权重。

第四,提供跨框架的加载入口。文档中的加载示例通过 safe_open 打开文件,并显式指定 framework(示例中为 "pt")与 device(示例中为 0),随后遍历 f.keys() 并调用 f.get_tensor(k) 取回张量。保存侧则通过 safetensors.torch.save_file 把「名称到张量」的字典写入文件。安装方式文档给出两种:pip install safetensors,或 conda install -c conda-forge safetensors。

文档还说明,一个名为 model.safetensors 的文件具有固定的内部格式布局,但抓取到的页面正文中该布局的具体字段说明未被完整保留,因此这里不展开描述其字节级结构。

典型例子

官方文档给出的示例可以直接反映它的典型用法。

加载全部张量:

from safetensors import safe_open

tensors = {}
with safe_open("model.safetensors", framework="pt", device=0) as f:
    for k in f.keys():
        tensors[k] = f.get_tensor(k)

只加载部分张量,文档注明这在多 GPU 运行时比较有用:

from safetensors import safe_open

tensors = {}
with safe_open("model.safetensors", framework="pt", device=0) as f:
    tensor_slice = f.get_slice("embedding")
    vocab_size, hidden_dim = tensor_slice.get_shape()
    tensor = tensor_slice[:, :hidden_dim]

保存张量:

import torch
from safetensors.torch import save_file

tensors = {
    "embedding": torch.zeros((2, 2)),
    "attention": torch.zeros((2, 3)),
}
save_file(tensors, "model.safetensors")

除了代码示例,文档列出的采用项目本身也是典型用例的集合:从模型训练与推理框架(transformers、diffusers、PaddleNLP),到推理运行时(llama-cpp、candle、mlx),再到面向终端用户的图像生成前端(stable-diffusion-webui、ComfyUI、InvokeAI、text-generation-webui)与模型分享站点(CivitAI)。这些项目名称均来自官方文档的清单。

边界与常见误解

第一,Safetensors 不是通用的序列化格式。官方文档把它定义为「存储张量的简单格式」,示例中的保存对象是「名称到张量」的字典。它并不打算承载任意 Python 对象、配置类、优化器状态中的非张量部分或自定义类实例。把 Safetensors 当作 pickle 的全面替代品,会超出它被设计的范围。

第二,「安全」是相对于 pickle 而言的。文档的表述是「安全地存储张量(相对于 pickle)」。这指的是格式本身不包含可执行构造逻辑,从而消除了加载即执行这一类风险。它并不意味着文件内容一定可信、来源一定可靠,也不覆盖张量数值本身可能带来的其他问题。

第三,「快」的具体含义是零拷贝。文档用「仍然很快(zero-copy)」来描述。零拷贝主要影响的是从磁盘到内存的加载路径,它不等于训练或推理本身更快,也不等于文件体积更小。文档没有给出与 pickle 或其他格式的体积对比数据。

第四,关于采用范围的表述需要归因。文档称 Safetensors「正在被多家领先 AI 企业广泛使用」,并给出了一份明确标注为「非穷尽」的项目清单。这是该文档的陈述,清单的完整性、各项目使用的深度与版本,文档本身并未展开,因此不宜把它读作对整个生态的普查结论。

第五,版本与安装方式需要区分。抓取到的文档页面顶部标注为 main 版本,并提示该版本需要从源码安装;若希望使用常规的 pip 安装,应查看最新的稳定版本。文档中出现的版本号标识(如 v0.5.0-rc.0、v0.3.2、v0.2.9)属于文档站点的版本切换控件,具体行为与兼容性应以对应版本文档为准。

参考资料