Skip to content

Commit

Permalink
add simple JS backend
Browse files Browse the repository at this point in the history
  • Loading branch information
zamfofex committed Dec 21, 2024
1 parent 3c2db95 commit 3dddc38
Show file tree
Hide file tree
Showing 21 changed files with 694 additions and 3 deletions.
26 changes: 26 additions & 0 deletions cross-files/wasm32-emscripten
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[host_machine]
system = 'emscripten'
cpu_family = 'wasm32'
cpu = 'wasm32'
endian = 'little'

[binaries]
c = 'emcc'
cpp = 'em++'
ar = 'emar'
strip = 'emstrip'

[built-in options]
cpp_args = ['--use-port=zlib', '-fexceptions', '-msse', '-msse2', '-msse3', '-msimd128']
cpp_link_args = [
'--use-port=zlib',
'-fexceptions',
'-sASYNCIFY', '-sASYNCIFY_STACK_SIZE=65536',
'-STACK_SIZE=1048576',
'-sMODULARIZE', '-sEXPORT_ES6',
'-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$stringToNewUTF8',
'-sALLOW_MEMORY_GROWTH',
'-sWASM_BIGINT',
'-sENVIRONMENT=worker',
'-sEXTRA_EXPORTED_RUNTIME_METHODS=["FS"]',
]
5 changes: 5 additions & 0 deletions js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
package-lock.json
dist
build
lc0.tar
19 changes: 19 additions & 0 deletions js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- TODO: improve documentation! -->

Leela Chess Zero (Wasm)
===

Lc0 compiled to WebAssembly (running on WebGPU when supported).

compiling
---

- Install [Emscripten].
- Install [npm].
- Install [Meson].
- Run `npm install`
- Run `npm run build` (or `./build.sh`)

[Emscripten]: <https://emscripten.org/docs/getting_started/downloads.html>
[npm]: <https://docs.npmjs.com/cli/configuring-npm/install>
[Meson]: <https://mesonbuild.com/Getting-meson.html>
29 changes: 29 additions & 0 deletions js/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env sh
set -ex
meson setup --buildtype=release -Ddefault_library=static --prefer-static --cross-file=../cross-files/wasm32-emscripten -Dblas=false build .. || :
meson compile -C build lc0
esbuild --minify --outdir=dist --format=esm main.js worker.js build/lc0.js build/lc0.worker.mjs
mv dist/build/lc0.worker.js dist/build/lc0.worker.mjs
cp build/lc0.wasm dist/build
cat > dist/package.json << END
{
"name": "lc0",
"description": "Leela Chess Zero",
"version": "0.0.0.1",
"license": "GPL",
"homepage": "https://lczero.org",
"repository": {
"type": "git",
"url": "https://github.com/LeelaChessZero/lc0"
},
"main": "./main.js",
"exports": {
".": {
"import": "./main.js"
}
},
"dependencies": {
"onnxruntime-web": "1.20.1"
}
}
END
5 changes: 5 additions & 0 deletions js/example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
package-lock.json
dist
net.pb.gz
.parcel-cache
16 changes: 16 additions & 0 deletions js/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!-- TODO: improve documentation! -->

Lc0 Web Example (Wasm)
===

First, download an Lc0 network and name the file `net.pb.gz` and place it on this directory. Then, run the following commands:

- Install [Emscripten].
- Install [npm].
- Install [Meson].
- Run `npm install`
- Run `npm run dev`

[Emscripten]: <https://emscripten.org/docs/getting_started/downloads.html>
[npm]: <https://docs.npmjs.com/cli/configuring-npm/install>
[Meson]: <https://mesonbuild.com/Getting-meson.html>
70 changes: 70 additions & 0 deletions js/example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!doctype html>
<meta charset="utf-8">

<!--
This file is part of Leela Chess Zero.
Copyright (C) 2024 The LCZero Authors
Leela Chess is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Leela Chess is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Leela Chess. If not, see <http://www.gnu.org/licenses/>.
Additional permission under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with NVIDIA Corporation's libraries from the NVIDIA CUDA
Toolkit and the NVIDIA CUDA Deep Neural Network library (or a
modified version of those libraries), containing parts covered by the
terms of the respective license agreement, the licensors of this
Program grant you additional permission to convey the resulting work.
-->

<title> Lc0 Web Example </title>

<style> @import "@xterm/xterm/css/xterm.css"; </style>

<script type="module">

import {Lc0} from "lc0"
import {Terminal} from "@xterm/xterm"

let response = await fetch("net.pb.gz")
let lc0 = Lc0(response.body)

let terminal = new Terminal()
terminal.open(document.querySelector("div"))

let show = async out =>
{
for await (let line of out) terminal.writeln(line)
}

show(lc0)
show(lc0.stderr)

let line = ""
terminal.onData(data =>
{
if (data === "\x7F") return
if (data === "\r") data = "\r\n"
terminal.write(data)
if (data === "\r\n") {
lc0.post(line)
line = ""
return
}
line += data
})

</script>

<div></div>
11 changes: 11 additions & 0 deletions js/example/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"scripts": {
"pack": "cd .. && npm run pack",
"dev": "npm run pack && npm install && vite"
},
"dependencies": {
"@xterm/xterm": "5.5.0",
"lc0": "../lc0.tar",
"vite": "6.0.4"
}
}
11 changes: 11 additions & 0 deletions js/example/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
server: {
headers: {
"Cross-Origin-Embedder-Policy": "require-corp",
"Cross-Origin-Opener-Policy": "same-origin",
}
},
optimizeDeps: {
exclude: ["lc0"],
},
}
94 changes: 94 additions & 0 deletions js/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
This file is part of Leela Chess Zero.
Copyright (C) 2024 The LCZero Authors
Leela Chess is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Leela Chess is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Leela Chess. If not, see <http://www.gnu.org/licenses/>.
Additional permission under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with NVIDIA Corporation's libraries from the NVIDIA CUDA
Toolkit and the NVIDIA CUDA Deep Neural Network library (or a
modified version of those libraries), containing parts covered by the
terms of the respective license agreement, the licensors of this
Program grant you additional permission to convey the resulting work.
*/

function Stream(worker, type, finished)
{
let gotLine
const lines = []

worker.addEventListener("message", ({data}) =>
{
if (data.type !== type) return
if (!gotLine) {
lines.push(data.text)
return
}
gotLine(data.text)
gotLine = undefined
})

function next()
{
if (lines.length !== 0) return lines.shift()
else return new Promise(resolve => gotLine = resolve)
}

const it = {next: async () => finished() && lines.length === 0 ? {done: true} : {done: false, value: await next()}}
Object.freeze(it)

const peek = () => lines[0]
return {next, peek, [Symbol.asyncIterator]: () => it}
}

export function Lc0(network)
{
const worker = new Worker(new URL("worker.js", import.meta.url), {type: "module"})

let commands = []
let post0 = command => commands.push(command)

worker.addEventListener("message", () =>
{
worker.postMessage({network}, [network])
for (const command of commands) worker.postMessage(command)
commands = undefined
post0 = command => worker.postMessage(command)
}, {once: true})

const post = command =>
{
if (finished) throw new Error("Cannot post command to finished Lc0")
post0(String(command))
}

let finished = false

// todo: this should send a message to the worker instead
// so that it can end its pthread workers too
function finish()
{
finished = true
worker.terminate()
}

const stdout = Stream(worker, "stdout", () => finished)
const stderr = Stream(worker, "stderr", () => finished)

const lc0 = {post, finish, ...stdout, stderr, get finished() { return finished }}
Object.freeze(lc0)
return lc0
}
10 changes: 10 additions & 0 deletions js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"scripts": {
"build": "npm install && ./build.sh",
"pack": "npm run build && tar cf lc0.tar dist"
},
"dependencies": {
"esbuild": "0.24.2"
}
}

Loading

0 comments on commit 3dddc38

Please sign in to comment.