model-serving について
LLM や機械学習の推論サービスをデプロイするエンジニア向けのスキルです。セルフホスト型サービングエンジン、GPU 最適化、ストリーミング API、オーケストレーション、RAG、性能測定、本番デプロイを扱います。
主な機能
- 01LLM サービングエンジンの比較と選定
- 02GPU 推論と量子化の設定
- 03ストリーミング応答 API の構築
- 04ベクトルデータベースを使った RAG の調整
- 05スループットとレイテンシの測定
- 06Kubernetes 本番デプロイの検討
活用シーン
- 01セルフホスト型 LLM 推論エンドポイントのデプロイ
- 02チャットアプリへの生成応答ストリーミング
- 03ベクトル検索を使う RAG パイプラインの構築
- 04推論サービスのレイテンシとスループット測定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ancoleman/ ai-design-components --skill model-servingインストール後、Claude Code で/model-servingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
outputs.yamlexamples/k8s-vllm-deployment/README.mdexamples/langchain-agents/main.pyREADME.mdrequirements.txtexamples/langchain-rag-qdrant/README.mdexamples/ollama-local/main.pyREADME.mdrequirements.txtexamples/vllm-serving/main.pyREADME.mdrequirements.txtreferences/bentoml.mdinference-optimization.mdlangchain-orchestration.mdstreaming-sse.mdtgi.mdvllm.mdscripts/benchmark_inference.pyvalidate_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を使ってプロファイリングする
次のステップ
- ローカル開発:GPU を使わないテストには、
examples/ollama-local/から始めます - 本番環境のセットアップ:
examples/vllm-serving/を使って vLLM をデプロイします - RAG の統合:
examples/langchain-rag-qdrant/を使ってベクトル DB を追加します - Kubernetes:
examples/k8s-vllm-deployment/を使ってスケールします - モニタリング:Prometheus と Grafana を使ってメトリクスを追加します