【AI系统】推理文件格式

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 本文介绍了神经网络模型的序列化与反序列化技术,涵盖跨平台通用序列化方法(如 Protobuf 和 FlatBuffers)、模型自定义序列化方法、语言级通用序列化方法等,重点讨论了这两种流行文件格式的特点、使用场景及其在模型部署中的作用。

在训练好一个模型后,需要将其保存下来,以便在需要时重新加载并进行推理或进一步的训练。为了实现这一目标,需要一种有效的方式来将模型的参数、结构等保存起来。

本文主要介绍在推理引擎中,针对神经网络模型的序列化与反序列化、不同的模型序列化方法,以及 Protobuf 和 FlatBuffers 两种在端侧常用的模型文件格式。

模型序列化

模型序列化是模型部署的第一步,如何把训练好的模型存储起来,以供后续的模型预测使用,是模型部署的首先要考虑的问题。

序列化与反序列化

训练好的模型通常存储在计算机的内存中。然而,内存中的数据是暂时的,不具备长期存储的能力。因此,为了将模型保存供将来使用,我们需要将其从内存中移动到硬盘上进行永久存储。

这个过程被称为模型的保存和加载,或者说是序列化和反序列化。在这个过程中,模型的参数、结构和其他相关信息会被保存到硬盘上的文件中,以便在需要时重新加载到内存中。

image

  • 模型序列化:模型序列化是模型部署的第一步,如何把训练好的模型存储起来,以供后续的模型预测使用,是模型部署的首先要考虑的问题。

  • 模型反序列化:将硬盘当中的二进制数据反序列化的存储到内存中,得到网络模型对应的内存对象。无论是序列化与反序列的目的是将数据、模型长久的保存。

序列化分类

  1. 跨平台跨语言通用序列化方法

    常用的序列化主要有四种格式:XML(一种可扩展标记语言,非常适合存储和传输结构化数据),JSON(一种轻量级的数据交换格式),Protocol Buffers(谷歌开发的语言中立、平台中立、可扩展的序列化结构数据格式)和 Flatbuffer(谷歌开发的另一种高效序列化库,与 Protobuffer 类似)。前两种是文本格式,人和机器都可以理解,后两种是二进制格式,只有机器能理解,但在存储传输解析上有很大的速度优势。使用最广泛为 Protobuffer。

    下图中的ONNX使用的就是 Protobuf 这个序列化数据结构去存储神经网络的权重信息。ONNX(Open Neural Network Exchange)是一个开放格式,用于机器学习模型的跨平台共享。通过使用 Protobuf,ONNX 能够在不同的 AI 框架之间高效地传输模型数据。

    CoreML既是一种文件格式,又是一个强大的机器学习运行时环境,它使用了 Protocol Buffers 的二进制序列化格式,并在所有苹果操作系统平台上提供了高效的推理和重新训练功能。CoreML 允许开发者轻松地将机器学习模型集成到 iOS、macOS、watchOS 和 tvOS 应用中,利用苹果设备的硬件加速特性实现高效的模型推理。

    image

  2. 模型本身提供的自定义序列化方法

    模型可以提供自定义的序列化方法,这些方法可以是文本格式或者二进制格式。自定义的序列化方法通常是根据特定的模型结构和需求来设计的,以优化存储和传输效率。

    文本格式: 这类自定义方法通常采用类似 JSON 或 XML 的格式,方便调试和分析。例如,一些机器学习模型会输出人类可读的文本文件,包含模型参数和结构信息。

    二进制格式: 与 Protobuf 和 Flatbuffer 类似,这类自定义方法采用高效的二进制格式,提升存储和解析速度。例如,TensorFlow 的 Checkpoint 文件和 PyTorch 的模型文件都是专门设计的二进制格式,能快速存取大量模型参数。

    自定义的序列化方法可以是语言专有或者跨语言跨平台的格式:

    语言专有格式: 某些机器学习框架提供了特定语言的序列化方法,比如 Scikit-Learn 的 .pkl 文件,专门用于 Python 环境中的模型保存和加载。

    跨语言跨平台格式: 这类格式旨在实现不同编程语言和操作系统之间的互操作性。比如 ONNX 就是一种跨平台的模型序列化格式,可以在不同的 AI 框架和运行时环境之间共享模型。

  3. 语言级通用序列化方法

    不同编程语言提供了各自的通用序列化方法,以方便开发者保存和加载各种数据结构,包括机器学习模型。

    Python:

    • pickle:Python 内置的对象序列化库,支持序列化几乎所有 Python 对象。其 C 实现版本 cPickle 提供了更高的序列化速度。以下是使用 pickle 保存和加载训练模型的简单示例:
    import pickle  # 导入 pickle 模块,用于序列化对象
    
    # 训练模型并保存模型的代码段
    model.fit(x_data, y_data)  # 使用训练数据 x_data 和标签 y_data 来训练模型
    s = pickle.dumps(model)  # 将训练好的模型序列化为字节串并保存到变量 s with open('myModel.model', 'wb+') as f:  # 打开一个文件 'myModel.model' 用于写入,并使用二进制模式
      f.write(s)  # 将序列化后的模型字节串写入到文件中
    
    # 加载模型并进行预测的代码段
    with open('myModel.model', 'rb') as f:  # 打开一个文件 'myModel.model' 用于读取,并使用二进制模式
      s = f.read()  # 读取文件中的内容(模型字节串)并保存到变量 s 中
      model = pickle.loads(s)  # 使用 pickle.loads() 函数将模型字节串反序列化为模型对象并保存到变量 model 中
    
    • joblib:专为大规模数据和数值数组设计的序列化库,在处理包含大量 NumPy 数组的模型时性能优异。Joblib 使用磁盘存储策略,能有效减少内存占用。

    R:

    • rda:R 语言中的一种数据保存格式,通常用于保存 R 对象以便在不同 R 会话之间共享。save函数可以将 R 对象序列化为二进制文件,支持复杂数据结构的高效存储和加载。

    这些语言级的序列化方法,提供了方便的接口,开发者可以根据需要选择最合适的方法来存储和加载模型或数据。

  4. 用户自定义序列化方法

    在某些特殊情况下,以上所有方法都无法满足特定需求,用户可以设计自己的序列化格式。这种自定义方法通常用于满足特殊的部署需求,例如优化部署性能、减少模型大小或者满足特定的环境要求。

    自定义序列化方法的设计需要考虑以下几个方面:

    • 部署性能:如何在不牺牲运行时性能的情况下,快速加载和解析模型数据。
    • 模型大小:如何最大限度地压缩模型数据以节省存储空间和传输带宽。
    • 环境要求:如何确保序列化格式在目标环境中兼容运行,尤其是在资源受限的嵌入式系统或边缘设备上。

    虽然自定义序列化方法可以精确满足特定需求,但其维护和版本兼容性可能成为挑战。每次模型升级或格式变更都需要确保兼容性和数据完整性。因此,选择模型序列化方法,可以优先使用跨平台跨语言通用序列化方法,最后再考虑使用自定义序列化方法。

Pytorch 模型序列化方法

Pytorch 模型序列化有两种方法,一种是基于 Pytorch 内部格式,另一种是使用 ONNX。前者只保存了网络模型的参数、结构,不能保存网络模型的信息计算图。

  1. Pytorch 内部格式

    PyTorch 内部格式主要通过torch.savetorch.load函数实现模型的保存与加载。这种方法仅存储已训练模型的状态,包括网络模型的权重、偏置和优化器状态等信息。以下是详细步骤:

    # Saving & Loading Model for Inference
    
    torch.save(model.state_dict(), PATH)
    
    model = TheModelClass(*args, **kwargs)
    model.load_state_dict(torch.load(PATH))
    model.eval()
    

    torch.save 将序列化对象保存到磁盘。该函数使用 Python 的 pickle 实用程序进行序列化。使用此函数可以保存各种对象的模型、张量和字典。

    torch.nn.Module.load_state_dict 使用反序列化的 state_dict 加载模型的参数字典 。在 PyTorch 中,模型的可学习参数(即权重和偏差)torch.nn.Module 包含在模型的参数中 (通过访问 model.parameters())。

    state_dict只是一个 Python 字典对象,它将每个层映射到其参数张量。请注意,只有具有可学习参数的层(卷积层、线性层等)和注册缓冲区(batchnorm 的 running_mean)在模型的state_dict中具有条目。

    优化器对象 torch.optim 还有一个state_dict,其中包含有关优化器状态以及所使用的超参数的信息。由于 state_dict 对象是 Python 字典,因此可以轻松保存、更新、更改和恢复它们,从而为 PyTorch 模型和优化器添加大量模块化功能。

    当训练的模型在 GPU 中时,torch.save函数将其存储到磁盘中。当再次加载该模型时,会将该模型从磁盘先加载 CPU 中,再移动到指定的 GPU 中。但是,当重新加载的机器不存在 GPU 时,模型加载可能会出错。要将在 GPU 上训练的模型加载到 CPU 内存中,可以使用 PyTorch 库的.to()方法将模型转移到 CPU 设备。以下是一个示例代码:

    import torch
    import torchvision.models as models
    
    # 在 GPU 上训练模型
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = models.resnet50().to(device)
    # 训练模型...
    
    # 将模型加载到 CPU 内存中
    model = model.to("cpu")
    

    在这个示例中,首先检查是否有可用的 cuda 设备,然后将模型移动到 cuda 设备上进行训练。最后,使用.to("cpu")将模型加载到 CPU 内存中。将模型从 GPU 移动到 CPU 可能会导致一些性能损失,因为 GPU 设备通常比 CPU 设备更适合进行大规模并行计算。所以在通常情况下,在需要使用模型进行推理时再将其移动到 CPU 上。

  2. ONNX

    PyTorch 提供了内置支持,可以使用torch.onnx.export方法将 PyTorch 模型导出为 ONNX 格式。

    以下代码将预训练的 AlexNet 导出到名为alexnet.onnx的 ONNX 文件。调用 torch.onnx.export 运行模型一次以跟踪其执行情况,然后将跟踪的模型导出到指定文件。

    import torch
    import torchvision
    
    dummy_input = torch.randn(10, 3, 224, 224, device="cuda")
    model = torchvision.models.alexnet(pretrained=True).cuda()
    
    input_names = [ "actual_input_1" ] + [ "learned_%d" % i for i in range(16) ]
    output_names = [ "output1" ]
    
    torch.onnx.export(model, dummy_input, "alexnet.onnx", verbose=True, input_names=input_names, output_names=output_names)
    

    然后,可以运行如下代码来加载模型:

    import onnx
    
    # Load the ONNX model
    model = onnx.load("alexnet.onnx")
    
    # Check that the model is well formed
    onnx.checker.check_model(model)
    
    # Print a human readable representation of the graph
    print(onnx.helper.printable_graph(model.graph))
    

    这种方法不仅保存了模型的参数,还包括完整的计算图信息,使得模型可以在支持 ONNX 的不同框架和平台之间进行转换和部署。

目标文件格式

在序列化与反序列化的过程中,选择合适的目标文件格式至关重要,它决定了数据的存储方式、传输效率和系统的整体性能。下文将介绍 Protobuf 和 FlatBuffers 两种流行的目标文件格式。

Protocol Buffers

Protobuf是 “Protocol Buffers” 的缩写,是一种高效、与语言无关的数据序列化机制。它使开发人员能够在文件中定义结构化数据.proto,然后使用该文件生成可以从不同数据流写入和读取数据的源代码。

Protobuf 最初是由谷歌的工程师开发的,他们需要一种有效的方法来跨各种内部服务序列化结构化数据。其特点是语言无关、平台无关;比 XML 更小更快更为简单;扩展性、兼容性好。

文件语法详解

  • 基本语法: 字段规则数据类型名称 = 域值 [选项 = 选项值]
// 字段规则数据类型名称 = 域值 [选项 = 选项值]

message Net{ // message 属于 Net 域;
  optional string name = 'conv_1*1_0_3';
  repeated Layer layer = 2;
}
  • 字段规则

    • required:一个格式良好的消息一定要含有 1 个这种字段。表示该值是必须要设置的。
    • optional:消息格式中该字段可以有 0 个或 1 个值(不超过 1 个)。
    • repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括 0 次)。重复的值的顺序会被保留。表示该值可以重复,相当于 java 中的 List。

Protobuf 例子

我们将编写一个 caffe::NetParameter(或在 Python 中 caffe.proto.caffe_pb2.NetParameter)protobuf。

  • 编写数据层
  layer {
  name: "mnist"
  type: "Data"
  transform_param {
      scale: 0.00390625
  }
  data_param {
      source: "mnist_train_lmdb"
      backend: LMDB
      batch_size: 64
  }
  top: "data"
  top: "label"
  }
  • 编写卷积层
  layer {
  name: "conv1"
  type: "Convolution"
  param { lr_mult: 1 }
  param { lr_mult: 2 }
  convolution_param {
      num_output: 20
      kernel_size: 5
      stride: 1
      weight_filler {
      type: "xavier"
      }
      bias_filler {
      type: "constant"
      }
  }
  bottom: "data"
  top: "conv1"
  }

TensorFlow 编码和解码

// 将给定的 Protocol Buffer 对象编码为字节字符串

tf.io.encode_proto(
    sizes: Annotated[Any, _atypes.Int32],
    values,
    field_names,
    message_type: str,
    descriptor_source: str = 'local://',
    name=None
) -> Annotated[Any, _atypes.String]

// 将给定的字节字符串解码为 Protocol Buffer 对象

tf.io.decode_proto(
    bytes: Annotated[Any, _atypes.String],
    message_type: str,
    field_names,
    output_types,
    descriptor_source: str = 'local://',
    message_format: str = 'binary',
    sanitize: bool = False,
    name=None
)

编码模式

计算机里一般常用的是二进制编码,如 int 类型由 32 位组成,每位代表数值 2 的 n 次方,n 的范围是 0-31。Protobuf 采用 TLV 编码模式,即把一个信息按照 tag-length-value 的模式进行编码。tag 和 value 部分类似于字典的 key 和 value,tag 标识字段的类型和唯一性,它是一个整数,表示字段号和数据类型。Protobuf 中,Tag 是通过字段号和数据类型组合编码的。length 表示 value 的长度,对于定长数据类型(如整数、浮点数),Length 可以省略,因为值的长度是已知的。对于可变长数据类型(如字符串、字节数组),Length 表示值的字节数。Value 是实际的数据内容。根据 Tag 和 Length,可以正确解析和理解 Value 部分。

在 Protobuf 中,Tag 的编码结合了字段号和数据类型,具体采用 Varint 编码方式:字段号(Field Number)唯一标识消息中的字段,值为正整数。线类型(Wire Type)表示字段的数据类型,如整数、浮点数、长度前缀的字符串等。Protobuf 使用 3 位来表示线类型(Wire Type),其余部分表示字段号(Field Number)。编码格式如下:

Tag = (Field Number << 3) | Wire Type

编解码过程

Protobuf 的编解码过程是基于其 TLV 结构进行的。解析根消息(Root Message)时,会逐个解析其包含的字段。以下是具体的步骤:

编码过程: 首先构建消息结构,根据.proto文件定义的消息结构,构建消息对象;然后对逐个字段进行编码,编码 Tag(将字段号和线类型编码成 Tag)、Length(对于可变长数据类型,计算并编码值的长度)、Value(将实际数据编码成二进制格式);然后将 Tag、Length 和 Value 组合成二进制格式的数据块,最后将所有字段的二进制数据块组合成完整的消息二进制流。

解码过程: 根 message 由多个 TLV 形式的 field 组成,解析 message 的时候逐个去解析 field。由于 field 是 TLV 形式,因此可以知道每个 field 的长度,然后通过偏移上一个 field 长度找到下一个 field 的起始地址。其中 field 的 value 有可能是一个嵌套的 message,这时候需要递归地应用相同的解析方法。在解析 field 时,首先解析其 tag,以获取 field_num(属性 ID)和 type。field_num 标识了该属性的唯一标识,而 type 则指示了用于解码 value 的编码算法。

FlatBuffers

FlatBuffers 是一个开源的、跨平台的、高效的、提供了多种语言接口的序列化工具库。实现了与 Protocal Buffers 类似的序列化格式。主要由 Wouter van Oortmerssen 编写,并由谷歌开源。

FlatBuffers 主要针对部署和对性能有要求的应用。相对于 Protocol Buffers,FlatBuffers 不需要解析,只通过序列化后的二进制 buffer 即可完成数据访问。

FlatBuffers 具有数据访问不需要解析(将数据序列化成二进制 buffer,之后的数据访问直接读取这个 buffer)、内存高效且速度快(数据访问只在序列化后的二进制 buffer,不需额外的内存分配,数据访问速度接近原生的 struct,只多了一次解引用)、生成的代码量小(只需依赖一个头文件)、可扩展性强(支持灵活的 schema 定义,可以方便地扩展数据结构)、支持强类型检测(提供编译时的类型检查,确保数据结构的安全性和正确性)和易于使用(提供多种语言接口)等特点。

与 Protocol Buffers 类似,使用 FlatBuffers 需要先定义一个 schema 文件,用于描述要序列化的数据结构的组织关系。以下是一个简单的示例:

table Monster {
  name: string;
  mana: int = 150;
  hp: int;
  inventory: [ubyte];
  color: int;
}
root_type Monster;

通过 schema 文件,可以生成相应的代码,用于序列化和反序列化操作。

很多 AI 推理框架都是用的 FlatBuffers,最主要的有以下两个:

MNN 阿里巴巴的神经网络推理引擎,是一个轻量级的神经网络引擎,支持深度学习的推理与训练。适用于服务器/个人电脑/手机/嵌入式各类设备。目前,MNN 已经在阿里巴巴的手机淘宝、手机天猫、优酷等 30 多个 App 中使用,覆盖直播、短视频、搜索推荐、商品图像搜索、互动营销、权益发放、安全风控等场景。MNN 模型文件采用的存储结构是 FlatBuffers。

MindSpore Lite: 一种适用于端边云场景的新型开源深度学习训练/推理框架,提供离线转换模型功能的工具,支持多种类型的模型转换,转换后的模型可用于推理。除了基本的模型转换功能之外,还支持用户对模型进行自定义的优化与构建,生成用户自定义算子的模型。

MindSpore Lite 提供了一套注册机制,允许用户基于转换工具进行能力扩展:包括节点解析扩展、模型解析扩展以及图优化扩展,用户可以根据自身的需要对模型实现自定义的解析与融合优化。节点解析扩展需要依赖 flatbuffers 和 protobuf 及三方框架的序列化文件。

image

Protobuf VS FlatBuffers

下面的表格从支持的语言、版本、协议文件、代码生成工具及协议字段类型几个方面比较了 ProtoBufers 和 Flatbuffers 两种格式:

Proto Bufers Flatbuffers
支持语言 C/C++, C#, Go, Java, Python, Ruby, Objective-C, Dart C/C++, C#, Go, Java, JavaScript, TypeScript, Lua, PHP, Python, Rust, Lobster
版本 2.x/3.x,不相互兼容 1.x
协议文件 .proto,需指定协议文件版本 .fbs
代码生成工具 有(生成代码量较多) 有(生成代码量较少)
协议字段类型 bool, bytes, int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, float, double, string bool, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float, double, string, vector
目录
相关文章
|
7天前
|
机器学习/深度学习 人工智能 安全
GLM-Zero:智谱AI推出与 OpenAI-o1-Preview 旗鼓相当的深度推理模型,开放在线免费使用和API调用
GLM-Zero 是智谱AI推出的深度推理模型,专注于提升数理逻辑、代码编写和复杂问题解决能力,支持多模态输入与完整推理过程输出。
112 24
GLM-Zero:智谱AI推出与 OpenAI-o1-Preview 旗鼓相当的深度推理模型,开放在线免费使用和API调用
|
10天前
|
人工智能 自然语言处理 调度
Casevo:开源的社会传播模拟系统,基于 AI 模拟人类认知、决策和社会交互,预测社会传播现象
Casevo 是中国传媒大学推出的开源社会传播模拟系统,结合大语言模型和多智能体技术,支持复杂社会网络建模与动态交互,适用于新闻传播、社会计算等领域。
71 22
Casevo:开源的社会传播模拟系统,基于 AI 模拟人类认知、决策和社会交互,预测社会传播现象
|
20天前
|
人工智能 前端开发 小程序
2024年12月30日蜻蜓蜻蜓AI工具系统v1.0.0发布-优雅草科技本产品前端源代码已对外开源可免费商用-优雅草老八
2024年12月30日蜻蜓蜻蜓AI工具系统v1.0.0发布-优雅草科技本产品前端源代码已对外开源可免费商用-优雅草老八
2024年12月30日蜻蜓蜻蜓AI工具系统v1.0.0发布-优雅草科技本产品前端源代码已对外开源可免费商用-优雅草老八
|
15天前
|
机器学习/深度学习 人工智能 自然语言处理
AigcPanel:开源的 AI 虚拟数字人系统,一键安装开箱即用,支持视频合成、声音合成和声音克隆
AigcPanel 是一款开源的 AI 虚拟数字人系统,支持视频合成、声音克隆等功能,适用于影视制作、虚拟主播、教育培训等多种场景。
143 12
AigcPanel:开源的 AI 虚拟数字人系统,一键安装开箱即用,支持视频合成、声音合成和声音克隆
|
16天前
|
存储 人工智能 开发框架
Eliza:TypeScript 版开源 AI Agent 开发框架,快速搭建智能、个性的 Agents 系统
Eliza 是一个开源的多代理模拟框架,支持多平台连接、多模型集成,能够快速构建智能、高效的AI系统。
125 8
Eliza:TypeScript 版开源 AI Agent 开发框架,快速搭建智能、个性的 Agents 系统
|
19天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
89 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
1天前
|
人工智能 自然语言处理 数据可视化
校企合作|TsingtaoAI携手潍坊学院,共建AI驱动的党建信息化系统
TsingtaoAI与潍坊学院近日达成合作,正式签署《人工智能党建信息化系统开发》技术开发合同,计划在未来两年内联合开发一套集党员教育、党务管理、党建活动智能化以及数据可视化于一体的智能党建系统。本次合作将充分结合TsingtaoAI在AI大模型领域的技术优势和潍坊学院的学术资源,为推动党建工作的数字化、智能化和高效化注入新的动力。
23 10
|
15天前
|
机器学习/深度学习 人工智能 监控
AI在交通管理系统中的应用
AI在交通管理系统中的应用
64 23
|
17天前
|
人工智能 供应链 安全
面向高效大模型推理的软硬协同加速技术 多元化 AI 硬件引入评测体系
本文介绍了AI硬件评测体系的三大核心方面:统一评测标准、平台化与工具化、多维度数据消费链路。通过标准化评测流程,涵盖硬件性能、模型推理和训练性能,确保评测结果客观透明。平台化实现资源管理与任务调度,支持大规模周期性评测;工具化则应对紧急场景,快速适配并生成报告。最后,多维度数据消费链路将评测数据结构化保存,服务于综合通用、特定业务及专业性能分析等场景,帮助用户更好地理解和使用AI硬件。
|
4天前
|
机器学习/深度学习 人工智能 缓存
基于英特尔平台加速 AI 应用及 LLM 推理性能介绍|龙蜥大讲堂第115期
本文摘自龙蜥大讲堂英特尔 AI 软件工程师黄文欢的分享,主要包括以下三个方面的内容: 1. 第五代英特尔至强处理器 2. LLM 推理加速框架 xFast Transformer 及其优化策略 3. 性能数据及 Demo 展示

热门文章

最新文章