GPU WorkerとWorker自己登録の仕組みを作った話

前回はThinkCentreをWorkerとして動かすところまでやった。

Redisのキューを監視して、タスクを処理して、結果をPostgreSQLに保存する。

最小構成は動いた。

次のステップは決まっていた。

「RTX 3070 TiをGPU Workerとして追加する」


Windows機をWorkerにする

手持ちのRTX 3070 Ti搭載PCはWindows 11だ。

ThinkCentreはUbuntu Serverで動いているので、同じように使えるかと言うと少し事情が違う。

選択肢は3つあった。

  • WSL2(Ubuntu)で動かす
  • Windows上でネイティブに動かす
  • Hyper-VでUbuntu VMを立てる

GPUを最大限活かしたい。そうなると、Hyper-V VMはGPUパススルーの設定が面倒だ。Windowsネイティブはサービス化が煩雑になる。

WSL2が現実的な選択だった。


WSL2のインストール

まずWSL2が入っていなかったので、PowerShellで1行叩く。

wsl --install

再起動後にUbuntuの初期設定が走る。

ユーザー名はThinkCentreと揃えて sagawa にした。パスが共通化されて後が楽になる。


GPUが見えるか確認する

WSL2からGPUが使えるかどうかは自明ではない。

Ubuntuを起動して nvidia-smi を叩く。

| NVIDIA GeForce RTX 3070 Ti     On  |
| CUDA Version: 13.2                 |

問題なく見えた。

WSL2はWindowsのNVIDIAドライバをそのまま使う設計になっている。ドライバが新しければ基本的に動く。


Ollamaのインストール

インストールスクリプトを叩く。

curl -fsSL https://ollama.com/install.sh | sh

ここで一度止まった。

ERROR: This version requires zstd for extraction.

zstd が入っていなかった。先にインストールしてから再実行で通った。


モデルの選定

ThinkCentreには1Bクラスの軽量モデルしか入れていない。

RTX 3070 TiはVRAM 8GB。せっかくGPUがあるなら、もう少し大きいモデルを入れたい。

入れたのはこの3つ。

  • gemma3:12b(8.1GB)
  • qwen2.5:7b(4.7GB)
  • llama3.2:3b(2.0GB)

ThinkCentreの1Bモデルと役割を分けることで、Coordinatorがタスクの重さで振り分けられる構成に近づく。


WSL2特有の問題

ThinkCentreはUbuntu Serverなので、OllamaをLAN公開するだけで済んだ。

WSL2は少し違う。

WSL2内のIPはLANから直接見えない。WindowsがNAT的に動いているため、外からアクセスするにはポートフォワーディングが必要だ。

PowerShellで設定する。

netsh interface portproxy add v4tov4 listenport=11434 listenaddress=0.0.0.0 connectport=11434 connectaddress=172.28.138.1

172.28.138.1 はWSL2のIP。ここにWindowsの11434番ポートへのアクセスを転送する。

さらにWindowsファイアウォールで11434を開放する。

netsh advfirewall firewall add rule name="Ollama WSL2" dir=in action=allow protocol=TCP localport=11434

AI-CoreからcurlでモデルのJSONが返ってきたとき、ようやく繋がった実感があった。


PostgreSQLの接続許可も必要だった

worker_gpu.pyを起動してみると、推論は走るがDBに保存できない。

FATAL: no pg_hba.conf entry for host "192.168.0.200"

ThinkCentreのIPはpg_hba.confに追加済みだったが、RTX 3070 Ti PCのIPは未登録だった。

AI-Coreのpg_hba.confに1行追加して再起動。これで通った。

「Workerを増やすたびにこの作業が発生する」

もう少し自動化できる余地がある。今後の課題だ。


動いた

設定が揃ったところで比較タスクを投げる。

{
  "prompt": "日本の首都はどこですか?",
  "models": ["gemma3:12b", "qwen2.5:7b", "llama3.2:3b"]
}

Workerのログに結果が流れた。

[compare] done model=gemma3:12b 45087ms
[compare] done model=qwen2.5:7b 16769ms
[compare] done model=llama3.2:3b 3186ms

APIで確認する。

{
  "progress": "3/3",
  "results": [
    { "model": "gemma3:12b",  "response": "日本の首都は東京都です。", "duration_ms": 45087 },
    { "model": "qwen2.5:7b",  "response": "日本の首都は東京です。",   "duration_ms": 16769 },
    { "model": "llama3.2:3b", "response": "東京です。",               "duration_ms":  3186 }
  ]
}

progress: 3/3。全モデル正答。

llama3.2:3bの3秒に対してgemma3:12bは45秒。モデルサイズと速度のトレードオフが数字で見えた。


Worker自己登録の仕組みを作った

ここまでで2台のWorkerが動いている。

ただ、Coordinatorはどのマシンが今動いているかを把握していない。

「Coordinatorが有効なWorkerを知っている」

状態にしたかった。

設計はシンプルにした。

Worker起動
    ↓
サニティチェック(各モデルに簡単な質問を投げる)
    ↓
正常応答 → Coordinatorの /worker/register に登録
失敗     → そのモデルは除外
    ↓
Redisキュー監視へ

サニティチェックの質問は「1+1は?」。厳密な正答チェックはしない。何かしら返ってくればOKとした。

登録情報はRedisにTTL付きで保存する。TTLは5分。Workerが落ちれば自動的に失効する。


登録結果の確認

2台ともWorkerを再起動してから /workers を叩く。

{
  "workers": [
    {
      "worker_id": "moon",
      "host": "192.168.0.4",
      "models": ["hf.co/LiquidAI/LFM2.5-1.2B-JP-GGUF:Q4_K_M", "gemma3:1b"]
    },
    {
      "worker_id": "rtx3070ti",
      "host": "192.168.0.200",
      "models": ["gemma3:12b", "qwen2.5:7b", "llama3.2:3b", "gemma3:1b", "hf.co/LiquidAI/LFM2.5-1.2B-JP-GGUF:Q4_K_M"]
    }
  ]
}

2台が自分のモデル情報をCoordinatorに伝えている。

構想段階で描いていた、

「Workerが自律的に参加する」

形に少し近づいた気がした。


現在の構成

ThinkPad P50(192.168.0.2)
  └─ Hyper-V
       └─ AI-Core VM(192.168.0.40)
            ├─ FastAPI Coordinator  :8000
            ├─ Redis               :6379
            └─ PostgreSQL          :5432

ThinkCentre moon(192.168.0.4)
  ├─ Ollama
  │    ├─ LFM2.5-1.2B-JP-GGUF:Q4_K_M
  │    └─ gemma3:1b
  └─ ollama-worker.service(常時稼働)

RTX 3070 Ti PC(192.168.0.200)
  └─ Windows 11 + WSL2
       ├─ Ollama
       │    ├─ gemma3:12b
       │    ├─ qwen2.5:7b
       │    ├─ llama3.2:3b
       │    ├─ gemma3:1b
       │    └─ LFM2.5-1.2B-JP-GGUF:Q4_K_M
       └─ ollama-worker-gpu.service(常時稼働)

次にやること

残っている課題がいくつかある。

WSL2のIPは再起動で変わる可能性がある。ポートフォワーディングの自動更新が必要だ。

Worker登録のTTLが5分なので、長時間稼働するWorkerは定期的に再登録するHeatbeat的な仕組みも欲しい。

そして本来やりたかったこと、

「Coordinatorがモデル一覧を見て、タスクを自動で振り分ける」

機能がまだ未実装だ。

登録情報は揃った。あとは使う側を作るだけ。