use crate::ir::{ElemType, TensorWasmKernelBlueprint, TensorWasmOp};
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct OracleConfig {
pub disabled: bool,
}
pub struct DifferentialOracle {
cfg: OracleConfig,
}
impl DifferentialOracle {
pub fn new() -> Self {
Self {
cfg: OracleConfig::default(),
}
}
pub fn with_config(cfg: OracleConfig) -> Self {
Self { cfg }
}
pub fn compare(&self, bp: &TensorWasmKernelBlueprint, inputs: &[u8]) -> OracleVerdict {
if self.cfg.disabled {
return OracleVerdict::Skipped("oracle disabled by config");
}
let cpu_output = reference_eval(bp, inputs).ok();
if cuda_runtime_available() {
match cpu_output {
Some(out) => OracleVerdict::HostOnlyOk {
cpu_output_len: out.len(),
cpu_output_first_bytes: head_bytes(&out),
},
None => {
OracleVerdict::Skipped("reference-eval-unavailable; falling back to no-cuda")
}
}
} else {
match cpu_output {
Some(out) => OracleVerdict::HostOnlyOk {
cpu_output_len: out.len(),
cpu_output_first_bytes: head_bytes(&out),
},
None => OracleVerdict::Skipped("no-cuda; v0.4 wires this against the S22 runner"),
}
}
}
}
impl Default for DifferentialOracle {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OracleVerdict {
Match {
output_len: usize,
},
HostOnlyOk {
cpu_output_len: usize,
cpu_output_first_bytes: [u8; 16],
},
Divergence(OracleDivergence),
Skipped(&'static str),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OracleDivergence {
pub blueprint_fingerprint: u64,
pub cpu_output_len: usize,
pub gpu_output_len: usize,
pub first_diff_offset: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlueprintKind {
VectorAdd,
VectorMul,
VectorFma,
Matmul,
Conv2d,
Other,
}
impl BlueprintKind {
pub fn classify(bp: &TensorWasmKernelBlueprint) -> Self {
let mut has_matmul = false;
let mut has_fma = false;
let mut has_add = false;
let mut has_mul = false;
let mut has_barrier = false;
for op in &bp.ops {
match op {
TensorWasmOp::MatMul { .. } => has_matmul = true,
TensorWasmOp::VecFma { .. } => has_fma = true,
TensorWasmOp::VecAdd { .. } => has_add = true,
TensorWasmOp::VecMul { .. } => has_mul = true,
TensorWasmOp::Barrier => has_barrier = true,
_ => {}
}
}
if has_matmul {
BlueprintKind::Matmul
} else if has_fma && has_barrier {
BlueprintKind::Conv2d
} else if has_fma {
BlueprintKind::VectorFma
} else if has_add {
BlueprintKind::VectorAdd
} else if has_mul {
BlueprintKind::VectorMul
} else {
BlueprintKind::Other
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Dtype {
F32,
F16,
Int,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Tolerance {
pub ulps: u32,
pub abs: f32,
pub rel: f32,
}
impl Tolerance {
pub const STRICT: Tolerance = Tolerance {
ulps: 0,
abs: 0.0,
rel: 0.0,
};
pub fn approx_eq_f32(self, expected: f32, actual: f32) -> bool {
if expected.is_nan() && actual.is_nan() {
return true;
}
if !expected.is_finite() || !actual.is_finite() {
return expected == actual;
}
let diff = (expected - actual).abs();
if diff <= self.abs {
return true;
}
if diff <= self.rel * expected.abs() {
return true;
}
let to_biased = |x: f32| -> i32 {
let bits = x.to_bits() as i32;
if bits < 0 {
i32::MIN.wrapping_sub(bits)
} else {
bits
}
};
let ulp_dist = (to_biased(expected) - to_biased(actual)).unsigned_abs();
ulp_dist <= self.ulps
}
}
#[derive(Debug, Clone)]
pub struct ToleranceTable {
pub vector_add_f32: Tolerance,
pub vector_mul_f32: Tolerance,
pub vector_fma_f32: Tolerance,
pub matmul_f32: Tolerance,
pub conv2d_f32: Tolerance,
pub f16_ulp_multiplier: u32,
}
impl ToleranceTable {
pub const fn strict() -> Self {
Self {
vector_add_f32: Tolerance::STRICT,
vector_mul_f32: Tolerance::STRICT,
vector_fma_f32: Tolerance::STRICT,
matmul_f32: Tolerance::STRICT,
conv2d_f32: Tolerance::STRICT,
f16_ulp_multiplier: 4,
}
}
pub const fn default_table() -> Self {
Self {
vector_add_f32: Tolerance {
ulps: 1,
abs: 0.0,
rel: 0.0,
},
vector_mul_f32: Tolerance {
ulps: 1,
abs: 0.0,
rel: 0.0,
},
vector_fma_f32: Tolerance {
ulps: 1,
abs: 0.0,
rel: 0.0,
},
matmul_f32: Tolerance {
ulps: 2,
abs: 1e-6,
rel: 1e-6,
},
conv2d_f32: Tolerance {
ulps: 2,
abs: 1e-6,
rel: 1e-6,
},
f16_ulp_multiplier: 4,
}
}
pub fn for_blueprint(&self, kind: BlueprintKind, dtype: Dtype) -> Tolerance {
if matches!(dtype, Dtype::Int) {
return Tolerance::STRICT;
}
let base = match kind {
BlueprintKind::VectorAdd => self.vector_add_f32,
BlueprintKind::VectorMul => self.vector_mul_f32,
BlueprintKind::VectorFma => self.vector_fma_f32,
BlueprintKind::Matmul => self.matmul_f32,
BlueprintKind::Conv2d => self.conv2d_f32,
BlueprintKind::Other => self.vector_add_f32,
};
if matches!(dtype, Dtype::F16) {
Tolerance {
ulps: base.ulps.saturating_mul(self.f16_ulp_multiplier),
abs: base.abs * self.f16_ulp_multiplier as f32,
rel: base.rel * self.f16_ulp_multiplier as f32,
}
} else {
base
}
}
}
impl Default for ToleranceTable {
fn default() -> Self {
Self::default_table()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReferenceEvalError {
UnsupportedOp,
InputTooShort,
StackUnderflow,
}
pub fn reference_eval(
bp: &TensorWasmKernelBlueprint,
inputs: &[u8],
) -> Result<Vec<u8>, ReferenceEvalError> {
let mut stack: Vec<f32> = Vec::with_capacity(bp.ops.len());
let mut output: Vec<u8> = Vec::new();
let mut in_cursor: usize = 0;
for op in &bp.ops {
match op {
TensorWasmOp::LoadUnified { elem, lanes } => {
if *elem != ElemType::F32 {
return Err(ReferenceEvalError::UnsupportedOp);
}
let lanes = *lanes as usize;
for _ in 0..lanes {
let end = in_cursor
.checked_add(4)
.ok_or(ReferenceEvalError::InputTooShort)?;
if end > inputs.len() {
return Err(ReferenceEvalError::InputTooShort);
}
let mut buf = [0u8; 4];
buf.copy_from_slice(&inputs[in_cursor..end]);
stack.push(f32::from_le_bytes(buf));
in_cursor = end;
}
}
TensorWasmOp::StoreUnified { elem, lanes } => {
if *elem != ElemType::F32 {
return Err(ReferenceEvalError::UnsupportedOp);
}
let lanes = *lanes as usize;
let mut popped: Vec<f32> = Vec::with_capacity(lanes);
for _ in 0..lanes {
popped.push(stack.pop().ok_or(ReferenceEvalError::StackUnderflow)?);
}
for v in popped.into_iter().rev() {
output.extend_from_slice(&v.to_le_bytes());
}
}
TensorWasmOp::VecAdd { elem, lanes } => {
if *elem != ElemType::F32 {
return Err(ReferenceEvalError::UnsupportedOp);
}
let lanes = *lanes as usize;
let (a, b) = pop_two_lane_vectors(&mut stack, lanes)?;
for i in 0..lanes {
stack.push(a[i] + b[i]);
}
}
TensorWasmOp::VecMul { elem, lanes } => {
if *elem != ElemType::F32 {
return Err(ReferenceEvalError::UnsupportedOp);
}
let lanes = *lanes as usize;
let (a, b) = pop_two_lane_vectors(&mut stack, lanes)?;
for i in 0..lanes {
stack.push(a[i] * b[i]);
}
}
TensorWasmOp::VecFma { elem, lanes } => {
if *elem != ElemType::F32 {
return Err(ReferenceEvalError::UnsupportedOp);
}
let lanes = *lanes as usize;
let c = pop_lane_vector(&mut stack, lanes)?;
let b = pop_lane_vector(&mut stack, lanes)?;
let a = pop_lane_vector(&mut stack, lanes)?;
for i in 0..lanes {
stack.push(a[i].mul_add(b[i], c[i]));
}
}
TensorWasmOp::Barrier => {
}
TensorWasmOp::MatMul { .. } => {
return Err(ReferenceEvalError::UnsupportedOp);
}
}
}
Ok(output)
}
fn pop_lane_vector(stack: &mut Vec<f32>, lanes: usize) -> Result<Vec<f32>, ReferenceEvalError> {
if stack.len() < lanes {
return Err(ReferenceEvalError::StackUnderflow);
}
Ok(stack.split_off(stack.len() - lanes))
}
fn pop_two_lane_vectors(
stack: &mut Vec<f32>,
lanes: usize,
) -> Result<(Vec<f32>, Vec<f32>), ReferenceEvalError> {
let b = pop_lane_vector(stack, lanes)?;
let a = pop_lane_vector(stack, lanes)?;
Ok((a, b))
}
pub fn matmul_reference(
m: usize,
k: usize,
n: usize,
a: &[f32],
b: &[f32],
) -> Result<Vec<f32>, ReferenceEvalError> {
if a.len() != m * k || b.len() != k * n {
return Err(ReferenceEvalError::InputTooShort);
}
let mut out = vec![0.0f32; m * n];
for i in 0..m {
for j in 0..n {
let mut acc = 0.0f32;
for kk in 0..k {
acc = a[i * k + kk].mul_add(b[kk * n + j], acc);
}
out[i * n + j] = acc;
}
}
Ok(out)
}
pub fn conv2d_reference(
h: usize,
w: usize,
kh: usize,
kw: usize,
input: &[f32],
kernel: &[f32],
) -> Result<Vec<f32>, ReferenceEvalError> {
if input.len() != h * w || kernel.len() != kh * kw {
return Err(ReferenceEvalError::InputTooShort);
}
if h < kh || w < kw {
return Err(ReferenceEvalError::InputTooShort);
}
let oh = h - kh + 1;
let ow = w - kw + 1;
let mut out = vec![0.0f32; oh * ow];
for i in 0..oh {
for j in 0..ow {
let mut acc = 0.0f32;
for ki in 0..kh {
for kj in 0..kw {
let v = input[(i + ki) * w + (j + kj)];
let k = kernel[ki * kw + kj];
acc = v.mul_add(k, acc);
}
}
out[i * ow + j] = acc;
}
}
Ok(out)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WmmaStructuralError {
MmaCountMismatch {
expected: usize,
actual: usize,
},
MissingOp(&'static str),
FragmentShapeMismatch {
which: &'static str,
expected: usize,
actual: usize,
},
AccumulatorNotChained,
RegisterDeclInconsistent(&'static str),
}
const WMMA_FRAG_REGS: usize = 8;
fn fragment_operands(text: &str, marker: &str) -> Option<Vec<String>> {
let start = text.find(marker)?;
let rest = &text[start..];
let open = rest.find('{')?;
let close = rest[open..].find('}')? + open;
let inner = &rest[open + 1..close];
Some(
inner
.split(',')
.map(|t| t.trim().to_string())
.filter(|t| !t.is_empty())
.collect(),
)
}
fn reg_decl_count(text: &str, prefix: &str) -> Option<u32> {
for line in text.lines() {
let line = line.trim();
if line.starts_with(".reg") && line.contains(&format!("%{prefix}<")) {
return line
.split('<')
.nth(1)
.and_then(|s| s.split('>').next())
.and_then(|s| s.trim().parse().ok());
}
}
None
}
fn max_reg_index(tokens: &[String], prefix: &str) -> Option<u32> {
let needle = format!("%{prefix}");
let mut max = None;
for t in tokens {
let n: u32 = t.strip_prefix(&needle)?.parse().ok()?;
max = Some(max.map_or(n, |m: u32| m.max(n)));
}
max
}
pub fn check_wmma_structure(ptx: &str, m: u32, n: u32, k: u32) -> Vec<WmmaStructuralError> {
let mut errs = Vec::new();
let tiles_m = m.div_ceil(16) as usize;
let tiles_n = n.div_ceil(16) as usize;
let tiles_k = k.div_ceil(16) as usize;
let expected_mma = tiles_m * tiles_n * tiles_k;
let actual_mma = ptx.matches("wmma.mma.sync.aligned").count();
if actual_mma != expected_mma {
errs.push(WmmaStructuralError::MmaCountMismatch {
expected: expected_mma,
actual: actual_mma,
});
}
for (needle, label) in [
("wmma.load.a.sync.aligned.row.m16n16k16", "wmma.load.a"),
("wmma.load.b.sync.aligned.col.m16n16k16", "wmma.load.b"),
("wmma.load.c.sync.aligned.row.m16n16k16", "wmma.load.c"),
("wmma.store.d.sync.aligned.row.m16n16k16", "wmma.store.d"),
] {
if !ptx.contains(needle) {
errs.push(WmmaStructuralError::MissingOp(label));
}
}
let frag_checks: [(&str, &str); 3] = [
("wmma.load.a.sync.aligned.row.m16n16k16", "a"),
("wmma.load.b.sync.aligned.col.m16n16k16", "b"),
("wmma.load.c.sync.aligned.row.m16n16k16", "c"),
];
for (marker, which) in frag_checks {
if let Some(ops) = fragment_operands(ptx, marker) {
if ops.len() != WMMA_FRAG_REGS {
errs.push(WmmaStructuralError::FragmentShapeMismatch {
which,
expected: WMMA_FRAG_REGS,
actual: ops.len(),
});
}
}
}
if let Some(mma_ops) = fragment_operands(ptx, "wmma.mma.sync.aligned") {
let _ = mma_ops; if let Some(line) = ptx.lines().find(|l| l.contains("wmma.mma.sync.aligned")) {
let groups: Vec<Vec<String>> = collect_brace_groups(line);
if groups.len() == 4 {
let d_frag = &groups[0];
let c_frag = &groups[3];
if d_frag != c_frag {
errs.push(WmmaStructuralError::AccumulatorNotChained);
}
if d_frag.len() != WMMA_FRAG_REGS {
errs.push(WmmaStructuralError::FragmentShapeMismatch {
which: "d",
expected: WMMA_FRAG_REGS,
actual: d_frag.len(),
});
}
} else {
errs.push(WmmaStructuralError::AccumulatorNotChained);
}
}
}
let collect_all = |marker: &str, prefix: &str| -> Option<u32> {
fragment_operands(ptx, marker).and_then(|ops| max_reg_index(&ops, prefix))
};
let rb_max = [
collect_all("wmma.load.a.sync.aligned.row.m16n16k16", "rb"),
collect_all("wmma.load.b.sync.aligned.col.m16n16k16", "rb"),
]
.into_iter()
.flatten()
.max();
if let Some(used) = rb_max {
match reg_decl_count(ptx, "rb") {
Some(decl) if decl > used => {}
_ => errs.push(WmmaStructuralError::RegisterDeclInconsistent("rb")),
}
}
if let Some(used) = collect_all("wmma.load.c.sync.aligned.row.m16n16k16", "f") {
match reg_decl_count(ptx, "f") {
Some(decl) if decl > used => {}
_ => errs.push(WmmaStructuralError::RegisterDeclInconsistent("f")),
}
}
errs
}
fn collect_brace_groups(line: &str) -> Vec<Vec<String>> {
let mut groups = Vec::new();
let mut rest = line;
while let Some(open) = rest.find('{') {
let after = &rest[open + 1..];
let Some(close_rel) = after.find('}') else {
break;
};
let inner = &after[..close_rel];
let toks: Vec<String> = inner
.split(',')
.map(|t| t.trim().to_string())
.filter(|t| !t.is_empty())
.collect();
groups.push(toks);
rest = &after[close_rel + 1..];
}
groups
}
fn cuda_runtime_available() -> bool {
#[cfg(feature = "cuda-oxide-backend")]
{
false
}
#[cfg(not(feature = "cuda-oxide-backend"))]
{
false
}
}
fn head_bytes(slice: &[u8]) -> [u8; 16] {
let mut out = [0u8; 16];
let n = slice.len().min(16);
out[..n].copy_from_slice(&slice[..n]);
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::{GridHint, TensorWasmKernelBlueprint, TensorWasmOp};
fn add_blueprint(lanes: u32) -> TensorWasmKernelBlueprint {
let elem = ElemType::F32;
TensorWasmKernelBlueprint::new("vector_add")
.push(TensorWasmOp::LoadUnified { elem, lanes })
.push(TensorWasmOp::LoadUnified { elem, lanes })
.push(TensorWasmOp::VecAdd { elem, lanes })
.push(TensorWasmOp::StoreUnified { elem, lanes })
.with_grid(GridHint {
total_threads: lanes,
preferred_block_size: lanes,
})
}
#[test]
fn reference_eval_vector_add_matches_native() {
let bp = add_blueprint(4);
let a = [1.0f32, 2.0, 3.0, 4.0];
let b = [10.0f32, 20.0, 30.0, 40.0];
let mut input = Vec::new();
for v in a.iter().chain(b.iter()) {
input.extend_from_slice(&v.to_le_bytes());
}
let out = reference_eval(&bp, &input).expect("eval");
assert_eq!(out.len(), 16);
let expected: Vec<f32> = a.iter().zip(b.iter()).map(|(x, y)| x + y).collect();
for (i, &e) in expected.iter().enumerate() {
let mut buf = [0u8; 4];
buf.copy_from_slice(&out[i * 4..(i + 1) * 4]);
assert_eq!(f32::from_le_bytes(buf), e);
}
}
#[test]
fn tolerance_strict_rejects_one_ulp() {
let t = Tolerance::STRICT;
let a = 1.0f32;
let b = f32::from_bits(a.to_bits() + 1);
assert!(!t.approx_eq_f32(a, b));
assert!(t.approx_eq_f32(a, a));
}
#[test]
fn tolerance_default_accepts_one_ulp() {
let t = ToleranceTable::default().for_blueprint(BlueprintKind::VectorAdd, Dtype::F32);
let a = 1.0f32;
let b = f32::from_bits(a.to_bits() + 1);
assert!(t.approx_eq_f32(a, b));
}
#[test]
fn classify_picks_matmul() {
let bp = TensorWasmKernelBlueprint::new("k").push(TensorWasmOp::MatMul {
m: 16,
n: 16,
k: 16,
});
assert_eq!(BlueprintKind::classify(&bp), BlueprintKind::Matmul);
}
#[test]
fn matmul_reference_smoke() {
let a = [1.0f32, 2.0, 3.0, 4.0];
let b = [1.0f32, 0.0, 0.0, 1.0];
let out = matmul_reference(2, 2, 2, &a, &b).expect("matmul");
assert_eq!(out, vec![1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn conv2d_reference_smoke() {
let input = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
let kernel = [1.0f32; 4];
let out = conv2d_reference(3, 3, 2, 2, &input, &kernel).expect("conv");
assert_eq!(out, vec![12.0, 16.0, 24.0, 28.0]);
}
#[test]
fn oracle_default_runs_cpu_path_when_input_present() {
let bp = add_blueprint(2);
let mut input = Vec::new();
for v in [1.0f32, 2.0, 10.0, 20.0] {
input.extend_from_slice(&v.to_le_bytes());
}
let verdict = DifferentialOracle::new().compare(&bp, &input);
match verdict {
OracleVerdict::HostOnlyOk { cpu_output_len, .. } => {
assert_eq!(cpu_output_len, 2 * 4);
}
other => panic!("expected HostOnlyOk, got {other:?}"),
}
}
#[test]
fn wmma_structure_accepts_opt_in_emission() {
use crate::ptx_emit::{emit_with, EmitConfig};
let bp = TensorWasmKernelBlueprint::new("matmul").push(TensorWasmOp::MatMul {
m: 16,
n: 16,
k: 16,
});
let cfg = EmitConfig {
enable_experimental_matmul: true,
..EmitConfig::default()
};
let ptx = emit_with(&bp, &cfg).expect("opt-in emit");
let errs = check_wmma_structure(&ptx.text, 16, 16, 16);
assert!(errs.is_empty(), "structural oracle flagged: {errs:?}");
}
#[test]
fn wmma_structure_rejects_unchained_accumulator() {
let ptx = "\
.reg .b32 %rb<16>;\n\
.reg .f32 %f<16>;\n\
wmma.load.a.sync.aligned.row.m16n16k16.global.f16 {%rb0, %rb1, %rb2, %rb3, %rb4, %rb5, %rb6, %rb7}, [%rd0], 16;\n\
wmma.load.b.sync.aligned.col.m16n16k16.global.f16 {%rb8, %rb9, %rb10, %rb11, %rb12, %rb13, %rb14, %rb15}, [%rd2], 16;\n\
wmma.load.c.sync.aligned.row.m16n16k16.global.f32 {%f0, %f1, %f2, %f3, %f4, %f5, %f6, %f7}, [%rd3], 16;\n\
wmma.mma.sync.aligned.row.col.m16n16k16.f32.f32 {%f0, %f1, %f2, %f3, %f4, %f5, %f6, %f7}, {%rb0, %rb1, %rb2, %rb3, %rb4, %rb5, %rb6, %rb7}, {%rb8, %rb9, %rb10, %rb11, %rb12, %rb13, %rb14, %rb15}, {%f8, %f9, %f10, %f11, %f12, %f13, %f14, %f15};\n\
wmma.store.d.sync.aligned.row.m16n16k16.global.f32 [%rd1], {%f0, %f1, %f2, %f3, %f4, %f5, %f6, %f7}, 16;\n";
let errs = check_wmma_structure(ptx, 16, 16, 16);
assert!(errs.contains(&WmmaStructuralError::AccumulatorNotChained));
}
#[test]
fn wmma_structure_rejects_missing_store() {
let ptx = "\
wmma.load.a.sync.aligned.row.m16n16k16.global.f16 {%rb0, %rb1, %rb2, %rb3, %rb4, %rb5, %rb6, %rb7}, [%rd0], 16;\n\
wmma.mma.sync.aligned.row.col.m16n16k16.f32.f32 {%f0}, {%rb0}, {%rb8}, {%f0};\n";
let errs = check_wmma_structure(ptx, 16, 16, 16);
assert!(errs.contains(&WmmaStructuralError::MissingOp("wmma.store.d")));
}
#[test]
fn oracle_default_falls_back_to_no_cuda_skip_for_unrunnable_blueprint() {
let bp = TensorWasmKernelBlueprint::new("oracle_fixture").push(TensorWasmOp::VecAdd {
elem: ElemType::F32,
lanes: 4,
});
let verdict = DifferentialOracle::new().compare(&bp, &[]);
match verdict {
OracleVerdict::Skipped(reason) => {
assert!(reason.contains("no-cuda"));
}
other => panic!("expected Skipped, got {other:?}"),
}
}
}