/ ^---^ \
/ / @ @ \ \
|| \ v / ||
|| / \ ||
|| / / \ \ ||
|| \/\___/\/ ||
\ | | /
\ ^ ^ /
__ ___ __
/ \ | / \ | |
\__ __ |__ / |__ __ __ |
\ / \ | \ | | /__\ / |_/
\__/ \__/ | \__/ | | \__ \__ | \Жаль, что я этот канал начал, когда уже много кода написано, но все же
Коротко опишу все, что уже существует в самом движке и в проектах вокруг него. Начну с самого движка:
- быстрые правила с использованием Magic Bitboards и PEXT Bitboards (для новых CPU)
- проверка правил на корректность путем сравнения их с моей более старой реализацией, Dodecahedron
- почти полноценная реализация протокола UCI со стороны движка
- альфа-бета поиск с PVS
- сортировка ходов с MVV/LVA, киллерами и эвристикой истории
- хэш-таблица
- простая многопоточность: потоки обмениваются информацией только через хэш-таблицу, т. е. если поток видит, что позиция уже была просчитана, то он просто берет результат
- поиск взятий в конце для устранения эффекта горизонта
- простой Futility Pruning
- проверки для рекурсивного поиска: проверяют, что все инварианты в коде соблюдены, включаются отдельным флагом
- простая оценка позиций на основе таблиц фигура-поле + немного дополнительных факторов
- автоматический подбор коэффициентов оценки (об этом позже)
- немного бенчмарков и тестов ко всему этому
Коротко опишу все, что уже существует в самом движке и в проектах вокруг него. Начну с самого движка:
- быстрые правила с использованием Magic Bitboards и PEXT Bitboards (для новых CPU)
- проверка правил на корректность путем сравнения их с моей более старой реализацией, Dodecahedron
- почти полноценная реализация протокола UCI со стороны движка
- альфа-бета поиск с PVS
- сортировка ходов с MVV/LVA, киллерами и эвристикой истории
- хэш-таблица
- простая многопоточность: потоки обмениваются информацией только через хэш-таблицу, т. е. если поток видит, что позиция уже была просчитана, то он просто берет результат
- поиск взятий в конце для устранения эффекта горизонта
- простой Futility Pruning
- проверки для рекурсивного поиска: проверяют, что все инварианты в коде соблюдены, включаются отдельным флагом
- простая оценка позиций на основе таблиц фигура-поле + немного дополнительных факторов
- автоматический подбор коэффициентов оценки (об этом позже)
- немного бенчмарков и тестов ко всему этому
Для тестирования проекта используется программа Battlefield, которая может гонять партии движков друг с другом и оценивать статистическую значимость результатов. Эта программа написана на языке Pascal, потому что использует куски кода из моего более старого проекта, Chess256. Здесь правила реализованы практически «в лоб», поэтому Battlefield работает не очень быстро, что может быть заметно на ультракоротких партиях, а в остальном проблем не доставляет (т.к. все равно большую часть времени CPU уходит на обдумывание ходов движками)
Эта штука используется для проверки гипотез и оценки качества: если новая версия может статистически значимо победить предыдущую, то мы ее принимаем. К сожалению, так можно обнаружить только большие изменения в силе игры (на ~30-70 Эло). Чтобы обнаружить небольшое изменение, придется гонять очень много партий, а это долго
Эта штука используется для проверки гипотез и оценки качества: если новая версия может статистически значимо победить предыдущую, то мы ее принимаем. К сожалению, так можно обнаружить только большие изменения в силе игры (на ~30-70 Эло). Чтобы обнаружить небольшое изменение, придется гонять очень много партий, а это долго
Немного про идейную часть проекта. А идея заключается в том, чтобы как можно больше вещей объявить как
Поэтому сейчас сделано так: существуют генераторы, которые генерируют заголовочные файлы с константами (например, такой). Потом CMake запускает этот генератор (сама функция
Получается сложно во время компиляции, зато не несет никаких расходов в рантайме: константы вкомпилированы прямо в код :)
constexpr, чтобы дать компилятору большой простор для оптимизаций. В то же время, неплохо было бы генерировать некоторые константы автоматически, в частности, «магические значения» для Magic Bitboards.Поэтому сейчас сделано так: существуют генераторы, которые генерируют заголовочные файлы с константами (например, такой). Потом CMake запускает этот генератор (сама функция
generate_file объявлена здесь), а сгенерированный заголовочный файл подключается в проектПолучается сложно во время компиляции, зато не несет никаких расходов в рантайме: константы вкомпилированы прямо в код :)
Про подбор весов:
В оценке позиции используется простая линейная модель. Из доски извлекаются некоторые признаки (к примеру, «сколько коней стоит на поле c6» или «сколько пешек на поле»). Признаки симметричны для обеих сторон, т. е. белый конь на c6 прибавляет 1 к признаку «сколько коней стоит на поле c6», а черный конь на этом поле — отнимает 1. Оценка равна скалярному произведению вектора весов на вектор признаков. Возникает вопрос, как подобрать вектор весов таким образом, чтобы движок играл как можно сильнее
Со стороны самого движка есть такие инструменты:
- файлик features.json, который содержит все веса и описание признаков
- шаблонная магия, которая позволяет одному и тому же коду извлекать признаки и их же применять (в зависимости от шаблонных параметров)
- вспомогательные утилиты, которые помогают извлекать коэффициенты из позиций и удобно применять новые веса
- генераторы, которые читают features.json и генерируют из него заголовочные файлы (про автогенерацию пост выше)
В оценке позиции используется простая линейная модель. Из доски извлекаются некоторые признаки (к примеру, «сколько коней стоит на поле c6» или «сколько пешек на поле»). Признаки симметричны для обеих сторон, т. е. белый конь на c6 прибавляет 1 к признаку «сколько коней стоит на поле c6», а черный конь на этом поле — отнимает 1. Оценка равна скалярному произведению вектора весов на вектор признаков. Возникает вопрос, как подобрать вектор весов таким образом, чтобы движок играл как можно сильнее
Со стороны самого движка есть такие инструменты:
- файлик features.json, который содержит все веса и описание признаков
- шаблонная магия, которая позволяет одному и тому же коду извлекать признаки и их же применять (в зависимости от шаблонных параметров)
- вспомогательные утилиты, которые помогают извлекать коэффициенты из позиций и удобно применять новые веса
- генераторы, которые читают features.json и генерируют из него заголовочные файлы (про автогенерацию пост выше)
Теперь о том, как, собственно, происходит сам подбор весов. Метод описан в этом посте на Хабре, там же можно найти всякую теорию и формулы. Я больше расскажу, как именно происходит процесс в самом SoFCheck:
- сначала надо откуда-то взять датасет. Для этого можно запустить движок играть с самим собой огромное количество раз (к примеру, 30000) и извлечь позиции. Это делается с помощью Battlefield. Движки играли на моем Android-планшете в Termux, чтобы не нагружать ноутбук, но об этом, возможно, будет отдельный пост
- с помощью утилиты make_dataset удаляем неспокойные позиции (со взятиями и шахами), из оставшихся извлекает признаки. Получаем огромный csv-файл
- csv-файл скармливаем в Jupyter-ноутбук и получаем новые веса
- вставляем новые веса в features.json с помощью утилиты apply_weights
- проверяем, что получилось
Как можно видеть, кода вокруг подбора весов очень много, зато он хорошо автоматизирует процесс и облегчает его
Что дает подбор весов? У меня он улучшил результат где-то на 100 Эло по сравнению с предыдущей версией, где коэффициенты были вбиты вручную. Полезная штука :)
- сначала надо откуда-то взять датасет. Для этого можно запустить движок играть с самим собой огромное количество раз (к примеру, 30000) и извлечь позиции. Это делается с помощью Battlefield. Движки играли на моем Android-планшете в Termux, чтобы не нагружать ноутбук, но об этом, возможно, будет отдельный пост
- с помощью утилиты make_dataset удаляем неспокойные позиции (со взятиями и шахами), из оставшихся извлекает признаки. Получаем огромный csv-файл
- csv-файл скармливаем в Jupyter-ноутбук и получаем новые веса
- вставляем новые веса в features.json с помощью утилиты apply_weights
- проверяем, что получилось
Как можно видеть, кода вокруг подбора весов очень много, зато он хорошо автоматизирует процесс и облегчает его
Что дает подбор весов? У меня он улучшил результат где-то на 100 Эло по сравнению с предыдущей версией, где коэффициенты были вбиты вручную. Полезная штука :)
В целом, про уровень игры. Я точно не могу сказать, насколько мой движок силен, но могу оценить его примерно на уровне 2200-2300 Эло. Доказательством этому служит то, что SoFCheck уверенно побеждает MORA в режиме 1 секунда на ход. При этом MORA имеет рейтинг 2200 на CCRL. Battlefield мне выдал такие результаты на 100 играх:
Wins: 49, Loses: 26, Draws: 25
Score: 61.5:38.5
Confidence interval:
p = 0.90: First wins
p = 0.95: First wins
p = 0.97: First wins
p = 0.99: Unclear
Other stats:
LOS = 1.00
Elo difference = 81.37
Как видно из предыдущих постов, уже сделано много классных вещей. Но я на этом не собираюсь останавливаться :) Еще не все реализовано, и определенно есть куда расти
В следующих постах немного про то, чем я занимался в последнее время. Изменения больше касаются инфраструктуры вокруг проекта и не приносят никаких улучшений в силе игры, но я считаю, что рассказ про них будет интересным
В следующих постах немного про то, чем я занимался в последнее время. Изменения больше касаются инфраструктуры вокруг проекта и не приносят никаких улучшений в силе игры, но я считаю, что рассказ про них будет интересным
Во-первых, такое изменение. Чтобы показать, что файл принадлежит проекту под лицензией GPL (а SoFCheck сейчас использует GPLv3), стоит в начало каждого файла добавить уведомление о лицензии. Мне было лень делать это вручную, поэтому я написал простой скрипт на питоне, который добавляет такие комментарии в начало каждого файла. Еще этот скрипт умеет обновлять даты изменения файлов (всякие
Copyright (C) 2020-2021 кто-то там), глядя на git logВо-вторых, я улучшил сборку в CI. Раньше использовался Travis для сборок только под GNU/Linux, сейчас я перешел на GitHub Actions и собираю проект сразу под Windows, GNU/Linux и Mac
Пайплайн там не очень сложный, но имеет некоторые особенности. Во-первых, я фиксирую версии компиляторов (сейчас используются
Все эти действия усложняют процесс. Я не смог просто и компактно описать процесс сборки в yml-файле для GitHub Actions, а готовые action'ы мне не подходили ввиду специфичных требований выше Поэтому весь код, связанный со сборкой, запускает скрипт на питоне, а сам пайпайн для GitHub Actions содержит лишь вызовы этого скрипта. Несомненный плюс подхода — можно легко сменить провайдера CI, не переписывая большую часть кода
Были и подводные камни:
Во-первых, отлаживать пайплайн у себя локально не получилось. Поэтому приходилось каждый раз коммитить, пушить на сервер и смотреть, что получится. После примерно 80 итераций сборка все же прошла успешно :)
Во-вторых,
В-третьих, код, собранный с BMI2, падал на macOS с
Пайплайн там не очень сложный, но имеет некоторые особенности. Во-первых, я фиксирую версии компиляторов (сейчас используются
gcc-8 и clang-9), чтобы гарантировать, что код точно соберется на этих версиях. Значит, придется устанавливать эти версии компиляторов вручную. Во-вторых, мне надо предварительно собрать и установить зависимости (Google Test и Google Benchmark). В-третьих, у меня есть много разных конфигураций с разными флагами под разные платформыВсе эти действия усложняют процесс. Я не смог просто и компактно описать процесс сборки в yml-файле для GitHub Actions, а готовые action'ы мне не подходили ввиду специфичных требований выше Поэтому весь код, связанный со сборкой, запускает скрипт на питоне, а сам пайпайн для GitHub Actions содержит лишь вызовы этого скрипта. Несомненный плюс подхода — можно легко сменить провайдера CI, не переписывая большую часть кода
Были и подводные камни:
Во-первых, отлаживать пайплайн у себя локально не получилось. Поэтому приходилось каждый раз коммитить, пушить на сервер и смотреть, что получится. После примерно 80 итераций сборка все же прошла успешно :)
Во-вторых,
clang-tidy не работает под Windows. Причина такая: CMake для Windows убирает часть параметров в response-файлы, чтобы обойти ограничение на длину командной строки. К сожалению, clang-tidy ничего не знает про response-файлы и падает. По этой же причине не удалось завести clang под WindowsВ-третьих, код, собранный с BMI2, падал на macOS с
SIGILL, поэтому пришлось отключить этот режим сборки
