首页
/ HuggingFace Transformers 模型导出 ONNX 完整指南:从 Optimum CLI 到内置 OnnxExporter 的源码级实践

HuggingFace Transformers 模型导出 ONNX 完整指南:从 Optimum CLI 到内置 OnnxExporter 的源码级实践

2026-09-04 09:10:06作者:羿妍玫Ivan

在 Transformers 中,模型训练完成后的关键一步是序列化与部署:把 PyTorch(或 TensorFlow)模型导出为跨框架、可被专用硬件与运行时高效加载执行的中间格式。本文以 Transformers 官方文档《تصدير إلى ONNX(导出到 ONNX)》为核心,完整覆盖 ONNX 导出的两种官方路径——🤗 Optimum CLI 命令行导出与 optimum.onnxruntime 编程式导出,并结合当前仓库 src/transformers/exporters/ 目录中内置的 OnnxExporter 实现,深入讲解导出配置参数、验证流程与底层导出流水线的五阶段机制。读完后你将能够:用 CLI 一键导出 Hub 上的模型检查点、导出本地模型并指定任务头、用 ONNX Runtime 加载推理,并理解 OnnxConfig 各参数在源码中如何影响 torch.onnx.export 的行为。

一、为什么选择 ONNX:跨框架部署的中间表示

ONNX(Open Neural Network Exchange)是一个开放标准,它定义了一组通用的算子(op)共享的文件格式,用于在 PyTorch、TensorFlow 等众多框架之间表示深度学习模型。当一个模型被导出为 ONNX 后,这些标准算子会被组织成一张计算图(通常称为中间表示,IR),该图描述了数据在神经网络中的完整流动路径。

由于 ONNX 图由标准算子与标准数据类型构成,框架之间的切换成本被大幅降低:例如,一个在 PyTorch 中训练的模型可以先导出为 ONNX,再导入 TensorFlow(反之亦然),而无需重写模型代码。

导出到 ONNX 之后,模型可以获得以下三类能力:

  • 推理优化:通过图优化量化等技术降低延迟与内存占用;
  • ONNX Runtime 推理:通过 ORTModelForXXX 系列类加载,它们遵循与 Transformers 中熟悉的 AutoModel 相同的 API 风格;
  • 优化的推理管道:使用与 Transformers pipeline 函数相同 API 的推理管线,直接替换推理链路。

🤗 Optimum 是 Transformers 的扩展框架,它的 exporters 模块提供了基于**配置对象(config object)**的 ONNX 导出能力:这些配置对象已为一批模型架构预置就绪,并且设计上可以方便地扩展到其他架构。完整的预置配置与任务清单可查阅 Optimum 官方文档。

版本适用前提:当前仓库中的内置导出器声明了测试版本 torch 2.12.0onnx 1.21.0onnxscript 0.7.0,且要求依赖 torchonnxonnxscript 三个包(见 OnnxExporter)。因此使用仓库内置导出路径时请确认环境版本。

二、用 Optimum CLI 导出 ONNX

2.1 安装与帮助

首先安装附加依赖:

pip install optimum-onnx

查看所有可用参数的两种方式:查阅 Optimum 文档,或在命令行直接查看帮助:

optimum-cli export onnx --help

2.2 从 Hub 导出模型检查点

要导出 Hub 上的一个检查点(例如 distilbert/distilbert-base-uncased-distilled-squad),运行:

optimum-cli export onnx --model distilbert/distilbert-base-uncased-distilled-squad distilbert_base_uncased_squad_onnx/

命令执行过程中会打印进度日志,最终显示生成的 model.onnx 文件保存位置,例如:

Validating ONNX model distilbert_base_uncased_squad_onnx/model.onnx...
	-[✓] ONNX model output names match reference model (start_logits, end_logits)
	- Validating ONNX Model output "start_logits":
		-[✓] (2, 16) matches (2, 16)
		-[✓] all values close (atol: 0.0001)
	- Validating ONNX Model output "end_logits":
		-[✓] (2, 16) matches (2, 16)
		-[✓] all values close (atol: 0.0001)
The ONNX export succeeded and the exported model was saved at: distilbert_base_uncased_squad_onnx

这段日志值得逐行理解:导出后会自动执行数值一致性校验——ONNX 模型的输出名必须与参考模型(原始 PyTorch 模型)的输出名匹配(此处为 start_logitsend_logits),每个输出的张量形状必须一致((2, 16)),且所有数值在绝对误差容差 atol: 0.0001 内接近。这一步等价于对导出过程做了一次回归测试,确保 ONNX 图与原模型行为一致。

2.3 导出本地模型

当导出的是本地模型而非 Hub 检查点时,需要遵守两条约定:

  1. 先把模型权重文件与分词器文件保存在同一目录(记为 local_path);
  2. 在 CLI 中把 local_path 传给 model 参数(替代 Hub 检查点名),并显式提供 --task 参数。
optimum-cli export onnx --model local_path --task question-answering distilbert_base_uncased_squad_onnx/

如果不提供 task 参数,系统会自动推断任务,缺省为不带任何任务特定头的纯模型架构(例如不带问答头的 DistilBertModel)。支持的任务列表以 Optimum 文档中的任务管理器说明为准。

2.4 导出 TensorFlow 检查点

对 Hub 上的 TensorFlow 检查点,流程完全相同。例如导出来自 Keras 组织的纯 TensorFlow 检查点:

optimum-cli export onnx --model keras-io/transformers-qa distilbert_base_cased_squad_onnx/

2.5 用 ONNX Runtime 加载与推理

导出完成后,model.onnx 可以在任何支持 ONNX 标准的运行时/加速器上运行。以 ONNX Runtime 为例:

>>> from transformers import AutoTokenizer
>>> from optimum.onnxruntime import ORTModelForQuestionAnswering

>>> tokenizer = AutoTokenizer.from_pretrained("distilbert_base_uncased_squad_onnx")
>>> model = ORTModelForQuestionAnswering.from_pretrained("distilbert_base_uncased_squad_onnx")
>>> inputs = tokenizer("What am I using?", "Using DistilBERT with ONNX Runtime!", return_tensors="pt")
>>> outputs = model(**inputs)

注意 ORTModelForQuestionAnswering 遵循与 AutoModelForQuestionAnswering 一致的调用约定:分词后直接 model(**inputs),输出为包含 start_logits/end_logits 的标准输出对象。不同任务对应不同的 ORTModelForXXX 类(序列分类、因果语言模型等),选择依据就是导出时指定的 --task

三、用 optimum.onnxruntime 编程式导出

如果不想走 CLI,也可以纯脚本方式完成导出。核心技巧是在 ORTModelForXXX.from_pretrained 中传入 export=True——此时 Optimum 会在内存中完成 PyTorch 模型到 ONNX 的转换,再调用 save_pretrained 落盘:

>>> from optimum.onnxruntime import ORTModelForSequenceClassification
>>> from transformers import AutoTokenizer

>>> model_checkpoint = "distilbert_base_uncased_squad"
>>> save_directory = "onnx/"

>>> # 加载 transformers 模型并导出为 ONNX
>>> ort_model = ORTModelForSequenceClassification.from_pretrained(model_checkpoint, export=True)
>>> tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)

>>> # 保存 ONNX 模型与分词器
>>> ort_model.save_pretrained(save_directory)
>>> tokenizer.save_pretrained(save_directory)

该方式的好处是:导出逻辑可以嵌入训练流水线末尾、CI 脚本或模型发布流程中,与 Hub 上传步骤无缝衔接。保存目录中会包含 model.onnx(大权重模型还会伴随 .onnx_data 外部数据文件)以及分词器文件,后续部署只需拷贝整个目录。

四、深入仓库源码:内置 exporters 模块的 ONNX 导出机制

从源码结构看,当前仓库已将导出能力内置到 src/transformers/exporters/ 包中,提供 dynamotorch.export 产物)、onnxexecutorch 三种格式导出器,通过 AutoHfExporter 工厂统一分发(见 auto.py 中的 AUTO_EXPORTER_MAPPING)。这与文档中 Optimum"基于配置对象、可按架构扩展"的设计理念一脉相承,并且把扩展机制显式化:外部框架可以通过 register_exporter / register_export_config 注册自己的格式(见 auto.py)。

4.1 OnnxConfig:导出配置参数详解

OnnxConfig 是控制 ONNX 导出行为的配置类,定义在 configs.py,它继承自 DynamoConfigtorch.export 相关参数),完整参数及默认值如下表:

参数 类型 / 默认值 说明
output_path str / None .onnx 文件的输出路径。为 None 时导出的模型仅以内存中的 ONNXProgram 存在,不写磁盘
dynamic bool / False 是否以动态(符号化)形状导出。为 True 且未显式指定 dynamic_shapes 时,所有张量维度自动标记为 Dim.AUTO
strict bool / False 是否启用 torch.export 严格模式:执行完整符号化追踪、捕获更多错误,但更慢且复杂模型更易失败
dynamic_shapes dict / None 显式的逐输入动态形状规格,优先级高于 dynamic
opset_version int / None 目标 ONNX opset 版本;缺省时使用已安装 onnxscript 支持的最新 opset
external_data bool / True 大型权重张量是否存入独立的 .onnx_data 伴随文件而非内嵌 protobuf——权重超过 2GB protobuf 限制时必须启用
optimize bool / True 导出后是否运行 onnxscript 优化通道(常量折叠、死代码消除等);命中上游优化器 bug 时可关闭
export_params bool / True 是否把模型权重内嵌进 ONNX 图;设为 False 可导出无权重图(权重需运行时提供)
keep_initializers_as_inputs bool / False 是否把权重 initializer 暴露为显式图输入,部分 opset < 9 的旧运行时需要

OnnxConfig 还支持 to_dict() / from_dict() 往返序列化(见 ExportConfigMixin),export_format 字段保证从字典反序列化时能正确定位到 OnnxConfig 子类,这让"导出配方"可以被持久化、随模型一起分发。

4.2 导出流水线:五阶段转换机制

OnnxExporterexporter_onnx.py)继承自 DynamoExporter,先经 torch.export 得到 ExportedProgram,再通过 torch.onnx.export 转为 ONNX。文件头部的模块 docstring 完整描述了 ONNX 专属的五阶段转换机制:

  1. Torch 补丁:通过 apply_patches("onnx") 可逆地 monkey-patch torch 算子(如 torch.wheretorch.histctorch.roll 等数十处,见 exporter_onnx.py),让追踪期产生可被 ONNX 下放的算子模式;补丁在上下文退出时自动还原;
  2. ONNX 内部钩子:钩住 torch.onnx 私有入口 _prepare_exported_program_for_export,确保第 3 阶段的 FX 节点修复在 run_decompositions 之后再次执行(注意源码中以警告标注:该钩子针对私有 API,PyTorch 升级后可能需要迁移钩点,见 exporter_onnx.py);
  3. FX 节点修复:对 GraphModule 做逐节点原地重写,剔除或替换 ONNX 无法下放的节点(别名节点、原地操作、死比较、_assert_* 断言等,见 exporter_onnx.py);
  4. ONNX 自定义翻译表:以 onnxscript 函数覆盖 torchlib 默认 lowering 中缺失或有 bug 的算子,当前覆盖 bincountindex_put_grouped_mmrepeat_interleavemasked_fill、整型 floordiv 等(见 _ONNX_TRANSLATION_TABLE);
  5. ONNX IR 修复:导出后对 ONNXProgram IR 做原地修复以保证 ORT 兼容性,例如给 TopK 节点强制 sorted=1(ORT CUDA EP 的要求,见 exporter_onnx.py)。

父类 DynamoExporter 的流水线(见 exporter_dynamo.py)还包含五个前置环节:扁平化 forward 签名、模型级可导出补丁、Cache 对象的 Pytree 注册、Dim.AUTO 动态形状自动推断、以及模型状态清理(防止 eager forward 的状态泄漏进追踪)。

OnnxExporter.export 的调用链可以概括为:

# 来自 OnnxExporter.export 的核心调用(src/transformers/exporters/exporter_onnx.py 第 116-134 行)
with patch_model_outputs(model) as (inputs_names, outputs_names), apply_patches("onnx"):
    exported_program = super().export(model, sample_inputs, config=config)   # torch.export
    inputs_names, outputs_names = disambiguate_io_names(inputs_names, outputs_names)
    apply_fx_node_fixes("onnx", exported_program.graph_module)
    onnx_program = torch.onnx.export(
        exported_program, args=(), f=config.output_path,
        input_names=inputs_names, output_names=outputs_names,
        kwargs=copy.deepcopy(dict(sample_inputs)),
        custom_translation_table=_ONNX_TRANSLATION_TABLE,
        opset_version=config.opset_version,
        external_data=config.external_data,
        export_params=config.export_params,
        optimize=config.optimize,
    )
apply_onnx_ir_fixes(onnx_program)

可以看到,OnnxConfig 中的 opset_versionexternal_dataexport_paramsoptimize 等参数正是在这里逐项传入 torch.onnx.export,配置对象与底层行为是一一对应的。此外 patch_model_outputs 会把模型 forward 包装为返回扁平字典的形式并采集输入/输出张量名,disambiguate_io_names 则在输入输出重名时自动加 input. / output. 前缀消歧(见 exporter_onnx.py)。

官方用法示例(来自 OnnxExporter 类 docstring):

>>> from transformers.exporters.exporter_onnx import OnnxExporter
>>> from transformers.exporters import OnnxConfig

>>> exporter = OnnxExporter()
>>> onnx_program = exporter.export(model, inputs, config=OnnxConfig(dynamic=True))
>>> outputs = onnx_program(**inputs)  # 内存中直接运行
>>> exporter.export(model, inputs, config=OnnxConfig(output_path="model.onnx"))  # 保存到磁盘

相关的导出行为由 tests/exporters/test_export.pytests/exporters/test_runtime.py 中的测试用例覆盖,可以作为验证导出正确性的参照。

五、导出未支持架构的模型:如何贡献

如果你希望为当前无法导出的模型添加支持:先确认目标架构是否已在导出器(Optimum 的 optimum.exporters.onnx,或本仓库 transformers.exporters 的注册表)中有配置对象;如果没有,可以直接向 Optimum 仓库贡献。从本仓库源码看,扩展点相当清晰:

  • 新格式:继承 HfExporter 后用 register_exporter(name) 注册到 AUTO_EXPORTER_MAPPING
  • 新配置:继承 ExportConfigMixin 后用 register_export_config(name) 注册(见 auto.py);
  • ONNX 算子兼容问题:按需添加 @register_patch("onnx", ...) 补丁、@register_fx_node_fix("onnx") 节点修复,或向 _ONNX_TRANSLATION_TABLE 增加自定义翻译函数。

AutoHfExporter.from_pretrained(加载"随模型发布的导出配方")目前尚未实现,源码中明确标注为占位接口,预留了模型所有者在 Hub 上发布 export_config.json 的工作流(见 auto.py),可以推断这一机制将让导出配置像 config.json 一样随检查点分发。

六、总结:选择哪种导出路径

场景 推荐路径 要点
快速把 Hub 模型转成可部署的 ONNX optimum-cli export onnx 自动完成导出 + 数值校验;本地模型需同目录放权重与分词器,并用 --task 指定任务头
嵌入训练/发布流水线 ORTModelForXXX.from_pretrained(..., export=True) 脚本化,save_pretrained 落盘
需要精细控制 opset / 动态形状 / 大权重外置 仓库内置 OnnxExporter + OnnxConfig 参数与 torch.onnx.export 一一对应,external_data=True 规避 2GB 限制

三条路径的共同出口是同一个产物:一个与参考模型数值一致(atol: 0.0001 校验通过)的 model.onnx(大模型伴生 .onnx_data),可被 ONNX Runtime 等任何 ONNX 运行时加载,并进一步做图优化与量化。文档中指向的 TFLite 导出流程属于同一 exporters 体系下的另一格式,可按相同思路查阅对应指南。

登录后查看全文
热门项目推荐
相关项目推荐