use crate::kernel::BindingEntry;
use fuel_ir::DType;
#[derive(Debug, Clone, PartialEq)]
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
pub struct HostTensor {
pub dtype: DType,
pub shape: Vec<usize>,
pub bytes: Vec<u8>,
}
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
pub type ProbeInputs = Vec<HostTensor>;
#[derive(Debug)]
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
pub enum VerifyError {
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
Invoke(String),
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
NoReference,
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
Backend(String),
}
#[derive(Debug, PartialEq)]
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
pub enum VerifyOutcome {
Pass,
Fail { detail: String },
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
NoReference,
}
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
pub trait KernelInvoker {
fn invoke(
&self,
entry: &BindingEntry,
inputs: &[HostTensor],
) -> Result<HostTensor, VerifyError>;
}
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
pub fn verify_bit_stability(
inv: &dyn KernelInvoker,
entry: &BindingEntry,
probes: &[ProbeInputs],
iters: usize,
) -> Result<VerifyOutcome, VerifyError> {
for (probe_idx, probe) in probes.iter().enumerate() {
let first = inv.invoke(entry, probe)?;
for call_idx in 1..iters {
let next = inv.invoke(entry, probe)?;
if next.bytes != first.bytes {
return Ok(VerifyOutcome::Fail {
detail: format!(
"probe {probe_idx} diverged at call {call_idx}: bytes differ from call 0"
),
});
}
}
}
Ok(VerifyOutcome::Pass)
}
#[allow(
dead_code,
reason = "GAP-236 (unpublished verify API): fkc::verify's modules are private, so nothing outside this crate can reach it. Does NOT retire itself -- the expiry lives in GAP-236 and in Unpopped's handback precondition guard, which fires on their side when the API is named."
)]
pub fn fill_deterministic(len: usize, mut seed: u64) -> Vec<f32> {
let mut v = Vec::with_capacity(len);
for _ in 0..len {
seed ^= seed >> 12;
seed ^= seed << 25;
seed ^= seed >> 27;
let r = seed.wrapping_mul(0x2545F4914F6CDD1D);
v.push(((r >> 40) as f32 / (1u64 << 24) as f32) - 0.5);
}
v
}
#[cfg(test)]
mod fake_tests {
use super::*;
use crate::fkc::verify::ulp::{Bound, verify_precision_bound};
use fuel_ir::DType;
use std::sync::atomic::{AtomicU8, Ordering};
struct ConstInvoker(Vec<u8>);
impl KernelInvoker for ConstInvoker {
fn invoke(&self, _e: &BindingEntry, _i: &[HostTensor]) -> Result<HostTensor, VerifyError> {
Ok(HostTensor {
dtype: DType::F32,
shape: vec![1],
bytes: self.0.clone(),
})
}
}
struct FlakyInvoker(AtomicU8);
impl KernelInvoker for FlakyInvoker {
fn invoke(&self, _e: &BindingEntry, _i: &[HostTensor]) -> Result<HostTensor, VerifyError> {
let n = self.0.fetch_add(1, Ordering::Relaxed);
Ok(HostTensor {
dtype: DType::F32,
shape: vec![1],
bytes: vec![n],
})
}
}
fn probe() -> ProbeInputs {
vec![HostTensor {
dtype: DType::F32,
shape: vec![1],
bytes: vec![0, 0, 0, 0],
}]
}
fn dummy_entry() -> BindingEntry {
fn k(
_inputs: &[std::sync::Arc<std::sync::RwLock<fuel_memory::Storage>>],
_outputs: &mut [std::sync::Arc<std::sync::RwLock<fuel_memory::Storage>>],
_layouts: &[fuel_ir::Layout],
_params: &crate::kernel::OpParams,
) -> fuel_ir::Result<()> {
Ok(())
}
BindingEntry {
kernel: k,
caps: crate::kernel::KernelCaps::empty(),
precision: crate::fused::PrecisionGuarantee::UNAUDITED,
cost: crate::kernel::unknown_cost,
kernel_source: "",
is_generic: false,
kernel_revision_hash: 0,
cost_expr: None,
}
}
#[test]
fn verify_bit_stability_passes_for_a_deterministic_invoker_and_fails_for_a_flaky_one() {
let e = dummy_entry();
assert!(matches!(
verify_bit_stability(&ConstInvoker(vec![1, 2, 3, 4]), &e, &[probe()], 16).unwrap(),
VerifyOutcome::Pass
));
match verify_bit_stability(&FlakyInvoker(AtomicU8::new(0)), &e, &[probe()], 16).unwrap() {
VerifyOutcome::Fail { detail } => {
assert!(detail.contains("diverged"), "detail: {detail}")
}
other => panic!("expected Fail, got {other:?}"),
}
}
#[test]
fn verify_precision_bound_flags_a_candidate_exceeding_max_absolute() {
let e = dummy_entry();
let reference = ConstInvoker(1.0f32.to_le_bytes().to_vec());
let candidate = ConstInvoker(1.5f32.to_le_bytes().to_vec());
assert!(matches!(
verify_precision_bound(
&candidate,
&reference,
&e,
&[probe()],
Bound::MaxAbsolute(0.25)
)
.unwrap(),
VerifyOutcome::Fail { .. }
));
assert!(matches!(
verify_precision_bound(
&candidate,
&reference,
&e,
&[probe()],
Bound::MaxAbsolute(1.0)
)
.unwrap(),
VerifyOutcome::Pass
));
}
}