# modelctl 모범 사례

> modelctl로 신뢰할 수 있고 재현 가능하며 엣지에 안전한 ONNX 번들을 준비하기 위한 실전 사례 — 설치 프로필, 검증 게이트, 양자화, 재현 가능한 빌드, CI.

워크스테이션의 모델을 air-gapped 엣지 박스로 **신뢰성 있게** 올리기 위한 지침입니다.
단계별 명령은 [modelctl로 모델 준비하기](/ko/configure/prepare-models/)를 참고하세요 —
이 페이지는 그 작업 흐름 위에서의 *이유*와 *해야 할 것/하지 말아야 할 것*입니다.

## 가장 작은 설치 프로필을 선택하세요

`modelctl`은 무거운 ML 프레임워크를 **선택 사항**으로 둡니다. 코어 설치는 PyTorch나
TensorFlow 없이 `validate`와 `package`를 실행하므로, 운영자가 가벼운 머신에서 번들을
검증할 수 있습니다. 작업에 필요한 extra만 추가하세요.

  
**Validate / package**

    ```bash
    pip install modelctl            # onnx + onnxruntime + numpy 만
    ```
  
  
**Convert**

    ```bash
    pip install 'modelctl[pytorch]'      # 또는 [tensorflow] / [all]
    ```
  
  
**Quantize**

    ```bash
    pip install 'modelctl[quantize]'     # INT8용 ml_dtypes 설치
    ```
  

**quantize와 tensorflow는 상호 배타적입니다**

`[quantize]` extra는 `[tensorflow]`/`[all]` extra가 허용하는 것보다 더 새로운
`ml_dtypes`가 필요합니다(TensorFlow는 `ml-dtypes<0.4`로 제한). **한 프로필 또는 다른
프로필**을 설치하세요 — 한 환경에서 TensorFlow로 convert, 다른 환경에서 quantize.

## 자체 완결적인 소스를 전달하세요

convert 머신이 **학습 코드 없이** 로드할 수 있는 모델을 주세요:

- **PyTorch → TorchScript `.pt`** (`torch.jit.save`). 순수 `state_dict`는 거부됩니다
  (아키텍처 없음). pickle된 `nn.Module`은 최선의 대체 경로일 뿐입니다.
- **TensorFlow → SavedModel 디렉터리** (잘 지원되는 경로; `.keras`/`.h5`도 동작).

이유: `convert`를 실행하는 워크스테이션은 모델 클래스 정의가 필요 없으며, export는
아티팩트만으로 재현됩니다.

## 항상 `--target-ort`를 기기에 맞춰 고정하세요

opset 지원 여부는 워크스테이션이 아니라 **엣지** ONNX Runtime 기준으로 확인됩니다.
기기 버전을 고정해서, 너무 새로운 export가 현장이 아니라 책상 위에서 실패하게 하세요:

```bash
modelctl validate --model model.onnx --target-ort 1.18
```

기기의 런타임이 실행할 수 없는 opset으로 export된 모델은 "여기선 되고 저기선 안 되는"
가장 흔한 함정입니다. 고정이 번들 출고 전에 이 함정을 막습니다.

## `validate`를 릴리스 게이트로 다루세요

검증은 fail-soft입니다 — 첫 문제에서 멈추지 않고 **모든** 검사를 실행하고 각각을
보고합니다. exit가 0이 아니면 출고를 막으세요. 전체 목록:

| 검사 | 확인 내용 |
|------|-----------|
| ONNX schema valid | `onnx.checker`가 그래프를 허용 |
| Opset supported | opset ≤ 엣지 ORT의 최대값 |
| Single input tensor | 엔진이 정확히 하나의 입력을 위치 기반으로 feed |
| Input dtype float32 | 엔진이 float32를 feed |
| Input rank | 선언된 shape와 일치(제공된 경우) |
| ONNX Runtime load | 런타임이 세션을 인스턴스화 |
| Test inference | 0으로 채운 forward pass 성공 |

도구에서 exit 코드를 매핑하세요:

| Exit | 의미 |
|------|------|
| `0` | 모든 검사 통과 |
| `1` | 검증 검사 실패 |
| `3` | converter 프레임워크(torch/TF) 미설치 |
| `5` | convert 후 검증 실패 |
| `7` | 양자화 실패(예: `ml_dtypes` 누락) |

## 의도적으로 양자화하고, 다시 검증하세요

Dynamic INT8(`modelctl quantize` 또는 `convert`/`package`의 `--quantize`)은 정확도를
크기·속도와 맞바꿉니다. 두 가지 규칙:

1. **양자화된 모델을 다시 검증**하고 대표 데이터로 예측 품질을 확인한 뒤 출고하세요 —
   양자화는 출력을 이동시킬 수 있습니다.
2. **줄어든다고 가정하지 마세요.** 아주 작은 모델에서는 INT8의 scale/zero-point 노드가
   절감분을 넘어설 수 있습니다. 이득은 실제 크기의 모델에서 나타납니다.

**Quantize에는 extra가 필요합니다**

`quantize` / `package --quantize`에는 `pip install 'modelctl[quantize]'`가 필요합니다.
없으면 누락 모듈을 명시한 exit `7`이 나옵니다 — 설치 후 다시 실행하세요.

## 재현 가능하고 검증 가능한 번들을 출고하세요

timestamp를 고정해 재빌드가 byte-identical이 되게 하세요 — air-gapped 박스로 올릴 때
감사와 변경 검토에 필수입니다:

```bash
modelctl package --model model.onnx --name pump-anomaly-v1 --timestamp 1700000000
# 또는: export SOURCE_DATE_EPOCH=1700000000
```

그런 다음 활성화 전에 **기기에서** 무결성을 확인하세요:

```bash
cd pump-anomaly-v1 && sha256sum -c checksums.sha256
```

`checksums.sha256`은 `model.onnx`, `metadata.json`, `input_schema.json`을 포함하므로
변조나 부분 복사가 추론 시점이 아니라 엣지에서 잡힙니다.

## 엣지 입력 계약을 준수하세요

런타임은 **float32 텐서 하나를 위치 기반으로** feed합니다. 다중 입력이나 float32가
아닌 모델은 *로드*되지만 박스에서 추론 시 죽습니다. `validate`가 워크스테이션에서 이를
잡아줍니다 — export 모델을 단일 float32 입력으로 설계하고, **window size**와 **특징
수**를 페어링할 datasource와 일치시키세요([모델 배포](/ko/configure/models/) 참조).

## `validate`를 CI에 연결하세요

모든 모델 빌드에서 `validate`를 실행하고 exit `0` 기준으로 아티팩트를 게이팅하세요. 이
검사는 torch/TF가 필요 없으므로 잡이 가볍게 유지됩니다:

```bash
pip install modelctl
modelctl validate --model build/model.onnx --target-ort 1.18 || exit 1
```

도달할 수 없는 기기에서 발견하는 대신 파이프라인에서 잘못된 export를 잡으세요.

## 다음 단계

  - [모델 준비하기 (방법)](/ko/configure/prepare-models/) — convert → validate → quantize → package 명령.
  - [모델 배포](/ko/configure/models/) — ONNX 업로드, datasource와 페어링, 활성화.
  - [운영 모범 사례](/ko/operate/best-practices/) — 프로덕션에서 박스를 건강하게 운영.
