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 changepull/3771/head
commit
b79ec2e0b2
|
@ -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
.travis.yml
101
.travis.yml
|
@ -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
|
11
AUTHORS
11
AUTHORS
|
@ -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
|
||||
Tord Romstad (romstad)
|
||||
|
@ -21,12 +21,14 @@ Alexander Kure
|
|||
Alexander Pagel (Lolligerhans)
|
||||
Alfredo Menezes (lonfom169)
|
||||
Ali AlZhrani (Cooffe)
|
||||
Andrei Vetrov (proukornew)
|
||||
Andrew Grant (AndyGrant)
|
||||
Andrey Neporada (nepal)
|
||||
Andy Duplain
|
||||
Antoine Champion (antoinechampion)
|
||||
Aram Tumanian (atumanian)
|
||||
Arjun Temurnikar
|
||||
Artem Solopiy (EntityFX)
|
||||
Auguste Pop
|
||||
Balint Pfliegel
|
||||
Ben Koshy (BKSpurgeon)
|
||||
|
@ -51,6 +53,7 @@ Dieter Dobbelaere (ddobbelaere)
|
|||
DiscanX
|
||||
Dominik Schlösser (domschl)
|
||||
double-beep
|
||||
Douglas Matos Gomes (dsmsgms)
|
||||
Eduardo Cáceres (eduherminio)
|
||||
Eelco de Groot (KingDefender)
|
||||
Elvin Liu (solarlight2)
|
||||
|
@ -67,6 +70,7 @@ gamander
|
|||
Gary Heckman (gheckman)
|
||||
George Sobala (gsobala)
|
||||
gguliash
|
||||
Giacomo Lorenzetti (G-Lorenz)
|
||||
Gian-Carlo Pascutto (gcp)
|
||||
Gontran Lemaire (gonlem)
|
||||
Goodkov Vasiliy Aleksandrovich (goodkov)
|
||||
|
@ -94,6 +98,7 @@ Joost VandeVondele (vondele)
|
|||
Jörg Oster (joergoster)
|
||||
Joseph Ellis (jhellis3)
|
||||
Joseph R. Prostko
|
||||
Julian Willemer (NightlyKing)
|
||||
jundery
|
||||
Justin Blanchard (UncombedCoconut)
|
||||
Kelly Wilson
|
||||
|
@ -104,6 +109,7 @@ Kojirion
|
|||
Krystian Kuzniarek (kuzkry)
|
||||
Leonardo Ljubičić (ICCF World Champion)
|
||||
Leonid Pechenik (lp--)
|
||||
Liam Keegan (lkeegan)
|
||||
Linus Arver (listx)
|
||||
loco-loco
|
||||
Lub van den Berg (ElbertoOne)
|
||||
|
@ -138,6 +144,7 @@ Nikolay Kostov (NikolayIT)
|
|||
Nguyen Pham (nguyenpham)
|
||||
Norman Schmidt (FireFather)
|
||||
notruck
|
||||
Ofek Shochat (OfekShochat, ghostway)
|
||||
Ondrej Mosnáček (WOnder93)
|
||||
Oskar Werkelin Ahlin
|
||||
Pablo Vazquez
|
||||
|
@ -173,6 +180,7 @@ Stefan Geschwentner (locutus2)
|
|||
Stefano Cardanobile (Stefano80)
|
||||
Steinar Gunderson (sesse)
|
||||
Stéphane Nicolet (snicolet)
|
||||
Prokop Randáček (ProkopRandacek)
|
||||
Thanar2
|
||||
thaspel
|
||||
theo77186
|
||||
|
@ -180,6 +188,7 @@ Tom Truscott
|
|||
Tom Vijlbrief (tomtor)
|
||||
Tomasz Sobczyk (Sopel97)
|
||||
Torsten Franz (torfranz, tfranzer)
|
||||
Torsten Hellwig (Torom)
|
||||
Tracey Emery (basepr1me)
|
||||
tttak
|
||||
Unai Corzo (unaiic)
|
||||
|
|
89
README.md
89
README.md
|
@ -1,6 +1,6 @@
|
|||
## 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)
|
||||
|
||||
[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:
|
||||
|
||||
* 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.
|
||||
|
||||
* AUTHORS, a text file with the list of authors for the project
|
||||
* [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt), a text file containing the GNU General Public License version 3.
|
||||
|
||||
* 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.
|
||||
|
||||
* 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.
|
||||
|
||||
## 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
|
||||
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
|
||||
(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
|
||||
Assume a time delay of x ms due to network and GUI overheads. This is useful to
|
||||
avoid losses on time in those cases.
|
||||
|
@ -136,6 +135,34 @@ Currently, Stockfish has the following UCI options:
|
|||
* #### Debug Log 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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
|
@ -175,7 +202,7 @@ replacing `[filename]` as needed.
|
|||
|
||||
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.
|
||||
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.
|
||||
|
||||
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```,
|
||||
include ```mpiexec```, ```srun```). Use 1 mpi rank per node, and employ threading
|
||||
according to the cores per node. To build the cluster
|
||||
branch, it is sufficient to specify ```COMPILER=mpicxx``` on the make command line,
|
||||
and do a clean build:
|
||||
branch, it is sufficient to specify ```COMPILER=mpicxx``` (or e.g. CC depending on the name
|
||||
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
|
||||
not match ```mpi``` an edit to the Makefile is required. Make sure that the MPI
|
||||
installation is configured to support ```MPI_THREAD_MULTIPLE```, this might require
|
||||
adding system specific compiler options to the Makefile. Stockfish employs
|
||||
Make sure that the MPI 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
|
||||
implementation that efficiently supports this. Some MPI implentations might benefit
|
||||
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
|
||||
file *types.h* for a quick reference.
|
||||
|
||||
When reporting an issue or a bug, please tell us which version and
|
||||
compiler you used to create your executable. These informations can
|
||||
be found by typing the following commands in a console:
|
||||
When reporting an issue or a bug, please tell us which Stockfish version
|
||||
and which compiler you used to create your executable. This information
|
||||
can be found by typing the following command in a console:
|
||||
|
||||
```
|
||||
./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
|
||||
|
||||
Stockfish's improvement over the last couple of years has been a great
|
||||
community effort. There are a few ways to help contribute to its growth.
|
||||
Stockfish's improvement over the last decade has been a great community
|
||||
effort. There are a few ways to help contribute to its growth.
|
||||
|
||||
### 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.
|
||||
|
||||
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).
|
||||
|
|
|
@ -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!
|
||||
|
||||
Username CPU Hours Games played
|
||||
----------------------------------------------------
|
||||
noobpwnftw 23930906 1560559941
|
||||
dew 1169948 70333008
|
||||
mlang 957168 61657446
|
||||
mibere 703840 46867607
|
||||
tvijlbrief 517888 33379462
|
||||
JojoM 515404 30334272
|
||||
cw 443276 29385549
|
||||
crunchy 427035 27344275
|
||||
grandphish2 425794 26347253
|
||||
fastgm 414133 24519696
|
||||
gvreuls 377843 24708884
|
||||
CSU_Dynasty 338718 23030006
|
||||
Fisherman 326795 21820747
|
||||
TueRens 313730 19490246
|
||||
ctoks 298442 20052551
|
||||
velislav 270519 17355456
|
||||
bcross 241064 17196165
|
||||
glinscott 217799 13780820
|
||||
nordlandia 211692 13484886
|
||||
bking_US 198894 11876016
|
||||
drabel 191096 13129722
|
||||
leszek 189170 11446821
|
||||
mgrabiak 187153 12013300
|
||||
robal 181389 11539242
|
||||
Thanar 179852 12365359
|
||||
vdv 175274 9889046
|
||||
spams 157128 10319326
|
||||
marrco 150292 9401741
|
||||
sqrt2 147963 9724586
|
||||
CoffeeOne 137086 5022516
|
||||
vdbergh 137041 8926915
|
||||
malala 136182 8002293
|
||||
mhoram 132780 8398229
|
||||
xoto 124729 8652088
|
||||
davar 122092 7960001
|
||||
dsmith 122059 7570238
|
||||
Data 113305 8220352
|
||||
BrunoBanani 112960 7436849
|
||||
pemo 109598 5036441
|
||||
Dantist 106768 6431396
|
||||
MaZePallas 102741 6630419
|
||||
ElbertoOne 99028 7023771
|
||||
brabos 92118 6186135
|
||||
linrock 90903 6708639
|
||||
psk 89957 5984901
|
||||
sunu 88614 6020673
|
||||
sterni1971 86948 5613788
|
||||
Vizvezdenec 83761 5344740
|
||||
BRAVONE 81239 5054681
|
||||
nssy 76497 5259388
|
||||
cuistot 76366 4370584
|
||||
racerschmacer 75753 5442626
|
||||
teddybaer 75125 5407666
|
||||
Pking_cda 73776 5293873
|
||||
0x3C33 73133 4670293
|
||||
jromang 72117 5054915
|
||||
solarlight 70517 5028306
|
||||
dv8silencer 70287 3883992
|
||||
Bobo1239 68515 4652287
|
||||
manap 66273 4121774
|
||||
tinker 64321 4268390
|
||||
robnjr 57262 4053117
|
||||
Freja 56938 3733019
|
||||
ttruscott 56010 3680085
|
||||
rkl 54986 4150767
|
||||
renouve 53811 3501516
|
||||
finfish 51360 3370515
|
||||
eva42 51272 3599691
|
||||
rap 49985 3219146
|
||||
pb00067 49727 3298270
|
||||
amicic 49691 3042481
|
||||
ronaldjerum 47654 3240695
|
||||
bigpen0r 47278 3291647
|
||||
biffhero 46564 3111352
|
||||
VoyagerOne 45476 3452465
|
||||
eastorwest 45033 3071805
|
||||
speedycpu 43842 3003273
|
||||
jbwiebe 43305 2805433
|
||||
Antihistamine 41788 2761312
|
||||
mhunt 41735 2691355
|
||||
homyur 39893 2850481
|
||||
gri 39871 2515779
|
||||
oryx 38282 2944400
|
||||
Spprtr 38157 2470529
|
||||
SC 37290 2731014
|
||||
csnodgrass 36207 2688994
|
||||
jmdana 36157 2210661
|
||||
strelock 34716 2074055
|
||||
Garf 33800 2747562
|
||||
skiminki 33515 2055584
|
||||
EthanOConnor 33370 2090311
|
||||
slakovv 32915 2021889
|
||||
yurikvelo 32600 2255966
|
||||
Prcuvu 30377 2170122
|
||||
manapbk 30326 1770143
|
||||
anst 30301 2190091
|
||||
jkiiski 30136 1904470
|
||||
hyperbolic.tom 29840 2017394
|
||||
Pyafue 29650 1902349
|
||||
qurashee 27758 1509620
|
||||
OuaisBla 27636 1578800
|
||||
chriswk 26902 1868317
|
||||
achambord 26582 1767323
|
||||
Fifis 26376 1776853
|
||||
Patrick_G 26276 1801617
|
||||
yorkman 26193 1992080
|
||||
SFTUser 25182 1675689
|
||||
nabildanial 24942 1519409
|
||||
Sharaf_DG 24765 1786697
|
||||
ncfish1 24411 1520927
|
||||
agg177 23890 1395014
|
||||
JanErik 23408 1703875
|
||||
Isidor 23388 1680691
|
||||
Norabor 23164 1591830
|
||||
cisco2015 22895 1762069
|
||||
Zirie 22542 1472937
|
||||
team-oh 22272 1636708
|
||||
MazeOfGalious 21978 1629593
|
||||
sg4032 21945 1643065
|
||||
ianh2105 21725 1632562
|
||||
xor12 21628 1680365
|
||||
dex 21612 1467203
|
||||
nesoneg 21494 1463031
|
||||
jjoshua2 20997 1422689
|
||||
horst.prack 20878 1465656
|
||||
0xB00B1ES 20590 1208666
|
||||
sphinx 20515 1352368
|
||||
j3corre 20405 941444
|
||||
Adrian.Schmidt123 20316 1281436
|
||||
Ente 20017 1432602
|
||||
wei 19973 1745989
|
||||
rstoesser 19569 1293588
|
||||
eudhan 19274 1283717
|
||||
jundery 18445 1115855
|
||||
iisiraider 18247 1101015
|
||||
ville 17883 1384026
|
||||
chris 17698 1487385
|
||||
purplefishies 17595 1092533
|
||||
DMBK 17357 1279152
|
||||
DragonLord 17014 1162790
|
||||
dju 16515 929427
|
||||
IgorLeMasson 16064 1147232
|
||||
ako027ako 15671 1173203
|
||||
Nikolay.IT 15154 1068349
|
||||
Andrew Grant 15114 895539
|
||||
OssumOpossum 14857 1007129
|
||||
enedene 14476 905279
|
||||
bpfliegel 14298 884523
|
||||
jpulman 13982 870599
|
||||
joster 13794 950160
|
||||
Nesa92 13786 1114691
|
||||
crocogoat 13753 1114622
|
||||
Hjax 13535 915487
|
||||
Dark_wizzie 13422 1007152
|
||||
mpx86 12941 693640
|
||||
mabichito 12903 749391
|
||||
thijsk 12886 722107
|
||||
AdrianSA 12860 804972
|
||||
Flopzee 12698 894821
|
||||
fatmurphy 12547 853210
|
||||
scuzzi 12511 845761
|
||||
Karby 12429 735880
|
||||
SapphireBrand 12416 969604
|
||||
modolief 12386 896470
|
||||
pgontarz 12151 848794
|
||||
stocky 11954 699440
|
||||
mschmidt 11941 803401
|
||||
infinity 11470 727027
|
||||
torbjo 11395 729145
|
||||
Thomas A. Anderson 11372 732094
|
||||
d64 11263 789184
|
||||
Maxim 11129 804704
|
||||
snicolet 11106 869170
|
||||
MooTheCow 11008 694942
|
||||
savage84 10965 641068
|
||||
Rudolphous 10915 741268
|
||||
Wolfgang 10809 580032
|
||||
rpngn 10712 688203
|
||||
basepi 10637 744851
|
||||
michaelrpg 10409 735127
|
||||
dzjp 10343 732529
|
||||
ali-al-zhrani 10324 726502
|
||||
ols 10259 570669
|
||||
lbraesch 10252 647825
|
||||
Username CPU Hours Games played
|
||||
-----------------------------------------------------
|
||||
noobpwnftw 27649494 1834734733
|
||||
mlang 1426107 89454622
|
||||
dew 1380910 82831648
|
||||
mibere 703840 46867607
|
||||
grandphish2 692707 41737913
|
||||
tvijlbrief 669642 42371594
|
||||
JojoM 597778 35297180
|
||||
TueRens 519226 31823562
|
||||
cw 458421 30307421
|
||||
fastgm 439667 25950040
|
||||
gvreuls 436599 28177460
|
||||
crunchy 427035 27344275
|
||||
CSU_Dynasty 374765 25106278
|
||||
Fisherman 326901 21822979
|
||||
ctoks 325477 21767943
|
||||
velislav 295343 18844324
|
||||
linrock 292789 10624427
|
||||
bcross 278584 19488961
|
||||
okrout 262818 13803272
|
||||
pemo 245982 11376085
|
||||
glinscott 217799 13780820
|
||||
leszek 212346 12959025
|
||||
nordlandia 211692 13484886
|
||||
bking_US 198894 11876016
|
||||
drabel 196463 13450602
|
||||
robal 195473 12375650
|
||||
mgrabiak 187226 12016564
|
||||
Dantist 183202 10990484
|
||||
Thanar 179852 12365359
|
||||
vdv 175274 9889046
|
||||
spams 157128 10319326
|
||||
marrco 150295 9402141
|
||||
sqrt2 147963 9724586
|
||||
mhoram 141278 8901241
|
||||
CoffeeOne 137100 5024116
|
||||
vdbergh 137041 8926915
|
||||
malala 136182 8002293
|
||||
xoto 133702 9156676
|
||||
davar 122092 7960001
|
||||
dsmith 122059 7570238
|
||||
Data 113305 8220352
|
||||
BrunoBanani 112960 7436849
|
||||
MaZePallas 102823 6633619
|
||||
sterni1971 100532 5880772
|
||||
ElbertoOne 99028 7023771
|
||||
brabos 92118 6186135
|
||||
oz 92100 6486640
|
||||
psk 89957 5984901
|
||||
amicic 89156 5392305
|
||||
sunu 88851 6028873
|
||||
Vizvezdenec 83761 5344740
|
||||
0x3C33 82614 5271253
|
||||
BRAVONE 81239 5054681
|
||||
racerschmacer 80899 5759262
|
||||
cuistot 80300 4606144
|
||||
nssy 76497 5259388
|
||||
teddybaer 75125 5407666
|
||||
Pking_cda 73776 5293873
|
||||
jromang 72192 5057715
|
||||
solarlight 70517 5028306
|
||||
dv8silencer 70287 3883992
|
||||
Bobo1239 68515 4652287
|
||||
manap 66273 4121774
|
||||
skiminki 65088 4023328
|
||||
tinker 64333 4268790
|
||||
sschnee 60767 3500800
|
||||
qurashee 57344 3168264
|
||||
robnjr 57262 4053117
|
||||
Freja 56938 3733019
|
||||
ttruscott 56010 3680085
|
||||
rkl 55132 4164467
|
||||
renouve 53811 3501516
|
||||
finfish 51360 3370515
|
||||
eva42 51272 3599691
|
||||
rap 49985 3219146
|
||||
pb00067 49727 3298270
|
||||
ronaldjerum 47654 3240695
|
||||
bigpen0r 47653 3335327
|
||||
eastorwest 47585 3221629
|
||||
biffhero 46564 3111352
|
||||
VoyagerOne 45476 3452465
|
||||
yurikvelo 44834 3034550
|
||||
speedycpu 43842 3003273
|
||||
jbwiebe 43305 2805433
|
||||
Spprtr 42279 2680153
|
||||
DesolatedDodo 42007 2447516
|
||||
Antihistamine 41788 2761312
|
||||
mhunt 41735 2691355
|
||||
homyur 39893 2850481
|
||||
gri 39871 2515779
|
||||
Fifis 38776 2529121
|
||||
oryx 38724 2966648
|
||||
SC 37290 2731014
|
||||
csnodgrass 36207 2688994
|
||||
jmdana 36157 2210661
|
||||
strelock 34716 2074055
|
||||
rpngn 33951 2057395
|
||||
Garf 33922 2751802
|
||||
EthanOConnor 33370 2090311
|
||||
slakovv 32915 2021889
|
||||
manapbk 30987 1810399
|
||||
Prcuvu 30377 2170122
|
||||
anst 30301 2190091
|
||||
jkiiski 30136 1904470
|
||||
hyperbolic.tom 29840 2017394
|
||||
Pyafue 29650 1902349
|
||||
Wolfgang 29260 1658936
|
||||
zeryl 28156 1579911
|
||||
OuaisBla 27636 1578800
|
||||
DMBK 27051 1999456
|
||||
chriswk 26902 1868317
|
||||
achambord 26582 1767323
|
||||
Patrick_G 26276 1801617
|
||||
yorkman 26193 1992080
|
||||
SFTUser 25182 1675689
|
||||
nabildanial 24942 1519409
|
||||
Sharaf_DG 24765 1786697
|
||||
ncfish1 24411 1520927
|
||||
rodneyc 24227 1409514
|
||||
agg177 23890 1395014
|
||||
JanErik 23408 1703875
|
||||
Isidor 23388 1680691
|
||||
Norabor 23164 1591830
|
||||
cisco2015 22897 1762669
|
||||
Zirie 22542 1472937
|
||||
team-oh 22272 1636708
|
||||
MazeOfGalious 21978 1629593
|
||||
sg4032 21947 1643265
|
||||
ianh2105 21725 1632562
|
||||
xor12 21628 1680365
|
||||
dex 21612 1467203
|
||||
nesoneg 21494 1463031
|
||||
sphinx 21211 1384728
|
||||
jjoshua2 21001 1423089
|
||||
horst.prack 20878 1465656
|
||||
Ente 20865 1477066
|
||||
0xB00B1ES 20590 1208666
|
||||
j3corre 20405 941444
|
||||
Adrian.Schmidt123 20316 1281436
|
||||
wei 19973 1745989
|
||||
MaxKlaxxMiner 19850 1009176
|
||||
rstoesser 19569 1293588
|
||||
gopeto 19491 1174952
|
||||
eudhan 19274 1283717
|
||||
jundery 18445 1115855
|
||||
megaman7de 18377 1067540
|
||||
iisiraider 18247 1101015
|
||||
ville 17883 1384026
|
||||
chris 17698 1487385
|
||||
purplefishies 17595 1092533
|
||||
dju 17353 978595
|
||||
DragonLord 17014 1162790
|
||||
IgorLeMasson 16064 1147232
|
||||
ako027ako 15671 1173203
|
||||
chuckstablers 15289 891576
|
||||
Nikolay.IT 15154 1068349
|
||||
Andrew Grant 15114 895539
|
||||
OssumOpossum 14857 1007129
|
||||
Karby 14808 867120
|
||||
enedene 14476 905279
|
||||
bpfliegel 14298 884523
|
||||
mpx86 14019 759568
|
||||
jpulman 13982 870599
|
||||
crocogoat 13803 1117422
|
||||
joster 13794 950160
|
||||
Nesa92 13786 1114691
|
||||
Hjax 13535 915487
|
||||
jsys14 13459 785000
|
||||
Dark_wizzie 13422 1007152
|
||||
mabichito 12903 749391
|
||||
thijsk 12886 722107
|
||||
AdrianSA 12860 804972
|
||||
Flopzee 12698 894821
|
||||
fatmurphy 12547 853210
|
||||
Rudolphous 12520 832340
|
||||
scuzzi 12511 845761
|
||||
SapphireBrand 12416 969604
|
||||
modolief 12386 896470
|
||||
Machariel 12335 810784
|
||||
pgontarz 12151 848794
|
||||
stocky 11954 699440
|
||||
mschmidt 11941 803401
|
||||
Maxim 11543 836024
|
||||
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
|
||||
|
|
72
src/Makefile
72
src/Makefile
|
@ -31,13 +31,17 @@ PREFIX = /usr/local
|
|||
BINDIR = $(PREFIX)/bin
|
||||
|
||||
### 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
|
||||
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 \
|
||||
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))
|
||||
|
||||
|
@ -57,9 +61,11 @@ endif
|
|||
# ----------------------------------------------------------------------------
|
||||
#
|
||||
# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
|
||||
# sanitize = undefined/thread/no (-fsanitize )
|
||||
# sanitize = none/<sanitizer> ... (-fsanitize )
|
||||
# --- ( 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
|
||||
# arch = (name) --- (-arch) --- Target architecture
|
||||
# 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
|
||||
# or modifying existing flags, you have to make sure there are no extra spaces
|
||||
# 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
|
||||
|
||||
|
@ -93,7 +103,7 @@ endif
|
|||
ifeq ($(ARCH), $(filter $(ARCH), \
|
||||
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 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))
|
||||
SUPPORTED_ARCH=true
|
||||
else
|
||||
|
@ -102,7 +112,7 @@ endif
|
|||
|
||||
optimize = yes
|
||||
debug = no
|
||||
sanitize = no
|
||||
sanitize = none
|
||||
bits = 64
|
||||
prefetch = no
|
||||
popcnt = no
|
||||
|
@ -289,6 +299,17 @@ ifeq ($(ARCH),ppc-64)
|
|||
prefetch = yes
|
||||
endif
|
||||
|
||||
ifeq ($(findstring e2k,$(ARCH)),e2k)
|
||||
arch = e2k
|
||||
mmx = yes
|
||||
bits = 64
|
||||
sse = yes
|
||||
sse2 = yes
|
||||
ssse3 = yes
|
||||
sse41 = yes
|
||||
popcnt = yes
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
### ==========================================================================
|
||||
|
@ -349,7 +370,7 @@ ifeq ($(COMP),mingw)
|
|||
CXX=g++
|
||||
endif
|
||||
|
||||
CXXFLAGS += -Wextra -Wshadow
|
||||
CXXFLAGS += -pedantic -Wextra -Wshadow
|
||||
LDFLAGS += -static
|
||||
endif
|
||||
|
||||
|
@ -367,10 +388,12 @@ ifeq ($(COMP),clang)
|
|||
ifneq ($(KERNEL),Darwin)
|
||||
ifneq ($(KERNEL),OpenBSD)
|
||||
ifneq ($(KERNEL),FreeBSD)
|
||||
ifneq ($(RTLIB),compiler-rt)
|
||||
LDFLAGS += -latomic
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(arch),$(filter $(arch),armv7 armv8))
|
||||
ifeq ($(OS),Android)
|
||||
|
@ -384,8 +407,12 @@ ifeq ($(COMP),clang)
|
|||
endif
|
||||
|
||||
ifeq ($(KERNEL),Darwin)
|
||||
CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.14
|
||||
LDFLAGS += -arch $(arch) -mmacosx-version-min=10.14
|
||||
CXXFLAGS += -mmacosx-version-min=10.14
|
||||
LDFLAGS += -mmacosx-version-min=10.14
|
||||
ifneq ($(arch),any)
|
||||
CXXFLAGS += -arch $(arch)
|
||||
LDFLAGS += -arch $(arch)
|
||||
endif
|
||||
XCRUN = xcrun
|
||||
endif
|
||||
|
||||
|
@ -460,9 +487,9 @@ else
|
|||
endif
|
||||
|
||||
### 3.2.2 Debugging with undefined behavior sanitizers
|
||||
ifneq ($(sanitize),no)
|
||||
CXXFLAGS += -g3 -fsanitize=$(sanitize)
|
||||
LDFLAGS += -fsanitize=$(sanitize)
|
||||
ifneq ($(sanitize),none)
|
||||
CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize))
|
||||
LDFLAGS += $(addprefix -fsanitize=,$(sanitize))
|
||||
endif
|
||||
|
||||
### 3.3 Optimization
|
||||
|
@ -492,7 +519,7 @@ ifeq ($(bits),64)
|
|||
CXXFLAGS += -DIS_64BIT
|
||||
endif
|
||||
|
||||
### 3.5 prefetch
|
||||
### 3.5 prefetch and popcount
|
||||
ifeq ($(prefetch),yes)
|
||||
ifeq ($(sse),yes)
|
||||
CXXFLAGS += -msse
|
||||
|
@ -501,7 +528,6 @@ else
|
|||
CXXFLAGS += -DNO_PREFETCH
|
||||
endif
|
||||
|
||||
### 3.6 popcnt
|
||||
ifeq ($(popcnt),yes)
|
||||
ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64))
|
||||
CXXFLAGS += -DUSE_POPCNT
|
||||
|
@ -512,7 +538,7 @@ ifeq ($(popcnt),yes)
|
|||
endif
|
||||
endif
|
||||
|
||||
|
||||
### 3.6 SIMD architectures
|
||||
ifeq ($(avx2),yes)
|
||||
CXXFLAGS += -DUSE_AVX2
|
||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
||||
|
@ -642,6 +668,8 @@ endif
|
|||
### 3.10 MPI
|
||||
ifneq (,$(findstring mpi, $(CXX)))
|
||||
mpi = yes
|
||||
endif
|
||||
ifeq ($(mpi),yes)
|
||||
CXXFLAGS += -DUSE_MPI -Wno-cast-qual -fexceptions
|
||||
DEPENDFLAGS += -DUSE_MPI
|
||||
endif
|
||||
|
@ -687,6 +715,7 @@ help:
|
|||
@echo "armv7 > ARMv7 32-bit"
|
||||
@echo "armv7-neon > ARMv7 32-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 "general-64 > unspecified 64-bit"
|
||||
@echo "general-32 > unspecified 32-bit"
|
||||
|
@ -787,6 +816,9 @@ profileclean:
|
|||
@rm -rf profdir
|
||||
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s
|
||||
@rm -f stockfish.profdata *.profraw
|
||||
@rm -f stockfish.exe.lto_wrapper_args
|
||||
@rm -f stockfish.exe.ltrans.out
|
||||
@rm -f ./-lstdc++.res
|
||||
|
||||
default:
|
||||
help
|
||||
|
@ -830,11 +862,10 @@ config-sanity: net
|
|||
@echo "Testing config sanity. If this fails, try 'make help' ..."
|
||||
@echo ""
|
||||
@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 "$(SUPPORTED_ARCH)" = "true"
|
||||
@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 "$(bits)" = "32" || test "$(bits)" = "64"
|
||||
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
|
||||
|
@ -870,14 +901,15 @@ clang-profile-use:
|
|||
all
|
||||
|
||||
gcc-profile-make:
|
||||
@mkdir -p profdir
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
||||
EXTRACXXFLAGS='-fprofile-generate' \
|
||||
EXTRACXXFLAGS='-fprofile-generate=profdir' \
|
||||
EXTRALDFLAGS='-lgcov' \
|
||||
all
|
||||
|
||||
gcc-profile-use:
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
||||
EXTRACXXFLAGS='-fprofile-use -fno-peel-loops -fno-tracer' \
|
||||
EXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \
|
||||
EXTRALDFLAGS='-lgcov' \
|
||||
all
|
||||
|
||||
|
@ -892,7 +924,7 @@ icc-profile-use:
|
|||
EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \
|
||||
all
|
||||
|
||||
.depend:
|
||||
.depend: $(SRCS)
|
||||
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
|
||||
|
||||
-include .depend
|
||||
|
|
|
@ -150,8 +150,8 @@ namespace {
|
|||
Bitboard b = attacks_bb<KING>(ksq[stm]);
|
||||
|
||||
while (b)
|
||||
r |= stm == WHITE ? db[index(BLACK, ksq[BLACK] , pop_lsb(&b), psq)]
|
||||
: db[index(WHITE, pop_lsb(&b), ksq[WHITE], psq)];
|
||||
r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)]
|
||||
: db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)];
|
||||
|
||||
if (stm == WHITE)
|
||||
{
|
||||
|
|
|
@ -29,6 +29,7 @@ uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
|||
|
||||
Bitboard SquareBB[SQUARE_NB];
|
||||
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||
Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
||||
Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
|
||||
|
||||
|
@ -107,8 +108,14 @@ void Bitboards::init() {
|
|||
|
||||
for (PieceType pt : { BISHOP, ROOK })
|
||||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++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))
|
||||
{
|
||||
Square s = sq;
|
||||
while(safe_destination(s, d) && !(occupied & s))
|
||||
while (safe_destination(s, d) && !(occupied & s))
|
||||
attacks |= (s += d);
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ extern uint8_t PopCnt16[1 << 16];
|
|||
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
extern Bitboard SquareBB[SQUARE_NB];
|
||||
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||
extern Bitboard PseudoAttacks[PIECE_TYPE_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) {
|
||||
|
||||
assert(is_ok(s1) && is_ok(s2));
|
||||
|
||||
return LineBB[s1][s2];
|
||||
}
|
||||
|
||||
|
||||
/// between_bb() returns a bitboard representing squares that are linearly
|
||||
/// between the two given squares (excluding the given squares). If the given
|
||||
/// squares are not on a same file/rank/diagonal, we return 0. For instance,
|
||||
/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5 and E6.
|
||||
/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open
|
||||
/// segment between the squares s1 and s2 (excluding s1 but including s2). If the
|
||||
/// 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, 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) {
|
||||
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
|
||||
/// in front of the given one, from the point of view of the given color. For instance,
|
||||
/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in
|
||||
/// 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.
|
||||
|
||||
constexpr Bitboard forward_ranks_bb(Color c, Square s) {
|
||||
|
@ -414,13 +421,20 @@ inline Square msb(Bitboard b) {
|
|||
|
||||
#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
|
||||
|
||||
inline Square pop_lsb(Bitboard* b) {
|
||||
assert(*b);
|
||||
const Square s = lsb(*b);
|
||||
*b &= *b - 1;
|
||||
inline Square pop_lsb(Bitboard& b) {
|
||||
assert(b);
|
||||
const Square s = lsb(b);
|
||||
b &= b - 1;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ bool getline(std::istream& input, std::string& str) {
|
|||
|
||||
int size;
|
||||
std::vector<char> vec;
|
||||
bool state;
|
||||
int state;
|
||||
|
||||
if (is_root())
|
||||
{
|
||||
|
@ -177,7 +177,7 @@ bool getline(std::istream& input, std::string& str) {
|
|||
MPI_Bcast(vec.data(), size, MPI_CHAR, 0, InputComm);
|
||||
if (!is_root())
|
||||
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;
|
||||
}
|
||||
|
|
193
src/evaluate.cpp
193
src/evaluate.cpp
|
@ -34,6 +34,7 @@
|
|||
#include "misc.h"
|
||||
#include "pawns.h"
|
||||
#include "thread.h"
|
||||
#include "timeman.h"
|
||||
#include "uci.h"
|
||||
#include "incbin/incbin.h"
|
||||
|
||||
|
@ -55,14 +56,13 @@
|
|||
|
||||
|
||||
using namespace std;
|
||||
using namespace Stockfish::Eval::NNUE;
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
namespace Eval {
|
||||
|
||||
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
|
||||
/// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
|
||||
|
@ -79,6 +79,8 @@ namespace Eval {
|
|||
return;
|
||||
|
||||
string eval_file = string(Options["EvalFile"]);
|
||||
if (eval_file.empty())
|
||||
eval_file = EvalFileDefaultName;
|
||||
|
||||
#if defined(DEFAULT_NNUE_DIRECTORY)
|
||||
#define stringify2(x) #x
|
||||
|
@ -89,13 +91,13 @@ namespace Eval {
|
|||
#endif
|
||||
|
||||
for (string directory : dirs)
|
||||
if (eval_file_loaded != eval_file)
|
||||
if (currentEvalFileName != eval_file)
|
||||
{
|
||||
if (directory != "<internal>")
|
||||
{
|
||||
ifstream stream(directory + eval_file, ios::binary);
|
||||
if (load_eval(eval_file, stream))
|
||||
eval_file_loaded = eval_file;
|
||||
currentEvalFileName = eval_file;
|
||||
}
|
||||
|
||||
if (directory == "<internal>" && eval_file == EvalFileDefaultName)
|
||||
|
@ -110,7 +112,7 @@ namespace Eval {
|
|||
|
||||
istream stream(&buffer);
|
||||
if (load_eval(eval_file, stream))
|
||||
eval_file_loaded = eval_file;
|
||||
currentEvalFileName = eval_file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,16 +121,16 @@ namespace Eval {
|
|||
void NNUE::verify() {
|
||||
|
||||
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 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 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.";
|
||||
|
||||
sync_cout << "info string ERROR: " << msg1 << sync_endl;
|
||||
|
@ -184,7 +186,7 @@ namespace Trace {
|
|||
else
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -194,11 +196,9 @@ using namespace Trace;
|
|||
namespace {
|
||||
|
||||
// Threshold for lazy and space evaluation
|
||||
constexpr Value LazyThreshold1 = Value(1565);
|
||||
constexpr Value LazyThreshold2 = Value(1102);
|
||||
constexpr Value SpaceThreshold = Value(11551);
|
||||
constexpr Value NNUEThreshold1 = Value(682);
|
||||
constexpr Value NNUEThreshold2 = Value(176);
|
||||
constexpr Value LazyThreshold1 = Value(3130);
|
||||
constexpr Value LazyThreshold2 = Value(2204);
|
||||
constexpr Value SpaceThreshold = Value(11551);
|
||||
|
||||
// KingAttackWeights[PieceType] contains king attack weights by piece type
|
||||
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)
|
||||
};
|
||||
|
||||
constexpr Value CorneredBishop = Value(50);
|
||||
|
||||
// Assorted bonuses and penalties
|
||||
constexpr Score UncontestedOutpost = S( 1, 10);
|
||||
constexpr Score BishopOnKingRing = S( 24, 0);
|
||||
constexpr Score BishopXRayPawns = S( 4, 5);
|
||||
constexpr Score CorneredBishop = S( 50, 50);
|
||||
constexpr Score FlankAttacks = S( 8, 0);
|
||||
constexpr Score Hanging = S( 69, 36);
|
||||
constexpr Score KnightOnQueen = S( 16, 11);
|
||||
|
@ -400,8 +401,9 @@ namespace {
|
|||
|
||||
attackedBy[Us][Pt] = 0;
|
||||
|
||||
while (b1) {
|
||||
Square s = pop_lsb(&b1);
|
||||
while (b1)
|
||||
{
|
||||
Square s = pop_lsb(b1);
|
||||
|
||||
// Find attacked squares, including x-ray attacks for bishops and rooks
|
||||
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);
|
||||
if (pos.piece_on(s + d) == make_piece(Us, PAWN))
|
||||
score -= !pos.empty(s + d + pawn_push(Us)) ? CorneredBishop * 4
|
||||
: pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2
|
||||
: CorneredBishop;
|
||||
score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop)
|
||||
: 3 * make_score(CorneredBishop, CorneredBishop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -662,11 +663,11 @@ namespace {
|
|||
{
|
||||
b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]);
|
||||
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];
|
||||
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])
|
||||
score += ThreatByKing;
|
||||
|
@ -764,7 +765,7 @@ namespace {
|
|||
|
||||
while (b)
|
||||
{
|
||||
Square s = pop_lsb(&b);
|
||||
Square s = pop_lsb(b);
|
||||
|
||||
assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
|
||||
|
||||
|
@ -910,7 +911,7 @@ namespace {
|
|||
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
|
||||
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 (pos.opposite_bishops())
|
||||
|
@ -983,7 +984,7 @@ namespace {
|
|||
// Initialize score by reading the incrementally updated scores included in
|
||||
// the position object (material + piece square tables) and the material
|
||||
// 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
|
||||
pe = Pawns::probe(pos);
|
||||
|
@ -991,7 +992,7 @@ namespace {
|
|||
|
||||
// Early exit if score is high
|
||||
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))
|
||||
|
@ -1037,12 +1038,44 @@ make_v:
|
|||
v = (v / 16) * 16;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
} // 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
|
||||
|
@ -1052,37 +1085,22 @@ Value Eval::evaluate(const Position& pos) {
|
|||
|
||||
Value v;
|
||||
|
||||
if (!Eval::useNNUE)
|
||||
v = Evaluation<NO_TRACE>(pos).value();
|
||||
// Deciding between classical and NNUE eval: for high PSQ imbalance we use classical,
|
||||
// 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
|
||||
{
|
||||
// Scale and shift NNUE for compatibility with search and classical evaluation
|
||||
auto adjusted_NNUE = [&](){
|
||||
int mat = pos.non_pawn_material() + 2 * PawnValueMg * pos.count<PAWN>();
|
||||
return NNUE::evaluate(pos) * (641 + mat / 32 - 4 * pos.rule50_count()) / 1024 + Tempo;
|
||||
};
|
||||
int scale = 883
|
||||
+ 32 * pos.count<PAWN>()
|
||||
+ 32 * pos.non_pawn_material() / 1024;
|
||||
|
||||
// If there is PSQ imbalance use classical eval, with small probability if it is small
|
||||
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));
|
||||
v = NNUE::evaluate(pos, true) * scale / 1024; // NNUE
|
||||
|
||||
// Use classical evaluation for really low piece endgames.
|
||||
// The most critical case is a bishop + A/H file pawn vs naked king draw.
|
||||
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();
|
||||
if (pos.is_chess960())
|
||||
v += fix_FRC(pos);
|
||||
}
|
||||
|
||||
// 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.
|
||||
/// 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())
|
||||
return "Final evaluation: none (in check)";
|
||||
|
@ -1111,44 +1129,53 @@ std::string Eval::trace(const Position& pos) {
|
|||
|
||||
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();
|
||||
|
||||
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
|
||||
<< " Term | White | Black | Total \n"
|
||||
<< " | MG EG | MG EG | MG EG \n"
|
||||
<< " ------------+-------------+-------------+------------\n"
|
||||
<< " Material | " << Term(MATERIAL)
|
||||
<< " Imbalance | " << Term(IMBALANCE)
|
||||
<< " Pawns | " << Term(PAWN)
|
||||
<< " Knights | " << Term(KNIGHT)
|
||||
<< " Bishops | " << Term(BISHOP)
|
||||
<< " Rooks | " << Term(ROOK)
|
||||
<< " Queens | " << Term(QUEEN)
|
||||
<< " Mobility | " << Term(MOBILITY)
|
||||
<< " King safety | " << Term(KING)
|
||||
<< " Threats | " << Term(THREAT)
|
||||
<< " Passed | " << Term(PASSED)
|
||||
<< " Space | " << Term(SPACE)
|
||||
<< " Winnable | " << Term(WINNABLE)
|
||||
<< " ------------+-------------+-------------+------------\n"
|
||||
<< " Total | " << Term(TOTAL);
|
||||
|
||||
v = pos.side_to_move() == WHITE ? v : -v;
|
||||
|
||||
ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n";
|
||||
<< " Contributing terms for the classical eval:\n"
|
||||
<< "+------------+-------------+-------------+-------------+\n"
|
||||
<< "| Term | White | Black | Total |\n"
|
||||
<< "| | MG EG | MG EG | MG EG |\n"
|
||||
<< "+------------+-------------+-------------+-------------+\n"
|
||||
<< "| Material | " << Term(MATERIAL)
|
||||
<< "| Imbalance | " << Term(IMBALANCE)
|
||||
<< "| Pawns | " << Term(PAWN)
|
||||
<< "| Knights | " << Term(KNIGHT)
|
||||
<< "| Bishops | " << Term(BISHOP)
|
||||
<< "| Rooks | " << Term(ROOK)
|
||||
<< "| Queens | " << Term(QUEEN)
|
||||
<< "| Mobility | " << Term(MOBILITY)
|
||||
<< "|King safety | " << Term(KING)
|
||||
<< "| Threats | " << Term(THREAT)
|
||||
<< "| Passed | " << Term(PASSED)
|
||||
<< "| Space | " << Term(SPACE)
|
||||
<< "| Winnable | " << Term(WINNABLE)
|
||||
<< "+------------+-------------+-------------+-------------+\n"
|
||||
<< "| Total | " << Term(TOTAL)
|
||||
<< "+------------+-------------+-------------+-------------+\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)
|
||||
{
|
||||
v = NNUE::evaluate(pos);
|
||||
v = NNUE::evaluate(pos, false);
|
||||
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 = 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();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#define EVALUATE_H_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
|
@ -29,24 +30,29 @@ class Position;
|
|||
|
||||
namespace Eval {
|
||||
|
||||
std::string trace(const Position& pos);
|
||||
std::string trace(Position& pos);
|
||||
Value evaluate(const Position& pos);
|
||||
|
||||
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
|
||||
// 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.
|
||||
#define EvalFileDefaultName "nn-62ef826d1a6d.nnue"
|
||||
#define EvalFileDefaultName "nn-13406b1dcbe0.nnue"
|
||||
|
||||
namespace NNUE {
|
||||
|
||||
Value evaluate(const Position& pos);
|
||||
bool load_eval(std::string name, std::istream& stream);
|
||||
std::string trace(Position& pos);
|
||||
Value evaluate(const Position& pos, bool adjusted = false);
|
||||
|
||||
void init();
|
||||
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 Eval
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace {
|
|||
|
||||
bool is_KBPsK(const Position& pos, Color us) {
|
||||
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) {
|
||||
|
|
54
src/misc.cpp
54
src/misc.cpp
|
@ -51,7 +51,7 @@ typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
|
|||
#include <sys/mman.h>
|
||||
#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
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
@ -67,7 +67,7 @@ namespace {
|
|||
|
||||
/// Version number. If Version is left empty, then compile date in the format
|
||||
/// 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
|
||||
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
||||
|
@ -110,7 +110,14 @@ public:
|
|||
|
||||
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);
|
||||
|
||||
|
@ -123,12 +130,6 @@ public:
|
|||
cin.rdbuf(&l.in);
|
||||
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 += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
|
||||
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__
|
||||
compiler += "g++ (GNUC) ";
|
||||
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.
|
||||
|
||||
#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 { };
|
||||
LUID luid { };
|
||||
|
@ -406,21 +424,18 @@ static void* aligned_large_pages_alloc_win(size_t allocSize) {
|
|||
CloseHandle(hProcessToken);
|
||||
|
||||
return mem;
|
||||
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void* aligned_large_pages_alloc(size_t allocSize) {
|
||||
|
||||
#if defined(_WIN64)
|
||||
// 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
|
||||
if (!mem)
|
||||
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;
|
||||
}
|
||||
|
@ -456,8 +471,9 @@ void aligned_large_pages_free(void* mem) {
|
|||
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
|
||||
{
|
||||
DWORD err = GetLastError();
|
||||
std::cerr << "Failed to free transposition table. Error code: 0x" <<
|
||||
std::hex << err << std::dec << std::endl;
|
||||
std::cerr << "Failed to free large page memory. Error code: 0x"
|
||||
<< std::hex << err
|
||||
<< std::dec << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
|
66
src/misc.h
66
src/misc.h
|
@ -66,9 +66,10 @@ std::ostream& operator<<(std::ostream&, SyncCout);
|
|||
#define sync_cout std::cout << IO_LOCK
|
||||
#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
|
||||
// number of elements in the array.
|
||||
|
||||
// align_ptr_up() : get the first aligned element of an 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>
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
/// This class is based on original code written and dedicated
|
||||
/// to the public domain by Sebastiano Vigna (2014).
|
||||
|
|
219
src/movegen.cpp
219
src/movegen.cpp
|
@ -26,21 +26,16 @@ namespace Stockfish {
|
|||
namespace {
|
||||
|
||||
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)
|
||||
{
|
||||
*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)
|
||||
{
|
||||
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
|
||||
*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;
|
||||
|
@ -57,20 +52,16 @@ namespace {
|
|||
constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
|
||||
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
|
||||
|
||||
const Square ksq = pos.square<KING>(Them);
|
||||
Bitboard emptySquares;
|
||||
const Bitboard emptySquares = ~pos.pieces();
|
||||
const Bitboard enemies = Type == EVASIONS ? pos.checkers()
|
||||
: pos.pieces(Them);
|
||||
|
||||
Bitboard pawnsOn7 = 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
|
||||
if (Type != CAPTURES)
|
||||
{
|
||||
emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces());
|
||||
|
||||
Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
|
||||
Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
|
||||
|
||||
|
@ -82,33 +73,24 @@ namespace {
|
|||
|
||||
if (Type == QUIET_CHECKS)
|
||||
{
|
||||
b1 &= pawn_attacks_bb(Them, ksq);
|
||||
b2 &= pawn_attacks_bb(Them, ksq);
|
||||
|
||||
// Add pawn pushes which give discovered check. This is possible only
|
||||
// if the pawn is not on the same file as the enemy king, because we
|
||||
// don't generate captures. Note that a possible discovered check
|
||||
// promotion has been already generated amongst the captures.
|
||||
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;
|
||||
}
|
||||
// To make a quiet check, you either make a direct check by pushing a pawn
|
||||
// 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.
|
||||
Square ksq = pos.square<KING>(Them);
|
||||
Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq);
|
||||
b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns);
|
||||
b2 &= pawn_attacks_bb(Them, ksq) | shift<Up+Up>(dcCandidatePawns);
|
||||
}
|
||||
|
||||
while (b1)
|
||||
{
|
||||
Square to = pop_lsb(&b1);
|
||||
Square to = pop_lsb(b1);
|
||||
*moveList++ = make_move(to - Up, to);
|
||||
}
|
||||
|
||||
while (b2)
|
||||
{
|
||||
Square to = pop_lsb(&b2);
|
||||
Square to = pop_lsb(b2);
|
||||
*moveList++ = make_move(to - Up - Up, to);
|
||||
}
|
||||
}
|
||||
|
@ -116,24 +98,21 @@ namespace {
|
|||
// Promotions and underpromotions
|
||||
if (pawnsOn7)
|
||||
{
|
||||
if (Type == CAPTURES)
|
||||
emptySquares = ~pos.pieces();
|
||||
|
||||
if (Type == EVASIONS)
|
||||
emptySquares &= target;
|
||||
|
||||
Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
|
||||
Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
|
||||
Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
|
||||
|
||||
if (Type == EVASIONS)
|
||||
b3 &= target;
|
||||
|
||||
while (b1)
|
||||
moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(&b1), ksq);
|
||||
moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(b1));
|
||||
|
||||
while (b2)
|
||||
moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(&b2), ksq);
|
||||
moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(b2));
|
||||
|
||||
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
|
||||
|
@ -144,13 +123,13 @@ namespace {
|
|||
|
||||
while (b1)
|
||||
{
|
||||
Square to = pop_lsb(&b1);
|
||||
Square to = pop_lsb(b1);
|
||||
*moveList++ = make_move(to - UpRight, to);
|
||||
}
|
||||
|
||||
while (b2)
|
||||
{
|
||||
Square to = pop_lsb(&b2);
|
||||
Square to = pop_lsb(b2);
|
||||
*moveList++ = make_move(to - UpLeft, to);
|
||||
}
|
||||
|
||||
|
@ -158,7 +137,7 @@ namespace {
|
|||
{
|
||||
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)))
|
||||
return moveList;
|
||||
|
||||
|
@ -167,7 +146,7 @@ namespace {
|
|||
assert(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>
|
||||
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard piecesToMove, Bitboard target) {
|
||||
template<Color Us, PieceType Pt, bool Checks>
|
||||
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
|
||||
|
||||
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
|
||||
|
||||
Bitboard bb = piecesToMove & pos.pieces(Pt);
|
||||
|
||||
if (!bb)
|
||||
return moveList;
|
||||
|
||||
[[maybe_unused]] const Bitboard checkSquares = pos.check_squares(Pt);
|
||||
|
||||
while (bb) {
|
||||
Square from = pop_lsb(&bb);
|
||||
Bitboard bb = pos.pieces(Us, Pt);
|
||||
|
||||
while (bb)
|
||||
{
|
||||
Square from = pop_lsb(bb);
|
||||
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)
|
||||
*moveList++ = make_move(from, pop_lsb(&b));
|
||||
*moveList++ = make_move(from, pop_lsb(b));
|
||||
}
|
||||
|
||||
return moveList;
|
||||
|
@ -208,42 +184,34 @@ namespace {
|
|||
static_assert(Type != LEGAL, "Unsupported type in generate_all()");
|
||||
|
||||
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)
|
||||
piecesToMove &= ~pos.blockers_for_king(~Us);
|
||||
|
||||
switch (Type)
|
||||
// Skip generating non-king moves when in double check
|
||||
if (Type != EVASIONS || !more_than_one(pos.checkers()))
|
||||
{
|
||||
case CAPTURES:
|
||||
target = pos.pieces(~Us);
|
||||
break;
|
||||
case QUIETS:
|
||||
case QUIET_CHECKS:
|
||||
target = ~pos.pieces();
|
||||
break;
|
||||
case EVASIONS:
|
||||
target = between_bb(pos.square<KING>(Us), lsb(pos.checkers())) | pos.checkers();
|
||||
break;
|
||||
case NON_EVASIONS:
|
||||
target = ~pos.pieces(Us);
|
||||
break;
|
||||
target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers()))
|
||||
: Type == NON_EVASIONS ? ~pos.pieces( Us)
|
||||
: Type == CAPTURES ? pos.pieces(~Us)
|
||||
: ~pos.pieces( ); // QUIETS || QUIET_CHECKS
|
||||
|
||||
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
|
||||
moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
|
||||
moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
|
||||
moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
|
||||
moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
|
||||
}
|
||||
|
||||
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
|
||||
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)
|
||||
if (!Checks || pos.blockers_for_king(~Us) & ksq)
|
||||
{
|
||||
Square ksq = pos.square<KING>(Us);
|
||||
Bitboard b = attacks_bb<KING>(ksq) & target;
|
||||
while (b)
|
||||
*moveList++ = make_move(ksq, pop_lsb(&b));
|
||||
Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
|
||||
if (Checks)
|
||||
b &= ~attacks_bb<QUEEN>(pos.square<KING>(~Us));
|
||||
|
||||
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 } )
|
||||
if (!pos.castling_impeded(cr) && pos.can_castle(cr))
|
||||
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
|
||||
|
@ -255,8 +223,10 @@ namespace {
|
|||
} // namespace
|
||||
|
||||
|
||||
/// <CAPTURES> Generates all pseudo-legal captures plus queen and checking knight promotions
|
||||
/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions (except checking knight)
|
||||
/// <CAPTURES> Generates all pseudo-legal captures plus queen promotions
|
||||
/// <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
|
||||
///
|
||||
/// Returns a pointer to the end of the move list.
|
||||
|
@ -264,8 +234,8 @@ namespace {
|
|||
template<GenType Type>
|
||||
ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
||||
|
||||
static_assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS, "Unsupported type in generate()");
|
||||
assert(!pos.checkers());
|
||||
static_assert(Type != LEGAL, "Unsupported type in generate()");
|
||||
assert((Type == EVASIONS) == (bool)pos.checkers());
|
||||
|
||||
Color us = pos.side_to_move();
|
||||
|
||||
|
@ -276,70 +246,11 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
|||
// Explicit template instantiations
|
||||
template ExtMove* generate<CAPTURES>(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*);
|
||||
|
||||
|
||||
/// 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
|
||||
|
||||
template<>
|
||||
|
|
|
@ -111,7 +111,7 @@ void MovePicker::score() {
|
|||
+ (*continuationHistory[1])[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)]
|
||||
+ (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
|
||||
{
|
||||
|
|
|
@ -86,11 +86,11 @@ enum StatsType { NoCaptures, Captures };
|
|||
/// unsuccessful during the current search, and is used for reduction and move
|
||||
/// 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
|
||||
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
|
||||
/// and quiet moves which are/were in the PV (ttPv). It is cleared with each new
|
||||
/// search and filled during iterative deepening.
|
||||
/// and quiet moves which are/were in the PV (ttPv). LowPlyHistory is populated during
|
||||
/// iterative deepening and at each new search the data is shifted down by 2 plies
|
||||
constexpr int MAX_LPH = 4;
|
||||
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
|
|
@ -20,6 +20,9 @@
|
|||
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
|
||||
#include "../evaluate.h"
|
||||
#include "../position.h"
|
||||
|
@ -32,26 +35,27 @@
|
|||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
// Input feature converter
|
||||
LargePagePtr<FeatureTransformer> feature_transformer;
|
||||
LargePagePtr<FeatureTransformer> featureTransformer;
|
||||
|
||||
// Evaluation function
|
||||
AlignedPtr<Network> network;
|
||||
AlignedPtr<Network> network[LayerStacks];
|
||||
|
||||
// Evaluation function file name
|
||||
std::string fileName;
|
||||
std::string netDescription;
|
||||
|
||||
namespace Detail {
|
||||
|
||||
// Initialize the evaluation function parameters
|
||||
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))));
|
||||
std::memset(pointer.get(), 0, sizeof(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");
|
||||
pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
|
||||
|
@ -60,85 +64,352 @@ namespace Stockfish::Eval::NNUE {
|
|||
|
||||
// Read evaluation function parameters
|
||||
template <typename T>
|
||||
bool ReadParameters(std::istream& stream, T& reference) {
|
||||
bool read_parameters(std::istream& stream, T& reference) {
|
||||
|
||||
std::uint32_t header;
|
||||
header = read_little_endian<std::uint32_t>(stream);
|
||||
if (!stream || header != T::GetHashValue()) return false;
|
||||
return reference.ReadParameters(stream);
|
||||
if (!stream || header != T::get_hash_value()) return false;
|
||||
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
|
||||
|
||||
// Initialize the evaluation function parameters
|
||||
void Initialize() {
|
||||
void initialize() {
|
||||
|
||||
Detail::Initialize(feature_transformer);
|
||||
Detail::Initialize(network);
|
||||
Detail::initialize(featureTransformer);
|
||||
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||
Detail::initialize(network[i]);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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);
|
||||
if (!stream || version != kVersion) return false;
|
||||
architecture->resize(size);
|
||||
stream.read(&(*architecture)[0], size);
|
||||
if (!stream || version != Version) return false;
|
||||
desc->resize(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();
|
||||
}
|
||||
|
||||
// Read network parameters
|
||||
bool ReadParameters(std::istream& stream) {
|
||||
bool read_parameters(std::istream& stream) {
|
||||
|
||||
std::uint32_t hash_value;
|
||||
std::string architecture;
|
||||
if (!ReadHeader(stream, &hash_value, &architecture)) return false;
|
||||
if (hash_value != kHashValue) return false;
|
||||
if (!Detail::ReadParameters(stream, *feature_transformer)) return false;
|
||||
if (!Detail::ReadParameters(stream, *network)) return false;
|
||||
std::uint32_t hashValue;
|
||||
if (!read_header(stream, &hashValue, &netDescription)) return false;
|
||||
if (hashValue != HashValue) return false;
|
||||
if (!Detail::read_parameters(stream, *featureTransformer)) return false;
|
||||
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||
if (!Detail::read_parameters(stream, *(network[i]))) return false;
|
||||
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.
|
||||
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
|
||||
// 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)
|
||||
TransformedFeatureType transformed_features_unaligned[
|
||||
FeatureTransformer::kBufferSize + alignment / sizeof(TransformedFeatureType)];
|
||||
char buffer_unaligned[Network::kBufferSize + alignment];
|
||||
TransformedFeatureType transformedFeaturesUnaligned[
|
||||
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
|
||||
char bufferUnaligned[Network::BufferSize + alignment];
|
||||
|
||||
auto* transformed_features = align_ptr_up<alignment>(&transformed_features_unaligned[0]);
|
||||
auto* buffer = align_ptr_up<alignment>(&buffer_unaligned[0]);
|
||||
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
||||
auto* buffer = align_ptr_up<alignment>(&bufferUnaligned[0]);
|
||||
#else
|
||||
alignas(alignment)
|
||||
TransformedFeatureType transformed_features[FeatureTransformer::kBufferSize];
|
||||
alignas(alignment) char buffer[Network::kBufferSize];
|
||||
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
|
||||
alignas(alignment) char buffer[Network::BufferSize];
|
||||
#endif
|
||||
|
||||
ASSERT_ALIGNED(transformed_features, alignment);
|
||||
ASSERT_ALIGNED(transformedFeatures, alignment);
|
||||
ASSERT_ALIGNED(buffer, alignment);
|
||||
|
||||
feature_transformer->Transform(pos, transformed_features);
|
||||
const auto output = network->Propagate(transformed_features, buffer);
|
||||
const std::size_t bucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
||||
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
|
||||
bool load_eval(std::string name, std::istream& stream) {
|
||||
|
||||
Initialize();
|
||||
initialize();
|
||||
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
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
// Hash value of evaluation function structure
|
||||
constexpr std::uint32_t kHashValue =
|
||||
FeatureTransformer::GetHashValue() ^ Network::GetHashValue();
|
||||
constexpr std::uint32_t HashValue =
|
||||
FeatureTransformer::get_hash_value() ^ Network::get_hash_value();
|
||||
|
||||
// Deleter for automating release of memory area
|
||||
template <typename T>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -22,13 +22,141 @@
|
|||
#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#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 {
|
||||
|
||||
// Affine transformation layer
|
||||
template <typename PreviousLayer, IndexType OutputDimensions>
|
||||
class AffineTransform {
|
||||
// Fallback implementation for older/other architectures.
|
||||
// Identical for both approaches. Requires the input to be padded to at least 16 values.
|
||||
#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:
|
||||
// Input/output type
|
||||
using InputType = typename PreviousLayer::OutputType;
|
||||
|
@ -36,396 +164,197 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||
static_assert(std::is_same<InputType, std::uint8_t>::value, "");
|
||||
|
||||
// Number of input/output dimensions
|
||||
static constexpr IndexType kInputDimensions =
|
||||
PreviousLayer::kOutputDimensions;
|
||||
static constexpr IndexType kOutputDimensions = OutputDimensions;
|
||||
static constexpr IndexType kPaddedInputDimensions =
|
||||
CeilToMultiple<IndexType>(kInputDimensions, kMaxSimdWidth);
|
||||
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.");
|
||||
|
||||
#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)
|
||||
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
|
||||
|
||||
// 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
|
||||
static constexpr std::size_t kSelfBufferSize =
|
||||
CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize);
|
||||
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 kBufferSize =
|
||||
PreviousLayer::kBufferSize + kSelfBufferSize;
|
||||
static constexpr std::size_t BufferSize =
|
||||
PreviousLayer::BufferSize + SelfBufferSize;
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t GetHashValue() {
|
||||
std::uint32_t hash_value = 0xCC03DAE4u;
|
||||
hash_value += kOutputDimensions;
|
||||
hash_value ^= PreviousLayer::GetHashValue() >> 1;
|
||||
hash_value ^= PreviousLayer::GetHashValue() << 31;
|
||||
return hash_value;
|
||||
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;
|
||||
}
|
||||
|
||||
// Read network parameters
|
||||
bool ReadParameters(std::istream& stream) {
|
||||
if (!previous_layer_.ReadParameters(stream)) return false;
|
||||
for (std::size_t i = 0; i < kOutputDimensions; ++i)
|
||||
biases_[i] = read_little_endian<BiasType>(stream);
|
||||
for (std::size_t i = 0; i < kOutputDimensions * kPaddedInputDimensions; ++i)
|
||||
#if !defined (USE_SSSE3)
|
||||
weights_[i] = read_little_endian<WeightType>(stream);
|
||||
#else
|
||||
weights_[
|
||||
(i / 4) % (kPaddedInputDimensions / 4) * kOutputDimensions * 4 +
|
||||
i / kPaddedInputDimensions * 4 +
|
||||
i % 4
|
||||
] = read_little_endian<WeightType>(stream);
|
||||
/*
|
||||
Transposes the small blocks within a block.
|
||||
Effectively means that weights can be traversed sequentially during inference.
|
||||
*/
|
||||
static IndexType get_weight_index(IndexType i)
|
||||
{
|
||||
const IndexType smallBlock = (i / SmallBlockSize) % NumSmallBlocksInBigBlock;
|
||||
const IndexType smallBlockCol = smallBlock / NumSmallBlocksPerOutput;
|
||||
const IndexType smallBlockRow = smallBlock % NumSmallBlocksPerOutput;
|
||||
const IndexType bigBlock = i / BigBlockSize;
|
||||
const IndexType rest = i % SmallBlockSize;
|
||||
|
||||
// Determine if eights of weight and input products can be summed using 16bits
|
||||
// without saturation. We assume worst case combinations of 0 and 127 for all inputs.
|
||||
if (kOutputDimensions > 1 && !stream.fail())
|
||||
{
|
||||
canSaturate16.count = 0;
|
||||
#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];
|
||||
}
|
||||
const IndexType idx =
|
||||
bigBlock * BigBlockSize
|
||||
+ smallBlockRow * SmallBlockSize * NumOutputRegs
|
||||
+ smallBlockCol * SmallBlockSize
|
||||
+ rest;
|
||||
|
||||
IndexType idx = maxK / 2 * kOutputDimensions * 4 + maxK % 2;
|
||||
sum[sign == -1] -= w[idx];
|
||||
canSaturate16.add(j, i + maxK / 2 * 4 + maxK % 2 + x * 2, w[idx]);
|
||||
w[idx] = 0;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Non functional optimization for faster more linear access
|
||||
std::sort(canSaturate16.ids, canSaturate16.ids + canSaturate16.count,
|
||||
[](const typename CanSaturate::Entry& e1, const typename CanSaturate::Entry& e2)
|
||||
{ return e1.in == e2.in ? e1.out < e2.out : e1.in < e2.in; });
|
||||
#endif
|
||||
}
|
||||
#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* transformed_features, char* buffer) const {
|
||||
const auto input = previous_layer_.Propagate(
|
||||
transformed_features, buffer + kSelfBufferSize);
|
||||
|
||||
#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
|
||||
const OutputType* propagate(
|
||||
const TransformedFeatureType* transformedFeatures, char* buffer) const {
|
||||
const auto input = previousLayer.propagate(
|
||||
transformedFeatures, buffer + SelfBufferSize);
|
||||
OutputType* output = reinterpret_cast<OutputType*>(buffer);
|
||||
|
||||
#if defined (USE_AVX512)
|
||||
using vec_t = __m512i;
|
||||
#define vec_setzero _mm512_setzero_si512
|
||||
#define vec_set_32 _mm512_set1_epi32
|
||||
auto& vec_add_dpbusd_32 = m512_add_dpbusd_epi32;
|
||||
auto& vec_add_dpbusd_32x4 = m512_add_dpbusd_epi32x4;
|
||||
auto& vec_hadd = m512_hadd;
|
||||
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2
|
||||
#define vec_hadd Simd::m512_hadd
|
||||
#define vec_haddx4 Simd::m512_haddx4
|
||||
#elif defined (USE_AVX2)
|
||||
using vec_t = __m256i;
|
||||
#define vec_setzero _mm256_setzero_si256
|
||||
#define vec_set_32 _mm256_set1_epi32
|
||||
auto& vec_add_dpbusd_32 = m256_add_dpbusd_epi32;
|
||||
auto& vec_add_dpbusd_32x4 = m256_add_dpbusd_epi32x4;
|
||||
auto& vec_hadd = m256_hadd;
|
||||
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
|
||||
#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
|
||||
auto& vec_add_dpbusd_32 = m128_add_dpbusd_epi32;
|
||||
auto& vec_add_dpbusd_32x4 = m128_add_dpbusd_epi32x4;
|
||||
auto& vec_hadd = m128_hadd;
|
||||
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
|
||||
#define vec_hadd Simd::m128_hadd
|
||||
#define vec_haddx4 Simd::m128_haddx4
|
||||
#endif
|
||||
|
||||
#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);
|
||||
|
||||
// kOutputDimensions is either 1 or a multiple of kSimdWidth
|
||||
// because then it is also an input dimension.
|
||||
if constexpr (kOutputDimensions % kOutputSimdWidth == 0)
|
||||
// Perform accumulation to registers for each big block
|
||||
for (IndexType bigBlock = 0; bigBlock < NumBigBlocks; ++bigBlock)
|
||||
{
|
||||
constexpr IndexType kNumChunks = kPaddedInputDimensions / 4;
|
||||
vec_t acc[NumOutputRegs] = { vec_setzero() };
|
||||
|
||||
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
|
||||
vec_t* outptr = reinterpret_cast<vec_t*>(output);
|
||||
std::memcpy(output, biases_, kOutputDimensions * sizeof(OutputType));
|
||||
// Each big block has NumOutputRegs small blocks in each "row", one per register.
|
||||
// We process two small blocks at a time to save on one addition without VNNI.
|
||||
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 vec_t in1 = vec_set_32(input32[i + 1]);
|
||||
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]);
|
||||
const IndexType idx = (bigBlock * NumOutputRegs + k) / 4;
|
||||
outputvec[idx] = vec_haddx4(acc[k+0], acc[k+1], acc[k+2], acc[k+3], biasvec[idx]);
|
||||
}
|
||||
for (int i = 0; i < canSaturate16.count; ++i)
|
||||
output[canSaturate16.ids[i].out] += input[canSaturate16.ids[i].in] * canSaturate16.ids[i].w;
|
||||
}
|
||||
else if constexpr (kOutputDimensions == 1)
|
||||
{
|
||||
#if defined (USE_AVX512)
|
||||
if constexpr (kPaddedInputDimensions % (kSimdWidth * 2) != 0)
|
||||
}
|
||||
else
|
||||
{
|
||||
for (IndexType k = 0; k < NumOutputRegs; ++k)
|
||||
{
|
||||
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
|
||||
const auto input_vector256 = reinterpret_cast<const __m256i*>(input);
|
||||
|
||||
__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]);
|
||||
const IndexType idx = (bigBlock * NumOutputRegs + k);
|
||||
output[idx] = vec_hadd(acc[k], biases[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# undef vec_setzero
|
||||
# undef vec_set_32
|
||||
# undef vec_add_dpbusd_32
|
||||
# undef vec_add_dpbusd_32x2
|
||||
# undef vec_hadd
|
||||
# undef vec_haddx4
|
||||
#else
|
||||
|
||||
// Use old implementation for the other architectures.
|
||||
|
||||
auto output = reinterpret_cast<OutputType*>(buffer);
|
||||
|
||||
#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
|
||||
// Use old implementation for the other architectures.
|
||||
affine_transform_non_ssse3<
|
||||
InputDimensions,
|
||||
PaddedInputDimensions,
|
||||
OutputDimensions>(output, weights, biases, input);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -436,27 +365,188 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||
using BiasType = OutputType;
|
||||
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)
|
||||
struct CanSaturate {
|
||||
int count;
|
||||
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;
|
||||
static constexpr const IndexType OutputSimdWidth = SimdWidth / 4;
|
||||
static constexpr const IndexType InputSimdWidth = SimdWidth;
|
||||
#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
|
||||
|
|
|
@ -35,130 +35,165 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||
static_assert(std::is_same<InputType, std::int32_t>::value, "");
|
||||
|
||||
// Number of input/output dimensions
|
||||
static constexpr IndexType kInputDimensions =
|
||||
PreviousLayer::kOutputDimensions;
|
||||
static constexpr IndexType kOutputDimensions = kInputDimensions;
|
||||
static constexpr IndexType InputDimensions = PreviousLayer::OutputDimensions;
|
||||
static constexpr IndexType OutputDimensions = InputDimensions;
|
||||
static constexpr IndexType PaddedOutputDimensions =
|
||||
ceil_to_multiple<IndexType>(OutputDimensions, 32);
|
||||
|
||||
// Size of forward propagation buffer used in this layer
|
||||
static constexpr std::size_t kSelfBufferSize =
|
||||
CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize);
|
||||
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 kBufferSize =
|
||||
PreviousLayer::kBufferSize + kSelfBufferSize;
|
||||
static constexpr std::size_t BufferSize =
|
||||
PreviousLayer::BufferSize + SelfBufferSize;
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t GetHashValue() {
|
||||
std::uint32_t hash_value = 0x538D24C7u;
|
||||
hash_value += PreviousLayer::GetHashValue();
|
||||
return hash_value;
|
||||
static constexpr std::uint32_t get_hash_value() {
|
||||
std::uint32_t hashValue = 0x538D24C7u;
|
||||
hashValue += PreviousLayer::get_hash_value();
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
// Read network parameters
|
||||
bool ReadParameters(std::istream& stream) {
|
||||
return previous_layer_.ReadParameters(stream);
|
||||
bool read_parameters(std::istream& stream) {
|
||||
return previousLayer.read_parameters(stream);
|
||||
}
|
||||
|
||||
// Write network parameters
|
||||
bool write_parameters(std::ostream& stream) const {
|
||||
return previousLayer.write_parameters(stream);
|
||||
}
|
||||
|
||||
// Forward propagation
|
||||
const OutputType* Propagate(
|
||||
const TransformedFeatureType* transformed_features, char* buffer) const {
|
||||
const auto input = previous_layer_.Propagate(
|
||||
transformed_features, buffer + kSelfBufferSize);
|
||||
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)
|
||||
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
|
||||
const __m256i kZero = _mm256_setzero_si256();
|
||||
const __m256i kOffsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
|
||||
const auto in = reinterpret_cast<const __m256i*>(input);
|
||||
const auto out = reinterpret_cast<__m256i*>(output);
|
||||
for (IndexType i = 0; i < kNumChunks; ++i) {
|
||||
const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
|
||||
_mm256_load_si256(&in[i * 4 + 0]),
|
||||
_mm256_load_si256(&in[i * 4 + 1])), kWeightScaleBits);
|
||||
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
|
||||
_mm256_load_si256(&in[i * 4 + 2]),
|
||||
_mm256_load_si256(&in[i * 4 + 3])), kWeightScaleBits);
|
||||
_mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
|
||||
_mm256_packs_epi16(words0, words1), kZero), kOffsets));
|
||||
if constexpr (InputDimensions % SimdWidth == 0) {
|
||||
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||
const __m256i Zero = _mm256_setzero_si256();
|
||||
const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
|
||||
const auto in = reinterpret_cast<const __m256i*>(input);
|
||||
const auto out = reinterpret_cast<__m256i*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i) {
|
||||
const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
|
||||
_mm256_load_si256(&in[i * 4 + 0]),
|
||||
_mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits);
|
||||
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
|
||||
_mm256_load_si256(&in[i * 4 + 2]),
|
||||
_mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits);
|
||||
_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)
|
||||
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
|
||||
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||
|
||||
#ifdef USE_SSE41
|
||||
const __m128i kZero = _mm_setzero_si128();
|
||||
const __m128i Zero = _mm_setzero_si128();
|
||||
#else
|
||||
const __m128i k0x80s = _mm_set1_epi8(-128);
|
||||
#endif
|
||||
|
||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||
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(
|
||||
_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(
|
||||
_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);
|
||||
_mm_store_si128(&out[i],
|
||||
|
||||
#ifdef USE_SSE41
|
||||
_mm_max_epi8(packedbytes, kZero)
|
||||
_mm_max_epi8(packedbytes, Zero)
|
||||
#else
|
||||
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
||||
#endif
|
||||
|
||||
);
|
||||
}
|
||||
constexpr IndexType kStart = kNumChunks * kSimdWidth;
|
||||
constexpr IndexType Start = NumChunks * SimdWidth;
|
||||
|
||||
#elif defined(USE_MMX)
|
||||
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
|
||||
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||
const __m64 k0x80s = _mm_set1_pi8(-128);
|
||||
const auto in = reinterpret_cast<const __m64*>(input);
|
||||
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(
|
||||
_mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]),
|
||||
kWeightScaleBits);
|
||||
WeightScaleBits);
|
||||
const __m64 words1 = _mm_srai_pi16(
|
||||
_mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]),
|
||||
kWeightScaleBits);
|
||||
WeightScaleBits);
|
||||
const __m64 packedbytes = _mm_packs_pi16(words0, words1);
|
||||
out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
|
||||
}
|
||||
_mm_empty();
|
||||
constexpr IndexType kStart = kNumChunks * kSimdWidth;
|
||||
constexpr IndexType Start = NumChunks * SimdWidth;
|
||||
|
||||
#elif defined(USE_NEON)
|
||||
constexpr IndexType kNumChunks = kInputDimensions / (kSimdWidth / 2);
|
||||
const int8x8_t kZero = {0};
|
||||
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
||||
const int8x8_t Zero = {0};
|
||||
const auto in = reinterpret_cast<const int32x4_t*>(input);
|
||||
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;
|
||||
const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
|
||||
pack[0] = vqshrn_n_s32(in[i * 2 + 0], kWeightScaleBits);
|
||||
pack[1] = vqshrn_n_s32(in[i * 2 + 1], kWeightScaleBits);
|
||||
out[i] = vmax_s8(vqmovn_s16(shifted), kZero);
|
||||
pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits);
|
||||
pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits);
|
||||
out[i] = vmax_s8(vqmovn_s16(shifted), Zero);
|
||||
}
|
||||
constexpr IndexType kStart = kNumChunks * (kSimdWidth / 2);
|
||||
constexpr IndexType Start = NumChunks * (SimdWidth / 2);
|
||||
#else
|
||||
constexpr IndexType kStart = 0;
|
||||
constexpr IndexType Start = 0;
|
||||
#endif
|
||||
|
||||
for (IndexType i = kStart; i < kInputDimensions; ++i) {
|
||||
for (IndexType i = Start; i < InputDimensions; ++i) {
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
PreviousLayer previous_layer_;
|
||||
PreviousLayer previousLayer;
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE::Layers
|
||||
|
|
|
@ -26,38 +26,43 @@
|
|||
namespace Stockfish::Eval::NNUE::Layers {
|
||||
|
||||
// Input layer
|
||||
template <IndexType OutputDimensions, IndexType Offset = 0>
|
||||
template <IndexType OutDims, IndexType Offset = 0>
|
||||
class InputSlice {
|
||||
public:
|
||||
// Need to maintain alignment
|
||||
static_assert(Offset % kMaxSimdWidth == 0, "");
|
||||
static_assert(Offset % MaxSimdWidth == 0, "");
|
||||
|
||||
// Output type
|
||||
using OutputType = TransformedFeatureType;
|
||||
|
||||
// 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
|
||||
static constexpr std::size_t kBufferSize = 0;
|
||||
static constexpr std::size_t BufferSize = 0;
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t GetHashValue() {
|
||||
std::uint32_t hash_value = 0xEC42E90Du;
|
||||
hash_value ^= kOutputDimensions ^ (Offset << 10);
|
||||
return hash_value;
|
||||
static constexpr std::uint32_t get_hash_value() {
|
||||
std::uint32_t hashValue = 0xEC42E90Du;
|
||||
hashValue ^= OutputDimensions ^ (Offset << 10);
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Forward propagation
|
||||
const OutputType* Propagate(
|
||||
const TransformedFeatureType* transformed_features,
|
||||
const OutputType* propagate(
|
||||
const TransformedFeatureType* transformedFeatures,
|
||||
char* /*buffer*/) const {
|
||||
return transformed_features + Offset;
|
||||
return transformedFeatures + Offset;
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -25,14 +25,11 @@
|
|||
|
||||
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
|
||||
struct alignas(kCacheLineSize) Accumulator {
|
||||
std::int16_t
|
||||
accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions];
|
||||
AccumulatorState state[2];
|
||||
struct alignas(CacheLineSize) Accumulator {
|
||||
std::int16_t accumulation[2][TransformedFeatureDimensions];
|
||||
std::int32_t psqtAccumulation[2][PSQTBuckets];
|
||||
bool computed[2];
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
|
|
@ -21,17 +21,39 @@
|
|||
#ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
||||
#define NNUE_ARCHITECTURE_H_INCLUDED
|
||||
|
||||
// Defines the network structure
|
||||
#include "architectures/halfkp_256x2-32-32.h"
|
||||
#include "nnue_common.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 {
|
||||
|
||||
static_assert(kTransformedFeatureDimensions % kMaxSimdWidth == 0, "");
|
||||
static_assert(Network::kOutputDimensions == 1, "");
|
||||
static_assert(std::is_same<Network::OutputType, std::int32_t>::value, "");
|
||||
// Input features used in evaluation function
|
||||
using FeatureSet = Features::HalfKAv2_hm;
|
||||
|
||||
// Trigger for full calculation instead of difference calculation
|
||||
constexpr auto kRefreshTriggers = RawFeatures::kRefreshTriggers;
|
||||
// Number of input feature dimensions after conversion
|
||||
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
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "../misc.h" // for IsLittleEndian
|
||||
|
||||
#if defined(USE_AVX2)
|
||||
#include <immintrin.h>
|
||||
|
||||
|
@ -46,58 +48,30 @@
|
|||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
// Version of the evaluation file
|
||||
constexpr std::uint32_t kVersion = 0x7AF32F16u;
|
||||
constexpr std::uint32_t Version = 0x7AF32F20u;
|
||||
|
||||
// Constant used in evaluation value calculation
|
||||
constexpr int FV_SCALE = 16;
|
||||
constexpr int kWeightScaleBits = 6;
|
||||
constexpr int OutputScale = 16;
|
||||
constexpr int WeightScaleBits = 6;
|
||||
|
||||
// Size of cache line (in bytes)
|
||||
constexpr std::size_t kCacheLineSize = 64;
|
||||
constexpr std::size_t CacheLineSize = 64;
|
||||
|
||||
// SIMD width (in bytes)
|
||||
#if defined(USE_AVX2)
|
||||
constexpr std::size_t kSimdWidth = 32;
|
||||
constexpr std::size_t SimdWidth = 32;
|
||||
|
||||
#elif defined(USE_SSE2)
|
||||
constexpr std::size_t kSimdWidth = 16;
|
||||
constexpr std::size_t SimdWidth = 16;
|
||||
|
||||
#elif defined(USE_MMX)
|
||||
constexpr std::size_t kSimdWidth = 8;
|
||||
constexpr std::size_t SimdWidth = 8;
|
||||
|
||||
#elif defined(USE_NEON)
|
||||
constexpr std::size_t kSimdWidth = 16;
|
||||
constexpr std::size_t SimdWidth = 16;
|
||||
#endif
|
||||
|
||||
constexpr std::size_t kMaxSimdWidth = 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 }
|
||||
};
|
||||
constexpr std::size_t MaxSimdWidth = 32;
|
||||
|
||||
// Type of input feature after conversion
|
||||
using TransformedFeatureType = std::uint8_t;
|
||||
|
@ -105,7 +79,7 @@ namespace Stockfish::Eval::NNUE {
|
|||
|
||||
// Round n up to be a multiple of base
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -114,19 +88,77 @@ namespace Stockfish::Eval::NNUE {
|
|||
// necessary to return a result with the byte ordering of the compiling machine.
|
||||
template <typename IntType>
|
||||
inline IntType read_little_endian(std::istream& stream) {
|
||||
|
||||
IntType result;
|
||||
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];
|
||||
if (IsLittleEndian)
|
||||
stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
#endif // #ifndef NNUE_COMMON_H_INCLUDED
|
||||
|
|
|
@ -23,72 +23,158 @@
|
|||
|
||||
#include "nnue_common.h"
|
||||
#include "nnue_architecture.h"
|
||||
#include "features/index_list.h"
|
||||
|
||||
#include <cstring> // std::memset()
|
||||
|
||||
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
|
||||
// accumulator tile by tile such that each tile fits in the CPU's
|
||||
// vector registers.
|
||||
#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
|
||||
typedef __m512i vec_t;
|
||||
typedef __m256i psqt_vec_t;
|
||||
#define vec_load(a) _mm512_load_si512(a)
|
||||
#define vec_store(a,b) _mm512_store_si512(a,b)
|
||||
#define vec_add_16(a,b) _mm512_add_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
|
||||
typedef __m256i vec_t;
|
||||
typedef __m256i psqt_vec_t;
|
||||
#define vec_load(a) _mm256_load_si256(a)
|
||||
#define vec_store(a,b) _mm256_store_si256(a,b)
|
||||
#define vec_add_16(a,b) _mm256_add_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
|
||||
typedef __m128i vec_t;
|
||||
typedef __m128i psqt_vec_t;
|
||||
#define vec_load(a) (*(a))
|
||||
#define vec_store(a,b) *(a)=(b)
|
||||
#define vec_add_16(a,b) _mm_add_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
|
||||
typedef __m64 vec_t;
|
||||
typedef __m64 psqt_vec_t;
|
||||
#define vec_load(a) (*(a))
|
||||
#define vec_store(a,b) *(a)=(b)
|
||||
#define vec_add_16(a,b) _mm_add_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
|
||||
typedef int16x8_t vec_t;
|
||||
typedef int32x4_t psqt_vec_t;
|
||||
#define vec_load(a) (*(a))
|
||||
#define vec_store(a,b) *(a)=(b)
|
||||
#define vec_add_16(a,b) vaddq_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
|
||||
#undef VECTOR
|
||||
|
||||
#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
|
||||
class FeatureTransformer {
|
||||
|
||||
private:
|
||||
// Number of output dimensions for one side
|
||||
static constexpr IndexType kHalfDimensions = kTransformedFeatureDimensions;
|
||||
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
|
||||
|
||||
#ifdef VECTOR
|
||||
static constexpr IndexType kTileHeight = kNumRegs * sizeof(vec_t) / 2;
|
||||
static_assert(kHalfDimensions % kTileHeight == 0, "kTileHeight must divide kHalfDimensions");
|
||||
static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
|
||||
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
|
||||
|
||||
public:
|
||||
|
@ -96,174 +182,218 @@ namespace Stockfish::Eval::NNUE {
|
|||
using OutputType = TransformedFeatureType;
|
||||
|
||||
// Number of input/output dimensions
|
||||
static constexpr IndexType kInputDimensions = RawFeatures::kDimensions;
|
||||
static constexpr IndexType kOutputDimensions = kHalfDimensions * 2;
|
||||
static constexpr IndexType InputDimensions = FeatureSet::Dimensions;
|
||||
static constexpr IndexType OutputDimensions = HalfDimensions * 2;
|
||||
|
||||
// Size of forward propagation buffer
|
||||
static constexpr std::size_t kBufferSize =
|
||||
kOutputDimensions * sizeof(OutputType);
|
||||
static constexpr std::size_t BufferSize =
|
||||
OutputDimensions * sizeof(OutputType);
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t GetHashValue() {
|
||||
|
||||
return RawFeatures::kHashValue ^ kOutputDimensions;
|
||||
static constexpr std::uint32_t get_hash_value() {
|
||||
return FeatureSet::HashValue ^ OutputDimensions;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Convert input features
|
||||
void Transform(const Position& pos, OutputType* output) const {
|
||||
|
||||
UpdateAccumulator(pos, WHITE);
|
||||
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
|
||||
std::int32_t transform(const Position& pos, OutputType* output, int bucket) const {
|
||||
update_accumulator(pos, WHITE);
|
||||
update_accumulator(pos, BLACK);
|
||||
|
||||
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
|
||||
for (IndexType p = 0; p < 2; ++p) {
|
||||
const IndexType offset = kHalfDimensions * p;
|
||||
const auto& accumulation = pos.state()->accumulator.accumulation;
|
||||
const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation;
|
||||
|
||||
const auto psqt = (
|
||||
psqtAccumulation[perspectives[0]][bucket]
|
||||
- psqtAccumulation[perspectives[1]][bucket]
|
||||
) / 2;
|
||||
|
||||
|
||||
#if defined(USE_AVX512)
|
||||
auto out = reinterpret_cast<__m512i*>(&output[offset]);
|
||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||
__m512i sum0 = _mm512_load_si512(
|
||||
&reinterpret_cast<const __m512i*>(accumulation[perspectives[p]][0])[j * 2 + 0]);
|
||||
__m512i sum1 = _mm512_load_si512(
|
||||
&reinterpret_cast<const __m512i*>(accumulation[perspectives[p]][0])[j * 2 + 1]);
|
||||
_mm512_store_si512(&out[j], _mm512_permutexvar_epi64(kControl,
|
||||
_mm512_max_epi8(_mm512_packs_epi16(sum0, sum1), kZero)));
|
||||
}
|
||||
|
||||
constexpr IndexType NumChunks = HalfDimensions / (SimdWidth * 2);
|
||||
static_assert(HalfDimensions % (SimdWidth * 2) == 0);
|
||||
const __m512i Control = _mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7);
|
||||
const __m512i Zero = _mm512_setzero_si512();
|
||||
|
||||
for (IndexType p = 0; p < 2; ++p)
|
||||
{
|
||||
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)
|
||||
auto out = reinterpret_cast<__m256i*>(&output[offset]);
|
||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||
__m256i sum0 = _mm256_load_si256(
|
||||
&reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 0]);
|
||||
__m256i sum1 = _mm256_load_si256(
|
||||
&reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 1]);
|
||||
_mm256_store_si256(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8(
|
||||
_mm256_packs_epi16(sum0, sum1), kZero), kControl));
|
||||
}
|
||||
|
||||
constexpr IndexType NumChunks = HalfDimensions / SimdWidth;
|
||||
constexpr int Control = 0b11011000;
|
||||
const __m256i Zero = _mm256_setzero_si256();
|
||||
|
||||
for (IndexType p = 0; p < 2; ++p)
|
||||
{
|
||||
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)
|
||||
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
|
||||
_mm_max_epi8(packedbytes, kZero)
|
||||
#else
|
||||
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
||||
#endif
|
||||
for (IndexType p = 0; p < 2; ++p)
|
||||
{
|
||||
const IndexType offset = HalfDimensions * p;
|
||||
auto out = reinterpret_cast<__m128i*>(&output[offset]);
|
||||
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)
|
||||
auto out = reinterpret_cast<__m64*>(&output[offset]);
|
||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||
__m64 sum0 = *(&reinterpret_cast<const __m64*>(
|
||||
accumulation[perspectives[p]][0])[j * 2 + 0]);
|
||||
__m64 sum1 = *(&reinterpret_cast<const __m64*>(
|
||||
accumulation[perspectives[p]][0])[j * 2 + 1]);
|
||||
const __m64 packedbytes = _mm_packs_pi16(sum0, sum1);
|
||||
out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
|
||||
}
|
||||
|
||||
constexpr IndexType NumChunks = HalfDimensions / SimdWidth;
|
||||
const __m64 k0x80s = _mm_set1_pi8(-128);
|
||||
|
||||
for (IndexType p = 0; p < 2; ++p)
|
||||
{
|
||||
const IndexType offset = HalfDimensions * p;
|
||||
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)
|
||||
const auto out = reinterpret_cast<int8x8_t*>(&output[offset]);
|
||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||
int16x8_t sum = reinterpret_cast<const int16x8_t*>(
|
||||
accumulation[perspectives[p]][0])[j];
|
||||
out[j] = vmax_s8(vqmovn_s16(sum), kZero);
|
||||
}
|
||||
|
||||
constexpr IndexType NumChunks = HalfDimensions / (SimdWidth / 2);
|
||||
const int8x8_t Zero = {0};
|
||||
|
||||
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
|
||||
for (IndexType j = 0; j < kHalfDimensions; ++j) {
|
||||
BiasType sum = accumulation[static_cast<int>(perspectives[p])][0][j];
|
||||
output[offset + j] = static_cast<OutputType>(
|
||||
std::max<int>(0, std::min<int>(127, sum)));
|
||||
}
|
||||
|
||||
for (IndexType p = 0; p < 2; ++p)
|
||||
{
|
||||
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
|
||||
|
||||
}
|
||||
#if defined(USE_MMX)
|
||||
_mm_empty();
|
||||
#endif
|
||||
}
|
||||
} // end of function transform()
|
||||
|
||||
|
||||
|
||||
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
|
||||
// Gcc-10.2 unnecessarily spills AVX2 registers if this array
|
||||
// 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
|
||||
|
||||
// Look for a usable accumulator of an earlier position. We keep track
|
||||
// of the estimated gain in terms of features to be added/subtracted.
|
||||
StateInfo *st = pos.state(), *next = nullptr;
|
||||
int gain = pos.count<ALL_PIECES>() - 2;
|
||||
while (st->accumulator.state[c] == EMPTY)
|
||||
int gain = FeatureSet::refresh_cost(pos);
|
||||
while (st->previous && !st->accumulator.computed[perspective])
|
||||
{
|
||||
auto& dp = st->dirtyPiece;
|
||||
// The first condition tests whether an incremental update is
|
||||
// possible at all: if this side's king has moved, it is not possible.
|
||||
static_assert(std::is_same_v<RawFeatures::SortedTriggerSet,
|
||||
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)
|
||||
// This governs when a full feature refresh is needed and how many
|
||||
// updates are better than just one full refresh.
|
||||
if ( FeatureSet::requires_refresh(st, perspective)
|
||||
|| (gain -= FeatureSet::update_cost(st) + 1) < 0)
|
||||
break;
|
||||
next = st;
|
||||
st = st->previous;
|
||||
}
|
||||
|
||||
if (st->accumulator.state[c] == COMPUTED)
|
||||
if (st->accumulator.computed[perspective])
|
||||
{
|
||||
if (next == nullptr)
|
||||
return;
|
||||
|
@ -271,85 +401,129 @@ namespace Stockfish::Eval::NNUE {
|
|||
// Update incrementally in two steps. First, we update the "next"
|
||||
// accumulator. Then, we update the current accumulator (pos.state()).
|
||||
|
||||
// Gather all features to be updated. This code assumes HalfKP features
|
||||
// only and doesn't support refresh triggers.
|
||||
static_assert(std::is_same_v<Features::FeatureSet<Features::HalfKP<Features::Side::kFriend>>,
|
||||
RawFeatures>);
|
||||
Features::IndexList removed[2], added[2];
|
||||
Features::HalfKP<Features::Side::kFriend>::AppendChangedIndices(pos,
|
||||
next->dirtyPiece, c, &removed[0], &added[0]);
|
||||
// Gather all features to be updated.
|
||||
const Square ksq = pos.square<KING>(perspective);
|
||||
FeatureSet::IndexList removed[2], added[2];
|
||||
FeatureSet::append_changed_indices(
|
||||
ksq, next->dirtyPiece, perspective, removed[0], added[0]);
|
||||
for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous)
|
||||
Features::HalfKP<Features::Side::kFriend>::AppendChangedIndices(pos,
|
||||
st2->dirtyPiece, c, &removed[1], &added[1]);
|
||||
FeatureSet::append_changed_indices(
|
||||
ksq, st2->dirtyPiece, perspective, removed[1], added[1]);
|
||||
|
||||
// Mark the accumulators as computed.
|
||||
next->accumulator.state[c] = COMPUTED;
|
||||
pos.state()->accumulator.state[c] = COMPUTED;
|
||||
next->accumulator.computed[perspective] = true;
|
||||
pos.state()->accumulator.computed[perspective] = true;
|
||||
|
||||
// Now update the accumulators listed in info[], where the last element is a sentinel.
|
||||
StateInfo *info[3] =
|
||||
// Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
|
||||
StateInfo *states_to_update[3] =
|
||||
{ next, next == pos.state() ? nullptr : pos.state(), nullptr };
|
||||
#ifdef VECTOR
|
||||
for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j)
|
||||
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
|
||||
{
|
||||
// Load accumulator
|
||||
auto accTile = reinterpret_cast<vec_t*>(
|
||||
&st->accumulator.accumulation[c][0][j * kTileHeight]);
|
||||
for (IndexType k = 0; k < kNumRegs; ++k)
|
||||
&st->accumulator.accumulation[perspective][j * TileHeight]);
|
||||
for (IndexType k = 0; k < NumRegs; ++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
|
||||
for (const auto index : removed[i])
|
||||
{
|
||||
const IndexType offset = kHalfDimensions * index + j * kTileHeight;
|
||||
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
|
||||
for (IndexType k = 0; k < kNumRegs; ++k)
|
||||
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
||||
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
acc[k] = vec_sub_16(acc[k], column[k]);
|
||||
}
|
||||
|
||||
// Difference calculation for the activated features
|
||||
for (const auto index : added[i])
|
||||
{
|
||||
const IndexType offset = kHalfDimensions * index + j * kTileHeight;
|
||||
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
|
||||
for (IndexType k = 0; k < kNumRegs; ++k)
|
||||
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
||||
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
acc[k] = vec_add_16(acc[k], column[k]);
|
||||
}
|
||||
|
||||
// Store accumulator
|
||||
accTile = reinterpret_cast<vec_t*>(
|
||||
&info[i]->accumulator.accumulation[c][0][j * kTileHeight]);
|
||||
for (IndexType k = 0; k < kNumRegs; ++k)
|
||||
&states_to_update[i]->accumulator.accumulation[perspective][j * TileHeight]);
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
vec_store(&accTile[k], acc[k]);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
for (IndexType i = 0; info[i]; ++i)
|
||||
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
|
||||
{
|
||||
std::memcpy(info[i]->accumulator.accumulation[c][0],
|
||||
st->accumulator.accumulation[c][0],
|
||||
kHalfDimensions * sizeof(BiasType));
|
||||
st = info[i];
|
||||
// Load accumulator
|
||||
auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
||||
&st->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
|
||||
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
|
||||
for (const auto index : removed[i])
|
||||
{
|
||||
const IndexType offset = kHalfDimensions * index;
|
||||
const IndexType offset = HalfDimensions * index;
|
||||
|
||||
for (IndexType j = 0; j < kHalfDimensions; ++j)
|
||||
st->accumulator.accumulation[c][0][j] -= weights_[offset + j];
|
||||
for (IndexType j = 0; j < HalfDimensions; ++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
|
||||
for (const auto index : added[i])
|
||||
{
|
||||
const IndexType offset = kHalfDimensions * index;
|
||||
const IndexType offset = HalfDimensions * index;
|
||||
|
||||
for (IndexType j = 0; j < kHalfDimensions; ++j)
|
||||
st->accumulator.accumulation[c][0][j] += weights_[offset + j];
|
||||
for (IndexType j = 0; j < HalfDimensions; ++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
|
||||
|
@ -358,43 +532,69 @@ namespace Stockfish::Eval::NNUE {
|
|||
{
|
||||
// Refresh the accumulator
|
||||
auto& accumulator = pos.state()->accumulator;
|
||||
accumulator.state[c] = COMPUTED;
|
||||
Features::IndexList active;
|
||||
Features::HalfKP<Features::Side::kFriend>::AppendActiveIndices(pos, c, &active);
|
||||
accumulator.computed[perspective] = true;
|
||||
FeatureSet::IndexList active;
|
||||
FeatureSet::append_active_indices(pos, perspective, active);
|
||||
|
||||
#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*>(
|
||||
&biases_[j * kTileHeight]);
|
||||
for (IndexType k = 0; k < kNumRegs; ++k)
|
||||
&biases[j * TileHeight]);
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
acc[k] = biasesTile[k];
|
||||
|
||||
for (const auto index : active)
|
||||
{
|
||||
const IndexType offset = kHalfDimensions * index + j * kTileHeight;
|
||||
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
|
||||
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
||||
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]);
|
||||
}
|
||||
|
||||
auto accTile = reinterpret_cast<vec_t*>(
|
||||
&accumulator.accumulation[c][0][j * kTileHeight]);
|
||||
for (unsigned k = 0; k < kNumRegs; k++)
|
||||
&accumulator.accumulation[perspective][j * TileHeight]);
|
||||
for (unsigned k = 0; k < NumRegs; 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
|
||||
std::memcpy(accumulator.accumulation[c][0], biases_,
|
||||
kHalfDimensions * sizeof(BiasType));
|
||||
std::memcpy(accumulator.accumulation[perspective], biases,
|
||||
HalfDimensions * sizeof(BiasType));
|
||||
|
||||
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||
accumulator.psqtAccumulation[perspective][k] = 0;
|
||||
|
||||
for (const auto index : active)
|
||||
{
|
||||
const IndexType offset = kHalfDimensions * index;
|
||||
const IndexType offset = HalfDimensions * index;
|
||||
|
||||
for (IndexType j = 0; j < kHalfDimensions; ++j)
|
||||
accumulator.accumulation[c][0][j] += weights_[offset + j];
|
||||
for (IndexType j = 0; j < HalfDimensions; ++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
|
||||
}
|
||||
|
@ -404,12 +604,9 @@ namespace Stockfish::Eval::NNUE {
|
|||
#endif
|
||||
}
|
||||
|
||||
using BiasType = std::int16_t;
|
||||
using WeightType = std::int16_t;
|
||||
|
||||
alignas(kCacheLineSize) BiasType biases_[kHalfDimensions];
|
||||
alignas(kCacheLineSize)
|
||||
WeightType weights_[kHalfDimensions * kInputDimensions];
|
||||
alignas(CacheLineSize) BiasType biases[HalfDimensions];
|
||||
alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions];
|
||||
alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets];
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
|
|
@ -109,8 +109,9 @@ namespace {
|
|||
e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem));
|
||||
|
||||
// Loop through all pawns of the current color and score each pawn
|
||||
while (b) {
|
||||
s = pop_lsb(&b);
|
||||
while (b)
|
||||
{
|
||||
s = pop_lsb(b);
|
||||
|
||||
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))
|
||||
minPawnDist = 1;
|
||||
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);
|
||||
}
|
||||
|
|
110
src/position.cpp
110
src/position.cpp
|
@ -73,13 +73,13 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
|||
<< std::setfill(' ') << std::dec << "\nCheckers: ";
|
||||
|
||||
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())
|
||||
&& !pos.can_castle(ANY_CASTLING))
|
||||
{
|
||||
StateInfo st;
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize);
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
||||
|
||||
Position p;
|
||||
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_state(st);
|
||||
|
||||
// 4. En passant square.
|
||||
// Ignore if square is invalid or not on side to move relative rank 6.
|
||||
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
|
||||
// b) there is an enemy pawn in front of 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)
|
||||
&& (pieces(~sideToMove, PAWN) & (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))));
|
||||
&& !(pieces() & (st->epSquare | (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) {
|
||||
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
|
||||
if (!enpassant)
|
||||
st->epSquare = SQ_NONE;
|
||||
|
||||
// 5-6. Halfmove clock and fullmove number
|
||||
|
@ -295,8 +281,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
|
|||
|
||||
chess960 = isChess960;
|
||||
thisThread = th;
|
||||
st->accumulator.state[WHITE] = Eval::NNUE::INIT;
|
||||
st->accumulator.state[BLACK] = Eval::NNUE::INIT;
|
||||
set_state(st);
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
|
@ -359,7 +344,7 @@ void Position::set_state(StateInfo* si) const {
|
|||
|
||||
for (Bitboard b = pieces(); b; )
|
||||
{
|
||||
Square s = pop_lsb(&b);
|
||||
Square s = pop_lsb(b);
|
||||
Piece pc = piece_on(s);
|
||||
si->key ^= Zobrist::psq[pc][s];
|
||||
|
||||
|
@ -476,7 +461,7 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners
|
|||
|
||||
while (snipers)
|
||||
{
|
||||
Square sniperSq = pop_lsb(&snipers);
|
||||
Square sniperSq = pop_lsb(snipers);
|
||||
Bitboard b = between_bb(s, sniperSq) & occupancy;
|
||||
|
||||
if (b && !more_than_one(b))
|
||||
|
@ -517,11 +502,23 @@ bool Position::legal(Move m) const {
|
|||
assert(color_of(moved_piece(m)) == us);
|
||||
assert(piece_on(square<KING>(us)) == make_piece(us, KING));
|
||||
|
||||
// st->previous->blockersForKing consider capsq as empty.
|
||||
// If pinned, it has to move along the king ray.
|
||||
// En passant captures are a tricky special case. Because they are rather
|
||||
// uncommon, we do it simply by testing whether the king is attacked after
|
||||
// the move is made.
|
||||
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
|
||||
// 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
|
||||
// attacked by the opponent.
|
||||
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
|
||||
// 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()))
|
||||
return false;
|
||||
|
||||
// Our move must be a blocking evasion or a capture of the checking piece
|
||||
if (!((between_bb(lsb(checkers()), square<KING>(us)) | checkers()) & to))
|
||||
// Our move must be a blocking interposition or a capture of the checking piece
|
||||
if (!(between_bb(square<KING>(us), lsb(checkers())) & to))
|
||||
return false;
|
||||
}
|
||||
// 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:
|
||||
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.
|
||||
// The only discovery check that wasn't handle is through capsq and fromsq
|
||||
// So the King must be in the same rank as fromsq to consider this possibility.
|
||||
// st->previous->blockersForKing consider capsq as empty.
|
||||
// En passant capture with check? We have already handled the case
|
||||
// of direct checks and ordinary discovered check, so the only case we
|
||||
// need to handle is the unusual case of a discovered check through
|
||||
// the captured pawn.
|
||||
case EN_PASSANT:
|
||||
return st->previous->checkersBB
|
||||
|| ( rank_of(square<KING>(~sideToMove)) == rank_of(from)
|
||||
&& st->previous->blockersForKing[~sideToMove] & from);
|
||||
{
|
||||
Square capsq = make_square(file_of(to), rank_of(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
|
||||
{
|
||||
// Castling is encoded as 'king captures the rook'
|
||||
|
@ -702,8 +702,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||
++st->pliesFromNull;
|
||||
|
||||
// Used by NNUE
|
||||
st->accumulator.state[WHITE] = Eval::NNUE::EMPTY;
|
||||
st->accumulator.state[BLACK] = Eval::NNUE::EMPTY;
|
||||
st->accumulator.computed[WHITE] = false;
|
||||
st->accumulator.computed[BLACK] = false;
|
||||
auto& dp = st->dirtyPiece;
|
||||
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.
|
||||
|
||||
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.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator()
|
||||
st->accumulator.state[WHITE] = Eval::NNUE::EMPTY;
|
||||
st->accumulator.state[BLACK] = Eval::NNUE::EMPTY;
|
||||
st->accumulator.computed[WHITE] = false;
|
||||
st->accumulator.computed[BLACK] = false;
|
||||
|
||||
if (st->epSquare != SQ_NONE)
|
||||
{
|
||||
|
@ -1013,9 +1013,9 @@ void Position::do_null_move(StateInfo& newSt) {
|
|||
}
|
||||
|
||||
st->key ^= Zobrist::side;
|
||||
++st->rule50;
|
||||
prefetch(TT.first_entry(key()));
|
||||
|
||||
++st->rule50;
|
||||
st->pliesFromNull = 0;
|
||||
|
||||
sideToMove = ~sideToMove;
|
||||
|
@ -1027,6 +1027,9 @@ void Position::do_null_move(StateInfo& newSt) {
|
|||
assert(pos_is_ok());
|
||||
}
|
||||
|
||||
|
||||
/// Position::undo_null_move() must be used to undo a "null move"
|
||||
|
||||
void Position::undo_null_move() {
|
||||
|
||||
assert(!checkers());
|
||||
|
@ -1077,8 +1080,9 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||
if (swap <= 0)
|
||||
return true;
|
||||
|
||||
assert(color_of(piece_on(from)) == sideToMove);
|
||||
Bitboard occupied = pieces() ^ from ^ to;
|
||||
Color stm = color_of(piece_on(from));
|
||||
Color stm = sideToMove;
|
||||
Bitboard attackers = attackers_to(to, occupied);
|
||||
Bitboard stmAttackers, bb;
|
||||
int res = 1;
|
||||
|
@ -1092,8 +1096,8 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||
if (!(stmAttackers = attackers & pieces(stm)))
|
||||
break;
|
||||
|
||||
// Don't allow pinned pieces to attack (except the king) as long as
|
||||
// there are pinners on their original square.
|
||||
// Don't allow pinned pieces to attack as long as there are
|
||||
// pinners on their original square.
|
||||
if (pinners(~stm) & occupied)
|
||||
stmAttackers &= ~blockers_for_king(stm);
|
||||
|
||||
|
@ -1109,7 +1113,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||
if ((swap = PawnValueMg - swap) < res)
|
||||
break;
|
||||
|
||||
occupied ^= lsb(bb);
|
||||
occupied ^= least_significant_square_bb(bb);
|
||||
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)
|
||||
break;
|
||||
|
||||
occupied ^= lsb(bb);
|
||||
occupied ^= least_significant_square_bb(bb);
|
||||
}
|
||||
|
||||
else if ((bb = stmAttackers & pieces(BISHOP)))
|
||||
|
@ -1126,7 +1130,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||
if ((swap = BishopValueMg - swap) < res)
|
||||
break;
|
||||
|
||||
occupied ^= lsb(bb);
|
||||
occupied ^= least_significant_square_bb(bb);
|
||||
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)
|
||||
break;
|
||||
|
||||
occupied ^= lsb(bb);
|
||||
occupied ^= least_significant_square_bb(bb);
|
||||
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)
|
||||
break;
|
||||
|
||||
occupied ^= lsb(bb);
|
||||
occupied ^= least_significant_square_bb(bb);
|
||||
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, 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 s2 = to_sq(move);
|
||||
|
||||
if (!(between_bb(s1, s2) & pieces()))
|
||||
if (!((between_bb(s1, s2) ^ s2) & pieces()))
|
||||
{
|
||||
if (ply > i)
|
||||
return true;
|
||||
|
@ -1315,7 +1319,7 @@ bool Position::pos_is_ok() const {
|
|||
assert(0 && "pos_is_ok: Bitboards");
|
||||
|
||||
StateInfo si = *st;
|
||||
ASSERT_ALIGNED(&si, Eval::NNUE::kCacheLineSize);
|
||||
ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize);
|
||||
|
||||
set_state(&si);
|
||||
if (std::memcmp(&si, st, sizeof(StateInfo)))
|
||||
|
|
|
@ -51,11 +51,11 @@ struct StateInfo {
|
|||
// Not copied when making a move (will be recomputed anyhow)
|
||||
Key key;
|
||||
Bitboard checkersBB;
|
||||
Piece capturedPiece;
|
||||
StateInfo* previous;
|
||||
Bitboard blockersForKing[COLOR_NB];
|
||||
Bitboard pinners[COLOR_NB];
|
||||
Bitboard checkSquares[PIECE_TYPE_NB];
|
||||
Piece capturedPiece;
|
||||
int repetition;
|
||||
|
||||
// Used by NNUE
|
||||
|
@ -115,7 +115,6 @@ public:
|
|||
Bitboard blockers_for_king(Color c) const;
|
||||
Bitboard check_squares(PieceType pt) const;
|
||||
Bitboard pinners(Color c) const;
|
||||
bool is_discovered_check_on_king(Color c, Move m) const;
|
||||
|
||||
// Attacks to/from a given square
|
||||
Bitboard attackers_to(Square s) const;
|
||||
|
@ -128,7 +127,6 @@ public:
|
|||
bool capture(Move m) const;
|
||||
bool capture_or_promotion(Move m) const;
|
||||
bool gives_check(Move m) const;
|
||||
bool advanced_pawn_push(Move m) const;
|
||||
Piece moved_piece(Move m) const;
|
||||
Piece captured_piece() const;
|
||||
|
||||
|
@ -173,6 +171,9 @@ public:
|
|||
// Used by NNUE
|
||||
StateInfo* state() const;
|
||||
|
||||
void put_piece(Piece pc, Square s);
|
||||
void remove_piece(Square s);
|
||||
|
||||
private:
|
||||
// Initialization helpers (used while setting up a position)
|
||||
void set_castling_right(Color c, Square rfrom);
|
||||
|
@ -180,8 +181,6 @@ private:
|
|||
void set_check_info(StateInfo* si) const;
|
||||
|
||||
// Other helpers
|
||||
void put_piece(Piece pc, Square s);
|
||||
void remove_piece(Square s);
|
||||
void move_piece(Square from, Square to);
|
||||
template<bool Do>
|
||||
void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
|
||||
|
@ -194,11 +193,11 @@ private:
|
|||
int castlingRightsMask[SQUARE_NB];
|
||||
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
||||
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
||||
Thread* thisThread;
|
||||
StateInfo* st;
|
||||
int gamePly;
|
||||
Color sideToMove;
|
||||
Score psq;
|
||||
Thread* thisThread;
|
||||
StateInfo* st;
|
||||
bool chess960;
|
||||
};
|
||||
|
||||
|
@ -302,19 +301,10 @@ inline Bitboard Position::check_squares(PieceType pt) const {
|
|||
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 {
|
||||
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 {
|
||||
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[type_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[make_piece(color_of(pc), ALL_PIECES)]--;
|
||||
psq -= PSQT::psq[pc][s];
|
||||
|
|
522
src/search.cpp
522
src/search.cpp
|
@ -60,22 +60,19 @@ using namespace Search;
|
|||
namespace {
|
||||
|
||||
// Different node types, used as a template parameter
|
||||
enum NodeType { NonPV, PV };
|
||||
|
||||
constexpr uint64_t TtHitAverageWindow = 4096;
|
||||
constexpr uint64_t TtHitAverageResolution = 1024;
|
||||
enum NodeType { NonPV, PV, Root };
|
||||
|
||||
// Futility margin
|
||||
Value futility_margin(Depth d, bool improving) {
|
||||
return Value(234 * (d - improving));
|
||||
return Value(214 * (d - improving));
|
||||
}
|
||||
|
||||
// Reductions lookup table, initialized at startup
|
||||
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];
|
||||
return (r + 503) / 1024 + (!i && r > 915);
|
||||
return (r + 534) / 1024 + (!i && r > 904) + rangeReduction;
|
||||
}
|
||||
|
||||
constexpr int futility_move_count(bool improving, Depth depth) {
|
||||
|
@ -84,7 +81,7 @@ namespace {
|
|||
|
||||
// History and stats update bonus, based on depth
|
||||
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
|
||||
|
@ -92,6 +89,30 @@ namespace {
|
|||
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
|
||||
struct Skill {
|
||||
explicit Skill(int l) : level(l) {}
|
||||
|
@ -103,53 +124,10 @@ namespace {
|
|||
Move best = MOVE_NONE;
|
||||
};
|
||||
|
||||
// Breadcrumbs are used to mark nodes as being searched by a given thread
|
||||
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>
|
||||
template <NodeType nodeType>
|
||||
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 value_to_tt(Value v, int ply);
|
||||
|
@ -166,7 +144,7 @@ namespace {
|
|||
uint64_t perft(Position& pos, Depth depth) {
|
||||
|
||||
StateInfo st;
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize);
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
||||
|
||||
uint64_t cnt, nodes = 0;
|
||||
const bool leaf = (depth == 2);
|
||||
|
@ -196,7 +174,7 @@ namespace {
|
|||
void Search::init() {
|
||||
|
||||
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.
|
||||
// The former is needed to allow update_continuation_histories(ss-1, ...),
|
||||
// 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;
|
||||
Move pv[MAX_PLY+1];
|
||||
Value bestValue, alpha, beta, delta;
|
||||
|
@ -340,6 +318,9 @@ void Thread::search() {
|
|||
for (int i = 7; i > 0; i--)
|
||||
(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;
|
||||
|
||||
bestValue = delta = alpha = -VALUE_INFINITE;
|
||||
|
@ -379,21 +360,14 @@ void Thread::search() {
|
|||
multiPV = std::max(multiPV, (size_t)4);
|
||||
|
||||
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
|
||||
if (Limits.infinite || Options["UCI_AnalyseMode"])
|
||||
ct = Options["Analysis Contempt"] == "Off" ? 0
|
||||
: Options["Analysis Contempt"] == "Both" ? ct
|
||||
: 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));
|
||||
nodesLastExplosive = nodes;
|
||||
nodesLastNormal = nodes;
|
||||
state = EXPLOSION_NONE;
|
||||
trend = SCORE_ZERO;
|
||||
|
||||
int searchAgainCounter = 0;
|
||||
|
||||
|
@ -440,21 +414,21 @@ void Thread::search() {
|
|||
alpha = std::max(prev - delta,-VALUE_INFINITE);
|
||||
beta = std::min(prev + delta, VALUE_INFINITE);
|
||||
|
||||
// Adjust contempt based on root move's previousScore (dynamic contempt)
|
||||
int dct = ct + (113 - ct / 2) * prev / (abs(prev) + 147);
|
||||
// Adjust trend based on root move's previousScore (dynamic contempt)
|
||||
int tr = 113 * prev / (abs(prev) + 147);
|
||||
|
||||
contempt = (us == WHITE ? make_score(dct, dct / 2)
|
||||
: -make_score(dct, dct / 2));
|
||||
trend = (us == WHITE ? make_score(tr, tr / 2)
|
||||
: -make_score(tr, tr / 2));
|
||||
}
|
||||
|
||||
// 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 anymore.
|
||||
failedHighCnt = 0;
|
||||
int failedHighCnt = 0;
|
||||
while (true)
|
||||
{
|
||||
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
|
||||
// is done with a stable algorithm because all the values but the
|
||||
|
@ -557,8 +531,8 @@ void Thread::search() {
|
|||
totBestMoveChanges += th->bestMoveChanges;
|
||||
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;
|
||||
|
||||
// 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
|
||||
|
||||
template <NodeType NT>
|
||||
template <NodeType nodeType>
|
||||
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
|
||||
|
||||
constexpr bool PvNode = NT == PV;
|
||||
const bool rootNode = PvNode && ss->ply == 0;
|
||||
Thread* thisThread = pos.this_thread();
|
||||
|
||||
// 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;
|
||||
|
||||
// Check if we have an upcoming move which draws by repetition, or
|
||||
// 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
|
||||
&& !rootNode
|
||||
&& pos.has_game_cycle(ss->ply))
|
||||
{
|
||||
alpha = value_draw(pos.this_thread());
|
||||
|
@ -625,7 +607,7 @@ namespace {
|
|||
|
||||
// Dive into quiescence search when the depth reaches zero
|
||||
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(PvNode || (alpha == beta - 1));
|
||||
|
@ -634,28 +616,26 @@ namespace {
|
|||
|
||||
Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64];
|
||||
StateInfo st;
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize);
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
||||
|
||||
TTEntry* tte;
|
||||
Key posKey;
|
||||
Move ttMove, move, excludedMove, bestMove;
|
||||
Depth extension, newDepth;
|
||||
Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
|
||||
bool formerPv, givesCheck, improving, didLMR, priorCapture;
|
||||
bool givesCheck, improving, didLMR, priorCapture;
|
||||
bool captureOrPromotion, doFullDepthSearch, moveCountPruning,
|
||||
ttCapture, singularQuietLMR;
|
||||
Piece movedPiece;
|
||||
int moveCount, captureCount, quietCount;
|
||||
int moveCount, captureCount, quietCount, bestMoveCount, improvement;
|
||||
|
||||
// Step 1. Initialize node
|
||||
Thread* thisThread = pos.this_thread();
|
||||
ss->inCheck = pos.checkers();
|
||||
priorCapture = pos.captured_piece();
|
||||
Color us = pos.side_to_move();
|
||||
moveCount = captureCount = quietCount = ss->moveCount = 0;
|
||||
bestValue = -VALUE_INFINITE;
|
||||
maxValue = VALUE_INFINITE;
|
||||
ss->distanceFromPv = (PvNode ? 0 : ss->distanceFromPv);
|
||||
ss->inCheck = pos.checkers();
|
||||
priorCapture = pos.captured_piece();
|
||||
Color us = pos.side_to_move();
|
||||
moveCount = bestMoveCount = captureCount = quietCount = ss->moveCount = 0;
|
||||
bestValue = -VALUE_INFINITE;
|
||||
maxValue = VALUE_INFINITE;
|
||||
|
||||
// Check for the available remaining time
|
||||
if (thisThread == Threads.main())
|
||||
|
@ -688,11 +668,15 @@ namespace {
|
|||
|
||||
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+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
|
||||
Square prevSq = to_sq((ss-1)->currentMove);
|
||||
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
|
||||
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.
|
||||
// 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;
|
||||
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
|
||||
: ss->ttHit ? tte->move() : MOVE_NONE;
|
||||
ttCapture = ttMove && pos.capture_or_promotion(ttMove);
|
||||
if (!excludedMove)
|
||||
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
|
||||
if ( ss->ttPv
|
||||
|
@ -723,14 +707,10 @@ namespace {
|
|||
&& is_ok((ss-1)->currentMove))
|
||||
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
|
||||
if ( !PvNode
|
||||
&& ss->ttHit
|
||||
&& tte->depth() >= depth
|
||||
&& tte->depth() > depth - (thisThread->id() % 2 == 1)
|
||||
&& ttValue != VALUE_NONE // Possible in case of TT access race
|
||||
&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
|
||||
: (tte->bound() & BOUND_UPPER)))
|
||||
|
@ -741,7 +721,7 @@ namespace {
|
|||
if (ttValue >= beta)
|
||||
{
|
||||
// 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);
|
||||
|
||||
// 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));
|
||||
}
|
||||
// Penalty for a quiet ttMove that fails low
|
||||
else if (!pos.capture_or_promotion(ttMove))
|
||||
else if (!ttCapture)
|
||||
{
|
||||
int penalty = -stat_bonus(depth);
|
||||
thisThread->mainHistory[us][from_to(ttMove)] << penalty;
|
||||
|
@ -824,6 +804,7 @@ namespace {
|
|||
// Skip early pruning when in check
|
||||
ss->staticEval = eval = VALUE_NONE;
|
||||
improving = false;
|
||||
improvement = 0;
|
||||
goto moves_loop;
|
||||
}
|
||||
else if (ss->ttHit)
|
||||
|
@ -844,35 +825,34 @@ namespace {
|
|||
}
|
||||
else
|
||||
{
|
||||
// In case of null move search use previous static eval with a different sign
|
||||
// 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;
|
||||
ss->staticEval = eval = evaluate(pos);
|
||||
|
||||
// Save static evaluation into transposition table
|
||||
Cluster::save(thisThread, tte,
|
||||
posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE,
|
||||
eval);
|
||||
if (!excludedMove)
|
||||
Cluster::save(thisThread, tte,
|
||||
posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE,
|
||||
eval);
|
||||
}
|
||||
|
||||
// Use static evaluation difference to improve quiet move ordering
|
||||
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;
|
||||
}
|
||||
|
||||
// Set up improving flag that is used in various pruning heuristics
|
||||
// We define position as improving if static evaluation of position is better
|
||||
// Than the previous static evaluation at our turn
|
||||
// In case of us being in check at our previous move we look at move prior to it
|
||||
improving = (ss-2)->staticEval == VALUE_NONE
|
||||
? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE
|
||||
: ss->staticEval > (ss-2)->staticEval;
|
||||
// Set up the improvement variable, which is the difference between the current
|
||||
// static evaluation and the previous static evaluation at our turn (if we were
|
||||
// in check at our previous move we look at the move prior to it). The improvement
|
||||
// margin and the improving flag are used in various pruning heuristics.
|
||||
improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval
|
||||
: (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->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
|
||||
&& depth < 9
|
||||
&& eval - futility_margin(depth, improving) >= beta
|
||||
|
@ -882,10 +862,10 @@ namespace {
|
|||
// Step 8. Null move search with verification search (~40 Elo)
|
||||
if ( !PvNode
|
||||
&& (ss-1)->currentMove != MOVE_NULL
|
||||
&& (ss-1)->statScore < 24185
|
||||
&& (ss-1)->statScore < 23767
|
||||
&& eval >= beta
|
||||
&& eval >= ss->staticEval
|
||||
&& ss->staticEval >= beta - 24 * depth - 34 * improving + 162 * ss->ttPv + 159
|
||||
&& ss->staticEval >= beta - 20 * depth - improvement / 15 + 204
|
||||
&& !excludedMove
|
||||
&& pos.non_pawn_material(us)
|
||||
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
|
||||
|
@ -893,7 +873,7 @@ namespace {
|
|||
assert(eval - beta >= 0);
|
||||
|
||||
// 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->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
|
||||
|
@ -931,7 +911,7 @@ namespace {
|
|||
|
||||
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
|
||||
// much above beta, we can (almost) safely prune the previous move.
|
||||
if ( !PvNode
|
||||
|
@ -946,31 +926,19 @@ namespace {
|
|||
&& ttValue != VALUE_NONE
|
||||
&& 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);
|
||||
|
||||
MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
|
||||
int probCutCount = 0;
|
||||
bool ttPv = ss->ttPv;
|
||||
ss->ttPv = false;
|
||||
|
||||
while ( (move = mp.next_move()) != MOVE_NONE
|
||||
&& probCutCount < 2 + 2 * cutNode)
|
||||
while ((move = mp.next_move()) != MOVE_NONE)
|
||||
if (move != excludedMove && pos.legal(move))
|
||||
{
|
||||
assert(pos.capture_or_promotion(move));
|
||||
assert(depth >= 5);
|
||||
|
||||
captureOrPromotion = true;
|
||||
probCutCount++;
|
||||
|
||||
ss->currentMove = move;
|
||||
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
|
||||
|
@ -1004,18 +972,23 @@ namespace {
|
|||
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
|
||||
&& depth >= 6
|
||||
&& !ttMove)
|
||||
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
|
||||
probCutBeta = beta + 400;
|
||||
probCutBeta = beta + 409;
|
||||
if ( ss->inCheck
|
||||
&& !PvNode
|
||||
&& depth >= 4
|
||||
|
@ -1046,8 +1019,12 @@ moves_loop: // When in check, search starts from here
|
|||
value = bestValue;
|
||||
singularQuietLMR = moveCountPruning = false;
|
||||
|
||||
// Mark this node as being searched
|
||||
ThreadHolding th(thisThread, posKey, ss->ply);
|
||||
// Indicate PvNodes that will probably fail low if the node was searched
|
||||
// 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
|
||||
// or a beta cutoff occurs.
|
||||
|
@ -1084,18 +1061,10 @@ moves_loop: // When in check, search starts from here
|
|||
movedPiece = pos.moved_piece(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
|
||||
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
|
||||
&& pos.non_pawn_material(us)
|
||||
&& 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);
|
||||
|
||||
// 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
|
||||
|| givesCheck)
|
||||
|
@ -1121,24 +1090,21 @@ moves_loop: // When in check, search starts from here
|
|||
}
|
||||
else
|
||||
{
|
||||
// Countermoves based pruning (~20 Elo)
|
||||
if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
|
||||
&& (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
|
||||
&& (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold)
|
||||
// Continuation history based pruning (~20 Elo)
|
||||
if (lmrDepth < 5
|
||||
&& (*contHist[0])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[1])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[3])[movedPiece][to_sq(move)] < -3000 * depth + 3000)
|
||||
continue;
|
||||
|
||||
// Futility pruning: parent node (~5 Elo)
|
||||
if ( lmrDepth < 7
|
||||
&& !ss->inCheck
|
||||
&& ss->staticEval + 174 + 157 * 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)
|
||||
if ( !ss->inCheck
|
||||
&& lmrDepth < 8
|
||||
&& ss->staticEval + 172 + 145 * lmrDepth <= alpha)
|
||||
continue;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
// 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.
|
||||
if ( depth >= 7
|
||||
if ( !rootNode
|
||||
&& depth >= 7
|
||||
&& move == ttMove
|
||||
&& !rootNode
|
||||
&& !excludedMove // Avoid recursive singular search
|
||||
/* && ttValue != VALUE_NONE Already implicit in the next condition */
|
||||
&& abs(ttValue) < VALUE_KNOWN_WIN
|
||||
&& (tte->bound() & BOUND_LOWER)
|
||||
&& tte->depth() >= depth - 3)
|
||||
{
|
||||
Value singularBeta = ttValue - ((formerPv + 4) * depth) / 2;
|
||||
Depth singularDepth = (depth - 1 + 3 * formerPv) / 2;
|
||||
Value singularBeta = ttValue - 3 * depth;
|
||||
Depth singularDepth = (depth - 1) / 2;
|
||||
|
||||
ss->excludedMove = move;
|
||||
value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
|
||||
ss->excludedMove = MOVE_NONE;
|
||||
|
@ -1169,6 +1136,12 @@ moves_loop: // When in check, search starts from here
|
|||
{
|
||||
extension = 1;
|
||||
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
|
||||
|
@ -1179,31 +1152,33 @@ moves_loop: // When in check, search starts from here
|
|||
else if (singularBeta >= beta)
|
||||
return singularBeta;
|
||||
|
||||
// If the eval of ttMove is greater than beta we try also if there is another
|
||||
// move that pushes it over beta, if so also produce a cutoff.
|
||||
// If the eval of ttMove is greater than beta, we reduce it (negative extension)
|
||||
else if (ttValue >= beta)
|
||||
{
|
||||
ss->excludedMove = move;
|
||||
value = search<NonPV>(pos, ss, beta - 1, beta, (depth + 3) / 2, cutNode);
|
||||
ss->excludedMove = MOVE_NONE;
|
||||
|
||||
if (value >= beta)
|
||||
return beta;
|
||||
}
|
||||
extension = -2;
|
||||
}
|
||||
|
||||
// Check extension (~2 Elo)
|
||||
else if ( givesCheck
|
||||
&& (pos.is_discovered_check_on_king(~us, move) || pos.see_ge(move)))
|
||||
// Capture extensions for PvNodes and cutNodes
|
||||
else if ( (PvNode || cutNode)
|
||||
&& captureOrPromotion
|
||||
&& moveCount != 1)
|
||||
extension = 1;
|
||||
|
||||
// Last captures extension
|
||||
else if ( PieceValue[EG][pos.captured_piece()] > PawnValueEg
|
||||
&& pos.non_pawn_material() <= 2 * RookValueMg)
|
||||
// Check extensions
|
||||
else if ( givesCheck
|
||||
&& 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;
|
||||
|
||||
// Add extension to new depth
|
||||
newDepth += extension;
|
||||
ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2);
|
||||
|
||||
// Speculative prefetch as early as possible
|
||||
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
|
||||
pos.do_move(move, st, givesCheck);
|
||||
|
||||
(ss+1)->distanceFromPv = ss->distanceFromPv + moveCount - 1;
|
||||
|
||||
// Step 16. Late moves reduction / extension (LMR, ~200 Elo)
|
||||
// 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
|
||||
// cases where we extend a son if it has good chances to be "interesting".
|
||||
if ( depth >= 3
|
||||
&& moveCount > 1 + 2 * rootNode
|
||||
&& ( !captureOrPromotion
|
||||
|| moveCountPruning
|
||||
|| ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
|
||||
|| cutNode
|
||||
|| (!PvNode && !formerPv && captureHistory[movedPiece][to_sq(move)][type_of(pos.captured_piece())] < 3678)
|
||||
|| thisThread->ttHitAverage < 432 * TtHitAverageResolution * TtHitAverageWindow / 1024))
|
||||
&& ( !ss->ttPv
|
||||
|| !captureOrPromotion
|
||||
|| (cutNode && (ss-1)->moveCount > 1)))
|
||||
{
|
||||
Depth r = reduction(improving, depth, moveCount);
|
||||
Depth r = reduction(improving, depth, moveCount, rangeReduction > 2);
|
||||
|
||||
// Decrease reduction if the ttHit running average is large
|
||||
if (thisThread->ttHitAverage > 537 * TtHitAverageResolution * TtHitAverageWindow / 1024)
|
||||
// Decrease reduction if on the PV (~2 Elo)
|
||||
if ( PvNode
|
||||
&& bestMoveCount <= 3)
|
||||
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
|
||||
// and node is not likely to fail low. (~10 Elo)
|
||||
if (ss->ttPv && !likelyFailLow)
|
||||
// and node is not likely to fail low. (~3 Elo)
|
||||
if ( ss->ttPv
|
||||
&& !likelyFailLow)
|
||||
r -= 2;
|
||||
|
||||
// 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++;
|
||||
|
||||
// More reductions for late moves if position was not in previous PV
|
||||
if (moveCountPruning && !formerPv)
|
||||
r++;
|
||||
|
||||
// Decrease reduction if opponent's move count is high (~5 Elo)
|
||||
// Decrease reduction if opponent's move count is high (~1 Elo)
|
||||
if ((ss-1)->moveCount > 13)
|
||||
r--;
|
||||
|
||||
// Decrease reduction if ttMove has been singularly extended (~3 Elo)
|
||||
// Decrease reduction if ttMove has been singularly extended (~1 Elo)
|
||||
if (singularQuietLMR)
|
||||
r--;
|
||||
|
||||
if (captureOrPromotion)
|
||||
{
|
||||
// Unless giving check, this capture is likely bad
|
||||
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 for cut nodes (~3 Elo)
|
||||
if (cutNode && move != ss->killers[0])
|
||||
r += 2;
|
||||
|
||||
// Increase reduction at root if failing high
|
||||
r += rootNode ? thisThread->failedHighCnt * thisThread->failedHighCnt * moveCount / 512 : 0;
|
||||
// Increase reduction if ttMove is a capture (~3 Elo)
|
||||
if (ttCapture)
|
||||
r++;
|
||||
|
||||
// Increase reduction for cut nodes (~10 Elo)
|
||||
if (cutNode)
|
||||
r += 2;
|
||||
ss->statScore = thisThread->mainHistory[us][from_to(move)]
|
||||
+ (*contHist[0])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[1])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[3])[movedPiece][to_sq(move)]
|
||||
- 4923;
|
||||
|
||||
// Decrease reduction for moves that escape a capture. Filter out
|
||||
// castling moves, because they are coded as "king captures rook" and
|
||||
// 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);
|
||||
// Decrease/increase reduction for moves with a good/bad history (~30 Elo)
|
||||
r -= ss->statScore / 14721;
|
||||
|
||||
ss->statScore = thisThread->mainHistory[us][from_to(move)]
|
||||
+ (*contHist[0])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[1])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[3])[movedPiece][to_sq(move)]
|
||||
- 4741;
|
||||
// In general we want to cap the LMR depth search at newDepth. But if reductions
|
||||
// are really negative and movecount is low, we allow this move to be searched
|
||||
// deeper than the first move (this may lead to hidden double extensions).
|
||||
int deeper = r >= -1 ? 0
|
||||
: moveCount <= 5 ? 2
|
||||
: PvNode && depth > 6 ? 1
|
||||
: 0;
|
||||
|
||||
// Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
|
||||
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));
|
||||
Depth d = std::clamp(newDepth - r, 1, newDepth + deeper);
|
||||
|
||||
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
|
||||
doFullDepthSearch = value > alpha && d < newDepth;
|
||||
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)
|
||||
rm.pv.push_back(*m);
|
||||
|
||||
// We record how often the best move has been changed in each
|
||||
// iteration. This information is used for time management and LMR
|
||||
if (moveCount > 1)
|
||||
// We record how often the best move has been changed in each iteration.
|
||||
// This information is used for time management and LMR. In MultiPV mode,
|
||||
// we must take care to only do this for the first PV line.
|
||||
if ( moveCount > 1
|
||||
&& !thisThread->pvIdx)
|
||||
++thisThread->bestMoveChanges;
|
||||
}
|
||||
else
|
||||
|
@ -1411,11 +1354,13 @@ moves_loop: // When in check, search starts from here
|
|||
update_pv(ss->pv, move, (ss+1)->pv);
|
||||
|
||||
if (PvNode && value < beta) // Update alpha! Always alpha < beta
|
||||
{
|
||||
alpha = value;
|
||||
bestMoveCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(value >= beta); // Fail high
|
||||
ss->statScore = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1448,8 +1393,9 @@ moves_loop: // When in check, search starts from here
|
|||
assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
|
||||
|
||||
if (!moveCount)
|
||||
bestValue = excludedMove ? alpha
|
||||
: ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW;
|
||||
bestValue = excludedMove ? alpha :
|
||||
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
|
||||
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
|
||||
else if ( (depth >= 3 || PvNode)
|
||||
&& !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)
|
||||
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
|
||||
// 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) {
|
||||
|
||||
constexpr bool PvNode = NT == PV;
|
||||
static_assert(nodeType != Root);
|
||||
constexpr bool PvNode = nodeType == PV;
|
||||
|
||||
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
|
||||
assert(PvNode || (alpha == beta - 1));
|
||||
|
@ -1500,7 +1447,7 @@ moves_loop: // When in check, search starts from here
|
|||
|
||||
Move pv[MAX_PLY+1];
|
||||
StateInfo st;
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize);
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
||||
|
||||
TTEntry* tte;
|
||||
Key posKey;
|
||||
|
@ -1518,7 +1465,6 @@ moves_loop: // When in check, search starts from here
|
|||
}
|
||||
|
||||
Thread* thisThread = pos.this_thread();
|
||||
(ss+1)->ply = ss->ply + 1;
|
||||
bestMove = MOVE_NONE;
|
||||
ss->inCheck = pos.checkers();
|
||||
moveCount = 0;
|
||||
|
@ -1571,10 +1517,9 @@ moves_loop: // When in check, search starts from here
|
|||
}
|
||||
else
|
||||
// In case of null move search use previous static eval with a different sign
|
||||
// and addition of two tempos
|
||||
ss->staticEval = bestValue =
|
||||
(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
|
||||
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
|
||||
// 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.
|
||||
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
|
||||
&thisThread->captureHistory,
|
||||
|
@ -1612,6 +1557,10 @@ moves_loop: // When in check, search starts from here
|
|||
{
|
||||
assert(is_ok(move));
|
||||
|
||||
// Check for legality
|
||||
if (!pos.legal(move))
|
||||
continue;
|
||||
|
||||
givesCheck = pos.gives_check(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
|
||||
&& !givesCheck
|
||||
&& futilityBase > -VALUE_KNOWN_WIN
|
||||
&& !pos.advanced_pawn_push(move))
|
||||
&& type_of(move) != PROMOTION)
|
||||
{
|
||||
|
||||
if (moveCount > 2)
|
||||
|
@ -1650,20 +1599,13 @@ moves_loop: // When in check, search starts from here
|
|||
// Speculative prefetch as early as possible
|
||||
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->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
|
||||
[captureOrPromotion]
|
||||
[pos.moved_piece(move)]
|
||||
[to_sq(move)];
|
||||
|
||||
// CounterMove based pruning
|
||||
// Continuation history based pruning
|
||||
if ( !captureOrPromotion
|
||||
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY
|
||||
&& (*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
|
||||
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);
|
||||
|
||||
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)));
|
||||
|
||||
bonus1 = stat_bonus(depth + 1);
|
||||
bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus
|
||||
: std::min(bonus1, stat_bonus(depth)); // smaller bonus
|
||||
bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus
|
||||
: stat_bonus(depth); // smaller bonus
|
||||
|
||||
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) {
|
||||
|
||||
StateInfo st;
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::kCacheLineSize);
|
||||
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
||||
|
||||
bool ttHit;
|
||||
|
||||
|
|
|
@ -48,12 +48,13 @@ struct Stack {
|
|||
Move excludedMove;
|
||||
Move killers[2];
|
||||
Value staticEval;
|
||||
Depth depth;
|
||||
int statScore;
|
||||
int moveCount;
|
||||
int distanceFromPv;
|
||||
bool inCheck;
|
||||
bool ttPv;
|
||||
bool ttHit;
|
||||
int doubleExtensions;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -106,9 +106,6 @@ template<> inline void swap_endian<uint8_t>(uint8_t&) {}
|
|||
|
||||
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;
|
||||
|
||||
if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare)
|
||||
|
@ -193,7 +190,8 @@ public:
|
|||
std::stringstream ss(Paths);
|
||||
std::string path;
|
||||
|
||||
while (std::getline(ss, path, SepChar)) {
|
||||
while (std::getline(ss, path, SepChar))
|
||||
{
|
||||
fname = path + "/" + f;
|
||||
std::ifstream::open(fname);
|
||||
if (is_open())
|
||||
|
@ -568,7 +566,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
|||
int buf64Size = 64;
|
||||
Sym sym;
|
||||
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
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
|
||||
|
@ -606,8 +605,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
|||
// 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
|
||||
// that will store the value we need.
|
||||
while (d->symlen[sym]) {
|
||||
|
||||
while (d->symlen[sym])
|
||||
{
|
||||
Sym left = d->btree[sym].get<LR::Left>();
|
||||
|
||||
// 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);
|
||||
do
|
||||
squares[size++] = pop_lsb(&b) ^ flipSquares;
|
||||
squares[size++] = pop_lsb(b) ^ flipSquares;
|
||||
while (b);
|
||||
|
||||
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.
|
||||
b = pos.pieces() ^ leadPawns;
|
||||
do {
|
||||
Square s = pop_lsb(&b);
|
||||
Square s = pop_lsb(b);
|
||||
squares[size] = s ^ flipSquares;
|
||||
pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);
|
||||
} while (b);
|
||||
|
@ -1539,6 +1538,14 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
|
|||
WDLScore wdl = -probe_wdl(pos, &result);
|
||||
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
|
||||
{
|
||||
// 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;
|
||||
StateInfo st;
|
||||
WDLScore wdl;
|
||||
|
||||
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);
|
||||
|
||||
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]);
|
||||
|
||||
|
|
|
@ -128,14 +128,16 @@ void Thread::idle_loop() {
|
|||
|
||||
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();
|
||||
|
||||
while (size() > 0)
|
||||
delete back(), pop_back();
|
||||
}
|
||||
|
||||
if (requested > 0) { // create new thread(s)
|
||||
if (requested > 0) // create new thread(s)
|
||||
{
|
||||
push_back(new MainThread(0));
|
||||
|
||||
while (size() < requested)
|
||||
|
|
11
src/thread.h
11
src/thread.h
|
@ -56,14 +56,18 @@ public:
|
|||
void idle_loop();
|
||||
void start_searching();
|
||||
void wait_for_search_finished();
|
||||
size_t id() const { return idx; }
|
||||
|
||||
Pawns::Table pawnsTable;
|
||||
Material::Table materialTable;
|
||||
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;
|
||||
Color nmpColor;
|
||||
std::atomic<uint64_t> nodes, tbHits, TTsaves, bestMoveChanges;
|
||||
ExplosionState state;
|
||||
|
||||
Position rootPos;
|
||||
StateInfo rootState;
|
||||
|
@ -74,8 +78,7 @@ public:
|
|||
LowPlyHistory lowPlyHistory;
|
||||
CapturePieceToHistory captureHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
Score contempt;
|
||||
int failedHighCnt;
|
||||
Score trend;
|
||||
#ifdef USE_MPI
|
||||
struct {
|
||||
std::mutex mutex;
|
||||
|
|
|
@ -68,6 +68,9 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||
TimePoint timeLeft = std::max(TimePoint(1),
|
||||
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"
|
||||
// Default is 100 and changing this value will probably lose elo.
|
||||
timeLeft = slowMover * timeLeft / 100;
|
||||
|
@ -78,15 +81,16 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||
if (limits.movestogo == 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// x moves in y seconds (+ z increment)
|
||||
else
|
||||
{
|
||||
optScale = std::min((0.8 + ply / 128.0) / mtg,
|
||||
0.8 * limits.time[us] / double(timeLeft));
|
||||
optScale = std::min((0.88 + ply / 116.4) / mtg,
|
||||
0.88 * limits.time[us] / double(timeLeft));
|
||||
maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
|
||||
}
|
||||
|
||||
|
|
19
src/tune.cpp
19
src/tune.cpp
|
@ -30,7 +30,6 @@ namespace Stockfish {
|
|||
|
||||
bool Tune::update_on_last;
|
||||
const UCI::Option* LastOption = nullptr;
|
||||
BoolConditions Conditions;
|
||||
static std::map<std::string, int> TuneResults;
|
||||
|
||||
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>::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
|
||||
|
||||
|
||||
|
|
34
src/tune.h
34
src/tune.h
|
@ -46,27 +46,6 @@ struct SetRange {
|
|||
#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
|
||||
/// 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
|
||||
|
@ -159,14 +138,6 @@ class Tune {
|
|||
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;
|
||||
|
||||
public:
|
||||
|
@ -187,11 +158,6 @@ public:
|
|||
|
||||
#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
|
||||
|
||||
#endif // #ifndef TUNE_H_INCLUDED
|
||||
|
|
|
@ -173,6 +173,11 @@ enum Bound {
|
|||
BOUND_EXACT = BOUND_UPPER | BOUND_LOWER
|
||||
};
|
||||
|
||||
enum ExplosionState {
|
||||
EXPLOSION_NONE,
|
||||
MUST_CALM_DOWN
|
||||
};
|
||||
|
||||
enum Value : int {
|
||||
VALUE_ZERO = 0,
|
||||
VALUE_DRAW = 0,
|
||||
|
@ -191,7 +196,6 @@ enum Value : int {
|
|||
BishopValueMg = 825, BishopValueEg = 915,
|
||||
RookValueMg = 1276, RookValueEg = 1380,
|
||||
QueenValueMg = 2538, QueenValueEg = 2682,
|
||||
Tempo = 28,
|
||||
|
||||
MidgameLimit = 15258, EndgameLimit = 3915
|
||||
};
|
||||
|
|
14
src/uci.cpp
14
src/uci.cpp
|
@ -211,13 +211,13 @@ namespace {
|
|||
// Coefficients of a 3rd order polynomial fit based on fishtest data
|
||||
// for two parameters needed to transform eval to the argument of a
|
||||
// logistic function.
|
||||
double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679};
|
||||
double bs[] = {-3.37154371, 28.44489198, -56.67657741, 72.05858751};
|
||||
double as[] = {-3.68389304, 30.07065921, -60.52878723, 149.53378557};
|
||||
double bs[] = {-2.0181857, 15.85685038, -29.83452023, 47.59078827};
|
||||
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];
|
||||
|
||||
// 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 int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
|
||||
|
@ -285,6 +285,14 @@ void UCI::loop(int argc, char* argv[]) {
|
|||
trace_eval(pos);
|
||||
else if (token == "compiler" && Cluster::is_root())
|
||||
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())
|
||||
sync_cout << "Unknown command: " << cmd << sync_endl;
|
||||
|
||||
|
|
|
@ -61,8 +61,6 @@ void init(OptionsMap& o) {
|
|||
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||
|
||||
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["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
|
||||
o["Clear Hash"] << Option(on_clear_hash);
|
||||
|
@ -166,7 +164,7 @@ Option& Option::operator=(const string& v) {
|
|||
|
||||
assert(!type.empty());
|
||||
|
||||
if ( (type != "button" && v.empty())
|
||||
if ( (type != "button" && type != "string" && v.empty())
|
||||
|| (type == "check" && v != "true" && v != "false")
|
||||
|| (type == "spin" && (stof(v) < min || stof(v) > max)))
|
||||
return *this;
|
||||
|
|
|
@ -13,7 +13,7 @@ case $1 in
|
|||
--valgrind)
|
||||
echo "valgrind testing started"
|
||||
prefix=''
|
||||
exeprefix='valgrind --error-exitcode=42'
|
||||
exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full'
|
||||
postfix='1>/dev/null'
|
||||
threads="1"
|
||||
;;
|
||||
|
@ -98,7 +98,7 @@ cat << EOF > game.exp
|
|||
expect "bestmove"
|
||||
|
||||
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"
|
||||
|
||||
send "quit\n"
|
||||
|
|
|
@ -10,7 +10,7 @@ trap 'error ${LINENO}' ERR
|
|||
|
||||
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
|
||||
# the same node count for each iteration.
|
||||
cat << EOF > repeat.exp
|
||||
|
|
Loading…
Reference in New Issue