Skip to content

Store NNUE accumulators in the stack#443

Merged
bdmendes merged 1 commit into
masterfrom
acc-stack
Apr 22, 2026
Merged

Store NNUE accumulators in the stack#443
bdmendes merged 1 commit into
masterfrom
acc-stack

Conversation

@bdmendes

@bdmendes bdmendes commented Apr 22, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Refactor
    • Optimized neural network evaluation efficiency through improved memory management and simplified indexing logic.

@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Modified the neural network evaluator to replace heap-allocated accumulator vectors with a fixed-size stack array and simplified indexing by removing the perspective_index field, replaced with direct perspective-to-index casting. Updated imports accordingly.

Changes

Cohort / File(s) Summary
Neural Network Accumulator Optimization
src/evaluation/nnue.rs
Replaced [Vec<f64>; 2] accumulator with [[f64; HIDDEN_LAYER_SIZE]; 2] stack array; removed perspective_index field and replaced all usages with perspective as usize indexing in set, clear, forward, forward_and_cache, and evaluate_unscaled methods; adjusted imports by removing std::array.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through stack allocation land,
No more heaps bouncing far and wide!
Perspective casting, hand in paw so grand,
Fixed-size arrays now our guide,
NNUE optimization with pride! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Store NNUE accumulators in the stack' directly and accurately summarizes the main change: replacing heap-allocated accumulator vectors with a fixed-size stack array.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/evaluation/nnue.rs (1)

131-166: 🧹 Nitpick | 🔵 Trivial

Encapsulate the perspective-to-index conversion to prevent silent reordering bugs.

The code uses perspective as usize directly to index two-element arrays (acc, last_seen). This works only because Color::White = 0 and Color::Black = 1 (enforced by primitive_enum!), but an explicit conversion helper makes the invariant local and exhaustively checked. This pattern appears throughout the codebase; consider applying the same pattern here as a starting point.

Proposed fix
 impl NeuralNetwork {
+    fn perspective_index(perspective: Perspective) -> usize {
+        match perspective {
+            Color::White => 0,
+            Color::Black => 1,
+        }
+    }
+
     fn set(&mut self, piece: Piece, color: Color, square: Square, perspective: Perspective) {
         let input_idx = Self::input_index(
             piece,
             Self::transformed_color(color, perspective),
             Self::transformed_square(square, perspective),
         );
+        let perspective_idx = Self::perspective_index(perspective);
         for i in 0..HIDDEN_LAYER_SIZE {
-            self.acc[perspective as usize][i] += self.params.acc_weights[i * INPUT_SIZE + input_idx];
+            self.acc[perspective_idx][i] += self.params.acc_weights[i * INPUT_SIZE + input_idx];
         }
     }
 
     fn clear(&mut self, piece: Piece, color: Color, square: Square, perspective: Perspective) {
         let input_idx = Self::input_index(
             piece,
             Self::transformed_color(color, perspective),
             Self::transformed_square(square, perspective),
         );
+        let perspective_idx = Self::perspective_index(perspective);
         for i in 0..HIDDEN_LAYER_SIZE {
-            self.acc[perspective as usize][i] -= self.params.acc_weights[i * INPUT_SIZE + input_idx];
+            self.acc[perspective_idx][i] -= self.params.acc_weights[i * INPUT_SIZE + input_idx];
         }
     }
 
     fn forward(&self, perspective: Perspective) -> f64 {
         let mut eval: f64 = 0.0;
+        let perspective_idx = Self::perspective_index(perspective);
 
         for i in 0..HIDDEN_LAYER_SIZE {
-            let hidden_out = Self::relu(self.acc[perspective as usize][i] + self.params.acc_biases[i]);
+            let hidden_out = Self::relu(self.acc[perspective_idx][i] + self.params.acc_biases[i]);
             eval += hidden_out * self.params.out_weights[i];
         }
 
         eval + self.params.out_bias
@@
     fn forward_and_cache(&mut self, position: &Position) -> f64 {
         let perspective = position.side_to_move();
         let res = self.forward(perspective);
-        self.last_seen[perspective as usize] = *position;
+        self.last_seen[Self::perspective_index(perspective)] = *position;
         res
     }
 
     fn evaluate_unscaled(&mut self, position: &Position) -> f64 {
         let perspective = position.side_to_move();
-        let diff = position.diff(&self.last_seen[perspective as usize]);
+        let diff = position.diff(&self.last_seen[Self::perspective_index(perspective)]);

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e34dfefd-8cd8-4e39-86d3-7807411a1f80

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4486c and e4f37a3.

📒 Files selected for processing (1)
  • src/evaluation/nnue.rs

@codecov

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91%. Comparing base (4d4486c) to head (e4f37a3).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@          Coverage Diff          @@
##           master   #443   +/-   ##
=====================================
- Coverage      92%    91%   -0%     
=====================================
  Files          33     33           
  Lines        3760   3754    -6     
  Branches     3760   3754    -6     
=====================================
- Hits         3422   3416    -6     
  Misses        319    319           
  Partials       19     19           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown

Against master [hash=64]: 🆗 Elo: 2.78 +/- 15.98, nElo: 3.75 +/- 21.53

@github-actions

Copy link
Copy Markdown

Against v1.6.0 [hash=64]: ✅ Elo: 77.34 +/- 19.23, nElo: 89.60 +/- 21.53

@bdmendes bdmendes merged commit 4fb625f into master Apr 22, 2026
8 checks passed
@bdmendes bdmendes deleted the acc-stack branch April 22, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant