//! `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> { 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()); } }