sklearn과 ONNX는 같은 질문의 답이 아니다. “LR 하나 학습하는데 뭘 쓰지?“로 시작해서 이 둘을 나란히 놓는 순간, 비교 자체가 착시가 된다. 한 쪽은 모델을 학습시키는 프레임워크고, 다른 쪽은 학습된 모델을 담아 운반하는 포맷이다. 같은 레이어에 속하지 않는다.

sklearn과 ONNX를 고르는 결정 자체보다, 왜 “sklearn이냐 ONNX냐"가 애초에 성립하는 질문이 아닌지가 먼저다.

프레임워크 비교의 전제

“sklearn vs ONNX"를 검색하면 두 도구가 같은 역할을 놓고 경쟁하는 것처럼 묶여 나온다. 장단점 표, 벤치마크, 사용 예시가 나란히 붙는다. 이 배치 자체가 착시를 만든다.

sklearn은 데이터를 받아 모델을 학습시키는 라이브러리다. LogisticRegression, RandomForest, GradientBoosting — 학습 알고리즘과 그 구현이 들어 있다. 학습이 끝나면 결과 모델 객체를 .pkl 파일로 저장하고, Python 프로세스에서 다시 읽어 predict을 실행한다. 학습부터 서빙까지 Python 생태계 안에서 완결된다.

ONNX에는 학습 알고리즘이 없다. ONNX가 제공하는 것은 “이미 학습된 모델"을 프레임워크 중립적으로 표현하는 포맷이다. PyTorch에서 학습한 트랜스포머도, sklearn에서 학습한 LR도, 모두 같은 ONNX 그래프로 변환할 수 있다. 그다음에 어떤 런타임에서든 그 그래프를 실행하면 된다.

정리하면 — 한 쪽은 학습자고 다른 쪽은 운송 수단이다. “학습자와 운송 수단 중 뭘 쓸까"라는 질문은 성립하지 않는다. 둘이 같이 움직이거나, 운송 수단이 필요 없거나 둘 중 하나다.

sklearn

sklearn은 두 가지 일을 한 번에 한다. 모델을 학습시키는 것, 그리고 학습된 모델을 Python 객체로 저장해 다시 읽는 것.

from sklearn.linear_model import LogisticRegression
import joblib

model = LogisticRegression()
model.fit(X_train, y_train)
joblib.dump(model, "model.pkl")

.pkl 파일은 Python의 네이티브 직렬화 포맷을 따른다. Python 외 언어에서는 읽지 못한다. 같은 sklearn 버전, 같은 NumPy 버전이 설치된 환경이어야 안전하게 재로딩된다. 대신 학습 → 저장 → 서빙이 하나의 파이프라인에서 끊김 없이 연결된다.

대부분의 ML 코드는 Python으로 학습하고 Python 프로세스로 서빙한다. 이 경로에 다른 레이어를 추가할 이유가 없다면, sklearn이 직접 내놓는 저장 포맷이 가장 짧은 길이다.

ONNX

ONNX는 프레임워크-중립 중간 표현(Intermediate Representation)이다. 모델의 연산 그래프를 표준화된 opset으로 기록하고, ONNX Runtime 같은 별도 런타임이 그 그래프를 읽어 실행한다.

이 한 단계가 추가되면 몇 가지 제약이 해제된다.

  • 언어 경계 — PyTorch/sklearn에서 학습한 모델을 C++, C#, Java, Rust 프로세스에서 추론할 수 있다. Python 없이.
  • 하드웨어 경계 — ONNX Runtime은 그래프 최적화와 하드웨어별 execution provider를 제공한다. 같은 모델이 CPU, CUDA GPU, TensorRT, CoreML에서 실행된다.
  • 프레임워크 경계 — 팀 안에 PyTorch 모델과 TensorFlow 모델이 섞여 있는데 서빙 스택은 하나로 통일하고 싶을 때, ONNX가 공통분모가 된다.

이 세 경계가 실제로 존재하는 프로젝트라면 ONNX 레이어는 도입 비용을 정당화한다. 경계가 없으면, 이 레이어는 파이프라인에 단계 하나를 추가하는 일 이상이 아니다.

성능 이득은 조건부다

“ONNX Runtime이 더 빠르다"는 이야기가 자주 들린다. 절반만 맞다.

ONNX Runtime은 그래프 최적화(operator fusion, constant folding)와 하드웨어 가속기(CUDA, TensorRT, OpenVINO)를 붙일 수 있다. 그래서 같은 모델을 네이티브 프레임워크보다 빠르게 실행할 수 있는 경우가 있다. 핵심은 경우가 있다는 것이다.

그 이득이 실제로 생기려면 보통 다음 중 하나 이상이 전제되어야 한다.

  • GPU나 가속기 같은 전용 하드웨어
  • Python GIL을 벗어나는 non-Python 런타임
  • 그래프가 충분히 커서 최적화 효과가 유의미한 모델

Logistic Regression은 이 중 어느 조건에도 해당하지 않는다. 가중치 벡터와 입력 벡터의 내적 한 번이 전부다. 여기에 graph fusion을 적용해도 줄일 연산이 거의 없다. CPU에서 벡터를 한 번 곱하는 작업에 ONNX Runtime과 sklearn 사이의 유의미한 지연 차이는 기대하기 어렵다.

그래서 “ONNX가 빠르다"는 문장은, 어떤 모델인지 어디서 실행하는지를 덧붙이지 않으면 참이 되지 않는다.

언제 ONNX 레이어가 필요한가

추상적인 판단 기준 몇 개보다는, ONNX를 도입했을 때 이득이 명확해지는 조건들을 나열하는 쪽이 낫다.

  • 학습 언어와 서빙 언어가 다르다. Python으로 학습하고 C++/Java/Go 서버에서 추론해야 한다. ONNX가 그 사이를 연결한다.
  • GPU나 edge 추론이 필요하다. 모델이 크거나, 지연 요구가 엄격하거나, edge device에 올려야 한다. ONNX Runtime의 execution provider가 이를 지원한다.
  • 여러 프레임워크의 모델을 한 서빙 스택으로 통일하고 싶다. PyTorch, sklearn, TensorFlow가 섞인 모델들을 같은 인퍼런스 서버에서 실행해야 한다. ONNX가 공통 포맷이 된다.
  • 학습 코드와 서빙 인프라의 수명이 분리된다. 학습 코드는 자주 리팩토링하고 싶지만 서빙 쪽 바이너리는 안정적으로 고정되어 있어야 한다. ONNX가 그 사이의 고정점이 된다.

이 조건 중 어느 것도 해당하지 않으면, ONNX 레이어가 주는 것은 “변환 단계 하나 + opset 버전 호환성 걱정 + float/double precision 디버깅"이다. 비용만 들고 얻는 것이 없는 쪽이다.

경량 LR 시나리오

경량 LR 모델을 Python 학습 + Python 서빙 경로에서 운영하는 시나리오를 가정해보자. GPU 추론은 필요 없고, 모델은 가중치 벡터 하나 크기다. 다른 프레임워크의 모델을 함께 배치할 계획도 없다. 위에서 나열한 네 조건 중 어느 것도 걸리지 않는다.

이 경우 실제 결정은 “ONNX를 쓸 것인가"가 아니라, 애초에 “ONNX 레이어가 이 그림에 들어갈 자리가 있는가"로 내려간다. 자리가 없다. sklearn이 직접 내놓는 .pkl 저장이 학습에서 서빙까지 가장 짧은 길이다.

정리

처음 질문으로 돌아가자. “sklearn과 ONNX 중 뭘 쓸까?“는 답할 수 있는 형태의 질문이 아니다. 두 도구가 같은 레이어에 속하지 않기 때문이다.

이 질문은 둘로 쪼개져야 한다. 하나는 “어떤 라이브러리로 학습할 것인가” — sklearn, PyTorch, XGBoost 같은 학습 프레임워크들 사이의 선택이다. 다른 하나는 “학습된 모델을 어떤 포맷으로 배포할 것인가” — 각 프레임워크의 네이티브 저장 포맷일 수도, ONNX일 수도 있다.

두 질문으로 분리하면, “ONNX 레이어가 필요한가"는 학습 프레임워크 선택과 독립된 문제가 된다. 그리고 가벼운 모델에서 이 질문은 대체로 “아니다"로 빠르게 닫힌다. 결론이 난 문제에 레이어를 추가할 이유는 없다.

같은 질문의 답이 아닌 두 도구를 같은 질문에 억지로 넣으면, 답은 매번 어색해진다. 질문을 먼저 다시 써야 답도 명확해진다.