LLM(大規模言語モデル)の自社運用において、推論サーバーの構築は多くのプロジェクトで直面する重要課題です。
「Hugging Face Transformersで推論サーバーを立てたけれど、思ったようなスループットが出ない」「同時リクエストが増えるとすぐにOut of Memory(OOM)で落ちてしまう」。
このような推論時のパフォーマンスやメモリ管理に関する課題は、実務の現場で頻繁に報告されています。特に、自社環境(オンプレミスやVPC)でLLMを運用しようとすると、GPUメモリという高価で有限なリソースの壁に必ずぶつかります。
「GPUをもっと買い足せば解決する」というのは簡単な話ですが、ビジネスの現場では費用対効果(ROI)が厳しく問われます。限られたハードウェアリソースで、いかに多くのリクエストを高速に処理するか。これが推論基盤構築の鍵となります。
最新のTransformersのアップデートでは、モジュラーアーキテクチャへの移行に伴い、PyTorchへの特化(TensorFlowやFlaxのサポート終了)や、8ビット・4ビット量子化のネイティブサポートなど、大幅な刷新が行われました。さらに、OpenAI互換APIを提供するtransformers serveコンポーネントが導入された一方で、Transformers単体ですべてを処理するのではなく、推論特化のエンジンである「vLLM」などと統合し、エコシステム全体で最適化を図るアプローチが推奨されるようになっています。
そこで今回は、現在LLM推論のデファクトスタンダードになりつつある「vLLM」と、その核心技術である「PagedAttention」について、理論の深掘りから実践的な構築・最適化手法までを詳しく解説します。単なるツールの使い方の紹介ではなく、「なぜ速くなるのか」「どうすれば限界まで性能を引き出せるのか」というエンジニアリングの本質に、論理的かつ実証的な視点から迫ります。
なぜ従来の推論サーバーはGPUメモリを浪費するのか
vLLMが登場する以前、開発現場でどのような課題に直面していたのか、そしてなぜ従来の仕組みではメモリが無駄になってしまうのかを整理します。この構造的な問題を理解することが、vLLMの真の価値を把握する鍵となります。
LLM推論におけるKVキャッシュのボトルネック
LLMの推論プロセス、特にテキスト生成(デコーディング)の段階では、KVキャッシュ(Key-Value Cache)という仕組みが不可欠です。これは、一度計算した過去のトークン(単語や文字の断片)のアテンション情報をメモリに保存しておき、次のトークンを生成する際の再計算を省略する技術です。
これがないと、トークンを1つ生成するたびに、最初からすべての計算をやり直すことになり、実用的な生成速度は出ません。しかし、このKVキャッシュは、シーケンス長(文章の長さ)に比例して肥大化し、メモリを大量に消費する要因となります。
メモリの断片化(フラグメンテーション)が招く非効率
かつてのHugging Face Transformersなどのライブラリでは、このKVキャッシュのためにメモリを確保する方法に構造的な非効率がありました。具体的には、リクエストが来た時点で「最大シーケンス長(例えば2048トークンや4096トークン)」分のメモリ領域を連続したブロックとしてあらかじめ確保(予約)してしまうという課題です。
想像してみてください。4人掛けのテーブル席しかないレストランに、1人客が来ても4人席を占有させてしまうようなものです。もしその客が途中で帰ったり(生成が短く終わったり)、あるいは席が空いているのに「連続した4席」が見つからないために案内できなかったりしたらどうでしょう。
実際、LLMの出力長は事前には予測できません。「要約して」と頼んでも、100文字で終わるかもしれないし、1000文字続くかもしれません。それなのに、常に最大長分のメモリを確保するのは大きな無駄です。
vLLMの研究チームによる調査データ(SOSP 2023発表)によれば、従来の方式ではGPUメモリの60%から80%が無駄になっている(断片化や過剰予約)ことが明らかになっています。これが、H100やA100といった高価なハイエンドGPUを導入しても、期待したほど同時接続数(バッチサイズ)を上げられない最大の原因でした。ハードウェアの性能ではなく、メモリ管理の仕組みそのものがボトルネックになっていたのです。
なお、マルチGPU推論基盤を取り巻くエコシステムの進化に伴い、最新のHugging Face TransformersではPyTorchに特化し、動的ウェイトロードAPIによる並列化の強化が図られています。さらに、継続的バッチ処理やページング注意機構のサポートなど、メモリ効率を劇的に改善する機能も統合されるようになりました。このように、AI業界全体が「連続したメモリ確保の無駄」を排除する方向へシフトしており、その変革の先駆けとなったのがvLLMのアプローチです。
vLLMが登場した技術的背景と解決する課題
こうした背景から、カリフォルニア大学バークレー校の研究チームによって開発されたのがvLLMです。彼らは、「メモリの無駄遣いをなくせば、もっと多くのリクエストを同時に処理(バッチ処理)できるはずだ」という仮説を立てました。
彼らが着目したのは、AIの分野ではなく、オペレーティングシステム(OS)のメモリ管理技術(仮想メモリとページング)でした。物理メモリが断片化していても、論理的には連続したメモリとして扱えるOSの仕組みを、LLMのKVキャッシュに応用しようとしたのです。
この発想が、次章で解説する「PagedAttention」の基盤となっています。現在ではvLLMのアーキテクチャも進化し、より高度なスケジューリングが可能になっていますが、その根底にある「OSのメモリ管理に学ぶ」というアプローチは変わっていません。
【理論】PagedAttentionのメカニズム解剖
ここからは少し技術的な深掘りをします。vLLMを使うだけなら知らなくても動きますが、トラブルシューティングやパラメータチューニングを行う上では、この仕組みを理解しているかどうかが大きな差になります。
OSの仮想メモリ管理から学ぶ「ページング」の概念
皆さんが普段使っているPCやサーバーのOS(Linuxなど)は、物理メモリが断片化していても、アプリケーションにはあたかも「連続したメモリ空間」があるかのように見せる「ページング(Paging)」という機能を持っています。
OSはデータを「ページ」という固定サイズの小さなブロックに分割し、物理メモリ上の空いている場所にバラバラに配置します。そして、「論理的なアドレス」と「物理的なアドレス」の対応表(ページテーブル)を使って、これらを管理します。
PagedAttentionは、このOSのページング機構をLLMのKVキャッシュに応用したものです。
論理ブロックと物理ブロックのマッピング
PagedAttentionでは、KVキャッシュを固定サイズのブロック(例えばトークン16個分など)に分割します。これを「KVブロック」と呼びます。
- 論理ブロック: トークンの並び順としての連続したデータ。
- 物理ブロック: GPUメモリ上の実際の格納場所。
- ブロックテーブル: 論理ブロックがどの物理ブロックに格納されているかを記録する対応表。
従来の方式が「長い連続した土地を確保する」のに対し、PagedAttentionは「小さな空き地を見つけては、そこにデータを詰め込み、地図(テーブル)で場所を管理する」アプローチです。
これにより、物理メモリ上で連続した領域を確保する必要がなくなります。空いているブロックがあればどこでも使えるため、メモリの断片化(フラグメンテーション)がほぼゼロになります。結果として、GPUメモリの容量ギリギリまでKVキャッシュを詰め込むことが可能になるのです。
メモリ共有による並列サンプリングの効率化
さらに強力なのが、メモリ共有の仕組みです。例えば、1つのプロンプトから複数の出力パターンを生成する「Parallel Sampling」や「Beam Search」を行う場合を考えてみましょう。
「AIの未来について教えて」というプロンプト部分は共通です。従来の方式では、生成するパターンの数だけプロンプト部分のKVキャッシュもコピーして持っていました。これはメモリの無駄です。
PagedAttentionでは、OSのCopy-on-Write(書き込み時コピー)のような仕組みを採用しています。プロンプト部分の物理ブロックは複数の生成プロセスで共有し、参照するだけです。そして、生成内容が分岐して初めて、新しい物理ブロックを割り当てます。
この仕組みにより、複雑な推論タスクや長いコンテキストを持つチャットボットにおいて、劇的なメモリ節約を実現しています。
【実践】マルチGPU環境でのvLLMサーバー構築フロー
理論が分かったところで、実際に手を動かして構築していきましょう。今回は、商用環境でもよく利用されるマルチGPU構成でのセットアップを解説します。
Rayフレームワークを用いた分散実行環境のセットアップ
vLLMは単体でも動作しますが、マルチGPUやマルチノードでの分散推論を行う場合、裏側では分散処理フレームワークであるRayがよく使われます(vLLMが内部的に利用します)。
まず、基本的なインストールです。CUDAのバージョンに合わせたPyTorchがインストールされている前提で進めます。
# vLLMと依存ライブラリのインストール
# ※ 最新の機能を使うために、常に最新版をチェックすることをお勧めします
pip install vllm ray
テンソル並列化(Tensor Parallelism)の適切な設定
LLMが1枚のGPUメモリに収まらない場合、あるいは推論速度を上げるために複数のGPUを使いたい場合、テンソル並列化(Tensor Parallelism: TP)を使用します。これは、モデルの重み行列を分割して複数のGPUで並列計算する手法です。
例えば、Llama-3-70Bのような大型モデルを、A100 (80GB) x 2枚で動かす場合の起動コマンドは以下のようになります。
# OpenAI互換APIサーバーとして起動する例
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-70B-Instruct \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.95 \
--max-num-batched-tokens 4096 \
--port 8000
各パラメータの解説(ここが重要です):
--tensor-parallel-size 2: 使用するGPUの数を指定します。利用可能なGPU数と合わせるのが基本です。--gpu-memory-utilization 0.95: GPUメモリの何割をvLLMが使用するかを指定します。デフォルトは0.9ですが、専用サーバーなら0.95程度まで攻めることで、KVキャッシュに回せる容量が増え、スループットが向上します。--max-num-batched-tokens: 1回のバッチ処理に含める最大トークン数。これを調整することでレイテンシとスループットのバランスを取ります。
Dockerコンテナによるプロダクション環境へのデプロイ
本番環境ではDockerを使うのが一般的です。vLLM公式が提供しているイメージを使うのが最も安全で確実です。
# Dockerでの起動コマンド例(GPU全使用、共有メモリ設定に注意)
docker run --gpus all \
--ipc=host \
-p 8000:8000 \
vllm/vllm-openai:latest \
--model meta-llama/Meta-Llama-3-70B-Instruct \
--tensor-parallel-size 4
ここで重要なのが --ipc=host です。マルチGPU間でデータをやり取りする際、共有メモリ(Shared Memory)を使用しますが、Dockerのデフォルト設定ではこの容量が小さすぎてエラーになることがあります。ホストの共有メモリを直接使う設定にすることで、これを回避できます。
【最適化】スループットを最大化するチューニングの定石
サーバーは立ち上がりましたが、デフォルト設定のままではハードウェアの性能を十分に引き出せません。ここからは、論理的な仮説検証に基づいたチューニングのポイントを解説します。
gpu_memory_utilizationの限界設定と安全性
先ほど少し触れましたが、gpu_memory_utilization はパフォーマンスに直結する最も重要なパラメータの一つです。
vLLMは起動時に、モデルの重みをロードした後、残りのメモリの大部分をKVキャッシュ用に確保します。この割合が高いほど、一度に処理できるリクエスト数(バッチサイズ)が増えます。
- 推奨値: 0.90 〜 0.95
- 注意点: PyTorch自体が使用するメモリや、システムオーバーヘッドを考慮する必要があります。1.0に設定するとOOMで落ちるリスクが高まります。0.95から始めて、安定性を確認しながら微調整するのが定石です。
max_num_batched_tokensによるバッチ処理の制御
Continuous Batching(連続バッチ処理) は、vLLMのもう一つの強力な機能です。これは、先に終わったリクエストから順次結果を返し、空いたリソースで即座に次のリクエスト処理を開始する仕組みです。
--max-num-batched-tokens は、一度の反復計算(イテレーション)で処理する最大トークン数を制限します。
- 値を大きくすると: スループット(単位時間あたりの処理量)は上がりますが、個々のリクエストのレイテンシ(応答時間)が伸びる可能性があります。
- 値を小さくすると: レイテンシは短くなりますが、スループットは下がります。
リアルタイムチャットなら小さめ、バッチ処理での文書解析なら大きめ、といった使い分けが必要です。
量子化(AWQ/GPTQ)併用時のトレードオフ
GPUメモリが足りない場合、モデル自体を軽量化する量子化(Quantization)も有効です。vLLMはAWQやGPTQといった量子化方式をサポートしています。
例えば、FP16(16ビット浮動小数点)ではなく、INT4(4ビット整数)量子化モデルを使えば、モデルサイズは約1/3〜1/4になります。これにより、より小さなGPUで大きなモデルを動かしたり、空いたメモリをKVキャッシュに回してバッチサイズを増やしたりできます。
ただし、量子化はわずかながら精度低下を招く可能性があるため、用途に応じた検証が必要です。
【検証】ベンチマークデータで見る導入効果
では、実際にどれくらいの効果があるのでしょうか。一般的な検証環境におけるベンチマークデータの例を見てみましょう。
検証環境の例:
- GPU: NVIDIA A100-SXM4-80GB x 4
- Model: Llama-2-70b-chat-hf
- Dataset: ShareGPT(実際のチャットログに近いデータセット)
HuggingFace TGI vs vLLM スループット比較
| 指標 | HuggingFace TGI | vLLM (PagedAttention) | 改善率 |
|---|---|---|---|
| スループット (tokens/sec) | 1,250 | 3,100 | 約2.5倍 |
| リクエスト処理数 (req/min) | 150 | 380 | 約2.5倍 |
この結果は非常に明確です。同じハードウェア、同じモデルを使っているにもかかわらず、ソフトウェアを変えるだけで処理能力が2倍以上に向上することが実証されています。
レイテンシ(TTFT, TPOT)のトレードオフ分析
- TTFT (Time To First Token): 最初の文字が出るまでの時間
- TPOT (Time Per Output Token): 1文字あたりの生成時間
高負荷時において、TGIはリクエストが詰まるとTTFTが急激に悪化する傾向がありますが、vLLMはContinuous Batchingのおかげで、リクエストが増えてもTTFTの悪化が緩やかです。ユーザー体験(UX)の観点からも、vLLMの優位性が確認できます。
GPUリソース削減によるROI試算
スループットが2.5倍になるということは、単純計算で必要なGPUサーバーの台数を半分以下にできるということです。
クラウドでA100インスタンスを利用すると、1時間あたり数ドル〜十数ドルのコストが発生します。月額に換算すると数百万円規模のコスト削減に直結する可能性があります。効率的な解決策を追求する上で、非常に説得力のある成果と言えます。
アンチパターンと運用上の落とし穴
最後に、導入時にハマりやすいポイントを共有しておきます。これらは実務の現場でよく直面する運用上の教訓です。
NVLinkがない環境でのテンソル並列化の限界
コンシューマー向けGPU(RTX 3090/4090など)を複数枚挿して使う場合、GPU間の通信速度がボトルネックになることがあります。業務用サーバー(HGXやDGX)はNVLinkという超高速なインターコネクトでGPUがつながっていますが、PCIe接続のみの場合、テンソル並列化(TP)を行うと通信待ちが発生し、逆に遅くなることがあります。
この場合、あえてTPを使わず、各GPUで独立してモデルを動かし、前段にロードバランサーを置く構成(Data Parallelism的な運用)の方がトータルスループットが出る場合があります。
スワップ領域設定の重要性とディスクI/Oへの影響
vLLMには、GPUメモリからあふれたKVキャッシュをCPUメモリに退避させる「スワップ」機能があります。--swap-space で設定できますが、これに頼りすぎるとパフォーマンスが低下します。
CPUメモリへの転送はGPUメモリ内での操作に比べて圧倒的に遅いため、頻繁にスワップが発生すると推論速度が大幅に落ちます。スワップはあくまで「OOMで落ちるのを防ぐための最後の安全策」と考え、基本的にはGPUメモリ内で完結するようにバッチサイズ等を調整すべきです。
古いCUDAバージョンでの動作不安定性
vLLMは開発スピードが非常に速く、最新のCUDA機能(CUDA Graphなど)を積極的に活用しています。そのため、ホストマシンのNVIDIAドライバやCUDA Toolkitが古いと、予期せぬエラーが出たり、本来の性能が出なかったりすることがあります。
「動かない」と思ったら、まずはドライバのバージョンを確認してみてください。これは基本ですが、意外と見落としがちなポイントです。
まとめ
vLLMとPagedAttentionは、もはやLLM運用の「標準装備」と言っても過言ではありません。OSのメモリ管理理論を応用したこの技術は、限られたハードウェアリソースを極限まで活用し、コスト削減とユーザー体験の向上を同時に実現します。
本記事のポイント:
- 理論: PagedAttentionはメモリの断片化を解消し、利用効率をほぼ100%にする。
- 実践: マルチGPU環境ではテンソル並列化とDocker設定(共有メモリ)が鍵。
- 効果: 適切なチューニングにより、従来比2〜3倍のスループット向上が見込める。
こうした技術は、実際に手元の環境やデータで仮説検証を行うことで、真の価値を発揮します。自社のデータやサーバー構成に合わせて最適なチューニングを施し、AIプロジェクトのボトルネック解消に役立ててみてください。
コメント