Stockfish
  • Stockfish
  • NNUE
    • Documentation
    • Experiments
      • Remove 8th Subnetwork
      • Store Key in Accumulator Cache
      • Tweak fwdOut Multipliers
      • Improve Accumulator Update Heuristics
      • Remove Duplicate Perspective Code
      • Update All Accumulators
  • Improve Build System
    • Overview
    • Rewrite Makefile
    • Tests
Powered by GitBook
On this page
  • Description
  • Branches
  • Tests
  • SPSA #1
  • Test #1
  • Test #2
  • SPSA #2
  • Test #3
  • Test #4
  • Test #5
  • Test #6
  • Test #7
  1. NNUE
  2. Experiments

Tweak fwdOut Multipliers

Last updated 8 months ago

Description

Use different fwdOut multipliers for each subnetwork. These are SPSA tuned alongside L1 biases.

To apply the tuned parameters, I just scanned bytes in NNUE files and replaced matching patterns with them, because it was too tedious for me to calculate offsets.

patch-net.py
import argparse
import json
import os
import shutil
import subprocess

SOURCE = """
#ifndef PATCHER_H_
#define PATCHER_H_

#include <cstdint>

std::int32_t gBigL1BiasesPatch[8][16] = {{
{big}
}};

std::int32_t gSmallL1BiasesPatch[8][16] = {{
{small}
}};

#endif  // PATCHER_H_
""".lstrip()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("spsa", type=argparse.FileType("r"))
    args = parser.parse_args()

    spsa = json.load(args.spsa)

    biases_big = [
        [round(float(spsa[f"gBigL1Biases[{i}][{j}]"]["value"])) for j in range(16)]
        for i in range(8)
    ]
    biases_small = [
        [round(float(spsa[f"gSmallL1Biases[{i}][{j}]"]["value"])) for j in range(16)]
        for i in range(8)
    ]

    replace = SOURCE.format(
        big=",\n".join(
            [f"    \x7B {', '.join([str(n) for n in l])} \x7D" for l in biases_big]
        ),
        small=",\n".join(
            [f"    \x7B {', '.join([str(n) for n in l])} \x7D" for l in biases_small]
        ),
    )

    with open("patcher.h", "w") as f:
        f.write(replace)

    print("Building patcher...")
    p = subprocess.Popen(["clang++", "-o", "patcher", "patcher.cc"])
    p.wait()

    print("Running patcher...")
    p = subprocess.Popen(["./patcher"])
    p.wait()
    os.unlink("patcher")

    def rename(filename: str):
        p = subprocess.Popen(["sha256sum", filename], stdout=subprocess.PIPE)
        out, _ = p.communicate()
        new_filename = f"nn-{out.decode()[:12]}.nnue"
        os.rename(filename, new_filename)
        print(f"  {filename} -> {new_filename}")
        return new_filename

    print("Renaming patched networks...")
    big = rename("nn-big.nnue")
    small = rename("nn-small.nnue")

    print("Copying patched networks...")
    shutil.copy(big, f"../../src/{big}")
    shutil.copy(small, f"../../src/{small}")

    fwdout_big = [round(float(spsa[f"gBigFwdOutMultiplier[{i}]"]["value"])) for i in range(8)]
    fwdout_small = [round(float(spsa[f"gSmallFwdOutMultiplier[{i}]"]["value"])) for i in range(8)]

    print()
    print(f"FwdOutMultipliersBig = \x7B {', '.join([str(n) for n in fwdout_big])} \x7D")
    print(f"FwdOutMultipliersSmall = \x7B {', '.join([str(n) for n in fwdout_small])} \x7D")


if __name__ == "__main__":
    main()
patcher.cc
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>

#include <fcntl.h>
#include <unistd.h>

#include <sys/mman.h>

#define NNUE_BIG_FILENAME   "nn-1111cefa1111.nnue"
#define NNUE_SMALL_FILENAME "nn-37f18f62d772.nnue"

std::int32_t gBigL1Biases[8][16] = {
    { -2684, 7895, -6, 708, 6843, -100, 3483, -1489, 3302, -944, -2445, 1705, -1231, 4758, -5838, 1246 },
    { -2846, 1390, -1762, 2838, -384, 2369, 253, 525, 1352, -661, -984, 5167, 3024, -758, -2553, 691 },
    { -837, 1910, 449, -468, 583, 2462, -215, 466, 3934, -1540, -3219, 1274, 1022, -707, 2660, 904 },
    { 577, 183, 1145, 4290, -2356, -128, -1378, 1396, 5405, -2113, -2265, -2564, -3378, -3846, 2157, 115 },
    { -191, 4973, 1095, 627, -3551, -2123, -1055, 2521, 765, 1947, -1466, -165, -2599, -1511, -4311, 826 },
    { -264, -1084, 4379, -5117, -4194, -1648, 1042, 3994, 3221, 1521, -2092, 4079, -1167, -1418, 6122, 789 },
    { -700, -720, 5141, -3246, -4768, -1825, 1422, 608, 905, -781, -3121, 3333, 4825, -2090, -2882, 1186 },
    { -864, 301, 3064, -2015, -2131, -1115, 1467, 3108, 2178, -961, 666, 986, -1327, -2337, -1242, 162 }
};

std::int32_t gSmallL1Biases[8][16] = {
    { 4520, -224, -745, 2226, -379, 873, -862, 1802, -90, -969, -2685, -6127, 1663, 1524, 1182, 2867 },
    { 3322, -134, 689, 1822, 3909, 1769, -1781, -1741, 951, 736, 165, -6250, 1622, -3435, 2048, 2256 },
    { 3874, -1638, 1939, 7323, 305, 3074, -2712, -5057, -927, 4995, -2754, -12267, -2169, -937, 3790, 1843 },
    { 9299, -1797, 1208, 6096, 2377, 1987, -331, -1677, 273, 3748, -3183, -13408, 70, 3943, -1714, 1009 },
    { 10780, -2128, 1986, 5180, 382, 1401, 713, -5299, -283, 2682, 341, -14512, 347, 5684, -49, 965 },
    { 6527, -2984, -25, 6793, -751, 1099, 1796, -2767, -1368, 2182, 119, -9668, 1234, 3580, -26, 851 },
    { 7046, -2980, -1083, 6516, -1700, 953, 645, -2145, -3258, 1983, -898, -10751, 396, 2700, 0, 1067 },
    { 4711, -2034, -1082, 3914, 331, 1114, 845, -1524, -2016, 2820, -2159, -7452, 1536, 2796, 1246, 1635 }
};

#include "patcher.h"

void *find_patterns(const void *memory, const void *pattern, std::size_t mem_size, std::size_t size) {
    for (std::size_t i = 0; i < mem_size - size; i++) {
        const void *p = static_cast<const char *>(memory) + i;

        if (memcmp(p, pattern, size) == 0)
            return const_cast<void *>(p);
    }

    return nullptr;
}

void *load(const std::string &filename, std::size_t &size) {
    int fd = open(filename.c_str(), O_RDONLY);

    if (fd == -1)
        return nullptr;

    size = std::filesystem::file_size(filename);
    void *data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if (data == MAP_FAILED) {
        close(fd);
        return nullptr;
    }

    if (read(fd, data, size) != size) {
        close(fd);
        munmap(data, size);
        return nullptr;
    }

    close(fd);

    return data;
}

bool save(const std::string &filename, const void *data, std::size_t size) {
    int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);

    if (fd == -1)
        return false;

    if (write(fd, data, size) != size) {
        unlink(filename.c_str());
        return false;
    }

    close(fd);
}

void patch(const std::string &filename, const std::string &output,
           std::int32_t patterns[8][16], std::int32_t patch[8][16]) {
    std::size_t size;
    const void *data = load(filename, size);

    if (data == nullptr) {
        std::cerr << "Failed to load " << filename << std::endl;
        exit(1);
    }

    for (int i = 0; i < 8; ++i) {
        void *ptr = find_patterns(data, patterns[i], size, 64);

        if (ptr == nullptr) {
            std::cerr << "Failed to patch " << filename << std::endl;
            exit(1);
        }

        memcpy(ptr, &patch[i], 64);
    }

    if (!save(output, data, size)) {
        std::cerr << "Failed to patch " << filename << std::endl;
        exit(1);
    }
}

int main() {
    patch("../../src/" NNUE_BIG_FILENAME, "nn-big.nnue", gBigL1Biases, gBigL1BiasesPatch);
    patch("../../src/" NNUE_SMALL_FILENAME, "nn-small.nnue", gSmallL1Biases, gSmallL1BiasesPatch);

    return 0;
}

Branches

Tests

SPSA #1

Test #1

Failed

LLR: -2.91 (-2.94,2.94) <0.00,2.00>
Total: 2304 W: 440 L: 713 D: 1151
Ptnml(0-2): 17, 420, 537, 175, 3

Parameters obtained after 15k iterations.

Test #2

Failed

LLR: -2.94 (-2.94,2.94) <0.00,2.00>
Total: 2208 W: 440 L: 730 D: 1038
Ptnml(0-2): 10, 440, 491, 156, 7

Parameters obtained after 33k iterations.

SPSA #2

The first SPSA tune session was not good, presumably due to too high ckc_{k}ck​ values. Following linrock and Viren's suggestion, the second SPSA test is launched with much lower cendc_{\text{end}}cend​ values (128).

Test #3

Failed

LLR: -2.95 (-2.94,2.94) <0.00,2.00>
Total: 190112 W: 49392 L: 49374 D: 91346
Ptnml(0-2): 639, 22675, 48447, 22619, 676

Parameters obtained after 15,445 iterations.

Test #4

Failed

LLR: -2.94 (-2.94,2.94) <0.00,2.00>
Total: 214688 W: 55801 L: 55746 D: 103141
Ptnml(0-2): 685, 25770, 54425, 25733, 731

Parameters obtained after 31,369 iterations.

Test #5

Passed

LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 201792 W: 52447 L: 51887 D: 97458
Ptnml(0-2): 551, 23928, 51553, 24138, 726

Failed

LLR: -2.94 (-2.94,2.94) <0.50,2.50>
Total: 117324 W: 29618 L: 29620 D: 58086
Ptnml(0-2): 56, 13027, 32530, 12961, 88

Parameters obtained after 150,000 iterations. In some games it double kills master, but the overall strength seems equal.

Test #6

Failed

LLR: -2.94 (-2.94,2.94) <0.00,2.00>
Total: 90112 W: 23344 L: 23474 D: 43294
Ptnml(0-2): 309, 10813, 22963, 10641, 330

Same L1 bias values as Test #5 but average fwdOutMultiplier value (584) is applied.

Test #7

Passed

LLR: 2.97 (-2.94,2.94) <0.00,2.00>
Total: 298592 W: 77598 L: 76889 D: 144105
Ptnml(0-2): 969, 35373, 76010, 35868, 1076

Failed

LLR: -2.94 (-2.94,2.94) <0.50,2.50>
Total: 117324 W: 29618 L: 29620 D: 58086
Ptnml(0-2): 56, 13027, 32530, 12961, 88

Same L1 bias values as Test #5 but keep the original fwdOutMultiplier value. It also yields more double kills but not as effective as Test #5.

38e0cc7b-3
e0bfc4b6-2
2680c9c7-1
f677aee2-2
Stockfish Testing Framework
Logo
Stockfish Testing Framework
Logo
Stockfish Testing Framework
Logo
Stockfish Testing Framework
Logo
Stockfish Testing Framework
Logo
Stockfish Testing Framework
Logo
Stockfish Testing Framework
Logo
Stockfish Testing Framework
LTC
Stockfish Testing Framework
LTC
Logo
Logo
Stockfish Testing Framework
Logo
Stockfish Testing Framework
Logo