Efficient Memory Management for Large Language Model Serving with PagedAttention
AI/LLM 시리즈 : vLLM PagedAttention 논문 리뷰
저자: Woosuk Kwon, Zhuohan Li, Siyuan Zhuang, Ying Sheng, Lianmin Zheng, Cody Hao Yu, Joseph E. Gonzalez, Hao Zhang, Ion Stoica
GeekNews를 보다가 흥미로운 주제가 있어서 읽고 정리해보았습니다. 업무에서 vLLM을 주로 많이 사용하는데요. 양자화 지원이 다소 부족하지만 이만한 서빙용 라이브러리가 없는 것 같습니다. Aleksa Gordić의 블로그에서도 vLLM의 중심 알고리즘인 PagedAttention을 다루고 있어서 저도 한번 정리해보겠습니다.
논문 요약
LLM 서빙의 병목은 연산이 아니라 메모리(특히 KV cache) 입니다. vLLM은 OS의 페이징 아이디어를 가져온 PagedAttention으로 KV cache를 비연속 블록으로 관리해 단편화와 중복을 없애고, 프리픽스·빔 서치 등 공유 가능한 상태를 공유합니다. 그 결과 기존 시스템(Orca, FasterTransformer) 대비 2~4× 처리량 향상을 달성합니다.
왜 메모리에서 병목이 발생할까?
GPU는 빠른데 메모리는 늘 부족합니다. 최신 가속기의 연산 성능 증가 속도는 메모리 용량 증가 속도를 앞지르고 있으며, KV cache는 요청마다 동적으로 커지고 줄어들며 크기도 매우 큽니다. 기존 방식처럼 요청별 연속 메모리를 미리 크게 잡아두면 세 가지 문제가 발생합니다.
- 예약 공간 — 최대 길이를 미리 확보
- 내부 단편화(over-provisioning) — 실제 사용량보다 더 많이 점유
- 외부 단편화 — buddy allocator 등으로 인한 분산
[기존] 요청 A (최대 2048) ──────────────────────────
[실제 300][ 예약 ][ 내부 단편화 ][외부 단편화]
[기존] 요청 B (최대 512) ──────────
[실제 120][ 예약 ][내부 ][외부 ]
이 때문에 실제로 쓸 수 있는 KV 메모리는 20~38% 수준으로 떨어질 수 있습니다. 요청 수가 늘수록 KV cache가 급증해 batch size가 잠겨버리고, 결국 LLM 서빙의 throughput은 메모리 배치 전략이 결정합니다.
PagedAttention — "KV cache = 가상 메모리처럼"
논문이 제안한 해법은 키·값을 고정 크기 블록(KV block) 으로 쪼개 비연속(Non-contiguous) 공간에 저장하고, 논리 블록 ↔ 물리 블록을 블록 테이블로 매핑하는 방식입니다.
[논리 KV 블록] B0 B1 B2 B3 ...
│ │ │
[물리 KV 블록] P7 ← P1 ← P3 P9 ...
^ ^
(block table: 논리→물리 매핑 + #filled)
이 구조 덕분에 세 가지 효과를 얻습니다.
- 필요할 때만 블록 할당 — 사전 과할당 없음
- 외부 단편화 0 — 크기가 같은 블록만 사용
- 블록 공유 및 copy-on-write — 프리픽스·빔 간 KV 공유 활성화
블록 테이블로 매핑하면 왼쪽→오른쪽으로 차곡차곡 채우며, 이전 블록이 다 차야 다음 블록을 할당하므로 요청당 낭비는 최대 1블록으로 한정됩니다.
공유가 성능이다: 프리픽스·패럴럴 샘플링·빔 서치
프리픽스 공유(Shared Prefix)
시스템 프롬프트나 few-shot 예시 같은 공통 프리픽스의 KV를 미리 캐시해 두고, 사용자 프롬프트는 그 뒤에만 얹어 계산합니다.
[공유 프리픽스 KV] ==== (여러 요청이 공용 매핑)
[사용자 입력 KV] ---- (요청별 생성)
패럴럴 샘플링 (한 프롬프트 → 여러 샘플)
프롬프트 구간은 완전 공유, 생성 구간만 분기합니다. 마지막 블록에서만 copy-on-write가 일어나 메모리 절감이 큽니다.
빔 서치
빔 후보들이 여러 블록을 더 넓게 공유하며, 후보가 교체될 때는 refcount 0인 블록을 회수하고 필요한 블록만 새로 할당합니다. 기존 시스템처럼 대량의 KV 복사 대신 블록 공유 + 필요 시 1블록 복사만 수행합니다.
실험 결과: 패럴럴 샘플링은 6~10%, 빔 서치는 38~55% 수준의 KV 블록 절약이 관찰되었습니다.
스케줄링·스와핑·재계산: 메모리 압박 대응
요청 폭주로 GPU 블록이 바닥나면 vLLM은 FCFS + 그룹 단위 선점을 적용하고 두 가지 복구 전략을 사용합니다.
| 전략 | 방식 | 유리한 조건 |
|---|---|---|
| Swapping | KV 블록을 CPU RAM으로 내보냈다가 복귀 | 블록이 클 때 |
| Recomputation | 필요 시 다시 프리필해 KV 재계산 | 블록이 작을 때 |
실험적으로 블록 16~64 구간에서는 둘의 E2E 성능이 비슷합니다.
성능 결과
vLLM은 동일 지연에서 기존 시스템 대비 다음과 같은 처리량 향상을 달성합니다.
| 비교 대상 | 처리량 향상 |
|---|---|
| Orca (Oracle) | 1.7~2.7× |
| Orca (Max) | 2.7~8× |
| FasterTransformer | 최대 22× |
이유는 단순합니다. 메모리를 쪼개고 공유해 더 많은 요청을 한 번에 처리할 수 있기 때문입니다. 특히 긴 프롬프트 / 긴 시퀀스일수록 이득이 크며, 반대로 짧은 시퀀스 + 넉넉한 메모리 상황에서는 시스템이 compute-bound가 되어 이득이 줄어듭니다.
블록 크기, 어떻게 잡는 것이 좋을까?
| 설정 | 문제점 |
|---|---|
| 너무 작게 | 병렬성 저하, GPU 메모리 접근 효율 하락 |
| 너무 크게 | 내부 단편화 심화, KV 공유 기회 감소 |
논문에서는 기본값으로 16을 권장합니다. 실험에서도 ShareGPT는 16128, Alpaca는 1632 범위에서 안정적인 결과를 보입니다. 실무에서는 16으로 시작하여 필요할 경우 32까지 조정하는 방식이 가장 합리적입니다.
마무리 — 제 결론
vLLM의 핵심 성과는 사실 단순한 발상에서 시작합니다.
"KV 메모리를 운영체제의 가상 메모리처럼 다루자."
이 단순한 접근이 LLM 서빙의 진짜 병목인 메모리 문제를 해결합니다. 모델 크기가 커지고 시퀀스가 길어질수록 vLLM은 더욱 강력해집니다. 긴 프롬프트 + 샘플 여러 개 + 빔 서치 같은 워크로드에서는 vLLM을 기본값으로 설정하고 있습니다.
물론 한계도 있습니다. 요청률이 시스템 용량을 넘어서는 순간 지연 시간이 급격하게 폭발합니다.
요청률 ↑ → 시스템 용량 초과 → 대기열 길이 ∞ → 지연(요청당/토큰당) 급증
그래도 핵심은 메모리를 효율적으로 관리하여 동시에 더 많은 요청을 처리하는 것이고, 그 점에서 PagedAttention은 여전히 LLM 서빙의 표준이라고 생각합니다. 읽어주셔서 감사합니다!