返回列表

Approvers Submission (2nd)

643. FIDE & Google Efficient Chess AI Challenge | fide-google-efficiency-chess-ai-challenge

开始: 2024-11-18 结束: 2025-03-06 游戏AI AI大模型赛
Approvers 提交方案(第 2 名)

Approvers 提交方案(第 2 名)

作者:Arseniy Surkov (@rickonaut)

合作者:Shahin M. Shahin (@peregrineshahin)

发布日期:2025-03-24

竞赛排名:第 2 名

Approvers 提交方案

源代码地址:https://github.com/peregrineshahin/Approvers

背景

我们最初是两个独立的团队,在排行榜顶端徘徊。最终,我们决定联手。
我们两人都有国际象棋引擎开发经验 —— @peregrineshahin 是 Stockfish 的贡献者,一位技能高超的专业人士,而我,@rickonaut,则将开发自己的国际象棋引擎作为个人项目。

我们意识到,高排名的提交方案可能需要优化竞赛的所有四个关键方面。鉴于此,我们的方法致力于以专注的方式开发国际象棋引擎,重点优化大小限制、内存限制、测试时限 (TC) 和开局库。

测试

作为国际象棋引擎社区的黄金标准,我们使用 SPRT(序贯概率比检验)来确定更改是否具有统计效益,并使用 SPSA(同时扰动随机近似)来调整各种常数和参数。总共,我们使用分布式计算资源进行了约 2000 万* 场对局。我们的 GitHub 仓库拥有超过 1250 个分支,每个分支包含不同的想法和改进提交的尝试。在从私有仓库转为公开仓库时,我们保留了所有提交和分支以供历史参考。main 分支上的大多数功能性提交都包含描述,其中附有相关 SPRT 测试的结果。

在合并团队之前,我自己运行了一个私有的 OpenBench 本地实例,这是一个由 @agethereal 开发的通用开源国际象棋测试框架,这使得起步更容易,而不是争论是否使用 Fishtest,尽管两者都能胜任。
一旦我们联手,这个设置在帮助我们共同开发和完善引擎方面发挥了关键作用。

* 相比之下,Fix the bugs? 报告了约 3800 万场对局。

开发与策略

起点是 Cfish,它是 Stockfish 的 C 语言移植版。

不幸的是,虽然仓库中有超过 300 个提交,但合并团队之前的一些提交未被追踪。不过,它们可能与最终提交方案没有特别大的关系。

我们很早就认识到结合领域特定知识与通用开发技能的重要性。

搜索特性

由于大小限制,我们确定最有效的策略是包含四个基本搜索特性。这些特性显著塑造了我们国际象棋引擎后来的参数,使其区别于传统引擎,通常是因为这些特性在长时限下会损害性能:

  • 短时限 (STC) 等级分增益优化 —— 此技术在迭代深化中跳过奇数 ply 的根深度,该方法最初由 Shahin 在处理 Stockfish 时发现。
  • STC Fail-High 处理 —— 一种在发生 Fail-High 时移动到下一个根节点的策略,最初在 Stockfish 早期发现。
  • 静止搜索时间检查 —— 一种 STC 优化,通过在静止搜索期间监控时间来提高 Stockfish 克隆引擎的性能。这也是 Shahin 在之前处理 Stockfish 时发现的。
  • 突然死亡时限优化 —— 由 Stockfish 团队开发的技术,当时限接近限制时动态缩放剩余步数 (MTG) 参数。

有趣的是,我们发现实施某些众所周知的短时限 (STC) 优化(需要完全重新调整引擎的超参数),使我们能够结合已建立的超长时限 (VVLTC) 优化 —— 这些技术通常在 STC 条件下无法发挥作用!

评估

对于评估,我们引入了 NNUE,采用了一种在国际象棋社区不同形式中采用的相当直接的 NNUE 架构 — (768x1hm -> 64)x1 -> 1x8。

  • 768 个输入特征,带有水平王镜像。输入沿垂直轴翻转,即 a1 变为 h1,b1 变为 g1 等,基于友方王的位置。
  • 1 个隐藏层,包含 64 个神经元,采用平方裁剪线性整流单元 (SCReLU) 激活函数 f(x) = min(max(x, 0), 1)^2。
  • 8 个输出桶(层栈),根据棋盘上剩余的棋子数量选择 (piece_count - 2) / 4。
  • 它输出一个数字,代表引擎内部单位中节点的评估值。

网络训练涉及 3 阶段渐进式训练,每个阶段都从前一个阶段重新开始并进行修改,最后 followed by 一个 SPSA 会话。完整的训练配置请参阅仓库中的 training/config.rs,与 Bullet 训练器兼容。

网络量化为 8 位用于 FT 权重/偏置和 L1 权重,16 位用于 L1 偏置。此外,由于兵未使用的特征(根据国际象棋规则,第 1 和第 8 横排是非法的)和王的镜像格,输入特征减少到 704

尺寸优化

为了最小化二进制文件的大小并适应最大的 NNUE 模型,同时保持 NNUE 性能关键的 -O3 标志,我们做了大量的清理和简化(包括功能性修改,这些修改在我们的 SPRT 测试中没有回归)。此外,我们从 gcc 切换到 clang,因为它生成的二进制文件更小且速度至少一样快,后来结合各种 cflags、#pragma 指令禁用个别循环的展开,并对非热点函数应用 minsizecoldsection(".text.small") 属性的组合,这对实现我们的目标起到了很大作用。我们还通过用自定义实现替换必要的函数并使应用程序真正成为单线程,完全移除了对 libmlpthread 的依赖。

本地结果

在所有前 3 名条目的源代码发布后,我们针对它们测试了我们的引擎。条件尽可能接近 Kaggle —— 1 线程,1MB 哈希,每步 10 秒(根据机器速度单独缩放以匹配 Kaggle 的机器 NPS),以及 Kaggle 开局库。延迟/增量未设置,因为它在 Kaggle 上不可预测并会导致超时负。有人可能会争辩说它甚至不起作用。因此,我们测试中缺失的唯一部分是预思考 (pondering)。

在 8 万场对局中,我们的任何机器上都没有发生崩溃或超时。

Linrock 对阵 Approvers

等级分   | -3.95 +- 1.90 (95%)
配置  | 10.0+0.00s 线程=1 哈希=1MB
对局数 | 总数:40000 胜:7577 负:8032 和:24391
五维统计 | [513, 4465, 10444, 4120, 458]

Fix the bugs? 对阵 Approvers

等级分   | -3.43 +- 1.97 (95%)
配置  | 10.0+0.00s 线程=1 哈希=1MB
对局数 | 总数:40002 胜:8195 负:8590 和:23217
五维统计 | [576, 4636, 9946, 4293, 550]

前 3 名非常接近,我们的条目略有优势。最终,由于评级系统高度不稳定,这纯粹取决于运气。
尽管如此,我们在比赛期间度过了愉快的时光,希望你们也是。

额外彩蛋

在上述条件下,以下是 Approvers 与测试时最新开发版本的 Stockfish(提交 fa6c30af)之间的简短比赛。

Approvers 对阵 Stockfish 得分:136 - 2012 - 1622  [0.251] 3770
...      Approvers 执白:100 - 710 - 1075  [0.338] 1885
...      Approvers 执黑:36 - 1302 - 547  [0.164] 1885
...      白方 对阵 黑方:1402 - 746 - 1622  [0.587] 3770
等级分差异:-189.7 +/- 8.4, 胜率可能性:0.0 %, 和棋率:43.0 %

请注意,Stockfish 针对更长的时限进行了优化,在这种短时限下表现会下降,但我们的提交方案看起来相当强大。

同比赛其他方案