인기 있는 LLM 추론 프레임워크인 vLLM의 경량화된 버전인 tiny-vLLm을 C++와 CUDA를 사용해 밑바닥부터 직접 구현해 보는 오픈소스 프로젝트 및 강의 자료입니다. 모델 가중치 로딩부터 PagedAttention, 연속 배치(Continuous Batching) 등 실제 운영 환경에서 필요한 핵심 기술들을 직관적으로 학습할 수 있습니다. 개발자와 강사 모두가 LLM의 작동 원리와 추론 서버 구축 과정을 깊이 있게 이해하는 데 매우 유용한 자료입니다.
번역된 본문
tiny-vllm
여러분은 이제 C++와 CUDA를 사용하여 고성능 LLM 추론 엔진을 직접 구현할 것입니다. 이 프로젝트는 vLLM의 더 어리고 작은 동생 격인 'tiny-vllm'입니다. 우리는 이 과정에서 많은 것을 배우고, 실수도 해보며, 아이디어와 수학적 원리를 밑바닥부터 직접 도출해 볼 것입니다.
이 저장소는 두 가지로 구성되어 있습니다: 1. 추론 서버의 전체 소스 코드, 2. 엔진 구현 과정을 안내하는 강의 자료. 학습 여정의 도구로 자유롭게 활용하시거나, 대학 강사님들이라면 강의용 교재로 사용하셔도 좋습니다.
이 추론 엔진은 다음과 같은 기능과 요소들로 구성됩니다:
Safetensors 포맷으로부터 실제 LLM 모델(Llama 3.2 1B Instruct) 로드
전체 LLM 순전파(Forward pass: Prefill + Decode)
CUDA 커널을 활용한 모든 연산
KV 캐시(KV cache)
정적 배치(Static batching)
연속 배치(Continuous batching)
온라인 소프트맥스(Online softmax)
FlashAttention과 유사한 PagedAttention
따뜻한 차를 한 잔 준비하고 시작해 봅시다.
tiny-vllm 목차:
소개: LLM, vLLM, 모델, 추론 서버
기술적 사전 지식
Safetensors와 모델
부동소수점(Floating-point)의 작동 원리와 bfloat16을 사용하는 이유
GPU와 CPU 메모리
단일 토큰 추론(Single token inference)
토큰화(Tokenization)
임베딩(Embeddings)
CUDA 커널 엔지니어링 - 임베딩
CUDA에서의 RMSNorm과 병렬 축소(Parallel reduction)
RoPE
잔차 연결(Residual connections)
cublasGemmEx
열 기반(Column-major)에서 행 기반(Row-major)으로의 전치(Transposition) 트릭
Prefill vs Decode
KV 캐시가 존재하는 이유
어텐션(Attention)
GQA(Grouped Query Attention)
SiLU
소프트맥스(Softmax)
인과 마스크(Causal mask)
Argmax
피드포워드 네트워크(Feed forward network)
버퍼 재사용(Buffer reuse)
정적 배치(Static batching)
연속 배치(Continuous batching)
온라인 소프트맥스(Online softmax)
페이즈드 어텐션(Paged Attention)
페이즈드 KV 캐시(Paged KV cache)
페이즈드 어텐션 CUDA 커널
소개: LLM, vLLM, 모델, 추론 서버
최근 몇 년간 일어나는 수많은 일들 속에서 길을 잃기 쉽습니다. 한번 하나씩 풀어보겠습니다.
LLM은 '모델'입니다. 물리적으로 LLM은 수많은 실수(float)들이 들어있는 파일입니다. 개념적으로 이 숫자들은 연산의 가중치(Weights)를 나타냅니다. 가중치는 학습(Training) 단계에서 학습되고 발견됩니다. 일부 연산들은 이 가중치들을 사용합니다. 모든 연산은 함수(Function)로, 어떤 데이터를 입력으로 받아 무언가를 처리한 뒤 데이터를 출력으로 생성합니다. 연산과 그 순서는 LLM의 아키텍처에 의해 정의됩니다. 모든 모델은 엔지니어와 연구원들이 설계한 고유한 아키텍처를 가지고 있습니다.
아무것도 없는 상태에서 LLM이 텍스트를 작성하기까지의 과정은 다음과 같습니다:
모델 설계 - 엔지니어와 연구원들은 PyTorch나 tinygrad 같은 텐서 라이브러리가 포함된 고급 언어(주로 Python)를 사용하여 모델의 아키텍처를 설계합니다. 모델의 소규모 버전을 학습시키고, 다양한 연산, 데이터 및 하이퍼파라미터(연산을 위한 매개변수)로 실험합니다. 이 단계는 명세(Specification)를 구체화하는 단계입니다.
모델 구현 - 최종 모델 아키텍처를 결정하고 학습할 데이터를 준비한 후, 최종 모델을 정의하는 코드를 작성합니다. 이 역시 PyTorch나 유사한 프레임워크로 작성될 수 있습니다.
모델 학습 - 선택된 모델 아키텍처는 더미 가중치(Dummy weights)로 초기화됩니다. 그런 다음 PyTorch 등을 사용하여 역전파(Backpropagation)와 같은 학습 알고리즘을 GPU나 TPU 같은 많은 하드웨어에서 실행하는 스크립트를 작성합니다. 이 단계는 많은 에너지, 비용, 컴퓨팅 파워를 소모합니다. 학습 단계의 결과물은 Safetensors 포맷과 같은 특정 형식의 모델 가중치 파일입니다. 즉, 학습 단계는 주어진 아키텍처를 사용하여 좋은 텍스트를 생성해 내는 가중치의 집합을 찾는 과정입니다.
모델 서빙 (우리가 있는 곳) - 가중치가 든 파일은 그 자체로는 컴퓨터에서 실행될 수 없습니다. 이는 실행 파일(Executable)이 아니라 수많은 숫자들일 뿐입니다. 아키텍처 역시 실행될 수 없습니다. 그것은 단지 계획, 청사진, 연산에 대한 설명일 뿐입니다. 실제로 모델을 실행하려면 아키텍처와 그 연산을 실행 가능한 코드로 변환하고, 가중치 파일을 사용해 아키텍처에 가중치를 불러오는(Load) 프로그램이 필요합니다. 연산을 구현하는 프로그램을 작성하고 프로그램이 가중치를 로드하면(가중치는 프로그램 시작 시 런타임에 로드됩니다), 마침내 모델에 프롬프트를 보내 의미 있는 응답을 얻을 수 있습니다. 모델에서 출력을 생성하는 이 과정을 '추론(Inference)'이라고 부릅니다. 그렇기에 우리가 여기서 구축하는 것을 추론 서버(Inference server) 또는 추론 엔진이라고 부르는 것입니다.
tiny-vllm You're going to build a high performance LLM inference engine with C++ and CUDA - tiny-vllm, a younger and smaller sibling of vLLM We will learn a lot along the way, make mistakes and derive the ideas and maths from scratch This repository consists of two things: 1. a full source code of the inference server and 2. a course where I lead you through the process of implementing the engine. Feel invited to use it as a learning tool on your learning path or if you are a lecturer, feel welcome to use it as a teaching resource at your university The inference engine consists of: load a real LLM model from Safetensors (Llama 3.2 1B Instruct) full LLM forward pass (prefill + decode) all computation with CUDA kernels KV cache static batching continuous batching online softmax, FlashAttention-like PagedAttention Make yourself a hot beverage and let's begin tiny-vllm Intro: LLM, vLLM, models, inference servers Technical prerequisities Safetensors and your model How floating-point numbers work and why we use bfloat16 GPU and CPU memory Single token inference Tokenization Embeddings CUDA kernel engineering - embeddings RMSNorm and parallel reduction in CUDA RoPE Residual connections cublasGemmEx The column-major to row-major transposition trick Prefill vs decode Why KV cache exists Attention GQA SiLU Softmax Causal mask Argmax Feed forward network Buffer reuse Static batching Continuous batching Online softmax Paged Attention Paged KV cache Paged Attention CUDA kernel Intro: LLM, vLLM, models, inference servers It's easy to get lost with so much going on recent years. Let's unpack it LLM is a model . Physically, LLM is a file which contains a lot of float numbers . Conceptually, these numbers represent weights of operations. Weights are learned/discovered/found during training phase. Some of the operations use these weights. Every operation is a function, which takes some data as input, do something with it and produces data as output. Operations and their order are defined by LLM's architecture. Every model has its own architecture, which is designed by engineers and researchers. The process of going from 0 to LLM writing a text is like this: Design the model - engineers and researchers use high level language like Python with tensor library like PyTorch or tinygrad to design model's architecture. They train small versions of the model, make experiments with different operations, data and hyperparameters (parameters for operations). It's the phase of figuring out the specification Implement the model - Once they decide on final model architecture and prepare the data for training, they write the code that defines the final model. It can be also in PyTorch or similar Train the model - The chosen model architecture is initialized with dummy weights. They write a script which again uses PyTorch or similar to run learning algorithm like backpropagation on a lot of hardware, like GPUs and TPUs . This phase burns a lot of energy, money and computational power. The product of training phase is a file with model weights, in some format, like Safetensors format . So, the training phase is finding such a set of weights which produces good text using the given architecture Serve the model (we are here) - The file with weights can't be ran on a computer. It's not an executable. It's a lot of numbers. The architecture can't be ran either - it's just a plan, a blueprint, a description of computation. To actually run the model, we need a program that turns the architecture and its operations into executable code and uses file with model weights to load the weights into the architecture. Once you write a program that implements the operations and once the program loads the weights (weights are loaded in the runtime of the program, at the startup), you can finally send prompts to the model and get a meaningful response. Generating an output from a model is called inference. That's why what we build here is called an inference server or inference engine Knowing the reason behind a need for an inference server, let's think why we build it in C++ and CUDA. It's because we want to maximize efficient use of the hardware and get high performance. It means that we want to get responses fast and we want to be able to handle multiple prompts at the same time. CUDA is the whole ecosystem, but also a language that you use to write code that runs on GPUs. We need to write code on GPUs, because many operations inside LLM are multiplying and adding multiple numbers. If you need to do small amount of math, CPU enough. If a lot, GPU better. LLMs are mostly about multiplying the matrices, which boils down to computing dot products of two vectors, for many numbers and for many vectors. The math of LLMs is simple, we will need basics of linear algebra and you can learn while coding and fill the gaps on the go. I find this way of JIT learning the most effective and perhaps you will like it too My take on a relationship between AI and computation which you maybe find useful is that the intelligence comes from a lot of parameters of the model and a lot of computation of input values using these parameters . There is no a single element, that you can point to and say: "this is what makes the model intelligent or useful". Every part of the model you can replace with a different one and get different tradeoffs in return, like trade accuracy for complexity. I hope I won't forget to get back to this topic later, when we touch the math of attention. Because - the default attention mechanism is very computationally complex (O(n^2*d)). And this complexity can be challenged and in fact people do it and figure out alternative attention mechanisms, like linear attention . If more people will find this course useful, I will think about another one, about ML compilers (a practical one in Python or C++ + some SSA theory) or about alternative attention mechanisms (math + CUDA kernels). If you are interested, please let me know! If you will find this course valuable, please let other people know about it Out of scope: The training phase of an LLM is something we don't do in this course. We take a trained LLM and write a program which will run this LLM fast on NVIDIA GPU for multiple requests in parallel. If you want to train your own LLM, I strongly recommend sensei Karpathy repositories like nanoGPT and llm.c and his YouTube channel . Similarly, we don't design the model, but the tensor libraries are also fascinating topic and worth understanding from scratch. George Hotz's tinygrad is a project which implements a tensor library with a very little amount of code, so if you want to get inspired and learn the internals, it's a good place to do it (also their Discord is nice )! There is also a bit older and smaller version by Andrej Karpathy - micrograd . And since I brought the Discord, I want to recommend you Mark Saroufim's GPU MODE . Many great people hanging out there! And if you feel lost with what is going on here, and you are new on your AI/ML journey, start with Jeremy Howard and Rachel Thomas fastai book . I conveniently omit the data science and engineering part here, because I don't know much about it. Probably Kaggle can be a good place to start with it and learn on-hands. Last but not least, we're going to code in C++ and CUDA and use cuBLAS where applicable. You can learn on the go. NVIDIA official resources are good and helpful Technical prerequisities You can build and run it on any platform, with minor changes, assuming you have a NVIDIA GPU. You might need to adjust some paths, like CUDA or GCC in c_cpp_propertiesjson or NVCC in CMakeLists.txt I suggest you to fork this repo and make the necessary adjustments so it works on your machine and then create a pull request to jmaczan/tiny-vllm and upstream your changes for benefit of another readers The exact setup on which I develop and test it: Linux (6.19.8 x64_64) CUDA Toolkit (13.1) C++ 17 GCC (15.2.1) The only external dependency you wil