Some checks failed
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 17s
wai-quantum wasmQ / does this change touch it (push) Successful in 11s
WAI examples / WAI examples · does this change touch them (push) Successful in 1m5s
wai-quantum clippy / does this change touch it (push) Successful in 1m0s
wai-quantum contract / does this change touch it (push) Successful in 59s
wai-quantum command line / does this change touch it (push) Successful in 1m2s
WAI operation catalogue / does this change touch it (push) Successful in 1m6s
wai-quantum wasmQ / the reference modules rebuild to their modules of record · linux (push) Successful in 52s
wai-quantum contract / the policy is linked, and the deprecations page reads (push) Successful in 18s
wai-quantum extension points / does this change touch it (push) Successful in 32s
wai-quantum editors / does this change touch it (push) Successful in 1m33s
WAI conformance / does this change touch it (push) Successful in 1m42s
wai-quantum other formats / does this change touch it (push) Successful in 1m34s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (push) Has been skipped
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (push) Has been skipped
wai-audit collector and player integration / does this change touch it (push) Successful in 1m51s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (push) Has been skipped
WAI conformance / v2 vocabulary · manifests validate as stated (push) Has been skipped
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (push) Has been skipped
WAI conformance / independent verifiers · every verifier step, each corpus's Rust side (push) Has been skipped
WAI conformance / each feature of the independent verifiers' steps compiles alone (push) Has been skipped
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (push) Has been skipped
WAI conformance / cross-arch byte-equality · linux (push) Has been skipped
WAI conformance / cross-arch byte-equality · linux-arm64 (push) Has been skipped
WAI conformance / cross-arch byte-equality · macos (push) Has been skipped
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (push) Has been skipped
wai-quantum docs, course and locales / does this change touch it (push) Successful in 1m38s
wai-quantum error correction and mitigation / does this change touch it (push) Successful in 37s
WAI conformance / C2PA manifest emission · signed, read back, validated (push) Has been skipped
WAI conformance / independent verifiers · each needs= is what a run without packages shows (nightly) (push) Has been skipped
wai-quantum for Python / does this change touch it (push) Successful in 37s
wai-quantum pulse and dynamics / does this change touch it (push) Successful in 37s
wai-quantum for JavaScript / does this change touch it (push) Successful in 1m37s
wai-quantum OpenQASM reader / does this change touch it (push) Successful in 1m39s
wai-quantum resource estimation / does this change touch it (push) Successful in 1m40s
wai-quantum service / does this change touch it (push) Successful in 33s
wai-sink-swift native sink package / does this change touch it (push) Successful in 38s
WAI Studio / does this change touch it (push) Successful in 38s
wai-sink-android native sink library / does this change touch it (push) Successful in 40s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · macos (push) Successful in 2m35s
wai-quantum views in agent hosts / does this change touch it (push) Successful in 1m49s
wai-quantum variational and algorithm library / does this change touch it (push) Successful in 1m51s
wai-quantum verdicts and repairs / does this change touch it (push) Successful in 1m51s
wai-quantum studio and desktop / does this change touch it (push) Successful in 1m52s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m15s
wai-quantum command line / command line · macos (push) Successful in 3m6s
wai-quantum pulse and dynamics / the same bits · macos (push) Successful in 1m42s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 4m34s
wai-quantum wasmQ / interpreter and vectors under wasm32-wasip2 (wasmtime) (push) Successful in 5m3s
wai-quantum service / service energy · linux packages (push) Successful in 3m5s
wai-quantum for Python / python · macos (push) Successful in 4m3s
wai-audit collector and player integration / collector · linux (push) Successful in 4m50s
wai-quantum extension points / the registry under wasm32-wasip2 (wasmtime) (push) Successful in 5m1s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (push) Successful in 13s
wai-quantum clippy / clippy, warnings denied, every feature set · linux (push) Successful in 7m21s
wai-quantum extension points / the registry and the example · linux (push) Successful in 7m0s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · linux (push) Successful in 8m36s
wai-quantum pulse and dynamics / the same bits · linux-arm64 (push) Successful in 4m8s
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (push) Successful in 1m14s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m43s
wai-quantum editors / editors · linux (push) Successful in 7m34s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs under wasm32-wasip2 (wasmtime) (push) Successful in 1m13s
wai-quantum other formats / the set under wasm32-wasip2 (wasmtime) (push) Successful in 4m22s
wai-quantum extension points / the registry and the example · linux-arm64 (push) Successful in 9m42s
wai-quantum contract / API compatibility · linux (push) Successful in 11m28s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 8m19s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 13m14s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs · linux, release (push) Successful in 4m34s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m41s
wai-quantum editors / editors · macos (push) Successful in 12m50s
wai-quantum docs, course and locales / the course and the command line's languages · linux (push) Successful in 9m16s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (push) Successful in 3m18s
wai-quantum other formats / readers, writers and the set · linux (push) Successful in 8m48s
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (push) Successful in 7m48s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 15m42s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 11m38s
wai-quantum OpenQASM reader / reader and corpus · linux (push) Successful in 7m53s
wai-quantum for JavaScript / javascript · linux (push) Successful in 5m43s
wai-quantum for Python / python · linux (push) Successful in 10m39s
WAI examples / WAI examples · android-app · linux-arm64 (push) Successful in 17m12s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 17m44s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 9m14s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (push) Successful in 4m38s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (push) Successful in 4m0s
wai-quantum views in agent hosts / views · the page and its engine (push) Successful in 4m25s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (push) Successful in 6m15s
wai-quantum contract / schemas and the committed tool lists · linux (push) Successful in 20m37s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 20m20s
wai-quantum studio and desktop / studio · browser (push) Successful in 8m53s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 9m9s
wai-quantum service / service · macos (push) Failing after 23m3s
wai-quantum resource estimation / estimation and calibration · linux, release (push) Successful in 15m12s
WAI examples / WAI examples · portable · linux (push) Successful in 26m19s
wai-quantum views in agent hosts / views · a scripted host (push) Successful in 13m50s
wai-quantum service / service · linux (push) Successful in 19m27s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Failing after 31m2s
wai-quantum command line / command line · linux (push) Successful in 36m0s
WAI Studio / Studio in WebKit · linux (push) Successful in 26m20s
WAI conformance / cargo-class corpora · linux (linux) (push) Successful in 36m38s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (push) Successful in 43m17s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 12m0s
A matrix of 2^20 entries or more (and 128 columns) is now reduced in panels of 32 steps whose updates of the rest are matrix products. The bidiagonal (the blocked reduction of Dongarra, Sorensen and Hammarling 1989) runs on a transposed copy: within a panel the matrix is not written, its updates kept as A - V Y^H - X U^H and applied to the rest by one product at the panel's end, and a step reads each column once (A^H v along it, the row's entry, then the column times that entry into A u-bar while it is in cache, the right reflection's scale applied after the sum). The copy is scaled by an exact power of two, since A u-bar is the square of the matrix's scale. The QR goes in panels of 32 columns in compact WY form (Schreiber and Van Loan 1989), the rest updated by two products: the path that takes a matrix twice as tall as wide to its triangle first (after Chan 1982), and qr(). Smaller matrices keep the unblocked steps and their bits. With the same bits everywhere: wy forms V^H V in one pass over V, reflect_rows forms its blocks side by side and applies each in 512-row stretches across a worker's 16 vectors, and on x86_64 every inner loop also exists compiled for AVX2 without fused multiply-add, taken where the processor has it (the same operations in the same order, four lanes wide). Measured (examples/svd_bench.rs, Keep::All, Linux x86_64 i9-13900H, the old and new builds alternately, best of three, one and eight threads pinned to the performance cores, load 5 to 9 from CI): 2048^2 23.6 -> 13.8 s on one thread and 5.77 -> 3.22 s on eight; 16384x512 8.61 -> 3.64 s and 4.26 -> 1.40 s; 4096x256 0.478 -> 0.236 s and 0.242 -> 0.102 s; 1024^2 2.75 -> 1.84 s; 512^2 0.338 -> 0.238 s and 256^2 0.037 -> 0.027 s (AVX2 alone, the bits unchanged). On macOS arm64 (load 30 to 38) the panels were a tie on one thread where they are now taken. The peaked P9 56x1917 (chain, 16 threads, load 8 to 10): 61.3 and 65.6 s before, 54.3 and 35.5 s after, 381 to 394 MB; its decompositions are mostly under 2^20 entries and its gain mostly AVX2's. OLE 49x648 at chi=128 (8 workers, load 4.8 to 7.2): 105.9 and 106.9 s -> 82.1 and 81.3 s, the evolution 19.7 and 20.1 s -> 10.6 and 10.8 s; the value 3 ulps from the committed receipt's, the same bits on Linux x86_64 and Linux arm64. Bits: tests/data/svd re-run on Linux x86_64 and macOS arm64, and a third receipt from Linux arm64: the twelve matrices' hashes are the same on the three at 1, 4 and 16 threads, and those under 2^20 entries keep their previous hashes. Residuals 4.3e-15 to 1.8e-14, the same or lower at every size; orthonormality to 3.4e-14. The P9 receipt is re-pinned from a run of the new build on Linux x86_64 (Linux arm64 gives every number bit for bit): its first 62 layers are the same bits, the peak string, samples, bonds and swaps unchanged, |<s|C|0>|^2 0.0999640527080971 -> 0.09996405270810678 (9.7e-14 relative), the discarded weight 3e-16 absolute. Tests: the reductions in panels against the unblocked ones to rounding; reconstruction and orthonormality in panels (forced at small sizes), graded and extreme-scale matrices; the same bits across thread counts in panels; the AVX2 and narrow kernels' bits compared on one machine; tests/svd_bench.rs checks three machines' receipts and re-derives 2048x512 and 1024^2 here. Change note: wai/changes/2026-11-13-quantum-svd-blocked.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
148 lines
8.6 KiB
Rust
148 lines
8.6 KiB
Rust
//! The dense SVD (`quantum_svd`) against the scalar one-sided Jacobi rotations
|
||
//! the tensor networks used before it: time, accuracy, and the bits of the
|
||
//! result at each thread count.
|
||
//!
|
||
//! `cargo run --release --example svd_bench --features quantum_engine,quantum_receipt,quantum_bptn -- [sizes=64,128,256,512,1024,2048] [threads=1,4,16] [jacobi_max=512] [shapes=square,tall,wide] [dims=4096x256,16384x512]`
|
||
//!
|
||
//! Each matrix is complex, its entries from a fixed integer sequence, its
|
||
//! columns scaled by `0.995^j` (formed by repeated multiplication, so the input
|
||
//! is the same bits everywhere) so its spectrum decays as a truncated bond's
|
||
//! does. For each size and shape: the new SVD at every thread count given (the
|
||
//! result hashed, bit for bit; every count must give the same hash), the
|
||
//! Jacobi rotations where the size allows, the reconstruction error
|
||
//! `‖A − U·S·V†‖/‖A‖`, the departures of `U†U` and `V†V` from the identity, and
|
||
//! the largest difference between the two methods' singular values. The hash
|
||
//! is the same on every machine: compare a run on arm64 with one on x86_64.
|
||
//!
|
||
//! Writes a signed receipt to `RECEIPTS_DIR` (default `receipts`).
|
||
|
||
#[path = "tracker/common.rs"]
|
||
mod common;
|
||
#[path = "svd/inputs.rs"]
|
||
mod inputs;
|
||
|
||
use std::time::Instant;
|
||
|
||
use serde_json::{Value, json};
|
||
use inputs::{hash, input};
|
||
use wai_quantum::quantum_svd::{C, Keep, Svd, jacobi, matmul, phases, svd};
|
||
|
||
fn arg<T: std::str::FromStr>(args: &[String], key: &str) -> Option<Vec<T>> {
|
||
args.iter().find_map(|a| a.strip_prefix(&format!("{key}=")).map(|v| v.split(',').map(|x| x.parse().unwrap_or_else(|_| panic!("{key}={v}"))).collect()))
|
||
}
|
||
|
||
fn frob(a: &[C]) -> f64 {
|
||
a.iter().map(|x| x.re * x.re + x.im * x.im).sum::<f64>().sqrt()
|
||
}
|
||
|
||
/// `‖A − U·S·V†‖/‖A‖` and the departures of `U†U`, `V†V` from `I` (max entry).
|
||
fn accuracy(a: &[C], m: usize, n: usize, f: &Svd, threads: usize) -> (f64, f64, f64) {
|
||
let k = f.k;
|
||
let us: Vec<C> = (0..m * k).map(|i| C { re: f.u[i].re * f.s[i % k], im: f.u[i].im * f.s[i % k] }).collect();
|
||
let back = matmul(&us, &f.vh, m, k, n, threads);
|
||
let diff: Vec<C> = back.iter().zip(a).map(|(x, y)| C { re: x.re - y.re, im: x.im - y.im }).collect();
|
||
let uh: Vec<C> = (0..k * m).map(|t| f.u[(t % m) * k + t / m]).map(|z| C { re: z.re, im: -z.im }).collect();
|
||
let uu = matmul(&uh, &f.u, k, m, k, threads);
|
||
let vv_h: Vec<C> = (0..n * k).map(|t| f.vh[(t % k) * n + t / k]).map(|z| C { re: z.re, im: -z.im }).collect();
|
||
let vv = matmul(&f.vh, &vv_h, k, n, k, threads);
|
||
let off = |g: &[C]| (0..k * k).map(|t| (g[t].re - if t / k == t % k { 1.0 } else { 0.0 }).abs().max(g[t].im.abs())).fold(0.0, f64::max);
|
||
(frob(&diff) / frob(a), off(&uu), off(&vv))
|
||
}
|
||
|
||
fn main() {
|
||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||
let cores = std::thread::available_parallelism().map_or(1, |p| p.get());
|
||
let sizes: Vec<usize> = arg(&args, "sizes").unwrap_or_else(|| vec![64, 128, 256, 512, 1024, 2048]);
|
||
let threads: Vec<usize> = arg(&args, "threads").unwrap_or_else(|| vec![1, 4, cores]);
|
||
let jacobi_max: usize = arg(&args, "jacobi_max").and_then(|v| v.first().copied()).unwrap_or(512);
|
||
let shapes: Vec<String> = arg(&args, "shapes").unwrap_or_else(|| vec!["square".into(), "tall".into()]);
|
||
// Explicit shapes, `dims=4096x256,16384x512`, run after the sized ones.
|
||
let dims: Vec<(usize, usize)> = arg::<String>(&args, "dims")
|
||
.unwrap_or_default()
|
||
.iter()
|
||
.map(|d| {
|
||
let (m, n) = d.split_once('x').unwrap_or_else(|| panic!("dims: MxN, not {d}"));
|
||
(m.parse().unwrap_or_else(|_| panic!("dims: {d}")), n.parse().unwrap_or_else(|_| panic!("dims: {d}")))
|
||
})
|
||
.collect();
|
||
println!("host {} {}, {cores} logical cores", std::env::consts::OS, std::env::consts::ARCH);
|
||
println!("{:<7} {:>11} {:>8} {:>10} {:>10} {:>9} {:>9} {:>9} {:>9} hash", "shape", "m x n", "threads", "new s", "jacobi s", "speedup", "resid", "U'U-I", "V'V-I");
|
||
let (rows, secs, energy) = common::metered(|| {
|
||
let mut rows: Vec<Value> = Vec::new();
|
||
let mut cases: Vec<(String, usize, usize)> = Vec::new();
|
||
for shape in &shapes {
|
||
for &n0 in &sizes {
|
||
cases.push(match shape.as_str() {
|
||
"square" => (shape.clone(), n0, n0),
|
||
"tall" => (shape.clone(), 4 * n0, n0),
|
||
"wide" => (shape.clone(), n0 / 4, n0),
|
||
other => panic!("square, tall or wide, not {other}"),
|
||
});
|
||
}
|
||
}
|
||
cases.extend(dims.iter().map(|&(m, n)| ("dims".to_string(), m, n)));
|
||
for (shape, m, n) in cases {
|
||
{
|
||
let a = input(m, n, (m * 1_000_003 + n) as u64);
|
||
let mut runs = Vec::new();
|
||
let mut first: Option<(Svd, String)> = None;
|
||
for &t in &threads {
|
||
let (before, r0) = phases();
|
||
let started = Instant::now();
|
||
let f = svd(&a, m, n, Keep::All, t);
|
||
let s = started.elapsed().as_secs_f64();
|
||
let (after, r1) = phases();
|
||
let split: Vec<Value> = before.iter().zip(&after).filter(|(x, y)| y.1 > x.1).map(|(x, y)| json!([x.0, y.1 - x.1])).collect();
|
||
let h = hash(&f);
|
||
runs.push(json!({"threads": t, "seconds": s, "hash": h, "converged": f.converged, "phases": split, "rotations": r1 - r0}));
|
||
if first.is_none() {
|
||
first = Some((f, h));
|
||
}
|
||
}
|
||
let (f, h0) = first.expect("a thread count");
|
||
let same = runs.iter().all(|r| r["hash"] == h0.as_str());
|
||
let (resid, ou, ov) = accuracy(&a, m, n, &f, cores);
|
||
let (jac_secs, sdiff) = if m.max(n) <= jacobi_max {
|
||
let started = Instant::now();
|
||
let (_, sj, _) = jacobi(&a, m, n);
|
||
let s = started.elapsed().as_secs_f64();
|
||
let d = f.s.iter().zip(&sj).map(|(x, y)| (x - y).abs()).fold(0.0, f64::max) / f.s[0];
|
||
(Some(s), Some(d))
|
||
} else {
|
||
(None, None)
|
||
};
|
||
let best = runs.iter().map(|r| r["seconds"].as_f64().unwrap()).fold(f64::INFINITY, f64::min);
|
||
for r in &runs {
|
||
let t = r["threads"].as_u64().unwrap();
|
||
let s = r["seconds"].as_f64().unwrap();
|
||
let js = jac_secs.map_or("-".to_string(), |j| format!("{j:.3}"));
|
||
let sp = jac_secs.map_or("-".to_string(), |j| format!("{:.1}x", j / s));
|
||
println!(
|
||
"{shape:<7} {:>11} {t:>8} {s:>10.4} {js:>10} {sp:>9} {resid:>9.1e} {ou:>9.1e} {ov:>9.1e} {}",
|
||
format!("{m}x{n}"),
|
||
&r["hash"].as_str().unwrap()[..16]
|
||
);
|
||
let ph: Vec<String> = r["phases"].as_array().unwrap().iter().map(|p| format!("{} {:.3}", p[0].as_str().unwrap(), p[1].as_f64().unwrap())).collect();
|
||
println!(" {}; {} rotations", ph.join(", "), r["rotations"]);
|
||
}
|
||
assert!(same, "{m}x{n}: the thread counts gave different bits");
|
||
rows.push(json!({
|
||
"shape": shape, "m": m, "n": n, "runs": runs, "same_bits_at_every_thread_count": same,
|
||
"best_seconds": best, "jacobi_seconds": jac_secs, "jacobi_max_singular_value_difference": sdiff,
|
||
"residual": resid, "u_orthonormality": ou, "v_orthonormality": ov,
|
||
}));
|
||
}
|
||
}
|
||
rows
|
||
});
|
||
let request = json!({
|
||
"benchmark": "dense complex SVD (quantum_svd::svd, Keep::All) against the scalar one-sided Jacobi rotations (quantum_svd::jacobi)",
|
||
"input": "entries from a splitmix64 sequence, uniform in [-1/2, 1/2) for each part, column j scaled by 0.995^j (by repeated multiplication)",
|
||
"sizes": sizes, "shapes": shapes, "dims": dims.iter().map(|(m, n)| format!("{m}x{n}")).collect::<Vec<_>>(), "threads": threads, "jacobi_max": jacobi_max,
|
||
"host": { "os": std::env::consts::OS, "arch": std::env::consts::ARCH, "logical_cores": cores },
|
||
});
|
||
let result = json!({ "rows": rows, "wall_seconds": secs, "peak_rss_bytes": common::peak_rss_bytes(), "energy_figure": energy });
|
||
let out = common::receipt_path(&format!("svd_bench_{}_{}.json", std::env::consts::OS, std::env::consts::ARCH));
|
||
assert!(common::verifies(&common::seal(request, result, Some(&out))));
|
||
println!("receipt {}", out.display());
|
||
}
|