オープンソースでLLM構築 | OllamaからvLLMへのマイグレーションで複数モデル同時利用一覧に戻るオープンソースでLLM構築 | OllamaからvLLMへのマイグレーションで複数モデル同時利用
ローカルLLM推論エンジンとして広く使われている
オープンソースLLM推論エンジンの流れ
2023〜2025年:軽量化・ローカル推論の民主化
LLM推論のオープンソース化は、急速に進んでいます。
| 時期 | できごと | インパクト |
|---|
| 2023年前半 | llama.cpp登場 | CPU推論の実用化。ggml/gguf量子化により消費者GPUでの動作が現実に |
| 2023年後半 | Ollama登場 | ollama pull → ollama run の2コマンドで誰でもローカルLLM。Docker感覚のUX |
| 2024年 | vLLM普及 | PagedAttention によるGPUメモリの効率化。サーバー用途で高スループットを実現 |
| 2025年 | 量子化・軽量モデルの成熟 | Qwen2.5 (1.5B〜72B)、Gemma3、Llama3.2などが実用レベルに。VRAM 8GBでも十分動く時代 |
エンジン選択の現状
現在、ローカルLLM推論エンジンは大きく3つの系統に分かれます。
ご紹介
弊社では下記プロダクトを取り扱いしております。ご興味ありましたらご覧ください。
[llama.cpp系] [Python系] [CUDA最適化系]
llama.cpp vLLM TensorRT-LLM
Ollama SGLang TGI
LM Studio ...
- llama.cpp系:CPU/GPU対応、量子化(GGUF)、セットアップ簡単
- Python系(vLLM等):PagedAttentionによるGPU効率化、OpenAI互換API標準装備
- CUDA最適化系:最大性能だがセットアップ複雑、NVIDIA限定
一般的な導入ステップとして、「Ollamaで始めて → 本番要件が出たらvLLMへ」という流れが自然です。
アーキテクチャ比較
Ollamaのアーキテクチャ
Ollamaは、llama.cppをバックエンドに持つ統合型エンジンです。
┌─────────────────────────────────────────┐
│ Ollama Server │
│ ┌─────────────────────────────────┐ │
│ │ Model Manager │ │
│ │ (pull / run / list / ps) │ │
│ ├─────────────────────────────────┤ │
│ │ Inference Engine │ │
│ │ (llama.cpp / ggml) │ │
│ ├─────────────────────────────────┤ │
│ │ REST API │ │
│ │ /api/chat /api/generate │ │
│ │ /api/embed /api/tags │ │
│ └─────────────────────────────────┘ │
│ │
│ ✅ 1プロセスで全機能 │
│ ✅ モデルの自動ダウンロード・管理 │
│ ✅ GGUFフォーマット(量子化対応) │
│ ⚠️ 単一モデルの逐次推論 │
└─────────────────────────────────────────┘
- シングルサーバーアーキテクチャ:1つの
ollama serveが全リクエストを処理
- 独自API:
/api/chat、/api/generate、/api/embed 等(OpenAI非互換)
- モデル管理統合:
ollama pullで自動ダウンロード、Modelfileでカスタマイズ
- メモリ管理:モデルをロード/アンロード。同時に1〜数モデル(
OLLAMA_NUM_PARALLELで並列数制御)
vLLMのアーキテクチャ
vLLMは、GPUメモリ効率を最大化するPython製推論エンジンです。
┌───────────────────────────────────────────────────────┐
│ vLLM Architecture │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Chat Server │ │ Embed Server │ │Vision Server │ │
│ │ :8080 │ │ :8081 │ │ :8082 │ │
│ │ │ │ │ │ (optional) │ │
│ │ Qwen2.5 │ │ E5-large │ │ Qwen2-VL │ │
│ │ -1.5B-Inst │ │ -instruct │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ┌──────┴─────────────────┴─────────────────┴───────┐ │
│ │ GPU Memory (PagedAttention) │ │
│ │ ┌─────────────┐ ┌──────────┐ ┌───────────────┐ │ │
│ │ │ Chat: 55% │ │Embed:35% │ │ Vision: 10% │ │ │
│ │ │ GPU VRAM │ │GPU VRAM │ │ GPU VRAM │ │ │
│ │ └─────────────┘ └──────────┘ └───────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ✅ モデルごとに独立プロセス │
│ ✅ OpenAI互換API (/v1/chat/completions) │
│ ✅ GPUメモリの割合を個別制御 │
│ ✅ Continuous Batching で高スループット │
└───────────────────────────────────────────────────────┘
- マルチサーバーアーキテクチャ:用途ごとに独立したサーバープロセスを起動
- OpenAI互換API:
/v1/chat/completions、/v1/embeddings、/v1/models
- PagedAttention:GPUメモリをページ単位で管理し、KVキャッシュの無駄を最小化
- Continuous Batching:複数リクエストを動的にバッチ処理し、スループットを最大化
- HuggingFaceモデル直接利用:ダウンロードからロードまで自動
OllamaとvLLMのメリット・デメリット
Ollama
| 観点 | 内容 |
|---|
| セットアップ | brew install ollama → ollama pull qwen2.5 → 完了。圧倒的に簡単 |
| モデル管理 | ollama listでインストール済みモデル一覧、ollama rmで削除。Docker感覚 |
| 量子化 | GGUF形式で4bit/8bit量子化モデルを標準サポート。VRAM 8GBでも7Bモデルが動く |
| CPU推論 | llama.cppベースのため、GPUなしでも動作する(遅いが可能) |
| Modelfile | FROM qwen2.5:7b + SYSTEM "..." でカスタムモデルを定義可能 |
| 制約 | 独自API(OpenAI非互換)、GPUメモリの細かな制御が難しい、マルチモデル同時利用が制限的 |
vLLM
| 観点 | 内容 |
|---|
| スループット | PagedAttention + Continuous Batching で同時リクエスト処理能力が高い |
| マルチモデル | モデルごとに独立サーバー。Chat + Embedding + Visionを同時に安定運用 |
| GPUメモリ制御 | --gpu-memory-utilization 0.55 のようにモデルごとのVRAM使用率を指定可能 |
| API互換性 | OpenAI APIと完全互換。既存のOpenAIクライアントコードがそのまま使える |
| HuggingFace統合 | Qwen/Qwen2.5-1.5B-Instruct のようにHuggingFace IDを直接指定してモデルロード |
| 制約 | GPU必須(CPU推論不可)、セットアップがやや複雑、メモリ消費が大きい |
比較表
| 項目 | Ollama | vLLM |
|---|
| セットアップ時間 | 5分 | 30分〜1時間 |
| GPU要否 | なくても動く | 必須 |
| API形式 | 独自 (/api/chat) | OpenAI互換 (/v1/chat/completions) |
| モデル形式 | GGUF (量子化) | HuggingFace (FP16/BF16) |
| 同時モデル数 | 1〜2(メモリ次第) | 用途別に複数プロセス |
| GPUメモリ制御 | 自動(制御困難) | --gpu-memory-utilization で精密制御 |
| バッチ処理 | 逐次 | Continuous Batching |
| Embedding | 同一サーバー | 別サーバー(専用ポート) |
| 推論速度 (単発) | 高速(ggml最適化) | 高速(CUDA最適化) |
| 推論速度 (並列) | 低下しやすい | 高スループット維持 |
| Tensor Parallel | 非対応 | 対応(マルチGPU) |
OllamaからvLLMへのマイグレーション
Step 1: API クライアント層の変更
最も大きな変更は、Ollama独自APIからOpenAI互換APIへの切り替えです。
# Ollama: /api/chat エンドポイント(独自フォーマット)
async with client.stream(
"POST",
f"{OLLAMA_BASE_URL}/api/chat",
json={
"model": request.model,
"messages": messages,
"stream": True,
"options": {
"temperature": request.temperature,
"num_predict": request.max_tokens, # Ollama独自パラメータ名
}
},
) as response:
async for line in response.aiter_lines():
chunk = json.loads(line)
text = chunk.get("message", {}).get("content", "")
# vLLM: /v1/chat/completions エンドポイント(OpenAI互換)
async for chunk in stream_chat_completions(
client,
model=request.model,
messages=messages,
temperature=request.temperature,
max_tokens=request.max_tokens, # OpenAI標準パラメータ名
top_p=request.top_p,
top_k=request.top_k, # vLLM拡張パラメータ
repetition_penalty=request.repeat_penalty, # vLLM拡張パラメータ
thinking=request.thinking,
stream=request.stream,
):
text = chunk.get("text", "")
| Ollama | vLLM | 備考 |
|---|
/api/chat | /v1/chat/completions | OpenAI互換エンドポイント |
/api/generate | /v1/completions | テキスト補完 |
/api/embed | /v1/embeddings | Embedding API |
/api/tags | /v1/models | モデル一覧 |
num_predict | max_tokens | パラメータ名の違い1 |
repeat_penalty | repetition_penalty | パラメータ名の違い2 |
| NDJSON stream | SSE (Server-Sent Events) | ストリーミング形式の違い |
Step 2: Embedding サーバーの分離
Ollamaでは1つのサーバーでChatもEmbeddingも処理しますが、vLLMではEmbeddingを専用サーバーとして分離します。
async def embed_text_with_ollama(text: str, model: str = None) -> List[float]:
"""Ollama /api/embed で埋め込み生成"""
async with httpx.AsyncClient(timeout=60.0) as client:
resp = await client.post(
f"{OLLAMA_BASE_URL}/api/embed", # 同じサーバー
json={"model": "embeddinggemma:latest", "input": text},
)
data = resp.json()
return data.get("embeddings", [[]])[0]
async def embed_text(text: str, model: str = None) -> List[float]:
"""vLLM /v1/embeddings で埋め込み生成(専用サーバー)"""
async with httpx.AsyncClient(timeout=60.0) as client:
resp = await client.post(
f"{VLLM_EMBED_BASE_URL}/v1/embeddings", # 別サーバー (:8081)
json={
"model": "intfloat/multilingual-e5-large-instruct",
"input": text,
},
)
data = resp.json()
return data.get("data", [{}])[0].get("embedding", [])
- メモリ独立管理:Chat 55%、Embedding 35%のようにGPUメモリを用途別に割り当て
- 独立スケーリング:Embeddingの負荷が高い場合、そのサーバーだけスケールアウト可能
- 障害分離:Chatサーバーがクラッシュしても、Embedding処理は継続
Step 3: Vision API の移行
# Ollama: imagesフィールドにbase64画像を配列で渡す
response = await client.post(
f"{OLLAMA_BASE_URL}/api/chat",
json={
"model": vision_model,
"messages": [{
"role": "user",
"content": prompt,
"images": [image_b64] # Ollama独自形式
}],
"stream": False
}
)
# vLLM: OpenAI形式のcontent配列にimage_urlブロックを含める
content = [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{image_b64}"} # OpenAI形式
},
]
response = await client.post(
f"{VLLM_BASE_URL}/v1/chat/completions",
json={
"model": vision_model,
"messages": [{"role": "user", "content": content}],
"max_tokens": 4096,
}
)
Step 4: インフラ層の変更
Ollamaは ollama serve 1プロセスで完結しますが、vLLMは用途別にプロセスを起動します。
# Ollama版 infrastructure.py
def start_ollama() -> bool:
subprocess.Popen(['ollama', 'serve'], ...) # 1プロセス
def stop_ollama() -> bool:
subprocess.run(['ollama', 'stop'], ...) # 1コマンド
# vLLM版 infrastructure.py
def start_vllm() -> bool:
# 1. Chatサーバーを起動(GPUメモリを先に確保)
_start_vllm_server("chat", "Qwen/Qwen2.5-1.5B-Instruct", 8080)
_wait_for_server(VLLM_BASE_URL, ...) # 起動待ち
# 2. Embeddingサーバーを起動(残りのGPUメモリを使用)
_start_vllm_server("embed", "intfloat/multilingual-e5-large-instruct",
8081, convert="embed")
_wait_for_server(VLLM_EMBED_BASE_URL, ...)
# 3. Visionサーバーを起動(オプション)
if DEFAULT_VISION_MODEL:
_start_vllm_server("vision", DEFAULT_VISION_MODEL, 8082)
重要なのは起動順序です。vLLMはモデルロード時にGPUメモリを確保するため、メモリ使用量の大きいChatサーバーを先に起動し、完了を待ってからEmbeddingサーバーを起動する必要があります。
Step 5: SSE ストリーミングパーサーの実装
OllamaはNDJSON(1行1JSON)でストリーミングしますが、vLLMはSSE(Server-Sent Events)形式です。
# vLLM SSEストリーミングパーサー(common/vllm.py)
async with client.stream("POST", f"{VLLM_BASE_URL}/v1/chat/completions",
json=payload) as response:
async for line in response.aiter_lines():
if not line.startswith("data: "):
continue
data_str = line[6:] # "data: " プレフィックスを除去
if data_str == "[DONE]":
yield {"text": "", "done": True, "usage": final_usage}
return
chunk = json.loads(data_str)
delta = chunk["choices"][0].get("delta", {})
yield {
"text": delta.get("content", ""),
"done": False,
"reasoning_content": delta.get("reasoning_content"), # 思考モデル対応
}
vLLMサーバーの起動コマンド
マイグレーション後のvLLMサーバー起動コマンドを示します。
Chatサーバー
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-1.5B-Instruct \
--host 0.0.0.0 \
--port 8080 \
--gpu-memory-utilization 0.55 \
--tensor-parallel-size 1
Embeddingサーバー
# vLLM 0.15.0以降: --convert embed を使用(旧 --task embed)
python -m vllm.entrypoints.openai.api_server \
--model intfloat/multilingual-e5-large-instruct \
--host 0.0.0.0 \
--port 8081 \
--gpu-memory-utilization 0.35 \
--convert embed
Visionサーバー(オプション)
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2-VL-7B-Instruct \
--host 0.0.0.0 \
--port 8082 \
--gpu-memory-utilization 0.10
各エンジンの用途イメージ
Ollamaが適切なケース
| シーン | 理由 |
|---|
| 個人の開発・実験 | ollama pull → ollama run で即座に試せる |
| GPUなし環境 | CPU推論対応。MacBook (Apple Silicon) でも快適 |
| デスクトップアプリ | LM Studioやバイナリ配布型のUI付きアプリとの統合 |
| 低リソース環境 | GGUF量子化でメモリ使用量を最小化 |
| プロトタイピング | モデル切り替えが簡単。Modelfileでカスタムプリセット作成 |
| エッジデバイス | Raspberry Pi等の軽量デバイスでも動作可能 |
vLLMが適切なケース
| シーン | 理由 |
|---|
| 本番APIサービス | Continuous Batchingで安定した同時接続処理 |
| 複数モデル同時利用 | Chat + Embedding + Visionを独立プロセスで並行運用 |
| RAGパイプライン | Chat推論とEmbedding生成を同時に高速処理 |
| マルチGPU環境 | Tensor Parallelで複数GPUにモデルを分散 |
| GPU効率の最大化 | --gpu-memory-utilizationでVRAMを精密制御 |
| OpenAI互換が必要 | 既存のOpenAIクライアントコードをそのまま使いたい |
| 高スループット | 複数ユーザーからの同時リクエストを効率よく処理 |
段階的導入の推奨パス
Phase 1: Ollama(開発・検証)
↓ 要件が出てきたら
Phase 2: vLLM(本番運用)
↓ さらにスケールが必要なら
Phase 3: TensorRT-LLM / SGLang(最大性能)
- 同時接続ユーザー数 < 5人 → Ollamaで十分
- 同時接続ユーザー数 5〜50人 → vLLMを検討
- GPUメモリの使用率を最適化したい → vLLM推奨
- GPUがない / MacBook Airで動かしたい → Ollama一択
まとめ
OllamaからvLLMへのマイグレーションは、以下のステップで進めることができます。
- APIエンドポイントの変更:独自API → OpenAI互換API
- Embeddingの分離:同一サーバー → 専用サーバー
- Vision APIの変更:images配列 → OpenAI content配列
- プロセス管理の変更:単一プロセス → マルチプロセス(起動順序に注意)
- ストリーミング形式の変更:NDJSON → SSE
- フロントエンド互換性の維持:レスポンスフィールド名の統一
どちらのエンジンも活発に開発が続いており、それぞれの強みを活かした使い分けが重要です。「Ollamaで手軽に始めて、vLLMでスケールさせる」 ── この2段階アプローチが、現時点でのオープンソースLLM構築における最も実践的な戦略と言えるでしょう。