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 runtime (ORT, TensorRT, OpenVINO)
ExecutorchExporterExecutorchProgramManagermodel.pteМобильные и периферийные устройства (ExecuTorch)
OpenVINOExporteropenvino.Modelopenvino_model.xml + .binOpenVINO runtime (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(). Укажите 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 (OpenVINO на CPU, ONNX Runtime на CUDA), а квантованные экспорты работают аналогичным образом. Входы, с которыми экспорт не был прослежен, отбрасываются, поэтому можно передать весь вывод процессора.

Свежий экспорт выполняется при первом вызове (на устройстве по умолчанию для бэкенда или на том, которое выбирает ~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", "xpu" для GPU Intel или AUTO по умолчанию) или место, куда перемещается модуль torch.export. Программа ExecuTorch выполняется там, для чего она была опущена (lowered). Экспорты с несколькими компонентами (генерация) пока не поддерживаются.

Динамические формы

Передача 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 разные формы на шаге prefill (полный промпт, без KV-кэша) и на шаге decode (один токен, заполненный KV-кэш). Экспортеры предоставляют ~HfExporter.export_for_generation, который разделяет оба этапа и экспортирует каждый из них.

Для мультимодальных генеративных моделей префилл дополнительно разделяется на энкодер изображений или аудио, языковую модель и lm_head. Обнаружение энкодера и языковой модели выполняется с помощью аксессоров get_encoder() (modality="image" или "audio") и get_decoder(), поэтому любая новая архитектура, использующая их, работает без дополнительной настройки.

Компонент проектора появляется только в том случае, если модель предоставляет его под одним из следующих имён атрибутов: multi_modal_projector, connector, embed_vision, embed_audio. В модели Qwen2-VL, приведённой ниже, проектор объединён с визуальной башней, поэтому в её словаре компонентов нет отдельного ключа multi_modal_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, чтобы перехватить фактические аргументы kwargs для prefill и decode (а также аргументы kwargs для каждого подмодуля через хуки на каждом энкодере/проекторе/языковой модели, если модель мультимодальная). Именно поэтому метод работает с любой архитектурой, включая модели только с декодером, SSM, encoder-decoder и мультимодальные модели, без необходимости писать отдельный код для каждой модели. export_for_generation представляет собой однострочную обёртку над этим методом.

Захват выполняет модель в режиме eager на 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 представляет собой шаг single-token — один токен запроса против KV-кэша — поэтому torch.export специализирует ось последовательности запросов на значение 1. Передайте multi_token_decode=True, чтобы захватить decode как multi-token декодирование: decompose_for_generation() объединяет два последовательных шага декодирования (захват выполняется с max_new_tokens=3) в один forward, благодаря чему эта ось остаётся символьной. Тогда один граф обслуживает любую длину запроса — один токен (обычное декодирование), несколько токенов одновременно (продолжение из прошлого, например, приём блока спекулятивных токенов) и обычный 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); при статическом экспорте она фиксируется на длине, зафиксированной при захвате, что приводит к графу с фиксированным количеством токенов. Это сочетается со статическим кэшем KV, описанным ниже: объединённый decode на каждом шаге записывает токены в кэш фиксированного размера на месте, а кэш самостоятельно определяет, куда они попадают.

Статический кэш KV

Примеры ExecuTorch в этом разделе, включая обновления без копирования и цикл декодирования, поддерживаются только в XNNPACK. Экспортировщик MLX отклоняет StaticCache; для MLX используйте DynamicCache.

generate() по умолчанию наращивает DynamicCache, перераспределяя память по мере расширения последовательности, — это движущаяся цель для экспортированного графа. Статический кэш представляет собой буфер фиксированного размера, выделяемый один раз и заполняемый на месте в текущей позиции на каждом шаге. В сочетании с декодированием нескольких токенов это сводит генерацию к одному экспортированному графу: граф decode принимает кэш фиксированного размера и переменное количество query-токенов, поэтому один граф обслуживает и промпт (пустой кэш → prefill), и каждый сгенерированный токен (заполненный кэш → decode). Экспортируйте его, передавая GenerationConfig с cache_implementation="static" (и max_cache_len) вместе с 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 теперь имеет две символические оси — длину запроса (количество токенов, которые вы передаёте) и длину кэша (max_cache_len, изменяемую при загрузке). dynamic=True помечает эти (и все остальные оси) как Dim.AUTO, поэтому экспортированный граф принимает любую длину промпта и размер кэша при загрузке.

Обновления на месте без копирования

Статический кэш передаётся и изменяется на месте, поэтому один буфер несёт состояние между шагами декодирования без копирования на хост — при условии, что рантайм привязывает буферы вызывающего кода, а не копирует их через собственную арену. Единственная оставшаяся часть, специфичная для каждого бэкенда, — это то, что для этого требуется:

  • Dynamo — экспортированная программа моделирует запись в кэш как USER_INPUT_MUTATION, поэтому вызов exported.runners["decode"](...) напрямую обновляет переданные вами тензоры кэша. Повторно используйте одни и те же тензоры на каждом шаге; настраивать ничего не нужно.

  • ONNX Runtime — граф декодирования представляет кэш в виде пар input.<name> / output.<name>. Метод CudaSession.set_buffer_sharing в ORT (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,
    )

    Запись на месте без копирования (zero-copy) также требует, чтобы вызывающая сторона привязывала выходные буферы во время выполнения через Method::set_output_data_ptr — это не доступно в Python-рантайме (executorch.runtime.Method предоставляет только execute/set_inputs/get_outputs). Флаги выше настраивают этот механизм, однако запись на месте — это путь, доступный только в C++ (см. пример цикла декодирования ExecuTorch ниже). Из Python обновлённый кэш следует читать обратно из выходов метода на каждом шаге.

Decode-loop inference examples

Цикл имеет одинаковую структуру на всех бэкендах — это один и тот же граф на протяжении всего процесса. Начните с пустого кэша фиксированного размера, передайте весь промпт один раз (пустой кэш → prefill), а затем по одному токену (заполненный кэш → decode). Каждый вызов принимает input_ids, каузальную attention_mask и position_ids (сдвигается на количество новых токенов на каждом шаге), а также кэш, и возвращает логиты для каждой позиции запроса. То, где каждый токен размещается в кэше, отслеживается внутри статического кэша, поэтому нет необходимости дополнительно передавать что-либо через вызов. Настройка кэша различается в зависимости от рантайма (объект StaticCache для Dynamo, сырые буферы устройства для ONNX Runtime, массивы вызывающей стороны на C++ для ExecuTorch), поэтому каждая вкладка ниже строит свой вариант. Вкладки 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 перед сохранением. ONNX Runtime выполняет слиянный граф значительно быстрее, чем обычный. Слияния охватывают RMS и layer norm, внимание (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, с включённым rotaryGroupQueryAttention с нормализацией запросов и ключей; на этапе prefill пока сохраняется MultiHeadAttention (медленнее)GroupQueryAttention, rotary в виде отдельного RotaryEmbeddingMultiHeadAttention, с отдельным RotaryEmbedding
Другие механизмы вниманияпока что обычные операции (медленнее)MultiHeadAttentionMultiHeadAttentionMultiHeadAttention
Смесь экспертовunfusedMoEunfusedunfused
CUDA-графы—статические экспорты можно воспроизвести——

Граф "cuda" с MoE не выполняется на CPU, поскольку MoE в ONNX Runtime существует только для 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, который квантует модель во время экспорта, на графе FX torch.export (PT2E или NNCF, для каждого бэкенда) или на сконвертированной модели ONNX или OpenVINO (ONNX Runtime или NNCF). См. Квантование экспортов для информации о квантизаторах, их бэкендах, калибровке и о том, как добавить один из них.

Ограничения и обходные решения

torch.export, torch.onnx.export и ExecuTorch имеют ряд ограничений, связанных с конкретными паттернами PyTorch. Экспортеры обходят эти проблемы с помощью небольшого набора обратимых патчей и исправлений на уровне FX, применяемых в определённых точках процесса экспорта. Ничего из этого не видно из публичного API export, но вот наиболее важные моменты, о которых стоит знать:

  • FlashAttention и FlexAttention не поддерживают экспорт ни на одном бэкенде. sdpa — рекомендуемый вариант, eager также работает (медленнее). Если модель использует другой вариант, установите один из них перед вызовом export.
  • grouped_mm корректно отслеживается через DynamoExporter и автоматически переводится для OnnxExporter. Для ExecutorchExporter с XNNPACK или MLX экспортер заменяет MoE-эксперты на batched_mm перед экспортом.
  • Слои со скользящим окном (Mistral, Gemma 3) экспортируются с полным кэшем ключей и значений; скользящая маска по-прежнему применяет окно.

Дальнейшие шаги

Update on GitHub