Qwen 3를 위한 순수 C 언어 기반의 CPU 전용 추론 엔진
A barebones CPU-only inference engine for Qwen 3, written from scratch in pure C
핵심 요약
학습 목적으로 처음부터 직접 구현한 Qwen 3용 C 언어 추론 엔진을 공개함.
- 직접 구현 — 외부 라이브러리 의존성을 최소화한 순수 C 언어 기반 엔진임
- 학습 목적 — 성능보다는 코드의 가독성과 이해 가능성에 중점을 둠
- 기능 구현 — safetensors 로드, 실시간 4비트 양자화, KV 캐싱 지원
- 오픈 소스 — 코드 정리 및 개선을 위한 풀 리퀘스트를 환영함
TL;DR: (개판인) 코드랑 설명은 여기서 봐라: https://github.com/jakint0sh/qwen3-engine
시작하는 법은 README 읽어보고.
그냥 요약만 보고 싶은 놈들을 위해 정리해준다:
- Qwen 3 4B 이하 모델용 추론 엔진
- 순수 C로 밑바닥부터 직접 짬
- libc, libm, cJSON 외엔 의존성 없음 (병렬 처리용 OpenMP는 선택 사항)
- HF safetensors에서 바로 불러오고, 4비트 아핀 양자화 실시간으로 때림
- KV 캐싱 지원
- 채팅 인터페이스 내장
- 존나 느림. 근데 코드는 읽기 쉽고 파악하기 좋아서 공부용으론 딱임
이제 잡소리 좀 풀어보자...
제목 보면 알겠지만, Qwen 3 소형 모델 타겟으로 순수 C 언어 써서 밑바닥부터 LLM 추론 엔진을 직접 만들었다. 왜 이런 짓을 했냐고 물어볼 놈들이 있을 텐데, 일단 LLM이 어떻게 돌아가는지 전혀 몰라서 공부 좀 해보려고 시작한 게 컸고, 또 하나는 직접 추론 엔진 한번 만들어보라는 도전을 받았는데 파이썬 라이브러리나 갖다 붙이는 쉬운 길은 가기 싫었거든. 나름 C 좀 다룰 줄 아니까, 어차피 추론 속도가 생명인 분야라 C가 딱이겠다 싶었지.
그래서 밥 먹고, 공부하고, 코딩하고, 자는 루틴을 한 일주일 반 정도 반복했다. 그 짧은 시간에 트랜스포머 모델이 어떻게 돌아가는지 1도 모르던 상태에서 추론 전체 과정을 밑바닥부터 직접 구현해낸 거다. 진짜 꽤나 강렬한 경험이었지.
머신러닝이나 수치 해석, HPC 쪽 배경지식이 아예 없어서 ChatGPT한테 LLM 핵심 개념(토큰화, 트랜스포머 수학, KV 캐싱, 양자화 등)을 엄청나게 물어봤다. 수학 베이스는 좀 있어서 선형대수나 일반적인 수학 개념은 문제없었는데, 양자화나 소프트맥스 같은 부분은 로봇 대장님들 도움 안 받았으면 분명히 삽질 엄청 했을 거다.
코딩하면서 몇 가지 선택을 했는데, 일단 성능보다는 코드의 명확성과 구조적 정확성을 최우선으로 뒀다. 워낙 빠르게 움직이면서 방대한 양의 메커니즘을 배우고 구현해야 했거든. 초반부터 C의 날카로운 맛에 베이지 않으려고 안전장치를 안 해두면 구현 디테일이랑 버그에 파묻혀서 허우적댈 게 뻔했으니까. 그래서 그냥 편하게 여기저기 assert를 떡칠해놨는데, 신기하게도 그 assert가 터진 적은 한 번도 없었다. 그래도 혹시나 내가 멍청한 짓을 하면 런타임이 바로 징징거려 줄 거라는 생각에 마음은 편하더라.
아쉽게도 성능을 뒷전으로 미뤄두기도 했고, 요즘 컴퓨터에서 뭐가 빠르고 느린지에 대한 감각이 좀 부족해서(내 경험은 주로 빈티지 컴퓨팅이나 어셈블리 프로그래밍 쪽이라, 룩업 테이블이 무조건 빠르고 캐시라는 개념 자체가 없던 시절이었거든) 컴파일러 최적화나 캐시 지역성 측면에서 보면 아주 개판인 설계 결정들을 좀 내렸다. 결국 OpenMP 병렬 처리를 써서 컴파일해도 엔진이 존나 느리다. 내 노트북(i5-1240P, 16스레드, CPU 성능은 대충 애플 M1이랑 비슷함)에서 초당 토큰 1개 뽑아내는 게 고작임.
둘째로, 구현 과정에서 내 손길을 최대한 많이 남기고 싶었어. 외부 코드나 라이브러리는 (합리적인 선에서) 최대한 안 썼고, LLM이 짠 코드도 일절 안 썼다. ChatGPT한테 수학이나 LLM 구조랑 직접적인 상관없는 구현 아이디어 몇 개 물어본 적은 있지만, 코드는 전부 내가 직접 짰고 핵심 아이디어나 개념도 대부분 내 머리에서 나온 거야. 뭐 추론 방식이나 수학적 원리를 내가 발명한 건 아니지만, 적어도 내 손으로 직접 다 구현했다는 건 자신 있게 말할 수 있어. C 표준 라이브러리랑 수학 함수는 썼고, 설정 파일 불러오거나 safetensors 파일 형식 다루려고 JSON 파서까지 직접 짜긴 싫어서 cJSON 정도는 갖다 썼다. 마음만 먹으면 짤 수도 있었겠지만, 시간 낭비도 심하고 버그 터질 구석이 너무 많아 보이더라고.
셋째로, 위 내용이랑 이어지는 건데 외부 의존성을 최대한 없애고 싶었어. 모델 가중치를 C 엔진이 읽을 수 있게 변환하려고 파이썬 스크립트 돌리고 런타임 라이브러리 수십 개씩 깔아야 하는 상황은 피하고 싶었거든. 같은 추론 엔진이 딱 그런 방식인데, 가중치를 엔진 전용 바이너리 형식으로 일일이 변환해야 하잖아. 근데 난 사서 고생하는 걸 좋아하는 변태라, 배포되는 상태 그대로(Qwen3-4B의 경우 BF16 safetensors) 가중치를 불러와서 로딩할 때 바로 양자화(quantize)하기로 했어. 이러면 엔진 돌리려고 복잡하게 삽질할 필요가 없지. 그냥 가중치 다운받고, 컴파일하고, 바로 실행하면 끝이야. 적어도 이 부분은 꽤 괜찮지 않냐.
