Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
official-stockfish
GitHub Repository: official-stockfish/Stockfish
Path: blob/master/src/nnue/network.cpp
375 views
1
/*
2
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
3
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
4
5
Stockfish is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9
10
Stockfish is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
14
15
You should have received a copy of the GNU General Public License
16
along with this program. If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
#include "network.h"
20
21
#include <cstdlib>
22
#include <fstream>
23
#include <iostream>
24
#include <memory>
25
#include <optional>
26
#include <type_traits>
27
#include <vector>
28
29
#define INCBIN_SILENCE_BITCODE_WARNING
30
#include "../incbin/incbin.h"
31
32
#include "../evaluate.h"
33
#include "../memory.h"
34
#include "../misc.h"
35
#include "../position.h"
36
#include "../types.h"
37
#include "nnue_architecture.h"
38
#include "nnue_common.h"
39
#include "nnue_misc.h"
40
41
// Macro to embed the default efficiently updatable neural network (NNUE) file
42
// data in the engine binary (using incbin.h, by Dale Weiler).
43
// This macro invocation will declare the following three variables
44
// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data
45
// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end
46
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
47
// Note that this does not work in Microsoft Visual Studio.
48
#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
49
INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig);
50
INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall);
51
#else
52
const unsigned char gEmbeddedNNUEBigData[1] = {0x0};
53
const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1];
54
const unsigned int gEmbeddedNNUEBigSize = 1;
55
const unsigned char gEmbeddedNNUESmallData[1] = {0x0};
56
const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1];
57
const unsigned int gEmbeddedNNUESmallSize = 1;
58
#endif
59
60
namespace {
61
62
struct EmbeddedNNUE {
63
EmbeddedNNUE(const unsigned char* embeddedData,
64
const unsigned char* embeddedEnd,
65
const unsigned int embeddedSize) :
66
data(embeddedData),
67
end(embeddedEnd),
68
size(embeddedSize) {}
69
const unsigned char* data;
70
const unsigned char* end;
71
const unsigned int size;
72
};
73
74
using namespace Stockfish::Eval::NNUE;
75
76
EmbeddedNNUE get_embedded(EmbeddedNNUEType type) {
77
if (type == EmbeddedNNUEType::BIG)
78
return EmbeddedNNUE(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize);
79
else
80
return EmbeddedNNUE(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize);
81
}
82
83
}
84
85
86
namespace Stockfish::Eval::NNUE {
87
88
89
namespace Detail {
90
91
// Read evaluation function parameters
92
template<typename T>
93
bool read_parameters(std::istream& stream, T& reference) {
94
95
std::uint32_t header;
96
header = read_little_endian<std::uint32_t>(stream);
97
if (!stream || header != T::get_hash_value())
98
return false;
99
return reference.read_parameters(stream);
100
}
101
102
// Write evaluation function parameters
103
template<typename T>
104
bool write_parameters(std::ostream& stream, T& reference) {
105
106
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
107
return reference.write_parameters(stream);
108
}
109
110
} // namespace Detail
111
112
template<typename Arch, typename Transformer>
113
Network<Arch, Transformer>::Network(const Network<Arch, Transformer>& other) :
114
evalFile(other.evalFile),
115
embeddedType(other.embeddedType) {
116
117
if (other.featureTransformer)
118
featureTransformer = make_unique_large_page<Transformer>(*other.featureTransformer);
119
120
network = make_unique_aligned<Arch[]>(LayerStacks);
121
122
if (!other.network)
123
return;
124
125
for (std::size_t i = 0; i < LayerStacks; ++i)
126
network[i] = other.network[i];
127
}
128
129
template<typename Arch, typename Transformer>
130
Network<Arch, Transformer>&
131
Network<Arch, Transformer>::operator=(const Network<Arch, Transformer>& other) {
132
evalFile = other.evalFile;
133
embeddedType = other.embeddedType;
134
135
if (other.featureTransformer)
136
featureTransformer = make_unique_large_page<Transformer>(*other.featureTransformer);
137
138
network = make_unique_aligned<Arch[]>(LayerStacks);
139
140
if (!other.network)
141
return *this;
142
143
for (std::size_t i = 0; i < LayerStacks; ++i)
144
network[i] = other.network[i];
145
146
return *this;
147
}
148
149
template<typename Arch, typename Transformer>
150
void Network<Arch, Transformer>::load(const std::string& rootDirectory, std::string evalfilePath) {
151
#if defined(DEFAULT_NNUE_DIRECTORY)
152
std::vector<std::string> dirs = {"<internal>", "", rootDirectory,
153
stringify(DEFAULT_NNUE_DIRECTORY)};
154
#else
155
std::vector<std::string> dirs = {"<internal>", "", rootDirectory};
156
#endif
157
158
if (evalfilePath.empty())
159
evalfilePath = evalFile.defaultName;
160
161
for (const auto& directory : dirs)
162
{
163
if (evalFile.current != evalfilePath)
164
{
165
if (directory != "<internal>")
166
{
167
load_user_net(directory, evalfilePath);
168
}
169
170
if (directory == "<internal>" && evalfilePath == evalFile.defaultName)
171
{
172
load_internal();
173
}
174
}
175
}
176
}
177
178
179
template<typename Arch, typename Transformer>
180
bool Network<Arch, Transformer>::save(const std::optional<std::string>& filename) const {
181
std::string actualFilename;
182
std::string msg;
183
184
if (filename.has_value())
185
actualFilename = filename.value();
186
else
187
{
188
if (evalFile.current != evalFile.defaultName)
189
{
190
msg = "Failed to export a net. "
191
"A non-embedded net can only be saved if the filename is specified";
192
193
sync_cout << msg << sync_endl;
194
return false;
195
}
196
197
actualFilename = evalFile.defaultName;
198
}
199
200
std::ofstream stream(actualFilename, std::ios_base::binary);
201
bool saved = save(stream, evalFile.current, evalFile.netDescription);
202
203
msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net";
204
205
sync_cout << msg << sync_endl;
206
return saved;
207
}
208
209
210
template<typename Arch, typename Transformer>
211
NetworkOutput
212
Network<Arch, Transformer>::evaluate(const Position& pos,
213
AccumulatorStack& accumulatorStack,
214
AccumulatorCaches::Cache<FTDimensions>* cache) const {
215
216
constexpr uint64_t alignment = CacheLineSize;
217
218
alignas(alignment)
219
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
220
221
ASSERT_ALIGNED(transformedFeatures, alignment);
222
223
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
224
const auto psqt =
225
featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
226
const auto positional = network[bucket].propagate(transformedFeatures);
227
return {static_cast<Value>(psqt / OutputScale), static_cast<Value>(positional / OutputScale)};
228
}
229
230
231
template<typename Arch, typename Transformer>
232
void Network<Arch, Transformer>::verify(std::string evalfilePath,
233
const std::function<void(std::string_view)>& f) const {
234
if (evalfilePath.empty())
235
evalfilePath = evalFile.defaultName;
236
237
if (evalFile.current != evalfilePath)
238
{
239
if (f)
240
{
241
std::string msg1 =
242
"Network evaluation parameters compatible with the engine must be available.";
243
std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully.";
244
std::string msg3 = "The UCI option EvalFile might need to specify the full path, "
245
"including the directory name, to the network file.";
246
std::string msg4 = "The default net can be downloaded from: "
247
"https://tests.stockfishchess.org/api/nn/"
248
+ evalFile.defaultName;
249
std::string msg5 = "The engine will be terminated now.";
250
251
std::string msg = "ERROR: " + msg1 + '\n' + "ERROR: " + msg2 + '\n' + "ERROR: " + msg3
252
+ '\n' + "ERROR: " + msg4 + '\n' + "ERROR: " + msg5 + '\n';
253
254
f(msg);
255
}
256
257
exit(EXIT_FAILURE);
258
}
259
260
if (f)
261
{
262
size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks;
263
f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024))
264
+ "MiB, (" + std::to_string(featureTransformer->InputDimensions) + ", "
265
+ std::to_string(network[0].TransformedFeatureDimensions) + ", "
266
+ std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS)
267
+ ", 1))");
268
}
269
}
270
271
272
template<typename Arch, typename Transformer>
273
NnueEvalTrace
274
Network<Arch, Transformer>::trace_evaluate(const Position& pos,
275
AccumulatorStack& accumulatorStack,
276
AccumulatorCaches::Cache<FTDimensions>* cache) const {
277
278
constexpr uint64_t alignment = CacheLineSize;
279
280
alignas(alignment)
281
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
282
283
ASSERT_ALIGNED(transformedFeatures, alignment);
284
285
NnueEvalTrace t{};
286
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
287
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)
288
{
289
const auto materialist =
290
featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
291
const auto positional = network[bucket].propagate(transformedFeatures);
292
293
t.psqt[bucket] = static_cast<Value>(materialist / OutputScale);
294
t.positional[bucket] = static_cast<Value>(positional / OutputScale);
295
}
296
297
return t;
298
}
299
300
301
template<typename Arch, typename Transformer>
302
void Network<Arch, Transformer>::load_user_net(const std::string& dir,
303
const std::string& evalfilePath) {
304
std::ifstream stream(dir + evalfilePath, std::ios::binary);
305
auto description = load(stream);
306
307
if (description.has_value())
308
{
309
evalFile.current = evalfilePath;
310
evalFile.netDescription = description.value();
311
}
312
}
313
314
315
template<typename Arch, typename Transformer>
316
void Network<Arch, Transformer>::load_internal() {
317
// C++ way to prepare a buffer for a memory stream
318
class MemoryBuffer: public std::basic_streambuf<char> {
319
public:
320
MemoryBuffer(char* p, size_t n) {
321
setg(p, p, p + n);
322
setp(p, p + n);
323
}
324
};
325
326
const auto embedded = get_embedded(embeddedType);
327
328
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(embedded.data)),
329
size_t(embedded.size));
330
331
std::istream stream(&buffer);
332
auto description = load(stream);
333
334
if (description.has_value())
335
{
336
evalFile.current = evalFile.defaultName;
337
evalFile.netDescription = description.value();
338
}
339
}
340
341
342
template<typename Arch, typename Transformer>
343
void Network<Arch, Transformer>::initialize() {
344
featureTransformer = make_unique_large_page<Transformer>();
345
network = make_unique_aligned<Arch[]>(LayerStacks);
346
}
347
348
349
template<typename Arch, typename Transformer>
350
bool Network<Arch, Transformer>::save(std::ostream& stream,
351
const std::string& name,
352
const std::string& netDescription) const {
353
if (name.empty() || name == "None")
354
return false;
355
356
return write_parameters(stream, netDescription);
357
}
358
359
360
template<typename Arch, typename Transformer>
361
std::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream) {
362
initialize();
363
std::string description;
364
365
return read_parameters(stream, description) ? std::make_optional(description) : std::nullopt;
366
}
367
368
369
// Read network header
370
template<typename Arch, typename Transformer>
371
bool Network<Arch, Transformer>::read_header(std::istream& stream,
372
std::uint32_t* hashValue,
373
std::string* desc) const {
374
std::uint32_t version, size;
375
376
version = read_little_endian<std::uint32_t>(stream);
377
*hashValue = read_little_endian<std::uint32_t>(stream);
378
size = read_little_endian<std::uint32_t>(stream);
379
if (!stream || version != Version)
380
return false;
381
desc->resize(size);
382
stream.read(&(*desc)[0], size);
383
return !stream.fail();
384
}
385
386
387
// Write network header
388
template<typename Arch, typename Transformer>
389
bool Network<Arch, Transformer>::write_header(std::ostream& stream,
390
std::uint32_t hashValue,
391
const std::string& desc) const {
392
write_little_endian<std::uint32_t>(stream, Version);
393
write_little_endian<std::uint32_t>(stream, hashValue);
394
write_little_endian<std::uint32_t>(stream, std::uint32_t(desc.size()));
395
stream.write(&desc[0], desc.size());
396
return !stream.fail();
397
}
398
399
400
template<typename Arch, typename Transformer>
401
bool Network<Arch, Transformer>::read_parameters(std::istream& stream,
402
std::string& netDescription) const {
403
std::uint32_t hashValue;
404
if (!read_header(stream, &hashValue, &netDescription))
405
return false;
406
if (hashValue != Network::hash)
407
return false;
408
if (!Detail::read_parameters(stream, *featureTransformer))
409
return false;
410
for (std::size_t i = 0; i < LayerStacks; ++i)
411
{
412
if (!Detail::read_parameters(stream, network[i]))
413
return false;
414
}
415
return stream && stream.peek() == std::ios::traits_type::eof();
416
}
417
418
419
template<typename Arch, typename Transformer>
420
bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
421
const std::string& netDescription) const {
422
if (!write_header(stream, Network::hash, netDescription))
423
return false;
424
if (!Detail::write_parameters(stream, *featureTransformer))
425
return false;
426
for (std::size_t i = 0; i < LayerStacks; ++i)
427
{
428
if (!Detail::write_parameters(stream, network[i]))
429
return false;
430
}
431
return bool(stream);
432
}
433
434
// Explicit template instantiations
435
436
template class Network<NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>,
437
FeatureTransformer<TransformedFeatureDimensionsBig>>;
438
439
template class Network<NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>,
440
FeatureTransformer<TransformedFeatureDimensionsSmall>>;
441
442
} // namespace Stockfish::Eval::NNUE
443
444