//! The vectors whose outcome only load step 8 decides (§14.1 item 6), checked against //! the reason the text gives for each: the bounds pass must refuse or tag them for //! that reason, not another. use std::path::PathBuf; use wai_ops::bounds::{self, Profile}; use wai_ops::json::{self, unhex}; use wai_ops::{graph::Graph, plan}; fn lines(file: &str) -> Vec { let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../ops-conformance").join(file); std::fs::read_to_string(&p).unwrap().lines().map(|l| json::parse(l.as_bytes()).unwrap()).collect() } fn graph_of(v: &json::Value) -> Graph { Graph::parse(&unhex(v.get("graph").unwrap().as_str().unwrap()).unwrap()).unwrap() } /// Every key of §7.2's table has a vector that fails `acc32` by that parameter /// condition alone: its graph meets `acc64`, every interval fits `acc32`, and its one /// node's only failure is that key. #[test] fn each_acc32_parameter_key_fails_alone() { let want = ["rq.mult", "rq.shift", "rq.post_bias", "weights", "chscale.m", "chscale.s", "igdn.gamma", "igdn.shift", "leaky.a"]; let mut seen: Vec = Vec::new(); for op in ["rescale", "conv", "conv_transpose", "chscale", "igdn", "leaky"] { for v in lines(&format!("vectors/{op}.jsonl")) { if !v.get("id").unwrap().as_str().unwrap().contains("/acc32_param/") { continue; } let g = graph_of(&v); let sizes = plan::load_plan(&g).unwrap(); let b = bounds::pass(&g, sizes.as_deref()).unwrap(); assert!(b.meets(&g, Profile::Acc64)); let n = &b.nodes[0]; assert!(n.inter.iter().all(|i| i.iv.fits(i.class.width(Profile::Acc32))), "{op}: an interval fails acc32"); assert!(n.results.iter().all(|r| r.fits(32))); assert_eq!(n.params32.len(), 1, "{op}: {:?}", n.params32); let f = b.first_failure(&g, Profile::Acc32).unwrap(); assert_eq!(f.miss.key, n.params32[0]); seen.push(f.miss.key.clone()); } } // `weights` has one for each of `conv` and `conv_transpose`. seen.sort(); seen.dedup(); let mut w: Vec = want.iter().map(|s| s.to_string()).collect(); w.sort(); assert_eq!(seen, w); } /// `sigmoid` and `elu` at −2^63 fail `acc64` through `exp.negx`, at 65 bits; an `rq` /// whose `q` passes 64 bits fails at `rq.q` while `rq.y` fits where a `post` bias /// brings it back. Without a bias (`chscale`, `mul`) `y` and the producer result pass /// with `q`, at the same bits, and `out.0` is the smallest key of the three. #[test] fn step8_refusals_fail_where_the_text_says() { for (file, key) in [("sigmoid", "exp.negx"), ("elu", "exp.negx"), ("rescale", "rq.q"), ("conv", "rq.q"), ("conv_transpose", "rq.q"), ("chscale", "out.0"), ("mul", "out.0")] { let mut n = 0; for v in lines(&format!("vectors/{file}.jsonl")) { let id = v.get("id").unwrap().as_str().unwrap(); if !(id.contains("/step8/") || id.contains("/step8_q/")) || v.get("load").unwrap().as_str() == Some("ok") { continue; } let g = graph_of(&v); let b = bounds::pass(&g, None).unwrap(); let f = b.first_failure(&g, Profile::Acc64).unwrap(); assert_eq!((f.miss.key.as_str(), f.miss.bits), (key, Some(65)), "{id}"); if matches!(file, "rescale" | "conv" | "conv_transpose") { let y = b.nodes[0].inter.iter().find(|i| i.key == "rq.y").unwrap(); assert!(y.iv.fits(64), "{id}: y fits"); } n += 1; } assert!(n > 0, "{file}: no step-8 refusal"); } }