The same pure-i64 Rust that generates natively compiles to wasm32 and produces byte-identical output in the browser — the create-side of "conformance is a hash", in-page with no ML runtime. - generate feature added to the wai dep; src/generate.rs binds generateSpectral (modelJson, prompt, seed, steps, w, h) -> GeneratedImage and fnv1aHex (the portable generation hash). No float, no ONNX. - Drives the live demo at wai.transaction.science/generate (Astro page + the tiny 6.4 KB pinned model): interactive prompt/seed/steps generate at 320² in ~41 ms, and a conformance line regenerates the pinned inputs and verifies the output hash equals the native reference 10b690999f6c5616 byte-for-byte (browser == native). Co-Authored-By: Claude Opus 4.8 <[email protected]>
55 lines
2 KiB
Rust
55 lines
2 KiB
Rust
//! In-browser **deterministic byte-exact generation** — the create-side of the
|
|
//! integer contract, zero JS ML runtime.
|
|
//!
|
|
//! The same pure-i64 Rust that generates natively compiles straight to wasm32 and
|
|
//! produces the IDENTICAL bytes here: a generated image whose hash is portable, so
|
|
//! the page can pin "this prompt+seed under this model produces exactly these
|
|
//! bytes" and verify it client-side. No float, no ONNX — `wai.generate.spectral`.
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
/// A generated image: RGB row-major, `width * height * 3` bytes.
|
|
#[wasm_bindgen]
|
|
pub struct GeneratedImage {
|
|
rgb: Vec<u8>,
|
|
width: u32,
|
|
height: u32,
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
impl GeneratedImage {
|
|
#[wasm_bindgen(getter)]
|
|
pub fn rgb(&self) -> Box<[u8]> {
|
|
self.rgb.clone().into_boxed_slice()
|
|
}
|
|
#[wasm_bindgen(getter)]
|
|
pub fn width(&self) -> u32 {
|
|
self.width
|
|
}
|
|
#[wasm_bindgen(getter)]
|
|
pub fn height(&self) -> u32 {
|
|
self.height
|
|
}
|
|
}
|
|
|
|
/// Generate an image deterministically from `(model, prompt, seed, steps)`, in the
|
|
/// browser, integer-exact. `model_json` is the pinned model bundle
|
|
/// (`generate_spectral_model.json`); the output is byte-identical to the native
|
|
/// reference, so its FNV-1a is portable. No float, no ML runtime.
|
|
#[wasm_bindgen(js_name = generateSpectral)]
|
|
pub fn generate_spectral(
|
|
model_json: &[u8], prompt: &str, seed: u32, steps: u32, w: u32, h: u32,
|
|
) -> Result<GeneratedImage, JsError> {
|
|
let v: serde_json::Value =
|
|
serde_json::from_slice(model_json).map_err(|e| JsError::new(&e.to_string()))?;
|
|
let m = wai::generate::SpectralModel::from_value(&v);
|
|
let rgb = wai::generate::generate(&m, prompt, seed as u64, steps as usize, h as usize, w as usize);
|
|
Ok(GeneratedImage { rgb, width: w, height: h })
|
|
}
|
|
|
|
/// FNV-1a 64-bit of `bytes` as lowercase hex — the portable generation hash the
|
|
/// page pins and re-checks, the same checksum the native generator emits.
|
|
#[wasm_bindgen(js_name = fnv1aHex)]
|
|
pub fn fnv1a_hex(bytes: &[u8]) -> String {
|
|
format!("{:016x}", wai::generate::fnv1a(bytes))
|
|
}
|