Netflix의 사내 LLM 서빙 플랫폼
요약
Netflix가 기존 ML 인프라에 vLLM과 Triton을 통합하여 LLM을 서빙하는 플랫폼 구축 사례를 소개합니다. 모델 규모에 따른 실행 전략과 vLLM을 기본 엔진으로 선택한 이유, 그리고 안정적인 배포를 위한 운영 노하우를 다룹니다.
핵심 포인트
- vLLM과 Triton을 통합하여 사용자 정의 모델 지원 및 디버깅 용이성 확보
- Red-Black 및 Versioned 전략을 통한 안정적인 모델 배포 및 업그레이드
- 모델 규모에 따라 CPU 프로세스 내 실행 또는 GPU 원격 추론(MSS)으로 분리
- vLLM backend를 활용해 모델 아티팩트와 프런트엔드 간 결합도 완화
- 넷플릭스는 LLM을 별도 사일로로 분리하지 않고 기존 ML 인프라에서 함께 운영하며,
vLLM과 Triton을 통합 서빙 체계에 연결함 - 기본 엔진으로 선택한
vLLM은 사용자 정의 모델 지원, 디버깅 용이성, 확장 훅, 연구 환경과의 친숙성을 갖췄으며, Triton의 vLLM backend로 모델과 프런트엔드의 결합도 줄임 - 기존 gRPC와 OpenAI 호환 API를 함께 제공하지만
response_format
누락, Triton·vLLM 버전 불일치, 비표준 모델 처리처럼 프로덕션에서 드러난 간극을 직접 보완해야 했음
- 안정적인 배포에는 비용이 낮은
Red-Black 전략을 우선 적용하고, 호환되지 않는 I/O 변경이 불가피할 때만 여러 버전을 동시에 유지하는 Versioned 전략을 사용함 - 요청별 제약을 디코딩 루프에서 강제하는 로짓 프로세서를 vLLM V1의 배치 처리와 멀티스레드 C++로 재구현했으며, 앞으로 GPU 융합 커널·비동기 스케줄링·저정밀 모델로 확장할 계획임
기존 ML 인프라에 통합한 서빙 구조
-
Netflix의 JVM 기반 통합 서빙 시스템은
라우팅과 A/B 테스트, 후보 생성, 특징 조회, 추론, 후처리, 단계별 로깅을 처리하며 실시간 및 캐시된 배치 경로를 모두 지원함 -
호출자는 기존 서빙 시스템의
gRPC 경로 또는 새로운 LLM 애플리케이션용 직접 HTTP 경로로 추론에 접근함 -
실행 위치는 모델 규모에 따라 달라짐
-
작은 CPU 모델은 원격 호출 비용을 피하도록 프로세스 안에서 실행함
-
큰 GPU 모델은 전·후처리를 로컬에서 수행하고 추론을 원격
Model Scoring Service(MSS) 에 위임함 -
MSS는 XGBoost, TensorFlow, PyTorch, LLM을 하나의 인터페이스로 제공하며, 하부의 NVIDIA Triton Inference Server가 모델 로딩, 배치 처리, GPU 스케줄링을 담당함
-
Triton 위의 Java 제어 영역은 배포, 버전 관리, 상태 확인, 자동 확장, 다중 리전 롤아웃을 처리함
-
모델 개발자가 아티팩트와 배포 설정을 패키징하면 GPU 인스턴스를 프로비저닝하고 Triton을 구성함
-
업그레이드는
무중단 방식으로 조정함
기본 추론 엔진으로 vLLM 선택
-
초기 플랫폼은 당시 성능이 높고 MSS의 Triton과 이미 통합돼 있던
TensorRT-LLM을 사용함 -
2025년 여름에는 오픈소스 엔진이 특화 스택과의 성능 차이를 대부분 좁혔고, 워크로드도 다음 범위로 넓어짐
-
임베딩 생성
-
순위화와 검색을 위한 prefill-only 추론
-
자기회귀 디코딩
-
단계별 제약 로직이 복잡한 사용자 정의 모델
-
이 워크로드를 다시 벤치마크한 뒤 운영 적합성을 기준으로
vLLM을 기본 경로 엔진으로 선정함 -
다단계 컴파일 없이 사용자 정의 모델 아키텍처를 로드할 수 있어 비표준 모델의 반복 개발이 빨라짐
-
사용자 정의 디코딩 로직을 위한 확장 훅을 제공함
-
컴파일 엔진이던 초기 TensorRT-LLM보다 장애와 중간 상태를 조사하기 쉬움
-
연구 단계에서 이미 vLLM에 익숙한 ML 실무자가 많아 프로덕션 이관 비용이 줄어듦
Triton과 vLLM의 패키징 방식
- Triton에는
Python backend와 vLLM backend라는 두 가지 패키징 경로가 있으며, 프런트엔드 업그레이드와 모델 아티팩트가 얼마나 강하게 결합되는지가 핵심 차이임 - Python backend에서는 개발자가 패키징 시점에 입출력 텐서 명세를 정의함
- 명세가 아티팩트에 고정되고 외부 프런트엔드의 요청 빌더와 일치해야 함
- 프런트엔드 업그레이드로 I/O가 바뀌면 패키징 코드도 함께 수정해야 하며, 그렇지 않으면 런타임 요청이 실패함
vLLM backend의 아티팩트는 모델 가중치와 토크나이저를 가리키는 JSON 설정으로 구성됨
-
배포 시 Triton backend가 I/O 텐서 명세를 동적으로 생성함
-
모델 개발자가 텐서 명세를 정의하지 않아도 되며 모델과 프런트엔드를 독립적으로 변경할 수 있음
-
기본 선택은 vLLM backend지만 프로덕션에서 두 가지 제약이 나타남
버전 불일치: Triton backend는 특정 vLLM API를 기준으로 컴파일되므로 두 버전이 어긋나면 backend 전체를 로드할 수 없음 -
예를 들어 Triton 25.09는
vllm.engine.metrics
를 가져오지만, 해당 모듈은 vLLM 0.11.2에서 제거됨
- 서비스 이미지 생성 시 호환 버전을 고정하고, 모델 개발자가 패키징 단계에서 vLLM 버전을 덮어쓰지 못하게 해야 함
사용자 정의 실행 로직: vLLM backend는 표준 HuggingFace 호환 모델과 전체 추론 수명주기를 전제로 함
- 사용자 정의 전·후처리, 앙상블 파이프라인, 별도 토큰화 등 비표준 실행에는
execute()
를 제어할 수 있는 Python backend가 필요함
- 일부 모델에는 이 우회 경로가 계속 필요함
gRPC와 OpenAI 호환 HTTP API
- XGBoost 앙상블부터 대규모 LLM까지 동일한
gRPC 호출로 평가해 기존 클라이언트 라이브러리, 상태 확인, 배포 파이프라인을 재사용함 - LLM 생태계의 추론 엔진, 오케스트레이션 프레임워크, 평가 도구, 클라이언트 라이브러리가 OpenAI 호환 인터페이스를 사용하므로 이를 gRPC와 나란히 제공함
- 같은 API를 유지하므로 품질, 지연시간, 비용, 데이터 프라이버시를 이유로 호스팅 모델에서 미세 조정한 자체 호스팅 모델로 옮길 때 코드 변경이 작음
- 구현은 NVIDIA의 Triton OpenAI 호환 프런트엔드를 재사용함
- 내장 Triton 서버를 시작함
TritonLLMEngine
이 요청 스키마를 Triton 추론 요청으로 변환함
-
FastAPI를 통해 응답을 제공함
-
KServe HTTP/gRPC 프런트엔드도 함께 활성화해 Java 제어 영역이 같은 Triton 인스턴스에 gRPC로 접근할 수 있음
-
프런트엔드가 스키마에서 허용한
response_format
을 vLLM에 전달하기 전에 조용히 버리는 문제가 발견됨
- JSON 출력을 요청해도 유도 디코딩 제약 없이 실행돼 잘못된 JSON을 반환할 수 있었고, 플랫폼 오류도 노출되지 않았음
- 프런트엔드를 Git subtree로 가져와
response_format
요청을 vLLM의 유도 디코딩 매개변수로 변환하도록 패치함
무중단 모델 배포 전략
-
GPU 배포는 CPU 서비스보다 시작 시간이 길고 모델 버전 사이에서 I/O 스키마까지 바뀔 수 있어, 요청을 중단하지 않는 롤아웃에 추가 조정이 필요함
Red-Black 배포는 기존 버전 옆에 새 버전을 올린 뒤 상태 확인을 통과하면 단계적으로 트래픽을 전환함 -
새 버전과 기존 버전의 확장·축소를 같은 비율로 진행함
-
어느 단계에서든 실패하면 원자적으로 롤백함
-
모델 인터페이스가 안정적일 때 적합함
-
새 텐서 차원처럼 I/O 스키마가 바뀌면 Red-Black에
조정 간극이 생김 -
새 모델이 완전히 활성화되기 전에는 상위 소비자가 설정을 바꿀 수 없음
-
전환 구간에 기존 형식의 요청이 새 배포로 전달되면 실패함
Versioned 배포는 (modelId, modelVersion)
쌍마다 독립적인 배포를 유지해 이 문제를 해결함
-
여러 버전을 동시에 서비스하므로 모델 배포와 소비자 업데이트가 분리됨
-
소비자는 새 버전이 완전히 준비된 뒤 설정을 바꾸고, 기존 버전은 레거시 트래픽을 계속 처리함
-
비활성 상태가 된 이전 배포는 정리하지만 최신 버전은 항상 보존함
-
전환 중 버전이 겹치는 기간에는 GPU 비용이 일시적으로 증가함
-
텐서 형태처럼 변할 수 있는 설정은 추론 모델 안에 직접 넣어
버전 독립적으로 만들고, 비용이 낮은 Red-Black을 사용하는 방식을 권장함 -
Versioned는 호환되지 않는 인터페이스 변경을 피할 수 없는 경우에만 사용함
시작 절차와 모델 캐시
-
vLLM-on-Triton 인스턴스는 여러 시작 단계를 마쳐야 gRPC 포트를 열 수 있음
-
대형 LLM을 시작할 때 S3나 Hugging Face에서 직접 내려받으면 콜드 스타트가 스케줄러 허용 범위를 넘을 만큼 길어짐
-
모델 발표 시점에 모델을
Amazon FSx에 미리 구체화함 -
이후 시작 과정에서는 객체 저장소 대신 고성능 파일 시스템을 사용함
-
OpenAI 호환 API가 필요한 배포에서는 Triton을 해당 프런트엔드 프로세스 안의
내장 서버로 실행함 -
그렇지 않은 배포는 Triton을 독립 실행함
-
실행 방식은 패키징 시 배포별로 설정함
-
나머지 시작 절차에는 모델 패키지 압축 해제, Python
entry_points
를 통한 사용자 정의 vLLM 플러그인 설치, Prometheus 멀티프로세스 디렉터리 정리, 엔진 준비 완료까지 gRPC 포트 차단이 포함됨
Triton과 vLLM 메트릭 통합
- vLLM은
PROMETHEUS_MULTIPROC_DIR
에 .db
파일로 메트릭을 기록하고, Triton은 별도의 Prometheus 엔드포인트로 서버 메트릭을 제공함
-
두 시스템은 서로의 메트릭을 인식하지 못하며, Triton 내장 브리지는 vLLM의
40개 이상 메트릭 중 9개만 노출함 -
토큰 처리량
-
KV 캐시 사용률
-
접두사 캐시 적중률 같은 핵심 지표가 빠짐
-
경량 HTTP 프록시가 Triton 메트릭을 HTTP로 가져오고, Prometheus
MultiProcessCollector
로 디스크의 vLLM 메트릭을 읽어 하나의 /metrics
응답으로 합침
- 기존 대시보드와 경보는 수정 없이 그대로 사용할 수 있음
디코딩 과정에서 출력 제약 강제
- 일부 프로덕션 워크로드는 토큰 생성에 세밀한 제어가 필요해, 추론 후 잘못된 결과를 재시도하거나 복구하는 대신
디코딩 루프 안에서 제약을 적용함 - 각 제약은 생성된 토큰 이력에 따라 상태가 변하고 단계마다 허용 토큰 마스크를 내보내는 상태 머신으로 모델링함
- vLLM의 사용자 정의
로짓 프로세서 인터페이스를 사용하며, 요청마다 규칙이 다르므로 별도로 설정된 프로세서를 할당함 - 초기에는 기능 격차 때문에 vLLM V0를 사용했고, V1이 성숙한 2025년 4분기에 이전함
vLLM V0에서 발생한 확장 병목
-
최초의 순수 Python 구현은 기능적으로 동작했지만 동시 요청이 늘면 확장되지 않았음
-
vLLM V0의 사용자 정의 로짓 프로세서는 요청별로 실행됨
-
GPU가 전체 배치의 로짓을 생성함
-
CPU가 이를 복사하고 전송이 끝날 때까지 대기함
-
각 요청의 제약 로직을 순차 실행함
-
Python의
GIL 때문에 요청별 작업을 병렬화할 수 없어 로짓 처리 CPU 시간이 배치 크기에 비례해 증가하고 꼬리 지연시간이 커짐 -
GPU의 모델 순전파가 효율적으로 배치 처리돼도 전체 지연시간은 CPU에 묶임
-
이 병목은 단일 요청 벤치마크에서는 보이지 않고 실제 수준의 동시성에서만 나타남
vLLM V1의 배치 단위 처리
- vLLM V1은 로짓 처리를 요청별 방식에서
배치 단위로 옮김 - 사용자 정의 프로세서를 배치 자료구조 기반으로 다시 작성해 여러 요청의 마스크를 함께 계산함
- 성능 핵심 경로는 GIL을 피하도록 멀티스레드
C++ 로 재구현했으며, 배치 크기가 커져도 로짓 처리 시간이 일정하게 유지됨 - V1 API에서는
update_state(batch_update)
를 통해 배치 구성원 변화를 명시적으로 추적해야 함
- V0의 요청별 인터페이스보다 복잡함
- 동적으로 바뀌는 배치에서 요청별 상태를 정확히 유지하는 데 필요함
상태 기반 제약 처리의 운영 보강
- 성능 병목을 해결한 뒤에도 상태를 가진 디코딩 로직에서 두 가지 문제가 나타남
부분 prefill
- V1은 청크 단위 prefilling을 수행하므로 한 요청의 prefill이 여러 엔진 단계에 걸칠 수 있음
BatchUpdate
만으로는 완전한 prefill과 부분 prefill을 구분할 수 없어 내부 추적을 추가함
선점(preemption)
- 메모리가 부족하면 vLLM이 일부 완료된 요청의 KV 캐시를 제거하고, 이후 다른 프롬프트 및 출력 토큰 목록으로 다시 예약할 수 있음
- 이는 출력 토큰 목록이 계속 증가한다는 상태 머신의 전제를 깨뜨림
- 디코딩 단계 사이에서 토큰 이력이 짧아졌는지 감지하고, 상태 머신을 초기화한 뒤 새 프롬프트로 다시 구성함
다음 투자 영역
-
현재 플랫폼은 낮은 지연시간, 깊은 사용자 정의, 기존 인프라 통합을 목표로 하며
vLLM과 Triton, 일관된 API를 통해 실험에서 프로덕션까지 이어지는 경로를 제공함 -
버전 고정, 조용히 누락되는 API 필드, 패키징 선택의 절충을 보완해 플랫폼 안정성과 개발자 경험을 개선함
-
다음 네 가지 개선을 계획함
-
품질을 희생하지 않고 프롬프트 길이를 줄이는
시스템 프롬프트 압축 -
vLLM V1의 비동기 스케줄링
-
CPU 코드 대신 GPU 융합 커널로 실행하는 벡터화 로짓 프로세서
-
메모리 사용량을 줄이고 처리량을 높이는 저정밀 모델 변형
-
Triton, vLLM, PyTorch 등 오픈소스 ML 라이브러리를 계속 활용하며 관련 커뮤니티와 협력할 계획임
댓글과 토론
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기