콘텐츠로 이동

분리형 Prefill/Decode 서빙

Continuum Router는 prefill 단계(프롬프트 처리)와 decode 단계(토큰 생성)를 별도의 GPU 워커에서 실행하는 분리형 추론(disaggregated inference) 아키텍처를 지원합니다. 서빙 엔진은 prefill 단계에서 계산된 KV 블록을 자체 전송 계층(예: Dynamo 배포의 NIXL)으로 워커 간에 직접 전달합니다. 라우터는 텐서 바이트를 직접 다루지 않으며, 요청을 처리할 prefill 워커, decode 워커, 라우팅 경로를 선택하고 그 결과를 응답 헤더에 기록합니다.

개요

표준(통합형) 추론에서는 단일 GPU 워커가 요청의 두 단계를 모두 처리합니다.

  1. Prefill: 전체 입력 프롬프트에 대한 key-value attention 텐서 계산
  2. Decode: prefill에서 계산된 KV 캐시를 읽어 출력 토큰을 자기회귀적으로 생성

분리형 서빙은 이 단계들을 역할별 워커로 분리합니다.

  • Prefill 워커: 배치 KV 연산에 최적화된 고처리량 GPU
  • Decode 워커: 저지연 토큰 생성을 위해 웜 KV 캐시를 유지하는 GPU
  • 엔진 주도 KV 전송: 서빙 엔진이 NIXL, NCCL, 또는 자체 스토리지 티어를 통해 prefill 워커에서 decode 워커로 KV 블록을 직접 전달하며, 라우터는 참여할 두 워커만 선택합니다

이 방식은 다음과 같은 환경에서 특히 효과적입니다.

  • 많은 요청이 긴 시스템 프롬프트를 공유하는 경우 (예: RAG 문서, 도구 정의)
  • Prefill과 decode 워크로드의 GPU 메모리 요구사항이 서로 다른 경우
  • 토큰 생성 지연 시간 단축이 핵심 최적화 목표인 경우

요청 흐름

DisaggregatedOrchestrator는 스트리밍(stream: true) 파이프라인과 비스트리밍 파이프라인이 분기하기 전에 각 chat completion 요청의 라우팅 경로를 한 번 결정합니다. 선택된 백엔드는 단일 원소 허용 목록 형태로 일반 디스패치 파이프라인에 전달되므로, 상태 확인, 서킷 브레이커, 백엔드별 속도 제한, Hub 자격 증명 바인딩 등 모든 하위 게이트가 동일하게 적용됩니다.

오케스트레이터는 KV 리스너가 캐시 이벤트를 발행할 때 사용하는 키와 동일한 64자 16진수 SHA-256 프리픽스 키로 KV 인덱스를 조회합니다. 이 키는 prefix_routing.enabled가 true일 때만 생성되므로, fast-decode 라우팅을 사용하려면 프리픽스 라우팅이 활성화되어 있어야 합니다. 프리픽스 라우팅이 비활성화되어 있으면 오케스트레이터는 KV 인덱스 조회를 건너뛰고 곧바로 prefill-then-decode 선택 단계로 넘어갑니다.

라우팅 경로

경로 설명 응답 헤더 값
FastDecode decode 워커 GPU에 KV 데이터가 이미 상주하거나(GpuHot), 엔진이 오프로드한 데이터(StorageWarm)를 최소 부하의 정상 decode 워커가 다시 로드할 수 있는 경우 fast_decode
PrefillThenDecode 전체 prefill-then-decode 사이클을 수행할 prefill 워커와 decode 워커를 각각 선택한 경우 prefill_then_decode
Unified 모델을 서빙하는 prefill 워커가 없는 Dynamo shim 디스패치(집계 폴백). 단일 워커가 두 단계를 모두 처리 unified (Dynamo shim 전용)
Fallback 분리형 백엔드를 사용할 수 없어 통합형 서빙으로 폴백했거나, fallback_to_unified가 비활성화되어 503 오류 엔벨로프로 응답한 경우 fallback

선택된 라우팅 경로는 X-Continuum-Routing-Path 응답 헤더에 기록됩니다. 범용 prefill-then-decode 와이어 프로토콜은 이번 릴리스에 아직 구현되어 있지 않습니다. 엔진 전용 피커가 활성화되어 있지 않은 경우 해당 경로는 두 워커를 선택한 뒤 503 disaggregated_prefill_decode_unavailable을 반환하거나 통합형 서빙으로 폴백합니다.

unified 값은 모델을 서빙하는 prefill 워커가 없어 라우터가 decode(또는 집계) 워커만 지정하는 Dynamo shim 디스패치에서만 기록됩니다. 이 경로를 사용하려면 dynamo 기능으로 빌드한 바이너리와 dynamo.shim.backends 풀 설정이 필요합니다.

prefill 또는 decode 역할이 지정된 백엔드가 하나도 없으면, 오케스트레이터는 헤더나 메트릭을 기록하기 전에 분리형 라우팅을 건너뛰며 요청은 일반 통합형 서빙으로 처리됩니다.

응답 헤더

헤더 설명 예시
X-Continuum-Routing-Path 선택된 라우팅 경로. 항상 기록됨 prefill_then_decode
X-Continuum-Prefill-Backend Prefill 단계를 실행한 백엔드. 요청에 X-Backend가 있을 때만 기록 prefill-worker-1
X-Continuum-Decode-Backend Decode 단계를 실행한 백엔드. 요청에 X-Backend가 있을 때만 기록 decode-worker-2
X-Served-Backend 응답을 반환한 백엔드. 요청에 X-Backend가 있을 때만 기록 decode-worker-2

백엔드 이름 헤더는 라우터 전체의 X-Served-Backend 노출 정책을 따릅니다. 클라이언트가 X-Backend 요청 헤더를 보낸 경우에만 백엔드 이름을 기록하므로, 일반 클라이언트 요청에서는 내부 토폴로지가 노출되지 않습니다. 빈 X-Backend 헤더를 보내면 특정 백엔드를 지정하지 않고도 백엔드 이름 헤더 노출을 요청할 수 있습니다.

이 정책은 버퍼링 응답과 스트리밍 응답 모두에 동일하게 적용됩니다. 반면 X-Continuum-Routing-Path는 백엔드 이름이 아니라 라우팅 경로를 나타내므로 분리형 라우팅이 실행될 때 항상 기록됩니다.

백엔드 역할

설정에서 각 백엔드에 역할을 부여할 수 있습니다.

역할 설명
unified 기본값. 백엔드가 prefill과 decode 단계를 모두 처리하며 모든 라우팅 경로에 참여합니다.
prefill 백엔드가 prefill 연산만 처리합니다. decode 전용 라우팅에서는 제외됩니다.
decode 백엔드가 토큰 생성만 처리합니다. prefill 라우팅에서는 제외됩니다.

분리형 서빙이 활성화되면 백엔드 풀에 등록된 RoleFilterScorer가 역할을 기반으로 후보를 필터링합니다. 이 스코어러는 현재 추론 단계와 맞지 않는 백엔드에 f64::NEG_INFINITY 점수를 부여하며, unified 역할의 백엔드는 모든 단계에서 후보로 유지합니다.

RoleFilterScorer는 풀의 실시간 설정에서 역할을 읽으므로, 동일한 이름과 URL을 유지한 채 백엔드의 role만 변경하는 핫 리로드는 드레인이나 재생성 없이 다음 요청부터 즉시 반영됩니다.

반면 models, weight, request_extensions가 변경되면 백엔드를 드레인하고 재생성하되 엔진 통계 스냅샷은 유지합니다. 백엔드의 URL이 변경될 때만 엔진 통계 스냅샷과 컨트롤 플레인 용량 상태가 함께 초기화됩니다.

KV 전송

Prefill 워커와 decode 워커 간의 KV 전송은 서빙 엔진이 담당합니다. Dynamo 배포에서는 워커들이 NIXL을 통해 KV 블록을 주고받으며, 다른 엔진은 NCCL이나 자체 스토리지 티어를 사용합니다. 라우터는 텐서 데이터를 읽거나 쓰거나 주소를 지정하지 않습니다.

PrefillThenDecode 흐름에서 라우터는 다음 단계만 수행합니다.

  1. 요청을 처리할 최소 부하의 정상 prefill 워커와 최소 부하의 정상 decode 워커를 선택합니다.
  2. 요청에 X-Backend 헤더가 포함된 경우 선택한 두 워커를 X-Continuum-Prefill-Backend와 X-Continuum-Decode-Backend에 기록합니다.
  3. 실제 KV 전송은 서빙 엔진에 위임합니다.

레거시 외부 스토리지 필드

disaggregated_serving.default_external_storage, 백엔드별 external_storage 필드, 그리고 KvReference 타입({endpoint}/{kv_namespace}/{prefix_hash}, 스토리지 경로, 프리픽스 해시, 토큰 수, 텐서 형식)은 설정 하위 호환성을 위해 유지되며 설정 레퍼런스에 정리되어 있습니다. 현재 이 참조를 소비하는 서빙 엔진은 없으며 요청 경로에서도 이 필드를 읽지 않습니다.

설정

최상위 분리형 서빙

disaggregated_serving:
  enabled: false                # 분리형 prefill/decode 서빙 활성화
  prefill_timeout: "30s"        # Prefill 단계의 타임아웃
  kv_transfer_timeout: "10s"    # 예약됨. KV 전송은 엔진이 소유
  fallback_to_unified: true     # 분리형 사용 불가 시 통합형으로 폴백

  # 레거시: 자체 설정을 지정하지 않는 백엔드의 기본 외부 스토리지.
  # 호환성을 위해 허용되며 요청 경로는 읽지 않습니다.
  default_external_storage:
    endpoint: "http://storage-cluster:8080"
    kv_namespace: "inference/kv-cache"
    # credentials: "${STORAGE_CREDENTIALS}"   # 선택 사항, 단일 문자열

백엔드별 역할 할당

각 백엔드에 role을 추가합니다 (external_storage는 레거시 필드로 허용되지만 사용되지 않습니다):

backends:
  # Prefill 워커 - KV 텐서 계산
  - name: prefill-worker-1
    url: "http://vllm-prefill-1:8000"
    role: prefill

  # Decode 워커 - 캐시된 KV 데이터를 사용하여 토큰 생성
  - name: decode-worker-1
    url: "http://vllm-decode-1:8000"
    role: decode
    weight: 2

  - name: decode-worker-2
    url: "http://vllm-decode-2:8000"
    role: decode
    weight: 2

  # 통합형 폴백 백엔드 (선택적)
  - name: unified-fallback
    url: "http://vllm-unified:8000"
    role: unified   # 또는 생략 - unified가 기본값

최소 설정 예시

Fast-decode 경로에서 KV 인덱스를 조회하려면 프리픽스 라우팅을 함께 활성화해야 합니다. 프리픽스 라우팅이 꺼져 있으면 모든 요청이 prefill-then-decode 경로로 처리됩니다.

prefix_routing:
  enabled: true

kv_cache_index:
  enabled: true

disaggregated_serving:
  enabled: true

backends:
  - name: prefill-gpu
    url: "http://vllm-prefill:8000"
    role: prefill
  - name: decode-gpu-1
    url: "http://vllm-decode-1:8000"
    role: decode
  - name: decode-gpu-2
    url: "http://vllm-decode-2:8000"
    role: decode

설정 레퍼런스

disaggregated_serving

필드 타입 기본값 설명
enabled bool false 분리형 서빙 활성화
prefill_timeout string "30s" Prefill 단계 타임아웃 (ms, s, m 접미사 지원)
kv_transfer_timeout string "10s" 예약됨. 워커 간 KV 전송은 엔진이 담당하며 요청 경로는 이 값을 읽지 않습니다
fallback_to_unified bool true 분리형 백엔드 사용 불가 시 통합형 서빙으로 폴백
default_external_storage object null 레거시. 자체 설정을 정의하지 않는 백엔드의 기본 외부 스토리지 설정. 호환성을 위해 허용되며 요청 경로는 읽지 않습니다

external_storage (백엔드별, 레거시)

이전 외부 스토리지 전송 설계와의 설정 호환성을 위해 허용됩니다. 요청 경로는 이 필드들을 읽지 않습니다.

필드 타입 기본값 설명
endpoint string 필수 외부 스토리지 엔드포인트 URL
kv_namespace string "inference/kv-cache" KV 텐서의 네임스페이스 경로
credentials string null 선택적 접근 자격 증명, 단일 문자열 (로그 및 디버그 출력에서 마스킹됨)

role (백엔드별)

값 설명
unified 기본값. 백엔드가 prefill과 decode 라우팅 모두에 참여합니다.
prefill 백엔드가 prefill 단계 요청만 처리합니다.
decode 백엔드가 decode 단계 요청만 처리합니다.

메트릭

전역 /metrics 엔드포인트는 disaggregated_ Prometheus 메트릭 패밀리를 내보냅니다. 버퍼링 요청과 스트리밍 요청 모두에서 disaggregated_requests_total은 분리형 경로가 처리한 요청마다 정확히 한 번 증가하며, 통합형 서빙으로 폴백된 요청에서는 증가하지 않습니다.

메트릭 타입 레이블 증가 시점
disaggregated_requests_total counter routing_path = fast_decode, prefill_then_decode, unified, fallback 요청이 분리형 경로로 응답될 때 (fallback 오류 엔벨로프, 라우팅된 백엔드에서 전달된 스트리밍 클라이언트 오류, fallback_to_unified가 꺼진 상태에서 반환된 스트리밍 실패 포함. 뒤의 두 경우는 시도한 라우팅 경로로 레이블됨)
disaggregated_fallback_total counter 없음 fallback_to_unified가 요청을 통합형 서빙으로 전환할 때
disaggregated_errors_total counter phase = prefill, decode, kv_transfer, orchestration decode: 라우팅된 디스패치가 실패했거나 다른 백엔드가 응답한 경우 (스트리밍 경로에서 408과 429를 제외한 4xx는 클라이언트 오류이므로 제외). orchestration: 사용 가능한 분리형 경로가 없는 경우 (정상 prefill/decode 워커가 없거나 prefill-then-decode 와이어 프로토콜 미지원)

메트릭 카디널리티를 제한하기 위해 위 목록에 없는 레이블 값은 unknown으로 기록됩니다. routing_path="unified"는 Dynamo shim의 집계 폴백에서만 기록되므로(라우팅 경로 참고), shim 풀이 없는 배포에서는 이 레이블이 나타나지 않는 것이 정상입니다.

disaggregated_prefill_duration_seconds{backend}, disaggregated_decode_duration_seconds{backend}, disaggregated_kv_transfer_duration_seconds 히스토그램은 엔진 전용 피커를 위해 등록되어 있으며 범용 오케스트레이터에서는 값을 기록하지 않습니다.

KV Cache Index와의 통합

분리형 서빙은 KV Cache Index (Tier 4)와 연동됩니다. KV 인덱스는 특정 프리픽스 키에 대해 어떤 decode 워커가 웜 GPU 캐시를 보유하고 있는지 추적하므로, 데이터가 이미 decode 워커의 GPU 메모리(GpuHot 티어)에 상주할 때 prefill 단계를 건너뛸 수 있습니다.

KV 캐시 인덱스 설정에서 storage_offloading.enabled가 true이면, 오케스트레이터는 엔진이 오프로드한 StorageWarm 티어 데이터를 보유한 decode 워커로도 라우팅하여 엔진이 필요 시 다시 로드하도록 할 수 있습니다.

부하 분산을 위한 백엔드 선택

각 단계에서 오케스트레이터는 실시간 풀 상태를 바탕으로 최소 부하의 정상 백엔드를 선택합니다.

  • 후보군: 사용자 라우팅이 가능한 백엔드(internal: true 및 enabled: false 제외)를 API 키 허용 목록으로 좁힌 뒤, 현재 단계의 RoleFilterScorer로 다시 필터링합니다.
  • 상태 확인: 백엔드별 상태 확인기의 현재 판정을 따르며 비정상 워커는 제외합니다. 상태 확인이 비활성화된 경우 모든 백엔드를 정상으로 간주합니다.
  • 부하 기준: Admin API 및 CHWBL 부하 상한과 공유하는 풀의 실시간 in-flight 요청 게이지를 사용합니다. 부하가 같으면 설정 파일에 정의된 순서를 따릅니다.
  • 엔진 통계: engine_stats 폴러에 최신 스냅샷이 있으면 엔진 상태를 참조하는 피커를 위해 각 후보에 waiting_requests와 kv_cache_usage를 함께 전달합니다. 기본 최소 부하 선택 자체는 in-flight 요청 수를 기준으로 동작합니다.

이를 통해 prefill 풀과 decode 풀 각각의 GPU 활용도를 독립적으로 균등하게 유지합니다.

폴백 동작

fallback_to_unified: true(기본값)일 때 사용 가능한 분리형 경로가 없거나 라우팅된 디스패치가 실패하면 다음과 같이 통합형 서빙으로 폴백합니다.

  1. 통합형 역할 필터링: 폴백 요청은 호출자가 접근할 수 있는 사용자 라우팅 가능 백엔드 중 역할이 unified인 백엔드로만 제한됩니다. prefill 전용 및 decode 전용 워커는 제외되며, 역할 전용 워커를 지정한 x-backend 요청 헤더도 무시됩니다.
  2. 실패한 백엔드 제외: 방금 디스패치에 실패한 백엔드는 다른 후보가 남아 있는 동안 재시도 후보에서 제외됩니다. 단, Dynamo shim 디스패치는 허용 목록이 단일 워커가 아니라 레플리카 풀을 나타내므로 예외적으로 후보에 남습니다.
  3. 준비 상태 확인: unified 전용 제한은 모델을 서빙하고 현재 선택 가능한(상태 확인, 서킷 브레이커, 백엔드별 속도 제한, Hub 자격 증명 바인딩을 모두 통과한) unified 백엔드가 하나 이상 있을 때만 적용됩니다. 조건을 만족하는 unified 백엔드가 없으면 역할 구분 없이 전체 백엔드 풀을 대상으로 폴백하며, 실패한 백엔드는 유일한 후보인 경우에만 유지됩니다.
  4. 허용 범위 교집합: 이 필터링은 API 키의 allowed_backends, Hub 라우팅 결정, 모델 실험 고정 목록과 교집합으로만 적용되며 권한 범위를 넓히지 않습니다. 모델 폴백 체인(fallback.fallback_chains) 홉은 호출자의 원래 허용 목록으로 실행되므로, 역할 전용 워커만 서빙하는 체인 대상 모델로도 정상 전환됩니다.
  5. 관측성: 폴백된 요청은 응답 헤더와 메트릭에 fallback으로 기록되며 disaggregated_fallback_total을 증가시킵니다.

3번 단계의 준비 상태 확인은 통합형 디스패치 직전에 조회한 스냅샷이며 엔진 어드미션 게이트(routing.engine_load.admission)는 확인하지 않습니다. unified 백엔드가 스냅샷 확인을 통과한 뒤 선택 단계나 백엔드 호출에서 실패하면, 역할 전용 워커로 다시 시도하지 않고 일반 선택 오류 또는 5xx 오류를 반환합니다(폴백 체인이 설정된 경우 다음 체인으로 진행).

fallback_to_unified: false 동작

fallback_to_unified: false인 경우 실패한 요청은 X-Continuum-Routing-Path: fallback 헤더와 함께 503 오류 엔벨로프(disaggregated_unavailable, disaggregated_prefill_decode_unavailable, disaggregated_backend_mismatch, disaggregated_decode_failed)를 반환합니다. 스트리밍 요청에서 선택된 decode 워커가 스트림 시작 전에 실패한 경우에는 원인 추적을 위해 시도했던 라우팅 경로를 X-Continuum-Routing-Path에 그대로 기록합니다.

스트리밍 및 버퍼링 오류 처리

스트리밍 경로에서는 스트림 시작 전에 발생한 전송 오류, 5xx, 429, 408만 decode 실패로 간주하여 통합형 서빙으로 폴백합니다. 429와 408은 지정된 백엔드의 일시적인 용량이나 속도 제한 상태를 나타내므로 다른 백엔드에서 정상 처리할 수 있습니다.

그 외 스트리밍 경로의 4xx 응답(백엔드 검증 오류, 앞단 게이트의 413 또는 403 등)은 요청 자체의 오류이므로 폴백하지 않고 클라이언트에 그대로 전달합니다. 이때 응답에는 시도한 라우팅 경로가 기록되며 decode 오류 메트릭에는 집계되지 않습니다. 반면 버퍼링 경로에서는 4xx를 포함한 모든 디스패치 실패를 decode 실패로 처리합니다.

가드레일 연동

가드레일이 설정된 경우 입력 평가는 분리형 라우팅 이전에 실행되는 chat 입력 게이트에서 한 번만 수행됩니다. 분리형 디스패치가 실패하여 fallback_to_unified로 재시도하더라도 입력 가드레일을 다시 평가하지 않으며 처음 기록된 입력 판정을 그대로 유지합니다. 버퍼링 경로의 출력 가드레일은 폴백 응답을 포함해 클라이언트에 반환되는 모든 최종 응답에 대해 실행됩니다.

배포 권고사항

  • GPU 할당: Prefill 워커는 높은 메모리 대역폭에서 이점을 얻고, decode 워커는 KV 캐시 상주를 위한 대용량 GPU 메모리에서 이점을 얻습니다.
  • KV 전송 크기 산정: 엔진이 요청당 이동시키는 KV 크기는 2 * num_layers * num_heads * head_dim * seq_len * 2 bytes(fp16)로 추정할 수 있습니다. 32레이어, 4096 토큰 컨텍스트의 7B 모델은 요청당 약 256MB가 필요하며, 워커 간 패브릭(RDMA 기반 NIXL, NCCL)이 이 대역폭을 감당해야 합니다.
  • 상태 확인: GPU OOM 및 드라이버 오류를 빠르게 감지해 비정상 워커를 후보군에서 즉시 제외하도록 백엔드 상태 확인을 구성하세요.
  • Decode 풀 크기: Decode 워커 수를 늘리면 토큰 생성 단계의 대기 지연이 줄어듭니다. 일반적으로 decode 대 prefill 비율을 3:1로 시작하는 것을 권장합니다.