Transformers documentation

Экспортеры

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
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 рассматривайте патчи как привязанные к версиям, используемым в тестовом наборе. Зафиксируйте эти версии в производственных инструментах и ожидайте, что по мере поступления изменений в исходных проектах будут появляться новые патчи, а старые исчезать.

ЭкспортерВыводВремя выполнения
DynamoExporterExportedProgramЛюбой рантайм PyTorch, AOT-компиляция
OnnxExporterONNXProgramЛюбой ONNX runtime (ORT, TensorRT, OpenVINO)
ExecutorchExporterExecutorchProgramManagerМобильные и периферийные устройства (ExecuTorch)
OpenVINOExporteropenvino.ModelOpenVINO runtime (Intel CPU/GPU/NPU)

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)

onnx_program = 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.module()(**inputs)

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

Передача 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)
components = exporter.export_for_generation(model, inputs, config=config)
# components = {"image_encoder": ExportedProgram, "language_model": ExportedProgram, "lm_head": ExportedProgram, "decode": 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)
components = exporter.export_for_generation(model, inputs, config=config, multi_token_decode=True)
# 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)
components = exporter.export_for_generation(
    model, inputs, config=DynamoConfig(dynamic=True), generation_config=gen_config, multi_token_decode=True
)

Граф decode теперь имеет две символические оси — длину запроса (количество токенов, которые вы передаёте) и длину кэша (max_cache_len, изменяемую при загрузке). dynamic=True помечает эти (и все остальные оси) как Dim.AUTO, поэтому экспортированный граф принимает любую длину промпта и размер кэша при загрузке.

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

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

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

  • 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, поэтому module() загруженного графа обновляет StaticCache, который вы передаёте напрямую — один кэш несёт состояние на протяжении всего цикла, и ничего не нужно привязывать или возвращать обратно. register_pytree_node(StaticCache) позволяет torch.export.load развернуть вход StaticCache. Кэш должен быть инициализирован заранее (torch.export встраивает выделенные K/V в спецификацию входа, поэтому ленивый пустой кэш не совпадёт) — но сохранённая программа содержит собственные example_inputs, поэтому переиспользуйте этот уже инициализированный шаблон StaticCache, сбросив его в пустое состояние:

import copy
import torch
from transformers import StaticCache
from transformers.exporters.exporter_dynamo import register_pytree_node

register_pytree_node(StaticCache)
exported = torch.export.load("decode.pt2")
decode = exported.module()   # runs on the device its inputs / cache live on (CUDA here)

# the artifact carries an initialized StaticCache template — reuse it (reset to empty)
_, example_kwargs = exported.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)

Квантование

Каждая конфигурация экспорта принимает 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