open-standards/wai/wai-web/demo/README.md
Transaction Science a998381232 wai-quantum WebGPU member: tile runs, a second implementation certified on its own
plan (PLAN_REVISION 2): Config { tile_bits, run_max, skip_identity } with
PER_GATE (what compile makes) and TILED (tiles of up to 1024 amplitudes, runs
of up to 1024 gates, capped at 2^30 / 2^n, 16 at 26 qubits); Step::Tile
{ first, count, size }; compile_with, schedule, execute_from,
PlannedGate::{new, is_identity}. A tile run is a maximal run of consecutive
gates that act inside a tile: diagonal gates on any target, others with their
target's bit below the tile size. Only exact identities are skipped (circuit/1's
P(19..=32) is 2^30 - 1 and stays). The CPU executor mirrors the tile kernel's
index arithmetic tile by tile; lanes::exact_row is shared with apply_words.
Tests: every configuration (10 of them) equals the native simulator over
random-v1 (300) and fabric-v1 and certifies with the four lanes32 pins; long
random circuits and full-range random words tile exactly as they run gate by
gate; fabric-v1 covers every tile path; malformed programs are refused;
a_rounded_product_is_never_negated runs under every configuration.

wgsl: TILE_SHADER = lanes32.wgsl followed by lanes32_tile.wgsl (entry point
apply_tile, workgroup 256, one workgroup per tile, the tile size, gate range
and grid in a 32-byte uniform run record, gates in a 48-byte read-only storage
record), tile_implementation_id() = wgsl-lanes32-tile/de6fb388454a0144 (pinned),
tile records, tile_grid (x at most 8), TILE_MUTANTS T1 (barrier between gates
removed, detector Race over race_witness), T2 (the report's minimum dropped,
Range) and T3 (tbit < size -> <=, Malformed over malformed_tile_run).

Deviation from the plan's kernel text: no workgroup memory. naga 30.0.1
decorates every array type with ArrayStride and struct members with Offset,
and spirv-val (SPIRV-Tools 2026.4) rejects explicit layouts on Workgroup
variables (VUID-StandaloneSpirv-None-10684); per the plan the kernel was
restructured instead: each workgroup updates its own tile in place in the state
planes, with storageBarrier() between gates. The uniformity argument is
unchanged (one barrier, at the top level of the loop over the run's gates).
The min of the report is taken once per gate so that T2's text occurs once; a
tile outside the state reports bit 2; a record outside the gate buffer reads
as malformed.

Checker: SHADER and TILE_SHADER each under their own rules (entry points and
workgroup sizes, bindings, no workgroup memory, bool/u32 only, literal shifts,
atomics, barrier kind and placement at the top level of the body or of a
top-level for loop with no break, continue or return), translation to MSL,
SPIR-V and HLSL, mutants; lanes32_tile.spv for spirv-val; --tile-shader.

Member: WebGpuMember::tiled() compiles TILE_SHADER, probes it, plans with
TILED and reports the tile implementation; per-gate stays the default and the
fallback. Runs interleave gate and tile dispatches per submission (a second
uniform ring for run records, a storage buffer for the runs' gates), within
SUBMIT_BUDGET. Device tests: g8 kills T1-T3 on the tile member; new g12 runs
2,000 full-range cases tiled vs per-gate vs the CPU, the four pinned
certificates under the tile implementation, forced geometries, the conformance
vectors, five repeated runs, and prints per-gate vs tiled timing. The member
script requires 13 tests and both implementations' certificates and mutants.

Browser: QuantumWebGpu.tiled(); the worker certifies and runs the vectors on
the tile member too; browser_webgpu.mjs requires "tiles certified bit-exact 4
of 4" and fabric/expected.json pins tile_implementation.

CI: checks validate lanes32_tile.spv and package lanes32_tile.wgsl; the
browser job checks the tile pin and greps the tile certificates; the wasip2 job
requires the new tests by name, and its timeout is 90 minutes (the planner test
runs several minutes alone in a debug wasm build).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 17:01:16 -04:00

7.9 KiB
Raw Permalink Blame History

wai-web demo — interactive-worlds browser sink

world.html is a conformant wai.world.replay sink running in a tab: the WWLD payload (a tick-0 world + signed op log, ~1.3 KB for the truss vector) is re-derived frame by frame in WebGPU by the same wai crate native sinks run, compiled to wasm32 with the codec chain off (--no-default-features --features world). At the sealed final tick the sink recomputes the BLAKE3 state hash and compares it to the publisher's Ed25519-signed seal — replay-equivalence, demonstrated: the hash the browser computes is byte-identical to world-conformance/expected_hashes.json.

Run

# from wai-web/: rebuild the wasm pkg after engine changes
wasm-pack build --target web --out-dir demo/pkg

# vectors come from the conformance corpus
cp ../world-conformance/vectors/{truss_fracture,ballistic_bounce,basic_kinematics}.wwld demo/vectors/

# serve (any static server; WASM needs http, not file://)
python3 -m http.server 8741
# → http://localhost:8741/demo/world.html

Needs a browser with WebGPU enabled. No build steps beyond wasm-pack; no JS dependencies.

int_synth.html — integer-exact neural image sink (no ML runtime)

int_synth.html decodes a wai.neural.int_synth payload to RGB with the same pure-i64 Rust that decodes natively, compiled to wasm32 — no onnxruntime-web, no WebGPU, no float. Because the decode is integer all the way down (the integer-entropy contract, SPEC §6), the reconstruction is byte-identical on every machine, so the page re-checks its BLAKE3 against the hash the native reference froze into neural-conformance/expected_hashes.json. The four staged vectors (demo/int_synth/, copied from the corpus) include the real trained photo model (128×128, 21.6 dB PSNR).

# from wai-web/: build the web pkg, then serve
wasm-pack build --release --target web --out-dir demo/pkg --out-name wai_web
python3 -m http.server 8741   # → http://localhost:8741/demo/int_synth.html

Reproducible CLI proof that the wasm decode equals the native corpus (builds the nodejs-target pkg and checks all four vectors' hashes):

./demo/verify_int_synth.sh

Staged delivery — StageSink in the browser

src/stage.rs binds wai::staged::StageSink, the staged-delivery sink a native player runs (extensions/staged-delivery.md), for JS: StageBase.decode decodes a base envelope by SPEC §4 through the integer path, with its pin and under the page's cost limit; StageBase.fromStage0 takes a base the page decoded elsewhere, which is claimed (its chain's reports, units and stage hashes say so). StageSink adds bases (taking them), offers refinement layers, expires holds and removes bases in any order, and every unit's stage, producer, tier, output and stage hash reads back. stageLayerInfo reads a layer's refines, WIR1 header and declared cost, and a base's declared decode cost, before either is offered. Reports and units come back in the corpus's own form (staged-conformance/README.md).

verify_staged_wasm.mjs replays every case of staged-conformance/ through the wasm32 build and requires every report, final unit, stage hash and clip hash the native sink wrote; each integer base is decoded in wasm32 and must give its stage0.bin. It then checks the binding's guards: bases that declare huge clips (wai-rs/tests/vectors/staged_bounds/) refused under a cost limit with linear memory watched, a base past the chain budget, base facts a page cannot raise, and a seeded fuzz that must not trap:

# from wai-web/
cargo build --release --target wasm32-unknown-unknown
wasm-bindgen --target nodejs --out-dir demo/pkg-node --out-name wai_web \
  target/wasm32-unknown-unknown/release/wai_web.wasm
node demo/verify_staged_wasm.mjs ./pkg-node/wai_web.js ../staged-conformance

Staged measurement — the native figures in the browser

src/measure.rs binds wai::stage_measure, the code the native measurement tool wai_stage_measure runs (extensions/staged-measurement.md), for JS. A title is read from a Map of files by their path below wai/: titleMissing(dir, files) names the next file it needs, so a page fetches files one at a time until it holds them all. evaluateTitle (or new MeasureTitle(dir, files).json()) gives the text wai_stage_measure eval --json prints: counted bytes per object, stage and prefix, quality integers per presentation and per prefix, first presentable bytes, the layering overhead's points, deduplication and the output tree. simulate and simulateModel give the text wai_stage_measure sim prints. countObject counts one envelope (or names the code it is not countable with), qualityRgb8 gives one picture pair's integers (sums of squares as decimal strings), and outputRoot the output tree's root over presentations.

sessionBegin(entry, host, nonce) is wai::session_recorder::SessionRecorder over the session's entry object: decoderFor names the decoder that decoded an envelope, presented and received record what the page did, and finish(seed, signer_id, issued_at) seals the reconstruct-session claim (extensions/jwp-receipts.md §2.4.12), checked against the record first; a browser session states no energy figure. verifyClaim(claim, outputs, objects) checks a claim against the presentations and the objects received, and gives "" or the reason.

verify_measure.sh builds the nodejs package (or takes one already built), runs verify_measure_wasm.mjs over staged-measure-conformance/ — every golden, refusal title, counted object, quality pair and simulator case, byte for byte, and a session per fixture recorded the way a page records one, which must be the native tool's claim byte for byte — then requires the native wai_stage_measure eval --json to equal the wasm32 evaluation of every fixture, and the native and independent Python claim verifiers to give the wasm32 build's verdicts on every session it sealed:

# from wai-web/ (claim_verify.py needs the `cryptography` package)
bash demo/verify_measure.sh

quantum_webgpu.html — the fabric's WebGPU member in a page

src/quantum_webgpu.rs (feature quantum_webgpu, its own bundle) binds wai::quantum_fabric_webgpu for JS: quantum_webgpu_discover() opens the adapter the page is offered as a fabric member and probes its arithmetic against the CPU's, and the member it returns certifies the gate kernel against the CPU reference on a corpus (certify) and runs a WQC container to its statevector hash (statevector_hash_hex). The kernel is the same WGSL text the native member runs, compiled by the page's own WebGPU. A bit-exact member reproduces the CPU reference's pinned corpus digests, quantum-conformance/fabric/expected.json.

The page runs quantum_webgpu_worker.js in a module worker: the four pinned certificates (both gatesets, random-v1 and fabric-v1) and every vector of the quantum corpus, on the member and again on the same adapter running the tile implementation (tiled(): runs of gates applied tile by tile, one dispatch per run, whose barrier the page's WGSL compiler holds to the uniformity rules); then every reject file (each refused with its listed class), and a circuit one qubit wider than the member holds. A second worker repeats the last check with the adapter offering only the WebGPU default buffer limits, where the member holds 25 qubits and refuses 26 as too wide.

# from wai-web/: the bundle has its own --out-name, so its imports never meet the main bundle's
cargo build --release --target wasm32-unknown-unknown --features quantum_webgpu
wasm-bindgen --target web --out-dir demo/pkg-webgpu --out-name wai_quantum_webgpu \
  target/wasm32-unknown-unknown/release/wai_web.wasm
# serve wai/: the page reads ../../quantum-conformance/
cd .. && python3 -m http.server 8741   # → http://localhost:8741/wai-web/demo/quantum_webgpu.html

quantum-conformance/browser_webgpu.mjs drives the page headless and checks every result against the committed corpus.