원문 캡처 · blog.lukesalamone.com
원문 캡처 · blog.lukesalamone.com

0.8B, 2B, 9B 규모의 언어 모델로 체스를 두는 추론 에이전트를 훈련했다. 에이전트는 보드 상태를 추적하는 도구를 호출해 상대적으로 적은 매개변수로도 좋은 성능을 내도록 설계됐다.

데모 코드는 GitHub에서 확인할 수 있다. 프로젝트 설명에 따르면, 이 접근법은 모델이 보드 상태를 직접 추적하는 대신 도구에 맡기고 수를 어떻게 탐색할지 추론하도록 한다.

Deepseek R1 논문에 관한 글이 나온 뒤 검증 가능한 보상 기반 강화학습(RLVR)에 관심을 갖게 됐다. 해당 논문은 ‘생각’ 토큰을 사용해 언어 모델이 수학 문제처럼 검증 가능한 과제를 풀 수 있음을 보였다. 또 말이 되지 않는 사고 과정을 생성하지 않도록 지도 학습 예시로 추론 과정을 먼저 보완할 필요가 있다고 설명했다.

체스는 검증 가능한 보상을 적용하기 좋은 과제로 제시된다. 게임의 결과뿐 아니라 개별 수의 강도, 그 수를 정당화하는 개별 주장도 검증할 수 있기 때문이다. 기존 체스 엔진 연구의 주된 목표가 경기력 극대화였다면, 나는 최선의 수만 찾기보다 설명 가능하고 직관적인 수를 우선하는 엔진을 지향한다. 체스 엔진의 역사와 정성적 분석에 관한 글은 여기와 여기에서 볼 수 있다.

처음에는 도구를 쓰지 않는 순수 언어 모델을 훈련하려 했지만, RTX 4090으로 다룰 수 있는 기반 모델은 약 30억 매개변수까지였다. 작은 모델에서는 실제로 없는 위협에 반응하거나 불법 수를 두는 등 현실의 보드 상태와 맞지 않는 오류가 나타났다. 보드 상태를 별도로 추적하는 도구를 허용하면 이런 유형의 오류를 없앨 수 있다.

도구는 언어 모델이 포지션에 대한 판단을 도구에 맡기지 않고, 보드 상태를 확인하며 탐색 방법을 정하도록 설계했다. 각 도구는 사람이 체스판을 따라가며 이해할 수 있는 단순하고 원자적인 작업을 수행한다.

도구 호출설명
piece_on(square)특정 칸에 있는 기물을 확인한다.
pieces()양쪽 기물의 종류와 위치를 모두 확인한다.
material()양쪽의 기물 가치를 폰 단위로 계산한다.
attackers(square)특정 칸을 공격하는 기물을 확인한다. 해당 칸에 기물이 있으면 공격자와 수비자를 구분한다.
targets(square)특정 칸의 기물이 공격하는 상대 기물과 수비하는 아군 기물을 확인한다.
hanging(color)지정한 색의 기물 중 잡힐 위험이 있는 기물을 확인한다.
pins(color)지정한 색의 킹을 상대로 한 핀을 확인한다.
forcing_moves()현재 포지션에서 가능한 합법적인 체크·잡기·프로모션 수를 확인한다.
legal_moves()현재 포지션의 모든 합법적인 수와 둘 차례인 쪽이 체크 상태인지 확인한다.
try(move)한 수를 두어 한 수 더 깊이 평가한다.
undo()탐색을 되돌리기 위해 직전 수를 취소한다.
quiet_moves()forcing_moves()에 포함되지 않는 합법적인 수를 확인한다.
root()탐색 스택을 모두 되돌려 시작 포지션으로 돌아간다.
threats()상대 차례라고 가정했을 때 가능한 메이트·체크·기물 이득을 동반하는 잡기를 확인한다.
exchange(square)특정 칸에서 가장 좋은 첫 잡기부터 시작하는 연속 잡기의 결과를 계산한다.

언어 모델은 도구를 호출할 때마다 호출 이유도 함께 제시해야 한다. 이렇게 하면 추론 과정이 이유 없이 도구 호출만 나열하는 형태로 흐르는 것을 막을 수 있다.