vLLMを活用した大規模言語モデル(LLM)の推論スループット最大化とメモリ最適化

GPU追加購入の前に試すべきvLLM設定5選:メモリ断片化解消とスループット最大化の定石

この記事は急速に進化する技術について解説しています。最新情報は公式ドキュメントをご確認ください。

約15分で読めます
文字サイズ:
GPU追加購入の前に試すべきvLLM設定5選:メモリ断片化解消とスループット最大化の定石
目次

この記事の要点

  • PagedAttentionによるGPUメモリ効率の劇的な向上
  • LLM推論スループットの最大化と低遅延化
  • OOMエラーの抑制と安定した運用

「Hugging Faceから最新のLLMをダウンロードして動かしてみたけれど、複数人からアクセスがあると急激に遅くなる」
「GPUメモリは足りているはずなのに、なぜかOOM(Out of Memory)エラーで落ちてしまう」

実務の現場では、生成AIアプリのプロトタイプ開発を進める中で、こうした課題に直面するケースが多く見られます。特に、PoC(概念実証)から試験運用へフェーズが進み、同時アクセス数が増え始めたタイミングで、この壁にぶつかることが多いようです。

「やはりA100のような高価なGPUを追加購入しないとダメでしょうか?」という疑問が生じがちですが、少し立ち止まって考えてみましょう。ハードウェアに投資する前に、ソフトウェア側でできる「チューニングの余地」は驚くほど残されています。

今回は、LLM推論ライブラリのデファクトスタンダードになりつつある「vLLM」を活用し、設定を見直すだけで推論速度(スループット)を劇的に改善し、限られたGPUメモリを使い切るための5つの定石を解説します。CUDAカーネルを書くような複雑な操作は不要です。起動オプションや設定ファイルの変更で対応できる、実践的かつ論理的なアプローチをご紹介します。

なぜあなたのLLMは遅いのか?メモリの断片化を知る

具体的な設定の話に入る前に、そもそもなぜvLLMを使うと速くなるのか、その根本的な理由を論理的に整理しておきましょう。キーワードは「メモリの断片化(フラグメンテーション)」と、vLLMの中核技術である「PagedAttention」です。

GPUメモリの「隙間」がボトルネック

LLMがテキストを生成する際、過去の計算結果(KeyとValue)を「KVキャッシュ」としてメモリ上に保存します。従来の仕組みでは、このKVキャッシュのために、将来生成されるかもしれない最大長分のメモリ領域をあらかじめ連続して確保していました。

これは、本棚に例えるとわかりやすいでしょう。

あるシリーズものの漫画が全30巻あるとして、まだ1巻しか出ていないのに、本棚に30巻分のスペースを「予約」して空けておくようなものです。もしその漫画が5巻で完結したら、残りの25巻分のスペースは無駄になります。逆に、予想外に続いて31巻が出たら、もう置く場所がありません。

コンピュータのメモリ上でも同じことが起きています。連続した領域を確保しようとすると、実際には使われない「予約済みだけど空っぽ」な領域(内部断片化)や、半端なサイズの隙間(外部断片化)が大量に発生します。その結果、物理的にはメモリが空いているのに、新しいリクエストのためのまとまった領域が確保できず、処理待ちが発生したりOOM(Out of Memory)になったりするのです。

vLLMの革新技術「PagedAttention」の直感的理解

ここで登場するのがvLLMの「PagedAttention」です。これはOS(オペレーティングシステム)のメモリ管理手法である「ページング」をLLMに応用したものです。

先ほどの本棚の例で言えば、PagedAttentionは「本をバラバラの場所に置いてもOK」にする仕組みです。1巻は1段目の右端、2巻は3段目の左端、といった具合に、空いている隙間(ページ)があればどこにでも詰め込みます。そして、「どの本の何巻がどこにあるか」を管理する台帳(ブロックテーブル)を用意します。

さらに、最新のvLLMアーキテクチャ(V1以降)では、このメモリ管理技術に加え、リクエスト処理のスケジューリング自体が大幅に刷新されています。

  1. スケジューラの統合: 入力処理(Prefill)と生成処理(Decode)を統一されたスケジューラで管理し、GPUの稼働率を平準化しています。
  2. Chunked Prefillの標準化: 長いプロンプト処理を分割して実行する「Chunked Prefill」がデフォルトで有効になり、他のリクエストへの応答遅延(レイテンシ)を抑えつつスループットを維持します。
  3. FP8 KVキャッシング: 最新版ではKVキャッシュを8ビット浮動小数点(FP8)で保存する機能も強化され、メモリ消費量をさらに削減しつつ大規模モデルの実行を可能にしています。

これにより、以下のメリットが生まれます。

  1. 隙間なく詰め込める: メモリの無駄遣いがほぼゼロになります。
  2. バッチサイズが増える: メモリを効率的に使える分、一度に処理できるリクエスト数(バッチサイズ)を増やせます。
  3. スループット向上: バッチサイズが増えれば、単位時間あたりに処理できるトークン数(スループット)が劇的に向上します。

つまり、vLLMを使う最大の意義は、「高度なメモリ管理と最適化されたスケジューリングにより、計算資源の稼働率を限界まで高める」点にあります。これを理解した上で、具体的な設定を見ていきましょう。

Tip 1: gpu-memory-utilizationの設定は「攻め」と「守り」で使い分ける

vLLMを起動する際、最も頻繁に調整し、かつトラブルの原因になりやすいのが --gpu-memory-utilization というパラメータです。これは「GPUの全メモリのうち、vLLMがどれだけを確保(予約)するか」を決める数値です。

デフォルト0.90の意味とは

特に指定しない場合、vLLMはデフォルトでGPUメモリの 90% (0.90) を確保しようとします。これはかなりアグレッシブな設定です。例えば24GBのメモリを持つGPUなら、約21.6GBをvLLMが占有し、残りの2.4GBしかOSや他のプロセスに残しません。

この設定の意図は、「可能な限りKVキャッシュ用の領域を確保して、スループットを最大化したい」という設計思想にあります。しかし、これが裏目に出るケースがあります。

他プロセスとの共存時は0.5〜0.7へ下げる勇気

開発環境や、1つのGPUサーバー上でEmbedding(埋め込み)モデルやWebサーバー、あるいはDBなどを同居させている場合、デフォルトの0.90では他のプロセスがメモリ不足で落ちるか、vLLM自体の起動に失敗することがあります。

このような「同居環境」では、「守り」の設定が必要です。

# 開発環境や他プロセスと同居する場合の例
python -m vllm.entrypoints.api_server \
    --model <model-name> \
    --gpu-memory-utilization 0.6

0.6(60%)程度に設定しておけば、残りの40%を他の用途に使えます。もちろん、確保できるKVキャッシュの量は減るため、同時に処理できるリクエスト数は減りますが、OOMでサービスが停止するリスクは回避できます。

専用インスタンスなら0.95まで攻める

逆に、本番環境でそのGPUインスタンスをvLLM専用に割り当てている場合は、「攻め」の設定が有効です。

# 本番専用サーバーの場合の例
python -m vllm.entrypoints.api_server \
    --model <model-name> \
    --gpu-memory-utilization 0.95

PyTorchの現行バージョンや最新のCUDA環境においても、フレームワークのオーバーヘッドやコンテキスト分を考慮すると、一般的に0.95程度までは安全に設定できるケースが大半です。この5%の差が、高負荷時に「あと1リクエスト捌けるかどうか」の差につながり、スループット全体に効いてきます。

ただし、最新のGPUアーキテクチャや、FP8などの新しい精度サポートを利用する場合は注意が必要です。最新のハードウェア環境やNightlyビルド(開発版)のPyTorchを使用する際は、ドライバやCUDAバージョンの整合性がシビアになる場合があり、予期せぬメモリ挙動を示すことがあります。

ポイント: 環境が「シェアハウス(開発機)」なのか「一人暮らし(専用機)」なのかによって、この数値を明確に使い分けましょう。特にOSやライブラリを最新版にアップデートした直後は、まずはデフォルトの0.90で安定稼働を確認し、そこから0.95へ引き上げる段階的なアプローチを推奨します。

Tip 2: スループット重視なら「バッチサイズ」を最大化せよ

Tip 1: gpu-memory-utilizationの設定は「攻め」と「守り」で使い分ける - Section Image

LLMのパフォーマンス指標には、「レイテンシ(応答速度)」と「スループット(処理量)」の2つがあります。

  • レイテンシ: 1人のユーザーが質問してから回答が返ってくるまでの時間。
  • スループット: サーバー全体で1秒間に何トークンを処理できたか。

チャットボットのような対話型アプリではレイテンシが重要視されますが、社内文書の要約やデータ抽出など、バックグラウンド処理が多いタスクではスループットが圧倒的に重要です。

レイテンシとスループットのトレードオフ

vLLMは Continuous Batching(連続バッチ処理) という技術を採用しています。これは、先に終わったリクエストがあれば、次のリクエストを即座に割り込ませて計算リソースを埋める仕組みです。

この効果を最大化するには、サーバーに対して並列にリクエストを投げ込み、バッチサイズを大きく保つことが重要です。バッチサイズが大きくなると、個々のリクエストのレイテンシは若干伸びる(待たされる時間が増える)可能性がありますが、システム全体のスループットは向上します。

Continuous Batchingの効果を引き出すパラメータ

デフォルト設定でもvLLMはうまく機能しますが、明示的に並列数を制御したい場合は --max-num-seqs を調整します。

# 同時に処理するシーケンス(リクエスト)数の最大値を設定
python -m vllm.entrypoints.api_server \
    --model <model-name> \
    --max-num-seqs 256

デフォルトは256であることが多いですが、メモリに余裕があり、かつ小さなモデルを使っている場合は、これを増やすことでGPUの使用率(Compute Utilization)を100%に近づけることができます。

逆に、レイテンシを最優先したい(ユーザーを待たせたくない)場合は、あえてこの数値を下げて、同時処理数を制限するというアプローチもあります。しかし、コスト対効果を考えるなら、「ユーザーが許容できるギリギリのレイテンシを維持しつつ、スループット(同時処理数)を最大化する」のが最適解です。

Tip 3: メモリ不足の救世主「AWQ量子化」を積極的に選ぶ

「GPUメモリが足りない、でも新しいGPUを買う予算はない」。そんな時の切り札が量子化(Quantization)です。

通常、LLMはFP16(16ビット浮動小数点)で動かしますが、これを4ビットや8ビットに圧縮することで、モデルサイズを劇的に小さくできます。vLLMは特に AWQ (Activation-aware Weight Quantization) という量子化手法と相性が抜群です。

モデルサイズを1/2以下にして速度を倍にする

例えば、70億パラメータ(7B)のモデルをFP16でロードすると約14GBのVRAMが必要ですが、4bit AWQ量子化モデルなら約5〜6GBで済みます。空いたメモリはすべてKVキャッシュに回せるため、バッチサイズを大幅に増やせます。

さらに、メモリアクセス量が減るため、メモリ帯域がボトルネックになりがちな推論処理においては、実行速度そのものも向上します。

vLLMと相性の良い量子化形式

Hugging Faceには量子化モデルが多数公開されていますが、vLLMで使うならモデル名に -AWQ がついているものを選ぶのが実証的にも有効です。

# AWQ量子化モデルを使用する場合
python -m vllm.entrypoints.api_server \
    --model TheBloke/Llama-2-7b-Chat-AWQ \
    --quantization awq

かつてはGPTQが主流でしたが、vLLMにおいてはAWQの方が推論カーネルの最適化が進んでおり、高速に動作する傾向があります。精度劣化も実用上ほとんど気にならないレベルです。「まずはFP16で」とこだわりすぎず、リソース制約があるなら迷わずAWQを試すべきです。

Tip 4: プロンプト長と生成長の「最大値」を制限して安定させる

Tip 3: メモリ不足の救世主「AWQ量子化」を積極的に選ぶ - Section Image

意外と見落としがちなのが、--max-model-len の設定です。これはモデルが扱えるコンテキスト長(入力プロンプト+生成される出力の最大トークン数)の上限を指定します。

max_model_lenの罠

最近のモデルは、32k(約32,000トークン)や128kといった非常に長いコンテキストに対応しています。しかし、vLLMの設定でこれをそのまま許容すると、システムは潜在的に巨大なメモリ消費に備える必要があります。

vLLMの中核技術であるPagedAttentionは、KVキャッシュをブロック単位で分割し、必要な分だけ動的にメモリを割り当てることで断片化を防ぎます。
さらに、最新バージョンでは、アーキテクチャが「V1」へと完全に移行しました。これにより、リクエスト管理単位の最適化やChunked Prefill(プレフィルの分割処理)の強化が行われ、スケジューリング効率は飛躍的に向上しています。また、FP8 KVキャッシングのような新機能により、キャッシュ自体のメモリ使用量を削減することも可能になりました。

しかし、どれほどソフトウェア側で管理が効率化されても、物理的なGPUメモリの上限は変わりません
もし、対象のアプリケーションが「カスタマーサポートのチャットボット」で、過去の履歴を含めてもせいぜい4,000トークンしか使わないのであれば、32kや128kのコンテキスト枠を空けておくのは、リスク管理の観点からもリソース配分の観点からも得策ではありません。意図しない長文リクエストが1つ入るだけで、KVキャッシュが急激に消費され、他の並列リクエストが処理待ち(Preemption)に追い込まれる可能性があるからです。

コンテキストウィンドウを使い切らない運用設計

実運用のユースケースに合わせて、この値を現実的なラインまで下げましょう。最新のvLLMでは serve コマンドを使用するのが一般的です。

# 最大コンテキスト長を4096トークンに制限して起動
vllm serve <model-name> \
    --max-model-len 4096

このように上限を明示的に制限することで、vLLMはより安定したメモリ計画を立てることができます。結果として、1リクエストあたりのメモリ消費リスクが下がり、より多くのリクエストを同時に捌ける(バッチサイズが増加する) ようになります。

特に最新のV1アーキテクチャでは、こうした設定を行うことでスケジューラの予測精度が高まり、システム全体のスループット向上に直結します。「大は小を兼ねる」と言いますが、GPUメモリにおいては「大はリソースを食いつぶす」です。必要なサイズに切り詰めることが、高スループットと安定稼働を両立させる秘訣です。

Tip 5: 非同期エンジン(AsyncLLMEngine)でサーバーとして稼働させる

最後に実装面の話です。vLLMをPythonスクリプトから使う際、単純に LLM クラスを使って同期的に処理していませんか?

# 悪い例:同期処理(これではWebサーバーとしてスケールしない)
llm = LLM(model="...")
outputs = llm.generate(prompts)

これはいわゆる「オフライン推論」向けの書き方です。バッチ処理には向いていますが、Web APIとしてリクエストを次々に受け付ける「オンライン推論」には不向きです。

Pythonスクリプト実行からAPIサーバー化へ

アプリケーションに組み込む場合は、AsyncLLMEngine を使うか、vLLMが標準で提供しているOpenAI互換のAPIサーバー機能を使いましょう。

最も簡単なのは、以下のコマンドでAPIサーバーとして立ち上げてしまうことです。

python -m vllm.entrypoints.openai.api_server \
    --model <model-name> \
    --port 8000

こうすれば、クライアント側(アプリ側)からは標準的なOpenAIのライブラリを使ってアクセスできます。

# クライアント側のコード例
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="EMPTY",
)

completion = client.chat.completions.create(
    model="<model-name>",
    messages=[...]
)

この構成にすることで、vLLMの内部で非同期処理が効率的に行われ、Continuous Batchingの恩恵を最大限に受けられます。アプリのロジックと推論エンジンを分離できるため、アーキテクチャとしても疎結合になり、メンテナンス性が向上します。

まとめ:まずはデフォルト設定+αから始めよう

Tip 4: プロンプト長と生成長の「最大値」を制限して安定させる - Section Image 3

vLLMの進化は非常に速く、最新のアーキテクチャではスケジューリング機能が刷新され、Prefill(プロンプト処理)とDecode(生成処理)の効率が大幅に統合・強化されています。デフォルト設定でも十分に高速ですが、環境と用途に合わせて「もう一歩」踏み込んだ設定を行うことで、そのポテンシャルを最大限に引き出すことが可能です。

今回解説した5つのポイントを、最新の技術動向も交えて振り返りましょう。

  1. メモリ断片化を知る: PagedAttentionによるメモリ管理は、現在もvLLMのパフォーマンスを支える核心です。OSの仮想メモリのような仕組みで、無駄なくGPUメモリを活用します。
  2. GPU利用率 (--gpu-memory-utilization): 他のプロセスと共存なら0.6、専用サーバーなら0.90〜0.95が目安です。まずはここを調整してOOMを防ぎましょう。
  3. バッチサイズとスケジューリング: 最新のvLLMではリクエスト単位の管理(V1アーキテクチャ)が進み、待ち時間を最小化するよう最適化されています。スループット重視の設定がより効果を発揮しやすくなっています。
  4. 量子化 (--quantization): メモリ制約がある場合、AWQなどが定石ですが、最新環境ではFP8 KVキャッシングのサポートにより、大規模モデルでも効率的な推論が可能になっています。
  5. コンテキスト長 (--max-model-len): 必要なトークン長だけを確保し、KVキャッシュの領域を最大化することが安定稼働の鍵です。

過度なチューニングより計測が先

まずは、お手元の環境で gpu-memory-utilizationmax-model-len を見直すことから始めてみてください。これらを適切に設定するだけで、エラーの頻度が減り、処理速度の改善を体感できるはずです。

推論の最適化は奥が深く、これらを極めた先には「マルチGPU分散推論」や、最新バージョンで強化された「Chunked Prefill(チャンク化されたプレフィル)」によるレイテンシ改善といった、さらに高度な技術も待っています。特に新しいアーキテクチャでは、これらの機能がより使いやすく統合されています。

ですが、焦る必要はありません。まずは足元の設定を固め、実際に計測し、効果を実証することが第一歩です。この記事が、AIアプリケーションを次のステージへ進めるための論理的かつ実践的な指針となれば幸いです。

GPU追加購入の前に試すべきvLLM設定5選:メモリ断片化解消とスループット最大化の定石 - Conclusion Image

参考リンク

コメント

コメントは1週間で消えます
コメントを読み込み中...