use fuel_ir::DType;
use fuel_ir::dispatch::OpKind;
use crate::fkc::verify::bit_stability::{HostTensor, KernelInvoker, VerifyError};
use crate::kernel::{BindingEntry, OpParams};
#[derive(Debug, Clone, Copy)]
#[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."
)]
enum Scalar {
F(f64),
I(i128),
}
#[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(super) fn f8e4m3_decode(bits: u8) -> f64 {
let sign = if bits & 0x80 != 0 { -1.0 } else { 1.0 };
let exp = ((bits >> 3) & 0x0F) as i32;
let mant = (bits & 0x07) as f64;
if (bits & 0x7F) == 0x7F {
return f64::NAN;
}
if exp == 0 {
sign * (mant / 8.0) * 2f64.powi(1 - 7)
} else {
sign * (1.0 + mant / 8.0) * 2f64.powi(exp - 7)
}
}
#[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."
)]
fn f8e4m3_encode(v: f64) -> u8 {
if v.is_nan() {
return 0x7F;
}
const MAX_FINITE: f64 = 448.0;
if v.abs() >= MAX_FINITE {
return if v < 0.0 { 0xFE } else { 0x7E };
}
let mut best: Option<(f64, u8)> = None;
for bits in 0u16..256 {
let b = bits as u8;
if (b & 0x7F) == 0x7F {
continue; }
let cand = f8e4m3_decode(b);
let d = (cand - v).abs();
best = match best {
None => Some((d, b)),
Some((bd, bb)) => {
if d < bd {
Some((d, b))
} else if d == bd {
if (b & 1) == 0 && (bb & 1) != 0 {
Some((d, b))
} else {
Some((bd, bb))
}
} else {
Some((bd, bb))
}
}
};
}
best.map(|(_, b)| b).unwrap_or(0)
}
#[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."
)]
fn width_of(dt: DType) -> Option<usize> {
Some(match dt {
DType::F8E4M3 => 1,
DType::Bool | DType::U8 | DType::I8 => 1,
DType::F16 | DType::BF16 | DType::I16 => 2,
DType::F32 | DType::U32 | DType::I32 => 4,
DType::F64 | DType::I64 => 8,
_ => return None,
})
}
#[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."
)]
fn decode(dt: DType, b: &[u8]) -> Option<Scalar> {
Some(match dt {
DType::F8E4M3 => Scalar::F(f8e4m3_decode(b[0])),
DType::Bool | DType::U8 => Scalar::I(i128::from(b[0])),
DType::I8 => Scalar::I(i128::from(b[0] as i8)),
DType::I16 => Scalar::I(i128::from(i16::from_le_bytes([b[0], b[1]]))),
DType::U32 => Scalar::I(i128::from(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))),
DType::I32 => Scalar::I(i128::from(i32::from_le_bytes([b[0], b[1], b[2], b[3]]))),
DType::I64 => {
let mut a = [0u8; 8];
a.copy_from_slice(b);
Scalar::I(i128::from(i64::from_le_bytes(a)))
}
DType::F16 => Scalar::F(f64::from(
half::f16::from_bits(u16::from_le_bytes([b[0], b[1]])).to_f32(),
)),
DType::BF16 => Scalar::F(f64::from(
half::bf16::from_bits(u16::from_le_bytes([b[0], b[1]])).to_f32(),
)),
DType::F32 => Scalar::F(f64::from(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))),
DType::F64 => {
let mut a = [0u8; 8];
a.copy_from_slice(b);
Scalar::F(f64::from_le_bytes(a))
}
_ => return None,
})
}
#[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."
)]
fn encode(dt: DType, s: Scalar) -> Option<Vec<u8>> {
let as_f = |s: Scalar| match s {
Scalar::F(v) => v,
Scalar::I(v) => v as f64,
};
let sat = |s: Scalar, lo: i128, hi: i128| -> i128 {
match s {
Scalar::I(v) => v,
Scalar::F(v) => {
if v.is_nan() {
0
} else {
(v.trunc() as i128).clamp(lo, hi)
}
}
}
};
let from_int = matches!(s, Scalar::I(_));
let raw = match s {
Scalar::I(v) => v,
Scalar::F(v) => {
if v.is_nan() {
0
} else {
v.trunc() as i128
}
}
};
Some(match dt {
DType::Bool => vec![u8::from(match s {
Scalar::I(v) => v != 0,
Scalar::F(v) => v != 0.0,
})],
DType::U8 => {
let v = if from_int {
raw as u8
} else {
sat(s, 0, i128::from(u8::MAX)) as u8
};
vec![v]
}
DType::I8 => {
let v = if from_int {
raw as i8
} else {
sat(s, i128::from(i8::MIN), i128::from(i8::MAX)) as i8
};
vec![v as u8]
}
DType::I16 => {
let v = if from_int {
raw as i16
} else {
sat(s, i128::from(i16::MIN), i128::from(i16::MAX)) as i16
};
v.to_le_bytes().to_vec()
}
DType::U32 => {
let v = if from_int {
raw as u32
} else {
sat(s, 0, i128::from(u32::MAX)) as u32
};
v.to_le_bytes().to_vec()
}
DType::I32 => {
let v = if from_int {
raw as i32
} else {
sat(s, i128::from(i32::MIN), i128::from(i32::MAX)) as i32
};
v.to_le_bytes().to_vec()
}
DType::I64 => {
let v = if from_int {
raw as i64
} else {
sat(s, i128::from(i64::MIN), i128::from(i64::MAX)) as i64
};
v.to_le_bytes().to_vec()
}
DType::F8E4M3 => vec![f8e4m3_encode(as_f(s))],
DType::F16 => half::f16::from_f64(as_f(s)).to_le_bytes().to_vec(),
DType::BF16 => half::bf16::from_f64(as_f(s)).to_le_bytes().to_vec(),
DType::F32 => (as_f(s) as f32).to_le_bytes().to_vec(),
DType::F64 => as_f(s).to_le_bytes().to_vec(),
_ => return None,
})
}
#[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(crate) struct ExactRefInvoker {
pub(crate) op: OpKind,
pub(crate) out_dtype: DType,
pub(crate) out_shape: Vec<usize>,
pub(crate) params: OpParams,
#[cfg(test)]
pub(crate) poison: bool,
}
#[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(crate) fn has_exact_reference(op: OpKind, dtypes: &[DType]) -> bool {
if !dtypes.iter().all(|d| width_of(*d).is_some()) {
return false;
}
matches!(
op,
OpKind::Gather
| OpKind::IndexSelect
| OpKind::Cast
| OpKind::AddElementwise
| OpKind::SubElementwise
| OpKind::MulElementwise
| OpKind::DivElementwise
| OpKind::MaximumElementwise
| OpKind::MinimumElementwise
)
}
impl KernelInvoker for ExactRefInvoker {
fn invoke(
&self,
_entry: &BindingEntry,
inputs: &[HostTensor],
) -> Result<HostTensor, VerifyError> {
let n: usize = self.out_shape.iter().product();
let ow = width_of(self.out_dtype)
.ok_or_else(|| VerifyError::Backend(format!("no width for {:?}", self.out_dtype)))?;
let mut out = Vec::with_capacity(n * ow);
let elem = |t: &HostTensor, i: usize| -> Result<Scalar, VerifyError> {
let w = width_of(t.dtype)
.ok_or_else(|| VerifyError::Backend(format!("no width for {:?}", t.dtype)))?;
let b = t
.bytes
.get(i * w..(i + 1) * w)
.ok_or_else(|| VerifyError::Backend(format!("input short at elem {i}")))?;
decode(t.dtype, b)
.ok_or_else(|| VerifyError::Backend(format!("cannot decode {:?}", t.dtype)))
};
if matches!(self.op, OpKind::Gather | OpKind::IndexSelect) {
let src = inputs
.first()
.ok_or_else(|| VerifyError::Backend("needs a source input".into()))?;
let idx_t = inputs
.get(1)
.ok_or_else(|| VerifyError::Backend("needs a U32 index input".into()))?;
if idx_t.dtype != DType::U32 {
return Err(VerifyError::Backend(format!(
"index operand is {:?}, expected U32",
idx_t.dtype
)));
}
let w = width_of(src.dtype)
.ok_or_else(|| VerifyError::Backend(format!("no width for {:?}", src.dtype)))?;
let idx = |k: usize| -> Result<usize, VerifyError> {
let b = idx_t
.bytes
.get(k * 4..(k + 1) * 4)
.ok_or_else(|| VerifyError::Backend(format!("index short at {k}")))?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize)
};
let copy_from = |src_elem: usize, out: &mut Vec<u8>| -> Result<(), VerifyError> {
let b = src
.bytes
.get(src_elem * w..(src_elem + 1) * w)
.ok_or_else(|| {
VerifyError::Backend(format!("source short at elem {src_elem}"))
})?;
out.extend_from_slice(b);
Ok(())
};
match (&self.params, self.op) {
(
OpParams::IndexSelect {
outer_count,
source_dim_size,
n_indices,
inner_count,
},
OpKind::IndexSelect,
) => {
for o in 0..*outer_count {
for k in 0..*n_indices {
let sel = idx(k)?;
for j in 0..*inner_count {
let src_elem =
o * source_dim_size * inner_count + sel * inner_count + j;
copy_from(src_elem, &mut out)?;
}
}
}
}
(
OpParams::Gather {
source_shape,
output_shape,
dim,
},
OpKind::Gather,
) => {
let total: usize = output_shape.iter().product();
for flat in 0..total {
let mut coords = vec![0usize; output_shape.len()];
let mut rem = flat;
for d in (0..output_shape.len()).rev() {
coords[d] = rem % output_shape[d];
rem /= output_shape[d];
}
coords[*dim] = idx(flat)?;
let mut src_elem = 0usize;
for d in 0..source_shape.len() {
src_elem = src_elem * source_shape[d] + coords[d];
}
copy_from(src_elem, &mut out)?;
}
}
(params, op) => {
return Err(VerifyError::Backend(format!(
"{op:?} with {params:?} is not the params shape this reference implements"
)));
}
}
#[cfg(test)]
if self.poison
&& let Some(b) = out.first_mut()
{
*b ^= 0x01;
}
return Ok(HostTensor {
dtype: self.out_dtype,
shape: self.out_shape.clone(),
bytes: out,
});
}
for i in 0..n {
let s = match self.op {
OpKind::Cast => {
let a = inputs
.first()
.ok_or_else(|| VerifyError::Backend("cast needs 1 input".into()))?;
elem(a, i)?
}
OpKind::AddElementwise
| OpKind::SubElementwise
| OpKind::MulElementwise
| OpKind::DivElementwise
| OpKind::MaximumElementwise
| OpKind::MinimumElementwise => {
let (a, b) = (
inputs.first().ok_or_else(|| {
VerifyError::Backend("binary op needs 2 inputs".into())
})?,
inputs.get(1).ok_or_else(|| {
VerifyError::Backend("binary op needs 2 inputs".into())
})?,
);
let (x, y) = (
match elem(a, i)? {
Scalar::F(v) => v,
Scalar::I(v) => v as f64,
},
match elem(b, i)? {
Scalar::F(v) => v,
Scalar::I(v) => v as f64,
},
);
Scalar::F(match self.op {
OpKind::AddElementwise => x + y,
OpKind::SubElementwise => x - y,
OpKind::MulElementwise => x * y,
OpKind::DivElementwise => x / y,
OpKind::MaximumElementwise => {
if x.is_nan() || y.is_nan() {
f64::NAN
} else if x >= y {
x
} else {
y
}
}
_ => {
if x.is_nan() || y.is_nan() {
f64::NAN
} else if x <= y {
x
} else {
y
}
}
})
}
other => {
return Err(VerifyError::Backend(format!(
"no exact reference for {other:?}"
)));
}
};
out.extend_from_slice(&encode(self.out_dtype, s).ok_or_else(|| {
VerifyError::Backend(format!("cannot encode {:?}", self.out_dtype))
})?);
}
#[cfg(test)]
if self.poison {
if let Some(b) = out.first_mut() {
*b ^= 0x01;
}
}
Ok(HostTensor {
dtype: self.out_dtype,
shape: self.out_shape.clone(),
bytes: out,
})
}
}