quantum_fabric_hip (opt-in, Linux): the gate kernel the CUDA member runs, unchanged, compiled at run time by HIP's runtime compiler for each device's own architecture (--offload-arch from the device), with -ffp-contract=off. Both libraries are loaded when first used, so a build with the feature runs anywhere and finds no member where there is no HIP. The device's properties are read in the runtime's frozen layout (hipDeviceProp_tR0600); properties that do not read as that layout are refused, never guessed at. quantum_fabric::hip holds what names a member (the kernel text, the implementation id hip-lanes64/..., distinct from the CUDA one over the same text, the device identity and the key gpu-hip-<arch>); the mutants are the CUDA kernel's, the same edits of the same text. With quantum_sv the dense f64 state vector runs the CUDA kernels' text too (hip-sv64/..., contraction off). Measured on gfx1151: hip_member 9/9 (66020 probe pairs, all four pinned certificates, circuits to 22 qubits, 6 of 6 mutants killed) and hip_sv 3/3 (the state vector's pin b7d129d0..., 16 and 20 qubits bit for bit, 4 of 4 mutants killed). Co-Authored-By: Claude Opus 5.5 <[email protected]>
112 lines
4.7 KiB
Rust
112 lines
4.7 KiB
Rust
//! The dense f64 state vector on the HIP devices of the machine that runs
|
|
//! this: bit for bit the CPU's, or the certificate says not.
|
|
//!
|
|
//! The machine declares its devices in `WAI_QUANTUM_HIP_EXPECT` (comma-
|
|
//! separated keys, or `none`), as for `hip_member`; `WAI_QUANTUM_HIP_REPORT`
|
|
//! names a file the established lines are appended to.
|
|
#![cfg(not(target_arch = "wasm32"))]
|
|
|
|
use std::io::Write;
|
|
use wai_quantum::gates::Gate;
|
|
use wai_quantum::quantum_fabric::sv64::{self, MUTANTS, SV_DEFAULT, SV_DEFAULT_PIN, SvCorpus};
|
|
use wai_quantum::quantum_fabric::{Certification, Parity};
|
|
use wai_quantum::quantum_fabric_hip::{HipMember, discover};
|
|
use wai_quantum::quantum_sv::StateVector;
|
|
|
|
fn report(line: &str) {
|
|
println!("{line}");
|
|
if let Some(path) = std::env::var_os("WAI_QUANTUM_HIP_REPORT") {
|
|
let mut f = std::fs::OpenOptions::new().create(true).append(true).open(&path).expect("the report file opens");
|
|
writeln!(f, "{line}").expect("the report line is written");
|
|
}
|
|
}
|
|
|
|
fn members() -> Vec<HipMember> {
|
|
let d = discover();
|
|
let mut found: Vec<String> = d.found.iter().map(|m| m.key().to_string()).collect();
|
|
found.sort();
|
|
let found_list = if found.is_empty() { "none".to_string() } else { found.join(",") };
|
|
let expect = std::env::var("WAI_QUANTUM_HIP_EXPECT").unwrap_or_else(|_| panic!("WAI_QUANTUM_HIP_EXPECT is not set; found {found_list}"));
|
|
assert_eq!(found_list, expect, "the devices found are not the declared ones");
|
|
d.found
|
|
}
|
|
|
|
fn hex(b: &[u8]) -> String {
|
|
b.iter().map(|x| format!("{x:02x}")).collect()
|
|
}
|
|
|
|
/// The CPU certifying itself on `corpus`: the digest a bit-exact device
|
|
/// reproduces.
|
|
fn cpu_digest(corpus: SvCorpus) -> String {
|
|
let mut cert = Certification::new();
|
|
for case in corpus.cases() {
|
|
let r = case.reference().unwrap();
|
|
cert.add(&case.bytes(), &r, &r);
|
|
}
|
|
hex(&cert.finish(sv64::KERNEL_SV_RUN, "cpu", "cpu", &corpus.generator()).corpus)
|
|
}
|
|
|
|
#[test]
|
|
fn s0_the_state_vector_is_the_cpus_bit_for_bit() {
|
|
let cpu = cpu_digest(SV_DEFAULT);
|
|
assert_eq!(cpu, SV_DEFAULT_PIN, "this machine's CPU does not reproduce the pin");
|
|
for m in members() {
|
|
let sv = m.sv().unwrap_or_else(|e| panic!("{}: {e}", m.key()));
|
|
let c = sv.certify(SV_DEFAULT).unwrap_or_else(|e| panic!("{}: {e}", m.key()));
|
|
assert_eq!(c.parity, Parity::BitExact, "{}", m.key());
|
|
assert_eq!(c.cases, SV_DEFAULT.cases as u64);
|
|
assert_eq!(hex(&c.corpus), cpu, "{}: the digest is not the CPU's", m.key());
|
|
report(&format!("hip sv certified: key={} member={} kernel={} implementation={} parity=bit-exact cases={} digest={}", m.key(), m.id(), c.kernel, c.implementation, c.cases, hex(&c.corpus)));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn s1_wide_states_match_the_cpu() {
|
|
for m in members() {
|
|
let sv = m.sv().unwrap();
|
|
for (n, width) in [(16u32, 0usize), (20, 3)] {
|
|
let mut gates = Vec::new();
|
|
let mut a = 0.1f64;
|
|
for layer in 0..6u32 {
|
|
for q in 0..n {
|
|
a += 0.731;
|
|
gates.push(match (q + layer) % 5 {
|
|
0 => Gate::rx(q, a),
|
|
1 => Gate::ry(q, a),
|
|
2 => Gate::rz(q, a),
|
|
3 => Gate::sqrt_w(q),
|
|
_ => Gate::t(q),
|
|
});
|
|
}
|
|
let mut q = layer % 2;
|
|
while q + 1 < n {
|
|
gates.push(if q % 3 == 0 { Gate::fsim(q, q + 1, a, 0.5 * a) } else { Gate::cz(q, q + 1) });
|
|
q += 2;
|
|
}
|
|
gates.push(Gate::iswap(0, n - 1));
|
|
}
|
|
let mut cpu = StateVector::zero(n).unwrap();
|
|
cpu.run(&gates, width).unwrap();
|
|
let (re, im) = sv.run(n, &gates, width).unwrap();
|
|
let same = |a: &[f64], b: &[f64]| a.iter().zip(b).all(|(x, y)| x.to_bits() == y.to_bits());
|
|
assert!(same(&re, &cpu.re) && same(&im, &cpu.im), "{}: n={n} width={width}", m.key());
|
|
}
|
|
report(&format!("hip sv wide: key={} 16 and 20 qubits match the CPU bit for bit", m.key()));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn s2_mutants_are_caught() {
|
|
let small = SvCorpus { cases: 18, ..SV_DEFAULT };
|
|
let cpu = cpu_digest(small);
|
|
for m in members() {
|
|
let mut killed = 0;
|
|
for mutant in &MUTANTS {
|
|
let sv = m.sv_with_source(&sv64::mutate(mutant)).unwrap_or_else(|e| panic!("mutant {}: {e}", mutant.name));
|
|
let c = sv.certify(small).unwrap();
|
|
assert!(c.parity != Parity::BitExact || hex(&c.corpus) != cpu, "{}: mutant {} survived", m.key(), mutant.name);
|
|
killed += 1;
|
|
}
|
|
report(&format!("hip sv mutants: key={} killed {killed} of {}", m.key(), MUTANTS.len()));
|
|
}
|
|
}
|