LabHub

한국어

시작하기
블로그

블로그

NVIDIA Dynamo 직접 써 보기: KV 인식 라우팅은 거의 다 맞혔고, 한 장짜리 분리 서빙은 5배 느렸다

상태: 초안 (2026-10-07). 이 글의 수치는 모두 필자가 직접 잰 값입니다. 환경은 8GB급 GPU 한 장, 0.6B 모델, Dynamo 1.6.0.dev20261006(개발 빌드)입니다. 6편이 읽은 문서는 v1.5.0 이라서 플래그와 기본값이 다를 수 있습니다. GPU 가 둘 이상인 환경과 RDMA 연결은 미실측 입니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」으로 표시합니다.

목차

6편은 Dynamo 를 문서로 읽고 정리한 글이었습니다. 문서가 말하는 것이 실제로 그런지 확인하려고 GPU 한 장짜리 환경에 직접 설치해 보았습니다. 이 글은 네 가지를 답합니다.

  1. Dynamo 프런트엔드를 앞에 두면 요청마다 얼마나 늘어나는가.
  2. KV 인식 라우팅은 같은 접두사를 다시 보낸 요청을 얼마나 같은 워커로 보내는가, 라운드 로빈과 얼마나 다른가.
  3. 라우터가 KV 이벤트를 받지 못하면 어떻게 되는가.
  4. prefill 과 decode 를 프로세스 둘로 나누면 한 장 위에서 첫 토큰이 어떻게 달라지는가.

짧은 답. 프런트엔드의 비용은 서버가 한가할 때 약 13 ms 이고 바쁘면 보이지 않았습니다. KV 인식 라우팅은 같은 접두사를 다시 보낸 60번 가운데 53~60번을 같은 워커로 보냈고 라운드 로빈은 35~43번이었습니다. 라우터를 이벤트 모드로 켰는데 워커가 이벤트를 내보내지 않으면 적중이 무작위와 같은 수준으로 내려가는데도 오류는 없었고 로그에 경고 한 줄만 남았습니다. 한 장 위의 분리 서빙은 모든 조건에서 첫 토큰이 4.7~7.2배 느렸습니다. 두 워커가 같은 GPU 를 나눠 쓰는데 KV 를 옮기는 값이 더해졌기 때문이고, 이것은 분리 서빙의 이득을 잰 것이 아닙니다.

1. 환경과 방법

2. 프런트엔드를 거치는 비용

같은 모델을 vLLM 만 띄운 경우(solo)와 Dynamo 프런트엔드 뒤에 둔 경우(dyn)를 입력 512 토큰, 출력 128 토큰으로 세 번씩 비교했습니다. 값은 첫 토큰 시간(TTFT)의 가운데 값입니다.

동시 요청 vLLM 단독 Dynamo 뒤
1 29~39 ms 42~43 ms
8 129~130 ms 129~141 ms
32 272~274 ms 272~274 ms

동시성 1 에서 가운데 값은 30 ms 에서 43 ms 로 약 13 ms 늘었습니다. 동시성 8 과 32 에서는 구별되지 않았고 처리량도 같았습니다(동시성 32 에서 초당 1,507 대 1,509 토큰). 프런트엔드(HTTP, 토큰화, 라우팅 결정)의 비용은 서버가 한가할 때만 보이는 고정 비용이고 바쁘면 묻혔습니다. 이 모델은 토큰이 아주 빨라서 13 ms 가 보이는 것이고, 큰 모델이면 상대 비중은 더 작아질 것으로 예상하지만 미실측 입니다.

3. KV 인식 라우팅

3.1 설정

워커 둘을 같은 모델로 띄우고 프런트엔드의 --router-mode 만 바꿨습니다.

이름 프런트엔드 옵션 워커 쪽
라운드 로빈 --router-mode round-robin KV 이벤트 끔
무작위 --router-mode random KV 이벤트 끔
KV 인식, 근사 --router-mode kv --no-router-kv-events KV 이벤트 끔
KV 인식, 이벤트 모드인데 이벤트 없음 --router-mode kv KV 이벤트 끔
KV 인식, 이벤트 모드 --router-mode kv KV 이벤트 켬(ZMQ 로 발행)

부하는 접두사(약 1,500 단어, 합성)를 12종 만들어 각각 6번씩 섞어 보내고 매번 꼬리 24 단어를 바꿨습니다. 요청은 72개이고 접두사를 두 번째 이후로 보낸 재요청이 60개입니다. 재요청의 첫 토큰이 처음 보낸 요청(약 80 ms)의 60% 미만이면 캐시에 적중한 것으로 셌습니다. 적중하면 약 15 ms, 놓치면 약 80 ms 였습니다.

3.2 결과

라우팅 재요청 60번 중 적중(세 번) 재요청 첫 토큰 평균
라운드 로빈 35, 40, 43 34~42 ms
무작위 44, 31, 45 32~46 ms
KV 인식, 근사 60, 53, 60 15~26 ms
KV 인식, 이벤트 모드인데 이벤트 없음 39, 44, 41 32~38 ms
KV 인식, 이벤트 모드 60, 60, 60 15 ms

같은 구성을 앞서 한 번 더 돌린 결과(라운드 로빈 35, 40, 43, 무작위 37, 37, 41, 근사 60, 60, 60, 이벤트 없음 41, 44, 37, 이벤트 모드 60, 60, 60)도 방향이 같았습니다. 근사 방식은 첫 실행에서 세 번 모두 60번이었는데 재실행에서 한 번 53번이 나왔으므로, 스스로 기억하는 방식이 이벤트 방식보다 조금 덜 안정할 수 있다고 보지만 확정하지는 못합니다. 이벤트 방식은 여섯 번이 모두 60번이었습니다.

3.3 조용한 실패

표의 네 번째 줄이 이 실험에서 가장 중요한 결과입니다. --router-mode kv 로 이벤트 모드를 켰는데 워커가 KV 이벤트를 내보내지 않으면 라우터의 색인이 영원히 비어 있고, 모든 요청이 겹침 0 이 되어 부하만으로 워커를 고르게 됩니다. 적중은 무작위 수준(39, 44, 41)으로 내려갔는데 서버는 정상으로 응답했고 오류도 없었습니다. 프런트엔드 로그에 아래 경고가 한 줄 있었고, 이벤트를 내보내는 워커로 바꾼 실행에는 없었습니다.

WARN ... KV-event publishing is disabled for a role that requires KV events
diagnostic_code="kv_event_publisher_disabled" ... requirement="cache_aware_routing"

적중률이 무작위와 같다는 것을 알아채려면 로그의 경고를 보거나, 같은 접두사를 다시 보냈을 때 첫 토큰이 줄어드는지를 따로 재야 합니다. 라우터를 켜기만 하고 적중을 재지 않으면 이 상태로 운영될 수 있습니다.

3.4 시뮬레이터와 실측의 차이

이 라우터의 규칙을 순수 파이썬으로 만들어 같은 부하를 돌려 보면(앞에서부터 이어서 겹치는 블록이 가장 많은 워커, 비기면 일이 적은 워커) KV 인식은 60 중 60 을 맞히고 이벤트를 알리지 않으면 라운드 로빈과 같은 값이 나옵니다. 방향은 실측과 같습니다. 다만 시뮬레이터의 라운드 로빈은 60 중 49 로 실측(35~43)보다 높습니다. 시뮬레이터의 워커는 한 번 처리한 접두사를 영원히 기억하지만 실제 워커는 KV 자리가 14,816 토큰이라 접두사 12종(약 18,000 토큰)을 다 담지 못하고 오래된 것부터 밀려납니다. KV 인식 라우팅은 접두사를 워커에 나눠 각 워커가 6종(약 9,000 토큰)만 담으므로 밀려나지 않습니다. 접두사 라우팅의 이득에는 "같은 곳으로 보낸다" 외에 "각 워커의 작업 집합이 작아진다" 가 더해진다는 뜻입니다.

4. 한 장 위의 prefill/decode 분리

4.1 설정

프리필 전용 워커와 디코드 전용 워커를 프로세스 둘로 같은 GPU 에 올리고(각각 GPU 메모리의 38%, KV 전송은 NIXL), 한 프로세스가 둘 다 하는 구성과 같은 부하로 비교했습니다. 출력은 64 토큰이고 값은 반복 세 번의 TTFT 가운데 값입니다.

4.2 결과

입력(단어) 동시 요청 한 프로세스(ms) 분리(ms) 배율
1,500 1 78 406 5.2
1,500 4 190 1,359 7.2
3,000 1 171 812 4.7
3,000 4 398 2,442 6.1

모든 칸에서 분리가 느렸습니다. 워커의 KV 전송 지표로 보면 동시성 1, 입력 1,500 단어에서 요청 하나당 평균 196 ms(전송당 119.3 MB, 처리량 608 MB/s)가 걸렸고, 동시성 4 에 입력 3,000 단어이면 평균 711~1,723 ms 였습니다. 첫 토큰이 한 프로세스보다 약 330 ms 늦은 동시성 1 의 경우, 전송 196 ms 가 그 가운데 큰 부분입니다. 나머지 약 130 ms 가 무엇인지는 분해해서 재지 않았습니다(미실측).

4.3 이 결과를 읽는 법

이것은 "분리 서빙이 느리다" 가 아닙니다. 두 워커가 같은 GPU 한 장을 나눠 쓰므로 계산 자원이 늘지 않았고, 분리가 해결하려는 문제(긴 prefill 이 다른 요청의 decode 를 방해하는 것)를 풀 장치가 하나뿐인 구성입니다. 거기에 KV 를 옮기는 비용만 더해졌습니다. GPU 가 둘 이상이고 NVLink 나 RDMA 로 연결된 환경에서 이득이 나는지는 이 글이 재지 못했습니다(미실측).

토큰당 KV 가 114,688 바이트이므로 1,500 토큰은 약 164 MiB 입니다. 6편이 인용한 문서의 경고(느린 경로로 KV 를 옮기면 TTFT 가 약 98초까지 늘 수 있다)와 같은 방향의 결과입니다. 다만 그 98초는 다른 모델, 다른 입력 길이, 다른 연결의 값이라 이 실험과 크기를 견줄 수는 없습니다. 608 MB/s 로 1,500 토큰을 옮기면 약 280 ms 이고, 서버 사이 TCP 처럼 0.1 GB/s 대이면 1.5 초가 넘습니다.

4.4 KV 전송이 왜 느렸나

원인은 확정하지 못했습니다. 전송 방식을 바꿔 보았고, 결과는 아래와 같습니다.

UCX 설정 결과
기본(제한 없음) 전송 처리량 608 MB/s, 첫 토큰 379~384 ms
디버그 로그를 켠 기본 615 MB/s, 첫 토큰 387~389 ms
TCP 계열로 제한(UCX_TLS=tcp,cuda_copy,sm,self) 621 MB/s, 첫 토큰 381~384 ms
TCP 와 장치 안 IPC 를 함께 허용 첫 토큰 383~384 ms(처리량 줄은 로그에 남지 않음)
장치 안 IPC 만 허용(UCX_TLS=cuda_ipc,cuda_copy,sm,self) 엔진이 시작하지 못함

IPC 만 허용했을 때의 오류는 no active messages transport ... cuda_ipc/cuda - no am bcopy 와 Destination is unreachable 였습니다. NIXL 의 UCX 백엔드가 초기화에 능동 메시지(active message)를 보낼 수 있는 전송을 요구하는데 IPC 전송은 그것을 제공하지 않는 것으로 읽힙니다. 따라서 장치 안 전송을 쓰고 싶어서 UCX_TLS 를 IPC 로만 좁히는 설정은 이 조합에서 쓸 수 없습니다. 기본 설정에서 UCX 가 만든 인터페이스는 tcp/eth0, tcp/lo, cuda_ipc/cuda 였습니다.

전송 방식을 바꿔도 속도가 달라지지 않았으므로, 병목이 UCX 의 전송 선택이 아닐 수 있습니다. 어디가 병목인지(요청 단위 직렬화, 블록 단위 전송 기술자 28개, 복사 경로 등)는 분해해서 재지 않았습니다(미실측).

5. 겪은 일

6. 한계와 미실측

7. 운영에 가져갈 점검 목록

  1. KV 인식 라우팅을 켰다면 워커가 KV 이벤트를 내보내는지 먼저 확인합니다. 프런트엔드 로그에 kv_event_publisher_disabled 경고가 없는지 봅니다.
  2. 라우팅을 바꾼 뒤에는 "같은 접두사를 다시 보냈을 때 첫 토큰이 줄어드는가" 를 직접 잽니다. 오류 없이 무작위 수준으로 내려갈 수 있습니다.
  3. 접두사가 한 워커의 KV 자리보다 많이 다양하면 라우팅이 워커 사이에 접두사를 나누는 효과가 더해집니다. 워커의 KV 자리(서버 로그의 GPU KV cache size)와 접두사 합계를 견주어 봅니다.
  4. 분리 서빙은 GPU 가 둘 이상이고 KV 이동 경로(전송 처리량)를 따로 잰 뒤에 검토합니다. 토큰당 KV 크기(층 x KV 헤드 x 헤드 크기 x 2 x 바이트)에 입력 길이를 곱하면 한 요청이 옮길 바이트가 나오고, 그것을 실제 전송 처리량으로 나누면 첫 토큰에 더해질 시간입니다.
  5. 매 실행 전에 이전 프로세스(엔진 코어)가 남아 있지 않은지, 모델 다운로드 잠금이 남아 있지 않은지 확인합니다.
  6. 서버마다 NVIDIA 드라이버 버전이 다르면, 설치한 vLLM 과 torch 의 CUDA 빌드를 그 드라이버가 받아 주는지 먼저 확인합니다. 받아 주지 않으면 서버 로그가 아니라 엔진 시작 단계에서 CUDA 오류로 죽습니다.

참고 자료

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

댓글

아직 댓글이 없습니다.

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