상태: 초안 (2026-11-14), 실측. 이 글의 숫자는 2026-11-13 에 필자가 직접 잰 값입니다. 환경과 요청은 1편과 같습니다. 쿠버네티스 v1.34, 24GB급 GPU 한 장짜리 서버와 8GB급 노트북 GPU 서버, 노드 사이 1GbE, RDMA 없음, Dynamo v1.6.2 의
vllm-runtime:1.6.2이미지(vLLM 0.31.0), 모델 Qwen3-0.6B 입니다. 분리 서빙이 불리한 쪽의 조건이라는 점을 먼저 밝힙니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.
분리 서빙을 작은 클러스터에서 재 보기
목차
1. 분리 서빙이 하려는 일
요청 하나를 처리하는 일은 둘로 나뉩니다. 프롬프트 전체를 읽어 KV 를 만드는 일(prefill)과, 그 KV 를 보며 토큰을 하나씩 내는 일(decode)입니다. prefill 은 한 번에 큰 계산을 하고 decode 는 작은 계산을 오래 반복합니다. 둘이 같은 GPU 에서 돌면 긴 prefill 하나가 다른 요청들의 토큰 생성을 멈춰 세웁니다.
분리 서빙은 이 둘을 서로 다른 워커에 맡깁니다. prefill 워커가 KV 를 만들고, 그것을 decode 워커로 옮긴 뒤, decode 워커가 토큰을 냅니다. 값은 KV 를 옮기는 시간입니다. Dynamo 는 이 전송에 NIXL 이라는 라이브러리를 쓰고, 문서는 RDMA 를 전제로 설명합니다. TCP 로도 돌지만 훨씬 느리다고 적혀 있습니다.
이 편이 답하려는 것은 셋입니다.
- 쿠버네티스에서 Grove 없이 분리 서빙 그래프가 뜨는가.
- RDMA 가 없는 환경에서 같은 서버 안의 분리는 얼마를 치르는가.
- 1GbE 를 건너는 분리는 얼마를 치르는가.
2. 구성
DGD 의 구성 요소에 type: prefill 과 type: decode 를 두고, 워커 인자에 역할을 적습니다.
# prefill 워커
- --disaggregation-mode
- prefill
- --kv-transfer-config
- '{"kv_connector":"NixlConnector","kv_role":"kv_producer","kv_buffer_device":"cpu"}'
# decode 워커
- --disaggregation-mode
- decode
- --kv-transfer-config
- '{"kv_connector":"NixlConnector","kv_role":"kv_consumer","kv_buffer_device":"cpu"}'
# 둘 다
env:
- {name: UCX_TLS, value: "tcp,self"}
- {name: UCX_RNDV_THRESH, value: "inf"}
RDMA 가 없으므로 전송을 TCP 로 고정했습니다. kv_buffer_device 를 cpu 로 둔 것은 Dynamo 예제의 주석을 따른 것입니다. GPU 버퍼로 TCP 를 타면 UCX 안에서 죽는다고 적혀 있습니다. 이 설정에서는 KV 가 호스트 메모리를 거쳐 갑니다. 워커 로그에도 「UCX CUDA support was not found」라는 경고가 남았습니다.
배치는 둘입니다.
| 이름 | prefill 워커 | decode 워커 | KV 가 지나는 길 |
|---|---|---|---|
| 같은 서버 | 24GB급 GPU 서버에 2대 | 같은 서버에 2대 | 한 서버 안의 TCP |
| 서버 사이 | 8GB급 GPU 서버에 1대 | 24GB급 GPU 서버에 2대 | 1GbE |
프런트엔드는 3편과 같이 KV 라우팅 모드입니다. 엔진 인자와 요청도 같습니다. 요청 하나는 평균 2,371토큰이고, 토큰 하나의 KV 가 114,688바이트이므로 요청 하나의 KV 는 약 272MB 입니다.
Grove 없이 떴습니다. DGD 를 적용하자 오퍼레이터가 프런트엔드, prefill, decode 를 각각 Deployment 로 만들었고, 2분 안에 다섯 파드가 모두 Ready 가 됐습니다. 프런트엔드는 요청을 prefill 워커에 먼저 보내고 이어서 decode 워커로 넘겼습니다. 차트 기본값에서 Grove 는 꺼져 있고, 이 크기의 그래프에는 필요하지 않았습니다.
3. 같은 서버 안의 분리
접두 2·7·12·24종, 동시 4·8, 점마다 3회의 중앙값입니다. 24개 측정 모두 오류는 0건이었습니다. 견줄 값은 3편의 KV 라우팅(워커 6대)입니다.
| 접두 종류 | 동시 | TTFT p50: KV 라우팅 / 분리 | TTFT p99: KV 라우팅 / 분리 | 초당 요청: KV 라우팅 / 분리 |
|---|---|---|---|---|
| 2 | 4 | 34 / 286ms | 73 / 592ms | 10.9 / 6.5 |
| 7 | 4 | 40 / 375ms | 129 / 1,007ms | 8.9 / 5.6 |
| 12 | 4 | 43 / 465ms | 138 / 887ms | 8.7 / 5.0 |
| 24 | 4 | 64 / 518ms | 149 / 958ms | 8.1 / 4.8 |
| 2 | 8 | 49 / 462ms | 145 / 981ms | 14.7 / 9.0 |
| 7 | 8 | 60 / 650ms | 163 / 1,477ms | 12.2 / 7.2 |
| 12 | 8 | 69 / 794ms | 172 / 1,744ms | 12.1 / 6.4 |
| 24 | 8 | 106 / 896ms | 184 / 1,776ms | 11.5 / 6.0 |
두 구성은 자원이 똑같지 않습니다. KV 라우팅 쪽은 같은 24GB급 GPU 에 워커 4대를 두고 다른 서버의 워커 2대를 더 썼습니다. 분리 쪽은 그 GPU 한 장에 워커 4대(prefill 2, decode 2)만 썼습니다. 그래서 처리량의 차이에는 워커 2대의 몫이 섞여 있습니다. 반면 첫 토큰 시간의 차이는 그것으로 설명되지 않습니다.
가장 유리한 칸을 보면 알 수 있습니다. 접두 2종·동시 4 에서 캐시 토큰 비율은 0.857 로, prefill 워커가 접두를 거의 다시 계산하지 않았습니다. 그런데도 첫 토큰까지 286ms 가 걸렸습니다. KV 라우팅에서 같은 칸은 34ms 였습니다. prefill 워커의 캐시에 맞아도 decode 워커는 그 KV 를 갖고 있지 않으므로 272MB 를 통째로 받아야 합니다. 늘어난 약 250ms 가 그 값입니다. 272MB 를 0.25초에 옮겼다면 초당 1GB 남짓인데, 같은 서버 안에서 호스트 메모리를 거쳐 TCP 로 복사한 속도로는 그럴듯한 크기입니다. 전송만 따로 잰 것은 아니므로 이 분해는 추정입니다.
동시성이 오르면 전송이 겹쳐 더 늘어납니다. 동시 8 에서 중앙값은 0.46~0.9초, p99 는 1~1.8초였습니다.
적중률도 낮아졌습니다. 접두 12종에서 캐시 토큰 비율이 0.40~0.42, 24종에서 0.20 안팎으로, KV 라우팅(0.57~0.66, 0.36~0.43)에 못 미칩니다. 접두 캐시를 쥐고 있는 쪽이 prefill 워커 둘뿐이라, 담을 수 있는 접두의 총량이 워커 6대일 때의 3분의 1이기 때문입니다.
정리하면 이 조건에서 같은 서버 안의 분리는 첫 토큰이 8~12배 늦고 처리량이 37~48% 낮았습니다. GPU 가 한 장이라 계산 자원은 늘지 않았고, prefill 이 100ms 안쪽으로 끝나는 작은 모델이라 「긴 prefill 이 decode 를 막는다」는 문제가 처음부터 작았습니다. 얻을 것이 없는 곳에 전송 비용만 얹은 구성입니다.
4. 서버 사이의 분리: 느렸고, 그 전에 메모리가 먼저 찼다
1GbE 는 실제로 초당 110~117MB 를 나릅니다. 272MB 를 옮기면 2.3~2.5초가 걸린다는 계산이 나옵니다.
이 배치는 끝까지 재지 못했습니다. prefill 워커가 요청 몇 건 만에 OOMKilled 됐기 때문입니다.
| 시도 | 성공한 요청 | 그 요청들의 TTFT | 그 뒤 |
|---|---|---|---|
| 동시 4 | 2건 | 2,039ms, 2,040ms | 부하를 건 지 9초 만에 prefill 워커 OOMKilled(종료 코드 137), 남은 요청 전부 실패 |
| 동시 1 | 4건 | 중앙값 1,637ms, 최대 2,653ms | 같은 워커가 다시 OOMKilled |
성공한 여섯 건의 첫 토큰은 1.6~2.7초로, 링크 속도로 계산한 값과 같은 크기입니다. 표본이 여섯 건뿐이므로 분포를 말할 수는 없습니다. KV 라우팅의 34ms 와 견주면 50배 안팎입니다.
메모리 쪽이 더 쓸모 있는 발견입니다. prefill 워커의 메모리 한도는 3,584Mi 였습니다. 같은 이미지와 같은 엔진 인자로 3편의 모든 측정을 오류 없이 돌린 한도입니다. kv_buffer_device: cpu 에서는 옮길 KV 가 호스트 메모리에 올라가므로 요청 하나에 272MB 가 더 듭니다. 동시 4 면 1GB 를 넘습니다. 동시 1 에서도 네 건 만에 한도를 넘은 것을 보면 전송이 끝난 버퍼가 곧바로 돌아오지 않은 것으로 보입니다. 워커의 메모리 사용량을 시간에 따라 재지는 않았으므로 원인은 미확인입니다.
같은 서버 배치에서는 이 문제가 없었습니다. 그쪽 워커의 한도는 10Gi 였습니다. 서버 사이 배치의 한도를 올려 다시 재지는 않았습니다. 그 서버에 다른 워크로드가 함께 있어서 한도를 넉넉히 줄 수 없었습니다.
여기서 가져갈 것은 하나입니다. 분리 서빙으로 옮기면 워커의 메모리 한도를 다시 계산해야 합니다. GPU 버퍼로 RDMA 를 타는 구성에서는 사정이 다르겠지만(미실측), TCP 와 호스트 버퍼를 쓰는 구성에서는 「동시 요청 수 × 요청 하나의 KV 크기」에 여유를 더한 만큼이 기존 한도 위에 필요합니다.
5. 그러면 언제 이득인가
이 글은 분리 서빙이 이기는 조건을 재지 못했습니다. 잰 것은 지는 조건 둘입니다. 다만 손익을 가르는 양이 무엇인지는 측정에서 드러났습니다.
분리 서빙이 치르는 값 ≈ 요청 하나의 KV 바이트 ÷ 전송 처리량
분리 서빙이 얻는 값 ≈ 긴 prefill 이 같은 GPU 의 decode 를 막던 시간
- 치르는 값은 미리 계산할 수 있습니다. 토큰당 KV 바이트에 입력 길이를 곱하고 링크 처리량으로 나눕니다. 이 글의 모델과 요청으로는 같은 서버 안에서 약 0.25초, 1GbE 에서 약 2초였습니다. KV 가 큰 모델일수록, 입력이 길수록 늘어납니다.
- 얻는 값은 모델이 크고 입력이 길어 prefill 이 초 단위로 걸리고, prefill 과 decode 를 서로 다른 GPU 에 둘 수 있을 때 생깁니다. 0.6B 모델의 prefill 은 100ms 안쪽이라 얻을 것이 없었습니다.
문서가 RDMA 를 전제로 삼는 까닭이 이 식에 있습니다. 전송 처리량이 수십 GB/s 가 되어야 치르는 값이 prefill 시간보다 작아집니다. RDMA 나 NVLink 가 있는 환경에서의 손익은 미실측입니다.
6. 정리
- 분리 서빙 그래프는 Grove 없이 DGD 하나로 떴습니다. 구성 요소의
type과 워커 인자 둘이면 됩니다. - 같은 서버 안에서 TCP 로 나누면 첫 토큰이 34~106ms 에서 286~896ms 로 늘었고 처리량은 37~48% 줄었습니다. 캐시에 맞은 요청도 KV 전체를 옮기기 때문입니다.
- 1GbE 를 건너면 첫 토큰이 1.6~2.7초였습니다(성공한 6건). 메모리 한도 3,584Mi 의 prefill 워커는 동시 4 에서 9초 만에, 동시 1 에서 네 건 만에 OOMKilled 됐습니다.
- TCP 와 호스트 버퍼를 쓰는 분리 서빙에서는 워커의 메모리 한도에 「동시 요청 수 × 요청 하나의 KV 크기」를 더해야 합니다.
- 작은 모델, GPU 한 장, RDMA 없는 망에서는 분리 서빙을 켤 이유가 없습니다. 같은 장비라면 3편의 KV 라우팅만 쓰는 쪽이 모든 칸에서 나았습니다.
시리즈 전체를 한 줄씩 줄이면 이렇습니다. Service 뒤의 vLLM 은 접두가 한 대의 용량을 넘으면 캐시를 버립니다(1편). Dynamo 를 들이려면 오퍼레이터의 웹훅 범위와 권한부터 봐야 합니다(2편). KV 라우팅은 그 구간에서 적중을 두세 배로 올립니다(3편). 분리 서빙은 빠른 망과 큰 모델이 있을 때의 이야기입니다(이 편).
참고
- NVIDIA Dynamo v1.6.2 문서와 예제의 분리 서빙(disaggregated serving) 항목.
- vLLM 문서, Disaggregated Prefilling 과 NixlConnector.
- 매니페스트(
d3-disagg.yaml), 격자 기록, 워커 로그는 저장소의docs/experiments/2026-11-13-dynamo-adoption/에 있습니다. - 쿠버네티스 없이 서버 둘 사이에서 KV 전송량과 처리량을 따로 잰 기록은 NVIDIA Dynamo 직접 써 보기에 있습니다.