Transformers documentation

导出器

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

导出器

将任意 PreTrainedModel 导出为 ONNX、ExecuTorch 或独立的 PyTorch 程序,无论目标运行时为何。

exporter = DynamoExporter()  # or OnnxExporter, ExecutorchExporter
config = DynamoConfig(dynamic=True)
exported = exporter.export(model, inputs, config=config)

导出器位于 Transformers 内部,而非下游库中,因此,只要架构变更、新的注意力模式或自定义缓存类型在建模代码中落地,导出时即可立即获得支持。

这些导出器目前处于实验阶段。本模块中的许多补丁用于规避特定的上游缺陷(Torch、ONNX Script、ONNX Runtime、ExecuTorch),一旦上游修复落地,这些补丁将被移除。在 API 稳定之前,请将补丁视为与测试套件中使用的版本绑定。在生产工具中固定这些版本,并预期随着上游变更的落地,会出现新的补丁,同时旧的补丁会消失。

导出器程序 (.program)已保存为运行时
DynamoExporterExportedProgrammodel.pt2任意 PyTorch 运行时,AOT 编译
OnnxExporteronnx_ir.Modelmodel.onnx任何 ONNX 运行时(ORT、TensorRT、OpenVINO)
ExecutorchExporterExecutorchProgramManagermodel.pte移动和边缘设备(ExecuTorch)
OpenVINOExporteropenvino.Modelopenvino_model.xml + .binOpenVINO 运行时(Intel CPU/GPU/NPU)

每次导出都会返回一个 ExportedModel,其 components 将名称映射到 ExportedComponent。组件包含后端的程序(.program)以及在跟踪过程中记录的 ExportMetadata(输入和输出名称、精度、跟踪的输入形状、导出配置)。~HfExporter.export 生成单个 "model" 组件,因此其程序直接为 exported.program。~ExportedModel.save_pretrained 会为每个组件写入一个文件、一个包含每个组件的文件和元数据的 export.json 清单,以及模型配置。

~ExportedModel.from_pretrained 会从本地目录或 Hub 仓库中将其加载回来,并支持与 from_pretrained() 相同的下载 kwargs。如果导出文件与模型权重保存在同一位置,请传入 subfolder;此时仅会下载导出文件本身。Dynamo、ONNX 和 OpenVINO 程序会按照导出时返回的类型进行加载。加载后的 ONNX 图会将其权重保留在 model.onnx.data 中,直到被读取。ExecuTorch .pte 会作为 executorch.runtime.Program 加载,因为无法从该文件重建 ExecutorchProgramManager。

from transformers.exporters import ExportedModel

exported.program  # the backend's program
exported.save_pretrained("exported-model")  # model.<suffix>, export.json, config.json
loaded = ExportedModel.from_pretrained("exported-model")

一个目录仅包含一个导出,因为 export.json 和 config.json 的文件名是固定的。请将不同格式的导出文件分别保存在各自的子文件夹中(onnx/、openvino/),并使用 subfolder="onnx" 进行加载。

AutoHfExporter 根据配置选择正确的导出器,而 AutoExportConfig 则根据字典选择正确的配置类。两者都遵循 Transformers 中相同的自动类模式,这在后端是在运行时选择而非在调用点硬编码的情况下非常有用。

from transformers.exporters import AutoExportConfig, AutoHfExporter

export_config_dict = {"export_format": "onnx", "dynamic": True}
config = AutoExportConfig.from_dict(export_config_dict)
exporter = AutoHfExporter.from_config(config)

exported = exporter.export(model, inputs, config=config)

安装

安装您计划导出的后端所需的依赖项。

以下列出的版本是导出器测试套件所固定的版本。较新或较旧的版本通常也能正常工作,但导出器的补丁针对的是特定的 API 接口,因此在生产环境中应固定这些版本,并预期 HfExporter 在检测到版本偏差时记录警告。

Dynamo
ONNX
ExecuTorch XNNPACK
ExecuTorch MLX
OpenVINO
pip install transformers "torch==2.12.0"

导出模型

所有导出器共享相同的接口。使用配置创建一个导出器,然后调用 export()。

通过替换导出器类来切换运行时。

Dynamo
ONNX
ExecuTorch
OpenVINO
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import DynamoExporter, DynamoConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer("Hello, world!", return_tensors="pt")

exporter = DynamoExporter()
config = DynamoConfig(dynamic=True)
exported = exporter.export(model, inputs, config=config)

# run the exported graph directly
outputs = exported(**inputs)

运行导出

ExportedModel 会在其后端运行时(torch.export 模块、ONNX Runtime、OpenVINO 或 ExecuTorch 运行时)上执行导出,并且其调用方式与原始模型一致:相同的 kwargs,相同的 ModelOutput 类。计算在后端中运行,这通常比 eager PyTorch 更快(例如 CPU 上的 OpenVINO,CUDA 上的 ONNX Runtime),量化导出的运行方式也相同。由于导出时未追踪的输入会被丢弃,因此可以直接传入处理器的完整输出。

首次调用时会执行一次全新的导出(在默认后端设备上,或在~ExportedModel.to所选的设备上),而ExportedModel.from_pretrained则从本地目录或 Hub 加载已保存的导出模型,使其随时可运行。

from transformers.exporters import ExportedModel

outputs = exported(**inputs)

exported.save_pretrained("qwen3-onnx")
model = ExportedModel.from_pretrained("qwen3-onnx", device="cuda")
logits = model(**inputs).logits

device 决定运行位置:ONNX Runtime 提供程序("cpu"、"cuda")、OpenVINO 设备("cpu"、用于 Intel GPU 的 "xpu",或默认的 AUTO),或 torch.export 模块被移动到的位置。ExecuTorch 程序在其被 lower 的目标位置运行。包含多个组件(生成)的导出目前尚不支持运行。

动态形状

传入 dynamic=True 会将每个张量维度标记为动态,从而使导出的图在运行时能够接受任意大小的输入,而无需重新追踪。

如需对哪些维度是动态的进行细粒度控制,请改为传入显式的 dynamic_shapes,它会直接转发给 torch.export.export。

Dynamo
ONNX
ExecuTorch
OpenVINO
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import DynamoExporter, DynamoConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer(["Hello, world!", "Hi"], padding=True, return_tensors="pt")

batch = torch.export.Dim("batch", min=1, max=32)
seq = torch.export.Dim("seq", min=1, max=2048)

exporter = DynamoExporter()
config = DynamoConfig(
    dynamic_shapes={"input_ids": {0: batch, 1: seq}, "attention_mask": {0: batch, 1: seq}},
    # Emit data-dependent shape guards as runtime asserts instead of failing the export when a
    # guard wouldn't hold across the explicit symbolic range. Most LLMs need this under fine-grained
    # ``Dim(min=, max=)`` bounds. Not needed with ``dynamic=True`` / ``Dim.AUTO``, where torch.export
    # infers shape relations instead of verifying them against user-stated bounds.
    prefer_deferred_runtime_asserts_over_guards=True,
)
exported = exporter.export(model, inputs, config=config)

生成式模型

对于自回归生成,模型的 forward 在预填充阶段(完整提示词,无 KV 缓存)和解码阶段(单个 token,已填充的 KV 缓存)具有不同的形状。导出器暴露了 ~HfExporter.export_for_generation,该方法会拆分这两个阶段并分别导出。

对于多模态生成模型,预填充(prefill)还会进一步拆分为图像或音频编码器、语言模型以及 lm_head。编码器和语言模型的发现使用 get_encoder()(modality="image" 或 "audio")和 get_decoder() 访问器,因此任何使用这些组件的新架构都可以开箱即用。

projector 组件仅在模型通过某个属性名(multi_modal_projector、connector、embed_vision、embed_audio)暴露该组件时才会出现。下方的 Qwen2-VL 将其 projector 整合进了视觉塔,因此其组件字典中没有单独的 multi_modal_projector 键。新的架构必须将其 projector 属性对齐到这些名称之一,而不是不断扩充该列表。

Dynamo
ONNX
ExecuTorch
OpenVINO
from transformers import AutoModelForImageTextToText, AutoProcessor
from transformers.exporters import DynamoExporter, DynamoConfig

model = AutoModelForImageTextToText.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
messages = [{"role": "user", "content": [{"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"}, {"type": "text", "text": "Describe this image."}]}]
text = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = processor(text=text, images=messages[0]["content"][0]["url"], return_tensors="pt").to(model.device)

exporter = DynamoExporter()
config = DynamoConfig(dynamic=True)
exported = exporter.export_for_generation(model, inputs, config=config)
# exported: ExportedModel with "image_encoder", "language_model", "lm_head" and "decode",
# each an ExportedComponent whose `.program` is an ExportedProgram

导出的组件是独立的图,而非可直接运行的推理流水线。调用方负责运行每个编码器、投影嵌入以及编排生成循环。

export_for_generation 的工作原理

decompose_for_generation() 会执行一次 model.generate(**inputs, max_new_tokens=2),并挂钩 model.forward 以捕获真实的 prefill 和 decode kwargs(如果模型是多模态的,还会通过在每个编码器/投影器/语言模型上设置钩子来捕获各子模块的 kwargs)。正因如此,它适用于任何架构,包括仅解码器、SSM、编码器-解码器以及多模态模型,且无需针对每个模型编写额外的胶水代码。export_for_generation 只是基于它的一行封装。

捕获过程会以急切模式在 inputs 上运行模型,因此请传入较小但具有代表性的值,例如简短的提示词、单张小图片或几帧音频。导出的程序并不绑定于这些尺寸(动态形状仍然可以正常传递),但较小的捕获输入会使 decompose_for_generation 的开销更低,并保持符号形状推断的可处理性。

直接调用 decompose_for_generation 以在分解和导出之间执行操作,例如运行 eager 前向传播进行验证、替换子模块的输入,或跳过某个阶段。

from transformers.exporters.utils import decompose_for_generation

components = decompose_for_generation(model, inputs)
# {"image_encoder": (submodel, fwd_kwargs), "language_model": (...), ..., "decode": (...)}

exported = {}
for name, (submodel, subinputs) in components.items():
    eager_outputs = submodel(**subinputs)  # sanity-check the eager forward before exporting
    exported[name] = exporter.export(submodel, subinputs, config=config)

多令牌解码

默认情况下,decode 组件是一个 单 token 步骤——即针对 KV 缓存的一个查询 token——因此 torch.export 会将其查询序列轴特化为 1。传入 multi_token_decode=True 可将 decode 捕获为 多 token 解码:decompose_for_generation() 会将两个连续的解码步骤(以 max_new_tokens=3 进行捕获)合并为一次前向传播,从而使该轴保持符号化。这样,单个图即可服务于任意查询长度——单 token(常规解码)、一次处理多个 token(从过去继续,例如接受一批投机 token),以及在缓存为空时的普通 prefill。

Dynamo
ONNX
ExecuTorch
from transformers.exporters import DynamoExporter, DynamoConfig

exporter = DynamoExporter()
config = DynamoConfig(dynamic=True)
exported = exporter.export_for_generation(model, inputs, config=config, multi_token_decode=True)
# exported.components["decode"] now accepts a variable number of query tokens

查询轴仅在动态形状导出(dynamic=True)下保持符号化;静态导出会将其固定为捕获时的长度,从而生成一个固定的多 token 图。它与下文中的静态 KV 缓存相组合——合并后的解码过程会将每一步的 token 就地写入固定大小的缓存中,而缓存内部会处理这些 token 的落位。

静态 KV 缓存

本部分中的 ExecuTorch 示例(包括零拷贝更新和解码循环)仅支持 XNNPACK。MLX 导出器会拒绝 StaticCache;在 MLX 中请使用 DynamicCache。

generate() 默认会构建一个 DynamicCache,并随着序列的延长而重新分配内存——这对于导出的计算图而言是一个不断变化的目标。静态缓存则是一个固定大小的缓冲区,仅分配一次,并在每一步中将数据就地写入当前位置。结合 多 token 解码,可以将生成过程收敛为单个导出的计算图:decode 图接收一个固定大小的缓存和 可变数量的查询 token,因此同一个图既能处理提示词(空缓存 → prefill),也能处理每个生成的 token(已填充的缓存 → decode)。通过传递一个包含 cache_implementation="static"(以及 max_cache_len)的 GenerationConfig,并设置 multi_token_decode=True,即可导出该图:

Dynamo
ONNX
ExecuTorch XNNPACK
from transformers import GenerationConfig
from transformers.exporters import DynamoExporter, DynamoConfig

exporter = DynamoExporter()
gen_config = GenerationConfig(cache_implementation="static", max_cache_len=2048)
exported = exporter.export_for_generation(
    model, inputs, config=DynamoConfig(dynamic=True), generation_config=gen_config, multi_token_decode=True
)
exported.save_pretrained("qwen3-static")

decode 图现在包含两个符号轴——查询长度(即你输入的 token 数量)和缓存长度(max_cache_len,可在加载时调整)。dynamic=True 将这些轴(以及其他所有轴)标记为 Dim.AUTO,因此导出的图在加载时可接受任意提示长度和缓存大小。

零拷贝就地更新

静态缓存被传入并就地修改,因此一个缓冲区即可在解码步骤之间携带状态,无需主机端拷贝——前提是运行时绑定调用方的缓冲区,而不是通过其自身的内存区域进行拷贝。为此所需的工作是各后端中唯一剩余的部分:

  • Dynamo — 导出的程序将缓存写入建模为 USER_INPUT_MUTATION,因此调用 exported.runners["decode"](...) 会直接更新您传入的缓存张量。每一步都复用相同的张量即可,无需任何配置。

  • ONNX Runtime — 解码图将缓存暴露为配对的 input.<name> / output.<name>。ORT 的 CudaSession.set_buffer_sharing(onnxruntime.transformers.io_binding_helper)将每一对绑定到同一个设备缓冲区,因此缓存可以在循环中就地读取和更新,无需经过主机往返。

  • ExecuTorch (XNNPACK) — 在 ExecutorchConfig 中关闭内存规划分配,以便就地写入可以落在调用者自己的张量中(请参阅参考文档了解每个标志的作用):

    config = ExecutorchConfig(
        backend="xnnpack",
        dynamic=True,
        alloc_graph_input=False,
        alloc_graph_output=False,
        alloc_mutable_buffers=False,
    )

    零拷贝的原地写入同样要求调用方在运行时通过 Method::set_output_data_ptr 绑定输出缓冲区——Python 运行时并未暴露此功能(executorch.runtime.Method 仅暴露 execute/set_inputs/get_outputs)。上述标志位完成了相关配置,但原地写入是一条 仅限 C++ 的路径(参见下文 ExecuTorch 解码循环示例)。在 Python 中,需在每一步从方法输出中读取更新后的缓存。

Decode-loop inference examples

该循环在所有后端上都具有相同的结构——整个过程中都是相同的图。从一个空的固定大小缓存开始,一次性输入整个提示词(空缓存 → prefill),然后逐个生成 token(已填充的缓存 → decode)。每次调用都会传入 input_ids、因果 attention_mask 以及 position_ids(每一步根据新增 token 的数量进行递增),外加缓存,并返回每个查询位置的 logits。每个 token 在缓存中的位置由静态缓存内部跟踪,因此无需在调用中额外传递任何信息。缓存的初始化方式因运行时而异(Dynamo 使用 StaticCache 对象,ONNX Runtime 使用原始设备缓冲区,ExecuTorch 在 C++ 中使用调用方数组),因此每个选项卡下方都会构建自己的实现。Dynamo 和 ONNX Runtime 选项卡会就地更新缓存;ExecuTorch 的就地更新路径是 C++ 实现的(如上所述,其 Python 运行时无法做到这一点)。

Dynamo
ONNX Runtime
ExecuTorch XNNPACK

torch.export 将静态缓存的写入记录为 USER_INPUT_MUTATION,因此 decode 运行器会直接更新您在 直接 传入的 StaticCache —— 一个缓存即可在整个循环中承载状态,无需进行任何绑定或回传。缓存必须 预先初始化(torch.export 会将已分配的 K/V 固化到输入规范中,因此延迟初始化的空缓存无法匹配)—— 但保存的程序自带 example_inputs,因此请复用该已初始化的 StaticCache 模板,并将其重置为空:

import copy
import torch
from transformers.exporters import ExportedModel

exported = ExportedModel.from_pretrained("qwen3-static", device="cuda")
decode = exported.runners["decode"]

# the program carries an initialized StaticCache template — reuse it (reset to empty)
_, example_kwargs = exported.components["decode"].program.example_inputs
past_key_values = copy.deepcopy(example_kwargs["past_key_values"])
past_key_values.reset()

def causal_mask(positions, cache_len):   # [1, 1, len(positions), cache_len]
    return (torch.arange(cache_len, device="cuda")[None, :] <= positions[:, None])[None, None]

# prefill: the whole prompt in one call
positions = torch.arange(prompt_len, device="cuda")
logits = decode(input_ids=prompt_ids, attention_mask=causal_mask(positions, max_cache_len),
                position_ids=positions[None], past_key_values=past_key_values).logits
next_token = logits[:, -1:].argmax(-1)

# decode: query=1 buffers reused in place
input_ids = torch.empty((1, 1), dtype=torch.long, device="cuda")
position_ids = torch.empty((1, 1), dtype=torch.long, device="cuda")
attention_mask = torch.empty((1, 1, 1, max_cache_len), dtype=torch.bool, device="cuda")
slots = torch.arange(max_cache_len, device="cuda")
for position in range(prompt_len, max_cache_len):
    input_ids.copy_(next_token)
    position_ids.fill_(position)
    attention_mask[0, 0, 0].copy_(slots <= position)
    logits = decode(input_ids=input_ids, attention_mask=attention_mask,
                    position_ids=position_ids, past_key_values=past_key_values).logits
    next_token = logits[:, -1:].argmax(-1)

ONNX Runtime 融合

将 ort_fusions 设置为在保存之前,将 ONNX 导出重写为 ONNX Runtime 的融合 Transformer 算子。ONNX Runtime 运行融合后的图比运行普通图快得多。这些融合涵盖了 RMS 和层归一化、注意力机制(MultiHeadAttention,或者当存在键值缓存时为 GroupQueryAttention)、旋转位置嵌入、GELU,以及共享输入的投影。每个融合算子产生的结果与其所替换的算子相同。

将 ort_fusions 设置为图将运行的执行提供程序。每个提供程序都会获得其支持的融合算子。

config = OnnxConfig(ort_fusions="cpu")
config = OnnxConfig(ort_fusions="cuda")
config = OnnxConfig(ort_fusions="webgpu")
config = OnnxConfig(ort_fusions="dml")
"cpu""cuda""webgpu""dml"
使用键值缓存的注意力GroupQueryAttention,包含旋转带有查询和键归一化的GroupQueryAttention;预填充阶段目前仍保留MultiHeadAttention(速度较慢)GroupQueryAttention,将旋转位置编码作为独立的 RotaryEmbeddingMultiHeadAttention,将旋转位置编码作为独立的RotaryEmbedding
其他注意力目前使用普通操作(较慢)MultiHeadAttentionMultiHeadAttentionMultiHeadAttention
专家混合未融合MoE未融合未融合
CUDA 图—静态导出可以重放——

带有 MoE 的 "cuda" 图无法在 CPU 上运行,因为 ONNX Runtime 的 MoE 仅支持 CUDA。带有 "webgpu" 的图可以通过 onnxruntime-web 在浏览器中运行,也可以通过 onnxruntime-webgpu 原生运行。带有 "dml" 的图可以在 Windows 上通过 onnxruntime-directml 运行。

融合算子并非标准 ONNX,因此其他 ONNX 使用者(如 TensorRT 或 OpenVINO)无法读取它们。默认值为 ort_fusions=None,这会保持图的标准性。如果需要在多个运行时上使用模型,请为每个运行时分别导出一次。

这些融合操作在 OnnxRuntimeQuantizer 对图进行量化之前执行,这是 ONNX Runtime 所推荐的。如果模型在导出前已经量化,则这些融合操作会被跳过,并且它们需要 optimize=True(默认值)。"cuda" 融合操作需要 onnxruntime 1.28 或更高版本。

量化

每个导出配置都接受一个 quantizer,用于在导出过程中对模型进行量化,量化对象可以是 torch.export FX 图(适用于所有后端的 PT2E 或 NNCF),也可以是转换后的 ONNX 或 OpenVINO 模型(适用于 ONNX Runtime 或 NNCF)。有关量化器、其支持的后端、校准方法以及如何添加量化器,请参阅 量化导出。

局限性与变通方案

torch.export、torch.onnx.export 和 ExecuTorch 在处理某些特定的 PyTorch 模式时都存在一些不足。导出器通过在导出流程中明确定义的节点应用一组可逆的补丁和 FX 级别的修复来规避这些问题。这些细节在公开的 export API 中是不可见的,但以下是需要了解的最常见事项:

  • FlashAttention 和 FlexAttention 无法在任何后端上导出。sdpa 是首选设置,eager 也可以使用(速度较慢)。如果模型当前使用的是其他设置,请在调用 export 之前将其设置为其中之一。
  • grouped_mm 可以顺利被 DynamoExporter 追踪,并且会被 OnnxExporter 自动转换。 对于使用 XNNPACK 或 MLX 的 ExecutorchExporter,导出器会在导出前将 MoE 专家替换为 batched_mm。
  • 滑动窗口层(Mistral、Gemma 3)导出时带有完整的键值缓存;滑动掩码仍会应用窗口。

后续步骤

  • 在 扩展导出器 中,通过补丁和修复注册表,为新架构或后端添加导出支持。
  • 在量化导出中量化导出或添加量化器。
Update on GitHub