WebAssembly
Near-native speed in every browser.
WebAssembly (Wasm) is a compact binary instruction format that runs in all modern browsers and in standalone runtimes. You compile C, C++, Rust, Go or Zig to Wasm to run fast code safely on the web, at the edge and in plugins. It is how AI models such as Whisper and small LLMs run directly in the browser.
Why learn WebAssembly?
WebAssembly lets you ship heavy computation, including AI models, to users' devices with no install and no server cost.
- Fast: near-native speed, with SIMD and threads.
- Portable and safe: sandboxed bytecode that runs anywhere.
- On-device AI: run models privately in the browser with ONNX Runtime Web, Transformers.js or llama.cpp builds.
Set up and run your first program
You rarely write Wasm by hand. Most people compile Rust (wasm-pack) or C/C++ (Emscripten). To read the text format, install WABT.
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
wasm-pack build --target webuse wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, AI! Welcome to WebAssembly, {}.", name)
}wasm-bindgen to Cargo.toml and set crate-type = ["cdylib"].The text format (WAT)
Wasm has a readable text format. A module exports functions that JavaScript can call. Instructions push and pop values on a stack.
(module
(func $add (export "add") (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add)
(func (export "fp16_bytes") (param $params f64) (result f64)
local.get $params
f64.const 2
f64.mul))Loading Wasm from JavaScript
Compile WAT to a .wasm binary with wat2wasm, then instantiate it in the browser or Node.js and call its exports like normal functions.
// wat2wasm math.wat -o math.wasm
const { instance } = await WebAssembly.instantiateStreaming(fetch("math.wasm"));
console.log(instance.exports.add(2, 3)); // 5
console.log(instance.exports.fp16_bytes(8.03e9)); // 16060000000Linear memory
Each module has a linear memory: one big array of bytes shared with JavaScript. Passing arrays means writing them into this memory and passing a pointer.
(module
(memory (export "mem") 1) ;; 1 page = 64 KiB
(func (export "sum") (param $ptr i32) (param $n i32) (result f32)
(local $i i32) (local $acc f32)
(block $done
(loop $next
(br_if $done (i32.ge_u (local.get $i) (local.get $n)))
(local.set $acc (f32.add (local.get $acc)
(f32.load (i32.add (local.get $ptr) (i32.mul (local.get $i) (i32.const 4))))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $next)))
(local.get $acc)))Compiling Rust to Wasm
wasm-bindgen generates the JavaScript glue, so you can pass strings and typed arrays directly.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn dot(a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b).map(|(x, y)| x * y).sum()
}
// JavaScript:
// import init, { dot } from "./pkg/my_wasm.js";
// await init();
// dot(new Float32Array([1, 2, 3]), new Float32Array([4, 5, 6])); // 32WASI and runtimes outside the browser
WASI gives Wasm safe access to files, clocks and networking, so the same binary runs on servers, edge platforms and as plugins.
# Build a Rust CLI for WASI and run it with Wasmtime
rustup target add wasm32-wasip1
cargo build --target wasm32-wasip1 --release
wasmtime target/wasm32-wasip1/release/app.wasm
# C with the WASI SDK
clang --target=wasm32-wasi -O2 main.c -o main.wasm
wasmtime main.wasmAI with WebAssembly
WebAssembly is one of the main ways to run AI on users' devices. ONNX Runtime Web and Transformers.js use a Wasm backend (with SIMD and threads) and switch to WebGPU when available. llama.cpp and whisper.cpp compile to Wasm, and WebLLM combines Wasm with WebGPU to run LLMs fully in the browser.
The essential libraries
Sentiment analysis in the browser
Transformers.js downloads a small model once and runs it locally with WebAssembly.
<script type="module">
import { pipeline } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.0";
const classify = await pipeline("sentiment-analysis",
"Xenova/distilbert-base-uncased-finetuned-sst-2-english", { device: "wasm" });
console.log(await classify("Running AI in the browser is amazing!"));
// [{ label: "POSITIVE", score: 0.99 }]
</script>A browser agent with WebLLM
WebLLM runs an LLM on the user's GPU through WebGPU, with WebAssembly for the runtime. Its OpenAI-style API means the agent loop is the same as on a server, except nothing leaves the device.
<script type="module">
import { CreateMLCEngine } from "https://esm.run/@mlc-ai/web-llm";
const engine = await CreateMLCEngine("Hermes-3-Llama-3.1-8B-q4f16_1-MLC",
{ initProgressCallback: p => console.log(p.text) });
const tools = [{ type: "function", function: { name: "get_time", description: "Current local time",
parameters: { type: "object", properties: {} } } }];
const messages = [{ role: "user", content: "What time is it right now?" }];
for (let round = 0; round < 5; round++) {
const msg = (await engine.chat.completions.create({ messages, tools })).choices[0].message;
messages.push(msg);
if (!msg.tool_calls?.length) { document.body.textContent = msg.content; break; }
for (const call of msg.tool_calls) {
messages.push({ role: "tool", tool_call_id: call.id, content: new Date().toLocaleTimeString() });
}
}
</script>Tool calling in WebLLM only works with models that support it, such as the Hermes series; check the WebLLM model list.
ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.WebAssembly learning roadmap
Tick items off as you go. Your progress is saved in this browser.
Beginner
Intermediate
Advanced
Projects to build
WebAssembly cheat sheet
| Task | Syntax |
|---|---|
| Instantiate | WebAssembly.instantiateStreaming(fetch(u)) |
| Call export | instance.exports.add(1, 2) |
| Rust export | #[wasm_bindgen] pub fn f() |
| Build Rust | wasm-pack build --target web |
| WAT to binary | wat2wasm a.wat -o a.wasm |
| Run WASI | wasmtime app.wasm |
Common mistakes
- Copying large arrays between JavaScript and Wasm memory can cost more than the computation.
- Browsers block
.wasmloading from file:// URLs; use a local web server. - Threads need cross-origin isolation headers (COOP and COEP).
Where to go next
- MDN WebAssembly — guide
- Rust and WebAssembly — book
- webassembly.org — official
On TensorCore.si: Rust volume, JavaScript volume.
Keyboard: [ previous language · ] next language · / search the library