ここまでの連載(第1回〜第4回)では、Ollamaを用いた手軽なAPI構築や、llama.cppによるリソース制約の厳しい環境(CPU/低VRAM)での限界チューニングについて解説してきました。これらは検証環境や少人数での利用、あるいはエッジデバイスでの稼働において非常に優れた選択肢となります。
しかし、生成AIのプロジェクトがPoC(概念実証)フェーズを終え、社内の全従業員が利用する全社AIチャットボットシステムや、大量のドキュメントをバッチ処理するRAG(検索拡張生成)のバックエンドなど、「数十〜数百の同時アクセス(高並行トラフィック)」を捌く本番環境へと移行する際、従来の推論エンジンは深刻なパフォーマンスの壁に直面します。
リナックス先生、前回構築したOllama環境を社内の10人くらいにテストで使ってもらったんですが、「自分一人で使っている時は速いのに、他の人とタイミングが被ると極端にレスポンスが遅くなる」とクレームが来てしまいました。サーバーのCPUもGPUもまだ余裕があるはずなのに、なぜ急に遅くなるんでしょうか?
それは非常に良い気づきです。Ollama(裏で動くllama.cppなど)は「バッチサイズ1(単一ユーザー)」の処理レイテンシを極限まで下げることに特化したアーキテクチャだからです。複数のリクエストが同時に来ると、VRAM内のメモリの断片化が発生し、順番待ち(キューイング)が起きてしまいます。エンタープライズの本番環境で大規模トラフィックを捌くためには、OSの仮想メモリ管理にヒントを得た「vLLM」という全く異なるアーキテクチャの推論エンジンが必要になります。
本連載「LinuxエンジニアのためのローカルLLM」第5回では、カリフォルニア大学バークレー校(UC Berkeley)の研究から生まれ、現在エンタープライズのローカルLLM運用におけるデファクトスタンダードとなっている推論エンジン「vLLM」をAlmaLinux 9環境に構築し、インフラエンジニアの視点からスループットを最大化するための高度なチューニング手法を徹底解説します。
📚 本連載のカリキュラム(全8回)
- 【第1回】LinuxエンジニアのためのローカルLLM入門と全体像
- 【第2回】AlmaLinux 9/10の初期構築とNVIDIA CUDA環境の最適化
- 【第3回】Ollamaを使ったDockerライクなLLM導入と管理
- 【第4回】llama.cppによるCPU/低VRAM環境でのモデル推論
- 【第5回】vLLMを利用した本番向け高スループット推論サーバーの構築(本記事)
- 【第6回】RAG(検索拡張生成)基盤の構築とベクトルデータベースの連携
- 【第7回】ローカルLLM環境の監視(Prometheus/Grafana)とセキュリティ設計
- 【第8回】エンタープライズ向け運用ベストプラクティスとトラブルシューティング
目次
1. LLM推論のメモリ枯渇問題と「KVキャッシュ」の正体
複数ユーザーからの同時リクエストを捌くシステム(高スループットシステム)を設計するにあたり、まずはLLM特有のメモリ消費メカニズムである「KVキャッシュ(Key-Value Cache)」について深く理解する必要があります。これがインフラのボトルネックの根源だからです。
1.1. 生成AIは「過去の記憶」をメモリにため込む
LLMが文章を生成する際、1文字(正しくは1トークン)を出力するたびに、入力されたプロンプトとこれまで生成した全てのトークンを自己アテンション(Self-Attention)機構で再計算します。しかし、毎回最初から計算し直すのは極めて非効率であるため、LLMは過去に計算した途中結果(KeyテンソルとValueテンソル)をGPUのVRAMにキャッシュとして保持しながら次のトークンを予測します。
これが「KVキャッシュ」です。重要なのは、KVキャッシュはユーザー(リクエスト)ごとに独立して確保されるという点です。モデルの重み(Weight)自体は全ユーザーで共有できますが、KVキャッシュは同時アクセスする人数分だけVRAM上に動的に膨れ上がっていきます。
1.2. VRAMの断片化(フラグメンテーション)問題
従来の推論エンジンでは、ユーザーからのリクエストを受け取った際、「このユーザーは最大でどれくらい長い文章を生成するかわからない」ため、念のためにコンテキストの最大長(例:4096トークン分)のKVキャッシュ領域を連続したメモリ空間としてVRAM上に事前確保(Pre-allocation)していました。
あ!それって、Linuxのメモリ管理でいうところの「メモリの断片化(フラグメンテーション)」と同じ状態じゃないですか?
その通りです、コウ君。事前確保したものの、ユーザーが「こんにちは」と短い会話で終わってしまった場合、確保した巨大なVRAM領域のほとんどが「無駄な空き地」になります。この無駄遣いと断片化によって、VRAMの60%〜80%が有効活用されず、結果として同時に捌ける人数(バッチサイズ)が数人程度で頭打ちになっていたのです。
2. PagedAttention:OSのページング技術をAIに応用したvLLMの革命
このKVキャッシュの断片化という致命的なインフラ課題を、OS(オペレーティングシステム)の古典的な仮想メモリ管理の仕組みを応用して解決したのが、vLLMのコア技術である「PagedAttention(ページドアテンション)」です。
2.1. OSのページングとPagedAttentionの共通点
LinuxなどのOSは、プロセスに対して物理メモリを連続した塊として割り当てるのではなく、「ページ」と呼ばれる固定長(通常4KB)の小さなブロックに分割し、仮想アドレスと物理アドレスをページテーブルでマッピングして管理しています。これにより、メモリの断片化を防ぎ、効率的なメモリの利用を可能にしています。
vLLMの「PagedAttention」は、このOSのページングの概念をそのままLLMのKVキャッシュ管理に持ち込みました。
- ユーザーのKVキャッシュを連続した巨大なVRAM領域として確保するのではなく、固定長の「ブロック(例:16トークン分のキャッシュ)」に分割します。
- トークンが生成されるたびに、必要なブロックだけを動的にVRAM上に割り当てます。
- これらのブロックはVRAM上のどこに散らばっていても良く、vLLM内の「ブロックテーブル(ページテーブルに相当)」が論理的な連続性を保証します。
2.2. 高スループット(並行バッチ処理)の実現
PagedAttentionの導入により、VRAMの無駄な事前割り当てがほぼゼロになり、メモリの利用効率が飛躍的に向上しました。これにより、同じGPUリソースであっても、より多くのユーザーのKVキャッシュを同時にVRAMに乗せることができるようになります。
AIモデルは、1人のリクエストを計算するのも、数十人のリクエストを行列としてまとめて計算(並行バッチ処理)するのも、GPUの演算器の稼働効率の観点ではほとんど同じ時間を要します。つまり、VRAMに多くの人の状態(KVキャッシュ)を保持できればできるほど、1秒間に処理できるシステム全体のトークン数(=スループット)は劇的にスケールするのです。これが、vLLMがエンタープライズ本番環境で「必須」とされる理由です。
3. AlmaLinuxでのvLLM環境構築:PodmanとAWQ量子化モデルの準備
それでは、AlmaLinux 9上にvLLMをデプロイしていきましょう。vLLMはCUDA(NVIDIA環境)への深い最適化が行われているため、今回はPythonライブラリを直接インストールするのではなく、環境が完全に隔離され最適化された公式のDockerコンテナイメージを利用し、AlmaLinux標準のPodman(Quadlet)でサービス化します。
3.1. vLLMに最適な量子化フォーマット「AWQ」の選定
第4回で利用した「llama.cpp」はCPUや低VRAMに強い「GGUF」フォーマットを採用していましたが、GPUをフル活用して最高のスループットを出すvLLM環境においては「AWQ(Activation-aware Weight Quantization)」または「GPTQ」と呼ばれる、GPUの並列計算に特化した4ビット量子化フォーマットを利用するのが現在のベストプラクティスです。
Hugging Faceから、Llama 3 (8B) のAWQ量子化モデルをダウンロードしておきます。
# モデル保存ディレクトリの準備
mkdir -p /opt/ai-models/
cd /opt/ai-models/
# Hugging Face CLIを用いてAWQモデルをダウンロード
huggingface-cli download \
casperhansen/llama-3-8b-instruct-awq \
--local-dir /opt/ai-models/llama-3-8b-instruct-awq \
--local-dir-use-symlinks False
3.2. QuadletによるvLLMのコンテナ・デーモン化
第3回のOllama構築時と同様に、PodmanのQuadlet機能を用いてvLLMをsystemdのサービスとして定義します。
cat << 'EOF' | sudo tee /etc/containers/systemd/vllm.container
[Unit]
Description=vLLM High-Throughput Inference Server
After=network-online.target
[Container]
Image=docker.io/vllm/vllm-openai:latest
ContainerName=vllm-server
# ホストの全GPUをコンテナにパススルー
AddDevice=nvidia.com/gpu=all
# vLLMのデフォルトポート8000をローカルにバインド
PublishPort=127.0.0.1:8000:8000
# ホストにダウンロードしたモデルディレクトリをコンテナ内にマウント
Volume=/opt/ai-models:/opt/ai-models:ro
# vLLMの起動コマンドとチューニングパラメータ
Exec=--model /opt/ai-models/llama-3-8b-instruct-awq \
--quantization awq \
--gpu-memory-utilization 0.85 \
--max-model-len 4096 \
--port 8000
[Install]
WantedBy=multi-user.target
EOF
ファイルを作成したら、systemdをリロードして起動します。
systemdの思想と機能 Linuxを支えるシステム管理のためのソフトウェアスイート 【電子書籍】[ 森若 和雄 ] 価格:3080円 |
sudo systemctl daemon-reload
sudo systemctl enable --now vllm.service
sudo journalctl -u vllm.service -f
ログを確認し、Uvicorn running on http://0.0.0.0:8000 というメッセージが出力されれば、vLLMはOpenAI互換の高速APIサーバーとして起動完了です。フロントエンドにNginx等のリバースプロキシを配置する設計は第3回と同様になります。
4. 高スループットを引き出すvLLMのパラメータ・チューニング
vLLMをただ起動するだけでは、ハードウェアの限界性能は引き出せません。インフラエンジニアとして、システムのトラフィック要件とGPUのVRAM容量に合わせた厳密なチューニングを行います。先ほどのQuadlet定義内の Exec= に指定したパラメータの意図を解説します。
⚠️ チューニングの要:VRAMの境界を攻める
vLLMは起動時、モデルの重みをVRAMにロードした上で、残りの空きVRAM領域を可能な限り「KVキャッシュ用」として一括で確保しようとします。これによりPagedAttentionが広大なブロックプールを利用できるようになりますが、設定を誤るとOOM(Out of Memory)でサーバープロセス自体がクラッシュします。
4.1. –gpu-memory-utilization の制御
--gpu-memory-utilization は、vLLMプロセス全体でGPUの総VRAMの何パーセントを使用するかを指定する最も重要なフラグです。
デフォルトは 0.9(90%)ですが、同じGPU上でメトリクス収集用エージェントや他の軽量なプロセスが動いている場合、VRAMが競合してクラッシュする原因になります。
安定運用のベストプラクティスとしては、少し余裕を持たせて 0.85(85%)程度に設定し、監視ツール(nvidia-smi や Prometheus)でVRAM使用状況を見ながらギリギリまで引き上げていくアプローチを取ります。
4.2. –max-model-len の制限(コンテキストサイズ)
vLLMは起動時、モデルがサポートする最大コンテキストサイズ(Llama 3 8Bの場合はデフォルトで8192トークン)を前提に内部のテンソルバッファを初期化します。しかし、VRAM容量が限られているGPU(例えば16GBのRTX 4060 Tiなど)でこの大きなコンテキストを許容しようとすると、vLLMが「確保可能なKVキャッシュブロックが少なすぎて並行バッチ処理ができない」と判断し、エラーを吐いて起動しないことがあります。
そこで、社内チャットボット用途であれば、コンテキストを4096程度に制限します。--max-model-len 4096 と指定することで、1ユーザーあたりの最大会話履歴は制限されますが、その分浮いたVRAM領域を「より多くの同時接続ユーザーのKVキャッシュ」に回すことができ、システム全体のスループットが大幅に向上します。
4.3. スループット(Tokens/sec)の監視手法
チューニングがうまくいったかどうか、どうやって確認すればいいですか? WebアプリならApache Benchみたいなツールがありますが……。
vLLM自体が、稼働状況を外部から監視できるようにメトリクスエンドポイントを持っています。/metrics エンドポイントを叩くことで、Prometheus形式で内部のVRAMブロックの空き状況や、現在のバッチサイズ、スループットなどをリアルタイムに取得できるんですよ。
# vLLMのメトリクス情報を取得
curl -s http://127.0.0.1:8000/metrics | grep vllm:
# 注目すべきメトリクス例
# vllm:gpu_cache_usage_perc (GPUキャッシュの使用率。1.0に近づくとユーザーが順番待ち状態に入る)
# vllm:num_requests_running (現在並行して処理しているリクエスト数 = バッチサイズ)
# vllm:prompt_tokens_total / vllm:generation_tokens_total (トークン生成のスループット)
第7回で詳しく解説しますが、これらの数値をGrafanaでダッシュボード化することで、エンタープライズ水準のAIインフラ監視が実現します。
5. 複数GPUを束ねる「テンソル並列(Tensor Parallelism)」の設計
本番環境において、70Bクラスの巨大なモデル(Llama 3 70Bなど)を動かす、あるいは8Bモデルで数百人の同時アクセスに耐えるためには、1枚のGPUでは物理的な限界があります。データセンターでは、複数のGPU(NVIDIA A100x4枚、RTX 4090×2枚など)を搭載したマルチGPUサーバーを運用します。
vLLMは、複数枚のGPUのVRAMと計算リソースを1つの巨大なGPUとして束ねて利用する分散推論技術「テンソル並列(Tensor Parallelism)」を標準でサポートしています。
5.1. テンソル並列の設定方法
vLLMのコンテナ起動時に、GPUを束ねる枚数を --tensor-parallel-size(または -tp)で指定するだけです。
# QuadletのExec行に追記(例:2枚のGPUを束ねて分散推論を行う)
Exec=--model /opt/ai-models/llama-3-8b-instruct-awq \
--quantization awq \
--tensor-parallel-size 2 \
--port 8000
5.2. インフラ設計上のボトルネック:NVLinkとPCIe
テンソル並列を有効にすると、vLLMはニューラルネットワークの行列計算を各GPUに分割して同時に計算させ、その結果をGPU間で高速に同期(All-Reduce通信)しながら推論を進めます。
ここでインフラエンジニアが絶対に注意すべきなのが、「GPU間の通信帯域幅」です。
- NVLinkブリッジ: GPU同士を専用の物理コネクタ(NVLink)で直接接続している場合、通信速度は数百GB/sに達し、テンソル並列の恩恵を最大限に受けることができます。
- PCIe経由の通信: NVLinkを持たない安価なGPU(RTX 4090など)で複数枚構成を組んだ場合、GPU間の同期通信はマザーボードのPCIeバスとCPUを経由することになります。この場合、通信帯域がボトルネックとなり、「GPUを2枚に増やしたのに、推論速度が1枚の時よりも遅くなる」という逆転現象が頻発します。
インフラのサイジングを行う際は、「単にGPUの枚数を増やせば速くなるわけではない」というハードウェアのインターコネクトの原理原則を理解し、VRAM容量の確保(メモリ分割)と通信オーバーヘッドのトレードオフを厳密にベンチマークする必要があります。
6. まとめと次回の予告
本記事「【第5回】vLLMを利用した本番向け高スループット推論サーバーの構築」では、エンタープライズの商用トラフィックに耐えうるAIインフラの設計として、以下の極めて重要な概念を解説しました。
- KVキャッシュの断片化問題: LLMが過去の文脈を記憶するKVキャッシュの肥大化が、単一ユーザー向けエンジン(Ollama等)の同時アクセス性能を低下させる原因であることを学んだ。
- PagedAttentionの革新性: OSの仮想メモリ管理(ページング)を応用し、VRAMを固定ブロック化して動的に割り当てるvLLMのアーキテクチャを理解した。
- AWQとvLLMコンテナの運用: GPUでの並列処理に最適化されたAWQ量子化モデルを採用し、Podmanコンテナで堅牢な高スループットAPIサーバーを構築した。
- インフラ視点のチューニングとテンソル並列:
--gpu-memory-utilizationによるVRAM境界の制御、コンテキスト長制限によるバッチサイズ最大化、およびマルチGPU環境におけるPCIe通信のボトルネックについて考察した。
これで、少人数の検証環境から、全社展開に向けた数千RPS規模の大規模トラフィックまで、あらゆるビジネス要件に応じたローカルLLM基盤の「エンジン選定と構築」が完了しました。
しかし、LLM単体では「一般的な知識」しか答えることができず、自社の機密データや最新の社内ドキュメントに基づいた回答はできません。自社専用のAIシステムを完成させるためには、外部データとLLMを接続する仕組みが必要です。
そこで次回、【第6回】RAG(検索拡張生成)基盤の構築とベクトルデータベースの連携では、構築したvLLM APIと連携し、社内PDFや社内Wikiをエンベディング(ベクトル化)して検索・回答させるエンタープライズAIの核、「RAGアーキテクチャ」のインフラ構築設計に踏み込みます。ご期待ください!
▼ 【本番向けAIインフラ環境を構築しよう】 ▼
GPUのスケールアウトに対応!
「高コスパおすすめVPS・専用サーバー」
次世代AIインフラを牽引する技術者へ
「インフラエンジニア専門転職」

コメント