~ / posts / developer

vLLM KV 캐시 용량 계산, 동시 요청 수와 GPU 비용으로 설정 고르기

H100 한 장에 Qwen3-8B를 올리면 요청마다 32K 토큰을 미리 잡을 때 12개, 블록 단위로 잡으면 263개, fp8 KV면 526개를 동시에 담아요. 토큰당 KV 공식, 모델별 용량표, 월 GPU 비용, 설정별 추천과 체크리스트를 정리했어요.

vLLM으로 모델을 띄워 놓고 "이 GPU 한 장으로 동시 사용자를 몇 명까지 받을 수 있지?", "gpu_memory_utilization이나 kv_cache_dtype을 바꾸면 얼마나 달라지지?"를 계산하고 계신가요? 결론부터 말하면 동시 처리 수는 (GPU 메모리 × 사용 비율 − 가중치 − 활성값) ÷ 토큰당 KV 바이트 ÷ 요청당 평균 토큰 수로 거의 정해져요. H100 80GB 한 장에 Qwen3-8B를 올리면 요청 평균 1,500토큰 기준 약 263개, KV 캐시를 fp8로 저장하면 약 526개예요.

블록 테이블이 어떻게 돌아가는지는 알고 있다고 보고, 이 글은 설정을 고르는 데 필요한 숫자만 다뤄요. config.json으로 토큰당 KV 크기를 계산하고, 공식 가격표 단가로 월 GPU 비용을 낸 뒤, 어떤 값을 언제 바꿀지 정리해요.

광고

토큰당 KV 캐시 크기 계산 공식

디코드 단계에서 모델은 지금까지 나온 모든 토큰의 Key·Value 벡터를 층마다 보관해요. 그래서 토큰 하나가 차지하는 KV 메모리는 다음과 같아요.

토큰당 KV 바이트 = 2(K와 V) × 층 수 × KV 헤드 수 × 헤드 차원 × 자료형 바이트

여기서 쓰는 값은 모두 모델의 config.json에 있어요. num_hidden_layers(층 수), num_key_value_heads(KV 헤드 수), head_dim 또는 hidden_size ÷ num_attention_heads(헤드 차원)예요. 자료형은 bf16·fp16이면 2바이트, fp8이면 1바이트예요. 주의할 점은 어텐션 헤드 수가 아니라 KV 헤드 수를 넣어야 한다는 거예요. GQA(그룹 쿼리 어텐션)를 쓰는 요즘 모델은 KV 헤드가 어텐션 헤드보다 훨씬 적어서, 어텐션 헤드 수를 넣으면 몇 배 크게 계산돼요.

Hugging Face에 공개된 설정 파일(Qwen3-8B config.json, Qwen2.5-7B-Instruct config.json 등)로 직접 계산한 값이에요. 비교용으로 PagedAttention 논문(Kwon 외, SOSP 2023)이 예로 든 OPT-13B도 넣었어요. OPT-13B는 GQA가 없는 옛 구조라 KV 헤드가 40개 그대로예요.

모델층KV 헤드헤드 차원토큰당 KV (bf16)1,500토큰 요청 1개32,768토큰 요청 1개
Qwen2.5-7B28412856KiB82MiB1.75GiB
Mistral-7B v0.3328128128KiB188MiB4.0GiB
Qwen3-8B368128144KiB211MiB4.5GiB
Qwen2.5-32B648128256KiB375MiB8.0GiB
Qwen2.5-72B808128320KiB469MiB10.0GiB
OPT-13B (논문 예시)4040128800KB약 1.2GB약 26.8GB
모델별 토큰 하나의 KV 캐시 크기 (bf16)
Qwen2.5-7BQwen2.5-7B · 56KiB56KiBMistral-7B v0.3Mistral-7B v0.3 · 128KiB128KiBQwen3-8BQwen3-8B · 144KiB144KiBQwen2.5-32BQwen2.5-32B · 256KiB256KiBQwen2.5-72BQwen2.5-72B · 320KiB320KiBOPT-13BOPT-13B · 800KiB800KiB

파라미터 수가 비슷해도 층 수와 KV 헤드 수에 따라 두 배 넘게 차이 나요.

단위: KiB · 자료: 각 모델 config.json의 층·KV 헤드·헤드 차원으로 직접 계산 (OPT-13B는 PagedAttention 논문 수치 819,200바이트를 KiB로 환산)
표로 보기
구분토큰당 KV
Qwen2.5-7B56
Mistral-7B v0.3128
Qwen3-8B144
Qwen2.5-32B256
Qwen2.5-72B320
OPT-13B800

MiB·GiB(1024 단위)와 MB·GB(1000 단위)를 섞으면 수십 % 어긋난 용량 계획이 나와요. 단위가 헷갈리면 데이터 용량 변환기에서 1000 기준과 1024 기준을 나란히 확인해 보세요. 한국어 서비스라면 요청당 토큰 수 자체가 영어보다 많게 나오는 경우가 흔해서, 평균 토큰 수는 한국어 LLM 토큰 비용 비교에서 정리한 것처럼 실제 토크나이저로 세야 해요.

GPU 한 장에 동시 요청이 몇 개 들어가나

vLLM은 시작할 때 가중치를 올리고 메모리를 측정한 뒤, gpu_memory_utilization 한도 안에서 남은 공간을 전부 KV 블록으로 잘라 둬요. vLLM Engine Arguments 문서에 따르면 이 값의 기본은 0.92이고, 인스턴스마다 따로 적용돼요.

H100 80GB 한 장에 Qwen3-8B(bf16 가중치 8,190,735,360개 × 2바이트 ≈ 15.26GiB)를 올리는 경우로 계산해 볼게요. 계산을 단순하게 하려고 GPU 메모리를 80GiB로, 활성값·CUDA 그래프 몫을 4GiB로 가정했어요. 활성값은 모델·배치 설정마다 달라서 실제 값은 시작 로그로 확인해야 해요.

H100 80GiB를 남김 6.4, 가중치 15.3, 활성값 4, KV 블록 54.3GiB로 나눈 막대와, 토큰당 144KiB로 나눠 약 39만 토큰, 요청당 1,504토큰으로 나눠 약 263개 동시 요청이 되는 세 단계 계산
메모리 한도에서 가중치와 활성값을 빼고 남는 KV 블록이 동시 처리 수를 정해요
  1. 쓸 수 있는 메모리: 80 × 0.92 = 73.6GiB
  2. KV 블록 몫: 73.6 − 15.26 − 4 = 약 54.34GiB
  3. 담을 수 있는 토큰: 54.34GiB ÷ 144KiB = 약 395,717토큰
  4. 요청당 평균 입력 1,000 + 출력 500 = 1,500토큰이면 16토큰 블록 94개(1,504슬롯)가 필요해요. 395,717 ÷ 1,504 = 약 263개

같은 메모리로 요청마다 최대 길이를 통째로 예약하는 방식이라면 숫자가 확 줄어요. 아래 차트가 그 비교예요.

H100 1장 · Qwen3-8B 동시 요청 수 (메모리 기준 상한)
KV bf16KV fp8
010020030040050060032K 통째 예약 · KV bf16 12개32K 통째 예약 · KV fp8 24개32K 통째 예약8K 통째 예약 · KV bf16 48개8K 통째 예약 · KV fp8 96개8K 통째 예약블록 단위(평균 1500토큰) · KV bf16 263개블록 단위(평균 1500토큰) · KV fp8 526개블록 단위(평균 1500토큰)

통째 예약은 요청마다 최대 길이만큼 연속으로 잡는 기존 방식이에요. 블록 단위는 실제 길이만큼만 잡아요.

단위: 개 · 자료: gpu_memory_utilization 0.92, 활성값 4GiB 가정, config.json 값으로 직접 계산
표로 보기
구분KV bf16KV fp8
32K 통째 예약1224
8K 통째 예약4896
블록 단위(평균 1500토큰)263526

이 차이는 이론만이 아니에요. PagedAttention 논문은 기존 시스템의 KV 메모리 중 실제 토큰이 쓴 비율이 20.4~38.2%뿐이었다고 측정했고, OPT-13B·ShareGPT 실험에서 vLLM이 동시에 처리한 요청 수가 출력 길이를 미리 안다고 가정한 Orca(Oracle)보다 2.2배, 최대 길이로 예약하는 Orca(Max)보다 4.3배 많았다고 적었어요. vLLM 공식 블로그는 블록 방식의 낭비가 4% 미만이라고 밝혀요.

위 계산은 모든 요청이 마지막 길이까지 자란 순간 기준이라 보수적이에요. 반대로 max_num_seqs나 연산량이 먼저 막히면 이 숫자까지 못 가요. "이보다 많이는 못 받는다"는 기준선으로 쓰세요.

GPU 크기가 작으면 가중치가 다 먹는다

같은 Qwen2.5-7B라도 GPU 크기에 따라 KV 몫이 크게 달라져요. Lambda GPU 클라우드 가격표의 1장 인스턴스 단가(2026-10-25 확인, 세금 별도)로 계산했어요.

GPU (1장)시간당 요금KV 블록 몫동시 요청 (bf16)동시 요청 (fp8 KV)
A10 24GB1.29달러약 3.9GiB4896
H100 SXM 80GB4.29달러약 55.4GiB6891,379

A10에서는 메모리의 60% 가까이를 가중치(약 14.2GiB)가 차지해서 KV 몫이 4GiB가 안 돼요. 요금은 H100의 30%인데 메모리 기준 동시 처리는 7% 수준이라, 동시 요청이 많은 서비스라면 슬롯 하나당 단가가 오히려 비싸요. 연산 속도는 H100이 훨씬 빠르니 이 표는 메모리 쪽 비교로만 읽으세요. 같은 식으로 32B를 H100 한 장에 bf16으로 올리면 KV 몫이 약 8.6GiB라 평균 1,500토큰 요청 23개가 한계예요.

월 GPU 비용으로 바꿔 보기

메모리 상한을 GPU 대수로 바꾸면 비용 차이가 바로 보여요. 피크 때 평균 1,500토큰 요청 500개를 동시에 담아야 한다고 두고, Qwen3-8B·H100 단가 시간당 4.29달러 × 730시간(한 달)으로 계산했어요. 필요한 GPU 수는 500을 앞 차트의 장당 동시 요청 수로 나눠 올림한 값이에요.

동시 500요청을 담는 데 드는 월 GPU 비용 (메모리 기준 · Qwen3-8B · H100)
32K 통째 예약(42장)32K 통째 예약(42장) · 131,531달러131,531달러8K 통째 예약(11장)8K 통째 예약(11장) · 34,449달러34,449달러블록 단위 bf16(2장)블록 단위 bf16(2장) · 6,263달러6,263달러블록 단위 fp8(1장)블록 단위 fp8(1장) · 3,132달러3,132달러

메모리만 본 하한이에요. 실제 대수는 처리량(초당 토큰) 목표까지 넣어 정해야 해요.

단위: 달러 · 자료: Lambda 가격표 H100 SXM 1장 시간당 4.29달러(2026-10-25 확인) × 730시간으로 직접 계산
표로 보기
구분월 비용
32K 통째 예약(42장)131,531
8K 통째 예약(11장)34,449
블록 단위 bf16(2장)6,263
블록 단위 fp8(1장)3,132

32K 통째 예약은 극단적인 예지만, 출력 길이를 모를 때 넉넉히 잡는 습관이 얼마나 비싼지 보여 줘요.

처리량 쪽으로는 논문 초록이 같은 지연에서 FasterTransformer·Orca 대비 2~4배라고 밝혀요. 같은 GPU 시간으로 토큰을 2~4배 만드니 출력 토큰당 비용은 25~50% 수준이 되는 셈이에요. vLLM 블로그의 Hugging Face Transformers 대비 최대 24배는 2023년 당시 버전끼리 비교한 숫자라 지금 그대로 대입하면 안 돼요.

지연과 품질에서 내는 값

지연. 블록 테이블을 따라가는 어텐션 커널은 공짜가 아니에요. 논문 측정에서 어텐션 커널 지연이 FasterTransformer 구현보다 20~26% 길었어요. 다만 이 비용은 어텐션 연산에만 붙고, 배치를 키워 얻는 이득이 훨씬 커서 같은 지연에서 처리량이 2~4배 나왔어요. 요청 하나를 혼자 돌리는 벤치마크로만 재면 이 이득이 보이지 않으니, 동시 부하로 재야 해요.

선점 비용. 블록을 미리 예약하지 않으니 생성 도중 빈 블록이 동날 수 있어요. 그러면 vLLM은 일부 요청을 내보내고, 나중에 다시 계산해요. vLLM 최적화 문서는 V1에서 선점된 요청을 재계산(RECOMPUTE)으로 되살린다고 적고, 선점이 잦으면 gpu_memory_utilization을 올리거나 max_num_seqs·max_num_batched_tokens를 낮추라고 안내해요. 재계산은 그때까지의 토큰을 프롬프트처럼 다시 프리필하는 거라, 1,200토큰까지 생성하다 밀려난 요청은 1,200토큰을 다시 처리해요. 그 요청의 지연이 튀고, 다른 요청이 쓸 연산도 그만큼 빼앗겨요. 논문 실험에서는 블록이 작을수록 스왑(CPU로 복사) 비용이 커졌고, 재계산 비용은 블록 크기와 무관하게 일정했어요.

품질. 블록 단위 할당 자체는 근사가 아니라서 출력이 바뀌지 않아요. 품질이 걸리는 건 kv_cache_dtype=fp8이에요. vLLM Quantized KV Cache 문서는 보정 없이 fp8만 지정하면 모든 스케일이 1.0으로 잡히고, 정확도를 높이려면 llm-compressor로 데이터셋 보정을 하라고 권장해요. FlashAttention 3 백엔드에서는 쿼리까지 FP8로 계산한다는 점, 슬라이딩 윈도 층처럼 양자화에 민감한 층은 --kv-cache-dtype-skip-layers로 뺄 수 있다는 점도 적혀 있어요. 문서에 품질 저하 폭이 숫자로 나와 있지 않으니, 켜기 전후로 기존 평가 세트를 꼭 다시 돌려야 해요.

설정값별 선택 가이드

설정바꾸면 생기는 일추천
block_size작으면 커널 병렬성 손해, 크면 마지막 블록 낭비 증가기본값 유지 (논문 기본 16)
gpu_memory_utilization0.01 올릴 때마다 H100에서 약 0.8GiB, Qwen3-8B 기준 약 5,825토큰 추가혼자 쓰는 GPU면 기본 0.92, 같이 쓰면 나눠 배정
kv_cache_dtypefp8이면 토큰당 바이트 절반, 담는 토큰 2배용량이 모자랄 때 1순위 후보, 품질 평가 필수
max_model_len최대 길이 요청 하나가 KV에 다 들어가야 서버가 떠요실제로 필요한 길이로 낮춰 두기
max_num_seqs한 스텝에 묶는 시퀀스 상한선점이 잦으면 낮추기
접두사 캐싱같은 앞부분의 KV 블록을 재사용공통 시스템 프롬프트가 길면 켜 두기
tensor_parallel_size가중치를 여러 GPU로 나눠 장당 KV 몫이 늘어요큰 모델이 한 장에서 KV 몫이 너무 작을 때

블록 크기는 손댈 이유가 거의 없어요. 마지막 블록 빈칸은 평균 (B − 1) ÷ 2토큰이라, 직접 계산하면 블록 16에서 요청당 7.5토큰(1,500토큰 요청의 0.5%, 300토큰 요청의 2.5%)이에요. 블록 128이면 63.5토큰으로 각각 4.2%, 21.2%까지 올라가요. 짧은 요청이 많을수록 큰 블록이 손해예요.

논문 7.2절도 긴 대화(ShareGPT)에서는 16~128, 짧은 요청(Alpaca)에서는 16·32만 좋았다며 기본값을 16으로 정했어요. Engine Arguments 문서상 block_size는 지정하지 않으면 기본값을 쓰니, 특별한 이유가 없으면 비워 두세요.

병렬 샘플링(n>1)과 빔 서치. 한 프롬프트에서 답을 여러 개 뽑으면 프롬프트 블록을 공유해서 메모리가 n배로 늘지 않아요. 논문 측정(OPT-13B)에서 공유로 아낀 블록 비율은 병렬 샘플링 6.1~9.8%(Alpaca)·16.2~30.5%(ShareGPT), 빔 서치 37.6~55.2%(Alpaca)·44.3~66.3%(ShareGPT)였어요. 용량을 계산할 때 n개 답의 출력 토큰은 따로, 프롬프트 토큰은 한 번만 세면 돼요.

초안 모델을 같이 올린다면. 투기적 디코딩용 초안 모델은 가중치와 KV를 한 벌 더 써서 타깃 모델의 KV 몫을 줄여요. 그 비용 비교는 투기적 디코딩 방식 고르기에 정리해 두었어요.

상황별 추천

상황먼저 할 일이유
사내 도구, 동시 사용자 수십 명기본값 그대로H100 한 장 메모리 상한이 수백 개라 여유가 커요
동시 요청이 메모리 상한에 닿음kv_cache_dtype=fp8 시험 → 품질 평가같은 GPU에서 담는 토큰이 2배예요
긴 문서 RAG, 입력이 수만 토큰max_model_len을 실제 필요 길이로, 접두사 캐싱 확인요청 하나가 수 GiB라 동시 수가 한 자릿수로 떨어져요
32B 이상을 GPU 한 장에가중치 양자화 또는 텐서 병렬 2장bf16 32B면 KV 몫이 8.6GiB뿐이에요
다른 프로세스와 GPU 공유인스턴스마다 gpu_memory_utilization 나눠 배정문서상 이 값은 인스턴스별 한도예요
선점 경고가 계속 찍힘사용 비율 올리기 또는 max_num_seqs 낮추기재계산은 밀려난 토큰을 처음부터 다시 처리해요

도입 체크리스트

띄우기 전 계산(토큰당 KV, 요청당 평균 토큰, 필요한 동시 요청, 가중치와 KV 합계), 시작 로그 확인(GPU KV cache size, Maximum concurrency, max_num_seqs, 접두사 캐싱), 부하와 운영 지표(KV 사용률 피크, 선점 횟수, TTFT·TPOT p99, fp8 품질 평가)를 세 칸으로 나눈 체크리스트
왼쪽 칸 계산값과 가운데 칸 로그 값이 크게 다르면 가정부터 다시 봐요
  1. 토큰당 KV 바이트를 계산해요. 어텐션 헤드 수가 아니라 KV 헤드 수를 넣었는지 다시 봐요.
  2. 요청당 평균 토큰과 피크 동시 요청 수를 실제 데이터로 재요. 한국어 비중이 높으면 실제 토크나이저로 세고, 평균과 함께 p95도 봐 둬요.
  3. 시작 로그의 KV 캐시 크기·최대 동시 처리 배수를 계산값과 비교해요. 크게 다르면 활성값 가정이나 단위(GiB/GB)가 틀린 거예요.
  4. 설정은 하나씩 바꾸고 같은 부하로 재요. TTFT·TPOT의 p50·p99와 초당 토큰을 기록해요.
  5. 운영 지표를 걸어 둬요. KV 사용률이 피크에 붙어 있거나 선점 횟수가 늘면 용량을 늘릴 때예요. fp8을 켰다면 품질 평가도 다시 돌려요.

자주 묻는 질문

nvidia-smi에 메모리가 90% 넘게 차 있는데 꽉 찬 건가요?

아니에요. vLLM은 시작할 때 gpu_memory_utilization(기본 0.92)만큼 미리 잡아서 KV 블록으로 잘라 두기 때문에, 요청이 하나도 없어도 사용량이 높게 보여요. 실제로 블록이 얼마나 쓰이는지는 vLLM의 통계 로그나 Prometheus 지표의 KV 캐시 사용률로 봐야 해요.

메모리를 아끼려고 gpu_memory_utilization을 낮추면 좋은가요?

다른 프로세스와 GPU를 나눠 써야 하는 게 아니면 손해예요. H100에서 0.01 낮출 때마다 약 0.8GiB, Qwen3-8B 기준 약 5,825토큰분의 블록이 사라져 동시 처리 수가 줄고 선점이 잦아져요. 공식 문서도 선점이 잦으면 이 값을 올리라고 안내해요.

kv_cache_dtype을 fp8로 바꾸면 품질이 떨어지나요?

모델과 보정 방법에 따라 달라서 미리 숫자로 말하기 어려워요. vLLM 문서는 보정 없이 fp8만 지정하면 스케일이 1.0으로 잡히고, 정확도를 원하면 llm-compressor로 데이터셋 보정을 하라고 권장해요. 켜기 전후로 같은 평가 세트를 돌려 비교하는 게 가장 확실해요.

block_size를 1로 줄이면 낭비가 0이 되니 가장 좋지 않나요?

낭비는 줄지만 커널이 블록 테이블을 토큰마다 따라가야 해서 GPU 병렬성이 떨어져요. 논문 실험에서도 너무 작은 블록은 성능이 나빴고, 스왑 비용도 블록이 작을수록 컸어요. 기본값 16에서 요청당 평균 낭비는 7.5토큰이라 1,500토큰 요청이면 0.5%뿐이에요.

n=4로 답을 네 개 뽑으면 KV 메모리도 네 배가 드나요?

아니에요. 프롬프트 블록은 참조 횟수로 공유되고, 각 답이 새로 만드는 출력 토큰만 따로 블록을 써요. 마지막 프롬프트 블록에 처음 쓸 때 그 블록 하나만 복사돼요. 용량 계획은 프롬프트 1벌 + 출력 n벌로 계산하면 돼요.

정리

참고한 자료

#vLLM KV 캐시 계산#gpu_memory_utilization 설정#kv_cache_dtype fp8#LLM 동시 요청 수#block_size 선택#vLLM 선점 RECOMPUTE#LLM 서빙 GPU 비용#PagedAttention 용량 계획
← 이전 글타입스크립트 strict 옵션 고르기, 타입 주석 판단표와 tsc 검사 한계

더 둘러보기

💻 개발자 다른 도구

🔥 많이 찾는 도구

🔮 재미로 한 번 더

📝 읽어 볼 만한 글

글 전체 보기 →