open-standards/wai/wai-quantum/tests/metal_tensor_ops.rs
Transaction Science fb08994eee
All checks were successful
wai-quantum WebGPU member / member · DX12 and Vulkan (cbio-windows) (pull_request) Successful in 12m12s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 10s
standards workspaces build + test / macOS energy backends (pull_request) Has been skipped
WAI conformance / does this change touch it (pull_request) Successful in 29s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m26s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 27s
WAI neural sink gates / does this change touch it (pull_request) Successful in 26s
WAI energy receipts / does this change touch it (pull_request) Successful in 31s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 10s
wai-quantum dataflow member / does this change touch it (pull_request) Successful in 8s
wai-quantum CUDA member / does this change touch it (pull_request) Successful in 9s
wai-quantum HIP member / does this change touch it (pull_request) Successful in 5s
wai-quantum signal processor / does this change touch it (pull_request) Successful in 6s
wai-quantum FPGA member / does this change touch it (pull_request) Successful in 7s
wai-quantum Metal member / does this change touch it (pull_request) Successful in 5s
wai-quantum npu-cores member / does this change touch it (pull_request) Successful in 4s
wai-quantum neural-processor member / does this change touch it (pull_request) Successful in 7s
wai-quantum OpenCL member / does this change touch it (pull_request) Successful in 6s
wai-quantum-qpu / does this change touch it (pull_request) Successful in 8s
wai-quantum matrix-extension member / does this change touch it (pull_request) Successful in 9s
wai-quantum tensor-engine member / does this change touch it (pull_request) Successful in 8s
wai-quantum portable tensor program / does this change touch it (pull_request) Successful in 5s
wai-quantum CPU vector units / does this change touch it (pull_request) Successful in 4s
WAI mbt2018_mean integer decode (regenerated vectors) / does this change touch it (pull_request) Successful in 4s
wai-quantum WebGPU member / does this change touch it (pull_request) Successful in 6s
wai-web wasm32 / does this change touch it (pull_request) Successful in 7s
wai-quantum HIP member / member · HIP (framework-128gb) (pull_request) Successful in 19s
wai-quantum npu-cores member / member · npu-cores, neural processor (framework-128gb silicon) (pull_request) Successful in 1m11s
wai-quantum OpenCL member / member · OpenCL GPU (framework-128gb) (pull_request) Successful in 13s
wai-quantum portable tensor program / tensor program · integrated GPU (framework-128gb) (pull_request) Successful in 7s
wai-quantum CPU vector units / member · vector units, avx2 and avx512 (framework-128gb silicon) (pull_request) Successful in 10s
wai-quantum WebGPU member / member · integrated GPU, Vulkan (framework-128gb) (pull_request) Successful in 7m22s
wai-quantum CUDA member / member · CUDA (jetson-hub) (pull_request) Successful in 22s
wai-quantum OpenCL member / member · OpenCL GPU and CPU (jetson-hub) (pull_request) Successful in 21s
wai-quantum portable tensor program / tensor program · GPUs (jetson-hub) (pull_request) Successful in 16s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 16s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 12s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 30s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 58s
wai-quantum on every processor / emulated · aarch64-unknown-linux-gnu (pull_request) Successful in 1m31s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 1m55s
wai-quantum on every processor / emulated · armv7-unknown-linux-gnueabihf (pull_request) Successful in 2m13s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m21s
wai-quantum on every processor / emulated · loongarch64-unknown-linux-gnu (pull_request) Successful in 3m50s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 3m54s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 22s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 10m6s
wai-quantum on every processor / emulated · powerpc64-unknown-linux-gnu (pull_request) Successful in 5m8s
wai-quantum HIP member / member · HIP absent (linux-arm64) (pull_request) Successful in 20s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m7s
wai-quantum npu-cores member / member · npu-cores absent (linux-arm64) (pull_request) Successful in 19s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 11m0s
wai-quantum tensor-engine member / member · tensor engine absent (linux-arm64) (pull_request) Successful in 17s
wai-quantum on every processor / emulated · powerpc64le-unknown-linux-gnu (pull_request) Successful in 5m41s
wai-quantum on every processor / emulated · riscv64gc-unknown-linux-gnu (pull_request) Successful in 3m48s
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Successful in 18m23s
wai-quantum on every processor / emulated · s390x-unknown-linux-gnu (pull_request) Successful in 3m46s
wai-quantum on every processor / emulated · sparc64-unknown-linux-gnu (pull_request) Successful in 5m53s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
wai-quantum on every processor / emulated · i686-unknown-linux-gnu (pull_request) Successful in 3m48s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 8m1s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 18s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 39s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 1m32s
wai-pack / wai-pack · macos (pull_request) Successful in 59s
wai-quantum dataflow member / member · dataflow device absent (macos) (pull_request) Successful in 12s
wai-quantum FPGA member / member · FPGA datapath, simulated (macos) (pull_request) Successful in 1m4s
wai-quantum Metal member / member · Metal (macos) (pull_request) Successful in 26s
wai-quantum neural-processor member / member · neural processor (macos) (pull_request) Successful in 5m31s
wai-quantum OpenCL member / member · OpenCL GPU (macos) (pull_request) Successful in 19s
wai-quantum matrix-extension member / member · matrix extension absent (macos) (pull_request) Successful in 9s
wai-quantum portable tensor program / tensor program · Metal and the arm64 CPU (macos) (pull_request) Successful in 13s
wai-quantum CPU vector units / member · vector unit absent (macos) (pull_request) Successful in 12s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 2m16s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 1m32s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m26s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 26s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 1m32s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 2m15s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 11m17s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m29s
wai-pack / wai-pack · linux (pull_request) Successful in 1m26s
wai-quantum CUDA member / member · CUDA absent (linux) (pull_request) Successful in 26s
wai-quantum dataflow member / member · dataflow device, simulated (pci 1e52:b140) (pull_request) Successful in 3m28s
wai-quantum dataflow member / member · dataflow device, simulated (pci 1e52:401e) (pull_request) Successful in 4m37s
wai-quantum FPGA member / member · FPGA simulation absent (linux) (pull_request) Successful in 20s
wai-quantum Metal member / member · Metal absent (linux) (pull_request) Successful in 21s
wai-quantum neural-processor member / member · neural processor absent (linux) (pull_request) Successful in 20s
wai-quantum OpenCL member / member · OpenCL absent (linux) (pull_request) Successful in 27s
wai-quantum-qpu / clients against their contracts (linux) (pull_request) Successful in 20s
wai-quantum matrix-extension member / member · matrix extension, emulated (aarch64 Linux) (pull_request) Successful in 18s
wai-quantum tensor-engine member / member · tensor engine, the kernel toolchain's simulator (linux) (pull_request) Successful in 8m14s
wai-quantum portable tensor program / tensor program · CPU, mutants, GPU builds (pull_request) Successful in 31s
wai-quantum CPU vector units / member · vector unit, rvv 1024-bit (riscv64, emulated) (pull_request) Successful in 17s
wai-quantum CPU vector units / member · vector unit, sve 128-bit (aarch64, emulated) (pull_request) Successful in 26s
wai-quantum CPU vector units / member · vector unit, rvv 128-bit (riscv64, emulated) (pull_request) Successful in 12s
wai-quantum CPU vector units / member · vector unit, sve 2048-bit (aarch64, emulated) (pull_request) Successful in 10s
wai-quantum signal processor / processor and member · signal processor, emulated (pull_request) Successful in 13m28s
wai-quantum CPU vector units / member · vector unit, avx2 (x86_64 silicon) (pull_request) Successful in 13s
wai-quantum CPU vector units / member · vector unit, sve 512-bit (aarch64, emulated) (pull_request) Successful in 12s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 2m54s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 2m18s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 5m27s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m7s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m35s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m36s
wai-quantum: the Metal member's tensor-operation kernel; M5 measured
tensor_ops.metal (metal-tensor-ops-limbs8/..., MetalMember::tensor_ops):
every product formed by the shading language's 4.0 tensor operation
(matmul2d, bf16 accumulated in f32), which the platform maps to the GPU's
matrix hardware where it has it. Each threadgroup owns a scratch slice in
device memory (64x32 limbs, 32x128 weights, 64x128 f32 coefficients); a
64-pair tile is one multiply in four 32-column runs. Offset-binary limbs,
as for the HIP kernel. Family 1007+ under OS 26+, else Unsupported. Eight
mutants. Build records each kernel's language version and scratch.

Measured, silicon:
- family 1009 (M3 Ultra build host / macos runner): 9/9, 66020 probe
  pairs, four pins, 22 qubits, 8 of 8 mutants;
- family 1010 (the only M5 in the fleet, the dev Mac; test binaries
  built on the build host, run once niced): all three Metal kernels 9/9,
  four pins each, 6/6, 7/7 and 8/8 mutants.
With signed limbs instead, the tensor op agrees on all 66020 pairs on
both generations. Off macOS all three suites pass over no device. CI:
a step of wai-quantum-metal and its absent leg.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 10:19:30 -04:00

446 lines
21 KiB
Rust

//! The Metal member's tensor-operation kernel on the devices of the machine
//! that runs this: every product the gate needs formed by the shading
//! language's tensor operation, from offset-binary 8-bit limbs in bf16
//! accumulated in f32, held to the same tests as the other kernels
//! (`metal_member`).
//!
//! The machine declares its devices in `WAI_QUANTUM_METAL_EXPECT`, as for
//! `metal_member`: their keys, comma-separated and sorted, or `none`. Every
//! declared device with the tensor operation (GPU family 1007 or later, under
//! operating system 26 or later) must run the kernel and pass its probe; one
//! without must be refused as unsupported. Each test prints what it established and, when
//! `WAI_QUANTUM_METAL_REPORT` names a file, appends the same lines to it.
//!
//! ```text
//! WAI_QUANTUM_METAL_EXPECT=gpu-msl-1009 \
//! cargo test --features quantum_fabric_metal --test metal_tensor_ops -- --test-threads=1
//! ```
#![cfg(not(target_arch = "wasm32"))]
use std::io::Write;
use std::sync::OnceLock;
use wai_quantum::ed25519_dalek::SigningKey;
use wai_quantum::quantum::{BaseGate, Circuit, Gate, Gateset, MAX_QUBITS, QuantumError};
use wai_quantum::quantum_fabric::metal::{self, TENSOR_OPS_MUTANTS};
use wai_quantum::quantum_fabric::lanes::{self, GateWords, Lane};
use wai_quantum::quantum_fabric::plan::{Program, Step};
use wai_quantum::quantum_fabric::wgsl::{Detector, Mutant};
use wai_quantum::quantum_fabric::{
Corpus, CpuReference, FABRIC_DEFAULT, Fabric, FabricClass, FabricReceipt, MemberError, Parity, Presence, REFERENCE, apply_kernel, detect_local, place, ready,
};
use wai_quantum::quantum_fabric_metal::{Discovery, MetalError, MetalMember, discover};
/// The CPU's corpus digests: random-v1 (seed 7, 200 cases) and fabric-v1
/// (seed 7, 200 + 24 cases), per gateset. A bit-exact member reproduces them.
const PINS: [(Gateset, &str, &str); 2] = [
(Gateset::V1, "d18454c639acfd9c7b66450f4ded1aac4ab46cc10a7bedbcf4958a96e204fb36", "41c726a0ac80f7defef92e15ecf893bdb7a3076b5e7f670fa0b7142e5c033df7"),
(Gateset::V2, "3dbde276c1095bf827faec71d5980e050f8a55a5ba7d97cd3bbdaec3f43a13b2", "9a84b55af68f1a8b07390b234cf562dd8d85cef3185a697ab85690c8790a7809"),
];
const RANDOM_V1: Corpus = Corpus::Random { seed: 7, cases: 200 };
static DISCOVERY: OnceLock<Discovery> = OnceLock::new();
static TENSOR_OPS: OnceLock<Vec<MetalMember>> = OnceLock::new();
fn discovery() -> &'static Discovery {
DISCOVERY.get_or_init(discover)
}
/// Prints `line` and appends it to the file `WAI_QUANTUM_METAL_REPORT` names.
fn report(line: &str) {
println!("{line}");
if let Some(path) = std::env::var_os("WAI_QUANTUM_METAL_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");
}
}
/// The declared device set, as written, if it is exactly what was found;
/// otherwise why not, with what was found and refused and how to declare it.
fn declared(d: &Discovery) -> Result<String, String> {
let mut found: Vec<&str> = d.found.iter().map(|m| m.key()).collect();
found.sort_unstable();
let found_list = if found.is_empty() { "none".to_string() } else { found.join(",") };
let refused: Vec<String> = d.refused.iter().map(|r| format!("{} ({:?}): {}", r.key, r.device, r.error)).collect();
let refused_list = if refused.is_empty() { "none".to_string() } else { refused.join("; ") };
let unavailable = d.unavailable.as_deref().unwrap_or("no");
let how = format!("found: {found_list}; refused: {refused_list}; unavailable: {unavailable}. Declare the devices this machine must have: WAI_QUANTUM_METAL_EXPECT={found_list}");
let expect = match std::env::var("WAI_QUANTUM_METAL_EXPECT") {
Ok(v) if !v.is_empty() => v,
_ => return Err(format!("WAI_QUANTUM_METAL_EXPECT is not set. {how}")),
};
let keys: Vec<&str> = if expect == "none" { Vec::new() } else { expect.split(',').collect() };
let well_formed = keys.windows(2).all(|w| w[0] <= w[1]) && keys.iter().all(|k| !k.is_empty() && *k != "none" && k.trim() == *k);
if !well_formed {
return Err(format!("WAI_QUANTUM_METAL_EXPECT={expect:?} is not a sorted, comma-separated list of keys, nor none. {how}"));
}
for k in &keys {
if !found.contains(k)
&& let Some(r) = d.refused.iter().find(|r| r.key == *k)
{
return Err(format!("the declared device {k} was refused: {}. {how}", r.error));
}
}
if found != keys {
return Err(format!("the devices found are not the declared ones ({expect}). {how}"));
}
Ok(expect)
}
/// The tensor-operation members: one per declared device with the operation, once
/// the declared devices are exactly the ones found.
fn members() -> &'static [MetalMember] {
let d = discovery();
if let Err(why) = declared(d) {
panic!("{why}");
}
TENSOR_OPS.get_or_init(|| {
d.found.iter().filter(|m| m.identity().has_tensor_ops()).map(|m| m.tensor_ops().unwrap_or_else(|e| panic!("{}: the tensor-operation kernel: {e}", m.key()))).collect()
})
}
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
fn next(s: &mut u64) -> u64 {
*s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *s;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
/// A value in `[-bound, bound]`.
fn within(s: &mut u64, bound: i64) -> i32 {
((next(s) % (2 * bound as u64 + 1)) as i64 - bound) as i32
}
/// A random circuit of `ops` gates on `n` qubits: every base gate, up to three
/// distinct controls, `P(1..=32)`.
fn random_circuit(n: u8, ops: usize, seed: u64, gs: Gateset) -> Circuit {
let bases = [BaseGate::I, BaseGate::X, BaseGate::Y, BaseGate::Z, BaseGate::H, BaseGate::S, BaseGate::Sdg, BaseGate::T, BaseGate::Tdg, BaseGate::P];
let mut s = seed;
let mut c = Circuit::with_gateset(n, gs);
for _ in 0..ops {
let base = bases[(next(&mut s) % bases.len() as u64) as usize];
let target = (next(&mut s) % u64::from(n)) as u8;
let mut controls = Vec::new();
for _ in 0..next(&mut s) % 4 {
let q = (next(&mut s) % u64::from(n)) as u8;
if q != target && !controls.contains(&q) {
controls.push(q);
}
}
let param = if base == BaseGate::P { 1 + (next(&mut s) % 32) as u16 } else { 0 };
c.ops.push(Gate { base, controls, target, param });
}
c
}
/// Five circuits the native simulator refuses, in each gateset.
fn refused_circuits() -> Vec<Circuit> {
let gate = |base, controls: Vec<u8>, target, param| Gate { base, controls, target, param };
let bad = [
(1, vec![gate(BaseGate::H, vec![], 0, 0), gate(BaseGate::P, vec![], 0, 0)]),
(1, vec![gate(BaseGate::P, vec![], 0, 33)]),
(1, vec![gate(BaseGate::X, vec![], 3, 0)]),
(2, vec![gate(BaseGate::X, vec![0], 0, 0)]),
(2, vec![gate(BaseGate::X, vec![5], 1, 0)]),
];
let mut out = Vec::new();
for gs in Gateset::ALL {
for (n, ops) in bad.clone() {
let mut c = Circuit::with_gateset(n, gs);
c.ops = ops;
out.push(c);
}
}
out
}
/// `Z` on qubit 0: on `[(0, 0), (i32::MIN, 0)]` its exact result is `2^31`,
/// which leaves 32 bits.
fn z_words() -> GateWords {
lanes::gate_words(&Gate { base: BaseGate::Z, controls: vec![], target: 0, param: 0 }, Gateset::V2)
}
const Z_STATE: [Lane; 2] = [[0, 0], [i32::MIN, 0]];
#[test]
fn c0_devices_are_declared() {
let d = discovery();
match declared(d) {
Ok(expect) => report(&format!("metal tensor-ops devices: as declared ({expect})")),
Err(why) => panic!("{why}"),
}
for m in &d.found {
if !m.identity().has_tensor_ops() {
assert!(matches!(m.tensor_ops(), Err(MetalError::Unsupported(_))), "{}", m.key());
report(&format!("metal tensor-ops unsupported: key={} family {} os {}", m.key(), m.identity().family, m.identity().os));
}
}
for t in members() {
assert_eq!(t.class(), FabricClass::Gpu);
assert!(t.id().starts_with("local/gpu-msl-"), "{}", t.id());
assert_eq!(t.implementation(), metal::tensor_ops_implementation_id());
assert_ne!(t.implementation(), metal::implementation_id());
assert_eq!(t.identity().member_id(), t.id());
let lanes64 = d.found.iter().find(|m| m.id() == t.id()).expect("the device's member");
assert_eq!((lanes64.implementation(), lanes64.max_qubits()), (metal::implementation_id().as_str(), t.max_qubits()));
assert!(t.max_qubits() >= 20, "{}: holds only {} qubits", t.key(), t.max_qubits());
assert!(t.detail().starts_with(lanes64.detail()) && t.detail().contains("tensor operation"), "{}", t.detail());
report(&format!("metal tensor-ops member: key={} id={} implementation={} {}", t.key(), t.id(), t.implementation(), t.detail()));
}
}
#[test]
fn c1_probe_matches_lanes() {
for m in members() {
let pairs = lanes::probe_pairs(1 << 16, 0x0cda);
let out = m.probe(&pairs).unwrap_or_else(|e| panic!("{}: {e}", m.key()));
assert_eq!(lanes::first_mismatch(&pairs, &out), None, "{}", m.key());
report(&format!("metal tensor-ops probe: key={} {} pairs agree", m.key(), pairs.len()));
}
}
#[test]
fn c2_kernel_matches_run_words() {
for m in members() {
let mut s = 0x6b65_726e_656c_u64;
let (mut flagged, mut clean) = (0, 0);
for case in 0..2000 {
let n = 1 + (next(&mut s) % 12) as u32;
// Three regimes: the whole 32-bit range (nearly always out of
// range), small states and entries (never), and states and
// entries up to 2^30 (either).
let mode = case % 3;
let value = |s: &mut u64| -> i32 {
match mode {
0 => next(s) as i32,
1 => within(s, 1 << 22),
_ => within(s, 1 << 30),
}
};
let entry = |s: &mut u64| -> u32 {
match mode {
0 => next(s) as u32,
1 => within(s, 1 << 29) as u32,
_ => within(s, 1 << 30) as u32,
}
};
let state: Vec<Lane> = (0..1usize << n).map(|_| [value(&mut s), value(&mut s)]).collect();
let gates: Vec<GateWords> = (0..1 + next(&mut s) % 8)
.map(|_| {
let t = (next(&mut s) % u64::from(n)) as u32;
let ctrl = (0..n).filter(|&b| b != t && next(&mut s).is_multiple_of(4)).fold(0u32, |acc, b| acc | 1 << b);
let mut w = [0u32; 8];
w.iter_mut().for_each(|x| *x = entry(&mut s));
GateWords { m: w, tbit: 1 << t, ctrl }
})
.collect();
let want = lanes::run_words(&state, &gates).expect("valid shapes");
let got = m.run_lanes(&state, &gates).unwrap_or_else(|e| panic!("{}: case {case}: {e}", m.key()));
assert_eq!(got.out_of_range, want.out_of_range, "{}: case {case}, n = {n}", m.key());
assert!(got.amps == want.amps, "{}: case {case}, n = {n}: the lanes differ", m.key());
if want.out_of_range.is_some() {
flagged += 1;
} else {
clean += 1;
}
}
assert!(flagged >= 100 && clean >= 100, "{}: {flagged} flagged, {clean} clean", m.key());
report(&format!("metal tensor-ops kernel: key={} ok", m.key()));
}
}
#[test]
fn c3_certificates_reproduce_the_pins() {
let ms = members();
for (gs, random_pin, fabric_pin) in PINS {
let cpu = lanes::certify_corpus(gs, "local/test", RANDOM_V1);
assert_eq!(hex(&cpu.corpus), random_pin, "{gs:?}");
let other = Corpus::Random { seed: 8, cases: 300 };
let cpu_other = lanes::certify_corpus(gs, "local/test", other);
for m in ms {
for (corpus, cases, pin) in [(RANDOM_V1, 200, random_pin), (FABRIC_DEFAULT, 224, fabric_pin)] {
let c = m.certify(gs, corpus).unwrap_or_else(|e| panic!("{} {gs:?} {}: {e}", m.key(), corpus.name()));
assert_eq!(c.parity, Parity::BitExact, "{} {gs:?} {}", m.key(), corpus.name());
assert_eq!(c.cases, cases);
assert_eq!(hex(&c.corpus), pin, "{} {gs:?} {}", m.key(), corpus.name());
assert_eq!((c.kernel.as_str(), c.implementation.as_str(), c.member.as_str()), (apply_kernel(gs), m.implementation(), m.id()));
assert_eq!(c.generator, corpus.generator(gs));
report(&format!(
"metal tensor-ops certified: key={} member={} kernel={} corpus={} implementation={} parity=bit-exact cases={} digest={}",
m.key(),
m.id(),
c.kernel,
corpus.name(),
c.implementation,
c.cases,
hex(&c.corpus)
));
}
// A corpus with no pin: the member and the CPU agree on it.
let c = m.certify(gs, other).unwrap_or_else(|e| panic!("{} {gs:?} seed 8: {e}", m.key()));
assert_eq!((c.parity, c.cases), (Parity::BitExact, 300), "{} {gs:?}", m.key());
assert_eq!(c.corpus, cpu_other.corpus, "{} {gs:?}", m.key());
}
}
}
#[test]
fn c4_wide_circuits_match_native() {
for m in members() {
let widest = m.max_qubits().min(22);
let mut s = 0x0077_6964_6500_u64;
for (k, n) in [12u8, 16, 18, 20, widest].into_iter().enumerate() {
for gs in Gateset::ALL {
let c = random_circuit(n, 40, 0x1000 + k as u64, gs);
let start = (next(&mut s) as usize) % (1usize << n);
let want = c.simulate_from(start).expect("the native run");
let got = m.simulate_from(&c, start).unwrap_or_else(|e| panic!("{}: n = {n} {gs:?}: {e}", m.key()));
assert!(got == want, "{}: n = {n} {gs:?}: the states differ", m.key());
}
}
// The transforms the conformance corpus carries.
for n in [3u8, 5, 8, 12] {
let qft = Circuit::qft(n);
assert!(m.simulate(&qft).unwrap() == qft.simulate().unwrap(), "{}: QFT-{n}", m.key());
}
report(&format!("metal tensor-ops wide: key={} up to {widest} qubits match native", m.key()));
}
}
#[test]
fn c5_refusals_are_typed() {
for m in members() {
for c in refused_circuits() {
let native = c.simulate_from(0).unwrap_err();
assert_eq!(m.simulate_from(&c, 0).unwrap_err(), MetalError::Circuit(native), "{c:?}");
}
assert_eq!(m.simulate(&Circuit::new(MAX_QUBITS + 1)).unwrap_err(), MetalError::Circuit(QuantumError::TooManyQubits(MAX_QUBITS + 1)));
let small = m.with_max_qubits(4);
assert_eq!(small.simulate(&Circuit::new(5)).unwrap_err(), MetalError::TooWide { n_qubits: 5, max: 4 });
let wide = Program { n_qubits: 5, start: 0, gates: Vec::new(), steps: Vec::new() };
assert_eq!(small.run_program(&wide).unwrap_err(), MetalError::TooWide { n_qubits: 5, max: 4 });
// A result that leaves 32 bits is reported at its op, and the lane
// holds its low 32 bits.
let r = m.run_lanes(&Z_STATE, &[z_words()]).unwrap();
assert_eq!(r.out_of_range, Some(0), "{}", m.key());
assert_eq!(r.amps[1][0] as u32, 0x8000_0000);
// Malformed words, a state the host cannot lay out and a tile step are
// host defects, never results.
let bad = GateWords { tbit: 3, ..z_words() };
assert!(matches!(m.run_lanes(&[[0, 0]; 4], &[bad]), Err(MetalError::Internal(_))));
assert!(matches!(m.run_lanes(&[[0, 0]; 3], &[z_words()]), Err(MetalError::Internal(_))));
let tiled = Program { n_qubits: 2, start: 0, gates: Vec::new(), steps: vec![Step::Tile { first: 0, count: 1, size: 2 }] };
assert!(matches!(m.run_program(&tiled), Err(MetalError::Internal(_))));
// The refusals that may still fall back to another member, do.
assert!(MemberError::from(MetalError::TooWide { n_qubits: 5, max: 4 }).falls_back());
report(&format!("metal tensor-ops refusals: key={} typed", m.key()));
}
}
/// Whether a member running `mutant`'s text is caught by the mutant's
/// detector.
fn caught(mm: &MetalMember, mutant: &Mutant, key: &str) -> bool {
match mutant.detector {
Detector::Probe => {
let edges = lanes::probe_pairs(0, 0);
let out = mm.probe(&edges).expect("the mutant's probe runs");
let differing = edges.iter().zip(&out).filter(|&(&(a, b), o)| *o != (lanes::mul_wide(a, b), lanes::fxmul_wide(a, b))).count();
println!("metal tensor-ops mutant {} on {key}: {differing} of 484 edge pairs differ", mutant.name);
differing >= 1
}
Detector::Certificate => Gateset::ALL.into_iter().all(|gs| match mm.certify(gs, RANDOM_V1) {
Err(e) => {
println!("metal tensor-ops mutant {} on {key}: {gs:?} certification refused: {e}", mutant.name);
true
}
Ok(c) => {
let cpu = lanes::certify_corpus(gs, "local/test", RANDOM_V1);
c.parity != Parity::BitExact || c.corpus != cpu.corpus
}
}),
Detector::Range => mm.run_lanes(&Z_STATE, &[z_words()]).map(|r| r.out_of_range) != Ok(Some(0)),
other => panic!("no tensor-operation mutant uses the {other:?} detector"),
}
}
#[test]
fn c6_mutants_are_killed() {
assert_eq!(lanes::probe_pairs(0, 0).len(), 484);
for m in members() {
let mut killed = 0;
for mutant in &TENSOR_OPS_MUTANTS {
let mm = m.with_tensor_ops_source(&metal::tensor_ops_mutate(mutant)).unwrap_or_else(|e| panic!("{}: mutant {} does not compile: {e}", m.key(), mutant.name));
assert_ne!(mm.implementation(), m.implementation());
assert!(caught(&mm, mutant, m.key()), "{}: mutant {} survived", m.key(), mutant.name);
killed += 1;
}
assert_eq!(killed, TENSOR_OPS_MUTANTS.len());
report(&format!("metal tensor-ops mutants: key={} killed {killed} of {}", m.key(), TENSOR_OPS_MUTANTS.len()));
}
}
#[test]
fn c7_receipt_placement_and_the_fabric() {
for m in members() {
let member = m.member(FABRIC_DEFAULT).unwrap_or_else(|e| panic!("{}: {e}", m.key()));
assert_eq!((member.id.as_str(), member.class, member.presence), (m.id(), FabricClass::Gpu, Presence::Detected));
assert_eq!(member.kernels.len(), 2);
for (entry, gs) in member.kernels.iter().zip(Gateset::ALL) {
assert_eq!((entry.kernel.as_str(), entry.implementation.as_str()), (apply_kernel(gs), m.implementation()));
assert!(entry.energy.is_none());
assert_eq!(entry.certificate.parity, Parity::BitExact, "{gs:?}");
}
// A receipt over a member's run.
let qft = Circuit::qft(5);
let state = m.simulate(&qft).unwrap();
assert_eq!(state, qft.simulate().unwrap());
let entry = member.kernels.iter().find(|k| k.kernel == apply_kernel(qft.gateset)).unwrap();
let signer = SigningKey::from_bytes(&[7; 32]);
let receipt = FabricReceipt::seal(&signer, "local/test", state.statevector_hash(), &member, entry, 0, None);
assert!(receipt.verify());
assert!(receipt.certificate_matches(&entry.certificate));
// Placement: beside the CPU reference with no energy figures, the
// reference; alone, the member.
let local = detect_local();
for gs in Gateset::ALL {
let both = [local.clone(), member.clone()];
let (placed, k) = place(&both, apply_kernel(gs), &Parity::BitExact).unwrap();
assert_eq!((placed.id.as_str(), k.implementation.as_str()), (local.id.as_str(), REFERENCE));
let (placed, k) = place(std::slice::from_ref(&member), apply_kernel(gs), &Parity::BitExact).unwrap();
assert_eq!((placed.id.as_str(), k.implementation.as_str()), (m.id(), m.implementation()));
}
// On the fabric: the member alone runs the circuit; a pairing with
// another member's entries is refused.
let mut fabric = Fabric::new();
fabric.add(member.clone(), m);
let run = ready(fabric.run(&qft, 0, &Parity::BitExact, &signer, "local/test")).expect("resolves").expect("runs");
assert_eq!(run.state, state);
assert_eq!(run.receipt.member_id, m.id());
assert!(run.receipt.verify());
let mut mismatched = Fabric::new();
mismatched.add(local.clone(), m);
assert!(ready(mismatched.run(&qft, 0, &Parity::BitExact, &signer, "local/test")).expect("resolves").is_err());
let mut both = Fabric::new();
both.add(local.clone(), CpuReference).add(member.clone(), m);
let run = ready(both.run(&qft, 0, &Parity::BitExact, &signer, "local/test")).expect("resolves").expect("runs");
assert_eq!(run.state, state);
report(&format!("metal tensor-ops receipt: key={} ok", m.key()));
}
}
#[test]
fn c8_repeated_runs_agree() {
for m in members() {
let c = random_circuit(16, 60, 0xabc, Gateset::V2);
let first = m.simulate(&c).unwrap();
for _ in 0..5 {
assert!(m.simulate(&c).unwrap() == first, "{}", m.key());
}
report(&format!("metal tensor-ops repeat: key={} ok", m.key()));
}
}