]> git.sesse.net Git - stockfish/blob - src/nnue/evaluate_nnue.cpp
Sometimes change the (materialist, positional) balance
[stockfish] / src / nnue / evaluate_nnue.cpp
1 /*
2   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
3   Copyright (C) 2004-2021 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 // Code for calculating NNUE evaluation function
20
21 #include <iostream>
22 #include <set>
23
24 #include "../evaluate.h"
25 #include "../position.h"
26 #include "../misc.h"
27 #include "../uci.h"
28 #include "../types.h"
29
30 #include "evaluate_nnue.h"
31
32 namespace Stockfish::Eval::NNUE {
33
34   // Input feature converter
35   LargePagePtr<FeatureTransformer> featureTransformer;
36
37   // Evaluation function
38   AlignedPtr<Network> network[LayerStacks];
39
40   // Evaluation function file name
41   std::string fileName;
42   std::string netDescription;
43
44   namespace Detail {
45
46   // Initialize the evaluation function parameters
47   template <typename T>
48   void initialize(AlignedPtr<T>& pointer) {
49
50     pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
51     std::memset(pointer.get(), 0, sizeof(T));
52   }
53
54   template <typename T>
55   void initialize(LargePagePtr<T>& pointer) {
56
57     static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
58     pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
59     std::memset(pointer.get(), 0, sizeof(T));
60   }
61
62   // Read evaluation function parameters
63   template <typename T>
64   bool read_parameters(std::istream& stream, T& reference) {
65
66     std::uint32_t header;
67     header = read_little_endian<std::uint32_t>(stream);
68     if (!stream || header != T::get_hash_value()) return false;
69     return reference.read_parameters(stream);
70   }
71
72   // Write evaluation function parameters
73   template <typename T>
74   bool write_parameters(std::ostream& stream, const T& reference) {
75
76     write_little_endian<std::uint32_t>(stream, T::get_hash_value());
77     return reference.write_parameters(stream);
78   }
79
80   }  // namespace Detail
81
82   // Initialize the evaluation function parameters
83   void initialize() {
84
85     Detail::initialize(featureTransformer);
86     for (std::size_t i = 0; i < LayerStacks; ++i)
87       Detail::initialize(network[i]);
88   }
89
90   // Read network header
91   bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc)
92   {
93     std::uint32_t version, size;
94
95     version     = read_little_endian<std::uint32_t>(stream);
96     *hashValue  = read_little_endian<std::uint32_t>(stream);
97     size        = read_little_endian<std::uint32_t>(stream);
98     if (!stream || version != Version) return false;
99     desc->resize(size);
100     stream.read(&(*desc)[0], size);
101     return !stream.fail();
102   }
103
104   // Write network header
105   bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc)
106   {
107     write_little_endian<std::uint32_t>(stream, Version);
108     write_little_endian<std::uint32_t>(stream, hashValue);
109     write_little_endian<std::uint32_t>(stream, desc.size());
110     stream.write(&desc[0], desc.size());
111     return !stream.fail();
112   }
113
114   // Read network parameters
115   bool read_parameters(std::istream& stream) {
116
117     std::uint32_t hashValue;
118     if (!read_header(stream, &hashValue, &netDescription)) return false;
119     if (hashValue != HashValue) return false;
120     if (!Detail::read_parameters(stream, *featureTransformer)) return false;
121     for (std::size_t i = 0; i < LayerStacks; ++i)
122       if (!Detail::read_parameters(stream, *(network[i]))) return false;
123     return stream && stream.peek() == std::ios::traits_type::eof();
124   }
125
126   // Write network parameters
127   bool write_parameters(std::ostream& stream) {
128
129     if (!write_header(stream, HashValue, netDescription)) return false;
130     if (!Detail::write_parameters(stream, *featureTransformer)) return false;
131     for (std::size_t i = 0; i < LayerStacks; ++i)
132       if (!Detail::write_parameters(stream, *(network[i]))) return false;
133     return (bool)stream;
134   }
135
136   // Evaluation function. Perform differential calculation.
137   Value evaluate(const Position& pos, bool adjusted) {
138
139     // We manually align the arrays on the stack because with gcc < 9.3
140     // overaligning stack variables with alignas() doesn't work correctly.
141
142     constexpr uint64_t alignment = CacheLineSize;
143
144 #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
145     TransformedFeatureType transformedFeaturesUnaligned[
146       FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
147     char bufferUnaligned[Network::BufferSize + alignment];
148
149     auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
150     auto* buffer = align_ptr_up<alignment>(&bufferUnaligned[0]);
151 #else
152     alignas(alignment)
153       TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
154     alignas(alignment) char buffer[Network::BufferSize];
155 #endif
156
157     ASSERT_ALIGNED(transformedFeatures, alignment);
158     ASSERT_ALIGNED(buffer, alignment);
159
160     const std::size_t bucket = (pos.count<ALL_PIECES>() - 1) / 4;
161     const auto [psqt, lazy] = featureTransformer->transform(pos, transformedFeatures, bucket);
162
163     if (lazy)
164       return static_cast<Value>(psqt / OutputScale);
165     else
166     {
167       const auto output = network[bucket]->propagate(transformedFeatures, buffer);
168
169       int materialist = psqt;
170       int positional  = output[0];
171
172       int delta_npm = abs(pos.non_pawn_material(WHITE) - pos.non_pawn_material(BLACK));
173       int entertainment = (adjusted && delta_npm <= BishopValueMg - KnightValueMg ? 7 : 0);
174
175       int A = 128 - entertainment;
176       int B = 128 + entertainment;
177
178       int sum = (A * materialist + B * positional) / 128;
179
180       return static_cast<Value>( sum / OutputScale );
181     }
182   }
183
184   // Load eval, from a file stream or a memory stream
185   bool load_eval(std::string name, std::istream& stream) {
186
187     initialize();
188     fileName = name;
189     return read_parameters(stream);
190   }
191
192   // Save eval, to a file stream or a memory stream
193   bool save_eval(std::ostream& stream) {
194
195     if (fileName.empty())
196       return false;
197
198     return write_parameters(stream);
199   }
200
201 } // namespace Stockfish::Eval::NNUE