家庭内分散AI基盤をアップグレード:queue分離・capability routing・モデル追加

前回の記事ではOpenWebUI連携とWorker実負荷ルーティングを実装した。今回はその土台をさらに強化し、「GPUタスクはGPU Workerへ、CPUタスクはCPU Workerへ」という能力ベースのルーティングと、新モデルの大量追加を行った。

今回やったこと

  • Redisキューを tasks:gpu / tasks:cpu に分離
  • WorkerにCapability(能力宣言)を持たせるcapability routingを実装
  • moon・rtx3070tiに新モデルを追加(合計6モデル)
  • GPU情報取得の修正(WSL2環境のnvidia-smiパス問題)
  • 全ドメインの実績データ収集

なぜqueue分離が必要だったか

これまでタスクキューは tasks という1本だった。この構成には問題があった。

gemma3:12b のような大型モデルはVRAM 8GBのGPUがないと実用速度が出ない。しかしキューが1本だと、GPUを持たないmoon(ThinkCentre・CPU推論専用)にもこのタスクが飛んでしまう。moonで gemma3:12b を動かすと数分待つことになる。

さらに将来、埋め込みWorkerやWeb検索Workerを追加することを考えると、「誰でも何でも処理する」設計では破綻する。Worker側が「自分は何ができるか」を宣言し、Coordinatorがそれを見てタスクを振る仕組みが必要だった。

設計:supportsとキューの対応

各WorkerのHeartbeatに supports フィールドを追加した。

# moon(CPU推論のみ)
"supports": ["cpu_inference"]

# rtx3070ti(CPU・GPU両方)
"supports": ["cpu_inference", "gpu_inference"]

# 将来のRAG/Web検索Worker(未実装)
"supports": ["embedding", "web_search"]

Coordinator側ではモデル名を見てキューを決める。

GPU_MODELS = {"gemma3:12b", "qwen2.5:7b", "phi4-mini:3.8b", "gemma3:4b"}

def resolve_queue(model: str) -> str:
    return "tasks:gpu" if model in GPU_MODELS else "tasks:cpu"

GPU_MODELS に追記するだけで新しいGPU専用モデルを追加できる。

Workerは自分が監視するキューを設定で持つ。rtx3070tiは tasks:gpu を優先しつつ tasks:cpu も監視する。moonが落ちているときのフォールバックにもなる。

# rtx3070ti:GPU優先・CPUもフォールバック
"redis_queues": ["tasks:gpu", "tasks:cpu"]

# moon:CPUのみ
"redis_queues": ["tasks:cpu"]

Redisの brpop はリストを渡すと先頭から順に優先して取り出す。この性質を使って優先度制御を実現している。

実装の変更点

変更したファイルは4つ。

worker_base.py

最大の変更は brpop の呼び出し部分。

# 変更前
r.brpop("tasks", timeout=5)

# 変更後(複数キューを優先順に監視)
r.brpop(["tasks:gpu", "tasks:cpu"], timeout=5)

また register() 関数に supports パラメータを追加し、HeartbeatのたびにCoordinatorへcapabilityを送信するようにした。後方互換として旧形式の redis_queue(単数文字列)が渡された場合は自動でリストに変換する。

coordinator_api.py

Worker選択ロジックに capability フィルタリングを追加。

def filter_workers_by_capability(workers, required):
    return [w for w in workers if required in w.get("supports", [])]

Worker登録時に supports をRedisに保存する。supports を送ってこない古いWorkerは ["cpu_inference"] とみなすことで後方互換を確保している。

追加したモデル

rtx3070ti(VRAM 8GB)

モデル 追加理由
phi4-mini:3.8b Microsoftモデル・コード系に強いとされる
gemma3:4b 12bと1bの間を埋める中間サイズ
llama3.2:1b 最速候補・moonとの比較
qwen2.5:3b GPU推論なら実用速度が出るはず

moon(ThinkCentre・CPU推論)

モデル 追加理由
llama3.2:1b CPU推論でも実用速度が出る超軽量モデル
qwen2.5:3b 追加後に実測で遅すぎたためrtx3070ti専用に変更

qwen2.5:3b はmoonのCPUで動かすと26〜70秒かかることが判明した。rtx3070tiのGPU推論に限定することで適切な速度が期待できる。「試して実測して判断する」という設計思想が早速活きた。

GPU情報取得の修正(WSL2のPATH問題)

rtx3070tiのWorkerはWSL2上で動いている。GPU使用率やVRAM使用量を取得するために nvidia-smi を呼んでいるが、systemdサービスはデフォルトのPATHが短く /usr/lib/wsl/lib が含まれていない。

解決策はsystemdのserviceファイルに Environment=PATH=... を1行追加するだけだった。

[Service]
User=hogehoge
Environment=PATH=/usr/lib/wsl/lib:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=/home/hogehoge/worker/venv/bin/python /home/hogehoge/worker_gpu.py

修正後、/workers エンドポイントでVRAM情報が正しく取れるようになった。

"gpu_util": 30.0,
"vram_used_mb": 6674.0,
"vram_total_mb": 8192.0

実績データの収集結果

/compare で全モデルに同じプロンプトを投げ、ドメインごとの実績を収集した。

codeドメイン

モデル avg_ms n
LFM2.5-1.2B-JP 6,838 5
qwen2.5:7b 10,320 3
phi4-mini:3.8b 11,399 1
gemma3:4b 13,773 1
gemma3:12b 143,702 5

japaneseドメイン(今回重点収集)

モデル avg_ms n
gemma3:1b 1,943 5
llama3.2:1b 4,647 4
phi4-mini:3.8b 4,703 4
qwen2.5:7b 4,877 4
gemma3:12b 30,570 4

前回まで japanese ドメインの実績がほぼゼロだったが、今回の収集で全モデルn=4以上に揃った。

興味深いのは gemma3:1b がjapaneseで突出して速いこと(2位の2倍以上速い)。日本語タスクではモデルサイズよりアーキテクチャの相性が出ているのかもしれない。ただし速度と品質は別の話で、品質評価(LLM-as-judge)は今後の課題だ。

一方 qwen2.5:3b はjapaneseでp50=2,540msと速いのにp95=22,856msと大きく乖離している。moonのCPUで動いたケースとrtx3070tiのGPUで動いたケースが混在しているためで、Worker間のばらつきが数字に出ている。

capability routingの動作確認

/route/preview エンドポイントで振り分け結果を確認できる。

curl -G http://localhost:8000/route/preview \
  --data-urlencode "prompt=大きなデータを分析して"
{
  "domain": "general",
  "candidates": [
    {"model": "llama3.2:3b",  "queue": "tasks:cpu", "capability": "cpu_inference"},
    {"model": "qwen2.5:7b",   "queue": "tasks:gpu", "capability": "gpu_inference"},
    {"model": "gemma3:12b",   "queue": "tasks:gpu", "capability": "gpu_inference"},
    ...
  ]
}

GPU専用モデルが tasks:gpu に、それ以外が tasks:cpu に正しく振り分けられている。

次回の予定

次はmars(専用回線サーバー)にSearXNGをDockerで立てる。SearXNGはオープンソースのメタ検索エンジンで、JSON APIで検索結果を取得できる。これをAI基盤に組み込むことで、ローカルLLMに「最新情報を踏まえた回答」という新しい能力を持たせるのが目的だ。

完全ローカル完結・無料無制限・プライバシー保護という基盤の思想とも合っている。

実装順序としては:SearXNG構築 → Web検索Worker → RAG基盤(Qdrant)→ Coordinatorへの統合、という流れで進める予定だ。