赞
踩
在日常AI模型训练过程中,需要好的模型权重通常需要以一种格式存储在磁盘中。比如:目前最流行的AI框架 PyTorch 使用 pickle 格式存储模型权重文件。但 PyTorch 文档中有一段话说明如下:使用 torch.load()
保存模型,除非 weights_only 参数设置为True (只加载张量、原始类型和字典),否则隐式使用 pickle
模块,这是不安全的。可以构造恶意的 pickle 数据,该数据将在 unpickling 期间执行任意代码。切勿在不安全模式下加载可能来自不受信任来源的数据或可能已被篡改的数据,仅加载您信任的数据。为了规避类似的问题,新的权重存储格式 Safetensors 应运而生。
Safetensors 是一种用于安全地存储张量的新格式,非常简单,但速度仍然很快(零拷贝)。它是 pickle
格式的替代品,因为,pickle
格式不安全,可能包含可以执行的恶意代码。
假设现在有一个名为 model.safetensors
的Safetensors文件,那么 model.safetensors
内部格式如下:
以GPT2的Safetensors文件为例,其元数据信息可以通过如下代码获取:
import requests # pip install requests import struct def parse_single_file(url): # Fetch the first 8 bytes of the file headers = {'Range': 'bytes=0-7'} response = requests.get(url, headers=headers) # Interpret the bytes as a little-endian unsigned 64-bit integer length_of_header = struct.unpack('<Q', response.content)[0] # Fetch length_of_header bytes starting from the 9th byte headers = {'Range': f'bytes=8-{7 + length_of_header}'} response = requests.get(url, headers=headers) # Interpret the response as a JSON object header = response.json() return header url = "https://huggingface.co/gpt2/resolve/main/model.safetensors" header = parse_single_file(url) print(header) # { # "__metadata__": { "format": "pt" }, # "h.10.ln_1.weight": { # "dtype": "F32", # "shape": [768], # "data_offsets": [223154176, 223157248] # }, # ... # }
对于不同模型权重格式,从如下几个方面进行全面的对比:
下图展示了常见的存储格式的特性。
safetensors
和 ONNX
的不同safetensors
和 ONNX
具有不同的用途。
safetensors
是一种简单、安全、快速的文件格式,用于存储和加载张量。它是 Python 的 pickle
实用程序的替代品,更加安全;而后者不安全,可能包含可以执行的恶意代码。
ONNX
(开放神经网络交换)是一种用于表示深度学习模型的开放格式。它允许您用不同深度学习框架(例如:PyTorch、TensorFlow、Caffe2 等)加载模型以一种方式保存模型。这使得在不同框架之间共享模型变得更容易。
综上所述, safetensors
用于安全快速地存储和加载张量,而 ONNX
用于在不同深度学习框架之间共享模型。这同样适用于其他模型共享框架。
零拷贝的主要任务就是避免CPU将数据从一块存储中拷贝到另一块存储,主要就是利用各种技术,避免让CPU做大量的数据拷贝任务,以此减少不必要的拷贝。或者借助其他的一些组件来完成简单的数据传输任务,让CPU解脱出来专注别的任务,使得系统资源的利用更加有效。
下面通过从磁盘读取数据并将其发送到套接字(socket)的示例来了解零拷贝(这种情况在大多数 Web 应用程序中经常发生)。为了完成此操作,内核会将数据读取到用户空间。
操作系统术语:
用户空间和内核空间是由操作系统分隔的两个虚拟内存区域,以提供内存保护和硬件保护,防止恶意或错误的软件行为。
用户空间是应用程序软件和一些驱动程序执行的内存区域。每个用户空间进程通常运行在自己的虚拟内存空间中,除非明确允许,否则不能访问其他进程的内存或内核空间。用户空间进程只能通过系统调用与内核交互,系统调用是内核向用户空间公开的一组函数。
内核空间是操作系统内核、内核扩展和大多数设备驱动程序运行的内存区域。内核空间程序运行在内核模式下,也称为管理模式,这是一种特权模式,允许访问所有CPU指令和硬件资源。
一旦数据加载到用户空间,它将再次执行内核调用,内核会将数据写入套接字(socket)。每次数据穿越user-kernel boundary时,都必须进行复制,这会消耗 CPU 周期和内存带宽。
而零拷贝请求即内核直接将数据从磁盘文件复制到套接字,而不经过应用程序。
下图为传统的拷贝操作流程。除了拷贝操作之外,这些系统调用会导致用户空间和内核空间之间发生大量上下文切换,使得这个过程变慢。
下图为零拷贝数据传输流程:
零拷贝的特点是 CPU 不全程负责内存中的数据写入其他组件,CPU 仅仅起到管理的作用。但注意,零拷贝不是不进行拷贝,而是 CPU 不再全程负责数据拷贝时的搬运工作。如果数据本身不在内存中,那么必须先通过某种方式拷贝到内存中(这个过程 CPU 可以不参与),因为数据只有在内存中,才能被转移,才能被 CPU 直接读取计算。
上面对比了不同模型权重存储格式,从中可以发现 Safetensors 主要优势有:
安全:使用 torch.load()
加载模型权重可能会执行被插入的恶意代码(因为 pickle 模块是不安全的),不过可以设置weights_only=False
避免这个问题。而 Safetensors 天然就没有这个问题。
速度快:Safetensors 接口的速度比原生的 Pytorch 接口加载权重更快。
惰性加载:可以在不加载整个文件的情况下查看文件的信息或者只加载文件中的部分张量而不是所有张量。当我们有一个包含许多键和值对的大文件时,延迟加载非常重要。如果我们可以单独加载单个键的值,它将提高内存效率并且速度更快,否则我们将不得不将完整文件加载到内存中以检查任何键。
pip install safetensors
保存张量:
import torch
from safetensors.torch import save_file
tensors = {
"embedding": torch.zeros((2, 2)),
"attention": torch.zeros((2, 3))
}
save_file(tensors, "model.safetensors")
加载张量:
from safetensors import safe_open
tensors = {}
# with safe_open("model.safetensors", framework="pt", device=0) as f:
with safe_open("model.safetensors", framework="pt", device='cpu') as f:
for k in f.keys():
tensors[k] = f.get_tensor(k) # loads the full tensor given a key
print(tensors)
# {'attention': tensor([[0., 0., 0.],
# [0., 0., 0.]], device='cuda:0'),
# 'embedding': tensor([[0., 0.],
# [0., 0.]], device='cuda:0')}
仅加载部分张量(延迟加载):
tensors = {}
# with safe_open("model.safetensors", framework="pt", device=0) as f:
with safe_open("model.safetensors", framework="pt", device='cpu') as f:
tensor_slice = f.get_slice("embedding")
vocab_size, hidden_dim = tensor_slice.get_shape()
print(vocab_size, hidden_dim)
tensor = tensor_slice[:, :hidden_dim] # change the hidden_dim to load part of the tensor
print(tensor)
# 2 2
# tensor([[0., 0.],
# [0., 0.]])
from torchvision.models import resnet18 from safetensors.torch import load_model, save_model import torch model_pt = resnet18(pretrained=True) # 保存 state dict 为 safetensors格式 save_model(model_pt, "resnet18.safetensors") # Instead of save_file(model.state_dict(), "model.safetensors") # 加载没有权重的模型 model_st = resnet18(pretrained=False) load_model(model_st, "resnet18.safetensors") # Instead of model.load_state_dict(load_file("model.safetensors")) # 使用随机图像对初始模型和从safetensors中加载权重的新模型进行推理 img = torch.randn(2, 3, 224, 224) model_pt.eval() model_st.eval() with torch.no_grad(): print(torch.all(model_pt(img)==model_st(img))) # tensor(True)
本文简要介绍了模型权重存储新格式 Safetensors,它具备安全、加载速度快等多个优点;并且可以在 HuggingFace 上面看到越来越多的模型使用Safetensors格式进行存储。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。