Nano-vLLM 분석: 1200줄의 코드로 이해하는 LLM 인퍼런스의 심장


LLM을 프로덕션 레벨에서 서빙해 본 엔지니어라면 vLLM 이라는 이름을 모를 수가 없을 겁니다. OpenAI나 Anthropic 같은 거대 기업들이 어떻게 그 많은 트래픽을 처리하면서도 낮은 Latency를 유지하는지 궁금해하다 보면, 결국 도달하게 되는 종착지 중 하나가 vLLM이기 때문이죠.

하지만 vLLM의 코드베이스는 이제 너무 거대해졌습니다. 수많은 하드웨어 백엔드와 모델 아키텍처를 지원하려다 보니, 정작 핵심 로직인 ‘스케줄링’과 ‘메모리 관리’가 어떻게 돌아가는지 파악하기가 쉽지 않습니다. 주니어들이 “vLLM 내부 좀 뜯어보고 싶습니다”라고 하면, 솔직히 어디서부터 보라고 해야 할지 난감할 때가 많았거든요.

그런데 최근 Hacker News에서 흥미로운 프로젝트가 화제가 되었습니다. 바로 Nano-vLLM 입니다. 단 1,200줄의 파이썬 코드로 vLLM의 핵심 기능을 구현했다는 이 프로젝트, 과연 찍먹해볼 가치가 있을까요?

왜 1200줄짜리 장난감이 중요한가?

보통 이런 ‘Nano’ 류의 프로젝트들은 기능이 너무 빈약해서 실무에 도움이 안 되는 경우가 많습니다. 하지만 Nano-vLLM은 다릅니다. DeepSeek의 기여자가 작성했다는 배경도 흥미롭지만, 이 프로젝트가 구현하고 있는 기능의 밀도가 상당히 높습니다.

  • Prefix Caching
  • Tensor Parallelism
  • CUDA Graph Compilation
  • Torch Compilation

이 모든 걸 1,200줄에 담았습니다. 벤치마크상으로는 원본 vLLM과 대등하거나 소폭 앞서는 성능을 보여준다고 하니, 단순한 장난감으로 치부하기엔 아까운 물건입니다.

아키텍처: OS를 닮아가는 LLM 엔진

LLM 인퍼런스 엔진을 뜯어보면 볼수록, 이건 일종의 운영체제(OS) 와 닮았다는 생각이 듭니다. 한정된 자원(GPU Memory, Compute)을 여러 프로세스(Request)가 효율적으로 나눠 써야 하니까요. Nano-vLLM의 구조를 보면 이 점이 명확히 보입니다.

가장 핵심은 Producer-Consumer 패턴 입니다. generate 요청이 들어오면 바로 GPU로 던지는 게 아니라, Scheduler의 큐에 넣습니다. 그리고 별도의 루프가 큐에서 요청을 꺼내 배치(Batch)로 묶어 처리합니다.

여기서 엔지니어링의 영원한 숙제인 Throughput vs Latency 트레이드오프가 발생합니다. 배치를 크게 잡으면 처리량은 늘어나지만, 개별 요청의 응답 속도는 느려집니다. Nano-vLLM은 이 스케줄링 로직을 아주 직관적으로 보여줍니다.

Prefill과 Decode: 두 개의 다른 세상

LLM 인퍼런스가 까다로운 이유는 Prefill(프롬프트 처리) 단계와 Decode(토큰 생성) 단계의 연산 특성이 완전히 다르기 때문입니다.

  • Prefill: 한 번에 많은 토큰을 병렬 처리 (Compute Bound)
  • Decode: 이전 토큰에 의존하여 하나씩 생성 (Memory Bandwidth Bound)

스케줄러는 이 두 가지 상태의 요청들을 적절히 섞어서 GPU가 놀지 않게 만들어야 합니다. Nano-vLLM의 스케줄러는 Waiting QueueRunning Queue를 관리하며, GPU 메모리가 부족해지면 실행 중인 요청을 다시 대기열로 보내는 Preemption(선점) 기능까지 구현되어 있습니다. OS의 스케줄러가 하는 일과 똑같죠.

Block Manager: PagedAttention의 재발견

이 프로젝트에서 가장 인상 깊은 부분은 메모리 관리, 즉 KV Cache 관리입니다. 가변 길이의 시퀀스를 처리하기 위해 메모리를 미리 잡아두는 것은 비효율의 극치입니다. 마치 OS가 페이징(Paging) 기법으로 메모리 파편화를 해결하듯, vLLM은 PagedAttention 이라는 기법을 사용합니다.

Nano-vLLM은 시퀀스를 256 토큰 단위의 고정 크기 ‘블록’으로 쪼개서 관리합니다. 흥미로운 점은, Hacker News 댓글에서 한 유저가 “코드를 보니 PagedAttention이라는 용어가 한 번도 안 나온다. 이거 AI가 짠 코드 아니냐?”라고 의혹을 제기했다는 겁니다.

저자의 답변이 걸작입니다. 본인은 ML 배경이 아니라 클라우드 인프라 엔지니어 출신이라, 학술적인 용어인 PagedAttention은 몰랐지만 “코드를 뜯어보니 블록 단위로 관리하는 게 효율적이라 그렇게 짰다” 는 것입니다. 즉, 인프라 관점에서 최적화를 하다 보니 자연스럽게 PagedAttention과 같은 결론에 도달했다는 거죠. 저는 이 지점이 오히려 이 프로젝트의 진정성을 보여준다고 생각합니다.

또한, 블록의 해시(Hash)를 계산해서 중복된 블록이 있으면 재사용하는 Prefix Caching 로직은 시스템 프롬프트를 많이 쓰는 챗봇 서비스에서 왜 vLLM이 필수적인지를 잘 보여줍니다.

Model Runner: 텐서 병렬화와 CUDA 그래프

파이썬으로 고성능 인퍼런스 엔진을 짤 때 가장 큰 병목은 파이썬 인터프리터 자체의 오버헤드입니다. 특히 Decode 단계처럼 작은 연산을 수천 번 반복할 때, GPU는 연산을 끝내고 다음 명령을 기다리는데 CPU가 파이썬 코드를 해석하느라 바쁜 상황이 발생합니다.

Nano-vLLM은 이를 CUDA Graphs 로 해결합니다. GPU 연산의 흐름을 미리 녹화(Capture)해두고, 실제 실행 시에는 녹화된 그래프를 재생(Replay)하는 방식입니다. 이를 통해 커널 런칭 오버헤드를 획기적으로 줄입니다.

또한, 여러 GPU에 모델을 쪼개서 올리는 Tensor Parallelism 을 Leader-Worker 패턴으로 구현했습니다. 공유 메모리를 통해 명령을 전달하는 방식은 단일 노드 멀티 GPU 환경에서 네트워크 오버헤드 없이 깔끔하게 동작합니다.

결론: Senior Engineer의 시선

Hacker News의 댓글 논쟁을 보면서 느낀 점은, 때로는 학술적 용어보다 “왜 이렇게 설계했는가” 에 대한 엔지니어링 직관이 더 중요하다는 것입니다. 저자는 ML 논문을 달달 외우는 대신, 인프라 엔지니어의 시각으로 문제를 해결했고 그 결과물은 놀랍도록 효율적이었습니다.

Nano-vLLM은 프로덕션용 엔진은 아닙니다. 에러 처리나 다양한 모델 지원 측면에서 부족함이 많습니다. 하지만 “LLM 인퍼런스 엔진이 도대체 어떻게 돌아가는 거야?” 라는 질문을 가진 엔지니어에게는 최고의 교과서입니다.

복잡한 vLLM 소스 코드에서 길을 잃기 전에, 주말 동안 이 1,200줄짜리 코드를 일독해보시길 권합니다. 특히 BlockManagerScheduler 클래스는 백엔드 엔지니어들에게도 꽤나 신선한 영감을 줄 겁니다.

Reference: