Transformers documentation

Exporters

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Exporters

대상 런타임과 관계없이 모든 PreTrainedModel을 ONNX, ExecuTorch 또는 독립형 PyTorch 프로그램으로 내보낼 수 있습니다.

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

exporter는 하위 라이브러리가 아닌 Transformers 내부에 위치하므로, modeling 코드에 반영되는 시점부터 아키텍처 변경, 새로운 attention 패턴, 사용자 정의 cache type이 export 시점에 지원됩니다.

exporter는 실험적입니다. 이 모듈의 패치 중 상당수는 특정 업스트림 버그(Torch, ONNX Script, ONNX Runtime, ExecuTorch)를 우회하기 위해 작성되었으며, 해당 버그가 업스트림에서 수정되는 대로 제거됩니다. API가 안정화될 때까지 패치는 테스트 스위트에서 사용된 버전과 묶여 있다고 간주하십시오. 프로덕션 도구에서는 해당 버전을 고정하고, 업스트림 변경 사항이 반영됨에 따라 새로운 패치가 추가되고 기존 패치가 제거될 수 있음을 예상하십시오.

Exporter출력런타임
DynamoExporterExportedProgram모든 PyTorch 런타임, AOT 컴파일
OnnxExporterONNXProgram모든 ONNX 런타임(ORT, TensorRT, OpenVINO)
ExecutorchExporterExecutorchProgramManager모바일 및 엣지 디바이스 (ExecuTorch)
OpenVINOExporteropenvino.ModelOpenVINO 런타임 (Intel CPU/GPU/NPU)

AutoHfExporter는 설정에서 올바른 exporter를 선택하고, AutoExportConfig는 dict에서 올바른 config 클래스를 선택합니다. 둘 다 Transformers의 auto-class 패턴을 따르며, 이는 백엔드가 호출 지점에서 하드코딩되는 대신 런타임에 선택될 때 유용합니다.

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)

설치

내보내려는 백엔드에 대한 종속성을 설치합니다.

아래 버전은 exporter 테스트 스위트가 고정된(pinned) 버전입니다. 더 최신이거나 오래된 릴리스도 종종 작동하지만, exporter 패치는 특정 API 표면을 대상으로 하므로 프로덕션 도구에서는 이 버전을 고정하고, HfExporter가 드리프트를 감지할 때 경고를 기록하도록 기대해야 합니다.

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

모델 내보내기

모든 exporter는 동일한 인터페이스를 공유합니다. config를 사용하여 exporter를 생성하고 export()를 호출합니다.

exporter 클래스를 교체하여 런타임 간 전환을 수행합니다.

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를 전달하면 모든 텐서 차원이 동적으로 표시되어, 재추적 없이 런타임에 임의 크기의 입력을 받는 내보낸 그래프를 생성합니다.

어떤 차원이 동적인지에 대한 세밀한 제어를 위해, torch.export.export로 직접 전달되는 명시적인 dynamic_shapes를 대신 전달하세요.

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 캐시 있음)에서 서로 다른 형태를 가집니다. Exporter는 ~HfExporter.export_for_generation을 노출하며, 이 메서드는 두 단계를 분리하여 각각 내보냅니다.

멀티모달 생성 모델의 경우, prefill은 이미지 또는 오디오 인코더, 언어 모델, lm_head로 추가로 분리됩니다. 인코더와 언어 모델 탐색에는 get_encoder()(modality="image" 또는 "audio") 및 get_decoder() 접근자가 사용되므로, 이러한 접근자를 사용하는 새로운 아키텍처는 추가 설정 없이 바로 작동합니다.

모델이 특정 속성 이름(multi_modal_projector, connector, embed_vision, embed_audio)으로 projector를 노출할 때만 projector 구성 요소가 표시됩니다. 아래 Qwen2-VL은 projector를 vision tower에 통합하므로, 해당 구성 요소 사전에 별도의 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)
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에 훅을 걸어 실제 prefill 및 decode kwargs(모델이 멀티모달인 경우 각 encoder/projector/language model에 대한 훅을 통해 서브모듈별 kwargs 포함)를 캡처합니다. 이 때문에 decoder-only, SSM, encoder-decoder, 멀티모달 모델을 포함한 모든 아키텍처에서 모델별 추가 코딩 없이 작동합니다. export_for_generation는 이를 기반으로 한 한 줄 함수입니다.

캡처는 inputs에 대해 모델을 즉시(eagerly) 실행하므로, 짧은 프롬프트, 작은 단일 이미지, 또는 몇 프레임의 오디오와 같이 작지만 대표성을 갖는 값을 전달하세요. 내보낸 프로그램은 해당 크기에 고정되지 않습니다(동적 shape가 여전히 흐름을 유지하지만), 더 작은 캡처 입력은 decompose_for_generation의 비용을 낮추고 심볼릭 shape 추론을 관리 가능하게 유지합니다.

decompose_for_generation을 직접 호출하여 분해와 내보내기 사이에 동작을 수행할 수 있습니다. 예를 들어, 검증을 위해 eager forward를 실행하거나, 서브모듈의 입력을 교체하거나, 특정 단계를 건너뛰는 등의 작업을 수행할 수 있습니다.

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 컴포넌트는 단일 토큰 단계입니다. 즉, KV 캐시를 대상으로 하나의 쿼리 토큰을 처리하므로 torch.export는 쿼리 시퀀스 축을 1로 특수화합니다. multi_token_decode=True를 전달하면 decode를 멀티 토큰 디코딩으로 캡처합니다. decompose_for_generation()는 두 개의 연속된 디코딩 단계(max_new_tokens=3로 캡처)를 하나의 포워드로 병합하여 해당 축이 심볼릭 상태로 유지되도록 합니다. 이렇게 하나의 그래프가 모든 쿼리 길이를 처리할 수 있습니다. 하나의 토큰(일반 디코딩), 여러 토큰을 한 번에 처리(예: 스펙ulative 토큰 청크를 수락하는 과거로부터의 연속 처리), 그리고 캐시가 비어 있는 경우의 일반 프리필입니다.

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 캐시와 조합하여 사용할 수 있으며, 병합된 디코드는 각 단계의 토큰을 고정 크기 캐시에 인플레이스로 기록하고, 캐시는 내부적으로 토큰이 저장될 위치를 처리합니다.

정적 KV 캐시

이 섹션의 ExecuTorch 예제, 즉 zero-copy 업데이트와 decode 루프는 XNNPACK 전용입니다. MLX exporter는 StaticCache를 거부하므로, MLX에서는 DynamicCache를 사용하세요.

generate()는 기본적으로 DynamicCache를 확장하며, 시퀀스가 길어질 때마다 재할당을 수행합니다. 이는 내보낸 그래프에 대해 움직이는 대상이 됩니다. static 캐시는 고정된 크기의 버퍼로, 한 번만 할당되고 각 단계에서 현재 위치에 인플레이스로 기록됩니다. multi-token decode와 결합하면 생성 과정을 단일 내보내기 그래프로 축소할 수 있습니다. decode 그래프는 고정된 크기의 캐시와 변수 수의 쿼리 토큰을 입력으로 받으므로, 하나의 그래프가 프롬프트(빈 캐시 → 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로 표시하므로, 내보낸 그래프는 로드 시점에 임의의 프롬프트 길이와 캐시 크기를 받아들입니다.

Zero-copy in-place 업데이트

정적 캐시는 전달된 후 제자리에서 변경되므로, 런타임이 자체 아레나를 통해 복사하는 대신 호출자의 버퍼를 바인딩하는 한, 호스트 복사 없이 하나의 버퍼가 디코딩 단계 간에 상태를 유지합니다. 이를 위해 필요한 것은 백엔드별로 남은 유일한 부분입니다:

  • Dynamo — 내보낸 프로그램은 캐시 쓰기를 USER_INPUT_MUTATION으로 모델링하므로, components["decode"].module()(...)를 호출하면 전달한 캐시 텐서를 직접 업데이트합니다. 각 단계마다 동일한 텐서를 재사용하면 되며, 설정할 것이 없습니다.

  • ONNX Runtime — 디코드 그래프는 캐시를 매칭된 input.<name> / output.<name> 쌍으로 노출합니다. ORT의 CudaSession.set_buffer_sharing (onnxruntime.transformers.io_binding_helper)는 각 쌍을 하나의 디바이스 버퍼에 바인딩하여, 루프 동안 캐시를 호스트 왕복 없이 제자리에서 읽고 업데이트합니다.

  • ExecuTorch (XNNPACK) — ExecutorchConfig에서 메모리 계획 할당을 비활성화하여 in-place write가 호출자의 자체 텐서에 기록되도록 합니다(각 플래그의 역할은 참고 문서를 확인하세요):

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

    zero-copy in-place write는 호출자가 런타임에 Method::set_output_data_ptr을 통해 출력 버퍼를 바인딩해야 합니다 — Python 런타임에서는 노출되지 않습니다 (executorch.runtime.Method는 execute/set_inputs/get_outputs만 노출합니다). 위의 플래그는 이를 설정하지만, in-place write는 C++ 전용 경로입니다(아래의 ExecuTorch decode-loop 예시 참조). Python에서는 각 단계마다 메서드 출력에서 업데이트된 캐시를 다시 읽어 와야 합니다.

Decode-loop inference examples

루프는 모든 백엔드에서 동일한 형태이며, 전체적으로 동일한 그래프를 따릅니다. 빈 고정 크기 캐시에서 시작하여 프롬프트 전체를 한 번에 입력합니다(빈 캐시 → prefill), 이후에는 토큰을 하나씩 처리합니다(채워진 캐시 → decode). 각 호출에서는 input_ids, 인과적 attention_mask, position_ids(각 단계마다 새 토큰 수만큼 증가)와 캐시를 전달하고, 모든 쿼리 위치의 logits를 반환받습니다. 각 토큰이 캐시의 어디에 배치되는지는 정적 캐시가 내부적으로 추적하므로, 호출을 통해 별도로 전달할 것이 없습니다. 캐시 설정 방식은 런타임마다 다릅니다(Dynamo의 경우 StaticCache 객체, ONNX Runtime의 경우 원시 디바이스 버퍼, ExecuTorch의 경우 C++에서 호출자 배열), 따라서 각 탭은 아래에서 자체적으로 구성합니다. Dynamo와 ONNX Runtime 탭은 캐시를 제자리(in-place)로 업데이트하며, 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)

양자화

모든 export config는 quantizer를 받으며, 이 quantizer는 export 과정에서 모델을 양자화합니다. 대상은 torch.export FX 그래프(모든 백엔드에서 PT2E 또는 NNCF)이거나 변환된 ONNX 또는 OpenVINO 모델(ONNX Runtime 또는 NNCF)입니다. quantizer, 해당 백엔드, calibration, 그리고 추가 방법에 대해서는 Quantizing exports를 참조하세요.

한계 및 우회 방법

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로 교체합니다.

다음 단계

  • 내보내기 확장의 패치 및 수정 레지스트리를 사용하여 새로운 아키텍처 또는 백엔드에 대한 내보내기 지원을 추가합니다.
  • Quantizing exports에서 내보내기를 양자화하거나 양자화기를 추가합니다.
Update on GitHub