LLMのプロダクション環境へのデプロイ:Ray Serveによる高性能推論クラスタの構築

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

なぜLLMはローカル環境ではスムーズに動くのに、プロダクション環境では「クラッシュ」するのか?

個人のPCで大規模言語モデル(LLM)を実行するのは非常に簡単です。しかし、それをサーバーに上げ、5〜10人のユーザーが同時に利用し始めると問題が発生します。システムは目に見えて低速になり、最悪の場合GPUが完全にハングアップします。問題はPythonコードにあるのではなく、リソース管理にあります。LLMは膨大なVRAMを消費し、継続的な並列計算を必要とするからです。

直面する大きな3つの壁は以下の通りです:

  • GPUのボトルネック: 1つのリクエストがリソースを独占し、他のリクエストが数十秒間キュー(待ち行列)で待たされる。
  • 予算の浪費: 夜間にユーザーがいない間も4枚のA100 GPUを稼働させ続けると、サーバーコストが急騰する。
  • スケーリングの難しさ: 独自のロードバランサーを構築して複数の物理サーバー間でリクエストを分散させるのは、非常に困難な課題である。

これらの課題を解決するために、私はRay Serveを第一の選択肢として推奨します。このフレームワークは、AIモデルをLinuxでproduction-readyなREST APIとしてパッケージ化し、柔軟にオートスケーリング可能なマイクロサービスへと変換してくれます。

システム環境の準備

NVIDIAドライバとCUDA ToolkitがインストールされたUbuntu 22.04サーバーの使用を推奨します。最初からDockerを使用するのではなく、システムの動作フローを理解するために、まずはPythonで直接インストールする方法を説明します。

# システムの更新とPythonのインストール
sudo apt update && sudo apt upgrade -y
sudo apt install python3-pip -y

# Ray ServeとvLLMのインストール
pip install "ray[serve]" vllm torch

実際の運用では、私は常にRay ServeとvLLMを組み合わせて使用しています。vLLMは、PagedAttention技術により、ローカル推論AIを最適化し、HuggingFace Transformersと比較して10〜20倍の推論速度を実現するエンジンです。

Rayクラスタの設定:単一マシンから大規模スケールまで

RayはHeadノードとWorkerノードというモデルで動作します。Headノードはオーケストレーターとして機能し、WorkerノードはGPU上で直接計算処理を行います。

メインサーバーでHeadノードを初期化するには、以下のコマンドを実行します:

ray start --head --port=6379 --dashboard-host=0.0.0.0

実行後、ターミナルにIPとトークンが表示されます。2台目のサーバーがある場合は、ray start --address='IP_HEAD_NODE:6379'を実行するだけで、新しいマシンのGPUリソースが即座に計算クラスタに統合されます。

推論サービスのデプロイコードの作成

複雑なボイラープレートコードは不要です。Ray Serveでは、@serve.deploymentデコレータを使用してクラス形式でサービスを定義できます。以下は、Qwen-2.5やLlama-3を実行するための最適化されたコードです。

import ray
from ray import serve
from vllm import LLM, SamplingParams
from fastapi import FastAPI

app = FastAPI()

@serve.deployment(num_replicas=1, ray_actor_options={"num_gpus": 1})
@serve.ingress(app)
class LLMDeployment:
    def __init__(self, model_name: str):
        # Out of Memoryエラーを避けるためにVRAMの使用率を制限する
        self.llm = LLM(model=model_name, gpu_memory_utilization=0.8)
        self.sampling_params = SamplingParams(temperature=0.7, max_tokens=512)

    @app.post("/generate")
    async def generate(self, prompt: str):
        outputs = self.llm.generate([prompt], self.sampling_params)
        return {"text": outputs[0].outputs[0].text}

model_path = "Qwen/Qwen2.5-7B-Instruct"
serve.run(LLMDeployment.bind(model_path))

num_replicasパラメータに注目してください。GPUを4枚所有している場合は、この数値を増やすことで、より多くのユーザーに並列でサービスを提供できます。

オートスケーリングの設定:コストを60%削減する秘訣

プロダクション環境では、トラフィックは常に一定ではありません。私はYAML設定ファイルを使用して、実際の負荷に基づいてRay Serveがレプリカ数を自動的に増減させるようにしています。

deployments:
  - name: LLMDeployment
    autoscaling_config:
      min_replicas: 1
      max_replicas: 10
      target_ongoing_requests: 5
    ray_actor_options:
      num_gpus: 1

target_ongoing_requests: 5の設定は非常に重要です。各レプリカが同時に5つ以上のリクエストを処理している場合、Rayは自動的に新しいGPUを起動します。需要が減少すると、システムは自動的にGPUを解放し、電気代やサーバー費用を節約します。これはAIアプリを高速化しコストを削減する上でも非常に有効な考え方です。

パフォーマンスの監視とテスト

システムの稼働状況は、ポート8265のRayダッシュボードから確認できます。このダッシュボードでは、1秒あたりのリクエスト数(RPS)や各GPUのVRAM使用状況を視覚的に把握でき、LLMアプリのUnit Testingを自動化することで、デプロイ後の信頼性をさらに高めることができます。

ターミナルから素早くテストするには、curlコマンドを使用します:

curl -X POST "http://localhost:8000/generate" \
     -H "Content-Type: application/json" \
     -d '{"prompt": "Ray Serveとは何ですか?"}'

レスポンスが1秒以内に返ってき、GPU使用率のグラフが動けば、システムは実ユーザーにサービスを提供する準備が整っています。

運用における実践的なアドバイス

多くのデプロイプロジェクトを経て、システムをクラッシュさせないための3つの重要な教訓を得ました:

  1. VRAM의制御: vLLMはデフォルトでVRAMの90%を占有します。同じGPUで他のタスクを実行する必要がある場合は、gpu_memory_utilizationを0.7〜0.8程度に下げてください。
  2. ヘルスチェックの設定: LLMはCUDAドライバのエラーなどでフリーズすることがあります。AIエージェントによるサーバーログ監視やヘルスチェックを設定して、Rayが手動介入なしにエラーが発生したプロセスを自動的に再起動するようにしてください。
  3. ネットワーク帯域幅: 複数マシンのクラスタを実行する場合、ノード間は最低10Gbpsの内部ネットワークで接続してください。これにより、数十GBのモデル重みの転送が迅速に行われます。

この方法でのデプロイは、単純なPythonスクリプトよりも最初は複雑に見えるかもしれません。しかし、ユーザーが急増した際にもAIアプリケーションを安定して稼働させるための、最も持続可能な道です。

Share: