【2026年最新】ローカルLLM実践ガイド|Gemma 4からQwen 3.5に乗り換えた理由とtool calling非対応の現実解
【2026年最新】ローカルLLM実践ガイド — Gemma 4からQwen 3.5に乗り換えた理由とtool calling非対応の現実解
2026年、ローカルLLMの選択肢は爆発的に増えた。Qwen 3.5とGemma 4はApache 2.0、Llama 4はLlama Community License——いずれも商用利用可能で、ベンチマークも拮抗している。
だが、ベンチマークで選ぶと痛い目を見る。
筆者はGemma 4(当時最新)を業務監視システムに導入しようとして、tool callingが壊れている問題にぶつかった。Ollamaでは動かない。llama.cppでもパーサーにバグがある。結局、Qwen 3.5-27Bに乗り換え、tool callingを使わない設計に切り替えた。
本記事では、この移行で得た知見を全て公開する。モデル選定の判断基準、llama.cppでの実運用設定、tool callingなしで構造化出力を実現する方法、そしてクラウドAIへの自動フォールバック設計まで。
【広告・PR】 本記事には A8.net のアフィリエイトリンク(XServer VPS)が含まれます。記事内のリンクからサービス契約があった場合、執筆者に報酬が発生することがあります。掲載は独立した実体験に基づく評価で、報酬の有無とは無関係です。料金・条件はキャンペーン等で変動するため、契約前に必ず公式サイトで最新条件を確認してください。
なぜGemma 4からQwen 3.5に移行したのか
Gemma 4のtool calling問題(2026年4月時点)
Gemma 4は2026年4月2日にリリースされた。ベンチマークは素晴らしい。AIME 2026で89.2%、Codeforces ELOは2150。マルチモーダル対応で、MoE(Mixture of Experts:複数の専門ネットワークを切り替えて効率的に推論するアーキテクチャ)により効率も良い。
しかし、tool callingが壊れている。
具体的には:
- Ollama v0.20.x: tool callパーサーが失敗。ストリーミングではtool callデータがreasoningフィールドに入り、contentが空になる
- llama.cpp: tool call配列パラメータがJSON文字列としてシリアライズされる(
{や}を含む値で発生) - トークナイザーバグ: ガベージ出力が発生するケースあり
llama.cppには修正PRがマージされたが(PR #21326、#21343)、筆者の環境では安定しなかった。Ollamaはそもそもv0.20.2時点でMetal 4に非対応。
移行先にQwen 3.5を選んだ理由
| 評価軸 | Gemma 4 31B | Qwen 3.5 27B | 判定 |
|---|---|---|---|
| MMLU Pro | 85.2% | 86.1% | Qwen |
| GPQA Diamond | 84.3% | 85.5% | Qwen |
| AIME 2026 | 89.2% | 83.7% | Gemma |
| コーディング | 2150 ELO | 1980 ELO | Gemma |
| 日本語性能 | 良好 | トップクラス | Qwen |
| tool calling安定性 | ❌ 不安定 | ✅ 安定 | Qwen |
| llama.cpp互換性 | △ パッチ必要 | ◎ そのまま動く | Qwen |
| ライセンス | Apache 2.0 | Apache 2.0 | 同等 |
数学・競プロではGemma 4が勝つ。だが筆者の用途は監視・分類・オーケストレーション。日本語のメッセージを分類し、JSON構造化出力を返すタスクが中心。Qwen 3.5の方が適していた。
llama.cppでのQwen 3.5運用設定(Mac Apple Silicon)
なぜOllamaではなくllama.cppか — M4/M5 Mac GPU推論の選択肢
Mac Apple Silicon(M5 Pro)での推論バックエンドは3つの選択肢がある。
| バックエンド | 速度 (27B Q3_K_M) | Metal対応 | tool calling | 安定性 |
|---|---|---|---|---|
| llama.cpp | 18 tok/s | ✅ Metal 4 | ✅(Jinjaテンプレート) | ◎ |
| Ollama | — | ❌ Metal 4非対応 | △ モデル依存 | △ |
| MLX | 25-30 tok/s | ✅ ネイティブ | ❌ 非対応 | ○ |
OllamaはMetal 4に非対応(v0.20.2時点)で選択肢から外れた。MLXは速度では優位だが、OpenAI互換APIサーバーとしての機能が限定的。llama.cppが唯一、Metal 4 GPU対応+OpenAI互換API+tool callingテンプレートの全てを満たした。
起動設定
# launchdで常駐化(macOS)
llama-server \
-m ~/models/gguf/Qwen3.5-27B-Q3_K_M.gguf \
-c 32768 \
--parallel 1 \
--jinja \
--flash-attn on \
--metrics \
--host 127.0.0.1 \
--port 8080
各オプションの意味:
-c 32768: コンテキスト長。監視タスクには十分--parallel 1: 同時リクエスト数。メモリ節約のため1に制限--jinja: Qwen 3.5のfunction callingテンプレートを有効化--flash-attn on: Metal GPUでFlash Attention有効化--metrics: Prometheusメトリクスエンドポイント有効化(tok/s監視用)
thinking mode無効化: サーバー起動時ではなく、API呼び出し時に
extra_body={"reasoning_budget": 0}で制御する方が柔軟。用途によってthinking modeのON/OFFを切り替えられる。
量子化の選択 — GGUF形式とは
GGUF(GPT-Generated Unified Format)は、llama.cppで使用されるモデルファイル形式。量子化とは、モデルの重みを32bit浮動小数点から低ビット(Q3=3bit、Q4=4bit等)に圧縮する技術で、精度を多少犠牲にしてファイルサイズとメモリ使用量を削減する。
Qwen3.5-27B-Q3_K_M.gguf → 13GB → 18 tok/s
Qwen3.5-27B-Q4_K_M.gguf → 16GB → 15 tok/s(VRAM制約)
Qwen3.5-27B-Q8_0.gguf → 30GB → メモリ不足
Q3_K_Mは品質と速度のバランスが最も良かった。Q4_K_Mの方が品質は上だが、メモリ使用量が増えてスワップが発生し、実効速度が落ちる環境だった。
注意: 極端な量子化(Q2_K以下)はtool calling精度を著しく低下させる。Q3_K_M以上を推奨。
メモリが足りない場合: 統合メモリ36GB以上のMacなら27B Q4_K_Mが快適に動く。Apple Silicon Macの選び方は、ローカルLLM運用を視野に入れるならメモリ36GB以上を強く推奨する。
クラウドGPUで試したい場合: ローカル環境が不十分な場合、VPSにGPUインスタンスを立ててリモートでllama-serverを動かす方法もある。XServer VPSはコスパが良く、GPU付きプランでローカルLLMの検証環境としても使える。
tool calling非対応でも戦える構造化出力
問題:tool callingは不安定
Qwen 3.5はllama.cppのtool calling(--jinja)に対応しているが、実運用では問題がある。
- 量子化モデルではtool callの引数パースが不安定
- 小さいモデル(9B以下)ではtool呼び出しに固執して文脈を無視する
- マルチターンのtool使用でループに入ることがある
そこで筆者は、tool callingを使わず、プロンプトエンジニアリング+json_schema制約で構造化出力を実現する方針に切り替えた。
方法1: json_schema制約付き出力(推奨)
llama.cppのresponse_formatパラメータでJSONスキーマを強制する方法。出力が必ずスキーマに従うため、パース失敗がゼロになる。
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8080/v1",
api_key="sk-local", # llama-serverは認証不要だがSDKが要求
)
response = client.chat.completions.create(
model="27b",
messages=[{"role": "user", "content": f"分類してください: {message}"}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "task_classification",
"strict": True,
"schema": {
"type": "object",
"properties": {
"priority": {"type": "string", "enum": ["P0", "P1", "P2", "P3"]},
"category": {"type": "string", "enum": ["client", "sales", "content", "infra", "monitor"]},
"action": {"type": "string", "enum": ["execute", "notify", "skip"]},
},
"required": ["priority", "category", "action"]
}
}
},
max_tokens=200,
)
利点: llama.cppの制約デコーディングにより、出力トークンがスキーマから逸脱しない。tool callingのパーサーを経由しないため安定。
欠点: 複雑なネスト構造は対応しづらい。enumの選択肢が多すぎると精度が下がる。
方法2: プロンプトエンジニアリング+後処理
JSONスキーマ制約が使えない環境(古いllama.cppバージョンなど)向け。
import json
import re
SYSTEM_PROMPT = """あなたはタスク分類AIです。
以下のJSON形式で必ず回答してください。他のテキストは出力しないでください。
{"priority": "P0-P3", "category": "client|sales|content|infra|monitor", "action": "execute|notify|skip"}
"""
result = llm_call(f"{SYSTEM_PROMPT}\n\n分類対象: {message}")
# JSON抽出(モデルが余計なテキストを付与するケースへの対処)
json_match = re.search(r'\{[^{}]+\}', result)
if json_match:
parsed = json.loads(json_match.group())
方法3: thinking mode除去
Qwen 3.5はデフォルトでthinking mode(<think>...</think>タグ)を出力する。--reasoning-budget 0で抑制できるが、完全には消えないことがある。
import re
def clean_thinking(content: str) -> str:
"""Qwen 3.5のthinkingタグを除去"""
content = re.sub(r"<think>.*?</think>\s*", "", content, flags=re.DOTALL).strip()
content = re.sub(r"^</think>\s*", "", content).strip()
return content
この処理は全てのLLM呼び出しに一律で適用している。
実装アーキテクチャ:ローカルLLM+クラウドAIの二層構成(Apple Silicon最適化)
役割分離の設計思想
全てをローカルLLMで賄おうとすると破綻する。筆者の環境では以下のように役割を分離している。
┌─────────────────────────────────────┐
│ ローカルAI (Qwen 3.5-27B, :8080) │
│ 役割: 監視・分類・振り分け │
│ │
│ ・サービスのヘルスチェック │
│ ・受信メッセージの優先度分類 │
│ ・タスクのルーティング │
│ ・異常検知時のアラート生成 │
│ ・状況レポート生成 │
└──────────────┬──────────────────────┘
│ 重い仕事は委任
▼
┌─────────────────────────────────────┐
│ クラウドAI (Claude) │
│ 役割: 全実務 │
│ │
│ ・記事生成・SNS投稿 │
│ ・コードレビュー・品質評価 │
│ ・営業提案・顧客対応 │
│ ・ブラウザ操作 │
└─────────────────────────────────────┘
ローカルAIは「振り分け係」に徹する。軽量な分類タスクだけを処理し、実務はクラウドAIに委任する。
Python統一呼び出しレイヤー
ローカルAIとクラウドAIを透過的に切り替えるライブラリを実装した。
"""llm_provider.py — ローカルAI統一呼び出しレイヤー"""
from openai import OpenAI
import subprocess
import re
import os
def llm_call(prompt: str, *, provider: str = "llama", model: str = "27b",
max_tokens: int = 4096, temperature: float = 0.7, timeout: int = 120) -> str:
"""統一LLM呼び出し — プロバイダーを意識せず使える"""
if provider == "claude":
return _call_claude(prompt, model=model, timeout=timeout)
return _call_llama(prompt, model=model, max_tokens=max_tokens,
temperature=temperature, timeout=timeout)
def _call_llama(prompt, *, model="27b", max_tokens=4096,
temperature=0.7, timeout=120) -> str:
"""llama-server (OpenAI互換API) 呼び出し"""
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="sk-local")
try:
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
max_tokens=max_tokens,
temperature=temperature,
timeout=timeout,
)
content = response.choices[0].message.content or ""
# thinkingタグ除去
content = re.sub(r"<think>.*?</think>\s*", "", content, flags=re.DOTALL).strip()
return content
except Exception:
# llama-server停止時 → Claude CLIに自動フォールバック
return _call_claude(prompt, model="sonnet", timeout=timeout)
def _call_claude(prompt, *, model="sonnet", timeout=300) -> str:
"""Claude CLIフォールバック"""
cmd = ["claude", "--model", f"claude-{model}-4-6",
"--print", "--bare", prompt]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
return result.stdout.strip()
ポイント: _call_llamaが失敗したら自動的に_call_claudeにフォールバックする。llama-serverがダウンしても、呼び出し側のコードを変更する必要がない。
Bash版の呼び出し
# scripts/lib/llm-provider.sh
source scripts/lib/llm-provider.sh
# タスクタイプでルーティング
LLM_TASK_TYPE=health_check llm_call "サーバー状態を確認" # → ローカルAI
LLM_TASK_TYPE=article_write llm_call "記事を書いて" # → Claude
llm-routing.confで1行変更するだけでルーティングを切り替えられる。
# scripts/lib/llm-routing.conf
LLM_ROUTE_health_check=llama:27b
LLM_ROUTE_article_write=claude:opus
LLM_ROUTE_message_classify=llama:27b
モデル検証で学んだこと
試して落ちたモデルたち
筆者は最終的にQwen 3.5-27Bに落ち着くまでに、いくつかのモデルを試した。
| モデル | 結果 | 不採用理由 |
|---|---|---|
| Qwen 3.5-35B-A3B (Flash-MoE) | ❌ 不採用 | 日本語が文字化け。サーバーも不安定。llama.cppでMoE GGUFがkernel panic(GitHub #19825) |
| Gemma 4 31B | ❌ 不採用 | tool callingが壊れている。Ollamaは非対応。llama.cppもトークナイザーバグ |
| Qwen 3.5-9B Q4_K_M | △ フォールバック用 | 動くが精度不足。分類タスクでの誤判定率が27Bの3倍 |
| Qwen 3.5-27B Q3_K_M | ✅ 採用 | 安定動作。日本語分類で実用レベル。18 tok/sで監視タスクに十分 |
MoEモデルの罠
Qwen 3.5-35B-A3Bは「35Bパラメータのうち3Bだけアクティブ」というFlash-MoE。理論上は軽くて速いが、実際は:
- llama.cppでGGUF変換するとkernel panic
- 公式の
safetensors形式で直接推論するとサーバーが不安定 - 英語は問題ないが日本語が文字化け
2026年4月時点ではDenseモデル一択。MoEのGGUF対応はllama.cppの今後のアップデートに期待。
パフォーマンスチューニング(Apple Silicon Metal GPU)
Metal GPUの活用
Flash Attentionは、Attention計算のメモリ効率を改善するアルゴリズム。通常のAttentionがO(n²)のメモリを消費するのに対し、Flash AttentionはタイリングによりVRAM使用量を大幅に削減する。
# Flash Attentionを有効化(必須)
--flash-attn on
# Metal GPUのレイヤー数を明示(デフォルトで全レイヤーGPU)
-ngl 99
M5 Proの統合メモリアーキテクチャでは、CPU⇔GPU間のデータコピーが不要。これがllama.cppのMetal対応の最大の利点。
thinking mode無効化の効果
# thinking modeあり(デフォルト)
--reasoning-budget -1 # → 平均応答時間 4.2秒
# thinking mode無効化
--reasoning-budget 0 # → 平均応答時間 1.8秒
監視タスクでは「深く考える」必要はない。思考トークンを省略するだけで応答時間が半分以下になる。
ヘルスチェック
# llama-serverの状態確認
curl http://localhost:8080/health
# → {"status":"ok"}
# launchdで常駐化 → ダウン時自動復旧
launchctl load ~/Library/LaunchAgents/com.local.llama-server.plist
よくある質問
Q: OllamaじゃなくてMLXは?
MLXはApple Silicon向けに最適化されており、速度ではllama.cppを上回る(同じ27Bモデルで25-50%高速)。ただし、OpenAI互換APIサーバーとしての機能が限定的で、response_format(json_schema制約)に対応していない。速度重視ならMLX、API互換性重視ならllama.cpp。
Q: Gemma 4のtool callingはいつ直る?
llama.cppにはトークナイザー修正がマージ済み(2026年4月)。Ollamaはv0.21で対応予定。ただし、量子化モデルでのtool calling精度は構造的に限界がある。json_schema制約の方が安定する可能性が高い。
Q: クラウドAIなしで全部ローカルでやれる?
27Bモデルで実用的にこなせるのは、分類・要約・レポート生成など「出力が短いタスク」。長文記事の生成、複雑なコードレビュー、マルチステップのエージェント動作は、クラウドAIの方が圧倒的に品質が高い。筆者は「分類はローカル、実務はクラウド」の二層構成を推奨する。
まとめ
| 判断ポイント | 筆者の選択 | 理由 |
|---|---|---|
| モデル | Qwen 3.5-27B | 日本語性能・llama.cpp互換性・安定性 |
| バックエンド | llama.cpp | Metal 4対応・OpenAI互換API・tool callingテンプレート |
| 構造化出力 | json_schema制約 | tool callingより安定・パース失敗ゼロ |
| 量子化 | Q3_K_M | 品質と速度のバランス(13GB/18 tok/s) |
| アーキテクチャ | ローカル+クラウド二層 | 分類=ローカル、実務=クラウド |
ローカルLLMは「万能ツール」ではない。得意なことに特化させ、苦手な部分はクラウドに任せる。この割り切りが、実運用での安定性を生む。
ベンチマークではなく、自分のタスクで動くかどうか。それがモデル選定の唯一の基準だ。
関連記事
ローカルLLMやAI開発に興味がある方は、以下の記事も参考にしてほしい。
- Claude Code Auto-Fix完全ガイド2026|PR自動修正で開発効率を劇的に上げるワークフロー構築 — AIによるCI/CD自動修正の実践
- フリーランスエンジニア向けプラットフォーム比較2026 — AI開発スキルを活かすフリーランス案件の探し方
GPUサーバーでローカルLLMを動かしたい方へ: Macのメモリが足りない、あるいはリモートから常時アクセスしたい場合、VPSにGPUインスタンスを立てるのが現実的な選択肢だ。XServer VPSはGPU付きプランがコスパに優れ、llama-serverの常駐にも適している。
参考リソース
本記事の執筆にあたり、以下の情報源を参照・検証した。
モデル公式・ベンチマーク
- Qwen 3.5 公式リポジトリ(GitHub) — モデルカード・ベンチマーク数値の一次ソース
- Gemma 4 公式ブログ(Google) — リリース情報・アーキテクチャ詳細
- bartowski/Qwen_Qwen3.5-27B-GGUF(Hugging Face) — GGUF量子化モデルの配布元
llama.cpp tool calling関連
- llama.cpp function-calling.md(公式ドキュメント) — tool calling実装の公式解説
- llama.cpp Issue #21384 — Gemma 4 tool call配列パラメータのシリアライズバグ
- llama.cpp Issue #21516 — Gemma 4
<unused>トークン無限ループバグ
比較・分析記事
- Gemma 4 vs Qwen 3.5: Open-Weight Model Comparison(MindStudio) — ベンチマーク横断比較
- Notes on Qwen3.5 vs Gemma4 for Local Agentic Coding(Aayush Garg) — ローカルエージェント用途での実測比較
- Qwen 3.5 Mac MLX vs Ollama Speed Test(InsiderLLM) — Apple Silicon上のバックエンド速度比較
筆者環境での検証条件
- ハードウェア: Mac(Apple Silicon M5 Pro / 統合メモリ36GB)
- llama.cpp: mainブランチ最新ビルド(2026年4月8日時点)
- 検証期間: 2026年4月2日〜4月9日(Gemma 4リリース翌日から1週間)
- 計測方法: llama-serverの
/healthエンドポイント+手動タイマー計測(tok/sはllama-server標準出力から取得)
本記事のリンクにはアフィリエイト広告(A8.net)を含みます。XServer VPSを契約された場合、執筆者に報酬が発生することがあります。サービス選定は実体験に基づく独立した評価であり、掲載順や評価は報酬の有無と無関係です。料金・条件は2026年4月時点の情報を参照しており、契約前には公式サイトで最新条件を必ず確認してください。記事最終更新日: 2026-04-27。