Shortcuts

基于TorchDynamo的ONNX导出器

警告

TorchDynamo 的 ONNX 导出器是一项快速发展的测试版技术。

概述

ONNX 导出器利用 TorchDynamo 引擎来钩入 Python 的帧评估 API,并动态地将字节码重写为 FX 图。生成的 FX 图在最终转换为 ONNX 图之前会进行优化。

这种方法的主要优点是,FX 图是通过字节码分析捕获的,保留了模型的动态特性,而不是使用传统的静态追踪技术。

导出器被设计为模块化和可扩展的。它由以下组件组成:

  • ONNX 导出器: Exporter 主要类,负责协调导出过程。

  • ONNX 导出选项: ExportOptions 有一组选项用于控制导出过程。

  • ONNX 注册表: OnnxRegistry 是 ONNX 操作符和函数的注册表。

  • FX Graph Extractor: FXGraphExtractor 从 PyTorch 模型中提取 FX 图。

  • 假模式: ONNXFakeContext 是一个上下文管理器,用于为大规模模型启用假模式。

  • ONNX 程序: ONNXProgram 是导出器的输出,包含导出的 ONNX 图和诊断信息。

  • ONNX 程序序列化器: ONNXProgramSerializer 将导出的模型序列化到文件中。

  • ONNX 诊断选项: DiagnosticOptions 有一组选项,用于控制导出器发出的诊断信息。

依赖项

ONNX 导出器依赖于额外的 Python 包:

它们可以通过pip安装:

pip install --upgrade onnx onnxscript

一个简单的例子

请参见以下使用简单多层感知器(MLP)作为示例的exporter API演示:

import torch
import torch.nn as nn

class MLPModel(nn.Module):
  def __init__(self):
      super().__init__()
      self.fc0 = nn.Linear(8, 8, bias=True)
      self.fc1 = nn.Linear(8, 4, bias=True)
      self.fc2 = nn.Linear(4, 2, bias=True)
      self.fc3 = nn.Linear(2, 2, bias=True)

  def forward(self, tensor_x: torch.Tensor):
      tensor_x = self.fc0(tensor_x)
      tensor_x = torch.sigmoid(tensor_x)
      tensor_x = self.fc1(tensor_x)
      tensor_x = torch.sigmoid(tensor_x)
      tensor_x = self.fc2(tensor_x)
      tensor_x = torch.sigmoid(tensor_x)
      output = self.fc3(tensor_x)
      return output

model = MLPModel()
tensor_x = torch.rand((97, 8), dtype=torch.float32)
onnx_program = torch.onnx.dynamo_export(model, tensor_x)

如上面的代码所示,您只需要向torch.onnx.dynamo_export()提供模型实例及其输入。 导出器将返回一个torch.onnx.ONNXProgram实例,其中包含导出的ONNX图以及额外信息。

通过 onnx_program.model_proto 提供的内存模型是一个符合 ONNX IR 规范onnx.ModelProto 对象。 然后,可以使用 torch.onnx.ONNXProgram.save() API 将 ONNX 模型序列化为 Protobuf 文件

onnx_program.save("mlp.onnx")

使用GUI检查ONNX模型

您可以使用Netron查看导出的模型。

使用Netron查看的MLP模型

请注意,每个图层都以一个矩形框表示,右上角有一个f图标。

MLP模型上突出显示的ONNX函数

通过展开它,函数体将显示出来。

ONNX 函数体

函数体是一系列 ONNX 运算符或其他函数。

诊断SARIF的问题

ONNX 诊断通过采用 静态分析结果交换格式(即 SARIF) 超越了常规日志,以帮助用户使用 GUI 调试和改进他们的模型,例如 Visual Studio Code 的 SARIF Viewer

主要优势包括:

  • 诊断信息以机器可解析的格式发出,格式为静态分析结果交换格式 (SARIF)

  • 一种更清晰、结构化的新方法,用于添加新的诊断规则并跟踪它们。

  • 作为未来更多改进的基础,利用诊断信息。

API参考

torch.onnx.dynamo_export(model, /, *model_args, export_options=None, **model_kwargs)

将 torch.nn.Module 导出为 ONNX 图。

Parameters
  • 模型 (联合[模块, 可调用, 导出程序]) – 要导出到 ONNX 的 PyTorch 模型。

  • model_args – 传递给 model 的位置参数。

  • model_kwargs – 输入到 model 的关键字参数。

  • export_options (可选[ExportOptions]) – 影响导出到ONNX的选项。

Returns

导出的ONNX模型的内存表示。

Return type

ONNX程序

示例 1 - 最简单的导出

class MyModel(torch.nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.linear = torch.nn.Linear(2, 2)
    def forward(self, x, bias=None):
        out = self.linear(x)
        out = out + bias
        return out
model = MyModel()
kwargs = {"bias": 3.}
args = (torch.randn(2, 2, 2),)
onnx_program = torch.onnx.dynamo_export(
    model,
    *args,
    **kwargs).save("my_simple_model.onnx")

示例 2 - 使用动态形状导出

# 之前的模型可以导出为动态形状
export_options = torch.onnx.ExportOptions(dynamic_shapes=True)
onnx_program = torch.onnx.dynamo_export(
    model,
    *args,
    **kwargs,
    export_options=export_options)
onnx_program.save("my_dynamic_model.onnx")

通过打印输入的动态维度,我们可以看到输入形状不再是 (2,2,2)

>>> print(onnx_program.model_proto.graph.input[0])
名称: "arg0"
类型 {
  张量类型 {
    元素类型: 1
    形状 {
      维度 {
        维度参数: "arg0_dim_0"
      }
      维度 {
        维度参数: "arg0_dim_1"
      }
      维度 {
        维度参数: "arg0_dim_2"
      }
    }
  }
}
class torch.onnx.ExportOptions(*, dynamic_shapes=None, op_level_debug=None, fake_context=None, onnx_registry=None, diagnostic_options=None)

影响 TorchDynamo ONNX 导出器的选项。

Variables
  • dynamic_shapes (可选[布尔值]) – 输入/输出张量的形状信息提示。 当 None 时,导出器确定最兼容的设置。 当 True 时,所有输入形状都被视为动态的。 当 False 时,所有输入形状都被视为静态的。

  • op_level_debug (可选[布尔值]) – 是否导出带有操作级调试信息的模型

  • diagnostic_options (诊断选项) – 导出器的诊断选项。

  • fake_context (可选[ONNXFakeContext]) – 用于符号追踪的假上下文。

  • onnx_registry (可选[OnnxRegistry]) – 用于将ATen运算符注册到ONNX函数的ONNX注册表。

torch.onnx.enable_fake_mode()

在上下文持续期间启用假模式。

在内部,它实例化了一个 torch._subclasses.fake_tensor.FakeTensorMode 上下文管理器, 将用户输入和模型参数转换为 torch._subclasses.fake_tensor.FakeTensor

一个 torch._subclasses.fake_tensor.FakeTensor 是一个具有在不需要实际通过分配在 meta 设备上的张量进行计算的情况下运行 PyTorch 代码的能力的 torch.Tensor。因为设备上没有实际的数据分配,这个 API 允许在不实际需要执行它所需的内存占用的情况下导出大型模型。

强烈建议在导出过大而无法放入内存的模型时启用假模式。

Returns

一个 ONNXFakeContext 对象,必须通过 ExportOptions.fake_context 参数传递给 dynamo_export()

示例:

# xdoctest: +REQUIRES(env:TORCH_DOCTEST_ONNX)
>>> import torch
>>> import torch.onnx
>>> class MyModel(torch.nn.Module):  # 虚拟模型
...     def __init__(self) -> None:
...         super().__init__()
...         self.linear = torch.nn.Linear(2, 2)
...     def forward(self, x):
...         out = self.linear(x)
...         return out
>>> with torch.onnx.enable_fake_mode() as fake_context:
...     my_nn_module = MyModel()
...     arg1 = torch.randn(2, 2, 2)  # 位置输入 1
>>> export_options = torch.onnx.ExportOptions(fake_context=fake_context)
>>> onnx_program = torch.onnx.dynamo_export(
...     my_nn_module,
...     arg1,
...     export_options=export_options
... )
>>> # 保存模型,不带初始化器
>>> onnx_program.save("my_model_without_initializers.onnx")
>>> # 保存模型,带初始化器
>>> onnx_program.save("my_model_with_initializers.onnx", model_state=MyModel().state_dict())

警告

此API是实验性的,并且向后兼容。

class torch.onnx.ONNXProgram(model_proto, input_adapter, output_adapter, diagnostic_context, *, fake_context=None, export_exception=None, model_signature=None, model_torch=None)

一个已经导出到ONNX的PyTorch模型的内存表示。

Parameters
  • model_proto (onnx.ModelProto) – 导出的 ONNX 模型,作为 onnx.ModelProto

  • input_adapter (io_adapter.InputAdapter) – 用于将PyTorch输入转换为ONNX输入的输入适配器。

  • output_adapter (io_adapter.OutputAdapter) – 用于将PyTorch输出转换为ONNX输出的输出适配器。

  • 诊断上下文 (诊断.诊断上下文) – 负责记录错误和元数据的SARIF诊断系统上下文对象。

  • fake_context (可选[ONNXFakeContext]) – 用于符号追踪的假上下文。

  • export_exception (可选[异常]) – 导出过程中发生的异常,如果有的话。

  • model_signature (可选[torch.export.ExportGraphSignature]) – 导出的ONNX图的模型签名。

adapt_torch_inputs_to_onnx(*model_args, model_with_state_dict=None, **model_kwargs)[源代码]

将 PyTorch 模型输入转换为导出的 ONNX 模型输入格式。

由于设计差异,PyTorch模型与导出的ONNX模型之间的输入/输出格式通常不同。例如,PyTorch模型允许使用None,但ONNX不支持。PyTorch模型允许张量的嵌套结构,但ONNX仅支持扁平化的张量等。

实际的适配步骤与每个单独的导出相关。它取决于PyTorch模型、用于导出的特定model_args和model_kwargs集合,以及导出选项。

此方法重放导出期间记录的适应步骤。

Parameters
  • model_args – PyTorch 模型输入。

  • model_with_state_dict (可选[联合[模块, 可调用, 导出程序]]) – 用于获取额外状态的PyTorch模型。 如果未指定,则使用导出期间使用的模型。 当使用enable_fake_mode()提取ONNX图所需的实际初始化器时,这是必需的。

  • model_kwargs – PyTorch 模型关键字输入。

Returns

从PyTorch模型输入转换的一系列张量。

Return type

序列[联合[张量, 整数, 浮点数, 布尔值]]

示例:

# xdoctest: +REQUIRES(env:TORCH_DOCTEST_ONNX)
>>> import torch
>>> import torch.onnx
>>> from typing import Dict, Tuple
>>> def func_nested_input(
...     x_dict: Dict[str, torch.Tensor],
...     y_tuple: Tuple[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]
... ):
...     if "a" in x_dict:
...         x = x_dict["a"]
...     elif "b" in x_dict:
...         x = x_dict["b"]
...     else:
...         x = torch.randn(3)
...
...     y1, (y2, y3) = y_tuple
...
...     return x + y1 + y2 + y3
>>> x_dict = {"a": torch.tensor(1.)}
>>> y_tuple = (torch.tensor(2.), (torch.tensor(3.), torch.tensor(4.)))
>>> onnx_program = torch.onnx.dynamo_export(func_nested_input, x_dict, y_tuple)
>>> print(x_dict, y_tuple)
{'a': tensor(1.)} (tensor(2.), (tensor(3.), tensor(4.)))
>>> print(onnx_program.adapt_torch_inputs_to_onnx(x_dict, y_tuple, model_with_state_dict=func_nested_input))
(tensor(1.), tensor(2.), tensor(3.), tensor(4.))

警告

此API是实验性的,并且向后兼容。

adapt_torch_outputs_to_onnx(model_outputs, model_with_state_dict=None)[源代码]

将 PyTorch 模型输出转换为导出的 ONNX 模型输出格式。

由于设计差异,PyTorch模型和导出的ONNX模型之间的输入/输出格式通常不同。例如,PyTorch模型允许使用None,但ONNX不支持。PyTorch模型允许张量的嵌套结构,但ONNX仅支持扁平化的张量等。

实际的适配步骤与每个单独的导出相关。它取决于PyTorch模型、用于导出的特定model_args和model_kwargs集合,以及导出选项。

此方法重放导出期间记录的适应步骤。

Parameters
  • model_outputs (任意) – PyTorch 模型输出。

  • model_with_state_dict (可选[联合[模块, 可调用, 导出程序]]) – 用于获取额外状态的PyTorch模型。 如果未指定,则使用导出期间使用的模型。 当使用enable_fake_mode()提取ONNX图所需的实际初始化器时,这是必需的。

Returns

PyTorch 模型输出在导出的 ONNX 模型输出格式中。

Return type

序列[联合[张量, 整数, 浮点数, 布尔值]]

示例:

# xdoctest: +REQUIRES(env:TORCH_DOCTEST_ONNX)
>>> import torch
>>> import torch.onnx
>>> def func_returning_tuples(x, y, z):
...     x = x + y
...     y = y + z
...     z = x + y
...     return (x, (y, z))
>>> x = torch.tensor(1.)
>>> y = torch.tensor(2.)
>>> z = torch.tensor(3.)
>>> onnx_program = torch.onnx.dynamo_export(func_returning_tuples, x, y, z)
>>> pt_output = func_returning_tuples(x, y, z)
>>> print(pt_output)
(tensor(3.), (tensor(5.), tensor(8.)))
>>> print(onnx_program.adapt_torch_outputs_to_onnx(pt_output, model_with_state_dict=func_returning_tuples))
[tensor(3.), tensor(5.), tensor(8.)]

警告

此API是实验性的,并且向后兼容。

property diagnostic_context: diagnostics.DiagnosticContext

与导出相关的诊断上下文。

property fake_context: Optional[ONNXFakeContext]

与导出相关的虚假上下文。

property model_proto: onnx.ModelProto

导出的 ONNX 模型作为一个 onnx.ModelProto

property model_signature: Optional[导出图签名]

导出的ONNX图的模型签名。

此信息是相关的,因为ONNX规范通常与PyTorch的不同,导致生成的ONNX图的输入和输出模式与实际的PyTorch模型实现不同。通过使用模型签名,用户可以理解输入和输出的差异,并在ONNX Runtime中正确执行模型。

注意:模型签名仅在ONNX图从torch.export.ExportedProgram对象导出时可用。

注意:对模型进行的任何改变模型签名的转换都必须通过InputAdaptStep和/或OutputAdaptStep来更新此模型签名。

示例

以下模型生成不同的输入和输出集。 前4个输入是模型参数(即 conv1.weight、conv2.weight、fc1.weight、fc2.weight), 接下来的2个输入是注册的缓冲区(即 my_buffer2、my_buffer1),最后 最后2个输入是用户输入(即 x 和 b)。 第一个输出是缓冲区突变(即 my_buffer2),最后一个输出是实际的模型输出。

>>> class CustomModule(torch.nn.Module):
...     def __init__(self):
...         super().__init__()
...         self.my_parameter = torch.nn.Parameter(torch.tensor(2.0))
...         self.register_buffer("my_buffer1", torch.tensor(3.0))
...         self.register_buffer("my_buffer2", torch.tensor(4.0))
...         self.conv1 = torch.nn.Conv2d(1, 32, 3, 1, bias=False)
...         self.conv2 = torch.nn.Conv2d(32, 64, 3, 1, bias=False)
...         self.fc1 = torch.nn.Linear(9216, 128, bias=False)
...         self.fc2 = torch.nn.Linear(128, 10, bias=False)
...     def forward(self, x, b):
...         tensor_x = self.conv1(x)
...         tensor_x = torch.nn.functional.sigmoid(tensor_x)
...         tensor_x = self.conv2(tensor_x)
...         tensor_x = torch.nn.functional.sigmoid(tensor_x)
...         tensor_x = torch.nn.functional.max_pool2d(tensor_x, 2)
...         tensor_x = torch.flatten(tensor_x, 1)
...         tensor_x = self.fc1(tensor_x)
...         tensor_x = torch.nn.functional.sigmoid(tensor_x)
...         tensor_x = self.fc2(tensor_x)
...         output = torch.nn.functional.log_softmax(tensor_x, dim=1)
...         (
...         self.my_buffer2.add_(1.0) + self.my_buffer1
...         )  # 通过就地加法改变缓冲区
...         return output
>>> inputs = (torch.rand((64, 1, 28, 28), dtype=torch.float32), torch.randn(3))
>>> exported_program = torch.export.export(CustomModule(), args=inputs)
>>> onnx_program = torch.onnx.dynamo_export(exported_program, *inputs)
>>> print(onnx_program.model_signature)
ExportGraphSignature(
    input_specs=[
        InputSpec(kind=, arg=TensorArgument(name='arg0_1'),
                  target='conv1.weight', persistent=None),
        InputSpec(kind=, arg=TensorArgument(name='arg1_1'),
                  target='conv2.weight', persistent=None),
        InputSpec(kind=, arg=TensorArgument(name='arg2_1'),
                  target='fc1.weight', persistent=None),
        InputSpec(kind=, arg=TensorArgument(name='arg3_1'),
                  target='fc2.weight', persistent=None),
        InputSpec(kind=, arg=TensorArgument(name='arg4_1'),
                  target='my_buffer2', persistent=True),
        InputSpec(kind=, arg=TensorArgument(name='arg5_1'),
                  target='my_buffer1', persistent=True),
        InputSpec(kind=, arg=TensorArgument(name='arg6_1'),
                  target=None, persistent=None),
        InputSpec(kind=, arg=TensorArgument(name='arg7_1'),
                  target=None, persistent=None)
    ],
    output_specs=[
        OutputSpec(kind=, arg=TensorArgument(name='add'), target='my_buffer2'),
        OutputSpec(kind=, arg=TensorArgument(name='_log_softmax'), target=None)
    ]
)
save(destination, *, model_state=None, serializer=None)[源代码]

将内存中的 ONNX 模型保存到 destination 使用指定的 serializer

Parameters
  • 目标 (Union[str, BufferedIOBase]) – 保存ONNX模型的目标位置。它可以是字符串或类文件对象。 当与model_state一起使用时,它必须是一个带有目标完整路径的字符串。 如果目标是字符串,除了将ONNX模型保存到文件中,模型权重也会存储在与ONNX模型相同的目录中的单独文件中。例如,对于目标="/path/model.onnx",初始化器将保存在“/path/”文件夹中,与“onnx.model”一起。

  • model_state (可选[联合[字典[字符串, 任意], 字符串]]) – 包含所有权重的PyTorch模型的state_dict。 它可以是一个包含检查点路径的字符串,也可以是一个包含实际模型状态的字典。 支持的文件格式与torch.loadsafetensors.safe_open支持的格式相同。 当使用enable_fake_mode()时需要,但在ONNX图上需要真实的初始化器。

  • 序列化器 (可选[ONNXProgramSerializer]) – 要使用的序列化器。如果未指定,模型将以Protobuf格式序列化。

save_diagnostics(destination)[源代码]

将导出诊断信息保存为SARIF日志到指定的目标路径。

Parameters

目标 (str) – 保存诊断SARIF日志的目标位置。 它必须具有.sarif扩展名。

Raises

ValueError – 如果目标路径不以 .sarif 扩展名结尾。

class torch.onnx.ONNXProgramSerializer(*args, **kwargs)

将ONNX图序列化为特定格式的协议(例如Protobuf)。 请注意,这是一个高级使用场景。

serialize(onnx_program, destination)[源代码]

必须为序列化实现的协议方法。

Parameters
  • onnx_program (ONNXProgram) – 表示内存中导出的ONNX模型

  • 目标 (BufferedIOBase) – 一个二进制IO流或预分配的缓冲区,序列化模型应写入其中。

示例

一个简单的序列化器,将导出的 onnx.ModelProto 以 Protobuf 格式写入 destination

# xdoctest: +REQUIRES(env:TORCH_DOCTEST_ONNX)
>>> import io
>>> import torch
>>> import torch.onnx
>>> class MyModel(torch.nn.Module):  # 虚拟模型
...     def __init__(self) -> None:
...         super().__init__()
...         self.linear = torch.nn.Linear(2, 2)
...     def forward(self, x):
...         out = self.linear(x)
...         return out
>>> class ProtobufONNXProgramSerializer:
...     def serialize(
...         self, onnx_program: torch.onnx.ONNXProgram, destination: io.BufferedIOBase
...     ) -> None:
...         destination.write(onnx_program.model_proto.SerializeToString())
>>> model = MyModel()
>>> arg1 = torch.randn(2, 2, 2)  # 位置输入 1
>>> torch.onnx.dynamo_export(model, arg1).save(
...     destination="exported_model.onnx",
...     serializer=ProtobufONNXProgramSerializer(),
... )
class torch.onnx.ONNXRuntimeOptions(*, session_options=None, execution_providers=None, execution_provider_options=None)

通过ONNX Runtime影响ONNX模型执行的选项。

Variables
  • session_options (可选[序列['onnxruntime.SessionOptions']]) – ONNX Runtime 会话选项。

  • execution_providers (可选[序列[联合[str, 元组[str, 字典[任意, 任意]]]]]) – 在模型执行期间使用的ONNX Runtime执行提供程序。

  • execution_provider_options (可选[序列[字典[任意, 任意]]]) – ONNX Runtime 执行提供程序选项。

class torch.onnx.InvalidExportOptionsError

当用户为ExportOptions指定了一个无效值时引发。

class torch.onnx.OnnxExporterError(onnx_program, message)

当发生 ONNX 导出器错误时引发。

当在ONNX导出过程中发生错误时,会抛出此异常。它封装了在失败之前生成的ONNXProgram对象,允许访问部分导出结果和相关元数据。

class torch.onnx.OnnxRegistry

ONNX 函数的注册表。

注册表在固定的操作集版本下维护从限定名称到符号函数的映射。它支持注册自定义onnx-script函数,并为调度器调度调用适当的函数。

get_op_functions(namespace, op_name, overload=None)[源代码]

返回给定操作的ONNXFunctions列表:torch.ops...

列表按注册时间排序。自定义操作符应位于列表的后半部分。

Parameters
  • 命名空间 (str) – 要获取的运算符的命名空间。

  • op_name (str) – 要获取的运算符的名称。

  • 重载 (可选[字符串]) – 要获取的运算符的重载。如果是默认重载,请将其保留为None。

Returns

与给定名称对应的ONNXFunctions列表,如果名称不在注册表中,则为None。

Return type

可选[列表[ONNXFunction]]

is_registered_op(namespace, op_name, overload=None)[源代码]

返回给定的操作是否已注册:torch.ops...

Parameters
  • 命名空间 (str) – 要检查的运算符的命名空间。

  • op_name (str) – 要检查的运算符的名称。

  • 重载 (可选[字符串]) – 要检查的运算符的重载。如果是默认重载,请将其保留为None。

Returns

如果给定的操作已注册,则为真,否则为假。

Return type

bool

property opset_version: int

导出器应针对的ONNX操作集版本。默认为最新支持的ONNX操作集版本:18。随着ONNX的不断发展,默认版本将会随时间递增。

register_op(function, namespace, op_name, overload=None, is_complex=False)[源代码]

注册一个自定义操作符:torch.ops...

Parameters
  • 函数 (联合[onnxscript.OnnxFunction, onnxscript.TracedOnnxFunction]) – 要注册的onnx-sctip函数。

  • 命名空间 (str) – 要注册的运算符的命名空间。

  • op_name (str) – 要注册的运算符的名称。

  • 重载 (可选[字符串]) – 要注册的运算符的重载。如果是默认重载,请将其保留为 None。

  • is_complex (布尔值) – 该函数是否是一个处理复数值输入的函数。

Raises

ValueError – 如果名称不是以‘namespace::op’的形式。

class torch.onnx.DiagnosticOptions(verbosity_level=20, warnings_as_errors=False)

诊断上下文的选项。

Variables
  • verbosity_level (int) – 设置每个诊断日志的信息量,相当于Python logging模块中的‘level’。

  • warnings_as_errors (bool) – 当为True时,警告诊断被视为错误诊断。

优云智算