# Netflix의 사내 LLM 서빙 플랫폼

> Clean Markdown view of GeekNews topic #31856. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=31856](https://news.hada.io/topic?id=31856)
- GeekNews Markdown: [https://news.hada.io/topic/31856.md](https://news.hada.io/topic/31856.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-07-27T10:07:02+09:00
- Updated: 2026-07-27T10:07:02+09:00
- Original source: [netflixtechblog.com](https://netflixtechblog.com/in-house-llm-serving-at-netflix-a5a8e799ea2c)
- Points: 7
- Comments: 0

## Topic Body

- 넷플릭스는 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 호환 프런트엔드](https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/client_guide/openai_readme.html)를 재사용함  
  - 내장 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 라이브러리를 계속 활용하며 관련 커뮤니티와 협력할 계획임

## Comments



_No public comments on this page._
