분리형 인코더
분리형 인코더 (Disaggregated Encoder)
이 예제 스크립트들은 vLLM의 분리형 인코더(disaggregated encoder, EPD) 기능을 시연합니다. EPD는 인코더(encode)·프리필(prefill)·디코드(decode) 단계를 서로 다른 인스턴스로 분리해, 비전 인코더를 별도 GPU에서 실행하고 인코더 캐시(EC)를 공유 스토리지로 전달합니다. 상세 설명은 Disaggregated Encoder 기능 문서를 참고하세요.
출처: 문서
본문
소스: https://github.com/vllm-project/vllm/tree/main/examples/disaggregated/disaggregated_encoder
이 예제 스크립트들은 vLLM의 disaggregated encoder(EPD) 기능을 시연합니다. EPD 기능에 대한 자세한 설명은 Disaggregated Encoder Feature Documentation을 참고하세요.
파일 (Files)
disagg_epd_proxy.py— XeYpZd(e개 encode 인스턴스, Y개 prefill 인스턴스, Z개 decode 인스턴스) 구성을 시연하는 프록시 스크립트. 현재 1e1p1d 구성에서 안정적입니다.disagg_1e1p1d_example.sh— 1e1p1d 구성으로 VisionArena 벤치마크를 실행하고 로컬 이미지로 단일 요청을 처리합니다.disagg_1e1pd_example.sh— 1e1pd 구성으로 VisionArena 벤치마크를 실행하고 로컬 이미지로 단일 요청을 처리합니다.
커스텀 구성 (Custom Configuration)
# Use specific GPUs
GPU_E=0 GPU_PD=1 GPU_P=1 GPU_D=2 bash disagg_1e1p1d_example.sh
# Use specific ports
ENDPOINT_PORT=10001 bash disagg_1e1p1d_example.sh
# Use specific model
MODEL="Qwen/Qwen2.5-VL-3B-Instruct" bash disagg_1e1p1d_example.sh
# Use specific storage path
EC_SHARED_STORAGE_PATH="/tmp/my_ec_cache" bash disagg_1e1p1d_example.sh
# Run on XPU; scripts switch from CUDA_VISIBLE_DEVICES to ZE_AFFINITY_MASK
DEVICE_PLATFORM=xpu GPU_E=0 GPU_PD=1 bash disagg_1e1pd_example.sh
DEVICE_PLATFORM은 기본값이 cuda입니다. Intel GPU에서 실행할 때는 DEVICE_PLATFORM=xpu로 설정해 스크립트가 CUDA_VISIBLE_DEVICES 대신 ZE_AFFINITY_MASK로 디바이스를 선택하게 합니다.
인코더 인스턴스 (Encoder Instances)
인코더 엔진은 다음 플래그로 띄워야 합니다.
--enforce-eager(필수) — 현재 EPD 구현은 이 모드로 실행되는 인코더 인스턴스와만 호환됩니다.--no-enable-prefix-caching(필수) — 인코더 인스턴스는 KV 캐시를 소비하지 않습니다. 다른 기능과의 충돌을 피하려고 프리픽스 캐싱을 끕니다.--max-num-batched-tokens=<대값>(기본 2048) — 이 플래그는 디코딩 스텝당 토큰 스케줄링 예산을 제어하며 인코더 전용 인스턴스와는 무관합니다. 스케줄러 한계를 우회하도록 아주 높은 값(사실상 무제한)으로 설정하세요. 실제 토큰 예산은 인코더 캐시 매니저가 관리합니다.--mm-encoder-only(선택) — 가능하면 언어 모델을 초기화에서 생략해 디바이스 메모리 사용을 줄입니다.
로컬 미디어 입력 (Local media inputs)
MEDIA_PATH 디렉토리의 로컬 이미지 입력을 지원하려면 인코더 인스턴스에 다음 플래그를 추가합니다.
--allowed-local-media-path $MEDIA_PATH
vllm 인스턴스와 disagg_encoder_proxy는 {"url": "file://'"$MEDIA_PATH_FILENAME"'} 형태의 로컬 URI를 멀티모달 입력으로 지원합니다. 각 URI는 disagg_encoder_proxy에서 인코더 인스턴스로 그대로 전달되어 인코더가 미디어를 로컬에서 로드할 수 있게 합니다.
EC 커넥터와 KV 전송 (EC connector and KV transfer)
ECExampleonnector는 인코더 캐시를 로컬 디스크에 저장하고 전송을 돕습니다. 인코더 분리 기능을 켜려면 다음 구성을 추가합니다.
# Add to encoder instance:
--ec-transfer-config '{
"ec_connector": "ECExampleConnector",
"ec_role": "ec_producer",
"ec_connector_extra_config": {
"shared_storage_path": "'"$EC_SHARED_STORAGE_PATH"'"
}
}'
# Add to prefill/prefill+decode instance:
--ec-transfer-config '{
"ec_connector": "ECExampleConnector",
"ec_role": "ec_consumer",
"ec_connector_extra_config": {
"shared_storage_path": "'"$EC_SHARED_STORAGE_PATH"'"
}
}'
$EC_SHARED_STORAGE_PATH는 EC 커넥터가 캐시를 임시로 저장하는 경로입니다.
prefill 인스턴스(--prefill-servers-urls가 비활성화되지 않음)를 켜면 PD 분리를 돕는 --kv-transfer-config가 필요합니다. 현재는 NixlConnector를 이 용도로 씁니다. Nixl로 PD 분리를 하는 더 많은 예제 코드는 tests/v1/kv_connector/nixl_integration을 참고하세요.
# Add to prefill instance:
--kv-transfer-config '{
"kv_connector": "NixlConnector",
"kv_role": "kv_producer"
}'
# Add to decode instance:
--kv-transfer-config '{
"kv_connector": "NixlConnector",
"kv_role": "kv_consumer"
}'
프록시 인스턴스 플래그 (Proxy Instance Flags, disagg_epd_proxy.py)
| Flag | Description |
|---|---|
--encode-servers-urls |
인코더 엔드포인트의 쉼표 구분 목록. 요청에서 추출한 모든 멀티모달 항목이 라운드로빈으로 이 URL들에 fan-out됩니다. |
--prefill-servers-urls |
prefill 엔드포인트의 쉼표 구분 목록. disable, none, ""로 설정하면 전용 prefill 단계를 건너뛰고 E+PD(인코더 + 결합 prefill/decode)를 실행합니다. |
--decode-servers-urls |
decode 엔드포인트의 쉼표 구분 목록. non-stream·stream 경로 모두 라운드로빈합니다. |
--host, --port |
프록시 자체의 바인드 주소(기본 0.0.0.0:8000). |
동적 등록 (Dynamic registration)
한편, 외부 런처가 HTTP로 준비된 인스턴스를 등록하게 할 수도 있습니다. vLLM 구성 변경이나 워커 등록 스레드가 필요 없습니다. 프록시에 ADMIN_API_KEY를 설정하고, 등록·제거에 X-API-Key로 제공하세요. 이들은 신뢰된 컨트롤 플레인 API이므로 공개로 노출하지 마세요.
export ADMIN_API_KEY="your-admin-key"
python disagg_epd_proxy.py --port 8000 --dynamic-registration
정적 서버 URL 플래그를 생략하고 각 단계를 등록하세요. 역할이 토폴로지를 결정합니다. encode + prefill_decode는 E+PD, encode + prefill + decode는 E+P+D입니다. 독립 decode는 D가 먼저 등록하거나 모든 P 인스턴스가 오프라인이어도 항상 사용 가능한 prefill을 요구합니다. 그렇지 않으면 요청이 503을 반환합니다. 프록시는 결합 PD와 독립 P/D를 섞는 것을(등록부에 아직 있는 비정상 인스턴스 포함) 거부합니다. 토폴로지를 바꾸기 전에 이전 토폴로지의 P/D 또는 PD 등록을 명시적으로 제거하세요.
각 인스턴스가 준비되면 런처는 접근 가능한 HTTP URL을 등록합니다.
curl --fail-with-body http://proxy-host:8000/instances \
-H "X-API-Key: ***" -H 'Content-Type: application/json' \
-d '{"role":"encode","url":"http://e-host:8001"}'
curl --fail-with-body http://proxy-host:8000/instances \
-H "X-API-Key: ***" -H 'Content-Type: application/json' \
-d '{"role":"prefill_decode","url":"http://pd-host:8002"}'
E+P+D에 대해서는 P를 role: "prefill", D를 role: "decode"로 등록합니다. Example·NIXL EC 커넥터는 추가 등록 필드가 필요 없습니다. Mooncake는 EC 소비자(prefill_decode 또는 prefill)에 ec_zmq_addrs를 등록하며, 설정된 ec_ip와 ec_port를 사용합니다. 고정 포트 레이아웃을 유지하세요. 각 DP 레플리카의 TP-rank-0 주소를 DP-rank 순서로 제공합니다. 레플리카 r은 ec_port + r * tensor_parallel_size를 사용합니다. 커넥터가 나머지 TP 랭크를 스스로 발견합니다. 예: DP=2, TP=2, ec_port=19019인 경우:
{
"role": "prefill_decode",
"url": "http://pd-host:8002",
"dp_size": 2,
"ec_zmq_addrs": ["tcp://pd-host:19019", "tcp://pd-host:19021"]
}
프록시는 소비자 레플리카 하나를 골라 인코더 push와 소비자 HTTP 요청 양쪽에 사용합니다. 독립 D는 EC 컨트롤 주소가 필요 없습니다. 포트 할당과 충돌 방지는 런처의 책임입니다.
프록시를 재시작하지 않고 인스턴스를 조회·제거할 수 있습니다.
curl http://proxy-host:8000/instances
curl --fail-with-body -X DELETE \
'http://proxy-host:8000/instances?url=http://e-host:8001' \
-H "X-API-Key: ***"
등록은 멱등(idempotent)입니다. 프록시는 --probe-interval 초마다(기본 5) 등록된 인스턴스를 프로브하며, 프로브당 --probe-timeout 초(기본 2)를 씁니다. --fail-threshold(기본 3) 연속 실패 후에는 그 인스턴스에 새 요청을 보내지 않습니다. 건강한 인스턴스는 자동으로 다시 합류하며, 도달 불가능한 인스턴스는 --evicted-ttl 초(기본 900, 0이면 무기한) 후 잊혀집니다. 제거는 새 라우팅만 막으며, 이미 라우팅된 요청은 선택된 엔드포인트를 유지하므로 인스턴스 중지 전에 요청을 소진(drain)하세요. 프록시 재시작 후에는 런처가 인스턴스를 다시 등록해야 합니다.
예제 실행 스크립트도 이 흐름을 지원합니다. disagg_1e1pd_example.sh 또는 disagg_1e1p1d_example.sh 실행 시 ADMIN_API_KEY를 export하고 DYNAMIC_REGISTRATION=1로 설정하세요. 기본값은 정적 라우팅입니다.
정적 구성 (Static configuration)
프록시는 같은 사용자 요청의 이미지를 같은 인코더에 배치합니다. ENCODER_MAX_BATCH_SIZE 환경변수로 인코더 서브요청당 이미지 수를 제한합니다. 기본값은 0(무제한)이며, 1이면 각 이미지를 개별로 보냅니다. 오디오·비디오는 별도 서브요청으로 유지됩니다.
P/PD보다 작은 --limit-mm-per-prompt 이미지 한도를 가진 인코더라면, ENCODER_MAX_BATCH_SIZE를 가장 작은 인코더 이미지 한도보다 높게 설정하지 마세요. 예: --limit-mm-per-prompt '{"image": 2}'로 구성된 인코더:
ENCODER_MAX_BATCH_SIZE=2 python disagg_epd_proxy.py \
--encode-servers-urls "http://e1:8001,http://e2:8002" \
--prefill-servers-urls disable \
--decode-servers-urls "http://pd1:8003"
E + PD 구성 예:
$ python disagg_encoder_proxy.py \
--encode-servers-urls "http://e1:8001,http://e2:8002" \
--prefill-servers-urls "disable" \
--decode-servers-urls "http://pd1:8003,http://pd2:8004"
E + P + D 구성 예:
$ python disagg_encoder_proxy.py \
--encode-servers-urls "http://e1:8001,http://e2:8001" \
--prefill-servers-urls "http://p1:8003,http://p2:8004" \
--decode-servers-urls "http://d1:8005,http://d2:8006"
예제 스크립트 (Example scripts)
이 디렉토리는 다음 실행 스크립트들을 포함합니다(원문은 GitHub 저장소에서 확인):
- disagg_1e1p1d_example.sh — 인코더(GPU_E) 1대, prefill(GPU_P) 1대, decode(GPU_D) 1대를 띄우고
disagg_epd_proxy.py프록시(ENCODE_PORT/PREFILL_PORT/DECODE_PORT/PROXY_PORT)로 연결해, VisionArena 데이터셋으로vllm bench serve벤치마크와 단일 로컬 이미지 요청을 처리합니다. 인코더에는 EC(ec_producer), prefill/decode에는NixlConnector(kv_producer/kv_consumer)를 설정합니다. - disagg_1e1pd_example.sh — 인코더 1대 + 결합 prefill/decode 1대(1e1pd)를 구성해 동일하게 실행합니다.
- disagg_epd_proxy.py — Encode/Prefill/Decode 서비스를 조율하는 FastAPI 프록시. 멀티모달 항목을 인코더에 fan-out하고, 인코더 캐시 전송 후 prefill/decode로 라우팅합니다.
동작 요약은 다음과 같습니다.
- 인코더 인스턴스가 멀티모달 입력의 비전·오디오·비디오를 처리하고, 결과 인코더 캐시를 EC 커넥터(
ECExampleConnector,ec_role: ec_producer)로 공유 스토리지에 저장합니다. - prefill(decode 포함) 인스턴스는
ec_role: ec_consumer로 저장된 인코더 캐시를 로드해 재사용합니다. 별도 prefill+decode 분리 시에는NixlConnector(kv_producer/kv_consumer)로 KV를 전송합니다. - 프록시가 요청을 인코더 → prefill → decode 단계로 조율하며, 동적 등록(ADMIN_API_KEY) 또는 정적 URL 플래그를 사용합니다.
더 알아보기 (Learn more)
- Disaggregated Serving — P/D 분리 서빙
--ec-transfer-config(ECExampleConnector,ec_role)DYNAMIC_REGISTRATION/ADMIN_API_KEY기반 동적 인스턴스 등록