분리형 서빙
분리형 서빙 (Disaggregated Serving)
이 예제는 vLLM의 disaggregated serving 기능을 보여주는 스크립트들을 담고 있습니다. prefill 인스턴스와 decode 인스턴스를 분리해 XpYd(X개 prefill, Y개 decode) 방식을 구성하고, 이를 조율하는 프록시를 제공합니다. 또한 KV 캐시 이벤트 발행과 MoRI-IO 커넥터 레퍼런스 프록시도 포함됩니다.
출처: 문서
본문
소스: https://github.com/vllm-project/vllm/tree/main/examples/disaggregated/disaggregated_serving
이 예제는 vLLM의 disaggregated serving 기능을 보여주는 스크립트들을 포함합니다.
파일 (Files)
disagg_proxy_demo.py— XpYd(X개 prefill 인스턴스, Y개 decode 인스턴스) 구성을 시연합니다.kv_events.sh— KV 캐시 이벤트 발행을 시연합니다.mooncake_connector— MooncakeConnector용 프록시 데모.
disagg_proxy_demo.py
XpYd disaggregated prefill을 시연하는 프록시 데모입니다. 여러 vLLM 인스턴스(예: prefill 2대, decode 2대)를 띄우고 이 프록시 데모를 실행합니다.
python3 examples/disaggregated/disaggregated_serving/disagg_proxy_demo.py \
--model $model_name \
--prefill localhost:8100 localhost:8101 \
--decode localhost:8200 localhost:8201 \
--port 8000
참고: 이 데모는 PR 15343(https://github.com/vllm-project/vllm/pull/15343)의 PDController가 XpYd를 지원하게 되면 제거될 예정입니다.
프록시 클래스는 다음 기능을 제공합니다.
- 스케줄링 정책(
SchedulingPolicy) —schedule(cycler)을 구현하는 추상 클래스로, prefill·decode 인스턴스를 어떤 순서로 선택할지 결정합니다(라운드로빈 cycler 사용). - 요청 라우팅 —
/v1/completions·/v1/chat/completions를 받아 요청을 prefill 인스턴스로 보내고(응답 무시,max_tokens=1), 그 다음 decode 인스턴스에서 스트리밍 응답을 받아 클라이언트로 되돌립니다.forward_request는 aiohttp로 청크(chunked) 스트리밍하며, 비정상 상태(4xx/5xx)는 HTTPException으로 변환합니다. - 동적 인스턴스 등록 —
/instances/add엔드포인트(X-API-Key인증,ADMIN_API_KEY사용)로 prefill/decode 인스턴스를 런타임에 추가합니다. 추가 전에/v1/models로 모델 id가 일치하는지 검증합니다. - 상태 확인 —
/status로 prefill/decode 노드 수와 목록을 반환합니다. - 커스텀 오버라이드 — 생성자에서
custom_create_completion/custom_create_chat_completion콜러블을 넘겨 기본 라우팅을 대체할 수 있습니다.
kv_events.sh
KV 캐시 이벤트 발행을 시연합니다. KV 캐시 이벤트를 발행하도록 구성된 vLLM 인스턴스를 띄우고, 그 이벤트를 로깅하는 간단한 구독자를 실행합니다.
⚠️ KV 캐시 이벤트 사용은 실험적이며 변경될 수 있습니다.
#!/bin/bash
# This file demonstrates the KV cache event publishing
# We will launch a vllm instances configured to publish KV cache
# events and launch a simple subscriber to log those events.
set -xe
echo "🚧🚧 Warning: The usage of KV cache events is experimental and subject to change 🚧🚧"
sleep 1
MODEL_NAME=${HF_MODEL_NAME:-meta-llama/Meta-Llama-3.1-8B-Instruct}
# Trap the SIGINT signal (triggered by Ctrl+C)
trap 'cleanup' INT
# Cleanup function
cleanup() {
echo "Caught Ctrl+C, cleaning up..."
# Cleanup commands
pgrep python | xargs kill -9
pkill -f python
echo "Cleanup complete. Exiting."
exit 0
}
export VLLM_HOST_IP=$(hostname -I | awk '{print $1}')
# a function that waits vLLM server to start
wait_for_server() {
local port=$1
timeout 1200 bash -c "
until curl -s localhost:${port}/v1/completions > /dev/null; do
sleep 1
done" && return 0 || return 1
}
vllm serve "$MODEL_NAME" \
--port 8100 \
--max-model-len 100 \
--enforce-eager \
--gpu-memory-utilization 0.8 \
--trust-remote-code \
--kv-events-config \
'{"enable_kv_cache_events": true, "publisher": "zmq", "topic": "kv-events"}' &
wait_for_server 8100
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
python3 "$SCRIPT_DIR/kv_events_subscriber.py" &
sleep 1
# serve two example requests
output1=$(curl -X POST -s http://localhost:8100/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "'"$MODEL_NAME"'",
"prompt": "Explain quantum computing in simple terms a 5-year-old could understand.",
"max_tokens": 80,
"temperature": 0
}')
output2=$(curl -X POST -s http://localhost:8100/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "'"$MODEL_NAME"'",
"prompt": "Explain quantum computing in simple terms a 50-year-old could understand.",
"max_tokens": 80,
"temperature": 0
}')
# Cleanup commands
pkill -9 -u "$USER" -f python
pkill -9 -u "$USER" -f vllm
sleep 1
echo "Cleaned up"
# Print the outputs of the curl requests
echo ""
echo "Output of first request: $output1"
echo "Output of second request: $output2"
echo "🎉🎉 Successfully finished 2 test requests! 🎉🎉"
echo ""
동작 방식:
--kv-events-config '{"enable_kv_cache_events": true, "publisher": "zmq", "topic": "kv-events"}'로 KV 캐시 이벤트 발행을 켭니다(ZMQ publisher).kv_events_subscriber.py(별도 스크립트)가 이벤트를 구독해 로깅합니다.- vLLM 서버가 준비되면(
wait_for_server) 두 개의 예시 완성 요청을 보내고 결과를 출력합니다.
moriio_toy_proxy_server.py
MoRI-IO prefill/decode 분리를 위한 최소 단일 노드 레퍼런스 프록시입니다. 커넥터가 기대하는 DP-rank 핀 계약을 시연합니다: 요청마다 prefill DP 랭크 하나를 골라(flat_interleaved_dp_route), X-data-parallel-rank 헤더와 kv_transfer_params(remote_dp_rank/remote_dp_size/remote_tp_size)로 양쪽 레그를 그 랭크에 고정합니다.
핵심 특징:
- 서비스 디스커버리 — ZMQ ROUTER 소켓(
tcp://...:36367)으로 prefill/decode 인스턴스가HELLO/P/Dmsgpack 등록 메시지를 보내면 목록에 추가/갱신합니다.transfer_mode불일치는 거부합니다. - 플랫 인터리브 라우팅(
flat_interleaved_dp_route) —(n_instances * dp_size)슬롯 공간에 하나의 카운터를 사용합니다.inst0_r0, inst1_r0, inst0_r1, inst1_r1, ...순으로 연속 요청이 인스턴스를 번갈아 타면서 모든 랭크가 순회됩니다. 이전의req % n/req % dp방식은n | dp일 때 각 인스턴스가 일부 랭크만 쓰게 돼 절반 GPU가 유휴로 남아 '스케일링 안 됨'처럼 보이는 문제를 고쳤습니다.dp_size == 1(예: TP decode)이면dp_rank=None을 반환합니다. - 요청 처리 — 요청을 prefill(응답 무시,
max_tokens=1)으로 보내고,kv_transfer_params에remote_dp_size/remote_tp_size/transfer_id/remote_dp_rank를 채운 뒤 decode에서 스트리밍 응답을 받습니다.request_id에 양쪽 zmq 주소를 임베딩해 커넥터가 P2P-NCCL처럼 피어를 파싱합니다. READ 전송 모드에서는 prefill 후 decode를 직렬로 실행합니다. - 프로파일링 —
/start_profile//stop_profile로 등록된 모든 인스턴스에start_profile/stop_profile요청을 보냅니다. - 운영 — hypercorn으로 깊은 listen backlog(기본 4096,
PROXY_LISTEN_BACKLOG)와 긴 keep-alive(SSE 스트림용)를 설정해 512 동시 연결 폭주 시 connection-reset 손실을 방지합니다.
mooncake_connector
MooncakeConnector를 위한 프록시 데모입니다. 자세한 구성은 Mooncake 커넥터 예제를 참고하세요.
더 알아보기 (Learn more)
- Disaggregated Encoder — EPD(인코더 분리)
- Mooncake 커넥터
- LMCache 예제
--kv-transfer-config(NixlConnector 등)와--kv-events-config