neuron-explorer
Steering-first neuron explorer for Qwen3.5-4B: push every MLP neuron, keep every rollout, browse the results
Run language models locally in your browser using Python and WebAssembly without sending data to a server
AI summary · website and README
Developer toolsJavaScript · PythonGitHub @takano32OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Language models that run in your browser, with the model code written in Python and run by
Pyodide (Python compiled to WebAssembly). Nothing is sent to a server: the page downloads
the model and runs it on your device. This is an experiment in how far Python on WebAssembly can go, not a product.
(Until September 2026 the project was called pyodide-llama-py.)
Python does the work around the model; the arithmetic of each token runs in small WebAssembly SIMD kernels, and in
WebGPU where the device has it and it is faster there. See How it works.Read the full README ↗Language models that run in your browser, with the model code written in Python and run by Pyodide (Python compiled to WebAssembly). Nothing is sent to a server: the page downloads the model and runs it on your device. This is an experiment in how far Python on WebAssembly can go, not a product. (Until September 2026 the project was called pyodide-llama-py.)
flowchart TD
page["The page: chat and model list"]
worker["Web Worker: downloads the model in parts, starts Pyodide"]
python["Pyodide, Python in WebAssembly: loads and converts the model to int8, tokenizer, sampling"]
engine["Forward engine: forward.js"]
cpu["CPU: WebAssembly SIMD kernels, on software threads where the browser allows"]
gpu["GPU: WebGPU shaders, for the prompt and the answer where this device measured them faster"]
page -- "prompt" --> worker
worker -- "text, token by token" --> page
worker --> python
python -- "the prompt in blocks, the answer a few tokens at a time" --> engine
engine --> cpu
engine --> gpu
Python does the work around the model; the arithmetic of each token runs in small WebAssembly SIMD kernels, and in WebGPU where the device has it and it is faster there. See How it works.
Open https://takano32.github.io/pyodide-llm/ and write a prompt.
?offline=off turns that off).The weekly CI runs the deployed site on Linux, Windows (x86-64 and ARM) and macOS, in Playwright's Chromium, Firefox and WebKit and in the installed Chrome and Edge: all 23 combinations run. What each browser adds:
| Chrome, Edge | Firefox | Safari (WebKit) | |
|---|---|---|---|
| WebAssembly SIMD kernels | yes | yes | yes |
| relaxed SIMD (int8 about 30% faster) | yes | yes | no |
| software threads (through the Service Worker) | yes | yes | yes |
| 64-bit memory: models over 4 GB | yes | yes | no |
| WebGPU for the prompt | yes | not in the Firefox of the CI (no WebGPU in a worker) | on the owner's iPhone; not in the CI's WebKit |
| WebGPU for the answer | yes, for Llama, Qwen2, Qwen3, GPT-2 and GPT-NeoX models | not measured (no WebGPU in a worker in the CI) | where the prompt has it; not tried on the owner's iPhone yet |
/benchmark/, loads the model again on the CPU if that is
faster, and remembers it for the next visit. Other models too large to hold twice stay on the CPU./benchmark/ skips its round without the
kernels there (NumPy widens the weights to float32: for llm-jp-3-150m,
Pyodide grew to 958 MB in CI's Chromium).Details and numbers: docs/performance.md and docs/webgpu.md.
The model list has three groups:
?model=<id> opens a model of the list directly.
?hf=<owner>/<repository> (optionally &revision= and &template= with {prompt} in it) converts a Hugging
Face repository, and says in words what it cannot run. What it reads: the Llama architecture (with Llama 3,
linear and yarn RoPE scaling, yarn only as a factor and an original context, nothing else it can set; Mistral is read
as Llama), Qwen2, Qwen3, GPT-2 and GPT-NeoX, in safetensors (one file or
several shards), with a Unigram or byte-level BPE tokenizer.json or a sentencepiece model. It does not open a
repository that has only GGUF files: the GGUFs of the list are read together with the vocabulary and
configuration of their original repository. Instruction models get what you type inside their chat template,
for one turn; the page keeps no conversation.?checkpoint=<url>&tokenizer=<url> reads files in llama2.c's format from any server that answers cross-origin
range requests, for example
?checkpoint=https://huggingface.co/karpathy/tinyllamas/resolve/main/stories110M.bin&tokenizer=https://raw.githubusercontent.com/karpathy/llama2.c/master/tokenizer.bin.The folder button next to the model list (or dropping the files on the page) opens a model from your disk. The files are read where they are: nothing is uploaded. Choose the files together:
.safetensors file (not several shards), config.json and
tokenizer.json, tokenizer.model or spiece.model. The page converts them to int8 in the browser, a few
megabytes at a time, with no more memory than the converted model. tokenizer_config.json or
chat_template.jinja, if you add them, give the chat template.stories110M.bin, or what convert_hf.py and quantize.py
write) and its tokenizer.bin. float32, float16 and int8 are told apart by the header and the file size..json with what differs from llama2.c's conventions, shaped like an entry of src/models.js:{ "name": "tiny-lm", "options": { "tokenizer_kind": "unigram", "stop_tokens": [1, 2] },
"generation": { "steps": 256, "temperature": 0.7, "topp": 0.9, "repetition_penalty": 1.3 }, "prompt": "これからの流行りは" }
For the Hugging Face files, the .json may also say {"conversion": {"dtype": "float16", "max_seq_len": 1024}}.
A checkpoint of more than 1 GB asks first.
?pyodide=<version> forces one) and starts it in a
Web Worker. The model is downloaded at the same time, in parts over several connections.src/python/llama2_numpy.py, src/python/llama2_convert.py) reads the model, converts a Hugging Face model to
int8 as its file arrives, and handles the tokenizer and the sampling.src/runtime/forward.js), which calls WebAssembly SIMD kernels
(kernels/, written in AssemblyScript) on the model's own WebAssembly memory. With threads, software threads
(src/runtime/helper.ts) share the rows of each matrix. The page measures how many threads are fastest on your
device.src/runtime/gpu.js, src/runtime/shaders.js). The page checks each shader against JavaScript on your device, times them,
and uses the GPU only where it measured it faster than the CPU. The answer goes to the GPU 4 tokens at a time,
each sampled there with the random number the CPU drew for it.More:
Requirements: Node.js 24 LTS (.nvmrc; the page is built with Astro) and Python 3 with
NumPy.
git clone https://github.com/takano32/pyodide-llm.git
cd pyodide-llm
make run # downloads and converts the models (about 1 GB), builds the kernels, starts http://localhost:8080/pyodide-llm/
Or with Docker: docker build -t pyodide-llm . && docker run -p 8080:8080 pyodide-llm.
make models downloads, converts, quantizes and splits the models into public/models/. No binary is
committed: the site's models are built when it is deployed.make kernels compiles the SIMD kernels (no Emscripten needed; see kernels/README.md).npm run build builds the site into dist/: the page's script and the workers (src/runtime/) bundled, every
file named by a hash of its content.Checks:
python -m pytest tests -q: the engine and the converter (native Python and NumPy).node tests/smoke.mjs: the engine and the models in Node's Pyodide (after make models).node tests/forward-check.mjs: the page's forward pass against NumPy.npm run build && node tests/e2e.mjs [model] [chromium|firefox]: the page in a real browser (Playwright).Pushing to main deploys to GitHub Pages. AGENTS.md (in Japanese) holds the decisions, measurements
and pitfalls, and TASKS.md the tasks.
Mozilla Public License 2.0, the same as Pyodide's. Some files carry code from other projects under their own licenses, and keep those notices where the code is: src/python/llama2_numpy.py (tairov/llama2.py and karpathy/llama2.c, MIT) and the files of src/runtime/shaders/ (src/runtime/shaders.js is their window; llama.cpp and ONNX Runtime, MIT; TensorFlow.js, MLC LLM and Apache TVM, Apache-2.0). The models are not in this repository and each keeps its own license (see the model list).