LabHub

한국어

시작하기
블로그

블로그

작은 모델로 vLLM 직접 재 보기: 부하, KV 캐시 용량, 접두사 캐시, 청크 크기

상태: 초안 (2026-10-07), 실측 글입니다. 모든 숫자는 필자가 아래 환경에서 직접 잰 값이고, 계산한 것은 「계산」, 못 잰 것은 「미실측」으로 표시했습니다. 한 환경, 한 모델, 한 번의 시험 묶음이므로 절대값보다 모양과 원리를 가져가시기 바랍니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」으로 표시합니다.

1. 무엇을 알고 싶었는가

vLLM 문서는 옵션이 많고, 면접과 운영에서는 「이 옵션을 켜면 무엇이 얼마나 달라지는가」를 묻습니다. 4편은 원리(KV 캐시 식, 접두사 캐시, 스케줄러)를 문서로 정리했고, 이 글은 같은 질문을 숫자로 답합니다.

  1. 동시 요청이 늘면 처리량과 지연은 어디서 꺾이는가.
  2. KV 캐시 용량은 식으로 계산한 값과 맞는가, 그리고 그 용량이 꺾이는 지점과 어떻게 이어지는가.
  3. 접두사 캐시, KV 8비트, CUDA 그래프, 청크 크기, 동시 시퀀스 상한은 각각 무엇을 바꾸는가.

2. 환경과 방법

항목 값
GPU 8GB급 노트북 GPU 한 장(메모리 약 7.6GiB), 다른 작업 없음
서버 vLLM 0.31.0 공식 이미지, PyTorch 2.13(CUDA 13)
모델 Qwen3-1.7B(bf16, 서버가 적재한 가중치 3.22GiB), 모델 설정은 28층, KV 헤드 8, head_dim 128
공통 옵션 --max-model-len 4096, --gpu-memory-utilization 0.85, --max-num-seqs 64(비교 시험만 16)
클라이언트 서버와 같은 컨테이너 안의 비동기 클라이언트, 스트리밍으로 토큰마다 시각을 기록해 첫 토큰 시간(TTFT)과 토큰 간격(ITL)을 계산
입력 요청마다 다른 무작위 단어열(접두사 캐시가 우연히 적중하지 않게), 출력 128토큰(ignore_eos), 온도 0
반복 동시성 c 에 대해 3c 개 요청(긴 입력 시험은 2c 개), 첫 요청 둘은 예열로 버림

서버는 시험마다 새로 띄웠습니다(KV 캐시 용량이 첫 기동과 재기동에서 다릅니다, 3절 참고). 표의 값은 한 번의 묶음에서 읽었고, 반복 변동은 따로 쟀습니다. 같은 서버에서 같은 시험을 5번 반복했을 때(동시 32, 입력 512, 출력 128) 처리량이 831, 833, 831, 832, 831 tok/s, TTFT 중앙값이 666~670ms, TTFT p95 가 1,606~1,608ms, ITL p99 가 223~224ms 로 0.5% 안이었습니다. 서버를 3번 다시 띄워도 동시 1 은 66 tok/s, 동시 32 는 831~832 tok/s 로 같았습니다. 접두사 캐시(3회)와 끼어들기(3회, 최대 ITL 227, 226, 226ms)도 같았습니다. 이 환경(GPU 한 장, 다른 작업 없음)에서 시험 자체의 변동은 작다는 뜻이고, 다른 장비나 다른 이웃이 있는 환경으로 옮기면 달라질 수 있습니다.

3. KV 캐시 용량은 식과 맞는다

토큰당 KV 크기는 2 × 층 수 × KV 헤드 수 × head_dim × 자료형 바이트입니다. 이 모델은 28층, KV 헤드 8개, head_dim 128 이고 KV 를 bf16(2바이트)으로 두므로 다음과 같습니다(계산).

2 × 28 × 8 × 128 × 2 = 114,688 바이트/토큰 (112KiB)
설정 서버 로그의 KV 캐시 메모리 서버 로그의 토큰 수 토큰당 바이트(계산) 식의 값
bf16 KV (기본) 2.36GiB 22,048 약 114,900 114,688
fp8 KV (--kv-cache-dtype fp8) 2.24GiB 41,920 약 57,400 57,344

4. 동시 요청을 늘리면: 처리량은 둔해지다가 KV 용량에서 대기열이 폭발한다

입력 512, 출력 128, --max-num-seqs 64:

동시 요청 처리량(tok/s) TTFT 중앙 TTFT p95 ITL 중앙 ITL p99
1 66 73ms 73ms 15ms 16ms
8 383 351ms 474ms 17ms 19ms
32 831 666ms 1.6s 24ms 223ms
64 787 5.7s 6.7s 25ms 228ms

입력을 2,048토큰으로 늘리면 한 요청이 2,176토큰이라 캐시가 담는 요청은 약 10개입니다(계산).

동시 요청 처리량(tok/s) TTFT 중앙 TTFT p95
4 151 0.73s 1.0s
16 207 4.0s 6.7s
32 213 12.5s 16.7s

동시 4개에서 16개로 4배를 늘렸는데 처리량은 37% 만 늘었고 TTFT 는 5.4배가 되었습니다. 긴 입력에서는 처리량이 꺾이는 동시성이 훨씬 낮아집니다. 운영에서 동시성 한도를 「GPU 가 버티는 만큼」이 아니라 「평균 입력 길이로 KV 가 담는 요청 수」로 잡아야 하는 이유입니다.

KV 가 모자랄 때: 대기만 하는가, 쫓겨나는가

서버 지표(vllm:num_preemptions_total)로 선점 횟수를 읽었습니다. 입력 512, 동시 32 를 5번 반복하는 동안(KV 가 담는 요청 수 약 34개 이내)은 선점이 0건이었습니다. 입력 2,048, 동시 32(요청 64개)를 보내자 7건, 이어서 입력 1,500, 출력 256, 동시 64(요청 128개)를 보내자 누적 29건(이 부하에서 22건)이었습니다. 이때 TTFT 중앙값은 각각 12.5초, 35.4초였습니다. KV 가 모자라면 요청이 대기열에서 기다리는 데 그치지 않고, 실행 중이던 요청이 KV 를 내놓고 쫓겨났다가 처음부터 다시 처리됩니다(선점은 되돌려 놓은 요청을 다시 계산하는 것이라 입력 처리가 한 번 더 듭니다, 문서 기준). 선점 횟수가 0이 아닌 구간은 처리량이 늘지 않고 지연이 급증하는 구간이므로 알림 지표로 쓸 만합니다.

동시 시퀀스 상한(--max-num-seqs)은 대기열의 위치를 바꿀 뿐이다

동시 64, 입력 512:

설정 처리량 TTFT 중앙
--max-num-seqs 64 787 tok/s 5.7s
--max-num-seqs 16 608 tok/s 10.3s

상한을 16으로 낮추자 처리량이 23% 줄고 TTFT 가 1.8배가 되었습니다. 상한이 용량(약 34개)보다 낮으면 용량이 남는데도 대기열이 생기므로 손해이고, 용량보다 높아도 KV 가 모자라면 이득이 없습니다. 상한은 KV 용량이 담는 요청 수 근처에 맞추는 값입니다.

5. KV 8비트: 용량이 늘면 긴 입력의 지연이 줄어든다

시험 bf16 KV fp8 KV
동시 1, 입력 512 (처리량, ITL) 66 tok/s, 15ms 66 tok/s, 15ms
동시 16, 입력 2048 (처리량) 207 tok/s 303 tok/s (+46%)
동시 16, 입력 2048 (TTFT 중앙) 4.0s 0.94s (−76%)
동시 32, 입력 2048 (처리량) 213 tok/s 299 tok/s (+40%)
동시 32, 입력 2048 (TTFT 중앙) 12.5s 7.0s (−44%)
KV 정밀도 어텐션 백엔드 퍼플렉서티 bf16 대비
bf16 자동 선택(FLASH_ATTN) 17.7775 기준
bf16 FLASH_ATTN(고정, 반복 2회) 17.7796, 17.7796 +0.01%
bf16 FLASHINFER 17.7750 -0.01%
bf16 TRITON_ATTN 17.7732 -0.02%
fp8 FLASHINFER 17.4228 -2.0%
fp8 TRITON_ATTN 17.0340 -4.2%

6. 접두사 캐시: 공통 앞부분이 있으면 첫 토큰이 14배 빨라진다

3,000토큰의 공통 접두사에 요청마다 다른 32토큰 꼬리를 붙여 한 개씩 순서대로 보냈습니다(출력 1토큰).

설정 첫 요청 TTFT 이후 요청 TTFT 중앙 접두사가 전혀 없는 3,032토큰
접두사 캐시 켜짐(기본) 394ms 28ms 395ms
--no-enable-prefix-caching 395ms 393ms 393ms

7. 청크 프리필: 긴 입력이 디코딩을 멈추는 시간은 청크 크기가 정한다

디코딩 중인 요청 하나(300토큰)가 돌아가는 동안 3,500토큰짜리 입력 요청 3개를 끼워 넣고, 끼운 구간의 토큰 간격을 쟀습니다.

--max-num-batched-tokens 끼운 구간 ITL 최대 끼운 구간 ITL 중앙 긴 입력 자체의 TTFT
512 69ms 53ms 481~494ms
기본(확인한 값 2,048) 227ms(반복 3회 227, 226, 226) 14ms 466~480ms
8192(입력이 3,500토큰이라 실효 청크는 3,500) 445ms 14ms 461~474ms

8. CUDA 그래프를 끄면(--enforce-eager)

설정 서버 준비 시간 동시 1 처리량 동시 32 처리량 ITL 중앙(동시 1)
기본(CUDA 그래프) 71초(첫 기동, 컴파일 19초 포함), 이후 재기동 35~55초 66 tok/s 831 tok/s 15ms
--enforce-eager 30초 64 tok/s 814 tok/s 15ms

이 크기의 모델에서는 CUDA 그래프가 속도에 주는 이득이 약 3% 로 작았고(동시 1 에서 66 대 64), 서버 준비는 약 25~40초 빨랐습니다. 토큰당 14~15ms 걸리는 모델에서는 커널 시작 간격이 전체에서 차지하는 비율이 작기 때문으로 보입니다(추정). 더 작은 모델에서는 이득이 커질 수 있다고 적었는데, Qwen3-0.6B 로 같은 시험을 3회 반복하니 실제로 그랬습니다. 동시 1 에서 CUDA 그래프 기본 168 tok/s(ITL 6ms) 대 --enforce-eager 108 tok/s(ITL 9ms)로 그래프가 55% 빨랐고, 동시 32 에서는 1,557 대 1,503 tok/s 로 약 3.6% 였습니다. 서버 준비는 55초 대 30초 였습니다. 모델이 작아 토큰당 시간이 6ms 대면 커널을 시작하는 간격이 전체의 큰 몫이 되기 때문으로 보입니다(추정). 더 빠른 GPU 는 시험하지 못했습니다(미실측).

9. 서버를 처음 띄울 때 걸리는 시간

서버 로그의 시각으로 읽은 값입니다.

단계 값
프로세스 기동과 모듈 불러오기(엔진 초기화 시작까지) 약 15~17초
가중치 적재(디스크에서 GPU 로) 4~6초(모델 파일이 이미 있을 때)
엔진 초기화(컴파일, 프로파일링, KV 캐시 할당, CUDA 그래프 포착, 예열) 첫 기동 29초(컴파일 19초 포함), 컴파일 캐시가 있으면 7.5초
준비까지 합계 첫 기동 71초, 재기동 35~55초(LoRA 를 켠 서버는 70초)

모델을 내려받는 시간은 위에서 뺐습니다(별도). 오토스케일링에서 새 파드가 트래픽을 받기까지의 시간은 이 숫자에 이미지 받기와 모델 내려받기가 더해지므로, 미리 데운 레플리카와 모델 캐시 볼륨의 필요성이 숫자로 나옵니다.

10. 운영에서 가져갈 것

  1. 동시성 한도 = KV 용량 ÷ 요청당 토큰. 식은 3절, 근거는 4절입니다. 한도를 넘기면 처리량은 늘지 않고 TTFT 만 늘어납니다.
  2. TTFT p95 와 ITL p99 를 따로 봅니다. 중앙값은 멀쩡해도 꼬리가 큽니다(ITL p99 223ms).
  3. 공통 접두사를 설계로 만듭니다. 시스템 프롬프트와 문서를 앞에 두고 요청별 내용을 뒤에 두면 접두사 캐시가 14배의 이득을 줍니다.
  4. KV 8비트는 처리량 옵션이 아니라 용량 옵션입니다. 긴 입력에서 대기열이 길 때 효과가 큽니다. 품질 평가를 먼저 합니다.
  5. 청크 크기는 끊김과 입력 처리 속도의 교환입니다. 대화 서비스는 작게, 배치는 크게.
  6. 성능 시험은 입력을 매번 다르게 만들고, 하드웨어 한계와 대 봅니다.

11. 면접에서 나올 만한 질문

외부 근거: 논문과 공식 자료로 확인한 것

이 글의 수치는 모두 직접 잰 값입니다. 같은 현상을 보고한 외부 자료를 열어 수치와 조건을 확인했고, 우리 결과와의 관계를 적었습니다. 외부 수치는 모델, GPU, 부하가 달라 직접 비교하지 않고 방향과 구조의 일치만 판정했습니다.

주제 자료와 확인한 위치 확인한 내용과 조건 이 글과의 관계
KV 크기 식 Kwon 외, PagedAttention, SOSP 2023, arXiv 2309.06180, 3절과 표 1 OPT-13B 의 토큰당 KV 는 2(키·값) × 5,120(은닉 크기) × 40(층) × 2(바이트) = 800KB. A100 40GB 에서 가중치 26GB 를 빼고 12GB 를 KV 에 쓰면 15.7K 토큰이고, 12GiB ÷ 800KiB 의 산술과 맞음 지지. 이 글의 식(3절)과 같은 식
기존 시스템의 KV 낭비 같은 논문 1절과 그림 2 실제 토큰 상태로 쓰는 KV 비율이 20.4~38.2%. 저자들이 직접 구현한 Orca 변형 세 가지의 평균이며 FasterTransformer 는 포함되지 않음 관련. 블록 16토큰 단위 관리(이 글에서 확인한 기본값)가 이 낭비를 줄이는 방법
청크 크기와 토큰 간 지연 Agrawal 외, Sarathi-Serve, OSDI 2024, arXiv 2403.02310, 4.3절과 그림 6, 5.4.1절 청크(토큰 예산)가 작을수록 반복 시간이 짧아 토큰 간 지연이 줄고, 연산 한계 구간에서 반복 시간은 토큰 수에 선형. 청크 512 에서 전체 입력 처리 시간이 최대 약 25% 늘고(Yi-34B, 텐서 병렬 2), 2,048 에서는 거의 무시할 만함 지지. 7절의 방향과 같음. 우리 TTFT 증가(3~5%)는 모델·GPU 가 달라 크기 비교는 못 함
동시성과 처리량의 모양 같은 논문 3.1절과 그림 3(Mistral-7B, A100 한 장, 입력 1,024) 배칭이 디코딩 처리량을 거의 선형으로 올리고 입력 처리에는 거의 영향이 없음 제한적으로 지지. 21편의 짧은 문맥(동시 16까지 14~15배)과 같은 모양. 4절의 입력 512 표는 더 일찍 둔해짐
용량을 넘을 때의 지연 폭발 PagedAttention 6.2절 요청률이 시스템 용량을 넘으면 대기열이 끝없이 길어져 지연이 폭발 지지. 4절의 TTFT 8.6배와 같은 현상
용량 전에 처리량이 평탄해지는 경우 Recasens 외, 「Mind the Memory Gap」, arXiv 2503.08311, 3~5절 H100 에서 OPT-2.7B 등으로 큰 배치에서도 메모리 한계에 머물며, 배치 1 의 225 tok/s 가 배치 256 에서 7,607 tok/s(약 33.8배, 기대한 256배가 아님)에 그침 제한적으로 지지. 4절의 둔해짐이 KV 용량만의 문제가 아닐 수 있다는 근거
KV 8비트의 품질 vLLM 공식 블로그 「The State of FP8 KV-Cache and Attention Quantization in vLLM」(2026-04-22) 보정하지 않은 FP8(스케일 1.0)에서 추론 과제 정확도 하락이 Qwen3-30B-A3B-Thinking-2507 에서 최대 1~2점, Qwen3.5-27B 에서 최대 0.7점(H100) 지지. 문헌상 변화는 작지만 모델과 보정에 의존. 우리 모델(1.7B)과 우리 평가는 5절 참고
KV 8비트(정수) LMDeploy 문서(INT8 KV, OpenCompass) llama2-7b-chat MMLU 35.64에서 35.58, internlm2.5-chat-7b MMLU 72.30에서 72.27. FP8 이 아닌 INT8 이라 그대로 옮길 수 없음 관련. vLLM 의 --kv-cache-dtype fp8 과 다른 방식
선점 방식 vLLM 공식 문서(최적화 페이지), v0.30.0 scheduler.py, PagedAttention 4.5절과 7.3절 V1 의 선점은 스왑이 아니라 재계산이 기본이고, 희생 요청의 블록을 모두 반납하고 계산된 토큰 수를 0 으로 되돌려 대기열 앞에 넣음. 논문은 작은 블록에서 재계산이 효율적이라고 설명 지지. 4절의 선점 서술과 같음(선점 후 접두사 캐시 적중은 미확인)
기본값 v0.30.0 config/cache.py 접두사 캐시 기본 켜짐, 블록 크기 16 지지. 0.31.0 에서 직접 읽은 값(켜짐, 16, 청크 예산 2,048)과 같음

벤더·프로젝트 블로그(vLLM, LMDeploy)의 수치는 조건이 본문에 적힌 것만 옮겼습니다. ACM 의 PagedAttention 페이지는 열지 못해 arXiv 초록 페이지의 표기를 따랐습니다.

확인하지 못한 것

확인한 버전과 날짜

2026-10-07 기준. vLLM 0.31.0, PyTorch 2.13(CUDA 13), Qwen3-1.7B(bf16).

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다