본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 20. 10:51

KV cache와 PagedAttention: 역할과 중요성

요약

LLM 추론 시 발생하는 KV cache 메모리 병목 현상과 그 해결책을 다룹니다. KV cache의 역할과 메모리 점유 문제를 설명하고, vLLM의 PagedAttention 기술이 운영체제의 메모리 관리 방식을 통해 이를 어떻게 효율적으로 해결하는지 분석합니다.

핵심 포인트

  • KV cache는 토큰 생성 시 재계산을 방지하여 지연 시간을 줄이는 필수 요소임
  • 긴 컨텍스트와 높은 동시성 환경에서 KV cache는 모델 가중치보다 더 많은 메모리를 점유함
  • 전통적인 방식은 연속적인 텐서 할당으로 인해 메모리 낭비와 파편화가 발생함
  • PagedAttention은 OS의 페이징 개념을 도입하여 메모리 효율성을 극대화함

KV cache와 PagedAttention: 역할과 중요성

여러분의 프로덕션 LLM 서버가 예정된 일정보다 늦어지고 있습니다. 각각 80GB 용량의 A100 4대에 70B 모델을 배포했습니다. 사양과 예산 범위 내에 있는 설정입니다. 하지만 동시 사용자 수가 증가함에 따라 첫 번째 토큰 생성 시간(time-to-first-token)이 점차 늘어나고 있습니다. 점심시간이 되자 지연 시간(latency)은 오전 8시의 두 배가 되었습니다. GPU 메모리를 확인해 보니, nvidia-smi가 "tensor buffers"라고 보고하는 영역이 HBM의 70%를 점유하고 있습니다. 하지만 이는 사실 아무도 정리하지 않은 수십 개의 장기 실행 대화에서 발생한 캐싱된 트랜스포머 상태(transformer states)입니다. 서버를 재시작하자 다시 정상 작동합니다. 하지만 오후 4시가 되자 동일한 속도 저하 현상이 다시 나타납니다.

이것이 바로 KV cache 메모리 문제이며, GPU 기반의 프로덕션 LLM 서빙에서 발생하는 가장 큰 운영상의 병목 현상입니다. 이 포스트에서는 KV cache가 실제로 무엇을 저장하는지, 왜 대화 중에 제한 없이 커지는지, 그리고 vLLM을 구동하는 기술인 PagedAttention이 운영체제(OS)에서 영감을 얻은 메모리 관리 방식을 통해 이를 어떻게 해결하는지 설명합니다.

KV cache가 중요한 이유

KV cache는 선택 사항이 아닙니다. 모든 자기회귀(autoregressive) 트랜스포머는 토큰을 하나씩 생성합니다. 토큰 N을 생성하기 위해, 어텐션 메커니즘(attention mechanism)은 토큰 0부터 N-1까지의 Key와 Value 텐서가 필요합니다. 매 새로운 토큰마다 이를 처음부터 다시 계산한다면 단계당 $O(N^2)$의 비용이 발생하며, 이는 수백 개의 토큰보다 긴 대화에서는 재앙적인 수준이 됩니다. 대신, 추론 엔진(inference engine)은 이전 토큰들의 K와 V 텐서를 캐싱하고 매 단계마다 이를 추가합니다. 이 구조가 바로 KV cache입니다.

문제는 메모리 점유율(memory footprint)입니다. 80개의 레이어(layers), 8개의 KV 헤드(grouped-query attention), 128의 헤드 차원(head dimension)을 가진 Llama 3.1 70B 모델의 경우, 단일 4096-토큰 시퀀스는 대략 다음과 같은 메모리를 요구합니다:

2 (K+V) * 80 layers * 8 KV heads * 128 dim * 4096 tokens * 2 bytes (FP16)
= 1,342,177,280 bytes per sequence
= ~1.3 GB per sequence

256개의 4096-토큰 동시 시퀀스(concurrent sequences)의 경우, 이는 336 GB의 HBM을 필요로 합니다. 이는 A100 4대가 제공하는 총 용량(320 GB)보다 많습니다. 게다가 이는 모델 가중치(70B 모델의 FP16 기준 약 140 GB), 중간 활성화 함수(intermediate activations), 어텐션 스코어 행렬(attention scores matrix), 또는 배치 오버헤드(batching overhead)를 고려하기 전의 수치입니다.

이것이 근본적인 갈등 요소입니다: KV 캐시(KV cache)는 수용 가능한 지연 시간(latency)을 위해 필수적이지만, 유의미한 동시성(concurrency)이나 긴 컨텍스트 창(context windows)을 가진 모든 워크로드에서 모델 가중치보다 더 많은 메모리를 소비합니다.

전통적인 서빙(serving) 방식에서의 KV 캐시 모습

vLLM 이외의 대부분의 트랜스포머 추론(transformer inference) 구현에서, KV 캐시는 미리 할당된 연속적인 텐서(contiguous tensor)입니다. 시퀀스가 시작될 때, 프레임워크는 최대 시퀀스 길이(또는 사용자가 지정한 max_new_tokens)에 맞는 크기로 past_key_values 튜플을 할당합니다. 이 할당은 사전에 이루어지며 시퀀스가 완료될 때까지 고정(pinned)된 상태로 유지됩니다.

다음은 단일 생성 단계(generation step) 동안 발생하는 일을 단순화한 모습입니다:

# 전통적인 연속적 KV 캐시를 사용한 단순화된 어텐션 단계
# key_cache shape: [batch, num_heads, max_seq_len, head_dim]
# value_cache shape: [batch, num_heads, max_seq_len, head_dim]
...

연속적인 할당(contiguous allocation)은 대화가 최대 길이에 도달하지 않더라도 첫 번째 토큰부터 가능한 최대 메모리 비용을 지불해야 함을 의미합니다. 이는 고정된 길이의 시퀀스를 사용하는 오프라인 평가(offline evaluation)에는 괜찮지만, 대부분의 대화가 짧은 대화형 서빙(interactive serving)에서는 낭비가 심합니다.

세 가지 구체적인 비효율성이 발생합니다:

  • 내부 단편화 (Internal fragmentation). 4096개의 토큰을 위해 할당된 시퀀스가 실제로는 300개의 토큰만 사용하는 경우, 할당량의 93%가 낭비됩니다.
  • 공유 불가 (No sharing). 동일한 시스템 프롬프트(system prompt)로 시작하는 두 대화는 공유되는 접두사(prefix)에 대해 각각 자신만의 K 및 V 텐서를 저장해야 합니다. 중복을 제거할 메커니즘이 없습니다.
  • 전부 아니면 전무 식의 축출 (All-or-nothing eviction). 메모리가 부족해지면 시퀀스 전체를 축출하거나 CPU 메모리로 스왑(swap)해야 합니다. 70B 모델을 위한 4096개 토큰의 KV cache를 PCIe를 통해 이동하는 데는 수십 밀리초가 소요되며, 그동안 GPU는 스톨(stall, 정지) 상태가 됩니다.

PagedAttention의 작동 원리

1. 온디맨드 할당 (On-demand allocation). 시퀀스는 성장함에 따라 페이지를 소비합니다. 만약 사용자가 150개의 토큰을 생성하는 단일 턴(one-turn) 질문을 던지면, 캐시는 10개의 페이지(페이지당 16개 토큰 기준)를 사용합니다. 다른 사용자가 5,000개 토큰 규모의 문서 분석을 실행하면, 페이지는 동적으로 할당됩니다. 사용되지 않는 용량에 대한 메모리 낭비가 없습니다.

2. 공유 접두사 페이지를 위한 쓰기 시 복사 (Copy-on-write for shared prefix pages). 여러 시퀀스가 공통된 접두사(prefix) — 시스템 프롬프트(system prompt), 대화 기록, 몇 가지 퓨샷 예시(few-shot examples) — 를 공유할 때, PagedAttention은 동일한 물리적 페이지를 여러 가상 주소 공간(virtual address spaces)에 매핑합니다. 이 페이지들은 읽기 전용(read-only)으로 표시됩니다. 만약 생성 과정에서 하나의 시퀀스가 분기되면 (첫 번째 샘플링 단계 이후에는 항상 분기됩니다), 실제로 변경되는 페이지만 복사됩니다. 많은 채팅 애플리케이션에서 배치(batch) 내 토큰의 40-60%가 공유 접두사 토큰일 수 있으므로, 메모리 절감 효과는 상당합니다.

3. 세밀한 축출 및 스와핑 (Fine-grained eviction and swapping). GPU 메모리가 고갈되면, 블록 관리자(block manager)는 최근 최소 사용(least-recently-used, LRU) 정책에 따라 축출할 페이지를 선택합니다. 축출된 페이지는 CPU DRAM에 기록됩니다. 페이지 크기가 작기 때문에 (16-32개 토큰), 전송 입도(granularity)가 세밀하며, PCIe 대역폭 비용은 하나의 커다란 차단 이동(blocking move) 대신 많은 작은 전송들에 걸쳐 분산(amortized)됩니다.

PagedAttention vs 전통적인 KV 캐시 관리

측면 (Aspect)전통적인 연속적 KV 캐시 (Traditional contiguous KV cache)PagedAttention
할당 전략 (Allocation strategy)시퀀스당 최대 길이 사전 할당 (Pre-allocate max length per sequence)온디맨드, 한 번에 한 페이지씩 (On-demand, one page at a time)
...

처리량(throughput) 이득은 워크로드(workload)에 따라 달라집니다. vLLM의 발표된 벤치마크에 따르면, 연속적 할당을 사용하는 프레임워크 대비 2-4배의 성능 향상을 보고했으며, 짧은 시퀀스와 긴 시퀀스가 혼합된 워크로드에서 가장 큰 이득을 보였습니다. 균일한 길이의 배치(uniform-length batches)의 경우, 그 이점은 줄어듭니다.

일반적인 실수 (Common pitfalls)

1. 매우 작은 페이지 크기로 인한 페이지 테이블 오버헤드 (Page table overhead with very small page sizes). 페이지 테이블 자체는 GPU 메모리에 상주합니다. 페이지 크기가 4-8 토큰인 경우, 메타데이터가 HBM (High Bandwidth Memory)의 상당 부분을 차지할 수 있습니다. vLLM은 실질적인 최적 지점(sweet spot)으로 16-토큰 페이지를 기본값으로 사용합니다. 매우 긴 컨텍스트(context)에서 예상보다 낮은 처리량(throughput)이 관찰된다면, 페이지 크기가 너무 작은지 확인하십시오.

2. PagedAttention에 역효과를 주는 스케줄러 파라미터 (Scheduler parameters that work against PagedAttention). vLLM은 단일 반복(iteration)에서 얼마나 많은 토큰과 시퀀스(sequence)를 배치(batch)할지 제어하는 --max-num-batched-tokens--max-num-seqs를 노출합니다. 이 값들을 너무 높게 설정하면 처리량을 개선하지 못한 채 배치 자원만 낭비하게 됩니다. 반대로 너무 낮게 설정하면 GPU 활용도가 떨어집니다. 일반적인 가이드는 70B 모델의 경우 --max-num-seqs 256--max-num-batched-tokens 8192로 시작하여 이를 기준으로 튜닝하는 것입니다.

3. 접두사 캐싱(Prefix caching)이 무조건적으로 유익한 것은 아님. vLLM의 자동 접두사 캐싱 (--enable-prefix-caching)은 모든 토큰 블록에 대해 해시(hash)를 계산합니다. 매우 짧은 프롬프트(prompt)나 빠르게 교체되는 시스템 프롬프트의 경우, 해시 계산 오버헤드가 재사용 이점보다 커질 수 있습니다. 워크로드에 대해 해당 기능을 켰을 때와 껐을 때를 프로파일링(profile)해 보십시오.

4. KV 캐시 양자화(KV cache quantization)와의 상호작용. PagedAttention은 FP8 및 INT4 KV 캐시 양자화와 함께 작동하지만, 페이지당 데이터가 작을수록 각 페이지가 가지는 메타데이터의 비중이 상대적으로 더 커집니다. vLLM v0.23.0은 Ada Lovelace 및 Hopper GPU를 위한 FP8 KV 캐시 지원을 추가했으며, --kv-cache-dtype fp8로 사용할 수 있습니다. 기능을 활성화하기 전에 결합된 효과를 측정하십시오.

사용하지 말아야 할 때 (When NOT to use it)

PagedAttention과 vLLM이 모든 배포 환경에 적합한 선택은 아닙:

  • 단일 사용자 로컬 추론 (Single-user local inference). 하나의 GPU에서 한 명의 사용자를 위해 모델을 실행하는 경우, PagedAttention이 해결하고자 하는 메모리 압박은 발생하지 않습니다. llama.cpp 또는 Hugging Face Transformers와 같은 더 단순한 프레임워크가 오버헤드가 더 적고 오류 발생 가능성(failure modes)도 낮습니다.

  • 100ms 미만의 대화형 지연 시간 (Sub-100ms interactive latency) 요구 사항. 어텐션(attention) 과정 중 발생하는 페이지 워킹(page-walking) 로직은 토큰당 작지만 측정 가능한 오버헤드를 추가합니다. 16개 토큰 페이지의 경우 대략 3-5% 정도입니다. 만약 애플리케이션이 일관된 100ms 미만의 첫 번째 토큰 생성 시간(time-to-first-token)을 요구한다면, 정적 사전 할당(static pre-allocation)을 사용하는 연속적 캐시(contiguous cache)가 (처리량(throughput)을 희생하는 대신) 더 낮은 꼬리 지연 시간(tail latency)을 제공합니다.

  • 고용량 메모리 GPU에서의 소형 모델. A100-80GB에서 7B 모델을 실행하면 가중치(weights)에 약 14GB를 사용하며, 4096 토큰 컨텍스트(context) 기준 시퀀스당 KV 캐시(KV cache)로 약 300MB를 사용합니다. 일반적인 동시성(concurrency) 수준에서는 페이징(paging) 없이도 캐시가 충분히 들어갑니다. 이 경우 PagedAttention의 복잡성은 얻을 것이 없습니다.

  • 비자기회귀 아키텍처 (Non-autoregressive architectures). 토큰을 왼쪽에서 오른쪽으로 생성하지 않는 모델들, 즉 인코더 전용 모델 (BERT, RoBERTa), 확산 기반 언어 모델(diffusion-based language models), 비인과적 디코더(non-causal decoders) 등은 관리할 KV 캐시가 없습니다. PagedAttention은 자기회귀 디코딩(autoregressive decoding)에 특화되어 있습니다.

  • 균일한 길이의 오프라인 평가 (Uniform-length offline evaluation). 배치(batch) 내의 모든 시퀀스 길이가 동일하다면(평가 벤치마크에서 흔히 발생), 페이징의 파편화 방지 및 온디맨드(on-demand) 이점은 최소화됩니다. 이럴 때는 연속적 방식(contiguous approach)이 잘 작동합니다.

요약 (TL;DR)

  • KV cache는 자기회귀 디코딩 (autoregressive decoding) 과정에서 이전 모든 토큰의 Key 및 Value 텐서를 저장합니다. 이는 수용 가능한 지연 시간 (latency)을 위해 필수적이지만, 시퀀스 길이 (sequence length)와 배치 크기 (batch size)에 따라 선형적으로 증가합니다.
  • 256개의 4096-토큰 시퀀스를 동시에 처리하는 Llama 3.1 70B 모델의 경우, KV cache는 약 336 GB의 HBM을 소비하며, 이는 A100 4대 이상이 제공할 수 있는 용량을 초과합니다.
  • PagedAttention (Kwon et al., 2023)은 운영체제 (OS) 스타일의 가상 메모리 페이징 (virtual memory paging)을 KV cache에 적용합니다: 고정 크기 페이지 (fixed-size pages), 온디맨드 할당 (on-demand allocation), 쓰기 시 복사 (copy-on-write) 페이지 공유, 그리고 세밀한 교체 (fine-grained eviction) 방식입니다.
  • vLLM (v0.23.0, 2026년 6월, GitHub 스타 83k 이상)은 PagedAttention을 구현하여 혼합 워크로드 (mixed workloads)에서 연속적 할당 (contiguous-allocation) 프레임워크 대비 2~4배의 처리량 (throughput)을 달성합니다.
  • 기본값으로 16-토큰 페이지를 사용하고, 모델과 워크로드에 맞춰 --max-num-seqs--max-num-batched-tokens를 조정하십시오.
  • 동시성 (concurrency)이 높거나, 시퀀스 길이가 다양하거나, 프롬프트가 접두사 (prefix)를 공유하는 경우 PagedAttention을 사용하십시오. 단일 사용자 추론 (single-user inference), 소규모 모델, 또는 균일한 배치 크기 (uniform batch sizes)의 경우에는 사용하지 않아도 됩니다.

다음 포스트: vLLM vs TGI vs llama.cpp -- 동일한 70B 모델을 실제 동시성 환경에서 실행하여 처리량 (throughput), 지연 시간 (latency), 토큰당 비용을 비교하는 실질적인 서빙 벤치마크.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0