Netflix の LLM サービングスタック構築
Netflix は、LLM の完全なスタックを既存の生産環境内で自社運用し、CPU/GPUのハイブリッド構成やTritonを活用した独自の推論基盤を構築・公開した。
キーポイント
独自インフラでのフルスタック運用
Netflix は外部API依存から脱却し、モデルデプロイメントから推論までを既存の生産環境内で完結させる戦略を採用している。
JVMベースの統一サービングシステム
ルーティング、A/Bテスト、特徴量取得、推論、後処理を一貫して処理するJVMベースのシステムが中核を担い、gRPCとHTTPの両経路をサポートしている。
GPUリソースの効率的な管理
小規模モデルはインプロセスで実行し、大規模モデルはModel Scoring Service (MSS) を介してNVIDIA Triton Inference Serverに委譲するハイブリッド構成を採用している。
Java制御平面による運用自動化
デプロイ、バージョン管理、ヘルスチェック、自動スケーリングをJavaの制御平面が担い、ゼロダウンタイムでのアップグレードを実現している。
重要な引用
Netflix went further — we run the full stack ourselves, from model deployment through inference, inside our existing production environment rather than a separate ML silo.
MSS is the shared inference backend, supporting XGBoost, TensorFlow, PyTorch, and LLMs behind a unified interface, with NVIDIA Triton Inference Server underneath managing model loading, batching, and GPU scheduling.
影響分析・編集コメントを表示
影響分析
この記事は、大規模企業におけるLLM運用の実践的なアーキテクチャパターンを明確に示しており、特に既存インフラとの統合やハイブリッド推論戦略において他社への強力な参考事例となる。外部API依存からの脱却と独自制御平面の構築は、コスト削減とデータガバナンス強化を目指す組織にとって重要な指針を与える。
編集コメント
Netflixが公開したこの技術記事は、LLMの運用コストと制御性を両立させるための具体的なアーキテクチャ設計を示しており、業界全体におけるオンプレミスおよびハイブリッド推論戦略の標準化に寄与する可能性が高い。特に既存Java基盤との統合事例は、大規模システムを持つ企業にとって非常に参考になる実装パターンである。
10 分程度でお読みいただけます
3 日前に投稿
--
*AI Platform の Model Runtime チームおよび Inference チームより*
イントロダクション
多くの組織は、ホストされた API を通じて LLM(大規模言語モデル)を利用しています。しかし Netflix ではさらに一歩踏み込み、モデルのデプロイメントから推論までを、既存のプロダクション環境内で完結させています。ML 専用のサイロ(閉じた領域)に任せるのではなく、自前でフルスタックを運用しているのです。
これらの判断の中には、直感的でないものもあれば、本番環境での負荷がかかるまでそのトレードオフが明らかにならなかったものもありました。
この記事では、特に代替案が真剣に検討されたポイント——エンジン選定、モデルのパッケージ化、API サーフェスの設計、デプロイメント戦略、そして出力制約の強制——に焦点を当てます。単に何を作ったかだけでなく、「なぜそう決めたのか」、そして設計段階では予測できなかった本番環境が教えてくれた教訓をお伝えします。
アーキテクチャ概要
Netflix におけるメンバー規模の ML(機械学習)は、JVM ベースの統一されたサービングシステムによって支えられています。このシステムは、下流の消費者向けにエンドツーエンドの流れを処理します。具体的には、ルーティングや A/B テストロジック、候補生成、特徴量フェッチ、推論、ポストプロセッシング、そして各段階でのログ出力までを一貫して担当しています。
リアルタイム処理とキャッシュされたバッチ処理の両方をサポートしています。図 1 は、現在の呼び出し元が推論にアクセスする 2 つの方法を示しています。1 つは、このサービングシステムを経由する gRPC パスです。もう 1 つは、新しい LLM ドライブ型アプリケーションで使用される直接 HTTP パスです。
推論の実行場所はモデルによって異なります。小規模な CPU モデルはプロセス内で実行され、リモート呼び出しのオーバーヘッドを回避します。一方、大規模なモデルには GPU が必要であり、サービングシステムは前処理と後処理をローカルで処理しつつ、推論自体はリモートの「Model Scoring Service (MSS)」に委譲します。
MSS は共有推論バックエンドとして機能し、XGBoost、TensorFlow、PyTorch、LLM を統一されたインターフェースの背後でサポートしています。その下層には NVIDIA Triton Inference Server が配置され、モデルの読み込み、バッチ処理、GPU スケジューリングを管理しています。
Triton の上には Java で書かれた制御プレーンが構築されており、デプロイ、バージョン管理、ヘルスチェック、自動スケーリング、複数リージョンへの展開を担当します。モデル作成者はアーティファクトをパッケージ化してデプロイ設定を行い、制御プレーンが GPU インスタンスの割り当てや Triton の構成を行い、ダウンタイムゼロでのアップグレードを実現するためのオーケストレーションを行います。
Press enter or click to view image in full size
Figure 1. Serving Architecture Overview
デザイン判断と実装
このプラットフォームを形作っているのは、エンジン、パッケージ化、API サーフェス、展開の 4 つの判断です。これらは依存関係の順序で提示されており、それぞれの判断が次の判断に制約を与えます。
vLLM を舗装されたパス(標準)エンジンとして採用
当初、このプラットフォームは TensorRT-LLM を基盤にしていました。当時、高性能な推論エンジンであり、すでに MSS で使用されている計算バックエンドである Triton とも統合されていました。
2025 年夏までに、状況は二点大きく変化しました。まず、オープンソースの推論エンジンが専用スタックとの性能差をほぼ埋めたことです。次に、私たちのワークロード構成が多様化し、埋め込みベクトルの生成やランク付け・検索のためのプレフィルのみを行う推論、自己回帰的なデコーディング、そしてステップごとの複雑な制約ロジックを持つカスタムモデルが含まれるようになったことです。
こうした多様なワークロードを踏まえて再ベンチマークを行った結果、運用上の適合性を重視し、vLLM を標準化された推論エンジンとして採用しました。選定理由は以下の通りです。
- 多段階のコンパイルパイプラインを経ずにカスタムモデルアーキテクチャを読み込めるため、非標準的なモデルに対する開発サイクルが短縮される。
- カスタムのデコーディングロジックに対応するための拡張フックが用意されており、後述する制約付きデコーディングの実装に不可欠である。
- デバッグ性が高く、コンパイル済みのエンジン(以前の TensorRT-LLM)と比較して、失敗の原因や中間状態の調査が容易である。
- 親和性が良いこと。多くの ML プラクティショナーが研究段階で vLLM を利用しており、研究から本番環境への移行コストを大幅に削減できた。
Triton への vLLM の統合
vLLM を採用したことで、次なる課題はモデルをどのようにパッケージ化するかでした。Triton では二つの方法が用意されていますが、この選択は保守性に大きな影響を与えます。具体的には、モデルのアーティファクトとフロントエンドのアップグレードとの結合度をどう制御するかが鍵となります。
Python ベースエンドでは、パッケージ作成時に明示的な入力・出力テンソル仕様が定義されます。この仕様はアーティファクトに凍結されるため、サードパーティベンダーのフロントエンドがリクエストビルダーで期待する内容と一致している必要があります。そのため、I/O 仕様に関わるすべてのフロントエンドアップグレードには、パッケージコードとの調整変更が不可欠です。対応しないと、実行時にリクエストが失敗します。
vLLM ベースエンドでは、アーティファクトはモデルの重みとトークナイザーを指す単なる JSON 設定ファイルに過ぎません。Triton の vLLM バックエンドがこの設定を読み込み、デプロイ時に I/O テンソル仕様を動的に生成します。著者はこれらの仕様を手動で定義することはありません。その結果、モデルとフロントエンドは独立して進化できます。
vLLM ベースエンドがアーキテクチャ上では正しいデフォルトです。しかし、本番環境で二つの問題が発生しました:
Triton と vLLM のバージョン不一致の問題です。Triton の vLLM バックエンドは、特定の vLLM API 仕様に合わせてコンパイルされています。両者のバージョンがズレると、例えば Triton 25.09 が vLLM 0.11.2 で削除された vllm.engine.metrics モジュールをインポートしようとして、バックエンド自体が起動しなくなります。そのため、プラットフォーム側はサービスイメージを作成する際に互換性のあるバージョンを固定(ピン留め)し、モデル作成者がパッケージング時に vLLM のバージョンを上書きできないように制御する必要があります。
カスタムモデルロジックについてはどうでしょうか。vLLM バックエンドは標準的な HuggingFace 互換のモデルを想定しており、推論ライフサイクル全体を処理します。一方、事前処理や事後処理のカスタマイズが必要だったり、アンサンブルパイプラインや独自トークナイゼーションなど非標準的な実行が必要なモデルは、Python バックエンドを使用する必要があります。これにより execute() 関数に対する完全な制御権を得られますが、この機能(エスケープハッチ)は今後も特定のモデル群に対して必要不可欠であり続けるでしょう。
Ecosystem-Compatible HTTP Frontend
エンジンとパッケージングの設計が決まったところで、次は外部からの呼び出し経路について考えます。システム設計における重要な目標の一つは、LLM モデルを特別扱いしないことです。XGBoost のアンサンブルモデルであれ大規模な LLM であれ、すべて同じ gRPC コールでスコアリングされます。これにより、クライアントライブラリ、ヘルスチェック、デプロイパイプラインを共通化して再利用できます。現在、LLM エコシステムにおいて OpenAI 互換 API が事実上の標準インターフェースとなっています。推論エンジン、オーケストレーションフレームワーク、評価ツール、そしてクライアントライブラリすべてがこの仕様に従っているため、gRPC と並行して OpenAI 互換 API を追加のフロントエンドとして公開することになりました。
実験から本番環境への移行における成果は、ホスト型モデルからファインチューニングを施したセルフホスト型モデルへ移行する際(品質、レイテンシ、コスト、データプライバシーのいずれかの目的で)に顕著です。API はそのままに、コードの変更も最小限で済みます。
Netflix Technology Blog の記事をあなたのメールボックスへ
このライターからの更新を受け取るには、Medium に無料で登録してください。
サインインを高速化するために、ログイン情報を記憶します。
API の背後では、実装において NVIDIA の Triton OpenAI 互換フロントエンド が再利用されています。埋め込まれた Triton サーバーを起動し、これを TritonLLMEngine でラップしてリクエストスキーマを Triton の推論リクエストに変換し、FastAPI を介してレスポンスを提供します。同時に KServe の HTTP/gRPC フロントエンドも有効化されており、同じ Triton インスタンスは Java コントロールプレーンからも gRPC 経由でアクセス可能です。
Triton のフロントエンドを直接採用したことで、一つの課題が浮き彫りになりました。スキーマで受け入れられる response_format が、vLLM に到達する前に静かに捨てられていたのです。その結果、JSON 出力を要求する呼び出し元は、ガイダンス付きデコーディングの制約なしに処理が進み、プラットフォーム側からエラーが報告されることなく不正な JSON が返される事態が発生しました。私たちはこのフロントエンドを git-subtree でフォークし、パッチを適用して、リクエスト時に response_format を vLLM のガイダンス付きデコーディングパラメータに変換するように修正しました。
デプロイ戦略
API サーフェスとエンジンが整った今、残る課題は新バージョンをリクエストの中断なくロールアウトする方法です。GPU 環境でのデプロイは CPU サービスに比べて起動までに時間がかかり、さらにモデルのバージョン間で入出力スキーマが変更される可能性もあるため、調整が必要になります。このプラットフォームでは、2 つの戦略を提供しています。
Red-Black デプロイは、新しいバージョンを現在のバージョンと並行して展開します。新しいインスタンスがヘルスチェックに合格すると、トラフィックは段階的に切り替わります。具体的には、新しいバージョンのスケーリングアップと古いバージョンのスケーリングダウンが同率で行われます。もしいずれかのステップで失敗した場合、システムは即座に原子ロールバックを実行します。モデルインターフェースが安定している場合、Red-Black は最適な選択肢です。
しかし、本番環境では、新しいバージョンの展開に伴って I/O スキーマの変更(例えばテンソルの次元変更など)が必要になった際、調整不足が露呈しました。この場合、アップストリームの消費者は新しいモデルが完全に稼働するまで設定を更新できず、移行期間中に「古い」リクエストを「新しい」デプロイメントに送信して失敗してしまうのです。
Versioned はこのギャップを埋めるために設計されました。これは、すべての (modelId, modelVersion) ペアに対して独立したデプロイメントを維持する仕組みです。複数のバージョンが同時にサービスを提供することで、モデルの展開と消費者側の更新を切り離します。具体的には、消費者は新しいバージョンが完全に準備されるまで設定変更を待ち、その間も古いバージョンはレガシーなトラフィックを引き続き処理し続けます。プラットフォームは一定期間アクティビティがないと古いデプロイメントをクリーンアップしますが、最新バージョンは常に保持されます。この方式のトレードオフは、移行期間の重複に伴う一時的な GPU コストの増加です。
変数設定(テンソル形状など)を推論モデルに直接埋め込み、バージョン非依存化することで、より安価な Red-Black パスを利用可能にすることを推奨します。Versioned は、避けられない破壊的インターフェース変更が必要な稀なケースに限定して使用すべきです。
オペレーション上の注意点
上記の4つの決定事項に加え、設計段階では想定されていなかった2つの運用上の課題があります。これらは本番環境で直面した問題です。
ブートシーケンス
vLLM-on-Triton インスタントを起動し、gRPC ポートが利用可能になるまでには、いくつかの調整された手順が必要です。そのうち2つは通常のフローとは異なります。
- モデルのキャッシュ処理:大規模な LLM を起動時に S3 や Hugging Face から直接ダウンロードすると、コールドスタート時の遅延がスケジューラが許容する範囲を超えてしまいます。そのため、モデル発表時点で Amazon FSx 上にモデルを事前展開(マテリアライズ)し、ウォームスタート時にはオブジェクトストレージではなく高パフォーマンスなファイルシステムから読み込むようにしています。
- Embedded モードとスタンドアロンモードの切り替え:OpenAI 互換 API を必要とする場合、Triton は OpenAI 互換フロントエンドプロセス内に埋め込まれたサーバーとして動作します。それ以外の場合はスタンドアロンで稼働します。この設定は、パッケージ化時に各デプロイごとに個別に構成されます。
残りのブートシーケンスは機械的な手順です。モデルパッケージの展開、Python entry_points を介したカスタム vLLM プラグインのインストール、Prometheus のマルチプロセスディレクトリのクリーンアップ、そしてエンジンが準備完了するまで gRPC ポートをゲート制御することを行います。
ユニファイドメトリクスエンドポイント
上記の Prometheus の整理から、より広範な観測性のギャップが浮かび上がります。vLLM は .db ファイルとして PROMETHEUS_MULTIPROC_DIR にメトリクスを出力し、Triton は独自の Prometheus エンドポイントを介してサーバーレベルのメトリクスを報告しています。しかし、両者は互いの存在を認識しておらず、Triton 内蔵のブリッジが表面化するのは vLLM の 40 種類以上のメトリクスのうちわずか 9 種のみです。トークンスループット、KV キャッシュ利用率、プレフィックスキャッシュヒット率といった重要な指標が見落とされています。
そこで私たちは、両者を統合する軽量な HTTP プロキシを追加しました。このプロキシは HTTP を介して Triton のメトリクスを取得し、Prometheus の MultiProcessCollector を用いてディスク上の vLLM メトリクスを読み取り、統合された出力を返します。既存のダッシュボードやアラート設定も変更なしで動作可能です。
大規模な制約付きデコーディングの詳細
Netflix の一部生産ワークロードでは、トークン生成に対するきめ細やかな制御が不可欠です。推論後にビジネスロジックを適用して無効な生成物を処理し、再試行や修復を行うのではなく、デコードループ内部に制約を組み込みます。これにより、モデルは構造的にコンプライアンスを満たす出力を生成します。
実装は vLLM のカスタム logits プロセッサインターフェースを通じて行います。各制約は状態機械としてモデル化され、生成されたトークンの履歴に応じて進化し、各ステップでトークンの有効性を示すマスクを出力します。リクエストごとに独自の構成済みプロセッサが割り当てられます。なぜなら、異なるリクエストには異なるルールが適用されるからです。
このシステムをスケールさせる過程では、2 つのエンジンバージョンを経験しました。当初は vLLM V0 にデプロイしましたが、機能に不足があったため、V1 が成熟した 2025 年第 4 四半期に移行しています。以下の 2 つの小節では、移行前の状況と移行後の改善点を対比して解説します。
なぜ最初の導入がスケールしなかったのか
当初の純粋な Python 実装は機能面では問題ありませんでしたが、スケーラビリティのボトルネックに直面しました。vLLM V0 ではカスタムロジットプロセッサがリクエストごとに実行されます。具体的には、GPU がバッチ全体のロジットを生成した後、CPU がそれらをコピーして転送完了を待機し、その後、制約ロジックが各リクエストに対して順次処理されます。Python の GIL(グローバルインタプリタロック)が並列化を阻害するため、この処理は逐次的にならざるを得ません。
その結果、ロジット処理にかかる CPU 時間はバッチサイズに比例して直線的に増加し、レイテンシの尾部(遅延するケース)が悪化します。モデルの順方向計算自体は GPU で効率的にバッチ処理されているにもかかわらず、エンドツーエンドのレイテンシが CPU 依存の状態になってしまいます。これは単一リクエストでのベンチマークでは見えないボトルネックであり、実際の並行処理下で初めて顕在化する問題です。
Figure 2 では、この逐次的な処理パターンが視覚的に確認できます。
Press enter or click to view image in full size
図 2:vLLM V0 を使用した CPU 上でのロジットプロセッサの逐次実行
vLLM V1 でバッチレベル設計が可能に
構造的な改善は、vLLM V1 の登場で実現しました。ここではロジット処理をバッチレベルに移行しています。カスタムプロセッサを書き換え、バッチレベルのデータ構造上で動作するようにし、複数のリクエストに対してマスク計算を一括で行えるようにしました。また、ホットパス(頻繁に実行される経路)は C++ で再実装し、マルチスレッド化によって GIL(グローバルインタプリタロック)の影響を回避しています。
V1 の API では、バッチ構成の変化を update_state(batch_update) を通じて明示的に追跡する必要があります。これは V0 の個々のリクエストごとのインターフェースよりも複雑ですが、動的に変化するバッチ内で状態を正しく保つために不可欠な設計です。図 3 に示す通り、vLLM V1 ではバッチサイズが大きくなってもロジット処理にかかる時間は一定に保たれています。
(画像をクリックまたは Enter キーを押して全サイズで表示)
図 3:vLLM V1 を使用した CPU 上でのバッチ化されたロジットプロセッサの実行
運用面での強化
これでパフォーマンスがボトルネックになることはなくなりました。しかし、デコードループ内の状態保持型制約ロジックにより、設計段階では想定していなかった 2 つの問題が発生しました。
- パーシャルプリフィル。V1 ではチャンク化されたプリフィルを行うため、リクエストは複数のエンジンステップにわたってプリフィルされます。BatchUpdate には、リクエストが完全にプリフィルされたのか部分的なままなのかを区別する粒度がないため、内部での追跡機能を追加しました。
- プリーエンプション(割り込み)。メモリ圧力がかかると、vLLM は部分的に完了したリクエストの KV キャッシュを退去させ、異なるプロンプトと出力トークンリストで後から再スケジュールすることがあります。これにより、「出力トークンリストは単調増加する」という状態機械の前提が崩れます。そこで、デコードステップ間でトークン履歴が縮小した場合を検知し、状態機械をリセットして新しいプロンプトから再初期化するようにしています。
まとめ
私たちは、低レイテンシ、深いカスタマイズ性、既存インフラとの統合といった広範な生産環境での ML 要件に対応する LLM サービングプラットフォームの構築を目指しました。その結果、vLLM と Triton を基盤とし、一貫した API の背後に統合されたシステムが完成しました。これは、ML プラクティショナーが実験から本番環境へ迅速に進めるための高速パスを提供するように設計されています。
教訓は往々にして細部にありました——バージョンの固定(pinning)、目に見えない API のギャップ、パッケージ化におけるトレードオフなどです。これらに対処したことで、プラットフォームは実質的に堅牢になり、開発者体験も滑らかになりました。今後の投資先には、私たちが摩擦が生じると予想される領域が反映されています:
システムプロンプトの圧縮により、品質を損なうことなくプロンプト長を短縮します。
vLLM V1 の非同期スケジューリングを実装しました。また、CPU 上のコードではなく融合された GPU カーネルとして動作するベクトル化ロジットプロセッサを導入しています。さらに、メモリフットプリントの削減とスループットの向上を図るため、低精度モデルバリアントも活用しています。
この分野は日々進化しており、私たちはオープンソースコミュニティとの緊密な連携を継続してまいります。
貢献者
本システムは、Netflix の AI Platform オrg に所属する多くのチームによる緊密な協力と多大な貢献の結晶です。特に、Liping Peng はモデルパッケージングワークフローの設計・開発を担当し、Triton と vLLM を MSS に統合して LLM serving の統一パスを実現しました。
Hakan Baba、Nicolas Hortiguera、ZQ Zhang は、全生産モデルにおける GPU 容量計画、システムパフォーマンスチューニング、アプリケーション統合、観測性(オバザビリティ)、A/B テストの準備状況、および運用卓越性の取り組みを主導しました。Santino Ramos は vLLM を生産環境のモデルで利用可能にし、制約付きデコーディングのパフォーマンスを最適化しました。
Binh Tang はカスタムモデル serving の初期バージョンを開発し、さまざまな LLM serving フレームワークのベンチマークを行いました。Lanxi Huang と Daneo Zhang は、ユーザーによるセルフサービスを実現するための serving 開発ツールを構築しました。
Lingyi Liu は全体のシステムアーキテクチャと中核的な技術決定を主導し、Abhishek Agrawal と Shaojing Li が管理リーダーシップを発揮して、方向性の整合性、優先順位の付け方、そして実行の確保を行いました。
謝辞
本稿は、Triton、vLLM、PyTorch など、オープンソースの機械学習ライブラリを大きく活用して作成されました。コミュニティの皆様、特に各チームや貢献者の方々に深く感謝いたします。また、Netflix AI のメンバーシステム部門のパートナーチームには、モデリング分野における緊密な連携とイノベーションに多大なるご協力をいただき、心より御礼申し上げます。
原文を表示
10 min read
3 days ago
--
*By AI Platform’s Model Runtime team and Inference team*
Introduction
Most organizations consume LLMs through hosted APIs. Netflix went further — we run the full stack ourselves, from model deployment through inference, inside our existing production environment rather than a separate ML silo. Some of those decisions weren’t obvious, and a few revealed their trade-offs only under production load.
This post focuses on the choices where alternatives were seriously considered: engine selection, model packaging, API surface design, deployment strategy, and output constraints enforcement. The goal is to share not just what was built, but why — and what production revealed that the design phase didn’t anticipate.
Architecture Overview
Member-scale ML at Netflix is fronted by a unified JVM-based serving system that handles the end-to-end flow for downstream consumers: routing and A/B test logic, candidate generation, feature fetching, inference, post-processing, and logging at each stage. Both real-time and cached batch paths are supported. Figure 1 shows the two ways callers reach inference today: the gRPC path through this serving system and a direct HTTP path used by newer LLM-driven applications.
Where inference runs depends on the model. Small CPU models run in-process, avoiding remote-call overhead. Larger models need GPUs — the serving system handles pre- and post-processing locally but delegates inference to a remote service, Model Scoring Service (MSS). MSS is the shared inference backend, supporting XGBoost, TensorFlow, PyTorch, and LLMs behind a unified interface, with NVIDIA Triton Inference Server underneath managing model loading, batching, and GPU scheduling.
On top of Triton sits a Java control plane that handles deployment, versioning, health checking, autoscaling, and multi-region rollout. Model authors package their artifacts and configure the deployment; the control plane provisions GPU instances, configures Triton, and orchestrates zero-downtime upgrades.
Design Decisions and Implementation
Four decisions shape this platform — engine, packaging, API surface, and rollout — presented in dependency order, since each one constrains the next.
vLLM as the Paved-Path Engine
The platform was originally built on TensorRT-LLM, a performant inference engine at the time and already integrated with Triton — the compute backend in use within MSS.
By summer 2025, two things had shifted: open-source engines had largely closed the performance gap with specialized stacks, and our workload mix had broadened to include embedding generation, prefill-only inference for ranking and retrieval, autoregressive decoding, and custom models with non-trivial per-step constraint logic. We re-benchmarked against this mix and selected vLLM as our paved-path engine on operational fit:
- Loads custom model architectures without a multi-step compilation pipeline — faster iteration on non-standard models.
- Extensibility hooks for custom decoding logic — necessary for the constrained-decoding work described later.
- Debuggability — easier to inspect failures and intermediate state than with a compiled engine in earlier TensorRT-LLM.
- Familiarity — many ML practitioners were already using vLLM in research, which cut the research-to-production handoff cost.
Integrating vLLM into Triton
With vLLM picked, the next decision was how to package models for it. Triton supports two ways, and the choice has significant implications for maintainability — specifically, how tightly model artifacts are coupled to frontend upgrades.
- Python backend. The author defines explicit input/output tensor specs at packaging time. These specs are frozen in the artifact and must match what the third-party vendor’s frontend’s request builder expects, so every frontend upgrade that touches I/O specs requires a coordinated change to packaging code; otherwise, requests fail at runtime.
- vLLM backend. The artifact is just a JSON config pointing to the model weights and tokenizer. Triton’s vLLM backend reads this config and generates I/O tensor specs dynamically at deployment time — the author never defines them. Models and frontend evolve independently.
The vLLM backend is the architecturally correct default. Two things bit us in production:
- Triton/vLLM version mismatch. Triton’s vLLM backend is compiled against a specific vLLM API surface. When the two drift — for example, Triton 25.09 importing vllm.engine.metrics, a module removed in vLLM 0.11.2 — the backend fails to load entirely. The platform has to pin compatible versions when baking the service image, and prevent model authors from overriding the vLLM version at packaging time.
- Custom model logic. The vLLM backend expects a standard HuggingFace-compatible model and handles the full inference lifecycle. Models needing custom preprocessing, postprocessing, or non-standard execution — ensemble pipelines, custom tokenization — must use the Python backend, which gives full control over execute(). This escape hatch will likely remain necessary for a subset of models.
Ecosystem-Compatible HTTP Frontend
With engine and packaging settled, the next question is how callers reach the system. A key design goal of our system was that LLM models should NOT be special snowflakes. Every model — XGBoost ensemble or large-scale LLMs — is scored via the same gRPC call, so we reuse the same client libraries, health checking, and deployment pipelines. Given that the OpenAI-compatible API interface has become the de facto interface for the LLM ecosystem — inference engines, orchestration frameworks, evaluation tools, and client libraries all speak it — so we expose the OpenAI-compatible API as an additional frontend alongside gRPC.
The payoff shows up in the experimentation-to-production path: graduating from a hosted model to a fine-tuned self-hosted one — for quality, latency, cost, or data privacy — is nearly seamless. Same API, minimal code changes.
Get Netflix Technology Blog’s stories in your inbox
Join Medium for free to get updates from this writer.
Remember me for faster sign in
Behind the API, the implementation reuses NVIDIA’s Triton OpenAI-compatible frontend. It starts an embedded Triton server, wraps it in a TritonLLMEngine that converts request schemas into Triton inference requests, and serves responses through FastAPI. KServe HTTP/gRPC frontends are enabled alongside, so the same Triton instance remains accessible to the Java control plane over gRPC. Adopting Triton’s frontend directly exposed one gap: response_format — accepted by the schema — was silently dropped before reaching vLLM, so that a caller requesting JSON output proceeded without guided decoding constraints and could receive malformed JSON with no error surfaced by the platform. We git-subtreed and patched the frontend to translate response_format into vLLM’s guided decoding parameters at request time.
Deployment Strategies
With API surface and engine in place, the question that remains is how new versions roll out without dropping requests. GPU deployments take longer to bring up than CPU services, and the I/O schema may change between model versions — adding a coordination problem on top. The platform offers two strategies:
- Red-Black deploys a new version alongside the current one. Once the new instance passes health checks, traffic shifts in phases — the new version scales up while the old scales down at the same rate. If any step fails, the system triggers an atomic rollback. Red-Black is the right choice when the model interface is stable. Production revealed a coordination gap when a new version requires an I/O schema change (e.g., new tensor dimensions): the upstream consumer can’t update its config until the new model is fully live, so it inevitably sends “old” requests to a “new” deployment during the migration window, and those fail.
- Versioned solves that gap by maintaining an independent deployment for every (modelId, modelVersion) pair. Multiple versions serve simultaneously, decoupling model deployment from consumer updates: the consumer waits for the new version to be fully ready before switching its config, while the old version keeps serving legacy traffic. The platform cleans up older deployments after inactivity but always preserves the latest. The trade-off is a temporary increase in GPU cost during the transition overlap.
We recommend embedding variable configurations (e.g., tensor shapes) directly into the inference model to make it version-agnostic, so it can use the cheaper Red-Black path. Versioned is reserved for the rare cases where a breaking interface change is unavoidable.
Operational Notes
Beyond those four decisions, two operational details are worth flagging — both hit production gaps the design phase didn’t anticipate.
Boot sequence
Bringing a vLLM-on-Triton instance up involves several coordinated steps before the gRPC port opens. Two are non-routine.
- Model caching. Downloading large LLMs directly from S3 or Hugging Face at startup is slow enough to inflate cold-start latency past what schedulers tolerate. We materialize models on Amazon FSx at the time of model announcement, so warm starts hit a high-performance file system instead of object storage.
- Embedded vs standalone Triton. When consumers need the OpenAI-compatible API, Triton runs as an embedded server inside the OpenAI-compatible frontend process; otherwise, it runs standalone. This is configured per-deployment at packaging time.
The rest of the boot sequence is mechanical: extracting the model package, installing custom vLLM plugins via Python entry_points, cleaning the Prometheus multiprocess directory, and gating the gRPC port until the engine is ready.
Unified metrics endpoint
The Prometheus cleanup above hints at a wider observability gap. vLLM writes metrics to PROMETHEUS_MULTIPROC_DIR as .db files; Triton reports server-level metrics through its own Prometheus endpoint. Neither is aware of the other, and Triton’s built-in bridge surfaces only 9 of 40+ vLLM metrics — missing critical ones like token throughput, KV cache utilization, and prefix cache hit rates.
We added a lightweight HTTP proxy that merges both into a single /metrics endpoint: it fetches Triton metrics via HTTP, reads vLLM metrics from disk using Prometheus’s MultiProcessCollector, and returns the combined output. Existing dashboards and alerts work without modification.
Deep-Dive: Constrained Decoding at Scale
Some Netflix production workloads rely heavily on fine-grained control over token generation. Rather than applying business logic after inference — paying for invalid generations, then retrying or repairing — we push constraints inside the decode loop, so the model generates outputs that are compliant by construction. We implement this via vLLM’s custom logits processor interface, modeling each constraint as a state machine that evolves with the generated token history and emits token-eligibility masks at each step. Each request gets its own configured processor, since different requests apply different rules.
Getting this to scale ran across two engine versions: we initially deployed on vLLM V0 (V1 had feature gaps), then migrated to V1 in Q4 2025 once it matured. The two subsections that follow are the before-and-after.
Why the first implementation didn’t scale
Our initial pure-Python implementation worked functionally but hit a scaling bottleneck. In vLLM V0, custom logits processors run per-request: the GPU produces logits for the whole batch, the CPU copies them across and waits for the transfer, and then constraint logic runs sequentially for each request — sequentially because the GIL prevents Python from parallelizing the per-request work. CPU time in logit processing therefore grows linearly with batch size, hitting tail latencies. End-to-end latency becomes CPU-bound even though the model’s forward pass is batched efficiently on GPU. It’s a bottleneck invisible in single-request benchmarks that only surfaces under realistic concurrency. Figure 2 makes the serial pattern visible.
vLLM V1 enabled a batch-level design
The structural fix arrived in vLLM V1, which moved logits processing to batch level. We rewrote our custom processor to operate on batch-level data structures, computing masks across many requests together, and reimplemented the hot path in C++ with multi-threading to step around the GIL. The V1 API requires explicit tracking of batch membership changes via update_state(batch_update) — more complex than V0’s per-request interface, but necessary to maintain correct state in a dynamically evolving batch. Figure 3 shows logits processing time staying flat as batch size grows.
Operational hardening
Now, performance was no longer the bottleneck. But stateful constraint logic in the decode loop introduced two issues the design phase didn’t anticipate:
- Partial prefills. V1 performs chunked prefilling, so a request can be prefilled over multiple engine steps. BatchUpdate lacks the granularity to tell whether a request was fully or only partially prefilled, so we added internal tracking.
- Preemption. Under memory pressure, vLLM may evict a partially completed request’s KV cache and reschedule it later with a different prompt and output token list. This breaks the state machine’s assumption that the output token list grows monotonically. We detect when the token history shrinks between decode steps, reset the state machine, and reinitialize from the new prompt.
Wrap up
We set out to build an LLM serving platform for broad production ML requirements — low latency, deep customization, and integration with existing infrastructure. The result is a system on vLLM and Triton, unified behind a consistent API, designed to give ML practitioners a fast path from experimentation to production.
The lessons were often in the details — version pinning, silent API gaps, packaging trade-offs — but addressing them has made the platform meaningfully more robust and the developer experience smoother. Next investments reflect where we expect friction:
- System prompt compression to reduce prompt length without sacrificing quality.
- Asynchronous scheduling of vLLM V1.
- Vectorized logits processors that run as fused GPU kernels instead of CPU code.
- Lower-precision model variants to decrease memory footprint and increase throughput.
We’ll continue working closely with the open-source community as this space evolves.
Contributions
This system is the result of close collaboration and contributions from many teams within the AI Platform org at Netflix. In particular, Liping Peng designed and developed the model packaging workflow and drove the integration of Triton and vLLM with MSS to enable a unified pathway for serving LLMs. Hakan Baba, Nicolas Hortiguera, and ZQ Zhang led GPU capacity planning, system performance tuning, application integration and observability, as well as A/B test readiness and operational excellence efforts for all production models. Santino Ramos enabled vLLM for production models and optimized constrained decoding performance. Binh Tang developed the initial version of custom model serving and benchmarked different LLM serving frameworks. Lanxi Huang and Daneo Zhang built the serving development tools to enable user self-service. Lingyi Liu drove the overall system architecture and core technical decisions. Abhishek Agrawal and Shaojing Li provide management leadership to ensure alignment, prioritization and execution.
Acknowledgements
This work heavily leverages open-source ML libraries, such as Triton, vLLM and PyTorch, etc. We’re especially grateful to the teams and contributors from the community. We also thank our partner teams in Netflix AI for Member Systems for their close collaborations and innovation on the modeling side.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み