Merge official-stockfish/master
authorFabian Fichter <ianfab@users.noreply.github.com>
Sun, 18 Jul 2021 14:23:30 +0000 (16:23 +0200)
committerFabian Fichter <ianfab@users.noreply.github.com>
Sun, 18 Jul 2021 14:23:30 +0000 (16:23 +0200)
bench: 5903196

1  2 
README.md
src/evaluate.cpp
src/evaluate.h
src/nnue/evaluate_nnue.cpp
src/position.h
src/search.cpp
src/thread.h
src/uci.cpp
src/ucioption.cpp

diff --cc README.md
Simple merge
@@@ -243,11 -192,10 +219,10 @@@ 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(800);
+   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 };
 +  constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10, 40 };
  
    // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type,
    // higher if multiple safe checks are possible for that piece type.
      // 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()->trend;
 +    Score score = pos.psq_score();
 +    if (T)
 +        Trace::add(MATERIAL, score);
-     score += me->imbalance() + pos.this_thread()->contempt;
++    score += me->imbalance() + pos.this_thread()->trend;
  
      // Probe the pawn hash table
      pe = Pawns::probe(pos);
@@@ -1613,15 -1102,14 +1589,15 @@@ Value Eval::evaluate(const Position& po
           return nnue;
        };
  
-       // If there is PSQ imbalance we use the classical eval.
+       // If there is PSQ imbalance we use the classical eval, but we switch to
+       // NNUE eval faster when shuffling or if the material on the board is high.
+       int r50 = pos.rule50_count();
        Value psq = Value(abs(eg_value(pos.psq_score())));
-       int   r50 = 16 + pos.rule50_count();
-       bool  pure = !pos.check_counting();
-       bool  largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50 && !pure;
-       v = largePsq ? Evaluation<NO_TRACE>(pos).value()  // classical
-                    : adjusted_NNUE();                   // NNUE
 -      bool classical = psq * 5 > (750 + pos.non_pawn_material() / 64) * (5 + r50);
++      bool pure = !pos.check_counting();
++      bool classical = psq * 5 > (750 + pos.non_pawn_material() / 64) * (5 + r50) && !pure;
  
+       v = classical ? Evaluation<NO_TRACE>(pos).value()  // classical
+                     : adjusted_NNUE();                   // NNUE
    }
  
    // Damp down the evaluation linearly when shuffling
@@@ -1664,35 -1143,40 +1640,41 @@@ std::string Eval::trace(Position& pos) 
    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)
-      << "     Variant | " << Term(VARIANT)
-      << "    Winnable | " << Term(WINNABLE)
-      << " ------------+-------------+-------------+------------\n"
-      << "       Total | " << Term(TOTAL);
+      << " 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)
++     << "|    Variant | " << Term(VARIANT)
+      << "|   Winnable | " << Term(WINNABLE)
+      << "+------------+-------------+-------------+-------------+\n"
+      << "|      Total | " << Term(TOTAL)
+      << "+------------+-------------+-------------+-------------+\n";
  
-   v = pos.side_to_move() == WHITE ? v : -v;
+   if (Eval::useNNUE)
+       ss << '\n' << NNUE::trace(pos) << '\n';
  
-   ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\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);
diff --cc src/evaluate.h
Simple merge
@@@ -175,6 -178,220 +178,220 @@@ namespace Stockfish::Eval::NNUE 
      return static_cast<Value>( sum / 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");
+   // Requires the buffer to have capacity for at least 5 values
+   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;
+     }
+   }
+   // Requires the buffer to have capacity for at least 7 values
+   static void format_cp_aligned_dot(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] = '.';
+       buffer[5] = '0' + cp / 10; cp %= 10;
+       buffer[6] = '0' + cp;
+     }
+     else if (cp >= 1000)
+     {
+       buffer[1] = ' ';
+       buffer[2] = '0' + cp / 1000; cp %= 1000;
+       buffer[3] = '0' + cp / 100; cp %= 100;
+       buffer[4] = '.';
+       buffer[5] = '0' + cp / 10; cp %= 10;
+       buffer[6] = '0' + cp;
+     }
+     else
+     {
+       buffer[1] = ' ';
+       buffer[2] = ' ';
+       buffer[3] = '0' + cp / 100; cp %= 100;
+       buffer[4] = '.';
+       buffer[5] = '0' + cp / 10; cp %= 10;
+       buffer[6] = '0' + cp / 1;
+     }
+   }
+   // 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)
++        if (pc != NO_PIECE && type_of(pc) != pos.nnue_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) {
  
diff --cc src/position.h
@@@ -305,6 -171,9 +305,9 @@@ public
    // Used by NNUE
    StateInfo* state() const;
  
 -  void put_piece(Piece pc, Square s);
++  void put_piece(Piece pc, Square s, bool isPromoted = false, Piece unpromotedPc = NO_PIECE);
+   void remove_piece(Square s);
  private:
    // Initialization helpers (used while setting up a position)
    void set_castling_right(Color c, Square rfrom);
diff --cc src/search.cpp
Simple merge
diff --cc src/thread.h
Simple merge
diff --cc src/uci.cpp
@@@ -348,29 -277,14 +348,29 @@@ void UCI::loop(int argc, char* argv[]) 
        else if (token == "d")        sync_cout << pos << sync_endl;
        else if (token == "eval")     trace_eval(pos);
        else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
-       else if (token == "export_net") {
+       else if (token == "export_net")
+       {
            std::optional<std::string> filename;
            std::string f;
-           if (is >> skipws >> f) {
-             filename = f;
-           }
-           Eval::NNUE::export_net(filename);
+           if (is >> skipws >> f)
+               filename = f;
+           Eval::NNUE::save_eval(filename);
        }
 +      else if (token == "load")     { load(is); argc = 1; } // continue reading stdin
 +      else if (token == "check")    check(is);
 +      // UCI-Cyclone omits the "position" keyword
 +      else if (token == "fen" || token == "startpos")
 +      {
 +#ifdef LARGEBOARDS
 +          if (Options["Protocol"] == "uci" && Options["UCI_Variant"] == "chess")
 +          {
 +              Options["Protocol"].set_default("ucicyclone");
 +              Options["UCI_Variant"].set_default("xiangqi");
 +          }
 +#endif
 +          is.seekg(0);
 +          position(pos, is, states);
 +      }
        else if (!token.empty() && token[0] != '#')
            sync_cout << "Unknown command: " << cmd << sync_endl;
  
@@@ -166,10 -60,7 +166,8 @@@ void init(OptionsMap& o) 
  
    constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
  
 +  o["Protocol"]              << Option("uci", {"uci", "usi", "ucci", "ucicyclone", "xboard"});
    o["Debug Log File"]        << Option("", on_logger);
-   o["Contempt"]              << Option(24, -100, 100);
-   o["Analysis Contempt"]     << Option("Both", {"Both", "Off", "White", "Black"});
    o["Threads"]               << Option(1, 1, 512, on_threads);
    o["Hash"]                  << Option(16, 1, MaxHashMB, on_hash_size);
    o["Clear Hash"]            << Option(on_clear_hash);