前回までで、GPU Worker(RTX 3070 Ti)を分散AI環境に組み込み、Worker自己登録の仕組みまで動くようになった。
今回は「動いているけど、ちょっと不安な部分」を3つ潰した話。
課題1:WSL2のIPが再起動のたびに変わる
WSL2は起動するたびにIPアドレスが変わる。
前回、ポートフォワーディングをこう設定した。
netsh interface portproxy add v4tov4 listenport=11434 listenaddress=0.0.0.0 connectport=11434 connectaddress=172.28.138.1
この 172.28.138.1 が、再起動後には別のアドレスになっている可能性がある。そのたびに手動で設定し直すのは現実的ではない。
解決策:ログオン時に自動更新するスクリプト
C:\Scripts\update-wsl2-portproxy.ps1 を作成した。
やっていることはシンプルで、
wsl hostname -IでWSL2の現在のIPを取得- 既存のポートフォワーディングを削除
- 取得したIPで再設定
- ログに記録
$wslIp = (wsl hostname -I).Trim().Split(" ")[0]
netsh interface portproxy delete v4tov4 listenport=11434 listenaddress=0.0.0.0
netsh interface portproxy add v4tov4 listenport=11434 listenaddress=0.0.0.0 connectport=11434 connectaddress=$wslIp
これをWindowsのタスクスケジューラに登録して、ログオン時に自動実行されるようにした。管理者権限(RunLevel Highest)で動かす必要があるのがポイント。
再起動後のログを確認すると、ちゃんと動いていた。
2026-05-25 22:28:49 WSL2 IP: 172.28.138.1
2026-05-25 22:28:49 Set: 0.0.0.0:11434 -> 172.28.138.1:11434
2026-05-25 22:28:49 Done
これでWSL2のIPを意識しなくてよくなった。
課題2:WorkerのTTLが切れると登録が失効する
CoordinatorはWorkerの登録情報をRedisに保存しているが、TTLを5分に設定している。
TTLは「このWorkerは生きているか」を確認するための仕組み。5分経過すると自動的に登録が消え、Coordinatorはそのworkerを認識しなくなる。
長時間稼働させる場合、何もしなければ起動から5分後に登録が失効してしまう。
解決策:Heartbeatスレッド
worker.py と worker_gpu.py にHeartbeat機能を追加した。
バックグラウンドスレッドで4分ごとに /worker/register を叩き、TTLをリセットし続ける。
def heartbeat_loop(available_models: list):
while True:
time.sleep(240) # 4分
register(available_models)
# メインループと並走させる
t = threading.Thread(target=heartbeat_loop, args=(available_models,), daemon=True)
t.start()
daemon=True にしておくことで、Worker本体が終了したときにHeartbeatスレッドも自動で止まる。
TTL 5分・Heartbeat 4分間隔なので、Workerが生きている限り登録は維持され、落ちたら5分以内に失効する。ちょうどいいバランスだと思っている。
課題3:/compare でモデルを毎回手動指定するのが面倒
これまでの /compare はこう使っていた。
curl -X POST http://192.168.0.40:8000/compare \
-H "Content-Type: application/json" \
-d '{"prompt": "日本の首都は?", "models": ["gemma3:12b", "qwen2.5:7b", "llama3.2:3b"]}'
モデルが増えるたびにリクエストを書き直す必要があり、「今どのWorkerが何を持っているか」を把握していないと使えない。
解決策:models省略時に全Worker対象で自動振り分け
coordinator_api.py を修正した。
models フィールドを省略可能にして、省略された場合は登録済みWorkerから利用可能なモデルを自動収集してタスクを生成するようにした。
def collect_models_from_workers() -> list[str]:
keys = r.keys("worker:*")
seen = set()
models = []
for key in keys:
data = r.hgetall(key)
for m in json.loads(data.get("models", "[]")):
if m not in seen:
seen.add(m)
models.append(m)
return models
これで models を省略するだけで全モデルが対象になる。
# これだけでいい
curl -X POST http://192.168.0.40:8000/compare \
-H "Content-Type: application/json" \
-d '{"prompt": "日本の首都は?"}'
レスポンスに "auto_selected": true が含まれていれば自動振り分けが動いている。
まとめ
今回実装した3つは、どれも「動いているけど運用したら困る」類のものだった。
- WSL2のIPが変わっても自動で追従する
- Workerが長時間稼働しても登録が切れない
- モデルを意識せずに全Worker比較ができる
インフラとして「放っておいても動き続ける」状態に一歩近づいた。
次は、タスクの種類や過去の応答速度をもとにWorkerを賢く選ぶ仕組みを作っていきたい。
※ 記事内のIPアドレスは実際の環境とは異なる値を使用しています。