학습이 끝난 모델이 배포 아티팩트가 되기까지
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를 함께 두고, 같은 호출 형태로 다음 아티팩트를 만든다.
DynamoExporter→ PyTorchExportedProgramOnnxExporter→ONNXProgramExecutorchExporter→ 모바일·엣지용ExecutorchProgramManager
중요한 변화는 "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 | 결과 | 주된 사용 위치 | 여전히 필요한 것 |
|---|---|---|---|
DynamoExporter | ExportedProgram | PyTorch AOT compile, 자체 runtime integration | target compiler와 runtime packaging |
OnnxExporter | ONNXProgram | ONNX Runtime, TensorRT, OpenVINO 등 | execution provider 설정, runtime session, 성능 검증 |
ExecutorchExporter | ExecutorchProgramManager | 모바일·엣지 배포 | 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()가 성공했다는 사실은 다음을 보장하지 않는다.
- 실제 traffic shape에서 graph가 재사용된다.
- eager PyTorch와 target runtime의 출력 오차가 허용 범위다.
- prefill과 decode graph를 연결한 생성 loop가 올바르다.
- target accelerator에서 모든 operator가 원하는 kernel로 lowering된다.
- latency, memory, binary size가 배포 기준을 충족한다.
HfExporters는 배포 파이프라인 전체를 없애는 기능이 아니라, 모델마다 흩어졌던 export adapter를 한 곳에서 관리하는 기반이다.
공통 경로와 backend별 repair 단계
공통 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 patch | tracing 중 Python/PyTorch operation | data-dependent branch, 지원되지 않는 op 구조 | context 종료 시 원복 |
| FX node fix | export 뒤 개별 graph node | in-place op, alias, dead symbolic guard | artifact에 영구 반영 |
| FX program fix | ExportedProgram 전체 | signature 정렬, cross-graph 구조 수선 | artifact에 영구 반영 |
| ONNX translation | ATen → ONNX lowering | trace는 되지만 ONNX 표현이 없는 op | ONNX 변환에 반영 |
| ONNX IR fix | 완성 직전 ONNX graph | runtime별 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를 재실행하는 방식이어야 한다.
- export 성공 여부
- graph input/output signature
- eager 대비 수치 동등성
- 대표 shape와 최대 shape 실행
- unsupported operator와 fallback count
- target runtime latency·memory·binary size
- 동일 model의 eager forward가 export 뒤에도 정상인지
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를 나눈다. 여기에도 명확한 한계가 있다.
- 결과는 독립 graph 집합이다.
- encoder output을 projector와 language model에 연결하는 일은 caller가 맡는다.
- token sampling, stopping criteria, beam state, KV cache 교체도 caller가 구현한다.
- projector 탐색에는 canonical attribute와 heuristic name이 사용된다.
따라서 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)명시 범위에는 두 가지 효과가 있다.
- 배포 계약 밖 shape를 runtime assert나 validation 단계에서 거부할 수 있다.
- compiler와 runtime이 무한한 shape space를 가정하지 않고 optimization profile을 정할 수 있다.
대신 잘못된 범위는 export guard failure나 runtime rejection을 만든다. max=4096을 적었다고 KV cache와 position encoding이 4096까지 정확하다는 뜻도 아니다. 다음 matrix를 artifact test에 포함해야 한다.
| 축 | 최소 case | 경계 case | 실패를 기대하는 case |
|---|---|---|---|
| batch | 1 | 계약상 maximum | maximum + 1 |
| prompt length | 짧은 prompt | maximum context 근처 | model context 초과 |
| decode cache | 첫 decode token | 긴 cache 누적 | cache limit 초과 |
| modality | 최소 image/audio | production 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로 만든다
- model class와 정확한 model revision
- 현재
transformers, PyTorch, Optimum, ONNX, runtime version - custom model code와
trust_remote_code사용 여부 - attention backend와 quantization 방식
- decoder-only, encoder-decoder, SSM, VLM 중 어떤 generation 구조인지
- 기존 graph patch와 custom operator
- production batch·sequence·cache shape 범위
기존 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를 만든다
단순 forward와 export_for_generation을 구분한다. generation model은 prefill/decode component key, input name, dtype, dynamic constraint를 manifest로 저장한다. export warning을 log artifact로 보존한다.
4단계: graph별 동등성을 검증한다
- eager model vs
ExportedProgram - eager/reference vs ONNX Runtime
- prefill logits와 KV cache structure
- 한 token decode와 cache update
- VLM encoder, projector, language model component별 output
- export 직후 원본 model의 eager forward 재실행
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를 실행한다
- batch 1과 maximum batch
- 짧은 prompt와 maximum context 근처 prompt
- 첫 decode와 긴 cache 누적
- 단일 modality와 multimodal input
- cold start, warm latency, steady-state throughput
- peak resident memory와 artifact size
- target provider별 unsupported op·fallback
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로 되돌릴 수 있어야 한다.
채택 체크리스트
transformers==5.13.x와 model revision을 고정하고 official guide의 backend별 dependency pin을 재현한다.dynamic=True만 믿지 않고 batch·sequence·cache의 최소·경계·거부 shape를 실행한다.정리
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 안에서 함께 관리한다.
가장 중요한 설계는 세 가지다.
- prefill과 decode의 실제 kwargs를 model 실행에서 capture해 독립 graph로 내보낸다.
- workaround를 reversible patch, FX repair, ONNX translation과 IR fix 단계로 나눠 호환성 부채의 위치를 드러낸다.
- 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
- Transformers v5.13.0 release — released 2026-07-03
- HfExporters: Native, Unified export for PyTorch / ONNX / ExecuTorch — PR #41992, merged 2026-07-02
- Transformers 5.13.0 Exporters guide — version-pinned source
- Modeling changes for export, compile, and hybrid-attention standardization — PR #46738
- PyTorch 2.12
torch.exportdocumentation - PyTorch 2.12 ONNX exporter documentation