Go make a decision! Run System One models, 100% local in your browser using WebAssembly written in Go.
Jeyzma asks a model typed questions about a state and shows a probability for each option. The answer comes from one forward pass, with no generated text. There is no server, no API key, and no data leaves your machine. It uses the GPU through WebGPU when available, otherwise the CPU. Written in Go using yzma on TinyGo.
The code is written in Go and compiled with TinyGo. It uses the
yzma exp/decide package to run
llama.cpp, compiled to a WebAssembly
module. Everything runs in a Web Worker so the page stays responsive.
index.html
| postMessage
worker.js
| \
yzma.wasm yzma_wasm*.js
(Go, TinyGo) -> (llama.cpp, Emscripten)
exp/decide renders the state and each question in the format the model was
trained on, then reads the probabilities of the option tokens. DecideMany
decodes the state once and shares it across all the questions.
You need TinyGo 0.41 or later, Go
1.26, and node for the test.
make build
make serve
Open http://localhost:8080, click Load, and wait for the model to download. The browser caches it. Then click Decide.
The list has six models.
| Model | Size | Readout | Config |
|---|---|---|---|
| Julia-1 | approximately 170 MB | gguf |
none |
| Laya | approximately 450 MB | gguf |
none |
| d1 3B | approximately 1.7 GB | gguf |
none |
| Jev-Style 0.8B Decision v3 | approximately 530 MB | jev |
readout_config.json |
| JevK5 2B v0.2 | approximately 2.0 GB | jevk5 |
jevk5_config.json |
| decider 0.8B | approximately 530 MB | decider |
decider_config.json |
A gguf model holds its readout, prompt template, and temperatures in the
GGUF file, as converted for the llama-server /v1/systemone API. It loads with
decide.Open and needs no config. The others need their config file too.
Lev, OpenJev, Kev, Clef, and pplx-decider also load as gguf, but their files
are larger than 2 GB, the most a browser can hold.
Choose Another model to enter your own model URL, config URL, and readout. Any URL works if the host sends CORS headers. Hugging Face does.
make build downloads about 13 MB of llama.cpp into build/, compiles the Go
program, and copies the page. The only binary files in the repository are the
social card and the two screenshots.
The download comes from
llama-cpp-builder. It uses
v0.7.0, the release that yzma v1.30.0 installs. It has shim ABI 11. DecideMany
needs ABI 10 or later to share one state across questions, and d1 needs v0.7.0.
To use another build, pass its tag, or latest for the newest nightly build.
make build LLAMA_VERSION=latest
The questions box holds a JSON list. Each question has a question and
optional type and options.
| Type | Options | Answer |
|---|---|---|
choice |
a list of names, or an object of name to description | one of the names |
noul |
none, or {"false": "...", "true": "..."} |
true or false |
score |
a list of levels in order | the index of a level |
Without a type, a question with options is a choice and one without is noul.
[
{"question": "Which team handles this?", "options": {"billing": "payments, invoices", "technical": "bugs"}},
{"question": "The customer is angry."},
{"type": "score", "question": "How urgent is this?", "options": ["low", "medium", "high"]}
]The state is any text. A JSON object or list keeps its key order.
Exact mode gives each question the same result it gets alone. Batched mode is faster but can differ slightly, and on a near tie it can change a JevK5 answer. A change of mode loads the model again.
Both orders reads each choice question a second time with the options in reverse order, and averages the two. This cancels a preference for the first options. It also loads the model again.
Each answer shows its confidence. A score question also shows the expected level, which is the mean of the levels weighted by their probabilities.
/plays/pacman is Pac-Man with a model playing along. You play, and at each
junction the model gets the same board as a choice question and picks a
direction. The page shows its probabilities next to your move and counts how
often you agree. Wait for the model stops the game at each junction until
the model answers, so every turn is compared.
The game is HTML5 Pac-Man by Dale
Harvey, under the WTFPL. The copy in web/plays/pacman adds hooks so the page
can read the board and hold the game at a junction. The audio and the font are
left out.
Set Input to the request to send the same JSON that llama-server takes at
/v1/systemone. The page answers it with decide.ParseRequest and
Decider.Answer, and shows the response JSON under the bars.
{
"state": {"ticket": "I was charged twice for my subscription and want a refund."},
"questions": {
"team": {"type": "choice", "instructions": "Which team handles this?", "criteria": {"billing": "payments, invoices", "technical": "bugs"}},
"angry": {"type": "noul", "instructions": "The customer is angry."},
"urgency": {"type": "score", "instructions": "How urgent is this?", "criteria": ["low", "medium", "high"]}
}
}The Go program sets these functions for worker.js.
| Function | What it does |
|---|---|
yzmaDecideLoad(modelURL, configURL, readout, manyMode, orders) |
Downloads a model, and its config unless readout is gguf. readout is jev, jevk5, decider, or gguf. manyMode is exact or batched. orders is both or empty. |
yzmaDecideOpen(path, configJSON, readout, manyMode, orders) |
Loads a model that is already in the module's filesystem. |
yzmaDecide(state, questionJSON, category) |
Scores one question. |
yzmaDecideMany(state, questionsJSON, category) |
Scores a JSON list of questions about one state. |
yzmaAnswer(requestJSON) |
Answers a /v1/systemone request. |
The test asks three questions in Node, without a browser. It fails when
DecideMany and Decide differ in exact mode. --request also sends the
questions as a /v1/systemone request and fails when the answers differ from
DecideMany. --both turns on both orders.
make test READOUT=gguf MODEL=~/models/Julia-1-Q8_0.gguf TEST_FLAGS="--request"
make test READOUT=gguf MODEL=~/models/Laya-Q8_0.gguf
make test READOUT=gguf MODEL=~/models/d1-3B-Q4_K_M.gguf
make test MODEL=~/models/Jev-Style-0.8B-Decision-v3-Q4_K_M.gguf \
CONFIG=~/models/readout_config.json TEST_FLAGS="--expect billing,true"
make test READOUT=jevk5 MODEL=~/models/jevk5-2b-v0.2-Q8_0.gguf \
CONFIG=~/models/jevk5_config.json
make test READOUT=decider MODEL=~/models/decider-0.8b.Q4_K_M.gguf \
CONFIG=~/models/decider_config.json
llama.cpp has three WebAssembly builds. yzma-loader.js picks the best one the
browser can run.
| Build | What it needs |
|---|---|
yzma_wasm_webgpu |
WebGPU with f16 shaders, and JSPI. Chrome and Edge 137 or later. |
yzma_wasm_mt |
SharedArrayBuffer, so a page with the COOP and COEP headers. |
yzma_wasm |
Nothing. It runs everywhere. |
The loader tests the GPU against the CPU before it uses it, and falls back to the CPU if the GPU is missing or computes wrong values. WebGPU in Firefox is much slower than the CPU, so the loader picks the CPU there.
GitHub Pages cannot send the COOP and COEP headers, so the page loads
coi-serviceworker.js first.
It adds the two headers and reloads the page once, so the loader can pick the
multithreaded build. make serve sets the headers too.
The top right of the page shows the selected build. Add ?mode=cpu or
?mode=webgpu to the URL to force a build, and ?gpu=high-performance or
?gpu=low-power to pick the GPU on a machine with two. ?embed hides the
paragraph at the top of the page.
The yzma WebAssembly guide has more on the backends, the browsers, and how fast each one is.
- The model must be smaller than 2 GB, the limit of one JavaScript ArrayBuffer.
- The CPU build is slow for the 2B model. Three questions take about 17 seconds on 16 threads in Node.
- Discrete NVIDIA cards do not expose f16 shaders in Chrome. Start Chrome with
--enable-dawn-features=vulkan_enable_f16_on_nvidiato use one.
.github/workflows/pages.yml builds and deploys on each push to main. Set
Settings → Pages → Source to GitHub Actions once.
The page is served at jeyzma.com. Enter jeyzma.com under Settings → Pages →
Custom domain, and point the DNS at GitHub Pages with A records for
185.199.108.153, 185.199.109.153, 185.199.110.153, and 185.199.111.153, and a
CNAME record for www to hybridgroup.github.io. A deploy from Actions does
not read a CNAME file, so the setting is the only place the domain lives.
web/social-card.png is the preview image that social sites show for a link
to jeyzma.com. Its source is images/social-card.svg. Render it again after a
change.
inkscape images/social-card.svg --export-type=png --export-filename=web/social-card.png -w 1200 -h 630
Apache 2.0, the same as yzma. web/coi-serviceworker.js is MIT and keeps its
own notice.


