Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add simple JS backend #2072

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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