{
  "id": 569891,
  "title": "Approvers Submission (2nd)",
  "content": "<h2>Approvers' Submission</h2>\n<p>The source code is available at <a href=\"https://github.com/peregrineshahin/Approvers\" target=\"_blank\">https://github.com/peregrineshahin/Approvers</a>.</p>\n<h3>Background</h3>\n<p>We started out as 2 separate teams, shuffling near the top of the leaderboard. Eventually, we decided to join forces.<br>\nBoth of us had prior experience as chess engine developers — <a href=\"https://www.kaggle.com/peregrineshahin\" target=\"_blank\">@peregrineshahin</a> is a Stockfish contributor, a highly skilled professional, and I, <a href=\"https://www.kaggle.com/rickonaut\" target=\"_blank\">@rickonaut</a>, developed my own chess engine as a pet-project.</p>\n<p>We came to understand that a highly ranked submission would likely require optimizing all four key aspects of the tournament. With this in mind, our approach was driven by a commitment to developing a chess engine in a dedicated manner, focusing on optimizing for the size limit, memory limit, tested time control (TC), and the opening book.</p>\n<h3>Testing</h3>\n<p>As the gold standard in the chess engine community, we used SPRT (Sequential Probability Ratio Test) to determine whether a change is statistically beneficial and SPSA (Simultaneous Perturbation Stochastic Approximation) for tuning various constants and parameters. In total, we have played around 20M* games using distributed compute resources. Our GitHub repository has over 1250 branches, each containing different ideas and attempts to improve the submission. We have left all commits and branches intact for historical reference when switching from a private to a public repository. Most functional commits on the <code>main</code> branch include descriptions with the result of associated SPRT tests.</p>\n<p>Before merging teams, I had my own private local instance of <a href=\"https://github.com/AndyGrant/OpenBench\" target=\"_blank\">OpenBench</a> up and running, a generic open source chess testing framework, developed by <a href=\"https://www.kaggle.com/agethereal\" target=\"_blank\">@agethereal</a>, this made it easier to get started rather than debating whether to use <a href=\"https://github.com/official-stockfish/fishtest\" target=\"_blank\">Fishtest</a> while both can do the job.<br>\nOnce we joined forces, this setup played a key role in helping us develop and refine the engine together.</p>\n<p>* In comparison, <code>Fix the bugs?</code> reported ~38M games.</p>\n<h3>Development and Strategies</h3>\n<p>The starting point is the <a href=\"https://github.com/syzygy1/Cfish\" target=\"_blank\">Cfish</a>, a C port of <a href=\"https://github.com/official-stockfish/Stockfish\" target=\"_blank\">Stockfish</a>.</p>\n<p>Unfortunately, although we have over 300 commits in the repository, some commits from before merging the teams were not tracked. However, they might not be particularly relevant to the final submission.</p>\n<p>We recognized early on the importance of combining domain-specific knowledge with general development skills.</p>\n<h3>Search Features</h3>\n<p>Due to size limitations, we determined that the most effective strategy for our team was to include four fundamental search features. These features significantly shaped the parameters of our chess engine later on, distinguishing it from conventional engines, generally because these hurt the performance in long time controls:</p>\n<ul>\n<li>Short Time Control (STC) Elo Gainer Optimization – This technique skips root depths at odd plies in Iterative Deepening, a method initially discovered by Shahin while working on Stockfish.</li>\n<li>STC Fail-High Handling – A strategy that moves on to the next root node in the event of a fail-high, originally discovered in the early days of Stockfish.</li>\n<li>Quiescence Search Time Checking – An STC optimization that improves performance in Stockfish-clone engines by monitoring time even during Quiescence Search. This was also identified by Shahin during his previous work on Stockfish.</li>\n<li>Sudden Death Time Control Optimization – A technique developed by the Stockfish team, which scales the Move-To-Go (MTG) parameter dynamically as time control approaches its limit.</li>\n</ul>\n<p>Interestingly, we discovered that implementing certain well-known Short Time Control (STC) optimizations, which require a complete retuning of the engine’s hyperparameters, enabled us to incorporate established Very Long Long Time Control (VVLTC) optimizations — techniques that typically do not function under STC conditions!</p>\n<h3>Evaluation</h3>\n<p>For evaluation, we introducd NNUE with a pretty straightforward NNUE architecture adopted in different forms in the chess community — (768x1hm -&gt; 64)x1 -&gt; 1x8.</p>\n<ul>\n<li>768 input features with horizontal king mirroring. Inputs are flipped along the vertical axis, i.e., a1 becomes h1, b1 becomes g1, etc., based on the position of the friendly king.</li>\n<li>1 hidden layer with 64 neurons with Squared Clipped Rectified Linear Unit (SCReLU) activation function f(x) = min(max(x, 0), 1)^2.</li>\n<li>8 output buckets (a layer stack), selected based on the number of pieces remaining on the board (piece_count - 2) / 4.</li>\n<li>It outputs a single number, representing an evaluation of a node in the engine's internal units.</li>\n</ul>\n<p>The network training involves 3-stages of progressive training, with each stage restarting from the previous one with modifications, finally followed by an SPSA session. For the full training configuration, see <a href=\"https://github.com/peregrineshahin/Approvers/blob/main/training/config.rs\" target=\"_blank\">training/config.rs</a> in the repository, compatible with the <a href=\"https://github.com/jw1912/bullet\" target=\"_blank\">Bullet</a> trainer.</p>\n<p>The network is quantized to 8 bits for FT weights/biases and L1 weights, and 16 bits for L1 biases. Also, due to unused features for pawns<br>\n(1st and 8th ranks being illegal by the rules of chess) and the mirror squares of kings, the input features are reduced to <code>704</code>.</p>\n<h3>Size Optimization</h3>\n<p>To minimize the size of the binary and fit the largest NNUE model while keeping the crucial <code>-O3</code> flag for NNUE performance, we did lots of cleanups and simplifications (including functional ones that haven't regressed in our SPRT tests). Additionally, we switched from <code>gcc</code> to <code>clang</code>, as it produced smaller binaries and at least as fast, later combining with various cflags, <code>#pragma</code> directives to disable unrolling on individual loops, and applying a combination of <code>minsize</code>, <code>cold</code>, and <code>section(\".text.small\")</code> attributes to non-hot functions played a big role for achieving our goal. We also fully removed dependencies on <code>libm</code> and <code>lpthread</code> by replacing necessary functions with custom implementations and making the application truly single-threaded.</p>\n<h3>Local Results</h3>\n<p>After the source code of all top-3 entries was published, we tested our engine against them. The conditions are as close to Kaggle as possible – 1 thread, 1MB hash, 10s per move (scaled individually based on machine speed to match Kaggle's machine NPS), and the Kaggle opening book. The delay/increment was left unset, as it's unpredictable on Kaggle and causes time losses. One might argue it doesn't even work. So, the only piece missing in our testing was pondering.</p>\n<p>In 80K games, there wasn't a single crash or a time loss on any of our machines.</p>\n<p><code>Linrock</code> vs. <code>Approvers</code></p>\n<pre><code>   | -. +- . (%)\n  | .+.s Threads= Hash=MB\n | N:  W:  L:  D: \n |\n</code></pre>\n<p><code>Fix the bugs?</code> vs. <code>Approvers</code></p>\n<pre><code>   | -. +- . (%)\n  | .+.s Threads= Hash=MB\n | N:  W:  L:  D: \n |\n</code></pre>\n<p>The top-3 are very close, with a slight edge to our entry. Ultimately, it came down to pure luck due to a highly unstable rating system.<br>\nNevertheless, we had a great time and lots of fun during the competition and hope you did too.</p>\n<h3>Bonus</h3>\n<p>Under the previously mentioned conditions, here's a short match between Approvers and the latest development version of Stockfish at the time of testing (commit <code>fa6c30af</code>).</p>\n<pre><code>       [] \n             [] \n             [] \n             [] \n         \n</code></pre>\n<p>Please note, Stockfish is optimized for much longer time controls and regresses in such short ones, yet our submission looks quite powerful.</p>",
  "comments": [
    {
      "id": 3160268,
      "author_name": "Seren Ripity",
      "author_url": "",
      "post_date": "2025-03-26T14:50:46.370000",
      "content": "<p>Thanks for sharing, great work!</p>",
      "votes": 1,
      "replies": []
    }
  ],
  "comment_count": 1,
  "messages": [
    {
      "id": 3158706,
      "postDate": "2025-03-24T19:39:19.483Z",
      "content": "<h2>Approvers' Submission</h2>\n<p>The source code is available at <a href=\"https://github.com/peregrineshahin/Approvers\" target=\"_blank\">https://github.com/peregrineshahin/Approvers</a>.</p>\n<h3>Background</h3>\n<p>We started out as 2 separate teams, shuffling near the top of the leaderboard. Eventually, we decided to join forces.<br>\nBoth of us had prior experience as chess engine developers — <a href=\"https://www.kaggle.com/peregrineshahin\" target=\"_blank\">@peregrineshahin</a> is a Stockfish contributor, a highly skilled professional, and I, <a href=\"https://www.kaggle.com/rickonaut\" target=\"_blank\">@rickonaut</a>, developed my own chess engine as a pet-project.</p>\n<p>We came to understand that a highly ranked submission would likely require optimizing all four key aspects of the tournament. With this in mind, our approach was driven by a commitment to developing a chess engine in a dedicated manner, focusing on optimizing for the size limit, memory limit, tested time control (TC), and the opening book.</p>\n<h3>Testing</h3>\n<p>As the gold standard in the chess engine community, we used SPRT (Sequential Probability Ratio Test) to determine whether a change is statistically beneficial and SPSA (Simultaneous Perturbation Stochastic Approximation) for tuning various constants and parameters. In total, we have played around 20M* games using distributed compute resources. Our GitHub repository has over 1250 branches, each containing different ideas and attempts to improve the submission. We have left all commits and branches intact for historical reference when switching from a private to a public repository. Most functional commits on the <code>main</code> branch include descriptions with the result of associated SPRT tests.</p>\n<p>Before merging teams, I had my own private local instance of <a href=\"https://github.com/AndyGrant/OpenBench\" target=\"_blank\">OpenBench</a> up and running, a generic open source chess testing framework, developed by <a href=\"https://www.kaggle.com/agethereal\" target=\"_blank\">@agethereal</a>, this made it easier to get started rather than debating whether to use <a href=\"https://github.com/official-stockfish/fishtest\" target=\"_blank\">Fishtest</a> while both can do the job.<br>\nOnce we joined forces, this setup played a key role in helping us develop and refine the engine together.</p>\n<p>* In comparison, <code>Fix the bugs?</code> reported ~38M games.</p>\n<h3>Development and Strategies</h3>\n<p>The starting point is the <a href=\"https://github.com/syzygy1/Cfish\" target=\"_blank\">Cfish</a>, a C port of <a href=\"https://github.com/official-stockfish/Stockfish\" target=\"_blank\">Stockfish</a>.</p>\n<p>Unfortunately, although we have over 300 commits in the repository, some commits from before merging the teams were not tracked. However, they might not be particularly relevant to the final submission.</p>\n<p>We recognized early on the importance of combining domain-specific knowledge with general development skills.</p>\n<h3>Search Features</h3>\n<p>Due to size limitations, we determined that the most effective strategy for our team was to include four fundamental search features. These features significantly shaped the parameters of our chess engine later on, distinguishing it from conventional engines, generally because these hurt the performance in long time controls:</p>\n<ul>\n<li>Short Time Control (STC) Elo Gainer Optimization – This technique skips root depths at odd plies in Iterative Deepening, a method initially discovered by Shahin while working on Stockfish.</li>\n<li>STC Fail-High Handling – A strategy that moves on to the next root node in the event of a fail-high, originally discovered in the early days of Stockfish.</li>\n<li>Quiescence Search Time Checking – An STC optimization that improves performance in Stockfish-clone engines by monitoring time even during Quiescence Search. This was also identified by Shahin during his previous work on Stockfish.</li>\n<li>Sudden Death Time Control Optimization – A technique developed by the Stockfish team, which scales the Move-To-Go (MTG) parameter dynamically as time control approaches its limit.</li>\n</ul>\n<p>Interestingly, we discovered that implementing certain well-known Short Time Control (STC) optimizations, which require a complete retuning of the engine’s hyperparameters, enabled us to incorporate established Very Long Long Time Control (VVLTC) optimizations — techniques that typically do not function under STC conditions!</p>\n<h3>Evaluation</h3>\n<p>For evaluation, we introducd NNUE with a pretty straightforward NNUE architecture adopted in different forms in the chess community — (768x1hm -&gt; 64)x1 -&gt; 1x8.</p>\n<ul>\n<li>768 input features with horizontal king mirroring. Inputs are flipped along the vertical axis, i.e., a1 becomes h1, b1 becomes g1, etc., based on the position of the friendly king.</li>\n<li>1 hidden layer with 64 neurons with Squared Clipped Rectified Linear Unit (SCReLU) activation function f(x) = min(max(x, 0), 1)^2.</li>\n<li>8 output buckets (a layer stack), selected based on the number of pieces remaining on the board (piece_count - 2) / 4.</li>\n<li>It outputs a single number, representing an evaluation of a node in the engine's internal units.</li>\n</ul>\n<p>The network training involves 3-stages of progressive training, with each stage restarting from the previous one with modifications, finally followed by an SPSA session. For the full training configuration, see <a href=\"https://github.com/peregrineshahin/Approvers/blob/main/training/config.rs\" target=\"_blank\">training/config.rs</a> in the repository, compatible with the <a href=\"https://github.com/jw1912/bullet\" target=\"_blank\">Bullet</a> trainer.</p>\n<p>The network is quantized to 8 bits for FT weights/biases and L1 weights, and 16 bits for L1 biases. Also, due to unused features for pawns<br>\n(1st and 8th ranks being illegal by the rules of chess) and the mirror squares of kings, the input features are reduced to <code>704</code>.</p>\n<h3>Size Optimization</h3>\n<p>To minimize the size of the binary and fit the largest NNUE model while keeping the crucial <code>-O3</code> flag for NNUE performance, we did lots of cleanups and simplifications (including functional ones that haven't regressed in our SPRT tests). Additionally, we switched from <code>gcc</code> to <code>clang</code>, as it produced smaller binaries and at least as fast, later combining with various cflags, <code>#pragma</code> directives to disable unrolling on individual loops, and applying a combination of <code>minsize</code>, <code>cold</code>, and <code>section(\".text.small\")</code> attributes to non-hot functions played a big role for achieving our goal. We also fully removed dependencies on <code>libm</code> and <code>lpthread</code> by replacing necessary functions with custom implementations and making the application truly single-threaded.</p>\n<h3>Local Results</h3>\n<p>After the source code of all top-3 entries was published, we tested our engine against them. The conditions are as close to Kaggle as possible – 1 thread, 1MB hash, 10s per move (scaled individually based on machine speed to match Kaggle's machine NPS), and the Kaggle opening book. The delay/increment was left unset, as it's unpredictable on Kaggle and causes time losses. One might argue it doesn't even work. So, the only piece missing in our testing was pondering.</p>\n<p>In 80K games, there wasn't a single crash or a time loss on any of our machines.</p>\n<p><code>Linrock</code> vs. <code>Approvers</code></p>\n<pre><code>   | -. +- . (%)\n  | .+.s Threads= Hash=MB\n | N:  W:  L:  D: \n |\n</code></pre>\n<p><code>Fix the bugs?</code> vs. <code>Approvers</code></p>\n<pre><code>   | -. +- . (%)\n  | .+.s Threads= Hash=MB\n | N:  W:  L:  D: \n |\n</code></pre>\n<p>The top-3 are very close, with a slight edge to our entry. Ultimately, it came down to pure luck due to a highly unstable rating system.<br>\nNevertheless, we had a great time and lots of fun during the competition and hope you did too.</p>\n<h3>Bonus</h3>\n<p>Under the previously mentioned conditions, here's a short match between Approvers and the latest development version of Stockfish at the time of testing (commit <code>fa6c30af</code>).</p>\n<pre><code>       [] \n             [] \n             [] \n             [] \n         \n</code></pre>\n<p>Please note, Stockfish is optimized for much longer time controls and regresses in such short ones, yet our submission looks quite powerful.</p>",
      "rawMarkdown": "## Approvers' Submission\n\nThe source code is available at https://github.com/peregrineshahin/Approvers.\n\n### Background\n\nWe started out as 2 separate teams, shuffling near the top of the leaderboard. Eventually, we decided to join forces.\nBoth of us had prior experience as chess engine developers — @peregrineshahin is a Stockfish contributor, a highly skilled professional, and I, @rickonaut, developed my own chess engine as a pet-project.\n\nWe came to understand that a highly ranked submission would likely require optimizing all four key aspects of the tournament. With this in mind, our approach was driven by a commitment to developing a chess engine in a dedicated manner, focusing on optimizing for the size limit, memory limit, tested time control (TC), and the opening book.\n\n### Testing\n\nAs the gold standard in the chess engine community, we used SPRT (Sequential Probability Ratio Test) to determine whether a change is statistically beneficial and SPSA (Simultaneous Perturbation Stochastic Approximation) for tuning various constants and parameters. In total, we have played around 20M\\* games using distributed compute resources. Our GitHub repository has over 1250 branches, each containing different ideas and attempts to improve the submission. We have left all commits and branches intact for historical reference when switching from a private to a public repository. Most functional commits on the `main` branch include descriptions with the result of associated SPRT tests.\n\nBefore merging teams, I had my own private local instance of [OpenBench](https://github.com/AndyGrant/OpenBench) up and running, a generic open source chess testing framework, developed by @agethereal, this made it easier to get started rather than debating whether to use [Fishtest](https://github.com/official-stockfish/fishtest) while both can do the job.\nOnce we joined forces, this setup played a key role in helping us develop and refine the engine together.\n\n\\* In comparison, `Fix the bugs?` reported ~38M games.\n\n### Development and Strategies\n\nThe starting point is the [Cfish](https://github.com/syzygy1/Cfish), a C port of [Stockfish](https://github.com/official-stockfish/Stockfish).\n\nUnfortunately, although we have over 300 commits in the repository, some commits from before merging the teams were not tracked. However, they might not be particularly relevant to the final submission.\n\nWe recognized early on the importance of combining domain-specific knowledge with general development skills.\n\n### Search Features\n\nDue to size limitations, we determined that the most effective strategy for our team was to include four fundamental search features. These features significantly shaped the parameters of our chess engine later on, distinguishing it from conventional engines, generally because these hurt the performance in long time controls:\n\n- Short Time Control (STC) Elo Gainer Optimization – This technique skips root depths at odd plies in Iterative Deepening, a method initially discovered by Shahin while working on Stockfish.\n- STC Fail-High Handling – A strategy that moves on to the next root node in the event of a fail-high, originally discovered in the early days of Stockfish.\n- Quiescence Search Time Checking – An STC optimization that improves performance in Stockfish-clone engines by monitoring time even during Quiescence Search. This was also identified by Shahin during his previous work on Stockfish.\n- Sudden Death Time Control Optimization – A technique developed by the Stockfish team, which scales the Move-To-Go (MTG) parameter dynamically as time control approaches its limit.\n\nInterestingly, we discovered that implementing certain well-known Short Time Control (STC) optimizations, which require a complete retuning of the engine’s hyperparameters, enabled us to incorporate established Very Long Long Time Control (VVLTC) optimizations — techniques that typically do not function under STC conditions!\n\n### Evaluation\n\nFor evaluation, we introducd NNUE with a pretty straightforward NNUE architecture adopted in different forms in the chess community — (768x1hm -> 64)x1 -> 1x8.\n\n- 768 input features with horizontal king mirroring. Inputs are flipped along the vertical axis, i.e., a1 becomes h1, b1 becomes g1, etc., based on the position of the friendly king.\n- 1 hidden layer with 64 neurons with Squared Clipped Rectified Linear Unit (SCReLU) activation function f(x) = min(max(x, 0), 1)^2.\n- 8 output buckets (a layer stack), selected based on the number of pieces remaining on the board (piece_count - 2) / 4.\n- It outputs a single number, representing an evaluation of a node in the engine's internal units.\n\nThe network training involves 3-stages of progressive training, with each stage restarting from the previous one with modifications, finally followed by an SPSA session. For the full training configuration, see [training/config.rs](https://github.com/peregrineshahin/Approvers/blob/main/training/config.rs) in the repository, compatible with the [Bullet](https://github.com/jw1912/bullet) trainer.\n\nThe network is quantized to 8 bits for FT weights/biases and L1 weights, and 16 bits for L1 biases. Also, due to unused features for pawns\n(1st and 8th ranks being illegal by the rules of chess) and the mirror squares of kings, the input features are reduced to `704`.\n\n### Size Optimization\n\nTo minimize the size of the binary and fit the largest NNUE model while keeping the crucial `-O3` flag for NNUE performance, we did lots of cleanups and simplifications (including functional ones that haven't regressed in our SPRT tests). Additionally, we switched from `gcc` to `clang`, as it produced smaller binaries and at least as fast, later combining with various cflags, `#pragma` directives to disable unrolling on individual loops, and applying a combination of `minsize`, `cold`, and `section(\".text.small\")` attributes to non-hot functions played a big role for achieving our goal. We also fully removed dependencies on `libm` and `lpthread` by replacing necessary functions with custom implementations and making the application truly single-threaded.\n\n### Local Results\n\nAfter the source code of all top-3 entries was published, we tested our engine against them. The conditions are as close to Kaggle as possible – 1 thread, 1MB hash, 10s per move (scaled individually based on machine speed to match Kaggle's machine NPS), and the Kaggle opening book. The delay/increment was left unset, as it's unpredictable on Kaggle and causes time losses. One might argue it doesn't even work. So, the only piece missing in our testing was pondering.\n\nIn 80K games, there wasn't a single crash or a time loss on any of our machines.\n\n`Linrock` vs. `Approvers`\n\n```\nElo   | -3.95 +- 1.90 (95%)\nConf  | 10.0+0.00s Threads=1 Hash=1MB\nGames | N: 40000 W: 7577 L: 8032 D: 24391\nPenta | [513, 4465, 10444, 4120, 458]\n```\n\n`Fix the bugs?` vs. `Approvers`\n\n```\nElo   | -3.43 +- 1.97 (95%)\nConf  | 10.0+0.00s Threads=1 Hash=1MB\nGames | N: 40002 W: 8195 L: 8590 D: 23217\nPenta | [576, 4636, 9946, 4293, 550]\n```\n\nThe top-3 are very close, with a slight edge to our entry. Ultimately, it came down to pure luck due to a highly unstable rating system.\nNevertheless, we had a great time and lots of fun during the competition and hope you did too.\n\n### Bonus\n\nUnder the previously mentioned conditions, here's a short match between Approvers and the latest development version of Stockfish at the time of testing (commit `fa6c30af`).\n\n```\nScore of Approvers vs Stockfish: 136 - 2012 - 1622  [0.251] 3770\n...      Approvers playing White: 100 - 710 - 1075  [0.338] 1885\n...      Approvers playing Black: 36 - 1302 - 547  [0.164] 1885\n...      White vs Black: 1402 - 746 - 1622  [0.587] 3770\nElo difference: -189.7 +/- 8.4, LOS: 0.0 %, DrawRatio: 43.0 %\n```\n\nPlease note, Stockfish is optimized for much longer time controls and regresses in such short ones, yet our submission looks quite powerful.",
      "votes": 12
    },
    {
      "id": 3160268,
      "postDate": "2025-03-26T14:50:46.370Z",
      "content": "<p>Thanks for sharing, great work!</p>",
      "rawMarkdown": "Thanks for sharing, great work!",
      "votes": 1
    }
  ],
  "complete": true,
  "source": "live",
  "fetched_at": "2026-10-20T08:26:37Z",
  "competition_id": 86524,
  "competition_slug": "fide-google-efficiency-chess-ai-challenge",
  "team_id": 13002924,
  "solution_url": "/competitions/fide-google-efficiency-chess-ai-challenge/writeups/approvers-approvers-submission-2nd"
}