LLM 서빙 인프라 해부: PagedAttention과 Continuous Batching이 추론을 확장하는 방법
요약
본 기사는 LLM 프로덕션 환경에서 발생하는 메모리 단편화와 GPU 자원 비효율성을 다룹니다. vLLM의 PagedAttention과 Continuous Batching은 KV 캐시를 고정 크기 블록으로 분할하여, VRAM을 효율적으로 관리하고 높은 처리량을 달성하는 핵심 기술입니다.
핵심 포인트
- KV Cache는 시퀀스 길이와 배치 크기에 비례해 메모리를 많이 사용합니다.
- 전통적 할당 방식은 내부/외부 단편화로 인해 VRAM 낭비가 심각했습니다.
- PagedAttention은 KV 캐시를 블록 단위로 분할하여 메모리 단편화를 해결합니다.
- Continuous Batching을 통해 GPU 자원을 지속적으로 활용하며 처리량을 극대화합니다.
대규모 언어 모델(LLM)을 Jupyter 노트북의 로컬 프로토타입에서 높은 처리량의 멀티테넌트 프로덕션 환경으로 옮기는 것은 가혹한 각성입니다. 데이터 과학자들이 모델 가중치 최적화, 양자화(quantization), 파인튜닝에 몇 달을 보내는 동안, 인프라 엔지니어들은 완전히 다른 종류의 악마들—메모리 단편화(memory fragmentation), 큐 지연 시간(queue latency), GPU 부족 현상(GPU starvation)—에 직면합니다.
프로덕션 서빙 환경에서 병목 현상은 거의 항상 GPU의 순수 컴퓨팅 능력(FLOPs) 그 자체가 아닙니다. 그것은 거의 항상 메모리 대역폭과 용량입니다.
본 심층 분석에서는 vLLM과 같은 LLM 서빙 엔진에 혁명을 일으킨 두 가지 핵심 기둥, 즉 PagedAttention과 Continuous Batching을 파헤칠 것입니다.
- 침묵의 살인자: 자기회귀 생성 중 KV 캐시 메모리 폭발 이해하기
자기회귀(autoregressive) 방식으로 LLM이 토큰을 하나씩 생성할 때, 모든 이전 토큰에 대한 Key와 Value 행렬을 매 단계마다 재계산하는 것을 방지하기 위해 추론 엔진은 이 텐서들을 GPU VRAM에 캐시합니다. 이는 통틀어 KV Cache라고 알려져 있습니다. KV 캐시의 메모리 사용량은 시퀀스 길이(sequence length)와 배치 크기(batch size)에 선형적으로 비례합니다. 4K 컨텍스트 창을 가진 70B 파라미터 모델의 경우, KV 캐시는 요청당 쉽게 수십 기가바이트의 VRAM을 소비할 수 있습니다.
전통적인 정적 할당의 결함(The Flaw of Traditional Static Allocation)
역사적으로 서빙 프레임워크는 생성 중 메모리 부족 오류를 방지하기 위해 모델의 최대 가능한 시퀀스 길이(예: 4096 또는 8192 토큰)를 기반으로 VRAM의 연속적인 청크를 미리 할당했습니다. 이는 두 가지 거대한 비효율성을 초래합니다:
- 내부 단편화(Internal Fragmentation): 사용자가 200토큰 응답만 요청하는 경우, 해당 슬롯에 대해 사전 할당된 나머지 메모리는 유휴 상태이며 사용할 수 없습니다.
- 외부 단편화(External Fragmentation): 다양한 요청 길이는 메모리 블록을 깔끔하게 채우는 것을 거의 불가능하게 만들어, 사용되지 않고 고립된 VRAM의 거대한 덩어리를 초래합니다.
결과적으로 전통적인 시스템은 종종 GPU VRAM의 60%에서 80%를 낭비하여 동시성을 심각하게 저해합니다.
2. PagedAttention: LLM을 위한 가상 메모리
메모리 단편화(memory fragmentation)를 해결하기 위해 연구자들은 수십 년 동안 운영체제가 RAM을 관리하는 데 사용해 온 개념, 즉 가상 메모리(Virtual Memory)와 페이징(Paging)을 차용했습니다. vLLM에서 소개된 PagedAttention은 각 시퀀스의 KV 캐시를 작고 고정된 크기의 블록(예: 블록당 16 토큰)으로 분할합니다. 이 블록들은 물리적 GPU 메모리에 연속적이지 않은 방식으로 저장될 수 있습니다. [전통적인 연속 할당] [요청 A: 사용됨] [요청 A: 미사용/낭비 (최대 길이)]
[PagedAttention 블록 기반 할당]
블록 테이블 (논리적 -> 물리적 매핑):
Req 1 -> [블록 #12] -> [블록 #5] -> [블록 #28]
작동 원리:
- 블록 테이블(The Block Table): 각 요청은 추론 엔진에 의해 관리되는 논리적-물리적 블록 테이블을 유지합니다.
- 온디맨드 할당(On-Demand Allocation): 블록은 대규모 블록을 미리 예약하는 대신, 새로운 토큰이 생성됨에 따라 실시간으로 할당됩니다.
- 메모리 공유 (Copy-on-Write): PagedAttention은 여러 시퀀스가 공통 프롬프트 접두사(common prompt prefixes)를 공유하는 병렬 샘플링(parallel sampling), 빔 서치(beam search), 다중 턴 채팅 분기(multi-turn chat branching)와 같은 고급 기능에 대한 효율적인 메모리 공유를 가능하게 합니다. 영향: 메모리 낭비가 약 70%에서 4% 미만으로 떨어집니다. 이는 GPU가 VRAM에 훨씬 더 많은 동시 요청을 패킹할 수 있게 하여 처리량(throughput)을 직접적으로 증대시킵니다.
- 연속 배치 처리 (Continuous Batching, 반복 수준 스케줄링)
메모리 관리는 용량을 해결하지만, 스케줄링 효율성은 어떨까요? 전통적인 딥러닝 배치 처리(정적 배치 처리, static batching)에서는 요청 배치(batch of requests)가 형성되어 GPU로 전송되며, 전체 배치는 모든 요청이 최종 토큰 생성을 완료할 때까지 기다려야 합니다. 이는 심각한 GPU 유휴 상태(GPU starvation)를 초래합니다:
-
짧은 요청은 일찍 완료되지만, 배치 내에서 가장 긴 요청이 완료될 때까지 할당된 슬롯을 비워둡니다.
-
들어오는 요청은 현재 배치(batch)가 완전히 처리될 때까지 외부 큐에서 대기해야 합니다.
반복 수준 스케줄링 (Iteration-Level Scheduling)의 등장
Continuous Batching(또는 반복 수준 스케줄링)은 스케줄링 세분성(granularity)을 요청 수준에서 반복(토큰) 수준으로 변경합니다.
# Continuous Batching의 개념적 루프
while active_requests_queue or running_batch:
# 1. 하나의 순방향 패스(forward pass) 완료 (모든 활성 시퀀스에 대해 토큰 1개 생성)
logits = model.forward(running_batch)
# 2. 완료된 시퀀스 확인
for req in running_batch:
if req.is_finished():
running_batch.remove(req)
# 큐에서 새로운 요청으로 즉시 채움 (backfill)
if active_requests_queue:
running_batch.add(active_requests_queue.pop())
요청 생명주기(request lifecycles)를 배치 생명주기(batch lifecycles)와 분리함으로써 얻는 이점은 다음과 같습니다:
- 요청이 End-of-Sequence 토큰을 생성하는 즉시, 해당 슬롯이 해제됩니다.
- 큐에 대기하던 요청이 바로 다음 순방향 반복(forward iteration)에서 배치됩니다.
- GPU 컴퓨팅 유닛은 100%에 가깝게 포화 상태를 유지하여 평균 지연 시간(average latency)을 크게 줄이고, 정적 배치(static batching) 대비 시스템 전체 처리량(system throughput)을 최대 23배까지 증가시킵니다.
1. 프로덕션 환경에서 모든 것을 통합하기 (Bringing It All Together in Production)
오늘날 LLM 서빙 스택을 설계할 때, 처음부터 구축하는 경우는 드뭅니다. 이러한 정확한 원칙에 기반하여 만들어진 프로덕션급 엔진 덕분입니다. vLLM, TensorRT-LLM 또는 TGI(Text Generation Inference)를 사용하여 배포하든 관계없이, 이러한 내부 작동 방식을 이해하는 것은 다음을 위해 매우 중요합니다:
- GPU 인스턴스 적정 크기 결정 (Right-sizing GPU instances): KV 캐시 블록 크기를 알면 동시 사용자당 정확한 VRAM 요구 사항을 계산할 수 있습니다.
- 최대 모델 길이 튜닝 (Tuning max model len): 과도한 할당을 방지하고 동시성 제한(concurrency limits)을 극대화합니다.
- 청크 전처리 최적화 (Optimizing chunked prefill): 생성 큐가 중단되는 것을 막으면서 대규모 컨텍스트 창(context windows)을 처리할 수 있습니다.
AI 엔지니어를 위한 핵심 요약:
- VRAM이 주요 제약 사항입니다: 하드웨어를 더 많이 투입하기 전에 KV 캐시 관리를 최적화하세요.
- 배치(Batching)는 동적입니다: 자가회귀 텍스트 생성 시 정적인 배치 크기에 의존하지 마세요.
- 인프라는 코드입니다: 저수준 메모리 스케줄링 결정이 API의 토큰당 비용 경제성을 좌우합니다. 현재 프로덕션 스택에서 어떤 서빙 엔진을 사용하고 계신가요? 아래 댓글에서 논의해 봅시다! 인공지능, LLM, 머신러닝 인프라, 파이썬.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기