게시판바둑 on-policy 학습: 대국 로그를 KataGo 라벨로 바꾸기예제 소스

예제 소스

바둑 on-policy 학습: 대국 로그를 KataGo 라벨로 바꾸기 · 파일 169개

run_heuristic_gemma4_r2.py

#!/usr/bin/env python3
"""지난 대국 국면을 휴리스틱 수로 다시 라벨해 Gemma 4를 이어서 학습하고, 5국을 둔다."""

from __future__ import annotations

import json
import os
import signal
import socket
import subprocess
import time
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
PY = ROOT / ".venv" / "bin" / "python"
OUT = ROOT / "logs" / "heuristic_gemma4_r2_20261009"
HOLD = ROOT / "logs" / "mariago_web_watch" / "GEMMA_HOLD"
LORA_FILE = ROOT / "logs" / "mariago_web_watch" / "gemma_lora.txt"
MODEL = "********"
BASE = "********"
LORA = "********"
PORT = 9341


def log(msg: str) -> None:
    print(msg, flush=True)


def run(cmd: list[str], log_path: Path) -> int:
    log("[runner] " + " ".join(cmd))
    log_path.parent.mkdir(parents=True, exist_ok=True)
    with log_path.open("ab") as fh:
        return subprocess.call(cmd, cwd=ROOT, stdout=fh, stderr=subprocess.STDOUT)


def port_open(port: int) -> bool:
    sock = socket.socket()
    sock.settimeout(1)
    try:
        sock.connect(("127.0.0.1", port))
        return True
    except OSError:
        return False
    finally:
        sock.close()


def wait_port(port: int, timeout_s: float) -> bool:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        if port_open(port):
            return True
        time.sleep(5)
    return False


def gemma_pids() -> list[int]:
    try:
        out = subprocess.check_output(["pgrep", "-f", r"vlm_server serve.*--port 9341"], text=True)
    except subprocess.CalledProcessError:
        return []
    return [int(x) for x in out.split() if x.strip()]


def stop_gemma() -> None:
    pids = gemma_pids()
    if not pids:
        log("[runner] gemma already down")
        return
    log(f"[runner] stop gemma {pids}")
    for pid in pids:
        try:
            os.kill(pid, signal.SIGTERM)
        except ProcessLookupError:
            pass
    deadline = time.time() + 40
    while time.time() < deadline and (port_open(PORT) or gemma_pids()):
        time.sleep(1)
    for pid in gemma_pids():
        try:
            os.kill(pid, signal.SIGKILL)
        except ProcessLookupError:
            pass


def start_gemma() -> subprocess.Popen:
    cmd = [
        str(PY), "-m", "vlm_server", "serve",
        "--host", "127.0.0.1", "--port", str(PORT),
        "--model", MODEL, "--lora-dir", LORA,
        "--text-only-move", "--board-repr", "aligned", "--no-score-guided",
        "--illegal-retry-max", "16", "--illegal-retry-budget-s", "240",
        "--log-jsonl", str(OUT / "gemma4.jsonl"),
    ]
    OUT.mkdir(parents=True, exist_ok=True)
    logf = open(OUT / "gemma_serve.stdout", "ab")
    log(f"[runner] start gemma4 {LORA}")
    proc = subprocess.Popen(cmd, cwd=ROOT, stdout=logf, stderr=subprocess.STDOUT, start_new_session=True)
    if not wait_port(PORT, 600):
        proc.terminate()
        raise SystemExit("gemma4 did not listen")
    log(f"[runner] gemma4 listening pid={proc.pid}")
    return proc


def main() -> int:
    OUT.mkdir(parents=True, exist_ok=True)
    HOLD.parent.mkdir(parents=True, exist_ok=True)
    HOLD.write_text("r2 train\n", encoding="utf-8")
    stop_gemma()
    try:
        rc = run(
            [
                str(PY), "scripts/dataset_heuristic_teacher.py",
                "--relabel",
                "--source", "logs/heuristic_gemma4_20261009/matches.sqlite:BW",
                "--source", "logs/gemma4_r5_20261009/matches.sqlite:BW",
                "--source", "logs/heuristic_r5_eye_20261008/matches.sqlite:W",
                "--out", "sft_data/heuristic_gemma4_r2",
            ],
            OUT / "dataset.log",
        )
        log(f"[runner] dataset rc={rc}")
        if rc != 0:
            return rc
        rc = run(
            [
                str(PY), "scripts/train_ladder_lora.py",
                "--shape", "onpolicy",
                "--data-name", "heuristic_gemma4_r2",
                "--log-name", "gemma4_heuristic_r2",
                "--model", MODEL,
                "--base", BASE,
                "--out", LORA,
                "--shape-n", "2000",
                "--skip-base-eval",
            ],
            OUT / "train.log",
        )
        log(f"[runner] train rc={rc}")
        if rc != 0:
            return rc
        LORA_FILE.write_text(LORA + "\n", encoding="utf-8")
        start_gemma()
        reg_path = ROOT / "scripts" / "models.json"
        reg = json.loads(reg_path.read_text(encoding="utf-8"))
        reg.setdefault("models", {})["gemma4_heuristic"] = {
            "port": PORT,
            "model": MODEL,
            "lora": LORA,
            "note": "휴리스틱 착수 2차 학습. 대국 때는 --no-score-guided.",
        }
        reg_path.write_text(json.dumps(reg, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    finally:
        if HOLD.exists():
            HOLD.unlink()
    rc = run(
        [
            str(PY), "scripts/play_heuristic_vs_model.py",
            "--port", str(PORT),
            "--model-name", "gemma4_heuristic_r2",
            "--games", "5",
            "--max-moves", "0",
            "--out-dir", str(OUT.relative_to(ROOT)),
        ],
        OUT / "play.log",
    )
    log(f"[runner] match rc={rc}")
    return rc


if __name__ == "__main__":
    raise SystemExit(main())