summaryrefslogtreecommitdiff
path: root/src/nnueaccum.c
blob: 7a3010f9bf72a604988e9fa6b4cb26f49a6f7d2c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**

Copyright (c) Scott Gasch

Module Name:

    nnueaccum.c

Abstract:

    Real NNUE accumulator maintenance: the embedding-table storage and
    the three operations (add one feature's row, remove one feature's
    row, recompute an entire perspective from scratch) that move.c's
    MakeMove/UnmakeMove hook into LiftPiece/PlacePiece/SlidePiece/
    SlidePawn/SlidePieceWithoutSigs use to keep
    SEARCHER_THREAD_CONTEXT.sNnueAccum[2] incrementally up to date.

    Hot path -- called from the piece-movement primitives, which run
    millions of times/sec in a real search. Kept branch-light: Add/
    Remove are a single feature-index computation (chess.h's
    NnueFeatureIndex, shared with nnuefeatures.c's reference dump) and
    a straight-line 256-float add/subtract loop, written so -O3
    -ffast-math (this project's release profile) has the best shot at
    auto-vectorizing it on this box's confirmed AVX2+FMA target.
    Explicit SIMD intrinsics are an explicitly deferred follow-up (see
    docs/NNUE.md) once there's a correct baseline to profile against,
    not something to reach for pre-emptively here.

Author:

    Scott Gasch ([email protected]) 15 Oct 2026

**/

#include "chess.h"

// Quantized embedding table -- see chess.h's extern declaration and
// its own comment. Zero-initialized (C's static/global default) until
// NnueLoadWeights reads a real exported checkpoint.
SHORT g_rgfNnueWeight[NNUE_DIMENSIONS][NNUE_ACCUMULATOR_SIZE];
SHORT g_rgfNnueBias[NNUE_ACCUMULATOR_SIZE];
float g_fNnueFtScale = 1.0f;

void
NnueAccumAddFeature(NNUE_ACCUMULATOR *pAccum, ULONG uPerspective,
                    COOR cKingOwn, COOR cPieceSquare, PIECE pPiece)
/**

Routine description:

    Adds one feature's weight row into *pAccum (in place). Hot path.

**/
{
    int idx = NnueFeatureIndex(uPerspective, cKingOwn, cPieceSquare, pPiece);
    const SHORT *pRow = g_rgfNnueWeight[idx];
    ULONG i;

    for (i = 0; i < NNUE_ACCUMULATOR_SIZE; i++)
    {
        pAccum->rgiValue[i] += pRow[i];
    }
}


void
NnueAccumRemoveFeature(NNUE_ACCUMULATOR *pAccum, ULONG uPerspective,
                       COOR cKingOwn, COOR cPieceSquare, PIECE pPiece)
/**

Routine description:

    Subtracts one feature's weight row from *pAccum (in place). Hot
    path -- the exact inverse of NnueAccumAddFeature, so that applying
    Add then Remove (or Remove then Add) for the same arguments is an
    exact no-op (integer arithmetic, no float rounding to worry about).

**/
{
    int idx = NnueFeatureIndex(uPerspective, cKingOwn, cPieceSquare, pPiece);
    const SHORT *pRow = g_rgfNnueWeight[idx];
    ULONG i;

    for (i = 0; i < NNUE_ACCUMULATOR_SIZE; i++)
    {
        pAccum->rgiValue[i] -= pRow[i];
    }
}


void
NnueAccumRecompute(NNUE_ACCUMULATOR *pAccum, POSITION *pos, ULONG uPerspective)
/**

Routine description:

    Fully recomputes *pAccum from scratch against the CURRENT board in
    *pos, for perspective uPerspective -- the only correct way to
    update a perspective after its OWN king moves (every feature's
    king-bucket/orientation changes, not just the king's own feature;
    see chess.h's NNUE_ACCUMULATOR comment). Not hot in the same sense
    as Add/Remove -- only called on king moves, a small fraction of
    all moves in a tree -- so this walks the whole board rather than
    needing a piece list.

Parameters:

    NNUE_ACCUMULATOR *pAccum : accumulator to overwrite
    POSITION *pos : current board (already reflects the move)
    ULONG uPerspective : which color's accumulator this is

**/
{
    COOR cKing = pos->cKingSq[uPerspective];
    int f, r;
    ULONG i;

    ASSERT(IS_ON_BOARD(cKing));
    ASSERT(IS_KING(pos->rgSquare[cKing].pPiece));
    ASSERT(PIECE_COLOR(pos->rgSquare[cKing].pPiece) == uPerspective);

    for (i = 0; i < NNUE_ACCUMULATOR_SIZE; i++)
    {
        pAccum->rgiValue[i] = g_rgfNnueBias[i];
    }

    // 0x88 COORs aren't a simple monotonic range (A1=0x70 > H8=0x07),
    // so walk file/rank explicitly -- same idiom nnuefeatures.c's
    // ActiveFeatureIndices already uses.
    for (f = 0; f < 8; f++)
    {
        for (r = 1; r <= 8; r++)
        {
            COOR c = FILE_RANK_TO_COOR(f, r);
            PIECE p = pos->rgSquare[c].pPiece;

            if (IS_EMPTY(p))
            {
                continue;
            }
            NnueAccumAddFeature(pAccum, uPerspective, cKing, c, p);
        }
    }
}