
VLM サーバー
VLM サーバーは Gemma 4 E4B(mlx 4bit)を mlx-vlm で読み込み、盤の画像とテキストを見て一手を選ばせます。囲碁の GO19 と同じ枠組みの OT88 プロトコルで、Qt アプリとWebのリクエストを受けます。プロンプトには合法手のリストも一緒に入れます。
読み込み失敗
サーバーのログにはサーバーを起動した記録が2回あり、2回とも同じエラーで失敗していました。
ValueError: Missing 54 parameters
欠けている重みは、言語モデルの24〜41層の self_attn k_proj、v_proj、k_norm です。今インストールされている mlx-vlm は 0.5.0 です。モデルファイルと mlx-vlm が期待する構造が合っていないようです。
失敗してもサーバーは起動する
サーバーはモデルを読み込めないと「ランダム合法手モード」に切り替わり、そのままポートを開きます。監視スクリプトはポートが応答するかどうかしか見ないので、正常と判断します。画面で VLM を選ぶと、合法手の一つがランダムに返ってきていました。
ログには接続と切断だけが3万回以上残っていて、どの接続が実際の対局リクエストだったのかは区別できません。1回目の記事の31局のうち何局が VLM 相手だったのか分からないのはそのためです。
ランダムに切り替わる経路
モデルを読み込んだ後でも、ランダムに切り替わる経路が三つあります。
- 推論中にエラーが起きたとき
- 打てる場所があるのにモデルがパスしたとき
- モデルが打てない場所や禁止座標を選んだとき
Web画面は、VLM の応答が打てない場所ならローカルAIに切り替えます。今公開されているローカルAIは2回目の記事の一手AIです。どの場合も、画面には VLM が打ったように見えます。囲碁ではこっそり勝手な手に置き換えないことにしましたが、オセロは逆に作られていました。
この段階の実際のコード
モデルの読み込みに失敗すると model を空にして、サーバーを起動し続ける部分です。
vlm_server/server.py
model: Optional[_MlxVlmModel] = None
if load_model:
model = _MlxVlmModel(model_id, max_new_tokens=max_new_tokens, temperature=temperature)
try:
model.start(wait_load=True)
except BaseException as e:
LOGGER.exception("모델 로드 실패 → 무작위 합법수 모드: %s", e)
model = Noneリクエストを受けて手を決める部分です。モデルがないか、答えが合法手でなければランダムに選びます。
vlm_server/server.py
model = self.server.model
row = col = None
is_pass = False
if model is not None:
try:
row, col, is_pass = model.predict_move(
img, board, ai_side, legal, forbidden=list(bans), timeout_s=180.0
)
except Exception as e:
LOGGER.warning("VLM 추론 실패, 합법수 무작위: %s", e)
row = col = None
is_pass = False
if is_pass:
LOGGER.info("모델 PASS (합법수 있음) → 합법수 무작위")
row = col = None
if row is None or col is None or (row, col) not in legal or (row, col) in bans:
if row is not None and col is not None:
LOGGER.info("불법/금지 (%s,%s) → 합법수 무작위", row, col)
pick = _choose_legal(legal, bans)
if pick is None:
_send_pass(sock)
continue
row, col = pick
# ...
def _choose_legal(
legal: list[tuple[int, int]], forbidden: set[tuple[int, int]]
) -> Optional[tuple[int, int]]:
remain = [m for m in legal if m not in forbidden]
pool = remain or legal
if not pool:
return None
return random.choice(pool)次の記事は「4. 囲碁・五目並べで学んだことをオセロに移す計画」です。
COMMENTS · CHAPTER 03
VLM は一度も打たなかった: Gemma 4 の読み込み失敗 コメント 0
まだコメントはありません。気になった点や補足があれば残してください。
コメントを登録するには、Googleで始めてニックネームとメールアドレスを登録してください。
Googleで始める