LLM은 게임을 플레이하는 것보다 솔버(Solver)를 구축하는 데 더 뛰어났다
요약
LLM을 활용한 Dota 2 드래프팅 게임 수행의 한계를 극복하기 위해, 확률적 노이즈를 제거한 결정론적 솔버(Solver) 구축 과정을 다룹니다. 공통 무작위 숫자(common-random-numbers) 개념과 시뮬레이터 분리를 통해 정책을 정밀하게 평가하는 엔지니어링 방법론을 제시합니다.
핵심 포인트
- LLM의 확률적 판단을 결정론적 정책으로 대체하는 엔지니어링 접근법
- 공통 무작위 숫자를 활용해 운(noise)을 배제한 정책 비교 벤치마크 구축
- 시뮬레이터를 정책과 분리하여 평가의 권위와 재현성 확보
- 결정론적 Python 엔진을 통한 게임 규칙의 정밀한 재현
저는 하나의 LLM(Large Language Model) 때문에 짜증이 나서 이 프로젝트를 시작했습니다.
저는 매우 강력한 모델에게 322라는 작은 Dota 2 드래프팅(drafting) 게임을 맡겼습니다. 선택지들은 컴퓨터가 즐거워할 법한 종류의 작업들로 구성되어 있었습니다: 반복되는 플레이어와 영웅 팩, 가시적인 레이팅(ratings), 친숙도 점수(familiarity scores), 케미스트리(chemistry), 리롤(rerolls), 그리고 마지막에 진행되는 시뮬레이션 토너먼트까지 말이죠.
저는 LLM이 수행한 결과에 실망했습니다. 저는 Dota 전문가가 아니며, 지난 6개월 또는 1년 동안 가끔씩 다시 시청하기 시작했을 뿐입니다. 그럼에도 제가 여전히 더 잘하는 것처럼 보였습니다.
흥미로운 엔지니어링 질문은 어떻게 더 긴 프롬프트(prompt)를 작성할 것인가가 아니었습니다. 그것은 카드 한 장 한 장에 대한 언어 모델(language-model)의 판단을 결정론적 정책(deterministic policy)으로 어떻게 대체할 것인가, 그리고 개선 사항을 운과 혼동하지 않고 어떻게 그 정책을 테스트할 것인가였습니다.
확률적 벤치마크(stochastic benchmark)에는 공유된 무작위성(shared randomness)이 필요하다
브라우저 기록은 우리에게 유용한 짜증을 유발했을 뿐, 신뢰할 수 있는 비교 대상은 거의 제공하지 않았습니다.
저의 이전 수동 기록에는 14%의 타이틀 승률을 기록한 50번의 실행이 포함되어 있었습니다. LLM은 9번의 시도 중 단 한 번 승리했습니다. 14%를 11% 옆에 두는 것은 유혹적인 정량적 비교처럼 보이지만, 무작위 제안, 거부된 팩, 그리고 상대 필드는 보존되지 않았습니다. 샘플들은 작았고, 쌍을 이루지 않았으며(unpaired), 서로 다른 선택 하에 생성되었습니다.
그것은 모델 벤치마크가 아닙니다. 그것은 벤치마크를 구축해야 하는 이유입니다.
오프라인 솔버(offline solver)는 인덱싱된 테이프로부터 모든 무작위 선택을 생성했습니다. 정책 A와 정책 B는 주어진 에피소드(episode)에 대해 동일한 플레이어 제안, 영웅 샘플, 필드 후보 및 토너먼트 무작위성을 받았습니다. 그런 다음 우리는 쌍을 이룬 결과(paired result)를 비교할 수 있었습니다: 새로운 정책이 기존 정책이 패배했던 바로 그 에피소드에서 승리했는가?
이것은 실용적인 형태의 공통 무작위 숫자(common-random-numbers) 개념입니다. 운을 공유함으로써 정책 변경과 아무런 관련이 없는 방대한 양의 노이즈(noise)를 제거할 수 있습니다.
시뮬레이터(simulator)를 정책(policy)과 분리하라
전략을 평가하기 전에, 우리는 게임을 재현했습니다.
공용 클라이언트와 7개의 데이터 파일은 SHA-256 해시로 고정되었습니다. 드래프트의 적법성, 자동 영웅 할당(automatic hero allocation), 케미스트리(chemistry), 점수 산정 및 토너먼트는 결정론적(deterministic) Python 엔진으로 포팅되었습니다. 자동 할당은 120개의 모든 플레이어-영웅 순열(permutations)을 평가하므로, 해당 세부 사항을 재현하는 것이 중요했습니다.
또한 이 엔진은 중요한 의미론적 경계(semantic boundary)를 유지했습니다. 완성된 로스터는 복제된 규칙 하에서 정확한 점수를 가집니다. 하지만 완성되지 않은 드래프트는 그렇지 않습니다. 정책(policy)은 플레이어, 영웅 또는 리롤(reroll)의 미래 가치를 추정할 수 있지만, 그러한 부분 상태(partial-state) 추정치는 소수점 자릿수를 늘린다고 해서 정확해지지 않습니다.
시뮬레이터(simulator)를 권위 있는 기준(authoritative)으로 유지한다는 것은, 성공의 정의를 바꾸지 않고도 정책을 변경할 수 있음을 의미했습니다.
리드로우(redraw) 예산은 실험의 일부입니다
상대 필드(opponent fields)는 약간 기묘한 통제 문제(control problem)를 야기했습니다. 만약 솔버(solver)가 무료로 무한히 리드로우를 할 수 있다면, "더 쉬운 필드를 찾으라"는 결정이 결국 다른 모든 결정을 압도하게 됩니다.
대신 우리는 세 가지 유한한 예산을 선언했습니다:
K=1: 첫 번째 상대 필드를 수락합니다.K=4: 4개의 필드 중에서 선택합니다.K=16: 16개의 필드 중에서 선택합니다.
이 예산들은 중첩(nested)되었습니다. K=1의 첫 번째 필드는 K=4와 K=16의 첫 번째 필드이기도 했으며, 처음 4개 또한 공유되었습니다. 이를 통해 정책들은 서로 다른 필드 드로우를 몰래 받지 않고도 각 예산 단계에서 비교될 수 있었습니다.
이는 주장에 큰 영향을 미치는 작은 설계적 선택이었습니다. 필드 예산이 없는 승률(title rate)은 재현 가능한 정책을 설명할 수 없기 때문입니다.
최종 테스트는 건드리기 어렵게 만드십시오
솔버는 개선되고, 개선되고, 또 개선될 수 있습니다. 그것은 만족스러운 일이었지만, 일반적인 위험을 초래했습니다. 결과가 다음 변경 사항에 영향을 미칠 때마다, 그 결과는 훈련(training)의 일부가 되어버립니다.
우리는 훈련(train), 검증(validation), 테스트(test) 테이프를 분리했습니다. 후보 정책(candidate policies)들은 먼저 공유된 훈련 에피소드(training episodes)에서 연속적 절반 감소(successive halving) 과정을 거쳤습니다. 약한 후보들은 조기에 중단되었고, 생존자들에게는 더 많은 예산(budget)이 할당되었습니다. 선택된 후보는 테스트 파티션(test partition)을 열기 전에 반드시 새로운 검증 게이트(validation gate)를 통과해야 했습니다.
이 게이트는 양수(+)의 타이틀 레이트(title-rate) 결과, 유의미한 4위 또는 8위 이내 순위 하락(regression) 없음, 실패 제로, 결정론적 리플레이(deterministic replay), 그리고 허용 가능한 실행 시간(runtime)을 요구했습니다. 최종 테스트가 시작되면 정책 코드, 피처(features), 모델 바이트(model bytes), 파라미터(parameters), 시드(seeds), 그리고 에피소드 횟수(episode counts)는 동결되었습니다.
제어 시스템 자체도 여전히 정밀 조사가 필요했습니다. 독립적인 감사(audit) 결과, 초기 리플레이 체크(replay check)가 공개된 리플레이 경로(public replay path) 대신 중복된 인메모리 실행(in-memory runs)을 비교했다는 점과, 첫 번째 실행 시간 게이트(runtime gate)가 워밍 정책 캐시(warm policy caches)를 재사용했다는 점이 발견되었습니다. 두 사항 모두 프로덕션(production) 적용 선언 전에 수정되었습니다. 감사는 원시 로그(raw logs)로부터 최종 통계치를 별도로 재계산했습니다.
이러한 번거로움은 의도적인 것이었습니다. 이는 "한 번만 더 수정하자"는 시도가 정답을 후보 모델로 다시 유출(leak)시키는 것을 방지하기 위함이었습니다.
작은 결정론적 모델로도 충분했다
최종 테스트에서는 각 필드 예산(field budget)에서 10,000개의 쌍을 이룬 에피소드(paired episodes)를 통해 4세대의 정책을 비교했습니다. 모든 정책 및 예산 조합에 걸쳐, 실패가 전혀 없는 120,000개의 결과가 도출되었습니다.
K=16에서 진행 과정은 다음과 같았습니다:
| 정책 (Policy) | 타이틀 레이트 (Title rate) |
|---|---|
| 가장 높은 플레이어 레이팅 선택 (Pick the highest player rating) | 2.30% |
| ... |
최종 가치 모델(value model)은 릿지 선형(ridge-linear) 방식이었으며, 결정론적(deterministic)이고 8,511개의 액션 행(action rows)을 통해 오프라인에서 훈련되었습니다. 이 모델은 역할 가능 미래 강도(role-feasible future strength), 부분적 영웅 할당(partial hero allocation), 현재 및 잠재적 케미스트리(chemistry), 친숙도(familiarity), 드래프트 단계(draft stage), 그리고 남은 리롤(remaining rerolls)에 대한 피처(features)를 사용했습니다. 플레이하는 동안에는 LLM을 호출하지 않았습니다.
이전 컴포넌트 정책(component policy)과 비교했을 때, K=16에서의 타이틀 개선도는 +5.12 퍼센트 포인트였으며, 쌍을 이룬 95% 신뢰 구간은 +4.24에서 +6.00 사이였습니다. 4위 및 8위 이내 결과 또한 모든 예산 범위에서 개선되었습니다.
콜드 모델 빌드(cold model build) 시 전체 초안 루프(draft loop)는 중앙값 47.1밀리초가 소요되었습니다. 이는 컴포넌트 정책(component policy)의 지연 시간(latency)보다 약 1.8배 긴 수치였으며, 런타임 정책(runtime policy)은 어떤 오퍼(offer)에 대해서도 대형 모델 요청을 수행하지 않았습니다.
결정 루프 주변에 LLM 배치하기
우리는 현재 모든 것을 LLM으로 해결하려고 시도하고 있지만, 반드시 그럴 필요는 없습니다.
322의 경우, 런타임 결정권자(decision-maker)는 일반적인 결정론적 코드(deterministic code)가 되었습니다. LLM은 그 주변에서 훨씬 더 유용하게 사용되었습니다. 브라우저 게임을 조사하고, 메커니즘을 재구성하며, 엔진을 구현하고, 정책을 제안하며, 통제된 실험을 실행하고, 취약한 제어 장치를 감사하며, 개선 사항을 읽기 쉽게 만드는 시각화(visualisations)를 생성하는 등의 역할을 수행했습니다.
그 결과는 꽤 멋진 작은 통계 프로젝트가 되었습니다. 유용한 경계선은 "LLM은 게임에 서툴다"가 아니었습니다. 더 구체적으로는, 재현 가능한 상태(reproducible state)와 측정 가능한 결과(measurable outcome)를 가진 반복적인 수치 결정에는 핫 패스(hot path)에서 LLM이 필요하지 않다는 것이었습니다.
만약 제가 이 프로젝트를 계속한다면, 다음 실험을 선택하고 구축하는 데 LLM을 사용할 것입니다. 다음 카드 선택은 솔버(solver)에게 맡길 것입니다.
제가 작성하거나 구축한 것에 대해 이야기하고 싶으신가요? 연락하기.
이 기사는 billiem.uk의 원문 기사를 AI의 도움을 받아 각색한 것입니다. 원문 기사는 발행 전 검토를 거쳤습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기