968 Words

Article by editor Leo thinkabit.ukim@gmail.com

The Machine Learning terminology here is simplified and will have a loss quality, but not pertinent to the point.

Nibbler is a Graphical User Interface to analyse chess games using mainly the Leela Chess Zero chess engine (software that plays chess and provides an evaluation of positions). It is written specifically for using Leela.

Nibbler is written specifically to make use of Leela’s output. While traditional engines ouptut centipawns to indicate how much hundredths of a pawn either side is ahead. Leela outputs what it believes what is the Win, Draw and Loss chance for the current side, along with how certain it is of its evaluation and other information like how many moves left until the games reaches its conclusion.

Unfortunately for us it is written in Electron which makes it very hard to package for musl systems like Alpine Linux which I use and am a developer of. Fortunately there is flatpak which is a godsend for packaging heavyweight apps, so lets go package it.

Writing the boilerplate

Lets start by writing the boilerplate we need to get started on any Flatpak app.

app-id: org.fohristiwhirl.nibbler
runtime: org.freedesktop.Platform
runtime-version: '20.08'
branch: stable
sdk: org.freedesktop.Sdk
command: /app/nibbler/nibbler

finish-args:
  - --share=ipc # Needed for X11
  - --socket=x11 # Electron needs Ozone to support Wayland but it is not there yet
  - --device=dri # need access to the GPU
  - --filesystem=xdg-data # Access to $XDG_DATA_HOME, letting the user load networks

Writing the module

Now lets package Nibbler itself, in this case we will use the pre-packaged Nibbler for Glibc x86_64 instead of compiling Electron ourselves, which is rather time intensive.

modules:
    - name: nibbler
    buildsystem: simple
    sources:
      - type: archive
        url: https://github.com/fohristiwhirl/nibbler/releases/download/v1.5.7/nibbler-1.5.7-linux.zip
        sha256: f64d7fc89f5cd68b41dccff4fc7aa2f03a482750f2d7fa6af522f78d74264f4d
      - type: file
        path: org.fohristiwhirl.nibbler.desktop
        sha256: 75ce7081b35321eeeb80043483a6977babe11055ed41f3a22cc718b63fabc6fa
    build-commands:
      - |
        install -Dm0644 org.fohristiwhirl.nibbler.desktop -t /app/share/applications
        mkdir -p /nibbler
        mv * /nibbler
        mkdir -p /app
        mv /nibbler /app
        chmod +x /app/nibbler/nibbler        

.desktop file

Nibbler itself has no .desktop file, so let’s write one for it. One can notice that --no-sandbox is passed, which is required because Flatpak doesn’t allow SUID or root-owned binaries, which are required for Electron’s sandbox.

[Desktop Entry]
Name=Nibbler (Flatpak)
Comment=Leela Chess Zero (Lc0) Interface
Exec=/app/nibbler/nibbler --no-sandbox %U
Icon=Nibbler
StartupNotify=true
Terminal=false
Type=Application
Categories=Game

Packaging Lc0

Now the hardest part, packaging the Neural Network Engine itself. Neural Network chess engines have two main components:

  1. A binary which looks at a position on a chess board and uses a neural network to derive information from the position, such as the expected win rate for each side (or draw), the certainty of those win rates, and how many moves are left before the conclusion of the game. All that information and more is passed to Nibbler.
  2. A weights file, which is a representation of a Neural Network that was trained through either playing itself, observing high-level games, or both. Storing in it the useful connections (like a super simple model of a brain) that allow it to evaluate a position on a chess board with great accuracy.

Lets package them, by starting with the dependencies of lc0. These can be found in Flathub’s repos as they are used in other Flatpak-ed packages:

  - name: eigen
    buildsystem: cmake-ninja
    builddir: true
    sources:
     - type: archive
       url: https://gitlab.com/libeigen/eigen/-/archive/3.3.8/eigen-3.3.8.tar.gz
       sha256: 146a480b8ed1fb6ac7cd33fec9eb5e8f8f62c3683b3f850094d9d5c35a92419a
  - name: openblas
    no-autogen: true
    make-args:
      - DYNAMIC_ARCH=1
      - FC=gfortran
      - NO_LAPACKE=1
      - USE_OPENMP=0 # OpenMP off by default, this hack skips 'test_fork' which crashes on i386
    make-install-args:
      - PREFIX=/app
    sources:
      - type: archive
        url: https://github.com/xianyi/OpenBLAS/archive/v0.3.12.tar.gz
        sha256: 65a7d3a4010a4e3bd5c0baa41a234797cd3a1735449a4a5902129152601dc57b

And now we package lc0 and the weights file it uses. Under default settings the weights need to be in the same directory as the executable and be named nn.

  - name: lc0
    buildsystem: meson
    config-opts:
      - -Dopenblas=true
      - -Dopenblas_libdirs=/app/lib
      - -Dgtest=false
      - -Db_lto=true
      - -Dopencl=false
    sources:
      - type: git
        url: https://github.com/LeelaChessZero/lc0
        tag: v0.26.3
  - name: nn
    buildsystem: simple
    build-commands:
      - |
        mkdir -p /app/bin
        mv nn /app/bin        
    sources:
      - type: file
        dest-filename: nn
        url: https://training.lczero.org/get_network?sha=4df05b0f0e80523018c073fd151ba26d955140ba303e84cebd96e027c6e06a3e
        sha256: 821aabc4316d49a526ba737a218fb868dd7c72c5e7b1a91eb8e6c805e3503028

This is good enough, but let’s go further.

Packaging a NNUE Chess Engine

There is a different Neural Network type than the one used by lc0, called Efficiently Updatable Neural Networks. It is much smaller and gives less outputs that can be used by Nibbler but it is fast on CPUs, specially with instructions like AVX512, while Leela excels when using GPUs specially RTXes with CUDA.

So let’s package cfish, a C rewrite of Stockfish:

  - name: cfish-pure-NNUE
    buildsystem: simple
    build-options:
      env:
        ARCH: x86-64-bmi2
        COMP: clang
        COMPXX: clang++
    build-commands:
      - |
        # Replace their network with the one we want to use, in this case it is
        # dark horse 0.2a "Aldi"
        # Taken from: https://www.patreon.com/posts/dark-horse-0-2a-40420324
        sed -i 's|nn-2eb2e0707c2b.nnue|nn-c2fd094bce06.nnue|' src/evaluate.h
        # Move the Neural Network to src, so we don't need to download it
        mv nn-c2fd094bce06.nnue src
        # Farthest I can go in my CPU, others might want to use avx512, vnni256
        # or even vnni512 instead
        make -C src build nnue=yes pure=yes embed=yes numa=no lto=yes extra=yes
        install -Dm0755 src/cfish -t /app/bin        
    sources:
      - type: git
        url: https://github.com/syzygy1/Cfish.git
        commit: b5576f28b82828143fad03b52d3f041240204d6c
      - type: file
        path: nn-c2fd094bce06.nnue
        sha256: c2fd094bce06942e882a7526a7443007a511ed5ee256613cf3a60a9f4ac744e8

This will give us a binary called cfish that has an embedded NNUE network in it which guarantees it will always work, as long as you have a CPU that supports the instructions enabled by bmi2. Those with more modern CPUs might want to go even further and enable AVX512 or even VNNI by changing x86-64-bmi2 to the appropriate value.

Wrapping up

In this article we have packaged a Chess GUI Interface for Leela, Leela itself, a very strong weights file for Leela to use and an engine that uses a different type of Neural Network that is meant for CPU users.

All this and more (another Stockfish engine, patches for different functionality in Nibber) can be found in my Nibbler-flatpak repository.