들어가며: 넷플릭스는 왜 자체 LLM 서빙을 선택했나

대부분의 조직은 LLM을 호스팅 API로 소비한다. 하지만 넷플릭스는 모델 배포부터 추론까지 전 과정을 자체 프로덕션 환경에서 직접 운영한다. 별도의 ML 사일로가 아니라 기존 인프라에 통합한 것이다. 이 글은 넷플릭스 기술 블로그에 공개된 내용을 바탕으로, 실제 프로덕션 부하에서 드러난 설계 결정과 트레이드오프를 분석한다.

넷플릭스의 LLM 서빙 시스템은 JVM 기반의 통합 서빙 시스템을 중심으로 라우팅, A/B 테스트, 후보 생성, 피처 조회, 추론, 후처리, 로깅을 모두 처리한다. gRPC 경로와 HTTP 경로를 모두 지원하며, 작은 CPU 모델은 인프로세스로, 큰 GPU 모델은 원격 서비스(Model Scoring Service)로 위임한다.

아키텍처 개요: vLLM과 Triton의 만남

넷플릭스는 원래 TensorRT-LLM을 사용했지만, 2025년 여름 기준으로 오픈소스 엔진의 성능 격차가 줄어들고 워크로드가 다양해지면서 vLLM으로 전환했다. vLLM을 선택한 이유는 다음과 같다.

  • 커스텀 모델 아키텍처를 다단계 컴파일 없이 로드 가능
  • 커스텀 디코딩 로직 확장 훅 제공
  • 디버깅 용이성
  • 연구 단계에서 이미 많은 실무자가 사용 중

vLLM을 Triton에 통합하는 방식도 중요한 결정이었다. Triton은 두 가지 백엔드를 제공한다.

  • Python 백엔드: 패키징 시 I/O 텐서 스펙을 정의해야 하며, 프론트엔드 업그레이드 시 패키징 코드도 함께 변경해야 한다.
  • vLLM 백엔드: JSON 설정만으로 모델 가중치와 토크나이저를 지정하면, I/O 텐서 스펙을 동적으로 생성한다. 모델과 프론트엔드가 독립적으로 진화할 수 있다.

넷플릭스는 vLLM 백엔드를 기본으로 선택했다. 하지만 프로덕션에서 두 가지 문제가 발생했다.

문제 1: Triton/vLLM 버전 불일치

Triton의 vLLM 백엔드는 특정 vLLM API에 맞춰 컴파일된다. 버전이 어긋나면(예: Triton 25.09가 vLLM 0.11.2에서 제거된 모듈을 import) 백엔드 자체가 로드되지 않는다. 플랫폼은 서비스 이미지에 호환 버전을 고정하고, 모델 작성자가 패키징 시 vLLM 버전을 덮어쓰지 못하게 해야 한다.

문제 2: 커스텀 모델 로직

vLLM 백엔드는 표준 HuggingFace 호환 모델을 기대한다. 커스텀 전처리/후처리나 비표준 실행이 필요한 모델은 Python 백엔드를 사용해야 한다. 이 탈출구는 일부 모델에 계속 필요할 것이다.

API 설계: OpenAI 호환 인터페이스의 채택

넷플릭스는 모든 모델을 동일한 gRPC 호출로 스코어링한다. 이는 기존 클라이언트 라이브러리, 헬스 체크, 배포 파이프라인을 재사용하기 위함이다. 동시에 LLM 생태계의 사실상 표준이 된 OpenAI 호환 API를 추가 프론트엔드로 노출한다.

이 결정의 이점은 실험에서 프로덕션으로의 전환이 매끄럽다는 것이다. 호스팅 모델에서 파인튜닝된 자체 모델로 전환할 때 API가 동일하므로 코드 변경이 거의 없다.

구현은 NVIDIA Triton의 OpenAI 호환 프론트엔드를 재사용한다. 하지만 한 가지 갭이 있었다. response_format 파라미터가 조용히 무시되어 JSON 출력을 요청해도 제약 디코딩이 적용되지 않았다. 넷플릭스는 이 프론트엔드를 패치하여 response_format을 vLLM의 guided decoding 파라미터로 변환했다.

배포 전략: Red-Black vs Versioned

GPU 배포는 CPU 서비스보다 부팅 시간이 길고, 모델 버전 간 I/O 스키마가 바뀔 수 있다. 넷플릭스는 두 가지 전략을 제공한다.

  • Red-Black: 새 버전과 구 버전을 나란히 배포하고, 헬스 체크 후 트래픽을 단계적으로 전환한다. 인터페이스가 안정적일 때 적합하다. 하지만 I/O 스키마가 바뀌면 업스트림 컨슈머가 새 모델이 완전히 라이브될 때까지 설정을 업데이트할 수 없어 마이그레이션 기간 동안 실패가 발생한다.
  • Versioned: (modelId, modelVersion) 쌍마다 독립적인 배포를 유지한다. 여러 버전이 동시에 서빙되며, 컨슈머는 새 버전이 준비될 때까지 기다렸다가 설정을 전환할 수 있다. 트레이드오프는 전환 기간 동안 GPU 비용이 일시적으로 증가한다는 것이다.

넷플릭스는 가변 설정(예: 텐서 셰이프)을 추론 모델에 직접 포함시켜 버전에 독립적으로 만드는 것을 권장한다. 그래야 더 저렴한 Red-Black 경로를 사용할 수 있다. Versioned는 인터페이스 변경이 불가피한 드문 경우에만 사용한다.

운영 노하우: 부팅 시퀀스와 메트릭 통합

부팅 시퀀스

vLLM-on-Triton 인스턴스를 부팅할 때 중요한 두 가지가 있다.

  • 모델 캐싱: LLM을 시작 시 S3나 Hugging Face에서 다운로드하면 콜드 스타트 지연이 길어진다. 넷플릭스는 모델 발표 시 Amazon FSx에 모델을 구체화하여 웜 스타트가 고성능 파일 시스템을 사용하도록 한다.
  • 임베디드 vs 스탠드얼론 Triton: OpenAI 호환 API가 필요하면 임베디드 서버로, 아니면 스탠드얼론으로 실행한다. 이는 패키징 시 배포별로 설정한다.

통합 메트릭 엔드포인트

vLLM은 PROMETHEUS_MULTIPROC_DIR에 .db 파일로 메트릭을 쓰고, Triton은 자체 Prometheus 엔드포인트로 서버 메트릭을 보고한다. 하지만 Triton의 브리지는 40개 이상의 vLLM 메트릭 중 9개만 노출한다. 토큰 처리량, KV 캐시 활용률, 프리픽스 캐시 히트율 같은 중요한 메트릭이 빠진다.

넷플릭스는 경량 HTTP 프록시를 추가하여 두 메트릭을 단일 /metrics 엔드포인트로 병합했다. 기존 대시보드와 알림은 수정 없이 동작한다.

딥다이브: 제약 디코딩의 확장

일부 넷플릭스 프로덕션 워크로드는 토큰 생성에 세밀한 제어가 필요하다. 추론 후 비즈니스 로직을 적용하는 대신, 디코드 루프 내부에 제약을 넣어 모델이 처음부터 규칙을 준수하는 출력을 생성하도록 한다.

vLLM의 커스텀 logits processor 인터페이스를 사용하며, 각 제약을 상태 머신으로 모델링한다. 요청마다 다른 규칙이 적용될 수 있으므로 각 요청에 자체 프로세서가 할당된다.

첫 구현의 확장성 한계

vLLM V0에서 커스텀 logits processor는 요청별로 실행된다. GPU가 배치 전체의 logits를 생성하면 CPU가 복사하고, GIL 때문에 순차적으로 실행된다. 배치 크기가 커질수록 CPU 시간이 선형적으로 증가하여 꼬리 지연 시간이 발생한다. 이는 단일 요청 벤치마크에서는 보이지 않는 병목이다.

vLLM V1에서의 배치 레벨 설계

vLLM V1은 logits 처리를 배치 레벨로 이동했다. 넷플릭스는 배치 레벨 데이터 구조에서 마스크를 계산하고, 핫 패스를 C++로 재구현하여 GIL을 우회했다. update_state(batch_update)를 통해 동적으로 변화하는 배치에서 상태를 유지한다.

운영 강화

성능 문제가 해결된 후 두 가지 예상치 못한 문제가 발생했다.

  • 부분 프리필(Partial prefill): V1은 청크 프리필을 수행하므로 요청이 여러 엔진 스텝에 걸쳐 프리필될 수 있다. batch update의 세분성으로는 완전/부분 프리필을 구분할 수 없어 내부 추적을 추가했다.
  • 선점(Preemption): 메모리 압박 시 vLLM이 진행 중인 요청의 KV 캐시를 제거하고 다시 스케줄링할 수 있다. 이는 출력 토큰 목록이 단조 증가한다는 상태 머신의 가정을 깨뜨린다. 토큰 히스토리가 줄어드는 것을 감지하면 상태 머신을 리셋하고 새 프롬프트에서 다시 초기화한다.

한국 개발 생태계에서의 적용 맥락

국내에서도 LLM을 실제 서비스에 적용하려는 시도가 늘고 있다. 특히 대규모 트래픽을 처리해야 하는 서비스라면 넷플릭스의 접근 방식이 좋은 참고가 된다. 다만 국내 SI 환경에서는 레거시 시스템과의 통합이 중요하다. 기존 Java 기반 시스템에 gRPC를 도입하는 것은 어렵지 않지만, Triton과 vLLM의 버전 호환성 문제는 특히 주의가 필요하다. 또한 GPU 자원이 제한적인 경우 Red-Black 배포보다 Versioned 전략이 비용 부담이 될 수 있으므로, 서비스 특성에 맞는 전략을 선택해야 한다.

이 기술의 한계 또는 주의사항

  • 버전 관리의 복잡성: Triton과 vLLM의 호환성을 지속적으로 확인해야 한다. 커스텀 패치가 필요할 수 있다.
  • 커스텀 모델 지원의 제한: vLLM 백엔드는 표준 모델만 지원하므로, 커스텀 로직이 필요한 모델은 Python 백엔드로 전환해야 한다. 이는 유지보수 부담을 증가시킨다.
  • 제약 디코딩의 복잡성: 상태 머신 기반 제약은 구현이 어렵고, vLLM 버전에 따라 API가 달라질 수 있다.

다음 단계 학습 방향

  • vLLM V1의 비동기 스케줄링과 벡터화된 logits processor를 살펴보면 성능 최적화에 도움이 된다.
  • 시스템 프롬프트 압축 기술을 학습하여 프롬프트 길이를 줄이는 방법을 알아보자.
  • 저정밀도 모델 변환을 통해 메모리 사용량을 줄이고 처리량을 높이는 방법을 실험해보자.

결론: 프로덕션에서 배운 교훈

넷플릭스의 LLM 서빙 플랫폼은 vLLM과 Triton을 기반으로 일관된 API 뒤에서 통합되어 있다. 실무에서 얻은 교훈은 버전 고정, 조용한 API 갭, 패키징 트레이드오프 같은 세부 사항에 있었다. 이런 문제를 해결함으로써 플랫폼은 더욱 견고해졌고 개발자 경험도 개선되었다.

이 글이 실제 LLM 서빙 시스템을 설계하는 데 도움이 되길 바란다. 원문은 넷플릭스 기술 블로그에서 확인할 수 있다.

함께 보면 좋은 글

Netflix in-house LLM serving architecture with vLLM and Triton on GPU servers Programming Illustration

본 콘텐츠는 신뢰할 수 있는 출처를 바탕으로 AI 도구를 활용하여 초안이 작성되었으며, 편집자의 검토를 거쳐 발행되었습니다. 전문가의 조언을 대체하지 않습니다.