
VLM 서버
VLM 서버는 Gemma 4 E4B(mlx 4bit)를 mlx-vlm으로 불러 판 이미지와 텍스트를 보고 한 수를 고르게 합니다. 바둑의 GO19와 같은 틀의 OT88 프로토콜로 Qt 앱과 웹의 요청을 받습니다. 프롬프트에는 합법수 목록을 함께 넣습니다.
로드 실패
서버 로그에는 서버를 띄운 기록이 두 번 있고, 두 번 모두 같은 오류로 실패했습니다.
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 상대였는지 알 수 없는 이유입니다.
무작위로 바꾸는 길
모델을 불러온 뒤에도 무작위로 바꾸는 길이 세 가지 있습니다.
- 추론 중 오류가 날 때
- 둘 곳이 있는데 모델이 패스할 때
- 모델이 둘 수 없는 자리나 금지 좌표를 고를 때
웹 화면은 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
아직 댓글이 없습니다. 읽다가 궁금한 점이나 보탤 내용을 남겨 주세요.
댓글을 등록하려면 구글로 시작하기를 눌러 닉네임과 메일 주소를 등록해 주세요.
구글로 시작하기