2026年2月23日
AI
オープンソースで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版(chat/service.py)
# 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版(chat/service.py)
# 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を専用サーバーとして分離します。
Ollama版(chat/service.py)
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]
vLLM版(common/vllm.py)
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 の移行
画像認識もAPIフォーマットが異なります。
Ollama版
# 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版
# 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構築における最も実践的な戦略と言えるでしょう。