From 1efb35903d79724a7d7a6ab2520298a516b3dd03 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 13 Jun 2023 23:09:05 +0300 Subject: [PATCH 1/6] Implemented LEB128 in feature transformer. --- src/evaluate.h | 2 +- src/nnue/nnue_common.h | 42 +++++++++++++++++++++++++++++ src/nnue/nnue_feature_transformer.h | 12 ++++----- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index 94cd42cc67a..cb74ddc5aef 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-fdc1d0fe6455.nnue" + #define EvalFileDefaultName "nn-a84941349aad.nnue" namespace NNUE { diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 12309d262b1..989e2ae5590 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -159,6 +159,48 @@ namespace Stockfish::Eval::NNUE { write_little_endian(stream, values[i]); } + template + inline IntType read_leb_128(std::istream& stream) { + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + IntType result = 0, shift = 0; + while (true) { + std::uint8_t byte; + stream.read(reinterpret_cast(&byte), sizeof(std::uint8_t)); + result |= (byte & 0x7f) << shift; + shift += 7; + if ((byte & 0x80) == 0) { + return (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); + } + } + } + + template + inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { + for (std::size_t i = 0; i < count; ++i) + out[i] = read_leb_128(stream); + } + + template + inline void write_leb_128(std::ostream& stream, IntType value) { + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + while (true) { + std::uint8_t byte = value & 0x7f; + value >>= 7; + if ((value == 0 && (byte & 0x40) == 0) || (value == -1 && (byte & 0x40) != 0)) { + stream.write(reinterpret_cast(&byte), sizeof(std::uint8_t)); + return; + } + byte |= 0x80; + stream.write(reinterpret_cast(&byte), sizeof(std::uint8_t)); + } + } + + template + inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { + for (std::size_t i = 0; i < count; ++i) + write_leb_128(stream, values[i]); + } + } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index a1888c7a365..7571f398295 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -253,9 +253,9 @@ namespace Stockfish::Eval::NNUE { // Read network parameters bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases , HalfDimensions ); - read_little_endian(stream, weights , HalfDimensions * InputDimensions); - read_little_endian(stream, psqtWeights, PSQTBuckets * InputDimensions); + read_leb_128(stream, biases , HalfDimensions ); + read_leb_128(stream, weights , HalfDimensions * InputDimensions); + read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); return !stream.fail(); } @@ -263,9 +263,9 @@ namespace Stockfish::Eval::NNUE { // Write network parameters bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases , HalfDimensions ); - write_little_endian(stream, weights , HalfDimensions * InputDimensions); - write_little_endian(stream, psqtWeights, PSQTBuckets * InputDimensions); + write_leb_128(stream, biases , HalfDimensions ); + write_leb_128(stream, weights , HalfDimensions * InputDimensions); + write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); return !stream.fail(); } From c616689594cd71a54beaf8e93ec8f14e78943b9e Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 14 Jun 2023 00:29:15 +0300 Subject: [PATCH 2/6] Updates. --- src/nnue/nnue_common.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 989e2ae5590..90e3d4b9b28 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -162,16 +162,18 @@ namespace Stockfish::Eval::NNUE { template inline IntType read_leb_128(std::istream& stream) { static_assert(std::is_signed_v, "Not implemented for unsigned types"); - IntType result = 0, shift = 0; - while (true) { + static_assert(sizeof(IntType) <= 8); + IntType result = 0; + for (size_t i = 0; i <= sizeof(IntType); ++i) { std::uint8_t byte; stream.read(reinterpret_cast(&byte), sizeof(std::uint8_t)); - result |= (byte & 0x7f) << shift; - shift += 7; + result |= (byte & 0x7f) << (i * 7); if ((byte & 0x80) == 0) { - return (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); + return i == sizeof(IntType) || (byte & 0x40) == 0 ? result : result | ~((1 << (i + 1) * 7) - 1); } } + assert(false); + return 0; } template From 58a000adf5dcc88553a9234be264d20341f57240 Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 14 Jun 2023 01:04:53 +0300 Subject: [PATCH 3/6] More robust implementation. --- src/nnue/nnue_common.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 90e3d4b9b28..fc59a0d5b48 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -162,16 +162,17 @@ namespace Stockfish::Eval::NNUE { template inline IntType read_leb_128(std::istream& stream) { static_assert(std::is_signed_v, "Not implemented for unsigned types"); - static_assert(sizeof(IntType) <= 8); IntType result = 0; - for (size_t i = 0; i <= sizeof(IntType); ++i) { + size_t shift = 0; + do { std::uint8_t byte; stream.read(reinterpret_cast(&byte), sizeof(std::uint8_t)); - result |= (byte & 0x7f) << (i * 7); + result |= (byte & 0x7f) << shift; + shift += 7; if ((byte & 0x80) == 0) { - return i == sizeof(IntType) || (byte & 0x40) == 0 ? result : result | ~((1 << (i + 1) * 7) - 1); + return sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); } - } + } while (shift < sizeof(IntType) * 8); assert(false); return 0; } From 1ac883e03aaefbb1242a7b7fc13f52980b0ac938 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 15 Jun 2023 20:30:50 +0300 Subject: [PATCH 4/6] Buffered IO. --- src/nnue/nnue_common.h | 95 ++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 35 deletions(-) diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index fc59a0d5b48..32fc8a0f05a 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -159,49 +159,74 @@ namespace Stockfish::Eval::NNUE { write_little_endian(stream, values[i]); } - template - inline IntType read_leb_128(std::istream& stream) { - static_assert(std::is_signed_v, "Not implemented for unsigned types"); - IntType result = 0; - size_t shift = 0; - do { - std::uint8_t byte; - stream.read(reinterpret_cast(&byte), sizeof(std::uint8_t)); - result |= (byte & 0x7f) << shift; - shift += 7; - if ((byte & 0x80) == 0) { - return sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); - } - } while (shift < sizeof(IntType) * 8); - assert(false); - return 0; - } - template inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { - for (std::size_t i = 0; i < count; ++i) - out[i] = read_leb_128(stream); - } - - template - inline void write_leb_128(std::ostream& stream, IntType value) { static_assert(std::is_signed_v, "Not implemented for unsigned types"); - while (true) { - std::uint8_t byte = value & 0x7f; - value >>= 7; - if ((value == 0 && (byte & 0x40) == 0) || (value == -1 && (byte & 0x40) != 0)) { - stream.write(reinterpret_cast(&byte), sizeof(std::uint8_t)); - return; - } - byte |= 0x80; - stream.write(reinterpret_cast(&byte), sizeof(std::uint8_t)); + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + auto bytes_left = read_little_endian(stream); + std::uint32_t buf_pos = BUF_SIZE; + for (std::size_t i = 0; i < count; ++i) { + IntType result = 0; + size_t shift = 0; + do { + if (buf_pos == BUF_SIZE) { + stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); + buf_pos = 0; + } + std::uint8_t byte = buf[buf_pos++]; + --bytes_left; + result |= (byte & 0x7f) << shift; + shift += 7; + if ((byte & 0x80) == 0) { + out[i] = sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); + break; + } + } while (shift < sizeof(IntType) * 8); } + assert(bytes_left == 0); } template inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { - for (std::size_t i = 0; i < count; ++i) - write_leb_128(stream, values[i]); + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + std::uint32_t byte_count = 0; + for (std::size_t i = 0; i < count; ++i) { + IntType value = values[i]; + std::uint8_t byte; + do { + byte = value & 0x7f; + value >>= 7; + ++byte_count; + } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } + write_little_endian(stream, byte_count); + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + std::uint32_t buf_pos = 0; + auto flush = [&]() { + if (buf_pos > 0) { + stream.write(reinterpret_cast(buf), buf_pos); + buf_pos = 0; + } + }; + auto write = [&](std::uint8_t byte) { + buf[buf_pos++] = byte; + if (buf_pos == BUF_SIZE) flush(); + }; + for (std::size_t i = 0; i < count; ++i) { + IntType value = values[i]; + while (true) { + std::uint8_t byte = value & 0x7f; + value >>= 7; + if ((byte & 0x40) == 0 ? value == 0 : value == -1) { + write(byte); + break; + } + write(byte | 0x80); + } + } + flush(); } } // namespace Stockfish::Eval::NNUE From 968c7fed46e618f4f4981fb46f5b641f349e91df Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 15 Jun 2023 20:36:35 +0300 Subject: [PATCH 5/6] Net update. --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index c35b2f07bec..4e0f0737490 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-cd2ff4716c34.nnue" + #define EvalFileDefaultName "nn-39c85a5225de.nnue" namespace NNUE { From 58b3119b65fb84b8eb9c83eccbe844670cddfb31 Mon Sep 17 00:00:00 2001 From: maxim Date: Mon, 19 Jun 2023 19:57:29 +0300 Subject: [PATCH 6/6] Added magic string. --- src/evaluate.h | 2 +- src/nnue/nnue_common.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 4e0f0737490..33effb1cfd8 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-39c85a5225de.nnue" + #define EvalFileDefaultName "nn-78bacfcee510.nnue" namespace NNUE { diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 32fc8a0f05a..d338527d96b 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -57,6 +57,9 @@ namespace Stockfish::Eval::NNUE { // Size of cache line (in bytes) constexpr std::size_t CacheLineSize = 64; + constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; + constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; + // SIMD width (in bytes) #if defined(USE_AVX2) constexpr std::size_t SimdWidth = 32; @@ -162,6 +165,9 @@ namespace Stockfish::Eval::NNUE { template inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { static_assert(std::is_signed_v, "Not implemented for unsigned types"); + char leb128MagicString[Leb128MagicStringSize]; + stream.read(leb128MagicString, Leb128MagicStringSize); + assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); const std::uint32_t BUF_SIZE = 4096; std::uint8_t buf[BUF_SIZE]; auto bytes_left = read_little_endian(stream); @@ -190,6 +196,7 @@ namespace Stockfish::Eval::NNUE { template inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { static_assert(std::is_signed_v, "Not implemented for unsigned types"); + stream.write(Leb128MagicString, Leb128MagicStringSize); std::uint32_t byte_count = 0; for (std::size_t i = 0; i < count; ++i) { IntType value = values[i];