넷플릭스는 외부 호스팅 API에만 의존하지 않고, 모델 배포부터 추론·운영·관측까지 전체 LLM 서빙 스택을 기존 프로덕션 환경 안에서 직접 구축했다. 이 글은 엔진 선택, 모델 패키징, API 설계, 무중단 배포, 제약 디코딩을 결정하는 과정과 실제 운영에서 드러난 문제를 설명한다. 최종적으로 넷플릭스는 vLLM과 NVIDIA Triton을 결합하고, gRPC와 OpenAI 호환 HTTP API를 함께 제공하는 통합 서빙 플랫폼을 만들었다.
1. 기존 ML 서빙 환경에 LLM 통합하기
넷플릭스의 대규모 회원 대상 머신러닝 시스템은 JVM 기반의 통합 서빙 시스템을 중심으로 운영된다. 이 시스템은 하위 소비자가 필요로 하는 전체 흐름을 담당한다.
- 요청 라우팅과 A/B 테스트 로직
- 후보 생성
- 피처 조회
- 모델 추론
- 후처리
- 각 단계의 로깅
- 실시간 처리와 캐시된 배치 처리
넷플릭스는 LLM을 기존 머신러닝 모델과 완전히 별도의 시스템에서 운영하지 않고, XGBoost·TensorFlow·PyTorch·LLM을 하나의 서빙 체계 안에서 다루는 방향을 택했다.
작은 CPU 모델은 원격 호출 비용을 줄이기 위해 통합 서빙 시스템 내부에서 직접 실행한다. 반면 큰 모델은 GPU가 필요하므로, 전처리와 후처리는 기존 서빙 시스템이 맡고 실제 추론은 Model Scoring Service(MSS)라는 원격 백엔드로 넘긴다.
MSS 아래에서는 NVIDIA Triton Inference Server가 모델 로딩, 배칭, GPU 스케줄링을 담당한다. 그 위에는 Java 기반 컨트롤 플레인이 배치되어 다음 작업을 관리한다.
- 모델 배포와 버전 관리
- 상태 점검
- 오토스케일링
- 여러 리전에 걸친 롤아웃
- 무중단 업그레이드
모델 개발자는 모델 아티팩트와 배포 설정만 준비하면 되고, 컨트롤 플레인이 GPU 인스턴스를 만들고 Triton을 설정한 뒤 새 버전을 배포한다.

"LLM 모델은 특별 취급되는 예외적인 모델이 되어서는 안 된다."
넷플릭스의 핵심 목표는 LLM도 기존 모델과 동일한 운영 체계 안에서 다루는 것이었다. 기존 소비자는 통합 서빙 시스템을 통한 gRPC 경로를 사용하고, 최신 LLM 애플리케이션은 직접 HTTP 경로를 사용할 수 있다.
2. TensorRT-LLM에서 vLLM으로 전환한 이유
초기 플랫폼은 당시 성능이 뛰어나고 이미 Triton과 통합되어 있던 TensorRT-LLM을 기반으로 구축됐다. 하지만 2025년 여름 무렵 상황이 달라졌다.
오픈소스 추론 엔진들이 특화된 상용·컴파일 기반 스택과의 성능 차이를 크게 줄였고, 넷플릭스의 LLM 워크로드도 다양해졌다. 단순한 자동회귀 생성뿐 아니라 다음과 같은 작업을 처리해야 했다.
- 임베딩 생성
- 랭킹과 검색을 위한 프리필 전용 추론
- 자동회귀 디코딩
- 복잡한 단계별 제약 로직이 필요한 커스텀 모델
넷플릭스는 새로운 워크로드를 기준으로 다시 벤치마크한 뒤, 운영 적합성을 고려해 vLLM을 기본 엔진, 즉 '권장 경로(paved path)'로 선택했다.
vLLM을 선택한 주요 이유는 다음과 같다.
-
복잡한 다단계 컴파일 없이 커스텀 아키텍처를 로드할 수 있음
표준에서 벗어난 모델도 빠르게 실험하고 배포할 수 있었다. -
커스텀 디코딩 로직을 위한 확장 지점 제공
이후 설명할 제약 디코딩을 구현하는 데 필수적이었다. -
높은 디버깅 가능성
과거 TensorRT-LLM의 컴파일 기반 엔진보다 실패 원인과 중간 상태를 살펴보기 쉬웠다. -
높은 친숙도
많은 연구자와 ML 엔지니어가 연구 단계에서 이미 vLLM을 사용하고 있어, 연구 결과를 프로덕션으로 옮기는 비용을 줄일 수 있었다.
3. Triton에서 vLLM 모델을 패키징하는 방식
vLLM을 선택한 다음에는 모델을 Triton에 어떻게 패키징할지가 중요한 문제가 됐다. Triton에서는 크게 두 가지 방식을 사용할 수 있다.
Python 백엔드
Python 백엔드에서는 모델 작성자가 패키징 시점에 입력·출력 텐서의 명세를 직접 정의한다. 이 명세는 모델 아티팩트 안에 고정되며, 외부 프론트엔드의 요청 생성기가 기대하는 형식과 정확히 일치해야 한다.
따라서 프론트엔드가 입력·출력 형식을 변경하면 패키징 코드도 함께 수정해야 한다. 이를 놓치면 모델이 배포된 뒤 런타임에서 요청이 실패한다.
vLLM 백엔드
vLLM 백엔드에서 모델 아티팩트는 모델 가중치와 토크나이저 위치를 가리키는 JSON 설정 파일에 가깝다. Triton의 vLLM 백엔드가 배포 시점에 입력·출력 텐서 명세를 동적으로 생성하므로, 모델 작성자가 이를 직접 정의할 필요가 없다.
이 구조에서는 모델과 프론트엔드가 서로 독립적으로 발전할 수 있다. 따라서 일반적인 Hugging Face 호환 모델에는 vLLM 백엔드가 더 적합한 기본값이었다.
하지만 실제 운영에서는 두 가지 문제가 드러났다.
버전 불일치 문제
Triton의 vLLM 백엔드는 특정 vLLM API에 맞춰 컴파일된다. 두 버전이 어긋나면 백엔드 자체가 로드되지 않을 수 있다. 예를 들어 Triton 25.09가 vllm.engine.metrics를 가져오려 했지만, vLLM 0.11.2에서는 해당 모듈이 제거된 사례가 있었다.
이에 따라 넷플릭스는 다음 원칙을 적용했다.
- 서비스 이미지를 만들 때 호환되는 Triton·vLLM 버전을 함께 고정한다.
- 모델 작성자가 패키징 시점에 vLLM 버전을 임의로 바꾸지 못하게 한다.
커스텀 모델 문제
vLLM 백엔드는 표준 Hugging Face 호환 모델을 가정하고 전체 추론 생명주기를 처리한다. 그러나 다음과 같은 모델은 표준 흐름에서 벗어난다.
- 앙상블 파이프라인
- 커스텀 토크나이징
- 비표준 전처리·후처리
- 특수한 실행 방식이 필요한 모델
이런 경우에는 Python 백엔드를 사용해 execute()를 직접 제어해야 한다. 넷플릭스는 vLLM 백엔드를 기본 경로로 삼되, 일부 모델을 위해 Python 백엔드라는 탈출구를 계속 유지할 계획이다.
4. OpenAI 호환 API와 기존 gRPC의 공존
넷플릭스는 기존 시스템과의 통합을 위해 모든 모델을 동일한 gRPC 호출로 추론할 수 있게 만들었다. 동시에 LLM 생태계에서 사실상 표준이 된 OpenAI 호환 API도 추가 프론트엔드로 제공했다.
OpenAI 호환 API는 다음과 같은 도구와 생태계가 이미 지원하고 있다.
- 추론 엔진
- 오케스트레이션 프레임워크
- 평가 도구
- 다양한 클라이언트 라이브러리
이 덕분에 외부 호스팅 모델을 사용하던 애플리케이션을 넷플릭스 내부에서 파인튜닝한 모델로 전환할 때 코드 변경을 최소화할 수 있다. 품질, 지연 시간, 비용, 데이터 프라이버시 같은 이유로 자체 호스팅을 선택하더라도 실험에서 프로덕션으로 넘어가는 과정이 거의 끊기지 않는다.
"호스팅 모델에서 파인튜닝한 자체 호스팅 모델로 넘어가는 과정은 거의 매끄럽다. 같은 API를 사용하므로 코드 변경이 최소화된다."
구현에는 NVIDIA의 Triton OpenAI 호환 프론트엔드를 활용했다. 이 프론트엔드는 다음 구조로 동작한다.
- OpenAI 호환 프론트엔드 프로세스 안에 Triton 서버를 임베드한다.
TritonLLMEngine이 API 요청 스키마를 Triton 추론 요청으로 변환한다.- FastAPI를 통해 응답을 제공한다.
- KServe HTTP/gRPC 프론트엔드도 함께 활성화한다.
- Java 컨트롤 플레인은 동일한 Triton 인스턴스에 gRPC로 접근한다.
response_format에서 발견된 조용한 실패
Triton의 기본 OpenAI 호환 프론트엔드를 적용하면서 중요한 문제가 발견됐다. API 스키마에는 response_format이 허용되어 있었지만, 이 값이 실제로 vLLM까지 전달되지 않고 조용히 버려졌다.
그 결과 호출자가 JSON 응답을 요청해도 가이드 디코딩 제약이 적용되지 않았고, 잘못된 JSON이 생성될 수 있었다. 더 큰 문제는 플랫폼이 오류를 표시하지 않았다는 점이다.
넷플릭스는 프론트엔드를 코드 저장소에 통합한 뒤 직접 수정해, 요청 시점에 response_format을 vLLM의 가이드 디코딩 파라미터로 변환하도록 만들었다.
이 사례는 API가 형식상 지원된다고 해서 실제 엔진까지 의미가 전달되는 것은 아니라는 점을 보여준다. 특히 조용히 무시되는 옵션은 프로덕션에서 매우 위험할 수 있다.
5. GPU 모델의 무중단 배포 전략
GPU 기반 LLM 서비스는 CPU 서비스보다 시작 시간이 길다. 게다가 모델 버전이 바뀌면서 입력·출력 스키마가 변경될 수 있어, 단순히 새 인스턴스를 띄우는 것 이상의 조정이 필요하다.
넷플릭스는 두 가지 배포 전략을 제공한다.
Red-Black 배포
새 버전을 현재 버전과 나란히 배포한 뒤, 새 인스턴스가 상태 점검을 통과하면 트래픽을 단계적으로 이동한다. 새 버전이 확장되는 속도와 기존 버전이 축소되는 속도를 맞추며, 어느 단계에서든 실패하면 원자적 롤백을 실행한다.
인터페이스가 안정적인 모델에는 Red-Black 방식이 적합하다. 그러나 실제 운영에서 스키마 변경과 관련한 조정 문제가 나타났다.
예를 들어 새 모델이 텐서 차원을 변경하면 상위 소비자도 설정을 바꿔야 한다. 하지만 새 모델이 완전히 준비되기 전에는 소비자 설정을 바꿀 수 없다. 그 결과 전환 과정에서 상위 시스템이 새 배포에 기존 형식의 요청을 보내고, 이 요청이 실패할 수 있다.
Versioned 배포
Versioned 방식은 각 (modelId, modelVersion) 조합마다 독립적인 배포를 유지한다. 새 버전과 기존 버전이 동시에 요청을 처리하므로, 모델 배포와 소비자 설정 변경을 분리할 수 있다.
소비자는 새 버전이 완전히 준비된 뒤 설정을 전환하고, 그동안 기존 버전은 기존 트래픽을 계속 처리한다. 사용되지 않는 오래된 배포는 정리하지만 최신 버전은 항상 보존한다.
다만 전환 기간 동안 두 버전이 동시에 GPU를 사용하므로 일시적으로 GPU 비용이 증가한다.
넷플릭스의 권장 방식은 다음과 같다.
"텐서 형태처럼 변할 수 있는 설정은 추론 모델 내부에 직접 포함해 버전과 무관하게 만드는 것이 좋다. 그러면 더 저렴한 Red-Black 경로를 사용할 수 있다."
즉, 가능한 한 인터페이스를 깨지 않도록 모델을 설계하고, 불가피한 호환성 변경이 있을 때만 Versioned 배포를 사용한다.
6. 부팅 과정과 통합 메트릭
설계 단계에서는 쉽게 지나칠 수 있지만, 실제 프로덕션에서 중요한 운영 문제가 두 가지 더 발견됐다.
모델 캐싱과 부팅 순서
vLLM과 Triton 인스턴스를 시작하려면 여러 단계가 완료되어야 gRPC 포트를 열 수 있다. 특히 대형 LLM을 시작할 때마다 S3나 Hugging Face에서 직접 내려받으면 시작 시간이 너무 길어져 스케줄러의 허용 범위를 넘을 수 있다.
넷플릭스는 모델이 배포 대상으로 공지되는 시점에 모델을 Amazon FSx에 미리 저장한다. 그러면 실제 인스턴스가 시작될 때 객체 스토리지보다 빠른 파일 시스템에서 모델을 읽을 수 있어 웜 스타트가 가능하다.
OpenAI 호환 API가 필요한 배포에서는 Triton을 OpenAI 프론트엔드 프로세스 안에 임베드하고, 그렇지 않은 경우에는 Triton을 독립 프로세스로 실행한다. 이 방식은 배포 패키징 시점에 설정한다.
그 밖의 부팅 단계는 다음과 같다.
- 모델 패키지 압축 해제
- Python
entry_points를 통한 커스텀 vLLM 플러그인 설치 - Prometheus 멀티프로세스 디렉터리 정리
- 엔진이 준비될 때까지 gRPC 포트 개방 지연
Triton과 vLLM 메트릭 통합
vLLM과 Triton은 서로 다른 방식으로 메트릭을 노출한다.
- vLLM은
PROMETHEUS_MULTIPROC_DIR에.db파일 형태로 메트릭을 기록한다. - Triton은 자체 Prometheus 엔드포인트를 통해 서버 메트릭을 제공한다.
Triton의 기본 브리지는 vLLM의 40개가 넘는 메트릭 중 9개만 노출했다. 따라서 다음과 같은 중요한 지표를 확인할 수 없었다.
- 토큰 처리량
- KV 캐시 사용률
- 프리픽스 캐시 적중률
넷플릭스는 두 메트릭을 합쳐 제공하는 가벼운 HTTP 프록시를 만들었다. 이 프록시는 Triton에서 HTTP로 메트릭을 가져오고, Prometheus의 MultiProcessCollector를 사용해 디스크의 vLLM 메트릭을 읽은 뒤 하나의 /metrics 엔드포인트로 반환한다.
덕분에 기존 대시보드와 알림 시스템을 수정하지 않고도 LLM 관련 지표를 관측할 수 있게 됐다.
7. 대규모 제약 디코딩의 도전
넷플릭스의 일부 프로덕션 워크로드는 모델이 생성할 수 있는 토큰을 세밀하게 제어해야 한다. 일반적으로는 모델이 잘못된 결과를 생성한 뒤 후처리하거나 재시도할 수 있지만, 이 방식은 잘못된 생성에도 비용을 지불해야 한다.
넷플릭스는 제약 조건을 추론 이후에 적용하지 않고 디코딩 루프 내부에 넣는 방식을 선택했다. 모델이 처음부터 조건에 맞는 결과만 생성하도록 만드는 것이다.
각 제약은 상태 머신으로 표현된다. 상태 머신은 지금까지 생성된 토큰의 기록을 바탕으로 다음 단계에서 허용되는 토큰을 계산하고, 매 단계마다 토큰 선택 가능 여부를 나타내는 마스크를 만든다. 요청마다 서로 다른 규칙이 적용될 수 있으므로, 각 요청은 자신만의 제약 프로세서를 가진다.
이 기능은 처음에는 vLLM V0에서 구현됐다. 당시 V1은 필요한 기능이 충분히 성숙하지 않았기 때문이다. 이후 V1이 발전한 뒤 2025년 4분기에 V1으로 마이그레이션했다.
V0에서 발생한 CPU 병목
초기 Python 구현은 기능적으로는 동작했지만, 요청 수가 늘어나면 확장되지 않았다.
V0에서는 GPU가 배치 전체에 대한 로짓을 계산한 뒤 CPU로 복사한다. 이후 각 요청의 제약 로직을 순차적으로 실행한다. Python의 GIL 때문에 요청별 처리를 효과적으로 병렬화하기도 어려웠다.
따라서 배치 크기가 커질수록 CPU에서 수행하는 로짓 처리 시간이 선형으로 증가했고, 결국 모델 추론 자체가 아니라 CPU 제약 처리 때문에 전체 지연 시간이 늘어났다.
단일 요청 벤치마크에서는 발견되지 않았지만, 실제 동시성 환경에서는 꼬리 지연시간(tail latency)을 악화시키는 심각한 문제가 됐다.

V1의 배치 단위 처리
vLLM V1은 로짓 처리를 요청별이 아니라 배치 단위로 옮겼다. 넷플릭스는 이에 맞춰 커스텀 프로세서를 다시 작성했다.
- 여러 요청의 데이터를 배치 단위 구조로 처리
- 다수 요청에 대한 마스크를 한꺼번에 계산
- 핵심 연산을 C++로 재구현
- 멀티스레딩을 통해 Python GIL의 영향을 회피
V1에서는 동적으로 변하는 배치의 구성원을 추적하기 위해 update_state(batch_update)를 명시적으로 관리해야 했다. V0보다 구현은 복잡해졌지만, 배치에 요청이 추가되거나 빠지는 상황에서도 상태를 정확히 유지하기 위해 필요한 변화였다.
그 결과 배치 크기가 커져도 로짓 처리 시간이 크게 증가하지 않고 안정적으로 유지됐다.

운영 과정에서 추가로 해결한 문제
성능 병목을 해결한 뒤에도 상태를 유지하는 제약 로직에서 예상하지 못한 문제가 발생했다.
부분 프리필
V1은 프리필을 여러 조각으로 나누어 처리한다. 따라서 한 요청의 프리필이 여러 엔진 단계에 걸쳐 진행될 수 있다.
그러나 BatchUpdate만으로는 해당 요청이 완전히 프리필됐는지, 일부만 프리필됐는지 충분히 구분하기 어려웠다. 넷플릭스는 이를 위해 내부적으로 프리필 상태를 별도로 추적했다.
선점과 KV 캐시 재생성
메모리 압박이 발생하면 vLLM은 아직 끝나지 않은 요청의 KV 캐시를 제거하고 나중에 다시 예약할 수 있다. 이때 요청이 이전과 다른 프롬프트와 출력 토큰 목록을 가지고 재개될 수 있다.
이는 상태 머신이 출력 토큰 목록이 항상 단조롭게 늘어난다고 가정할 때 문제를 일으킨다. 넷플릭스는 디코딩 단계 사이에서 토큰 기록이 줄어든 것을 감지하면 상태 머신을 초기화하고, 새로운 프롬프트를 기준으로 다시 시작하도록 처리했다.
8. 구축 결과와 향후 계획
넷플릭스가 만든 플랫폼의 목표는 광범위한 프로덕션 ML 요구사항을 동시에 충족하는 것이었다.
- 낮은 지연 시간
- 커스텀 로직 지원
- 기존 인프라와의 통합
- 실험에서 프로덕션으로의 빠른 전환
- 대규모 GPU 운영
- 안정적인 버전 배포와 관측
최종 시스템은 vLLM과 Triton을 기반으로 하며, 기존 모델과 LLM을 일관된 방식으로 서빙한다. gRPC를 통해 기존 Java 서빙 시스템과 통합하고, OpenAI 호환 HTTP API를 통해 최신 LLM 애플리케이션과 연결한다.
넷플릭스는 구축 과정에서 성능 자체보다도 다음과 같은 세부 사항이 운영 안정성을 좌우한다는 점을 확인했다.
- Triton과 vLLM의 버전 고정
- 모델 패키징 방식의 결합도
- API 필드가 실제 엔진까지 전달되는지 검증
- 스키마 변경과 소비자 설정의 조정
- GPU 부팅 시간과 모델 캐싱
- 여러 엔진의 메트릭 통합
- 동시성 환경에서의 CPU 제약 처리
앞으로의 주요 투자 방향은 다음과 같다.
- 품질을 유지하면서 프롬프트 길이를 줄이는 시스템 프롬프트 압축
- vLLM V1의 비동기 스케줄링
- CPU 코드 대신 융합 GPU 커널로 실행하는 벡터화된 로짓 프로세서
- 메모리 사용량을 줄이고 처리량을 높이는 저정밀도 모델 버전
넷플릭스는 Triton, vLLM, PyTorch 등 오픈소스 프로젝트를 적극 활용했으며, 앞으로도 오픈소스 커뮤니티와 협력해 플랫폼을 발전시킬 계획이다.
마치며
넷플릭스의 사례는 LLM 서빙에서 단순히 가장 빠른 엔진을 고르는 것보다, 기존 ML 플랫폼과의 통합성·디버깅 가능성·API 호환성·배포 안정성·운영 관측성을 함께 고려해야 한다는 점을 보여준다. 특히 실제 프로덕션에서는 버전 불일치, 조용히 무시되는 API 옵션, 스키마 변경, 배치 처리 방식처럼 설계 단계에서 놓치기 쉬운 문제가 성능과 안정성을 좌우했다.
결국 넷플릭스가 얻은 가장 큰 교훈은 LLM을 특별한 예외 시스템으로 만들지 않고, 기존 모델 서빙 체계 안에 자연스럽게 편입하는 것이 장기적인 운영 효율과 개발자 경험을 높인다는 점이다.
