TT Lab
블로그

한국어 English 日本語 中文

시작하기
블로그

블로그

Service 뒤에 vLLM 을 여러 대 두면 접두 캐시는 얼마나 맞는가

상태: 초안 (2026-11-14), 실측. 이 글의 숫자는 2026-11-13 에 필자가 직접 잰 값입니다. 환경은 쿠버네티스 v1.34, GPU 서버 세 대(24GB급 GPU 한 장, 8GB급 노트북 GPU 두 장), 노드 사이 1GbE 입니다. 엔진은 Dynamo 런타임 이미지 vllm-runtime:1.6.2 에 든 vLLM 0.31.0, 모델은 Qwen3-0.6B 입니다. 작은 모델과 작은 캐시로 잰 값이라 절대 시간은 큰 모델에 그대로 옮길 수 없고, 옮길 수 있는 것은 적중률이 정해지는 방식입니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.

Service 뒤 vLLM 의 접두 캐시

목차

1. 왜 기준선부터 재는가

이 시리즈는 vLLM 만 쓰던 쿠버네티스에 NVIDIA Dynamo 를 들이는 과정을 실측으로 적습니다. Dynamo 가 내세우는 기능 가운데 하나가 KV 인지 라우팅입니다. 요청의 앞부분과 같은 KV 캐시를 이미 가진 워커로 요청을 보내 준다는 기능입니다.

그 값어치를 말하려면 「지금 방식은 캐시를 얼마나 버리는가」를 먼저 알아야 합니다. 지금 방식이란 vLLM 파드 여러 대 앞에 Service 하나를 두는 구성입니다. 이 글은 그 구성만 잽니다. Dynamo 는 다음 편부터 나옵니다.

배경 하나만 짚습니다. vLLM 의 접두 캐시(prefix cache)는 요청의 앞부분이 이전 요청과 같으면 그 부분의 KV 를 다시 계산하지 않고 재사용합니다. 시스템 프롬프트나 긴 문서를 앞에 붙이는 서비스에서는 첫 토큰까지의 시간(TTFT)을 크게 줄여 줍니다. 그런데 이 캐시는 프로세스마다 따로 있습니다. 파드 A 가 계산해 둔 KV 를 파드 B 는 모릅니다.

2. 무엇을 어떻게 쟀는가

항목 값
모델 Qwen3-0.6B, 토큰 하나의 KV 는 114,688바이트
엔진 인자 --max-model-len 4096 --kv-cache-memory-bytes 1073741824 --gpu-memory-utilization 0.01
워커 한 대의 KV 용량 1GiB ÷ 114,688바이트 = 약 9,362토큰
요청 공유 접두 약 2,000토큰 + 요청마다 다른 꼬리 약 300토큰, 출력 64토큰
실제 입력 길이 평균 2,371토큰, 캐시에 맞으면 그중 2,032토큰이 재사용됨
접두 종류 수 2, 7, 12, 24
동시 요청 수 4, 8, 16
반복 점마다 3회, 중앙값을 씀
요청 수 점마다 접두 수 × 20(최소 200)

KV 용량을 일부러 1GiB 로 묶었습니다. 요청 하나가 2,371토큰이므로 워커 한 대는 요청 약 3.95개 분량의 KV 만 담습니다. 접두가 4종을 넘으면 한 대에 다 들어가지 않는 조건을 만들려는 것입니다. 큰 모델과 긴 문맥을 쓰는 실제 서비스에서 생기는 캐시 압박을 작은 장비에서 흉내 내는 방법입니다.

배치는 둘입니다.

부하 발생기는 GPU 가 없는 다른 서버에서 돌렸고, Service 의 ClusterIP 로 요청을 보냈습니다. 공정하게 재려고 지킨 것이 셋 있습니다.

  1. 요청마다 새 연결을 맺습니다. Service 는 연결 단위로 파드를 고릅니다. 클라이언트가 연결을 재사용하면 같은 파드에 계속 붙어 기준선이 실제보다 좋아 보입니다.
  2. 요청 순서를 씨앗으로 고정합니다. 뒤 편의 Dynamo 변형에도 똑같은 요청을 똑같은 순서로 보냅니다.
  3. 예열을 집계에서 뺍니다. 측정 전에 접두마다 한 번씩 보내 두고, 그 요청은 세지 않습니다.

적중은 두 길로 확인했습니다. 하나는 응답의 usage.prompt_tokens_details.cached_tokens 이고, 다른 하나는 워커마다 긁은 vllm:prefix_cache_hits_total ÷ vllm:prefix_cache_queries_total 의 측정 구간 차이입니다. 두 값은 24개 점 가운데 23개에서 소수 셋째 자리까지 같았고, 나머지 하나도 0.001 차이였습니다. 예를 들어 접두 2종·동시 4 에서 둘 다 0.814 였습니다.

한 가지 함정이 있었습니다. vLLM 단독 서버는 --enable-prompt-tokens-details 를 주지 않으면 cached_tokens 를 응답에 넣지 않습니다. 이 스위치 없이 재면 모든 요청이 빗나간 것처럼 보입니다.

3. 결과: 접두가 늘면 적중이 무너진다

접두가 늘수록 적중이 줄어드는 모양 Service 뒤 vLLM · 동시 8 · 3회 중앙값 · 요청 단위 적중 비율 0 25% 50% 75% 100% 2 7 12 24 서로 다른 접두의 수 워커 3대(네모), 6대(동그라미) 거의 겹칩니다 점선: 3.95 ÷ 접두 수
그림: 접두 종류 수에 따른 요청 단위 적중 비율

요청 가운데 캐시에 맞은 비율입니다(동시 8, 3회 중앙값).

접두 종류 워커 6대 워커 3대 3.95 ÷ 접두 수
2 95.0% 98.0% 100%(상한)
7 55.5% 59.5% 56.4%
12 32.1% 33.8% 32.9%
24 17.5% 16.9% 16.5%

동시 4 와 16 에서도 같은 모양입니다. 워커 6대 기준으로 접두 7종은 52.5%·52.0%, 12종은 28.7%·32.1%, 24종은 16.0%·16.7% 였습니다.

눈여겨볼 것은 둘입니다.

첫째, 적중률이 간단한 식과 맞습니다. Service 가 요청을 고르게 흩으면 각 워커는 모든 접두가 섞인 요청을 받습니다. 워커는 가장 오래 안 쓴 블록부터 지우므로, 한 대의 캐시에는 「최근에 본 접두 약 3.95개」가 남습니다. 다음 요청의 접두가 그 안에 있을 확률은 3.95 ÷ 접두 수입니다. 표의 마지막 칸이 그 값이고 측정과 1~3%p 안에서 일치합니다.

둘째, 워커를 늘려도 적중률은 오르지 않습니다. 6대와 3대가 거의 같습니다. 무작위 분산에서는 워커마다 같은 일이 따로 벌어지기 때문입니다. 전체 KV 용량은 두 배인데 적중률은 그대로입니다. 워커 6대의 용량을 합치면 접두 약 23종을 담을 수 있으니, 24종짜리 부하에서 캐시의 대부분을 살릴 수 있었던 용량이 16% 만 쓰인 셈입니다.

접두 2종에서 100% 가 아닌 까닭도 계산으로 나옵니다. 워커 6대와 접두 2종이면 (워커, 접두) 조합이 12개입니다. 예열 2번이 그중 2개를 채우고, 나머지 10개 조합은 처음 만날 때 한 번씩 빗나갑니다. 측정값은 세 번 모두 200번 중 190번 적중이었습니다. 워커 3대는 조합 6개에서 2개를 뺀 4번이 빗나가 196번 적중이었고, 이것도 세 번 모두 같았습니다.

4. 적중과 빗나감은 첫 토큰에서 얼마나 다른가

동시 4 에서 요청을 적중과 빗나감으로 나눠 TTFT 를 보았습니다(워커 6대, 3회를 합친 값).

접두 종류 적중 요청 수 적중 p50 / p90 빗나간 요청 수 빗나감 p50 / p90
2 570 37 / 49ms 30 113 / 164ms
7 311 41 / 63ms 289 107 / 140ms
12 200 42 / 61ms 520 105 / 141ms
24 218 36 / 55ms 1,222 104 / 139ms

적중하면 약 40ms, 빗나가면 약 105~113ms 입니다. 요청 하나하나의 시간은 접두 종류가 늘어도 변하지 않고, 두 무리의 비율만 변합니다. 그래서 전체 TTFT 의 중앙값은 접두가 늘수록 빗나감 쪽으로 옮겨 갑니다.

접두 종류 동시 4 p50 / p99 동시 8 p50 / p99 동시 16 p50 / p99
2 37 / 122ms 47 / 190ms 64 / 325ms
7 63 / 184ms 93 / 526ms 142 / 1,353ms
12 94 / 193ms 126 / 493ms 194 / 1,441ms
24 101 / 254ms 136 / 635ms 205 / 1,508ms

(워커 6대, 3회 중앙값.) 동시 16 에서 꼬리가 길어집니다. 접두 2종일 때 p99 가 325ms 였는데 7종부터는 1.3~1.5초입니다. 빗나간 요청은 2,371토큰을 전부 계산해야 하고, 그 계산이 같은 GPU 에서 돌던 다른 요청의 걸음을 밀어내기 때문입니다.

처리량도 같이 줄었습니다. 워커 6대·동시 16 에서 접두 2종은 초당 18.9건, 24종은 13.3건이었습니다. 워커 3대에서는 25.1건에서 11.7건으로 줄었습니다.

워커 3대 배치는 GPU 를 나눠 쓰지 않는데도 동시 16 에서 꼬리가 더 길었습니다(접두 24종 p99 3,060ms). 같은 부하를 절반의 워커가 받기 때문입니다. 반대로 접두 2종에서는 3대 쪽의 처리량이 더 높았습니다. 24GB급 GPU 한 장을 워커 넷이 시분할하면 계산 자원은 늘지 않기 때문입니다. 두 배치의 절대 시간을 서로 견주는 것은 이 글의 목적이 아니고, 적중률이 배치와 무관하게 같은 식을 따른다는 점만 가져갑니다.

5. 이 기준선이 뜻하는 것

정리하면 Service 뒤의 vLLM 에서 접두 캐시 적중률은 다음 값으로 정해집니다.

적중률 ≈ min(1, 워커 한 대가 담는 접두 수 ÷ 자주 쓰이는 접두의 종류 수)

워커 수는 이 식에 없습니다. 그래서 다음과 같이 말할 수 있습니다.

마지막 항목이 KV 인지 라우팅이 약속하는 것입니다. 실제로 그 상한에 얼마나 다가가는지, 프런트엔드를 한 단계 더 거치는 비용은 얼마인지는 같은 요청을 같은 순서로 다시 보내서 잽니다.

이 측정의 한계도 적어 둡니다.

참고

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

댓글

아직 댓글이 없습니다.

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