Por que falhou no começo

O modelo inicial respondia coordenadas do canto inferior direito do tabuleiro com frequência exagerada. Parecia falta de registros de partidas, mas a causa real era o formato da entrada. Os dados de treinamento usavam um tabuleiro alinhado com números, enquanto o servidor real enviava um tabuleiro com espaçamento diferente. Em especial, a partir do ponto em que os números das colunas passavam a ter dois dígitos, o modelo lia a posição das pedras com uma casa de diferença.

O primeiro modelo, o Gemma 4 E4B com LoRA, não acertou nenhuma das 16 questões do teste de coordenadas. Mesmo depois de trocar para o Qwen3-VL-8B, um adaptador treinado rapidamente com apenas 400 exemplos de jogadas caiu para 2,1%. Por isso, criamos primeiro um adaptador só para coordenadas e treinamos as jogadas em cima dele.

Precisão no teste de coordenadas. Gemma 4 E4B + LoRA 0%, LoRA com dados de jogadas no Qwen 2,1%, Qwen3-VL-8B base 79,2%, LoRA de coordenadas 100%
Precisão no teste de coordenadas. Gemma 4 E4B + LoRA 0%, LoRA com dados de jogadas no Qwen 2,1%, Qwen3-VL-8B base 79,2%, LoRA de coordenadas 100%

Etapa 1: Fixar a largura das coordenadas

Linhas e colunas são escritas com dois dígitos, como 00, 01, 02, e não 0, 1, 2. Pontos vazios, pedras pretas e pedras brancas também ficam fixados em um caractere cada, e o número de espaços de separação nunca muda. Mesmo que a função que gera os dados de treinamento e a função que monta as requisições das partidas reais sejam separadas, o mais seguro é que elas compartilhem um único gerador da string final.

  1. Imprimir os cabeçalhos das colunas de 00 a 18.
  2. Colocar também um número de 00 a 18 na frente de cada linha.
  3. Deixar os símbolos de vazio, preta e branca com a mesma largura.
  4. Escrever a vez atual e os pontos proibidos em uma posição fixa abaixo do tabuleiro.

Etapa 2: Passar primeiro no teste de coordenadas

Antes de treinar a força de jogo, coloque uma pedra em um ponto aleatório e peça ao modelo que responda a coordenada. Não teste só o centro; crie o mesmo número de questões em cada uma das quatro áreas: superior esquerda, superior direita, inferior esquerda e inferior direita. Mesmo com precisão geral alta, se só o canto inferior direito for fraco, as partidas reais vão mostrar um viés de direção.

Até o Qwen3-VL-8B base teve sua menor precisão, 66,7%, no canto inferior direito entre as quatro áreas.

Precisão de coordenadas por área. Modelo base: superior esquerda 80, superior direita 69, inferior esquerda 93, inferior direita 67%. O modelo em produção tem 100% em todas
Precisão de coordenadas por área. Modelo base: superior esquerda 80, superior direita 69, inferior esquerda 93, inferior direita 67%. O modelo em produção tem 100% em todas

Rode o teste de coordenadas de novo sempre que treinar o modelo outra vez ou mudar o prompt. Salve a direção das respostas erradas junto com a taxa de acerto. Dependendo de ele errar repetidamente uma casa para a direita ou trocar linhas e colunas, dá para verificar separadamente o alinhamento da entrada e o parser da saída.

Etapa 3: Restringir o formato da saída

Quando o modelo responde com uma explicação longa, o parser de coordenadas fica instável. A saída normal é limitada a uma única coordenada ou PASS. Usar escondido só a primeira coordenada quando vem uma explicação junto pode ocultar jogadas erradas, então isso é registrado como erro de formato e a requisição é repetida apenas um número fixo de vezes.

Enquanto a precisão das coordenadas não estiver garantida, colocar mais registros de partidas pode só reforçar o hábito de ler errado.

O código real desta etapa

Este é o gerador da string do tabuleiro que deixa os cabeçalhos das colunas, os números das linhas e os símbolos das pedras todos com dois caracteres de largura. Tanto a geração dos dados de treinamento quanto o servidor real de recomendação de jogadas usam esta função, para que os formatos de entrada nunca fiquem diferentes.

vlm_server/dataset_baduk.pypython · 16 linhas
def board_text_aligned(cells: bytes, size: int = BOARD_SIZE) -> str:
    """v3 권장: 열·행·셀이 모두 두 글자 토큰으로 정렬.

    셀 기호는 **왼쪽 정렬** (``X `` / ``. ``). 오른쪽 정렬(`` X``)은
    모델이 열을 한 칸 왼쪽(col-1)으로 읽는 오차를 유발했다.
    """
    col_header = "   " + " ".join(f"{c:02d}" for c in range(size))
    lines: List[str] = [col_header]
    for r in range(size):
        cells_s: List[str] = []
        for c in range(size):
            v = cells[r * size + c]
            ch = "." if v == 0 else ("X" if v == 1 else "O")
            cells_s.append(f"{ch:<2}")
        lines.append(f"{r:02d} " + " ".join(cells_s))
    return "\n".join(lines)

A saída correta do modelo é limitada a uma linha de JSON contendo apenas uma coordenada ou um passe.

vlm_server/dataset_baduk.pypython · 5 linhas
def assistant_text(point: Optional[Tuple[int, int]]) -> str:
    if point is None:
        return '{"pass": true}'
    r, c = point
    return f'{{"row": {r}, "col": {c}}}'

Modelo usado: Qwen3-VL-8B-Instruct + adaptador LoRA (lora_mix_qwen_r5). O código-fonte completo e a descrição do modelo estão no post "8. O código-fonte completo e os modelos usados".