//! The super-resolution derivations of extensions/exact-upscale.md §3 with their graph on a GPU //! (`int_ops::gpu`, `wai_ops_gpu`): under every prior of the committed corpus //! (`live-conformance/upscale/priors/`), `wai-upscale:sr2` and `wai-upscale:sr4` of the corpus's //! generated pictures, and `sr2` of a larger one, give the same bytes on every adapter this //! process finds as on the CPU. Every graph the device runs is also run on the interpreter and //! compared (`gpu::with_gpu_compared`). //! //! **A skip is not a pass.** With no adapter the test prints `exact-upscale gpu: SKIPPED` and //! fails when `WAI_OPS_GPU_REQUIRE` is set. Each adapter's result is one line, //! `exact-upscale gpu: key=…`, appended to `WAI_OPS_GPU_REPORT` when it is set. #![cfg(all(feature = "ops_gpu", feature = "exact_upscale"))] use std::io::Write; use std::path::PathBuf; use std::sync::Arc; use wai::exact_upscale::corpus::picture; use wai::int_ops::gpu::{self, wai_ops_gpu}; use wai::int_sr; 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(); } } /// One derivation: its name, the prior's bytes, the picture and its size. type Case = (&'static str, Vec, Vec, usize, usize); fn cases() -> Vec { let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../live-conformance/upscale/priors"); let mut out = Vec::new(); for name in ["generic.wsr", "title.wsr", "seeded.wsr"] { let prior = std::fs::read(dir.join(name)).unwrap_or_else(|e| panic!("{name}: {e}")); out.push((int_sr::SR2, prior.clone(), picture("scene one", 8, 6), 8, 6)); out.push((int_sr::SR4, prior.clone(), picture("scene two", 4, 3), 4, 3)); out.push((int_sr::SR2, prior, picture("a larger picture", 64, 48), 64, 48)); } out } fn run(cases: &[Case]) -> Vec> { cases .iter() .map(|(d, prior, px, w, h)| { let g = int_sr::load(prior).expect("a corpus prior loads"); int_sr::derive(d, &g, px, *w, *h).expect("within the limits") }) .collect() } #[test] fn the_derivations_give_the_same_bytes_with_their_graph_on_every_adapter() { let d = wai_ops_gpu::blocking::discover(); for (a, why) in &d.refused { report(&format!("exact-upscale gpu: adapter refused: key={} {} reason={why:?}", a.key(), a.describe())); } if d.found.is_empty() { report("exact-upscale gpu: SKIPPED: no adapter"); assert!(std::env::var_os("WAI_OPS_GPU_REQUIRE").is_none(), "WAI_OPS_GPU_REQUIRE is set and no adapter was found"); return; } let cases = cases(); let want = run(&cases); // sr4 runs the graph twice. let runs = cases.iter().map(|c| if c.0 == int_sr::SR4 { 2 } else { 1 }).sum::(); for g in d.found.into_iter().map(Arc::new) { let (got, stats) = gpu::with_gpu_compared(&g, || run(&cases)); let same = got == want; report(&format!( "exact-upscale gpu: key={} {} cases={} graph_runs={} acc32={} acc64={} cpu={} compared={} differ={} errors={} {}", g.adapter().key(), g.adapter().describe(), cases.len(), runs, stats.acc32, stats.acc64, stats.on_cpu(), stats.compared, stats.differed, stats.errors.len(), if same { "equal" } else { "DIFFERENT" } )); assert!(same, "the derivations' bytes differ on the device"); assert_eq!((stats.on_gpu(), stats.on_cpu(), stats.differed), (runs, 0, 0), "every graph runs on the device and equals the interpreter: {stats:?}"); assert!(stats.errors.is_empty(), "{:?}", stats.errors); } }