-
KV cache FP8로 동시 요청 76배 수용하기 — LLM 메모리의 숨은 주범 정리IT 2026. 5. 6. 22:00
들어가며 — vLLM에 한 줄 옵션을 더했더니
vLLM 시작 명령에 옵션 한 줄을 추가했다.
--kv-cache-dtype fp8그 결과 같은 GPU(DGX Spark 80GB)에서 32K 컨텍스트 기준 동시 요청 수용 능력이 76배로 뛰었다. 한 사람이 30K 컨텍스트로 채팅 한 건 돌리는 동안에도, 같은 GPU 위에서 76개 동시 슬롯이 여유롭다.
"한 줄 옵션이 76배?" 의심스러운 숫자다. 하지만 KV cache가 LLM 메모리에서 차지하는 비중을 알면 이상한 일이 아니다. 이 글은 KV cache가 무엇이고, 왜 이렇게 폭발적으로 메모리를 잡아먹으며, FP8로 옮기면 무엇이 달라지는지를 풀어쓴다.
1. KV cache가 도대체 무엇인가
LLM은 토큰을 한 번에 하나씩 생성한다. 매 토큰마다 attention을 계산해야 하는데, attention의 입력은 지금까지 본 모든 토큰의 key와 value다. 매번 처음부터 다시 계산하면 O(N²)가 되니, 이전 단계에서 계산한 K·V를 캐시에 저장해 재사용한다 — 이게 KV cache다.
매 토큰마다 K·V 쌍 하나가 추가된다. 그리고 모델이 다음 토큰을 만들 때, 지금까지 누적된 K·V 전부를 다시 들여다본다. 그러니 컨텍스트가 길어질수록 KV cache의 메모리 점유가 폭발적으로 커진다.
2. KV cache는 얼마나 메모리를 잡아먹는가
구체적으로 계산해보자. 한 토큰의 K·V 쌍 크기는:
per-token KV size = 2 (K, V) × num_layers × num_kv_heads × head_dim × bytes_per_elementQwen3.6을 예로 들면 (단순화):
- 레이어 40개 (이 중 attention은 10개라 더 작긴 함)
- KV head 8개 (GQA)
- head 차원 128
- FP16 = 2바이트
한 토큰 K·V = 2 × 10 × 8 × 128 × 2 = 약 40KB. 32K 컨텍스트면 한 요청이 32,768 × 40KB = 약 1.3GB를 KV cache에만 쓴다.
한 요청만 봐도 1.3GB. 동시 16요청이면 21GB가 KV cache에만 들어간다. 모델 가중치 35GB와 합치면 56GB — 80GB GPU에서 시스템 여유까지 빼면 빠듯하다.
여기서 "왜 동시 요청 수가 KV cache 때문에 막히는가"가 보인다. GPU가 빠른지 느린지 이전에, KV cache가 들어갈 자리가 없어서 새 요청을 못 받는 것이다.
3. FP8 KV가 바꾸는 풍경
KV cache의 한 원소는 보통 BF16 또는 FP16으로 저장된다 — 한 개에 2바이트.
--kv-cache-dtype fp8옵션은 이걸 1바이트로 줄인다.왜 단순히 "절반"이 아니라 "76배"인가? 두 효과가 겹친다.
- 1차 효과 — 메모리 절반: 같은 21GB에 두 배 많은 KV가 들어간다.
- 2차 효과 — PagedAttention의 동적 할당: 모든 요청이 32K를 채우진 않는다. 짧은 채팅(평균 1~2K)이 다수면, KV가 작은 요청 수십 개가 한 GPU에 들어찬다. 이 두 효과가 곱해져 76배가 된다.
4. 정밀도 손실은? — 사실상 없음
"K·V를 8비트로 줄이면 응답 품질이 망가지지 않을까?" — 합리적 의심이다.
답: Blackwell에서는 거의 없다. 두 가지 이유다.
- FP8 어큐뮬레이션 정밀도: KV는 저장만 FP8이고, attention 내적 누산은 FP32로 한다. 즉 누산 정밀도는 그대로 유지된다.
- Blackwell의 FP8 회로 품질: 이전 세대보다 dynamic range·정밀도가 향상돼, 캘리브레이션 없이도 BF16 기준 perplexity 차이가 0.1% 이내라는 외부 벤치마크가 다수.
그래서 vLLM 공식 문서도 Blackwell에서는
--kv-cache-dtype fp8를 기본 권장으로 분류한다.
5. 트레이드오프 — 모든 GPU에 좋지는 않다
- Blackwell이 아니면 권장 안 됨. Hopper(H100)는 FP8 어큐뮬 정밀도가 떨어져, 긴 컨텍스트에서 누적 오차가 보일 수 있다.
- 아주 긴 컨텍스트(>100K)에서는 별도 검증 필요. 누산 오차가 길이에 비례해 쌓이는 케이스가 보고됨.
- 특정 모델은 호환 안 됨. KV head 수가 적은 일부 옛 모델은 FP8 KV로 품질 손실이 눈에 띄게 보일 수 있다.
정리
옵션 한 줄로 동시 슬롯 76배가 가능한 이유는 KV cache가 LLM 메모리에서 차지하는 위상이 그만큼 크기 때문이다.
- KV cache = 자기회귀 디코딩 중 매 토큰마다 누적되는 attention K·V 텐서.
- 긴 컨텍스트 + 동시 요청에서는 KV cache가 모델 가중치만큼 메모리를 잡아먹는다.
- FP8로 저장하면 한 원소 크기가 절반(2바이트→1바이트), 같은 메모리에 두 배 KV.
- PagedAttention의 동적 할당과 결합하면 짧은 채팅 다수에서 동시 슬롯이 폭증.
- Blackwell의 FP8 어큐뮬 정밀도가 충분해 품질 손실은 사실상 무시 가능.
한 줄로 줄이면, "긴 컨텍스트·다중 사용자 환경에서 KV cache는 모델 가중치만큼이나 중요한 1급 자원"이다. 그 자원을 절반 크기로 다루는 작은 옵션 하나가, 동시 사용자 수 같은 운영 지표를 두 자릿수로 끌어올린다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
Ralph Loop — bash while true + LLM CLI가 만든 어이없게 강력한 에이전트 패턴 (0) 2026.05.07 GPU 스케줄러를 Ollama warmup에서 vLLM 컨테이너로 옮긴 과정 — 시작·종료 시퀀스를 다시 짜다 (0) 2026.05.06 RAG 청크 맥락에서 thinking을 꺼야 하는 이유 — enable_thinking=False가 필요한 순간 (0) 2026.05.06 OpenAI-compat 표준화로 어댑터 100줄 들어내기 — passthrough가 가져온 코드 청결도 (0) 2026.05.06 vLLM reasoning_parser — <think> 블록을 정규식 말고 구조로 받는 법 (0) 2026.05.06 Ollama에서 vLLM으로 백엔드를 바꿨더니 throughput이 148% 올랐다 — 같은 모델, 다른 엔진 (0) 2026.05.06 Gemma 4에서 Qwen 3.6으로 갈아탔다 — 두 모델을 모든 지표로 비교한 기록 (0) 2026.05.06 Fused 커널은 왜 3~4배 빠른가 — GPU 메모리 계층과 Marlin의 비밀 (0) 2026.05.05 Q4 양자화는 GPU 안에서 어떻게 동작하나 — Blackwell FP8 텐서코어와의 만남 (0) 2026.05.05 FP8이 왜 FP16보다 45% 빠를까 — Blackwell GPU의 '텐서코어 네이티브 처리' 이해하기 (0) 2026.05.05