diff --git a/src/nnue.cpp b/src/nnue.cpp index d0f74a5..bedb580 100644 --- a/src/nnue.cpp +++ b/src/nnue.cpp @@ -21,7 +21,14 @@ // const unsigned char *const gEVALEnd; // a marker to the end // const unsigned int gEVALSize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. -#if !defined(_MSC_VER) +#if defined(NO_EMBED_NET) +// Rama Chess: no embedded net, see NNUE::loadNetworkFile() +#include +#include +#include +#include +#include "nnz_permutation.h" +#elif !defined(_MSC_VER) INCBIN(EVAL, EVALFILE); #else const unsigned char gEVALData[1] = {}; @@ -130,10 +137,123 @@ void load_unquantize_andquant() { exit(12); } +#if defined(NO_EMBED_NET) +bool NNUE::networkLoaded = false; +static Network* externalNet = nullptr; + +void NNUE::init() { + // zero-filled, committed lazily; filled by loadNetworkFile() + void* mem = mmap(nullptr, sizeof(Network), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mem == MAP_FAILED) { + std::cout << "info string ERROR: cannot allocate network memory" << std::endl; + std::exit(1); + } + externalNet = static_cast(mem); + net = externalNet; +} + +// Same transform as tools/preprocess.cpp (permute_transpose), done in the engine. +static void preprocessNetwork(const QuantisedNetwork& q, Network& out) { + const int* permutation = NNZ_PERMUTATION; + for (int feature = 0; feature < INPUT_BUCKETS * NUM_INPUTS; ++feature) + for (int output = 0; output < L1_SIZE / 2; ++output) { + const int source = permutation[output]; + out.FTWeights[feature * L1_SIZE + output] = q.FTWeights[feature * L1_SIZE + source]; + out.FTWeights[feature * L1_SIZE + output + L1_SIZE / 2] = q.FTWeights[feature * L1_SIZE + source + L1_SIZE / 2]; + } + for (int output = 0; output < L1_SIZE / 2; ++output) { + const int source = permutation[output]; + out.FTBiases[output] = q.FTBiases[source]; + out.FTBiases[output + L1_SIZE / 2] = q.FTBiases[source + L1_SIZE / 2]; + } +#if defined(USE_SIMD) + __m128i *weight = reinterpret_cast<__m128i*>(out.FTWeights); + __m128i *biases = reinterpret_cast<__m128i*>(out.FTBiases); + constexpr int numChunks = sizeof(__m128i) / sizeof(int16_t); +#if defined(USE_AVX512) + constexpr int numRegi = 8; + constexpr int order[numRegi] = {0, 2, 4, 6, 1, 3, 5, 7}; +#elif defined(USE_AVX2) + constexpr int numRegi = 4; + constexpr int order[numRegi] = {0, 2, 1, 3}; +#endif + __m128i regi[numRegi]; + for (int i = 0; i < INPUT_BUCKETS * NUM_INPUTS * L1_SIZE / numChunks; i += numRegi) { + for (int j = 0; j < numRegi; ++j) regi[j] = weight[i + j]; + for (int j = 0; j < numRegi; ++j) weight[i + j] = regi[order[j]]; + } + for (int i = 0; i < L1_SIZE / numChunks; i += numRegi) { + for (int j = 0; j < numRegi; ++j) regi[j] = biases[i + j]; + for (int j = 0; j < numRegi; ++j) biases[i + j] = regi[order[j]]; + } +#endif + for (int bucket = 0; bucket < OUTPUT_BUCKETS; ++bucket) { +#if defined(USE_SIMD) + for (int i = 0; i < L1_SIZE / L1_CHUNK_PER_32; ++i) + for (int j = 0; j < L2_SIZE; ++j) + for (int k = 0; k < L1_CHUNK_PER_32; ++k) { + const int output = i * L1_CHUNK_PER_32 + k; + const int source = permutation[output % (L1_SIZE / 2)] + output / (L1_SIZE / 2) * (L1_SIZE / 2); + out.L1Weights[bucket][i * L1_CHUNK_PER_32 * L2_SIZE + j * L1_CHUNK_PER_32 + k] = q.L1Weights[source][bucket][j]; + } +#else + for (int i = 0; i < L1_SIZE; ++i) { + const int source = permutation[i % (L1_SIZE / 2)] + i / (L1_SIZE / 2) * (L1_SIZE / 2); + for (int j = 0; j < L2_SIZE; ++j) + out.L1Weights[bucket][j * L1_SIZE + i] = q.L1Weights[source][bucket][j]; + } +#endif + for (int i = 0; i < L2_SIZE; ++i) + out.L1Biases[bucket][i] = q.L1Biases[bucket][i]; + for (int i = 0; i < EFFECTIVE_L2_SIZE; ++i) + for (int j = 0; j < L3_SIZE; ++j) + out.L2Weights[bucket][i * L3_SIZE + j] = q.L2Weights[i][bucket][j]; + for (int i = 0; i < L3_SIZE; ++i) + out.L2Biases[bucket][i] = q.L2Biases[bucket][i]; + for (int i = 0; i < L3_SIZE; ++i) + out.L3Weights[bucket][i] = q.L3Weights[i][bucket]; + out.L3Biases[bucket] = q.L3Biases[bucket]; + } +} + +// Loads the official quantised nn.net (sizeof(QuantisedNetwork) bytes; bullet's padding after it is ignored). +bool NNUE::loadNetworkFile(const std::string& path) { + networkLoaded = false; + std::ifstream f(path, std::ios::binary | std::ios::ate); + if (!f) { + std::cout << "info string ERROR: EvalFile " << path << ": cannot open file" << std::endl; + return false; + } + const std::streamsize size = f.tellg(); + if (size < static_cast(sizeof(QuantisedNetwork)) || size > static_cast(sizeof(QuantisedNetwork) + 64)) { + std::cout << "info string ERROR: EvalFile " << path << ": wrong size " << size + << " (expected " << sizeof(QuantisedNetwork) << ")" << std::endl; + return false; + } + f.seekg(0); + std::vector buf(sizeof(QuantisedNetwork)); + if (!f.read(buf.data(), sizeof(QuantisedNetwork))) { + std::cout << "info string ERROR: EvalFile " << path << ": read error" << std::endl; + return false; + } + preprocessNetwork(*reinterpret_cast(buf.data()), *externalNet); + networkLoaded = true; + std::cout << "info string EvalFile loaded: " << path << std::endl; + return true; +} +#else +bool NNUE::networkLoaded = true; + void NNUE::init() { net = reinterpret_cast(gEVALData); } +bool NNUE::loadNetworkFile(const std::string& path) { + std::cout << "info string EvalFile ignored (embedded net): " << path << std::endl; + return false; +} +#endif + // does FT activate for one pov at a time void NNUE::povActivateAffine(Position *pos, NNUE::FinnyTable *FinnyPointer, const int side, uint16_t *base, uint16_t *nnzIndices, int &nnzCount, uint8_t *output) { diff --git a/src/nnue.h b/src/nnue.h index d715e92..626a4e2 100644 --- a/src/nnue.h +++ b/src/nnue.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "bitboard.h" #include "simd.h" @@ -29,7 +30,8 @@ constexpr int NET_SCALE = 362; constexpr float L1_MUL = float(1 << FT_SHIFT) / float(FT_QUANT * FT_QUANT * L1_QUANT); constexpr float WEIGHT_CLIPPING = 1.98f; -static_assert(std::round(L1_QUANT * WEIGHT_CLIPPING) * (FT_QUANT * FT_QUANT >> FT_SHIFT) * 4 <= 32767); +// Rama Chess: libc++ std::round is not constexpr; 1.98 * 64 = 126.72 -> 127 +static_assert(127 * (FT_QUANT * FT_QUANT >> FT_SHIFT) * 4 <= 32767); #if defined(USE_SIMD) constexpr int FT_CHUNK_SIZE = sizeof(vepi16) / sizeof(int16_t); @@ -121,6 +123,9 @@ struct NNUE { static int output(Position *pos, FinnyTable* FinnyPointer); static void init(); + // Rama Chess: external net (UCI option EvalFile) + static bool networkLoaded; + static bool loadNetworkFile(const std::string& path); static size_t getIndex(const int piece, const int square, const int side, const int bucket, const bool flip); }; diff --git a/src/nnz_permutation.h b/src/nnz_permutation.h new file mode 100644 index 0000000..1a84690 --- /dev/null +++ b/src/nnz_permutation.h @@ -0,0 +1,38 @@ +#pragma once +// Rama Chess: copy of nnz-permutation.txt (Alexandria v9.1.0), used to preprocess an +// external nn.net inside the engine (same as tools/preprocess.cpp). +#include "nnue.h" +constexpr int NNZ_PERMUTATION[L1_SIZE / 2] = { + 603, 720, 312, 139, 684, 557, 433, 466, 194, 610, 315, 132, 677, 257, 498, 146, 54, 197, 61, 709, 427, 104, 101, 637, + 367, 226, 157, 214, 728, 269, 62, 566, 400, 316, 286, 300, 635, 620, 385, 46, 592, 484, 524, 734, 486, 428, 16, 463, + 325, 167, 622, 714, 686, 464, 752, 302, 346, 688, 97, 531, 37, 608, 134, 763, 365, 507, 579, 742, 310, 246, 409, 667, + 643, 76, 158, 227, 503, 85, 569, 488, 296, 593, 415, 175, 18, 461, 555, 695, 746, 153, 147, 223, 90, 248, 721, 717, + 20, 563, 523, 389, 654, 533, 764, 682, 651, 304, 422, 735, 261, 255, 114, 480, 508, 521, 67, 403, 205, 38, 66, 518, + 337, 761, 640, 89, 222, 641, 375, 369, 408, 108, 338, 577, 560, 196, 250, 529, 700, 730, 481, 527, 94, 465, 538, 174, + 2, 675, 98, 266, 715, 135, 206, 604, 636, 275, 332, 331, 288, 224, 618, 314, 323, 120, 431, 412, 436, 187, 565, 382, + 360, 178, 318, 562, 757, 109, 512, 418, 505, 550, 298, 691, 264, 390, 655, 600, 225, 396, 84, 653, 581, 320, 414, 597, + 543, 138, 397, 681, 513, 160, 5, 228, 470, 345, 221, 324, 210, 439, 425, 646, 59, 697, 343, 87, 473, 456, 79, 72, + 122, 21, 588, 236, 142, 719, 124, 383, 26, 726, 621, 736, 656, 384, 30, 370, 698, 665, 48, 501, 376, 670, 515, 105, + 743, 751, 475, 474, 107, 671, 253, 687, 349, 648, 762, 23, 379, 402, 491, 176, 299, 282, 166, 232, 605, 321, 152, 399, + 199, 578, 350, 692, 669, 144, 69, 193, 631, 267, 51, 148, 3, 696, 440, 362, 494, 458, 404, 272, 532, 242, 629, 17, + 68, 630, 102, 535, 237, 663, 493, 179, 43, 417, 81, 121, 411, 462, 190, 55, 279, 683, 301, 112, 231, 40, 77, 707, + 265, 44, 623, 701, 689, 34, 126, 489, 406, 195, 208, 177, 115, 50, 441, 738, 334, 580, 215, 613, 113, 615, 748, 666, + 568, 156, 584, 270, 693, 388, 363, 82, 426, 351, 116, 499, 386, 93, 506, 658, 32, 571, 387, 444, 413, 41, 162, 280, + 453, 31, 545, 335, 616, 712, 71, 537, 364, 690, 638, 747, 155, 283, 657, 287, 6, 86, 479, 249, 165, 611, 442, 731, + 342, 118, 556, 547, 29, 245, 534, 598, 644, 22, 211, 13, 80, 12, 448, 189, 230, 234, 530, 706, 495, 520, 599, 247, + 128, 549, 554, 354, 405, 169, 394, 548, 674, 567, 419, 47, 344, 88, 451, 217, 233, 368, 11, 519, 263, 528, 373, 756, + 407, 664, 713, 704, 36, 355, 268, 348, 574, 183, 609, 243, 49, 173, 650, 203, 239, 186, 10, 258, 457, 542, 673, 536, + 361, 328, 722, 172, 159, 305, 766, 398, 150, 626, 391, 497, 416, 477, 52, 393, 42, 200, 514, 339, 459, 750, 660, 619, + 306, 432, 680, 145, 737, 262, 303, 290, 632, 278, 274, 517, 606, 602, 284, 522, 509, 583, 358, 468, 130, 661, 180, 133, + 546, 372, 171, 739, 724, 582, 91, 289, 558, 625, 647, 754, 185, 614, 9, 741, 209, 649, 336, 594, 510, 745, 443, 437, + 685, 28, 154, 424, 733, 238, 445, 259, 202, 244, 371, 672, 642, 467, 421, 73, 749, 429, 449, 552, 595, 285, 420, 212, + 476, 292, 319, 308, 136, 612, 702, 25, 103, 235, 590, 35, 119, 322, 587, 359, 455, 377, 347, 341, 430, 662, 559, 19, + 576, 33, 188, 127, 564, 760, 181, 63, 727, 70, 487, 327, 634, 0, 291, 125, 703, 676, 483, 356, 472, 758, 333, 204, + 446, 585, 164, 526, 295, 591, 357, 168, 627, 326, 711, 198, 56, 723, 252, 573, 366, 732, 329, 64, 392, 110, 381, 45, + 14, 490, 254, 151, 53, 99, 96, 294, 271, 617, 92, 137, 220, 131, 744, 541, 589, 352, 1, 710, 83, 149, 516, 256, + 705, 447, 469, 141, 75, 540, 218, 755, 106, 492, 572, 753, 129, 586, 307, 191, 57, 633, 140, 452, 699, 471, 24, 423, + 117, 273, 15, 60, 668, 281, 207, 553, 260, 450, 78, 240, 678, 276, 219, 659, 143, 182, 500, 100, 438, 410, 170, 740, + 645, 478, 435, 317, 163, 504, 729, 309, 213, 718, 716, 378, 725, 95, 395, 460, 694, 4, 544, 759, 74, 192, 277, 765, + 374, 551, 767, 293, 311, 330, 297, 58, 39, 251, 570, 539, 241, 596, 229, 161, 184, 525, 607, 652, 434, 496, 27, 628, + 561, 679, 575, 502, 201, 65, 380, 485, 511, 216, 353, 708, 624, 401, 111, 639, 601, 123, 313, 454, 482, 7, 340, 8, +}; diff --git a/src/simd.h b/src/simd.h index 12de881..4961826 100644 --- a/src/simd.h +++ b/src/simd.h @@ -79,7 +79,11 @@ inline vepi16 vec_sub_epi16 (const vepi16 vec0, const vepi16 vec1) { return _mm inline vepi16 vec_max_epi16 (const vepi16 vec0, const vepi16 vec1) { return _mm256_max_epi16(vec0, vec1); } inline vepi16 vec_min_epi16 (const vepi16 vec0, const vepi16 vec1) { return _mm256_min_epi16(vec0, vec1); } inline vepi16 vec_mulhi_epi16(const vepi16 vec0, const vepi16 vec1) { return _mm256_mulhi_epi16(vec0, vec1); } +#ifdef ALEXANDRIA_SIMDE +inline vepi16 vec_slli_epi16 (const vepi16 vec, const int shift) { return _mm256_sll_epi16(vec, _mm_cvtsi32_si128(shift)); } +#else inline vepi16 vec_slli_epi16 (const vepi16 vec, const int shift) { return _mm256_slli_epi16(vec, shift); } +#endif inline uint16_t vec_nnz_mask(const vepi32 vec) { return _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(vec, _mm256_setzero_si256()))); } inline vepi8 vec_packus_epi16(const vepi16 vec0, const vepi16 vec1) { return _mm256_packus_epi16(vec0, vec1); } diff --git a/src/uci.cpp b/src/uci.cpp index 700098a..99c2782 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -192,6 +192,10 @@ void UciLoop(int argc, char** argv) { return; } } + if (!NNUE::networkLoaded) { + std::cout << "info string ERROR: no network loaded (built without an embedded net)" << std::endl; + return; + } tryhardmode = true; StartBench(benchDepth); return; @@ -240,6 +244,10 @@ void UciLoop(int argc, char** argv) { } // parse UCI "go" command + else if (tokens[0] == "go" && !NNUE::networkLoaded) { + std::cout << "info string ERROR: no network loaded, send setoption name EvalFile value " << std::endl; + std::cout << "bestmove 0000" << std::endl; + } else if (tokens[0] == "go") { StopHelperThreads(); // Join previous search thread if it exists @@ -267,7 +275,25 @@ void UciLoop(int argc, char** argv) { std::cout << "Invalid setoption format" << std::endl; continue; } - if (tokens.at(2) == "Hash") { + if (tokens.at(2) == "EvalFile") { + // the whole rest of the line after " value " is the path + const auto pos = input.find(" value "); + std::string path = input.substr(pos + 7); + while (!path.empty() && (path.back() == '\r' || path.back() == ' ')) path.pop_back(); + if (threads_state == Search) { + StopHelperThreads(); + td->info.stopped = true; + } + if (main_thread.joinable()) + main_thread.join(); + threads_state = Idle; + if (!path.empty() && path != "" && NNUE::loadNetworkFile(path)) { + td->resetFinnyTable(); + for (auto& helper : threads_data) + helper.resetFinnyTable(); + } + } + else if (tokens.at(2) == "Hash") { uciOptions.Hash = std::stoi(tokens.at(4)); std::cout << "Set Hash to " << uciOptions.Hash << " MB" << std::endl; InitTT(uciOptions.Hash); @@ -352,6 +378,9 @@ void UciLoop(int argc, char** argv) { std::cout << "option name Threads type spin default 1 min 1 max 256 \n"; std::cout << "option name Minimal type check default false \n"; std::cout << "option name UCI_Chess960 type check default false \n"; +#ifdef NO_EMBED_NET + std::cout << "option name EvalFile type string default \n"; +#endif #ifdef TUNE // spsa info dump for (const auto ¶m: tunables()) { @@ -368,6 +397,9 @@ void UciLoop(int argc, char** argv) { PrintBoard(&td->pos); } + else if (input == "eval" && !NNUE::networkLoaded) { + std::cout << "info string ERROR: no network loaded, send setoption name EvalFile value " << std::endl; + } else if (input == "eval") {// call parse position function if (!parsed_position) { ParsePosition("position startpos", &td->pos, td->keyHistory, uciOptions.chess960); @@ -377,6 +409,9 @@ void UciLoop(int argc, char** argv) { std::cout << "Scaled eval: " << adjustEval(&td->pos, 0, EvalPosition(&td->pos, &td->FTable)) << std::endl; } + else if (input == "bench" && !NNUE::networkLoaded) { + std::cout << "info string ERROR: no network loaded, send setoption name EvalFile value " << std::endl; + } else if (input == "bench") { tryhardmode = true; StartBench();