About

ONNX 는 모델이 아니라 포장 규격이다 — Xenova 는 BERT 를 만든 게 아니다

누가 학습시켰나와 어떤 상자에 담아 배포하나는 따로 논다

ONNX 는 모델이 아니다. 계산 순서(그래프)와 가중치를 한 파일에 담는 포장 규격이다. 누가 모델을 학습시켰는지와 어떤 포장으로 배포하는지는 따로 논다. 이걸 알면 Hugging Face 에서 「이 모델 브라우저에서 돌아가?」를 저장소 이름이 아니라 파일 목록으로 판단할 수 있다.

계기 — 한국어 BERT 를 못 넣었다

BERT 계열 모델 6 개를 브라우저(transformers.js)로 돌리는 실험 앱을 만들었다. 쓸 수 있는 건 Xenova/... 같은 ONNX 변환본뿐이었다. 한국어 BERT 인 klue/bert-base 는 Hugging Face API 로 봤을 때 .onnx 파일이 0 개라 넣지 못했다(2026-09-19 확인).

그러다 나온 질문이 「사전학습 · 파인튜닝은 Xenova 가 한 건가?」였다. 헷갈리는 게 당연하다. 저장소 이름에 만든 사람과 포장한 사람이 섞여 있으니까.

저장소 이름 하나를 풀어 보면

Xenova/bert-base-multilingual-uncased-sentiment 는 네 단계를 거쳤다.

Who did what in Xenova/bert-base-multilingual-uncased-sentimentGooglepre-trainingWikipedia, 104 languagesNLP Townfine-tuningproduct review starsXenovaONNX exportquantize q8 / fp16transformers.jsrunWASM / WebGPUTraining: the numbers get decided herePackaging and running: same numbers

학습은 앞의 두 단계에서 끝났다. Google 이 104 개 언어 위키백과로 사전학습했고, NLP Town 이 상품 리뷰 별점으로 파인튜닝했다. Xenova 는 같은 가중치를 ONNX 상자에 옮겨 담고 양자화(q8, fp16 처럼 숫자를 더 작은 형식으로 줄이는 것)한 버전을 같이 올렸다. transformers.js 는 그걸 ONNX Runtime(WASM 또는 WebGPU)으로 돌린다.

PyTorch 체크포인트와 비교하면

PyTorch 체크포인트 (.bin · .safetensors) ONNX (.onnx)
안에 든 것 가중치(숫자)만 계산 그래프 + 가중치
돌리려면 Python + 모델 클래스 코드(transformers 라이브러리) ONNX Runtime 만 있으면 됨
어디서 도나 주로 Python 서버 브라우저(WASM · WebGPU), C#, Java, 모바일 등
겪은 제약 — 변환할 때 정한 출력만 나온다. 층별 hidden state · attention 값은 밖에서 못 봤다

마지막 줄이 실제로 아팠다. 실험 앱에서 「문장이 인코더 안에서 어떻게 바뀌나」를 보여 주고 싶었는데, ONNX 변환본은 마지막 층 출력만 내놓았다. 포장할 때 문을 몇 개 낼지 정해 버리는 셈이다.

파일 하나하나도 역할이 갈린다.

파일 역할 어느 단계의 결과물
tokenizer.json 어휘 목록 사전학습 데이터로 만든 어휘
config.json 층 수 · 차원 · 헤드 수 = 설계도의 숫자 모델 설계
onnx/model_*.onnx 가중치 + 계산 그래프 사전학습(+파인튜닝) 결과

직접 재 본 숫자 (2026-09-19, macOS · Chrome · WebGPU shader-f16 사용 가능)

Xenova/paraphrase-multilingual-MiniLM-L12-v2 의 파일 크기는 model_fp16.onnx 235MB, model_quantized.onnx 118MB, tokenizer.json 17MB 였다. 같은 가중치인데 포장 방식에 따라 두 배 차이가 난다.

이 MiniLM 으로 문서 조각 372 개를 임베딩해 봤다. WebGPU + fp16 은 첫 다운로드까지 포함해 약 21 초. WASM + q8 은 단일 스레드(crossOriginIsolated=false)라 1 분이 지나도 25% 쯤이었다. 끝까지는 재지 않았다.

몇 가지 더 걸린 것.

npm 패키지가 무겁다. @huggingface/transformers 4.3.0 을 npm 으로 넣으면 onnxruntime-node(node_modules 에서 289MB)와 sharp 가 딸려 온다. 브라우저에서만 쓸 거라 CDN(jsDelivr) 버전 고정 import 로 바꿨다.

파일이 디스크에 이름으로 안 생긴다. 브라우저는 받은 파일을 Cache Storage transformers-cache 에 요청 URL 을 키로 저장한다. config.json 같은 파일을 찾아도 안 나온다.

그래서 모델을 고를 때

브라우저에서 돌릴 모델이면 저장소의 Files 탭에 onnx/ 폴더가 있는지부터 본다. 없으면 직접 변환해야 한다. 학습한 곳과 포장한 곳이 다를 수 있으니, 라이선스와 학습 데이터는 원래 저장소(위 예에선 nlptown/...)의 모델 카드에서 확인한다.

모델을 브라우저에서 돌릴지 서버에서 돌릴지, 쓸 모델에 변환본이 없을 때 어떻게 할지 막혀 있다면 negabaro 에 물어봐도 된다. 위 실험을 직접 굴려 본 경험으로 같이 볼 수 있다.

핵심 개념

1

저장소 이름의 앞부분은 「포장한 사람」일 수 있다 — 원래 모델은 모델 카드에서 찾는다

2

브라우저용이면 Files 탭에서 onnx/ 폴더부터 확인한다

3

같은 가중치라도 fp16 / q8 에 따라 크기가 두 배 다르다 — 속도 · 정확도와 맞바꾼다

4

층 안쪽 값이 필요하면 변환할 때 출력을 지정해야 한다 — 기본 변환본엔 마지막 층만 있다

사용 사례

브라우저에서 임베딩 · 감성 분석 — transformers.js + ONNX Python 없이 C# · Java 서버에서 모델 실행 — ONNX Runtime
← Jev — 글을 안 쓰는 LLM, 판단만 빠르게 PyTorch · Hugging Face · Ollama 를 같은 선반에 놓지 마라 — AI 프레임워크는 세 층이다 →