mcpField
M
model-serving

model-serving

github.com/ancolemanクラウド・DevOps52561 回インストール46 週間前に更新
リポジトリ

model-serving について

LLM や機械学習の推論サービスをデプロイするエンジニア向けのスキルです。セルフホスト型サービングエンジン、GPU 最適化、ストリーミング API、オーケストレーション、RAG、性能測定、本番デプロイを扱います。

主な機能

  1. 01LLM サービングエンジンの比較と選定
  2. 02GPU 推論と量子化の設定
  3. 03ストリーミング応答 API の構築
  4. 04ベクトルデータベースを使った RAG の調整
  5. 05スループットとレイテンシの測定
  6. 06Kubernetes 本番デプロイの検討

活用シーン

  1. 01セルフホスト型 LLM 推論エンドポイントのデプロイ
  2. 02チャットアプリへの生成応答ストリーミング
  3. 03ベクトル検索を使う RAG パイプラインの構築
  4. 04推論サービスのレイテンシとスループット測定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ancoleman/ai-design-components --skill model-serving

インストール後、Claude Code で/model-servingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

outputs.yaml
examples/k8s-vllm-deployment/
README.md
examples/langchain-agents/
main.py
README.md
requirements.txt
examples/langchain-rag-qdrant/
README.md
examples/ollama-local/
main.py
README.md
requirements.txt
examples/vllm-serving/
main.py
README.md
requirements.txt
references/
bentoml.md
inference-optimization.md
langchain-orchestration.md
streaming-sse.md
tgi.md
vllm.md
scripts/
benchmark_inference.py
validate_model_config.py

詳細

モデルサービング

目的

最適化されたサービングエンジン、ストリーミング応答パターン、オーケストレーションフレームワークを使用して、LLM および ML モデルを本番環境での推論向けにデプロイします。セルフホスト可能なモデルサービング、GPU の最適化、フロントエンドアプリケーションとの統合に重点を置いています。

使用する場面

  • 本番環境向けに LLM をデプロイする(セルフホストの Llama、Mistral、Qwen)
  • ストリーミング応答に対応した AI API を構築する
  • 従来型の ML モデルをサービングする(scikit-learn、XGBoost、PyTorch)
  • ベクトルデータベースを使用して RAG パイプラインを実装する
  • 推論のスループットとレイテンシを最適化する
  • LLM サービングをフロントエンドのチャットインターフェースと統合する

モデルサービングの選択

LLM サービングエンジン

vLLM(第一推奨)

  • PagedAttention によるメモリ管理(スループットを20~30倍向上)
  • 動的なリクエスト処理のための連続バッチ処理
  • OpenAI 互換の API エンドポイント
  • 用途:ほとんどのセルフホスト型 LLM のデプロイ

TensorRT-LLM

  • GPU 効率を最大化(vLLM より2~8倍高速)
  • モデルの変換と最適化が必要
  • 用途:スループットを絶対的に最大化する必要がある本番ワークロード

Ollama

  • GPU を使わないローカル開発
  • シンプルな CLI インターフェース
  • 用途:プロトタイピング、ノート PC での開発、教育目的

選択の指針:

Self-hosted LLM deployment needed?
├─ Yes, need maximum throughput → vLLM
├─ Yes, need absolute max GPU efficiency → TensorRT-LLM
├─ Yes, local development only → Ollama
└─ No, use managed API (OpenAI, Anthropic) → No serving layer needed
ML モデルのサービング(LLM 以外)

BentoML(推奨)

  • Python ネイティブで、簡単にデプロイ可能
  • スループット向上のための適応型バッチ処理
  • 複数のフレームワークに対応(scikit-learn、PyTorch、XGBoost)
  • 用途:ほとんどの従来型 ML モデルのデプロイ

Triton Inference Server

  • 同じ GPU 上で複数のモデルをサービング
  • モデルアンサンブル(複数モデルを連結)
  • 用途:NVIDIA GPU の最適化、10モデル以上のサービング
LLM オーケストレーション

LangChain

  • 汎用ワークフロー、エージェント、RAG
  • 100以上の統合機能(LLM、ベクトル DB、ツール)
  • 用途:ほとんどの RAG およびエージェントアプリケーション

LlamaIndex

  • 高度な検索戦略を備えた RAG 特化型
  • 100以上のデータコネクター(PDF、Notion、ウェブ)
  • 用途:主なユースケースが RAG の場合

クイックスタートの例

vLLM サーバーのセットアップ
# Install
pip install vllm

# Serve a model (OpenAI-compatible API)
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --dtype auto \
  --max-model-len 4096 \
  --gpu-memory-utilization 0.9 \
  --port 8000

主なパラメーター:

  • --dtype:モデルの精度(auto、float16、bfloat16)
  • --max-model-len:コンテキストウィンドウのサイズ
  • --gpu-memory-utilization:GPU メモリの使用割合(0.8~0.95)
  • --tensor-parallel-size:モデル並列処理に使用する GPU の数
ストリーミング応答(SSE パターン)

バックエンド(FastAPI):

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import OpenAI
import json

app = FastAPI()
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")

@app.post("/chat/stream")
async def chat_stream(message: str):
    async def generate():
        stream = client.chat.completions.create(
            model="meta-llama/Llama-3.1-8B-Instruct",
            messages=[{"role": "user", "content": message}],
            stream=True,
            max_tokens=512
        )

        for chunk in stream:
            if chunk.choices[0].delta.content:
                token = chunk.choices[0].delta.content
                yield f"data: {json.dumps({'token': token})}\n\n"

        yield f"data: {json.dumps({'done': True})}\n\n"

    return StreamingResponse(
        generate(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache"}
    )

フロントエンド(React):

// Integration with ai-chat skill
const sendMessage = async (message: string) => {
  const response = await fetch('/chat/stream', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ message })
  })

  const reader = response.body!.getReader()
  const decoder = new TextDecoder()

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value)
    const lines = chunk.split('\n\n')

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = JSON.parse(line.slice(6))
        if (data.token) {
          setResponse(prev => prev + data.token)
        }
      }
    }
  }
}
BentoML サービス
import bentoml
from bentoml.io import JSON
import numpy as np

@bentoml.service(
    resources={"cpu": "2", "memory": "4Gi"},
    traffic={"timeout": 10}
)
class IrisClassifier:
    model_ref = bentoml.models.get("iris_classifier:latest")

    def __init__(self):
        self.model = bentoml.sklearn.load_model(self.model_ref)

    @bentoml.api(batchable=True, max_batch_size=32)
    def classify(self, features: list[dict]) -> list[str]:
        X = np.array([[f['sepal_length'], f['sepal_width'],
                       f['petal_length'], f['petal_width']] for f in features])
        predictions = self.model.predict(X)
        return ['setosa', 'versicolor', 'virginica'][predictions]
LangChain RAG パイプライン
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Qdrant
from langchain.chains import RetrievalQA
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Load and chunk documents
text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=50)
chunks = text_splitter.split_documents(documents)

# Create vector store
embeddings = OpenAIEmbeddings()
vectorstore = Qdrant.from_documents(
    chunks,
    embeddings,
    url="http://localhost:6333",
    collection_name="docs"
)

# Create retrieval chain
llm = ChatOpenAI(model="gpt-4o")
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
    return_source_documents=True
)

# Query
result = qa_chain({"query": "What is PagedAttention?"})

パフォーマンスの最適化

GPU メモリの見積もり

LLM の経験則:

GPU Memory (GB) = Model Parameters (B) × Precision (bytes) × 1.2

例:

  • Llama-3.1-8B(FP16):8B × 2 bytes × 1.2 = 19.2 GB
  • Llama-3.1-70B(FP16):70B × 2 bytes × 1.2 = 168 GB(A100 が2~4基必要)

量子化によるメモリ削減:

  • FP16:パラメーターあたり2 bytes
  • INT8:パラメーターあたり1 byte(メモリを2分の1に削減)
  • INT4:パラメーターあたり0.5 bytes(メモリを4分の1に削減)
vLLM の最適化
# Enable quantization (AWQ for 4-bit)
vllm serve TheBloke/Llama-3.1-8B-AWQ \
  --quantization awq \
  --gpu-memory-utilization 0.9

# Multi-GPU deployment (tensor parallelism)
vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --gpu-memory-utilization 0.9
バッチ処理の戦略

連続バッチ処理(vLLM のデフォルト):

  • リクエストを動的にバッチへ追加・削除します
  • 静的バッチ処理より高いスループットを実現します
  • 設定は不要です

適応型バッチ処理(BentoML):

@bentoml.api(
    batchable=True,
    max_batch_size=32,
    max_latency_ms=1000  # Wait max 1s to fill batch
)
def predict(self, inputs: list[np.ndarray]) -> list[float]:
    # BentoML automatically batches requests
    return self.model.predict(np.array(inputs))

本番環境へのデプロイ

Kubernetes へのデプロイ

完全な YAML マニフェストについては、examples/k8s-vllm-deployment/ を参照してください。

主な考慮事項:

  • GPU リソースのリクエスト:nvidia.com/gpu: 1
  • ヘルスチェック:/health エンドポイント
  • キューの深さに基づく水平 Pod オートスケーリング
  • モデルのキャッシュ用の永続ボリューム
API ゲートウェイパターン

本番環境では、レート制限、認証、モニタリングを追加します。

Kong の設定:

services:
  - name: vllm-service
    url: http://vllm-llama-8b:8000
    plugins:
      - name: rate-limiting
        config:
          minute: 60  # 60 requests per minute per API key
      - name: key-auth
      - name: prometheus
モニタリングメトリクス

必須の LLM メトリクス:

  • 1秒あたりのトークン数(スループット)
  • 最初のトークンが生成されるまでの時間(TTFT)
  • トークン間レイテンシ
  • GPU 使用率とメモリ
  • キューの深さ

Prometheus の計装:

from prometheus_client import Counter, Histogram

requests_total = Counter('llm_requests_total', 'Total requests')
tokens_generated = Counter('llm_tokens_generated', 'Total tokens')
request_duration = Histogram('llm_request_duration_seconds', 'Request duration')

@app.post("/chat")
async def chat(request):
    requests_total.inc()
    start = time.time()
    response = await generate(request)
    tokens_generated.inc(len(response.tokens))
    request_duration.observe(time.time() - start)
    return response

統合パターン

フロントエンド(ai-chat)との統合

このスキルは、ai-chat スキルのバックエンドサービング層を提供します。

フロー:

Frontend (React) → API Gateway → vLLM Server → GPU Inference
     ↑                                                  ↓
     └─────────── SSE Stream (tokens) ─────────────────┘

実装パターンの詳細については、references/streaming-sse.md を参照してください。

ベクトルデータベースを使った RAG

アーキテクチャ:

User Query → LangChain
              ├─> Vector DB (Qdrant) for retrieval
              ├─> Combine context + query
              └─> LLM (vLLM) for generation

完全なパターンについては、references/langchain-orchestration.md と examples/langchain-rag-qdrant/ を参照してください。

非同期推論キュー

バッチ処理やリアルタイムではない推論に使用します。

Client → API → Message Queue (Celery) → Workers (vLLM) → Results DB

次の用途に役立ちます。

  • ドキュメントのバッチ処理
  • バックグラウンドでの要約
  • 対話を伴わないワークフロー

ベンチマーク

スクリプト/benchmark_inference.py を使って、デプロイメントを測定します。

python scripts/benchmark_inference.py \
  --endpoint http://localhost:8000/v1/chat/completions \
  --model meta-llama/Llama-3.1-8B-Instruct \
  --concurrency 32 \
  --requests 1000

出力:

  • 1 秒あたりのリクエスト数
  • P50/P95/P99 のレイテンシ
  • 1 秒あたりのトークン数
  • GPU メモリ使用量

同梱リソース

詳細ガイド:

  • references/vllm.md - vLLM のセットアップ、PagedAttention、最適化
  • references/tgi.md - Text Generation Inference のパターン
  • references/bentoml.md - BentoML のデプロイパターン
  • references/langchain-orchestration.md - LangChain の RAG とエージェント
  • references/inference-optimization.md - 量子化、バッチ処理、GPU チューニング

動作するサンプル:

  • examples/vllm-serving/ - vLLM と FastAPI による完全なストリーミングセットアップ
  • examples/ollama-local/ - Ollama を使ったローカル開発
  • examples/langchain-agents/ - LangChain エージェントのパターン

ユーティリティスクリプト:

  • scripts/benchmark_inference.py - スループットとレイテンシのベンチマーク
  • scripts/validate_model_config.py - デプロイ設定の検証

一般的なパターン

OpenAI API からの移行

vLLM は、簡単に移行できる OpenAI 互換のエンドポイントを提供します。

# Before (OpenAI)
from openai import OpenAI
client = OpenAI(api_key="sk-...")

# After (vLLM)
from openai import OpenAI
client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-needed"
)

# Same API calls work!
response = client.chat.completions.create(
    model="meta-llama/Llama-3.1-8B-Instruct",
    messages=[{"role": "user", "content": "Hello"}]
)
複数モデルのサービング

タスクに応じて、異なるモデルにリクエストを振り分けます。

MODEL_ROUTING = {
    "small": "meta-llama/Llama-3.1-8B-Instruct",  # Fast, cheap
    "large": "meta-llama/Llama-3.1-70B-Instruct", # Accurate, expensive
    "code": "codellama/CodeLlama-34b-Instruct"    # Code-specific
}

@app.post("/chat")
async def chat(message: str, task: str = "small"):
    model = MODEL_ROUTING[task]
    # Route to appropriate vLLM instance
コスト最適化

トークン使用量を追跡します。

import tiktoken

def estimate_cost(text: str, model: str, price_per_1k: float):
    encoding = tiktoken.encoding_for_model(model)
    tokens = len(encoding.encode(text))
    return (tokens / 1000) * price_per_1k

# Compare costs
openai_cost = estimate_cost(text, "gpt-4o", 0.005)  # $5 per 1M tokens
self_hosted_cost = 0  # Fixed GPU cost, unlimited tokens

トラブルシューティング

GPU メモリ不足:

  • --max-model-len を減らす
  • --gpu-memory-utilization を下げる(0.8 を試してください)
  • 量子化を有効にする(--quantization awq)
  • より小さいモデルのバリアントを使用する

スループットが低い:

  • --gpu-memory-utilization を上げる(0.95 を試してください)
  • 継続的バッチ処理を有効にする(vLLM のデフォルト)
  • GPU 使用率を確認する(80%を超えている必要があります)
  • 複数 GPU の場合はテンソル並列処理を検討する

レイテンシが高い:

  • 静的バッチ処理を使用している場合は、バッチサイズを減らす
  • GPU サーバーへのネットワークレイテンシを確認する
  • scripts/benchmark_inference.py を使ってプロファイリングする

次のステップ

  1. ローカル開発:GPU を使わないテストには、examples/ollama-local/ から始めます
  2. 本番環境のセットアップ:examples/vllm-serving/ を使って vLLM をデプロイします
  3. RAG の統合:examples/langchain-rag-qdrant/ を使ってベクトル DB を追加します
  4. Kubernetes:examples/k8s-vllm-deployment/ を使ってスケールします
  5. モニタリング:Prometheus と Grafana を使ってメトリクスを追加します

コメント