본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 엣지 디바이스 배포를 위한 ONNX 변환 시 5가지 호환성 문제 해결 방법 및 최적화 전략

by Papa Martino V 2026. 4. 16.
728x90

ONNX(Open Neural Network Exchange)
ONNX (Open Neural Network Exchange)

딥러닝 모델을 모바일, 임베디드 시스템, IoT 기기와 같은 엣지 디바이스(Edge Device)에 배포할 때 가장 큰 장벽은 프레임워크 간의 의존성입니다. PyTorch나 TensorFlow로 학습된 모델을 가벼운 런타임에서 실행하기 위해 ONNX(Open Neural Network Exchange)로 변환하는 과정은 필수적이지만, 이 과정에서 수많은 연산자(Operator) 호환성 문제와 성능 저하가 발생합니다. 본 가이드에서는 실무에서 마주하는 5가지 핵심 해결 방법을 상세히 다룹니다.


1. 엣지 배포의 핵심: 왜 ONNX인가?

엣지 디바이스는 클라우드 서버에 비해 컴퓨팅 자원(CPU/GPU/NPU)과 메모리가 매우 제한적입니다. PyTorch 전체 라이브러리를 임베디드 장치에 올리는 것은 불가능에 가깝습니다. ONNX는 모델을 그래프 구조로 직렬화하여 특정 프레임워크 없이도 ONNX Runtime을 통해 하드웨어 가속을 활용할 수 있게 해줍니다. 하지만 변환 과정에서 '지원되지 않는 연산자(Unsupported Ops)', '동적 셰이프(Dynamic Shapes) 문제', '정밀도 손실' 등의 병목이 발생하며, 이를 해결하지 못하면 배포 자체가 무산될 수 있습니다.

2. ONNX 변환 전략 비교: 표준 변환 vs 최적화 변환

단순 변환과 실무 배포용 변환의 기술적 차이점을 표로 정리했습니다.

항목 기본 Export (Native) 최적화 Export (Optimized) 양자화 Export (Quantized)
핵심 특징 프레임워크 기본 함수 사용 그래프 퓨전 및 상수 폴딩 적용 FP32 데이터를 INT8로 변환
장점 변환 속도가 빠르고 간편함 불필요한 연산 제거로 속도 향상 모델 용량 4배 감소, 엣지 최적화
단점 엣지 기기에서 실행 불가 확률 높음 변환 로직이 복잡함 미세한 정확도 하락 발생 가능
해결 목표 단순 포맷 변경 연산 병목 해결 메모리 부족 및 저전력 해결

3. 실무 호환성 해결을 위한 7가지 핵심 실전 예제 (Python)

실제 엣지 디바이스 배포 파이프라인에서 발생하는 문제를 해결하기 위한 실무 코드 스니펫입니다.

Example 1: PyTorch 모델의 기본 ONNX Export 및 Opset 지정

엣지 런타임 버전에 맞는 올바른 Opset(Operator Set) 버전을 선택하여 호환성 오류를 방지합니다.

import torch
import torchvision.models as models

# 1. 모델 정의 (ResNet18 예시)
model = models.resnet18(pretrained=True)
model.eval()

# 2. 더미 입력 데이터 생성 (배치, 채널, 높이, 너비)
dummy_input = torch.randn(1, 3, 224, 224)

# 3. ONNX 변환 (Opset 13 이상 권장)
torch.onnx.export(model, 
                  dummy_input, 
                  "model_v1.onnx", 
                  export_params=True, 
                  opset_version=13, 
                  do_constant_folding=True, 
                  input_names=['input'], 
                  output_names=['output'])
print("변환 완료: opset 13 적용")
        

Example 2: Dynamic Axes 설정을 통한 가변 입력 해결 방법

입력 이미지의 크기가 변하거나 배치 사이즈가 가변적인 경우 엣지에서 발생할 셰이프 오류를 해결합니다.

torch.onnx.export(model, 
                  dummy_input, 
                  "dynamic_model.onnx",
                  dynamic_axes={
                      'input': {0: 'batch_size', 2: 'width', 3: 'height'},
                      'output': {0: 'batch_size'}
                  })
# 이제 런타임에서 다양한 해상도의 이미지를 처리할 수 있습니다.
        

Example 3: 커스텀 연산자(Custom Ops) 매핑 문제 해결

ONNX에서 지원하지 않는 특수 연산자를 `symbolic` 함수를 통해 표준 연산자로 우회 등록하는 방법입니다.

from torch.onnx import register_custom_op_symbolic

def my_custom_op_symbolic(g, input, parameter):
    # ONNX 표준 연산자인 'Mul'과 'Add'의 조합으로 커스텀 로직 대체
    return g.op("Add", input, g.op("Constant", value_t=torch.tensor([parameter])))

# 커스텀 네임스페이스에 등록하여 변환 시 에러 해결
register_custom_op_symbolic("custom_namespace::my_op", my_custom_op_symbolic, 9)
        

Example 4: ONNX Simpler를 이용한 그래프 최적화 및 노드 제거

변환 후 발생하는 중복된 Identity 노드나 불필요한 연산을 제거하여 엣지 가속 효율을 극대화합니다.

import onnx
from onnxsim import simplify

# 1. 모델 로드
onnx_model = onnx.load("model_v1.onnx")

# 2. 그래프 단순화 (Simplification)
model_simp, check = simplify(onnx_model)

if check:
    onnx.save(model_simp, "model_optimized.onnx")
    print("그래프 최적화 성공: 불필요한 노드가 제거되었습니다.")
        

Example 5: ONNX Runtime을 활용한 엣지 디바이스 추론 검증

실제 배포 전, 변환된 모델이 원래의 PyTorch 결과와 일치하는지 수치적으로 검증합니다.

import onnxruntime as ort
import numpy as np

# 런타임 세션 생성 (CPU 또는 가속기 설정)
session = ort.InferenceSession("model_optimized.onnx", providers=['CPUExecutionProvider'])

# 입력 데이터 준비
ort_inputs = {session.get_inputs()[0].name: np.random.randn(1, 3, 224, 224).astype(np.float32)}

# 추론 실행
ort_outs = session.run(None, ort_inputs)
print(f"추론 결과 셰이프: {ort_outs[0].shape}")
        

Example 6: 대용량 모델을 위한 Static Quantization(양자화) 적용

메모리가 극도로 제한된 엣지 기기를 위해 FP32 가중치를 INT8로 압축하여 호환성을 확보합니다.

from onnxruntime.quantization import quantize_static, CalibrationDataReader

# 1. 캘리브레이션 데이터 리더 클래스 정의 (생략 가능하나 정확도를 위해 권장)
# 2. 양자화 실행
quantize_static("model_optimized.onnx", 
                "model_int8.onnx", 
                calibration_data_reader=None) # 예시를 위해 None 설정
# 결과: 모델 용량이 약 75% 감소함
        

Example 7: 변환 실패 원인 분석을 위한 ONNX Checker 활용

모델 구조가 ONNX 명세에 맞는지 문법적 오류를 찾아내는 필수 디버깅 코드입니다.

import onnx

model = onnx.load("model_v1.onnx")
try:
    onnx.checker.check_model(model)
    print("ONNX 모델 구조에 이상이 없습니다.")
except onnx.checker.ValidationError as e:
    print(f"호환성 오류 발견: {e}")
        

4. 결론: 성공적인 엣지 배포를 위한 로드맵

ONNX 변환은 단순한 '저장' 버튼이 아니라, 모델의 아키텍처를 타겟 하드웨어에 맞춰 재설계하는 과정입니다. 엣지 디바이스 배포의 성공은 얼마나 많은 연산자를 하드웨어 가속기(NPU)에 최적화하여 태울 수 있느냐에 달려 있습니다. 실무에서는 먼저 Opset 버전을 조정해보고, 안 될 경우 Custom Symbolic을 정의하며, 마지막으로 ONNX-Simplifier를 통해 그래프를 정제하는 프로세스를 추천합니다. 이를 통해 지연 시간(Latency)은 최소화하고 배포 호환성은 극대화할 수 있습니다.

내용 출처 및 참고 문헌

  • ONNX Official Documentation 
  • PyTorch ONNX Export Guide 
  • Microsoft ONNX Runtime Guide for Mobile: https://onnxruntime.ai/docs/mobile/
  • GitHub - daquexian/onnx-simplifier: Python library for ONNX model simplification.
728x90