Merge branch 'master' into clusterMergeMaster13

brings the cluster branch to SF 14.1

Fixes minor conflicts

local testing cluster 4x4T vs master 4T, 10+0.1s, noob_3moves:

Score of cluster vs master: 6 - 0 - 94  [0.530] 100
Elo difference: 20.9 +/- 16.2, LOS: 99.3 %, DrawRatio: 94.0 %

No functional change
This commit is contained in:
Joost VandeVondele
2021-10-31 08:38:10 +01:00
52 changed files with 3202 additions and 2150 deletions
+277
View File
@@ -0,0 +1,277 @@
name: Stockfish
on:
push:
branches:
- master
- tools
- github_ci
pull_request:
branches:
- master
- tools
jobs:
Stockfish:
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
env:
COMPILER: ${{ matrix.config.compiler }}
COMP: ${{ matrix.config.comp }}
CXXFLAGS: "-Werror"
strategy:
matrix:
config:
- {
name: "Ubuntu 20.04 GCC",
os: ubuntu-20.04,
compiler: g++,
comp: gcc,
run_expensive_tests: true,
run_32bit_tests: true,
run_64bit_tests: true,
shell: 'bash {0}'
}
- {
name: "Ubuntu 20.04 Clang",
os: ubuntu-20.04,
compiler: clang++,
comp: clang,
run_expensive_tests: false,
run_32bit_tests: true,
run_64bit_tests: true,
shell: 'bash {0}'
}
- {
name: "MacOS 10.15 Apple Clang",
os: macos-10.15,
compiler: clang++,
comp: clang,
run_expensive_tests: false,
run_32bit_tests: false,
run_64bit_tests: true,
shell: 'bash {0}'
}
- {
name: "MacOS 10.15 GCC 10",
os: macos-10.15,
compiler: g++-10,
comp: gcc,
run_expensive_tests: false,
run_32bit_tests: false,
run_64bit_tests: true,
shell: 'bash {0}'
}
- {
name: "Windows 2019 Mingw-w64 GCC x86_64",
os: windows-2019,
compiler: g++,
comp: gcc,
run_expensive_tests: false,
run_32bit_tests: false,
run_64bit_tests: true,
msys_sys: 'mingw64',
msys_env: 'x86_64',
shell: 'msys2 {0}'
}
- {
name: "Windows 2019 Mingw-w64 GCC i686",
os: windows-2019,
compiler: g++,
comp: gcc,
run_expensive_tests: false,
run_32bit_tests: true,
run_64bit_tests: false,
msys_sys: 'mingw32',
msys_env: 'i686',
shell: 'msys2 {0}'
}
defaults:
run:
working-directory: src
shell: ${{ matrix.config.shell }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Download required linux packages
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install expect valgrind g++-multilib
- name: Setup msys and install required packages
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: ${{matrix.config.msys_sys}}
install: mingw-w64-${{matrix.config.msys_env}}-gcc make git expect
- name: Download the used network from the fishtest framework
run: |
make net
- name: Extract the bench number from the commit history
run: |
git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
[ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found"
- name: Check compiler
run: |
$COMPILER -v
- name: Test help target
run: |
make help
# x86-32 tests
- name: Test debug x86-32 build
if: ${{ matrix.config.run_32bit_tests }}
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
make clean
make -j2 ARCH=x86-32 optimize=no debug=yes build
../tests/signature.sh $benchref
- name: Test x86-32 build
if: ${{ matrix.config.run_32bit_tests }}
run: |
make clean
make -j2 ARCH=x86-32 build
../tests/signature.sh $benchref
- name: Test x86-32-sse41-popcnt build
if: ${{ matrix.config.run_32bit_tests }}
run: |
make clean
make -j2 ARCH=x86-32-sse41-popcnt build
../tests/signature.sh $benchref
- name: Test x86-32-sse2 build
if: ${{ matrix.config.run_32bit_tests }}
run: |
make clean
make -j2 ARCH=x86-32-sse2 build
../tests/signature.sh $benchref
- name: Test general-32 build
if: ${{ matrix.config.run_32bit_tests }}
run: |
make clean
make -j2 ARCH=general-32 build
../tests/signature.sh $benchref
# x86-64 tests
- name: Test debug x86-64-modern build
if: ${{ matrix.config.run_64bit_tests }}
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
make clean
make -j2 ARCH=x86-64-modern optimize=no debug=yes build
../tests/signature.sh $benchref
- name: Test x86-64-modern build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-modern build
../tests/signature.sh $benchref
- name: Test x86-64-ssse3 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-ssse3 build
../tests/signature.sh $benchref
- name: Test x86-64-sse3-popcnt build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-sse3-popcnt build
../tests/signature.sh $benchref
- name: Test x86-64 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64 build
../tests/signature.sh $benchref
- name: Test general-64 build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j2 ARCH=general-64 build
../tests/signature.sh $benchref
# x86-64 with newer extensions tests
- name: Compile x86-64-avx2 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-avx2 build
- name: Compile x86-64-bmi2 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-bmi2 build
- name: Compile x86-64-avx512 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-avx512 build
- name: Compile x86-64-vnni512 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-vnni512 build
- name: Compile x86-64-vnni256 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-vnni256 build
# Other tests
- name: Check perft and search reproducibility
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-modern build
../tests/perft.sh
../tests/reprosearch.sh
# Sanitizers
- name: Run under valgrind
if: ${{ matrix.config.run_expensive_tests }}
run: |
export CXXFLAGS="-O1 -fno-inline"
make clean
make -j2 ARCH=x86-64-modern debug=yes optimize=no build > /dev/null
../tests/instrumented.sh --valgrind
../tests/instrumented.sh --valgrind-thread
- name: Run with UB sanitizer
if: ${{ matrix.config.run_expensive_tests }}
run: |
export CXXFLAGS="-O1 -fno-inline"
make clean
make -j2 ARCH=x86-64-modern sanitize=undefined optimize=no debug=yes build > /dev/null
../tests/instrumented.sh --sanitizer-undefined
- name: Run with thread sanitizer
if: ${{ matrix.config.run_expensive_tests }}
run: |
export CXXFLAGS="-O1 -fno-inline"
make clean
make -j2 ARCH=x86-64-modern sanitize=thread optimize=no debug=yes build > /dev/null
../tests/instrumented.sh --sanitizer-thread
-101
View File
@@ -1,101 +0,0 @@
language: cpp
dist: bionic
matrix:
include:
- os: linux
compiler: gcc
addons:
apt:
packages: ['g++-8', 'g++-8-multilib', 'g++-multilib', 'valgrind', 'expect', 'curl']
env:
- COMPILER=g++-8
- COMP=gcc
- os: linux
compiler: clang
addons:
apt:
packages: ['clang-10', 'llvm-10-dev', 'g++-multilib', 'valgrind', 'expect', 'curl']
env:
- COMPILER=clang++-10
- COMP=clang
- os: osx
osx_image: xcode12
compiler: gcc
env:
- COMPILER=g++
- COMP=gcc
- os: osx
osx_image: xcode12
compiler: clang
env:
- COMPILER=clang++
- COMP=clang
branches:
only:
- master
before_script:
- cd src
script:
# Download net
- make net
# Obtain bench reference from git log
- git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
- export benchref=$(cat git_sig)
- echo "Reference bench:" $benchref
# Compiler version string
- $COMPILER -v
# test help target
- make help
# Verify bench number against various builds
- export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
- make clean && make -j2 ARCH=x86-64-modern optimize=no debug=yes build && ../tests/signature.sh $benchref
- export CXXFLAGS="-Werror"
- make clean && make -j2 ARCH=x86-64-modern build && ../tests/signature.sh $benchref
- make clean && make -j2 ARCH=x86-64-ssse3 build && ../tests/signature.sh $benchref
- make clean && make -j2 ARCH=x86-64-sse3-popcnt build && ../tests/signature.sh $benchref
- make clean && make -j2 ARCH=x86-64 build && ../tests/signature.sh $benchref
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=general-64 build && ../tests/signature.sh $benchref; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32-sse41-popcnt build && ../tests/signature.sh $benchref; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32-sse2 build && ../tests/signature.sh $benchref; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=general-32 build && ../tests/signature.sh $benchref; fi
# workaround: exclude a custom version of llvm+clang, which doesn't find llvm-profdata on ubuntu
- if [[ "$TRAVIS_OS_NAME" != "linux" || "$COMP" == "gcc" ]]; then make clean && make -j2 ARCH=x86-64-modern profile-build && ../tests/signature.sh $benchref; fi
# compile only for some more advanced architectures (might not run in travis)
- make clean && make -j2 ARCH=x86-64-avx2 build
- make clean && make -j2 ARCH=x86-64-bmi2 build
- make clean && make -j2 ARCH=x86-64-avx512 build
- make clean && make -j2 ARCH=x86-64-vnni512 build
- make clean && make -j2 ARCH=x86-64-vnni256 build
#
# Check perft and reproducible search
- make clean && make -j2 ARCH=x86-64-modern build
- ../tests/perft.sh
- ../tests/reprosearch.sh
#
# Valgrind
#
- export CXXFLAGS="-O1 -fno-inline"
- if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64-modern debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi
- if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi
#
# Sanitizer
#
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-64-modern sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-64-modern sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi
+10 -1
View File
@@ -1,4 +1,4 @@
# List of authors for Stockfish, as of August 4, 2020 # List of authors for Stockfish
# Founders of the Stockfish project and fishtest infrastructure # Founders of the Stockfish project and fishtest infrastructure
Tord Romstad (romstad) Tord Romstad (romstad)
@@ -21,12 +21,14 @@ Alexander Kure
Alexander Pagel (Lolligerhans) Alexander Pagel (Lolligerhans)
Alfredo Menezes (lonfom169) Alfredo Menezes (lonfom169)
Ali AlZhrani (Cooffe) Ali AlZhrani (Cooffe)
Andrei Vetrov (proukornew)
Andrew Grant (AndyGrant) Andrew Grant (AndyGrant)
Andrey Neporada (nepal) Andrey Neporada (nepal)
Andy Duplain Andy Duplain
Antoine Champion (antoinechampion) Antoine Champion (antoinechampion)
Aram Tumanian (atumanian) Aram Tumanian (atumanian)
Arjun Temurnikar Arjun Temurnikar
Artem Solopiy (EntityFX)
Auguste Pop Auguste Pop
Balint Pfliegel Balint Pfliegel
Ben Koshy (BKSpurgeon) Ben Koshy (BKSpurgeon)
@@ -51,6 +53,7 @@ Dieter Dobbelaere (ddobbelaere)
DiscanX DiscanX
Dominik Schlösser (domschl) Dominik Schlösser (domschl)
double-beep double-beep
Douglas Matos Gomes (dsmsgms)
Eduardo Cáceres (eduherminio) Eduardo Cáceres (eduherminio)
Eelco de Groot (KingDefender) Eelco de Groot (KingDefender)
Elvin Liu (solarlight2) Elvin Liu (solarlight2)
@@ -67,6 +70,7 @@ gamander
Gary Heckman (gheckman) Gary Heckman (gheckman)
George Sobala (gsobala) George Sobala (gsobala)
gguliash gguliash
Giacomo Lorenzetti (G-Lorenz)
Gian-Carlo Pascutto (gcp) Gian-Carlo Pascutto (gcp)
Gontran Lemaire (gonlem) Gontran Lemaire (gonlem)
Goodkov Vasiliy Aleksandrovich (goodkov) Goodkov Vasiliy Aleksandrovich (goodkov)
@@ -94,6 +98,7 @@ Joost VandeVondele (vondele)
Jörg Oster (joergoster) Jörg Oster (joergoster)
Joseph Ellis (jhellis3) Joseph Ellis (jhellis3)
Joseph R. Prostko Joseph R. Prostko
Julian Willemer (NightlyKing)
jundery jundery
Justin Blanchard (UncombedCoconut) Justin Blanchard (UncombedCoconut)
Kelly Wilson Kelly Wilson
@@ -104,6 +109,7 @@ Kojirion
Krystian Kuzniarek (kuzkry) Krystian Kuzniarek (kuzkry)
Leonardo Ljubičić (ICCF World Champion) Leonardo Ljubičić (ICCF World Champion)
Leonid Pechenik (lp--) Leonid Pechenik (lp--)
Liam Keegan (lkeegan)
Linus Arver (listx) Linus Arver (listx)
loco-loco loco-loco
Lub van den Berg (ElbertoOne) Lub van den Berg (ElbertoOne)
@@ -138,6 +144,7 @@ Nikolay Kostov (NikolayIT)
Nguyen Pham (nguyenpham) Nguyen Pham (nguyenpham)
Norman Schmidt (FireFather) Norman Schmidt (FireFather)
notruck notruck
Ofek Shochat (OfekShochat, ghostway)
Ondrej Mosnáček (WOnder93) Ondrej Mosnáček (WOnder93)
Oskar Werkelin Ahlin Oskar Werkelin Ahlin
Pablo Vazquez Pablo Vazquez
@@ -173,6 +180,7 @@ Stefan Geschwentner (locutus2)
Stefano Cardanobile (Stefano80) Stefano Cardanobile (Stefano80)
Steinar Gunderson (sesse) Steinar Gunderson (sesse)
Stéphane Nicolet (snicolet) Stéphane Nicolet (snicolet)
Prokop Randáček (ProkopRandacek)
Thanar2 Thanar2
thaspel thaspel
theo77186 theo77186
@@ -180,6 +188,7 @@ Tom Truscott
Tom Vijlbrief (tomtor) Tom Vijlbrief (tomtor)
Tomasz Sobczyk (Sopel97) Tomasz Sobczyk (Sopel97)
Torsten Franz (torfranz, tfranzer) Torsten Franz (torfranz, tfranzer)
Torsten Hellwig (Torom)
Tracey Emery (basepr1me) Tracey Emery (basepr1me)
tttak tttak
Unai Corzo (unaiic) Unai Corzo (unaiic)
+57 -32
View File
@@ -1,6 +1,6 @@
## Overview ## Overview
[![Build Status](https://travis-ci.org/official-stockfish/Stockfish.svg?branch=master)](https://travis-ci.org/official-stockfish/Stockfish) [![Build Status](https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml/badge.svg)](https://github.com/official-stockfish/Stockfish/actions)
[![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master) [![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master)
[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine [Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine
@@ -21,21 +21,28 @@ intrinsics available on most CPUs (sse2, avx2, neon, or similar).
This distribution of Stockfish consists of the following files: This distribution of Stockfish consists of the following files:
* Readme.md, the file you are currently reading. * [Readme.md](https://github.com/official-stockfish/Stockfish/blob/master/README.md), the file you are currently reading.
* Copying.txt, a text file containing the GNU General Public License version 3. * [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt), a text file containing the GNU General Public License version 3.
* AUTHORS, a text file with the list of authors for the project
* src, a subdirectory containing the full source code, including a Makefile * [AUTHORS](https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS), a text file with the list of authors for the project
* [src](https://github.com/official-stockfish/Stockfish/tree/master/src), a subdirectory containing the full source code, including a Makefile
that can be used to compile Stockfish on Unix-like systems. that can be used to compile Stockfish on Unix-like systems.
* a file with the .nnue extension, storing the neural network for the NNUE * a file with the .nnue extension, storing the neural network for the NNUE
evaluation. Binary distributions will have this file embedded. evaluation. Binary distributions will have this file embedded.
## UCI options ## The UCI protocol and available options
Currently, Stockfish has the following UCI options: The Universal Chess Interface (UCI) is a standard protocol used to communicate with
a chess engine, and is the recommended way to do so for typical graphical user interfaces
(GUI) or chess tools. Stockfish implements the majority of it options as described
in [the UCI protocol](https://www.shredderchess.com/download/div/uci.zip).
Developers can see the default values for UCI options available in Stockfish by typing
`./stockfish uci` in a terminal, but the majority of users will typically see them and
change them via a chess GUI. This is a list of available UCI options in Stockfish:
* #### Threads * #### Threads
The number of CPU threads used for searching a position. For best performance, set The number of CPU threads used for searching a position. For best performance, set
@@ -113,14 +120,6 @@ Currently, Stockfish has the following UCI options:
Limit Syzygy tablebase probing to positions with at most this many pieces left Limit Syzygy tablebase probing to positions with at most this many pieces left
(including kings and pawns). (including kings and pawns).
* #### Contempt
A positive value for contempt favors middle game positions and avoids draws,
effective for the classical evaluation only.
* #### Analysis Contempt
By default, contempt is set to prefer the side to move. Set this option to "White"
or "Black" to analyse with contempt for that side, or "Off" to disable contempt.
* #### Move Overhead * #### Move Overhead
Assume a time delay of x ms due to network and GUI overheads. This is useful to Assume a time delay of x ms due to network and GUI overheads. This is useful to
avoid losses on time in those cases. avoid losses on time in those cases.
@@ -136,6 +135,34 @@ Currently, Stockfish has the following UCI options:
* #### Debug Log File * #### Debug Log File
Write all communication to and from the engine into a text file. Write all communication to and from the engine into a text file.
For developers the following non-standard commands might be of interest, mainly useful for debugging:
* #### bench *ttSize threads limit fenFile limitType evalType*
Performs a standard benchmark using various options. The signature of a version (standard node
count) is obtained using all defaults. `bench` is currently `bench 16 1 13 default depth mixed`.
* #### compiler
Give information about the compiler and environment used for building a binary.
* #### d
Display the current position, with ascii art and fen.
* #### eval
Return the evaluation of the current position.
* #### export_net [filename]
Exports the currently loaded network to a file.
If the currently loaded network is the embedded network and the filename
is not specified then the network is saved to the file matching the name
of the embedded network, as defined in evaluate.h.
If the currently loaded network is not the embedded network (some net set
through the UCI setoption) then the filename parameter is required and the
network is saved into that file.
* #### flip
Flips the side to move.
## A note on classical evaluation versus NNUE evaluation ## A note on classical evaluation versus NNUE evaluation
Both approaches assign a value to a position that is used in alpha-beta (PVS) search Both approaches assign a value to a position that is used in alpha-beta (PVS) search
@@ -162,7 +189,7 @@ Stockfish binary, but the default value of the EvalFile UCI option is the name o
that is guaranteed to be compatible with that binary. that is guaranteed to be compatible with that binary.
2) to use the NNUE evaluation, the additional data file with neural network parameters 2) to use the NNUE evaluation, the additional data file with neural network parameters
needs to be available. Normally, this file is already embedded in the binary or it needs to be available. Normally, this file is already embedded in the binary or it
can be downloaded. The filename for the default (recommended) net can be found as the default can be downloaded. The filename for the default (recommended) net can be found as the default
value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue` value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue`
(for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from (for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from
@@ -175,7 +202,7 @@ replacing `[filename]` as needed.
If the engine is searching a position that is not in the tablebases (e.g. If the engine is searching a position that is not in the tablebases (e.g.
a position with 8 pieces), it will access the tablebases during the search. a position with 8 pieces), it will access the tablebases during the search.
If the engine reports a very large score (typically 153.xx), this means If the engine reports a very large score (typically 153.xx), this means
it has found a winning line into a tablebase position. it has found a winning line into a tablebase position.
If the engine is given a position to search that is in the tablebases, it If the engine is given a position to search that is in the tablebases, it
@@ -209,15 +236,13 @@ mpirun -np N /path/to/stockfish
where ```N``` stands for the number of MPI processes used (alternatives to ```mpirun```, where ```N``` stands for the number of MPI processes used (alternatives to ```mpirun```,
include ```mpiexec```, ```srun```). Use 1 mpi rank per node, and employ threading include ```mpiexec```, ```srun```). Use 1 mpi rank per node, and employ threading
according to the cores per node. To build the cluster according to the cores per node. To build the cluster
branch, it is sufficient to specify ```COMPILER=mpicxx``` on the make command line, branch, it is sufficient to specify ```COMPILER=mpicxx``` (or e.g. CC depending on the name
and do a clean build: of the compiler providing MPI support) on the make command line, and do a clean build:
``` ```
make -j ARCH=x86-64-modern clean build COMPILER=mpicxx make -j ARCH=x86-64-modern clean build COMPILER=mpicxx mpi=yes
``` ```
If the name of the compiler wrapper (typically mpicxx, but sometimes e.g. CC) does Make sure that the MPI installation is configured to support ```MPI_THREAD_MULTIPLE```,
not match ```mpi``` an edit to the Makefile is required. Make sure that the MPI this might require adding system specific compiler options to the Makefile. Stockfish employs
installation is configured to support ```MPI_THREAD_MULTIPLE```, this might require
adding system specific compiler options to the Makefile. Stockfish employs
non-blocking (asynchronous) communication, and benefits from an MPI non-blocking (asynchronous) communication, and benefits from an MPI
implementation that efficiently supports this. Some MPI implentations might benefit implementation that efficiently supports this. Some MPI implentations might benefit
from leaving 1 core/thread free for these asynchronous communications, and might require from leaving 1 core/thread free for these asynchronous communications, and might require
@@ -271,9 +296,9 @@ When not using the Makefile to compile (for instance, with Microsoft MSVC) you
need to manually set/unset some switches in the compiler command line; see need to manually set/unset some switches in the compiler command line; see
file *types.h* for a quick reference. file *types.h* for a quick reference.
When reporting an issue or a bug, please tell us which version and When reporting an issue or a bug, please tell us which Stockfish version
compiler you used to create your executable. These informations can and which compiler you used to create your executable. This information
be found by typing the following commands in a console: can be found by typing the following command in a console:
``` ```
./stockfish compiler ./stockfish compiler
@@ -281,8 +306,8 @@ be found by typing the following commands in a console:
## Understanding the code base and participating in the project ## Understanding the code base and participating in the project
Stockfish's improvement over the last couple of years has been a great Stockfish's improvement over the last decade has been a great community
community effort. There are a few ways to help contribute to its growth. effort. There are a few ways to help contribute to its growth.
### Donating hardware ### Donating hardware
@@ -326,4 +351,4 @@ you are distributing. If you make any changes to the source code,
these changes must also be made available under the GPL. these changes must also be made available under the GPL.
For full details, read the copy of the GPL v3 found in the file named For full details, read the copy of the GPL v3 found in the file named
*Copying.txt*. [*Copying.txt*](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt).
+203 -187
View File
@@ -1,189 +1,205 @@
Contributors to Fishtest with >10,000 CPU hours, as of Feb 15, 2021. Contributors to Fishtest with >10,000 CPU hours, as of Jun 29, 2021.
Thank you! Thank you!
Username CPU Hours Games played Username CPU Hours Games played
---------------------------------------------------- -----------------------------------------------------
noobpwnftw 23930906 1560559941 noobpwnftw 27649494 1834734733
dew 1169948 70333008 mlang 1426107 89454622
mlang 957168 61657446 dew 1380910 82831648
mibere 703840 46867607 mibere 703840 46867607
tvijlbrief 517888 33379462 grandphish2 692707 41737913
JojoM 515404 30334272 tvijlbrief 669642 42371594
cw 443276 29385549 JojoM 597778 35297180
crunchy 427035 27344275 TueRens 519226 31823562
grandphish2 425794 26347253 cw 458421 30307421
fastgm 414133 24519696 fastgm 439667 25950040
gvreuls 377843 24708884 gvreuls 436599 28177460
CSU_Dynasty 338718 23030006 crunchy 427035 27344275
Fisherman 326795 21820747 CSU_Dynasty 374765 25106278
TueRens 313730 19490246 Fisherman 326901 21822979
ctoks 298442 20052551 ctoks 325477 21767943
velislav 270519 17355456 velislav 295343 18844324
bcross 241064 17196165 linrock 292789 10624427
glinscott 217799 13780820 bcross 278584 19488961
nordlandia 211692 13484886 okrout 262818 13803272
bking_US 198894 11876016 pemo 245982 11376085
drabel 191096 13129722 glinscott 217799 13780820
leszek 189170 11446821 leszek 212346 12959025
mgrabiak 187153 12013300 nordlandia 211692 13484886
robal 181389 11539242 bking_US 198894 11876016
Thanar 179852 12365359 drabel 196463 13450602
vdv 175274 9889046 robal 195473 12375650
spams 157128 10319326 mgrabiak 187226 12016564
marrco 150292 9401741 Dantist 183202 10990484
sqrt2 147963 9724586 Thanar 179852 12365359
CoffeeOne 137086 5022516 vdv 175274 9889046
vdbergh 137041 8926915 spams 157128 10319326
malala 136182 8002293 marrco 150295 9402141
mhoram 132780 8398229 sqrt2 147963 9724586
xoto 124729 8652088 mhoram 141278 8901241
davar 122092 7960001 CoffeeOne 137100 5024116
dsmith 122059 7570238 vdbergh 137041 8926915
Data 113305 8220352 malala 136182 8002293
BrunoBanani 112960 7436849 xoto 133702 9156676
pemo 109598 5036441 davar 122092 7960001
Dantist 106768 6431396 dsmith 122059 7570238
MaZePallas 102741 6630419 Data 113305 8220352
ElbertoOne 99028 7023771 BrunoBanani 112960 7436849
brabos 92118 6186135 MaZePallas 102823 6633619
linrock 90903 6708639 sterni1971 100532 5880772
psk 89957 5984901 ElbertoOne 99028 7023771
sunu 88614 6020673 brabos 92118 6186135
sterni1971 86948 5613788 oz 92100 6486640
Vizvezdenec 83761 5344740 psk 89957 5984901
BRAVONE 81239 5054681 amicic 89156 5392305
nssy 76497 5259388 sunu 88851 6028873
cuistot 76366 4370584 Vizvezdenec 83761 5344740
racerschmacer 75753 5442626 0x3C33 82614 5271253
teddybaer 75125 5407666 BRAVONE 81239 5054681
Pking_cda 73776 5293873 racerschmacer 80899 5759262
0x3C33 73133 4670293 cuistot 80300 4606144
jromang 72117 5054915 nssy 76497 5259388
solarlight 70517 5028306 teddybaer 75125 5407666
dv8silencer 70287 3883992 Pking_cda 73776 5293873
Bobo1239 68515 4652287 jromang 72192 5057715
manap 66273 4121774 solarlight 70517 5028306
tinker 64321 4268390 dv8silencer 70287 3883992
robnjr 57262 4053117 Bobo1239 68515 4652287
Freja 56938 3733019 manap 66273 4121774
ttruscott 56010 3680085 skiminki 65088 4023328
rkl 54986 4150767 tinker 64333 4268790
renouve 53811 3501516 sschnee 60767 3500800
finfish 51360 3370515 qurashee 57344 3168264
eva42 51272 3599691 robnjr 57262 4053117
rap 49985 3219146 Freja 56938 3733019
pb00067 49727 3298270 ttruscott 56010 3680085
amicic 49691 3042481 rkl 55132 4164467
ronaldjerum 47654 3240695 renouve 53811 3501516
bigpen0r 47278 3291647 finfish 51360 3370515
biffhero 46564 3111352 eva42 51272 3599691
VoyagerOne 45476 3452465 rap 49985 3219146
eastorwest 45033 3071805 pb00067 49727 3298270
speedycpu 43842 3003273 ronaldjerum 47654 3240695
jbwiebe 43305 2805433 bigpen0r 47653 3335327
Antihistamine 41788 2761312 eastorwest 47585 3221629
mhunt 41735 2691355 biffhero 46564 3111352
homyur 39893 2850481 VoyagerOne 45476 3452465
gri 39871 2515779 yurikvelo 44834 3034550
oryx 38282 2944400 speedycpu 43842 3003273
Spprtr 38157 2470529 jbwiebe 43305 2805433
SC 37290 2731014 Spprtr 42279 2680153
csnodgrass 36207 2688994 DesolatedDodo 42007 2447516
jmdana 36157 2210661 Antihistamine 41788 2761312
strelock 34716 2074055 mhunt 41735 2691355
Garf 33800 2747562 homyur 39893 2850481
skiminki 33515 2055584 gri 39871 2515779
EthanOConnor 33370 2090311 Fifis 38776 2529121
slakovv 32915 2021889 oryx 38724 2966648
yurikvelo 32600 2255966 SC 37290 2731014
Prcuvu 30377 2170122 csnodgrass 36207 2688994
manapbk 30326 1770143 jmdana 36157 2210661
anst 30301 2190091 strelock 34716 2074055
jkiiski 30136 1904470 rpngn 33951 2057395
hyperbolic.tom 29840 2017394 Garf 33922 2751802
Pyafue 29650 1902349 EthanOConnor 33370 2090311
qurashee 27758 1509620 slakovv 32915 2021889
OuaisBla 27636 1578800 manapbk 30987 1810399
chriswk 26902 1868317 Prcuvu 30377 2170122
achambord 26582 1767323 anst 30301 2190091
Fifis 26376 1776853 jkiiski 30136 1904470
Patrick_G 26276 1801617 hyperbolic.tom 29840 2017394
yorkman 26193 1992080 Pyafue 29650 1902349
SFTUser 25182 1675689 Wolfgang 29260 1658936
nabildanial 24942 1519409 zeryl 28156 1579911
Sharaf_DG 24765 1786697 OuaisBla 27636 1578800
ncfish1 24411 1520927 DMBK 27051 1999456
agg177 23890 1395014 chriswk 26902 1868317
JanErik 23408 1703875 achambord 26582 1767323
Isidor 23388 1680691 Patrick_G 26276 1801617
Norabor 23164 1591830 yorkman 26193 1992080
cisco2015 22895 1762069 SFTUser 25182 1675689
Zirie 22542 1472937 nabildanial 24942 1519409
team-oh 22272 1636708 Sharaf_DG 24765 1786697
MazeOfGalious 21978 1629593 ncfish1 24411 1520927
sg4032 21945 1643065 rodneyc 24227 1409514
ianh2105 21725 1632562 agg177 23890 1395014
xor12 21628 1680365 JanErik 23408 1703875
dex 21612 1467203 Isidor 23388 1680691
nesoneg 21494 1463031 Norabor 23164 1591830
jjoshua2 20997 1422689 cisco2015 22897 1762669
horst.prack 20878 1465656 Zirie 22542 1472937
0xB00B1ES 20590 1208666 team-oh 22272 1636708
sphinx 20515 1352368 MazeOfGalious 21978 1629593
j3corre 20405 941444 sg4032 21947 1643265
Adrian.Schmidt123 20316 1281436 ianh2105 21725 1632562
Ente 20017 1432602 xor12 21628 1680365
wei 19973 1745989 dex 21612 1467203
rstoesser 19569 1293588 nesoneg 21494 1463031
eudhan 19274 1283717 sphinx 21211 1384728
jundery 18445 1115855 jjoshua2 21001 1423089
iisiraider 18247 1101015 horst.prack 20878 1465656
ville 17883 1384026 Ente 20865 1477066
chris 17698 1487385 0xB00B1ES 20590 1208666
purplefishies 17595 1092533 j3corre 20405 941444
DMBK 17357 1279152 Adrian.Schmidt123 20316 1281436
DragonLord 17014 1162790 wei 19973 1745989
dju 16515 929427 MaxKlaxxMiner 19850 1009176
IgorLeMasson 16064 1147232 rstoesser 19569 1293588
ako027ako 15671 1173203 gopeto 19491 1174952
Nikolay.IT 15154 1068349 eudhan 19274 1283717
Andrew Grant 15114 895539 jundery 18445 1115855
OssumOpossum 14857 1007129 megaman7de 18377 1067540
enedene 14476 905279 iisiraider 18247 1101015
bpfliegel 14298 884523 ville 17883 1384026
jpulman 13982 870599 chris 17698 1487385
joster 13794 950160 purplefishies 17595 1092533
Nesa92 13786 1114691 dju 17353 978595
crocogoat 13753 1114622 DragonLord 17014 1162790
Hjax 13535 915487 IgorLeMasson 16064 1147232
Dark_wizzie 13422 1007152 ako027ako 15671 1173203
mpx86 12941 693640 chuckstablers 15289 891576
mabichito 12903 749391 Nikolay.IT 15154 1068349
thijsk 12886 722107 Andrew Grant 15114 895539
AdrianSA 12860 804972 OssumOpossum 14857 1007129
Flopzee 12698 894821 Karby 14808 867120
fatmurphy 12547 853210 enedene 14476 905279
scuzzi 12511 845761 bpfliegel 14298 884523
Karby 12429 735880 mpx86 14019 759568
SapphireBrand 12416 969604 jpulman 13982 870599
modolief 12386 896470 crocogoat 13803 1117422
pgontarz 12151 848794 joster 13794 950160
stocky 11954 699440 Nesa92 13786 1114691
mschmidt 11941 803401 Hjax 13535 915487
infinity 11470 727027 jsys14 13459 785000
torbjo 11395 729145 Dark_wizzie 13422 1007152
Thomas A. Anderson 11372 732094 mabichito 12903 749391
d64 11263 789184 thijsk 12886 722107
Maxim 11129 804704 AdrianSA 12860 804972
snicolet 11106 869170 Flopzee 12698 894821
MooTheCow 11008 694942 fatmurphy 12547 853210
savage84 10965 641068 Rudolphous 12520 832340
Rudolphous 10915 741268 scuzzi 12511 845761
Wolfgang 10809 580032 SapphireBrand 12416 969604
rpngn 10712 688203 modolief 12386 896470
basepi 10637 744851 Machariel 12335 810784
michaelrpg 10409 735127 pgontarz 12151 848794
dzjp 10343 732529 stocky 11954 699440
ali-al-zhrani 10324 726502 mschmidt 11941 803401
ols 10259 570669 Maxim 11543 836024
lbraesch 10252 647825 infinity 11470 727027
torbjo 11395 729145
Thomas A. Anderson 11372 732094
savage84 11358 670860
d64 11263 789184
MooTheCow 11237 720174
snicolet 11106 869170
ali-al-zhrani 11086 767926
AndreasKrug 10875 887457
pirt 10806 836519
basepi 10637 744851
michaelrpg 10508 739039
dzjp 10343 732529
aga 10302 622975
ols 10259 570669
lbraesch 10252 647825
FormazChar 10059 757283
+52 -20
View File
@@ -31,13 +31,17 @@ PREFIX = /usr/local
BINDIR = $(PREFIX)/bin BINDIR = $(PREFIX)/bin
### Built-in benchmark for pgo-builds ### Built-in benchmark for pgo-builds
PGOBENCH = ./$(EXE) bench ifeq ($(SDE_PATH),)
PGOBENCH = ./$(EXE) bench
else
PGOBENCH = $(SDE_PATH) -- ./$(EXE) bench
endif
### Source and object files ### Source and object files
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp cluster.cpp endgame.cpp evaluate.cpp main.cpp \ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp cluster.cpp endgame.cpp evaluate.cpp main.cpp \
material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp
OBJS = $(notdir $(SRCS:.cpp=.o)) OBJS = $(notdir $(SRCS:.cpp=.o))
@@ -57,9 +61,11 @@ endif
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# #
# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode # debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
# sanitize = undefined/thread/no (-fsanitize ) # sanitize = none/<sanitizer> ... (-fsanitize )
# --- ( undefined ) --- enable undefined behavior checks # --- ( undefined ) --- enable undefined behavior checks
# --- ( thread ) --- enable threading error checks # --- ( thread ) --- enable threading error checks
# --- ( address ) --- enable memory access checks
# --- ...etc... --- see compiler documentation for supported sanitizers
# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations # optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations
# arch = (name) --- (-arch) --- Target architecture # arch = (name) --- (-arch) --- Target architecture
# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system # bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system
@@ -81,6 +87,10 @@ endif
# Note that Makefile is space sensitive, so when adding new architectures # Note that Makefile is space sensitive, so when adding new architectures
# or modifying existing flags, you have to make sure there are no extra spaces # or modifying existing flags, you have to make sure there are no extra spaces
# at the end of the line for flag values. # at the end of the line for flag values.
#
# Example of use for these flags:
# make build ARCH=x86-64-avx512 debug=yes sanitize="address undefined"
### 2.1. General and architecture defaults ### 2.1. General and architecture defaults
@@ -93,7 +103,7 @@ endif
ifeq ($(ARCH), $(filter $(ARCH), \ ifeq ($(ARCH), $(filter $(ARCH), \
x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-bmi2 x86-64-avx2 \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-bmi2 x86-64-avx2 \
x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \
armv7 armv7-neon armv8 apple-silicon general-64 general-32)) armv7 armv7-neon armv8 apple-silicon general-64 general-32))
SUPPORTED_ARCH=true SUPPORTED_ARCH=true
else else
@@ -102,7 +112,7 @@ endif
optimize = yes optimize = yes
debug = no debug = no
sanitize = no sanitize = none
bits = 64 bits = 64
prefetch = no prefetch = no
popcnt = no popcnt = no
@@ -289,6 +299,17 @@ ifeq ($(ARCH),ppc-64)
prefetch = yes prefetch = yes
endif endif
ifeq ($(findstring e2k,$(ARCH)),e2k)
arch = e2k
mmx = yes
bits = 64
sse = yes
sse2 = yes
ssse3 = yes
sse41 = yes
popcnt = yes
endif
endif endif
### ========================================================================== ### ==========================================================================
@@ -349,7 +370,7 @@ ifeq ($(COMP),mingw)
CXX=g++ CXX=g++
endif endif
CXXFLAGS += -Wextra -Wshadow CXXFLAGS += -pedantic -Wextra -Wshadow
LDFLAGS += -static LDFLAGS += -static
endif endif
@@ -367,10 +388,12 @@ ifeq ($(COMP),clang)
ifneq ($(KERNEL),Darwin) ifneq ($(KERNEL),Darwin)
ifneq ($(KERNEL),OpenBSD) ifneq ($(KERNEL),OpenBSD)
ifneq ($(KERNEL),FreeBSD) ifneq ($(KERNEL),FreeBSD)
ifneq ($(RTLIB),compiler-rt)
LDFLAGS += -latomic LDFLAGS += -latomic
endif endif
endif endif
endif endif
endif
ifeq ($(arch),$(filter $(arch),armv7 armv8)) ifeq ($(arch),$(filter $(arch),armv7 armv8))
ifeq ($(OS),Android) ifeq ($(OS),Android)
@@ -384,8 +407,12 @@ ifeq ($(COMP),clang)
endif endif
ifeq ($(KERNEL),Darwin) ifeq ($(KERNEL),Darwin)
CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.14 CXXFLAGS += -mmacosx-version-min=10.14
LDFLAGS += -arch $(arch) -mmacosx-version-min=10.14 LDFLAGS += -mmacosx-version-min=10.14
ifneq ($(arch),any)
CXXFLAGS += -arch $(arch)
LDFLAGS += -arch $(arch)
endif
XCRUN = xcrun XCRUN = xcrun
endif endif
@@ -460,9 +487,9 @@ else
endif endif
### 3.2.2 Debugging with undefined behavior sanitizers ### 3.2.2 Debugging with undefined behavior sanitizers
ifneq ($(sanitize),no) ifneq ($(sanitize),none)
CXXFLAGS += -g3 -fsanitize=$(sanitize) CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize))
LDFLAGS += -fsanitize=$(sanitize) LDFLAGS += $(addprefix -fsanitize=,$(sanitize))
endif endif
### 3.3 Optimization ### 3.3 Optimization
@@ -492,7 +519,7 @@ ifeq ($(bits),64)
CXXFLAGS += -DIS_64BIT CXXFLAGS += -DIS_64BIT
endif endif
### 3.5 prefetch ### 3.5 prefetch and popcount
ifeq ($(prefetch),yes) ifeq ($(prefetch),yes)
ifeq ($(sse),yes) ifeq ($(sse),yes)
CXXFLAGS += -msse CXXFLAGS += -msse
@@ -501,7 +528,6 @@ else
CXXFLAGS += -DNO_PREFETCH CXXFLAGS += -DNO_PREFETCH
endif endif
### 3.6 popcnt
ifeq ($(popcnt),yes) ifeq ($(popcnt),yes)
ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64)) ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64))
CXXFLAGS += -DUSE_POPCNT CXXFLAGS += -DUSE_POPCNT
@@ -512,7 +538,7 @@ ifeq ($(popcnt),yes)
endif endif
endif endif
### 3.6 SIMD architectures
ifeq ($(avx2),yes) ifeq ($(avx2),yes)
CXXFLAGS += -DUSE_AVX2 CXXFLAGS += -DUSE_AVX2
ifeq ($(comp),$(filter $(comp),gcc clang mingw)) ifeq ($(comp),$(filter $(comp),gcc clang mingw))
@@ -642,6 +668,8 @@ endif
### 3.10 MPI ### 3.10 MPI
ifneq (,$(findstring mpi, $(CXX))) ifneq (,$(findstring mpi, $(CXX)))
mpi = yes mpi = yes
endif
ifeq ($(mpi),yes)
CXXFLAGS += -DUSE_MPI -Wno-cast-qual -fexceptions CXXFLAGS += -DUSE_MPI -Wno-cast-qual -fexceptions
DEPENDFLAGS += -DUSE_MPI DEPENDFLAGS += -DUSE_MPI
endif endif
@@ -687,6 +715,7 @@ help:
@echo "armv7 > ARMv7 32-bit" @echo "armv7 > ARMv7 32-bit"
@echo "armv7-neon > ARMv7 32-bit with popcnt and neon" @echo "armv7-neon > ARMv7 32-bit with popcnt and neon"
@echo "armv8 > ARMv8 64-bit with popcnt and neon" @echo "armv8 > ARMv8 64-bit with popcnt and neon"
@echo "e2k > Elbrus 2000"
@echo "apple-silicon > Apple silicon ARM64" @echo "apple-silicon > Apple silicon ARM64"
@echo "general-64 > unspecified 64-bit" @echo "general-64 > unspecified 64-bit"
@echo "general-32 > unspecified 32-bit" @echo "general-32 > unspecified 32-bit"
@@ -787,6 +816,9 @@ profileclean:
@rm -rf profdir @rm -rf profdir
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s
@rm -f stockfish.profdata *.profraw @rm -f stockfish.profdata *.profraw
@rm -f stockfish.exe.lto_wrapper_args
@rm -f stockfish.exe.ltrans.out
@rm -f ./-lstdc++.res
default: default:
help help
@@ -830,11 +862,10 @@ config-sanity: net
@echo "Testing config sanity. If this fails, try 'make help' ..." @echo "Testing config sanity. If this fails, try 'make help' ..."
@echo "" @echo ""
@test "$(debug)" = "yes" || test "$(debug)" = "no" @test "$(debug)" = "yes" || test "$(debug)" = "no"
@test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no"
@test "$(optimize)" = "yes" || test "$(optimize)" = "no" @test "$(optimize)" = "yes" || test "$(optimize)" = "no"
@test "$(SUPPORTED_ARCH)" = "true" @test "$(SUPPORTED_ARCH)" = "true"
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \
test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64"
@test "$(bits)" = "32" || test "$(bits)" = "64" @test "$(bits)" = "32" || test "$(bits)" = "64"
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
@@ -870,14 +901,15 @@ clang-profile-use:
all all
gcc-profile-make: gcc-profile-make:
@mkdir -p profdir
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
EXTRACXXFLAGS='-fprofile-generate' \ EXTRACXXFLAGS='-fprofile-generate=profdir' \
EXTRALDFLAGS='-lgcov' \ EXTRALDFLAGS='-lgcov' \
all all
gcc-profile-use: gcc-profile-use:
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
EXTRACXXFLAGS='-fprofile-use -fno-peel-loops -fno-tracer' \ EXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \
EXTRALDFLAGS='-lgcov' \ EXTRALDFLAGS='-lgcov' \
all all
@@ -892,7 +924,7 @@ icc-profile-use:
EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \ EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \
all all
.depend: .depend: $(SRCS)
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
-include .depend -include .depend
+2 -2
View File
@@ -150,8 +150,8 @@ namespace {
Bitboard b = attacks_bb<KING>(ksq[stm]); Bitboard b = attacks_bb<KING>(ksq[stm]);
while (b) while (b)
r |= stm == WHITE ? db[index(BLACK, ksq[BLACK] , pop_lsb(&b), psq)] r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)]
: db[index(WHITE, pop_lsb(&b), ksq[WHITE], psq)]; : db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)];
if (stm == WHITE) if (stm == WHITE)
{ {
+9 -2
View File
@@ -29,6 +29,7 @@ uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
Bitboard SquareBB[SQUARE_NB]; Bitboard SquareBB[SQUARE_NB];
Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB];
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
@@ -107,8 +108,14 @@ void Bitboards::init() {
for (PieceType pt : { BISHOP, ROOK }) for (PieceType pt : { BISHOP, ROOK })
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
{
if (PseudoAttacks[pt][s1] & s2) if (PseudoAttacks[pt][s1] & s2)
LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; {
LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
BetweenBB[s1][s2] = (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
}
BetweenBB[s1][s2] |= s2;
}
} }
} }
@@ -123,7 +130,7 @@ namespace {
for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
{ {
Square s = sq; Square s = sq;
while(safe_destination(s, d) && !(occupied & s)) while (safe_destination(s, d) && !(occupied & s))
attacks |= (s += d); attacks |= (s += d);
} }
+26 -12
View File
@@ -75,6 +75,7 @@ extern uint8_t PopCnt16[1 << 16];
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
extern Bitboard SquareBB[SQUARE_NB]; extern Bitboard SquareBB[SQUARE_NB];
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
@@ -211,23 +212,29 @@ constexpr Bitboard adjacent_files_bb(Square s) {
inline Bitboard line_bb(Square s1, Square s2) { inline Bitboard line_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2)); assert(is_ok(s1) && is_ok(s2));
return LineBB[s1][s2]; return LineBB[s1][s2];
} }
/// between_bb() returns a bitboard representing squares that are linearly /// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open
/// between the two given squares (excluding the given squares). If the given /// segment between the squares s1 and s2 (excluding s1 but including s2). If the
/// squares are not on a same file/rank/diagonal, we return 0. For instance, /// given squares are not on a same file/rank/diagonal, it returns s2. For instance,
/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5 and E6. /// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but
/// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick
/// allows to generate non-king evasion moves faster: the defending piece must either
/// interpose itself to cover the check or capture the checking piece.
inline Bitboard between_bb(Square s1, Square s2) { inline Bitboard between_bb(Square s1, Square s2) {
Bitboard b = line_bb(s1, s2) & ((AllSquares << s1) ^ (AllSquares << s2));
return b & (b - 1); //exclude lsb assert(is_ok(s1) && is_ok(s2));
return BetweenBB[s1][s2];
} }
/// forward_ranks_bb() returns a bitboard representing the squares on the ranks /// forward_ranks_bb() returns a bitboard representing the squares on the ranks in
/// in front of the given one, from the point of view of the given color. For instance, /// front of the given one, from the point of view of the given color. For instance,
/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. /// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2.
constexpr Bitboard forward_ranks_bb(Color c, Square s) { constexpr Bitboard forward_ranks_bb(Color c, Square s) {
@@ -414,13 +421,20 @@ inline Square msb(Bitboard b) {
#endif #endif
/// least_significant_square_bb() returns the bitboard of the least significant
/// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)).
inline Bitboard least_significant_square_bb(Bitboard b) {
assert(b);
return b & -b;
}
/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard /// pop_lsb() finds and clears the least significant bit in a non-zero bitboard
inline Square pop_lsb(Bitboard* b) { inline Square pop_lsb(Bitboard& b) {
assert(*b); assert(b);
const Square s = lsb(*b); const Square s = lsb(b);
*b &= *b - 1; b &= b - 1;
return s; return s;
} }
+2 -2
View File
@@ -143,7 +143,7 @@ bool getline(std::istream& input, std::string& str) {
int size; int size;
std::vector<char> vec; std::vector<char> vec;
bool state; int state;
if (is_root()) if (is_root())
{ {
@@ -177,7 +177,7 @@ bool getline(std::istream& input, std::string& str) {
MPI_Bcast(vec.data(), size, MPI_CHAR, 0, InputComm); MPI_Bcast(vec.data(), size, MPI_CHAR, 0, InputComm);
if (!is_root()) if (!is_root())
str.assign(vec.begin(), vec.end()); str.assign(vec.begin(), vec.end());
MPI_Bcast(&state, 1, MPI_CXX_BOOL, 0, InputComm); MPI_Bcast(&state, 1, MPI_INT, 0, InputComm);
return state; return state;
} }
+110 -83
View File
@@ -34,6 +34,7 @@
#include "misc.h" #include "misc.h"
#include "pawns.h" #include "pawns.h"
#include "thread.h" #include "thread.h"
#include "timeman.h"
#include "uci.h" #include "uci.h"
#include "incbin/incbin.h" #include "incbin/incbin.h"
@@ -55,14 +56,13 @@
using namespace std; using namespace std;
using namespace Stockfish::Eval::NNUE;
namespace Stockfish { namespace Stockfish {
namespace Eval { namespace Eval {
bool useNNUE; bool useNNUE;
string eval_file_loaded = "None"; string currentEvalFileName = "None";
/// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// NNUE::init() tries to load a NNUE network at startup time, or when the engine
/// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
@@ -79,6 +79,8 @@ namespace Eval {
return; return;
string eval_file = string(Options["EvalFile"]); string eval_file = string(Options["EvalFile"]);
if (eval_file.empty())
eval_file = EvalFileDefaultName;
#if defined(DEFAULT_NNUE_DIRECTORY) #if defined(DEFAULT_NNUE_DIRECTORY)
#define stringify2(x) #x #define stringify2(x) #x
@@ -89,13 +91,13 @@ namespace Eval {
#endif #endif
for (string directory : dirs) for (string directory : dirs)
if (eval_file_loaded != eval_file) if (currentEvalFileName != eval_file)
{ {
if (directory != "<internal>") if (directory != "<internal>")
{ {
ifstream stream(directory + eval_file, ios::binary); ifstream stream(directory + eval_file, ios::binary);
if (load_eval(eval_file, stream)) if (load_eval(eval_file, stream))
eval_file_loaded = eval_file; currentEvalFileName = eval_file;
} }
if (directory == "<internal>" && eval_file == EvalFileDefaultName) if (directory == "<internal>" && eval_file == EvalFileDefaultName)
@@ -110,7 +112,7 @@ namespace Eval {
istream stream(&buffer); istream stream(&buffer);
if (load_eval(eval_file, stream)) if (load_eval(eval_file, stream))
eval_file_loaded = eval_file; currentEvalFileName = eval_file;
} }
} }
} }
@@ -119,16 +121,16 @@ namespace Eval {
void NNUE::verify() { void NNUE::verify() {
string eval_file = string(Options["EvalFile"]); string eval_file = string(Options["EvalFile"]);
if (eval_file.empty())
eval_file = EvalFileDefaultName;
if (useNNUE && eval_file_loaded != eval_file) if (useNNUE && currentEvalFileName != eval_file)
{ {
UCI::OptionsMap defaults;
UCI::init(defaults);
string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available.";
string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully.";
string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file.";
string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + string(defaults["EvalFile"]); string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName);
string msg5 = "The engine will be terminated now."; string msg5 = "The engine will be terminated now.";
sync_cout << "info string ERROR: " << msg1 << sync_endl; sync_cout << "info string ERROR: " << msg1 << sync_endl;
@@ -184,7 +186,7 @@ namespace Trace {
else else
os << scores[t][WHITE] << " | " << scores[t][BLACK]; os << scores[t][WHITE] << " | " << scores[t][BLACK];
os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n"; os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n";
return os; return os;
} }
} }
@@ -194,11 +196,9 @@ using namespace Trace;
namespace { namespace {
// Threshold for lazy and space evaluation // Threshold for lazy and space evaluation
constexpr Value LazyThreshold1 = Value(1565); constexpr Value LazyThreshold1 = Value(3130);
constexpr Value LazyThreshold2 = Value(1102); constexpr Value LazyThreshold2 = Value(2204);
constexpr Value SpaceThreshold = Value(11551); constexpr Value SpaceThreshold = Value(11551);
constexpr Value NNUEThreshold1 = Value(682);
constexpr Value NNUEThreshold2 = Value(176);
// KingAttackWeights[PieceType] contains king attack weights by piece type // KingAttackWeights[PieceType] contains king attack weights by piece type
constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 }; constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
@@ -261,11 +261,12 @@ namespace {
S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43) S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43)
}; };
constexpr Value CorneredBishop = Value(50);
// Assorted bonuses and penalties // Assorted bonuses and penalties
constexpr Score UncontestedOutpost = S( 1, 10); constexpr Score UncontestedOutpost = S( 1, 10);
constexpr Score BishopOnKingRing = S( 24, 0); constexpr Score BishopOnKingRing = S( 24, 0);
constexpr Score BishopXRayPawns = S( 4, 5); constexpr Score BishopXRayPawns = S( 4, 5);
constexpr Score CorneredBishop = S( 50, 50);
constexpr Score FlankAttacks = S( 8, 0); constexpr Score FlankAttacks = S( 8, 0);
constexpr Score Hanging = S( 69, 36); constexpr Score Hanging = S( 69, 36);
constexpr Score KnightOnQueen = S( 16, 11); constexpr Score KnightOnQueen = S( 16, 11);
@@ -400,8 +401,9 @@ namespace {
attackedBy[Us][Pt] = 0; attackedBy[Us][Pt] = 0;
while (b1) { while (b1)
Square s = pop_lsb(&b1); {
Square s = pop_lsb(b1);
// Find attacked squares, including x-ray attacks for bishops and rooks // Find attacked squares, including x-ray attacks for bishops and rooks
b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN)) b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
@@ -481,9 +483,8 @@ namespace {
{ {
Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST);
if (pos.piece_on(s + d) == make_piece(Us, PAWN)) if (pos.piece_on(s + d) == make_piece(Us, PAWN))
score -= !pos.empty(s + d + pawn_push(Us)) ? CorneredBishop * 4 score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop)
: pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2 : 3 * make_score(CorneredBishop, CorneredBishop);
: CorneredBishop;
} }
} }
} }
@@ -662,11 +663,11 @@ namespace {
{ {
b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]);
while (b) while (b)
score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(&b)))]; score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))];
b = weak & attackedBy[Us][ROOK]; b = weak & attackedBy[Us][ROOK];
while (b) while (b)
score += ThreatByRook[type_of(pos.piece_on(pop_lsb(&b)))]; score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))];
if (weak & attackedBy[Us][KING]) if (weak & attackedBy[Us][KING])
score += ThreatByKing; score += ThreatByKing;
@@ -764,7 +765,7 @@ namespace {
while (b) while (b)
{ {
Square s = pop_lsb(&b); Square s = pop_lsb(b);
assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
@@ -910,7 +911,7 @@ namespace {
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
int sf = me->scale_factor(pos, strongSide); int sf = me->scale_factor(pos, strongSide);
// If scale factor is not already specific, scale down via general heuristics // If scale factor is not already specific, scale up/down via general heuristics
if (sf == SCALE_FACTOR_NORMAL) if (sf == SCALE_FACTOR_NORMAL)
{ {
if (pos.opposite_bishops()) if (pos.opposite_bishops())
@@ -983,7 +984,7 @@ namespace {
// Initialize score by reading the incrementally updated scores included in // Initialize score by reading the incrementally updated scores included in
// the position object (material + piece square tables) and the material // the position object (material + piece square tables) and the material
// imbalance. Score is computed internally from the white point of view. // imbalance. Score is computed internally from the white point of view.
Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->contempt; Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->trend;
// Probe the pawn hash table // Probe the pawn hash table
pe = Pawns::probe(pos); pe = Pawns::probe(pos);
@@ -991,7 +992,7 @@ namespace {
// Early exit if score is high // Early exit if score is high
auto lazy_skip = [&](Value lazyThreshold) { auto lazy_skip = [&](Value lazyThreshold) {
return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64; return abs(mg_value(score) + eg_value(score)) > lazyThreshold + pos.non_pawn_material() / 32;
}; };
if (lazy_skip(LazyThreshold1)) if (lazy_skip(LazyThreshold1))
@@ -1037,12 +1038,44 @@ make_v:
v = (v / 16) * 16; v = (v / 16) * 16;
// Side to move point of view // Side to move point of view
v = (pos.side_to_move() == WHITE ? v : -v) + Tempo; v = (pos.side_to_move() == WHITE ? v : -v);
return v; return v;
} }
} // namespace
/// Fisher Random Chess: correction for cornered bishops, to fix chess960 play with NNUE
Value fix_FRC(const Position& pos) {
constexpr Bitboard Corners = 1ULL << SQ_A1 | 1ULL << SQ_H1 | 1ULL << SQ_A8 | 1ULL << SQ_H8;
if (!(pos.pieces(BISHOP) & Corners))
return VALUE_ZERO;
int correction = 0;
if ( pos.piece_on(SQ_A1) == W_BISHOP
&& pos.piece_on(SQ_B2) == W_PAWN)
correction -= CorneredBishop;
if ( pos.piece_on(SQ_H1) == W_BISHOP
&& pos.piece_on(SQ_G2) == W_PAWN)
correction -= CorneredBishop;
if ( pos.piece_on(SQ_A8) == B_BISHOP
&& pos.piece_on(SQ_B7) == B_PAWN)
correction += CorneredBishop;
if ( pos.piece_on(SQ_H8) == B_BISHOP
&& pos.piece_on(SQ_G7) == B_PAWN)
correction += CorneredBishop;
return pos.side_to_move() == WHITE ? Value(5 * correction)
: -Value(5 * correction);
}
} // namespace Eval
/// evaluate() is the evaluator for the outer world. It returns a static /// evaluate() is the evaluator for the outer world. It returns a static
@@ -1052,37 +1085,22 @@ Value Eval::evaluate(const Position& pos) {
Value v; Value v;
if (!Eval::useNNUE) // Deciding between classical and NNUE eval: for high PSQ imbalance we use classical,
v = Evaluation<NO_TRACE>(pos).value(); // but we switch to NNUE during long shuffling or with high material on the board.
if ( !useNNUE
|| abs(eg_value(pos.psq_score())) * 5 > (850 + pos.non_pawn_material() / 64) * (5 + pos.rule50_count()))
v = Evaluation<NO_TRACE>(pos).value(); // classical
else else
{ {
// Scale and shift NNUE for compatibility with search and classical evaluation int scale = 883
auto adjusted_NNUE = [&](){ + 32 * pos.count<PAWN>()
int mat = pos.non_pawn_material() + 2 * PawnValueMg * pos.count<PAWN>(); + 32 * pos.non_pawn_material() / 1024;
return NNUE::evaluate(pos) * (641 + mat / 32 - 4 * pos.rule50_count()) / 1024 + Tempo;
};
// If there is PSQ imbalance use classical eval, with small probability if it is small v = NNUE::evaluate(pos, true) * scale / 1024; // NNUE
Value psq = Value(abs(eg_value(pos.psq_score())));
int r50 = 16 + pos.rule50_count();
bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50;
bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB));
// Use classical evaluation for really low piece endgames. if (pos.is_chess960())
// The most critical case is a bishop + A/H file pawn vs naked king draw. v += fix_FRC(pos);
bool strongClassical = pos.non_pawn_material() < 2 * RookValueMg && pos.count<PAWN>() < 2;
v = classical || strongClassical ? Evaluation<NO_TRACE>(pos).value() : adjusted_NNUE();
// If the classical eval is small and imbalance large, use NNUE nevertheless.
// For the case of opposite colored bishops, switch to NNUE eval with
// small probability if the classical eval is less than the threshold.
if ( largePsq && !strongClassical
&& ( abs(v) * 16 < NNUEThreshold2 * r50
|| ( pos.opposite_bishops()
&& abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50
&& !(pos.this_thread()->nodes & 0xB))))
v = adjusted_NNUE();
} }
// Damp down the evaluation linearly when shuffling // Damp down the evaluation linearly when shuffling
@@ -1099,7 +1117,7 @@ Value Eval::evaluate(const Position& pos) {
/// descriptions and values of each evaluation term. Useful for debugging. /// descriptions and values of each evaluation term. Useful for debugging.
/// Trace scores are from white's point of view /// Trace scores are from white's point of view
std::string Eval::trace(const Position& pos) { std::string Eval::trace(Position& pos) {
if (pos.checkers()) if (pos.checkers())
return "Final evaluation: none (in check)"; return "Final evaluation: none (in check)";
@@ -1111,44 +1129,53 @@ std::string Eval::trace(const Position& pos) {
std::memset(scores, 0, sizeof(scores)); std::memset(scores, 0, sizeof(scores));
pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt pos.this_thread()->trend = SCORE_ZERO; // Reset any dynamic contempt
v = Evaluation<TRACE>(pos).value(); v = Evaluation<TRACE>(pos).value();
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
<< " Term | White | Black | Total \n" << " Contributing terms for the classical eval:\n"
<< " | MG EG | MG EG | MG EG \n" << "+------------+-------------+-------------+-------------+\n"
<< " ------------+-------------+-------------+------------\n" << "| Term | White | Black | Total |\n"
<< " Material | " << Term(MATERIAL) << "| | MG EG | MG EG | MG EG |\n"
<< " Imbalance | " << Term(IMBALANCE) << "+------------+-------------+-------------+-------------+\n"
<< " Pawns | " << Term(PAWN) << "| Material | " << Term(MATERIAL)
<< " Knights | " << Term(KNIGHT) << "| Imbalance | " << Term(IMBALANCE)
<< " Bishops | " << Term(BISHOP) << "| Pawns | " << Term(PAWN)
<< " Rooks | " << Term(ROOK) << "| Knights | " << Term(KNIGHT)
<< " Queens | " << Term(QUEEN) << "| Bishops | " << Term(BISHOP)
<< " Mobility | " << Term(MOBILITY) << "| Rooks | " << Term(ROOK)
<< " King safety | " << Term(KING) << "| Queens | " << Term(QUEEN)
<< " Threats | " << Term(THREAT) << "| Mobility | " << Term(MOBILITY)
<< " Passed | " << Term(PASSED) << "|King safety | " << Term(KING)
<< " Space | " << Term(SPACE) << "| Threats | " << Term(THREAT)
<< " Winnable | " << Term(WINNABLE) << "| Passed | " << Term(PASSED)
<< " ------------+-------------+-------------+------------\n" << "| Space | " << Term(SPACE)
<< " Total | " << Term(TOTAL); << "| Winnable | " << Term(WINNABLE)
<< "+------------+-------------+-------------+-------------+\n"
v = pos.side_to_move() == WHITE ? v : -v; << "| Total | " << Term(TOTAL)
<< "+------------+-------------+-------------+-------------+\n";
ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n";
if (Eval::useNNUE)
ss << '\n' << NNUE::trace(pos) << '\n';
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
v = pos.side_to_move() == WHITE ? v : -v;
ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n";
if (Eval::useNNUE) if (Eval::useNNUE)
{ {
v = NNUE::evaluate(pos); v = NNUE::evaluate(pos, false);
v = pos.side_to_move() == WHITE ? v : -v; v = pos.side_to_move() == WHITE ? v : -v;
ss << "\nNNUE evaluation: " << to_cp(v) << " (white side)\n"; ss << "NNUE evaluation " << to_cp(v) << " (white side)\n";
} }
v = evaluate(pos); v = evaluate(pos);
v = pos.side_to_move() == WHITE ? v : -v; v = pos.side_to_move() == WHITE ? v : -v;
ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n"; ss << "Final evaluation " << to_cp(v) << " (white side)";
if (Eval::useNNUE)
ss << " [with scaled NNUE, hybrid, ...]";
ss << "\n";
return ss.str(); return ss.str();
} }
+11 -5
View File
@@ -20,6 +20,7 @@
#define EVALUATE_H_INCLUDED #define EVALUATE_H_INCLUDED
#include <string> #include <string>
#include <optional>
#include "types.h" #include "types.h"
@@ -29,24 +30,29 @@ class Position;
namespace Eval { namespace Eval {
std::string trace(const Position& pos); std::string trace(Position& pos);
Value evaluate(const Position& pos); Value evaluate(const Position& pos);
extern bool useNNUE; extern bool useNNUE;
extern std::string eval_file_loaded; extern std::string currentEvalFileName;
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the // for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile. // name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-62ef826d1a6d.nnue" #define EvalFileDefaultName "nn-13406b1dcbe0.nnue"
namespace NNUE { namespace NNUE {
Value evaluate(const Position& pos); std::string trace(Position& pos);
bool load_eval(std::string name, std::istream& stream); Value evaluate(const Position& pos, bool adjusted = false);
void init(); void init();
void verify(); void verify();
bool load_eval(std::string name, std::istream& stream);
bool save_eval(std::ostream& stream);
bool save_eval(const std::optional<std::string>& filename);
} // namespace NNUE } // namespace NNUE
} // namespace Eval } // namespace Eval
+1 -1
View File
@@ -74,7 +74,7 @@ namespace {
bool is_KBPsK(const Position& pos, Color us) { bool is_KBPsK(const Position& pos, Color us) {
return pos.non_pawn_material(us) == BishopValueMg return pos.non_pawn_material(us) == BishopValueMg
&& pos.count<PAWN >(us) >= 1; && pos.count<PAWN>(us) >= 1;
} }
bool is_KQKRPs(const Position& pos, Color us) { bool is_KQKRPs(const Position& pos, Color us) {
+35 -19
View File
@@ -51,7 +51,7 @@ typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
#include <sys/mman.h> #include <sys/mman.h>
#endif #endif
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) #if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__)
#define POSIXALIGNEDALLOC #define POSIXALIGNEDALLOC
#include <stdlib.h> #include <stdlib.h>
#endif #endif
@@ -67,7 +67,7 @@ namespace {
/// Version number. If Version is left empty, then compile date in the format /// Version number. If Version is left empty, then compile date in the format
/// DD-MM-YY and show in engine_info. /// DD-MM-YY and show in engine_info.
const string Version = ""; const string Version = "14.1";
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
@@ -110,7 +110,14 @@ public:
static Logger l; static Logger l;
if (!fname.empty() && !l.file.is_open()) if (l.file.is_open())
{
cout.rdbuf(l.out.buf);
cin.rdbuf(l.in.buf);
l.file.close();
}
if (!fname.empty())
{ {
l.file.open(fname, ifstream::out); l.file.open(fname, ifstream::out);
@@ -123,12 +130,6 @@ public:
cin.rdbuf(&l.in); cin.rdbuf(&l.in);
cout.rdbuf(&l.out); cout.rdbuf(&l.out);
} }
else if (fname.empty() && l.file.is_open())
{
cout.rdbuf(l.out.buf);
cin.rdbuf(l.in.buf);
l.file.close();
}
} }
}; };
@@ -192,6 +193,18 @@ std::string compiler_info() {
compiler += "(version "; compiler += "(version ";
compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
compiler += ")"; compiler += ")";
#elif defined(__e2k__) && defined(__LCC__)
#define dot_ver2(n) \
compiler += (char)'.'; \
compiler += (char)('0' + (n) / 10); \
compiler += (char)('0' + (n) % 10);
compiler += "MCST LCC ";
compiler += "(version ";
compiler += std::to_string(__LCC__ / 100);
dot_ver2(__LCC__ % 100)
dot_ver2(__LCC_MINOR__)
compiler += ")";
#elif __GNUC__ #elif __GNUC__
compiler += "g++ (GNUC) "; compiler += "g++ (GNUC) ";
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
@@ -362,8 +375,13 @@ void std_aligned_free(void* ptr) {
/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. /// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages.
#if defined(_WIN32) #if defined(_WIN32)
#if defined(_WIN64)
static void* aligned_large_pages_alloc_win(size_t allocSize) { static void* aligned_large_pages_alloc_windows(size_t allocSize) {
#if !defined(_WIN64)
(void)allocSize; // suppress unused-parameter compiler warning
return nullptr;
#else
HANDLE hProcessToken { }; HANDLE hProcessToken { };
LUID luid { }; LUID luid { };
@@ -406,21 +424,18 @@ static void* aligned_large_pages_alloc_win(size_t allocSize) {
CloseHandle(hProcessToken); CloseHandle(hProcessToken);
return mem; return mem;
#endif
} }
#endif
void* aligned_large_pages_alloc(size_t allocSize) { void* aligned_large_pages_alloc(size_t allocSize) {
#if defined(_WIN64)
// Try to allocate large pages // Try to allocate large pages
void* mem = aligned_large_pages_alloc_win(allocSize); void* mem = aligned_large_pages_alloc_windows(allocSize);
// Fall back to regular, page aligned, allocation if necessary // Fall back to regular, page aligned, allocation if necessary
if (!mem) if (!mem)
mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
#else
void* mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
#endif
return mem; return mem;
} }
@@ -456,8 +471,9 @@ void aligned_large_pages_free(void* mem) {
if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
{ {
DWORD err = GetLastError(); DWORD err = GetLastError();
std::cerr << "Failed to free transposition table. Error code: 0x" << std::cerr << "Failed to free large page memory. Error code: 0x"
std::hex << err << std::dec << std::endl; << std::hex << err
<< std::dec << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} }
+63 -3
View File
@@ -66,9 +66,10 @@ std::ostream& operator<<(std::ostream&, SyncCout);
#define sync_cout std::cout << IO_LOCK #define sync_cout std::cout << IO_LOCK
#define sync_endl std::endl << IO_UNLOCK #define sync_endl std::endl << IO_UNLOCK
// `ptr` must point to an array of size at least
// `sizeof(T) * N + alignment` bytes, where `N` is the // align_ptr_up() : get the first aligned element of an array.
// number of elements in the array. // ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes,
// where N is the number of elements in the array.
template <uintptr_t Alignment, typename T> template <uintptr_t Alignment, typename T>
T* align_ptr_up(T* ptr) T* align_ptr_up(T* ptr)
{ {
@@ -78,6 +79,65 @@ T* align_ptr_up(T* ptr)
return reinterpret_cast<T*>(reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment)); return reinterpret_cast<T*>(reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
} }
// IsLittleEndian : true if and only if the binary is compiled on a little endian machine
static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
static inline const bool IsLittleEndian = (Le.c[0] == 4);
// RunningAverage : a class to calculate a running average of a series of values.
// For efficiency, all computations are done with integers.
class RunningAverage {
public:
// Constructor
RunningAverage() {}
// Reset the running average to rational value p / q
void set(int64_t p, int64_t q)
{ average = p * PERIOD * RESOLUTION / q; }
// Update average with value v
void update(int64_t v)
{ average = RESOLUTION * v + (PERIOD - 1) * average / PERIOD; }
// Test if average is strictly greater than rational a / b
bool is_greater(int64_t a, int64_t b)
{ return b * average > a * PERIOD * RESOLUTION ; }
private :
static constexpr int64_t PERIOD = 4096;
static constexpr int64_t RESOLUTION = 1024;
int64_t average;
};
template <typename T, std::size_t MaxSize>
class ValueList {
public:
std::size_t size() const { return size_; }
void resize(std::size_t newSize) { size_ = newSize; }
void push_back(const T& value) { values_[size_++] = value; }
T& operator[](std::size_t index) { return values_[index]; }
T* begin() { return values_; }
T* end() { return values_ + size_; }
const T& operator[](std::size_t index) const { return values_[index]; }
const T* begin() const { return values_; }
const T* end() const { return values_ + size_; }
void swap(ValueList& other) {
const std::size_t maxSize = std::max(size_, other.size_);
for (std::size_t i = 0; i < maxSize; ++i) {
std::swap(values_[i], other.values_[i]);
}
std::swap(size_, other.size_);
}
private:
T values_[MaxSize];
std::size_t size_ = 0;
};
/// xorshift64star Pseudo-Random Number Generator /// xorshift64star Pseudo-Random Number Generator
/// This class is based on original code written and dedicated /// This class is based on original code written and dedicated
/// to the public domain by Sebastiano Vigna (2014). /// to the public domain by Sebastiano Vigna (2014).
+65 -154
View File
@@ -26,21 +26,16 @@ namespace Stockfish {
namespace { namespace {
template<GenType Type, Direction D> template<GenType Type, Direction D>
ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { ExtMove* make_promotions(ExtMove* moveList, Square to) {
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
{
*moveList++ = make<PROMOTION>(to - D, to, QUEEN); *moveList++ = make<PROMOTION>(to - D, to, QUEEN);
if (attacks_bb<KNIGHT>(to) & ksq)
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
}
if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
{ {
*moveList++ = make<PROMOTION>(to - D, to, ROOK); *moveList++ = make<PROMOTION>(to - D, to, ROOK);
*moveList++ = make<PROMOTION>(to - D, to, BISHOP); *moveList++ = make<PROMOTION>(to - D, to, BISHOP);
if (!(attacks_bb<KNIGHT>(to) & ksq)) *moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
} }
return moveList; return moveList;
@@ -57,20 +52,16 @@ namespace {
constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
const Square ksq = pos.square<KING>(Them); const Bitboard emptySquares = ~pos.pieces();
Bitboard emptySquares; const Bitboard enemies = Type == EVASIONS ? pos.checkers()
: pos.pieces(Them);
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target:
Type == CAPTURES ? target : pos.pieces(Them));
// Single and double pawn pushes, no promotions // Single and double pawn pushes, no promotions
if (Type != CAPTURES) if (Type != CAPTURES)
{ {
emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces());
Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares; Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares; Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
@@ -82,33 +73,24 @@ namespace {
if (Type == QUIET_CHECKS) if (Type == QUIET_CHECKS)
{ {
b1 &= pawn_attacks_bb(Them, ksq); // To make a quiet check, you either make a direct check by pushing a pawn
b2 &= pawn_attacks_bb(Them, ksq); // or push a blocker pawn that is not on the same file as the enemy king.
// Discovered check promotion has been already generated amongst the captures.
// Add pawn pushes which give discovered check. This is possible only Square ksq = pos.square<KING>(Them);
// if the pawn is not on the same file as the enemy king, because we Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq);
// don't generate captures. Note that a possible discovered check b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns);
// promotion has been already generated amongst the captures. b2 &= pawn_attacks_bb(Them, ksq) | shift<Up+Up>(dcCandidatePawns);
Bitboard dcCandidateQuiets = pos.blockers_for_king(Them) & pawnsNotOn7;
if (dcCandidateQuiets)
{
Bitboard dc1 = shift<Up>(dcCandidateQuiets) & emptySquares & ~file_bb(ksq);
Bitboard dc2 = shift<Up>(dc1 & TRank3BB) & emptySquares;
b1 |= dc1;
b2 |= dc2;
}
} }
while (b1) while (b1)
{ {
Square to = pop_lsb(&b1); Square to = pop_lsb(b1);
*moveList++ = make_move(to - Up, to); *moveList++ = make_move(to - Up, to);
} }
while (b2) while (b2)
{ {
Square to = pop_lsb(&b2); Square to = pop_lsb(b2);
*moveList++ = make_move(to - Up - Up, to); *moveList++ = make_move(to - Up - Up, to);
} }
} }
@@ -116,24 +98,21 @@ namespace {
// Promotions and underpromotions // Promotions and underpromotions
if (pawnsOn7) if (pawnsOn7)
{ {
if (Type == CAPTURES)
emptySquares = ~pos.pieces();
if (Type == EVASIONS)
emptySquares &= target;
Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies; Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies; Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares; Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
if (Type == EVASIONS)
b3 &= target;
while (b1) while (b1)
moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(&b1), ksq); moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(b1));
while (b2) while (b2)
moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(&b2), ksq); moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(b2));
while (b3) while (b3)
moveList = make_promotions<Type, Up >(moveList, pop_lsb(&b3), ksq); moveList = make_promotions<Type, Up >(moveList, pop_lsb(b3));
} }
// Standard and en passant captures // Standard and en passant captures
@@ -144,13 +123,13 @@ namespace {
while (b1) while (b1)
{ {
Square to = pop_lsb(&b1); Square to = pop_lsb(b1);
*moveList++ = make_move(to - UpRight, to); *moveList++ = make_move(to - UpRight, to);
} }
while (b2) while (b2)
{ {
Square to = pop_lsb(&b2); Square to = pop_lsb(b2);
*moveList++ = make_move(to - UpLeft, to); *moveList++ = make_move(to - UpLeft, to);
} }
@@ -158,7 +137,7 @@ namespace {
{ {
assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6));
// An en passant capture cannot resolve a discovered check. // An en passant capture cannot resolve a discovered check
if (Type == EVASIONS && (target & (pos.ep_square() + Up))) if (Type == EVASIONS && (target & (pos.ep_square() + Up)))
return moveList; return moveList;
@@ -167,7 +146,7 @@ namespace {
assert(b1); assert(b1);
while (b1) while (b1)
*moveList++ = make<EN_PASSANT>(pop_lsb(&b1), pos.ep_square()); *moveList++ = make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
} }
} }
@@ -175,27 +154,24 @@ namespace {
} }
template<PieceType Pt, bool Checks> template<Color Us, PieceType Pt, bool Checks>
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard piecesToMove, Bitboard target) { ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
Bitboard bb = piecesToMove & pos.pieces(Pt); Bitboard bb = pos.pieces(Us, Pt);
if (!bb)
return moveList;
[[maybe_unused]] const Bitboard checkSquares = pos.check_squares(Pt);
while (bb) {
Square from = pop_lsb(&bb);
while (bb)
{
Square from = pop_lsb(bb);
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target; Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
if constexpr (Checks)
b &= checkSquares; // To check, you either move freely a blocker or make a direct check.
if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from)))
b &= pos.check_squares(Pt);
while (b) while (b)
*moveList++ = make_move(from, pop_lsb(&b)); *moveList++ = make_move(from, pop_lsb(b));
} }
return moveList; return moveList;
@@ -208,42 +184,34 @@ namespace {
static_assert(Type != LEGAL, "Unsupported type in generate_all()"); static_assert(Type != LEGAL, "Unsupported type in generate_all()");
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations
Bitboard target, piecesToMove = pos.pieces(Us); const Square ksq = pos.square<KING>(Us);
Bitboard target;
if(Type == QUIET_CHECKS) // Skip generating non-king moves when in double check
piecesToMove &= ~pos.blockers_for_king(~Us); if (Type != EVASIONS || !more_than_one(pos.checkers()))
switch (Type)
{ {
case CAPTURES: target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers()))
target = pos.pieces(~Us); : Type == NON_EVASIONS ? ~pos.pieces( Us)
break; : Type == CAPTURES ? pos.pieces(~Us)
case QUIETS: : ~pos.pieces( ); // QUIETS || QUIET_CHECKS
case QUIET_CHECKS:
target = ~pos.pieces(); moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
break; moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
case EVASIONS: moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
target = between_bb(pos.square<KING>(Us), lsb(pos.checkers())) | pos.checkers(); moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
break; moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
case NON_EVASIONS:
target = ~pos.pieces(Us);
break;
} }
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target); if (!Checks || pos.blockers_for_king(~Us) & ksq)
moveList = generate_moves<KNIGHT, Checks>(pos, moveList, piecesToMove, target);
moveList = generate_moves<BISHOP, Checks>(pos, moveList, piecesToMove, target);
moveList = generate_moves< ROOK, Checks>(pos, moveList, piecesToMove, target);
moveList = generate_moves< QUEEN, Checks>(pos, moveList, piecesToMove, target);
if (Type != QUIET_CHECKS && Type != EVASIONS)
{ {
Square ksq = pos.square<KING>(Us); Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
Bitboard b = attacks_bb<KING>(ksq) & target; if (Checks)
while (b) b &= ~attacks_bb<QUEEN>(pos.square<KING>(~Us));
*moveList++ = make_move(ksq, pop_lsb(&b));
if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING)) while (b)
*moveList++ = make_move(ksq, pop_lsb(b));
if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING))
for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
if (!pos.castling_impeded(cr) && pos.can_castle(cr)) if (!pos.castling_impeded(cr) && pos.can_castle(cr))
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr)); *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
@@ -255,8 +223,10 @@ namespace {
} // namespace } // namespace
/// <CAPTURES> Generates all pseudo-legal captures plus queen and checking knight promotions /// <CAPTURES> Generates all pseudo-legal captures plus queen promotions
/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions (except checking knight) /// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
/// <EVASIONS> Generates all pseudo-legal check evasions when the side to move is in check
/// <QUIET_CHECKS> Generates all pseudo-legal non-captures giving check, except castling and promotions
/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures /// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
/// ///
/// Returns a pointer to the end of the move list. /// Returns a pointer to the end of the move list.
@@ -264,8 +234,8 @@ namespace {
template<GenType Type> template<GenType Type>
ExtMove* generate(const Position& pos, ExtMove* moveList) { ExtMove* generate(const Position& pos, ExtMove* moveList) {
static_assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS, "Unsupported type in generate()"); static_assert(Type != LEGAL, "Unsupported type in generate()");
assert(!pos.checkers()); assert((Type == EVASIONS) == (bool)pos.checkers());
Color us = pos.side_to_move(); Color us = pos.side_to_move();
@@ -276,70 +246,11 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) {
// Explicit template instantiations // Explicit template instantiations
template ExtMove* generate<CAPTURES>(const Position&, ExtMove*); template ExtMove* generate<CAPTURES>(const Position&, ExtMove*);
template ExtMove* generate<QUIETS>(const Position&, ExtMove*); template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
template ExtMove* generate<EVASIONS>(const Position&, ExtMove*);
template ExtMove* generate<QUIET_CHECKS>(const Position&, ExtMove*);
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*); template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
/// generate<QUIET_CHECKS> generates all pseudo-legal non-captures giving check,
/// except castling. Returns a pointer to the end of the move list.
template<>
ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
assert(!pos.checkers());
Color us = pos.side_to_move();
Bitboard dc = pos.blockers_for_king(~us) & pos.pieces(us) & ~pos.pieces(PAWN);
while (dc)
{
Square from = pop_lsb(&dc);
PieceType pt = type_of(pos.piece_on(from));
Bitboard b = attacks_bb(pt, from, pos.pieces()) & ~pos.pieces();
if (pt == KING)
b &= ~attacks_bb<QUEEN>(pos.square<KING>(~us));
while (b)
*moveList++ = make_move(from, pop_lsb(&b));
}
return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList)
: generate_all<BLACK, QUIET_CHECKS>(pos, moveList);
}
/// generate<EVASIONS> generates all pseudo-legal check evasions when the side
/// to move is in check. Returns a pointer to the end of the move list.
template<>
ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
assert(pos.checkers());
Color us = pos.side_to_move();
Square ksq = pos.square<KING>(us);
Bitboard sliderAttacks = 0;
Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN);
// Find all the squares attacked by slider checkers. We will remove them from
// the king evasions in order to skip known illegal moves, which avoids any
// useless legality checks later on.
while (sliders)
sliderAttacks |= line_bb(ksq, pop_lsb(&sliders)) & ~pos.checkers();
// Generate evasions for king, capture and non capture moves
Bitboard b = attacks_bb<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;
while (b)
*moveList++ = make_move(ksq, pop_lsb(&b));
if (more_than_one(pos.checkers()))
return moveList; // Double check, only a king move can save the day
// Generate blocking evasions or captures of the checking piece
return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList)
: generate_all<BLACK, EVASIONS>(pos, moveList);
}
/// generate<LEGAL> generates all the legal moves in the given position /// generate<LEGAL> generates all the legal moves in the given position
template<> template<>
+1 -1
View File
@@ -111,7 +111,7 @@ void MovePicker::score() {
+ (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
+ (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
+ (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)]
+ (ply < MAX_LPH ? std::min(4, depth / 3) * (*lowPlyHistory)[ply][from_to(m)] : 0); + (ply < MAX_LPH ? 6 * (*lowPlyHistory)[ply][from_to(m)] : 0);
else // Type == EVASIONS else // Type == EVASIONS
{ {
+3 -3
View File
@@ -86,11 +86,11 @@ enum StatsType { NoCaptures, Captures };
/// unsuccessful during the current search, and is used for reduction and move /// unsuccessful during the current search, and is used for reduction and move
/// ordering decisions. It uses 2 tables (one for each color) indexed by /// ordering decisions. It uses 2 tables (one for each color) indexed by
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
typedef Stats<int16_t, 13365, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory; typedef Stats<int16_t, 14365, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
/// At higher depths LowPlyHistory records successful quiet moves near the root /// At higher depths LowPlyHistory records successful quiet moves near the root
/// and quiet moves which are/were in the PV (ttPv). It is cleared with each new /// and quiet moves which are/were in the PV (ttPv). LowPlyHistory is populated during
/// search and filled during iterative deepening. /// iterative deepening and at each new search the data is shifted down by 2 plies
constexpr int MAX_LPH = 4; constexpr int MAX_LPH = 4;
typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory; typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory;
@@ -1,54 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Definition of input features and network structure used in NNUE evaluation function
#ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED
#define NNUE_HALFKP_256X2_32_32_H_INCLUDED
#include "../features/feature_set.h"
#include "../features/half_kp.h"
#include "../layers/input_slice.h"
#include "../layers/affine_transform.h"
#include "../layers/clipped_relu.h"
namespace Stockfish::Eval::NNUE {
// Input features used in evaluation function
using RawFeatures = Features::FeatureSet<
Features::HalfKP<Features::Side::kFriend>>;
// Number of input feature dimensions after conversion
constexpr IndexType kTransformedFeatureDimensions = 256;
namespace Layers {
// Define network structure
using InputLayer = InputSlice<kTransformedFeatureDimensions * 2>;
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
using OutputLayer = AffineTransform<HiddenLayer2, 1>;
} // namespace Layers
using Network = Layers::OutputLayer;
} // namespace Stockfish::Eval::NNUE
#endif // #ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED
+308 -37
View File
@@ -20,6 +20,9 @@
#include <iostream> #include <iostream>
#include <set> #include <set>
#include <sstream>
#include <iomanip>
#include <fstream>
#include "../evaluate.h" #include "../evaluate.h"
#include "../position.h" #include "../position.h"
@@ -32,26 +35,27 @@
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
// Input feature converter // Input feature converter
LargePagePtr<FeatureTransformer> feature_transformer; LargePagePtr<FeatureTransformer> featureTransformer;
// Evaluation function // Evaluation function
AlignedPtr<Network> network; AlignedPtr<Network> network[LayerStacks];
// Evaluation function file name // Evaluation function file name
std::string fileName; std::string fileName;
std::string netDescription;
namespace Detail { namespace Detail {
// Initialize the evaluation function parameters // Initialize the evaluation function parameters
template <typename T> template <typename T>
void Initialize(AlignedPtr<T>& pointer) { void initialize(AlignedPtr<T>& pointer) {
pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T)))); pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
std::memset(pointer.get(), 0, sizeof(T)); std::memset(pointer.get(), 0, sizeof(T));
} }
template <typename T> template <typename T>
void Initialize(LargePagePtr<T>& pointer) { void initialize(LargePagePtr<T>& pointer) {
static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T)))); pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
@@ -60,85 +64,352 @@ namespace Stockfish::Eval::NNUE {
// Read evaluation function parameters // Read evaluation function parameters
template <typename T> template <typename T>
bool ReadParameters(std::istream& stream, T& reference) { bool read_parameters(std::istream& stream, T& reference) {
std::uint32_t header; std::uint32_t header;
header = read_little_endian<std::uint32_t>(stream); header = read_little_endian<std::uint32_t>(stream);
if (!stream || header != T::GetHashValue()) return false; if (!stream || header != T::get_hash_value()) return false;
return reference.ReadParameters(stream); return reference.read_parameters(stream);
}
// Write evaluation function parameters
template <typename T>
bool write_parameters(std::ostream& stream, const T& reference) {
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
return reference.write_parameters(stream);
} }
} // namespace Detail } // namespace Detail
// Initialize the evaluation function parameters // Initialize the evaluation function parameters
void Initialize() { void initialize() {
Detail::Initialize(feature_transformer); Detail::initialize(featureTransformer);
Detail::Initialize(network); for (std::size_t i = 0; i < LayerStacks; ++i)
Detail::initialize(network[i]);
} }
// Read network header // Read network header
bool ReadHeader(std::istream& stream, std::uint32_t* hash_value, std::string* architecture) bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc)
{ {
std::uint32_t version, size; std::uint32_t version, size;
version = read_little_endian<std::uint32_t>(stream); version = read_little_endian<std::uint32_t>(stream);
*hash_value = read_little_endian<std::uint32_t>(stream); *hashValue = read_little_endian<std::uint32_t>(stream);
size = read_little_endian<std::uint32_t>(stream); size = read_little_endian<std::uint32_t>(stream);
if (!stream || version != kVersion) return false; if (!stream || version != Version) return false;
architecture->resize(size); desc->resize(size);
stream.read(&(*architecture)[0], size); stream.read(&(*desc)[0], size);
return !stream.fail();
}
// Write network header
bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc)
{
write_little_endian<std::uint32_t>(stream, Version);
write_little_endian<std::uint32_t>(stream, hashValue);
write_little_endian<std::uint32_t>(stream, desc.size());
stream.write(&desc[0], desc.size());
return !stream.fail(); return !stream.fail();
} }
// Read network parameters // Read network parameters
bool ReadParameters(std::istream& stream) { bool read_parameters(std::istream& stream) {
std::uint32_t hash_value; std::uint32_t hashValue;
std::string architecture; if (!read_header(stream, &hashValue, &netDescription)) return false;
if (!ReadHeader(stream, &hash_value, &architecture)) return false; if (hashValue != HashValue) return false;
if (hash_value != kHashValue) return false; if (!Detail::read_parameters(stream, *featureTransformer)) return false;
if (!Detail::ReadParameters(stream, *feature_transformer)) return false; for (std::size_t i = 0; i < LayerStacks; ++i)
if (!Detail::ReadParameters(stream, *network)) return false; if (!Detail::read_parameters(stream, *(network[i]))) return false;
return stream && stream.peek() == std::ios::traits_type::eof(); return stream && stream.peek() == std::ios::traits_type::eof();
} }
// Write network parameters
bool write_parameters(std::ostream& stream) {
if (!write_header(stream, HashValue, netDescription)) return false;
if (!Detail::write_parameters(stream, *featureTransformer)) return false;
for (std::size_t i = 0; i < LayerStacks; ++i)
if (!Detail::write_parameters(stream, *(network[i]))) return false;
return (bool)stream;
}
// Evaluation function. Perform differential calculation. // Evaluation function. Perform differential calculation.
Value evaluate(const Position& pos) { Value evaluate(const Position& pos, bool adjusted) {
// We manually align the arrays on the stack because with gcc < 9.3 // We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly. // overaligning stack variables with alignas() doesn't work correctly.
constexpr uint64_t alignment = kCacheLineSize; constexpr uint64_t alignment = CacheLineSize;
int delta = 7;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType transformed_features_unaligned[ TransformedFeatureType transformedFeaturesUnaligned[
FeatureTransformer::kBufferSize + alignment / sizeof(TransformedFeatureType)]; FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
char buffer_unaligned[Network::kBufferSize + alignment]; char bufferUnaligned[Network::BufferSize + alignment];
auto* transformed_features = align_ptr_up<alignment>(&transformed_features_unaligned[0]); auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
auto* buffer = align_ptr_up<alignment>(&buffer_unaligned[0]); auto* buffer = align_ptr_up<alignment>(&bufferUnaligned[0]);
#else #else
alignas(alignment) alignas(alignment)
TransformedFeatureType transformed_features[FeatureTransformer::kBufferSize]; TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
alignas(alignment) char buffer[Network::kBufferSize]; alignas(alignment) char buffer[Network::BufferSize];
#endif #endif
ASSERT_ALIGNED(transformed_features, alignment); ASSERT_ALIGNED(transformedFeatures, alignment);
ASSERT_ALIGNED(buffer, alignment); ASSERT_ALIGNED(buffer, alignment);
feature_transformer->Transform(pos, transformed_features); const std::size_t bucket = (pos.count<ALL_PIECES>() - 1) / 4;
const auto output = network->Propagate(transformed_features, buffer); const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket);
const auto positional = network[bucket]->propagate(transformedFeatures, buffer)[0];
return static_cast<Value>(output[0] / FV_SCALE); // Give more value to positional evaluation when material is balanced
if ( adjusted
&& abs(pos.non_pawn_material(WHITE) - pos.non_pawn_material(BLACK)) <= RookValueMg - BishopValueMg)
return static_cast<Value>(((128 - delta) * psqt + (128 + delta) * positional) / 128 / OutputScale);
else
return static_cast<Value>((psqt + positional) / OutputScale);
} }
struct NnueEvalTrace {
static_assert(LayerStacks == PSQTBuckets);
Value psqt[LayerStacks];
Value positional[LayerStacks];
std::size_t correctBucket;
};
static NnueEvalTrace trace_evaluate(const Position& pos) {
// We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly.
constexpr uint64_t alignment = CacheLineSize;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType transformedFeaturesUnaligned[
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
char bufferUnaligned[Network::BufferSize + alignment];
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
auto* buffer = align_ptr_up<alignment>(&bufferUnaligned[0]);
#else
alignas(alignment)
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
alignas(alignment) char buffer[Network::BufferSize];
#endif
ASSERT_ALIGNED(transformedFeatures, alignment);
ASSERT_ALIGNED(buffer, alignment);
NnueEvalTrace t{};
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) {
const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket);
const auto output = network[bucket]->propagate(transformedFeatures, buffer);
int materialist = psqt;
int positional = output[0];
t.psqt[bucket] = static_cast<Value>( materialist / OutputScale );
t.positional[bucket] = static_cast<Value>( positional / OutputScale );
}
return t;
}
static const std::string PieceToChar(" PNBRQK pnbrqk");
// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer.
// The buffer must have capacity for at least 5 chars.
static void format_cp_compact(Value v, char* buffer) {
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
int cp = std::abs(100 * v / PawnValueEg);
if (cp >= 10000)
{
buffer[1] = '0' + cp / 10000; cp %= 10000;
buffer[2] = '0' + cp / 1000; cp %= 1000;
buffer[3] = '0' + cp / 100; cp %= 100;
buffer[4] = ' ';
}
else if (cp >= 1000)
{
buffer[1] = '0' + cp / 1000; cp %= 1000;
buffer[2] = '0' + cp / 100; cp %= 100;
buffer[3] = '.';
buffer[4] = '0' + cp / 10;
}
else
{
buffer[1] = '0' + cp / 100; cp %= 100;
buffer[2] = '.';
buffer[3] = '0' + cp / 10; cp %= 10;
buffer[4] = '0' + cp / 1;
}
}
// format_cp_aligned_dot() converts a Value into (centi)pawns and writes it in a buffer,
// always keeping two decimals. The buffer must have capacity for at least 7 chars.
static void format_cp_aligned_dot(Value v, char* buffer) {
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
double cp = 1.0 * std::abs(int(v)) / PawnValueEg;
sprintf(&buffer[1], "%6.2f", cp);
}
// trace() returns a string with the value of each piece on a board,
// and a table for (PSQT, Layers) values bucket by bucket.
std::string trace(Position& pos) {
std::stringstream ss;
char board[3*8+1][8*8+2];
std::memset(board, ' ', sizeof(board));
for (int row = 0; row < 3*8+1; ++row)
board[row][8*8+1] = '\0';
// A lambda to output one box of the board
auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) {
const int x = ((int)file) * 8;
const int y = (7 - (int)rank) * 3;
for (int i = 1; i < 8; ++i)
board[y][x+i] = board[y+3][x+i] = '-';
for (int i = 1; i < 3; ++i)
board[y+i][x] = board[y+i][x+8] = '|';
board[y][x] = board[y][x+8] = board[y+3][x+8] = board[y+3][x] = '+';
if (pc != NO_PIECE)
board[y+1][x+4] = PieceToChar[pc];
if (value != VALUE_NONE)
format_cp_compact(value, &board[y+2][x+2]);
};
// We estimate the value of each piece by doing a differential evaluation from
// the current base eval, simulating the removal of the piece from its square.
Value base = evaluate(pos);
base = pos.side_to_move() == WHITE ? base : -base;
for (File f = FILE_A; f <= FILE_H; ++f)
for (Rank r = RANK_1; r <= RANK_8; ++r)
{
Square sq = make_square(f, r);
Piece pc = pos.piece_on(sq);
Value v = VALUE_NONE;
if (pc != NO_PIECE && type_of(pc) != KING)
{
auto st = pos.state();
pos.remove_piece(sq);
st->accumulator.computed[WHITE] = false;
st->accumulator.computed[BLACK] = false;
Value eval = evaluate(pos);
eval = pos.side_to_move() == WHITE ? eval : -eval;
v = base - eval;
pos.put_piece(pc, sq);
st->accumulator.computed[WHITE] = false;
st->accumulator.computed[BLACK] = false;
}
writeSquare(f, r, pc, v);
}
ss << " NNUE derived piece values:\n";
for (int row = 0; row < 3*8+1; ++row)
ss << board[row] << '\n';
ss << '\n';
auto t = trace_evaluate(pos);
ss << " NNUE network contributions "
<< (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl
<< "+------------+------------+------------+------------+\n"
<< "| Bucket | Material | Positional | Total |\n"
<< "| | (PSQT) | (Layers) | |\n"
<< "+------------+------------+------------+------------+\n";
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket)
{
char buffer[3][8];
std::memset(buffer, '\0', sizeof(buffer));
format_cp_aligned_dot(t.psqt[bucket], buffer[0]);
format_cp_aligned_dot(t.positional[bucket], buffer[1]);
format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], buffer[2]);
ss << "| " << bucket << " "
<< " | " << buffer[0] << " "
<< " | " << buffer[1] << " "
<< " | " << buffer[2] << " "
<< " |";
if (bucket == t.correctBucket)
ss << " <-- this bucket is used";
ss << '\n';
}
ss << "+------------+------------+------------+------------+\n";
return ss.str();
}
// Load eval, from a file stream or a memory stream // Load eval, from a file stream or a memory stream
bool load_eval(std::string name, std::istream& stream) { bool load_eval(std::string name, std::istream& stream) {
Initialize(); initialize();
fileName = name; fileName = name;
return ReadParameters(stream); return read_parameters(stream);
} }
// Save eval, to a file stream or a memory stream
bool save_eval(std::ostream& stream) {
if (fileName.empty())
return false;
return write_parameters(stream);
}
/// Save eval, to a file given by its name
bool save_eval(const std::optional<std::string>& filename) {
std::string actualFilename;
std::string msg;
if (filename.has_value())
actualFilename = filename.value();
else
{
if (currentEvalFileName != EvalFileDefaultName)
{
msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified";
sync_cout << msg << sync_endl;
return false;
}
actualFilename = EvalFileDefaultName;
}
std::ofstream stream(actualFilename, std::ios_base::binary);
bool saved = save_eval(stream);
msg = saved ? "Network saved successfully to " + actualFilename
: "Failed to export a net";
sync_cout << msg << sync_endl;
return saved;
}
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
+2 -2
View File
@@ -28,8 +28,8 @@
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
// Hash value of evaluation function structure // Hash value of evaluation function structure
constexpr std::uint32_t kHashValue = constexpr std::uint32_t HashValue =
FeatureTransformer::GetHashValue() ^ Network::GetHashValue(); FeatureTransformer::get_hash_value() ^ Network::get_hash_value();
// Deleter for automating release of memory area // Deleter for automating release of memory area
template <typename T> template <typename T>
-69
View File
@@ -1,69 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// A class template that represents the input feature set of the NNUE evaluation function
#ifndef NNUE_FEATURE_SET_H_INCLUDED
#define NNUE_FEATURE_SET_H_INCLUDED
#include "features_common.h"
#include <array>
namespace Stockfish::Eval::NNUE::Features {
// Class template that represents a list of values
template <typename T, T... Values>
struct CompileTimeList;
template <typename T, T First, T... Remaining>
struct CompileTimeList<T, First, Remaining...> {
static constexpr bool Contains(T value) {
return value == First || CompileTimeList<T, Remaining...>::Contains(value);
}
static constexpr std::array<T, sizeof...(Remaining) + 1>
kValues = {{First, Remaining...}};
};
// Base class of feature set
template <typename Derived>
class FeatureSetBase {
};
// Class template that represents the feature set
template <typename FeatureType>
class FeatureSet<FeatureType> : public FeatureSetBase<FeatureSet<FeatureType>> {
public:
// Hash value embedded in the evaluation file
static constexpr std::uint32_t kHashValue = FeatureType::kHashValue;
// Number of feature dimensions
static constexpr IndexType kDimensions = FeatureType::kDimensions;
// Maximum number of simultaneously active features
static constexpr IndexType kMaxActiveDimensions =
FeatureType::kMaxActiveDimensions;
// Trigger for full calculation instead of difference calculation
using SortedTriggerSet =
CompileTimeList<TriggerEvent, FeatureType::kRefreshTrigger>;
static constexpr auto kRefreshTriggers = SortedTriggerSet::kValues;
};
} // namespace Stockfish::Eval::NNUE::Features
#endif // #ifndef NNUE_FEATURE_SET_H_INCLUDED
-45
View File
@@ -1,45 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//Common header of input features of NNUE evaluation function
#ifndef NNUE_FEATURES_COMMON_H_INCLUDED
#define NNUE_FEATURES_COMMON_H_INCLUDED
#include "../../evaluate.h"
#include "../nnue_common.h"
namespace Stockfish::Eval::NNUE::Features {
class IndexList;
template <typename... FeatureTypes>
class FeatureSet;
// Trigger to perform full calculations instead of difference only
enum class TriggerEvent {
kFriendKingMoved // calculate full evaluation when own king moves
};
enum class Side {
kFriend // side to move
};
} // namespace Stockfish::Eval::NNUE::Features
#endif // #ifndef NNUE_FEATURES_COMMON_H_INCLUDED
+83
View File
@@ -0,0 +1,83 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//Definition of input features HalfKAv2_hm of NNUE evaluation function
#include "half_ka_v2_hm.h"
#include "../../position.h"
namespace Stockfish::Eval::NNUE::Features {
// Orient a square according to perspective (rotates by 180 for black)
inline Square HalfKAv2_hm::orient(Color perspective, Square s, Square ksq) {
return Square(int(s) ^ (bool(perspective) * SQ_A8) ^ ((file_of(ksq) < FILE_E) * SQ_H1));
}
// Index of a feature for a given king position and another piece on some square
inline IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) {
Square o_ksq = orient(perspective, ksq, ksq);
return IndexType(orient(perspective, s, ksq) + PieceSquareIndex[perspective][pc] + PS_NB * KingBuckets[o_ksq]);
}
// Get a list of indices for active features
void HalfKAv2_hm::append_active_indices(
const Position& pos,
Color perspective,
IndexList& active
) {
Square ksq = pos.square<KING>(perspective);
Bitboard bb = pos.pieces();
while (bb)
{
Square s = pop_lsb(bb);
active.push_back(make_index(perspective, s, pos.piece_on(s), ksq));
}
}
// append_changed_indices() : get a list of indices for recently changed features
void HalfKAv2_hm::append_changed_indices(
Square ksq,
const DirtyPiece& dp,
Color perspective,
IndexList& removed,
IndexList& added
) {
for (int i = 0; i < dp.dirty_num; ++i) {
if (dp.from[i] != SQ_NONE)
removed.push_back(make_index(perspective, dp.from[i], dp.piece[i], ksq));
if (dp.to[i] != SQ_NONE)
added.push_back(make_index(perspective, dp.to[i], dp.piece[i], ksq));
}
}
int HalfKAv2_hm::update_cost(const StateInfo* st) {
return st->dirtyPiece.dirty_num;
}
int HalfKAv2_hm::refresh_cost(const Position& pos) {
return pos.count<ALL_PIECES>();
}
bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
}
} // namespace Stockfish::Eval::NNUE::Features
+124
View File
@@ -0,0 +1,124 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//Definition of input features HalfKP of NNUE evaluation function
#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
#include "../nnue_common.h"
#include "../../evaluate.h"
#include "../../misc.h"
namespace Stockfish {
struct StateInfo;
}
namespace Stockfish::Eval::NNUE::Features {
// Feature HalfKAv2_hm: Combination of the position of own king
// and the position of pieces. Position mirrored such that king always on e..h files.
class HalfKAv2_hm {
// unique number for each piece type on each square
enum {
PS_NONE = 0,
PS_W_PAWN = 0,
PS_B_PAWN = 1 * SQUARE_NB,
PS_W_KNIGHT = 2 * SQUARE_NB,
PS_B_KNIGHT = 3 * SQUARE_NB,
PS_W_BISHOP = 4 * SQUARE_NB,
PS_B_BISHOP = 5 * SQUARE_NB,
PS_W_ROOK = 6 * SQUARE_NB,
PS_B_ROOK = 7 * SQUARE_NB,
PS_W_QUEEN = 8 * SQUARE_NB,
PS_B_QUEEN = 9 * SQUARE_NB,
PS_KING = 10 * SQUARE_NB,
PS_NB = 11 * SQUARE_NB
};
static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
// convention: W - us, B - them
// viewed from other side, W and B are reversed
{ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE },
{ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE }
};
// Orient a square according to perspective (rotates by 180 for black)
static Square orient(Color perspective, Square s, Square ksq);
// Index of a feature for a given king position and another piece on some square
static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq);
public:
// Feature name
static constexpr const char* Name = "HalfKAv2_hm(Friend)";
// Hash value embedded in the evaluation file
static constexpr std::uint32_t HashValue = 0x7f234cb8u;
// Number of feature dimensions
static constexpr IndexType Dimensions =
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
static constexpr int KingBuckets[64] = {
-1, -1, -1, -1, 31, 30, 29, 28,
-1, -1, -1, -1, 27, 26, 25, 24,
-1, -1, -1, -1, 23, 22, 21, 20,
-1, -1, -1, -1, 19, 18, 17, 16,
-1, -1, -1, -1, 15, 14, 13, 12,
-1, -1, -1, -1, 11, 10, 9, 8,
-1, -1, -1, -1, 7, 6, 5, 4,
-1, -1, -1, -1, 3, 2, 1, 0
};
// Maximum number of simultaneously active features.
static constexpr IndexType MaxActiveDimensions = 32;
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
// Get a list of indices for active features
static void append_active_indices(
const Position& pos,
Color perspective,
IndexList& active);
// Get a list of indices for recently changed features
static void append_changed_indices(
Square ksq,
const DirtyPiece& dp,
Color perspective,
IndexList& removed,
IndexList& added
);
// Returns the cost of updating one perspective, the most costly one.
// Assumes no refresh needed.
static int update_cost(const StateInfo* st);
static int refresh_cost(const Position& pos);
// Returns whether the change stored in this StateInfo means that
// a full accumulator refresh is required.
static bool requires_refresh(const StateInfo* st, Color perspective);
};
} // namespace Stockfish::Eval::NNUE::Features
#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
-68
View File
@@ -1,68 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//Definition of input features HalfKP of NNUE evaluation function
#include "half_kp.h"
#include "index_list.h"
namespace Stockfish::Eval::NNUE::Features {
// Orient a square according to perspective (rotates by 180 for black)
inline Square orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63));
}
// Index of a feature for a given king position and another piece on some square
inline IndexType make_index(Color perspective, Square s, Piece pc, Square ksq) {
return IndexType(orient(perspective, s) + kpp_board_index[perspective][pc] + PS_END * ksq);
}
// Get a list of indices for active features
template <Side AssociatedKing>
void HalfKP<AssociatedKing>::AppendActiveIndices(
const Position& pos, Color perspective, IndexList* active) {
Square ksq = orient(perspective, pos.square<KING>(perspective));
Bitboard bb = pos.pieces() & ~pos.pieces(KING);
while (bb) {
Square s = pop_lsb(&bb);
active->push_back(make_index(perspective, s, pos.piece_on(s), ksq));
}
}
// Get a list of indices for recently changed features
template <Side AssociatedKing>
void HalfKP<AssociatedKing>::AppendChangedIndices(
const Position& pos, const DirtyPiece& dp, Color perspective,
IndexList* removed, IndexList* added) {
Square ksq = orient(perspective, pos.square<KING>(perspective));
for (int i = 0; i < dp.dirty_num; ++i) {
Piece pc = dp.piece[i];
if (type_of(pc) == KING) continue;
if (dp.from[i] != SQ_NONE)
removed->push_back(make_index(perspective, dp.from[i], pc, ksq));
if (dp.to[i] != SQ_NONE)
added->push_back(make_index(perspective, dp.to[i], pc, ksq));
}
}
template class HalfKP<Side::kFriend>;
} // namespace Stockfish::Eval::NNUE::Features
-59
View File
@@ -1,59 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//Definition of input features HalfKP of NNUE evaluation function
#ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED
#define NNUE_FEATURES_HALF_KP_H_INCLUDED
#include "../../evaluate.h"
#include "features_common.h"
namespace Stockfish::Eval::NNUE::Features {
// Feature HalfKP: Combination of the position of own king
// and the position of pieces other than kings
template <Side AssociatedKing>
class HalfKP {
public:
// Feature name
static constexpr const char* kName = "HalfKP(Friend)";
// Hash value embedded in the evaluation file
static constexpr std::uint32_t kHashValue =
0x5D69D5B9u ^ (AssociatedKing == Side::kFriend);
// Number of feature dimensions
static constexpr IndexType kDimensions =
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_END);
// Maximum number of simultaneously active features
static constexpr IndexType kMaxActiveDimensions = 30; // Kings don't count
// Trigger for full calculation instead of difference calculation
static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kFriendKingMoved;
// Get a list of indices for active features
static void AppendActiveIndices(const Position& pos, Color perspective,
IndexList* active);
// Get a list of indices for recently changed features
static void AppendChangedIndices(const Position& pos, const DirtyPiece& dp, Color perspective,
IndexList* removed, IndexList* added);
};
} // namespace Stockfish::Eval::NNUE::Features
#endif // #ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED
-64
View File
@@ -1,64 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Definition of index list of input features
#ifndef NNUE_FEATURES_INDEX_LIST_H_INCLUDED
#define NNUE_FEATURES_INDEX_LIST_H_INCLUDED
#include "../../position.h"
#include "../nnue_architecture.h"
namespace Stockfish::Eval::NNUE::Features {
// Class template used for feature index list
template <typename T, std::size_t MaxSize>
class ValueList {
public:
std::size_t size() const { return size_; }
void resize(std::size_t size) { size_ = size; }
void push_back(const T& value) { values_[size_++] = value; }
T& operator[](std::size_t index) { return values_[index]; }
T* begin() { return values_; }
T* end() { return values_ + size_; }
const T& operator[](std::size_t index) const { return values_[index]; }
const T* begin() const { return values_; }
const T* end() const { return values_ + size_; }
void swap(ValueList& other) {
const std::size_t max_size = std::max(size_, other.size_);
for (std::size_t i = 0; i < max_size; ++i) {
std::swap(values_[i], other.values_[i]);
}
std::swap(size_, other.size_);
}
private:
T values_[MaxSize];
std::size_t size_ = 0;
};
//Type of feature index list
class IndexList
: public ValueList<IndexType, RawFeatures::kMaxActiveDimensions> {
};
} // namespace Stockfish::Eval::NNUE::Features
#endif // NNUE_FEATURES_INDEX_LIST_H_INCLUDED
+454 -364
View File
@@ -22,13 +22,141 @@
#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED #define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
#include <iostream> #include <iostream>
#include <algorithm>
#include <type_traits>
#include "../nnue_common.h" #include "../nnue_common.h"
#include "../../simd.h"
/*
This file contains the definition for a fully connected layer (aka affine transform).
Two approaches are employed, depending on the sizes of the transform.
Approach 1:
- used when the PaddedInputDimensions >= 128
- uses AVX512 if possible
- processes inputs in batches of 2*InputSimdWidth
- so in batches of 128 for AVX512
- the weight blocks of size InputSimdWidth are transposed such that
access is sequential
- N columns of the weight matrix are processed a time, where N
depends on the architecture (the amount of registers)
- accumulate + hadd is used
Approach 2:
- used when the PaddedInputDimensions < 128
- does not use AVX512
- expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32.
- that's why AVX512 is hard to implement
- expected use-case is small layers
- not optimized as well as the approach 1
- inputs are processed in chunks of 4, weights are respectively transposed
- accumulation happens directly to int32s
*/
namespace Stockfish::Eval::NNUE::Layers { namespace Stockfish::Eval::NNUE::Layers {
// Affine transformation layer // Fallback implementation for older/other architectures.
template <typename PreviousLayer, IndexType OutputDimensions> // Identical for both approaches. Requires the input to be padded to at least 16 values.
class AffineTransform { #if !defined(USE_SSSE3)
template <IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input)
{
# if defined(USE_SSE2)
// At least a multiple of 16, with SSE2.
static_assert(PaddedInputDimensions % 16 == 0);
constexpr IndexType NumChunks = PaddedInputDimensions / 16;
const __m128i Zeros = _mm_setzero_si128();
const auto inputVector = reinterpret_cast<const __m128i*>(input);
# elif defined(USE_MMX)
static_assert(InputDimensions % 8 == 0);
constexpr IndexType NumChunks = InputDimensions / 8;
const __m64 Zeros = _mm_setzero_si64();
const auto inputVector = reinterpret_cast<const __m64*>(input);
# elif defined(USE_NEON)
static_assert(PaddedInputDimensions % 16 == 0);
constexpr IndexType NumChunks = PaddedInputDimensions / 16;
const auto inputVector = reinterpret_cast<const int8x8_t*>(input);
# endif
for (IndexType i = 0; i < OutputDimensions; ++i) {
const IndexType offset = i * PaddedInputDimensions;
# if defined(USE_SSE2)
__m128i sumLo = _mm_cvtsi32_si128(biases[i]);
__m128i sumHi = Zeros;
const auto row = reinterpret_cast<const __m128i*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j) {
__m128i row_j = _mm_load_si128(&row[j]);
__m128i input_j = _mm_load_si128(&inputVector[j]);
__m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);
__m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8);
__m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros);
__m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros);
__m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo);
__m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi);
sumLo = _mm_add_epi32(sumLo, productLo);
sumHi = _mm_add_epi32(sumHi, productHi);
}
__m128i sum = _mm_add_epi32(sumLo, sumHi);
__m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2));
sum = _mm_add_epi32(sum, sumHigh_64);
__m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2));
sum = _mm_add_epi32(sum, sum_second_32);
output[i] = _mm_cvtsi128_si32(sum);
# elif defined(USE_MMX)
__m64 sumLo = _mm_cvtsi32_si64(biases[i]);
__m64 sumHi = Zeros;
const auto row = reinterpret_cast<const __m64*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j) {
__m64 row_j = row[j];
__m64 input_j = inputVector[j];
__m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8);
__m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8);
__m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros);
__m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros);
__m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo);
__m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi);
sumLo = _mm_add_pi32(sumLo, productLo);
sumHi = _mm_add_pi32(sumHi, productHi);
}
__m64 sum = _mm_add_pi32(sumLo, sumHi);
sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum));
output[i] = _mm_cvtsi64_si32(sum);
# elif defined(USE_NEON)
int32x4_t sum = {biases[i]};
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j) {
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
sum = vpadalq_s16(sum, product);
}
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
# else
std::int32_t sum = biases[i];
for (IndexType j = 0; j < InputDimensions; ++j) {
sum += weights[offset + j] * input[j];
}
output[i] = sum;
# endif
}
# if defined(USE_MMX)
_mm_empty();
# endif
}
#endif
template <typename PreviousLayer, IndexType OutDims, typename Enabled = void>
class AffineTransform;
// A specialization for large inputs.
template <typename PreviousLayer, IndexType OutDims>
class AffineTransform<PreviousLayer, OutDims, std::enable_if_t<(PreviousLayer::OutputDimensions >= 2*64-1)>> {
public: public:
// Input/output type // Input/output type
using InputType = typename PreviousLayer::OutputType; using InputType = typename PreviousLayer::OutputType;
@@ -36,396 +164,197 @@ namespace Stockfish::Eval::NNUE::Layers {
static_assert(std::is_same<InputType, std::uint8_t>::value, ""); static_assert(std::is_same<InputType, std::uint8_t>::value, "");
// Number of input/output dimensions // Number of input/output dimensions
static constexpr IndexType kInputDimensions = static constexpr IndexType InputDimensions = PreviousLayer::OutputDimensions;
PreviousLayer::kOutputDimensions; static constexpr IndexType OutputDimensions = OutDims;
static constexpr IndexType kOutputDimensions = OutputDimensions;
static constexpr IndexType kPaddedInputDimensions = static constexpr IndexType PaddedInputDimensions =
CeilToMultiple<IndexType>(kInputDimensions, kMaxSimdWidth); ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
static_assert(PaddedInputDimensions >= 128, "Something went wrong. This specialization should not have been chosen.");
#if defined (USE_AVX512) #if defined (USE_AVX512)
static constexpr const IndexType kOutputSimdWidth = kSimdWidth / 2; static constexpr const IndexType InputSimdWidth = 64;
static constexpr const IndexType MaxNumOutputRegs = 16;
#elif defined (USE_AVX2)
static constexpr const IndexType InputSimdWidth = 32;
static constexpr const IndexType MaxNumOutputRegs = 8;
#elif defined (USE_SSSE3) #elif defined (USE_SSSE3)
static constexpr const IndexType kOutputSimdWidth = kSimdWidth / 4; static constexpr const IndexType InputSimdWidth = 16;
static constexpr const IndexType MaxNumOutputRegs = 8;
#else
// The fallback implementation will not have permuted weights.
// We define these to avoid a lot of ifdefs later.
static constexpr const IndexType InputSimdWidth = 1;
static constexpr const IndexType MaxNumOutputRegs = 1;
#endif #endif
// A big block is a region in the weight matrix of the size [PaddedInputDimensions, NumOutputRegs].
// A small block is a region of size [InputSimdWidth, 1]
static constexpr const IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions);
static constexpr const IndexType SmallBlockSize = InputSimdWidth;
static constexpr const IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions;
static constexpr const IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize;
static constexpr const IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize;
static constexpr const IndexType NumBigBlocks = OutputDimensions / NumOutputRegs;
static_assert(OutputDimensions % NumOutputRegs == 0);
// Size of forward propagation buffer used in this layer // Size of forward propagation buffer used in this layer
static constexpr std::size_t kSelfBufferSize = static constexpr std::size_t SelfBufferSize =
CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize); ceil_to_multiple(OutputDimensions * sizeof(OutputType), CacheLineSize);
// Size of the forward propagation buffer used from the input layer to this layer // Size of the forward propagation buffer used from the input layer to this layer
static constexpr std::size_t kBufferSize = static constexpr std::size_t BufferSize =
PreviousLayer::kBufferSize + kSelfBufferSize; PreviousLayer::BufferSize + SelfBufferSize;
// Hash value embedded in the evaluation file // Hash value embedded in the evaluation file
static constexpr std::uint32_t GetHashValue() { static constexpr std::uint32_t get_hash_value() {
std::uint32_t hash_value = 0xCC03DAE4u; std::uint32_t hashValue = 0xCC03DAE4u;
hash_value += kOutputDimensions; hashValue += OutputDimensions;
hash_value ^= PreviousLayer::GetHashValue() >> 1; hashValue ^= PreviousLayer::get_hash_value() >> 1;
hash_value ^= PreviousLayer::GetHashValue() << 31; hashValue ^= PreviousLayer::get_hash_value() << 31;
return hash_value; return hashValue;
} }
// Read network parameters /*
bool ReadParameters(std::istream& stream) { Transposes the small blocks within a block.
if (!previous_layer_.ReadParameters(stream)) return false; Effectively means that weights can be traversed sequentially during inference.
for (std::size_t i = 0; i < kOutputDimensions; ++i) */
biases_[i] = read_little_endian<BiasType>(stream); static IndexType get_weight_index(IndexType i)
for (std::size_t i = 0; i < kOutputDimensions * kPaddedInputDimensions; ++i) {
#if !defined (USE_SSSE3) const IndexType smallBlock = (i / SmallBlockSize) % NumSmallBlocksInBigBlock;
weights_[i] = read_little_endian<WeightType>(stream); const IndexType smallBlockCol = smallBlock / NumSmallBlocksPerOutput;
#else const IndexType smallBlockRow = smallBlock % NumSmallBlocksPerOutput;
weights_[ const IndexType bigBlock = i / BigBlockSize;
(i / 4) % (kPaddedInputDimensions / 4) * kOutputDimensions * 4 + const IndexType rest = i % SmallBlockSize;
i / kPaddedInputDimensions * 4 +
i % 4
] = read_little_endian<WeightType>(stream);
// Determine if eights of weight and input products can be summed using 16bits const IndexType idx =
// without saturation. We assume worst case combinations of 0 and 127 for all inputs. bigBlock * BigBlockSize
if (kOutputDimensions > 1 && !stream.fail()) + smallBlockRow * SmallBlockSize * NumOutputRegs
{ + smallBlockCol * SmallBlockSize
canSaturate16.count = 0; + rest;
#if !defined(USE_VNNI)
for (IndexType i = 0; i < kPaddedInputDimensions; i += 16)
for (IndexType j = 0; j < kOutputDimensions; ++j)
for (int x = 0; x < 2; ++x)
{
WeightType* w = &weights_[i * kOutputDimensions + j * 4 + x * 2];
int sum[2] = {0, 0};
for (int k = 0; k < 8; ++k)
{
IndexType idx = k / 2 * kOutputDimensions * 4 + k % 2;
sum[w[idx] < 0] += w[idx];
}
for (int sign : {-1, 1})
while (sign * sum[sign == -1] > 258)
{
int maxK = 0, maxW = 0;
for (int k = 0; k < 8; ++k)
{
IndexType idx = k / 2 * kOutputDimensions * 4 + k % 2;
if (maxW < sign * w[idx])
maxK = k, maxW = sign * w[idx];
}
IndexType idx = maxK / 2 * kOutputDimensions * 4 + maxK % 2; return idx;
sum[sign == -1] -= w[idx]; }
canSaturate16.add(j, i + maxK / 2 * 4 + maxK % 2 + x * 2, w[idx]);
w[idx] = 0;
}
}
// Non functional optimization for faster more linear access // Read network parameters
std::sort(canSaturate16.ids, canSaturate16.ids + canSaturate16.count, bool read_parameters(std::istream& stream) {
[](const typename CanSaturate::Entry& e1, const typename CanSaturate::Entry& e2) if (!previousLayer.read_parameters(stream)) return false;
{ return e1.in == e2.in ? e1.out < e2.out : e1.in < e2.in; }); for (std::size_t i = 0; i < OutputDimensions; ++i)
#endif biases[i] = read_little_endian<BiasType>(stream);
}
#endif for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
return !stream.fail();
}
// Write network parameters
bool write_parameters(std::ostream& stream) const {
if (!previousLayer.write_parameters(stream)) return false;
for (std::size_t i = 0; i < OutputDimensions; ++i)
write_little_endian<BiasType>(stream, biases[i]);
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
return !stream.fail(); return !stream.fail();
} }
// Forward propagation // Forward propagation
const OutputType* Propagate( const OutputType* propagate(
const TransformedFeatureType* transformed_features, char* buffer) const { const TransformedFeatureType* transformedFeatures, char* buffer) const {
const auto input = previous_layer_.Propagate( const auto input = previousLayer.propagate(
transformed_features, buffer + kSelfBufferSize); transformedFeatures, buffer + SelfBufferSize);
OutputType* output = reinterpret_cast<OutputType*>(buffer);
#if defined (USE_AVX512)
[[maybe_unused]] const __m512i kOnes512 = _mm512_set1_epi16(1);
[[maybe_unused]] auto m512_hadd = [](__m512i sum, int bias) -> int {
return _mm512_reduce_add_epi32(sum) + bias;
};
[[maybe_unused]] auto m512_add_dpbusd_epi32 = [=](__m512i& acc, __m512i a, __m512i b) {
#if defined (USE_VNNI)
acc = _mm512_dpbusd_epi32(acc, a, b);
#else
__m512i product0 = _mm512_maddubs_epi16(a, b);
product0 = _mm512_madd_epi16(product0, kOnes512);
acc = _mm512_add_epi32(acc, product0);
#endif
};
[[maybe_unused]] auto m512_add_dpbusd_epi32x4 = [=](__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1,
__m512i a2, __m512i b2, __m512i a3, __m512i b3) {
#if defined (USE_VNNI)
acc = _mm512_dpbusd_epi32(acc, a0, b0);
acc = _mm512_dpbusd_epi32(acc, a1, b1);
acc = _mm512_dpbusd_epi32(acc, a2, b2);
acc = _mm512_dpbusd_epi32(acc, a3, b3);
#else
__m512i product0 = _mm512_maddubs_epi16(a0, b0);
__m512i product1 = _mm512_maddubs_epi16(a1, b1);
__m512i product2 = _mm512_maddubs_epi16(a2, b2);
__m512i product3 = _mm512_maddubs_epi16(a3, b3);
product0 = _mm512_add_epi16(product0, product1);
product2 = _mm512_add_epi16(product2, product3);
product0 = _mm512_add_epi16(product0, product2);
product0 = _mm512_madd_epi16(product0, kOnes512);
acc = _mm512_add_epi32(acc, product0);
#endif
};
#endif
#if defined (USE_AVX2)
[[maybe_unused]] const __m256i kOnes256 = _mm256_set1_epi16(1);
[[maybe_unused]] auto m256_hadd = [](__m256i sum, int bias) -> int {
__m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
return _mm_cvtsi128_si32(sum128) + bias;
};
[[maybe_unused]] auto m256_add_dpbusd_epi32 = [=](__m256i& acc, __m256i a, __m256i b) {
#if defined (USE_VNNI)
acc = _mm256_dpbusd_epi32(acc, a, b);
#else
__m256i product0 = _mm256_maddubs_epi16(a, b);
product0 = _mm256_madd_epi16(product0, kOnes256);
acc = _mm256_add_epi32(acc, product0);
#endif
};
[[maybe_unused]] auto m256_add_dpbusd_epi32x4 = [=](__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1,
__m256i a2, __m256i b2, __m256i a3, __m256i b3) {
#if defined (USE_VNNI)
acc = _mm256_dpbusd_epi32(acc, a0, b0);
acc = _mm256_dpbusd_epi32(acc, a1, b1);
acc = _mm256_dpbusd_epi32(acc, a2, b2);
acc = _mm256_dpbusd_epi32(acc, a3, b3);
#else
__m256i product0 = _mm256_maddubs_epi16(a0, b0);
__m256i product1 = _mm256_maddubs_epi16(a1, b1);
__m256i product2 = _mm256_maddubs_epi16(a2, b2);
__m256i product3 = _mm256_maddubs_epi16(a3, b3);
product0 = _mm256_add_epi16(product0, product1);
product2 = _mm256_add_epi16(product2, product3);
product0 = _mm256_add_epi16(product0, product2);
product0 = _mm256_madd_epi16(product0, kOnes256);
acc = _mm256_add_epi32(acc, product0);
#endif
};
#endif
#if defined (USE_SSSE3)
[[maybe_unused]] const __m128i kOnes128 = _mm_set1_epi16(1);
[[maybe_unused]] auto m128_hadd = [](__m128i sum, int bias) -> int {
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
return _mm_cvtsi128_si32(sum) + bias;
};
[[maybe_unused]] auto m128_add_dpbusd_epi32 = [=](__m128i& acc, __m128i a, __m128i b) {
__m128i product0 = _mm_maddubs_epi16(a, b);
product0 = _mm_madd_epi16(product0, kOnes128);
acc = _mm_add_epi32(acc, product0);
};
[[maybe_unused]] auto m128_add_dpbusd_epi32x4 = [=](__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1,
__m128i a2, __m128i b2, __m128i a3, __m128i b3) {
__m128i product0 = _mm_maddubs_epi16(a0, b0);
__m128i product1 = _mm_maddubs_epi16(a1, b1);
__m128i product2 = _mm_maddubs_epi16(a2, b2);
__m128i product3 = _mm_maddubs_epi16(a3, b3);
product0 = _mm_adds_epi16(product0, product1);
product2 = _mm_adds_epi16(product2, product3);
product0 = _mm_adds_epi16(product0, product2);
product0 = _mm_madd_epi16(product0, kOnes128);
acc = _mm_add_epi32(acc, product0);
};
#endif
#if defined (USE_AVX512) #if defined (USE_AVX512)
using vec_t = __m512i; using vec_t = __m512i;
#define vec_setzero _mm512_setzero_si512 #define vec_setzero _mm512_setzero_si512
#define vec_set_32 _mm512_set1_epi32 #define vec_set_32 _mm512_set1_epi32
auto& vec_add_dpbusd_32 = m512_add_dpbusd_epi32; #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
auto& vec_add_dpbusd_32x4 = m512_add_dpbusd_epi32x4; #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2
auto& vec_hadd = m512_hadd; #define vec_hadd Simd::m512_hadd
#define vec_haddx4 Simd::m512_haddx4
#elif defined (USE_AVX2) #elif defined (USE_AVX2)
using vec_t = __m256i; using vec_t = __m256i;
#define vec_setzero _mm256_setzero_si256 #define vec_setzero _mm256_setzero_si256
#define vec_set_32 _mm256_set1_epi32 #define vec_set_32 _mm256_set1_epi32
auto& vec_add_dpbusd_32 = m256_add_dpbusd_epi32; #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
auto& vec_add_dpbusd_32x4 = m256_add_dpbusd_epi32x4; #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
auto& vec_hadd = m256_hadd; #define vec_hadd Simd::m256_hadd
#define vec_haddx4 Simd::m256_haddx4
#elif defined (USE_SSSE3) #elif defined (USE_SSSE3)
using vec_t = __m128i; using vec_t = __m128i;
#define vec_setzero _mm_setzero_si128 #define vec_setzero _mm_setzero_si128
#define vec_set_32 _mm_set1_epi32 #define vec_set_32 _mm_set1_epi32
auto& vec_add_dpbusd_32 = m128_add_dpbusd_epi32; #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
auto& vec_add_dpbusd_32x4 = m128_add_dpbusd_epi32x4; #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
auto& vec_hadd = m128_hadd; #define vec_hadd Simd::m128_hadd
#define vec_haddx4 Simd::m128_haddx4
#endif #endif
#if defined (USE_SSSE3) #if defined (USE_SSSE3)
const vec_t* invec = reinterpret_cast<const vec_t*>(input);
const auto output = reinterpret_cast<OutputType*>(buffer);
const auto input_vector = reinterpret_cast<const vec_t*>(input);
static_assert(kOutputDimensions % kOutputSimdWidth == 0 || kOutputDimensions == 1); // Perform accumulation to registers for each big block
for (IndexType bigBlock = 0; bigBlock < NumBigBlocks; ++bigBlock)
// kOutputDimensions is either 1 or a multiple of kSimdWidth
// because then it is also an input dimension.
if constexpr (kOutputDimensions % kOutputSimdWidth == 0)
{ {
constexpr IndexType kNumChunks = kPaddedInputDimensions / 4; vec_t acc[NumOutputRegs] = { vec_setzero() };
const auto input32 = reinterpret_cast<const std::int32_t*>(input); // Each big block has NumOutputRegs small blocks in each "row", one per register.
vec_t* outptr = reinterpret_cast<vec_t*>(output); // We process two small blocks at a time to save on one addition without VNNI.
std::memcpy(output, biases_, kOutputDimensions * sizeof(OutputType)); for (IndexType smallBlock = 0; smallBlock < NumSmallBlocksPerOutput; smallBlock += 2)
{
const vec_t* weightvec =
reinterpret_cast<const vec_t*>(
weights
+ bigBlock * BigBlockSize
+ smallBlock * SmallBlockSize * NumOutputRegs);
for (int i = 0; i < (int)kNumChunks - 3; i += 4) const vec_t in0 = invec[smallBlock + 0];
const vec_t in1 = invec[smallBlock + 1];
for (IndexType k = 0; k < NumOutputRegs; ++k)
vec_add_dpbusd_32x2(acc[k], in0, weightvec[k], in1, weightvec[k + NumOutputRegs]);
}
// Horizontally add all accumulators.
if constexpr (NumOutputRegs % 4 == 0)
{
__m128i* outputvec = reinterpret_cast<__m128i*>(output);
const __m128i* biasvec = reinterpret_cast<const __m128i*>(biases);
for (IndexType k = 0; k < NumOutputRegs; k += 4)
{ {
const vec_t in0 = vec_set_32(input32[i + 0]); const IndexType idx = (bigBlock * NumOutputRegs + k) / 4;
const vec_t in1 = vec_set_32(input32[i + 1]); outputvec[idx] = vec_haddx4(acc[k+0], acc[k+1], acc[k+2], acc[k+3], biasvec[idx]);
const vec_t in2 = vec_set_32(input32[i + 2]);
const vec_t in3 = vec_set_32(input32[i + 3]);
const auto col0 = reinterpret_cast<const vec_t*>(&weights_[(i + 0) * kOutputDimensions * 4]);
const auto col1 = reinterpret_cast<const vec_t*>(&weights_[(i + 1) * kOutputDimensions * 4]);
const auto col2 = reinterpret_cast<const vec_t*>(&weights_[(i + 2) * kOutputDimensions * 4]);
const auto col3 = reinterpret_cast<const vec_t*>(&weights_[(i + 3) * kOutputDimensions * 4]);
for (int j = 0; j * kOutputSimdWidth < kOutputDimensions; ++j)
vec_add_dpbusd_32x4(outptr[j], in0, col0[j], in1, col1[j], in2, col2[j], in3, col3[j]);
} }
for (int i = 0; i < canSaturate16.count; ++i) }
output[canSaturate16.ids[i].out] += input[canSaturate16.ids[i].in] * canSaturate16.ids[i].w; else
} {
else if constexpr (kOutputDimensions == 1) for (IndexType k = 0; k < NumOutputRegs; ++k)
{
#if defined (USE_AVX512)
if constexpr (kPaddedInputDimensions % (kSimdWidth * 2) != 0)
{ {
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; const IndexType idx = (bigBlock * NumOutputRegs + k);
const auto input_vector256 = reinterpret_cast<const __m256i*>(input); output[idx] = vec_hadd(acc[k], biases[idx]);
__m256i sum0 = _mm256_setzero_si256();
const auto row0 = reinterpret_cast<const __m256i*>(&weights_[0]);
for (int j = 0; j < (int)kNumChunks; ++j)
{
const __m256i in = input_vector256[j];
m256_add_dpbusd_epi32(sum0, in, row0[j]);
}
output[0] = m256_hadd(sum0, biases_[0]);
}
else
#endif
{
#if defined (USE_AVX512)
constexpr IndexType kNumChunks = kPaddedInputDimensions / (kSimdWidth * 2);
#else
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
#endif
vec_t sum0 = vec_setzero();
const auto row0 = reinterpret_cast<const vec_t*>(&weights_[0]);
for (int j = 0; j < (int)kNumChunks; ++j)
{
const vec_t in = input_vector[j];
vec_add_dpbusd_32(sum0, in, row0[j]);
}
output[0] = vec_hadd(sum0, biases_[0]);
} }
}
} }
# undef vec_setzero
# undef vec_set_32
# undef vec_add_dpbusd_32
# undef vec_add_dpbusd_32x2
# undef vec_hadd
# undef vec_haddx4
#else #else
// Use old implementation for the other architectures.
// Use old implementation for the other architectures. affine_transform_non_ssse3<
InputDimensions,
auto output = reinterpret_cast<OutputType*>(buffer); PaddedInputDimensions,
OutputDimensions>(output, weights, biases, input);
#if defined(USE_SSE2)
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
const __m128i kZeros = _mm_setzero_si128();
const auto input_vector = reinterpret_cast<const __m128i*>(input);
#elif defined(USE_MMX)
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
const __m64 kZeros = _mm_setzero_si64();
const auto input_vector = reinterpret_cast<const __m64*>(input);
#elif defined(USE_NEON)
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
const auto input_vector = reinterpret_cast<const int8x8_t*>(input);
#endif
for (IndexType i = 0; i < kOutputDimensions; ++i) {
const IndexType offset = i * kPaddedInputDimensions;
#if defined(USE_SSE2)
__m128i sum_lo = _mm_cvtsi32_si128(biases_[i]);
__m128i sum_hi = kZeros;
const auto row = reinterpret_cast<const __m128i*>(&weights_[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
__m128i row_j = _mm_load_si128(&row[j]);
__m128i input_j = _mm_load_si128(&input_vector[j]);
__m128i extended_row_lo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);
__m128i extended_row_hi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8);
__m128i extended_input_lo = _mm_unpacklo_epi8(input_j, kZeros);
__m128i extended_input_hi = _mm_unpackhi_epi8(input_j, kZeros);
__m128i product_lo = _mm_madd_epi16(extended_row_lo, extended_input_lo);
__m128i product_hi = _mm_madd_epi16(extended_row_hi, extended_input_hi);
sum_lo = _mm_add_epi32(sum_lo, product_lo);
sum_hi = _mm_add_epi32(sum_hi, product_hi);
}
__m128i sum = _mm_add_epi32(sum_lo, sum_hi);
__m128i sum_high_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2));
sum = _mm_add_epi32(sum, sum_high_64);
__m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2));
sum = _mm_add_epi32(sum, sum_second_32);
output[i] = _mm_cvtsi128_si32(sum);
#elif defined(USE_MMX)
__m64 sum_lo = _mm_cvtsi32_si64(biases_[i]);
__m64 sum_hi = kZeros;
const auto row = reinterpret_cast<const __m64*>(&weights_[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
__m64 row_j = row[j];
__m64 input_j = input_vector[j];
__m64 extended_row_lo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8);
__m64 extended_row_hi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8);
__m64 extended_input_lo = _mm_unpacklo_pi8(input_j, kZeros);
__m64 extended_input_hi = _mm_unpackhi_pi8(input_j, kZeros);
__m64 product_lo = _mm_madd_pi16(extended_row_lo, extended_input_lo);
__m64 product_hi = _mm_madd_pi16(extended_row_hi, extended_input_hi);
sum_lo = _mm_add_pi32(sum_lo, product_lo);
sum_hi = _mm_add_pi32(sum_hi, product_hi);
}
__m64 sum = _mm_add_pi32(sum_lo, sum_hi);
sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum));
output[i] = _mm_cvtsi64_si32(sum);
#elif defined(USE_NEON)
int32x4_t sum = {biases_[i]};
const auto row = reinterpret_cast<const int8x8_t*>(&weights_[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
int16x8_t product = vmull_s8(input_vector[j * 2], row[j * 2]);
product = vmlal_s8(product, input_vector[j * 2 + 1], row[j * 2 + 1]);
sum = vpadalq_s16(sum, product);
}
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
#else
OutputType sum = biases_[i];
for (IndexType j = 0; j < kInputDimensions; ++j) {
sum += weights_[offset + j] * input[j];
}
output[i] = sum;
#endif
}
#if defined(USE_MMX)
_mm_empty();
#endif
#endif #endif
@@ -436,27 +365,188 @@ namespace Stockfish::Eval::NNUE::Layers {
using BiasType = OutputType; using BiasType = OutputType;
using WeightType = std::int8_t; using WeightType = std::int8_t;
PreviousLayer previous_layer_; PreviousLayer previousLayer;
alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
};
template <typename PreviousLayer, IndexType OutDims>
class AffineTransform<PreviousLayer, OutDims, std::enable_if_t<(PreviousLayer::OutputDimensions < 2*64-1)>> {
public:
// Input/output type
using InputType = typename PreviousLayer::OutputType;
using OutputType = std::int32_t;
static_assert(std::is_same<InputType, std::uint8_t>::value, "");
// Number of input/output dimensions
static constexpr IndexType InputDimensions =
PreviousLayer::OutputDimensions;
static constexpr IndexType OutputDimensions = OutDims;
static constexpr IndexType PaddedInputDimensions =
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
static_assert(PaddedInputDimensions < 128, "Something went wrong. This specialization should not have been chosen.");
alignas(kCacheLineSize) BiasType biases_[kOutputDimensions];
alignas(kCacheLineSize) WeightType weights_[kOutputDimensions * kPaddedInputDimensions];
#if defined (USE_SSSE3) #if defined (USE_SSSE3)
struct CanSaturate { static constexpr const IndexType OutputSimdWidth = SimdWidth / 4;
int count; static constexpr const IndexType InputSimdWidth = SimdWidth;
struct Entry {
uint16_t out;
uint16_t in;
int8_t w;
} ids[kPaddedInputDimensions * kOutputDimensions * 3 / 4];
void add(int i, int j, int8_t w) {
ids[count].out = i;
ids[count].in = j;
ids[count].w = w;
++count;
}
} canSaturate16;
#endif #endif
// Size of forward propagation buffer used in this layer
static constexpr std::size_t SelfBufferSize =
ceil_to_multiple(OutputDimensions * sizeof(OutputType), CacheLineSize);
// Size of the forward propagation buffer used from the input layer to this layer
static constexpr std::size_t BufferSize =
PreviousLayer::BufferSize + SelfBufferSize;
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value() {
std::uint32_t hashValue = 0xCC03DAE4u;
hashValue += OutputDimensions;
hashValue ^= PreviousLayer::get_hash_value() >> 1;
hashValue ^= PreviousLayer::get_hash_value() << 31;
return hashValue;
}
static IndexType get_weight_index_scrambled(IndexType i)
{
return
(i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 +
i / PaddedInputDimensions * 4 +
i % 4;
}
static IndexType get_weight_index(IndexType i)
{
#if defined (USE_SSSE3)
return get_weight_index_scrambled(i);
#else
return i;
#endif
}
// Read network parameters
bool read_parameters(std::istream& stream) {
if (!previousLayer.read_parameters(stream)) return false;
for (std::size_t i = 0; i < OutputDimensions; ++i)
biases[i] = read_little_endian<BiasType>(stream);
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
return !stream.fail();
}
// Write network parameters
bool write_parameters(std::ostream& stream) const {
if (!previousLayer.write_parameters(stream)) return false;
for (std::size_t i = 0; i < OutputDimensions; ++i)
write_little_endian<BiasType>(stream, biases[i]);
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
return !stream.fail();
}
// Forward propagation
const OutputType* propagate(
const TransformedFeatureType* transformedFeatures, char* buffer) const {
const auto input = previousLayer.propagate(
transformedFeatures, buffer + SelfBufferSize);
const auto output = reinterpret_cast<OutputType*>(buffer);
#if defined (USE_AVX2)
using vec_t = __m256i;
#define vec_setzero _mm256_setzero_si256
#define vec_set_32 _mm256_set1_epi32
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
#define vec_add_dpbusd_32x4 Simd::m256_add_dpbusd_epi32x4
#define vec_hadd Simd::m256_hadd
#define vec_haddx4 Simd::m256_haddx4
#elif defined (USE_SSSE3)
using vec_t = __m128i;
#define vec_setzero _mm_setzero_si128
#define vec_set_32 _mm_set1_epi32
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
#define vec_add_dpbusd_32x4 Simd::m128_add_dpbusd_epi32x4
#define vec_hadd Simd::m128_hadd
#define vec_haddx4 Simd::m128_haddx4
#endif
#if defined (USE_SSSE3)
const auto inputVector = reinterpret_cast<const vec_t*>(input);
static_assert(InputDimensions % 8 == 0);
static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1);
if constexpr (OutputDimensions % OutputSimdWidth == 0)
{
constexpr IndexType NumChunks = InputDimensions / 4;
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
const vec_t* biasvec = reinterpret_cast<const vec_t*>(biases);
vec_t acc[NumRegs];
for (IndexType k = 0; k < NumRegs; ++k)
acc[k] = biasvec[k];
for (IndexType i = 0; i < NumChunks; i += 2)
{
const vec_t in0 = vec_set_32(input32[i + 0]);
const vec_t in1 = vec_set_32(input32[i + 1]);
const auto col0 = reinterpret_cast<const vec_t*>(&weights[(i + 0) * OutputDimensions * 4]);
const auto col1 = reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
for (IndexType k = 0; k < NumRegs; ++k)
vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]);
}
vec_t* outptr = reinterpret_cast<vec_t*>(output);
for (IndexType k = 0; k < NumRegs; ++k)
outptr[k] = acc[k];
}
else if constexpr (OutputDimensions == 1)
{
constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth;
vec_t sum0 = vec_setzero();
const auto row0 = reinterpret_cast<const vec_t*>(&weights[0]);
for (int j = 0; j < (int)NumChunks; ++j)
{
const vec_t in = inputVector[j];
vec_add_dpbusd_32(sum0, in, row0[j]);
}
output[0] = vec_hadd(sum0, biases[0]);
}
# undef vec_setzero
# undef vec_set_32
# undef vec_add_dpbusd_32
# undef vec_add_dpbusd_32x2
# undef vec_add_dpbusd_32x4
# undef vec_hadd
# undef vec_haddx4
#else
// Use old implementation for the other architectures.
affine_transform_non_ssse3<
InputDimensions,
PaddedInputDimensions,
OutputDimensions>(output, weights, biases, input);
#endif
return output;
}
private:
using BiasType = OutputType;
using WeightType = std::int8_t;
PreviousLayer previousLayer;
alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
}; };
} // namespace Stockfish::Eval::NNUE::Layers } // namespace Stockfish::Eval::NNUE::Layers
+90 -55
View File
@@ -35,130 +35,165 @@ namespace Stockfish::Eval::NNUE::Layers {
static_assert(std::is_same<InputType, std::int32_t>::value, ""); static_assert(std::is_same<InputType, std::int32_t>::value, "");
// Number of input/output dimensions // Number of input/output dimensions
static constexpr IndexType kInputDimensions = static constexpr IndexType InputDimensions = PreviousLayer::OutputDimensions;
PreviousLayer::kOutputDimensions; static constexpr IndexType OutputDimensions = InputDimensions;
static constexpr IndexType kOutputDimensions = kInputDimensions; static constexpr IndexType PaddedOutputDimensions =
ceil_to_multiple<IndexType>(OutputDimensions, 32);
// Size of forward propagation buffer used in this layer // Size of forward propagation buffer used in this layer
static constexpr std::size_t kSelfBufferSize = static constexpr std::size_t SelfBufferSize =
CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize); ceil_to_multiple(OutputDimensions * sizeof(OutputType), CacheLineSize);
// Size of the forward propagation buffer used from the input layer to this layer // Size of the forward propagation buffer used from the input layer to this layer
static constexpr std::size_t kBufferSize = static constexpr std::size_t BufferSize =
PreviousLayer::kBufferSize + kSelfBufferSize; PreviousLayer::BufferSize + SelfBufferSize;
// Hash value embedded in the evaluation file // Hash value embedded in the evaluation file
static constexpr std::uint32_t GetHashValue() { static constexpr std::uint32_t get_hash_value() {
std::uint32_t hash_value = 0x538D24C7u; std::uint32_t hashValue = 0x538D24C7u;
hash_value += PreviousLayer::GetHashValue(); hashValue += PreviousLayer::get_hash_value();
return hash_value; return hashValue;
} }
// Read network parameters // Read network parameters
bool ReadParameters(std::istream& stream) { bool read_parameters(std::istream& stream) {
return previous_layer_.ReadParameters(stream); return previousLayer.read_parameters(stream);
}
// Write network parameters
bool write_parameters(std::ostream& stream) const {
return previousLayer.write_parameters(stream);
} }
// Forward propagation // Forward propagation
const OutputType* Propagate( const OutputType* propagate(
const TransformedFeatureType* transformed_features, char* buffer) const { const TransformedFeatureType* transformedFeatures, char* buffer) const {
const auto input = previous_layer_.Propagate( const auto input = previousLayer.propagate(
transformed_features, buffer + kSelfBufferSize); transformedFeatures, buffer + SelfBufferSize);
const auto output = reinterpret_cast<OutputType*>(buffer); const auto output = reinterpret_cast<OutputType*>(buffer);
#if defined(USE_AVX2) #if defined(USE_AVX2)
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth; if constexpr (InputDimensions % SimdWidth == 0) {
const __m256i kZero = _mm256_setzero_si256(); constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m256i kOffsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); const __m256i Zero = _mm256_setzero_si256();
const auto in = reinterpret_cast<const __m256i*>(input); const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
const auto out = reinterpret_cast<__m256i*>(output); const auto in = reinterpret_cast<const __m256i*>(input);
for (IndexType i = 0; i < kNumChunks; ++i) { const auto out = reinterpret_cast<__m256i*>(output);
const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32( for (IndexType i = 0; i < NumChunks; ++i) {
_mm256_load_si256(&in[i * 4 + 0]), const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
_mm256_load_si256(&in[i * 4 + 1])), kWeightScaleBits); _mm256_load_si256(&in[i * 4 + 0]),
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32( _mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits);
_mm256_load_si256(&in[i * 4 + 2]), const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
_mm256_load_si256(&in[i * 4 + 3])), kWeightScaleBits); _mm256_load_si256(&in[i * 4 + 2]),
_mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( _mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits);
_mm256_packs_epi16(words0, words1), kZero), kOffsets)); _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
_mm256_packs_epi16(words0, words1), Zero), Offsets));
}
} else {
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
const __m128i Zero = _mm_setzero_si128();
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < NumChunks; ++i) {
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 0]),
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 2]),
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero));
}
} }
constexpr IndexType kStart = kNumChunks * kSimdWidth; constexpr IndexType Start =
InputDimensions % SimdWidth == 0
? InputDimensions / SimdWidth * SimdWidth
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
#elif defined(USE_SSE2) #elif defined(USE_SSE2)
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth; constexpr IndexType NumChunks = InputDimensions / SimdWidth;
#ifdef USE_SSE41 #ifdef USE_SSE41
const __m128i kZero = _mm_setzero_si128(); const __m128i Zero = _mm_setzero_si128();
#else #else
const __m128i k0x80s = _mm_set1_epi8(-128); const __m128i k0x80s = _mm_set1_epi8(-128);
#endif #endif
const auto in = reinterpret_cast<const __m128i*>(input); const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output); const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < kNumChunks; ++i) { for (IndexType i = 0; i < NumChunks; ++i) {
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 0]),
_mm_load_si128(&in[i * 4 + 1])), kWeightScaleBits); _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 2]),
_mm_load_si128(&in[i * 4 + 3])), kWeightScaleBits); _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
const __m128i packedbytes = _mm_packs_epi16(words0, words1); const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i], _mm_store_si128(&out[i],
#ifdef USE_SSE41 #ifdef USE_SSE41
_mm_max_epi8(packedbytes, kZero) _mm_max_epi8(packedbytes, Zero)
#else #else
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
#endif #endif
); );
} }
constexpr IndexType kStart = kNumChunks * kSimdWidth; constexpr IndexType Start = NumChunks * SimdWidth;
#elif defined(USE_MMX) #elif defined(USE_MMX)
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth; constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m64 k0x80s = _mm_set1_pi8(-128); const __m64 k0x80s = _mm_set1_pi8(-128);
const auto in = reinterpret_cast<const __m64*>(input); const auto in = reinterpret_cast<const __m64*>(input);
const auto out = reinterpret_cast<__m64*>(output); const auto out = reinterpret_cast<__m64*>(output);
for (IndexType i = 0; i < kNumChunks; ++i) { for (IndexType i = 0; i < NumChunks; ++i) {
const __m64 words0 = _mm_srai_pi16( const __m64 words0 = _mm_srai_pi16(
_mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]), _mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]),
kWeightScaleBits); WeightScaleBits);
const __m64 words1 = _mm_srai_pi16( const __m64 words1 = _mm_srai_pi16(
_mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]), _mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]),
kWeightScaleBits); WeightScaleBits);
const __m64 packedbytes = _mm_packs_pi16(words0, words1); const __m64 packedbytes = _mm_packs_pi16(words0, words1);
out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
} }
_mm_empty(); _mm_empty();
constexpr IndexType kStart = kNumChunks * kSimdWidth; constexpr IndexType Start = NumChunks * SimdWidth;
#elif defined(USE_NEON) #elif defined(USE_NEON)
constexpr IndexType kNumChunks = kInputDimensions / (kSimdWidth / 2); constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
const int8x8_t kZero = {0}; const int8x8_t Zero = {0};
const auto in = reinterpret_cast<const int32x4_t*>(input); const auto in = reinterpret_cast<const int32x4_t*>(input);
const auto out = reinterpret_cast<int8x8_t*>(output); const auto out = reinterpret_cast<int8x8_t*>(output);
for (IndexType i = 0; i < kNumChunks; ++i) { for (IndexType i = 0; i < NumChunks; ++i) {
int16x8_t shifted; int16x8_t shifted;
const auto pack = reinterpret_cast<int16x4_t*>(&shifted); const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
pack[0] = vqshrn_n_s32(in[i * 2 + 0], kWeightScaleBits); pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits);
pack[1] = vqshrn_n_s32(in[i * 2 + 1], kWeightScaleBits); pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits);
out[i] = vmax_s8(vqmovn_s16(shifted), kZero); out[i] = vmax_s8(vqmovn_s16(shifted), Zero);
} }
constexpr IndexType kStart = kNumChunks * (kSimdWidth / 2); constexpr IndexType Start = NumChunks * (SimdWidth / 2);
#else #else
constexpr IndexType kStart = 0; constexpr IndexType Start = 0;
#endif #endif
for (IndexType i = kStart; i < kInputDimensions; ++i) { for (IndexType i = Start; i < InputDimensions; ++i) {
output[i] = static_cast<OutputType>( output[i] = static_cast<OutputType>(
std::max(0, std::min(127, input[i] >> kWeightScaleBits))); std::max(0, std::min(127, input[i] >> WeightScaleBits)));
} }
// Affine transform layers expect that there is at least
// ceil_to_multiple(OutputDimensions, 32) initialized values.
// We cannot do this in the affine transform because it requires
// preallocating space here.
for (IndexType i = OutputDimensions; i < PaddedOutputDimensions; ++i) {
output[i] = 0;
}
return output; return output;
} }
private: private:
PreviousLayer previous_layer_; PreviousLayer previousLayer;
}; };
} // namespace Stockfish::Eval::NNUE::Layers } // namespace Stockfish::Eval::NNUE::Layers
+17 -12
View File
@@ -26,38 +26,43 @@
namespace Stockfish::Eval::NNUE::Layers { namespace Stockfish::Eval::NNUE::Layers {
// Input layer // Input layer
template <IndexType OutputDimensions, IndexType Offset = 0> template <IndexType OutDims, IndexType Offset = 0>
class InputSlice { class InputSlice {
public: public:
// Need to maintain alignment // Need to maintain alignment
static_assert(Offset % kMaxSimdWidth == 0, ""); static_assert(Offset % MaxSimdWidth == 0, "");
// Output type // Output type
using OutputType = TransformedFeatureType; using OutputType = TransformedFeatureType;
// Output dimensionality // Output dimensionality
static constexpr IndexType kOutputDimensions = OutputDimensions; static constexpr IndexType OutputDimensions = OutDims;
// Size of forward propagation buffer used from the input layer to this layer // Size of forward propagation buffer used from the input layer to this layer
static constexpr std::size_t kBufferSize = 0; static constexpr std::size_t BufferSize = 0;
// Hash value embedded in the evaluation file // Hash value embedded in the evaluation file
static constexpr std::uint32_t GetHashValue() { static constexpr std::uint32_t get_hash_value() {
std::uint32_t hash_value = 0xEC42E90Du; std::uint32_t hashValue = 0xEC42E90Du;
hash_value ^= kOutputDimensions ^ (Offset << 10); hashValue ^= OutputDimensions ^ (Offset << 10);
return hash_value; return hashValue;
} }
// Read network parameters // Read network parameters
bool ReadParameters(std::istream& /*stream*/) { bool read_parameters(std::istream& /*stream*/) {
return true;
}
// Write network parameters
bool write_parameters(std::ostream& /*stream*/) const {
return true; return true;
} }
// Forward propagation // Forward propagation
const OutputType* Propagate( const OutputType* propagate(
const TransformedFeatureType* transformed_features, const TransformedFeatureType* transformedFeatures,
char* /*buffer*/) const { char* /*buffer*/) const {
return transformed_features + Offset; return transformedFeatures + Offset;
} }
private: private:
+4 -7
View File
@@ -25,14 +25,11 @@
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
// The accumulator of a StateInfo without parent is set to the INIT state
enum AccumulatorState { EMPTY, COMPUTED, INIT };
// Class that holds the result of affine transformation of input features // Class that holds the result of affine transformation of input features
struct alignas(kCacheLineSize) Accumulator { struct alignas(CacheLineSize) Accumulator {
std::int16_t std::int16_t accumulation[2][TransformedFeatureDimensions];
accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions]; std::int32_t psqtAccumulation[2][PSQTBuckets];
AccumulatorState state[2]; bool computed[2];
}; };
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
+29 -7
View File
@@ -21,17 +21,39 @@
#ifndef NNUE_ARCHITECTURE_H_INCLUDED #ifndef NNUE_ARCHITECTURE_H_INCLUDED
#define NNUE_ARCHITECTURE_H_INCLUDED #define NNUE_ARCHITECTURE_H_INCLUDED
// Defines the network structure #include "nnue_common.h"
#include "architectures/halfkp_256x2-32-32.h"
#include "features/half_ka_v2_hm.h"
#include "layers/input_slice.h"
#include "layers/affine_transform.h"
#include "layers/clipped_relu.h"
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
static_assert(kTransformedFeatureDimensions % kMaxSimdWidth == 0, ""); // Input features used in evaluation function
static_assert(Network::kOutputDimensions == 1, ""); using FeatureSet = Features::HalfKAv2_hm;
static_assert(std::is_same<Network::OutputType, std::int32_t>::value, "");
// Trigger for full calculation instead of difference calculation // Number of input feature dimensions after conversion
constexpr auto kRefreshTriggers = RawFeatures::kRefreshTriggers; constexpr IndexType TransformedFeatureDimensions = 1024;
constexpr IndexType PSQTBuckets = 8;
constexpr IndexType LayerStacks = 8;
namespace Layers {
// Define network structure
using InputLayer = InputSlice<TransformedFeatureDimensions * 2>;
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 8>>;
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
using OutputLayer = AffineTransform<HiddenLayer2, 1>;
} // namespace Layers
using Network = Layers::OutputLayer;
static_assert(TransformedFeatureDimensions % MaxSimdWidth == 0, "");
static_assert(Network::OutputDimensions == 1, "");
static_assert(std::is_same<Network::OutputType, std::int32_t>::value, "");
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
+77 -45
View File
@@ -24,6 +24,8 @@
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include "../misc.h" // for IsLittleEndian
#if defined(USE_AVX2) #if defined(USE_AVX2)
#include <immintrin.h> #include <immintrin.h>
@@ -46,58 +48,30 @@
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
// Version of the evaluation file // Version of the evaluation file
constexpr std::uint32_t kVersion = 0x7AF32F16u; constexpr std::uint32_t Version = 0x7AF32F20u;
// Constant used in evaluation value calculation // Constant used in evaluation value calculation
constexpr int FV_SCALE = 16; constexpr int OutputScale = 16;
constexpr int kWeightScaleBits = 6; constexpr int WeightScaleBits = 6;
// Size of cache line (in bytes) // Size of cache line (in bytes)
constexpr std::size_t kCacheLineSize = 64; constexpr std::size_t CacheLineSize = 64;
// SIMD width (in bytes) // SIMD width (in bytes)
#if defined(USE_AVX2) #if defined(USE_AVX2)
constexpr std::size_t kSimdWidth = 32; constexpr std::size_t SimdWidth = 32;
#elif defined(USE_SSE2) #elif defined(USE_SSE2)
constexpr std::size_t kSimdWidth = 16; constexpr std::size_t SimdWidth = 16;
#elif defined(USE_MMX) #elif defined(USE_MMX)
constexpr std::size_t kSimdWidth = 8; constexpr std::size_t SimdWidth = 8;
#elif defined(USE_NEON) #elif defined(USE_NEON)
constexpr std::size_t kSimdWidth = 16; constexpr std::size_t SimdWidth = 16;
#endif #endif
constexpr std::size_t kMaxSimdWidth = 32; constexpr std::size_t MaxSimdWidth = 32;
// unique number for each piece type on each square
enum {
PS_NONE = 0,
PS_W_PAWN = 1,
PS_B_PAWN = 1 * SQUARE_NB + 1,
PS_W_KNIGHT = 2 * SQUARE_NB + 1,
PS_B_KNIGHT = 3 * SQUARE_NB + 1,
PS_W_BISHOP = 4 * SQUARE_NB + 1,
PS_B_BISHOP = 5 * SQUARE_NB + 1,
PS_W_ROOK = 6 * SQUARE_NB + 1,
PS_B_ROOK = 7 * SQUARE_NB + 1,
PS_W_QUEEN = 8 * SQUARE_NB + 1,
PS_B_QUEEN = 9 * SQUARE_NB + 1,
PS_W_KING = 10 * SQUARE_NB + 1,
PS_END = PS_W_KING, // pieces without kings (pawns included)
PS_B_KING = 11 * SQUARE_NB + 1,
PS_END2 = 12 * SQUARE_NB + 1
};
constexpr uint32_t kpp_board_index[COLOR_NB][PIECE_NB] = {
// convention: W - us, B - them
// viewed from other side, W and B are reversed
{ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_KING, PS_NONE,
PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_KING, PS_NONE },
{ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_KING, PS_NONE,
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_KING, PS_NONE }
};
// Type of input feature after conversion // Type of input feature after conversion
using TransformedFeatureType = std::uint8_t; using TransformedFeatureType = std::uint8_t;
@@ -105,7 +79,7 @@ namespace Stockfish::Eval::NNUE {
// Round n up to be a multiple of base // Round n up to be a multiple of base
template <typename IntType> template <typename IntType>
constexpr IntType CeilToMultiple(IntType n, IntType base) { constexpr IntType ceil_to_multiple(IntType n, IntType base) {
return (n + base - 1) / base * base; return (n + base - 1) / base * base;
} }
@@ -114,19 +88,77 @@ namespace Stockfish::Eval::NNUE {
// necessary to return a result with the byte ordering of the compiling machine. // necessary to return a result with the byte ordering of the compiling machine.
template <typename IntType> template <typename IntType>
inline IntType read_little_endian(std::istream& stream) { inline IntType read_little_endian(std::istream& stream) {
IntType result; IntType result;
std::uint8_t u[sizeof(IntType)];
typename std::make_unsigned<IntType>::type v = 0;
stream.read(reinterpret_cast<char*>(u), sizeof(IntType)); if (IsLittleEndian)
for (std::size_t i = 0; i < sizeof(IntType); ++i) stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
v = (v << 8) | u[sizeof(IntType) - i - 1]; else
{
std::uint8_t u[sizeof(IntType)];
typename std::make_unsigned<IntType>::type v = 0;
stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
for (std::size_t i = 0; i < sizeof(IntType); ++i)
v = (v << 8) | u[sizeof(IntType) - i - 1];
std::memcpy(&result, &v, sizeof(IntType));
}
std::memcpy(&result, &v, sizeof(IntType));
return result; return result;
} }
// write_little_endian() is our utility to write an integer (signed or unsigned, any size)
// to a stream in little-endian order. We swap the byte order before the write if
// necessary to always write in little endian order, independantly of the byte
// ordering of the compiling machine.
template <typename IntType>
inline void write_little_endian(std::ostream& stream, IntType value) {
if (IsLittleEndian)
stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
else
{
std::uint8_t u[sizeof(IntType)];
typename std::make_unsigned<IntType>::type v = value;
std::size_t i = 0;
// if constexpr to silence the warning about shift by 8
if constexpr (sizeof(IntType) > 1)
{
for (; i + 1 < sizeof(IntType); ++i)
{
u[i] = v;
v >>= 8;
}
}
u[i] = v;
stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
}
}
// read_little_endian(s, out, N) : read integers in bulk from a little indian stream.
// This reads N integers from stream s and put them in array out.
template <typename IntType>
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
if (IsLittleEndian)
stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
else
for (std::size_t i = 0; i < count; ++i)
out[i] = read_little_endian<IntType>(stream);
}
// write_little_endian(s, values, N) : write integers in bulk to a little indian stream.
// This takes N integers from array values and writes them on stream s.
template <typename IntType>
inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
if (IsLittleEndian)
stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
else
for (std::size_t i = 0; i < count; ++i)
write_little_endian<IntType>(stream, values[i]);
}
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
#endif // #ifndef NNUE_COMMON_H_INCLUDED #endif // #ifndef NNUE_COMMON_H_INCLUDED
+385 -188
View File
@@ -23,72 +23,158 @@
#include "nnue_common.h" #include "nnue_common.h"
#include "nnue_architecture.h" #include "nnue_architecture.h"
#include "features/index_list.h"
#include <cstring> // std::memset() #include <cstring> // std::memset()
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
using BiasType = std::int16_t;
using WeightType = std::int16_t;
using PSQTWeightType = std::int32_t;
// If vector instructions are enabled, we update and refresh the // If vector instructions are enabled, we update and refresh the
// accumulator tile by tile such that each tile fits in the CPU's // accumulator tile by tile such that each tile fits in the CPU's
// vector registers. // vector registers.
#define VECTOR #define VECTOR
static_assert(PSQTBuckets % 8 == 0,
"Per feature PSQT values cannot be processed at granularity lower than 8 at a time.");
#ifdef USE_AVX512 #ifdef USE_AVX512
typedef __m512i vec_t; typedef __m512i vec_t;
typedef __m256i psqt_vec_t;
#define vec_load(a) _mm512_load_si512(a) #define vec_load(a) _mm512_load_si512(a)
#define vec_store(a,b) _mm512_store_si512(a,b) #define vec_store(a,b) _mm512_store_si512(a,b)
#define vec_add_16(a,b) _mm512_add_epi16(a,b) #define vec_add_16(a,b) _mm512_add_epi16(a,b)
#define vec_sub_16(a,b) _mm512_sub_epi16(a,b) #define vec_sub_16(a,b) _mm512_sub_epi16(a,b)
static constexpr IndexType kNumRegs = 8; // only 8 are needed #define vec_load_psqt(a) _mm256_load_si256(a)
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
#define vec_zero_psqt() _mm256_setzero_si256()
#define NumRegistersSIMD 32
#elif USE_AVX2 #elif USE_AVX2
typedef __m256i vec_t; typedef __m256i vec_t;
typedef __m256i psqt_vec_t;
#define vec_load(a) _mm256_load_si256(a) #define vec_load(a) _mm256_load_si256(a)
#define vec_store(a,b) _mm256_store_si256(a,b) #define vec_store(a,b) _mm256_store_si256(a,b)
#define vec_add_16(a,b) _mm256_add_epi16(a,b) #define vec_add_16(a,b) _mm256_add_epi16(a,b)
#define vec_sub_16(a,b) _mm256_sub_epi16(a,b) #define vec_sub_16(a,b) _mm256_sub_epi16(a,b)
static constexpr IndexType kNumRegs = 16; #define vec_load_psqt(a) _mm256_load_si256(a)
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
#define vec_zero_psqt() _mm256_setzero_si256()
#define NumRegistersSIMD 16
#elif USE_SSE2 #elif USE_SSE2
typedef __m128i vec_t; typedef __m128i vec_t;
typedef __m128i psqt_vec_t;
#define vec_load(a) (*(a)) #define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b) #define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) _mm_add_epi16(a,b) #define vec_add_16(a,b) _mm_add_epi16(a,b)
#define vec_sub_16(a,b) _mm_sub_epi16(a,b) #define vec_sub_16(a,b) _mm_sub_epi16(a,b)
static constexpr IndexType kNumRegs = Is64Bit ? 16 : 8; #define vec_load_psqt(a) (*(a))
#define vec_store_psqt(a,b) *(a)=(b)
#define vec_add_psqt_32(a,b) _mm_add_epi32(a,b)
#define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b)
#define vec_zero_psqt() _mm_setzero_si128()
#define NumRegistersSIMD (Is64Bit ? 16 : 8)
#elif USE_MMX #elif USE_MMX
typedef __m64 vec_t; typedef __m64 vec_t;
typedef __m64 psqt_vec_t;
#define vec_load(a) (*(a)) #define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b) #define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) _mm_add_pi16(a,b) #define vec_add_16(a,b) _mm_add_pi16(a,b)
#define vec_sub_16(a,b) _mm_sub_pi16(a,b) #define vec_sub_16(a,b) _mm_sub_pi16(a,b)
static constexpr IndexType kNumRegs = 8; #define vec_load_psqt(a) (*(a))
#define vec_store_psqt(a,b) *(a)=(b)
#define vec_add_psqt_32(a,b) _mm_add_pi32(a,b)
#define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b)
#define vec_zero_psqt() _mm_setzero_si64()
#define NumRegistersSIMD 8
#elif USE_NEON #elif USE_NEON
typedef int16x8_t vec_t; typedef int16x8_t vec_t;
typedef int32x4_t psqt_vec_t;
#define vec_load(a) (*(a)) #define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b) #define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) vaddq_s16(a,b) #define vec_add_16(a,b) vaddq_s16(a,b)
#define vec_sub_16(a,b) vsubq_s16(a,b) #define vec_sub_16(a,b) vsubq_s16(a,b)
static constexpr IndexType kNumRegs = 16; #define vec_load_psqt(a) (*(a))
#define vec_store_psqt(a,b) *(a)=(b)
#define vec_add_psqt_32(a,b) vaddq_s32(a,b)
#define vec_sub_psqt_32(a,b) vsubq_s32(a,b)
#define vec_zero_psqt() psqt_vec_t{0}
#define NumRegistersSIMD 16
#else #else
#undef VECTOR #undef VECTOR
#endif #endif
#ifdef VECTOR
// Compute optimal SIMD register count for feature transformer accumulation.
// We use __m* types as template arguments, which causes GCC to emit warnings
// about losing some attribute information. This is irrelevant to us as we
// only take their size, so the following pragma are harmless.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wignored-attributes"
template <typename SIMDRegisterType,
typename LaneType,
int NumLanes,
int MaxRegisters>
static constexpr int BestRegisterCount()
{
#define RegisterSize sizeof(SIMDRegisterType)
#define LaneSize sizeof(LaneType)
static_assert(RegisterSize >= LaneSize);
static_assert(MaxRegisters <= NumRegistersSIMD);
static_assert(MaxRegisters > 0);
static_assert(NumRegistersSIMD > 0);
static_assert(RegisterSize % LaneSize == 0);
static_assert((NumLanes * LaneSize) % RegisterSize == 0);
const int ideal = (NumLanes * LaneSize) / RegisterSize;
if (ideal <= MaxRegisters)
return ideal;
// Look for the largest divisor of the ideal register count that is smaller than MaxRegisters
for (int divisor = MaxRegisters; divisor > 1; --divisor)
if (ideal % divisor == 0)
return divisor;
return 1;
}
static constexpr int NumRegs = BestRegisterCount<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
static constexpr int NumPsqtRegs = BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
#pragma GCC diagnostic pop
#endif
// Input feature converter // Input feature converter
class FeatureTransformer { class FeatureTransformer {
private: private:
// Number of output dimensions for one side // Number of output dimensions for one side
static constexpr IndexType kHalfDimensions = kTransformedFeatureDimensions; static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
#ifdef VECTOR #ifdef VECTOR
static constexpr IndexType kTileHeight = kNumRegs * sizeof(vec_t) / 2; static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
static_assert(kHalfDimensions % kTileHeight == 0, "kTileHeight must divide kHalfDimensions"); static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;
static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions");
static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets");
#endif #endif
public: public:
@@ -96,174 +182,218 @@ namespace Stockfish::Eval::NNUE {
using OutputType = TransformedFeatureType; using OutputType = TransformedFeatureType;
// Number of input/output dimensions // Number of input/output dimensions
static constexpr IndexType kInputDimensions = RawFeatures::kDimensions; static constexpr IndexType InputDimensions = FeatureSet::Dimensions;
static constexpr IndexType kOutputDimensions = kHalfDimensions * 2; static constexpr IndexType OutputDimensions = HalfDimensions * 2;
// Size of forward propagation buffer // Size of forward propagation buffer
static constexpr std::size_t kBufferSize = static constexpr std::size_t BufferSize =
kOutputDimensions * sizeof(OutputType); OutputDimensions * sizeof(OutputType);
// Hash value embedded in the evaluation file // Hash value embedded in the evaluation file
static constexpr std::uint32_t GetHashValue() { static constexpr std::uint32_t get_hash_value() {
return FeatureSet::HashValue ^ OutputDimensions;
return RawFeatures::kHashValue ^ kOutputDimensions;
} }
// Read network parameters // Read network parameters
bool ReadParameters(std::istream& stream) { bool read_parameters(std::istream& stream) {
read_little_endian<BiasType >(stream, biases , HalfDimensions );
read_little_endian<WeightType >(stream, weights , HalfDimensions * InputDimensions);
read_little_endian<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
return !stream.fail();
}
// Write network parameters
bool write_parameters(std::ostream& stream) const {
write_little_endian<BiasType >(stream, biases , HalfDimensions );
write_little_endian<WeightType >(stream, weights , HalfDimensions * InputDimensions);
write_little_endian<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
for (std::size_t i = 0; i < kHalfDimensions; ++i)
biases_[i] = read_little_endian<BiasType>(stream);
for (std::size_t i = 0; i < kHalfDimensions * kInputDimensions; ++i)
weights_[i] = read_little_endian<WeightType>(stream);
return !stream.fail(); return !stream.fail();
} }
// Convert input features // Convert input features
void Transform(const Position& pos, OutputType* output) const { std::int32_t transform(const Position& pos, OutputType* output, int bucket) const {
update_accumulator(pos, WHITE);
UpdateAccumulator(pos, WHITE); update_accumulator(pos, BLACK);
UpdateAccumulator(pos, BLACK);
const auto& accumulation = pos.state()->accumulator.accumulation;
#if defined(USE_AVX512)
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth * 2);
static_assert(kHalfDimensions % (kSimdWidth * 2) == 0);
const __m512i kControl = _mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7);
const __m512i kZero = _mm512_setzero_si512();
#elif defined(USE_AVX2)
constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
constexpr int kControl = 0b11011000;
const __m256i kZero = _mm256_setzero_si256();
#elif defined(USE_SSE2)
constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
#ifdef USE_SSE41
const __m128i kZero = _mm_setzero_si128();
#else
const __m128i k0x80s = _mm_set1_epi8(-128);
#endif
#elif defined(USE_MMX)
constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
const __m64 k0x80s = _mm_set1_pi8(-128);
#elif defined(USE_NEON)
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
const int8x8_t kZero = {0};
#endif
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
for (IndexType p = 0; p < 2; ++p) { const auto& accumulation = pos.state()->accumulator.accumulation;
const IndexType offset = kHalfDimensions * p; const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation;
const auto psqt = (
psqtAccumulation[perspectives[0]][bucket]
- psqtAccumulation[perspectives[1]][bucket]
) / 2;
#if defined(USE_AVX512) #if defined(USE_AVX512)
auto out = reinterpret_cast<__m512i*>(&output[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) { constexpr IndexType NumChunks = HalfDimensions / (SimdWidth * 2);
__m512i sum0 = _mm512_load_si512( static_assert(HalfDimensions % (SimdWidth * 2) == 0);
&reinterpret_cast<const __m512i*>(accumulation[perspectives[p]][0])[j * 2 + 0]); const __m512i Control = _mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7);
__m512i sum1 = _mm512_load_si512( const __m512i Zero = _mm512_setzero_si512();
&reinterpret_cast<const __m512i*>(accumulation[perspectives[p]][0])[j * 2 + 1]);
_mm512_store_si512(&out[j], _mm512_permutexvar_epi64(kControl, for (IndexType p = 0; p < 2; ++p)
_mm512_max_epi8(_mm512_packs_epi16(sum0, sum1), kZero))); {
} const IndexType offset = HalfDimensions * p;
auto out = reinterpret_cast<__m512i*>(&output[offset]);
for (IndexType j = 0; j < NumChunks; ++j)
{
__m512i sum0 = _mm512_load_si512(&reinterpret_cast<const __m512i*>
(accumulation[perspectives[p]])[j * 2 + 0]);
__m512i sum1 = _mm512_load_si512(&reinterpret_cast<const __m512i*>
(accumulation[perspectives[p]])[j * 2 + 1]);
_mm512_store_si512(&out[j], _mm512_permutexvar_epi64(Control,
_mm512_max_epi8(_mm512_packs_epi16(sum0, sum1), Zero)));
}
}
return psqt;
#elif defined(USE_AVX2) #elif defined(USE_AVX2)
auto out = reinterpret_cast<__m256i*>(&output[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) { constexpr IndexType NumChunks = HalfDimensions / SimdWidth;
__m256i sum0 = _mm256_load_si256( constexpr int Control = 0b11011000;
&reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 0]); const __m256i Zero = _mm256_setzero_si256();
__m256i sum1 = _mm256_load_si256(
&reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 1]); for (IndexType p = 0; p < 2; ++p)
_mm256_store_si256(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8( {
_mm256_packs_epi16(sum0, sum1), kZero), kControl)); const IndexType offset = HalfDimensions * p;
} auto out = reinterpret_cast<__m256i*>(&output[offset]);
for (IndexType j = 0; j < NumChunks; ++j)
{
__m256i sum0 = _mm256_load_si256(&reinterpret_cast<const __m256i*>
(accumulation[perspectives[p]])[j * 2 + 0]);
__m256i sum1 = _mm256_load_si256(&reinterpret_cast<const __m256i*>
(accumulation[perspectives[p]])[j * 2 + 1]);
_mm256_store_si256(&out[j], _mm256_permute4x64_epi64(
_mm256_max_epi8(_mm256_packs_epi16(sum0, sum1), Zero), Control));
}
}
return psqt;
#elif defined(USE_SSE2) #elif defined(USE_SSE2)
auto out = reinterpret_cast<__m128i*>(&output[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
__m128i sum0 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
accumulation[perspectives[p]][0])[j * 2 + 0]);
__m128i sum1 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
accumulation[perspectives[p]][0])[j * 2 + 1]);
const __m128i packedbytes = _mm_packs_epi16(sum0, sum1);
_mm_store_si128(&out[j], #ifdef USE_SSE41
constexpr IndexType NumChunks = HalfDimensions / SimdWidth;
const __m128i Zero = _mm_setzero_si128();
#else
constexpr IndexType NumChunks = HalfDimensions / SimdWidth;
const __m128i k0x80s = _mm_set1_epi8(-128);
#endif
#ifdef USE_SSE41 for (IndexType p = 0; p < 2; ++p)
_mm_max_epi8(packedbytes, kZero) {
#else const IndexType offset = HalfDimensions * p;
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) auto out = reinterpret_cast<__m128i*>(&output[offset]);
#endif for (IndexType j = 0; j < NumChunks; ++j)
{
__m128i sum0 = _mm_load_si128(&reinterpret_cast<const __m128i*>
(accumulation[perspectives[p]])[j * 2 + 0]);
__m128i sum1 = _mm_load_si128(&reinterpret_cast<const __m128i*>
(accumulation[perspectives[p]])[j * 2 + 1]);
const __m128i packedbytes = _mm_packs_epi16(sum0, sum1);
); #ifdef USE_SSE41
} _mm_store_si128(&out[j], _mm_max_epi8(packedbytes, Zero));
#else
_mm_store_si128(&out[j], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s));
#endif
}
}
return psqt;
#elif defined(USE_MMX) #elif defined(USE_MMX)
auto out = reinterpret_cast<__m64*>(&output[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) { constexpr IndexType NumChunks = HalfDimensions / SimdWidth;
__m64 sum0 = *(&reinterpret_cast<const __m64*>( const __m64 k0x80s = _mm_set1_pi8(-128);
accumulation[perspectives[p]][0])[j * 2 + 0]);
__m64 sum1 = *(&reinterpret_cast<const __m64*>( for (IndexType p = 0; p < 2; ++p)
accumulation[perspectives[p]][0])[j * 2 + 1]); {
const __m64 packedbytes = _mm_packs_pi16(sum0, sum1); const IndexType offset = HalfDimensions * p;
out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); auto out = reinterpret_cast<__m64*>(&output[offset]);
} for (IndexType j = 0; j < NumChunks; ++j)
{
__m64 sum0 = *(&reinterpret_cast<const __m64*>(accumulation[perspectives[p]])[j * 2 + 0]);
__m64 sum1 = *(&reinterpret_cast<const __m64*>(accumulation[perspectives[p]])[j * 2 + 1]);
const __m64 packedbytes = _mm_packs_pi16(sum0, sum1);
out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
}
}
_mm_empty();
return psqt;
#elif defined(USE_NEON) #elif defined(USE_NEON)
const auto out = reinterpret_cast<int8x8_t*>(&output[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) { constexpr IndexType NumChunks = HalfDimensions / (SimdWidth / 2);
int16x8_t sum = reinterpret_cast<const int16x8_t*>( const int8x8_t Zero = {0};
accumulation[perspectives[p]][0])[j];
out[j] = vmax_s8(vqmovn_s16(sum), kZero); for (IndexType p = 0; p < 2; ++p)
} {
const IndexType offset = HalfDimensions * p;
const auto out = reinterpret_cast<int8x8_t*>(&output[offset]);
for (IndexType j = 0; j < NumChunks; ++j)
{
int16x8_t sum = reinterpret_cast<const int16x8_t*>(accumulation[perspectives[p]])[j];
out[j] = vmax_s8(vqmovn_s16(sum), Zero);
}
}
return psqt;
#else #else
for (IndexType j = 0; j < kHalfDimensions; ++j) {
BiasType sum = accumulation[static_cast<int>(perspectives[p])][0][j]; for (IndexType p = 0; p < 2; ++p)
output[offset + j] = static_cast<OutputType>( {
std::max<int>(0, std::min<int>(127, sum))); const IndexType offset = HalfDimensions * p;
} for (IndexType j = 0; j < HalfDimensions; ++j)
{
BiasType sum = accumulation[perspectives[p]][j];
output[offset + j] = static_cast<OutputType>(std::max<int>(0, std::min<int>(127, sum)));
}
}
return psqt;
#endif #endif
} } // end of function transform()
#if defined(USE_MMX)
_mm_empty();
#endif
}
private: private:
void UpdateAccumulator(const Position& pos, const Color c) const { void update_accumulator(const Position& pos, const Color perspective) const {
// The size must be enough to contain the largest possible update.
// That might depend on the feature set and generally relies on the
// feature set's update cost calculation to be correct and never
// allow updates with more added/removed features than MaxActiveDimensions.
#ifdef VECTOR #ifdef VECTOR
// Gcc-10.2 unnecessarily spills AVX2 registers if this array // Gcc-10.2 unnecessarily spills AVX2 registers if this array
// is defined in the VECTOR code below, once in each branch // is defined in the VECTOR code below, once in each branch
vec_t acc[kNumRegs]; vec_t acc[NumRegs];
psqt_vec_t psqt[NumPsqtRegs];
#endif #endif
// Look for a usable accumulator of an earlier position. We keep track // Look for a usable accumulator of an earlier position. We keep track
// of the estimated gain in terms of features to be added/subtracted. // of the estimated gain in terms of features to be added/subtracted.
StateInfo *st = pos.state(), *next = nullptr; StateInfo *st = pos.state(), *next = nullptr;
int gain = pos.count<ALL_PIECES>() - 2; int gain = FeatureSet::refresh_cost(pos);
while (st->accumulator.state[c] == EMPTY) while (st->previous && !st->accumulator.computed[perspective])
{ {
auto& dp = st->dirtyPiece; // This governs when a full feature refresh is needed and how many
// The first condition tests whether an incremental update is // updates are better than just one full refresh.
// possible at all: if this side's king has moved, it is not possible. if ( FeatureSet::requires_refresh(st, perspective)
static_assert(std::is_same_v<RawFeatures::SortedTriggerSet, || (gain -= FeatureSet::update_cost(st) + 1) < 0)
Features::CompileTimeList<Features::TriggerEvent, Features::TriggerEvent::kFriendKingMoved>>,
"Current code assumes that only kFriendlyKingMoved refresh trigger is being used.");
if ( dp.piece[0] == make_piece(c, KING)
|| (gain -= dp.dirty_num + 1) < 0)
break; break;
next = st; next = st;
st = st->previous; st = st->previous;
} }
if (st->accumulator.state[c] == COMPUTED) if (st->accumulator.computed[perspective])
{ {
if (next == nullptr) if (next == nullptr)
return; return;
@@ -271,85 +401,129 @@ namespace Stockfish::Eval::NNUE {
// Update incrementally in two steps. First, we update the "next" // Update incrementally in two steps. First, we update the "next"
// accumulator. Then, we update the current accumulator (pos.state()). // accumulator. Then, we update the current accumulator (pos.state()).
// Gather all features to be updated. This code assumes HalfKP features // Gather all features to be updated.
// only and doesn't support refresh triggers. const Square ksq = pos.square<KING>(perspective);
static_assert(std::is_same_v<Features::FeatureSet<Features::HalfKP<Features::Side::kFriend>>, FeatureSet::IndexList removed[2], added[2];
RawFeatures>); FeatureSet::append_changed_indices(
Features::IndexList removed[2], added[2]; ksq, next->dirtyPiece, perspective, removed[0], added[0]);
Features::HalfKP<Features::Side::kFriend>::AppendChangedIndices(pos,
next->dirtyPiece, c, &removed[0], &added[0]);
for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous) for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous)
Features::HalfKP<Features::Side::kFriend>::AppendChangedIndices(pos, FeatureSet::append_changed_indices(
st2->dirtyPiece, c, &removed[1], &added[1]); ksq, st2->dirtyPiece, perspective, removed[1], added[1]);
// Mark the accumulators as computed. // Mark the accumulators as computed.
next->accumulator.state[c] = COMPUTED; next->accumulator.computed[perspective] = true;
pos.state()->accumulator.state[c] = COMPUTED; pos.state()->accumulator.computed[perspective] = true;
// Now update the accumulators listed in info[], where the last element is a sentinel. // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
StateInfo *info[3] = StateInfo *states_to_update[3] =
{ next, next == pos.state() ? nullptr : pos.state(), nullptr }; { next, next == pos.state() ? nullptr : pos.state(), nullptr };
#ifdef VECTOR #ifdef VECTOR
for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
{ {
// Load accumulator // Load accumulator
auto accTile = reinterpret_cast<vec_t*>( auto accTile = reinterpret_cast<vec_t*>(
&st->accumulator.accumulation[c][0][j * kTileHeight]); &st->accumulator.accumulation[perspective][j * TileHeight]);
for (IndexType k = 0; k < kNumRegs; ++k) for (IndexType k = 0; k < NumRegs; ++k)
acc[k] = vec_load(&accTile[k]); acc[k] = vec_load(&accTile[k]);
for (IndexType i = 0; info[i]; ++i) for (IndexType i = 0; states_to_update[i]; ++i)
{ {
// Difference calculation for the deactivated features // Difference calculation for the deactivated features
for (const auto index : removed[i]) for (const auto index : removed[i])
{ {
const IndexType offset = kHalfDimensions * index + j * kTileHeight; const IndexType offset = HalfDimensions * index + j * TileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]); auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
for (IndexType k = 0; k < kNumRegs; ++k) for (IndexType k = 0; k < NumRegs; ++k)
acc[k] = vec_sub_16(acc[k], column[k]); acc[k] = vec_sub_16(acc[k], column[k]);
} }
// Difference calculation for the activated features // Difference calculation for the activated features
for (const auto index : added[i]) for (const auto index : added[i])
{ {
const IndexType offset = kHalfDimensions * index + j * kTileHeight; const IndexType offset = HalfDimensions * index + j * TileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]); auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
for (IndexType k = 0; k < kNumRegs; ++k) for (IndexType k = 0; k < NumRegs; ++k)
acc[k] = vec_add_16(acc[k], column[k]); acc[k] = vec_add_16(acc[k], column[k]);
} }
// Store accumulator // Store accumulator
accTile = reinterpret_cast<vec_t*>( accTile = reinterpret_cast<vec_t*>(
&info[i]->accumulator.accumulation[c][0][j * kTileHeight]); &states_to_update[i]->accumulator.accumulation[perspective][j * TileHeight]);
for (IndexType k = 0; k < kNumRegs; ++k) for (IndexType k = 0; k < NumRegs; ++k)
vec_store(&accTile[k], acc[k]); vec_store(&accTile[k], acc[k]);
} }
} }
#else for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
for (IndexType i = 0; info[i]; ++i)
{ {
std::memcpy(info[i]->accumulator.accumulation[c][0], // Load accumulator
st->accumulator.accumulation[c][0], auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
kHalfDimensions * sizeof(BiasType)); &st->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
st = info[i]; for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_load_psqt(&accTilePsqt[k]);
for (IndexType i = 0; states_to_update[i]; ++i)
{
// Difference calculation for the deactivated features
for (const auto index : removed[i])
{
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]);
}
// Difference calculation for the activated features
for (const auto index : added[i])
{
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
}
// Store accumulator
accTilePsqt = reinterpret_cast<psqt_vec_t*>(
&states_to_update[i]->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
vec_store_psqt(&accTilePsqt[k], psqt[k]);
}
}
#else
for (IndexType i = 0; states_to_update[i]; ++i)
{
std::memcpy(states_to_update[i]->accumulator.accumulation[perspective],
st->accumulator.accumulation[perspective],
HalfDimensions * sizeof(BiasType));
for (std::size_t k = 0; k < PSQTBuckets; ++k)
states_to_update[i]->accumulator.psqtAccumulation[perspective][k] = st->accumulator.psqtAccumulation[perspective][k];
st = states_to_update[i];
// Difference calculation for the deactivated features // Difference calculation for the deactivated features
for (const auto index : removed[i]) for (const auto index : removed[i])
{ {
const IndexType offset = kHalfDimensions * index; const IndexType offset = HalfDimensions * index;
for (IndexType j = 0; j < kHalfDimensions; ++j) for (IndexType j = 0; j < HalfDimensions; ++j)
st->accumulator.accumulation[c][0][j] -= weights_[offset + j]; st->accumulator.accumulation[perspective][j] -= weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
st->accumulator.psqtAccumulation[perspective][k] -= psqtWeights[index * PSQTBuckets + k];
} }
// Difference calculation for the activated features // Difference calculation for the activated features
for (const auto index : added[i]) for (const auto index : added[i])
{ {
const IndexType offset = kHalfDimensions * index; const IndexType offset = HalfDimensions * index;
for (IndexType j = 0; j < kHalfDimensions; ++j) for (IndexType j = 0; j < HalfDimensions; ++j)
st->accumulator.accumulation[c][0][j] += weights_[offset + j]; st->accumulator.accumulation[perspective][j] += weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
st->accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k];
} }
} }
#endif #endif
@@ -358,43 +532,69 @@ namespace Stockfish::Eval::NNUE {
{ {
// Refresh the accumulator // Refresh the accumulator
auto& accumulator = pos.state()->accumulator; auto& accumulator = pos.state()->accumulator;
accumulator.state[c] = COMPUTED; accumulator.computed[perspective] = true;
Features::IndexList active; FeatureSet::IndexList active;
Features::HalfKP<Features::Side::kFriend>::AppendActiveIndices(pos, c, &active); FeatureSet::append_active_indices(pos, perspective, active);
#ifdef VECTOR #ifdef VECTOR
for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
{ {
auto biasesTile = reinterpret_cast<const vec_t*>( auto biasesTile = reinterpret_cast<const vec_t*>(
&biases_[j * kTileHeight]); &biases[j * TileHeight]);
for (IndexType k = 0; k < kNumRegs; ++k) for (IndexType k = 0; k < NumRegs; ++k)
acc[k] = biasesTile[k]; acc[k] = biasesTile[k];
for (const auto index : active) for (const auto index : active)
{ {
const IndexType offset = kHalfDimensions * index + j * kTileHeight; const IndexType offset = HalfDimensions * index + j * TileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]); auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
for (unsigned k = 0; k < kNumRegs; ++k) for (unsigned k = 0; k < NumRegs; ++k)
acc[k] = vec_add_16(acc[k], column[k]); acc[k] = vec_add_16(acc[k], column[k]);
} }
auto accTile = reinterpret_cast<vec_t*>( auto accTile = reinterpret_cast<vec_t*>(
&accumulator.accumulation[c][0][j * kTileHeight]); &accumulator.accumulation[perspective][j * TileHeight]);
for (unsigned k = 0; k < kNumRegs; k++) for (unsigned k = 0; k < NumRegs; k++)
vec_store(&accTile[k], acc[k]); vec_store(&accTile[k], acc[k]);
} }
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
{
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_zero_psqt();
for (const auto index : active)
{
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
}
auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
&accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
vec_store_psqt(&accTilePsqt[k], psqt[k]);
}
#else #else
std::memcpy(accumulator.accumulation[c][0], biases_, std::memcpy(accumulator.accumulation[perspective], biases,
kHalfDimensions * sizeof(BiasType)); HalfDimensions * sizeof(BiasType));
for (std::size_t k = 0; k < PSQTBuckets; ++k)
accumulator.psqtAccumulation[perspective][k] = 0;
for (const auto index : active) for (const auto index : active)
{ {
const IndexType offset = kHalfDimensions * index; const IndexType offset = HalfDimensions * index;
for (IndexType j = 0; j < kHalfDimensions; ++j) for (IndexType j = 0; j < HalfDimensions; ++j)
accumulator.accumulation[c][0][j] += weights_[offset + j]; accumulator.accumulation[perspective][j] += weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k];
} }
#endif #endif
} }
@@ -404,12 +604,9 @@ namespace Stockfish::Eval::NNUE {
#endif #endif
} }
using BiasType = std::int16_t; alignas(CacheLineSize) BiasType biases[HalfDimensions];
using WeightType = std::int16_t; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions];
alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets];
alignas(kCacheLineSize) BiasType biases_[kHalfDimensions];
alignas(kCacheLineSize)
WeightType weights_[kHalfDimensions * kInputDimensions];
}; };
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
+4 -3
View File
@@ -109,8 +109,9 @@ namespace {
e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem)); e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem));
// Loop through all pawns of the current color and score each pawn // Loop through all pawns of the current color and score each pawn
while (b) { while (b)
s = pop_lsb(&b); {
s = pop_lsb(b);
assert(pos.piece_on(s) == make_piece(Us, PAWN)); assert(pos.piece_on(s) == make_piece(Us, PAWN));
@@ -290,7 +291,7 @@ Score Entry::do_king_safety(const Position& pos) {
if (pawns & attacks_bb<KING>(ksq)) if (pawns & attacks_bb<KING>(ksq))
minPawnDist = 1; minPawnDist = 1;
else while (pawns) else while (pawns)
minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns))); minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(pawns)));
return shelter - make_score(0, 16 * minPawnDist); return shelter - make_score(0, 16 * minPawnDist);
} }
+57 -53
View File
@@ -73,13 +73,13 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
<< std::setfill(' ') << std::dec << "\nCheckers: "; << std::setfill(' ') << std::dec << "\nCheckers: ";
for (Bitboard b = pos.checkers(); b; ) for (Bitboard b = pos.checkers(); b; )
os << UCI::square(pop_lsb(&b)) << " "; os << UCI::square(pop_lsb(b)) << " ";
if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
&& !pos.can_castle(ANY_CASTLING)) && !pos.can_castle(ANY_CASTLING))
{ {
StateInfo st; StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize); ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
Position p; Position p;
p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread());
@@ -251,8 +251,6 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
set_castling_right(c, rsq); set_castling_right(c, rsq);
} }
set_state(st);
// 4. En passant square. // 4. En passant square.
// Ignore if square is invalid or not on side to move relative rank 6. // Ignore if square is invalid or not on side to move relative rank 6.
bool enpassant = false; bool enpassant = false;
@@ -266,24 +264,12 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
// a) side to move have a pawn threatening epSquare // a) side to move have a pawn threatening epSquare
// b) there is an enemy pawn in front of epSquare // b) there is an enemy pawn in front of epSquare
// c) there is no piece on epSquare or behind epSquare // c) there is no piece on epSquare or behind epSquare
// d) enemy pawn didn't block a check of its own color by moving forward
enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN)
&& (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
&& !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))) && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));
&& ( file_of(square<KING>(sideToMove)) == file_of(st->epSquare)
|| !(blockers_for_king(sideToMove) & (st->epSquare + pawn_push(~sideToMove))));
} }
// It's necessary for st->previous to be intialized in this way because legality check relies on its existence if (!enpassant)
if (enpassant) {
st->previous = new StateInfo();
remove_piece(st->epSquare - pawn_push(sideToMove));
st->previous->checkersBB = attackers_to(square<KING>(~sideToMove)) & pieces(sideToMove);
st->previous->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE), st->previous->pinners[BLACK]);
st->previous->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(BLACK), st->previous->pinners[WHITE]);
put_piece(make_piece(~sideToMove, PAWN), st->epSquare - pawn_push(sideToMove));
}
else
st->epSquare = SQ_NONE; st->epSquare = SQ_NONE;
// 5-6. Halfmove clock and fullmove number // 5-6. Halfmove clock and fullmove number
@@ -295,8 +281,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
chess960 = isChess960; chess960 = isChess960;
thisThread = th; thisThread = th;
st->accumulator.state[WHITE] = Eval::NNUE::INIT; set_state(st);
st->accumulator.state[BLACK] = Eval::NNUE::INIT;
assert(pos_is_ok()); assert(pos_is_ok());
@@ -320,7 +305,7 @@ void Position::set_castling_right(Color c, Square rfrom) {
Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto) castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto))
& ~(kfrom | rfrom); & ~(kfrom | rfrom);
} }
@@ -359,7 +344,7 @@ void Position::set_state(StateInfo* si) const {
for (Bitboard b = pieces(); b; ) for (Bitboard b = pieces(); b; )
{ {
Square s = pop_lsb(&b); Square s = pop_lsb(b);
Piece pc = piece_on(s); Piece pc = piece_on(s);
si->key ^= Zobrist::psq[pc][s]; si->key ^= Zobrist::psq[pc][s];
@@ -476,7 +461,7 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners
while (snipers) while (snipers)
{ {
Square sniperSq = pop_lsb(&snipers); Square sniperSq = pop_lsb(snipers);
Bitboard b = between_bb(s, sniperSq) & occupancy; Bitboard b = between_bb(s, sniperSq) & occupancy;
if (b && !more_than_one(b)) if (b && !more_than_one(b))
@@ -517,11 +502,23 @@ bool Position::legal(Move m) const {
assert(color_of(moved_piece(m)) == us); assert(color_of(moved_piece(m)) == us);
assert(piece_on(square<KING>(us)) == make_piece(us, KING)); assert(piece_on(square<KING>(us)) == make_piece(us, KING));
// st->previous->blockersForKing consider capsq as empty. // En passant captures are a tricky special case. Because they are rather
// If pinned, it has to move along the king ray. // uncommon, we do it simply by testing whether the king is attacked after
// the move is made.
if (type_of(m) == EN_PASSANT) if (type_of(m) == EN_PASSANT)
return !(st->previous->blockersForKing[sideToMove] & from) {
|| aligned(from, to, square<KING>(us)); Square ksq = square<KING>(us);
Square capsq = to - pawn_push(us);
Bitboard occupied = (pieces() ^ from ^ capsq) | to;
assert(to == ep_square());
assert(moved_piece(m) == make_piece(us, PAWN));
assert(piece_on(capsq) == make_piece(~us, PAWN));
assert(piece_on(to) == NO_PIECE);
return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
&& !(attacks_bb<BISHOP>(ksq, occupied) & pieces(~us, QUEEN, BISHOP));
}
// Castling moves generation does not check if the castling path is clear of // Castling moves generation does not check if the castling path is clear of
// enemy attacks, it is delayed at a later time: now! // enemy attacks, it is delayed at a later time: now!
@@ -544,7 +541,7 @@ bool Position::legal(Move m) const {
// If the moving piece is a king, check whether the destination square is // If the moving piece is a king, check whether the destination square is
// attacked by the opponent. // attacked by the opponent.
if (type_of(piece_on(from)) == KING) if (type_of(piece_on(from)) == KING)
return !(attackers_to(to) & pieces(~us)); return !(attackers_to(to, pieces() ^ from) & pieces(~us));
// A non-king move is legal if and only if it is not pinned or it // A non-king move is legal if and only if it is not pinned or it
// is moving along the ray towards or away from the king. // is moving along the ray towards or away from the king.
@@ -613,8 +610,8 @@ bool Position::pseudo_legal(const Move m) const {
if (more_than_one(checkers())) if (more_than_one(checkers()))
return false; return false;
// Our move must be a blocking evasion or a capture of the checking piece // Our move must be a blocking interposition or a capture of the checking piece
if (!((between_bb(lsb(checkers()), square<KING>(us)) | checkers()) & to)) if (!(between_bb(square<KING>(us), lsb(checkers())) & to))
return false; return false;
} }
// In case of king moves under check we have to remove king so as to catch // In case of king moves under check we have to remove king so as to catch
@@ -654,15 +651,18 @@ bool Position::gives_check(Move m) const {
case PROMOTION: case PROMOTION:
return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~sideToMove); return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~sideToMove);
// The double-pushed pawn blocked a check? En Passant will remove the blocker. // En passant capture with check? We have already handled the case
// The only discovery check that wasn't handle is through capsq and fromsq // of direct checks and ordinary discovered check, so the only case we
// So the King must be in the same rank as fromsq to consider this possibility. // need to handle is the unusual case of a discovered check through
// st->previous->blockersForKing consider capsq as empty. // the captured pawn.
case EN_PASSANT: case EN_PASSANT:
return st->previous->checkersBB {
|| ( rank_of(square<KING>(~sideToMove)) == rank_of(from) Square capsq = make_square(file_of(to), rank_of(from));
&& st->previous->blockersForKing[~sideToMove] & from); Bitboard b = (pieces() ^ from ^ capsq) | to;
return (attacks_bb< ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
| (attacks_bb<BISHOP>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP));
}
default: //CASTLING default: //CASTLING
{ {
// Castling is encoded as 'king captures the rook' // Castling is encoded as 'king captures the rook'
@@ -702,8 +702,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
++st->pliesFromNull; ++st->pliesFromNull;
// Used by NNUE // Used by NNUE
st->accumulator.state[WHITE] = Eval::NNUE::EMPTY; st->accumulator.computed[WHITE] = false;
st->accumulator.state[BLACK] = Eval::NNUE::EMPTY; st->accumulator.computed[BLACK] = false;
auto& dp = st->dirtyPiece; auto& dp = st->dirtyPiece;
dp.dirty_num = 1; dp.dirty_num = 1;
@@ -988,7 +988,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
} }
/// Position::do(undo)_null_move() is used to do(undo) a "null move": it flips /// Position::do_null_move() is used to do a "null move": it flips
/// the side to move without executing any move on the board. /// the side to move without executing any move on the board.
void Position::do_null_move(StateInfo& newSt) { void Position::do_null_move(StateInfo& newSt) {
@@ -1003,8 +1003,8 @@ void Position::do_null_move(StateInfo& newSt) {
st->dirtyPiece.dirty_num = 0; st->dirtyPiece.dirty_num = 0;
st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator()
st->accumulator.state[WHITE] = Eval::NNUE::EMPTY; st->accumulator.computed[WHITE] = false;
st->accumulator.state[BLACK] = Eval::NNUE::EMPTY; st->accumulator.computed[BLACK] = false;
if (st->epSquare != SQ_NONE) if (st->epSquare != SQ_NONE)
{ {
@@ -1013,9 +1013,9 @@ void Position::do_null_move(StateInfo& newSt) {
} }
st->key ^= Zobrist::side; st->key ^= Zobrist::side;
++st->rule50;
prefetch(TT.first_entry(key())); prefetch(TT.first_entry(key()));
++st->rule50;
st->pliesFromNull = 0; st->pliesFromNull = 0;
sideToMove = ~sideToMove; sideToMove = ~sideToMove;
@@ -1027,6 +1027,9 @@ void Position::do_null_move(StateInfo& newSt) {
assert(pos_is_ok()); assert(pos_is_ok());
} }
/// Position::undo_null_move() must be used to undo a "null move"
void Position::undo_null_move() { void Position::undo_null_move() {
assert(!checkers()); assert(!checkers());
@@ -1077,8 +1080,9 @@ bool Position::see_ge(Move m, Value threshold) const {
if (swap <= 0) if (swap <= 0)
return true; return true;
assert(color_of(piece_on(from)) == sideToMove);
Bitboard occupied = pieces() ^ from ^ to; Bitboard occupied = pieces() ^ from ^ to;
Color stm = color_of(piece_on(from)); Color stm = sideToMove;
Bitboard attackers = attackers_to(to, occupied); Bitboard attackers = attackers_to(to, occupied);
Bitboard stmAttackers, bb; Bitboard stmAttackers, bb;
int res = 1; int res = 1;
@@ -1092,8 +1096,8 @@ bool Position::see_ge(Move m, Value threshold) const {
if (!(stmAttackers = attackers & pieces(stm))) if (!(stmAttackers = attackers & pieces(stm)))
break; break;
// Don't allow pinned pieces to attack (except the king) as long as // Don't allow pinned pieces to attack as long as there are
// there are pinners on their original square. // pinners on their original square.
if (pinners(~stm) & occupied) if (pinners(~stm) & occupied)
stmAttackers &= ~blockers_for_king(stm); stmAttackers &= ~blockers_for_king(stm);
@@ -1109,7 +1113,7 @@ bool Position::see_ge(Move m, Value threshold) const {
if ((swap = PawnValueMg - swap) < res) if ((swap = PawnValueMg - swap) < res)
break; break;
occupied ^= lsb(bb); occupied ^= least_significant_square_bb(bb);
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN); attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
} }
@@ -1118,7 +1122,7 @@ bool Position::see_ge(Move m, Value threshold) const {
if ((swap = KnightValueMg - swap) < res) if ((swap = KnightValueMg - swap) < res)
break; break;
occupied ^= lsb(bb); occupied ^= least_significant_square_bb(bb);
} }
else if ((bb = stmAttackers & pieces(BISHOP))) else if ((bb = stmAttackers & pieces(BISHOP)))
@@ -1126,7 +1130,7 @@ bool Position::see_ge(Move m, Value threshold) const {
if ((swap = BishopValueMg - swap) < res) if ((swap = BishopValueMg - swap) < res)
break; break;
occupied ^= lsb(bb); occupied ^= least_significant_square_bb(bb);
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN); attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
} }
@@ -1135,7 +1139,7 @@ bool Position::see_ge(Move m, Value threshold) const {
if ((swap = RookValueMg - swap) < res) if ((swap = RookValueMg - swap) < res)
break; break;
occupied ^= lsb(bb); occupied ^= least_significant_square_bb(bb);
attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN); attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
} }
@@ -1144,7 +1148,7 @@ bool Position::see_ge(Move m, Value threshold) const {
if ((swap = QueenValueMg - swap) < res) if ((swap = QueenValueMg - swap) < res)
break; break;
occupied ^= lsb(bb); occupied ^= least_significant_square_bb(bb);
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN)) attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
| (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN)); | (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN));
} }
@@ -1218,7 +1222,7 @@ bool Position::has_game_cycle(int ply) const {
Square s1 = from_sq(move); Square s1 = from_sq(move);
Square s2 = to_sq(move); Square s2 = to_sq(move);
if (!(between_bb(s1, s2) & pieces())) if (!((between_bb(s1, s2) ^ s2) & pieces()))
{ {
if (ply > i) if (ply > i)
return true; return true;
@@ -1315,7 +1319,7 @@ bool Position::pos_is_ok() const {
assert(0 && "pos_is_ok: Bitboards"); assert(0 && "pos_is_ok: Bitboards");
StateInfo si = *st; StateInfo si = *st;
ASSERT_ALIGNED(&si, Eval::NNUE::kCacheLineSize); ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize);
set_state(&si); set_state(&si);
if (std::memcmp(&si, st, sizeof(StateInfo))) if (std::memcmp(&si, st, sizeof(StateInfo)))
+7 -17
View File
@@ -51,11 +51,11 @@ struct StateInfo {
// Not copied when making a move (will be recomputed anyhow) // Not copied when making a move (will be recomputed anyhow)
Key key; Key key;
Bitboard checkersBB; Bitboard checkersBB;
Piece capturedPiece;
StateInfo* previous; StateInfo* previous;
Bitboard blockersForKing[COLOR_NB]; Bitboard blockersForKing[COLOR_NB];
Bitboard pinners[COLOR_NB]; Bitboard pinners[COLOR_NB];
Bitboard checkSquares[PIECE_TYPE_NB]; Bitboard checkSquares[PIECE_TYPE_NB];
Piece capturedPiece;
int repetition; int repetition;
// Used by NNUE // Used by NNUE
@@ -115,7 +115,6 @@ public:
Bitboard blockers_for_king(Color c) const; Bitboard blockers_for_king(Color c) const;
Bitboard check_squares(PieceType pt) const; Bitboard check_squares(PieceType pt) const;
Bitboard pinners(Color c) const; Bitboard pinners(Color c) const;
bool is_discovered_check_on_king(Color c, Move m) const;
// Attacks to/from a given square // Attacks to/from a given square
Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s) const;
@@ -128,7 +127,6 @@ public:
bool capture(Move m) const; bool capture(Move m) const;
bool capture_or_promotion(Move m) const; bool capture_or_promotion(Move m) const;
bool gives_check(Move m) const; bool gives_check(Move m) const;
bool advanced_pawn_push(Move m) const;
Piece moved_piece(Move m) const; Piece moved_piece(Move m) const;
Piece captured_piece() const; Piece captured_piece() const;
@@ -173,6 +171,9 @@ public:
// Used by NNUE // Used by NNUE
StateInfo* state() const; StateInfo* state() const;
void put_piece(Piece pc, Square s);
void remove_piece(Square s);
private: private:
// Initialization helpers (used while setting up a position) // Initialization helpers (used while setting up a position)
void set_castling_right(Color c, Square rfrom); void set_castling_right(Color c, Square rfrom);
@@ -180,8 +181,6 @@ private:
void set_check_info(StateInfo* si) const; void set_check_info(StateInfo* si) const;
// Other helpers // Other helpers
void put_piece(Piece pc, Square s);
void remove_piece(Square s);
void move_piece(Square from, Square to); void move_piece(Square from, Square to);
template<bool Do> template<bool Do>
void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
@@ -194,11 +193,11 @@ private:
int castlingRightsMask[SQUARE_NB]; int castlingRightsMask[SQUARE_NB];
Square castlingRookSquare[CASTLING_RIGHT_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB];
Bitboard castlingPath[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB];
Thread* thisThread;
StateInfo* st;
int gamePly; int gamePly;
Color sideToMove; Color sideToMove;
Score psq; Score psq;
Thread* thisThread;
StateInfo* st;
bool chess960; bool chess960;
}; };
@@ -302,19 +301,10 @@ inline Bitboard Position::check_squares(PieceType pt) const {
return st->checkSquares[pt]; return st->checkSquares[pt];
} }
inline bool Position::is_discovered_check_on_king(Color c, Move m) const {
return st->blockersForKing[c] & from_sq(m);
}
inline bool Position::pawn_passed(Color c, Square s) const { inline bool Position::pawn_passed(Color c, Square s) const {
return !(pieces(~c, PAWN) & passed_pawn_span(c, s)); return !(pieces(~c, PAWN) & passed_pawn_span(c, s));
} }
inline bool Position::advanced_pawn_push(Move m) const {
return type_of(moved_piece(m)) == PAWN
&& relative_rank(sideToMove, to_sq(m)) > RANK_6;
}
inline int Position::pawns_on_same_color_squares(Color c, Square s) const { inline int Position::pawns_on_same_color_squares(Color c, Square s) const {
return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares)); return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares));
} }
@@ -397,7 +387,7 @@ inline void Position::remove_piece(Square s) {
byTypeBB[ALL_PIECES] ^= s; byTypeBB[ALL_PIECES] ^= s;
byTypeBB[type_of(pc)] ^= s; byTypeBB[type_of(pc)] ^= s;
byColorBB[color_of(pc)] ^= s; byColorBB[color_of(pc)] ^= s;
/* board[s] = NO_PIECE; Not needed, overwritten by the capturing one */ board[s] = NO_PIECE;
pieceCount[pc]--; pieceCount[pc]--;
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
psq -= PSQT::psq[pc][s]; psq -= PSQT::psq[pc][s];
+232 -290
View File
@@ -60,22 +60,19 @@ using namespace Search;
namespace { namespace {
// Different node types, used as a template parameter // Different node types, used as a template parameter
enum NodeType { NonPV, PV }; enum NodeType { NonPV, PV, Root };
constexpr uint64_t TtHitAverageWindow = 4096;
constexpr uint64_t TtHitAverageResolution = 1024;
// Futility margin // Futility margin
Value futility_margin(Depth d, bool improving) { Value futility_margin(Depth d, bool improving) {
return Value(234 * (d - improving)); return Value(214 * (d - improving));
} }
// Reductions lookup table, initialized at startup // Reductions lookup table, initialized at startup
int Reductions[MAX_MOVES]; // [depth or moveNumber] int Reductions[MAX_MOVES]; // [depth or moveNumber]
Depth reduction(bool i, Depth d, int mn) { Depth reduction(bool i, Depth d, int mn, bool rangeReduction) {
int r = Reductions[d] * Reductions[mn]; int r = Reductions[d] * Reductions[mn];
return (r + 503) / 1024 + (!i && r > 915); return (r + 534) / 1024 + (!i && r > 904) + rangeReduction;
} }
constexpr int futility_move_count(bool improving, Depth depth) { constexpr int futility_move_count(bool improving, Depth depth) {
@@ -84,7 +81,7 @@ namespace {
// History and stats update bonus, based on depth // History and stats update bonus, based on depth
int stat_bonus(Depth d) { int stat_bonus(Depth d) {
return d > 14 ? 66 : 6 * d * d + 231 * d - 206; return std::min((6 * d + 229) * d - 215 , 2000);
} }
// Add a small random component to draw evaluations to avoid 3-fold blindness // Add a small random component to draw evaluations to avoid 3-fold blindness
@@ -92,6 +89,30 @@ namespace {
return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1); return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1);
} }
// Check if the current thread is in a search explosion
ExplosionState search_explosion(Thread* thisThread) {
uint64_t nodesNow = thisThread->nodes;
bool explosive = thisThread->doubleExtensionAverage[WHITE].is_greater(2, 100)
|| thisThread->doubleExtensionAverage[BLACK].is_greater(2, 100);
if (explosive)
thisThread->nodesLastExplosive = nodesNow;
else
thisThread->nodesLastNormal = nodesNow;
if ( explosive
&& thisThread->state == EXPLOSION_NONE
&& nodesNow - thisThread->nodesLastNormal > 6000000)
thisThread->state = MUST_CALM_DOWN;
if ( thisThread->state == MUST_CALM_DOWN
&& nodesNow - thisThread->nodesLastExplosive > 6000000)
thisThread->state = EXPLOSION_NONE;
return thisThread->state;
}
// Skill structure is used to implement strength limit // Skill structure is used to implement strength limit
struct Skill { struct Skill {
explicit Skill(int l) : level(l) {} explicit Skill(int l) : level(l) {}
@@ -103,53 +124,10 @@ namespace {
Move best = MOVE_NONE; Move best = MOVE_NONE;
}; };
// Breadcrumbs are used to mark nodes as being searched by a given thread template <NodeType nodeType>
struct Breadcrumb {
std::atomic<Thread*> thread;
std::atomic<Key> key;
};
std::array<Breadcrumb, 1024> breadcrumbs;
// ThreadHolding structure keeps track of which thread left breadcrumbs at the given
// node for potential reductions. A free node will be marked upon entering the moves
// loop by the constructor, and unmarked upon leaving that loop by the destructor.
struct ThreadHolding {
explicit ThreadHolding(Thread* thisThread, Key posKey, int ply) {
location = ply < 8 ? &breadcrumbs[posKey & (breadcrumbs.size() - 1)] : nullptr;
otherThread = false;
owning = false;
if (location)
{
// See if another already marked this location, if not, mark it ourselves
Thread* tmp = (*location).thread.load(std::memory_order_relaxed);
if (tmp == nullptr)
{
(*location).thread.store(thisThread, std::memory_order_relaxed);
(*location).key.store(posKey, std::memory_order_relaxed);
owning = true;
}
else if ( tmp != thisThread
&& (*location).key.load(std::memory_order_relaxed) == posKey)
otherThread = true;
}
}
~ThreadHolding() {
if (owning) // Free the marked location
(*location).thread.store(nullptr, std::memory_order_relaxed);
}
bool marked() { return otherThread; }
private:
Breadcrumb* location;
bool otherThread, owning;
};
template <NodeType NT>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
template <NodeType NT> template <NodeType nodeType>
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0);
Value value_to_tt(Value v, int ply); Value value_to_tt(Value v, int ply);
@@ -166,7 +144,7 @@ namespace {
uint64_t perft(Position& pos, Depth depth) { uint64_t perft(Position& pos, Depth depth) {
StateInfo st; StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize); ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
uint64_t cnt, nodes = 0; uint64_t cnt, nodes = 0;
const bool leaf = (depth == 2); const bool leaf = (depth == 2);
@@ -196,7 +174,7 @@ namespace {
void Search::init() { void Search::init() {
for (int i = 1; i < MAX_MOVES; ++i) for (int i = 1; i < MAX_MOVES; ++i)
Reductions[i] = int((21.3 + 2 * std::log(Threads.size())) * std::log(i + 0.25 * std::log(i))); Reductions[i] = int((21.9 + std::log(Threads.size()) / 2) * std::log(i));
} }
@@ -325,7 +303,7 @@ void Thread::search() {
// To allow access to (ss-7) up to (ss+2), the stack must be oversized. // To allow access to (ss-7) up to (ss+2), the stack must be oversized.
// The former is needed to allow update_continuation_histories(ss-1, ...), // The former is needed to allow update_continuation_histories(ss-1, ...),
// which accesses its argument at ss-6, also near the root. // which accesses its argument at ss-6, also near the root.
// The latter is needed for statScores and killer initialization. // The latter is needed for statScore and killer initialization.
Stack stack[MAX_PLY+10], *ss = stack+7; Stack stack[MAX_PLY+10], *ss = stack+7;
Move pv[MAX_PLY+1]; Move pv[MAX_PLY+1];
Value bestValue, alpha, beta, delta; Value bestValue, alpha, beta, delta;
@@ -340,6 +318,9 @@ void Thread::search() {
for (int i = 7; i > 0; i--) for (int i = 7; i > 0; i--)
(ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
for (int i = 0; i <= MAX_PLY + 2; ++i)
(ss+i)->ply = i;
ss->pv = pv; ss->pv = pv;
bestValue = delta = alpha = -VALUE_INFINITE; bestValue = delta = alpha = -VALUE_INFINITE;
@@ -379,21 +360,14 @@ void Thread::search() {
multiPV = std::max(multiPV, (size_t)4); multiPV = std::max(multiPV, (size_t)4);
multiPV = std::min(multiPV, rootMoves.size()); multiPV = std::min(multiPV, rootMoves.size());
ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2;
int ct = int(Options["Contempt"]) * PawnValueEg / 100; // From centipawns doubleExtensionAverage[WHITE].set(0, 100); // initialize the running average at 0%
doubleExtensionAverage[BLACK].set(0, 100); // initialize the running average at 0%
// In analysis mode, adjust contempt in accordance with user preference nodesLastExplosive = nodes;
if (Limits.infinite || Options["UCI_AnalyseMode"]) nodesLastNormal = nodes;
ct = Options["Analysis Contempt"] == "Off" ? 0 state = EXPLOSION_NONE;
: Options["Analysis Contempt"] == "Both" ? ct trend = SCORE_ZERO;
: Options["Analysis Contempt"] == "White" && us == BLACK ? -ct
: Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct
: ct;
// Evaluation score is from the white point of view
contempt = (us == WHITE ? make_score(ct, ct / 2)
: -make_score(ct, ct / 2));
int searchAgainCounter = 0; int searchAgainCounter = 0;
@@ -440,21 +414,21 @@ void Thread::search() {
alpha = std::max(prev - delta,-VALUE_INFINITE); alpha = std::max(prev - delta,-VALUE_INFINITE);
beta = std::min(prev + delta, VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE);
// Adjust contempt based on root move's previousScore (dynamic contempt) // Adjust trend based on root move's previousScore (dynamic contempt)
int dct = ct + (113 - ct / 2) * prev / (abs(prev) + 147); int tr = 113 * prev / (abs(prev) + 147);
contempt = (us == WHITE ? make_score(dct, dct / 2) trend = (us == WHITE ? make_score(tr, tr / 2)
: -make_score(dct, dct / 2)); : -make_score(tr, tr / 2));
} }
// Start with a small aspiration window and, in the case of a fail // Start with a small aspiration window and, in the case of a fail
// high/low, re-search with a bigger window until we don't fail // high/low, re-search with a bigger window until we don't fail
// high/low anymore. // high/low anymore.
failedHighCnt = 0; int failedHighCnt = 0;
while (true) while (true)
{ {
Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter); Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter);
bestValue = Stockfish::search<PV>(rootPos, ss, alpha, beta, adjustedDepth, false); bestValue = Stockfish::search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false);
// Bring the best move to the front. It is critical that sorting // Bring the best move to the front. It is critical that sorting
// is done with a stable algorithm because all the values but the // is done with a stable algorithm because all the values but the
@@ -557,8 +531,8 @@ void Thread::search() {
totBestMoveChanges += th->bestMoveChanges; totBestMoveChanges += th->bestMoveChanges;
th->bestMoveChanges = 0; th->bestMoveChanges = 0;
} }
double bestMoveInstability = 1 + 2 * totBestMoveChanges / Threads.size(); double bestMoveInstability = 1.073 + std::max(1.0, 2.25 - 9.9 / rootDepth)
* totBestMoveChanges / Threads.size();
double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability;
// Cap used time in case of a single legal move for a better viewer experience in tournaments // Cap used time in case of a single legal move for a better viewer experience in tournaments
@@ -604,18 +578,26 @@ namespace {
// search<>() is the main search function for both PV and non-PV nodes // search<>() is the main search function for both PV and non-PV nodes
template <NodeType NT> template <NodeType nodeType>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
constexpr bool PvNode = NT == PV; Thread* thisThread = pos.this_thread();
const bool rootNode = PvNode && ss->ply == 0;
// Step 0. Limit search explosion
if ( ss->ply > 10
&& search_explosion(thisThread) == MUST_CALM_DOWN
&& depth > (ss-1)->depth)
depth = (ss-1)->depth;
constexpr bool PvNode = nodeType != NonPV;
constexpr bool rootNode = nodeType == Root;
const Depth maxNextDepth = rootNode ? depth : depth + 1; const Depth maxNextDepth = rootNode ? depth : depth + 1;
// Check if we have an upcoming move which draws by repetition, or // Check if we have an upcoming move which draws by repetition, or
// if the opponent had an alternative move earlier to this position. // if the opponent had an alternative move earlier to this position.
if ( pos.rule50_count() >= 3 if ( !rootNode
&& pos.rule50_count() >= 3
&& alpha < VALUE_DRAW && alpha < VALUE_DRAW
&& !rootNode
&& pos.has_game_cycle(ss->ply)) && pos.has_game_cycle(ss->ply))
{ {
alpha = value_draw(pos.this_thread()); alpha = value_draw(pos.this_thread());
@@ -625,7 +607,7 @@ namespace {
// Dive into quiescence search when the depth reaches zero // Dive into quiescence search when the depth reaches zero
if (depth <= 0) if (depth <= 0)
return qsearch<NT>(pos, ss, alpha, beta); return qsearch<PvNode ? PV : NonPV>(pos, ss, alpha, beta);
assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
assert(PvNode || (alpha == beta - 1)); assert(PvNode || (alpha == beta - 1));
@@ -634,28 +616,26 @@ namespace {
Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64];
StateInfo st; StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize); ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
TTEntry* tte; TTEntry* tte;
Key posKey; Key posKey;
Move ttMove, move, excludedMove, bestMove; Move ttMove, move, excludedMove, bestMove;
Depth extension, newDepth; Depth extension, newDepth;
Value bestValue, value, ttValue, eval, maxValue, probCutBeta; Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
bool formerPv, givesCheck, improving, didLMR, priorCapture; bool givesCheck, improving, didLMR, priorCapture;
bool captureOrPromotion, doFullDepthSearch, moveCountPruning, bool captureOrPromotion, doFullDepthSearch, moveCountPruning,
ttCapture, singularQuietLMR; ttCapture, singularQuietLMR;
Piece movedPiece; Piece movedPiece;
int moveCount, captureCount, quietCount; int moveCount, captureCount, quietCount, bestMoveCount, improvement;
// Step 1. Initialize node // Step 1. Initialize node
Thread* thisThread = pos.this_thread(); ss->inCheck = pos.checkers();
ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece();
priorCapture = pos.captured_piece(); Color us = pos.side_to_move();
Color us = pos.side_to_move(); moveCount = bestMoveCount = captureCount = quietCount = ss->moveCount = 0;
moveCount = captureCount = quietCount = ss->moveCount = 0; bestValue = -VALUE_INFINITE;
bestValue = -VALUE_INFINITE; maxValue = VALUE_INFINITE;
maxValue = VALUE_INFINITE;
ss->distanceFromPv = (PvNode ? 0 : ss->distanceFromPv);
// Check for the available remaining time // Check for the available remaining time
if (thisThread == Threads.main()) if (thisThread == Threads.main())
@@ -688,11 +668,15 @@ namespace {
assert(0 <= ss->ply && ss->ply < MAX_PLY); assert(0 <= ss->ply && ss->ply < MAX_PLY);
(ss+1)->ply = ss->ply + 1; (ss+1)->ttPv = false;
(ss+1)->ttPv = false;
(ss+1)->excludedMove = bestMove = MOVE_NONE; (ss+1)->excludedMove = bestMove = MOVE_NONE;
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
Square prevSq = to_sq((ss-1)->currentMove); ss->doubleExtensions = (ss-1)->doubleExtensions;
ss->depth = depth;
Square prevSq = to_sq((ss-1)->currentMove);
// Update the running average statistics for double extensions
thisThread->doubleExtensionAverage[us].update(ss->depth > (ss-1)->depth);
// Initialize statScore to zero for the grandchildren of the current position. // Initialize statScore to zero for the grandchildren of the current position.
// So statScore is shared between all grandchildren and only the first grandchild // So statScore is shared between all grandchildren and only the first grandchild
@@ -711,9 +695,9 @@ namespace {
ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
: ss->ttHit ? tte->move() : MOVE_NONE; : ss->ttHit ? tte->move() : MOVE_NONE;
ttCapture = ttMove && pos.capture_or_promotion(ttMove);
if (!excludedMove) if (!excludedMove)
ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); ss->ttPv = PvNode || (ss->ttHit && tte->is_pv());
formerPv = ss->ttPv && !PvNode;
// Update low ply history for previous move if we are near root and position is or has been in PV // Update low ply history for previous move if we are near root and position is or has been in PV
if ( ss->ttPv if ( ss->ttPv
@@ -723,14 +707,10 @@ namespace {
&& is_ok((ss-1)->currentMove)) && is_ok((ss-1)->currentMove))
thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5); thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5);
// thisThread->ttHitAverage can be used to approximate the running average of ttHit
thisThread->ttHitAverage = (TtHitAverageWindow - 1) * thisThread->ttHitAverage / TtHitAverageWindow
+ TtHitAverageResolution * ss->ttHit;
// At non-PV nodes we check for an early TT cutoff // At non-PV nodes we check for an early TT cutoff
if ( !PvNode if ( !PvNode
&& ss->ttHit && ss->ttHit
&& tte->depth() >= depth && tte->depth() > depth - (thisThread->id() % 2 == 1)
&& ttValue != VALUE_NONE // Possible in case of TT access race && ttValue != VALUE_NONE // Possible in case of TT access race
&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER) && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
: (tte->bound() & BOUND_UPPER))) : (tte->bound() & BOUND_UPPER)))
@@ -741,7 +721,7 @@ namespace {
if (ttValue >= beta) if (ttValue >= beta)
{ {
// Bonus for a quiet ttMove that fails high // Bonus for a quiet ttMove that fails high
if (!pos.capture_or_promotion(ttMove)) if (!ttCapture)
update_quiet_stats(pos, ss, ttMove, stat_bonus(depth), depth); update_quiet_stats(pos, ss, ttMove, stat_bonus(depth), depth);
// Extra penalty for early quiet moves of the previous ply // Extra penalty for early quiet moves of the previous ply
@@ -749,7 +729,7 @@ namespace {
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1));
} }
// Penalty for a quiet ttMove that fails low // Penalty for a quiet ttMove that fails low
else if (!pos.capture_or_promotion(ttMove)) else if (!ttCapture)
{ {
int penalty = -stat_bonus(depth); int penalty = -stat_bonus(depth);
thisThread->mainHistory[us][from_to(ttMove)] << penalty; thisThread->mainHistory[us][from_to(ttMove)] << penalty;
@@ -824,6 +804,7 @@ namespace {
// Skip early pruning when in check // Skip early pruning when in check
ss->staticEval = eval = VALUE_NONE; ss->staticEval = eval = VALUE_NONE;
improving = false; improving = false;
improvement = 0;
goto moves_loop; goto moves_loop;
} }
else if (ss->ttHit) else if (ss->ttHit)
@@ -844,35 +825,34 @@ namespace {
} }
else else
{ {
// In case of null move search use previous static eval with a different sign ss->staticEval = eval = evaluate(pos);
// and addition of two tempos
if ((ss-1)->currentMove != MOVE_NULL)
ss->staticEval = eval = evaluate(pos);
else
ss->staticEval = eval = -(ss-1)->staticEval + 2 * Tempo;
// Save static evaluation into transposition table // Save static evaluation into transposition table
Cluster::save(thisThread, tte, if (!excludedMove)
posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, Cluster::save(thisThread, tte,
eval); posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE,
eval);
} }
// Use static evaluation difference to improve quiet move ordering // Use static evaluation difference to improve quiet move ordering
if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture)
{ {
int bonus = std::clamp(-depth * 4 * int((ss-1)->staticEval + ss->staticEval - 2 * Tempo), -1000, 1000); int bonus = std::clamp(-depth * 4 * int((ss-1)->staticEval + ss->staticEval), -1000, 1000);
thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus;
} }
// Set up improving flag that is used in various pruning heuristics // Set up the improvement variable, which is the difference between the current
// We define position as improving if static evaluation of position is better // static evaluation and the previous static evaluation at our turn (if we were
// Than the previous static evaluation at our turn // in check at our previous move we look at the move prior to it). The improvement
// In case of us being in check at our previous move we look at move prior to it // margin and the improving flag are used in various pruning heuristics.
improving = (ss-2)->staticEval == VALUE_NONE improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval
? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval
: ss->staticEval > (ss-2)->staticEval; : 200;
// Step 7. Futility pruning: child node (~50 Elo) improving = improvement > 0;
// Step 7. Futility pruning: child node (~50 Elo).
// The depth condition is important for mate finding.
if ( !PvNode if ( !PvNode
&& depth < 9 && depth < 9
&& eval - futility_margin(depth, improving) >= beta && eval - futility_margin(depth, improving) >= beta
@@ -882,10 +862,10 @@ namespace {
// Step 8. Null move search with verification search (~40 Elo) // Step 8. Null move search with verification search (~40 Elo)
if ( !PvNode if ( !PvNode
&& (ss-1)->currentMove != MOVE_NULL && (ss-1)->currentMove != MOVE_NULL
&& (ss-1)->statScore < 24185 && (ss-1)->statScore < 23767
&& eval >= beta && eval >= beta
&& eval >= ss->staticEval && eval >= ss->staticEval
&& ss->staticEval >= beta - 24 * depth - 34 * improving + 162 * ss->ttPv + 159 && ss->staticEval >= beta - 20 * depth - improvement / 15 + 204
&& !excludedMove && !excludedMove
&& pos.non_pawn_material(us) && pos.non_pawn_material(us)
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
@@ -893,7 +873,7 @@ namespace {
assert(eval - beta >= 0); assert(eval - beta >= 0);
// Null move dynamic reduction based on depth and value // Null move dynamic reduction based on depth and value
Depth R = (1062 + 68 * depth) / 256 + std::min(int(eval - beta) / 190, 3); Depth R = std::min(int(eval - beta) / 205, 3) + depth / 3 + 4;
ss->currentMove = MOVE_NULL; ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
@@ -931,7 +911,7 @@ namespace {
probCutBeta = beta + 209 - 44 * improving; probCutBeta = beta + 209 - 44 * improving;
// Step 9. ProbCut (~10 Elo) // Step 9. ProbCut (~4 Elo)
// If we have a good enough capture and a reduced search returns a value // If we have a good enough capture and a reduced search returns a value
// much above beta, we can (almost) safely prune the previous move. // much above beta, we can (almost) safely prune the previous move.
if ( !PvNode if ( !PvNode
@@ -946,31 +926,19 @@ namespace {
&& ttValue != VALUE_NONE && ttValue != VALUE_NONE
&& ttValue < probCutBeta)) && ttValue < probCutBeta))
{ {
// if ttMove is a capture and value from transposition table is good enough produce probCut
// cutoff without digging into actual probCut search
if ( ss->ttHit
&& tte->depth() >= depth - 3
&& ttValue != VALUE_NONE
&& ttValue >= probCutBeta
&& ttMove
&& pos.capture_or_promotion(ttMove))
return probCutBeta;
assert(probCutBeta < VALUE_INFINITE); assert(probCutBeta < VALUE_INFINITE);
MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
int probCutCount = 0;
bool ttPv = ss->ttPv; bool ttPv = ss->ttPv;
ss->ttPv = false; ss->ttPv = false;
while ( (move = mp.next_move()) != MOVE_NONE while ((move = mp.next_move()) != MOVE_NONE)
&& probCutCount < 2 + 2 * cutNode)
if (move != excludedMove && pos.legal(move)) if (move != excludedMove && pos.legal(move))
{ {
assert(pos.capture_or_promotion(move)); assert(pos.capture_or_promotion(move));
assert(depth >= 5); assert(depth >= 5);
captureOrPromotion = true; captureOrPromotion = true;
probCutCount++;
ss->currentMove = move; ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
@@ -1004,18 +972,23 @@ namespace {
ss->ttPv = ttPv; ss->ttPv = ttPv;
} }
// Step 10. If the position is not in TT, decrease depth by 2 // Step 10. If the position is not in TT, decrease depth by 2 or 1 depending on node type
if ( PvNode if ( PvNode
&& depth >= 6 && depth >= 6
&& !ttMove) && !ttMove)
depth -= 2; depth -= 2;
moves_loop: // When in check, search starts from here if ( cutNode
&& depth >= 9
&& !ttMove)
depth--;
ttCapture = ttMove && pos.capture_or_promotion(ttMove); moves_loop: // When in check, search starts here
int rangeReduction = 0;
// Step 11. A small Probcut idea, when we are in check // Step 11. A small Probcut idea, when we are in check
probCutBeta = beta + 400; probCutBeta = beta + 409;
if ( ss->inCheck if ( ss->inCheck
&& !PvNode && !PvNode
&& depth >= 4 && depth >= 4
@@ -1046,8 +1019,12 @@ moves_loop: // When in check, search starts from here
value = bestValue; value = bestValue;
singularQuietLMR = moveCountPruning = false; singularQuietLMR = moveCountPruning = false;
// Mark this node as being searched // Indicate PvNodes that will probably fail low if the node was searched
ThreadHolding th(thisThread, posKey, ss->ply); // at a depth equal or greater than the current depth, and the result of this search was a fail low.
bool likelyFailLow = PvNode
&& ttMove
&& (tte->bound() & BOUND_UPPER)
&& tte->depth() >= depth;
// Step 12. Loop through all pseudo-legal moves until no moves remain // Step 12. Loop through all pseudo-legal moves until no moves remain
// or a beta cutoff occurs. // or a beta cutoff occurs.
@@ -1084,18 +1061,10 @@ moves_loop: // When in check, search starts from here
movedPiece = pos.moved_piece(move); movedPiece = pos.moved_piece(move);
givesCheck = pos.gives_check(move); givesCheck = pos.gives_check(move);
// Indicate PvNodes that will probably fail low if node was searched with non-PV search
// at depth equal or greater to current depth and result of this search was far below alpha
bool likelyFailLow = PvNode
&& ttMove
&& (tte->bound() & BOUND_UPPER)
&& ttValue < alpha + 200 + 100 * depth
&& tte->depth() >= depth;
// Calculate new depth for this move // Calculate new depth for this move
newDepth = depth - 1; newDepth = depth - 1;
// Step 13. Pruning at shallow depth (~200 Elo) // Step 13. Pruning at shallow depth (~200 Elo). Depth conditions are important for mate finding.
if ( !rootNode if ( !rootNode
&& pos.non_pawn_material(us) && pos.non_pawn_material(us)
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
@@ -1104,7 +1073,7 @@ moves_loop: // When in check, search starts from here
moveCountPruning = moveCount >= futility_move_count(improving, depth); moveCountPruning = moveCount >= futility_move_count(improving, depth);
// Reduced depth of the next LMR search // Reduced depth of the next LMR search
int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0); int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount, rangeReduction > 2), 0);
if ( captureOrPromotion if ( captureOrPromotion
|| givesCheck) || givesCheck)
@@ -1121,24 +1090,21 @@ moves_loop: // When in check, search starts from here
} }
else else
{ {
// Countermoves based pruning (~20 Elo) // Continuation history based pruning (~20 Elo)
if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1) if (lmrDepth < 5
&& (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold && (*contHist[0])[movedPiece][to_sq(move)]
&& (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold) + (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)] < -3000 * depth + 3000)
continue; continue;
// Futility pruning: parent node (~5 Elo) // Futility pruning: parent node (~5 Elo)
if ( lmrDepth < 7 if ( !ss->inCheck
&& !ss->inCheck && lmrDepth < 8
&& ss->staticEval + 174 + 157 * lmrDepth <= alpha && ss->staticEval + 172 + 145 * lmrDepth <= alpha)
&& (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)]
+ (*contHist[5])[movedPiece][to_sq(move)] / 3 < 28255)
continue; continue;
// Prune moves with negative SEE (~20 Elo) // Prune moves with negative SEE (~20 Elo)
if (!pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth))) if (!pos.see_ge(move, Value(-21 * lmrDepth * lmrDepth - 21 * lmrDepth)))
continue; continue;
} }
} }
@@ -1150,17 +1116,18 @@ moves_loop: // When in check, search starts from here
// then that move is singular and should be extended. To verify this we do // then that move is singular and should be extended. To verify this we do
// a reduced search on all the other moves but the ttMove and if the // a reduced search on all the other moves but the ttMove and if the
// result is lower than ttValue minus a margin, then we will extend the ttMove. // result is lower than ttValue minus a margin, then we will extend the ttMove.
if ( depth >= 7 if ( !rootNode
&& depth >= 7
&& move == ttMove && move == ttMove
&& !rootNode
&& !excludedMove // Avoid recursive singular search && !excludedMove // Avoid recursive singular search
/* && ttValue != VALUE_NONE Already implicit in the next condition */ /* && ttValue != VALUE_NONE Already implicit in the next condition */
&& abs(ttValue) < VALUE_KNOWN_WIN && abs(ttValue) < VALUE_KNOWN_WIN
&& (tte->bound() & BOUND_LOWER) && (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3) && tte->depth() >= depth - 3)
{ {
Value singularBeta = ttValue - ((formerPv + 4) * depth) / 2; Value singularBeta = ttValue - 3 * depth;
Depth singularDepth = (depth - 1 + 3 * formerPv) / 2; Depth singularDepth = (depth - 1) / 2;
ss->excludedMove = move; ss->excludedMove = move;
value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
ss->excludedMove = MOVE_NONE; ss->excludedMove = MOVE_NONE;
@@ -1169,6 +1136,12 @@ moves_loop: // When in check, search starts from here
{ {
extension = 1; extension = 1;
singularQuietLMR = !ttCapture; singularQuietLMR = !ttCapture;
// Avoid search explosion by limiting the number of double extensions
if ( !PvNode
&& value < singularBeta - 75
&& ss->doubleExtensions <= 6)
extension = 2;
} }
// Multi-cut pruning // Multi-cut pruning
@@ -1179,31 +1152,33 @@ moves_loop: // When in check, search starts from here
else if (singularBeta >= beta) else if (singularBeta >= beta)
return singularBeta; return singularBeta;
// If the eval of ttMove is greater than beta we try also if there is another // If the eval of ttMove is greater than beta, we reduce it (negative extension)
// move that pushes it over beta, if so also produce a cutoff.
else if (ttValue >= beta) else if (ttValue >= beta)
{ extension = -2;
ss->excludedMove = move;
value = search<NonPV>(pos, ss, beta - 1, beta, (depth + 3) / 2, cutNode);
ss->excludedMove = MOVE_NONE;
if (value >= beta)
return beta;
}
} }
// Check extension (~2 Elo) // Capture extensions for PvNodes and cutNodes
else if ( givesCheck else if ( (PvNode || cutNode)
&& (pos.is_discovered_check_on_king(~us, move) || pos.see_ge(move))) && captureOrPromotion
&& moveCount != 1)
extension = 1; extension = 1;
// Last captures extension // Check extensions
else if ( PieceValue[EG][pos.captured_piece()] > PawnValueEg else if ( givesCheck
&& pos.non_pawn_material() <= 2 * RookValueMg) && depth > 6
&& abs(ss->staticEval) > 100)
extension = 1;
// Quiet ttMove extensions
else if ( PvNode
&& move == ttMove
&& move == ss->killers[0]
&& (*contHist[0])[movedPiece][to_sq(move)] >= 10000)
extension = 1; extension = 1;
// Add extension to new depth // Add extension to new depth
newDepth += extension; newDepth += extension;
ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2);
// Speculative prefetch as early as possible // Speculative prefetch as early as possible
prefetch(TT.first_entry(pos.key_after(move))); prefetch(TT.first_entry(pos.key_after(move)));
@@ -1218,109 +1193,75 @@ moves_loop: // When in check, search starts from here
// Step 15. Make the move // Step 15. Make the move
pos.do_move(move, st, givesCheck); pos.do_move(move, st, givesCheck);
(ss+1)->distanceFromPv = ss->distanceFromPv + moveCount - 1;
// Step 16. Late moves reduction / extension (LMR, ~200 Elo) // Step 16. Late moves reduction / extension (LMR, ~200 Elo)
// We use various heuristics for the sons of a node after the first son has // We use various heuristics for the sons of a node after the first son has
// been searched. In general we would like to reduce them, but there are many // been searched. In general we would like to reduce them, but there are many
// cases where we extend a son if it has good chances to be "interesting". // cases where we extend a son if it has good chances to be "interesting".
if ( depth >= 3 if ( depth >= 3
&& moveCount > 1 + 2 * rootNode && moveCount > 1 + 2 * rootNode
&& ( !captureOrPromotion && ( !ss->ttPv
|| moveCountPruning || !captureOrPromotion
|| ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha || (cutNode && (ss-1)->moveCount > 1)))
|| cutNode
|| (!PvNode && !formerPv && captureHistory[movedPiece][to_sq(move)][type_of(pos.captured_piece())] < 3678)
|| thisThread->ttHitAverage < 432 * TtHitAverageResolution * TtHitAverageWindow / 1024))
{ {
Depth r = reduction(improving, depth, moveCount); Depth r = reduction(improving, depth, moveCount, rangeReduction > 2);
// Decrease reduction if the ttHit running average is large // Decrease reduction if on the PV (~2 Elo)
if (thisThread->ttHitAverage > 537 * TtHitAverageResolution * TtHitAverageWindow / 1024) if ( PvNode
&& bestMoveCount <= 3)
r--; r--;
// Increase reduction if other threads are searching this position
if (th.marked())
r++;
// Decrease reduction if position is or has been on the PV // Decrease reduction if position is or has been on the PV
// and node is not likely to fail low. (~10 Elo) // and node is not likely to fail low. (~3 Elo)
if (ss->ttPv && !likelyFailLow) if ( ss->ttPv
&& !likelyFailLow)
r -= 2; r -= 2;
// Increase reduction at root and non-PV nodes when the best move does not change frequently // Increase reduction at root and non-PV nodes when the best move does not change frequently
if ((rootNode || !PvNode) && thisThread->rootDepth > 10 && thisThread->bestMoveChanges <= 2) if ( (rootNode || !PvNode)
&& thisThread->bestMoveChanges <= 2)
r++; r++;
// More reductions for late moves if position was not in previous PV // Decrease reduction if opponent's move count is high (~1 Elo)
if (moveCountPruning && !formerPv)
r++;
// Decrease reduction if opponent's move count is high (~5 Elo)
if ((ss-1)->moveCount > 13) if ((ss-1)->moveCount > 13)
r--; r--;
// Decrease reduction if ttMove has been singularly extended (~3 Elo) // Decrease reduction if ttMove has been singularly extended (~1 Elo)
if (singularQuietLMR) if (singularQuietLMR)
r--; r--;
if (captureOrPromotion) // Increase reduction for cut nodes (~3 Elo)
{ if (cutNode && move != ss->killers[0])
// Unless giving check, this capture is likely bad r += 2;
if ( !givesCheck
&& ss->staticEval + PieceValue[EG][pos.captured_piece()] + 210 * depth <= alpha)
r++;
}
else
{
// Increase reduction if ttMove is a capture (~5 Elo)
if (ttCapture)
r++;
// Increase reduction at root if failing high // Increase reduction if ttMove is a capture (~3 Elo)
r += rootNode ? thisThread->failedHighCnt * thisThread->failedHighCnt * moveCount / 512 : 0; if (ttCapture)
r++;
// Increase reduction for cut nodes (~10 Elo) ss->statScore = thisThread->mainHistory[us][from_to(move)]
if (cutNode) + (*contHist[0])[movedPiece][to_sq(move)]
r += 2; + (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)]
- 4923;
// Decrease reduction for moves that escape a capture. Filter out // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
// castling moves, because they are coded as "king captures rook" and r -= ss->statScore / 14721;
// hence break make_move(). (~2 Elo)
else if ( type_of(move) == NORMAL
&& !pos.see_ge(reverse_move(move)))
r -= 2 + ss->ttPv - (type_of(movedPiece) == PAWN);
ss->statScore = thisThread->mainHistory[us][from_to(move)] // In general we want to cap the LMR depth search at newDepth. But if reductions
+ (*contHist[0])[movedPiece][to_sq(move)] // are really negative and movecount is low, we allow this move to be searched
+ (*contHist[1])[movedPiece][to_sq(move)] // deeper than the first move (this may lead to hidden double extensions).
+ (*contHist[3])[movedPiece][to_sq(move)] int deeper = r >= -1 ? 0
- 4741; : moveCount <= 5 ? 2
: PvNode && depth > 6 ? 1
: 0;
// Decrease/increase reduction by comparing opponent's stat score (~10 Elo) Depth d = std::clamp(newDepth - r, 1, newDepth + deeper);
if (ss->statScore >= -89 && (ss-1)->statScore < -116)
r--;
else if ((ss-1)->statScore >= -112 && ss->statScore < -100)
r++;
// Decrease/increase reduction for moves with a good/bad history (~30 Elo)
// If we are not in check use statScore, but if we are in check we use
// the sum of main history and first continuation history with an offset.
if (ss->inCheck)
r -= (thisThread->mainHistory[us][from_to(move)]
+ (*contHist[0])[movedPiece][to_sq(move)] - 3833) / 16384;
else
r -= ss->statScore / 14790;
}
// In general we want to cap the LMR depth search at newDepth. But for nodes
// close to the principal variation the cap is at (newDepth + 1), which will
// allow these nodes to be searched deeper than the pv (up to 4 plies deeper).
Depth d = std::clamp(newDepth - r, 1, newDepth + ((ss+1)->distanceFromPv <= 4));
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true); value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
// Range reductions (~3 Elo)
if (ss->staticEval - value < 30 && depth > 7)
rangeReduction++;
// If the son is reduced and fails high it will be re-searched at full depth // If the son is reduced and fails high it will be re-searched at full depth
doFullDepthSearch = value > alpha && d < newDepth; doFullDepthSearch = value > alpha && d < newDepth;
didLMR = true; didLMR = true;
@@ -1387,9 +1328,11 @@ moves_loop: // When in check, search starts from here
for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m) for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m)
rm.pv.push_back(*m); rm.pv.push_back(*m);
// We record how often the best move has been changed in each // We record how often the best move has been changed in each iteration.
// iteration. This information is used for time management and LMR // This information is used for time management and LMR. In MultiPV mode,
if (moveCount > 1) // we must take care to only do this for the first PV line.
if ( moveCount > 1
&& !thisThread->pvIdx)
++thisThread->bestMoveChanges; ++thisThread->bestMoveChanges;
} }
else else
@@ -1411,11 +1354,13 @@ moves_loop: // When in check, search starts from here
update_pv(ss->pv, move, (ss+1)->pv); update_pv(ss->pv, move, (ss+1)->pv);
if (PvNode && value < beta) // Update alpha! Always alpha < beta if (PvNode && value < beta) // Update alpha! Always alpha < beta
{
alpha = value; alpha = value;
bestMoveCount++;
}
else else
{ {
assert(value >= beta); // Fail high assert(value >= beta); // Fail high
ss->statScore = 0;
break; break;
} }
} }
@@ -1448,8 +1393,9 @@ moves_loop: // When in check, search starts from here
assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size()); assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
if (!moveCount) if (!moveCount)
bestValue = excludedMove ? alpha bestValue = excludedMove ? alpha :
: ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; ss->inCheck ? mated_in(ss->ply)
: VALUE_DRAW;
// If there is a move which produces search value greater than alpha we update stats of searched moves // If there is a move which produces search value greater than alpha we update stats of searched moves
else if (bestMove) else if (bestMove)
@@ -1459,7 +1405,7 @@ moves_loop: // When in check, search starts from here
// Bonus for prior countermove that caused the fail low // Bonus for prior countermove that caused the fail low
else if ( (depth >= 3 || PvNode) else if ( (depth >= 3 || PvNode)
&& !priorCapture) && !priorCapture)
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth)); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + (PvNode || cutNode)));
if (PvNode) if (PvNode)
bestValue = std::min(bestValue, maxValue); bestValue = std::min(bestValue, maxValue);
@@ -1489,10 +1435,11 @@ moves_loop: // When in check, search starts from here
// qsearch() is the quiescence search function, which is called by the main search // qsearch() is the quiescence search function, which is called by the main search
// function with zero depth, or recursively with further decreasing depth per call. // function with zero depth, or recursively with further decreasing depth per call.
template <NodeType NT> template <NodeType nodeType>
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
constexpr bool PvNode = NT == PV; static_assert(nodeType != Root);
constexpr bool PvNode = nodeType == PV;
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
assert(PvNode || (alpha == beta - 1)); assert(PvNode || (alpha == beta - 1));
@@ -1500,7 +1447,7 @@ moves_loop: // When in check, search starts from here
Move pv[MAX_PLY+1]; Move pv[MAX_PLY+1];
StateInfo st; StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize); ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
TTEntry* tte; TTEntry* tte;
Key posKey; Key posKey;
@@ -1518,7 +1465,6 @@ moves_loop: // When in check, search starts from here
} }
Thread* thisThread = pos.this_thread(); Thread* thisThread = pos.this_thread();
(ss+1)->ply = ss->ply + 1;
bestMove = MOVE_NONE; bestMove = MOVE_NONE;
ss->inCheck = pos.checkers(); ss->inCheck = pos.checkers();
moveCount = 0; moveCount = 0;
@@ -1571,10 +1517,9 @@ moves_loop: // When in check, search starts from here
} }
else else
// In case of null move search use previous static eval with a different sign // In case of null move search use previous static eval with a different sign
// and addition of two tempos
ss->staticEval = bestValue = ss->staticEval = bestValue =
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos) (ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
: -(ss-1)->staticEval + 2 * Tempo; : -(ss-1)->staticEval;
// Stand pat. Return immediately if static value is at least beta // Stand pat. Return immediately if static value is at least beta
if (bestValue >= beta) if (bestValue >= beta)
@@ -1600,7 +1545,7 @@ moves_loop: // When in check, search starts from here
// Initialize a MovePicker object for the current position, and prepare // Initialize a MovePicker object for the current position, and prepare
// to search the moves. Because the depth is <= 0 here, only captures, // to search the moves. Because the depth is <= 0 here, only captures,
// queen and checking knight promotions, and other checks(only if depth >= DEPTH_QS_CHECKS) // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS)
// will be generated. // will be generated.
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
&thisThread->captureHistory, &thisThread->captureHistory,
@@ -1612,6 +1557,10 @@ moves_loop: // When in check, search starts from here
{ {
assert(is_ok(move)); assert(is_ok(move));
// Check for legality
if (!pos.legal(move))
continue;
givesCheck = pos.gives_check(move); givesCheck = pos.gives_check(move);
captureOrPromotion = pos.capture_or_promotion(move); captureOrPromotion = pos.capture_or_promotion(move);
@@ -1621,7 +1570,7 @@ moves_loop: // When in check, search starts from here
if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY
&& !givesCheck && !givesCheck
&& futilityBase > -VALUE_KNOWN_WIN && futilityBase > -VALUE_KNOWN_WIN
&& !pos.advanced_pawn_push(move)) && type_of(move) != PROMOTION)
{ {
if (moveCount > 2) if (moveCount > 2)
@@ -1650,20 +1599,13 @@ moves_loop: // When in check, search starts from here
// Speculative prefetch as early as possible // Speculative prefetch as early as possible
prefetch(TT.first_entry(pos.key_after(move))); prefetch(TT.first_entry(pos.key_after(move)));
// Check for legality just before making the move
if (!pos.legal(move))
{
moveCount--;
continue;
}
ss->currentMove = move; ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
[captureOrPromotion] [captureOrPromotion]
[pos.moved_piece(move)] [pos.moved_piece(move)]
[to_sq(move)]; [to_sq(move)];
// CounterMove based pruning // Continuation history based pruning
if ( !captureOrPromotion if ( !captureOrPromotion
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY && bestValue > VALUE_TB_LOSS_IN_MAX_PLY
&& (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold
@@ -1672,7 +1614,7 @@ moves_loop: // When in check, search starts from here
// Make and search the move // Make and search the move
pos.do_move(move, st, givesCheck); pos.do_move(move, st, givesCheck);
value = -qsearch<NT>(pos, ss+1, -beta, -alpha, depth - 1); value = -qsearch<nodeType>(pos, ss+1, -beta, -alpha, depth - 1);
pos.undo_move(move); pos.undo_move(move);
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
@@ -1786,8 +1728,8 @@ moves_loop: // When in check, search starts from here
PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); PieceType captured = type_of(pos.piece_on(to_sq(bestMove)));
bonus1 = stat_bonus(depth + 1); bonus1 = stat_bonus(depth + 1);
bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus
: std::min(bonus1, stat_bonus(depth)); // smaller bonus : stat_bonus(depth); // smaller bonus
if (!pos.capture_or_promotion(bestMove)) if (!pos.capture_or_promotion(bestMove))
{ {
@@ -2011,7 +1953,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
bool RootMove::extract_ponder_from_tt(Position& pos) { bool RootMove::extract_ponder_from_tt(Position& pos) {
StateInfo st; StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize); ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
bool ttHit; bool ttHit;
+2 -1
View File
@@ -48,12 +48,13 @@ struct Stack {
Move excludedMove; Move excludedMove;
Move killers[2]; Move killers[2];
Value staticEval; Value staticEval;
Depth depth;
int statScore; int statScore;
int moveCount; int moveCount;
int distanceFromPv;
bool inCheck; bool inCheck;
bool ttPv; bool ttPv;
bool ttHit; bool ttHit;
int doubleExtensions;
}; };
+341
View File
@@ -0,0 +1,341 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef STOCKFISH_SIMD_H_INCLUDED
#define STOCKFISH_SIMD_H_INCLUDED
#if defined(USE_AVX2)
# include <immintrin.h>
#elif defined(USE_SSE41)
# include <smmintrin.h>
#elif defined(USE_SSSE3)
# include <tmmintrin.h>
#elif defined(USE_SSE2)
# include <emmintrin.h>
#elif defined(USE_MMX)
# include <mmintrin.h>
#elif defined(USE_NEON)
# include <arm_neon.h>
#endif
// The inline asm is only safe for GCC, where it is necessary to get good codegen.
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101693
// Clang does fine without it.
// Play around here: https://godbolt.org/z/7EWqrYq51
#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER))
#define USE_INLINE_ASM
#endif
namespace Stockfish::Simd {
#if defined (USE_AVX512)
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
return _mm512_reduce_add_epi32(sum) + bias;
}
/*
Parameters:
sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]]
sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]]
sum2 = [zmm2.i128[0], zmm2.i128[1], zmm2.i128[2], zmm2.i128[3]]
sum3 = [zmm3.i128[0], zmm3.i128[1], zmm3.i128[2], zmm3.i128[3]]
Returns:
ret = [
reduce_add_epi32(zmm0.i128[0]), reduce_add_epi32(zmm1.i128[0]), reduce_add_epi32(zmm2.i128[0]), reduce_add_epi32(zmm3.i128[0]),
reduce_add_epi32(zmm0.i128[1]), reduce_add_epi32(zmm1.i128[1]), reduce_add_epi32(zmm2.i128[1]), reduce_add_epi32(zmm3.i128[1]),
reduce_add_epi32(zmm0.i128[2]), reduce_add_epi32(zmm1.i128[2]), reduce_add_epi32(zmm2.i128[2]), reduce_add_epi32(zmm3.i128[2]),
reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3])
]
*/
[[maybe_unused]] static __m512i m512_hadd128x16_interleave(
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
__m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1);
__m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1);
__m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3);
__m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3);
__m512i sum01 = _mm512_add_epi32(sum01a, sum01b);
__m512i sum23 = _mm512_add_epi32(sum23a, sum23b);
__m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23);
__m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23);
return _mm512_add_epi32(sum0123a, sum0123b);
}
[[maybe_unused]] static __m128i m512_haddx4(
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3,
__m128i bias) {
__m512i sum = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3);
__m256i sum256lo = _mm512_castsi512_si256(sum);
__m256i sum256hi = _mm512_extracti64x4_epi64(sum, 1);
sum256lo = _mm256_add_epi32(sum256lo, sum256hi);
__m128i sum128lo = _mm256_castsi256_si128(sum256lo);
__m128i sum128hi = _mm256_extracti128_si256(sum256lo, 1);
return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias);
}
[[maybe_unused]] static void m512_add_dpbusd_epi32(
__m512i& acc,
__m512i a,
__m512i b) {
# if defined (USE_VNNI)
# if defined (USE_INLINE_ASM)
asm(
"vpdpbusd %[b], %[a], %[acc]\n\t"
: [acc]"+v"(acc)
: [a]"v"(a), [b]"vm"(b)
);
# else
acc = _mm512_dpbusd_epi32(acc, a, b);
# endif
# else
# if defined (USE_INLINE_ASM)
__m512i tmp = _mm512_maddubs_epi16(a, b);
asm(
"vpmaddwd %[tmp], %[ones], %[tmp]\n\t"
"vpaddd %[acc], %[tmp], %[acc]\n\t"
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
: [ones]"v"(_mm512_set1_epi16(1))
);
# else
__m512i product0 = _mm512_maddubs_epi16(a, b);
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
acc = _mm512_add_epi32(acc, product0);
# endif
# endif
}
[[maybe_unused]] static void m512_add_dpbusd_epi32x2(
__m512i& acc,
__m512i a0, __m512i b0,
__m512i a1, __m512i b1) {
# if defined (USE_VNNI)
# if defined (USE_INLINE_ASM)
asm(
"vpdpbusd %[b0], %[a0], %[acc]\n\t"
"vpdpbusd %[b1], %[a1], %[acc]\n\t"
: [acc]"+v"(acc)
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
);
# else
acc = _mm512_dpbusd_epi32(acc, a0, b0);
acc = _mm512_dpbusd_epi32(acc, a1, b1);
# endif
# else
# if defined (USE_INLINE_ASM)
__m512i tmp0 = _mm512_maddubs_epi16(a0, b0);
__m512i tmp1 = _mm512_maddubs_epi16(a1, b1);
asm(
"vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
: [tmp1]"v"(tmp1), [ones]"v"(_mm512_set1_epi16(1))
);
# else
__m512i product0 = _mm512_maddubs_epi16(a0, b0);
__m512i product1 = _mm512_maddubs_epi16(a1, b1);
product0 = _mm512_adds_epi16(product0, product1);
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
acc = _mm512_add_epi32(acc, product0);
# endif
# endif
}
#endif
#if defined (USE_AVX2)
[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
__m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
return _mm_cvtsi128_si32(sum128) + bias;
}
[[maybe_unused]] static __m128i m256_haddx4(
__m256i sum0, __m256i sum1, __m256i sum2, __m256i sum3,
__m128i bias) {
sum0 = _mm256_hadd_epi32(sum0, sum1);
sum2 = _mm256_hadd_epi32(sum2, sum3);
sum0 = _mm256_hadd_epi32(sum0, sum2);
__m128i sum128lo = _mm256_castsi256_si128(sum0);
__m128i sum128hi = _mm256_extracti128_si256(sum0, 1);
return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias);
}
[[maybe_unused]] static void m256_add_dpbusd_epi32(
__m256i& acc,
__m256i a,
__m256i b) {
# if defined (USE_VNNI)
# if defined (USE_INLINE_ASM)
asm(
"vpdpbusd %[b], %[a], %[acc]\n\t"
: [acc]"+v"(acc)
: [a]"v"(a), [b]"vm"(b)
);
# else
acc = _mm256_dpbusd_epi32(acc, a, b);
# endif
# else
# if defined (USE_INLINE_ASM)
__m256i tmp = _mm256_maddubs_epi16(a, b);
asm(
"vpmaddwd %[tmp], %[ones], %[tmp]\n\t"
"vpaddd %[acc], %[tmp], %[acc]\n\t"
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
: [ones]"v"(_mm256_set1_epi16(1))
);
# else
__m256i product0 = _mm256_maddubs_epi16(a, b);
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
acc = _mm256_add_epi32(acc, product0);
# endif
# endif
}
[[maybe_unused]] static void m256_add_dpbusd_epi32x2(
__m256i& acc,
__m256i a0, __m256i b0,
__m256i a1, __m256i b1) {
# if defined (USE_VNNI)
# if defined (USE_INLINE_ASM)
asm(
"vpdpbusd %[b0], %[a0], %[acc]\n\t"
"vpdpbusd %[b1], %[a1], %[acc]\n\t"
: [acc]"+v"(acc)
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
);
# else
acc = _mm256_dpbusd_epi32(acc, a0, b0);
acc = _mm256_dpbusd_epi32(acc, a1, b1);
# endif
# else
# if defined (USE_INLINE_ASM)
__m256i tmp0 = _mm256_maddubs_epi16(a0, b0);
__m256i tmp1 = _mm256_maddubs_epi16(a1, b1);
asm(
"vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
: [tmp1]"v"(tmp1), [ones]"v"(_mm256_set1_epi16(1))
);
# else
__m256i product0 = _mm256_maddubs_epi16(a0, b0);
__m256i product1 = _mm256_maddubs_epi16(a1, b1);
product0 = _mm256_adds_epi16(product0, product1);
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
acc = _mm256_add_epi32(acc, product0);
# endif
# endif
}
#endif
#if defined (USE_SSSE3)
[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
return _mm_cvtsi128_si32(sum) + bias;
}
[[maybe_unused]] static __m128i m128_haddx4(
__m128i sum0, __m128i sum1, __m128i sum2, __m128i sum3,
__m128i bias) {
sum0 = _mm_hadd_epi32(sum0, sum1);
sum2 = _mm_hadd_epi32(sum2, sum3);
sum0 = _mm_hadd_epi32(sum0, sum2);
return _mm_add_epi32(sum0, bias);
}
[[maybe_unused]] static void m128_add_dpbusd_epi32(
__m128i& acc,
__m128i a,
__m128i b) {
# if defined (USE_INLINE_ASM)
__m128i tmp = _mm_maddubs_epi16(a, b);
asm(
"pmaddwd %[ones], %[tmp]\n\t"
"paddd %[tmp], %[acc]\n\t"
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
: [ones]"v"(_mm_set1_epi16(1))
);
# else
__m128i product0 = _mm_maddubs_epi16(a, b);
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
acc = _mm_add_epi32(acc, product0);
# endif
}
[[maybe_unused]] static void m128_add_dpbusd_epi32x2(
__m128i& acc,
__m128i a0, __m128i b0,
__m128i a1, __m128i b1) {
# if defined (USE_INLINE_ASM)
__m128i tmp0 = _mm_maddubs_epi16(a0, b0);
__m128i tmp1 = _mm_maddubs_epi16(a1, b1);
asm(
"paddsw %[tmp1], %[tmp0]\n\t"
"pmaddwd %[ones], %[tmp0]\n\t"
"paddd %[tmp0], %[acc]\n\t"
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
: [tmp1]"v"(tmp1), [ones]"v"(_mm_set1_epi16(1))
);
# else
__m128i product0 = _mm_maddubs_epi16(a0, b0);
__m128i product1 = _mm_maddubs_epi16(a1, b1);
product0 = _mm_adds_epi16(product0, product1);
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
acc = _mm_add_epi32(acc, product0);
# endif
}
#endif
}
#endif // STOCKFISH_SIMD_H_INCLUDED
+21 -10
View File
@@ -106,9 +106,6 @@ template<> inline void swap_endian<uint8_t>(uint8_t&) {}
template<typename T, int LE> T number(void* addr) template<typename T, int LE> T number(void* addr)
{ {
static const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
static const bool IsLittleEndian = (Le.c[0] == 4);
T v; T v;
if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare)
@@ -193,7 +190,8 @@ public:
std::stringstream ss(Paths); std::stringstream ss(Paths);
std::string path; std::string path;
while (std::getline(ss, path, SepChar)) { while (std::getline(ss, path, SepChar))
{
fname = path + "/" + f; fname = path + "/" + f;
std::ifstream::open(fname); std::ifstream::open(fname);
if (is_open()) if (is_open())
@@ -568,7 +566,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
int buf64Size = 64; int buf64Size = 64;
Sym sym; Sym sym;
while (true) { while (true)
{
int len = 0; // This is the symbol length - d->min_sym_len int len = 0; // This is the symbol length - d->min_sym_len
// Now get the symbol length. For any symbol s64 of length l right-padded // Now get the symbol length. For any symbol s64 of length l right-padded
@@ -606,8 +605,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
// We binary-search for our value recursively expanding into the left and // We binary-search for our value recursively expanding into the left and
// right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1
// that will store the value we need. // that will store the value we need.
while (d->symlen[sym]) { while (d->symlen[sym])
{
Sym left = d->btree[sym].get<LR::Left>(); Sym left = d->btree[sym].get<LR::Left>();
// If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and
@@ -712,7 +711,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
leadPawns = b = pos.pieces(color_of(pc), PAWN); leadPawns = b = pos.pieces(color_of(pc), PAWN);
do do
squares[size++] = pop_lsb(&b) ^ flipSquares; squares[size++] = pop_lsb(b) ^ flipSquares;
while (b); while (b);
leadPawnsCnt = size; leadPawnsCnt = size;
@@ -732,7 +731,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
// directly map them to the correct color and square. // directly map them to the correct color and square.
b = pos.pieces() ^ leadPawns; b = pos.pieces() ^ leadPawns;
do { do {
Square s = pop_lsb(&b); Square s = pop_lsb(b);
squares[size] = s ^ flipSquares; squares[size] = s ^ flipSquares;
pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);
} while (b); } while (b);
@@ -1539,6 +1538,14 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
WDLScore wdl = -probe_wdl(pos, &result); WDLScore wdl = -probe_wdl(pos, &result);
dtz = dtz_before_zeroing(wdl); dtz = dtz_before_zeroing(wdl);
} }
else if (pos.is_draw(1))
{
// In case a root move leads to a draw by repetition or
// 50-move rule, we set dtz to zero. Note: since we are
// only 1 ply from the root, this must be a true 3-fold
// repetition inside the game history.
dtz = 0;
}
else else
{ {
// Otherwise, take dtz for the new position and correct by 1 ply // Otherwise, take dtz for the new position and correct by 1 ply
@@ -1589,6 +1596,7 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
ProbeState result; ProbeState result;
StateInfo st; StateInfo st;
WDLScore wdl;
bool rule50 = Options["Syzygy50MoveRule"]; bool rule50 = Options["Syzygy50MoveRule"];
@@ -1597,7 +1605,10 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
{ {
pos.do_move(m.pv[0], st); pos.do_move(m.pv[0], st);
WDLScore wdl = -probe_wdl(pos, &result); if (pos.is_draw(1))
wdl = WDLDraw;
else
wdl = -probe_wdl(pos, &result);
pos.undo_move(m.pv[0]); pos.undo_move(m.pv[0]);
+4 -2
View File
@@ -128,14 +128,16 @@ void Thread::idle_loop() {
void ThreadPool::set(size_t requested) { void ThreadPool::set(size_t requested) {
if (size() > 0) { // destroy any existing thread(s) if (size() > 0) // destroy any existing thread(s)
{
main()->wait_for_search_finished(); main()->wait_for_search_finished();
while (size() > 0) while (size() > 0)
delete back(), pop_back(); delete back(), pop_back();
} }
if (requested > 0) { // create new thread(s) if (requested > 0) // create new thread(s)
{
push_back(new MainThread(0)); push_back(new MainThread(0));
while (size() < requested) while (size() < requested)
+7 -4
View File
@@ -56,14 +56,18 @@ public:
void idle_loop(); void idle_loop();
void start_searching(); void start_searching();
void wait_for_search_finished(); void wait_for_search_finished();
size_t id() const { return idx; }
Pawns::Table pawnsTable; Pawns::Table pawnsTable;
Material::Table materialTable; Material::Table materialTable;
size_t pvIdx, pvLast; size_t pvIdx, pvLast;
uint64_t ttHitAverage; RunningAverage doubleExtensionAverage[COLOR_NB];
uint64_t nodesLastExplosive;
uint64_t nodesLastNormal;
std::atomic<uint64_t> nodes, tbHits, TTsaves, bestMoveChanges;
int selDepth, nmpMinPly; int selDepth, nmpMinPly;
Color nmpColor; Color nmpColor;
std::atomic<uint64_t> nodes, tbHits, TTsaves, bestMoveChanges; ExplosionState state;
Position rootPos; Position rootPos;
StateInfo rootState; StateInfo rootState;
@@ -74,8 +78,7 @@ public:
LowPlyHistory lowPlyHistory; LowPlyHistory lowPlyHistory;
CapturePieceToHistory captureHistory; CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2]; ContinuationHistory continuationHistory[2][2];
Score contempt; Score trend;
int failedHighCnt;
#ifdef USE_MPI #ifdef USE_MPI
struct { struct {
std::mutex mutex; std::mutex mutex;
+7 -3
View File
@@ -68,6 +68,9 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
TimePoint timeLeft = std::max(TimePoint(1), TimePoint timeLeft = std::max(TimePoint(1),
limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg));
// Use extra time with larger increments
double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12);
// A user may scale time usage by setting UCI option "Slow Mover" // A user may scale time usage by setting UCI option "Slow Mover"
// Default is 100 and changing this value will probably lose elo. // Default is 100 and changing this value will probably lose elo.
timeLeft = slowMover * timeLeft / 100; timeLeft = slowMover * timeLeft / 100;
@@ -78,15 +81,16 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
if (limits.movestogo == 0) if (limits.movestogo == 0)
{ {
optScale = std::min(0.0084 + std::pow(ply + 3.0, 0.5) * 0.0042, optScale = std::min(0.0084 + std::pow(ply + 3.0, 0.5) * 0.0042,
0.2 * limits.time[us] / double(timeLeft)); 0.2 * limits.time[us] / double(timeLeft))
* optExtra;
maxScale = std::min(7.0, 4.0 + ply / 12.0); maxScale = std::min(7.0, 4.0 + ply / 12.0);
} }
// x moves in y seconds (+ z increment) // x moves in y seconds (+ z increment)
else else
{ {
optScale = std::min((0.8 + ply / 128.0) / mtg, optScale = std::min((0.88 + ply / 116.4) / mtg,
0.8 * limits.time[us] / double(timeLeft)); 0.88 * limits.time[us] / double(timeLeft));
maxScale = std::min(6.3, 1.5 + 0.11 * mtg); maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
} }
-19
View File
@@ -30,7 +30,6 @@ namespace Stockfish {
bool Tune::update_on_last; bool Tune::update_on_last;
const UCI::Option* LastOption = nullptr; const UCI::Option* LastOption = nullptr;
BoolConditions Conditions;
static std::map<std::string, int> TuneResults; static std::map<std::string, int> TuneResults;
string Tune::next(string& names, bool pop) { string Tune::next(string& names, bool pop) {
@@ -110,24 +109,6 @@ template<> void Tune::Entry<Score>::read_option() {
template<> void Tune::Entry<Tune::PostUpdate>::init_option() {} template<> void Tune::Entry<Tune::PostUpdate>::init_option() {}
template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); } template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
// Set binary conditions according to a probability that depends
// on the corresponding parameter value.
void BoolConditions::set() {
static PRNG rng(now());
static bool startup = true; // To workaround fishtest bench
for (size_t i = 0; i < binary.size(); i++)
binary[i] = !startup && (values[i] + int(rng.rand<unsigned>() % variance) > threshold);
startup = false;
for (size_t i = 0; i < binary.size(); i++)
sync_cout << binary[i] << sync_endl;
}
} // namespace Stockfish } // namespace Stockfish
-34
View File
@@ -46,27 +46,6 @@ struct SetRange {
#define SetDefaultRange SetRange(default_range) #define SetDefaultRange SetRange(default_range)
/// BoolConditions struct is used to tune boolean conditions in the
/// code by toggling them on/off according to a probability that
/// depends on the value of a tuned integer parameter: for high
/// values of the parameter condition is always disabled, for low
/// values is always enabled, otherwise it is enabled with a given
/// probability that depnends on the parameter under tuning.
struct BoolConditions {
void init(size_t size) { values.resize(size, defaultValue), binary.resize(size, 0); }
void set();
std::vector<int> binary, values;
int defaultValue = 465, variance = 40, threshold = 500;
SetRange range = SetRange(0, 1000);
};
extern BoolConditions Conditions;
inline void set_conditions() { Conditions.set(); }
/// Tune class implements the 'magic' code that makes the setup of a fishtest /// Tune class implements the 'magic' code that makes the setup of a fishtest
/// tuning session as easy as it can be. Mainly you have just to remove const /// tuning session as easy as it can be. Mainly you have just to remove const
/// qualifiers from the variables you want to tune and flag them for tuning, so /// qualifiers from the variables you want to tune and flag them for tuning, so
@@ -159,14 +138,6 @@ class Tune {
return add(value, (next(names), std::move(names)), args...); return add(value, (next(names), std::move(names)), args...);
} }
// Template specialization for BoolConditions
template<typename... Args>
int add(const SetRange& range, std::string&& names, BoolConditions& cond, Args&&... args) {
for (size_t size = cond.values.size(), i = 0; i < size; i++)
add(cond.range, next(names, i == size - 1) + "_" + std::to_string(i), cond.values[i]);
return add(range, std::move(names), args...);
}
std::vector<std::unique_ptr<EntryBase>> list; std::vector<std::unique_ptr<EntryBase>> list;
public: public:
@@ -187,11 +158,6 @@ public:
#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true
// Some macro to tune toggling of boolean conditions
#define CONDITION(x) (Conditions.binary[__COUNTER__] || (x))
#define TUNE_CONDITIONS() int UNIQUE(c, __LINE__) = (Conditions.init(__COUNTER__), 0); \
TUNE(Conditions, set_conditions)
} // namespace Stockfish } // namespace Stockfish
#endif // #ifndef TUNE_H_INCLUDED #endif // #ifndef TUNE_H_INCLUDED
+5 -1
View File
@@ -173,6 +173,11 @@ enum Bound {
BOUND_EXACT = BOUND_UPPER | BOUND_LOWER BOUND_EXACT = BOUND_UPPER | BOUND_LOWER
}; };
enum ExplosionState {
EXPLOSION_NONE,
MUST_CALM_DOWN
};
enum Value : int { enum Value : int {
VALUE_ZERO = 0, VALUE_ZERO = 0,
VALUE_DRAW = 0, VALUE_DRAW = 0,
@@ -191,7 +196,6 @@ enum Value : int {
BishopValueMg = 825, BishopValueEg = 915, BishopValueMg = 825, BishopValueEg = 915,
RookValueMg = 1276, RookValueEg = 1380, RookValueMg = 1276, RookValueEg = 1380,
QueenValueMg = 2538, QueenValueEg = 2682, QueenValueMg = 2538, QueenValueEg = 2682,
Tempo = 28,
MidgameLimit = 15258, EndgameLimit = 3915 MidgameLimit = 15258, EndgameLimit = 3915
}; };
+11 -3
View File
@@ -211,13 +211,13 @@ namespace {
// Coefficients of a 3rd order polynomial fit based on fishtest data // Coefficients of a 3rd order polynomial fit based on fishtest data
// for two parameters needed to transform eval to the argument of a // for two parameters needed to transform eval to the argument of a
// logistic function. // logistic function.
double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679}; double as[] = {-3.68389304, 30.07065921, -60.52878723, 149.53378557};
double bs[] = {-3.37154371, 28.44489198, -56.67657741, 72.05858751}; double bs[] = {-2.0181857, 15.85685038, -29.83452023, 47.59078827};
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
// Transform eval to centipawns with limited range // Transform eval to centipawns with limited range
double x = std::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0); double x = std::clamp(double(100 * v) / PawnValueEg, -2000.0, 2000.0);
// Return win rate in per mille (rounded to nearest) // Return win rate in per mille (rounded to nearest)
return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
@@ -285,6 +285,14 @@ void UCI::loop(int argc, char* argv[]) {
trace_eval(pos); trace_eval(pos);
else if (token == "compiler" && Cluster::is_root()) else if (token == "compiler" && Cluster::is_root())
sync_cout << compiler_info() << sync_endl; sync_cout << compiler_info() << sync_endl;
else if (token == "export_net" && Cluster::is_root())
{
std::optional<std::string> filename;
std::string f;
if (is >> skipws >> f)
filename = f;
Eval::NNUE::save_eval(filename);
}
else if (!token.empty() && token[0] != '#' && Cluster::is_root()) else if (!token.empty() && token[0] != '#' && Cluster::is_root())
sync_cout << "Unknown command: " << cmd << sync_endl; sync_cout << "Unknown command: " << cmd << sync_endl;
+1 -3
View File
@@ -61,8 +61,6 @@ void init(OptionsMap& o) {
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
o["Debug Log File"] << Option("", on_logger); o["Debug Log File"] << Option("", on_logger);
o["Contempt"] << Option(24, -100, 100);
o["Analysis Contempt"] << Option("Both var Off var White var Black var Both", "Both");
o["Threads"] << Option(1, 1, 512, on_threads); o["Threads"] << Option(1, 1, 512, on_threads);
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
o["Clear Hash"] << Option(on_clear_hash); o["Clear Hash"] << Option(on_clear_hash);
@@ -166,7 +164,7 @@ Option& Option::operator=(const string& v) {
assert(!type.empty()); assert(!type.empty());
if ( (type != "button" && v.empty()) if ( (type != "button" && type != "string" && v.empty())
|| (type == "check" && v != "true" && v != "false") || (type == "check" && v != "true" && v != "false")
|| (type == "spin" && (stof(v) < min || stof(v) > max))) || (type == "spin" && (stof(v) < min || stof(v) > max)))
return *this; return *this;
+2 -2
View File
@@ -13,7 +13,7 @@ case $1 in
--valgrind) --valgrind)
echo "valgrind testing started" echo "valgrind testing started"
prefix='' prefix=''
exeprefix='valgrind --error-exitcode=42' exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full'
postfix='1>/dev/null' postfix='1>/dev/null'
threads="1" threads="1"
;; ;;
@@ -98,7 +98,7 @@ cat << EOF > game.exp
expect "bestmove" expect "bestmove"
send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n" send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n"
send "go depth 20\n" send "go depth 10\n"
expect "bestmove" expect "bestmove"
send "quit\n" send "quit\n"
+1 -1
View File
@@ -10,7 +10,7 @@ trap 'error ${LINENO}' ERR
echo "reprosearch testing started" echo "reprosearch testing started"
# repeat two short games, separated by ucinewgame. # repeat two short games, separated by ucinewgame.
# with go nodes $nodes they should result in exactly # with go nodes $nodes they should result in exactly
# the same node count for each iteration. # the same node count for each iteration.
cat << EOF > repeat.exp cat << EOF > repeat.exp