//! `dred-int/1` on a GPU (extensions/opus-dred.md §4.8): the corpus's payload vectors and its //! streams' reconstructions decoded with every graph on each adapter this process finds, through //! `wai-ops/1-i32` in WGSL (`int_ops::gpu`), give the same integers as the decode on the CPU. //! 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. //! //! `WAI_DRED_WEIGHTS` names the weights file (`live-conformance/dred/fetch.sh`). **A skip is not //! a pass:** with no adapter the test prints `dred-gpu: SKIPPED` and fails when //! `WAI_OPS_GPU_REQUIRE` is set. Each adapter's result is one line, `dred-gpu: key=…`, appended //! to `WAI_OPS_GPU_REPORT` when it is set. use std::io::Write; use std::path::PathBuf; use std::sync::Arc; use serde_json::Value; use wai::dred_exact::{corpus, packet, payload, row_bytes, scenario, Model}; 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 unhex(s: &str) -> Vec { (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect() } #[test] fn the_profile_on_every_adapter_gives_the_cpus_integers() { let w = std::env::var("WAI_DRED_WEIGHTS").expect("WAI_DRED_WEIGHTS must name the draft's weights file (live-conformance/dred/fetch.sh)"); let model = Model::from_weights(&std::fs::read(&w).unwrap()).unwrap(); let corpus: Value = serde_json::from_slice(&std::fs::read(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../live-conformance/dred/vectors.json")).unwrap()).unwrap(); let payloads: Vec = corpus["payloads"] .as_array() .unwrap() .iter() .filter(|v| v["outcome"] == "dred") .map(|v| { let p = unhex(v["packet"].as_str().unwrap()); let (b, _) = packet::find_dred(&p).unwrap().unwrap(); payload::parse(b, None).unwrap() }) .collect(); // Each stream's packets, received or lost, for its reconstruction. let streams: Vec>>> = corpus_streams(&corpus); let decode_all = || { let mut out: Vec> = payloads.iter().map(|p| row_bytes(&model.decode(p, usize::MAX).unwrap())).collect(); for packets in &streams { let slots = scenario::plan(packets, 8); out.push(scenario::buffer(&slots, &scenario::rebuild(&model, packets, &slots).unwrap())); } out }; let cpu = decode_all(); let found = wai_ops_gpu::blocking::discover(); let adapters = found.found; if adapters.is_empty() { report("dred-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 (got, stats) = gpu::with_gpu_compared(&g, decode_all); let same = got == cpu; report(&format!( "dred-gpu: key={} payloads={} streams={} same={} graphs_on_gpu={} (acc32 {}, acc64 {}) on_cpu={} compared={} differed={} errors={:?}", g.adapter().key(), payloads.len(), streams.len(), same, stats.on_gpu(), stats.acc32, stats.acc64, stats.on_cpu(), stats.compared, stats.differed, stats.errors )); assert!(same, "the decode on the device gives the CPU's integers"); 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()); } } fn corpus_streams(corpus: &Value) -> Vec>>> { corpus["streams"] .as_object() .unwrap() .values() .map(|st| { st["objects"] .as_array() .unwrap() .iter() .map(|o| o.as_str().map(|h| corpus::envelope_payload(&unhex(h)).unwrap().to_vec())) .collect() }) .collect() }