콘텐츠로 이동

Disaggregated Encoder

Disaggregated Encoder 베타 상태

이 기능은 베타 버전입니다. 하위 호환성을 보장하지 않으며, 프로덕션 환경에서의 사용은 권장하지 않습니다.

Model implementation 지원

이 페이지에서 설명하는 설정은 transformers(optimum) model implementation에 적용됩니다.

개요

Disaggregated Encoder는 멀티모달 모델의 비전 인코더와 언어 모델을 별도의 vLLM 프로세스에서 실행합니다. 인코더는 이미지를 처리해 출력 텐서를 생성하고, 언어 모델은 이 텐서를 사용해 텍스트를 생성합니다.

비전 인코더와 언어 모델에 NPU를 독립적으로 할당할 수 있으며, 인코더 인스턴스를 추가해 이미지 인코딩 처리 용량을 늘릴 수 있습니다.

지원 모델

Disaggregated Encoder는 Qwen3-VL 모델군만 지원합니다.

아키텍처

배포 구성은 프록시, N개의 인코더 인스턴스, 하나의 prefill/decode(PD) 인스턴스로 이루어집니다. PD 인스턴스는 언어 모델의 prefill과 decode를 같은 프로세스에서 실행합니다.

각 인코더 인스턴스와 PD 인스턴스에 서로 겹치지 않는 NPU 집합을 할당해야 합니다.

구성 요소 역할
Proxy 클라이언트 요청을 받아 이미지를 인코더에 분배하고, 원본 요청을 PD에 전달
Encoder 비전 인코더로 이미지를 처리해 출력 텐서를 생성
PD 인코더 출력 텐서를 가져와 같은 인스턴스에서 언어 모델의 prefill과 decode를 실행

다음 다이어그램과 표는 이미지가 포함된 채팅 요청의 처리 과정을 보여줍니다.

---
config:
  theme: base
  themeVariables:
    actorLineColor: "#0d9488"
---
sequenceDiagram
    participant Client
    participant Proxy
    participant Encoder as Encoder × N
    participant PD as PD (Prefill + Decode)
    Client->>Proxy: 채팅 요청
    Proxy->>Encoder: 1. 이미지 전용 요청
    Note over Encoder: 2. 이미지 인코딩
    Encoder->>PD: 3. 출력 텐서 메타데이터 (ZMQ)
    Encoder-->>Proxy: 인코더 요청 성공
    Proxy->>PD: 4. 원본 요청
    Encoder->>PD: 5. 인코더 출력 텐서 (NIXL)
    Note over PD: 6. Prefill 및 decode 실행
    PD-->>Proxy: 응답
    Proxy-->>Client: 응답
단계 동작 설명
1 이미지 분배 프록시는 라운드 로빈 방식으로 이미지를 인코더에 할당한 뒤, 이미지마다 텍스트를 제외한 요청을 하나씩 만들어 동시에 전송합니다.
2 이미지 인코딩 각 인코더는 이미지를 처리하고, 출력 텐서를 NIXL에 등록합니다.
3 출력 텐서 메타데이터 전송 각 인코더는 ZMQ를 통해 출력 텐서의 메타데이터(위치, 형태, 데이터 타입)를 PD에 전송한 뒤, 프록시에 성공 응답을 보냅니다.
4 원본 요청 전달 모든 인코더 요청이 성공하면 프록시는 텍스트와 이미지를 포함한 원본 요청을 PD에 전달합니다.
5 출력 텐서 가져오기 PD는 메타데이터를 사용해 NIXL로 인코더 출력 텐서를 가져와 인코더 캐시에 저장합니다.
6 Prefill 및 decode 실행 PD는 캐시된 인코더 출력 텐서를 사용해 prefill과 decode를 실행합니다. 프록시는 생성된 응답을 클라이언트에 반환합니다.

텍스트만 포함된 요청은 프록시가 인코더를 거치지 않고 PD에 직접 전달합니다.

설정

nixl 설치

Disaggregated Encoder를 사용하려면 인코더와 PD 인스턴스를 실행하는 Python 환경에 nixl이 필요합니다. 설치 방법은 기능별 추가 패키지에서 확인할 수 있습니다.

인코더 및 PD

HTTP 서빙

각 인코더와 PD 인스턴스는 프록시의 요청을 받는 HTTP 서버를 실행합니다. 다음 vllm serve CLI 옵션으로 바인딩 주소와 포트를 설정합니다.

CLI 옵션 설정 대상 설명
--host Encoder, PD 프록시의 HTTP 요청을 받을 바인딩 주소
--port Encoder, PD 프록시의 HTTP 요청을 받을 수신 포트

같은 호스트의 프록시에서만 요청을 받으려면 --host를 127.0.0.1로 설정합니다. 다른 호스트의 프록시에서 요청을 받으려면 로컬 인터페이스의 IP에 바인딩하거나, 모든 로컬 인터페이스에서 요청을 받도록 0.0.0.0을 사용합니다.

인코더 출력 텐서 전송

PD가 인코더 출력 텐서를 사용할 수 있도록 encoder-cache(EC) 커넥터를 설정해야 합니다.

각 인코더와 PD 인스턴스에서 vllm serve의 CLI 옵션인 --ec-transfer-config에 JSON 객체를 전달합니다. 객체의 구조는 다음과 같습니다.

1
2
3
4
5
6
--ec-transfer-config (JSON)
├── ec_connector              # 커넥터 선택
├── ec_role                   # Producer 또는 consumer 역할
└── ec_connector_extra_config # 메타데이터 엔드포인트 설정
    ├── pull_host, pull_port          # PD에서 설정
    └── llm_host, llm_pull_port       # 인코더에서 설정
ec_connector

인코더와 PD 인스턴스 모두 RblnECNixlConnector로 설정해 NIXL을 통해 인코더 출력 텐서를 전송합니다.

ec_role

각 인스턴스의 역할을 설정합니다.

설정 대상 JSON 값 설명
Encoder ec_producer 출력 텐서 제공
PD ec_consumer 출력 텐서 가져오기
ec_connector_extra_config

인코더가 PD에 메타데이터를 전송할 때 사용할 연결 정보를 설정합니다.

JSON 키 설정 대상 설명
pull_host PD 인코더가 보내는 메타데이터를 받을 바인딩 주소
pull_port PD 인코더가 보내는 메타데이터를 받을 수신 포트
llm_host Encoder 접속할 PD 메타데이터 소켓의 주소
llm_pull_port Encoder 접속할 포트. PD의 pull_port와 같아야 합니다.

같은 호스트에서 실행할 때는 pull_host와 llm_host에 모두 127.0.0.1을 사용합니다.

서로 다른 호스트에서 실행할 때는 PD의 pull_host를 로컬 인터페이스의 IP 또는 0.0.0.0으로 설정하고, 각 인코더의 llm_host를 해당 인코더에서 접속할 수 있는 PD 주소로 설정합니다.

프록시

프록시는 자체 HTTP 포트로 클라이언트 요청을 받아 이미지 요청을 인코더에 보내고, 원본 요청을 PD에 전달합니다. 다음 CLI 옵션으로 프록시 스크립트를 설정합니다.

CLI 옵션 설정 대상 설명
--host Proxy 클라이언트의 HTTP 요청을 받을 바인딩 주소
--port Proxy 클라이언트의 HTTP 요청을 받을 수신 포트
--encode-servers-urls Proxy 쉼표로 구분한 인코더 인스턴스의 HTTP URL 목록. 인코더를 추가할 때 이 목록을 갱신해야 합니다.
--decode-servers-urls Proxy 단일 PD 인스턴스의 HTTP URL

같은 호스트의 클라이언트에서만 요청을 받으려면 프록시의 --host를 127.0.0.1로 설정합니다. 다른 호스트의 클라이언트에서 요청을 받으려면 로컬 인터페이스의 IP에 바인딩하거나, 모든 로컬 인터페이스에서 요청을 받도록 0.0.0.0을 사용합니다.

예제

이 예제는 ATOM™ NPU를 사용하는 단일 호스트에 Qwen3-VL-8B-Instruct를 배포합니다.

인코더 인스턴스에는 컴파일된 모델이 필요하므로, 이 구성에서는 모델을 미리 컴파일하는 방식을 권장합니다.

시작하기 전에 RBLN vLLM과 nixl을 설치해야 합니다.

포트 및 디바이스

이 예제는 16개의 NPU에서 인코더 인스턴스 8개와 prefill/decode(PD) 인스턴스 1개를 실행합니다. 명령어에서 사용하는 포트와 디바이스는 다음과 같습니다.

구성 요소 HTTP URL PD 메타데이터 포트 NPU 디바이스
Proxy http://127.0.0.1:19000 — —
PD http://127.0.0.1:19100 19500 0–7
Encoder 0–7 http://127.0.0.1:19101 ~ http://127.0.0.1:19108 19500 8–15(인코더당 1개)

사용 환경에서 이용 가능한 포트와 디바이스 ID를 선택합니다. 이 예제는 RBLN_VISIBLE_DEVICES로 각 인코더와 PD 프로세스에 NPU를 할당합니다. 커넥터와 프록시 설정은 설정에서 확인할 수 있습니다.

모델 컴파일

optimum-rbln Python API로 모델을 한 번 컴파일하고 Qwen3-VL-8B-Instruct에 저장해 인코더와 PD 인스턴스에서 함께 사용합니다.

from optimum.rbln import RBLNQwen3VLForConditionalGeneration

model_id = "Qwen/Qwen3-VL-8B-Instruct"
model = RBLNQwen3VLForConditionalGeneration.from_pretrained(
    model_id,
    export=True,
    rbln_config={
        "visual": {
            "max_seq_len": 5440,
            "num_devices": 1,
        },
        "num_devices": 8,
        "max_seq_len": 32_768,
        "batch_size": 8,
        "decoder_batch_sizes": [8],
    },
)
model.save_pretrained("Qwen3-VL-8B-Instruct")

Qwen3-VL-8B-Instruct 디렉터리가 있는 상위 디렉터리에서 서빙 명령어를 실행하거나, 각 명령어의 모델 경로를 변경해야 합니다.

PD 인스턴스 실행

PD를 EC consumer로 실행합니다.

PD_EC_CONFIG='{
    "ec_connector": "RblnECNixlConnector",
    "ec_role": "ec_consumer",
    "ec_connector_extra_config": {
        "pull_host": "127.0.0.1",
        "pull_port": 19500
    }
}'

RBLN_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
    vllm serve Qwen3-VL-8B-Instruct \
        --model-impl transformers \
        --host 127.0.0.1 \
        --port 19100 \
        --mm-processor-kwargs '{"max_pixels": 802816}' \
        --ec-transfer-config "$PD_EC_CONFIG"

필요하면 다른 터미널에서 다음 명령어를 실행해 PD가 요청을 받을 준비가 될 때까지 기다릴 수 있습니다. 서버 로그에서 PD가 준비된 것을 확인했다면 이 단계는 생략해도 됩니다.

1
2
3
until curl -sf http://127.0.0.1:19100/health > /dev/null; do
    sleep 2
done

인코더 인스턴스 실행

PD가 준비되면 인코더마다 별도의 터미널에서 다음 블록을 실행합니다. ENCODER_ID는 0부터 7까지 서로 다른 값으로 지정합니다.

ENCODER_ID=0

ENCODER_EC_CONFIG='{
    "ec_connector": "RblnECNixlConnector",
    "ec_role": "ec_producer",
    "ec_connector_extra_config": {
        "llm_host": "127.0.0.1",
        "llm_pull_port": 19500
    }
}'

RBLN_VISIBLE_DEVICES=$((8 + ENCODER_ID)) \
    vllm serve Qwen3-VL-8B-Instruct \
        --model-impl transformers \
        --host 127.0.0.1 \
        --port $((19101 + ENCODER_ID)) \
        --mm-processor-kwargs '{"max_pixels": 802816}' \
        --ec-transfer-config "$ENCODER_EC_CONFIG"

필요하면 다른 터미널에서 다음 명령어로 모든 인코더 인스턴스가 요청을 받을 준비가 될 때까지 기다린 뒤 프록시를 실행할 수 있습니다. 각 인코더의 로그에서 모두 준비된 것을 확인했다면 이 단계는 생략해도 됩니다.

1
2
3
4
5
for port in $(seq 19101 19108); do
    until curl -sf "http://127.0.0.1:$port/health" > /dev/null; do
        sleep 2
    done
done

프록시 실행

별도 터미널에서 예제 프록시를 실행합니다. /path/to/vllm-rbln을 로컬에 내려받은 vllm-rbln 저장소 경로로 바꿉니다.

ENCODER_URLS="http://127.0.0.1:19101,\
http://127.0.0.1:19102,\
http://127.0.0.1:19103,\
http://127.0.0.1:19104,\
http://127.0.0.1:19105,\
http://127.0.0.1:19106,\
http://127.0.0.1:19107,\
http://127.0.0.1:19108"

python /path/to/vllm-rbln/examples/optimum/ec_disagg/client_ec_disaggregated.py \
    --host 127.0.0.1 \
    --port 19000 \
    --encode-servers-urls "$ENCODER_URLS" \
    --decode-servers-urls "http://127.0.0.1:19100"

요청 전송

프록시가 준비되면 다른 터미널에서 /v1/chat/completions 엔드포인트로 이미지와 텍스트 프롬프트를 전송합니다.

REQUEST_BODY='{
    "model": "Qwen3-VL-8B-Instruct",
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "http://images.cocodataset.org/val2017/000000039769.jpg"
                    }
                },
                {
                    "type": "text",
                    "text": "Briefly describe this image."
                }
            ]
        }
    ],
    "max_tokens": 256
}'

curl -sS http://127.0.0.1:19000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d "$REQUEST_BODY"

응답의 choices[0].message.content에 모델이 생성한 이미지 설명이 포함됩니다.