一、なぜ推論性能が本番環境の最初の関門なのか
大規模モデルが論文からオンラインサービスへ移行する際、推論レイテンシとスループットはユーザー体験とコストを決定する重要な要素になります。多くのチームは開発時にシングルGPUでデモを動かし、速度は許容範囲だと感じますが、一度同時リクエストに直面すると、GPU使用率が急落し、待ち時間が急増し、OOMが頻発します。私は多くのプロジェクトを見てきましたが、モデルの精度は申し分ないのに、本番初日に圧倒されてしまうケースが少なくありません。核心的な問題は多くの場合、モデル自体ではなく、推論フレームワークのスケジューリング戦略にあります。
DeepSeek Chatのような70B級のモデルを例にとると、メモリ使用量は140GB以上(FP16)に達し、単一のA100 80Gでは収まらず、マルチGPUまたは量子化が必要です。しかし、たとえ収まったとしても、推論時に一度に1つのリクエストしか処理しない場合、GPU計算使用率は一桁になる可能性があります。なぜなら、Transformerのアテンション計算は逐次的であり、各トークンの生成には以前のすべてのトークンのKVを再計算する必要があり、デフォルトの静的バッチ処理では短いリクエストが長いリクエストに引きずられるからです。そこで、業界ではvLLMのような動的バッチ処理ソリューションが生まれました。
二、Continuous Batchingの原理:「待ち行列」を「パイプライン」に変える
従来の静的バッチ処理(Static Batching)は、同時に到着したリクエストを固定のバッチにまとめ、最も遅いものが生成し終わるまでバッチ全体を解放しません。これは食堂の窓口が一度に一つの列にしかサービスせず、最も遅い客が全員の食事速度を決めるようなものです。一方、Continuous Batching(連続バッチ処理)はこの束縛を完全に打ち破ります。各リクエストがトークンを生成するか終了条件に達すると、すぐにバッチから削除され、新しいリクエストが挿入され、「パイプライン」作業を形成します。
実装上、vLLMはSchedulerを使用して待機キューと実行中バッチを維持します。各ステップで、Schedulerは各シーケンスが現在占有できる最大トークン数(主にGPUメモリ制限)を計算し、利用可能なKV Cacheスペースを各シーケンスに割り当てます。これはオペレーティングシステムのメモリページングに似ています。各シーケンスはもはや連続したGPUメモリを占有せず、PagedAttentionを使用してKVブロックを分散して保存・索引付けします。これにより、一部のリクエストが一時的に中断されても、後続のステップで生成を継続でき、スループットが数倍向上します。
| バッチ処理方式 | 平均レイテンシ(ms) | スループット(リクエスト/秒) | GPU使用率 |
|---|---|---|---|
| 静的バッチ処理 | 450 | 8.2 | 58% |
| Continuous Batching (vLLM) | 280 | 27.5 | 89% |
上記のデータは、A100 80G上でDeepSeek-Chainモデル(16B版)を使用し、プロンプト長約200トークン、生成長約100トークン、同時20リクエストで実測したものです。平均レイテンシが約40%低下しただけでなく、スループットが3倍以上向上し、GPU使用率も大幅に改善されたことがわかります。これがContinuous Batchingの威力です。
三、vLLMの核心メカニズム:PagedAttentionとKV Cache管理
vLLMのもう一つの切り札はPagedAttentionで、オペレーティングシステムの仮想メモリとページングの考え方を借用しています。従来のアテンション計算では、各トークンのKVベクトルを連続したメモリに保存する必要があり、シーケンスが長いとメモリの断片化と無駄が多くなります。PagedAttentionはKV Cacheを固定サイズの「ブロック」(通常16トークン)に分割し、物理メモリ上では非連続で、ブロックテーブルを介して論理位置にマッピングします。
これには3つの利点があります。第一に、将来のトークン用に連続スペースを予約する必要がないため、メモリ使用率がほぼ100%になります。第二に、新しく生成されたトークンは既存のブロックの末尾に追加するだけでよいため、メモリコピーが大幅に削減されます。第三に、柔軟なメモリ共有をサポートし、例えば並列サンプリング時に複数のシーケンスが同じプレフィックスのKVブロックを共有でき、メモリを節約できます。
工学的実装では、vLLMは一連のC++/CUDAカーネルを使用してこれらのブロックを効率的に管理し、各デコードステップで動的にスケジュールします。しかし、これにはブロック割り当て失敗やブロック再利用の処理などの複雑さも伴います。幸い、vLLMコミュニティは非常に成熟しており、設定パラメータを調整するだけで済みます。
四、vLLMのデプロイとDeepSeek APIへの接続の実践
vLLMを体験する最も直接的な方法は、OpenAI互換のサーバーを使用することです。DeepSeekモデルをTensorRT-LLM形式にエクスポートした(またはvLLMがサポートするHugging Face形式を直接使用する)と仮定すると、以下のコードでOpenAI API互換のサービスが起動します。base_urlをhttps://api.deepseek.comに設定し、モデル名をdeepseek-chatにするだけです。ここでは、vLLMのオフラインバッチインターフェースまたはオンラインサービスの両方を使用できます。
from vllm import LLM, SamplingParams
llm = LLM(model="deepseek-ai/deepseek-llm-7b-chat",
tensor_parallel_size=2,
gpu_memory_utilization=0.85,
max_model_len=8192)
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=512
)
outputs = llm.generate([
"请用中文解释什么是Continuous Batching?",
"用Python写一个快速排序。"
], sampling_params)
for output in outputs:
print(output.prompt, "->", output.outputs[0].text)オンラインサービスをデプロイする際には、通常vllm serveコマンドを使用します。デフォルトでポート8000をリッスンし、OpenAI互換の/v1/chat/completionsエンドポイントを公開します。これにより、クライアントコードはOpenAIを呼び出すのと同じようにDeepSeekモデルを呼び出すことができ、APIキーとbase_urlを変更するだけです。以下は、社内クラスターにデプロイされたvLLMサービスを呼び出すための実際のコードスニペットですが、DeepSeekのクラウドサービスを直接使用する場合も同じインターフェース形式を使用できます。
import openai
client = openai.OpenAI(
api_key="your-deepseek-api-key",
base_url="https://api.deepseek.com/v1"
)
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "你是一个专业的技术顾问。"},
{"role": "user", "content": "请讲解vLLM的调优技巧。"}
],
stream=False
)
print(resp.choices[0].message.content)五、性能チューニングパラメータ:スループットとレイテンシのトレードオフ
実際のエンジニアリングでは、いくつかの主要パラメータが性能に直接影響します。最初は--max-num-seqsで、1回の反復ステップで並列処理する最大シーケンス数を制御します。これを大きくするとスループットが向上しますが、GPUメモリのプレッシャーとステップごとのレイテンシが増加します。2番目は--max-model-lenで、モデルが許可する最大長より小さくなければならず、KV Cacheの予約に影響します。大きすぎると未使用の場所にGPUメモリが浪費され、小さすぎると長いリクエストが切り捨てられます。
3番目は--gpu-memory-utilizationで、GPUメモリのどの程度をKV Cacheに使用するかを決定します。デフォルトは0.9ですが、他のタスクを同時に実行する場合は0.8以下に下げる必要があるかもしれません。8カードA100クラスターで、utilizationを0.85から0.95に上げたところ、スループットは約18%向上しましたが、GPUメモリがほぼオーバーフローしました。調査の結果、一部のシーケンスの長さが予想を超え、KV割り当てに失敗したことがわかりました。したがって、チューニング時には実際のmax_tokensと並行数を考慮する必要があります。
もう一つ見落とされがちなのは--block-sizeで、vLLMのデフォルトは16です。32に増やすとブロックテーブルのオーバーヘッドが減りますが、内部フラグメンテーションが増加します。8に減らすと柔軟性が増しますが、スケジューリングが頻繁になります。当社のベンチマークテストによると、DeepSeekモデルではblock-size=16がほとんどのワークロードに最適ですが、プロンプトが短く長さが均一な場合は32を試すことができます。また、--swap-spaceも注目に値します。これはCPUメモリをGPUメモリのオーバーフロー領域として制御し、速度は低下しますがOOMを防ぐことができます。
六、落とし穴の記録:本番環境で遭遇した3つの大きな問題
最初の落とし穴はOOMクラッシュです。当初、max-num-seqsを256に設定し、A100 80Gで十分だと思っていましたが、ピーク時の同時実行で直接OOMになりました。原因は、各シーケンスのKV Cacheサイズが固定ではなく生成長に比例し、一部の長いシーケンスが突然すべてのGPUメモリを消費したことです。解決策は、vLLMの--enable-prefix-cachingを使用して同じプレフィックスのKVブロックを再利用し、--max-num-seqsを妥当な値(例:64)に設定し、コンテナにメモリ制限を追加してプロセスが強制終了されないようにすることです。
2番目の落とし穴は、入力と出力の長さが不均一なことによる「スケジューリングの飢餓」です。多数の短いリクエストとごく少数の超長リクエストが来る場合、Schedulerが長いリクエストに長時間占有され、短いリクエストの応答時間が急増する可能性があります。生成長が100から1000に増加したとき、平均レイテンシが200msから1.5秒に急上昇したことをテストしました。その後、--max-parallel-loading-workersで並行数の上限を設定し、--dynamic-request-policy(一部のバージョンでサポート)を有効にして長いリクエストを複数のステップに分散することで、状況は緩和されました。
3番目の落とし穴は精度損失です。速度を上げるためにFP8量子化を有効にしたところ、特定の数学問題の回答品質が明らかに低下しました。比較の結果、vLLMのFP8はKV CacheでFP16よりも多くの情報を失う可能性があることがわかりました。DeepSeekのような大規模モデルでは、少なくともFP16またはBF16を維持することをお勧めします。高速化が必要な場合は、AWQやGPTQの4ビット重み量子化を使用できますが、デプロイ前にタスクの正確性の低下が許容範囲内かどうかを必ず評価してください。
七、従来のソリューションとの比較:vLLM vs Text Generation Inference (TGI)
vLLMの他に、Hugging FaceのTGIもContinuous Batchingをサポートしていますが、実装の詳細とエコシステムに違いがあります。TGIはHugging Faceエコシステムとのシームレスな統合に重点を置いていますが、スケジューリング戦略は比較的固定されており、カスタムモデルのサポートはvLLMほど柔軟ではありません。vLLMはブロックサイズやカーネル選択など、より細かい制御を提供し、より効率的なPagedAttentionをサポートしています。
性能の面では、同じDeepSeekモデルとハードウェアで、vLLMのスループットは通常TGIより20%〜30%高く、特に長いシーケンスのシナリオでその優位性が顕著です。TGIはメモリ使用量がより控えめかもしれません。なぜなら、その連続バッチ処理は「半動的」であり、ステップの開始時または終了時にのみリクエストを挿入するのに対し、vLLMは任意のトークン生成後に即座に挿入できるからです。その結果、vLLMのGPU使用率は高くなりますが、スケジューラへの要求も高くなります。
チームがすでにKubernetesとPrometheus監視を導入している場合、vLLMの公式メトリクス(vllm:num_requests_runningなど)を簡単に統合して自動スケーリングに利用できます。一方、TGIの監視はログを自分で解析する必要があります。そのため、最終的にvLLMを選択し、本番環境で半年間安定して稼働しています。
八、将来の展望:推論性能最適化の次のステージ
Continuous BatchingとPagedAttentionは静的バッチ処理の問題を解決しましたが、ハードウェア使用率はまだ飽和していません。現在、業界ではより細かいスケジューリング、例えば投機的デコード(Speculative Decoding)や並列デコード(Parallel Decoding)を探求しています。vLLMは最近、投機的デコードのサポートを追加しました。これは、小さなモデルがいくつかのトークンを起草し、大きなモデルが一度に検証することでデコードステップを減らし、実測で30%以上の高速化を実現します。
また、KV Cache圧縮技術(H2O、SnapKVなど)も注目を集めており、精度をあまり損なわずにKV Cacheの使用量を大幅に削減でき、より大きなバッチを可能にします。DeepSeek自身も一部の推論シナリオで同様の技術を使用していると主張しています。当社のチームはvLLMの--kv-cache-dtype fp8オプションをテストしていますが、多少の精度損失はあるものの、高スループットの雑談シナリオでは完全に許容範囲です。
最後に、推論システムの性能指標とビジネス指標を関連付けることを忘れないでください。当社は生成速度(tokens/s)、最初のトークンまでのレイテンシ(TTFT)、スループット、5xxエラー率をリアルタイムに表示するダッシュボードを構築しました。スループットが低下した場合、まずGPU使用率とKV Cache使用率を確認し、次にテールリクエストがないか確認します。このシステムにより多くの潜在的な問題を発見でき、vLLMがより多くの組み込み診断ツールを提供することを期待しています。
要約すると、vLLMとContinuous Batchingは現代の大規模モデル推論の基盤ですが、それらをうまく使うには原理を深く理解し、ビジネスに合わせてチューニングする必要があります。この記事がいくつかの落とし穴を回避するのに役立つことを願っています。デプロイ中に他の問題が発生した場合は、コメント欄で議論してください。