Zig로 18개의 모델을 직접 구현하며 LLM의 실제 작동 원리 배우기
요약
Zig 언어를 사용하여 18개의 다양한 LLM 아키텍처를 밑바닥부터 직접 구현한 교육용 프로젝트 'zigllm'을 소개합니다. 텐서 연산부터 텍스트 생성까지 단계별 스택 구조를 통해 LLM의 내부 작동 원리를 깊이 있게 학습할 수 있습니다.
핵심 포인트
- Zig 언어로 구현된 18개 모델(LLaMA, Mistral, GPT-2 등) 학습 프로젝트
- 로우 텐서 연산부터 추론까지 6단계 레이어 구조로 설계
- 단순 실행을 넘어 모델의 수학적/구조적 원리 이해에 집중
- LLaMA를 포함한 주요 아키텍처 간의 차이점 비교 가능
LLM (Large Language Models) 기반으로 기능을 출시하는 대부분의 사람들은 모델의 내부를 본 적이 없습니다. 저 또한 그 그룹에 속해 있었습니다. 저는 API를 호출하고, 프롬프트 (Prompt)를 튜닝하고, GGUF 파일을 양자화 (Quantize)할 수는 있었지만, RMSNorm이 무엇을 하는지 또는 RoPE가 왜 존재하는지는 여전히 설명할 수 없었습니다. 모델은 그저 온도 (Temperature) 조절 노브가 달린 블랙박스 (Black box)일 뿐이었습니다.
튜토리얼들은 도움이 되지 않았습니다. 어떤 것들은 nn.Linear 뒤로 모든 것을 숨겨버리는 40줄짜리 PyTorch 스크립트를 건네주거나, 어떤 것들은 수학적 원리가 커널 디스패치 (Kernel dispatch)와 메모리 풀 (Memory pools) 아래에 파묻혀 있는 프로덕션용 C++ 엔진에 당신을 던져놓습니다. 그 중 어느 것도 텐서 (Tensor)에서 토큰 (Token)에 이르는 전체 경로를 볼 수 있게 해주지 않습니다.
그래서 저는 zigllm을 만들었습니다. 이는 로우 텐서 연산 (Raw tensor operations)부터 텍스트 생성 (Text generation)에 이르기까지, Zig 언어로 구현한 트랜스포머 (Transformer) 아키텍처의 교육용 구현체입니다. 이 프로젝트는 18개의 모델 제품군 (LLaMA, Mistral, GPT-2, Falcon, Mamba, BERT 등)을 구현하며, 실행 가능한 문서 역할을 겸하는 285개 이상의 테스트를 포함하고 있습니다. 모든 구성 요소는 단순히 빠르게 실행되기 위해서가 아니라, 그것이 왜 작동하는지를 가르치기 위해 작성되었습니다.
핵심 아이디어: 이전 레이어를 기반으로 구축된 6개의 레이어
전체 프로젝트는 스택 (Stack) 구조로 조직되어 있습니다. 바닥에서 시작하여 위로 올라가며, 각 레이어는 바로 아래에 있는 레이어에만 의존합니다:
6. Inference 텍스트 생성 (Text generation), 샘플링 (Sampling), KV 캐싱 (KV caching), 스트리밍 (Streaming)
5. Models LLaMA, GPT-2, Mistral, Falcon, GGUF 로딩, 토큰화 (Tokenization)
4. Transformers 멀티 헤드 어텐션 (Multi-head attention), 피드 포워드 네트워크 (Feed-forward networks), 전체 블록 (Full blocks)
...
이러한 순서가 바로 교육 방식입니다. 행렬 곱셈 (Matrix multiplication)을 이해하기 전에는 어텐션 (Attention)을 이해할 수 없습니다. 어텐션 (Attention)과 정규화 (Normalization), 그리고 RoPE를 이해하기 전에는 전체 LLaMA 블록을 이해할 수 없습니다. 당신이 6번 레이어에 도달하여 첫 번째 토큰을 생성할 때쯤이면, 그 위의 어떤 것도 마법처럼 느껴지지 않을 것입니다. 왜냐하면 당신이 그 아래의 모든 레이어를 직접 구축했기 때문입니다.
그 스택 위에 쌓인 18개의 아키텍처는 실제 세계의 LLM 사용 사례의 약 80%를 커버합니다.
핵심은 이 모든 모델을 프로덕션(production) 환경에서 실행하는 것이 아닙니다. 실제로 엔드 투 엔드(end-to-end)로 실제 추론(inference)이 가능하도록 설계된 경로는 LLaMA 하나뿐입니다. 다른 모델 계열들은 읽고 비교하기 위해 존재하는 것이며, 그것이 바로 이 프로젝트의 목적입니다. 여러분은 LLaMA, Mistral, Falcon, GPT-2가 대부분 서로 다른 노브(knobs)를 가진 거의 동일한 기계라는 점과, Mamba와 BERT는 진정으로 다른 종류의 존재라는 것을 알게 될 것입니다.
왜 Zig인가, 그리고 엔지니어링은 어떻게 유지되는가
Zig는 직접 사용해 보기 전까지는 묘한 선택지처럼 보입니다. Zig는 런타임(runtime)과 가비지 컬렉터(garbage collector) 없이 수동 메모리 제어(manual memory control), 컴파일 타임 제네릭(comptime generics), 그리고 일급 시민 SIMD(first-class SIMD)를 제공합니다. 이 조합은 추론(inference)이 요구하는 바로 그것입니다. 결정론적 메모리(deterministic memory)를 얻을 수 있으며(학습할 때는 모든 할당(allocation)을 직접 볼 수 있으므로 매우 중요합니다), 인라인 어셈블리(inline assembly)로 내려가지 않고도 SIMD 인트린직(intrinsics)을 사용할 수 있습니다.
이 리포지토리의 최적화 방식은 가독성을 위해 작성되었을 뿐, 실제 프로덕션 엔진에서 발견되는 진짜 최적화 기법들입니다:
- KV 캐싱 (KV caching): 새로운 토큰이 생성될 때마다 전체 시퀀스에 대해 어텐션(attention)을 다시 계산하지 않도록 합니다.
- SIMD 가속 (SIMD acceleration) (AVX, AVX2, NEON 자동 감지): 행렬 연산(matrix ops)에서 3~5배의 속도 향상을 제공합니다.
- 18개 이상의 양자화 (quantization) 포맷: 최대 95%의 메모리 절감을 지원합니다.
- 메모리 맵 모델 로딩 (Memory-mapped model loading): 대용량 GGUF 파일을 사전에 RAM으로 모두 읽어 들일 필요가 없습니다.
이 각각은 하나의 교훈입니다. KV 캐싱은 그것이 없을 때 왜 생성 과정이 이차 함수적(quadratic)으로 늘어나는지를 가르쳐 줍니다. 양자화(Quantization)는 그 어떤 블로그 포스트보다도 메모리와 정밀도(precision) 사이의 트레이드오프(trade-off)를 명확하게 이해시켜 줍니다.
사용 방법
테스트 자체가 튜토리얼이기 때문에, 시작하는 방법은 클론(clone) 한 번과 명령어 한 줄이면 충분합니다:
git clone https://github.com/cognisoc/zigllm.git
cd zigllm
zig build test # 285개 이상의 모든 테스트
...
단일 레이어(layer)의 테스트를 실행하는 것이 해당 레이어를 공부하는 방법입니다. 각 테스트는 개념을 입증하고 수학적 계산을 검증하므로, 어설션(assertion) 실패는 수학적으로 실제로 무엇이 요구되는지에 대한 교훈이 됩니다.
이 예제들은 가이드 투어 역할을 하며, 추론 레이어(inference layer) 자체 문서(docs/06-inference/)는 각 최적화가 적용될 때마다 토큰당 비용(per-token cost)이 어떻게 감소하는지 보여줍니다:
최적화 미적용: ~200ms/token (naive)
KV 캐싱 (KV caching) 적용: ~50ms/token (4배)
모든 최적화 적용: ~5ms/token (총 40배)
이것이 직접 구축하는 것의 진정한 가치입니다. 이 수치는 README에 적힌 주장이 아니라, 여러분이 직접 재현하고 설명할 수 있는 것입니다. 즉, KV 캐싱(KV caching)만으로 4배가 빨라지며, 나머지는 그 위에 쌓인 SIMD와 양자화(quantization)를 통해 이루어진다는 사실을 말이죠.
동일한 문서들은 메모리에 대해서도 정직하게 다루고 있으며, 이는 반드시 내재화해야 할 내용입니다. Q4 양자화가 적용된 7B급 모델의 파라미터(parameter)는 약 3.5GB이지만, 2k 컨텍스트(context)에 대한 KV 캐시(KV cache)가 시퀀스당 약 1.5GB를 추가하므로, 실제 최소 요구량은 3.5GB가 아니라 약 8GB입니다. 사람들은 파라미터 수를 인용하지만, 실제로 시스템의 메모리 부족(OOM, Out of Memory)을 일으키는 것은 워킹 셋(working set)입니다. 여러분이 직접 작성한 코드에서 이 두 수치가 모두 도출되는 것을 확인하는 것이 핵심입니다.
examples/에는 더 많은 예제가 있습니다: 개요를 위한 simple_demo.zig, 실제 사전 학습된(pre-trained) 모델을 로드하기 위한 gguf_demo.zig, 그리고 18개의 모델 제품군을 나란히 비교하기 위한 model_architectures_demo.zig가 있습니다.
이 프로젝트가 적합하지 않은 경우
이 부분이 중요하므로 솔직하게 말씀드리겠습니다.
zigllm은 프로덕션용 추론 엔진(production inference engine)이 아니며, 이를 프로덕션 환경에 배포해서는 안 됩니다. 자체적인 동등성 분석(parity analysis)에 따르면, 프로덕션 측면에서 llama.cpp의 약 40% 수준으로 평가됩니다. 양자화(quantization) 지원 범위는 llama.cpp가 제공하는 것에 비해 극히 일부이며, 하드웨어 가속(hardware acceleration)은 CPU에 집중되어 있고, 광범위한 멀티 모델 서빙(multi-model serving)은 목표가 아닙니다. 실제 사용자에게 70B 모델을 서비스해야 한다면 llama.cpp나 vLLM을 사용하십시오. 그것들이 바로 그런 용도로 만들어진 도구들입니다.
또한, 이것은 Zig로 작성할 수 있는 가장 빠른 코드도 아닙니다. README에는 미세 최적화(micro-optimization)보다 교육적 명확성(educational clarity)을 우선시한다고 명시되어 있습니다. 읽기 쉬운 루프(loop)와 영리한 루프 사이에서 선택해야 했을 때, 읽기 쉬운 루프가 승리했습니다. 이는 교육용 프로젝트로서는 올바른 선택이지만, 서비스용 프로젝트로서는 잘못된 선택입니다.
그리고 만약 내부 동작 원리(internals)를 배우고 싶지 않다면, 이는 단순히 Ollama를 실행하는 것보다 나을 것이 전혀 없습니다. 여기에는 지름길이 없습니다. 가치는 전적으로 코드를 읽고 테스트를 실행하는 데 있습니다. 그 과정을 건너뛴다면, 전체 과정을 건너뛰는 것과 같습니다.
핵심 요약 (Takeaways)
- LLM을 마법처럼 취급하는 것을 멈추는 가장 빠른 방법은, 메모리를 숨기지 않는 언어를 사용하여 텐서(tensor)부터 직접 구축해 보는 것입니다.
- 점진적인 레이어 스택(기초, 선형 대수 (linear algebra), 기본 요소 (primitives), 트랜스포머 (transformers), 모델, 추론 (inference))을 쌓아가는 방식이 그 어떤 단일한 하향식 (top-down) 스크립트보다 더 나은 학습 순서입니다.
- 실행 가능한 문서로서의 테스트 (Tests as executable documentation)는 과소평가되어 있습니다. 하나의 개념을 각각 증명하는 285개 이상의 테스트가 그 어떤 양의 산문보다 낫습니다.
- Zig는 이를 위해 진정으로 적합한 언어임이 드러났습니다: 컴파일 타임 (comptime), SIMD, 그리고 수동 메모리 관리 (manual memory)를 지원하며, 런타임 (runtime)이 방해하지 않습니다.
코드, 6개 레이어 전체에 대한 문서, 그리고 모든 예제는 여기에 있습니다:
https://github.com/cognisoc/zigllm
단순히 어텐션 (attention)과 KV 캐싱 (KV caching)을 사용하는 대신 실제로 이해하고 싶다면, 저장소를 클론하고 zig build test-transformers를 실행하세요. 만약 특정 레이어의 설명이 불분명하다면, 그것은 교육 방식의 버그이며, 저는 진심으로 이슈 제기를 환영합니다. 직접 사용해 보시고(Kick the tyres), 교육적 명확성을 개선하는 PR (Pull Request)은 언제든 환영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기