mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-23 13:17:12 +00:00
Created by training an L1-128 net from scratch with a wider range of
evals in the training data and wld-fen-skipping disabled during
training. The differences in this training data compared to the first
dual nnue PR are:
- removal of all positions with 3 pieces
- when piece count >= 16, keep positions with simple eval above 750
- when piece count < 16, remove positions with simple eval above 3000
The asymmetric data filtering was meant to flatten the training data
piece count distribution, which was previously heavily skewed towards
positions with low piece counts.
Additionally, the simple eval range where the smallnet is used was
widened to cover more positions previously evaluated by the big net and
simple eval.
```yaml
experiment-name: 128--S1-hse-S7-v4-S3-v1-no-wld-skip
training-dataset:
- /data/hse/S3/leela96-filt-v2.min.high-simple-eval-1k.binpack
- /data/hse/S3/dfrc99-16tb7p-eval-filt-v2.min.high-simple-eval-1k.binpack
- /data/hse/S3/test80-apr2022-16tb7p.min.high-simple-eval-1k.binpack
- /data/hse/S7/test60-2020-2tb7p.v6-3072.high-simple-eval-v4.binpack
- /data/hse/S7/test60-novdec2021-12tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test77-nov2021-2tb7p.v6-3072.min.high-simple-eval-v4.binpack
- /data/hse/S7/test77-dec2021-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test77-jan2022-2tb7p.high-simple-eval-v4.binpack
- /data/hse/S7/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test79-apr2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test79-may2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test80-may2022-16tb7p.high-simple-eval-v4.binpack
- /data/hse/S7/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test80-jul2022-16tb7p.v6-dd.min.high-simple-eval-v4.binpack
- /data/hse/S7/test80-aug2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test80-sep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test80-oct2022-16tb7p.v6-dd.high-simple-eval-v4.binpack
- /data/hse/S7/test80-nov2022-16tb7p-v6-dd.min.high-simple-eval-v4.binpack
- /data/hse/S7/test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test80-feb2023-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack
- /data/hse/S7/test80-mar2023-2tb7p.v6-sk16.min.high-simple-eval-v4.binpack
- /data/hse/S7/test80-apr2023-2tb7p-filter-v6-sk16.min.high-simple-eval-v4.binpack
- /data/hse/S7/test80-may2023-2tb7p.v6.min.high-simple-eval-v4.binpack
- /data/hse/S7/test80-jun2023-2tb7p.v6-3072.min.high-simple-eval-v4.binpack
- /data/hse/S7/test80-jul2023-2tb7p.v6-3072.min.high-simple-eval-v4.binpack
- /data/hse/S7/test80-aug2023-2tb7p.v6.min.high-simple-eval-v4.binpack
- /data/hse/S7/test80-sep2023-2tb7p.high-simple-eval-v4.binpack
- /data/hse/S7/test80-oct2023-2tb7p.high-simple-eval-v4.binpack
wld-fen-skipping: False
start-from-engine-test-net: False
nnue-pytorch-branch: linrock/nnue-pytorch/L1-128
engine-test-branch: linrock/Stockfish/L1-128-nolazy
engine-base-branch: linrock/Stockfish/L1-128
num-epochs: 500
start-lambda: 1.0
end-lambda: 1.0
```
Experiment yaml configs converted to easy_train.sh commands with:
https://github.com/linrock/nnue-tools/blob/4339954/yaml_easy_train.py
Binpacks interleaved at training time with:
https://github.com/official-stockfish/nnue-pytorch/pull/259
FT weights permuted with 10k positions from fishpack32.binpack with:
https://github.com/official-stockfish/nnue-pytorch/pull/254
Data filtered for high simple eval positions (v4) with:
https://github.com/linrock/Stockfish/blob/b9c8440/src/tools/transform.cpp#L640-L675
Training data can be found at:
https://robotmoon.com/nnue-training-data/
Local elo at 25k nodes per move of
L1-128 smallnet (nnue-only eval) vs. L1-128 trained on standard S1 data:
nn-epoch319.nnue : -241.7 +/- 3.2
Passed STC vs. 36db936:
https://tests.stockfishchess.org/tests/view/6576b3484d789acf40aabbfe
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 21920 W: 5680 L: 5381 D: 10859
Ptnml(0-2): 82, 2488, 5520, 2789, 81
Passed LTC vs. DualNNUE #4915:
https://tests.stockfishchess.org/tests/view/65775c034d789acf40aac7e3
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 147606 W: 36619 L: 36063 D: 74924
Ptnml(0-2): 98, 16591, 39891, 17103, 120
closes https://github.com/official-stockfish/Stockfish/pull/4919
Bench: 1438336
475 lines
16 KiB
C++
475 lines
16 KiB
C++
/*
|
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
Copyright (C) 2004-2024 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/>.
|
|
*/
|
|
|
|
// Code for calculating NNUE evaluation function
|
|
|
|
#include "evaluate_nnue.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string_view>
|
|
|
|
#include "../evaluate.h"
|
|
#include "../misc.h"
|
|
#include "../position.h"
|
|
#include "../types.h"
|
|
#include "../uci.h"
|
|
#include "nnue_accumulator.h"
|
|
#include "nnue_common.h"
|
|
|
|
namespace Stockfish::Eval::NNUE {
|
|
|
|
// Input feature converter
|
|
LargePagePtr<FeatureTransformer<TransformedFeatureDimensionsBig, &StateInfo::accumulatorBig>>
|
|
featureTransformerBig;
|
|
LargePagePtr<FeatureTransformer<TransformedFeatureDimensionsSmall, &StateInfo::accumulatorSmall>>
|
|
featureTransformerSmall;
|
|
|
|
// Evaluation function
|
|
AlignedPtr<Network<TransformedFeatureDimensionsBig, L2Big, L3Big>> networkBig[LayerStacks];
|
|
AlignedPtr<Network<TransformedFeatureDimensionsSmall, L2Small, L3Small>> networkSmall[LayerStacks];
|
|
|
|
// Evaluation function file names
|
|
std::string fileName[2];
|
|
std::string netDescription[2];
|
|
|
|
namespace Detail {
|
|
|
|
// Initialize the evaluation function parameters
|
|
template<typename T>
|
|
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) {
|
|
|
|
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))));
|
|
std::memset(pointer.get(), 0, sizeof(T));
|
|
}
|
|
|
|
// Read evaluation function parameters
|
|
template<typename T>
|
|
bool read_parameters(std::istream& stream, T& reference) {
|
|
|
|
std::uint32_t header;
|
|
header = read_little_endian<std::uint32_t>(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
|
|
static void initialize(NetSize netSize) {
|
|
|
|
if (netSize == Small)
|
|
{
|
|
Detail::initialize(featureTransformerSmall);
|
|
for (std::size_t i = 0; i < LayerStacks; ++i)
|
|
Detail::initialize(networkSmall[i]);
|
|
}
|
|
else
|
|
{
|
|
Detail::initialize(featureTransformerBig);
|
|
for (std::size_t i = 0; i < LayerStacks; ++i)
|
|
Detail::initialize(networkBig[i]);
|
|
}
|
|
}
|
|
|
|
// Read network header
|
|
static 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);
|
|
*hashValue = read_little_endian<std::uint32_t>(stream);
|
|
size = read_little_endian<std::uint32_t>(stream);
|
|
if (!stream || version != Version)
|
|
return false;
|
|
desc->resize(size);
|
|
stream.read(&(*desc)[0], size);
|
|
return !stream.fail();
|
|
}
|
|
|
|
// Write network header
|
|
static 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, std::uint32_t(desc.size()));
|
|
stream.write(&desc[0], desc.size());
|
|
return !stream.fail();
|
|
}
|
|
|
|
// Read network parameters
|
|
static bool read_parameters(std::istream& stream, NetSize netSize) {
|
|
|
|
std::uint32_t hashValue;
|
|
if (!read_header(stream, &hashValue, &netDescription[netSize]))
|
|
return false;
|
|
if (hashValue != HashValue[netSize])
|
|
return false;
|
|
if (netSize == Big && !Detail::read_parameters(stream, *featureTransformerBig))
|
|
return false;
|
|
if (netSize == Small && !Detail::read_parameters(stream, *featureTransformerSmall))
|
|
return false;
|
|
for (std::size_t i = 0; i < LayerStacks; ++i)
|
|
{
|
|
if (netSize == Big && !Detail::read_parameters(stream, *(networkBig[i])))
|
|
return false;
|
|
if (netSize == Small && !Detail::read_parameters(stream, *(networkSmall[i])))
|
|
return false;
|
|
}
|
|
return stream && stream.peek() == std::ios::traits_type::eof();
|
|
}
|
|
|
|
// Write network parameters
|
|
static bool write_parameters(std::ostream& stream, NetSize netSize) {
|
|
|
|
if (!write_header(stream, HashValue[netSize], netDescription[netSize]))
|
|
return false;
|
|
if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig))
|
|
return false;
|
|
if (netSize == Small && !Detail::write_parameters(stream, *featureTransformerSmall))
|
|
return false;
|
|
for (std::size_t i = 0; i < LayerStacks; ++i)
|
|
{
|
|
if (netSize == Big && !Detail::write_parameters(stream, *(networkBig[i])))
|
|
return false;
|
|
if (netSize == Small && !Detail::write_parameters(stream, *(networkSmall[i])))
|
|
return false;
|
|
}
|
|
return bool(stream);
|
|
}
|
|
|
|
void hint_common_parent_position(const Position& pos) {
|
|
|
|
int simpleEval = simple_eval(pos, pos.side_to_move());
|
|
if (abs(simpleEval) > 1050)
|
|
featureTransformerSmall->hint_common_access(pos);
|
|
else
|
|
featureTransformerBig->hint_common_access(pos);
|
|
}
|
|
|
|
// Evaluation function. Perform differential calculation.
|
|
template<NetSize Net_Size>
|
|
Value evaluate(const Position& pos, bool adjusted, int* complexity) {
|
|
|
|
// 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;
|
|
constexpr int delta = 24;
|
|
|
|
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
|
|
TransformedFeatureType
|
|
transformedFeaturesUnaligned[FeatureTransformer < Small ? TransformedFeatureDimensionsSmall
|
|
: TransformedFeatureDimensionsBig,
|
|
nullptr
|
|
> ::BufferSize + alignment / sizeof(TransformedFeatureType)];
|
|
|
|
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
|
#else
|
|
|
|
alignas(alignment) TransformedFeatureType
|
|
transformedFeatures[FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall
|
|
: TransformedFeatureDimensionsBig,
|
|
nullptr > ::BufferSize];
|
|
#endif
|
|
|
|
ASSERT_ALIGNED(transformedFeatures, alignment);
|
|
|
|
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
|
const auto psqt = Net_Size == Small
|
|
? featureTransformerSmall->transform(pos, transformedFeatures, bucket)
|
|
: featureTransformerBig->transform(pos, transformedFeatures, bucket);
|
|
const auto positional = Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures)
|
|
: networkBig[bucket]->propagate(transformedFeatures);
|
|
|
|
if (complexity)
|
|
*complexity = std::abs(psqt - positional) / OutputScale;
|
|
|
|
// Give more value to positional evaluation when adjusted flag is set
|
|
if (adjusted)
|
|
return static_cast<Value>(((1024 - delta) * psqt + (1024 + delta) * positional)
|
|
/ (1024 * OutputScale));
|
|
else
|
|
return static_cast<Value>((psqt + positional) / OutputScale);
|
|
}
|
|
|
|
template Value evaluate<Big>(const Position& pos, bool adjusted, int* complexity);
|
|
template Value evaluate<Small>(const Position& pos, bool adjusted, int* complexity);
|
|
|
|
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<TransformedFeatureDimensionsBig, nullptr>::BufferSize
|
|
+ alignment / sizeof(TransformedFeatureType)];
|
|
|
|
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
|
#else
|
|
alignas(alignment) TransformedFeatureType
|
|
transformedFeatures[FeatureTransformer<TransformedFeatureDimensionsBig, nullptr>::BufferSize];
|
|
#endif
|
|
|
|
ASSERT_ALIGNED(transformedFeatures, alignment);
|
|
|
|
NnueEvalTrace t{};
|
|
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
|
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)
|
|
{
|
|
const auto materialist = featureTransformerBig->transform(pos, transformedFeatures, bucket);
|
|
const auto positional = networkBig[bucket]->propagate(transformedFeatures);
|
|
|
|
t.psqt[bucket] = static_cast<Value>(materialist / OutputScale);
|
|
t.positional[bucket] = static_cast<Value>(positional / OutputScale);
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
|
|
|
|
|
|
// 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(UCI::to_cp(v));
|
|
if (cp >= 10000)
|
|
{
|
|
buffer[1] = '0' + cp / 10000;
|
|
cp %= 10000;
|
|
buffer[2] = '0' + cp / 1000;
|
|
cp %= 1000;
|
|
buffer[3] = '0' + 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;
|
|
}
|
|
}
|
|
|
|
|
|
// Converts a Value into pawns, always keeping two decimals
|
|
static void format_cp_aligned_dot(Value v, std::stringstream& stream) {
|
|
|
|
const double pawns = std::abs(0.01 * UCI::to_cp(v));
|
|
|
|
stream << (v < 0 ? '-'
|
|
: v > 0 ? '+'
|
|
: ' ')
|
|
<< std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns;
|
|
}
|
|
|
|
|
|
// 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<NNUE::Big>(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->accumulatorBig.computed[WHITE] = false;
|
|
st->accumulatorBig.computed[BLACK] = false;
|
|
|
|
Value eval = evaluate<NNUE::Big>(pos);
|
|
eval = pos.side_to_move() == WHITE ? eval : -eval;
|
|
v = base - eval;
|
|
|
|
pos.put_piece(pc, sq);
|
|
st->accumulatorBig.computed[WHITE] = false;
|
|
st->accumulatorBig.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)
|
|
{
|
|
ss << "| " << bucket << " ";
|
|
ss << " | ";
|
|
format_cp_aligned_dot(t.psqt[bucket], ss);
|
|
ss << " "
|
|
<< " | ";
|
|
format_cp_aligned_dot(t.positional[bucket], ss);
|
|
ss << " "
|
|
<< " | ";
|
|
format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss);
|
|
ss << " "
|
|
<< " |";
|
|
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(const std::string name, std::istream& stream, NetSize netSize) {
|
|
|
|
initialize(netSize);
|
|
fileName[netSize] = name;
|
|
return read_parameters(stream, netSize);
|
|
}
|
|
|
|
// Save eval, to a file stream or a memory stream
|
|
bool save_eval(std::ostream& stream, NetSize netSize) {
|
|
|
|
if (fileName[netSize].empty())
|
|
return false;
|
|
|
|
return write_parameters(stream, netSize);
|
|
}
|
|
|
|
// Save eval, to a file given by its name
|
|
bool save_eval(const std::optional<std::string>& filename, NetSize netSize) {
|
|
|
|
std::string actualFilename;
|
|
std::string msg;
|
|
|
|
if (filename.has_value())
|
|
actualFilename = filename.value();
|
|
else
|
|
{
|
|
if (currentEvalFileName[netSize]
|
|
!= (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig))
|
|
{
|
|
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 = (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig);
|
|
}
|
|
|
|
std::ofstream stream(actualFilename, std::ios_base::binary);
|
|
bool saved = save_eval(stream, netSize);
|
|
|
|
msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net";
|
|
|
|
sync_cout << msg << sync_endl;
|
|
return saved;
|
|
}
|
|
|
|
|
|
} // namespace Stockfish::Eval::NNUE
|