# 에クスポート拡張

`torch.export`는 모델을 그래프로 추적하고, 이후 단계에서 해당 그래프를 변환하며, 마지막 단계에서는 대상 런타임에 맞게 lower하거나 emit합니다. 대부분의 모델은 그대로 통과합니다. 백엔드가 처리할 수 없는 PyTorch 패턴이 있는 경우, exporter는 모델을 수정하는 대신 해당 단계에서 작은 우회 처리를 적용합니다.

워크어라운드를 추가하려면 함수 하나를 작성하고 데코레이터로 등록합니다. 각 워크어라운드는 이를 깔끔하게 표현할 수 있는 가장 낮은 단계에 배치해야 합니다.

## 패치 및 수정

우회 방법은 패치 또는 수정입니다. 두 방식은 되돌릴 수 있는지 여부에 따라 구별됩니다.

|              | 패치                                                                          | 수정                                                                                   |
| ------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| 기능 | `torch` 연산, ExecuTorch 내부 요소, 또는 모델 메서드와 같은 속성을 내보내기 기간 동안 다른 것으로 교체합니다 | 추적된 그래프 또는 프로그램을 다시 작성합니다                                                  |
| 되돌림     | 네, 이후에 원래 값으로 복원됩니다                                        | 아니요, 다음 단계가 실행되기 전에 아티팩트를 복구합니다                                |
| 가입 | `@register_patch(backend, *paths)`                                            | `@register_fx_node_fix(backend)` 또는 `@register_fx_program_fix(backend)`               |

두 항목 모두 [exporters/utils.py](https://github.com/huggingface/transformers/blob/main/src/transformers/exporters/utils.py)의 레지스트리에 있으며, exporter는 해당 백엔드에 등록된 모든 항목을 적절한 단계에서 설치합니다.

패치는 단일 백엔드의 로우어링 버그, 즉 ONNX 변환 누락, ORT 검증의 특이한 동작, 또는 불필요한 연산(dead op)을 출력하는 FX 분해(decomposition)와 같은 문제를 해결할 때 사용하세요. 이 경우 우회 처리는 익스포터에 머물러 있고, 모델링 코드는 깔끔하게 유지됩니다.

데이터 의존 루프, `Cache` 외부의 상태ful 캐시, 수동으로 작성된 split-loop attention과 같이 여러 백엔드에서 패턴 블록이 export되는 경우, 모델 자체를 수정해야 합니다. 모델을 한 번만 수정해도 모든 exporter에 도움이 됩니다.

## 패치 추가

모델 메서드가 `torch.export`로 추적할 수 없는 작업을 수행한다고 가정해 보겠습니다. NLLB-MoE의 `NllbMoeTop2Router._cast_classifier`는 classifier 가중치를 다른 dtype으로 캐스팅하는데, 이는 추적할 수 없습니다. export가 진행되는 동안에는 이를 no-op로 대체하세요.

…

```python
from transformers.exporters.utils import register_patch

@register_patch("dynamo", "transformers.models.nllb_moe.modeling_nllb_moe.NllbMoeTop2Router._cast_classifier")
def _patch_classifier_cast(_original):
    # Replace the untraceable dtype cast with a no-op during export.
    return lambda self, *args, **kwargs: None
```

exporter는 트레이싱 전에 메서드를 교체하고, 트레이싱 후 원래대로 복원하므로 패치는 export에만 영향을 줍니다. 몇 가지 변형:

- 여러 호출 지점에서 하나의 팩토리를 공유하려면 추가 경로를 전달합니다. 예를 들어 `@register_patch("dynamo", path_a, path_b)`.
- `torch` op의 경로를 지정하여 패치합니다. 예를 들어 `@register_patch("onnx", "torch.where")`와 같이 사용합니다. 팩토리는 실제 op를 인수로 받으므로, 대체 구현에서 해당 op를 호출할 수 있습니다.
- 트레이싱 후 그래프를 다시 작성해야 하는 경우 패치가 아닌 수정(fix)을 작성하세요. 메커니즘은 동일하며, 매칭 레지스트리에 있는 데코레이터가 적용된 함수입니다.

## Stage 참조

각 exporter의 소스에는 `# ── Stage N: … ──` 형식의 주석 블록으로 각 단계가 표시되어 있어, 해당 파일과 이 참고 문서가 서로 일치합니다. 각 단계에서 처리하는 정확한 ops와 클래스는 해당 소스를 참고하세요.

### DynamoExporter

기본 exporter는 `DynamoExporter.export_program` 안에서 패치 단계 1개와 헬퍼 4개를 순서대로 실행합니다([exporter_dynamo.py](https://github.com/huggingface/transformers/blob/main/src/transformers/exporters/exporter_dynamo.py) 참고).

1. Forward-signature 패치: `model.forward`에 평탄한 인자 시그니처를 부여하여 `torch.export`가 입력을 하나의 `**kwargs` 튜플로 묶지 않도록 합니다. 이는 내부 전용이며 확장 지점이 아닙니다.
2. 모델 패치: 트레이싱 중에 추적할 수 없는 모델 메서드를 내보내기에 안전한 대안으로 교체합니다. `@register_patch("dynamo", ...)`로 확장할 수 있습니다.
3. Pytree 등록: `torch.export`가 `Cache`와 `ModelOutput`를 평탄화하고 다시 구성할 수 있도록 각각 등록합니다(보통 자동으로 수행됩니다). 속성 탐색으로 도달할 수 없는 타입에 대해서는 `_flatten_to_context` / `_unflatten_from_context`에 분기를 추가합니다.
4. 동적 shape: `dynamic=True`인 경우 모든 tensor와 cache leaf에 `Dim.AUTO`를 할당합니다. `DynamoConfig.dynamic_shapes`로 오버라이드할 수 있습니다.
5. 상태 정리: `torch.export`가 fake tensor로 남기는, 모델이 `forward` 내부에서 설정하는 tensor 속성을 초기화합니다. `_STATEFUL_CACHE_ATTRS`에 속성 이름을 추가하여 확장합니다.

### OnnxExporter

`OnnxExporter`는 `torch.onnx.export` 주위에 5단계의 처리를 추가합니다(자세한 내용은 [exporter_onnx.py](https://github.com/huggingface/transformers/blob/main/src/transformers/exporters/exporter_onnx.py)를 참조). 패치의 전체 목록은 해당 파일에서 grep으로 확인하세요:

```bash
grep -nE "^def (_patch_|_fix_|_aten_)" src/transformers/exporters/exporter_onnx.py
```

1. Torch 패치: ONNX 익스포터가 그대로 변환할 수 없는 `torch` 연산을 대체합니다. `@register_patch("onnx", ...)`로 확장할 수 있습니다.
2. ONNX 패치: `run_decompositions` 실행 후 노드 수정을 다시 실행하여, lowering 전에 새로 도입된 shape-guard 노드가 복구되도록 합니다. 동일한 `@register_patch("onnx", ...)` 레지스트리를 사용합니다.
3. FX 노드 수정: ONNX exporter가 lower할 수 없는 alias ops, in-place views, dead asserts와 같은 그래프 노드를 다시 작성합니다. `@register_fx_node_fix("onnx")`로 확장합니다.
4. ONNX 번역: 기본 구현이 없거나 버그가 있는 aten op에 대해 사용자 지정 lowering을 제공합니다(예: `aten.index_put` 또는 `aten._grouped_mm`). `_ONNX_TRANSLATION_TABLE`에 `_aten_*` 함수를 추가합니다.
5. ONNX IR 수정: 완성된 ONNX 프로그램을 다시 작성하여 ONNX Runtime의 버그를 우회합니다(예: `TopK(sorted=True)` 강제 적용). `_IR_FIXES`에 `_fix_ir_*` 함수를 추가합니다.

### ExecutorchExporter

`ExecutorchExporter`는 `to_edge_transform_and_lower`와 `to_executorch` 주위에 5개의 단계를 추가하며, 백엔드 준비 단계로 시작합니다(자세한 내용은 [exporter_executorch.py](https://github.com/huggingface/transformers/blob/main/src/transformers/exporters/exporter_executorch.py)를 참조).

1. 백엔드 준비: 모델을 대상 device와 dtype으로 이동하고 파티셔너를 선택합니다(`prepare_for_xnnpack`, `prepare_for_cuda`). `_BACKEND_PREPARE`에 `prepare_for_<name>`를 등록하여 백엔드를 추가합니다.
2. Torch 패치: `torch` 연산 중 ExecuTorch 백엔드가 처리할 수 없는 연산, 예: `split_copy`, `chunk`, `topk(k>dim)` 등을 대체합니다. `@register_patch("executorch", ...)`로 확장할 수 있습니다.
3. ExecuTorch 패치: 유효한 동적 shape 그래프에서 크래시가 발생하는 ExecuTorch 내부 구현을 대체합니다. 동일한 `@register_patch("executorch", ...)` 레지스트리를 사용합니다.
4. FX 프로그램 수정: 범위 제약 조건을 확장하거나 누락된 플레이스홀더 메타데이터를 채우는 등 전체 프로그램 컨텍스트가 필요한 경우, 내보낸 프로그램을 복구합니다. `@register_fx_program_fix("executorch")`로 확장합니다.
5. FX 노드 수정: `executorch_prim.*`으로 Python sym ops를 매핑하거나 `pow`를 `mul` 체인으로 재작성하는 등 개별 노드를 재작성합니다. `@register_fx_node_fix("executorch")`로 확장합니다.

## 백엔드 추가

백엔드는 `HfExporter`의 하위 클래스와 설정으로 구성됩니다. `export_program`은 하나의 그래프를 추적하고 추적 중에 기록된 메타데이터를 포함하여 백엔드의 프로그램을 반환하며, `save_program`은 하나의 프로그램을 디스크에 쓰고, `load_program`은 이를 다시 읽습니다. `~HfExporter.export`와 `~HfExporter.export_for_generation`는 이들을 기반으로 `ExportedModel`을 구성합니다. `DynamoExporter`를 하위 클래스로 만들면 그 추적과 메타데이터를 재사용하므로, 백엔드는 `ExportedProgram`만 변환하면 됩니다:

```python
from dataclasses import dataclass

from transformers.exporters import DynamoConfig, DynamoExporter, register_backend

@dataclass
class MyConfig(DynamoConfig):
    export_format: str = "my_format"

class MyExporter(DynamoExporter):
    export_format = "my_format"
    config_class = MyConfig
    program_suffix = ".bin"

    def export_program(self, model, sample_inputs, config):
        exported_program, metadata = super().export_program(model, sample_inputs, config)
        return convert(exported_program), metadata

    @classmethod
    def save_program(cls, program, path):
        program.save(path)

    @classmethod
    def load_program(cls, path):
        return load(path)

register_backend(MyExporter)
```

내보내기 항목을 호출하려면 `ModelRunner`를 등록해야 합니다: `register_backend(MyExporter, MyRunner)`.

Auto 클래스는 `"my_format"`을 그들에게 전달하고, `ExportedModel.from_pretrained`는 저장된 내보내기를 로드합니다:

```python
from transformers.exporters import AutoExportConfig, AutoHfExporter, ExportedModel

config = AutoExportConfig.from_dict({"export_format": "my_format", "dynamic": True})
exported = AutoHfExporter.from_config(config).export(model, inputs, config)
exported.save_pretrained("my-model-my_format")  # model.bin, export.json, config.json
exported.push_to_hub("my-org/my-model-my_format")

loaded = ExportedModel.from_pretrained("my-org/my-model-my_format")
```

## 알려진 상위 프로젝트 우회 방법

일부 모델 클래스에서 `onnxscript` 그래프 최적화기의 확인된 버그(상수 접기 시 `SplitToSequence`에서 크래시 발생, FPN 초기화 값이 제거되는 문제)가 발생했습니다. [ONNX_DISABLE_OPTIMIZE](https://github.com/huggingface/transformers/blob/main/tests/exporters/test_export.py)는 해당 모델에 대해 `onnxscript` 최적화를 비활성화합니다. 각 항목은 모델 이름 옆에 해당 업스트림 이슈를 기록합니다. 업스트림 버그가 수정됨에 따라 목록은 줄어 들 것으로 예상되므로, 새로운 항목은 임의로 최적화를 비활성화하는 것이 아니라 특정 업스트림 버그를 참조해야 합니다.

[EXPORT_SKIPS](https://github.com/huggingface/transformers/blob/main/tests/exporters/test_export.py)는 모델이 근본적으로 그대로 export할 수 없는 경우(벡터화할 수 없는 데이터 의존 제어 흐름, forward 인수로 처리되는 모듈 등) 일부 모델 클래스를 export sweep에서 완전히 제외하는 옵션입니다. 각 항목에는 모델 측에서 필요한 변경 사항을 명시하는 이유가 포함됩니다. 이 목록은 증가하기보다 축소될 것으로 예상됩니다.

