ONNX 기반 HyperCLOVA 모델 변환 및 양자화

Nemo_Nemo·2025년 11월 26일

ONNX

목록 보기
2/3
post-thumbnail

Transformer 모델을 ONNX로

HyperCLOVA 모델을 로컬에서 더 효율적으로 돌려보고 싶다는 생각이 들었다. 특히 safetensors 형태로 제공되는 모델을 ONNX로 변환해두면 CPU나 다양한 환경에서 공통적으로 추론을 돌릴 수 있고, 양자화까지 진행하면 모델 크기나 속도 측면에서 이득이 크다. 그래서 이번 글에서는

ONNX 소개 → 변환이 필요한 이유 → safetensors 기반 모델을 ONNX로 변환하는 방식 → HyperCLOVA 모델 변환 과정

순서로 정리해본다.


1. ONNX는 무엇인가

ONNX(Open Neural Network Exchange)는 여러 딥러닝 프레임워크(PyTorch, TensorFlow 등)를 하나의 통일된 표현 형식으로 묶는 모델 포맷이다.
ONNX를 사용하는 목적은 다음과 같다.

  • 프레임워크 종속성을 제거할 수 있다
  • 다양한 추론 엔진(onnxruntime, TensorRT, WebNN 등)에서 실행할 수 있다
  • 동일 모델을 더 가볍고 빠르게 돌릴 수 있다
  • CPU 성능 최적화에 강하다

HyperCLOVA처럼 크지 않은 모델을 다루는 경우 GPU가 없더라도 CPU 기반 최적화를 통해 운영할 수 있다는 점에서 ONNX는 실용성이 높다.


2. safetensors → ONNX 변환이 필요한 이유

HyperCLOVA 모델은 대체로 safetensors 포맷으로 제공된다. safetensors는 안전성과 속도 측면에서 뛰어난 포맷이지만, ONNX 런타임에서 직접 사용할 수 없기 때문에 중간 과정으로 PyTorch 모델 형태로 불러온 뒤 ONNX로 변환하는 절차가 필요하다.

일반적인 변환 흐름은 다음과 같다.

1) safetensors → PyTorch 모델 로드
2) PyTorch 모델 → ONNX(FP32) 변환
3) 변환된 FP32 ONNX를 기준(baseline) 모델로 사용
4) 필요에 따라 Dynamic Quantization 또는 Static Quantization으로 INT8 버전 생성

이렇게 하면 고정밀 모델과 경량 모델을 모두 확보할 수 있다.


3. ONNX 변환 방식 정리

HyperCLOVA처럼 Transformer 계열 구조를 가진 모델은 ONNX 변환을 두 가지 방식으로 진행할 수 있다.

3-1. optimum.exporters.onnx 사용 (권장 방식)

Hugging Face transformers 기반 모델이라면 가장 안정적인 방식이다.

from optimum.exporters.onnx import export, OnnxConfig
from transformers import AutoTokenizer, AutoModel

model_path = "./hyperclova"
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModel.from_pretrained(model_path)
model.eval()

onnx_config = OnnxConfig.from_model_config(model.config)

export(
    model=model,
    config=onnx_config,
    output="hyperclova_fp32.onnx",
)

이 방식을 사용하면:

  • dynamic axes 설정을 자동으로 처리할 수 있다

  • 모델 구조를 자동으로 분석한다

  • 최신 opset 적용도 수월하다

3-2. torch.onnx.export로 직접 변환

커스텀 구조나 지원되지 않는 모델인 경우 직접 변환해야 한다.
import torch

dummy_input = torch.randint(0, 1000, (1, 16))

torch.onnx.export(
    model,
    (dummy_input,),
    "hyperclova_fp32.onnx",
    opset_version=17,
    input_names=["input_ids"],
    output_names=["logits"],
    dynamic_axes={
        "input_ids": {0: "batch", 1: "sequence"},
        "logits": {0: "batch", 1: "sequence"},
    }
)

여기서 중요한 점은 다음과 같다.

  • model.eval() 호출이 필요하다

  • dynamic axes를 정확하게 정의해야 한다

  • opset version은 16 이상이 안정적이다

4. ONNX 양자화(INT8) 모델 생성

FP32 ONNX 모델은 성능 기준 모델(baseline)로 사용하고, 추가로 INT8 버전을 만들어두면 CPU에서 속도 개선이 크다.
가장 간단한 방식은 Dynamic Quantization이다.

4-1. Dynamic Quantization

from onnxruntime.quantization import quantize_dynamic, QuantType

quantize_dynamic(
    model_input="hyperclova_fp32.onnx",
    model_output="hyperclova_int8_dynamic.onnx",
    weight_type=QuantType.QInt8,
    optimize_model=True
)

Dynamic 방식은 별도 calibration 데이터가 필요 없고, 정확도 손실이 상대적으로 적다.

4-2. Static Quantization

Static Quantization은 calibration 데이터셋이 필요하지만, 메모리 절약 효과가 크다.

from onnxruntime.quantization import quantize_static, CalibrationDataReader, QuantType

class DataReader(CalibrationDataReader):
    def __init__(self, dataloader):
        self.dataloader = iter(dataloader)

    def get_next(self):
        try:
            batch = next(self.dataloader)
        except StopIteration:
            return None
        return {"input_ids": batch["input_ids"].numpy()}

quantize_static(
    model_input="hyperclova_fp32.onnx",
    model_output="hyperclova_int8_static.onnx",
    calibration_data_reader=DataReader(dataloader),
    weight_type=QuantType.QInt8,
    activation_type=QuantType.QInt8,
)

Static 방식은 calibration 품질에 따라 정확도 영향이 달라지기 때문에 평가가 필요하다.

5. HyperCLOVA 기반 모델 변환 과정 정리

HyperCLOVA 관련 모델을 safetensors에서 ONNX로 변환할 때의 핵심 흐름은 다음과 같다.

  1. safetensors 로드 → PyTorch 모델로 변환

  2. optimum 또는 torch.onnx.export를 사용해 FP32 ONNX 생성

  3. 이 ONNX 모델을 baseline으로 활용

  4. 필요에 따라 dynamic / static quantization 수행

  5. FP32와 INT8 모델의 정확도와 추론 속도 비교

  6. 운영 목적에 맞는 모델 선택

6. HyperCLOVA 모델을 직접 ONNX로 변환하고 Q4 양자화까지

HyperCLOVA 계열 모델을 safetensors 형태로 받아서 로컬 환경에서 테스트해보니, 원본 모델 크기가 꽤 커서 메모리 부담이 생각보다 컸다. 그래서 우선 PyTorch로 로드한 후 FP32 ONNX baseline을 먼저 만들고, 그 상태에서 바로 Q4(4-bit) 양자화 버전까지 직접 생성해봤다.

Q4 양자화는 INT8보다 더 공격적인 방식이긴 하지만, 로컬 환경에서 메모리를 크게 줄여야 할 때 상당히 도움이 된다. 실제로 변환을 마치고 나서 CPU 기반 추론 환경에서 돌려보니 메모리 점유율과 로딩 속도가 확 줄었고, 테스트 용도로는 충분히 실용적인 수준이라고 느꼈다.

전체 과정은 다음과 같은 순서로 진행했다.

  1. safetensors → PyTorch 모델 로드
  2. optimum.exporters.onnx 기반 FP32 ONNX 모델 생성
  3. 생성된 ONNX 모델을 직접 Q4로 양자화
  4. 로컬 환경에서 메모리 사용량을 크게 줄이고, 가벼운 inference 환경 확보

변환한 Q4 모델은 정리 차원에서 Hugging Face에도 업로드해두었다.
필요하면 누구나 바로 로컬에서 테스트해볼 수 있다.

🔗 내가 업로드한 Hugging Face 모델 링크:
https://huggingface.co/YOUR_MODEL_LINK_HERE

마무리

HyperCLOVA 모델을 ONNX로 변환해두면 실행 환경이 훨씬 넓어진다. 운영 환경에 따라 FP32와 INT8 버전을 선택해 사용할 수 있고, CPU 또는 Web 환경에서도 추론이 가능한 구조를 만들 수 있다.
특히 개발 과정에서 safetensors 포맷을 다루고 있다면 ONNX 변환 과정을 미리 준비해두는 것이 배포 단계에서 큰 도움이 된다.

0개의 댓글