Documents
Home>Documents>AI>Inference

KV 캐시 Offloading은 왜 생각보다 느린가: CPU DRAM·NVMe 계층별 전송 비용 실측

13 min readSep 3, 2026Sep 3, 2026

GPU 메모리가 부족할 때 가장 먼저 꺼내는 카드가 cpu_offload_gb--n-gpu-layers다. 설정 한 줄이면 OOM 없이 돌아간다. 문제는 "돌아간다"와 "쓸 만하다" 사이의 거리다.

Decode 스텝마다 반복되는 PCIe·DRAM 왕복 전송이 KV 캐시 오프로드의 실제 병목이다. 이 비용은 컨텍스트 길이보다 배치 크기에 비례해 더 빠르게 악화된다.

메모리 계층별 대역폭: 숫자부터

데이터가 어느 경로를 타는지 알면 오프로드 비용이 바로 보인다.

계층단방향 대역폭비고
A100 HBM2e~2 TB/sGPU 내부
PCIe 4.0 x16~32 GB/sGPU↔CPU 단방향 실효치
PCIe 5.0 x16~64 GB/s단방향 실효치
DDR5 DRAM~90 GB/s단일 소켓, CPU-local 접근
NVMe Gen4~7 GB/s순차 읽기 최대치

Llama-3-8B(32레이어, GQA 8헤드, 헤드 차원 128, bf16) 기준으로 레이어 하나의 KV 크기는 다음과 같다.

K 또는 V (seq 128K, 레이어 1개)
  = 128,000 tokens × 8 KV-heads × 128 head-dim × 2 bytes
  ≈ 250 MB

K + V 합계 ≈ 500 MB / 레이어

이 500 MB를 각 계층으로 전송하는 데 걸리는 절대 시간:

경로전송 시간HBM 대비
A100 HBM (내부 읽기)~0.25 ms기준
PCIe 4.0 → CPU DRAM~15.6 ms62×
NVMe Gen4 (순차)~71 ms284×

Decode 스텝마다 32개 레이어를 이 경로로 통과해야 한다. KV 전체를 CPU DRAM에 두면 한 토큰 생성에 32 × 15.6 ms = 약 500 ms가 PCIe 전송에 쓰인다. 실제로는 연산과 일부 겹치겠지만, Decode 스텝에서 레이어 하나의 attention 연산이 0.25 ms 안에 끝나는 데 비해 전송이 62배 더 오래 걸리면 overlap으로 숨길 수 있는 게 없다.

Decode 루프 안에서 전송이 일어나는 타이밍

Prefill은 한 번으로 끝나지만 Decode는 토큰마다 반복된다. 여기에 오프로드의 함정이 있다.

각 Decode 스텝에서 레이어 i를 처리하려면 레이어 i의 전체 KV 캐시를 오프로드 위치에서 GPU로 올려야 한다. attention이 끝나면 새 토큰의 KV를 다시 내보낸다. 이게 모든 레이어에서 순서대로 반복된다.

CUDA stream과 PCIe DMA를 쓰면 레이어 i+1 prefetch와 레이어 i 연산을 겹칠 수 있다. FlexGen(Sheng et al., ICML 2023)이 이 방식으로 double buffering을 구현한다 — 6개 논리 스레드가 현재 레이어 연산, 다음 레이어 KV prefetch, 이전 레이어 writeback을 동시에 진행한다.

overlap이 성립하는 조건은 "연산 시간 ≥ 전송 시간"이다. seq 128K에서 레이어 하나의 attention 연산은 HBM에서 500 MB를 읽는 시간 기준으로 약 0.25 ms다. PCIe 4.0은 15.6 ms를 쓴다. GPU가 연산을 끝내고 15 ms 넘게 다음 블록을 기다리며 stall에 빠진다.

배치 크기를 키우면 달라 보인다. batch=4이면 연산량이 4배 늘어 전송을 가릴 여지가 있는 것처럼 느껴진다. 그런데 batch=4는 동시에 4개 요청의 KV를 관리한다는 뜻이기도 하다. 전송할 KV 총량이 배치 크기와 같은 비율로 늘어난다. TPOT가 선형이 아니라 계단형으로 악화되는 이유다 — 배치가 PCIe 포화 임계점을 넘는 순간 모든 요청이 동시에 stall 구간에 들어간다.

llama.cpp vs vLLM vs FlexGen: 옮기는 단위가 다르다

"오프로드"라는 같은 단어 뒤에 근본적으로 다른 메커니즘이 있다.

llama.cpp (--n-gpu-layers)

레이어 단위로 GPU와 CPU를 나눈다. --n-gpu-layers 20이면 상위 20개 레이어는 GPU에서, 나머지는 CPU에서 연산한다. KV만 옮기는 게 아니라 해당 레이어의 weight 전체와 연산 자체가 CPU로 내려간다. 병목은 PCIe 전송보다 CPU 연산 속도다. 레이어 경계마다 GPU↔CPU 간 activation을 넘기는 동기화 오버헤드도 붙는다.

RTX 3070 Ti(8 GB VRAM) + Llama-3-8B Q4 실측에서 이 구조가 잘 드러난다. CPU 전용은 약 9–10 tok/s, GPU 전용은 약 34 tok/s다. 레이어 절반을 GPU에 올리면 22 tok/s(단순 중간값)가 나오지 않고 20–24 tok/s에 머문다 — GPU 연산과 CPU 연산 사이 동기화 오버헤드가 선형 보간을 방해한다.

# 35레이어 중 20개를 GPU에 올리는 예시 (Llama-3-8B)
./llama-cli -m llama-3-8b.Q4_K_M.gguf \
    --n-gpu-layers 20 \
    -p "summarize this long document: ..." \
    -n 512

vLLM (cpu_offload_gb / kv_offloading_size)

KV 캐시 전용이다. weight는 GPU에 그대로 있고, KV 블록만 CPU DRAM으로 내린다. 공식 문서는 이를 "GPU 메모리를 가상으로 늘리는 방식"으로 설명한다 — --cpu-offload-gb 10이면 24 GB GPU가 34 GB처럼 작동한다는 식이다. 실제로는 GPU 메모리 압박 시 LRU 순서로 KV 블록을 CPU로 내리고, 필요해지면 다시 fetch한다. 공식 문서에 latency 페널티 수치가 없다는 게 의미심장하다 — 용량 확장 수단으로 포지셔닝하기 때문이다.

vLLM v0.8.3부터 kv_offloading_size 파라미터가 추가됐다. TP 분산 환경에서 전체 rank에 걸친 KV 버퍼 크기를 지정하며, kv_offloading_backend로 native 구현 대신 LMCache를 선택할 수도 있다.

FlexGen과 InfLLM

FlexGen은 weight, KV, activation을 GPU/CPU/NVMe에 걸쳐 관리하고, LP로 배치를 자동 최적화한다. NVIDIA T4(16 GB) 한 장으로 OPT-175B를 0.69 tok/s(압축 없이)에서 1.12 tok/s(4-bit)까지 냈다. NVMe를 쓸 때 순차 접근 패턴을 강제한다 — 실험 환경의 실측치는 읽기 ~2 GB/s로 이론 7 GB/s를 크게 밑돌았다.

InfLLM(Xiao et al., NeurIPS 2024)은 선택적 로딩을 택한다. KV를 128토큰 단위 블록으로 묶고 블록당 대표 토큰 4개만 GPU에 남긴다. Attention 시 현재 쿼리와 대표 토큰 점수 기반으로 상위 k개 블록만 CPU에서 로딩한다. Mistral-7B 기준 ∞-Bench(평균 145K 토큰 시퀀스)에서 StreamingLLM의 21.5% 대비 57.7%를 달성하면서 full-attention 대비 메모리·벽시계 시간 각 34%를 줄인다.

어떤 조건에서 오프로드가 실용적인가

CPU DRAM 오프로드와 NVMe 오프로드는 단순히 대역폭 차이가 아니다. NVMe는 순차 대용량 IO에서 7 GB/s를 내지만 소블록 랜덤 IO에서 이 수치의 10% 이하로 떨어진다. 블록 크기를 잘못 설계하면 NVMe보다 CPU DRAM memcpy가 더 빠른 역전이 일어난다. FlexGen이 순차 스케줄을 강제한 이유가 이것이다.

PCIe 4.0 기준으로 TPOT < 50 ms를 유지하는 컨텍스트 상한을 계산하면:

레이어당 전송 시간 = seq_len × (8헤드 × 128차원 × 4 bytes) / 32 GB/s
                  = seq_len × 4.096 μs
32레이어 총계     = seq_len × 131 μs

seq 8K   → 32레이어 전송 약 33 ms  (TPOT 50 ms 경계선)
seq 32K  → 약 130 ms  (허용 불가)
seq 128K → 약 524 ms  (허용 불가)
조건CPU DRAM 오프로드NVMe 오프로드
batch=1, offline, latency 무관허용허용 (순차 IO 전제)
batch=1, seq < 8K, TPOT < 100 ms경계선불가
interactive serving, TPOT < 50 ms거의 불가불가
batch >= 4, throughput 지향불가불가

throughput 지향 서빙에서는 오프로드 대신 양자화로 HBM 수요를 줄이는 쪽이 거의 항상 낫다.

오프로드 없이 같은 목표를 달성하는 대안

W4A16 양자화로 Llama-3-8B를 내리면 weight 메모리가 16 GB에서 4–5 GB로 줄고 KV를 위한 공간이 넓어진다. INT8 KV 양자화를 추가하면 KV 메모리 자체도 절반이 된다. latency 페널티 없이 메모리를 확보하는 가장 직접적인 방법이다.

컨텍스트를 반드시 길게 유지해야 하는 게 아니라면 RAG로 관련 청크만 주입하고 윈도우를 8–16K로 제한하는 쪽이 latency·메모리 양쪽에서 오프로드보다 낫다.

방법메모리 절약TPOT 영향품질 손실복잡도
KV 오프로드 (CPU DRAM)크다크다 (seq 비례)없다낮다
W4A16 양자화크다거의 없다미미하다낮다
INT8 KV 양자화중간거의 없다미미하다낮다
RAG + 짧은 컨텍스트크다없다맥락 소실 위험높다

남은 질문은 PCIe 5.0 보편화가 이 그림을 바꾸느냐다. seq 128K 기준 CPU 오프로드 전송 시간이 524 ms에서 262 ms로 줄어드는 건 맞다. HBM 대비 여전히 31배다. 병목의 출처가 대역폭보다 Decode 루프의 레이어-순차 구조에 있는 한, 배선을 두 배로 굵혀도 절반만 해결된다.

Tags
KV 캐시InferenceGPU메모리vLLM서빙LLM아키텍처