상태: 초안 (2026-10-07), 실측 글입니다. 모든 숫자는 필자가 아래 환경에서 직접 잰 값이고, 계산한 것은 「계산」, 못 잰 것은 「미실측」으로 표시했습니다. 한 환경, 한 모델, 한 번의 시험 묶음이므로 절대값보다 모양과 원리를 가져가시기 바랍니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」으로 표시합니다.
1. 무엇을 알고 싶었는가
vLLM 문서는 옵션이 많고, 면접과 운영에서는 「이 옵션을 켜면 무엇이 얼마나 달라지는가」를 묻습니다. 4편은 원리(KV 캐시 식, 접두사 캐시, 스케줄러)를 문서로 정리했고, 이 글은 같은 질문을 숫자로 답합니다.
- 동시 요청이 늘면 처리량과 지연은 어디서 꺾이는가.
- KV 캐시 용량은 식으로 계산한 값과 맞는가, 그리고 그 용량이 꺾이는 지점과 어떻게 이어지는가.
- 접두사 캐시, 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 |
- 식이 로그와 일치합니다. 로그의 용량을 토큰 수로 나눈 값이 식과 0.2~0.3% 안에서 맞았습니다. KV 캐시 용량은 「남은 메모리 ÷ 토큰당 바이트」이므로, 어떤 모델이든 설정에서 읽은 숫자로 서버를 띄우기 전에 어림할 수 있습니다.
- KV 8비트는 같은 메모리에 토큰을 1.9배 담습니다. 22,048에서 41,920 토큰으로 늘었고, 토큰당 바이트가 정확히 절반이 되었습니다.
- 용량은 첫 기동과 재기동에서 달랐습니다. 같은 옵션인데도 파드에서 처음 띄운 서버는 22,048토큰이었고, 같은 파드에서 다시 띄운 서버 세 개는 모두 26,560토큰(약 20% 많음)이었습니다. 첫 기동에서는 컴파일과 프로파일링에 쓴 메모리가 KV 캐시 몫에서 빠지는 것으로 보이지만(추정, 직접 분해하지 않았습니다), 용량을 비교할 때는 메모리가 아니라 토큰당 바이트로 비교하고 서버의 기동 이력을 함께 적어야 합니다. 이 글의 표는 모두 첫 기동 서버(22,048토큰)에서 읽은 값입니다.
- 한 요청이 쓰는 토큰은 입력과 출력의 합입니다. 입력 512에 출력 128이면 640토큰이고 22,048토큰 캐시는 약 34개 요청을 동시에 담습니다(계산). 아래 꺾이는 지점이 이 숫자에서 나옵니다.
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 |
- 동시 8까지는 비례에 가깝게 늘었고(1에서 8, 5.8배, 효율 73%), 그 뒤로는 둔해졌습니다(8에서 32는 동시성 4배에 처리량 2.17배, 383에서 831). 1에서 32 전체로는 12.6배(효율 39%)입니다. 가중치를 읽는 비용을 나눠 갖는 효과(4편, 21편)는 짧은 문맥에서 동시 16까지 14~15배로 거의 선형이었으므로, 이 표에서 둔해지는 것은 입력 512 가 쌓인 KV 읽기와 입력 처리가 늘어서일 가능성이 큽니다(추정, 분해하지 않았습니다). 외부 연구도 큰 배치에서 어텐션의 메모리 읽기 때문에 KV 용량 한참 전에 처리량이 평탄해질 수 있다고 보고합니다(아래 「외부 근거」).
- 32에서 64로는 처리량이 오히려 줄었고 TTFT 는 8.6배(666ms에서 5.7s)로 뛰었습니다. 요청 하나가 640토큰이면 KV 캐시가 약 34개 요청까지만 담으므로, 64개를 보내면 절반 가까이는 자리가 날 때까지 대기열에서 기다립니다. 용량(KV)이 한계에 닿는 순간 TTFT 가 폭발합니다. 처리량은 이미 포화했으므로 동시성을 더 올려 봐야 지연만 늘어납니다.
- ITL 중앙값은 15에서 25ms 로 천천히 늘었고, p99 는 동시 32부터 약 220ms 로 뛰었습니다. 새 요청의 입력을 처리하는 단계가 디코딩 사이에 끼어들어 토큰 하나가 길게 늦어지는 것입니다(7절).
입력을 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%) |
- 짧은 문맥에서는 속도가 같습니다. 동시 1 에서 KV 읽기가 가중치 읽기에 비해 작아서 8비트의 이득이 없습니다.
- 이득은 용량에서 나옵니다. 3절의 1.9배 용량 덕에 긴 입력에서 대기열이 줄었고 TTFT 가 크게 줄었습니다. 즉 KV 8비트는 「더 빠르게 계산한다」가 아니라 「더 많이 동시에 담는다」입니다.
- 정확도는 퍼플렉서티로만 쟀고, 결과가 직관과 반대였습니다. 위키텍스트-2(테스트 분할, 토큰 약 30만 개 중 앞의 1,024토큰 구간 40개)를 서버에 보내 프롬프트 토큰의 로그 확률로 퍼플렉서티를 구했습니다(낮을수록 좋음). 같은 설정을 두 번 재면 값이 소수점 넷째 자리까지 같아(결정적) 오차를 따로 두지 않았습니다.
| 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% |
- bf16 은 백엔드가 달라도 같았습니다(17.773~17.780, 0.04% 안). 그래서 백엔드 차이는 측정 오차 수준입니다.
- fp8 KV 가 오히려 퍼플렉서티를 낮췄고, 두 백엔드의 값도 서로 달랐습니다(17.42 대 17.03). 양자화는 보통 오차를 더하므로 낮아질 이유가 없습니다. 이 결과를 「fp8 KV 가 품질을 높인다」로 읽으면 안 됩니다. 같은 8비트 형식인데 백엔드마다 결과가 2%p 갈린 것은 형식보다 커널의 수치 처리(스케일 적용, 누적 정밀도)가 더 크게 작용한다는 단서이며, 원인은 분해하지 않았습니다. 서버 로그는 스케일 보정 없이 fp8 을 쓰면 정확도가 떨어질 수 있다고 경고합니다.
- 정리하면 이 모델과 이 텍스트에서 fp8 KV 로 퍼플렉서티가 나빠진다는 증거는 얻지 못했지만, 방향이 설명되지 않아 품질이 보존된다는 증거로도 쓸 수 없습니다. 문헌(외부 근거 표)은 보정하지 않은 fp8 에서 추론 과제 정확도가 모델에 따라 최대 1~2점 떨어진다고 보고하므로, 도입 전에 자기 과제의 평가 세트로 확인해야 합니다(과제 정확도는 미실측).
- 처음 시험(bf16 4.217 대 fp8 4.015)은 서로 다른 텍스트를 읽은 값이라 무효였고, 같은 텍스트로 고쳐도 방향이 같아(4.2173 대 4.0148) 위와 같이 백엔드를 고정해 재확인했습니다.
6. 접두사 캐시: 공통 앞부분이 있으면 첫 토큰이 14배 빨라진다
3,000토큰의 공통 접두사에 요청마다 다른 32토큰 꼬리를 붙여 한 개씩 순서대로 보냈습니다(출력 1토큰).
| 설정 | 첫 요청 TTFT | 이후 요청 TTFT 중앙 | 접두사가 전혀 없는 3,032토큰 |
|---|---|---|---|
| 접두사 캐시 켜짐(기본) | 394ms | 28ms | 395ms |
--no-enable-prefix-caching |
395ms | 393ms | 393ms |
- 캐시가 적중하면 3,000토큰의 입력 처리를 건너뜁니다. 394ms 가 28ms 로 줄었습니다(14배). 시스템 프롬프트나 문서를 앞에 고정하는 서비스(RAG, 에이전트)에서 가장 값싼 최적화입니다.
- 켜 둔 비용은 이번 시험에서 보이지 않았습니다. 접두사가 없는 입력의 TTFT 가 켜짐 395ms, 꺼짐 393ms 로 같았습니다. 접두사가 전혀 겹치지 않는 입력 위주 부하에서의 비용도 쟀습니다. 동시 32, 입력 2,048토큰, 출력 16토큰의 요청 96개를 5회 보냈을 때 처리량은 켜짐 58, 꺼짐 59 tok/s(전체 시간 26.3~26.4초 대 26.0~26.1초, 약 1.2% 차이)였고 TTFT 중앙값은 켜짐 6.5~6.6초, 꺼짐 6.0~6.2초(약 8% 길었음)였습니다. 적중은 0건이었고 질의한 토큰은 약 105만이었습니다. 즉 켜 두는 비용은 입력 위주 부하에서 1% 남짓의 처리량과 한 자릿수 퍼센트의 TTFT 였습니다(TTFT 의 8%는 대기열 앞쪽 요청이 해시 계산을 더 치르는 효과로 추정하며 분해하지 않았습니다).
- 측정할 때 가장 조심할 함정입니다. 이 글을 쓰며 같은 문장을 반복해 입력 처리 속도를 쟀더니 GPU 가 낼 수 없는 연산량이 나왔고, 접두사 캐시 적중이 원인이었습니다(21편). 성능 시험의 입력은 요청마다 달라야 합니다. 반대로 캐시 효과를 보려면 공통 접두사를 일부러 만들어야 합니다.
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 |
- 한 단계에 처리하는 입력 토큰 수가 클수록 디코딩이 그만큼 길게 멈춥니다. 이 GPU 의 입력 처리 속도가 약 8,200 tok/s(21편)이므로, 청크 하나의 시간은 512토큰이면 약 62ms, 2,048토큰이면 약 250ms, 3,500토큰이면 약 430ms 입니다(계산). 위 최대 ITL 69, 227, 445ms 가 이 계산과 맞습니다. 기본값은 서버 설정을 읽어 확인했습니다. 이 버전에서
max_num_batched_tokens의 기본은 2,048 이고 청크 프리필은 켜져 있으며(enable_chunked_prefill참), 접두사 캐시도 켜져 있고 블록 크기는 16토큰이었습니다. - 청크를 작게 하면 디코딩이 덜 끊기는 대가로 입력 처리가 조금 늦어집니다. 긴 입력의 TTFT 가 512에서 481~494ms, 8192에서 461~474ms 로 약 3~5% 차이였습니다. 대신 끼운 구간의 중앙 ITL 은 512에서 53ms 로 늘었습니다(청크 단계마다 디코딩과 입력 처리가 섞여서).
- 용도에 따라 고릅니다. 토큰이 끊김 없이 나와야 하는 대화·음성 서비스는 작은 청크가, 긴 문서를 한꺼번에 처리하는 배치 작업은 큰 청크가 맞습니다. 두 값의 사이(1,024, 4,096)도 쟀습니다. 끼운 구간 최대 ITL 은 1,024에서 129ms(3회 128~129), 4,096에서 445ms(445~446)였고, 기본 2,048(227ms)과 512(69ms), 8,192(445ms)와 합치면 최대 ITL 은 청크 크기에 비례했습니다(입력 처리 속도 약 8,200 tok/s 로 1,024토큰이 약 125ms). 4,096 이 8,192 와 같은 값인 것은 입력이 3,500토큰이라 실효 청크가 3,500 이기 때문입니다. 긴 입력 자체의 TTFT 는 1,024에서 468~493ms, 4,096에서 461~476ms 로 거의 같았습니다.
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. 운영에서 가져갈 것
- 동시성 한도 = KV 용량 ÷ 요청당 토큰. 식은 3절, 근거는 4절입니다. 한도를 넘기면 처리량은 늘지 않고 TTFT 만 늘어납니다.
- TTFT p95 와 ITL p99 를 따로 봅니다. 중앙값은 멀쩡해도 꼬리가 큽니다(ITL p99 223ms).
- 공통 접두사를 설계로 만듭니다. 시스템 프롬프트와 문서를 앞에 두고 요청별 내용을 뒤에 두면 접두사 캐시가 14배의 이득을 줍니다.
- KV 8비트는 처리량 옵션이 아니라 용량 옵션입니다. 긴 입력에서 대기열이 길 때 효과가 큽니다. 품질 평가를 먼저 합니다.
- 청크 크기는 끊김과 입력 처리 속도의 교환입니다. 대화 서비스는 작게, 배치는 크게.
- 성능 시험은 입력을 매번 다르게 만들고, 하드웨어 한계와 대 봅니다.
11. 면접에서 나올 만한 질문
- vLLM 서버의 최대 동시 요청 수는 어떻게 어림하는가? KV 캐시 토큰 수를 요청당 평균 토큰(입력+출력)으로 나눈다. 토큰당 KV 는
2 × 층 × KV 헤드 × head_dim × 바이트이다. 1.7B 모델은 22,048토큰이라 640토큰 요청이면 약 34개이고, 이를 넘기면 TTFT 가 8배(666ms에서 5.7s)로 뛰었다. - 동시성을 올렸는데 처리량이 안 느는 이유는? 가중치 읽기를 나눠 갖는 이득은 KV 용량이 있는 동안만 이어지고, 용량이 차면 요청이 대기열에서 기다린다.
- 접두사 캐시는 어느 서비스에 효과가 큰가? 공통 앞부분이 긴 서비스. 3,000토큰 접두사에서 TTFT 가 394ms 에서 28ms 로 줄었다.
- KV 8비트의 효과는 속도인가 용량인가? 용량이다. 토큰당 바이트가 절반이라 같은 메모리에 1.9배 담았고, 긴 입력의 TTFT 가 4.0s 에서 0.94s 로 줄었다. 짧은 문맥의 속도는 같았다.
- 긴 입력이 오면 다른 사용자의 토큰이 왜 끊기는가, 어떻게 줄이는가? 입력 처리 단계가 디코딩과 같은 배치에서 돌아 그만큼 늦는다. 청크 프리필로 한 단계의 토큰 수를 줄이면 끊김이 청크 크기에 비례해 줄어든다(512토큰이면 최대 69ms).
- 성능 시험 값이 맞는지 어떻게 의심하는가? 하드웨어 한계(연산, 대역폭)와 대 본다. 같은 문장을 반복하면 접두사 캐시가 적중해 불가능한 처리 속도가 나온다.
외부 근거: 논문과 공식 자료로 확인한 것
이 글의 수치는 모두 직접 잰 값입니다. 같은 현상을 보고한 외부 자료를 열어 수치와 조건을 확인했고, 우리 결과와의 관계를 적었습니다. 외부 수치는 모델, 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 초록 페이지의 표기를 따랐습니다.
확인하지 못한 것
- 다른 장비에서의 반복 변동. 이 환경에서는 변동이 0.5% 안이었지만(2절), 다른 GPU 와 이웃 부하가 있는 환경에서는 달라질 수 있습니다. 동시 64 같은 대기열이 쌓이는 구간의 반복은 5회로 재지 않았습니다.
- 다른 모델, 다른 GPU, 더 긴 문맥. 1.7B 한 모델, 8GB급 GPU, 입력 4,096 까지입니다.
--max-num-batched-tokens의 512~8192 사이 값(기본 2,048 은 확인했습니다).- KV 8비트의 출력 품질과 접두사가 겹치지 않는 대량 요청에서 접두사 캐시의 CPU 부담.
- 선점된 요청이 재개될 때 접두사 캐시에 적중하는지. 반납된 블록이 해시를 간직한 채 빈 블록 큐에 남는다는 설계 서술에서 일부 적중할 수 있다고 추정할 뿐, 공식 서술이나 측정으로 확인하지 못했습니다.
- 선점이 발생하는 조건의 전체 모양. 서버 지표로 선점 횟수를 읽었지만(4절) 입력·출력 길이와 동시성을 촘촘히 바꿔 가며 곡선을 그리지는 않았습니다.
- SGLang 등 다른 서버와의 비교, 다중 GPU, 양자화 모델(21편에서 일부).
확인한 버전과 날짜
2026-10-07 기준. vLLM 0.31.0, PyTorch 2.13(CUDA 13), Qwen3-1.7B(bf16).