LLM WikiAccess-protected knowledge portal

WIKI

Transformers 5.13 HfExporters: ONNX·ExecuTorch·torch.export를 하나의 파이프라인으로 묶기

학습이 끝난 모델이 배포 아티팩트가 되기까지 PyTorch에서 잘 실행되는 모델이 있다고 해서 ONNX Runtime이나 모바일 런타임에서도 바로 실행되는 것은 아니다. 모델의 forward 에는 Python 분기, 동적으로 커지는 KV 캐시, backend 전용 attention 연산, 중첩된 ModelOutput 이 섞여 있다. 학습 코드에서는 자연스러운 표현도 정적 그래프로 내보내는 순간 제약이 된다. 그동안 Huggin

경로human/study/content/ai-frontier/07-transformers-5-13-hfexporters-unified-model-export.md
카테고리Study
태그#airflow #crawler #export #hfexporters #infra #model #study #transformers #unified

학습이 끝난 모델이 배포 아티팩트가 되기까지

PyTorch에서 잘 실행되는 모델이 있다고 해서 ONNX Runtime이나 모바일 런타임에서도 바로 실행되는 것은 아니다. 모델의 forward에는 Python 분기, 동적으로 커지는 KV 캐시, backend 전용 attention 연산, 중첩된 ModelOutput이 섞여 있다. 학습 코드에서는 자연스러운 표현도 정적 그래프로 내보내는 순간 제약이 된다.

그동안 Hugging Face 생태계에서는 모델 구현과 export 지원이 서로 다른 속도로 움직이기 쉬웠다. 새 attention 구조나 cache class가 transformers에 들어온 뒤 downstream export library가 별도 glue를 추가할 때까지 기다려야 했다. 모델은 업데이트됐지만 배포 변환기는 이전 구조를 가정하는 시간차가 생겼다.

2026년 7월 3일 공개된 Transformers 5.13은 이 경계를 바꾸는 실험적 API HfExporters를 추가했다. transformers 안에 공통 exporter 추상화와 세 backend를 함께 두고, 같은 호출 형태로 다음 아티팩트를 만든다.

중요한 변화는 "ONNX 저장 함수가 하나 더 생겼다"가 아니다. 모델 정의, cache 구조, dynamic shape, generation의 prefill/decode 분해, backend별 graph repair를 모델 library와 같은 release cycle에서 테스트하는 구조가 생겼다는 점이다.

이 장은 Transformers 5.13.0 release, version-pinned exporter guide, HfExporters PR과 modeling standardization PR을 기준으로 한다. 5.13.0 공개 후 약 11일 지난 기능이며 공식 문서가 experimental로 표시한다. production에서는 문서의 test-suite dependency를 pin하고 minor release에서도 재검증해야 한다.


무엇이 통합됐고 무엇은 그대로 남았는가

세 exporter는 공통 interface를 제공하지만 결과물의 실행 계약까지 같게 만들지는 않는다.

Exporter결과주된 사용 위치여전히 필요한 것
DynamoExporterExportedProgramPyTorch AOT compile, 자체 runtime integrationtarget compiler와 runtime packaging
OnnxExporterONNXProgramONNX Runtime, TensorRT, OpenVINO 등execution provider 설정, runtime session, 성능 검증
ExecutorchExporterExecutorchProgramManager모바일·엣지 배포backend lowering, device packaging, on-device 검증

통합된 것은 모델을 graph artifact로 바꾸는 앞부분이다. model server, decode loop, request batching, tokenizer, KV cache lifecycle, runtime별 quantization과 packaging까지 자동으로 제공하는 turnkey deployment system은 아니다.

이 경계를 먼저 분명히 해야 한다. export()가 성공했다는 사실은 다음을 보장하지 않는다.

HfExporters는 배포 파이프라인 전체를 없애는 기능이 아니라, 모델마다 흩어졌던 export adapter를 한 곳에서 관리하는 기반이다.


공통 경로와 backend별 repair 단계

모델 하나를 세 runtime artifact로 내보내는 경로 PreTrainedModel + inputs 실제 forward signature tensor · Cache · ModelOutput 작지만 대표적인 capture 입력 공통 export preparation forward signature 평탄화 **kwargs blob 방지 reversible model patch trace 중에만 교체 Cache pytree 등록 중첩 tensor 구조 평탄화 dynamic shape 생성 Dim.AUTO 또는 명시 범위 export 뒤 FakeTensor state 정리 generation이면 먼저 분해 generate(max_new_tokens=2) forward hook으로 실제 kwargs capture prefill decode VLM: encoder · projector · LM · lm_head 추가 각 component는 독립 graph DynamoExporter torch.export 기반 normalized graph model patch + FX program repair dynamic constraint와 signature 보존 ExportedProgram OnnxExporter 1. Torch op patch 2. FX node · program fix 3. custom ONNX translation 4. ONNX IR backend repair ONNXProgram ExecutorchExporter edge backend lowering 준비 model · FX graph repair XNNPACK 등 target backend lowering ExecutorchProgramManager artifact 생성은 끝이 아니다: runtime orchestration · 출력 동등성 · shape matrix · 성능 · rollback은 배포 팀의 책임
Transformers 5.13 HfExporters — 공통 capture와 backend별 graph 변환 경계

공통 pipeline에서 특히 중요한 네 가지는 다음과 같다.

Forward signature 평탄화: Transformers 모델은 **kwargs: Unpack[TransformersKwargs]를 많이 사용한다. 그대로 export하면 torch.export가 kwargs를 하나의 중첩 blob으로 묶어 dynamic shape specification과 실제 input name이 어긋날 수 있다. HfExporters는 사용자가 전달한 input에서 명시적인 flat signature를 만든다.

Cache pytree 등록: KV cache는 단일 tensor가 아니라 layer별 key/value, 길이와 metadata가 섞인 중첩 object다. exporter는 Cache subclass를 찾아 pytree node로 등록하고 tensor leaf까지 평탄화한다. 새 cache type이 표준 상속 구조를 따르면 model별 수동 axis table 없이 처리할 수 있다.

Dynamic shape 자동화: dynamic=True는 tensor와 cache leaf의 dimension에 torch.export.Dim.AUTO를 배치한다. 이 값은 "어떤 shape도 무조건 성공한다"는 선언이 아니다. capture에서 관찰한 shape relation을 compiler가 추론하도록 맡기는 시작점이다. production에서 batch와 sequence 상한이 계약으로 정해져 있다면 explicit Dim(min=..., max=...) 범위를 쓰는 편이 장애를 더 일찍 드러낸다.

State cleanup: export 중 실행된 forward가 module attribute에 tensor를 저장하면 tracing 뒤 FakeTensor가 남을 수 있다. HfExporters는 알려진 stateful cache attribute를 초기화해 같은 model instance의 다음 eager forward가 오염되지 않게 한다.


patch는 숨기는 기술이 아니라 호환성 부채를 모으는 기술이다

torch.export는 TorchScript trace보다 엄격하다. data-dependent Python if나 graph 밖의 side effect를 경고만 남기고 한쪽 경로로 고정하지 않고, guard를 증명하지 못하면 export를 실패시킬 수 있다. 이 엄격함은 배포 artifact의 의미를 더 분명하게 하지만, 다양한 모델을 한 번에 지원하려면 backend별 workaround가 필요하다.

HfExporters는 workaround를 임의의 model fork에 흩뿌리지 않고 단계별 registry로 모은다.

단계고치는 위치적합한 문제lifecycle
Torch patchtracing 중 Python/PyTorch operationdata-dependent branch, 지원되지 않는 op 구조context 종료 시 원복
FX node fixexport 뒤 개별 graph nodein-place op, alias, dead symbolic guardartifact에 영구 반영
FX program fixExportedProgram 전체signature 정렬, cross-graph 구조 수선artifact에 영구 반영
ONNX translationATen → ONNX loweringtrace는 되지만 ONNX 표현이 없는 opONNX 변환에 반영
ONNX IR fix완성 직전 ONNX graphruntime별 attribute·operator quirk최종 artifact에 반영

이 구조의 운영상 장점은 workaround의 소유 위치와 삭제 조건이 보인다는 것이다. 예를 들어 upstream PyTorch나 onnxscript가 문제를 해결하면 해당 registry entry를 제거할 수 있다. 반대로 private patch가 model code 곳곳에 숨어 있으면 dependency upgrade가 어떤 우회 경로를 무효화했는지 찾기 어렵다.

그러나 registry가 있다고 부채가 사라지는 것은 아니다. 공식 문서는 이 API가 experimental이며 patch가 test suite에서 사용한 dependency version에 묶여 있다고 경고한다. 5.13 guide가 제시하는 검증 조합은 다음과 같다.

Dynamo:    torch==2.12.0
ONNX:      torch==2.12.0, onnx==1.21.0, onnxscript==0.7.0, onnxruntime
ExecuTorch: torch==2.12.0, executorch==1.3.1

이 version들은 영구 권장값이 아니라 5.13.0 exporter patch가 검증된 기준선이다. upgrade 정책은 "최신으로 올리고 export가 되면 통과"가 아니라 다음 artifact contract를 재실행하는 방식이어야 한다.


generation은 왜 prefill과 decode를 따로 내보내야 하는가

autoregressive model의 forward는 첫 token과 이후 token에서 실제 계약이 다르다.

prefill:
  input_ids = [batch, prompt_length]
  past_key_values = empty
  output = prompt logits + populated KV cache

decode:
  input_ids = [batch, 1]
  past_key_values = populated
  output = next-token logits + updated KV cache

하나의 정적 graph가 두 상태를 모두 표현하게 만들면 empty cache 분기, sequence shape, cache update가 복잡해진다. HfExporters의 export_for_generation()은 model의 실제 generate(..., max_new_tokens=2)를 한 번 실행하고 forward hook으로 첫 prefill 호출과 다음 decode 호출의 kwargs를 capture한다. 그 뒤 각 상태를 독립적으로 export한다.

from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import OnnxConfig, OnnxExporter

model_id = "hf-internal-testing/tiny-random-LlamaForCausalLM"
model = AutoModelForCausalLM.from_pretrained(model_id).eval()
tokenizer = AutoTokenizer.from_pretrained(model_id)
inputs = tokenizer("export contract test", return_tensors="pt")

artifacts = OnnxExporter().export_for_generation(
    model,
    inputs,
    config=OnnxConfig(dynamic=True),
)

prefill = artifacts["prefill"]
decode = artifacts["decode"]

capture input은 작되 대표적이어야 한다. 너무 큰 image나 긴 prompt를 사용하면 eager capture와 symbolic shape inference가 불필요하게 비싸진다. 반대로 실제 경로에서만 생기는 modality나 cache 입력을 빼면 필요한 component를 발견하지 못할 수 있다.

VLM은 더 많이 분해된다. 표준 get_encoder(modality="image" | "audio"), get_decoder()와 알려진 projector attribute를 이용해 image/audio encoder, projector, language model, lm_head, decode graph를 나눈다. 여기에도 명확한 한계가 있다.

따라서 production acceptance test는 component별 test와 orchestration test를 분리해야 한다. 각 graph가 eager output과 맞더라도 연결 순서, dtype cast, cache position, attention mask가 틀리면 전체 생성 결과는 달라진다.


5.13의 model code 변경이 exporter보다 더 중요한 이유

HfExporters machinery만으로 모든 모델을 정적 graph로 바꿀 수는 없다. 같은 release의 PR #46738은 152개 파일을 바꾸며 model 쪽 export blocker를 줄였다. 핵심 방향은 세 가지다.

Python control flow를 tensor graph로 바꾼다

Swin 계열 window mask처럼 Python loop와 slice tuple로 만들던 mask를 arange 기반 vectorized operation으로 바꿨다. export 시 sequence나 image shape가 Python constant로 굳는 일을 줄인다. BigBird 계열의 helper reshape와 batch matrix multiply도 일반 torch.matmul로 단순화했다.

hybrid attention의 layer contract를 표준화한다

Mamba·linear attention·full attention이 섞인 모델은 layer마다 mask와 cache 의미가 다르다. 5.13은 legacy layer type을 표준 name으로 remap하고, layer type별 mask function dispatch를 공통화했다. generation 전에 static-size mask dictionary를 만들 수 있어 growing attention mask 때문에 Dynamo가 반복 compile되는 문제를 줄이는 기반이 된다.

fullgraph compile을 막는 data-dependent 구조를 제거한다

Granite-MoE family는 router와 experts를 분리하고 compilable grouped/batched matrix multiplication 구조로 바꿨다. 기존 checkpoint key는 conversion mapping으로 새 parameter layout에 연결한다. "export 때문에 model 의미를 우회"하는 대신 model implementation 자체를 compile 가능한 형태로 정리한 사례다.

이 변경에는 migration risk도 있다. internal modeling API나 layer type string에 의존한 코드가 있다면 5.13 upgrade에서 깨질 수 있다. Gemma 3/4의 local-layer image token attention mask 수정처럼 결과 재현성에 영향을 줄 수 있는 bug fix도 release note에 포함됐다. exporter 도입과 model version upgrade를 같은 canary에서 검증하되, failure가 graph conversion 때문인지 model behavior 변경 때문인지 분리해 기록해야 한다.


dynamic=True는 capacity plan을 대신하지 않는다

자동 dynamic shape는 model별 axis table을 줄이는 좋은 기본값이다. 하지만 runtime 운영에서는 허용 범위를 명시하는 편이 유리한 경우가 많다.

import torch
from transformers.exporters import OnnxConfig, OnnxExporter

batch = torch.export.Dim("batch", min=1, max=16)
sequence = torch.export.Dim("sequence", min=1, max=4096)

config = OnnxConfig(
    dynamic_shapes={
        "input_ids": {0: batch, 1: sequence},
        "attention_mask": {0: batch, 1: sequence},
    },
    prefer_deferred_runtime_asserts_over_guards=True,
)

program = OnnxExporter().export(model, inputs, config=config)

명시 범위에는 두 가지 효과가 있다.

  1. 배포 계약 밖 shape를 runtime assert나 validation 단계에서 거부할 수 있다.
  2. compiler와 runtime이 무한한 shape space를 가정하지 않고 optimization profile을 정할 수 있다.

대신 잘못된 범위는 export guard failure나 runtime rejection을 만든다. max=4096을 적었다고 KV cache와 position encoding이 4096까지 정확하다는 뜻도 아니다. 다음 matrix를 artifact test에 포함해야 한다.

최소 case경계 case실패를 기대하는 case
batch1계약상 maximummaximum + 1
prompt length짧은 promptmaximum context 근처model context 초과
decode cache첫 decode token긴 cache 누적cache limit 초과
modality최소 image/audioproduction maximum잘못된 shape·빈 modality
dtype/device기준 dtype허용된 대체 dtype지원하지 않는 조합

"dynamic graph 한 개"보다 중요한 것은 어떤 shape domain을 검증했고, domain 밖 요청을 어디에서 차단하는가다.


backend별로 달라지는 실패 지점

DynamoExporter

가장 PyTorch에 가까운 artifact를 만든다. ExportedProgram은 eager Python을 그대로 저장한 것이 아니라 operator와 constraint가 정규화된 graph다. data-dependent branch, side effect, Python object mutation은 export 전에 제거하거나 graph로 표현해야 한다. 성공한 artifact도 target AOT compiler가 모든 op를 지원하는지는 별도 문제다.

OnnxExporter

ATen graph를 ONNX operator로 lowering해야 하므로 변환 단계가 가장 많다. Flash Attention과 Flex Attention은 문서 기준 export할 수 없으며, model의 attention backend를 sdpa 또는 더 느린 eager로 바꾸는 것이 권장된다. 이 변경은 export 가능성만이 아니라 성능 특성도 바꾸므로 target runtime에서 다시 benchmark해야 한다.

ONNX artifact를 만든 뒤에는 Python-side reference와 ONNX Runtime 실행을 함께 비교한다.

import torch
from transformers.exporters import OnnxConfig, OnnxExporter

onnx_program = OnnxExporter().export(
    model,
    inputs,
    config=OnnxConfig(dynamic=True),
)

new_inputs = tokenizer(
    ["short", "a somewhat longer validation sentence"],
    padding=True,
    return_tensors="pt",
)

reference = onnx_program.call_reference(**new_inputs)[0]
runtime = onnx_program(**new_inputs)[0]
torch.testing.assert_close(reference, runtime, rtol=1e-4, atol=1e-4)

1e-4를 모든 model과 dtype의 보편 기준으로 사용하면 안 된다. FP32, BF16/FP16, quantized model, generation logits에 맞는 tolerance를 baseline 반복 측정으로 정한다.

ExecutorchExporter

edge backend는 operator coverage와 memory budget이 더 좁다. 예를 들어 공식 guide는 XNNPACK에 _grouped_mm.out kernel이 없어 MoE experts를 batched_mm로 바꾼다고 설명한다. 수학적으로 같은 output을 목표로 해도 kernel shape와 latency가 달라질 수 있다. desktop Python에서 .pte 생성에 성공한 것만으로 끝내지 말고 실제 target device에서 warm-up, peak memory, binary size, thermal throttling을 측정해야 한다.


안전한 도입 순서

1단계: 현재 export 경로를 inventory로 만든다

기존 Optimum pipeline이 동작한다면 즉시 삭제하지 않는다. HfExporters artifact와 같은 input corpus를 비교하는 rollback baseline으로 유지한다.

2단계: 검증된 dependency set을 별도 environment에 고정한다

5.13 guide의 pin을 그대로 재현한 뒤 lockfile과 container digest를 남긴다. application runtime dependency를 먼저 upgrade하지 않는다. exporter environment와 serving image를 분리하면 artifact 생성 도구의 experimental dependency가 online server 전체로 전파되는 것을 줄일 수 있다.

3단계: 작은 representative input으로 artifact를 만든다

단순 forwardexport_for_generation을 구분한다. generation model은 prefill/decode component key, input name, dtype, dynamic constraint를 manifest로 저장한다. export warning을 log artifact로 보존한다.

4단계: graph별 동등성을 검증한다

final token string만 비교하면 sampling randomness와 작은 logit 차이를 분리하기 어렵다. component output, cache tensor, top-k logit rank를 함께 본다.

5단계: orchestration contract를 검증한다

prefill output이 decode input으로 어떻게 연결되는지 명시한다. cache position, attention mask, dtype cast, device transfer, stopping criteria를 test fixture로 고정한다. greedy decoding처럼 결정적인 mode에서 end-to-end result를 먼저 비교한 뒤 sampling으로 확장한다.

6단계: shape와 성능 matrix를 실행한다

export 성공률이 아니라 production shape coverage를 release gate로 둔다.

7단계: canary와 rollback을 artifact 단위로 운영한다

model weight, tokenizer, exporter version, dependency lock, dynamic shape contract, artifact checksum을 한 release manifest로 묶는다. 새 artifact가 실패하면 source code를 다시 build하지 않고 이전 검증 artifact로 되돌릴 수 있어야 한다.


채택 체크리스트

소스와 dependency
transformers==5.13.x와 model revision을 고정하고 official guide의 backend별 dependency pin을 재현한다.
experimental API와 upstream workaround를 release risk로 기록하고 minor upgrade마다 contract test를 다시 실행한다.
기존 Optimum 또는 eager 경로를 즉시 제거하지 않고 비교 기준과 rollback 경로로 유지한다.
graph 정확성
eager, reference, target runtime의 output과 cache를 dtype별 tolerance로 비교한다.
prefill·decode와 VLM component를 각각 검증한 뒤 orchestration 전체를 검증한다.
export 뒤 원본 model의 eager forward를 다시 실행해 FakeTensor state 오염이 없는지 확인한다.
shape와 성능
dynamic=True만 믿지 않고 batch·sequence·cache의 최소·경계·거부 shape를 실행한다.
attention backend 변경, custom op lowering, runtime fallback이 latency와 memory에 미치는 영향을 측정한다.
ONNX는 execution provider별로, ExecuTorch는 실제 target device에서 warm latency·peak memory·artifact size를 확인한다.
배포와 rollback
weight·tokenizer·exporter·dependency·shape contract·checksum을 하나의 artifact manifest로 묶는다.
export warning, unsupported op, graph signature drift를 CI failure로 승격한다.
canary에서 정확성·latency·memory 기준을 넘으면 이전 검증 artifact로 즉시 되돌린다.
HfExporters production adoption gate

정리

Transformers 5.13 HfExporters가 해결하려는 문제는 file format 선택이 아니다. 모델 구현과 export adapter가 서로 다른 repository와 release cycle에서 진화하면서 생기던 시간차를 줄이는 것이다. 공통 HfExporter 아래에 Dynamo, ONNX, ExecuTorch backend를 두고, forward signature, cache pytree, dynamic shape, generation decomposition을 model library 안에서 함께 관리한다.

가장 중요한 설계는 세 가지다.

  1. prefill과 decode의 실제 kwargs를 model 실행에서 capture해 독립 graph로 내보낸다.
  2. workaround를 reversible patch, FX repair, ONNX translation과 IR fix 단계로 나눠 호환성 부채의 위치를 드러낸다.
  3. model test mixin과 export test를 연결해 새 architecture가 들어올 때 export breakage를 같은 codebase에서 발견한다.

동시에 production engineer가 떠안아야 할 경계도 분명하다. API는 experimental이고 dependency patch에 민감하다. generation 결과는 독립 component graph이지 완성된 decode engine이 아니다. dynamic shape는 capacity contract가 아니며, artifact 생성 성공은 runtime 정확성이나 성능을 보장하지 않는다.

따라서 좋은 도입 기준은 "ONNX file이 생겼다"가 아니다. 검증된 dependency에서 같은 model revision을 재현하고, prefill·decode·component의 수치 계약과 shape domain을 확인하며, target runtime에서 성능을 측정하고, 이전 artifact로 되돌릴 수 있는가가 기준이어야 한다.

References