Update to current Stockfish development version

Corresponds to commit 8b8a510fd6a1a17b39b2d4b166f60ac7be0dab23 in
Stockfish repository, from Wed Sep 16 17:39:11 2020 +0200.
This commit is contained in:
Peter Osterlund 2020-09-19 23:43:58 +02:00
parent ed5ef03dba
commit 1871f1d54a
13 changed files with 186 additions and 214 deletions

View File

@ -164,5 +164,7 @@ vector<string> setup_bench(const Position& current, istream& is) {
++posCounter; ++posCounter;
} }
list.emplace_back("setoption name Use NNUE value true");
return list; return list;
} }

View File

@ -1015,12 +1015,19 @@ make_v:
Value Eval::evaluate(const Position& pos) { Value Eval::evaluate(const Position& pos) {
// Use classical eval if there is a large imbalance
// If there is a moderate imbalance, use classical eval with probability (1/8),
// as derived from the node counter.
bool useClassical = abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count());
bool classical = !Eval::useNNUE bool classical = !Eval::useNNUE
|| abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count()); || useClassical
|| (abs(eg_value(pos.psq_score())) > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB));
Value v = classical ? Evaluation<NO_TRACE>(pos).value() Value v = classical ? Evaluation<NO_TRACE>(pos).value()
: NNUE::evaluate(pos) * 5 / 4 + Tempo; : NNUE::evaluate(pos) * 5 / 4 + Tempo;
if (classical && Eval::useNNUE && abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count())) if ( useClassical
&& Eval::useNNUE
&& abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count()))
v = NNUE::evaluate(pos) * 5 / 4 + Tempo; v = NNUE::evaluate(pos) * 5 / 4 + Tempo;
// Damp down the evaluation linearly when shuffling // Damp down the evaluation linearly when shuffling

View File

@ -38,7 +38,7 @@ namespace Eval {
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the // for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile. // name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-82215d0fd0df.nnue" #define EvalFileDefaultName "nn-03744f8d56d8.nnue"
namespace NNUE { namespace NNUE {

View File

@ -65,7 +65,7 @@ namespace {
/// Version number. If Version is left empty, then compile date in the format /// Version number. If Version is left empty, then compile date in the format
/// DD-MM-YY and show in engine_info. /// DD-MM-YY and show in engine_info.
const string Version = "12"; const string Version = "";
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We

View File

@ -115,31 +115,16 @@ namespace Eval::NNUE {
return stream && stream.peek() == std::ios::traits_type::eof(); return stream && stream.peek() == std::ios::traits_type::eof();
} }
// Proceed with the difference calculation if possible // Evaluation function. Perform differential calculation.
static void UpdateAccumulatorIfPossible(const Position& pos) { Value evaluate(const Position& pos) {
feature_transformer->UpdateAccumulatorIfPossible(pos);
}
// Calculate the evaluation value
static Value ComputeScore(const Position& pos, bool refresh) {
auto& accumulator = pos.state()->accumulator;
if (!refresh && accumulator.computed_score) {
return accumulator.score;
}
alignas(kCacheLineSize) TransformedFeatureType alignas(kCacheLineSize) TransformedFeatureType
transformed_features[FeatureTransformer::kBufferSize]; transformed_features[FeatureTransformer::kBufferSize];
feature_transformer->Transform(pos, transformed_features, refresh); feature_transformer->Transform(pos, transformed_features);
alignas(kCacheLineSize) char buffer[Network::kBufferSize]; alignas(kCacheLineSize) char buffer[Network::kBufferSize];
const auto output = network->Propagate(transformed_features, buffer); const auto output = network->Propagate(transformed_features, buffer);
auto score = static_cast<Value>(output[0] / FV_SCALE); return static_cast<Value>(output[0] / FV_SCALE);
accumulator.score = score;
accumulator.computed_score = true;
return accumulator.score;
} }
// Load eval, from a file stream or a memory stream // Load eval, from a file stream or a memory stream
@ -150,19 +135,4 @@ namespace Eval::NNUE {
return ReadParameters(stream); return ReadParameters(stream);
} }
// Evaluation function. Perform differential calculation.
Value evaluate(const Position& pos) {
return ComputeScore(pos, false);
}
// Evaluation function. Perform full calculation.
Value compute_eval(const Position& pos) {
return ComputeScore(pos, true);
}
// Proceed with the difference calculation if possible
void update_eval(const Position& pos) {
UpdateAccumulatorIfPossible(pos);
}
} // namespace Eval::NNUE } // namespace Eval::NNUE

View File

@ -29,9 +29,7 @@ namespace Eval::NNUE {
struct alignas(kCacheLineSize) Accumulator { struct alignas(kCacheLineSize) Accumulator {
std::int16_t std::int16_t
accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions]; accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions];
Value score;
bool computed_accumulation; bool computed_accumulation;
bool computed_score;
}; };
} // namespace Eval::NNUE } // namespace Eval::NNUE

View File

@ -29,6 +29,56 @@
namespace Eval::NNUE { namespace Eval::NNUE {
// 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 TILING
#ifdef USE_AVX512
typedef __m512i vec_t;
#define vec_load(a) _mm512_loadA_si512(a)
#define vec_store(a,b) _mm512_storeA_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
#elif USE_AVX2
typedef __m256i vec_t;
#define vec_load(a) _mm256_loadA_si256(a)
#define vec_store(a,b) _mm256_storeA_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;
#elif USE_SSE2
typedef __m128i 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;
#elif USE_MMX
typedef __m64 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;
#elif USE_NEON
typedef int16x8_t 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;
#else
#undef TILING
#endif
// Input feature converter // Input feature converter
class FeatureTransformer { class FeatureTransformer {
@ -36,6 +86,11 @@ namespace Eval::NNUE {
// Number of output dimensions for one side // Number of output dimensions for one side
static constexpr IndexType kHalfDimensions = kTransformedFeatureDimensions; static constexpr IndexType kHalfDimensions = kTransformedFeatureDimensions;
#ifdef TILING
static constexpr IndexType kTileHeight = kNumRegs * sizeof(vec_t) / 2;
static_assert(kHalfDimensions % kTileHeight == 0, "kTileHeight must divide kHalfDimensions");
#endif
public: public:
// Output type // Output type
using OutputType = TransformedFeatureType; using OutputType = TransformedFeatureType;
@ -50,11 +105,13 @@ namespace Eval::NNUE {
// Hash value embedded in the evaluation file // Hash value embedded in the evaluation file
static constexpr std::uint32_t GetHashValue() { static constexpr std::uint32_t GetHashValue() {
return RawFeatures::kHashValue ^ kOutputDimensions; return RawFeatures::kHashValue ^ kOutputDimensions;
} }
// Read network parameters // Read network parameters
bool ReadParameters(std::istream& stream) { bool ReadParameters(std::istream& stream) {
for (std::size_t i = 0; i < kHalfDimensions; ++i) for (std::size_t i = 0; i < kHalfDimensions; ++i)
biases_[i] = read_little_endian<BiasType>(stream); biases_[i] = read_little_endian<BiasType>(stream);
for (std::size_t i = 0; i < kHalfDimensions * kInputDimensions; ++i) for (std::size_t i = 0; i < kHalfDimensions * kInputDimensions; ++i)
@ -64,23 +121,26 @@ namespace Eval::NNUE {
// Proceed with the difference calculation if possible // Proceed with the difference calculation if possible
bool UpdateAccumulatorIfPossible(const Position& pos) const { bool UpdateAccumulatorIfPossible(const Position& pos) const {
const auto now = pos.state(); const auto now = pos.state();
if (now->accumulator.computed_accumulation) { if (now->accumulator.computed_accumulation)
return true; return true;
}
const auto prev = now->previous; const auto prev = now->previous;
if (prev && prev->accumulator.computed_accumulation) { if (prev && prev->accumulator.computed_accumulation) {
UpdateAccumulator(pos); UpdateAccumulator(pos);
return true; return true;
} }
return false; return false;
} }
// Convert input features // Convert input features
void Transform(const Position& pos, OutputType* output, bool refresh) const { void Transform(const Position& pos, OutputType* output) const {
if (refresh || !UpdateAccumulatorIfPossible(pos)) {
if (!UpdateAccumulatorIfPossible(pos))
RefreshAccumulator(pos); RefreshAccumulator(pos);
}
const auto& accumulation = pos.state()->accumulator.accumulation; const auto& accumulation = pos.state()->accumulator.accumulation;
#if defined(USE_AVX2) #if defined(USE_AVX2)
@ -177,74 +237,58 @@ namespace Eval::NNUE {
private: private:
// Calculate cumulative value without using difference calculation // Calculate cumulative value without using difference calculation
void RefreshAccumulator(const Position& pos) const { void RefreshAccumulator(const Position& pos) const {
auto& accumulator = pos.state()->accumulator; auto& accumulator = pos.state()->accumulator;
IndexType i = 0; IndexType i = 0;
Features::IndexList active_indices[2]; Features::IndexList active_indices[2];
RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i], RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i],
active_indices); active_indices);
for (Color perspective : { WHITE, BLACK }) { for (Color perspective : { WHITE, BLACK }) {
std::memcpy(accumulator.accumulation[perspective][i], biases_, #ifdef TILING
kHalfDimensions * sizeof(BiasType)); for (unsigned j = 0; j < kHalfDimensions / kTileHeight; ++j) {
for (const auto index : active_indices[perspective]) { auto biasesTile = reinterpret_cast<const vec_t*>(
const IndexType offset = kHalfDimensions * index; &biases_[j * kTileHeight]);
#if defined(USE_AVX512) auto accTile = reinterpret_cast<vec_t*>(
auto accumulation = reinterpret_cast<__m512i*>( &accumulator.accumulation[perspective][i][j * kTileHeight]);
&accumulator.accumulation[perspective][i][0]); vec_t acc[kNumRegs];
auto column = reinterpret_cast<const __m512i*>(&weights_[offset]);
constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
for (IndexType j = 0; j < kNumChunks; ++j)
_mm512_storeA_si512(&accumulation[j], _mm512_add_epi16(_mm512_loadA_si512(&accumulation[j]), column[j]));
#elif defined(USE_AVX2) for (unsigned k = 0; k < kNumRegs; ++k)
auto accumulation = reinterpret_cast<__m256i*>( acc[k] = biasesTile[k];
&accumulator.accumulation[perspective][i][0]);
auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
for (IndexType j = 0; j < kNumChunks; ++j)
_mm256_storeA_si256(&accumulation[j], _mm256_add_epi16(_mm256_loadA_si256(&accumulation[j]), column[j]));
#elif defined(USE_SSE2) for (const auto index : active_indices[perspective]) {
auto accumulation = reinterpret_cast<__m128i*>( const IndexType offset = kHalfDimensions * index + j * kTileHeight;
&accumulator.accumulation[perspective][i][0]); auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
for (IndexType j = 0; j < kNumChunks; ++j)
accumulation[j] = _mm_add_epi16(accumulation[j], column[j]);
#elif defined(USE_MMX) for (unsigned k = 0; k < kNumRegs; ++k)
auto accumulation = reinterpret_cast<__m64*>( acc[k] = vec_add_16(acc[k], column[k]);
&accumulator.accumulation[perspective][i][0]);
auto column = reinterpret_cast<const __m64*>(&weights_[offset]);
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
for (IndexType j = 0; j < kNumChunks; ++j) {
accumulation[j] = _mm_add_pi16(accumulation[j], column[j]);
} }
#elif defined(USE_NEON) for (unsigned k = 0; k < kNumRegs; k++)
auto accumulation = reinterpret_cast<int16x8_t*>( vec_store(&accTile[k], acc[k]);
&accumulator.accumulation[perspective][i][0]); }
auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
for (IndexType j = 0; j < kNumChunks; ++j)
accumulation[j] = vaddq_s16(accumulation[j], column[j]);
#else #else
std::memcpy(accumulator.accumulation[perspective][i], biases_,
kHalfDimensions * sizeof(BiasType));
for (const auto index : active_indices[perspective]) {
const IndexType offset = kHalfDimensions * index;
for (IndexType j = 0; j < kHalfDimensions; ++j) for (IndexType j = 0; j < kHalfDimensions; ++j)
accumulator.accumulation[perspective][i][j] += weights_[offset + j]; accumulator.accumulation[perspective][i][j] += weights_[offset + j];
#endif
} }
#endif
} }
#if defined(USE_MMX) #if defined(USE_MMX)
_mm_empty(); _mm_empty();
#endif #endif
accumulator.computed_accumulation = true; accumulator.computed_accumulation = true;
accumulator.computed_score = false;
} }
// Calculate cumulative value using difference calculation // Calculate cumulative value using difference calculation
void UpdateAccumulator(const Position& pos) const { void UpdateAccumulator(const Position& pos) const {
const auto prev_accumulator = pos.state()->previous->accumulator; const auto prev_accumulator = pos.state()->previous->accumulator;
auto& accumulator = pos.state()->accumulator; auto& accumulator = pos.state()->accumulator;
IndexType i = 0; IndexType i = 0;
@ -252,29 +296,55 @@ namespace Eval::NNUE {
bool reset[2]; bool reset[2];
RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i], RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i],
removed_indices, added_indices, reset); removed_indices, added_indices, reset);
for (Color perspective : { WHITE, BLACK }) {
#if defined(USE_AVX2) #ifdef TILING
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2); for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) {
auto accumulation = reinterpret_cast<__m256i*>( for (Color perspective : { WHITE, BLACK }) {
&accumulator.accumulation[perspective][i][0]); auto accTile = reinterpret_cast<vec_t*>(
&accumulator.accumulation[perspective][i][j * kTileHeight]);
vec_t acc[kNumRegs];
#elif defined(USE_SSE2) if (reset[perspective]) {
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2); auto biasesTile = reinterpret_cast<const vec_t*>(
auto accumulation = reinterpret_cast<__m128i*>( &biases_[j * kTileHeight]);
&accumulator.accumulation[perspective][i][0]); for (unsigned k = 0; k < kNumRegs; ++k)
acc[k] = biasesTile[k];
} else {
auto prevAccTile = reinterpret_cast<const vec_t*>(
&prev_accumulator.accumulation[perspective][i][j * kTileHeight]);
for (IndexType k = 0; k < kNumRegs; ++k)
acc[k] = vec_load(&prevAccTile[k]);
#elif defined(USE_MMX) // Difference calculation for the deactivated features
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2); for (const auto index : removed_indices[perspective]) {
auto accumulation = reinterpret_cast<__m64*>( const IndexType offset = kHalfDimensions * index + j * kTileHeight;
&accumulator.accumulation[perspective][i][0]); auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
#elif defined(USE_NEON) for (IndexType k = 0; k < kNumRegs; ++k)
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2); acc[k] = vec_sub_16(acc[k], column[k]);
auto accumulation = reinterpret_cast<int16x8_t*>( }
&accumulator.accumulation[perspective][i][0]); }
{ // Difference calculation for the activated features
for (const auto index : added_indices[perspective]) {
const IndexType offset = kHalfDimensions * index + j * kTileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
for (IndexType k = 0; k < kNumRegs; ++k)
acc[k] = vec_add_16(acc[k], column[k]);
}
}
for (IndexType k = 0; k < kNumRegs; ++k)
vec_store(&accTile[k], acc[k]);
}
}
#if defined(USE_MMX)
_mm_empty();
#endif #endif
#else
for (Color perspective : { WHITE, BLACK }) {
if (reset[perspective]) { if (reset[perspective]) {
std::memcpy(accumulator.accumulation[perspective][i], biases_, std::memcpy(accumulator.accumulation[perspective][i], biases_,
kHalfDimensions * sizeof(BiasType)); kHalfDimensions * sizeof(BiasType));
@ -286,83 +356,22 @@ namespace Eval::NNUE {
for (const auto index : removed_indices[perspective]) { for (const auto index : removed_indices[perspective]) {
const IndexType offset = kHalfDimensions * index; const IndexType offset = kHalfDimensions * index;
#if defined(USE_AVX2) for (IndexType j = 0; j < kHalfDimensions; ++j)
auto column = reinterpret_cast<const __m256i*>(&weights_[offset]); accumulator.accumulation[perspective][i][j] -= weights_[offset + j];
for (IndexType j = 0; j < kNumChunks; ++j) {
accumulation[j] = _mm256_sub_epi16(accumulation[j], column[j]);
}
#elif defined(USE_SSE2)
auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
accumulation[j] = _mm_sub_epi16(accumulation[j], column[j]);
}
#elif defined(USE_MMX)
auto column = reinterpret_cast<const __m64*>(&weights_[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
accumulation[j] = _mm_sub_pi16(accumulation[j], column[j]);
}
#elif defined(USE_NEON)
auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
accumulation[j] = vsubq_s16(accumulation[j], column[j]);
}
#else
for (IndexType j = 0; j < kHalfDimensions; ++j) {
accumulator.accumulation[perspective][i][j] -=
weights_[offset + j];
}
#endif
} }
} }
{ // Difference calculation for the activated features { // Difference calculation for the activated features
for (const auto index : added_indices[perspective]) { for (const auto index : added_indices[perspective]) {
const IndexType offset = kHalfDimensions * index; const IndexType offset = kHalfDimensions * index;
#if defined(USE_AVX2) for (IndexType j = 0; j < kHalfDimensions; ++j)
auto column = reinterpret_cast<const __m256i*>(&weights_[offset]); accumulator.accumulation[perspective][i][j] += weights_[offset + j];
for (IndexType j = 0; j < kNumChunks; ++j) {
accumulation[j] = _mm256_add_epi16(accumulation[j], column[j]);
}
#elif defined(USE_SSE2)
auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
accumulation[j] = _mm_add_epi16(accumulation[j], column[j]);
}
#elif defined(USE_MMX)
auto column = reinterpret_cast<const __m64*>(&weights_[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
accumulation[j] = _mm_add_pi16(accumulation[j], column[j]);
}
#elif defined(USE_NEON)
auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
for (IndexType j = 0; j < kNumChunks; ++j) {
accumulation[j] = vaddq_s16(accumulation[j], column[j]);
}
#else
for (IndexType j = 0; j < kHalfDimensions; ++j) {
accumulator.accumulation[perspective][i][j] +=
weights_[offset + j];
}
#endif
} }
} }
} }
#if defined(USE_MMX)
_mm_empty();
#endif #endif
accumulator.computed_accumulation = true; accumulator.computed_accumulation = true;
accumulator.computed_score = false;
} }
using BiasType = std::int16_t; using BiasType = std::int16_t;

View File

@ -704,7 +704,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
// Used by NNUE // Used by NNUE
st->accumulator.computed_accumulation = false; st->accumulator.computed_accumulation = false;
st->accumulator.computed_score = false;
auto& dp = st->dirtyPiece; auto& dp = st->dirtyPiece;
dp.dirty_num = 1; dp.dirty_num = 1;
@ -1000,7 +999,6 @@ void Position::do_null_move(StateInfo& newSt) {
if (Eval::useNNUE) if (Eval::useNNUE)
{ {
std::memcpy(&newSt, st, sizeof(StateInfo)); std::memcpy(&newSt, st, sizeof(StateInfo));
st->accumulator.computed_score = false;
} }
else else
std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); std::memcpy(&newSt, st, offsetof(StateInfo, accumulator));

View File

@ -520,7 +520,7 @@ void Thread::search() {
totBestMoveChanges += th->bestMoveChanges; totBestMoveChanges += th->bestMoveChanges;
th->bestMoveChanges = 0; th->bestMoveChanges = 0;
} }
double bestMoveInstability = 1 + totBestMoveChanges / Threads.size(); double bestMoveInstability = 1 + 2 * totBestMoveChanges / Threads.size();
double totalTime = rootMoves.size() == 1 ? 0 : double totalTime = rootMoves.size() == 1 ? 0 :
Time.optimum() * fallingEval * reduction * bestMoveInstability; Time.optimum() * fallingEval * reduction * bestMoveInstability;
@ -597,7 +597,7 @@ namespace {
Move ttMove, move, excludedMove, bestMove; Move ttMove, move, excludedMove, bestMove;
Depth extension, newDepth; Depth extension, newDepth;
Value bestValue, value, ttValue, eval, maxValue, probCutBeta; Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
bool ttHit, formerPv, givesCheck, improving, didLMR, priorCapture; bool formerPv, givesCheck, improving, didLMR, priorCapture;
bool captureOrPromotion, doFullDepthSearch, moveCountPruning, bool captureOrPromotion, doFullDepthSearch, moveCountPruning,
ttCapture, singularQuietLMR; ttCapture, singularQuietLMR;
Piece movedPiece; Piece movedPiece;
@ -654,9 +654,7 @@ namespace {
// starts with statScore = 0. Later grandchildren start with the last calculated // starts with statScore = 0. Later grandchildren start with the last calculated
// statScore of the previous grandchild. This influences the reduction rules in // statScore of the previous grandchild. This influences the reduction rules in
// LMR which are based on the statScore of parent position. // LMR which are based on the statScore of parent position.
if (rootNode) if (!rootNode)
(ss+4)->statScore = 0;
else
(ss+2)->statScore = 0; (ss+2)->statScore = 0;
// Step 4. Transposition table lookup. We don't want the score of a partial // Step 4. Transposition table lookup. We don't want the score of a partial
@ -664,12 +662,12 @@ namespace {
// position key in case of an excluded move. // position key in case of an excluded move.
excludedMove = ss->excludedMove; excludedMove = ss->excludedMove;
posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove); posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove);
tte = TT.probe(posKey, ttHit); tte = TT.probe(posKey, ss->ttHit);
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
: ttHit ? tte->move() : MOVE_NONE; : ss->ttHit ? tte->move() : MOVE_NONE;
if (!excludedMove) if (!excludedMove)
ss->ttPv = PvNode || (ttHit && tte->is_pv()); ss->ttPv = PvNode || (ss->ttHit && tte->is_pv());
formerPv = ss->ttPv && !PvNode; formerPv = ss->ttPv && !PvNode;
if ( ss->ttPv if ( ss->ttPv
@ -681,11 +679,11 @@ namespace {
// thisThread->ttHitAverage can be used to approximate the running average of ttHit // thisThread->ttHitAverage can be used to approximate the running average of ttHit
thisThread->ttHitAverage = (TtHitAverageWindow - 1) * thisThread->ttHitAverage / TtHitAverageWindow thisThread->ttHitAverage = (TtHitAverageWindow - 1) * thisThread->ttHitAverage / TtHitAverageWindow
+ TtHitAverageResolution * ttHit; + TtHitAverageResolution * ss->ttHit;
// At non-PV nodes we check for an early TT cutoff // At non-PV nodes we check for an early TT cutoff
if ( !PvNode if ( !PvNode
&& ttHit && ss->ttHit
&& tte->depth() >= depth && tte->depth() >= depth
&& ttValue != VALUE_NONE // Possible in case of TT access race && ttValue != VALUE_NONE // Possible in case of TT access race
&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER) && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
@ -778,7 +776,7 @@ namespace {
improving = false; improving = false;
goto moves_loop; goto moves_loop;
} }
else if (ttHit) else if (ss->ttHit)
{ {
// Never assume anything about values stored in TT // Never assume anything about values stored in TT
ss->staticEval = eval = tte->eval(); ss->staticEval = eval = tte->eval();
@ -882,14 +880,14 @@ namespace {
// there and in further interactions with transposition table cutoff depth is set to depth - 3 // there and in further interactions with transposition table cutoff depth is set to depth - 3
// because probCut search has depth set to depth - 4 but we also do a move before it // because probCut search has depth set to depth - 4 but we also do a move before it
// so effective depth is equal to depth - 3 // so effective depth is equal to depth - 3
&& !( ttHit && !( ss->ttHit
&& tte->depth() >= depth - 3 && tte->depth() >= depth - 3
&& ttValue != VALUE_NONE && ttValue != VALUE_NONE
&& ttValue < probCutBeta)) && ttValue < probCutBeta))
{ {
// if ttMove is a capture and value from transposition table is good enough produce probCut // if ttMove is a capture and value from transposition table is good enough produce probCut
// cutoff without digging into actual probCut search // cutoff without digging into actual probCut search
if ( ttHit if ( ss->ttHit
&& tte->depth() >= depth - 3 && tte->depth() >= depth - 3
&& ttValue != VALUE_NONE && ttValue != VALUE_NONE
&& ttValue >= probCutBeta && ttValue >= probCutBeta
@ -933,7 +931,7 @@ namespace {
if (value >= probCutBeta) if (value >= probCutBeta)
{ {
// if transposition table doesn't have equal or more deep info write probCut data into it // if transposition table doesn't have equal or more deep info write probCut data into it
if ( !(ttHit if ( !(ss->ttHit
&& tte->depth() >= depth - 3 && tte->depth() >= depth - 3
&& ttValue != VALUE_NONE)) && ttValue != VALUE_NONE))
tte->save(posKey, value_to_tt(value, ss->ply), ttPv, tte->save(posKey, value_to_tt(value, ss->ply), ttPv,
@ -1058,7 +1056,6 @@ moves_loop: // When in check, search starts from here
if ( !givesCheck if ( !givesCheck
&& lmrDepth < 6 && lmrDepth < 6
&& !(PvNode && abs(bestValue) < 2) && !(PvNode && abs(bestValue) < 2)
&& PieceValue[MG][type_of(movedPiece)] >= PieceValue[MG][type_of(pos.piece_on(to_sq(move)))]
&& !ss->inCheck && !ss->inCheck
&& ss->staticEval + 169 + 244 * lmrDepth && ss->staticEval + 169 + 244 * lmrDepth
+ PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha) + PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha)
@ -1129,11 +1126,6 @@ moves_loop: // When in check, search starts from here
&& pos.non_pawn_material() <= 2 * RookValueMg) && pos.non_pawn_material() <= 2 * RookValueMg)
extension = 1; extension = 1;
// Castling extension
if ( type_of(move) == CASTLING
&& popcount(pos.pieces(us) & ~pos.pieces(PAWN) & (to_sq(move) & KingSide ? KingSide : QueenSide)) <= 2)
extension = 1;
// Late irreversible move extension // Late irreversible move extension
if ( move == ttMove if ( move == ttMove
&& pos.rule50_count() > 80 && pos.rule50_count() > 80
@ -1168,13 +1160,6 @@ moves_loop: // When in check, search starts from here
{ {
Depth r = reduction(improving, depth, moveCount); Depth r = reduction(improving, depth, moveCount);
// Decrease reduction at non-check cut nodes for second move at low depths
if ( cutNode
&& depth <= 10
&& moveCount <= 2
&& !ss->inCheck)
r--;
// Decrease reduction if the ttHit running average is large // Decrease reduction if the ttHit running average is large
if (thisThread->ttHitAverage > 509 * TtHitAverageResolution * TtHitAverageWindow / 1024) if (thisThread->ttHitAverage > 509 * TtHitAverageResolution * TtHitAverageWindow / 1024)
r--; r--;
@ -1196,7 +1181,7 @@ moves_loop: // When in check, search starts from here
// Decrease reduction if ttMove has been singularly extended (~3 Elo) // Decrease reduction if ttMove has been singularly extended (~3 Elo)
if (singularQuietLMR) if (singularQuietLMR)
r -= 1 + formerPv; r--;
if (!captureOrPromotion) if (!captureOrPromotion)
{ {
@ -1430,7 +1415,7 @@ moves_loop: // When in check, search starts from here
Move ttMove, move, bestMove; Move ttMove, move, bestMove;
Depth ttDepth; Depth ttDepth;
Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha;
bool ttHit, pvHit, givesCheck, captureOrPromotion; bool pvHit, givesCheck, captureOrPromotion;
int moveCount; int moveCount;
if (PvNode) if (PvNode)
@ -1460,13 +1445,13 @@ moves_loop: // When in check, search starts from here
: DEPTH_QS_NO_CHECKS; : DEPTH_QS_NO_CHECKS;
// Transposition table lookup // Transposition table lookup
posKey = pos.key(); posKey = pos.key();
tte = TT.probe(posKey, ttHit); tte = TT.probe(posKey, ss->ttHit);
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
ttMove = ttHit ? tte->move() : MOVE_NONE; ttMove = ss->ttHit ? tte->move() : MOVE_NONE;
pvHit = ttHit && tte->is_pv(); pvHit = ss->ttHit && tte->is_pv();
if ( !PvNode if ( !PvNode
&& ttHit && ss->ttHit
&& tte->depth() >= ttDepth && tte->depth() >= ttDepth
&& ttValue != VALUE_NONE // Only in case of TT access race && ttValue != VALUE_NONE // Only in case of TT access race
&& (ttValue >= beta ? (tte->bound() & BOUND_LOWER) && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
@ -1481,7 +1466,7 @@ moves_loop: // When in check, search starts from here
} }
else else
{ {
if (ttHit) if (ss->ttHit)
{ {
// Never assume anything about values stored in TT // Never assume anything about values stored in TT
if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE)
@ -1500,7 +1485,7 @@ moves_loop: // When in check, search starts from here
// Stand pat. Return immediately if static value is at least beta // Stand pat. Return immediately if static value is at least beta
if (bestValue >= beta) if (bestValue >= beta)
{ {
if (!ttHit) if (!ss->ttHit)
tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
DEPTH_NONE, MOVE_NONE, ss->staticEval); DEPTH_NONE, MOVE_NONE, ss->staticEval);
@ -1564,7 +1549,9 @@ moves_loop: // When in check, search starts from here
} }
// Do not search moves with negative SEE values // Do not search moves with negative SEE values
if (!ss->inCheck && !pos.see_ge(move)) if ( !ss->inCheck
&& !(givesCheck && pos.is_discovery_check_on_king(~pos.side_to_move(), move))
&& !pos.see_ge(move))
continue; continue;
// Speculative prefetch as early as possible // Speculative prefetch as early as possible
@ -1716,8 +1703,8 @@ moves_loop: // When in check, search starts from here
else else
captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1;
// Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted // Extra penalty for a quiet early move that was not a TT move or main killer move in previous ply when it gets refuted
if ( ((ss-1)->moveCount == 1 || ((ss-1)->currentMove == (ss-1)->killers[0])) if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0]))
&& !pos.captured_piece()) && !pos.captured_piece())
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1);

View File

@ -49,6 +49,7 @@ struct Stack {
int moveCount; int moveCount;
bool inCheck; bool inCheck;
bool ttPv; bool ttPv;
bool ttHit;
}; };

View File

@ -170,7 +170,7 @@ namespace {
if (token == "go" || token == "eval") if (token == "go" || token == "eval")
{ {
cerr << "\nPosition: " << cnt++ << '/' << num << endl; cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl;
if (token == "go") if (token == "go")
{ {
go(pos, is, states); go(pos, is, states);

View File

@ -36,7 +36,7 @@ import org.petero.droidfish.EngineOptions;
/** Stockfish engine running as process, started from assets resource. */ /** Stockfish engine running as process, started from assets resource. */
public class InternalStockFish extends ExternalEngine { public class InternalStockFish extends ExternalEngine {
private static final String defaultNet = "nn-82215d0fd0df.nnue"; private static final String defaultNet = "nn-03744f8d56d8.nnue";
private static final String netOption = "evalfile"; private static final String netOption = "evalfile";
private File defaultNetFile; // To get the full path of the copied default network file private File defaultNetFile; // To get the full path of the copied default network file