open-standards/wai/wai-web/demo/verify_int_hyper_wasm.mjs
Transaction Science 89e0da789e wai: compact binary model + in-browser conditional-codec demo
Makes the in-browser decode of the full conditional codec practical: the model
shrinks from ~52MB JSON to ~23.5MB binary (~15.5MB gzipped on the wire), and a
real HTML page decodes a SOTA-class learned codec in the browser, verifying its
hash against the native reference.

- typed IntModel: refactored run_net/decode/decode_conditional off serde_json
  Value onto an IntModel { afrac, gf, shift, layers: Vec<IntLayer> }, built by
  either IntModel::from_json (existing schema) or IntModel::from_bytes (compact
  binary: magic WHM1, int12 weights as i16, IGDN gamma as i32, the rest i64).
  Decode is byte-identical whichever the source.
- export tool writes *_model.bin alongside the JSON (model_to_bin).
- conformance: the binary model decodes byte-identically to JSON (new assert in
  tests/int_hyper_synth.rs); all 3 tests green in release (~11s, the 100s was
  debug — release wasm decode is ~10s).
- wasm: decodeIntHyperBin (binary path) beside decodeIntHyper (JSON); shared
  cond_decode helper. demo/verify_int_hyper_wasm.mjs checks both paths.
- demo/int_hyper.html + .js: fetch the binary models + 1.4KB bitstream, decode
  with pure-i64 wasm (no ML runtime), paint the canvas, FNV-1a the RGB against
  cond.json. Verified in headless Chrome: 256x256, 42.61 dB, decoded hash
  adeb12d347d0bf7e == native, ~10s decode, zero console errors.

The PTQ model files are large and NOT committed (gitignored demo/int_hyper/);
regenerate with tools/wai_sota_int_synth_export.py. pkg/ stays a build artifact.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 03:14:46 -04:00

64 lines
3.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Reproducible proof: the wasm32 FULL CONDITIONAL codec decode is byte-identical
// to the native reference — a SOTA-class learned image codec (PTQ of mbt2018_mean)
// whose whole bitstream→image decode (factorized z, integer h_s, integer-σ
// entropy, integer g_s) runs in the browser engine with NO ML runtime, and whose
// RGB verifies against the native FNV-1a hash. The integer-entropy contract,
// in-browser.
//
// Run via demo/verify_int_hyper.sh (builds the nodejs-target pkg first), or:
// node verify_int_hyper_wasm.mjs [path/to/wai_web.js] [model-dir]
// The model dir (default $WAI_SOTA_INT_DIR or /tmp/sota_int) holds the artifacts
// tools/wai_sota_int_synth_export.py emits (the g_s/h_s model JSONs are large, so
// they are NOT committed — regenerate them with the export tool).
import { readFileSync, existsSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const here = dirname(fileURLToPath(import.meta.url));
const pkg = resolve(here, process.argv[2] ?? './pkg-node/wai_web.js');
const dir = resolve(process.argv[3] ?? process.env.WAI_SOTA_INT_DIR ?? '/tmp/sota_int');
if (!existsSync(`${dir}/cond.json`)) {
console.log(`skip: artifacts not in ${dir} (run tools/wai_sota_int_synth_export.py)`);
process.exit(0);
}
const wai = await import(pathToFileURL(pkg).href);
const bytes = (name) => new Uint8Array(readFileSync(`${dir}/${name}`));
// FNV-1a 64-bit — the same dependency-free checksum the export tool and the
// native Rust decode share (no domain separation; raw RGB bytes).
function fnv1a(buf) {
const mask = (1n << 64n) - 1n;
let h = 14695981039346656037n;
for (let i = 0; i < buf.length; i++) h = ((h ^ BigInt(buf[i])) * 1099511628211n) & mask;
return h.toString(16).padStart(16, '0');
}
const conf = JSON.parse(readFileSync(`${dir}/cond.json`, 'utf8'));
const ent = bytes('cond_entropy.json'); // y Gaussian-conditional + z factorized tables
const bs = bytes('cond_bitstream.json'); // z_rans + y_rans
function check(label, img) {
const got = fnv1a(img.rgb);
const ok = got === conf.rgb_hash
&& img.width === conf.out_w && img.height === conf.out_h
&& img.rgb.length === conf.out_w * conf.out_h * conf.out_c;
console.log(`${ok ? 'PASS' : 'FAIL'} ${label.padEnd(12)} ${img.width}x${img.height} ${img.rgb.length}B `
+ `${got.slice(0, 16)}… ${got === conf.rgb_hash ? '== native hash' : `MISMATCH (want ${conf.rgb_hash})`} `
+ `(${conf.psnr_cond?.toFixed?.(2) ?? '?'} dB)`);
return ok;
}
// JSON model path and the COMPACT BINARY model path must both reproduce the
// native hash (the binary is what the browser demo downloads — ~½ the size).
const json = check('json model', wai.decodeIntHyper(
bytes('int_synth_sota_model.json'), bytes('int_hs_model.json'), ent, bs));
const bin = check('binary model', wai.decodeIntHyperBin(
bytes('int_synth_sota_model.bin'), bytes('int_hs_model.bin'), ent, bs));
const pass = json && bin;
console.log(pass
? '\n✓ wasm32 conditional-codec decode (JSON + compact-binary model) is byte-identical to the native reference — a SOTA learned codec decoded in-browser, zero JS ML runtime.'
: '\n✗ mismatch');
process.exit(pass ? 0 : 1);