상태: 초안 (2026-11-14), 실측. 이 글의 숫자는 2026-11-13 에 필자가 직접 잰 값입니다. 환경과 요청은 1편과 같습니다. 쿠버네티스 v1.34, GPU 서버 세 대(24GB급 GPU 한 장, 8GB급 노트북 GPU 두 장), 노드 사이 1GbE, Dynamo v1.6.2 의
vllm-runtime:1.6.2이미지(vLLM 0.31.0), 모델 Qwen3-0.6B 입니다. 라우터 인자는 전부 기본값입니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.
라운드로빈과 KV 인지 라우팅
목차
- 1. 이 편에서 답할 질문
- 2. 매니페스트에서 바뀌는 것
- 3. 변형 다섯 개
- 4. 결과: 라운드로빈은 기준선과 같고, KV 라우팅은 다르다
- 5. 첫 토큰 시간과 처리량
- 6. 워커 3대에서도 같은가
- 7. 상한에는 얼마나 다가갔는가
- 8. 해시 씨앗과 근사 모드
- 9. 정리
- 참고
1. 이 편에서 답할 질문
1편의 결론은 이랬습니다. Service 뒤에 vLLM 을 여러 대 두면 접두 캐시의 적중률은 「워커 한 대가 담는 접두 수 ÷ 접두 종류 수」로 정해지고, 워커를 늘려도 오르지 않습니다. 이 편은 같은 요청을 Dynamo 로 보내서 세 가지를 확인합니다.
- Dynamo 를 거치기만 하면 달라지는 것이 있는가(라운드로빈).
- KV 인지 라우팅을 켜면 적중률과 첫 토큰 시간이 얼마나 달라지는가.
- 문서가 조심하라고 적은 두 가지, 곧 해시 씨앗과 KV 이벤트를 빼면 어떻게 되는가.
2. 매니페스트에서 바뀌는 것
기준선은 Deployment 와 Service 였습니다. Dynamo 에서는 그 둘이 DynamoGraphDeployment(DGD) 하나로 바뀝니다. 오퍼레이터가 이것을 읽고 파드와 Service 를 만듭니다. 엔진 인자, 이미지, 모델, 노드 배치는 그대로 두고 다음 넷만 바꿨습니다.
| 바뀌는 것 | 기준선 | Dynamo |
|---|---|---|
| 리소스 | Deployment + Service | DGD 하나(구성 요소: 프런트엔드 1, 워커 여럿) |
| 워커의 실행 명령 | python3 -m vllm.entrypoints.openai.api_server |
python3 -m dynamo.vllm |
| OpenAI API 를 여는 곳 | 워커마다 8000 | 프런트엔드만 8000, 워커는 HTTP 를 열지 않음 |
| 라우팅 | Service 가 연결 단위로 무작위 | 프런트엔드의 DYN_ROUTER_MODE |
KV 인지 라우팅에 필요한 설정은 둘입니다.
# 프런트엔드
env:
- {name: DYN_ROUTER_MODE, value: kv}
# 워커 인자
- --kv-events-config
- '{"publisher":"zmq","topic":"kv-events","endpoint":"tcp://*:20080","enable_kv_cache_events":true}'
워커는 KV 블록이 생기고 지워질 때마다 이 주소로 이벤트를 내보내고, 프런트엔드 안의 라우터가 그것을 받아 「어느 워커가 어떤 블록을 갖고 있는가」의 지도를 만듭니다. 요청이 오면 라우터는 요청의 앞부분과 가장 많이 겹치는 워커를 고르되 워커의 부하도 함께 따집니다.
옮기면서 확인한 사실을 적어 둡니다.
- etcd, NATS, Grove 없이 떴습니다. 워커는 자기 정보를
DynamoWorkerMetadata라는 리소스로 쿠버네티스 API 에 쓰고, 프런트엔드가 그것을 읽습니다. - 같은 종류의 구성 요소를 여럿 둘 수 있었습니다. 노드마다 자원 한도가 달라
type: worker구성 요소를 노드별로 나눴고, 프런트엔드는 그 워커들을 한 모델의 워커로 묶어 보았습니다. - 워커의 지표는 9090 에서 나옵니다. 오퍼레이터가 넣어 주는 시스템 포트입니다.
vllm:prefix_cache_hits_total같은 엔진 지표도 여기서 긁었습니다. cached_tokens는 따로 켜지 않아도 응답에 들어 있었습니다. vLLM 단독 서버에서는--enable-prompt-tokens-details가 필요했던 값입니다.- 워커가 Ready 가 된 뒤에도 프런트엔드의
/v1/models에 모델이 나타나기까지 몇 초가 걸렸습니다. 부하를 걸기 전에 이 목록을 확인해야 합니다.
3. 변형 다섯 개
| 이름 | 구성 | 보려는 것 |
|---|---|---|
| 기준선 | Service 뒤 vLLM(1편) | 견줄 값 |
| 라운드로빈 | DGD, DYN_ROUTER_MODE=round-robin |
Dynamo 를 거치는 것만의 효과 |
| KV 라우팅 | DGD, DYN_ROUTER_MODE=kv, KV 이벤트, PYTHONHASHSEED=0 |
본 기능 |
| 씨앗 없음 | KV 라우팅에서 PYTHONHASHSEED 만 뺌 |
해시 씨앗이 정말 필요한가 |
| 근사 모드 | DYN_ROUTER_USE_KV_EVENTS=false, 워커의 이벤트도 끔 |
이벤트 없이 추측만으로 얼마나 되는가 |
요청은 1편과 같습니다. 공유 접두 약 2,000토큰에 요청마다 다른 꼬리 약 300토큰을 붙였고, 워커 한 대의 KV 용량을 1GiB(요청 약 3.95개 분량)로 묶었습니다. 접두 종류는 2·7·12·24, 동시 요청은 4·8·16, 점마다 3회를 돌려 중앙값을 썼습니다. 씨앗이 같으므로 모든 변형이 똑같은 요청을 똑같은 순서로 받았습니다. 다섯 변형의 모든 점에서 오류는 0건이었습니다.
라운드로빈 변형의 한 점(접두 2종·동시 4)은 첫 반복을 뺐습니다. 직전에 같은 씨앗으로 시험 요청을 보낸 탓에 접두가 이미 캐시에 있었기 때문입니다. 그 점은 남은 2회의 중앙값입니다.
4. 결과: 라운드로빈은 기준선과 같고, KV 라우팅은 다르다
요청 가운데 캐시에 맞은 비율입니다(워커 6대, 동시 8).
| 접두 종류 | Service 뒤 vLLM | 라운드로빈 | KV 라우팅 |
|---|---|---|---|
| 2 | 95.0% | 95.0% | 97.5% |
| 7 | 55.5% | 53.5% | 83.0% |
| 12 | 32.1% | 29.2% | 67.9% |
| 24 | 17.5% | 17.5% | 43.3% |
입력 토큰 가운데 캐시에서 온 비율로 보아도 같습니다. 이 요청에서는 2,371토큰 가운데 2,032토큰이 접두이므로 0.857 이 낼 수 있는 가장 큰 값입니다.
| 접두 종류 | 동시 | Service 뒤 vLLM | 라운드로빈 | KV 라우팅 |
|---|---|---|---|---|
| 2 | 4 | 0.814 | 0.814 | 0.857 |
| 7 | 4 | 0.431 | 0.385 | 0.788 |
| 12 | 4 | 0.241 | 0.262 | 0.664 |
| 24 | 4 | 0.135 | 0.126 | 0.429 |
| 2 | 16 | 0.814 | 0.814 | 0.835 |
| 7 | 16 | 0.425 | 0.442 | 0.779 |
| 12 | 16 | 0.268 | 0.237 | 0.695 |
| 24 | 16 | 0.141 | 0.148 | 0.431 |
라운드로빈은 기준선과 구별되지 않습니다. Dynamo 의 프런트엔드를 앞에 두는 것만으로는 캐시가 살아나지 않습니다. 요청을 워커에 고르게 돌리는 것과 무작위로 뿌리는 것은 접두 캐시의 눈으로 보면 같은 일입니다.
KV 라우팅은 접두가 한 대의 용량을 넘는 구간에서 적중을 두 배에서 세 배로 올렸습니다. 접두 2종에서는 차이가 작습니다. 1편에서 적은 대로, 접두가 한 대에 다 들어가면 라우팅으로 얻을 것이 거의 없습니다.
라우터가 실제로 요청을 모았는지는 「접두 하나가 몇 대의 워커로 갔는가」로 확인했습니다. 요청의 nvext.extra_fields 에 worker_id 를 넣으면 응답에 처리한 워커의 식별자가 실려 옵니다. 그것으로 셌습니다.
| 접두 종류 | 라운드로빈 | KV 라우팅 |
|---|---|---|
| 2 | 6.00대 | 1.00대 |
| 7 | 6.00대 | 2.86대 |
| 12 | 6.00대 | 4.00대 |
| 24 | 5.92대 | 5.17대 |
(워커 6대, 동시 4, 3회 중앙값.) 라운드로빈은 모든 접두를 모든 워커에 보냈습니다. KV 라우팅은 접두 2종일 때 접두마다 한 대에만 보냈고, 접두가 늘수록 퍼졌습니다.
5. 첫 토큰 시간과 처리량
첫 토큰까지의 시간(TTFT)입니다. 칸마다 p50 / p99 입니다(워커 6대).
| 접두 종류 | 동시 | Service 뒤 vLLM | 라운드로빈 | KV 라우팅 |
|---|---|---|---|---|
| 2 | 4 | 37 / 122ms | 41 / 125ms | 34 / 73ms |
| 7 | 4 | 63 / 184ms | 89 / 142ms | 40 / 129ms |
| 12 | 4 | 94 / 193ms | 106 / 140ms | 43 / 138ms |
| 24 | 4 | 101 / 254ms | 113 / 143ms | 64 / 149ms |
| 2 | 16 | 64 / 325ms | 74 / 332ms | 67 / 206ms |
| 7 | 16 | 142 / 1,353ms | 111 / 576ms | 72 / 240ms |
| 12 | 16 | 193 / 1,441ms | 190 / 646ms | 77 / 193ms |
| 24 | 16 | 205 / 1,508ms | 199 / 774ms | 109 / 242ms |
읽을 것이 셋 있습니다.
- 프런트엔드를 거치는 비용은 몇 ms 입니다. 캐시 조건이 같은 접두 2종·동시 4 에서 라운드로빈의 p50 은 41ms, 기준선은 37ms 였습니다. 동시 8 에서는 64ms 대 47ms 로 벌어졌습니다. 프런트엔드가 토큰화를 하고 워커로 요청을 넘기는 한 단계가 더 있기 때문입니다.
- 라운드로빈은 적중률이 같은데도 꼬리가 짧았습니다. 동시 16·접두 24종의 p99 가 774ms 로, 기준선 1,508ms 의 절반입니다. Service 는 연결마다 무작위로 파드를 골라 한 워커에 요청이 몰리는 순간이 생기고, 라운드로빈은 고르게 돌린다는 차이에서 왔을 것으로 봅니다. 워커별 순간 대기열을 재지 않았으므로 원인은 추정입니다.
- KV 라우팅은 꼬리를 한 번 더 줄였습니다. 동시 16 에서 p99 가 접두 수와 무관하게 0.2초 안팎입니다. 빗나간 요청은 2,371토큰을 전부 계산하느라 같은 GPU 의 다른 요청을 밀어내는데, 그런 요청 자체가 줄었기 때문입니다.
처리량(초당 요청, 워커 6대·동시 16)도 따라 올랐습니다.
| 접두 종류 | Service 뒤 vLLM | 라운드로빈 | KV 라우팅 |
|---|---|---|---|
| 2 | 18.9 | 19.1 | 21.1 |
| 7 | 14.1 | 16.0 | 20.1 |
| 12 | 13.9 | 14.7 | 19.2 |
| 24 | 13.3 | 14.3 | 17.6 |
GPU 는 그대로인데 접두 24종에서 32% 를 더 처리했습니다. 접두를 다시 계산하는 데 쓰던 시간이 줄어든 만큼입니다.
6. 워커 3대에서도 같은가
GPU 한 장에 워커 하나씩 둔 배치입니다(동시 8).
| 접두 종류 | 캐시 토큰 비율: 기준선 / 라운드로빈 / KV | TTFT p99: 기준선 / 라운드로빈 / KV | 초당 요청: 기준선 / 라운드로빈 / KV |
|---|---|---|---|
| 2 | 0.840 / 0.840 / 0.848 | 165 / 171 / 83ms | 17.0 / 17.3 / 20.6 |
| 7 | 0.487 / 0.475 / 0.690 | 853 / 413 / 145ms | 12.4 / 13.6 / 18.5 |
| 12 | 0.283 / 0.291 / 0.474 | 1,125 / 679 / 152ms | 11.2 / 12.5 / 17.2 |
| 24 | 0.143 / 0.141 / 0.252 | 1,216 / 830 / 202ms | 10.7 / 11.8 / 16.6 |
방향은 같습니다. 접두 24종에서 처리량이 55% 늘었습니다. 다만 워커가 셋뿐이라 담을 수 있는 접두의 총량이 절반이고, 그만큼 적중률도 6대 배치보다 낮습니다.
부하가 용량을 넘는 점도 하나 있었습니다. 워커 3대·동시 16·접두 24종에서 KV 라우팅의 TTFT p50 은 279ms, p99 는 962ms 였습니다. 기준선(563ms, 3,060ms)보다는 낫지만 다른 점들처럼 0.2초 안으로 들어오지는 않았습니다. 라우팅은 낭비를 줄일 뿐이고 모자란 계산 자원을 만들어 주지는 않습니다.
7. 상한에는 얼마나 다가갔는가
1편에서 계산한 이론상 상한은 「워커 수 × 한 대가 담는 접두 수 ÷ 접두 종류 수」였습니다. 접두를 워커에 겹치지 않게 나눠 담는다고 가정한 값입니다.
| 배치 | 접두 종류 | 상한 | 실제 요청 단위 적중(동시 4 / 8 / 16) |
|---|---|---|---|
| 워커 6대 | 7 | 100% | 93 / 83 / 91% |
| 워커 6대 | 12 | 100% | 80 / 68 / 82% |
| 워커 6대 | 24 | 99% | 52 / 43 / 52% |
| 워커 3대 | 7 | 100% | 77 / 82 / 90% |
| 워커 3대 | 12 | 99% | 53 / 58 / 64% |
| 워커 3대 | 24 | 49% | 29 / 30 / 28% |
기본값의 KV 라우팅은 캐시가 빠듯한 구간에서 상한의 절반에서 3분의 2 사이를 냈습니다. 무작위 분산보다는 두세 배 낫지만, 용량을 남김없이 쓰는 배치에는 미치지 못합니다.
까닭은 4절의 표에 보입니다. 접두 24종에서 접두 하나가 평균 5대의 워커로 갔습니다. 라우터는 겹침만 보지 않고 워커의 부하도 함께 보기 때문에, 겹치는 워커가 바쁘면 다른 워커로 보냅니다. 그 워커는 접두를 새로 계산해 담고, 그러느라 다른 접두를 밀어냅니다. 캐시가 빠듯할수록 이 밀어내기가 연쇄로 이어집니다.
Dynamo 문서에는 겹침과 부하의 비중을 바꾸는 라우터 인자가 있습니다. 이 글은 기본값으로만 쟀고, 그 인자를 바꿔 상한에 더 다가갈 수 있는지는 미실측입니다.
8. 해시 씨앗과 근사 모드
문서와 예제는 워커에 PYTHONHASHSEED=0 을 주라고 적습니다. vLLM 의 블록 해시가 파이썬의 해시를 거치므로 씨앗이 프로세스마다 다르면 같은 접두가 워커마다 다른 해시가 된다는 이유입니다. 씨앗을 빼고 재 봤습니다(워커 6대, 동시 8, 캐시 토큰 비율).
| 접두 종류 | 라운드로빈 | KV 라우팅 | 씨앗 없음 | 근사 모드 |
|---|---|---|---|---|
| 2 | 0.814 | 0.835 | 0.848 | 0.848 |
| 7 | 0.439 | 0.702 | 0.729 | 0.638 |
| 12 | 0.247 | 0.573 | 0.547 | 0.480 |
| 24 | 0.149 | 0.361 | 0.343 | 0.245 |
씨앗을 빼도 결과는 같았습니다. 네 점의 차이는 0.03 안이고 방향도 엇갈립니다. 반복 사이의 흔들림과 구별되지 않는 크기입니다. 적어도 이 판과 이 구성에서는 씨앗이 없다고 라우팅이 깨지지 않았습니다. 까닭은 미확인입니다. 런타임이 씨앗을 스스로 정하는지, 라우터가 워커의 해시에 기대지 않는 길이 있는지를 코드에서 확인하지 못했습니다. 문서가 요구하는 설정이고 넣는 비용이 없으므로, 결과가 같았다는 이유로 빼라고 권하지는 않습니다.
근사 모드는 중간입니다. 이 모드에서 워커는 이벤트를 보내지 않고, 라우터는 자기가 보낸 요청의 기록으로 「그 워커에 아직 이 접두가 있을 것」이라고 추측합니다. 접두 7종에서는 KV 이벤트를 쓸 때와 큰 차이가 없었지만(0.638 대 0.702), 24종에서는 0.245 로 라운드로빈(0.149)과의 차이가 절반으로 줄었습니다. 캐시가 빠듯해 블록이 자주 밀려나면 라우터의 추측이 실제와 어긋나기 때문일 것입니다. 이벤트 포트를 열기 어려운 환경에서 쓸 수 있는 차선이라는 정도로 읽으면 됩니다.
9. 정리
- Dynamo 를 앞에 두는 것만으로는 접두 캐시가 살아나지 않습니다. 라운드로빈의 적중률은 Service 뒤 vLLM 과 같았습니다.
- KV 인지 라우팅은 접두의 총량이 워커 한 대의 용량을 넘는 구간에서 값을 합니다. 워커 6대에서 요청 단위 적중이 접두 7종 56%→83%, 12종 32%→68%, 24종 18%→43% 가 됐고, 동시 16 의 TTFT p99 는 1.3~1.5초에서 0.2초대가 됐습니다.
- 접두가 한 대에 다 들어가는 서비스라면 얻을 것이 거의 없습니다. 접두 2종에서 적중률 차이는 2~4%p 였습니다. 그 경우 치르는 값은 프런트엔드 한 단계의 몇 ms 와 2편에서 본 설치·권한의 부담입니다.
- 기본값으로는 이론상 상한의 절반에서 3분의 2 를 냅니다. 라우터 인자의 조정은 미실측입니다.
- 해시 씨앗을 빼도 결과가 같았습니다(까닭 미확인). 근사 모드는 캐시가 빠듯할수록 효과가 줄었습니다.
이 측정의 한계는 1편과 같습니다. 접두를 고르게 뽑았고, 요청 길이가 모두 같으며, 모델이 작습니다. 큰 모델에서는 빗나간 요청 하나의 계산이 훨씬 길어서 같은 적중률 차이가 더 큰 시간 차이로 나타날 것이지만, 그것은 이 글이 잰 값이 아닙니다. 프런트엔드를 여러 대 두었을 때 라우터들이 같은 지도를 보는지도 미실측입니다.
참고
- NVIDIA Dynamo v1.6.2 문서, KV Cache Routing 과 Router 설정.
- 매니페스트, 부하 도구, 격자 기록과 중앙값 요약은 저장소의
docs/experiments/2026-11-13-dynamo-adoption/에 있습니다. - KV 이벤트와 라우터의 원리는 LLM KV 캐시 해부 4편에 있습니다.