所要時間: 約60分 | 難易度: ★★★★☆
この記事で作るもの
- 2台以上のPC(GPU搭載)をネットワーク経由で束ね、Llama 3 70Bなどの巨大モデルを高速推論する分散環境を構築します。
- PythonとRay、そしてvLLMを組み合わせた、実務レベルの分散推論スクリプト。
- 複数枚のGPUを1つの仮想的な巨大GPUとして扱うためのネットワーク設定とランタイム。
📦 この記事に関連する商品
Intel X550-T2 10GbE NICマルチノード推論のボトルネックとなるGPU間通信を高速化するために必須のパーツです
※アフィリエイトリンクを含みます
前提知識として、Ubuntuの基本操作とPythonの仮想環境(venvやconda)の構築、およびNVIDIAドライバの導入が完了していることを想定します。
なぜこの方法を選ぶのか
ローカルで巨大なLLMを動かそうとしたとき、最大の壁は「VRAMの容量」です。 例えばRTX 4090を2枚挿しても48GB。これではLlama 3 70BをFP16で動かすには足りず、量子化に頼らざるを得ません。 しかし、この「Ray + vLLM」のスタックを使えば、物理的に別々のマシンにあるGPUをLAN経由で連結し、1つの大きな推論エンジンとして扱えます。
世の中にはLlama.cppを使った分散推論もありますが、あれはあくまで「動けばいい」ホビー用途に近い。 仕事で使うなら、スループットが圧倒的に高く、OpenAI互換APIを即座に叩き出せるvLLM一択です。 私がSIer時代に苦労した「複数サーバー間のMPI設定」のような泥臭い作業は、現代ではRayがすべて隠蔽してくれます。 レスポンス速度を犠牲にせず、スケールアウトの恩恵を最大化できるのがこの構成の強みです。
Step 1: 環境を整える
まずは全ノード(サーバー)に共通のライブラリをインストールします。 今回はUbuntu 22.04、CUDA 12.1の環境を前提とします。
# 全ノードで実行
# Pythonの仮想環境を作成
python3 -m venv vllm-cluster
source vllm-cluster/bin/activate
# RayとvLLMをインストール
# vLLMは分散推論のためにRayを内部で使用します
pip install "ray[default]" vllm==0.4.2
# 通信のために必要なNCCLの確認(NVIDIAの集団通信ライブラリ)
# これが正常に動かないと分散処理は100%失敗します
python3 -c "import torch; print(torch.cuda.nccl.version())"
各コマンドの意図を説明します。
ray[default]は分散実行のためのオーケストレーターです。
vllmは推論エンジンですが、バージョンを固定しているのは、分散推論周りのAPI変更が激しいためです。
特にNCCLのバージョン確認は重要で、複数のGPU間でデータを同期する際に、ここが不整合だとエラーを吐いて止まります。
⚠️ 落とし穴: ノード間の「パスワードなしSSH」設定を忘れる人が多いですが、これは必須ではありません。 ただし、各ノードのIPアドレスを固定し、ポート(デフォルトで6379番など)をファイアウォールで開放しておく必要があります。 私は最初、UFW(ファイアウォール)を有効にしたまま構築して「ノードが見つからない」というエラーで3時間を無駄にしました。検証中は一旦オフにするか、特定のサブネットを許可してください。
Step 2: 基本の設定
クラスターの司令塔となる「ヘッドノード」と、計算資源を提供する「ワーカーノード」を設定します。
# 【ヘッドノード(親機)で実行】
# node-ipには自身のIPアドレスを指定します
ray start --head --port=6379 --node-ip-address=192.168.1.10
# 【ワーカーノード(子機)で実行】
# addressにはヘッドノードのIPを指定
ray start --address='192.168.1.10:6379'
次に、Pythonからこのクラスターを制御するための初期化コードを書きます。
# config.py
import os
import ray
# クラスターに接続
# 既にシェルでray startしている場合は 'auto' で接続可能
ray.init(address="auto", ignore_reinit_error=True)
# 接続されているノードとGPUの数を確認
nodes = ray.nodes()
gpu_count = ray.cluster_resources().get("GPU", 0)
print(f"接続されたノード数: {len(nodes)}")
print(f"利用可能な総GPU数: {gpu_count}")
ここで「なぜ address="auto" にするのか」ですが、これはスクリプト実行時に既に立ち上がっているRayクラスターを自動検出させるためです。
スクリプト内で細かく設定を書くよりも、OS側でサービスとしてRayを立ち上げておくほうが、実務上の運用(ノードの増減など)が格段に楽になります。
Step 3: 動かしてみる
実際にLlama 3 8Bなどの軽量モデルを、2つのノードに跨ってロードしてみましょう。
ここでは tensor_parallel_size が鍵になります。
# distributed_inference.py
from vllm import LLM, SamplingParams
# tensor_parallel_size は使用する総GPU数を指定
# 2台のPCにそれぞれ1枚ずつGPUがあるなら '2' に設定
llm = LLM(
model="meta-llama/Meta-Llama-3-8B",
tensor_parallel_size=2,
distributed_executor_backend="ray"
)
sampling_params = SamplingParams(temperature=0.7, top_p=0.95, max_tokens=100)
outputs = llm.generate(["AIクラスターを構築するメリットは何ですか?"], sampling_params)
for output in outputs:
print(f"Prompt: {output.prompt}")
print(f"Generated text: {output.outputs[0].text}")
期待される出力
接続されたノード数: 2
利用可能な総GPU数: 2.0
...
Generated text: AIクラスターを構築する最大のメリットは、単体ではメモリ不足で実行不可能な巨大なモデルを扱えること、および推論速度(スループット)の向上です。
結果が正しく返ってくれば、物理的に分かれたGPUが1つのモデルを「分割して」持っている状態が作れています。 もしここでタイムアウトが発生する場合、ノード間の通信速度(LANケーブルが1GbEか、10GbEか)を疑ってください。 LLMの分散推論では、GPU間の通信がボトルネックになるため、最低でも2.5GbE、できれば10GbE以上の環境を推奨します。
Step 4: 実用レベルにする
実務では、単発のスクリプト実行ではなく「APIサーバー」として24時間稼働させる必要があります。 vLLMにはOpenAI互換のサーバー機能が備わっていますが、これを分散環境で起動するには以下のコマンドを叩きます。
# ヘッドノードで実行
python3 -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-70B \
--tensor-parallel-size 4 \
--host 0.0.0.0 \
--port 8000
ここでは tensor-parallel-size 4 としました。これは私の自宅にあるRTX 4090を2枚挿したマシン2台(計4枚)を想定した数値です。
なぜAPIサーバー形式にするのか。それは、既存のDifyやLangChainなどのツールから、単なる「OpenAIのエンドポイント」としてこのクラスターを再利用できるからです。
さらに、実務運用では「ノードの離脱」への対策が必要です。 以下のPythonスクリプトは、クラスターの死活監視を含めた、より堅牢な推論実行例です。
import time
from vllm import LLM
def safe_init_llm(model_name, num_gpus):
try:
# 起動前にリソースが足りているかチェック
available_gpus = ray.cluster_resources().get("GPU", 0)
if available_gpus < num_gpus:
raise RuntimeError(f"GPUが足りません。必要: {num_gpus}, 現在: {available_gpus}")
return LLM(model=model_name, tensor_parallel_size=num_gpus)
except Exception as e:
print(f"起動エラー: {e}")
return None
# メイン処理
model = "meta-llama/Meta-Llama-3-70B"
llm = safe_init_llm(model, 4)
if llm:
# 連続リクエストを想定したループ
while True:
# 実際の運用ではここでキューから入力を取得
start_time = time.time()
# ... 推論処理 ...
break # 今回はデモのため1回で終了
よくあるトラブルと解決法
| エラー内容 | 原因 | 解決策 |
|---|---|---|
NCCL Timeout | ノード間のネットワーク遮断または低速 | ファイアウォールをオフにし、10GbE以上の接続を確保する |
Ray node not found | ray start時のIP指定ミス | 自身のプライベートIP(192.168.x.x)を正確に指定する |
Out of Memory (OOM) | モデルに対してGPUメモリが不足 | tensor_parallel_size を増やすか、モデルを量子化(AWQ等)する |
次のステップ
このクラスターが組めるようになると、もはや「PC1台の限界」を気にする必要がなくなります。 次に挑戦すべきは、**「vLLMのLoRAアダプタ動的ロード」**です。 分散環境を維持したまま、リクエストごとに異なる性格のAI(マーケティング用、コーディング用など)を瞬時に切り替える運用は、実務において非常に強力な武器になります。
また、ハードウェア面では10GbEのNIC(ネットワークカード)を導入することを強くお勧めします。 GPU同士がデータをやり取りする速度が、推論トークン生成速度のボトルネックになるからです。 私の環境では、1GbEから10GbEに変えただけで、Llama 3 70Bの推論速度が約1.8倍に向上しました。 Redditの「16x Spark Cluster」のような巨大な構成は夢がありますが、まずは2〜3台の型落ちワークステーションを中古で集めて、この分散環境を構築することから始めてみてください。 その経験は、将来クラウドで大規模なH100クラスターを管理する際の確かな血肉となります。
よくある質問
Q1: 異なる型番のGPU(例:RTX 4090とRTX 3080)を混ぜても大丈夫ですか?
動きますが、おすすめしません。分散推論の速度は「クラスター内で最も遅いGPU」に引っ張られます。また、VRAM容量も最小のカードに合わせる必要があるため、資源が無駄になります。可能な限り、同じVRAM容量のカードで揃えるのが定石です。
Q2: ネットワーク越しだと推論が遅くなりませんか?
はい、1台のマシン内に複数枚挿す場合と比較すれば、ノード間のオーバーヘッドは確実に発生します。ただし、モデルが1台のメモリに収まらない場合、スワップ(ディスク使用)が発生して使い物にならなくなるため、それと比較すれば分散推論のほうが圧倒的に高速です。
Q3: インターネット経由(遠隔地)でクラスターを組めますか?
技術的にはVPN等で可能ですが、実用的ではありません。NCCL通信は非常に低遅延であることを要求するため、物理的に同じスイッチングハブに繋がっている必要があります。数ミリ秒の遅延が推論速度を致命的に低下させるため、基本はLAN内での運用に限られます。






