Registers wai.avatar.face (GenerativeDeterministic, ops face-int/1, one-file parameter set): the WFG1 generator, WFI1 identity prior and WFK1 keypoint segment readers and writers; the driver's integer warp and keypoint maps; the lowering face-int/1 to one wai-ops/1-i32 graph, with the same arithmetic written out loop by loop for the tests; the identity and track records; verification over them and synthesis's claims, log and label policy; the corpus generator (bin wai_avatar_face_vectors) and the corpus and GPU tests. Registry export regenerated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
90 lines
3.9 KiB
Rust
90 lines
3.9 KiB
Rust
//! `face-int/1` on a GPU (extensions/avatars.md §7.8): every frame the corpus names, generated
|
|
//! with the generator's graph on each adapter this process finds, through `wai-ops/1-i32` in WGSL
|
|
//! (`int_ops::gpu`), is the frame the CPU generates, byte for byte, and the digest the corpus
|
|
//! states. Every graph the device runs is also run on the interpreter and the two compared
|
|
//! (`gpu::with_gpu_compared`), so the equality is shown graph by graph.
|
|
//!
|
|
//! **A skip is not a pass:** with no adapter the test prints `face-gpu: SKIPPED` and fails when
|
|
//! `WAI_OPS_GPU_REQUIRE` is set. Each adapter's result is one line, `face-gpu: key=…`, appended
|
|
//! to `WAI_OPS_GPU_REPORT` when it is set, with the time each path took.
|
|
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::time::Instant;
|
|
|
|
use serde_json::Value;
|
|
use sha2::{Digest, Sha256};
|
|
use wai::avatar_face::{frame, load, parse_identity};
|
|
use wai::int_ops::gpu::{self, wai_ops_gpu};
|
|
|
|
fn report(line: &str) {
|
|
println!("{line}");
|
|
if let Ok(p) = std::env::var("WAI_OPS_GPU_REPORT") {
|
|
let mut f = std::fs::OpenOptions::new().create(true).append(true).open(&p).unwrap();
|
|
writeln!(f, "{line}").unwrap();
|
|
}
|
|
}
|
|
|
|
fn hex(b: &[u8]) -> String {
|
|
b.iter().map(|x| format!("{x:02x}")).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn every_frame_on_every_adapter_is_the_cpus() {
|
|
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../live-conformance/avatar-face");
|
|
let doc: Value = serde_json::from_slice(&std::fs::read(dir.join("vectors.json")).unwrap()).unwrap();
|
|
let vectors: Vec<&Value> = doc["frames"].as_array().unwrap().iter().collect();
|
|
let generate_all = || -> Vec<Vec<u8>> {
|
|
vectors
|
|
.iter()
|
|
.map(|v| {
|
|
let (g, l) = load(&std::fs::read(dir.join(v["generator"].as_str().unwrap())).unwrap()).unwrap();
|
|
let id = parse_identity(&std::fs::read(dir.join(v["identity"].as_str().unwrap())).unwrap()).unwrap();
|
|
let kp: Vec<(i32, i32)> = v["keypoints"].as_array().unwrap().iter().map(|p| (p[0].as_i64().unwrap() as i32, p[1].as_i64().unwrap() as i32)).collect();
|
|
frame(&g, &l, &id, &kp).unwrap()
|
|
})
|
|
.collect()
|
|
};
|
|
let t = Instant::now();
|
|
let cpu = generate_all();
|
|
let cpu_ms = t.elapsed().as_secs_f64() * 1e3;
|
|
for (v, f) in vectors.iter().zip(&cpu) {
|
|
assert_eq!(hex(&Sha256::digest(f)), v["frame_sha256"].as_str().unwrap(), "{}: the CPU's frame is the corpus's", v["name"]);
|
|
}
|
|
|
|
let found = wai_ops_gpu::blocking::discover();
|
|
let adapters = found.found;
|
|
if adapters.is_empty() {
|
|
report("face-gpu: SKIPPED (no adapter)");
|
|
assert!(std::env::var("WAI_OPS_GPU_REQUIRE").is_err(), "WAI_OPS_GPU_REQUIRE is set and no adapter was found");
|
|
return;
|
|
}
|
|
for g in adapters {
|
|
let g = Arc::new(g);
|
|
let t = Instant::now();
|
|
let (got, stats) = gpu::with_gpu_compared(&g, generate_all);
|
|
let compared_ms = t.elapsed().as_secs_f64() * 1e3;
|
|
let t = Instant::now();
|
|
let (again, _) = gpu::with_gpu(&g, generate_all);
|
|
let gpu_ms = t.elapsed().as_secs_f64() * 1e3;
|
|
let same = got == cpu && again == cpu;
|
|
report(&format!(
|
|
"face-gpu: key={} frames={} same={} graphs_on_gpu={} (acc32 {}, acc64 {}) on_cpu={} compared={} differed={} errors={:?} cpu_ms={cpu_ms:.0} gpu_ms={gpu_ms:.0} compared_ms={compared_ms:.0}",
|
|
g.adapter().key(),
|
|
cpu.len(),
|
|
same,
|
|
stats.on_gpu(),
|
|
stats.acc32,
|
|
stats.acc64,
|
|
stats.on_cpu(),
|
|
stats.compared,
|
|
stats.differed,
|
|
stats.errors
|
|
));
|
|
assert!(same, "the frames on the device are the CPU's");
|
|
assert!(stats.on_gpu() > 0 && stats.on_cpu() == 0, "every graph ran on the device");
|
|
assert_eq!(stats.differed, 0);
|
|
assert!(stats.errors.is_empty());
|
|
}
|
|
}
|