본문으로 건너뛰기

© 2026 Molayo

r/LocalLLaMA분석2026. 06. 28. 19:15

순수 C 언어로 밑바닥부터 작성한 Qwen 3용 초경량 CPU 전용 추론 엔진

요약

Qwen 3 4B 이하 모델을 위해 순수 C 언어로 밑바닥부터 구현한 초경량 CPU 추론 엔진 개발기입니다. 의존성을 최소화하고 4비트 양자화와 KV 캐싱을 지원하며, 학습과 코드 명확성에 중점을 두었습니다.

핵심 포인트

  • 순수 C 언어로 작성되어 libc 외 의존성 없음
  • Hugging Face safetensors 로드 및 4비트 아핀 양자화 지원
  • KV 캐싱 및 내장 채팅 인터페이스 포함
  • 성능보다 코드의 명확성과 학습 경험에 우선순위 부여
  • OpenMP를 통한 병렬화 지원

요약(TL;DR): (매우 지저분한) 코드와 설명글은 https://github.com/jakint0sh/qwen3-engine 에서 확인할 수 있습니다.
시작하는 방법은 README를 읽어보세요.

그냥 불렛 포인트 목록을 원하는 분들을 위해:

  • 4B 이하 크기의 Qwen 3 모델을 위한 추론 엔진 (Inference engine)
  • 순수 C 언어로 밑바닥부터 작성됨
  • libc, libm, cJSON을 제외한 의존성 없음 (병렬화를 위해 컴파일할 경우 OpenMP 포함)
  • Hugging Face (HF) safetensors에서 직접 로드하며, 실행 중 4비트 아핀 양자화 (4-bit affine quantization) 수행
  • KV 캐싱 (KV caching) 지원
  • 내장된 채팅 인터페이스 제공
  • 매우 느리지만, 코드를 읽기 쉽고 다루기 쉬우며 학습용으로 좋음

이제 본격적인 이야기를 시작해 보겠습니다...

제목에서 알 수 있듯이, 저는 특히 더 작은 규모의 Qwen 3 모델을 대상으로 순수 C 언어를 사용하여 밑바닥부터 저만의 LLM 추론 엔진을 작성했습니다. 아마 왜 그런 일을 하는지 궁금하실 겁니다. 부분적으로는 LLM이 어떻게 작동하는지 몰랐고 배우고 싶었기에 저에게는 학습 경험이었고, 또 다른 부분으로는 저만의 추론 엔진을 작성해보라는 도전을 받았고, 파이썬 (Python) 라이브러리들을 단순히 이어 붙이는 쉬운 길을 택하지 않기로 결정했기 때문입니다. 저는 제법 괜찮은 C 프로그래머이며, 어차피 추론에는 속도가 필요하기 때문에 C가 이 문제를 해결하기 위한 좋은 선택이라고 생각했습니다.

결국 저는 약 일주일 반 동안 먹고, 읽고, 코드를 쓰고, 자는 과정을 반복하며 시간을 보냈고, 그 시간 동안 트랜스포머 (Transformer) 모델이 어떻게 작동하는지에 대해 아무것도 모르던 상태에서 제 코드에 모든 추론 과정을 밑바닥부터 직접 구현하는 상태에 이르렀습니다. 정말 대단한 경험이었습니다.

저는 머신러닝 (Machine Learning), 수치 해석 (Numerics), 또는 고성능 컴퓨팅 (HPC) 배경지식이 없었기 때문에, 모든 핵심 LLM 개념(토큰화 (Tokenization), 트랜스포머 수학, KV 캐싱, 양자화 (Quantization) 등)을 설명받기 위해 ChatGPT에 크게 의존했습니다. 저는 수학적 배경이 있었기에 선형 대수 (Linear algebra)나 일반적인 수학 개념은 문제가 되지 않았습니다. 하지만 만약 로봇 군주들(ChatGPT)의 도움을 받지 않았다면, 양자화 (Quantization), 소프트맥스 (Softmax) 및 이와 유사한 개념들과 관련하여 분명히 많은 문제에 부딪혔을 것입니다.

코드를 작성하면서 몇 가지 선택을 내렸습니다.

첫째로, 저는 성능보다 표현의 정확성과 명확성에 큰 우선순위를 두었습니다. 저는 매우 빠르게 진행하고 있었고, C 언어의 까다로운 부분들로부터 저를 보호할 가드레일을 초기에 마련해두지 않는다면 구현 세부 사항과 버그의 수렁에 빠지게 될 것이라는 사실을 알고 있었기 때문입니다. 저는 쉬운 길을 택해 모든 곳에 어서트 (asserts)를 배치했습니다. 이상하게도 그 어서트 중 하나라도 작동했던 기억은 단 한 번도 없지만, 제가 만약 어리석은 행동을 하더라도 런타임 (runtime)에서 이를 알려줄 것이라는 사실을 알고 있었기에 마음이 편했습니다.

불행히도, 성능을 후순위로 미루었고 현대의 컴퓨터에서 무엇이 빠르고 느린지에 대한 감각이 부족했기 때문에 (제 경험은 주로 빈티지 컴퓨팅과 어셈블리 프로그래밍 영역에 머물러 있었으며, 그곳에서는 룩업 테이블 (lookup tables)이 값을 인라인 (inline)으로 계산하는 것보다 항상 빠르고 캐시 (cache)라는 개념 자체가 없었습니다), 컴파일러 최적화 (compiler optimization)와 캐시 지역성 (cache locality) 측면에서 상당히 형편없는 설계 결정을 내리기도 했습니다. 결국, OpenMP 병렬화 (parallelization)를 적용하여 컴파일했을 때조차 제 엔진은 매우 느리며, 제 노트북(16개 스레드를 가진 i5-1240P로, CPU 연산 성능은 Apple M1과 대략 비슷함)에서 초당 단 1개의 토큰 (token)만을 생성할 수 있는 수준입니다.

둘째로, 가능한 한 구현에 대한 저의 주도권을 최대한 유지하고 싶었습니다. 이는 (합리적인 범위 내에서) 외부 코드나 라이브러리 (libraries)를 사용하지 않는 것을 의미했으며, 또한 LLM이 작성한 코드도 사용하지 않음을 의미했습니다. ChatGPT가 수학이나 LLM 구조와 엄격하게 관련되지 않은 몇 가지 구현 아이디어를 제공해주기는 했지만, 모든 코드는 제가 직접 작성했으며 구현 아이디어와 개념의 대부분은 저의 것입니다. 제가 추론 (inference)이나 그에 포함된 수학을 발명한 것은 아니지만, 이 모든 것을 스스로 해냈다고 자신 있게 말할 수 있습니다. C 표준 라이브러리 (standard library)와 수학 함수들은 사용했으며, 설정을 로드하고 세이프텐서 (safetensors) 파일 형식을 다루기 위해 JSON 파서 (parser)를 직접 작성하고 싶지 않았기에 cJSON도 사용했습니다.

그렇게 할 수도 있었겠지만, 엄청난 시간을 허비하게 되고 버그가 발생할 잠재적 원인이 될 것이라고 판단했습니다.

셋째로, 위 내용과 관련하여 가능한 한 외부 의존성 (external dependencies)을 갖지 않기를 원했습니다. 이는 C 엔진이 받아들일 수 있는 형태로 모델 가중치 (model weights)를 처리하기 위해 수많은 런타임 라이브러리 (runtime libs)가 필요한 파이썬 스크립트 (python script)를 사용하지 않음을 의미했습니다. https://github.com/adriancable/qwen3.c 에 있는 추론 엔진 (inference engine)이 채택한 방식은 가중치를 추론 엔진을 위한 특수한 바이너리 형식 (binary format)으로 변환해야 합니다. 하지만 저는 고통과 고난을 즐기는 편인 것 같습니다. 그래서 저는 가중치가 배포된 그대로(Qwen3-4B의 경우 BF16 safetensors 형식) 받아들이고, 로딩하는 동안 실시간으로 양자화 (quantize)하기로 결정했습니다. 이는 엔진을 시작하기 위해 별다른 복잡한 작업을 할 필요가 없음을 의미합니다. 그저 가중치를 다운로드하고, 컴파일한 뒤, 바로 실행하면 됩니다. 적어도 그 점은 좋네요.

넷째로, 위의 사항들과 관련하여 저는 코드가 읽기 쉽고 다루기 쉬운 (tractable) 상태이기를 원했습니다. qwen3.c 구현체는 빠르지만, 대부분의 중요한 영역에서 밀도가 매우 높습니다 (MHA를 위한 병렬화된 for 루프 내의 포인터 연산 (pointer math)은... 이해하기 쉽지 않습니다). C 언어에 매우 능숙하고 추론 (inference) 분야에 대한 전문 지식도 매우 풍부하지 않다면 머리를 싸매고 이해하기가 정말 힘듭니다. 컴팩트하고 성능이 뛰어난 런타임 (runtime)을 위해서는 괜찮고, 실제로 좋은 성능을 얻으려면 컴파일러가 최적화할 수 있는 방식으로 코드를 작성해야 하므로 사실상 그렇게 해야만 합니다. 하지만 교육용 예제로는 좋지 않습니다. 저는 제 엔진이 더 살펴보기 쉽고 이해하기 쉬우며, 누군가가 실제로 배울 수 있는 것이 되기를 원했습니다.

또한 코드를 작성하는 동안 제 자신의 코드도 이해할 수 있기를 원했습니다. 다시 말하지만, 저는 많은 작업들을 매우 빠르게 진행하고 있었고, 구현상의 어려움 때문에 발목이 잡히고 싶지 않았기 때문입니다. 만약 코드를 나중에 매우 수면 부족 상태일 미래의 제가 이해하기 쉽게 만들어 두지 않았다면, 저는 스스로에게 큰 실수를 저지르는 셈이었을 것입니다.

다섯째로, 저는 제 엔진의 아키텍처(architecture)가 현대적인 구현체들과 어느 정도 비교될 수 있기를 원했습니다. 따라서 최소한 양자화 (quantization)와 KV 캐싱 (KV caching)은 구현해야 했습니다.

더 많은 이야기를 할 수 있겠지만, 요점은 이렇습니다. 이 엔진은 대부분의 가중치(weights)에 대해 간단한 아핀 4비트 양자화 (affine 4-bit quant)를 수행함으로써 적절한 메모리 점유율로 Qwen 3 4B를 실행하며, 터미널 기반의 간단한 채팅 인터페이스가 내장되어 있어 그대로 사용할 수 있습니다.

코드는 지저분하고, 미완성된 부분이 많으며, 몇 가지 버그도 있습니다. 코드를 공유하기 전에 이것들을 정리하고 싶었지만... 실제로 이 코드를 만진 지 거의 1년이 다 되어가고 있고, 분명 저는 영원히 정리할 시간을 내지 못할 것입니다. 그래서 지저분하긴 하지만 그냥 공유합니다. 만약 이 지저분하고 거친 부분들을 정리하고 싶은 마음이 드신다면, 풀 리퀘스트 (pull requests)를 환영합니다.

또한 이 프로젝트에 관한 몇 가지 기술 문서 (technical writeup documents)도 작성했으며, 이들은 저장소(repository) 내의 "writeups" 디렉토리에 들어 있습니다. 현재로서는 대부분 그저 기록물에 불과하지만, 그럼에도 불구하고 포함하는 것이 좋다고 생각했습니다. 아마 읽으면서 웃음이 나실지도 모릅니다. 또한 코드와 함께 읽으면서 전체 구현 과정을 밑바닥부터 끝까지 설명하는 문서를 작성할까도 고민 중이었는데, 혹시 관심 있는 분이 계신다면 기꺼이 작성해 보겠습니다.

댓글과 피드백은 언제나 환영하며, 코드에 대해 어떤 질문이라도 있으시다면 최대한 신속하게 답변해 드리겠습니다! 이 기술이 내부적으로 어떻게 작동하는지 배우기 위해 이제 막 발을 들이려는 분들이 이 코드를 통해 배울 점이 있기를 바랍니다.

수정: 포맷팅 실수
submitted by /u/jakint0sh
[link] [comments]

AI 자동 생성 콘텐츠

본 콘텐츠는 r/LocalLLaMA의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0