use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock, RwLock};
use fuel_ir::backend::{BackendCapabilities, SubstrateClass, TransferPath};
use fuel_ir::dispatch::OpKind;
use fuel_ir::probe::BackendId;
use fuel_ir::{DType, DeviceLocation, Error, Layout, Result};
#[cfg(feature = "cuda")]
use crate::kernel::KernelCaps;
use crate::kernel::{KernelBindingTable, MatmulM, OpParams};
use fuel_memory::{BackendStorage, Storage};
#[derive(Debug, Default)]
pub struct CapabilityRegistry {
backends: Vec<BackendCapabilities>,
}
impl CapabilityRegistry {
pub fn new() -> Self {
Self {
backends: Vec::new(),
}
}
pub fn register(&mut self, caps: BackendCapabilities) {
self.backends.push(caps);
}
pub fn backends(&self) -> &[BackendCapabilities] {
&self.backends
}
pub fn find_backends(&self, op: OpKind, dtype: DType) -> Vec<&BackendCapabilities> {
self.backends
.iter()
.filter(|caps| caps.op_dtype_support.contains(&(op, dtype)))
.collect()
}
pub fn find_backend_for(&self, op: OpKind, dtype: DType) -> Result<&BackendCapabilities> {
for caps in &self.backends {
if caps.op_dtype_support.contains(&(op, dtype)) {
return Ok(caps);
}
}
Err(Error::NoBackendForOp {
op,
dtypes: vec![dtype],
available_backends: self.backends.iter().map(|c| c.backend_id).collect(),
supported_combinations: self
.backends
.iter()
.flat_map(|c| {
c.op_dtype_support
.iter()
.map(|&(o, d)| (c.backend_id, o, vec![d]))
})
.collect(),
}
.bt())
}
pub fn build_transfer_matrix(&self) -> TransferMatrix {
let mut entries = HashMap::new();
for caps in &self.backends {
for (dst, path) in &caps.transfer_paths {
entries.insert((caps.device_location, *dst), *path);
}
}
TransferMatrix { entries }
}
}
#[derive(Debug, Default)]
pub struct TransferMatrix {
entries: HashMap<(DeviceLocation, DeviceLocation), TransferPath>,
}
impl TransferMatrix {
pub fn path(&self, src: DeviceLocation, dst: DeviceLocation) -> Option<TransferPath> {
if src == dst {
return Some(TransferPath::SameDevice);
}
self.entries.get(&(src, dst)).copied()
}
pub fn path_or_staging(&self, src: DeviceLocation, dst: DeviceLocation) -> TransferPath {
self.path(src, dst).unwrap_or(TransferPath::HostStaging)
}
pub fn entries(
&self,
) -> impl Iterator<Item = (&(DeviceLocation, DeviceLocation), &TransferPath)> {
self.entries.iter()
}
}
pub fn resolve_target_backend(
registry: &CapabilityRegistry,
op: OpKind,
dtype: DType,
) -> Result<BackendId> {
registry
.find_backend_for(op, dtype)
.map(|caps| caps.backend_id)
}
pub fn resolve_target_backend_residency_aware(
registry: &CapabilityRegistry,
op: OpKind,
dtype: DType,
input_locations: &[DeviceLocation],
) -> Result<BackendId> {
let candidates = registry.find_backends(op, dtype);
if candidates.is_empty() {
return registry.find_backend_for(op, dtype).map(|c| c.backend_id);
}
for caps in &candidates {
if input_locations.contains(&caps.device_location) {
return Ok(caps.backend_id);
}
}
Ok(candidates[0].backend_id)
}
pub fn cpu_input(s: &Storage) -> Result<&fuel_cpu_backend::CpuStorageBytes> {
match &s.inner {
BackendStorage::Cpu(c) => Ok(c),
#[allow(unreachable_patterns)]
_ => Err(Error::Msg("cpu kernel wrapper called with non-CPU input".to_string()).bt()),
}
}
pub fn cpu_output(s: &mut Storage) -> Result<&mut fuel_cpu_backend::CpuStorageBytes> {
match &mut s.inner {
BackendStorage::Cpu(c) => Ok(c),
#[allow(unreachable_patterns)]
_ => Err(Error::Msg("cpu kernel wrapper called with non-CPU output".to_string()).bt()),
}
}
pub fn read_storage(arc: &Arc<RwLock<Storage>>) -> Result<std::sync::RwLockReadGuard<'_, Storage>> {
arc.read()
.map_err(|_| Error::Msg("kernel wrapper: storage RwLock poisoned (read)".to_string()).bt())
}
pub fn write_storage(
arc: &Arc<RwLock<Storage>>,
) -> Result<std::sync::RwLockWriteGuard<'_, Storage>> {
arc.write()
.map_err(|_| Error::Msg("kernel wrapper: storage RwLock poisoned (write)".to_string()).bt())
}
macro_rules! cpu_binary_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 {
return Err(Error::Msg(format!(
"{} wrapper expects 2 inputs, got {}",
$op_name,
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 output, got {}",
$op_name,
outputs.len(),
))
.bt());
}
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let lhs_cpu = cpu_input(&lhs_guard)?;
let rhs_cpu = cpu_input(&rhs_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(lhs_cpu, rhs_cpu, out_cpu)
}
};
}
macro_rules! cpu_unary_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 input, got {}",
$op_name,
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 output, got {}",
$op_name,
outputs.len(),
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu)
}
};
}
cpu_binary_wrapper!(
add_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::add_f32,
"add_elementwise"
);
cpu_binary_wrapper!(
sub_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sub_f32,
"sub_elementwise"
);
cpu_binary_wrapper!(
mul_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::mul_f32,
"mul_elementwise"
);
cpu_binary_wrapper!(
div_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::div_f32,
"div_elementwise"
);
cpu_unary_wrapper!(
relu_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::relu_f32,
"relu_elementwise"
);
cpu_unary_wrapper!(
neg_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::neg_f32,
"neg_elementwise"
);
cpu_unary_wrapper!(
sqr_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sqr_f32,
"sqr_elementwise"
);
cpu_unary_wrapper!(
sqrt_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sqrt_f32,
"sqrt_elementwise"
);
cpu_unary_wrapper!(
recip_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::recip_f32,
"recip_elementwise"
);
cpu_unary_wrapper!(
abs_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::abs_f32,
"abs_elementwise"
);
cpu_unary_wrapper!(
tanh_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::tanh_f32,
"tanh_elementwise"
);
cpu_unary_wrapper!(
exp_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::exp_f32,
"exp_elementwise"
);
cpu_unary_wrapper!(
log_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_f32,
"log_elementwise"
);
cpu_unary_wrapper!(
sin_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sin_f32,
"sin_elementwise"
);
cpu_unary_wrapper!(
cos_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::cos_f32,
"cos_elementwise"
);
cpu_unary_wrapper!(
sigmoid_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sigmoid_f32,
"sigmoid_elementwise"
);
cpu_unary_wrapper!(
silu_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::silu_f32,
"silu_elementwise"
);
cpu_unary_wrapper!(
gelu_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gelu_f32,
"gelu_elementwise"
);
cpu_unary_wrapper!(
step_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::step_f32,
"step_elementwise"
);
cpu_binary_wrapper!(
add_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::add_f64,
"add_elementwise"
);
cpu_binary_wrapper!(
sub_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sub_f64,
"sub_elementwise"
);
cpu_binary_wrapper!(
mul_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::mul_f64,
"mul_elementwise"
);
cpu_binary_wrapper!(
div_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::div_f64,
"div_elementwise"
);
cpu_unary_wrapper!(
relu_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::relu_f64,
"relu_elementwise"
);
cpu_unary_wrapper!(
neg_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::neg_f64,
"neg_elementwise"
);
cpu_unary_wrapper!(
sqr_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sqr_f64,
"sqr_elementwise"
);
cpu_unary_wrapper!(
sqrt_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sqrt_f64,
"sqrt_elementwise"
);
cpu_unary_wrapper!(
recip_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::recip_f64,
"recip_elementwise"
);
cpu_unary_wrapper!(
abs_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::abs_f64,
"abs_elementwise"
);
cpu_unary_wrapper!(
tanh_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::tanh_f64,
"tanh_elementwise"
);
cpu_unary_wrapper!(
exp_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::exp_f64,
"exp_elementwise"
);
cpu_unary_wrapper!(
log_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_f64,
"log_elementwise"
);
cpu_unary_wrapper!(
sin_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sin_f64,
"sin_elementwise"
);
cpu_unary_wrapper!(
cos_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::cos_f64,
"cos_elementwise"
);
cpu_unary_wrapper!(
sigmoid_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sigmoid_f64,
"sigmoid_elementwise"
);
cpu_unary_wrapper!(
silu_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::silu_f64,
"silu_elementwise"
);
cpu_unary_wrapper!(
gelu_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gelu_f64,
"gelu_elementwise"
);
cpu_unary_wrapper!(
step_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::step_f64,
"step_elementwise"
);
cpu_binary_wrapper!(
maximum_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::maximum_f32,
"maximum_elementwise"
);
cpu_binary_wrapper!(
minimum_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::minimum_f32,
"minimum_elementwise"
);
cpu_binary_wrapper!(
maximum_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::maximum_f64,
"maximum_elementwise"
);
cpu_binary_wrapper!(
minimum_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::minimum_f64,
"minimum_elementwise"
);
cpu_binary_wrapper!(
add_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::add_bf16,
"add_elementwise"
);
cpu_binary_wrapper!(
sub_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sub_bf16,
"sub_elementwise"
);
cpu_binary_wrapper!(
mul_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::mul_bf16,
"mul_elementwise"
);
cpu_binary_wrapper!(
div_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::div_bf16,
"div_elementwise"
);
cpu_binary_wrapper!(
maximum_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::maximum_bf16,
"maximum_elementwise"
);
cpu_binary_wrapper!(
minimum_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::minimum_bf16,
"minimum_elementwise"
);
cpu_unary_wrapper!(
relu_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::relu_bf16,
"relu_elementwise"
);
cpu_unary_wrapper!(
neg_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::neg_bf16,
"neg_elementwise"
);
cpu_unary_wrapper!(
sqr_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sqr_bf16,
"sqr_elementwise"
);
cpu_unary_wrapper!(
sqrt_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sqrt_bf16,
"sqrt_elementwise"
);
cpu_unary_wrapper!(
recip_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::recip_bf16,
"recip_elementwise"
);
cpu_unary_wrapper!(
abs_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::abs_bf16,
"abs_elementwise"
);
cpu_unary_wrapper!(
tanh_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::tanh_bf16,
"tanh_elementwise"
);
cpu_unary_wrapper!(
exp_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::exp_bf16,
"exp_elementwise"
);
cpu_unary_wrapper!(
log_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_bf16,
"log_elementwise"
);
cpu_unary_wrapper!(
sin_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sin_bf16,
"sin_elementwise"
);
cpu_unary_wrapper!(
cos_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::cos_bf16,
"cos_elementwise"
);
cpu_unary_wrapper!(
sigmoid_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sigmoid_bf16,
"sigmoid_elementwise"
);
cpu_unary_wrapper!(
silu_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::silu_bf16,
"silu_elementwise"
);
cpu_unary_wrapper!(
gelu_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gelu_bf16,
"gelu_elementwise"
);
cpu_unary_wrapper!(
step_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::step_bf16,
"step_elementwise"
);
cpu_binary_wrapper!(
add_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::add_f16,
"add_elementwise"
);
cpu_binary_wrapper!(
sub_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sub_f16,
"sub_elementwise"
);
cpu_binary_wrapper!(
mul_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::mul_f16,
"mul_elementwise"
);
cpu_binary_wrapper!(
div_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::div_f16,
"div_elementwise"
);
cpu_binary_wrapper!(
maximum_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::maximum_f16,
"maximum_elementwise"
);
cpu_binary_wrapper!(
minimum_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::minimum_f16,
"minimum_elementwise"
);
cpu_unary_wrapper!(
relu_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::relu_f16,
"relu_elementwise"
);
cpu_unary_wrapper!(
neg_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::neg_f16,
"neg_elementwise"
);
cpu_unary_wrapper!(
sqr_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sqr_f16,
"sqr_elementwise"
);
cpu_unary_wrapper!(
sqrt_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sqrt_f16,
"sqrt_elementwise"
);
cpu_unary_wrapper!(
recip_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::recip_f16,
"recip_elementwise"
);
cpu_unary_wrapper!(
abs_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::abs_f16,
"abs_elementwise"
);
cpu_unary_wrapper!(
tanh_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::tanh_f16,
"tanh_elementwise"
);
cpu_unary_wrapper!(
exp_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::exp_f16,
"exp_elementwise"
);
cpu_unary_wrapper!(
log_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_f16,
"log_elementwise"
);
cpu_unary_wrapper!(
sin_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sin_f16,
"sin_elementwise"
);
cpu_unary_wrapper!(
cos_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::cos_f16,
"cos_elementwise"
);
cpu_unary_wrapper!(
sigmoid_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sigmoid_f16,
"sigmoid_elementwise"
);
cpu_unary_wrapper!(
silu_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::silu_f16,
"silu_elementwise"
);
cpu_unary_wrapper!(
gelu_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gelu_f16,
"gelu_elementwise"
);
cpu_unary_wrapper!(
step_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::step_f16,
"step_elementwise"
);
cpu_binary_wrapper!(
eq_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::eq_f32_u8,
"eq_elementwise"
);
cpu_binary_wrapper!(
eq_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::eq_f64_u8,
"eq_elementwise"
);
cpu_binary_wrapper!(
eq_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::eq_bf16_u8,
"eq_elementwise"
);
cpu_binary_wrapper!(
eq_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::eq_f16_u8,
"eq_elementwise"
);
cpu_binary_wrapper!(
ne_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ne_f32_u8,
"ne_elementwise"
);
cpu_binary_wrapper!(
ne_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ne_f64_u8,
"ne_elementwise"
);
cpu_binary_wrapper!(
ne_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ne_bf16_u8,
"ne_elementwise"
);
cpu_binary_wrapper!(
ne_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ne_f16_u8,
"ne_elementwise"
);
cpu_binary_wrapper!(
lt_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::lt_f32_u8,
"lt_elementwise"
);
cpu_binary_wrapper!(
lt_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::lt_f64_u8,
"lt_elementwise"
);
cpu_binary_wrapper!(
lt_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::lt_bf16_u8,
"lt_elementwise"
);
cpu_binary_wrapper!(
lt_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::lt_f16_u8,
"lt_elementwise"
);
cpu_binary_wrapper!(
le_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::le_f32_u8,
"le_elementwise"
);
cpu_binary_wrapper!(
le_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::le_f64_u8,
"le_elementwise"
);
cpu_binary_wrapper!(
le_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::le_bf16_u8,
"le_elementwise"
);
cpu_binary_wrapper!(
le_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::le_f16_u8,
"le_elementwise"
);
cpu_binary_wrapper!(
gt_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gt_f32_u8,
"gt_elementwise"
);
cpu_binary_wrapper!(
gt_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gt_f64_u8,
"gt_elementwise"
);
cpu_binary_wrapper!(
gt_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gt_bf16_u8,
"gt_elementwise"
);
cpu_binary_wrapper!(
gt_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gt_f16_u8,
"gt_elementwise"
);
cpu_binary_wrapper!(
ge_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ge_f32_u8,
"ge_elementwise"
);
cpu_binary_wrapper!(
ge_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ge_f64_u8,
"ge_elementwise"
);
cpu_binary_wrapper!(
ge_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ge_bf16_u8,
"ge_elementwise"
);
cpu_binary_wrapper!(
ge_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ge_f16_u8,
"ge_elementwise"
);
macro_rules! cpu_where_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 {
return Err(Error::Msg(format!(
"{} wrapper expects 3 inputs (cond, a, b), got {}",
$op_name,
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 output, got {}",
$op_name,
outputs.len(),
))
.bt());
}
let cond_guard = read_storage(&inputs[0])?;
if cond_guard.dtype != DType::Bool {
return Err(Error::Msg(format!(
"{}: cond must be Bool, got {:?} (GAP-168(c))",
$op_name, cond_guard.dtype,
))
.bt());
}
let a_guard = read_storage(&inputs[1])?;
let b_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let cond_cpu = cpu_input(&cond_guard)?;
let a_cpu = cpu_input(&a_guard)?;
let b_cpu = cpu_input(&b_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(cond_cpu, a_cpu, b_cpu, out_cpu)
}
};
}
cpu_where_wrapper!(
where_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::where_f32,
"where"
);
cpu_where_wrapper!(
where_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::where_f64,
"where"
);
cpu_where_wrapper!(
where_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::where_bf16,
"where"
);
cpu_where_wrapper!(
where_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::where_f16,
"where"
);
cpu_unary_wrapper!(
floor_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::floor_f32,
"floor_elementwise"
);
cpu_unary_wrapper!(
floor_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::floor_f64,
"floor_elementwise"
);
cpu_unary_wrapper!(
floor_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::floor_bf16,
"floor_elementwise"
);
cpu_unary_wrapper!(
floor_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::floor_f16,
"floor_elementwise"
);
cpu_unary_wrapper!(
ceil_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ceil_f32,
"ceil_elementwise"
);
cpu_unary_wrapper!(
ceil_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ceil_f64,
"ceil_elementwise"
);
cpu_unary_wrapper!(
ceil_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ceil_bf16,
"ceil_elementwise"
);
cpu_unary_wrapper!(
ceil_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ceil_f16,
"ceil_elementwise"
);
cpu_unary_wrapper!(
round_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::round_f32,
"round_elementwise"
);
cpu_unary_wrapper!(
round_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::round_f64,
"round_elementwise"
);
cpu_unary_wrapper!(
round_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::round_bf16,
"round_elementwise"
);
cpu_unary_wrapper!(
round_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::round_f16,
"round_elementwise"
);
cpu_unary_wrapper!(
sign_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sign_f32,
"sign_elementwise"
);
cpu_unary_wrapper!(
sign_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sign_f64,
"sign_elementwise"
);
cpu_unary_wrapper!(
sign_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sign_bf16,
"sign_elementwise"
);
cpu_unary_wrapper!(
sign_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sign_f16,
"sign_elementwise"
);
cpu_unary_wrapper!(
erf_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::erf_f32,
"erf_elementwise"
);
cpu_unary_wrapper!(
erf_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::erf_f64,
"erf_elementwise"
);
cpu_unary_wrapper!(
erf_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::erf_bf16,
"erf_elementwise"
);
cpu_unary_wrapper!(
erf_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::erf_f16,
"erf_elementwise"
);
cpu_unary_wrapper!(
gelu_erf_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gelu_erf_f32,
"gelu_erf_elementwise"
);
cpu_unary_wrapper!(
gelu_erf_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gelu_erf_f64,
"gelu_erf_elementwise"
);
cpu_unary_wrapper!(
gelu_erf_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gelu_erf_bf16,
"gelu_erf_elementwise"
);
cpu_unary_wrapper!(
gelu_erf_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::gelu_erf_f16,
"gelu_erf_elementwise"
);
cpu_binary_wrapper!(
pow_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::pow_f32,
"pow_elementwise"
);
cpu_binary_wrapper!(
pow_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::pow_f64,
"pow_elementwise"
);
cpu_binary_wrapper!(
pow_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::pow_bf16,
"pow_elementwise"
);
cpu_binary_wrapper!(
pow_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::pow_f16,
"pow_elementwise"
);
cpu_unary_wrapper!(
rsqrt_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rsqrt_f32,
"rsqrt_elementwise"
);
cpu_unary_wrapper!(
rsqrt_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rsqrt_f64,
"rsqrt_elementwise"
);
cpu_unary_wrapper!(
rsqrt_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rsqrt_bf16,
"rsqrt_elementwise"
);
cpu_unary_wrapper!(
rsqrt_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rsqrt_f16,
"rsqrt_elementwise"
);
cpu_binary_wrapper!(
rem_elementwise_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rem_f32,
"rem_elementwise"
);
cpu_binary_wrapper!(
rem_elementwise_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rem_f64,
"rem_elementwise"
);
cpu_binary_wrapper!(
rem_elementwise_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rem_bf16,
"rem_elementwise"
);
cpu_binary_wrapper!(
rem_elementwise_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rem_f16,
"rem_elementwise"
);
pub(crate) fn flip_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"flip wrapper expects 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer, dim_size, inner) = match params {
OpParams::Flip {
outer_count,
dim_size,
inner_count,
..
} => (*outer_count, *dim_size, *inner_count),
other => {
return Err(Error::Msg(format!(
"flip wrapper expects OpParams::Flip, got {other:?}",
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let dtype_size = out_guard.dtype.size_in_bytes();
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::flip_cpu(in_cpu, out_cpu, outer, dim_size, inner, dtype_size)
}
macro_rules! cpu_cumsum_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 input + 1 output, got {} + {}",
$op_name,
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer, dim_size, inner) = match params {
OpParams::CumSum {
outer_count,
dim_size,
inner_count,
..
} => (*outer_count, *dim_size, *inner_count),
other => {
return Err(Error::Msg(format!(
"{} wrapper expects OpParams::CumSum, got {other:?}",
$op_name,
))
.bt())
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, outer, dim_size, inner)
}
};
}
cpu_cumsum_wrapper!(
cumsum_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::cumsum_f32,
"cumsum_f32"
);
cpu_cumsum_wrapper!(
cumsum_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::cumsum_f64,
"cumsum_f64"
);
cpu_cumsum_wrapper!(
cumsum_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::cumsum_bf16,
"cumsum_bf16"
);
cpu_cumsum_wrapper!(
cumsum_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::cumsum_f16,
"cumsum_f16"
);
fn triu_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
triangular_wrapper_inner(
inputs,
outputs,
params,
true,
"triu_cpu_wrapper",
)
}
fn tril_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
triangular_wrapper_inner(
inputs,
outputs,
params,
false,
"tril_cpu_wrapper",
)
}
fn triangular_wrapper_inner(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
params: &OpParams,
keep_upper: bool,
op_name: &str,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{op_name}: expects 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (batch, rows, cols, diag) = match params {
OpParams::Triangular {
batch_count,
rows,
cols,
diagonal,
} => (*batch_count, *rows, *cols, *diagonal),
other => {
return Err(Error::Msg(format!(
"{op_name}: expects OpParams::Triangular, got {other:?}",
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let dtype_size = out_guard.dtype.size_in_bytes();
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::triangular_cpu(
in_cpu, out_cpu, batch, rows, cols, diag, keep_upper, dtype_size,
)
}
macro_rules! cpu_log_softmax_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 input + 1 output, got {} + {}",
$op_name,
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer, last_dim) = match params {
OpParams::LogSoftmaxLastDim {
outer_count,
last_dim,
} => (*outer_count, *last_dim),
other => {
return Err(Error::Msg(format!(
"{} wrapper expects OpParams::LogSoftmaxLastDim, got {other:?}",
$op_name,
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, outer, last_dim)
}
};
}
cpu_log_softmax_wrapper!(
log_softmax_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_softmax_last_dim_f32,
"log_softmax_f32"
);
cpu_log_softmax_wrapper!(
log_softmax_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_softmax_last_dim_f64,
"log_softmax_f64"
);
cpu_log_softmax_wrapper!(
log_softmax_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_softmax_last_dim_bf16,
"log_softmax_bf16"
);
cpu_log_softmax_wrapper!(
log_softmax_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_softmax_last_dim_f16,
"log_softmax_f16"
);
macro_rules! cpu_log_softmax_backward_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 2 inputs (y, g) + 1 output, got {} + {}",
$op_name,
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer, last_dim) = match params {
OpParams::LogSoftmaxLastDim {
outer_count,
last_dim,
} => (*outer_count, *last_dim),
other => {
return Err(Error::Msg(format!(
"{} wrapper expects OpParams::LogSoftmaxLastDim, got {other:?}",
$op_name,
))
.bt());
}
};
let y_guard = read_storage(&inputs[0])?;
let g_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let y_cpu = cpu_input(&y_guard)?;
let g_cpu = cpu_input(&g_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(y_cpu, g_cpu, out_cpu, outer, last_dim)
}
};
}
cpu_log_softmax_backward_wrapper!(
log_softmax_backward_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_softmax_last_dim_backward_f32,
"log_softmax_backward_f32"
);
cpu_log_softmax_backward_wrapper!(
log_softmax_backward_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_softmax_last_dim_backward_f64,
"log_softmax_backward_f64"
);
cpu_log_softmax_backward_wrapper!(
log_softmax_backward_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_softmax_last_dim_backward_bf16,
"log_softmax_backward_bf16"
);
cpu_log_softmax_backward_wrapper!(
log_softmax_backward_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::log_softmax_last_dim_backward_f16,
"log_softmax_backward_f16"
);
macro_rules! cpu_softmax_last_dim_backward_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"softmax_last_dim_backward wrapper expects 2 inputs (y, g) + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (outer, last_dim) = match params {
OpParams::SoftmaxLastDim { outer_count, last_dim } => (*outer_count, *last_dim),
other => {
return Err(Error::Msg(format!(
"softmax_last_dim_backward wrapper expects OpParams::SoftmaxLastDim, got {other:?}",
)).bt());
}
};
let y_guard = read_storage(&inputs[0])?;
let g_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let y_cpu = cpu_input(&y_guard)?;
let g_cpu = cpu_input(&g_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(y_cpu, g_cpu, out_cpu, outer, last_dim)
}
};
}
cpu_softmax_last_dim_backward_wrapper!(
softmax_last_dim_backward_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::softmax_last_dim_backward_f32
);
cpu_softmax_last_dim_backward_wrapper!(
softmax_last_dim_backward_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::softmax_last_dim_backward_f64
);
cpu_softmax_last_dim_backward_wrapper!(
softmax_last_dim_backward_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::softmax_last_dim_backward_bf16
);
cpu_softmax_last_dim_backward_wrapper!(
softmax_last_dim_backward_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::softmax_last_dim_backward_f16
);
macro_rules! cpu_norm_last_dim_backward_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 2 inputs (x, g) + 1 output, got {} + {}",
$op_name,
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer, last_dim, eps) = match params {
OpParams::NormLastDim {
outer_count,
last_dim,
eps,
} => (*outer_count, *last_dim, *eps),
other => {
return Err(Error::Msg(format!(
"{} wrapper expects OpParams::NormLastDim, got {other:?}",
$op_name,
))
.bt());
}
};
let x_guard = read_storage(&inputs[0])?;
let g_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let g_cpu = cpu_input(&g_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(x_cpu, g_cpu, out_cpu, outer, last_dim, eps)
}
};
}
cpu_norm_last_dim_backward_wrapper!(
layer_norm_last_dim_backward_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::layer_norm_last_dim_backward_f32,
"layer_norm_backward_f32"
);
cpu_norm_last_dim_backward_wrapper!(
layer_norm_last_dim_backward_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::layer_norm_last_dim_backward_f64,
"layer_norm_backward_f64"
);
cpu_norm_last_dim_backward_wrapper!(
layer_norm_last_dim_backward_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::layer_norm_last_dim_backward_bf16,
"layer_norm_backward_bf16"
);
cpu_norm_last_dim_backward_wrapper!(
layer_norm_last_dim_backward_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::layer_norm_last_dim_backward_f16,
"layer_norm_backward_f16"
);
cpu_norm_last_dim_backward_wrapper!(
rms_norm_last_dim_backward_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rms_norm_last_dim_backward_f32,
"rms_norm_backward_f32"
);
cpu_norm_last_dim_backward_wrapper!(
rms_norm_last_dim_backward_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rms_norm_last_dim_backward_f64,
"rms_norm_backward_f64"
);
cpu_norm_last_dim_backward_wrapper!(
rms_norm_last_dim_backward_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rms_norm_last_dim_backward_bf16,
"rms_norm_backward_bf16"
);
cpu_norm_last_dim_backward_wrapper!(
rms_norm_last_dim_backward_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rms_norm_last_dim_backward_f16,
"rms_norm_backward_f16"
);
macro_rules! cpu_reduce_max_to_backward_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"reduce_max_to_backward wrapper expects 2 inputs (x, upstream) + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (input_shape, output_shape) = match params {
OpParams::ReduceMaxToBackward { input_shape, output_shape } => {
(input_shape.as_slice(), output_shape.as_slice())
}
other => {
return Err(Error::Msg(format!(
"reduce_max_to_backward wrapper expects OpParams::ReduceMaxToBackward, got {other:?}",
)).bt());
}
};
let x_guard = read_storage(&inputs[0])?;
let up_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let up_cpu = cpu_input(&up_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(x_cpu, up_cpu, out_cpu, input_shape, output_shape)
}
};
}
cpu_reduce_max_to_backward_wrapper!(
reduce_max_to_backward_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_max_to_backward_f32
);
cpu_reduce_max_to_backward_wrapper!(
reduce_max_to_backward_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_max_to_backward_f64
);
cpu_reduce_max_to_backward_wrapper!(
reduce_max_to_backward_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_max_to_backward_bf16
);
cpu_reduce_max_to_backward_wrapper!(
reduce_max_to_backward_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_max_to_backward_f16
);
pub(crate) fn masked_fill_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"masked_fill_cpu_wrapper: expects 2 inputs (x, mask) + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let fill_bytes = match params {
OpParams::MaskedFill { fill_bytes } => fill_bytes.clone(),
other => {
return Err(Error::Msg(format!(
"masked_fill_cpu_wrapper: expects OpParams::MaskedFill, got {other:?}",
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mask_guard = read_storage(&inputs[1])?;
if mask_guard.dtype != DType::Bool {
return Err(Error::Msg(format!(
"masked_fill_cpu_wrapper: mask must be Bool, got {:?} (GAP-168(c))",
mask_guard.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let dtype_size = out_guard.dtype.size_in_bytes();
let in_cpu = cpu_input(&in_guard)?;
let mask_cpu = cpu_input(&mask_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::masked_fill_cpu(
in_cpu,
mask_cpu,
out_cpu,
&fill_bytes,
dtype_size,
)
}
pub(crate) fn pad_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"pad wrapper expects 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (in_shape, out_shape, padding, mode_tag, fill_bytes) = match params {
OpParams::Pad {
in_shape,
out_shape,
padding,
mode_tag,
fill_bytes,
} => (in_shape, out_shape, padding, *mode_tag, fill_bytes),
other => {
return Err(
Error::Msg(format!("pad wrapper expects OpParams::Pad, got {other:?}",)).bt(),
);
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let dtype_size = out_guard.dtype.size_in_bytes();
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
match mode_tag {
0 => fuel_cpu_backend::byte_kernels::pad_const_cpu(
in_cpu, out_cpu, in_shape, out_shape, padding, dtype_size, fill_bytes,
),
1 => {
for (k, (&n, &(b, a))) in in_shape.iter().zip(padding.iter()).enumerate() {
if n > 0 && (b > n - 1 || a > n - 1) {
return Err(Error::Msg(format!(
"pad reflect: axis {k} has dim_size {n}; before ({b}) and \
after ({a}) must each be <= dim_size - 1",
))
.bt());
}
}
fuel_cpu_backend::byte_kernels::pad_reflect_cpu(
in_cpu, out_cpu, in_shape, out_shape, padding, dtype_size,
)
}
2 => fuel_cpu_backend::byte_kernels::pad_replicate_cpu(
in_cpu, out_cpu, in_shape, out_shape, padding, dtype_size,
),
other => Err(Error::Msg(format!("pad: unknown mode_tag {other}",)).bt()),
}
}
macro_rules! cpu_pad_backward_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 input + 1 output, got {} + {}",
$op_name,
inputs.len(),
outputs.len(),
))
.bt());
}
let (in_shape, out_shape, padding, mode_tag) = match params {
OpParams::PadBackward {
in_shape,
out_shape,
padding,
mode_tag,
} => (in_shape, out_shape, padding, *mode_tag),
other => {
return Err(Error::Msg(format!(
"{} wrapper expects OpParams::PadBackward, got {other:?}",
$op_name,
))
.bt())
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, in_shape, out_shape, padding, mode_tag)
}
};
}
cpu_pad_backward_wrapper!(
pad_backward_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::pad_backward_f32,
"pad_backward_f32"
);
cpu_pad_backward_wrapper!(
pad_backward_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::pad_backward_f64,
"pad_backward_f64"
);
cpu_pad_backward_wrapper!(
pad_backward_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::pad_backward_bf16,
"pad_backward_bf16"
);
cpu_pad_backward_wrapper!(
pad_backward_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::pad_backward_f16,
"pad_backward_f16"
);
pub(crate) fn roll_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"roll wrapper expects 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer, dim_size, inner, shift) = match params {
OpParams::Roll {
outer_count,
dim_size,
inner_count,
shift,
..
} => (*outer_count, *dim_size, *inner_count, *shift),
other => {
return Err(Error::Msg(format!(
"roll wrapper expects OpParams::Roll, got {other:?}",
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let dtype_size = out_guard.dtype.size_in_bytes();
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::roll_cpu(
in_cpu, out_cpu, outer, dim_size, inner, shift, dtype_size,
)
}
macro_rules! cpu_arg_dim_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 input + 1 output, got {} + {}",
$op_name,
inputs.len(),
outputs.len(),
))
.bt());
}
let dims = match params {
OpParams::Reduce { dims, .. } => dims,
other => {
return Err(Error::Msg(format!(
"{} wrapper expects OpParams::Reduce, got {other:?}",
$op_name,
))
.bt())
}
};
if dims.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects exactly 1 reduce dim, got {dims:?}",
$op_name,
))
.bt());
}
let dim = dims[0];
let input_layout = layouts.first().ok_or_else(|| {
Error::Msg(format!(
"{} wrapper: layouts empty (executor must pass input layout at [0])",
$op_name,
))
.bt()
})?;
let in_guard = read_storage(&inputs[0])?;
if in_guard.dtype != DType::F32 {
return Err(Error::Msg(format!(
"{}: only F32 input is wired today, got {:?}",
$op_name, in_guard.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, input_layout.shape().dims(), dim)
}
};
}
cpu_arg_dim_wrapper!(
argmax_dim_u32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::argmax_dim_f32,
"argmax_dim"
);
cpu_arg_dim_wrapper!(
argmin_dim_u32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::argmin_dim_f32,
"argmin_dim"
);
pub(crate) fn index_add_f32_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"index_add wrapper expects 3 inputs (base, indices, src) + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, base_dim_size, n_indices, inner_count) = match params {
OpParams::IndexAdd {
outer_count,
base_dim_size,
n_indices,
inner_count,
} => (*outer_count, *base_dim_size, *n_indices, *inner_count),
other => {
return Err(Error::Msg(format!(
"index_add wrapper expects OpParams::IndexAdd, got {other:?}",
))
.bt());
}
};
let base_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"index_add: indices must be U32, got {:?}",
idx_guard.dtype,
))
.bt());
}
let src_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let base_cpu = cpu_input(&base_guard)?;
let idx_cpu = cpu_input(&idx_guard)?;
let src_cpu = cpu_input(&src_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::index_add_f32(
base_cpu,
idx_cpu,
src_cpu,
out_cpu,
outer_count,
base_dim_size,
n_indices,
inner_count,
)
}
pub(crate) fn scatter_add_f32_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"scatter_add wrapper expects 3 inputs (base, indices, src) + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (base_shape, src_shape, dim) = match params {
OpParams::ScatterAdd {
base_shape,
src_shape,
dim,
} => (base_shape, src_shape, *dim),
other => {
return Err(Error::Msg(format!(
"scatter_add wrapper expects OpParams::ScatterAdd, got {other:?}",
))
.bt());
}
};
let base_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"scatter_add: indices must be U32, got {:?}",
idx_guard.dtype,
))
.bt());
}
let src_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let base_cpu = cpu_input(&base_guard)?;
let idx_cpu = cpu_input(&idx_guard)?;
let src_cpu = cpu_input(&src_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::scatter_add_f32(
base_cpu, idx_cpu, src_cpu, out_cpu, base_shape, src_shape, dim,
)
}
pub(crate) fn rope_f32_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"rope wrapper expects 3 inputs (x, cos, sin) + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, seq, head_dim) = match params {
OpParams::Rope {
outer_count,
seq,
head_dim,
} => (*outer_count, *seq, *head_dim),
other => {
return Err(Error::Msg(format!(
"rope wrapper expects OpParams::Rope, got {other:?}",
))
.bt());
}
};
let x_guard = read_storage(&inputs[0])?;
let cos_guard = read_storage(&inputs[1])?;
let sin_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let cos_cpu = cpu_input(&cos_guard)?;
let sin_cpu = cpu_input(&sin_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::rope_f32(
x_cpu,
cos_cpu,
sin_cpu,
out_cpu,
outer_count,
seq,
head_dim,
)
}
pub(crate) fn gather_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"gather wrapper expects 2 inputs + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (source_shape, output_shape, dim) = match params {
OpParams::Gather {
source_shape,
output_shape,
dim,
} => (source_shape, output_shape, *dim),
other => {
return Err(Error::Msg(format!(
"gather wrapper expects OpParams::Gather, got {other:?}",
))
.bt());
}
};
let src_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"gather: indices must be U32, got {:?}",
idx_guard.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let dtype_size = out_guard.dtype.size_in_bytes();
let src_cpu = cpu_input(&src_guard)?;
let idx_cpu = cpu_input(&idx_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::gather_cpu(
src_cpu,
idx_cpu,
out_cpu,
source_shape,
output_shape,
dim,
dtype_size,
)
}
pub(crate) fn index_select_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 {
return Err(Error::Msg(format!(
"index_select wrapper expects 2 inputs (source, indices), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"index_select wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (outer_count, source_dim_size, n_indices, inner_count) = match params {
OpParams::IndexSelect {
outer_count,
source_dim_size,
n_indices,
inner_count,
} => (*outer_count, *source_dim_size, *n_indices, *inner_count),
other => {
return Err(Error::Msg(format!(
"index_select wrapper expects OpParams::IndexSelect, got {other:?}",
))
.bt());
}
};
let src_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"index_select: indices must be U32, got {:?}",
idx_guard.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let dtype_size = out_guard.dtype.size_in_bytes();
let src_cpu = cpu_input(&src_guard)?;
let idx_cpu = cpu_input(&idx_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::index_select_cpu(
src_cpu,
idx_cpu,
out_cpu,
outer_count,
source_dim_size,
n_indices,
inner_count,
dtype_size,
)
}
macro_rules! cpu_norm_last_dim_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 input + 1 output, got {} + {}",
$op_name,
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim, eps) = match params {
OpParams::NormLastDim {
outer_count,
last_dim,
eps,
} => (*outer_count, *last_dim, *eps),
other => {
return Err(Error::Msg(format!(
"{} wrapper expects OpParams::NormLastDim, got {other:?}",
$op_name,
))
.bt())
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, outer_count, last_dim, eps)
}
};
}
cpu_norm_last_dim_wrapper!(
rms_norm_last_dim_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rms_norm_last_dim_f32,
"rms_norm_last_dim"
);
cpu_norm_last_dim_wrapper!(
layer_norm_last_dim_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::layer_norm_last_dim_f32,
"layer_norm_last_dim"
);
cpu_norm_last_dim_wrapper!(
rms_norm_last_dim_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rms_norm_last_dim_bf16,
"rms_norm_last_dim"
);
cpu_norm_last_dim_wrapper!(
rms_norm_last_dim_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rms_norm_last_dim_f16,
"rms_norm_last_dim"
);
cpu_norm_last_dim_wrapper!(
layer_norm_last_dim_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::layer_norm_last_dim_bf16,
"layer_norm_last_dim"
);
cpu_norm_last_dim_wrapper!(
layer_norm_last_dim_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::layer_norm_last_dim_f16,
"layer_norm_last_dim"
);
cpu_norm_last_dim_wrapper!(
rms_norm_last_dim_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rms_norm_last_dim_f64,
"rms_norm_last_dim"
);
cpu_norm_last_dim_wrapper!(
layer_norm_last_dim_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::layer_norm_last_dim_f64,
"layer_norm_last_dim"
);
macro_rules! cpu_softmax_last_dim_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"softmax_last_dim wrapper expects 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim) =
match params {
OpParams::SoftmaxLastDim {
outer_count,
last_dim,
} => (*outer_count, *last_dim),
other => return Err(Error::Msg(format!(
"softmax_last_dim wrapper expects OpParams::SoftmaxLastDim, got {other:?}",
))
.bt()),
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, outer_count, last_dim)
}
};
}
cpu_softmax_last_dim_wrapper!(
softmax_last_dim_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::softmax_last_dim_f32
);
cpu_softmax_last_dim_wrapper!(
softmax_last_dim_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::softmax_last_dim_f64
);
cpu_softmax_last_dim_wrapper!(
softmax_last_dim_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::softmax_last_dim_bf16
);
cpu_softmax_last_dim_wrapper!(
softmax_last_dim_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::softmax_last_dim_f16
);
macro_rules! cpu_rope_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"rope wrapper expects 3 inputs + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, seq, head_dim) = match params {
OpParams::Rope {
outer_count,
seq,
head_dim,
} => (*outer_count, *seq, *head_dim),
other => {
return Err(Error::Msg(format!(
"rope wrapper expects OpParams::Rope, got {other:?}",
))
.bt())
}
};
let x_guard = read_storage(&inputs[0])?;
let cos_guard = read_storage(&inputs[1])?;
let sin_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let cos_cpu = cpu_input(&cos_guard)?;
let sin_cpu = cpu_input(&sin_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(x_cpu, cos_cpu, sin_cpu, out_cpu, outer_count, seq, head_dim)
}
};
}
cpu_rope_wrapper!(
rope_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rope_bf16
);
cpu_rope_wrapper!(
rope_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rope_f16
);
cpu_rope_wrapper!(
rope_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::rope_f64
);
pub(crate) fn qmatmul_f32_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 {
return Err(Error::Msg(format!(
"qmatmul wrapper expects 2 inputs (activations, weight_bytes), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"qmatmul wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (quant_type, batch_count, m, n, k) = match params {
OpParams::QMatMul {
quant_type,
batch_count,
m,
n,
k,
} => (*quant_type, *batch_count, *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"qmatmul wrapper expects OpParams::QMatMul, got {other:?}",
))
.bt());
}
};
let act_guard = read_storage(&inputs[0])?;
let w_guard = read_storage(&inputs[1])?;
if w_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"qmatmul: weight bytes must be U32-typed (raw block stream), got {:?}",
w_guard.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let act_cpu = cpu_input(&act_guard)?;
let w_cpu = cpu_input(&w_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
use fuel_graph::QuantType;
match quant_type {
QuantType::Q4_0 => fuel_cpu_backend::byte_kernels::qmatmul_q4_0_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q4_1 => fuel_cpu_backend::byte_kernels::qmatmul_q4_1_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q5_0 => fuel_cpu_backend::byte_kernels::qmatmul_q5_0_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q5_1 => fuel_cpu_backend::byte_kernels::qmatmul_q5_1_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q8_0 => fuel_cpu_backend::byte_kernels::qmatmul_q8_0_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q8_1 => fuel_cpu_backend::byte_kernels::qmatmul_q8_1_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q2K => fuel_cpu_backend::byte_kernels::qmatmul_q2k_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q3K => fuel_cpu_backend::byte_kernels::qmatmul_q3k_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q4_K_M => fuel_cpu_backend::byte_kernels::qmatmul_q4_k_m_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q5K => fuel_cpu_backend::byte_kernels::qmatmul_q5k_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
QuantType::Q6K => fuel_cpu_backend::byte_kernels::qmatmul_q6k_f32(
act_cpu,
w_cpu,
out_cpu,
batch_count,
m,
n,
k,
),
}
}
pub(crate) fn write_slice_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"write_slice wrapper expects 1 input (source) + 1 output (dest), \
got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (dest_shape, ranges) = match params {
OpParams::WriteSlice {
dest_shape, ranges, ..
} => (dest_shape, ranges),
other => {
return Err(Error::Msg(format!(
"write_slice wrapper expects OpParams::WriteSlice, got {other:?}",
))
.bt());
}
};
let src_guard = read_storage(&inputs[0])?;
let src_cpu = cpu_input(&src_guard)?;
let mut dest_guard = write_storage(&outputs[0])?;
let dtype_size = dest_guard.dtype.size_in_bytes();
let dest_cpu = cpu_output(&mut dest_guard)?;
fuel_cpu_backend::byte_kernels::write_slice_cpu(
src_cpu, dest_cpu, dest_shape, ranges, dtype_size,
)
}
pub(crate) fn write_slice_rotating_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"write_slice_rotating wrapper expects 2 inputs (source, position) + 1 output (dest), \
got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (dest_shape, axis, modulus, ranges) =
match params {
OpParams::WriteSliceRotating {
dest_shape,
axis,
modulus,
ranges,
} => (dest_shape, *axis, *modulus, ranges),
other => return Err(Error::Msg(format!(
"write_slice_rotating wrapper expects OpParams::WriteSliceRotating, got {other:?}",
))
.bt()),
};
let src_guard = read_storage(&inputs[0])?;
let src_cpu = cpu_input(&src_guard)?;
let pos_guard = read_storage(&inputs[1])?;
let pos_cpu = cpu_input(&pos_guard)?;
let mut dest_guard = write_storage(&outputs[0])?;
let dtype_size = dest_guard.dtype.size_in_bytes();
let dest_cpu = cpu_output(&mut dest_guard)?;
fuel_cpu_backend::byte_kernels::write_slice_rotating_cpu(
src_cpu, pos_cpu, dest_cpu, dest_shape, axis, modulus, ranges, dtype_size,
)
}
pub(crate) fn write_slice_doff_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"write_slice_doff wrapper expects 2 inputs (source, offset) + 1 output (dest), \
got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (dest_shape, axis, ranges) = match params {
OpParams::WriteSliceDoff {
dest_shape,
axis,
ranges,
} => (dest_shape, *axis, ranges),
other => {
return Err(Error::Msg(format!(
"write_slice_doff wrapper expects OpParams::WriteSliceDoff, got {other:?}",
))
.bt());
}
};
let src_guard = read_storage(&inputs[0])?;
let src_cpu = cpu_input(&src_guard)?;
let off_guard = read_storage(&inputs[1])?;
let off_cpu = cpu_input(&off_guard)?;
let mut dest_guard = write_storage(&outputs[0])?;
let dtype_size = dest_guard.dtype.size_in_bytes();
let dest_cpu = cpu_output(&mut dest_guard)?;
fuel_cpu_backend::byte_kernels::write_slice_doff_cpu(
src_cpu, off_cpu, dest_cpu, dest_shape, axis, ranges, dtype_size,
)
}
pub(crate) fn concat_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.is_empty() {
return Err(Error::Msg("concat wrapper expects ≥ 1 input, got 0".to_string()).bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"concat wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (outer_count, input_dim_sizes, inner_count) = match params {
OpParams::Concat {
outer_count,
input_dim_sizes,
inner_count,
..
} => (*outer_count, input_dim_sizes, *inner_count),
other => {
return Err(Error::Msg(format!(
"concat wrapper expects OpParams::Concat, got {other:?}",
))
.bt());
}
};
if input_dim_sizes.len() != inputs.len() {
return Err(Error::Msg(format!(
"concat wrapper: OpParams declares {} inputs but the work item \
carries {}",
input_dim_sizes.len(),
inputs.len(),
))
.bt());
}
let in_guards: Vec<_> = inputs
.iter()
.map(read_storage)
.collect::<Result<Vec<_>>>()?;
let mut in_cpus: Vec<&fuel_cpu_backend::CpuStorageBytes> = Vec::with_capacity(in_guards.len());
for g in &in_guards {
in_cpus.push(cpu_input(g)?);
}
let mut out_guard = write_storage(&outputs[0])?;
let dtype_size = out_guard.dtype.size_in_bytes();
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::concat_cpu(
&in_cpus,
out_cpu,
outer_count,
input_dim_sizes,
inner_count,
dtype_size,
)
}
macro_rules! cpu_index_add_wrapper {
($wrapper:ident, $kernel:path, $idx_ck:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{}: expects 3 inputs + 1 output, got {} + {}",
$idx_ck,
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, base_dim_size, n_indices, inner_count) = match params {
OpParams::IndexAdd {
outer_count,
base_dim_size,
n_indices,
inner_count,
} => (*outer_count, *base_dim_size, *n_indices, *inner_count),
other => {
return Err(Error::Msg(format!(
"{}: expects OpParams::IndexAdd, got {other:?}",
$idx_ck,
))
.bt())
}
};
let base_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!("{}: indices must be U32", $idx_ck,)).bt());
}
let src_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let base_cpu = cpu_input(&base_guard)?;
let idx_cpu = cpu_input(&idx_guard)?;
let src_cpu = cpu_input(&src_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
base_cpu,
idx_cpu,
src_cpu,
out_cpu,
outer_count,
base_dim_size,
n_indices,
inner_count,
)
}
};
}
cpu_index_add_wrapper!(
index_add_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::index_add_f64,
"index_add_f64"
);
cpu_index_add_wrapper!(
index_add_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::index_add_bf16,
"index_add_bf16"
);
cpu_index_add_wrapper!(
index_add_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::index_add_f16,
"index_add_f16"
);
macro_rules! cpu_scatter_add_wrapper {
($wrapper:ident, $kernel:path, $name_str:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{}: expects 3 inputs + 1 output, got {} + {}",
$name_str,
inputs.len(),
outputs.len(),
))
.bt());
}
let (base_shape, src_shape, dim) = match params {
OpParams::ScatterAdd {
base_shape,
src_shape,
dim,
} => (base_shape, src_shape, *dim),
other => {
return Err(Error::Msg(format!(
"{}: expects OpParams::ScatterAdd, got {other:?}",
$name_str,
))
.bt())
}
};
let base_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!("{}: indices must be U32", $name_str,)).bt());
}
let src_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let base_cpu = cpu_input(&base_guard)?;
let idx_cpu = cpu_input(&idx_guard)?;
let src_cpu = cpu_input(&src_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
base_cpu, idx_cpu, src_cpu, out_cpu, base_shape, src_shape, dim,
)
}
};
}
cpu_scatter_add_wrapper!(
scatter_add_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::scatter_add_f64,
"scatter_add_f64"
);
cpu_scatter_add_wrapper!(
scatter_add_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::scatter_add_bf16,
"scatter_add_bf16"
);
cpu_scatter_add_wrapper!(
scatter_add_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::scatter_add_f16,
"scatter_add_f16"
);
macro_rules! cpu_affine_wrapper_native {
($wrapper:ident, $kernel:path, $T:ty) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg("affine: 1 input + 1 output".to_string()).bt());
}
let (mul, add) = match params {
OpParams::Affine { mul, add } => (*mul as $T, *add as $T),
other => return Err(Error::Msg(format!("affine: bad params {other:?}")).bt()),
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, mul, add)
}
};
}
cpu_affine_wrapper_native!(
affine_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::affine_f64,
f64
);
macro_rules! cpu_affine_wrapper_half {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg("affine: 1 input + 1 output".to_string()).bt());
}
let (mul, add) = match params {
OpParams::Affine { mul, add } => (*mul as f32, *add as f32),
other => return Err(Error::Msg(format!("affine: bad params {other:?}")).bt()),
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, mul, add)
}
};
}
cpu_affine_wrapper_half!(
affine_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::affine_bf16
);
cpu_affine_wrapper_half!(
affine_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::affine_f16
);
macro_rules! cpu_clamp_wrapper {
($wrapper:ident, $kernel:path, $T:ty) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg("clamp: 1 input + 1 output".to_string()).bt());
}
let (min, max) = match params {
OpParams::Clamp { min, max } => (*min as $T, *max as $T),
other => return Err(Error::Msg(format!("clamp: bad params {other:?}")).bt()),
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, min, max)
}
};
}
cpu_clamp_wrapper!(
clamp_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::clamp_f64,
f64
);
cpu_clamp_wrapper!(
clamp_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::clamp_bf16,
f32
);
cpu_clamp_wrapper!(
clamp_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::clamp_f16,
f32
);
macro_rules! cpu_powi_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg("powi: 1 input + 1 output".to_string()).bt());
}
let exp = match params {
OpParams::PowI { exp } => *exp,
other => return Err(Error::Msg(format!("powi: bad params {other:?}")).bt()),
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, exp)
}
};
}
cpu_powi_wrapper!(
powi_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::powi_f64
);
cpu_powi_wrapper!(
powi_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::powi_bf16
);
cpu_powi_wrapper!(
powi_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::powi_f16
);
macro_rules! cpu_arg_dim_wrapper_typed {
($wrapper:ident, $kernel:path, $expected_in_dtype:expr, $op_name:literal) => {
fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!("{}: 1 input + 1 output", $op_name,)).bt());
}
let dims = match params {
OpParams::Reduce { dims, .. } => dims,
other => {
return Err(Error::Msg(format!(
"{}: expects OpParams::Reduce, got {other:?}",
$op_name,
))
.bt())
}
};
if dims.len() != 1 {
return Err(Error::Msg(format!(
"{}: expects exactly 1 reduce dim, got {dims:?}",
$op_name,
))
.bt());
}
let dim = dims[0];
let input_layout = layouts.first().ok_or_else(|| {
Error::Msg(format!(
"{}: layouts empty (executor must pass input layout at [0])",
$op_name,
))
.bt()
})?;
let in_guard = read_storage(&inputs[0])?;
if in_guard.dtype != $expected_in_dtype {
return Err(Error::Msg(format!(
"{}: expects input dtype {:?}, got {:?}",
$op_name, $expected_in_dtype, in_guard.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, input_layout.shape().dims(), dim)
}
};
}
fn argmax_dim_u32_cpu_dispatch(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
let in_dtype = read_storage(&inputs[0])?.dtype;
match in_dtype {
DType::F32 => argmax_dim_u32_cpu_wrapper(inputs, outputs, _layouts, params),
DType::F64 => argmax_dim_f64_only_wrapper(inputs, outputs, _layouts, params),
DType::BF16 => argmax_dim_bf16_only_wrapper(inputs, outputs, _layouts, params),
DType::F16 => argmax_dim_f16_only_wrapper(inputs, outputs, _layouts, params),
other => Err(Error::Msg(format!("argmax_dim: unsupported input dtype {other:?}",)).bt()),
}
}
fn argmin_dim_u32_cpu_dispatch(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
let in_dtype = read_storage(&inputs[0])?.dtype;
match in_dtype {
DType::F32 => argmin_dim_u32_cpu_wrapper(inputs, outputs, _layouts, params),
DType::F64 => argmin_dim_f64_only_wrapper(inputs, outputs, _layouts, params),
DType::BF16 => argmin_dim_bf16_only_wrapper(inputs, outputs, _layouts, params),
DType::F16 => argmin_dim_f16_only_wrapper(inputs, outputs, _layouts, params),
other => Err(Error::Msg(format!("argmin_dim: unsupported input dtype {other:?}",)).bt()),
}
}
cpu_arg_dim_wrapper_typed!(
argmax_dim_f64_only_wrapper,
fuel_cpu_backend::byte_kernels::argmax_dim_f64,
DType::F64,
"argmax_dim_f64"
);
cpu_arg_dim_wrapper_typed!(
argmin_dim_f64_only_wrapper,
fuel_cpu_backend::byte_kernels::argmin_dim_f64,
DType::F64,
"argmin_dim_f64"
);
cpu_arg_dim_wrapper_typed!(
argmax_dim_bf16_only_wrapper,
fuel_cpu_backend::byte_kernels::argmax_dim_bf16,
DType::BF16,
"argmax_dim_bf16"
);
cpu_arg_dim_wrapper_typed!(
argmin_dim_bf16_only_wrapper,
fuel_cpu_backend::byte_kernels::argmin_dim_bf16,
DType::BF16,
"argmin_dim_bf16"
);
cpu_arg_dim_wrapper_typed!(
argmax_dim_f16_only_wrapper,
fuel_cpu_backend::byte_kernels::argmax_dim_f16,
DType::F16,
"argmax_dim_f16"
);
cpu_arg_dim_wrapper_typed!(
argmin_dim_f16_only_wrapper,
fuel_cpu_backend::byte_kernels::argmin_dim_f16,
DType::F16,
"argmin_dim_f16"
);
pub(crate) fn affine_f32_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"affine wrapper expects 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (mul, add) = match params {
OpParams::Affine { mul, add } => (*mul as f32, *add as f32),
other => {
return Err(Error::Msg(format!(
"affine wrapper expects OpParams::Affine, got {other:?}",
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::affine_f32(in_cpu, out_cpu, mul, add)
}
macro_rules! cpu_affine_inplace_wrapper {
($name:ident, $kernel:ident, $T:ty) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if !inputs.is_empty() || outputs.len() != 1 {
return Err(Error::Msg(format!(
concat!(
stringify!($name),
": expected 0 inputs + 1 output (target adopted by executor), got {} + {}"
),
inputs.len(),
outputs.len(),
))
.bt());
}
let (mul, add) = match params {
OpParams::Affine { mul, add } => (*mul as $T, *add as $T),
other => {
return Err(Error::Msg(format!(
concat!(stringify!($name), ": expected OpParams::Affine, got {:?}"),
other,
))
.bt())
}
};
let mut out_guard = write_storage(&outputs[0])?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::$kernel(out_cpu, mul, add)
}
};
($name:ident, $kernel:ident, $T:ty, half) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if !inputs.is_empty() || outputs.len() != 1 {
return Err(Error::Msg(format!(
concat!(
stringify!($name),
": expected 0 inputs + 1 output (target adopted by executor), got {} + {}"
),
inputs.len(),
outputs.len(),
))
.bt());
}
let (mul, add) = match params {
OpParams::Affine { mul, add } => (*mul, *add),
other => {
return Err(Error::Msg(format!(
concat!(stringify!($name), ": expected OpParams::Affine, got {:?}"),
other,
))
.bt())
}
};
let mut out_guard = write_storage(&outputs[0])?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::$kernel(out_cpu, mul, add)
}
};
}
cpu_affine_inplace_wrapper!(inplace_affine_f32_cpu_wrapper, affine_inplace_f32, f32);
cpu_affine_inplace_wrapper!(inplace_affine_f64_cpu_wrapper, affine_inplace_f64, f64);
cpu_affine_inplace_wrapper!(
inplace_affine_bf16_cpu_wrapper,
affine_inplace_bf16,
f64,
half
);
cpu_affine_inplace_wrapper!(
inplace_affine_f16_cpu_wrapper,
affine_inplace_f16,
f64,
half
);
macro_rules! cpu_clamp_inplace_wrapper {
($name:ident, $kernel:ident, $T:ty) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if !inputs.is_empty() || outputs.len() != 1 {
return Err(Error::Msg(format!(
concat!(
stringify!($name),
": expected 0 inputs + 1 output (target adopted by executor), got {} + {}"
),
inputs.len(),
outputs.len(),
))
.bt());
}
let (min, max) = match params {
OpParams::Clamp { min, max } => (*min as $T, *max as $T),
other => {
return Err(Error::Msg(format!(
concat!(stringify!($name), ": expected OpParams::Clamp, got {:?}"),
other,
))
.bt())
}
};
let mut out_guard = write_storage(&outputs[0])?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::$kernel(out_cpu, min, max)
}
};
($name:ident, $kernel:ident, $T:ty, half) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if !inputs.is_empty() || outputs.len() != 1 {
return Err(Error::Msg(format!(
concat!(
stringify!($name),
": expected 0 inputs + 1 output (target adopted by executor), got {} + {}"
),
inputs.len(),
outputs.len(),
))
.bt());
}
let (min, max) = match params {
OpParams::Clamp { min, max } => (*min, *max),
other => {
return Err(Error::Msg(format!(
concat!(stringify!($name), ": expected OpParams::Clamp, got {:?}"),
other,
))
.bt())
}
};
let mut out_guard = write_storage(&outputs[0])?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::$kernel(out_cpu, min, max)
}
};
}
cpu_clamp_inplace_wrapper!(clamp_inplace_f32_cpu_wrapper, clamp_inplace_f32, f32);
cpu_clamp_inplace_wrapper!(clamp_inplace_f64_cpu_wrapper, clamp_inplace_f64, f64);
cpu_clamp_inplace_wrapper!(
clamp_inplace_bf16_cpu_wrapper,
clamp_inplace_bf16,
f64,
half
);
cpu_clamp_inplace_wrapper!(clamp_inplace_f16_cpu_wrapper, clamp_inplace_f16, f64, half);
macro_rules! cpu_powi_inplace_wrapper {
($name:ident, $kernel:ident) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if !inputs.is_empty() || outputs.len() != 1 {
return Err(Error::Msg(format!(
concat!(
stringify!($name),
": expected 0 inputs + 1 output (target adopted by executor), got {} + {}"
),
inputs.len(),
outputs.len(),
))
.bt());
}
let exp = match params {
OpParams::PowI { exp } => *exp,
other => {
return Err(Error::Msg(format!(
concat!(stringify!($name), ": expected OpParams::PowI, got {:?}"),
other,
))
.bt())
}
};
let mut out_guard = write_storage(&outputs[0])?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::$kernel(out_cpu, exp)
}
};
}
cpu_powi_inplace_wrapper!(powi_inplace_f32_cpu_wrapper, powi_inplace_f32);
cpu_powi_inplace_wrapper!(powi_inplace_f64_cpu_wrapper, powi_inplace_f64);
cpu_powi_inplace_wrapper!(powi_inplace_bf16_cpu_wrapper, powi_inplace_bf16);
cpu_powi_inplace_wrapper!(powi_inplace_f16_cpu_wrapper, powi_inplace_f16);
macro_rules! cpu_unary_inplace_wrapper {
($name:ident, $kernel:ident) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if !inputs.is_empty() || outputs.len() != 1 {
return Err(Error::Msg(format!(
concat!(
stringify!($name),
": expected 0 inputs + 1 output (target adopted by executor), got {} + {}"
),
inputs.len(),
outputs.len(),
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::$kernel(out_cpu)
}
};
}
cpu_unary_inplace_wrapper!(relu_inplace_f32_cpu_wrapper, relu_inplace_f32);
cpu_unary_inplace_wrapper!(silu_inplace_f32_cpu_wrapper, silu_inplace_f32);
cpu_unary_inplace_wrapper!(gelu_inplace_f32_cpu_wrapper, gelu_inplace_f32);
cpu_unary_inplace_wrapper!(tanh_inplace_f32_cpu_wrapper, tanh_inplace_f32);
cpu_unary_inplace_wrapper!(sigmoid_inplace_f32_cpu_wrapper, sigmoid_inplace_f32);
cpu_unary_inplace_wrapper!(relu_inplace_f64_cpu_wrapper, relu_inplace_f64);
cpu_unary_inplace_wrapper!(silu_inplace_f64_cpu_wrapper, silu_inplace_f64);
cpu_unary_inplace_wrapper!(gelu_inplace_f64_cpu_wrapper, gelu_inplace_f64);
cpu_unary_inplace_wrapper!(tanh_inplace_f64_cpu_wrapper, tanh_inplace_f64);
cpu_unary_inplace_wrapper!(sigmoid_inplace_f64_cpu_wrapper, sigmoid_inplace_f64);
cpu_unary_inplace_wrapper!(relu_inplace_bf16_cpu_wrapper, relu_inplace_bf16);
cpu_unary_inplace_wrapper!(silu_inplace_bf16_cpu_wrapper, silu_inplace_bf16);
cpu_unary_inplace_wrapper!(gelu_inplace_bf16_cpu_wrapper, gelu_inplace_bf16);
cpu_unary_inplace_wrapper!(tanh_inplace_bf16_cpu_wrapper, tanh_inplace_bf16);
cpu_unary_inplace_wrapper!(sigmoid_inplace_bf16_cpu_wrapper, sigmoid_inplace_bf16);
cpu_unary_inplace_wrapper!(relu_inplace_f16_cpu_wrapper, relu_inplace_f16);
cpu_unary_inplace_wrapper!(silu_inplace_f16_cpu_wrapper, silu_inplace_f16);
cpu_unary_inplace_wrapper!(gelu_inplace_f16_cpu_wrapper, gelu_inplace_f16);
cpu_unary_inplace_wrapper!(tanh_inplace_f16_cpu_wrapper, tanh_inplace_f16);
cpu_unary_inplace_wrapper!(sigmoid_inplace_f16_cpu_wrapper, sigmoid_inplace_f16);
cpu_unary_inplace_wrapper!(neg_inplace_f32_cpu_wrapper, neg_inplace_f32);
cpu_unary_inplace_wrapper!(neg_inplace_f64_cpu_wrapper, neg_inplace_f64);
cpu_unary_inplace_wrapper!(neg_inplace_bf16_cpu_wrapper, neg_inplace_bf16);
cpu_unary_inplace_wrapper!(neg_inplace_f16_cpu_wrapper, neg_inplace_f16);
cpu_unary_inplace_wrapper!(abs_inplace_f32_cpu_wrapper, abs_inplace_f32);
cpu_unary_inplace_wrapper!(abs_inplace_f64_cpu_wrapper, abs_inplace_f64);
cpu_unary_inplace_wrapper!(abs_inplace_bf16_cpu_wrapper, abs_inplace_bf16);
cpu_unary_inplace_wrapper!(abs_inplace_f16_cpu_wrapper, abs_inplace_f16);
cpu_unary_inplace_wrapper!(sqr_inplace_f32_cpu_wrapper, sqr_inplace_f32);
cpu_unary_inplace_wrapper!(sqr_inplace_f64_cpu_wrapper, sqr_inplace_f64);
cpu_unary_inplace_wrapper!(sqr_inplace_bf16_cpu_wrapper, sqr_inplace_bf16);
cpu_unary_inplace_wrapper!(sqr_inplace_f16_cpu_wrapper, sqr_inplace_f16);
cpu_unary_inplace_wrapper!(sqrt_inplace_f32_cpu_wrapper, sqrt_inplace_f32);
cpu_unary_inplace_wrapper!(sqrt_inplace_f64_cpu_wrapper, sqrt_inplace_f64);
cpu_unary_inplace_wrapper!(sqrt_inplace_bf16_cpu_wrapper, sqrt_inplace_bf16);
cpu_unary_inplace_wrapper!(sqrt_inplace_f16_cpu_wrapper, sqrt_inplace_f16);
cpu_unary_inplace_wrapper!(rsqrt_inplace_f32_cpu_wrapper, rsqrt_inplace_f32);
cpu_unary_inplace_wrapper!(rsqrt_inplace_f64_cpu_wrapper, rsqrt_inplace_f64);
cpu_unary_inplace_wrapper!(rsqrt_inplace_bf16_cpu_wrapper, rsqrt_inplace_bf16);
cpu_unary_inplace_wrapper!(rsqrt_inplace_f16_cpu_wrapper, rsqrt_inplace_f16);
cpu_unary_inplace_wrapper!(recip_inplace_f32_cpu_wrapper, recip_inplace_f32);
cpu_unary_inplace_wrapper!(recip_inplace_f64_cpu_wrapper, recip_inplace_f64);
cpu_unary_inplace_wrapper!(recip_inplace_bf16_cpu_wrapper, recip_inplace_bf16);
cpu_unary_inplace_wrapper!(recip_inplace_f16_cpu_wrapper, recip_inplace_f16);
cpu_unary_inplace_wrapper!(exp_inplace_f32_cpu_wrapper, exp_inplace_f32);
cpu_unary_inplace_wrapper!(exp_inplace_f64_cpu_wrapper, exp_inplace_f64);
cpu_unary_inplace_wrapper!(exp_inplace_bf16_cpu_wrapper, exp_inplace_bf16);
cpu_unary_inplace_wrapper!(exp_inplace_f16_cpu_wrapper, exp_inplace_f16);
cpu_unary_inplace_wrapper!(log_inplace_f32_cpu_wrapper, log_inplace_f32);
cpu_unary_inplace_wrapper!(log_inplace_f64_cpu_wrapper, log_inplace_f64);
cpu_unary_inplace_wrapper!(log_inplace_bf16_cpu_wrapper, log_inplace_bf16);
cpu_unary_inplace_wrapper!(log_inplace_f16_cpu_wrapper, log_inplace_f16);
cpu_unary_inplace_wrapper!(sin_inplace_f32_cpu_wrapper, sin_inplace_f32);
cpu_unary_inplace_wrapper!(sin_inplace_f64_cpu_wrapper, sin_inplace_f64);
cpu_unary_inplace_wrapper!(sin_inplace_bf16_cpu_wrapper, sin_inplace_bf16);
cpu_unary_inplace_wrapper!(sin_inplace_f16_cpu_wrapper, sin_inplace_f16);
cpu_unary_inplace_wrapper!(cos_inplace_f32_cpu_wrapper, cos_inplace_f32);
cpu_unary_inplace_wrapper!(cos_inplace_f64_cpu_wrapper, cos_inplace_f64);
cpu_unary_inplace_wrapper!(cos_inplace_bf16_cpu_wrapper, cos_inplace_bf16);
cpu_unary_inplace_wrapper!(cos_inplace_f16_cpu_wrapper, cos_inplace_f16);
cpu_unary_inplace_wrapper!(sign_inplace_f32_cpu_wrapper, sign_inplace_f32);
cpu_unary_inplace_wrapper!(sign_inplace_f64_cpu_wrapper, sign_inplace_f64);
cpu_unary_inplace_wrapper!(sign_inplace_bf16_cpu_wrapper, sign_inplace_bf16);
cpu_unary_inplace_wrapper!(sign_inplace_f16_cpu_wrapper, sign_inplace_f16);
cpu_unary_inplace_wrapper!(floor_inplace_f32_cpu_wrapper, floor_inplace_f32);
cpu_unary_inplace_wrapper!(floor_inplace_f64_cpu_wrapper, floor_inplace_f64);
cpu_unary_inplace_wrapper!(floor_inplace_bf16_cpu_wrapper, floor_inplace_bf16);
cpu_unary_inplace_wrapper!(floor_inplace_f16_cpu_wrapper, floor_inplace_f16);
cpu_unary_inplace_wrapper!(ceil_inplace_f32_cpu_wrapper, ceil_inplace_f32);
cpu_unary_inplace_wrapper!(ceil_inplace_f64_cpu_wrapper, ceil_inplace_f64);
cpu_unary_inplace_wrapper!(ceil_inplace_bf16_cpu_wrapper, ceil_inplace_bf16);
cpu_unary_inplace_wrapper!(ceil_inplace_f16_cpu_wrapper, ceil_inplace_f16);
cpu_unary_inplace_wrapper!(round_inplace_f32_cpu_wrapper, round_inplace_f32);
cpu_unary_inplace_wrapper!(round_inplace_f64_cpu_wrapper, round_inplace_f64);
cpu_unary_inplace_wrapper!(round_inplace_bf16_cpu_wrapper, round_inplace_bf16);
cpu_unary_inplace_wrapper!(round_inplace_f16_cpu_wrapper, round_inplace_f16);
cpu_unary_inplace_wrapper!(erf_inplace_f32_cpu_wrapper, erf_inplace_f32);
cpu_unary_inplace_wrapper!(erf_inplace_f64_cpu_wrapper, erf_inplace_f64);
cpu_unary_inplace_wrapper!(erf_inplace_bf16_cpu_wrapper, erf_inplace_bf16);
cpu_unary_inplace_wrapper!(erf_inplace_f16_cpu_wrapper, erf_inplace_f16);
cpu_unary_inplace_wrapper!(gelu_erf_inplace_f32_cpu_wrapper, gelu_erf_inplace_f32);
cpu_unary_inplace_wrapper!(gelu_erf_inplace_f64_cpu_wrapper, gelu_erf_inplace_f64);
cpu_unary_inplace_wrapper!(gelu_erf_inplace_bf16_cpu_wrapper, gelu_erf_inplace_bf16);
cpu_unary_inplace_wrapper!(gelu_erf_inplace_f16_cpu_wrapper, gelu_erf_inplace_f16);
pub(crate) fn clamp_elementwise_f32_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"clamp wrapper expects 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (min, max) = match params {
OpParams::Clamp { min, max } => (*min as f32, *max as f32),
other => {
return Err(Error::Msg(format!(
"clamp wrapper expects OpParams::Clamp, got {other:?}",
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::clamp_f32(in_cpu, out_cpu, min, max)
}
pub(crate) fn powi_elementwise_f32_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"powi wrapper expects 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let exp = match params {
OpParams::PowI { exp } => *exp,
other => {
return Err(Error::Msg(format!(
"powi wrapper expects OpParams::PowI, got {other:?}",
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::powi_f32(in_cpu, out_cpu, exp)
}
macro_rules! cpu_powi_backward_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 2 inputs + 1 output, got {} + {}",
$op_name,
inputs.len(),
outputs.len(),
))
.bt());
}
let exp = match params {
OpParams::PowI { exp } => *exp,
other => {
return Err(Error::Msg(format!(
"{} wrapper expects OpParams::PowI, got {other:?}",
$op_name,
))
.bt())
}
};
let x_guard = read_storage(&inputs[0])?;
let up_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let up_cpu = cpu_input(&up_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(x_cpu, up_cpu, out_cpu, exp)
}
};
}
cpu_powi_backward_wrapper!(
powi_backward_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::powi_backward_f32,
"powi_backward_f32"
);
cpu_powi_backward_wrapper!(
powi_backward_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::powi_backward_f64,
"powi_backward_f64"
);
cpu_powi_backward_wrapper!(
powi_backward_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::powi_backward_bf16,
"powi_backward_bf16"
);
cpu_powi_backward_wrapper!(
powi_backward_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::powi_backward_f16,
"powi_backward_f16"
);
macro_rules! cpu_reduce_wrapper {
($wrapper:ident, $kernel:path, $op_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 input, got {}",
$op_name,
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"{} wrapper expects 1 output, got {}",
$op_name,
outputs.len(),
))
.bt());
}
let dims = match params {
OpParams::Reduce { dims, .. } => dims,
other => {
return Err(Error::Msg(format!(
"{} wrapper expects OpParams::Reduce, got {:?}",
$op_name, other,
))
.bt())
}
};
let input_layout = layouts.first().ok_or_else(|| {
Error::Msg(format!(
"{} wrapper: layouts empty (executor must pass input layout at [0])",
$op_name,
))
.bt()
})?;
let input_shape: &[usize] = input_layout.shape().dims();
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, input_shape, dims)
}
};
}
cpu_reduce_wrapper!(
sum_reduce_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sum_reduce_f32,
"sum_reduce"
);
cpu_reduce_wrapper!(
max_reduce_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::max_reduce_f32,
"max_reduce"
);
cpu_reduce_wrapper!(
min_reduce_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::min_reduce_f32,
"min_reduce"
);
cpu_reduce_wrapper!(
mean_reduce_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::mean_reduce_f32,
"mean_reduce"
);
cpu_reduce_wrapper!(
sum_reduce_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sum_reduce_f64,
"sum_reduce"
);
cpu_reduce_wrapper!(
max_reduce_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::max_reduce_f64,
"max_reduce"
);
cpu_reduce_wrapper!(
min_reduce_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::min_reduce_f64,
"min_reduce"
);
cpu_reduce_wrapper!(
mean_reduce_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::mean_reduce_f64,
"mean_reduce"
);
cpu_reduce_wrapper!(
sum_reduce_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sum_reduce_bf16,
"sum_reduce"
);
cpu_reduce_wrapper!(
max_reduce_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::max_reduce_bf16,
"max_reduce"
);
cpu_reduce_wrapper!(
min_reduce_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::min_reduce_bf16,
"min_reduce"
);
cpu_reduce_wrapper!(
mean_reduce_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::mean_reduce_bf16,
"mean_reduce"
);
cpu_reduce_wrapper!(
sum_reduce_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::sum_reduce_f16,
"sum_reduce"
);
cpu_reduce_wrapper!(
max_reduce_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::max_reduce_f16,
"max_reduce"
);
cpu_reduce_wrapper!(
min_reduce_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::min_reduce_f16,
"min_reduce"
);
cpu_reduce_wrapper!(
mean_reduce_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::mean_reduce_f16,
"mean_reduce"
);
macro_rules! cpu_cast_wrapper {
(
$wrapper:ident,
$target_dtype:expr,
$target_name:literal,
{ $( $source:pat => $kernel:path ),+ $(,)? } $(,)?
) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 {
return Err(Error::Msg(format!(
"cast→{} wrapper expects 1 input, got {}",
$target_name,
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"cast→{} wrapper expects 1 output, got {}",
$target_name,
outputs.len(),
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let source_dtype = in_guard.dtype;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
match source_dtype {
$( $source => $kernel(in_cpu, out_cpu), )+
other => Err(Error::Msg(format!(
"cast→{}: source dtype {:?} not yet wired (Phase C \
extends the cast matrix as needed)",
$target_name, other,
))
.bt()),
}
}
};
}
macro_rules! cpu_conv2d_wrapper {
($name:ident, $kernel:path) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 && inputs.len() != 3 {
return Err(Error::Msg(format!(
"conv2d wrapper expects 2 or 3 inputs (x, weight, [bias]), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"conv2d wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (x_shape, w_shape, out_shape, stride, padding, dilation, groups) = match params {
OpParams::Conv2D {
x_shape,
w_shape,
out_shape,
stride,
padding,
dilation,
groups,
} => (
*x_shape, *w_shape, *out_shape, *stride, *padding, *dilation, *groups,
),
other => {
return Err(Error::Msg(format!(
"conv2d wrapper expects OpParams::Conv2D, got {other:?}",
))
.bt())
}
};
let x_guard = read_storage(&inputs[0])?;
let w_guard = read_storage(&inputs[1])?;
let bias_guard = match inputs.get(2) {
Some(arc) => Some(read_storage(arc)?),
None => None,
};
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let w_cpu = cpu_input(&w_guard)?;
let bias_cpu = match &bias_guard {
Some(g) => Some(cpu_input(g)?),
None => None,
};
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
x_cpu, w_cpu, bias_cpu, out_cpu, x_shape, w_shape, out_shape, stride, padding,
dilation, groups,
)
}
};
}
cpu_conv2d_wrapper!(
conv2d_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::conv2d_f32
);
cpu_conv2d_wrapper!(
conv2d_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::conv2d_f64
);
cpu_conv2d_wrapper!(
conv2d_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::conv2d_bf16
);
cpu_conv2d_wrapper!(
conv2d_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::conv2d_f16
);
macro_rules! cpu_conv_transpose2d_wrapper {
($name:ident, $kernel:path) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 && inputs.len() != 3 {
return Err(Error::Msg(format!(
"conv_transpose2d wrapper expects 2 or 3 inputs (x, weight, [bias]), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"conv_transpose2d wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (x_shape, w_shape, out_shape, stride, padding, dilation, groups) =
match params {
OpParams::ConvTranspose2D {
x_shape,
w_shape,
out_shape,
stride,
padding,
output_padding: _,
dilation,
groups,
} => (
*x_shape, *w_shape, *out_shape, *stride, *padding, *dilation, *groups,
),
other => return Err(Error::Msg(format!(
"conv_transpose2d wrapper expects OpParams::ConvTranspose2D, got {other:?}",
))
.bt()),
};
let x_guard = read_storage(&inputs[0])?;
let w_guard = read_storage(&inputs[1])?;
let bias_guard = match inputs.get(2) {
Some(arc) => Some(read_storage(arc)?),
None => None,
};
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let w_cpu = cpu_input(&w_guard)?;
let bias_cpu = match &bias_guard {
Some(g) => Some(cpu_input(g)?),
None => None,
};
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
x_cpu, w_cpu, bias_cpu, out_cpu, x_shape, w_shape, out_shape, stride, padding,
dilation, groups,
)
}
};
}
cpu_conv_transpose2d_wrapper!(
conv_transpose2d_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::conv_transpose2d_f32
);
cpu_conv_transpose2d_wrapper!(
conv_transpose2d_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::conv_transpose2d_f64
);
cpu_conv_transpose2d_wrapper!(
conv_transpose2d_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::conv_transpose2d_bf16
);
cpu_conv_transpose2d_wrapper!(
conv_transpose2d_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::conv_transpose2d_f16
);
macro_rules! cpu_reduce_sum_to_wrapper {
($name:ident, $kernel:path) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 {
return Err(Error::Msg(format!(
"reduce_sum_to wrapper expects 1 input, got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"reduce_sum_to wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (input_shape, output_shape) = match params {
OpParams::ReduceSumTo {
input_shape,
output_shape,
} => (input_shape.clone(), output_shape.clone()),
other => {
return Err(Error::Msg(format!(
"reduce_sum_to wrapper expects OpParams::ReduceSumTo, got {other:?}",
))
.bt())
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, &input_shape, &output_shape)
}
};
}
cpu_reduce_sum_to_wrapper!(
reduce_sum_to_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_sum_to_f32
);
cpu_reduce_sum_to_wrapper!(
reduce_sum_to_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_sum_to_f64
);
cpu_reduce_sum_to_wrapper!(
reduce_sum_to_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_sum_to_bf16
);
cpu_reduce_sum_to_wrapper!(
reduce_sum_to_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_sum_to_f16
);
macro_rules! cpu_reduce_max_to_wrapper {
($name:ident, $kernel:path) => {
pub(crate) fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 {
return Err(Error::Msg(format!(
"reduce_max_to wrapper expects 1 input, got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"reduce_max_to wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (input_shape, output_shape) = match params {
OpParams::ReduceMaxTo {
input_shape,
output_shape,
} => (input_shape.clone(), output_shape.clone()),
other => {
return Err(Error::Msg(format!(
"reduce_max_to wrapper expects OpParams::ReduceMaxTo, got {other:?}",
))
.bt())
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let in_cpu = cpu_input(&in_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(in_cpu, out_cpu, &input_shape, &output_shape)
}
};
}
cpu_reduce_max_to_wrapper!(
reduce_max_to_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_max_to_f32
);
cpu_reduce_max_to_wrapper!(
reduce_max_to_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_max_to_f64
);
cpu_reduce_max_to_wrapper!(
reduce_max_to_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_max_to_bf16
);
cpu_reduce_max_to_wrapper!(
reduce_max_to_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::reduce_max_to_f16
);
macro_rules! cpu_fused_linear_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 {
return Err(Error::Msg(format!(
"fused_linear wrapper expects 3 inputs (lhs, rhs, bias), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"fused_linear wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
..
} => (lhs_batch_dims, rhs_batch_dims, *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"fused_linear wrapper expects OpParams::Matmul, got {other:?}",
))
.bt())
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let bias_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let lhs_cpu = cpu_input(&lhs_guard)?;
let rhs_cpu = cpu_input(&rhs_guard)?;
let bias_cpu = cpu_input(&bias_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
lhs_cpu,
rhs_cpu,
bias_cpu,
out_cpu,
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
)
}
};
}
cpu_fused_linear_wrapper!(
fused_linear_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::fused_linear_f32
);
cpu_fused_linear_wrapper!(
fused_linear_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::fused_linear_f64
);
cpu_fused_linear_wrapper!(
fused_linear_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::fused_linear_bf16
);
cpu_fused_linear_wrapper!(
fused_linear_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::fused_linear_f16
);
macro_rules! cpu_fused_softmax_cross_entropy_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 {
return Err(Error::Msg(format!(
"fused_softmax_cross_entropy wrapper expects 2 inputs (logits, targets), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"fused_softmax_cross_entropy wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (n_rows, vocab, reduction, ignore_index) = match params {
OpParams::FusedSoftmaxCrossEntropy {
n_rows, vocab, reduction, ignore_index,
} => (*n_rows, *vocab, *reduction, *ignore_index),
other => {
return Err(Error::Msg(format!(
"fused_softmax_cross_entropy wrapper expects \
OpParams::FusedSoftmaxCrossEntropy, got {other:?}",
))
.bt());
}
};
let reduction_tag = match reduction {
fuel_graph::registry::Reduction::Mean => fuel_cpu_backend::byte_kernels::REDUCTION_MEAN,
fuel_graph::registry::Reduction::Sum => fuel_cpu_backend::byte_kernels::REDUCTION_SUM,
fuel_graph::registry::Reduction::None => fuel_cpu_backend::byte_kernels::REDUCTION_NONE,
};
let logits_guard = read_storage(&inputs[0])?;
let targets_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let logits_cpu = cpu_input(&logits_guard)?;
let targets_cpu = cpu_input(&targets_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
logits_cpu, targets_cpu, out_cpu,
n_rows, vocab, reduction_tag, ignore_index,
)
}
};
}
cpu_fused_softmax_cross_entropy_wrapper!(
fused_softmax_cross_entropy_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::fused_softmax_cross_entropy_f32
);
cpu_fused_softmax_cross_entropy_wrapper!(
fused_softmax_cross_entropy_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::fused_softmax_cross_entropy_f64
);
cpu_fused_softmax_cross_entropy_wrapper!(
fused_softmax_cross_entropy_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::fused_softmax_cross_entropy_bf16
);
cpu_fused_softmax_cross_entropy_wrapper!(
fused_softmax_cross_entropy_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::fused_softmax_cross_entropy_f16
);
macro_rules! cpu_causal_conv1d_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 {
return Err(Error::Msg(format!(
"causal_conv1d wrapper expects 3 inputs (x, weight, bias), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"causal_conv1d wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (batch, channels, seq_in, seq_out, kernel, use_silu) = match params {
OpParams::CausalConv1d {
batch,
channels,
seq_in,
seq_out,
kernel,
use_silu,
} => (*batch, *channels, *seq_in, *seq_out, *kernel, *use_silu),
other => {
return Err(Error::Msg(format!(
"causal_conv1d wrapper expects OpParams::CausalConv1d, got {other:?}",
))
.bt());
}
};
let x_guard = read_storage(&inputs[0])?;
let w_guard = read_storage(&inputs[1])?;
let bias_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let w_cpu = cpu_input(&w_guard)?;
let bias_cpu = cpu_input(&bias_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
x_cpu, w_cpu, bias_cpu, out_cpu, batch, channels, seq_in, seq_out, kernel, use_silu,
)
}
};
}
cpu_causal_conv1d_wrapper!(
causal_conv1d_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::causal_conv1d_f32
);
cpu_causal_conv1d_wrapper!(
causal_conv1d_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::causal_conv1d_f64
);
cpu_causal_conv1d_wrapper!(
causal_conv1d_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::causal_conv1d_bf16
);
cpu_causal_conv1d_wrapper!(
causal_conv1d_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::causal_conv1d_f16
);
macro_rules! cpu_selective_scan_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 5 {
return Err(Error::Msg(format!(
"selective_scan wrapper expects 5 inputs (u, delta, a, b, c), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"selective_scan wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (batch, seqlen, dim, dstate, delta_softplus) = match params {
OpParams::SelectiveScan {
batch,
seqlen,
dim,
dstate,
delta_softplus,
} => (*batch, *seqlen, *dim, *dstate, *delta_softplus),
other => {
return Err(Error::Msg(format!(
"selective_scan wrapper expects OpParams::SelectiveScan, got {other:?}",
))
.bt());
}
};
let u_guard = read_storage(&inputs[0])?;
let delta_guard = read_storage(&inputs[1])?;
let a_guard = read_storage(&inputs[2])?;
let b_guard = read_storage(&inputs[3])?;
let c_guard = read_storage(&inputs[4])?;
let mut out_guard = write_storage(&outputs[0])?;
let u_cpu = cpu_input(&u_guard)?;
let delta_cpu = cpu_input(&delta_guard)?;
let a_cpu = cpu_input(&a_guard)?;
let b_cpu = cpu_input(&b_guard)?;
let c_cpu = cpu_input(&c_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
u_cpu,
delta_cpu,
a_cpu,
b_cpu,
c_cpu,
out_cpu,
batch,
seqlen,
dim,
dstate,
delta_softplus,
)
}
};
}
cpu_selective_scan_wrapper!(
selective_scan_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::selective_scan_f32
);
cpu_selective_scan_wrapper!(
selective_scan_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::selective_scan_f64
);
cpu_selective_scan_wrapper!(
selective_scan_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::selective_scan_bf16
);
cpu_selective_scan_wrapper!(
selective_scan_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::selective_scan_f16
);
macro_rules! cpu_nonzero_indices_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 {
return Err(Error::Msg(format!(
"nonzero_indices wrapper expects 1 input, got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"nonzero_indices wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let capacity = match params {
OpParams::NonZeroIndices { capacity, .. } => *capacity,
other => {
return Err(Error::Msg(format!(
"nonzero_indices wrapper expects OpParams::NonZeroIndices, got {other:?}",
))
.bt());
}
};
let x_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(x_cpu, out_cpu, capacity)
}
};
}
cpu_nonzero_indices_wrapper!(
nonzero_indices_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::nonzero_indices_f32
);
cpu_nonzero_indices_wrapper!(
nonzero_indices_u32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::nonzero_indices_u32
);
macro_rules! cpu_ssd_chunk_scan_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 5 {
return Err(Error::Msg(format!(
"ssd_chunk_scan wrapper expects 5 inputs (x, dt, a, b, c), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"ssd_chunk_scan wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (batch, seqlen, heads, head_dim, state_dim, chunk_size) = match params {
OpParams::SsdChunkScan {
batch,
seqlen,
heads,
head_dim,
state_dim,
chunk_size,
} => (*batch, *seqlen, *heads, *head_dim, *state_dim, *chunk_size),
other => {
return Err(Error::Msg(format!(
"ssd_chunk_scan wrapper expects OpParams::SsdChunkScan, got {other:?}",
))
.bt());
}
};
let x_guard = read_storage(&inputs[0])?;
let dt_guard = read_storage(&inputs[1])?;
let a_guard = read_storage(&inputs[2])?;
let b_guard = read_storage(&inputs[3])?;
let c_guard = read_storage(&inputs[4])?;
let mut out_guard = write_storage(&outputs[0])?;
let x_cpu = cpu_input(&x_guard)?;
let dt_cpu = cpu_input(&dt_guard)?;
let a_cpu = cpu_input(&a_guard)?;
let b_cpu = cpu_input(&b_guard)?;
let c_cpu = cpu_input(&c_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
x_cpu, dt_cpu, a_cpu, b_cpu, c_cpu, out_cpu, batch, seqlen, heads, head_dim,
state_dim, chunk_size,
)
}
};
}
cpu_ssd_chunk_scan_wrapper!(
ssd_chunk_scan_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ssd_chunk_scan_f32
);
cpu_ssd_chunk_scan_wrapper!(
ssd_chunk_scan_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ssd_chunk_scan_f64
);
cpu_ssd_chunk_scan_wrapper!(
ssd_chunk_scan_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ssd_chunk_scan_bf16
);
cpu_ssd_chunk_scan_wrapper!(
ssd_chunk_scan_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::ssd_chunk_scan_f16
);
macro_rules! cpu_nf4_matmul_wrapper {
($wrapper:ident, $kernel:path) => {
fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 {
return Err(Error::Msg(format!(
"nf4_matmul wrapper expects 3 inputs (activations, w_packed, absmax), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"nf4_matmul wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (batch, m, n, k, block_size) = match params {
OpParams::Nf4Matmul {
batch,
m,
n,
k,
block_size,
} => (*batch, *m, *n, *k, *block_size),
other => {
return Err(Error::Msg(format!(
"nf4_matmul wrapper expects OpParams::Nf4Matmul, got {other:?}",
))
.bt());
}
};
let a_guard = read_storage(&inputs[0])?;
let w_guard = read_storage(&inputs[1])?;
let abs_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let a_cpu = cpu_input(&a_guard)?;
let w_cpu = cpu_input(&w_guard)?;
let abs_cpu = cpu_input(&abs_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(a_cpu, w_cpu, abs_cpu, out_cpu, batch, m, n, k, block_size)
}
};
}
cpu_nf4_matmul_wrapper!(
nf4_matmul_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::nf4_matmul_f32
);
cpu_nf4_matmul_wrapper!(
nf4_matmul_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::nf4_matmul_f16
);
cpu_nf4_matmul_wrapper!(
nf4_matmul_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::nf4_matmul_bf16
);
macro_rules! cpu_flash_attn_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 && inputs.len() != 4 {
return Err(Error::Msg(format!(
"flash_attn wrapper expects 3 or 4 inputs (q, k, v, [alibi]), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"flash_attn wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (b, hq, hkv, sq, sk, d, k_len, scale, causal, wl, wr, softcap) = match params {
OpParams::FlashAttn {
b,
hq,
hkv,
sq,
sk,
d,
k_len,
softmax_scale,
causal,
window_size_left,
window_size_right,
softcap,
} => (
*b,
*hq,
*hkv,
*sq,
*sk,
*d,
*k_len,
*softmax_scale,
*causal,
*window_size_left,
*window_size_right,
*softcap,
),
other => {
return Err(Error::Msg(format!(
"flash_attn wrapper expects OpParams::FlashAttn, got {other:?}",
))
.bt())
}
};
let q_guard = read_storage(&inputs[0])?;
let k_guard = read_storage(&inputs[1])?;
let v_guard = read_storage(&inputs[2])?;
let alibi_guard = match inputs.get(3) {
Some(arc) => Some(read_storage(arc)?),
None => None,
};
let mut out_guard = write_storage(&outputs[0])?;
let q_cpu = cpu_input(&q_guard)?;
let k_cpu = cpu_input(&k_guard)?;
let v_cpu = cpu_input(&v_guard)?;
let alibi_cpu = match &alibi_guard {
Some(g) => Some(cpu_input(g)?),
None => None,
};
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
q_cpu, k_cpu, v_cpu, alibi_cpu, out_cpu, b, hq, hkv, sq, sk, d, k_len, scale,
causal, wl, wr, softcap,
)
}
};
}
cpu_flash_attn_wrapper!(
flash_attn_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_f32
);
cpu_flash_attn_wrapper!(
flash_attn_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_f64
);
cpu_flash_attn_wrapper!(
flash_attn_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_bf16
);
cpu_flash_attn_wrapper!(
flash_attn_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_f16
);
macro_rules! cpu_flash_attn_backward_wrapper {
($wrapper:ident, $kernel:path, $which:expr) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 4 && inputs.len() != 5 {
return Err(Error::Msg(format!(
"flash_attn_backward wrapper expects 4 or 5 inputs (q, k, v, do, [alibi]), got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"flash_attn_backward wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (b, hq, hkv, sq, sk, d, scale, causal, wl, wr, softcap) = match params {
OpParams::FlashAttn {
b, hq, hkv, sq, sk, d,
softmax_scale, causal,
window_size_left, window_size_right, softcap,
k_len: _,
} => (
*b, *hq, *hkv, *sq, *sk, *d,
*softmax_scale, *causal,
*window_size_left, *window_size_right, *softcap,
),
other => {
return Err(Error::Msg(format!(
"flash_attn_backward wrapper expects OpParams::FlashAttn, got {other:?}",
))
.bt())
}
};
let q_guard = read_storage(&inputs[0])?;
let k_guard = read_storage(&inputs[1])?;
let v_guard = read_storage(&inputs[2])?;
let do_guard = read_storage(&inputs[3])?;
let alibi_guard = match inputs.get(4) {
Some(arc) => Some(read_storage(arc)?),
None => None,
};
let mut out_guard = write_storage(&outputs[0])?;
let q_cpu = cpu_input(&q_guard)?;
let k_cpu = cpu_input(&k_guard)?;
let v_cpu = cpu_input(&v_guard)?;
let do_cpu = cpu_input(&do_guard)?;
let alibi_cpu = match &alibi_guard {
Some(g) => Some(cpu_input(g)?),
None => None,
};
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
q_cpu, k_cpu, v_cpu, do_cpu, alibi_cpu, out_cpu, $which,
b, hq, hkv, sq, sk, d,
scale, causal, wl, wr, softcap,
)
}
};
}
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_q_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_f32,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::Q
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_k_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_f32,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::K
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_v_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_f32,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::V
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_q_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_f64,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::Q
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_k_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_f64,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::K
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_v_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_f64,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::V
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_q_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_bf16,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::Q
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_k_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_bf16,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::K
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_v_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_bf16,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::V
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_q_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_f16,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::Q
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_k_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_f16,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::K
);
cpu_flash_attn_backward_wrapper!(
flash_attn_backward_v_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::flash_attn_backward_f16,
fuel_cpu_backend::byte_kernels::FaBackwardWhich::V
);
macro_rules! cpu_paged_attn_wrapper {
($wrapper:ident, $kernel:path) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 5 && inputs.len() != 6 {
return Err(Error::Msg(format!(
"paged_attn wrapper expects 5 or 6 inputs, got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"paged_attn wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (b, hq, hkv, sq, d, block_size, max_blocks_per_seq, num_blocks, scale, softcap) =
match params {
OpParams::PagedAttn {
b,
hq,
hkv,
sq,
d,
block_size,
max_blocks_per_seq,
num_blocks,
softmax_scale,
softcap,
} => (
*b,
*hq,
*hkv,
*sq,
*d,
*block_size,
*max_blocks_per_seq,
*num_blocks,
*softmax_scale,
*softcap,
),
other => {
return Err(Error::Msg(format!(
"paged_attn wrapper expects OpParams::PagedAttn, got {other:?}",
))
.bt())
}
};
let q_g = read_storage(&inputs[0])?;
let kc_g = read_storage(&inputs[1])?;
let vc_g = read_storage(&inputs[2])?;
let bt_g = read_storage(&inputs[3])?;
let cl_g = read_storage(&inputs[4])?;
let alibi_g = match inputs.get(5) {
Some(arc) => Some(read_storage(arc)?),
None => None,
};
if bt_g.dtype != DType::U32 || cl_g.dtype != DType::U32 {
return Err(Error::Msg(format!(
"paged_attn: block_table and context_lens must be U32, got {:?} / {:?}",
bt_g.dtype, cl_g.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let q_cpu = cpu_input(&q_g)?;
let kc_cpu = cpu_input(&kc_g)?;
let vc_cpu = cpu_input(&vc_g)?;
let bt_cpu = cpu_input(&bt_g)?;
let cl_cpu = cpu_input(&cl_g)?;
let alibi_cpu = match &alibi_g {
Some(g) => Some(cpu_input(g)?),
None => None,
};
let out_cpu = cpu_output(&mut out_guard)?;
$kernel(
q_cpu,
kc_cpu,
vc_cpu,
bt_cpu,
cl_cpu,
alibi_cpu,
out_cpu,
b,
hq,
hkv,
sq,
d,
block_size,
max_blocks_per_seq,
num_blocks,
scale,
softcap,
)
}
};
}
cpu_paged_attn_wrapper!(
paged_attn_f32_cpu_wrapper,
fuel_cpu_backend::byte_kernels::paged_attn_f32
);
cpu_paged_attn_wrapper!(
paged_attn_f64_cpu_wrapper,
fuel_cpu_backend::byte_kernels::paged_attn_f64
);
cpu_paged_attn_wrapper!(
paged_attn_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::paged_attn_bf16
);
cpu_paged_attn_wrapper!(
paged_attn_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::paged_attn_f16
);
cpu_cast_wrapper!(
cast_to_f32_cpu_wrapper,
DType::F32,
"f32",
{
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_f32,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_f32,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_f32,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_f32,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_f32,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_f32,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_f32,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_f32,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_f32,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_f32,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_f32,
},
);
cpu_cast_wrapper!(
cast_to_f64_cpu_wrapper,
DType::F64,
"f64",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_f64,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_f64,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_f64,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_f64,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_f64,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_f64,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_f64,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_f64,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_f64,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_f64,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_f64,
},
);
cpu_cast_wrapper!(
cast_to_bf16_cpu_wrapper,
DType::BF16,
"bf16",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_bf16,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_bf16,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_bf16,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_bf16,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_bf16,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_bf16,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_bf16,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_bf16,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_bf16,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_bf16,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_bf16,
},
);
cpu_cast_wrapper!(
cast_to_f16_cpu_wrapper,
DType::F16,
"f16",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_f16,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_f16,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_f16,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_f16,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_f16,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_f16,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_f16,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_f16,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_f16,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_f16,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_f16,
},
);
cpu_cast_wrapper!(
cast_to_f8e4m3_cpu_wrapper,
DType::F8E4M3,
"f8e4m3",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_f8e4m3,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_f8e4m3,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_f8e4m3,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_f8e4m3,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_f8e4m3,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_f8e4m3,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_f8e4m3,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_f8e4m3,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_f8e4m3,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_f8e4m3,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_f8e4m3,
},
);
cpu_cast_wrapper!(
cast_to_u8_cpu_wrapper,
DType::U8,
"u8",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_u8,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_u8,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_u8,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_u8,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_u8,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_u8,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_u8,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_u8,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_u8,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_u8,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_u8,
},
);
cpu_cast_wrapper!(
cast_to_i8_cpu_wrapper,
DType::I8,
"i8",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_i8,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_i8,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_i8,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_i8,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_i8,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_i8,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_i8,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_i8,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_i8,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_i8,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_i8,
},
);
cpu_cast_wrapper!(
cast_to_u32_cpu_wrapper,
DType::U32,
"u32",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_u32,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_u32,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_u32,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_u32,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_u32,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_u32,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_u32,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_u32,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_u32,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_u32,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_u32,
},
);
cpu_cast_wrapper!(
cast_to_i16_cpu_wrapper,
DType::I16,
"i16",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_i16,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_i16,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_i16,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_i16,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_i16,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_i16,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_i16,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_i16,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_i16,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_i16,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_i16,
},
);
cpu_cast_wrapper!(
cast_to_i32_cpu_wrapper,
DType::I32,
"i32",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_i32,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_i32,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_i32,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_i32,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_i32,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_i32,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_i32,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_i32,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_i32,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_i32,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_i32,
},
);
cpu_cast_wrapper!(
cast_to_i64_cpu_wrapper,
DType::I64,
"i64",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_i64,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_i64,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_i64,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_i64,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_i64,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_i64,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_i64,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_i64,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_i64,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_i64,
DType::Bool => fuel_cpu_backend::byte_kernels::cast_bool_to_i64,
},
);
cpu_cast_wrapper!(
cast_to_bool_cpu_wrapper,
DType::Bool,
"bool",
{
DType::F32 => fuel_cpu_backend::byte_kernels::cast_f32_to_bool,
DType::F64 => fuel_cpu_backend::byte_kernels::cast_f64_to_bool,
DType::F16 => fuel_cpu_backend::byte_kernels::cast_f16_to_bool,
DType::BF16 => fuel_cpu_backend::byte_kernels::cast_bf16_to_bool,
DType::F8E4M3 => fuel_cpu_backend::byte_kernels::cast_f8e4m3_to_bool,
DType::U8 => fuel_cpu_backend::byte_kernels::cast_u8_to_bool,
DType::I8 => fuel_cpu_backend::byte_kernels::cast_i8_to_bool,
DType::U32 => fuel_cpu_backend::byte_kernels::cast_u32_to_bool,
DType::I16 => fuel_cpu_backend::byte_kernels::cast_i16_to_bool,
DType::I32 => fuel_cpu_backend::byte_kernels::cast_i32_to_bool,
DType::I64 => fuel_cpu_backend::byte_kernels::cast_i64_to_bool,
},
);
pub(crate) fn matmul_f32_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 {
return Err(Error::Msg(format!(
"matmul wrapper expects 2 inputs, got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"matmul wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k, m_compute) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
m_compute,
} => (lhs_batch_dims, rhs_batch_dims, *m, *n, *k, m_compute),
other => {
return Err(Error::Msg(format!(
"matmul wrapper expects OpParams::Matmul, got {other:?}",
))
.bt());
}
};
let rows = match m_compute {
MatmulM::All => m,
MatmulM::Rows(c) => *c,
MatmulM::Deferred(d) => {
return Err(Error::Msg(format!(
"matmul_f32 wrapper: unresolved data-determined row count {d:?} \
(must be resolved at execute before the kernel runs)",
))
.bt());
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let lhs_cpu = cpu_input(&lhs_guard)?;
let rhs_cpu = cpu_input(&rhs_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::matmul_f32_capacity(
lhs_cpu,
rhs_cpu,
out_cpu,
lhs_batch_dims,
rhs_batch_dims,
m,
rows,
n,
k,
)
}
macro_rules! cpu_matmul_wrapper {
($wrapper:ident, $kernel:path, $type_name:literal) => {
pub(crate) fn $wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 {
return Err(Error::Msg(format!(
"matmul wrapper expects 2 inputs, got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"matmul wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
..
} => (lhs_batch_dims, rhs_batch_dims, *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"matmul wrapper expects OpParams::Matmul, got {other:?}",
))
.bt())
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let lhs_cpu = cpu_input(&lhs_guard)?;
let rhs_cpu = cpu_input(&rhs_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
let _ = $type_name;
$kernel(
lhs_cpu,
rhs_cpu,
out_cpu,
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
)
}
};
}
cpu_matmul_wrapper!(
matmul_bf16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::matmul_bf16,
"matmul_bf16"
);
cpu_matmul_wrapper!(
matmul_f16_cpu_wrapper,
fuel_cpu_backend::byte_kernels::matmul_f16,
"matmul_f16"
);
cpu_matmul_wrapper!(
matmul_i8_cpu_wrapper,
fuel_cpu_backend::byte_kernels::matmul_i8,
"matmul_i8"
);
cpu_matmul_wrapper!(
matmul_u8_cpu_wrapper,
fuel_cpu_backend::byte_kernels::matmul_u8,
"matmul_u8"
);
pub(crate) fn matmul_f64_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 {
return Err(Error::Msg(format!(
"matmul wrapper expects 2 inputs, got {}",
inputs.len(),
))
.bt());
}
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"matmul wrapper expects 1 output, got {}",
outputs.len(),
))
.bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
..
} => (lhs_batch_dims, rhs_batch_dims, *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"matmul wrapper expects OpParams::Matmul, got {other:?}",
))
.bt());
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let lhs_cpu = cpu_input(&lhs_guard)?;
let rhs_cpu = cpu_input(&rhs_guard)?;
let out_cpu = cpu_output(&mut out_guard)?;
fuel_cpu_backend::byte_kernels::matmul_f64(
lhs_cpu,
rhs_cpu,
out_cpu,
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
)
}
fn copy_from_cpu_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"copy_from_cpu_wrapper: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let src = cpu_input(&in_guard)?;
let mut out_guard = write_storage(&outputs[0])?;
let n_out = out_guard.inner.len_bytes();
let n_src = src.len_bytes();
let n = n_src.min(n_out);
let src_slice = &src.bytes()[..n];
match &mut out_guard.inner {
BackendStorage::Cpu(dst) => {
dst.bytes_mut()[..n].copy_from_slice(src_slice);
}
#[cfg(feature = "cuda")]
BackendStorage::Cuda(dst) => {
if n == n_out {
dst.write_from_host(src_slice)?;
} else {
let mut staged = vec![0_u8; n_out];
staged[..n].copy_from_slice(src_slice);
dst.write_from_host(&staged)?;
}
}
#[cfg(feature = "vulkan")]
BackendStorage::Vulkan(dst) => {
let backend = dst
.backend()
.ok_or_else(|| {
Error::Msg(
"copy_from_cpu_wrapper: Vulkan output has no attached \
backend handle. The executor's Op::Copy arm must allocate \
via VulkanBackend::alloc_bytes_handle (which attaches \
the handle)."
.to_string(),
)
.bt()
})?
.clone();
if n == n_out {
backend.write_bytes(dst, src_slice)?;
} else {
let mut staged = vec![0_u8; n_out];
staged[..n].copy_from_slice(src_slice);
backend.write_bytes(dst, &staged)?;
}
}
#[allow(unreachable_patterns)]
other => {
return Err(Error::Msg(format!(
"copy_from_cpu_wrapper: output backend not wired ({other:?}); \
CPU + CUDA + Vulkan covered, Metal extends when its \
byte-storage substrate is ready.",
))
.bt());
}
}
Ok(())
}
const CPU_ELEMENTWISE_BINARY_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/cpu/elementwise-binary.fkc.md");
fn register_cpu_binary_from_contract(table: &mut KernelBindingTable) {
let provider = crate::fkc::import_bundle_str(
CPU_ELEMENTWISE_BINARY_CONTRACT,
&crate::fkc::CpuLinkRegistry,
)
.expect(
"authored CPU elementwise-binary contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"elementwise-binary contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU elementwise-binary contract must register into the binding table");
}
const CPU_AFFINE_CLAMP_POWI_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/cpu/affine-clamp-powi.fkc.md");
fn register_cpu_affine_clamp_powi_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_AFFINE_CLAMP_POWI_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU affine/clamp/powi contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"affine/clamp/powi contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU affine/clamp/powi contract must register into the binding table");
}
const CPU_ELEMENTWISE_UNARY_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/cpu/elementwise-unary.fkc.md");
fn register_cpu_unary_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_ELEMENTWISE_UNARY_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU elementwise-unary contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"elementwise-unary contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU elementwise-unary contract must register into the binding table");
}
const CPU_COMPARE_WHERE_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/cpu/compare-where.fkc.md");
fn register_cpu_compare_where_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_COMPARE_WHERE_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU compare/where contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"compare/where contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU compare/where contract must register into the binding table");
}
const CPU_REDUCE_CONTRACT: &str = include_str!("../../docs/kernel-contracts/cpu/reduce.fkc.md");
fn register_cpu_reduce_from_contract(table: &mut KernelBindingTable) {
let provider = crate::fkc::import_bundle_str(CPU_REDUCE_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU reduce contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"reduce contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU reduce contract must register into the binding table");
}
const CPU_REDUCE_TO_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/cpu/reduce-to.fkc.md");
fn register_cpu_reduce_to_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_REDUCE_TO_CONTRACT, &crate::fkc::CpuLinkRegistry).expect(
"authored CPU reduce-to contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"reduce-to contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU reduce-to contract must register into the binding table");
}
const CPU_NORM_CONTRACT: &str = include_str!("../../docs/kernel-contracts/cpu/norm.fkc.md");
fn register_cpu_norm_from_contract(table: &mut KernelBindingTable) {
let provider = crate::fkc::import_bundle_str(CPU_NORM_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU norm contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"norm forward contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU norm contract must register into the binding table");
}
const CPU_NORM_BACKWARD_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/cpu/norm-backward.fkc.md");
fn register_cpu_norm_backward_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_NORM_BACKWARD_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU norm-backward contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"norm backward contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU norm-backward contract must register into the binding table");
}
const CPU_ROPE_CONTRACT: &str = include_str!("../../docs/kernel-contracts/cpu/rope.fkc.md");
fn register_cpu_rope_from_contract(table: &mut KernelBindingTable) {
let provider = crate::fkc::import_bundle_str(CPU_ROPE_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU rope contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"rope contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU rope contract must register into the binding table");
}
const CPU_SSM_CONTRACT: &str = include_str!("../../docs/kernel-contracts/cpu/ssm.fkc.md");
fn register_cpu_ssm_from_contract(table: &mut KernelBindingTable) {
let provider = crate::fkc::import_bundle_str(CPU_SSM_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU ssm contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"ssm contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU ssm contract must register into the binding table");
}
const CPU_CONV_CONTRACT: &str = include_str!("../../docs/kernel-contracts/cpu/conv.fkc.md");
fn register_cpu_conv_from_contract(table: &mut KernelBindingTable) {
let provider = crate::fkc::import_bundle_str(CPU_CONV_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU conv contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"conv contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU conv contract must register into the binding table");
}
const CPU_PADDING_CONTRACT: &str = include_str!("../../docs/kernel-contracts/cpu/padding.fkc.md");
fn register_cpu_padding_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_PADDING_CONTRACT, &crate::fkc::CpuLinkRegistry).expect(
"authored CPU padding contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"padding contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU padding contract must register into the binding table");
}
const CPU_SHAPE_OPS_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/cpu/shape-ops.fkc.md");
fn register_cpu_shape_ops_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_SHAPE_OPS_CONTRACT, &crate::fkc::CpuLinkRegistry).expect(
"authored CPU shape-ops contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"shape-ops contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU shape-ops contract must register into the binding table");
}
const CPU_MATMUL_CONTRACT: &str = include_str!("../../docs/kernel-contracts/cpu/matmul.fkc.md");
fn register_cpu_matmul_from_contract(table: &mut KernelBindingTable) {
let provider = crate::fkc::import_bundle_str(CPU_MATMUL_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU matmul contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"matmul contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU matmul contract must register into the binding table");
}
const CPU_ATTENTION_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/cpu/attention.fkc.md");
fn register_cpu_attention_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_ATTENTION_CONTRACT, &crate::fkc::CpuLinkRegistry).expect(
"authored CPU attention contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"attention contract declares no fused ops (all op_kind, incl. PagedAttn)",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU attention contract must register into the binding table");
}
const CPU_INPLACE_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/cpu/inplace-unary-affine.fkc.md");
fn register_cpu_inplace_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_INPLACE_CONTRACT, &crate::fkc::CpuLinkRegistry).expect(
"authored CPU in-place contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"in-place contract declares no fused ops (all op_kind or describe-only umbrella)",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU in-place contract must register into the binding table");
}
const CPU_CAST_CONTRACT: &str = include_str!("../../docs/kernel-contracts/cpu/cast.fkc.md");
fn register_cpu_cast_from_contract(table: &mut KernelBindingTable) {
let provider = crate::fkc::import_bundle_str(CPU_CAST_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored CPU cast contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"cast contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU cast contract must register into the binding table");
}
const CPU_INDEXING_CONTRACT: &str = include_str!("../../docs/kernel-contracts/cpu/indexing.fkc.md");
fn register_cpu_indexing_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(CPU_INDEXING_CONTRACT, &crate::fkc::CpuLinkRegistry).expect(
"authored CPU indexing contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"indexing contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("CPU indexing contract must register into the binding table");
}
pub fn register_cpu_kernels(table: &mut KernelBindingTable) {
use OpKind::*;
let cpu = BackendId::Cpu;
let f32_dt = DType::F32;
let f64_dt = DType::F64;
let bf16_dt = DType::BF16;
let f16_dt = DType::F16;
let u32_dt = DType::U32;
let unary = |t: DType| [t, t]; let u8_dt = DType::U8;
register_cpu_binary_from_contract(table);
register_cpu_affine_clamp_powi_from_contract(table);
register_cpu_unary_from_contract(table);
register_cpu_compare_where_from_contract(table);
register_cpu_reduce_from_contract(table);
register_cpu_reduce_to_from_contract(table);
register_cpu_norm_from_contract(table);
register_cpu_norm_backward_from_contract(table);
register_cpu_rope_from_contract(table);
register_cpu_ssm_from_contract(table);
table.register(
NonZeroIndices,
&[f32_dt, u32_dt],
cpu,
nonzero_indices_f32_cpu_wrapper,
);
table.register(
NonZeroIndices,
&[u32_dt, u32_dt],
cpu,
nonzero_indices_u32_cpu_wrapper,
);
register_cpu_conv_from_contract(table);
register_cpu_padding_from_contract(table);
register_cpu_shape_ops_from_contract(table);
register_cpu_matmul_from_contract(table);
register_cpu_attention_from_contract(table);
register_cpu_inplace_from_contract(table);
register_cpu_cast_from_contract(table);
register_cpu_indexing_from_contract(table);
table.register(
Nf4Matmul,
&[DType::F32, DType::U8, DType::F32, DType::F32],
cpu,
nf4_matmul_f32_cpu_wrapper,
);
table.register(
Nf4Matmul,
&[DType::F16, DType::U8, DType::F32, DType::F16],
cpu,
nf4_matmul_f16_cpu_wrapper,
);
table.register(
Nf4Matmul,
&[DType::BF16, DType::U8, DType::F32, DType::BF16],
cpu,
nf4_matmul_bf16_cpu_wrapper,
);
table.register(Triu, &unary(f32_dt), cpu, triu_cpu_wrapper);
table.register(Triu, &unary(f64_dt), cpu, triu_cpu_wrapper);
table.register(Triu, &unary(bf16_dt), cpu, triu_cpu_wrapper);
table.register(Triu, &unary(f16_dt), cpu, triu_cpu_wrapper);
table.register(Triu, &unary(u32_dt), cpu, triu_cpu_wrapper);
table.register(Triu, &unary(u8_dt), cpu, triu_cpu_wrapper);
table.register(Tril, &unary(f32_dt), cpu, tril_cpu_wrapper);
table.register(Tril, &unary(f64_dt), cpu, tril_cpu_wrapper);
table.register(Tril, &unary(bf16_dt), cpu, tril_cpu_wrapper);
table.register(Tril, &unary(f16_dt), cpu, tril_cpu_wrapper);
table.register(Tril, &unary(u32_dt), cpu, tril_cpu_wrapper);
table.register(Tril, &unary(u8_dt), cpu, tril_cpu_wrapper);
table.register(
QMatMul,
&[f32_dt, u32_dt, f32_dt],
cpu,
qmatmul_f32_cpu_wrapper,
);
for dt in [f32_dt, f64_dt, bf16_dt, f16_dt] {
table.register(ArgMaxDim, &[dt, u32_dt], cpu, argmax_dim_u32_cpu_dispatch);
table.register(ArgMinDim, &[dt, u32_dt], cpu, argmin_dim_u32_cpu_dispatch);
}
let copy_dtypes = [
f32_dt,
f64_dt,
bf16_dt,
f16_dt,
u32_dt,
u8_dt,
DType::I16,
DType::I32,
DType::I64,
DType::Bool,
];
for dt in copy_dtypes {
table.register(Copy, &[dt, dt], cpu, copy_from_cpu_wrapper);
}
table.fill_unset_cpu_precision(crate::fused::PrecisionGuarantee::PRIMITIVE_DETERMINISTIC_CPU);
table.fill_unset_cpu_cost(crate::cost::default_cost_for_op_kind);
}
#[cfg(feature = "cuda")]
pub(crate) fn cuda_input(s: &Storage) -> Result<&fuel_cuda_backend::CudaStorageBytes> {
match &s.inner {
BackendStorage::Cuda(c) => Ok(c),
#[allow(unreachable_patterns)]
_ => Err(Error::Msg("cuda kernel wrapper called with non-CUDA input".to_string()).bt()),
}
}
#[cfg(feature = "cuda")]
pub(crate) fn cuda_output(s: &mut Storage) -> Result<&mut fuel_cuda_backend::CudaStorageBytes> {
match &mut s.inner {
BackendStorage::Cuda(c) => Ok(c),
#[allow(unreachable_patterns)]
_ => Err(Error::Msg("cuda kernel wrapper called with non-CUDA output".to_string()).bt()),
}
}
#[cfg(feature = "cuda")]
fn copy_from_cuda_wrapper(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"copy_from_cuda_wrapper: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let cuda_src = cuda_input(&in_guard)?;
let mut out_guard = write_storage(&outputs[0])?;
match &mut out_guard.inner {
BackendStorage::Cpu(_) => {
let bytes = cuda_src.to_cpu_bytes_finer()?;
let dst = cpu_output(&mut out_guard)?;
let n = bytes.len().min(dst.len_bytes());
dst.bytes_mut()[..n].copy_from_slice(&bytes[..n]);
Ok(())
}
BackendStorage::Cuda(_) => {
let dst = cuda_output(&mut out_guard)?;
dst.copy_from_device(cuda_src)?;
Ok(())
}
#[allow(unreachable_patterns)]
other => Err(Error::Msg(format!(
"copy_from_cuda_wrapper: unsupported output substrate {:?} \
(CUDA sources copy to CPU or CUDA outputs only; cross-vendor \
GPU transfer goes through host staging as two Copy hops)",
std::mem::discriminant(other),
))
.bt()),
}
}
#[cfg(feature = "cuda")]
pub fn register_cuda_kernels(table: &mut KernelBindingTable) {
use OpKind::*;
let cuda = BackendId::Cuda;
let copy_dtypes = [
DType::F32,
DType::BF16,
DType::F16,
DType::U32,
DType::F64,
DType::U8,
DType::I16,
DType::I32,
DType::I64,
DType::Bool,
];
for dt in copy_dtypes {
table.register(Copy, &[dt, dt], cuda, copy_from_cuda_wrapper);
}
}
static GLOBAL_REGISTRY: OnceLock<RwLock<CapabilityRegistry>> = OnceLock::new();
static TOPOLOGY_GENERATION: AtomicU64 = AtomicU64::new(0);
pub fn topology_generation() -> u64 {
TOPOLOGY_GENERATION.load(Ordering::Acquire)
}
pub fn bump_topology_generation() {
TOPOLOGY_GENERATION.fetch_add(1, Ordering::Release);
}
static DEVICE_INFLIGHT: OnceLock<RwLock<HashMap<DeviceLocation, AtomicU32>>> = OnceLock::new();
fn device_inflight_table() -> &'static RwLock<HashMap<DeviceLocation, AtomicU32>> {
DEVICE_INFLIGHT.get_or_init(|| RwLock::new(HashMap::new()))
}
pub fn inflight_inc(loc: DeviceLocation) {
let table = device_inflight_table();
if let Ok(guard) = table.read()
&& let Some(slot) = guard.get(&loc)
{
slot.fetch_add(1, Ordering::Relaxed);
return;
}
if let Ok(mut guard) = table.write() {
guard
.entry(loc)
.or_insert_with(|| AtomicU32::new(0))
.fetch_add(1, Ordering::Relaxed);
}
}
pub fn inflight_dec(loc: DeviceLocation) {
let table = device_inflight_table();
if let Ok(guard) = table.read()
&& let Some(slot) = guard.get(&loc)
{
let _ = slot.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
Some(v.saturating_sub(1))
});
}
}
pub fn inflight_count(loc: DeviceLocation) -> u32 {
let table = device_inflight_table();
table
.read()
.ok()
.and_then(|guard| guard.get(&loc).map(|slot| slot.load(Ordering::Relaxed)))
.unwrap_or(0)
}
static GLOBAL_BINDINGS: OnceLock<RwLock<Arc<KernelBindingTable>>> = OnceLock::new();
static BINDINGS_WRITER: Mutex<()> = Mutex::new(());
fn bindings_cell() -> &'static RwLock<Arc<KernelBindingTable>> {
GLOBAL_BINDINGS.get_or_init(|| {
lock_trace(" BINDINGS init: begin");
let mut t = KernelBindingTable::new();
register_cpu_kernels(&mut t);
lock_trace(" BINDINGS init: cpu done, entering optional backends");
register_optional_backends(&mut t);
lock_trace(" BINDINGS init: optional backends done");
t.finalize()
.expect("KernelBindingTable: duplicate kernel registration at init");
RwLock::new(Arc::new(t))
})
}
fn default_cpu_caps() -> BackendCapabilities {
use OpKind::*;
use std::collections::HashSet;
let mut op_dtype_support = HashSet::new();
let f32_dt = DType::F32;
for op in [
AddElementwise,
SubElementwise,
MulElementwise,
DivElementwise,
ReluElementwise,
NegElementwise,
SqrElementwise,
SqrtElementwise,
RecipElementwise,
AbsElementwise,
TanhElementwise,
ExpElementwise,
LogElementwise,
SinElementwise,
CosElementwise,
SigmoidElementwise,
SiluElementwise,
GeluElementwise,
StepElementwise,
SumReduce,
MaxReduce,
MinReduce,
MeanReduce,
MatMul,
Conv2D,
ConvTranspose2D,
ReduceSumTo,
ReduceMaxTo,
FusedLinear,
FlashAttn,
PagedAttn,
Affine,
ClampElementwise,
PowIElementwise,
MaximumElementwise,
MinimumElementwise,
Concat,
SoftmaxLastDim,
RmsNormLastDim,
LayerNormLastDim,
IndexSelect,
Gather,
Rope,
IndexAdd,
ScatterAdd,
QMatMul,
Copy,
] {
op_dtype_support.insert((op, f32_dt));
}
for op in [
AddElementwise,
SubElementwise,
MulElementwise,
DivElementwise,
ReluElementwise,
NegElementwise,
SqrElementwise,
SqrtElementwise,
RecipElementwise,
AbsElementwise,
TanhElementwise,
ExpElementwise,
LogElementwise,
SinElementwise,
CosElementwise,
SigmoidElementwise,
SiluElementwise,
GeluElementwise,
StepElementwise,
MaximumElementwise,
MinimumElementwise,
SumReduce,
MaxReduce,
MinReduce,
MeanReduce,
MatMul,
Conv2D,
ConvTranspose2D,
ReduceSumTo,
ReduceMaxTo,
FusedLinear,
FlashAttn,
PagedAttn,
] {
op_dtype_support.insert((op, DType::F64));
}
let half_elementwise_ops = [
AddElementwise,
SubElementwise,
MulElementwise,
DivElementwise,
MaximumElementwise,
MinimumElementwise,
ReluElementwise,
NegElementwise,
SqrElementwise,
SqrtElementwise,
RecipElementwise,
AbsElementwise,
TanhElementwise,
ExpElementwise,
LogElementwise,
SinElementwise,
CosElementwise,
SigmoidElementwise,
SiluElementwise,
GeluElementwise,
StepElementwise,
];
for op in half_elementwise_ops {
op_dtype_support.insert((op, DType::BF16));
op_dtype_support.insert((op, DType::F16));
}
for op in [SumReduce, MaxReduce, MinReduce, MeanReduce, MatMul] {
op_dtype_support.insert((op, DType::BF16));
op_dtype_support.insert((op, DType::F16));
}
for op in [
SoftmaxLastDim,
RmsNormLastDim,
LayerNormLastDim,
Rope,
Conv2D,
ConvTranspose2D,
ReduceSumTo,
ReduceMaxTo,
FusedLinear,
FlashAttn,
PagedAttn,
] {
op_dtype_support.insert((op, DType::BF16));
op_dtype_support.insert((op, DType::F16));
}
for dt in [
DType::F64,
DType::BF16,
DType::F16,
DType::U32,
DType::U8,
DType::I16,
DType::I32,
DType::I64,
] {
op_dtype_support.insert((Concat, dt));
op_dtype_support.insert((IndexSelect, dt));
op_dtype_support.insert((Gather, dt));
}
for dt in [DType::F64, DType::BF16, DType::F16] {
op_dtype_support.insert((IndexAdd, dt));
op_dtype_support.insert((ScatterAdd, dt));
op_dtype_support.insert((Affine, dt));
op_dtype_support.insert((ClampElementwise, dt));
op_dtype_support.insert((PowIElementwise, dt));
}
for op in [SoftmaxLastDim, RmsNormLastDim, LayerNormLastDim, Rope] {
op_dtype_support.insert((op, DType::F64));
}
for op in [ArgMaxDim, ArgMinDim] {
op_dtype_support.insert((op, DType::U32));
}
for target in [DType::F32, DType::F64, DType::BF16, DType::F16] {
op_dtype_support.insert((Cast, target));
}
let (compute_throughput_flops_per_ns, mem_bandwidth_bytes_per_ns) =
crate::ranker::default_backend_rates(BackendId::Cpu);
BackendCapabilities {
backend_id: BackendId::Cpu,
device_location: DeviceLocation::Cpu,
op_dtype_support,
required_alignment: 64,
access_granularity_bits: 8,
transfer_paths: vec![(DeviceLocation::Cpu, TransferPath::SameDevice)],
storage_substrate: SubstrateClass::HostBytes,
compute_throughput_flops_per_ns,
mem_bandwidth_bytes_per_ns,
}
}
fn substrate_for_backend(backend: BackendId) -> SubstrateClass {
match backend {
BackendId::Cpu => SubstrateClass::HostBytes,
BackendId::Cuda => SubstrateClass::CudaUntyped,
BackendId::Vulkan => SubstrateClass::VulkanBuffer,
BackendId::Metal => SubstrateClass::MetalBuffer,
_ => SubstrateClass::HostBytes,
}
}
pub fn derive_backend_caps(
backend: BackendId,
device_location: DeviceLocation,
table: &KernelBindingTable,
) -> BackendCapabilities {
use std::collections::HashSet;
let mut op_dtype_support: HashSet<(OpKind, DType)> = HashSet::new();
for (op, dtypes, this_backend) in table.iter_keys() {
if this_backend != backend {
continue;
}
if let Some(&output_dt) = dtypes.last() {
op_dtype_support.insert((op, output_dt));
}
}
let transfer_paths = if device_location == DeviceLocation::Cpu {
vec![(DeviceLocation::Cpu, TransferPath::SameDevice)]
} else {
vec![(DeviceLocation::Cpu, TransferPath::HostStaging)]
};
let (required_alignment, access_granularity_bits) = match backend {
BackendId::Cpu => (64, 8),
_ => (256, 8),
};
let (compute_throughput_flops_per_ns, mem_bandwidth_bytes_per_ns) =
crate::ranker::default_backend_rates(backend);
BackendCapabilities {
backend_id: backend,
device_location,
op_dtype_support,
required_alignment,
access_granularity_bits,
transfer_paths,
storage_substrate: substrate_for_backend(backend),
compute_throughput_flops_per_ns,
mem_bandwidth_bytes_per_ns,
}
}
fn register_derived_gpu_caps(table: &KernelBindingTable) {
use std::collections::HashSet;
let gpu_backends: HashSet<BackendId> = table
.iter_keys()
.map(|(_, _, backend)| backend)
.filter(|b| *b != BackendId::Cpu)
.collect();
for backend in gpu_backends {
let device_location = match backend {
BackendId::Cuda => DeviceLocation::Cuda { gpu_id: 0 },
BackendId::Vulkan => DeviceLocation::Vulkan { gpu_id: 0 },
BackendId::Metal => DeviceLocation::Metal { gpu_id: 0 },
_ => DeviceLocation::Cpu,
};
register_backend_capabilities(derive_backend_caps(backend, device_location, table));
}
}
#[inline]
pub(crate) fn lock_trace(what: &str) {
if std::env::var_os("FUEL_LOCK_TRACE").is_some() {
eprintln!("[locktrace] t={:?} {}", std::thread::current().id(), what);
}
}
pub fn global_registry() -> std::sync::RwLockReadGuard<'static, CapabilityRegistry> {
lock_trace("want REGISTRY.oncelock+read");
let l = GLOBAL_REGISTRY.get_or_init(|| {
lock_trace(" REGISTRY init: begin");
let mut r = CapabilityRegistry::new();
r.register(default_cpu_caps());
lock_trace(" REGISTRY init: end");
RwLock::new(r)
});
let g = l.read().unwrap();
lock_trace("got REGISTRY.read");
g
}
pub fn register_backend_capabilities(caps: BackendCapabilities) {
lock_trace("want REGISTRY.oncelock+WRITE (from register_backend_capabilities)");
let lock = GLOBAL_REGISTRY.get_or_init(|| {
lock_trace(" REGISTRY init: begin (via register_backend_capabilities)");
let mut r = CapabilityRegistry::new();
r.register(default_cpu_caps());
lock_trace(" REGISTRY init: end");
RwLock::new(r)
});
lock.write().unwrap().register(caps);
lock_trace("got+released REGISTRY.WRITE");
bump_topology_generation();
}
pub fn global_bindings() -> Arc<KernelBindingTable> {
lock_trace("want BINDINGS.oncelock+snapshot");
let cell = bindings_cell();
let snapshot = {
let guard = cell.read().unwrap_or_else(|e| e.into_inner());
Arc::clone(&guard)
};
lock_trace("got BINDINGS snapshot (guard already released)");
snapshot
}
fn register_optional_backends(table: &mut KernelBindingTable) {
#[cfg(feature = "cuda")]
{
register_cuda_kernels(table);
crate::baracuda_dispatch::register_baracuda_cuda_kernels(table);
table.fill_unset_cost_for_backend(BackendId::Cuda, crate::cost::default_cost_for_op_kind);
}
#[cfg(feature = "vulkan")]
{
crate::vulkan_dispatch::register_vulkan_kernels(table);
}
register_derived_gpu_caps(table);
let _ = table;
}
pub fn extend_global_bindings(register: impl FnOnce(&mut KernelBindingTable)) {
let cell = bindings_cell();
{
lock_trace("want BINDINGS.WRITER (extend_global_bindings)");
let _writer = BINDINGS_WRITER.lock().unwrap_or_else(|e| e.into_inner());
lock_trace("got BINDINGS.WRITER -- cloning table, extender runs OFF the RwLock");
let mut next = {
let guard = cell.read().unwrap_or_else(|e| e.into_inner());
(**guard).clone()
};
register(&mut next);
lock_trace(" extender returned");
next.finalize()
.expect("KernelBindingTable: duplicate kernel registration after extend");
{
let mut guard = cell.write().unwrap_or_else(|e| e.into_inner());
*guard = Arc::new(next);
}
lock_trace("released BINDINGS.WRITE (swap only)");
}
bump_topology_generation();
}
const FUSED_LINEAR_QUANT_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/fused/linear-quant.fkc.md");
fn register_cpu_linear_quant_fused_from_contract(r: &mut crate::fused::FusedKernelRegistry) {
let provider =
crate::fkc::import_bundle_str(FUSED_LINEAR_QUANT_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored linear-quant fused contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.primitives.is_empty(),
"linear-quant bundle declares only fused ops",
);
let mut table = KernelBindingTable::new();
provider
.register_into(&mut table, r)
.expect("linear-quant fused contract must register into the fused registry");
}
const FUSED_NORM_SOFTMAX_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/fused/norm-softmax.fkc.md");
fn register_cpu_norm_softmax_fused_from_contract(r: &mut crate::fused::FusedKernelRegistry) {
let provider =
crate::fkc::import_bundle_str(FUSED_NORM_SOFTMAX_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored norm-softmax fused contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.primitives.is_empty(),
"norm-softmax bundle declares only fused ops",
);
let mut table = KernelBindingTable::new();
provider
.register_into(&mut table, r)
.expect("norm-softmax fused contract must register into the fused registry");
}
const FUSED_CONV_ROPE_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/fused/conv-rope.fkc.md");
fn register_cpu_conv_rope_fused_from_contract(r: &mut crate::fused::FusedKernelRegistry) {
let provider =
crate::fkc::import_bundle_str(FUSED_CONV_ROPE_CONTRACT, &crate::fkc::CpuLinkRegistry)
.expect(
"authored conv-rope fused contract must import \
(embedded via include_str!, resolved through CpuLinkRegistry)",
);
debug_assert!(
provider.primitives.is_empty(),
"conv-rope bundle declares only fused ops",
);
let mut table = KernelBindingTable::new();
provider
.register_into(&mut table, r)
.expect("conv-rope fused contract must register into the fused registry");
}
pub fn register_default_fused_kernels(r: &mut crate::fused::FusedKernelRegistry) {
use crate::fused::{
ATTN_BACKWARD_CPU_PRECISION, ATTN_CPU_PRECISION, NF4_MATMUL_CPU_PRECISION,
cost_attn_backward_cpu, cost_attn_cpu, cost_nf4_matmul_cpu,
};
use crate::register_fused;
use fuel_graph::registry::FusedOps;
const FA_F32_NOA: &[DType] = &[DType::F32, DType::F32, DType::F32, DType::F32];
const FA_F32_A: &[DType] = &[DType::F32, DType::F32, DType::F32, DType::F32, DType::F32];
const FA_F64_NOA: &[DType] = &[DType::F64, DType::F64, DType::F64, DType::F64];
const FA_F64_A: &[DType] = &[DType::F64, DType::F64, DType::F64, DType::F64, DType::F64];
const FA_BF16_NOA: &[DType] = &[DType::BF16, DType::BF16, DType::BF16, DType::BF16];
const FA_BF16_A: &[DType] = &[
DType::BF16,
DType::BF16,
DType::BF16,
DType::BF16,
DType::BF16,
];
const FA_F16_NOA: &[DType] = &[DType::F16, DType::F16, DType::F16, DType::F16];
const FA_F16_A: &[DType] = &[DType::F16, DType::F16, DType::F16, DType::F16, DType::F16];
const FAB_F32_NOA: &[DType] = &[DType::F32, DType::F32, DType::F32, DType::F32, DType::F32];
const FAB_F32_A: &[DType] = &[
DType::F32,
DType::F32,
DType::F32,
DType::F32,
DType::F32,
DType::F32,
];
const FAB_F64_NOA: &[DType] = &[DType::F64, DType::F64, DType::F64, DType::F64, DType::F64];
const FAB_F64_A: &[DType] = &[
DType::F64,
DType::F64,
DType::F64,
DType::F64,
DType::F64,
DType::F64,
];
const FAB_BF16_NOA: &[DType] = &[
DType::BF16,
DType::BF16,
DType::BF16,
DType::BF16,
DType::BF16,
];
const FAB_BF16_A: &[DType] = &[
DType::BF16,
DType::BF16,
DType::BF16,
DType::BF16,
DType::BF16,
DType::BF16,
];
const FAB_F16_NOA: &[DType] = &[DType::F16, DType::F16, DType::F16, DType::F16, DType::F16];
const FAB_F16_A: &[DType] = &[
DType::F16,
DType::F16,
DType::F16,
DType::F16,
DType::F16,
DType::F16,
];
const PA_F32_NOA: &[DType] = &[
DType::F32,
DType::F32,
DType::F32,
DType::U32,
DType::U32,
DType::F32,
];
const PA_F32_A: &[DType] = &[
DType::F32,
DType::F32,
DType::F32,
DType::U32,
DType::U32,
DType::F32,
DType::F32,
];
const PA_F64_NOA: &[DType] = &[
DType::F64,
DType::F64,
DType::F64,
DType::U32,
DType::U32,
DType::F64,
];
const PA_F64_A: &[DType] = &[
DType::F64,
DType::F64,
DType::F64,
DType::U32,
DType::U32,
DType::F64,
DType::F64,
];
const PA_BF16_NOA: &[DType] = &[
DType::BF16,
DType::BF16,
DType::BF16,
DType::U32,
DType::U32,
DType::BF16,
];
const PA_BF16_A: &[DType] = &[
DType::BF16,
DType::BF16,
DType::BF16,
DType::U32,
DType::U32,
DType::BF16,
DType::BF16,
];
const PA_F16_NOA: &[DType] = &[
DType::F16,
DType::F16,
DType::F16,
DType::U32,
DType::U32,
DType::F16,
];
const PA_F16_A: &[DType] = &[
DType::F16,
DType::F16,
DType::F16,
DType::U32,
DType::U32,
DType::F16,
DType::F16,
];
let cpu = BackendId::Cpu;
register_cpu_linear_quant_fused_from_contract(r);
register_cpu_norm_softmax_fused_from_contract(r);
register_cpu_conv_rope_fused_from_contract(r);
register_fused!(
r,
FusedOps::FLASH_ATTN,
cpu,
FA_F32_NOA,
flash_attn_f32_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN,
cpu,
FA_F32_A,
flash_attn_f32_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN,
cpu,
FA_F64_NOA,
flash_attn_f64_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN,
cpu,
FA_F64_A,
flash_attn_f64_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN,
cpu,
FA_BF16_NOA,
flash_attn_bf16_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN,
cpu,
FA_BF16_A,
flash_attn_bf16_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN,
cpu,
FA_F16_NOA,
flash_attn_f16_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN,
cpu,
FA_F16_A,
flash_attn_f16_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_Q,
cpu,
FAB_F32_NOA,
flash_attn_backward_q_f32_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_Q,
cpu,
FAB_F32_A,
flash_attn_backward_q_f32_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_Q,
cpu,
FAB_F64_NOA,
flash_attn_backward_q_f64_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_Q,
cpu,
FAB_F64_A,
flash_attn_backward_q_f64_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_Q,
cpu,
FAB_BF16_NOA,
flash_attn_backward_q_bf16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_Q,
cpu,
FAB_BF16_A,
flash_attn_backward_q_bf16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_Q,
cpu,
FAB_F16_NOA,
flash_attn_backward_q_f16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_Q,
cpu,
FAB_F16_A,
flash_attn_backward_q_f16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_K,
cpu,
FAB_F32_NOA,
flash_attn_backward_k_f32_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_K,
cpu,
FAB_F32_A,
flash_attn_backward_k_f32_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_K,
cpu,
FAB_F64_NOA,
flash_attn_backward_k_f64_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_K,
cpu,
FAB_F64_A,
flash_attn_backward_k_f64_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_K,
cpu,
FAB_BF16_NOA,
flash_attn_backward_k_bf16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_K,
cpu,
FAB_BF16_A,
flash_attn_backward_k_bf16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_K,
cpu,
FAB_F16_NOA,
flash_attn_backward_k_f16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_K,
cpu,
FAB_F16_A,
flash_attn_backward_k_f16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_V,
cpu,
FAB_F32_NOA,
flash_attn_backward_v_f32_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_V,
cpu,
FAB_F32_A,
flash_attn_backward_v_f32_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_V,
cpu,
FAB_F64_NOA,
flash_attn_backward_v_f64_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_V,
cpu,
FAB_F64_A,
flash_attn_backward_v_f64_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_V,
cpu,
FAB_BF16_NOA,
flash_attn_backward_v_bf16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_V,
cpu,
FAB_BF16_A,
flash_attn_backward_v_bf16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_V,
cpu,
FAB_F16_NOA,
flash_attn_backward_v_f16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::FLASH_ATTN_BACKWARD_V,
cpu,
FAB_F16_A,
flash_attn_backward_v_f16_cpu_wrapper,
cost = cost_attn_backward_cpu,
precision = ATTN_BACKWARD_CPU_PRECISION
);
register_fused!(
r,
FusedOps::PAGED_ATTN,
cpu,
PA_F32_NOA,
paged_attn_f32_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::PAGED_ATTN,
cpu,
PA_F32_A,
paged_attn_f32_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::PAGED_ATTN,
cpu,
PA_F64_NOA,
paged_attn_f64_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::PAGED_ATTN,
cpu,
PA_F64_A,
paged_attn_f64_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::PAGED_ATTN,
cpu,
PA_BF16_NOA,
paged_attn_bf16_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::PAGED_ATTN,
cpu,
PA_BF16_A,
paged_attn_bf16_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::PAGED_ATTN,
cpu,
PA_F16_NOA,
paged_attn_f16_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
register_fused!(
r,
FusedOps::PAGED_ATTN,
cpu,
PA_F16_A,
paged_attn_f16_cpu_wrapper,
cost = cost_attn_cpu,
precision = ATTN_CPU_PRECISION
);
const NF4_F32: &[DType] = &[DType::F32, DType::U8, DType::F32, DType::F32];
const NF4_F16: &[DType] = &[DType::F16, DType::U8, DType::F32, DType::F16];
const NF4_BF16: &[DType] = &[DType::BF16, DType::U8, DType::F32, DType::BF16];
register_fused!(
r,
FusedOps::NF4_MATMUL,
cpu,
NF4_F32,
nf4_matmul_f32_cpu_wrapper,
cost = cost_nf4_matmul_cpu,
precision = NF4_MATMUL_CPU_PRECISION
);
register_fused!(
r,
FusedOps::NF4_MATMUL,
cpu,
NF4_F16,
nf4_matmul_f16_cpu_wrapper,
cost = cost_nf4_matmul_cpu,
precision = NF4_MATMUL_CPU_PRECISION
);
register_fused!(
r,
FusedOps::NF4_MATMUL,
cpu,
NF4_BF16,
nf4_matmul_bf16_cpu_wrapper,
cost = cost_nf4_matmul_cpu,
precision = NF4_MATMUL_CPU_PRECISION
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linear_quant_fused_family_migrated_to_fkc_contract() {
use crate::fused::{FusedKernelRegistry, KernelRevisionHash};
use fuel_graph::registry::FusedOps;
let mut r = FusedKernelRegistry::new();
register_default_fused_kernels(&mut r);
for (dt, expected) in [
(
DType::F32,
fused_linear_f32_cpu_wrapper as *const () as usize,
),
(
DType::F64,
fused_linear_f64_cpu_wrapper as *const () as usize,
),
(
DType::BF16,
fused_linear_bf16_cpu_wrapper as *const () as usize,
),
(
DType::F16,
fused_linear_f16_cpu_wrapper as *const () as usize,
),
] {
let got = r
.lookup_by_dtypes(FusedOps::FUSED_LINEAR, BackendId::Cpu, &[dt, dt, dt, dt])
.unwrap_or_else(|| panic!("FUSED_LINEAR {dt:?} migrated impl present"));
assert_eq!(
got.kernel as usize, expected,
"FUSED_LINEAR {dt:?} binds its exact per-dtype production wrapper",
);
assert_ne!(
got.revision,
KernelRevisionHash::UNTRACKED,
"FKC-imported FUSED_LINEAR {dt:?} carries the contract's real revision \
(hand-written register_fused! stamps UNTRACKED)",
);
}
let qm = r
.lookup_by_dtypes(
FusedOps::QMATMUL,
BackendId::Cpu,
&[DType::F32, DType::U32, DType::F32],
)
.expect("QMATMUL migrated impl present at [F32, U32, F32]");
assert_eq!(
qm.kernel as usize,
qmatmul_f32_cpu_wrapper as *const () as usize
);
assert_ne!(qm.revision, KernelRevisionHash::UNTRACKED);
for (dt, expected) in [
(
DType::F32,
inplace_affine_f32_cpu_wrapper as *const () as usize,
),
(
DType::F64,
inplace_affine_f64_cpu_wrapper as *const () as usize,
),
(
DType::BF16,
inplace_affine_bf16_cpu_wrapper as *const () as usize,
),
(
DType::F16,
inplace_affine_f16_cpu_wrapper as *const () as usize,
),
] {
let got = r
.lookup_by_dtypes(FusedOps::INPLACE_AFFINE, BackendId::Cpu, &[dt, dt])
.unwrap_or_else(|| panic!("INPLACE_AFFINE {dt:?} migrated impl present"));
assert_eq!(got.kernel as usize, expected);
assert_ne!(got.revision, KernelRevisionHash::UNTRACKED);
}
for (dt, expected) in [
(
DType::F32,
fused_softmax_cross_entropy_f32_cpu_wrapper as *const () as usize,
),
(
DType::F64,
fused_softmax_cross_entropy_f64_cpu_wrapper as *const () as usize,
),
(
DType::BF16,
fused_softmax_cross_entropy_bf16_cpu_wrapper as *const () as usize,
),
(
DType::F16,
fused_softmax_cross_entropy_f16_cpu_wrapper as *const () as usize,
),
] {
let got = r
.lookup_by_dtypes(
FusedOps::FUSED_SOFTMAX_CROSS_ENTROPY,
BackendId::Cpu,
&[dt, DType::I64, DType::F32],
)
.unwrap_or_else(|| panic!("FSCE {dt:?} migrated impl present"));
assert_eq!(got.kernel as usize, expected);
assert_ne!(got.revision, KernelRevisionHash::UNTRACKED);
}
let fa = r
.lookup_by_dtypes(
FusedOps::FLASH_ATTN,
BackendId::Cpu,
&[DType::F32, DType::F32, DType::F32, DType::F32],
)
.expect("FLASH_ATTN hand-written impl present");
assert_eq!(
fa.kernel as usize,
flash_attn_f32_cpu_wrapper as *const () as usize
);
assert_eq!(
fa.revision,
KernelRevisionHash::UNTRACKED,
"unmigrated FLASH_ATTN keeps its hand-written UNTRACKED revision",
);
let nf4 = r
.lookup_by_dtypes(
FusedOps::NF4_MATMUL,
BackendId::Cpu,
&[DType::F32, DType::U8, DType::F32, DType::F32],
)
.expect("NF4_MATMUL hand-written impl present (AFFINE_BLOCK deferred)");
assert_eq!(
nf4.kernel as usize,
nf4_matmul_f32_cpu_wrapper as *const () as usize
);
assert_eq!(
nf4.revision,
KernelRevisionHash::UNTRACKED,
"deferred NF4_MATMUL keeps its hand-written UNTRACKED revision",
);
}
#[test]
fn norm_softmax_fused_family_migrated_to_fkc_contract() {
use crate::fused::{FusedKernelRegistry, KernelRevisionHash};
use fuel_graph::registry::FusedOps;
let mut r = FusedKernelRegistry::new();
register_default_fused_kernels(&mut r);
let forward = [
(
FusedOps::SOFTMAX_LAST_DIM,
[
softmax_last_dim_f32_cpu_wrapper as *const () as usize,
softmax_last_dim_f64_cpu_wrapper as *const () as usize,
softmax_last_dim_bf16_cpu_wrapper as *const () as usize,
softmax_last_dim_f16_cpu_wrapper as *const () as usize,
],
),
(
FusedOps::RMS_NORM_LAST_DIM,
[
rms_norm_last_dim_f32_cpu_wrapper as *const () as usize,
rms_norm_last_dim_f64_cpu_wrapper as *const () as usize,
rms_norm_last_dim_bf16_cpu_wrapper as *const () as usize,
rms_norm_last_dim_f16_cpu_wrapper as *const () as usize,
],
),
(
FusedOps::LAYER_NORM_LAST_DIM,
[
layer_norm_last_dim_f32_cpu_wrapper as *const () as usize,
layer_norm_last_dim_f64_cpu_wrapper as *const () as usize,
layer_norm_last_dim_bf16_cpu_wrapper as *const () as usize,
layer_norm_last_dim_f16_cpu_wrapper as *const () as usize,
],
),
];
for (id, wrappers) in forward {
for (dt, expected) in [DType::F32, DType::F64, DType::BF16, DType::F16]
.iter()
.zip(wrappers)
{
let got = r
.lookup_by_dtypes(id, BackendId::Cpu, &[*dt, *dt])
.unwrap_or_else(|| panic!("{id:?} {dt:?} migrated impl present"));
assert_eq!(
got.kernel as usize, expected,
"{id:?} {dt:?} binds its exact per-dtype production wrapper",
);
assert_ne!(
got.revision,
KernelRevisionHash::UNTRACKED,
"FKC-imported {id:?} {dt:?} carries the contract's real revision \
(hand-written register_fused! stamps UNTRACKED)",
);
assert!(
got.precision.bit_stable_on_same_hardware,
"the 2026-07-03 audited:true flip rode through as bit-stable for {id:?} {dt:?} \
(NOT the UNAUDITED an audited:false contract would have lowered to)",
);
}
}
let backward = [
(
FusedOps::SOFTMAX_LAST_DIM_BACKWARD,
[
softmax_last_dim_backward_f32_cpu_wrapper as *const () as usize,
softmax_last_dim_backward_f64_cpu_wrapper as *const () as usize,
softmax_last_dim_backward_bf16_cpu_wrapper as *const () as usize,
softmax_last_dim_backward_f16_cpu_wrapper as *const () as usize,
],
),
(
FusedOps::LAYER_NORM_LAST_DIM_BACKWARD,
[
layer_norm_last_dim_backward_f32_cpu_wrapper as *const () as usize,
layer_norm_last_dim_backward_f64_cpu_wrapper as *const () as usize,
layer_norm_last_dim_backward_bf16_cpu_wrapper as *const () as usize,
layer_norm_last_dim_backward_f16_cpu_wrapper as *const () as usize,
],
),
(
FusedOps::RMS_NORM_LAST_DIM_BACKWARD,
[
rms_norm_last_dim_backward_f32_cpu_wrapper as *const () as usize,
rms_norm_last_dim_backward_f64_cpu_wrapper as *const () as usize,
rms_norm_last_dim_backward_bf16_cpu_wrapper as *const () as usize,
rms_norm_last_dim_backward_f16_cpu_wrapper as *const () as usize,
],
),
(
FusedOps::REDUCE_MAX_TO_BACKWARD,
[
reduce_max_to_backward_f32_cpu_wrapper as *const () as usize,
reduce_max_to_backward_f64_cpu_wrapper as *const () as usize,
reduce_max_to_backward_bf16_cpu_wrapper as *const () as usize,
reduce_max_to_backward_f16_cpu_wrapper as *const () as usize,
],
),
(
FusedOps::POWI_BACKWARD,
[
powi_backward_f32_cpu_wrapper as *const () as usize,
powi_backward_f64_cpu_wrapper as *const () as usize,
powi_backward_bf16_cpu_wrapper as *const () as usize,
powi_backward_f16_cpu_wrapper as *const () as usize,
],
),
];
for (id, wrappers) in backward {
for (dt, expected) in [DType::F32, DType::F64, DType::BF16, DType::F16]
.iter()
.zip(wrappers)
{
let got = r
.lookup_by_dtypes(id, BackendId::Cpu, &[*dt, *dt, *dt])
.unwrap_or_else(|| panic!("{id:?} {dt:?} migrated impl present"));
assert_eq!(got.kernel as usize, expected);
assert_ne!(
got.revision,
KernelRevisionHash::UNTRACKED,
"FKC-imported {id:?} {dt:?} carries the contract's real revision",
);
assert!(
got.precision.bit_stable_on_same_hardware,
"the audited:true flip rode through as bit-stable for {id:?} {dt:?}",
);
}
}
let fa = r
.lookup_by_dtypes(
FusedOps::FLASH_ATTN,
BackendId::Cpu,
&[DType::F32, DType::F32, DType::F32, DType::F32],
)
.expect("FLASH_ATTN hand-written impl present");
assert_eq!(
fa.revision,
KernelRevisionHash::UNTRACKED,
"unmigrated FLASH_ATTN keeps its hand-written UNTRACKED revision",
);
}
#[test]
fn conv_rope_fused_family_migrated_to_fkc_contract() {
use crate::fused::{FusedKernelRegistry, KernelRevisionHash};
use fuel_graph::registry::FusedOps;
let mut r = FusedKernelRegistry::new();
register_default_fused_kernels(&mut r);
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let rope_w = [
rope_f32_cpu_wrapper as *const () as usize,
rope_f64_cpu_wrapper as *const () as usize,
rope_bf16_cpu_wrapper as *const () as usize,
rope_f16_cpu_wrapper as *const () as usize,
];
for (dt, expected) in dts.iter().zip(rope_w) {
let got = r
.lookup_by_dtypes(FusedOps::ROPE, BackendId::Cpu, &[*dt, *dt, *dt, *dt])
.unwrap_or_else(|| panic!("ROPE {dt:?} migrated impl present"));
assert_eq!(got.kernel as usize, expected);
assert_ne!(
got.revision,
KernelRevisionHash::UNTRACKED,
"ROPE {dt:?} real revision"
);
assert!(
got.precision.bit_stable_on_same_hardware,
"audited:true rode through bit-stable for ROPE {dt:?}",
);
}
let conv = [
(
FusedOps::CONV2D,
[
conv2d_f32_cpu_wrapper as *const () as usize,
conv2d_f64_cpu_wrapper as *const () as usize,
conv2d_bf16_cpu_wrapper as *const () as usize,
conv2d_f16_cpu_wrapper as *const () as usize,
],
),
(
FusedOps::CONV_TRANSPOSE2D,
[
conv_transpose2d_f32_cpu_wrapper as *const () as usize,
conv_transpose2d_f64_cpu_wrapper as *const () as usize,
conv_transpose2d_bf16_cpu_wrapper as *const () as usize,
conv_transpose2d_f16_cpu_wrapper as *const () as usize,
],
),
];
for (id, wrappers) in conv {
for (dt, expected) in dts.iter().zip(wrappers) {
for key in [vec![*dt, *dt, *dt], vec![*dt, *dt, *dt, *dt]] {
let with_bias = key.len() == 4;
let got = r
.lookup_by_dtypes(id, BackendId::Cpu, &key)
.unwrap_or_else(|| {
panic!("{id:?} {dt:?} (with_bias={with_bias}) migrated impl present")
});
assert_eq!(got.kernel as usize, expected);
assert_ne!(
got.revision,
KernelRevisionHash::UNTRACKED,
"{id:?} {dt:?} (with_bias={with_bias}) real revision",
);
assert!(
got.precision.bit_stable_on_same_hardware,
"audited:true rode through bit-stable for {id:?} {dt:?} (with_bias={with_bias})",
);
}
}
}
let cc1d_w = [
causal_conv1d_f32_cpu_wrapper as *const () as usize,
causal_conv1d_f64_cpu_wrapper as *const () as usize,
causal_conv1d_bf16_cpu_wrapper as *const () as usize,
causal_conv1d_f16_cpu_wrapper as *const () as usize,
];
for (dt, expected) in dts.iter().zip(cc1d_w) {
let got = r
.lookup_by_dtypes(
FusedOps::CAUSAL_CONV1D,
BackendId::Cpu,
&[*dt, *dt, *dt, *dt],
)
.unwrap_or_else(|| panic!("CAUSAL_CONV1D {dt:?} migrated impl present"));
assert_eq!(got.kernel as usize, expected);
assert_ne!(got.revision, KernelRevisionHash::UNTRACKED);
assert!(got.precision.bit_stable_on_same_hardware);
}
let scan = [
(
FusedOps::SELECTIVE_SCAN,
[
selective_scan_f32_cpu_wrapper as *const () as usize,
selective_scan_f64_cpu_wrapper as *const () as usize,
selective_scan_bf16_cpu_wrapper as *const () as usize,
selective_scan_f16_cpu_wrapper as *const () as usize,
],
),
(
FusedOps::SSD_CHUNK_SCAN,
[
ssd_chunk_scan_f32_cpu_wrapper as *const () as usize,
ssd_chunk_scan_f64_cpu_wrapper as *const () as usize,
ssd_chunk_scan_bf16_cpu_wrapper as *const () as usize,
ssd_chunk_scan_f16_cpu_wrapper as *const () as usize,
],
),
];
for (id, wrappers) in scan {
for (dt, expected) in dts.iter().zip(wrappers) {
let got = r
.lookup_by_dtypes(id, BackendId::Cpu, &[*dt; 6])
.unwrap_or_else(|| panic!("{id:?} {dt:?} migrated impl present"));
assert_eq!(got.kernel as usize, expected);
assert_ne!(
got.revision,
KernelRevisionHash::UNTRACKED,
"{id:?} {dt:?} real revision",
);
assert!(
got.precision.bit_stable_on_same_hardware,
"audited:true rode through bit-stable for {id:?} {dt:?}",
);
}
}
let nf4 = r
.lookup_by_dtypes(
FusedOps::NF4_MATMUL,
BackendId::Cpu,
&[DType::F32, DType::U8, DType::F32, DType::F32],
)
.expect("NF4_MATMUL hand-written impl present");
assert_eq!(
nf4.revision,
KernelRevisionHash::UNTRACKED,
"deferred NF4_MATMUL keeps its hand-written UNTRACKED revision",
);
}
#[test]
fn precision_guarantee_lint_bit_stable_cpu_coverage_primitives() {
use fuel_ir::dispatch::OpKind;
use fuel_ir::probe::BackendId;
const KNOWN_GAPS: &[(OpKind, &str)] = &[];
const ALL_OP_KINDS: &[OpKind] = &[
OpKind::MatMul,
OpKind::AddElementwise,
OpKind::SubElementwise,
OpKind::MulElementwise,
OpKind::DivElementwise,
OpKind::ReluElementwise,
OpKind::NegElementwise,
OpKind::SqrElementwise,
OpKind::SqrtElementwise,
OpKind::RecipElementwise,
OpKind::AbsElementwise,
OpKind::TanhElementwise,
OpKind::ExpElementwise,
OpKind::LogElementwise,
OpKind::SinElementwise,
OpKind::CosElementwise,
OpKind::SigmoidElementwise,
OpKind::SiluElementwise,
OpKind::GeluTanhElementwise,
OpKind::StepElementwise,
OpKind::SumReduce,
OpKind::MaxReduce,
OpKind::MinReduce,
OpKind::MeanReduce,
OpKind::Cast,
OpKind::Conv2D,
OpKind::ConvTranspose2D,
OpKind::ReduceSumTo,
OpKind::ReduceMaxTo,
OpKind::FusedLinear,
OpKind::FlashAttn,
OpKind::PagedAttn,
OpKind::Affine,
OpKind::ClampElementwise,
OpKind::PowIElementwise,
OpKind::MaximumElementwise,
OpKind::MinimumElementwise,
OpKind::EqualElementwise,
OpKind::NotEqualElementwise,
OpKind::LessElementwise,
OpKind::LessEqualElementwise,
OpKind::GreaterElementwise,
OpKind::GreaterEqualElementwise,
OpKind::Where,
OpKind::FloorElementwise,
OpKind::CeilElementwise,
OpKind::RoundElementwise,
OpKind::SignElementwise,
OpKind::ErfElementwise,
OpKind::GeluElementwise,
OpKind::PowElementwise,
OpKind::RsqrtElementwise,
OpKind::RemElementwise,
OpKind::Flip,
OpKind::Roll,
OpKind::CumSum,
OpKind::Pad,
OpKind::PadBackward,
OpKind::Triu,
OpKind::Tril,
OpKind::LogSoftmaxLastDim,
OpKind::LogSoftmaxLastDimBackward,
OpKind::MaskedFill,
OpKind::Concat,
OpKind::SoftmaxLastDim,
OpKind::SoftmaxLastDimBackward,
OpKind::RmsNormLastDim,
OpKind::RmsNormLastDimBackward,
OpKind::LayerNormLastDim,
OpKind::LayerNormLastDimBackward,
OpKind::ReduceMaxToBackward,
OpKind::IndexSelect,
OpKind::Gather,
OpKind::Rope,
OpKind::IndexAdd,
OpKind::ScatterAdd,
OpKind::ArgMaxDim,
OpKind::ArgMinDim,
OpKind::QMatMul,
OpKind::Copy,
OpKind::FusedSoftmaxCrossEntropy,
OpKind::CausalConv1d,
OpKind::SelectiveScan,
OpKind::SsdChunkScan,
OpKind::Nf4Matmul,
];
let mut table = KernelBindingTable::new();
register_cpu_kernels(&mut table);
let mut by_op_kind: std::collections::HashMap<
OpKind,
Vec<crate::fused::PrecisionGuarantee>,
> = std::collections::HashMap::new();
for (op, _dtypes, backend, precision) in table.iter_precision() {
if backend == BackendId::Cpu {
by_op_kind.entry(op).or_default().push(precision);
}
}
let mut failures: Vec<String> = Vec::new();
let mut covered = 0usize;
let mut allowlisted = 0usize;
for op in ALL_OP_KINDS.iter().copied() {
if let Some((_, reason)) = KNOWN_GAPS.iter().find(|(g, _)| *g == op) {
allowlisted += 1;
if by_op_kind.contains_key(&op) {
failures.push(format!(
"OpKind::{op:?} is on the KNOWN_GAPS allowlist but DOES \
have a CPU registration now. Reason given was: \
{reason:?}. Remove the allowlist entry to enable the \
bit-stable lint for this op.",
));
}
continue;
}
let precisions = by_op_kind.get(&op);
let has_bit_stable_cpu =
precisions.is_some_and(|ps| ps.iter().any(|p| p.bit_stable_on_same_hardware));
if has_bit_stable_cpu {
covered += 1;
} else {
failures.push(format!(
"OpKind::{op:?} has no bit-stable CPU registration. \
Architecture v1.0 §05 requires the always-built backend \
(fuel-cpu-backend) to provide at least one \
`bit_stable_on_same_hardware: true` kernel per primitive \
op. Either add a CPU registration in \
register_cpu_kernels (the fill_unset_cpu_precision pass \
at the end will upgrade it to PRIMITIVE_DETERMINISTIC_CPU \
automatically), or add a line to KNOWN_GAPS above with a \
documented reason.",
));
}
}
assert!(
failures.is_empty(),
"Architecture v1.0 bit-stable CPU coverage lint (primitives) failed:\n{}",
failures.join("\n"),
);
assert_eq!(
allowlisted, 0,
"KNOWN_GAPS allowlist is empty by design; saw {allowlisted} allowlisted",
);
assert!(covered > 0, "lint covered 0 OpKinds — table appears empty",);
for op in ALL_OP_KINDS {
assert!(
by_op_kind.contains_key(op),
"OpKind::{op:?} has no CPU registration at all — either add \
one in register_cpu_kernels or document the gap in \
KNOWN_GAPS.",
);
}
}
#[test]
fn cost_lint_per_op_kind_cpu_coverage() {
use fuel_ir::dispatch::OpKind;
use fuel_ir::probe::BackendId;
const KNOWN_GAPS: &[(OpKind, &str)] = &[];
const ALL_OP_KINDS: &[OpKind] = &[
OpKind::MatMul,
OpKind::AddElementwise,
OpKind::SubElementwise,
OpKind::MulElementwise,
OpKind::DivElementwise,
OpKind::ReluElementwise,
OpKind::NegElementwise,
OpKind::SqrElementwise,
OpKind::SqrtElementwise,
OpKind::RecipElementwise,
OpKind::AbsElementwise,
OpKind::TanhElementwise,
OpKind::ExpElementwise,
OpKind::LogElementwise,
OpKind::SinElementwise,
OpKind::CosElementwise,
OpKind::SigmoidElementwise,
OpKind::SiluElementwise,
OpKind::GeluTanhElementwise,
OpKind::StepElementwise,
OpKind::SumReduce,
OpKind::MaxReduce,
OpKind::MinReduce,
OpKind::MeanReduce,
OpKind::Cast,
OpKind::Conv2D,
OpKind::ConvTranspose2D,
OpKind::ReduceSumTo,
OpKind::ReduceMaxTo,
OpKind::FusedLinear,
OpKind::FlashAttn,
OpKind::PagedAttn,
OpKind::Affine,
OpKind::ClampElementwise,
OpKind::PowIElementwise,
OpKind::MaximumElementwise,
OpKind::MinimumElementwise,
OpKind::EqualElementwise,
OpKind::NotEqualElementwise,
OpKind::LessElementwise,
OpKind::LessEqualElementwise,
OpKind::GreaterElementwise,
OpKind::GreaterEqualElementwise,
OpKind::Where,
OpKind::FloorElementwise,
OpKind::CeilElementwise,
OpKind::RoundElementwise,
OpKind::SignElementwise,
OpKind::ErfElementwise,
OpKind::GeluElementwise,
OpKind::PowElementwise,
OpKind::RsqrtElementwise,
OpKind::RemElementwise,
OpKind::Flip,
OpKind::Roll,
OpKind::CumSum,
OpKind::Pad,
OpKind::PadBackward,
OpKind::Triu,
OpKind::Tril,
OpKind::LogSoftmaxLastDim,
OpKind::LogSoftmaxLastDimBackward,
OpKind::MaskedFill,
OpKind::Concat,
OpKind::SoftmaxLastDim,
OpKind::SoftmaxLastDimBackward,
OpKind::RmsNormLastDim,
OpKind::RmsNormLastDimBackward,
OpKind::LayerNormLastDim,
OpKind::LayerNormLastDimBackward,
OpKind::ReduceMaxToBackward,
OpKind::IndexSelect,
OpKind::Gather,
OpKind::Rope,
OpKind::IndexAdd,
OpKind::ScatterAdd,
OpKind::ArgMaxDim,
OpKind::ArgMinDim,
OpKind::QMatMul,
OpKind::Copy,
OpKind::FusedSoftmaxCrossEntropy,
OpKind::CausalConv1d,
OpKind::SelectiveScan,
OpKind::SsdChunkScan,
OpKind::Nf4Matmul,
];
let mut table = KernelBindingTable::new();
register_cpu_kernels(&mut table);
let mut by_op_kind: std::collections::HashMap<OpKind, Vec<crate::kernel::CostFn>> =
std::collections::HashMap::new();
for (op, _dtypes, backend, cost) in table.iter_cost() {
if backend == BackendId::Cpu {
by_op_kind.entry(op).or_default().push(cost);
}
}
let unknown_sentinel = crate::kernel::unknown_cost as *const () as usize;
let mut failures: Vec<String> = Vec::new();
for op in ALL_OP_KINDS.iter().copied() {
if let Some((_, reason)) = KNOWN_GAPS.iter().find(|(g, _)| *g == op) {
if let Some(costs) = by_op_kind.get(&op) {
let has_real = costs.iter().any(|c| (*c as usize) != unknown_sentinel);
if has_real {
failures.push(format!(
"OpKind::{op:?} is on the cost-lint KNOWN_GAPS \
allowlist but DOES have a non-default cost fn \
now. Reason given: {reason:?}. Remove the \
allowlist entry.",
));
}
}
continue;
}
let costs = by_op_kind.get(&op);
let has_real_cost =
costs.is_some_and(|cs| cs.iter().any(|c| (*c as usize) != unknown_sentinel));
if !has_real_cost {
failures.push(format!(
"OpKind::{op:?} has no non-default cost fn in any CPU \
registration. Either add an arm in \
`crate::cost::default_cost_for_op_kind` (the \
fill_unset_cpu_cost pass at the end of \
register_cpu_kernels will pick it up automatically), \
register an explicit cost via `register_full(...)`, \
or add a line to KNOWN_GAPS with a documented reason.",
));
}
}
assert!(
failures.is_empty(),
"Phase 7.6 step 8 cost-coverage lint failed:\n{}",
failures.join("\n"),
);
for op in ALL_OP_KINDS {
assert!(
by_op_kind.contains_key(op),
"OpKind::{op:?} has no CPU registration at all.",
);
}
}
fn cpu_caps() -> BackendCapabilities {
BackendCapabilities {
backend_id: BackendId::Cpu,
device_location: DeviceLocation::Cpu,
op_dtype_support: [
(OpKind::MatMul, DType::F32),
(OpKind::MatMul, DType::F64),
(OpKind::AddElementwise, DType::F32),
(OpKind::AddElementwise, DType::F64),
]
.into_iter()
.collect(),
required_alignment: 64,
access_granularity_bits: 8,
transfer_paths: vec![(DeviceLocation::Cpu, TransferPath::SameDevice)],
storage_substrate: SubstrateClass::HostBytes,
compute_throughput_flops_per_ns: 1.0,
mem_bandwidth_bytes_per_ns: 4.0,
}
}
fn cuda_caps() -> BackendCapabilities {
BackendCapabilities {
backend_id: BackendId::Cuda,
device_location: DeviceLocation::Cuda { gpu_id: 0 },
op_dtype_support: [
(OpKind::MatMul, DType::F32),
(OpKind::MatMul, DType::F16),
(OpKind::AddElementwise, DType::F32),
]
.into_iter()
.collect(),
required_alignment: 256,
access_granularity_bits: 8,
transfer_paths: vec![
(DeviceLocation::Cpu, TransferPath::DeviceCopy),
(DeviceLocation::Cuda { gpu_id: 0 }, TransferPath::SameDevice),
],
storage_substrate: SubstrateClass::CudaUntyped,
compute_throughput_flops_per_ns: 30.0,
mem_bandwidth_bytes_per_ns: 40.0,
}
}
#[test]
fn empty_registry() {
let r = CapabilityRegistry::new();
assert!(r.backends().is_empty());
let result = r.find_backend_for(OpKind::MatMul, DType::F32);
assert!(result.is_err());
}
#[test]
fn find_backends_order_preserved() {
let mut r = CapabilityRegistry::new();
r.register(cuda_caps()); r.register(cpu_caps());
let backends = r.find_backends(OpKind::MatMul, DType::F32);
assert_eq!(backends.len(), 2);
assert_eq!(backends[0].backend_id, BackendId::Cuda);
assert_eq!(backends[1].backend_id, BackendId::Cpu);
}
#[test]
fn find_backend_first_wins() {
let mut r = CapabilityRegistry::new();
r.register(cuda_caps());
r.register(cpu_caps());
let chosen = r.find_backend_for(OpKind::MatMul, DType::F32).unwrap();
assert_eq!(chosen.backend_id, BackendId::Cuda);
}
#[test]
fn cpu_handles_what_gpu_doesnt() {
let mut r = CapabilityRegistry::new();
r.register(cuda_caps());
r.register(cpu_caps());
let chosen = r.find_backend_for(OpKind::MatMul, DType::F64).unwrap();
assert_eq!(chosen.backend_id, BackendId::Cpu);
}
#[test]
fn unsupported_combo_errors_with_diagnostic() {
let mut r = CapabilityRegistry::new();
r.register(cpu_caps());
let err = match r.find_backend_for(OpKind::MatMul, DType::BF16) {
Ok(_) => panic!("expected error"),
Err(e) => e,
};
let msg = err.to_string();
assert!(
msg.contains("MatMul") || msg.contains("matmul"),
"error names op: {msg}"
);
assert!(msg.contains("BF16"), "error names dtype: {msg}");
assert!(msg.contains("Cpu"), "error names available backends: {msg}");
}
#[test]
fn transfer_matrix_same_device() {
let r = CapabilityRegistry::new();
let m = r.build_transfer_matrix();
assert_eq!(
m.path(DeviceLocation::Cpu, DeviceLocation::Cpu),
Some(TransferPath::SameDevice)
);
}
#[test]
fn transfer_matrix_built_from_caps() {
let mut r = CapabilityRegistry::new();
r.register(cuda_caps());
let m = r.build_transfer_matrix();
assert_eq!(
m.path(DeviceLocation::Cuda { gpu_id: 0 }, DeviceLocation::Cpu),
Some(TransferPath::DeviceCopy)
);
}
#[test]
fn host_staging_fallback() {
let r = CapabilityRegistry::new();
let m = r.build_transfer_matrix();
assert_eq!(
m.path_or_staging(DeviceLocation::Cpu, DeviceLocation::Cuda { gpu_id: 0 }),
TransferPath::HostStaging
);
}
#[test]
fn resolve_picks_first_match() {
let mut r = CapabilityRegistry::new();
r.register(cuda_caps());
r.register(cpu_caps());
assert_eq!(
resolve_target_backend(&r, OpKind::MatMul, DType::F32).unwrap(),
BackendId::Cuda
);
}
#[test]
fn resolve_falls_back_to_cpu() {
let mut r = CapabilityRegistry::new();
r.register(cuda_caps());
r.register(cpu_caps());
assert_eq!(
resolve_target_backend(&r, OpKind::MatMul, DType::F64).unwrap(),
BackendId::Cpu
);
}
#[test]
fn resolve_errors_on_unsupported() {
let mut r = CapabilityRegistry::new();
r.register(cpu_caps());
let result = resolve_target_backend(&r, OpKind::MatMul, DType::BF16);
assert!(result.is_err());
}
#[test]
fn resolve_residency_aware_prefers_local() {
let mut r = CapabilityRegistry::new();
r.register(cuda_caps());
r.register(cpu_caps());
let chosen = resolve_target_backend_residency_aware(
&r,
OpKind::MatMul,
DType::F32,
&[DeviceLocation::Cpu],
)
.unwrap();
assert_eq!(chosen, BackendId::Cpu);
}
#[test]
fn resolve_residency_aware_no_local_falls_back() {
let mut r = CapabilityRegistry::new();
r.register(cuda_caps());
r.register(cpu_caps());
let chosen = resolve_target_backend_residency_aware(
&r,
OpKind::MatMul,
DType::F32,
&[DeviceLocation::Vulkan { gpu_id: 0 }],
)
.unwrap();
assert_eq!(chosen, BackendId::Cuda);
}
#[test]
fn register_cpu_kernels_populates_table() {
let mut table = KernelBindingTable::new();
assert!(table.is_empty());
register_cpu_kernels(&mut table);
assert!(!table.is_empty());
let _kernel = table
.lookup(
OpKind::AddElementwise,
&[DType::F32, DType::F32, DType::F32],
BackendId::Cpu,
)
.expect("registered");
}
#[test]
fn global_bindings_registers_binary_family_from_contract() {
let table = global_bindings();
let families: &[(OpKind, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::AddElementwise,
[
add_elementwise_f32_cpu_wrapper,
add_elementwise_f64_cpu_wrapper,
add_elementwise_bf16_cpu_wrapper,
add_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::SubElementwise,
[
sub_elementwise_f32_cpu_wrapper,
sub_elementwise_f64_cpu_wrapper,
sub_elementwise_bf16_cpu_wrapper,
sub_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::MulElementwise,
[
mul_elementwise_f32_cpu_wrapper,
mul_elementwise_f64_cpu_wrapper,
mul_elementwise_bf16_cpu_wrapper,
mul_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::DivElementwise,
[
div_elementwise_f32_cpu_wrapper,
div_elementwise_f64_cpu_wrapper,
div_elementwise_bf16_cpu_wrapper,
div_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::MaximumElementwise,
[
maximum_elementwise_f32_cpu_wrapper,
maximum_elementwise_f64_cpu_wrapper,
maximum_elementwise_bf16_cpu_wrapper,
maximum_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::MinimumElementwise,
[
minimum_elementwise_f32_cpu_wrapper,
minimum_elementwise_f64_cpu_wrapper,
minimum_elementwise_bf16_cpu_wrapper,
minimum_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::PowElementwise,
[
pow_elementwise_f32_cpu_wrapper,
pow_elementwise_f64_cpu_wrapper,
pow_elementwise_bf16_cpu_wrapper,
pow_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::RemElementwise,
[
rem_elementwise_f32_cpu_wrapper,
rem_elementwise_f64_cpu_wrapper,
rem_elementwise_bf16_cpu_wrapper,
rem_elementwise_f16_cpu_wrapper,
],
),
];
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let mut checked = 0usize;
for (op, wrappers) in families {
for (dt, expected) in dts.iter().zip(wrappers.iter()) {
let alts = table.lookup_alternatives(*op, &[*dt, *dt, *dt], BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{dt:?}/Cpu: the production wrapper must be bound \
FROM the elementwise-binary contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{dt:?}: binary family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{dt:?}: caps preserved (contiguous-only, strided_input=false)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{dt:?}: the contract's audited bit-stable claim rode through",
);
checked += 1;
}
}
assert_eq!(checked, 32, "all 8 ops × 4 dtypes checked");
}
#[test]
fn global_bindings_registers_affine_clamp_powi_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let forward: &[(OpKind, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::Affine,
[
affine_f32_cpu_wrapper,
affine_f64_cpu_wrapper,
affine_bf16_cpu_wrapper,
affine_f16_cpu_wrapper,
],
),
(
OpKind::ClampElementwise,
[
clamp_elementwise_f32_cpu_wrapper,
clamp_f64_cpu_wrapper,
clamp_bf16_cpu_wrapper,
clamp_f16_cpu_wrapper,
],
),
(
OpKind::PowIElementwise,
[
powi_elementwise_f32_cpu_wrapper,
powi_f64_cpu_wrapper,
powi_bf16_cpu_wrapper,
powi_f16_cpu_wrapper,
],
),
];
let mut checked = 0usize;
for (op, wrappers) in forward {
for (dt, expected) in dts.iter().zip(wrappers.iter()) {
let alts = table.lookup_alternatives(*op, &[*dt, *dt], BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{dt:?}/Cpu: the production wrapper must be bound \
FROM the affine-clamp-powi contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{dt:?}: family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{dt:?}: caps preserved (contiguous-only, strided_input=false)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{dt:?}: the contract's audited bit-stable claim rode through",
);
checked += 1;
}
}
let bw: [crate::kernel::KernelRef; 4] = [
powi_backward_f32_cpu_wrapper,
powi_backward_f64_cpu_wrapper,
powi_backward_bf16_cpu_wrapper,
powi_backward_f16_cpu_wrapper,
];
for (dt, expected) in dts.iter().zip(bw.iter()) {
let alts = table.lookup_alternatives(
OpKind::PowIElementwiseBackward,
&[*dt, *dt, *dt],
BackendId::Cpu,
);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"PowIElementwiseBackward/{dt:?}/Cpu: the production wrapper must \
be bound FROM the affine-clamp-powi contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"PowIElementwiseBackward/{dt:?}: must be contract-sourced; got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"PowIElementwiseBackward/{dt:?}: caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"PowIElementwiseBackward/{dt:?}: the audited bit-stable claim rode through",
);
checked += 1;
}
assert_eq!(checked, 16, "3 forward ops × 4 dtypes + powi_backward × 4");
}
#[test]
fn global_bindings_registers_unary_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let families: &[(OpKind, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::ReluElementwise,
[
relu_elementwise_f32_cpu_wrapper,
relu_elementwise_f64_cpu_wrapper,
relu_elementwise_bf16_cpu_wrapper,
relu_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::NegElementwise,
[
neg_elementwise_f32_cpu_wrapper,
neg_elementwise_f64_cpu_wrapper,
neg_elementwise_bf16_cpu_wrapper,
neg_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::SqrElementwise,
[
sqr_elementwise_f32_cpu_wrapper,
sqr_elementwise_f64_cpu_wrapper,
sqr_elementwise_bf16_cpu_wrapper,
sqr_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::SqrtElementwise,
[
sqrt_elementwise_f32_cpu_wrapper,
sqrt_elementwise_f64_cpu_wrapper,
sqrt_elementwise_bf16_cpu_wrapper,
sqrt_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::RecipElementwise,
[
recip_elementwise_f32_cpu_wrapper,
recip_elementwise_f64_cpu_wrapper,
recip_elementwise_bf16_cpu_wrapper,
recip_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::AbsElementwise,
[
abs_elementwise_f32_cpu_wrapper,
abs_elementwise_f64_cpu_wrapper,
abs_elementwise_bf16_cpu_wrapper,
abs_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::TanhElementwise,
[
tanh_elementwise_f32_cpu_wrapper,
tanh_elementwise_f64_cpu_wrapper,
tanh_elementwise_bf16_cpu_wrapper,
tanh_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::ExpElementwise,
[
exp_elementwise_f32_cpu_wrapper,
exp_elementwise_f64_cpu_wrapper,
exp_elementwise_bf16_cpu_wrapper,
exp_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::LogElementwise,
[
log_elementwise_f32_cpu_wrapper,
log_elementwise_f64_cpu_wrapper,
log_elementwise_bf16_cpu_wrapper,
log_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::SinElementwise,
[
sin_elementwise_f32_cpu_wrapper,
sin_elementwise_f64_cpu_wrapper,
sin_elementwise_bf16_cpu_wrapper,
sin_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::CosElementwise,
[
cos_elementwise_f32_cpu_wrapper,
cos_elementwise_f64_cpu_wrapper,
cos_elementwise_bf16_cpu_wrapper,
cos_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::SigmoidElementwise,
[
sigmoid_elementwise_f32_cpu_wrapper,
sigmoid_elementwise_f64_cpu_wrapper,
sigmoid_elementwise_bf16_cpu_wrapper,
sigmoid_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::SiluElementwise,
[
silu_elementwise_f32_cpu_wrapper,
silu_elementwise_f64_cpu_wrapper,
silu_elementwise_bf16_cpu_wrapper,
silu_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::StepElementwise,
[
step_elementwise_f32_cpu_wrapper,
step_elementwise_f64_cpu_wrapper,
step_elementwise_bf16_cpu_wrapper,
step_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::GeluTanhElementwise,
[
gelu_elementwise_f32_cpu_wrapper,
gelu_elementwise_f64_cpu_wrapper,
gelu_elementwise_bf16_cpu_wrapper,
gelu_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::FloorElementwise,
[
floor_elementwise_f32_cpu_wrapper,
floor_elementwise_f64_cpu_wrapper,
floor_elementwise_bf16_cpu_wrapper,
floor_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::CeilElementwise,
[
ceil_elementwise_f32_cpu_wrapper,
ceil_elementwise_f64_cpu_wrapper,
ceil_elementwise_bf16_cpu_wrapper,
ceil_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::RoundElementwise,
[
round_elementwise_f32_cpu_wrapper,
round_elementwise_f64_cpu_wrapper,
round_elementwise_bf16_cpu_wrapper,
round_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::SignElementwise,
[
sign_elementwise_f32_cpu_wrapper,
sign_elementwise_f64_cpu_wrapper,
sign_elementwise_bf16_cpu_wrapper,
sign_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::ErfElementwise,
[
erf_elementwise_f32_cpu_wrapper,
erf_elementwise_f64_cpu_wrapper,
erf_elementwise_bf16_cpu_wrapper,
erf_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::GeluElementwise,
[
gelu_erf_elementwise_f32_cpu_wrapper,
gelu_erf_elementwise_f64_cpu_wrapper,
gelu_erf_elementwise_bf16_cpu_wrapper,
gelu_erf_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::RsqrtElementwise,
[
rsqrt_elementwise_f32_cpu_wrapper,
rsqrt_elementwise_f64_cpu_wrapper,
rsqrt_elementwise_bf16_cpu_wrapper,
rsqrt_elementwise_f16_cpu_wrapper,
],
),
];
let mut checked = 0usize;
for (op, wrappers) in families {
for (dt, expected) in dts.iter().zip(wrappers.iter()) {
let alts = table.lookup_alternatives(*op, &[*dt, *dt], BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{dt:?}/Cpu: the production wrapper must be bound \
FROM the elementwise-unary contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{dt:?}: unary family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{dt:?}: caps preserved (contiguous-only, strided_input=false)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{dt:?}: the contract's audited bit-stable claim rode through",
);
checked += 1;
}
}
assert_eq!(checked, 88, "all 22 ops × 4 dtypes checked");
}
#[test]
fn global_bindings_registers_compare_where_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let compare_families: &[(OpKind, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::EqualElementwise,
[
eq_elementwise_f32_cpu_wrapper,
eq_elementwise_f64_cpu_wrapper,
eq_elementwise_bf16_cpu_wrapper,
eq_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::NotEqualElementwise,
[
ne_elementwise_f32_cpu_wrapper,
ne_elementwise_f64_cpu_wrapper,
ne_elementwise_bf16_cpu_wrapper,
ne_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::LessElementwise,
[
lt_elementwise_f32_cpu_wrapper,
lt_elementwise_f64_cpu_wrapper,
lt_elementwise_bf16_cpu_wrapper,
lt_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::LessEqualElementwise,
[
le_elementwise_f32_cpu_wrapper,
le_elementwise_f64_cpu_wrapper,
le_elementwise_bf16_cpu_wrapper,
le_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::GreaterElementwise,
[
gt_elementwise_f32_cpu_wrapper,
gt_elementwise_f64_cpu_wrapper,
gt_elementwise_bf16_cpu_wrapper,
gt_elementwise_f16_cpu_wrapper,
],
),
(
OpKind::GreaterEqualElementwise,
[
ge_elementwise_f32_cpu_wrapper,
ge_elementwise_f64_cpu_wrapper,
ge_elementwise_bf16_cpu_wrapper,
ge_elementwise_f16_cpu_wrapper,
],
),
];
let mut checked = 0usize;
for (op, wrappers) in compare_families {
for (dt, expected) in dts.iter().zip(wrappers.iter()) {
let alts = table.lookup_alternatives(*op, &[*dt, *dt, DType::Bool], BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{dt:?}/Cpu: the production wrapper must be bound \
FROM the compare-where contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{dt:?}: compare family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{dt:?}: caps preserved (contiguous-only, strided_input=false)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{dt:?}: the contract's audited bit-stable claim rode through",
);
checked += 1;
}
}
let where_wrappers: [crate::kernel::KernelRef; 4] = [
where_f32_cpu_wrapper,
where_f64_cpu_wrapper,
where_bf16_cpu_wrapper,
where_f16_cpu_wrapper,
];
for (dt, expected) in dts.iter().zip(where_wrappers.iter()) {
let alts = table.lookup_alternatives(
OpKind::Where,
&[DType::Bool, *dt, *dt, *dt],
BackendId::Cpu,
);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"Where/{dt:?}/Cpu: the production wrapper must be bound FROM \
the compare-where contract (where fans to [U8,T,T,T]) in \
global_bindings() — found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"Where/{dt:?}: where must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"Where/{dt:?}: caps preserved (contiguous-only, strided_input=false)",
);
checked += 1;
}
assert_eq!(checked, 28, "24 compare (6×4) + 4 where keys checked");
}
#[test]
fn global_bindings_registers_reduce_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let reduce_families: &[(OpKind, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::SumReduce,
[
sum_reduce_f32_cpu_wrapper,
sum_reduce_f64_cpu_wrapper,
sum_reduce_bf16_cpu_wrapper,
sum_reduce_f16_cpu_wrapper,
],
),
(
OpKind::MeanReduce,
[
mean_reduce_f32_cpu_wrapper,
mean_reduce_f64_cpu_wrapper,
mean_reduce_bf16_cpu_wrapper,
mean_reduce_f16_cpu_wrapper,
],
),
(
OpKind::MaxReduce,
[
max_reduce_f32_cpu_wrapper,
max_reduce_f64_cpu_wrapper,
max_reduce_bf16_cpu_wrapper,
max_reduce_f16_cpu_wrapper,
],
),
(
OpKind::MinReduce,
[
min_reduce_f32_cpu_wrapper,
min_reduce_f64_cpu_wrapper,
min_reduce_bf16_cpu_wrapper,
min_reduce_f16_cpu_wrapper,
],
),
];
let mut checked = 0usize;
for (op, wrappers) in reduce_families {
for (dt, expected) in dts.iter().zip(wrappers.iter()) {
let alts = table.lookup_alternatives(*op, &[*dt, *dt], BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{dt:?}/Cpu: the production wrapper must be bound \
FROM the reduce contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{dt:?}: reduce family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{dt:?}: caps preserved (contiguous-only, strided_input=false)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{dt:?}: the contract's audited bit-stable claim rode through",
);
checked += 1;
}
}
assert_eq!(checked, 16, "4 ops × 4 dtypes checked");
}
#[test]
fn global_bindings_registers_reduce_to_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let unary_families: &[(OpKind, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::ReduceSumTo,
[
reduce_sum_to_f32_cpu_wrapper,
reduce_sum_to_f64_cpu_wrapper,
reduce_sum_to_bf16_cpu_wrapper,
reduce_sum_to_f16_cpu_wrapper,
],
),
(
OpKind::ReduceMaxTo,
[
reduce_max_to_f32_cpu_wrapper,
reduce_max_to_f64_cpu_wrapper,
reduce_max_to_bf16_cpu_wrapper,
reduce_max_to_f16_cpu_wrapper,
],
),
];
let backward_family: (OpKind, [crate::kernel::KernelRef; 4]) = (
OpKind::ReduceMaxToBackward,
[
reduce_max_to_backward_f32_cpu_wrapper,
reduce_max_to_backward_f64_cpu_wrapper,
reduce_max_to_backward_bf16_cpu_wrapper,
reduce_max_to_backward_f16_cpu_wrapper,
],
);
let assert_contract_bound =
|op: OpKind, key: &[DType], expected: crate::kernel::KernelRef| {
let alts = table.lookup_alternatives(op, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{key:?}/Cpu: the production wrapper must be bound \
FROM the reduce-to contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{key:?}: reduce-to family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{key:?}: caps preserved (contiguous-only, strided_input=false)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{key:?}: the contract's audited bit-stable claim rode through",
);
};
let mut checked = 0usize;
for (op, wrappers) in unary_families {
for (dt, expected) in dts.iter().zip(wrappers.iter()) {
assert_contract_bound(*op, &[*dt, *dt], *expected);
checked += 1;
}
}
let (op, wrappers) = backward_family;
for (dt, expected) in dts.iter().zip(wrappers.iter()) {
assert_contract_bound(op, &[*dt, *dt, *dt], *expected);
checked += 1;
}
assert_eq!(checked, 12, "3 ops × 4 dtypes checked");
}
#[test]
fn global_bindings_registers_norm_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let norm_families: &[(OpKind, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::SoftmaxLastDim,
[
softmax_last_dim_f32_cpu_wrapper,
softmax_last_dim_f64_cpu_wrapper,
softmax_last_dim_bf16_cpu_wrapper,
softmax_last_dim_f16_cpu_wrapper,
],
),
(
OpKind::LogSoftmaxLastDim,
[
log_softmax_f32_cpu_wrapper,
log_softmax_f64_cpu_wrapper,
log_softmax_bf16_cpu_wrapper,
log_softmax_f16_cpu_wrapper,
],
),
(
OpKind::RmsNormLastDim,
[
rms_norm_last_dim_f32_cpu_wrapper,
rms_norm_last_dim_f64_cpu_wrapper,
rms_norm_last_dim_bf16_cpu_wrapper,
rms_norm_last_dim_f16_cpu_wrapper,
],
),
(
OpKind::LayerNormLastDim,
[
layer_norm_last_dim_f32_cpu_wrapper,
layer_norm_last_dim_f64_cpu_wrapper,
layer_norm_last_dim_bf16_cpu_wrapper,
layer_norm_last_dim_f16_cpu_wrapper,
],
),
];
let mut checked = 0usize;
for (op, wrappers) in norm_families {
for (dt, expected) in dts.iter().zip(wrappers.iter()) {
let alts = table.lookup_alternatives(*op, &[*dt, *dt], BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{dt:?}/Cpu: the production wrapper must be bound \
FROM the norm contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{dt:?}: norm family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{dt:?}: caps preserved (contiguous-only, strided_input=false)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{dt:?}: the contract's audited bit-stable claim rode through",
);
checked += 1;
}
}
assert_eq!(checked, 16, "4 ops × 4 dtypes checked");
}
#[test]
fn global_bindings_registers_norm_backward_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let backward_families: &[(OpKind, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::SoftmaxLastDimBackward,
[
softmax_last_dim_backward_f32_cpu_wrapper,
softmax_last_dim_backward_f64_cpu_wrapper,
softmax_last_dim_backward_bf16_cpu_wrapper,
softmax_last_dim_backward_f16_cpu_wrapper,
],
),
(
OpKind::LogSoftmaxLastDimBackward,
[
log_softmax_backward_f32_cpu_wrapper,
log_softmax_backward_f64_cpu_wrapper,
log_softmax_backward_bf16_cpu_wrapper,
log_softmax_backward_f16_cpu_wrapper,
],
),
(
OpKind::LayerNormLastDimBackward,
[
layer_norm_last_dim_backward_f32_cpu_wrapper,
layer_norm_last_dim_backward_f64_cpu_wrapper,
layer_norm_last_dim_backward_bf16_cpu_wrapper,
layer_norm_last_dim_backward_f16_cpu_wrapper,
],
),
(
OpKind::RmsNormLastDimBackward,
[
rms_norm_last_dim_backward_f32_cpu_wrapper,
rms_norm_last_dim_backward_f64_cpu_wrapper,
rms_norm_last_dim_backward_bf16_cpu_wrapper,
rms_norm_last_dim_backward_f16_cpu_wrapper,
],
),
];
let mut checked = 0usize;
for (op, wrappers) in backward_families {
for (dt, expected) in dts.iter().zip(wrappers.iter()) {
let alts = table.lookup_alternatives(*op, &[*dt, *dt, *dt], BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{dt:?}/Cpu: the production wrapper must be bound \
FROM the norm-backward contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{dt:?}: norm-backward family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{dt:?}: caps preserved (contiguous-only, strided_input=false)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{dt:?}: the contract's audited bit-stable claim rode through",
);
checked += 1;
}
}
assert_eq!(checked, 16, "4 ops × 4 dtypes checked");
}
#[test]
fn global_bindings_registers_rope_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let expected_wrappers: [crate::kernel::KernelRef; 4] = [
rope_f32_cpu_wrapper,
rope_f64_cpu_wrapper,
rope_bf16_cpu_wrapper,
rope_f16_cpu_wrapper,
];
let mut checked = 0usize;
for (dt, expected) in dts.iter().zip(expected_wrappers.iter()) {
let alts =
table.lookup_alternatives(OpKind::Rope, &[*dt, *dt, *dt, *dt], BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"Rope/{dt:?}/Cpu: the production wrapper must be bound \
FROM the rope contract in global_bindings() \
— found {} alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"Rope/{dt:?}: rope family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"Rope/{dt:?}: caps preserved (contiguous-only, strided_input=false)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"Rope/{dt:?}: the contract's audited bit-stable claim rode through",
);
checked += 1;
}
assert_eq!(checked, 4, "1 op × 4 dtypes checked");
}
#[test]
fn global_bindings_registers_ssm_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let check = |op: OpKind, key: &[DType], expected: crate::kernel::KernelRef, label: &str| {
let alts = table.lookup_alternatives(op, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"{label}/Cpu (key {key:?}): the production wrapper must be bound \
FROM the ssm contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{label}: family must be contract-sourced (kernel_source=\"portable-cpu\"); \
got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{label}: caps preserved (contiguous-only)"
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{label}: contract's bit-stable claim rode through",
);
};
let fsce: [crate::kernel::KernelRef; 4] = [
fused_softmax_cross_entropy_f32_cpu_wrapper,
fused_softmax_cross_entropy_f64_cpu_wrapper,
fused_softmax_cross_entropy_bf16_cpu_wrapper,
fused_softmax_cross_entropy_f16_cpu_wrapper,
];
let conv: [crate::kernel::KernelRef; 4] = [
causal_conv1d_f32_cpu_wrapper,
causal_conv1d_f64_cpu_wrapper,
causal_conv1d_bf16_cpu_wrapper,
causal_conv1d_f16_cpu_wrapper,
];
let sscan: [crate::kernel::KernelRef; 4] = [
selective_scan_f32_cpu_wrapper,
selective_scan_f64_cpu_wrapper,
selective_scan_bf16_cpu_wrapper,
selective_scan_f16_cpu_wrapper,
];
let ssd: [crate::kernel::KernelRef; 4] = [
ssd_chunk_scan_f32_cpu_wrapper,
ssd_chunk_scan_f64_cpu_wrapper,
ssd_chunk_scan_bf16_cpu_wrapper,
ssd_chunk_scan_f16_cpu_wrapper,
];
let mut checked = 0usize;
for (i, dt) in dts.iter().enumerate() {
check(
OpKind::FusedSoftmaxCrossEntropy,
&[*dt, DType::I64, DType::F32],
fsce[i],
"FusedSoftmaxCrossEntropy",
);
check(OpKind::CausalConv1d, &[*dt; 4], conv[i], "CausalConv1d");
check(OpKind::SelectiveScan, &[*dt; 6], sscan[i], "SelectiveScan");
check(OpKind::SsdChunkScan, &[*dt; 6], ssd[i], "SsdChunkScan");
checked += 1;
}
assert_eq!(
checked, 4,
"4 ops × 4 dtypes checked (16 contract-sourced bindings)"
);
}
#[test]
fn global_bindings_registers_conv_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let conv2d_wrappers: [crate::kernel::KernelRef; 4] = [
conv2d_f32_cpu_wrapper,
conv2d_f64_cpu_wrapper,
conv2d_bf16_cpu_wrapper,
conv2d_f16_cpu_wrapper,
];
let convt_wrappers: [crate::kernel::KernelRef; 4] = [
conv_transpose2d_f32_cpu_wrapper,
conv_transpose2d_f64_cpu_wrapper,
conv_transpose2d_bf16_cpu_wrapper,
conv_transpose2d_f16_cpu_wrapper,
];
let mut checked = 0usize;
for (i, dt) in dts.iter().enumerate() {
for (op, expected) in [
(OpKind::Conv2D, conv2d_wrappers[i]),
(OpKind::ConvTranspose2D, convt_wrappers[i]),
] {
let no_bias: &[DType] = &[*dt, *dt, *dt];
let with_bias: &[DType] = &[*dt, *dt, *dt, *dt];
for (key, label) in [(no_bias, "no-bias"), (with_bias, "with-bias")] {
let alts = table.lookup_alternatives(op, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{dt:?}/Cpu ({label}): the production wrapper must be \
bound FROM the conv contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{dt:?} ({label}): family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{dt:?} ({label}): caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{dt:?} ({label}): contract's bit-stable claim rode through",
);
checked += 1;
}
}
}
assert_eq!(
checked, 16,
"2 ops × 4 dtypes × 2 operand-counts checked (16 contract-sourced bindings)"
);
}
#[test]
fn global_bindings_registers_padding_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let pad_backward: [crate::kernel::KernelRef; 4] = [
pad_backward_f32_cpu_wrapper,
pad_backward_f64_cpu_wrapper,
pad_backward_bf16_cpu_wrapper,
pad_backward_f16_cpu_wrapper,
];
let mut checked = 0usize;
for (i, dt) in dts.iter().enumerate() {
let key: &[DType] = &[*dt, *dt];
let expected = pad_backward[i];
let alts = table.lookup_alternatives(OpKind::PadBackward, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"PadBackward/{dt:?}/Cpu: the production wrapper must be bound \
FROM the padding contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"PadBackward/{dt:?}: family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"PadBackward/{dt:?}: caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"PadBackward/{dt:?}: contract's bit-stable claim rode through",
);
checked += 1;
}
let pad_forward_dts = [
DType::U8,
DType::U32,
DType::BF16,
DType::F16,
DType::F32,
DType::F64,
];
for dt in pad_forward_dts {
let key: &[DType] = &[dt, dt];
let alts = table.lookup_alternatives(OpKind::Pad, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == pad_cpu_wrapper as *const () as usize)
.unwrap_or_else(|| {
panic!(
"Pad/{dt:?}/Cpu: the production wrapper must be bound \
FROM the padding contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"Pad/{dt:?}: forward family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"Pad/{dt:?}: caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"Pad/{dt:?}: contract's bit-stable claim rode through",
);
checked += 1;
}
assert_eq!(
checked, 10,
"4 PadBackward + 6 forward Pad dtypes checked (contract-sourced bindings)"
);
}
#[test]
fn global_bindings_registers_shape_ops_family_from_contract() {
let table = global_bindings();
let mut checked = 0usize;
let mut check =
|op: OpKind, key: &[DType], expected: crate::kernel::KernelRef, label: &str| {
let alts = table.lookup_alternatives(op, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{label}/Cpu: the production wrapper must be bound \
FROM the shape-ops contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{label}: family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{label}: caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{label}: contract's bit-stable claim rode through",
);
checked += 1;
};
let byte_dts = [
DType::F32,
DType::F64,
DType::BF16,
DType::F16,
DType::U32,
DType::U8,
];
for dt in byte_dts {
check(OpKind::Flip, &[dt, dt], flip_cpu_wrapper, "flip");
check(OpKind::Roll, &[dt, dt], roll_cpu_wrapper, "roll");
check(
OpKind::MaskedFill,
&[dt, DType::Bool, dt], masked_fill_cpu_wrapper,
"masked_fill",
);
}
let cumsum_dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let cumsum_wrappers: [crate::kernel::KernelRef; 4] = [
cumsum_f32_cpu_wrapper,
cumsum_f64_cpu_wrapper,
cumsum_bf16_cpu_wrapper,
cumsum_f16_cpu_wrapper,
];
for (i, dt) in cumsum_dts.iter().enumerate() {
check(OpKind::CumSum, &[*dt, *dt], cumsum_wrappers[i], "cumsum");
}
let concat_dts = [
DType::F32,
DType::F64,
DType::BF16,
DType::F16,
DType::U32,
DType::U8,
DType::I16,
DType::I32,
DType::I64,
];
for dt in concat_dts {
check(OpKind::Concat, &[dt, dt], concat_cpu_wrapper, "concat");
}
let scatter_dts = [
DType::F32,
DType::F64,
DType::BF16,
DType::F16,
DType::U32,
DType::U8,
];
for dt in scatter_dts {
check(
OpKind::WriteSlice,
&[dt, dt],
write_slice_cpu_wrapper,
"write_slice",
);
check(
OpKind::WriteSliceRotating,
&[dt, dt],
write_slice_rotating_cpu_wrapper,
"write_slice_rotating",
);
check(
OpKind::WriteSliceDoff,
&[dt, dt],
write_slice_doff_cpu_wrapper,
"write_slice_doff",
);
}
assert_eq!(
checked,
6 + 6 + 6 + 4 + 9 + 6 + 6 + 6,
"flip(6) + roll(6) + masked_fill(6) + cumsum(4) + concat(9) + \
write_slice(6) + write_slice_rotating(6) + write_slice_doff(6) contract-sourced bindings",
);
}
#[test]
fn global_bindings_registers_indexing_family_from_contract() {
let table = global_bindings();
let mut checked = 0usize;
let mut check =
|op: OpKind, key: &[DType], expected: crate::kernel::KernelRef, label: &str| {
let alts = table.lookup_alternatives(op, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{label}/Cpu: the production wrapper must be bound \
FROM the indexing contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{label}: family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{label}: caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{label}: contract's bit-stable claim rode through",
);
checked += 1;
};
let byte_dts = [
DType::F32,
DType::F64,
DType::BF16,
DType::F16,
DType::U32,
DType::U8,
DType::I16,
DType::I32,
DType::I64,
];
for dt in byte_dts {
check(
OpKind::IndexSelect,
&[dt, DType::U32, dt],
index_select_cpu_wrapper,
"index_select",
);
check(
OpKind::Gather,
&[dt, DType::U32, dt],
gather_cpu_wrapper,
"gather",
);
}
let acc_dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let index_add_wrappers: [crate::kernel::KernelRef; 4] = [
index_add_f32_cpu_wrapper,
index_add_f64_cpu_wrapper,
index_add_bf16_cpu_wrapper,
index_add_f16_cpu_wrapper,
];
let scatter_add_wrappers: [crate::kernel::KernelRef; 4] = [
scatter_add_f32_cpu_wrapper,
scatter_add_f64_cpu_wrapper,
scatter_add_bf16_cpu_wrapper,
scatter_add_f16_cpu_wrapper,
];
for (i, dt) in acc_dts.iter().enumerate() {
check(
OpKind::IndexAdd,
&[*dt, DType::U32, *dt, *dt],
index_add_wrappers[i],
"index_add",
);
check(
OpKind::ScatterAdd,
&[*dt, DType::U32, *dt, *dt],
scatter_add_wrappers[i],
"scatter_add",
);
}
assert_eq!(
checked,
9 + 9 + 4 + 4,
"index_select(9) + gather(9) + index_add(4) + scatter_add(4) \
contract-sourced bindings",
);
}
#[test]
fn global_bindings_registers_matmul_family_from_contract() {
let table = global_bindings();
let mut checked = 0usize;
let mut check =
|op: OpKind, key: &[DType], expected: crate::kernel::KernelRef, label: &str| {
let alts = table.lookup_alternatives(op, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{label}/Cpu: the portable wrapper must be bound \
FROM the matmul contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{label}: portable kernel must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{label}: caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{label}: contract's bit-stable claim rode through",
);
checked += 1;
};
let mm_dts = [
DType::F32,
DType::F64,
DType::BF16,
DType::F16,
DType::I8,
DType::U8,
];
let mm_wrappers: [crate::kernel::KernelRef; 6] = [
matmul_f32_cpu_wrapper,
matmul_f64_cpu_wrapper,
matmul_bf16_cpu_wrapper,
matmul_f16_cpu_wrapper,
matmul_i8_cpu_wrapper,
matmul_u8_cpu_wrapper,
];
for (i, dt) in mm_dts.iter().enumerate() {
check(OpKind::MatMul, &[*dt, *dt, *dt], mm_wrappers[i], "matmul");
}
let fl_dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let fl_wrappers: [crate::kernel::KernelRef; 4] = [
fused_linear_f32_cpu_wrapper,
fused_linear_f64_cpu_wrapper,
fused_linear_bf16_cpu_wrapper,
fused_linear_f16_cpu_wrapper,
];
for (i, dt) in fl_dts.iter().enumerate() {
check(
OpKind::FusedLinear,
&[*dt, *dt, *dt, *dt],
fl_wrappers[i],
"fused_linear",
);
}
assert_eq!(
checked,
6 + 4,
"matmul(6) + fused_linear(4) contract-sourced bindings",
);
}
#[test]
fn global_bindings_registers_attention_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let fa_wrappers: [crate::kernel::KernelRef; 4] = [
flash_attn_f32_cpu_wrapper,
flash_attn_f64_cpu_wrapper,
flash_attn_bf16_cpu_wrapper,
flash_attn_f16_cpu_wrapper,
];
let fabq: [crate::kernel::KernelRef; 4] = [
flash_attn_backward_q_f32_cpu_wrapper,
flash_attn_backward_q_f64_cpu_wrapper,
flash_attn_backward_q_bf16_cpu_wrapper,
flash_attn_backward_q_f16_cpu_wrapper,
];
let fabk: [crate::kernel::KernelRef; 4] = [
flash_attn_backward_k_f32_cpu_wrapper,
flash_attn_backward_k_f64_cpu_wrapper,
flash_attn_backward_k_bf16_cpu_wrapper,
flash_attn_backward_k_f16_cpu_wrapper,
];
let fabv: [crate::kernel::KernelRef; 4] = [
flash_attn_backward_v_f32_cpu_wrapper,
flash_attn_backward_v_f64_cpu_wrapper,
flash_attn_backward_v_bf16_cpu_wrapper,
flash_attn_backward_v_f16_cpu_wrapper,
];
let pa_wrappers: [crate::kernel::KernelRef; 4] = [
paged_attn_f32_cpu_wrapper,
paged_attn_f64_cpu_wrapper,
paged_attn_bf16_cpu_wrapper,
paged_attn_f16_cpu_wrapper,
];
let check = |op: OpKind, key: &[DType], expected: crate::kernel::KernelRef, label: &str| {
let alts = table.lookup_alternatives(op, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{label}/Cpu: the production wrapper must be bound \
FROM the attention contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{label}: family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{label}: caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{label}: contract's bit-stable claim rode through",
);
};
let mut checked = 0usize;
for (i, dt) in dts.iter().enumerate() {
let fa_noa: &[DType] = &[*dt, *dt, *dt, *dt];
let fa_a: &[DType] = &[*dt, *dt, *dt, *dt, *dt];
check(OpKind::FlashAttn, fa_noa, fa_wrappers[i], "flash no-alibi");
check(OpKind::FlashAttn, fa_a, fa_wrappers[i], "flash with-alibi");
checked += 2;
let bw_noa: &[DType] = &[*dt, *dt, *dt, *dt, *dt];
let bw_a: &[DType] = &[*dt, *dt, *dt, *dt, *dt, *dt];
for (op, ws) in [
(OpKind::FlashAttnBackwardQ, &fabq),
(OpKind::FlashAttnBackwardK, &fabk),
(OpKind::FlashAttnBackwardV, &fabv),
] {
check(op, bw_noa, ws[i], "backward no-alibi");
check(op, bw_a, ws[i], "backward with-alibi");
checked += 2;
}
let pa_noa: &[DType] = &[*dt, *dt, *dt, DType::U32, DType::U32, *dt];
let pa_a: &[DType] = &[*dt, *dt, *dt, DType::U32, DType::U32, *dt, *dt];
check(OpKind::PagedAttn, pa_noa, pa_wrappers[i], "paged no-alibi");
check(OpKind::PagedAttn, pa_a, pa_wrappers[i], "paged with-alibi");
checked += 2;
}
assert_eq!(
checked, 40,
"FlashAttn(8) + FlashAttnBackward{{Q,K,V}}(24) + PagedAttn(8) = 40 \
contract-sourced bindings",
);
}
#[test]
fn global_bindings_registers_inplace_family_from_contract() {
let table = global_bindings();
let dts = [DType::F32, DType::F64, DType::BF16, DType::F16];
let check = |op: OpKind, dt: DType, expected: crate::kernel::KernelRef, label: &str| {
let key: &[DType] = &[dt, dt];
let alts = table.lookup_alternatives(op, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"{op:?}/{label}/{dt:?}/Cpu: the production wrapper must be bound \
FROM the in-place contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"{op:?}/{label}/{dt:?}: family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?}/{label}/{dt:?}: caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"{op:?}/{label}/{dt:?}: contract's bit-stable claim rode through",
);
};
let unary_family: &[(OpKind, &str, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::ReluInplace,
"relu",
[
relu_inplace_f32_cpu_wrapper,
relu_inplace_f64_cpu_wrapper,
relu_inplace_bf16_cpu_wrapper,
relu_inplace_f16_cpu_wrapper,
],
),
(
OpKind::SiluInplace,
"silu",
[
silu_inplace_f32_cpu_wrapper,
silu_inplace_f64_cpu_wrapper,
silu_inplace_bf16_cpu_wrapper,
silu_inplace_f16_cpu_wrapper,
],
),
(
OpKind::GeluTanhInplace,
"gelu_tanh",
[
gelu_inplace_f32_cpu_wrapper,
gelu_inplace_f64_cpu_wrapper,
gelu_inplace_bf16_cpu_wrapper,
gelu_inplace_f16_cpu_wrapper,
],
),
(
OpKind::TanhInplace,
"tanh",
[
tanh_inplace_f32_cpu_wrapper,
tanh_inplace_f64_cpu_wrapper,
tanh_inplace_bf16_cpu_wrapper,
tanh_inplace_f16_cpu_wrapper,
],
),
(
OpKind::SigmoidInplace,
"sigmoid",
[
sigmoid_inplace_f32_cpu_wrapper,
sigmoid_inplace_f64_cpu_wrapper,
sigmoid_inplace_bf16_cpu_wrapper,
sigmoid_inplace_f16_cpu_wrapper,
],
),
(
OpKind::NegInplace,
"neg",
[
neg_inplace_f32_cpu_wrapper,
neg_inplace_f64_cpu_wrapper,
neg_inplace_bf16_cpu_wrapper,
neg_inplace_f16_cpu_wrapper,
],
),
(
OpKind::AbsInplace,
"abs",
[
abs_inplace_f32_cpu_wrapper,
abs_inplace_f64_cpu_wrapper,
abs_inplace_bf16_cpu_wrapper,
abs_inplace_f16_cpu_wrapper,
],
),
(
OpKind::SqrInplace,
"sqr",
[
sqr_inplace_f32_cpu_wrapper,
sqr_inplace_f64_cpu_wrapper,
sqr_inplace_bf16_cpu_wrapper,
sqr_inplace_f16_cpu_wrapper,
],
),
(
OpKind::SqrtInplace,
"sqrt",
[
sqrt_inplace_f32_cpu_wrapper,
sqrt_inplace_f64_cpu_wrapper,
sqrt_inplace_bf16_cpu_wrapper,
sqrt_inplace_f16_cpu_wrapper,
],
),
(
OpKind::RsqrtInplace,
"rsqrt",
[
rsqrt_inplace_f32_cpu_wrapper,
rsqrt_inplace_f64_cpu_wrapper,
rsqrt_inplace_bf16_cpu_wrapper,
rsqrt_inplace_f16_cpu_wrapper,
],
),
(
OpKind::RecipInplace,
"recip",
[
recip_inplace_f32_cpu_wrapper,
recip_inplace_f64_cpu_wrapper,
recip_inplace_bf16_cpu_wrapper,
recip_inplace_f16_cpu_wrapper,
],
),
(
OpKind::ExpInplace,
"exp",
[
exp_inplace_f32_cpu_wrapper,
exp_inplace_f64_cpu_wrapper,
exp_inplace_bf16_cpu_wrapper,
exp_inplace_f16_cpu_wrapper,
],
),
(
OpKind::LogInplace,
"log",
[
log_inplace_f32_cpu_wrapper,
log_inplace_f64_cpu_wrapper,
log_inplace_bf16_cpu_wrapper,
log_inplace_f16_cpu_wrapper,
],
),
(
OpKind::SinInplace,
"sin",
[
sin_inplace_f32_cpu_wrapper,
sin_inplace_f64_cpu_wrapper,
sin_inplace_bf16_cpu_wrapper,
sin_inplace_f16_cpu_wrapper,
],
),
(
OpKind::CosInplace,
"cos",
[
cos_inplace_f32_cpu_wrapper,
cos_inplace_f64_cpu_wrapper,
cos_inplace_bf16_cpu_wrapper,
cos_inplace_f16_cpu_wrapper,
],
),
(
OpKind::SignInplace,
"sign",
[
sign_inplace_f32_cpu_wrapper,
sign_inplace_f64_cpu_wrapper,
sign_inplace_bf16_cpu_wrapper,
sign_inplace_f16_cpu_wrapper,
],
),
(
OpKind::FloorInplace,
"floor",
[
floor_inplace_f32_cpu_wrapper,
floor_inplace_f64_cpu_wrapper,
floor_inplace_bf16_cpu_wrapper,
floor_inplace_f16_cpu_wrapper,
],
),
(
OpKind::CeilInplace,
"ceil",
[
ceil_inplace_f32_cpu_wrapper,
ceil_inplace_f64_cpu_wrapper,
ceil_inplace_bf16_cpu_wrapper,
ceil_inplace_f16_cpu_wrapper,
],
),
(
OpKind::RoundInplace,
"round",
[
round_inplace_f32_cpu_wrapper,
round_inplace_f64_cpu_wrapper,
round_inplace_bf16_cpu_wrapper,
round_inplace_f16_cpu_wrapper,
],
),
(
OpKind::ErfInplace,
"erf",
[
erf_inplace_f32_cpu_wrapper,
erf_inplace_f64_cpu_wrapper,
erf_inplace_bf16_cpu_wrapper,
erf_inplace_f16_cpu_wrapper,
],
),
(
OpKind::GeluInplace,
"gelu",
[
gelu_erf_inplace_f32_cpu_wrapper,
gelu_erf_inplace_f64_cpu_wrapper,
gelu_erf_inplace_bf16_cpu_wrapper,
gelu_erf_inplace_f16_cpu_wrapper,
],
),
];
let scalar_family: &[(OpKind, &str, [crate::kernel::KernelRef; 4])] = &[
(
OpKind::InplaceAffine,
"affine",
[
inplace_affine_f32_cpu_wrapper,
inplace_affine_f64_cpu_wrapper,
inplace_affine_bf16_cpu_wrapper,
inplace_affine_f16_cpu_wrapper,
],
),
(
OpKind::ClampInplace,
"clamp",
[
clamp_inplace_f32_cpu_wrapper,
clamp_inplace_f64_cpu_wrapper,
clamp_inplace_bf16_cpu_wrapper,
clamp_inplace_f16_cpu_wrapper,
],
),
(
OpKind::PowIInplace,
"powi",
[
powi_inplace_f32_cpu_wrapper,
powi_inplace_f64_cpu_wrapper,
powi_inplace_bf16_cpu_wrapper,
powi_inplace_f16_cpu_wrapper,
],
),
];
let mut checked = 0usize;
for (op, label, ws) in unary_family.iter().chain(scalar_family.iter()) {
for (i, dt) in dts.iter().enumerate() {
check(*op, *dt, ws[i], label);
checked += 1;
}
}
assert_eq!(
checked, 96,
"21 in-place unary (×4) + InplaceAffine/ClampInplace/PowIInplace (×4) = 96 \
contract-sourced bindings",
);
}
#[test]
fn global_bindings_registers_cast_family_from_contract() {
let table = global_bindings();
let targets: [(DType, crate::kernel::KernelRef); 11] = [
(DType::F32, cast_to_f32_cpu_wrapper),
(DType::F64, cast_to_f64_cpu_wrapper),
(DType::F16, cast_to_f16_cpu_wrapper),
(DType::BF16, cast_to_bf16_cpu_wrapper),
(DType::F8E4M3, cast_to_f8e4m3_cpu_wrapper),
(DType::U8, cast_to_u8_cpu_wrapper),
(DType::I8, cast_to_i8_cpu_wrapper),
(DType::U32, cast_to_u32_cpu_wrapper),
(DType::I16, cast_to_i16_cpu_wrapper),
(DType::I32, cast_to_i32_cpu_wrapper),
(DType::I64, cast_to_i64_cpu_wrapper),
];
let all_dts: [DType; 11] = [
DType::F32,
DType::F64,
DType::F16,
DType::BF16,
DType::F8E4M3,
DType::U8,
DType::I8,
DType::U32,
DType::I16,
DType::I32,
DType::I64,
];
let mut checked = 0usize;
for (dst, expected) in targets {
for src in all_dts {
if src == dst {
continue; }
let key: &[DType] = &[src, dst];
let alts = table.lookup_alternatives(OpKind::Cast, key, BackendId::Cpu);
let entry = alts
.iter()
.find(|e| e.kernel as usize == expected as usize)
.unwrap_or_else(|| {
panic!(
"Cast {src:?}->{dst:?}/Cpu: the production per-target wrapper must be \
bound FROM the cast contract in global_bindings() — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "portable-cpu",
"Cast {src:?}->{dst:?}: family must be contract-sourced \
(kernel_source=\"portable-cpu\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"Cast {src:?}->{dst:?}: caps preserved (contiguous-only)",
);
assert!(
entry.precision.bit_stable_on_same_hardware,
"Cast {src:?}->{dst:?}: contract's bit-stable claim rode through",
);
checked += 1;
}
}
assert_eq!(
checked, 110,
"11 target dtypes × 10 sources each = 110 directed cast pairs (identity excluded)"
);
}
#[test]
fn binding_table_lookup_miss_errors() {
let mut table = KernelBindingTable::new();
register_cpu_kernels(&mut table);
let result = table.lookup(
OpKind::AddElementwise,
&[DType::I64, DType::I64, DType::I64],
BackendId::Cpu,
);
assert!(result.is_err());
}
#[test]
fn cpu_f8e4m3_cast_pairs_registered() {
let mut table = KernelBindingTable::new();
register_cpu_kernels(&mut table);
for other in [DType::F32, DType::F16, DType::BF16] {
for (src, dst) in [(DType::F8E4M3, other), (other, DType::F8E4M3)] {
table
.lookup(OpKind::Cast, &[src, dst], BackendId::Cpu)
.unwrap_or_else(|e| panic!("Cast {src:?} → {dst:?} (CPU) not registered: {e}"));
}
}
}
#[test]
fn cpu_int_matmul_registered() {
let mut table = KernelBindingTable::new();
register_cpu_kernels(&mut table);
for t in [DType::I8, DType::U8] {
table
.lookup(OpKind::MatMul, &[t, t, t], BackendId::Cpu)
.unwrap_or_else(|e| panic!("MatMul {t:?} (CPU) not registered: {e}"));
}
}
#[test]
fn b5_end_to_end_add_elementwise_f32() {
let mut registry = CapabilityRegistry::new();
registry.register(cpu_caps());
let backend =
resolve_target_backend(®istry, OpKind::AddElementwise, DType::F32).expect("resolve");
assert_eq!(backend, BackendId::Cpu);
let mut bindings = KernelBindingTable::new();
register_cpu_kernels(&mut bindings);
let kernel = bindings
.lookup(
OpKind::AddElementwise,
&[DType::F32, DType::F32, DType::F32],
backend,
)
.expect("lookup");
let lhs = fuel_memory::from_slice_cpu(&[1.0_f32, 2.0, 3.0, 4.0]);
let rhs = fuel_memory::from_slice_cpu(&[10.0_f32, 20.0, 30.0, 40.0]);
let out = fuel_memory::alloc_cpu_zeroed(DType::F32, 4).expect("alloc");
let inputs = vec![Arc::new(RwLock::new(lhs)), Arc::new(RwLock::new(rhs))];
let mut outputs = vec![Arc::new(RwLock::new(out))];
kernel(&inputs, &mut outputs, &[], &OpParams::None).expect("kernel");
let out_guard = outputs[0].read().unwrap();
if let BackendStorage::Cpu(c) = &out_guard.inner {
let typed: &[f32] = c.as_slice().expect("cast");
assert_eq!(typed, &[11.0, 22.0, 33.0, 44.0]);
} else {
panic!("expected CPU output");
}
}
#[test]
fn b5_wrapper_arity_check() {
let mut bindings = KernelBindingTable::new();
register_cpu_kernels(&mut bindings);
let kernel = bindings
.lookup(
OpKind::AddElementwise,
&[DType::F32, DType::F32, DType::F32],
BackendId::Cpu,
)
.unwrap();
let lhs = fuel_memory::from_slice_cpu(&[1.0_f32, 2.0]);
let out = fuel_memory::alloc_cpu_zeroed(DType::F32, 2).unwrap();
let inputs = vec![Arc::new(RwLock::new(lhs))];
let mut outputs = vec![Arc::new(RwLock::new(out))];
let result = kernel(&inputs, &mut outputs, &[], &OpParams::None);
assert!(result.is_err());
}
#[test]
fn held_snapshot_does_not_block_a_concurrent_write() {
use std::sync::mpsc;
use std::time::Duration;
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let snap = global_bindings();
let before = snap
.lookup(
OpKind::AddElementwise,
&[DType::F32, DType::F32, DType::F32],
BackendId::Cpu,
)
.is_ok();
extend_global_bindings(|_t| {});
let _snap2 = global_bindings();
let after = snap
.lookup(
OpKind::AddElementwise,
&[DType::F32, DType::F32, DType::F32],
BackendId::Cpu,
)
.is_ok();
tx.send((before, after)).ok();
});
match rx.recv_timeout(Duration::from_secs(30)) {
Ok((before, after)) => {
assert!(
before,
"CPU AddElementwise+F32 should resolve in the snapshot"
);
assert!(
after,
"snapshot must stay readable after a concurrent table swap"
);
}
Err(_) => panic!(
"GAP-015 regression: holding a binding-table snapshot across \
extend_global_bindings() deadlocked (30s watchdog). The read \
path must hand out an owned snapshot, not a lock guard."
),
}
}
#[test]
fn global_registry_auto_registers_cpu() {
let r = global_registry();
assert!(!r.backends().is_empty());
let cpu = r.backends().iter().find(|c| c.backend_id == BackendId::Cpu);
assert!(cpu.is_some(), "CPU should be auto-registered");
}
#[test]
fn global_bindings_auto_registers_cpu_wrappers() {
let b = global_bindings();
let result = b.lookup(
OpKind::AddElementwise,
&[DType::F32, DType::F32, DType::F32],
BackendId::Cpu,
);
assert!(
result.is_ok(),
"CPU AddElementwise+F32 should be registered"
);
}
#[cfg(feature = "cuda")]
#[test]
fn global_bindings_auto_registers_cuda_paths() {
let b = global_bindings();
let cublas = b.lookup(
OpKind::MatMul,
&[DType::F32, DType::F32, DType::F32],
BackendId::Cuda,
);
assert!(
cublas.is_ok(),
"cuBLAS F32 MatMul should be auto-registered on Cuda"
);
let baracuda_int8 = b.lookup(
OpKind::MatMul,
&[DType::I8, DType::I8, DType::I8],
BackendId::Cuda,
);
assert!(
baracuda_int8.is_ok(),
"baracuda int8 MatMul should be auto-registered on Cuda",
);
let alts = b.lookup_alternatives(
OpKind::NegElementwise,
&[DType::F32, DType::F32],
BackendId::Cuda,
);
assert!(
alts.len() == 1,
"expected exactly 1 CUDA Neg F32 alternative (baracuda); got {}",
alts.len(),
);
}
#[test]
#[ignore]
fn audit_multi_backend_coverage() {
let b = global_bindings();
type CoverageGroup = ((OpKind, Vec<DType>), Vec<(BackendId, usize)>);
let mut grouped: Vec<CoverageGroup> = Vec::new();
for (op, dtypes, backend, _precision) in b.iter_precision() {
let key = (op, dtypes.to_vec());
if let Some((_, entry)) = grouped.iter_mut().find(|(k, _)| *k == key) {
if let Some(slot) = entry.iter_mut().find(|(be, _)| *be == backend) {
slot.1 += 1;
} else {
entry.push((backend, 1));
}
} else {
grouped.push((key, vec![(backend, 1)]));
}
}
grouped.sort_by(|a, b| {
let oa = format!("{:?}", a.0.0);
let ob = format!("{:?}", b.0.0);
oa.cmp(&ob).then_with(|| {
let da = format!("{:?}", a.0.1);
let db = format!("{:?}", b.0.1);
da.cmp(&db)
})
});
let total_keys = grouped.len();
let mut multi_backend = 0;
let mut multi_impl_single_backend = 0;
let mut single_alt = 0;
println!("\n=== Picker-alternatives audit ===");
println!("Total unique (op, dtypes) keys: {total_keys}");
println!("Total alternatives (sum): {}", b.len());
println!();
println!("Keys with >1 alternative (per Judge-picking working set):");
for ((op, dtypes), backends) in &grouped {
let total_alts: usize = backends.iter().map(|(_, n)| *n).sum();
if total_alts <= 1 {
single_alt += 1;
continue;
}
if backends.len() > 1 {
multi_backend += 1;
} else {
multi_impl_single_backend += 1;
}
let backend_summary: Vec<String> = backends
.iter()
.map(|(be, n)| {
if *n == 1 {
format!("{be:?}")
} else {
format!("{be:?}×{n}")
}
})
.collect();
println!(
" {op:?} {dtypes:?} → {} alts: [{}]",
total_alts,
backend_summary.join(", "),
);
}
println!();
println!("--- summary ---");
println!("Single-alternative keys (no pick to make): {single_alt}");
println!("Multi-backend keys (cross-backend pick): {multi_backend}");
println!("Multi-impl single-backend keys: {multi_impl_single_backend}");
}
#[test]
fn inflight_inc_dec_balances_to_baseline() {
let loc = DeviceLocation::Cuda { gpu_id: 99_001 };
let base = inflight_count(loc);
const N: u32 = 7;
for i in 0..N {
inflight_inc(loc);
assert_eq!(
inflight_count(loc),
base + i + 1,
"inc must raise the count"
);
}
assert_eq!(inflight_count(loc), base + N);
for i in 0..N {
inflight_dec(loc);
assert_eq!(
inflight_count(loc),
base + N - i - 1,
"dec must lower the count",
);
}
assert_eq!(
inflight_count(loc),
base,
"balanced inc/dec must return the count to its pre-sequence baseline",
);
}
#[test]
fn inflight_dec_is_underflow_safe() {
let untouched = DeviceLocation::Vulkan { gpu_id: 99_002 };
inflight_dec(untouched);
assert_eq!(
inflight_count(untouched),
0,
"dec on an untouched device stays 0"
);
let loc = DeviceLocation::Vulkan { gpu_id: 99_003 };
inflight_inc(loc);
inflight_dec(loc);
assert_eq!(inflight_count(loc), 0);
inflight_dec(loc);
assert_eq!(
inflight_count(loc),
0,
"extra dec past zero must saturate, not wrap"
);
}
#[test]
fn inflight_counts_are_per_device_independent() {
let cuda = DeviceLocation::Cuda { gpu_id: 99_004 };
let vulkan = DeviceLocation::Vulkan { gpu_id: 99_004 };
let base_c = inflight_count(cuda);
let base_v = inflight_count(vulkan);
inflight_inc(cuda);
inflight_inc(cuda);
inflight_inc(vulkan);
assert_eq!(inflight_count(cuda), base_c + 2);
assert_eq!(inflight_count(vulkan), base_v + 1);
inflight_dec(cuda);
inflight_dec(cuda);
inflight_dec(vulkan);
assert_eq!(inflight_count(cuda), base_c);
assert_eq!(inflight_count(vulkan), base_v);
}
#[test]
fn derive_backend_caps_mirrors_binding_table_output_dtypes() {
fn stub(
_i: &[Arc<RwLock<Storage>>],
_o: &mut [Arc<RwLock<Storage>>],
_l: &[Layout],
_p: &OpParams,
) -> Result<()> {
Ok(())
}
fn stub_b(
_i: &[Arc<RwLock<Storage>>],
_o: &mut [Arc<RwLock<Storage>>],
_l: &[Layout],
_p: &OpParams,
) -> Result<()> {
Ok(())
}
fn stub_c(
_i: &[Arc<RwLock<Storage>>],
_o: &mut [Arc<RwLock<Storage>>],
_l: &[Layout],
_p: &OpParams,
) -> Result<()> {
Ok(())
}
let mut table = KernelBindingTable::new();
table.register(
OpKind::AddElementwise,
&[DType::F32, DType::F32, DType::F32],
BackendId::Cpu,
stub,
);
table.register(
OpKind::MulElementwise,
&[DType::F32, DType::F32, DType::F32],
BackendId::Vulkan,
stub_b,
);
table.register(
OpKind::MatMul,
&[DType::F16, DType::F16, DType::F32],
BackendId::Vulkan,
stub_c,
);
let vk0 = DeviceLocation::Vulkan { gpu_id: 0 };
let caps = derive_backend_caps(BackendId::Vulkan, vk0, &table);
assert_eq!(caps.backend_id, BackendId::Vulkan);
assert_eq!(caps.device_location, vk0);
assert_eq!(caps.storage_substrate, SubstrateClass::VulkanBuffer);
assert!(
caps.op_dtype_support
.contains(&(OpKind::MulElementwise, DType::F32))
);
assert!(
caps.op_dtype_support
.contains(&(OpKind::MatMul, DType::F32))
);
assert!(
!caps
.op_dtype_support
.contains(&(OpKind::MatMul, DType::F16))
);
assert!(
!caps
.op_dtype_support
.contains(&(OpKind::AddElementwise, DType::F32))
);
assert!(caps
.transfer_paths
.iter()
.any(|&(dst, path)| dst == DeviceLocation::Cpu && path == TransferPath::HostStaging));
}
#[test]
#[ignore = "requires a live CUDA device"]
#[cfg(feature = "cuda")]
fn copy_from_cuda_wrapper_cuda_output_write_into_matches_old_behavior() {
use fuel_cuda_backend::{CudaDevice, CudaStorageBytes};
let Ok(dev) = CudaDevice::new(0) else {
return fuel_test_support::hardware::skip(
fuel_test_support::hardware::Hardware::Cuda,
fuel_test_support::hardware::Missing::device("CudaDevice::new(0) failed"),
);
};
let src_data: Vec<f32> = (0..17).map(|i| i as f32 * 1.5 - 3.0).collect();
let src_bytes: Vec<u8> = src_data.iter().flat_map(|f| f.to_le_bytes()).collect();
let src_cb = CudaStorageBytes::from_cpu_bytes(&dev, &src_bytes).expect("h2d src");
let src_arc: Arc<RwLock<Storage>> = Arc::new(RwLock::new(Storage::new(
BackendStorage::Cuda(src_cb),
DType::F32,
)));
let garbage: Vec<u8> = vec![0xAAu8; src_bytes.len()];
let dst_cb = CudaStorageBytes::from_cpu_bytes(&dev, &garbage).expect("h2d garbage");
let dst_arc: Arc<RwLock<Storage>> = Arc::new(RwLock::new(Storage::new(
BackendStorage::Cuda(dst_cb),
DType::F32,
)));
let before_ptr = {
let g = dst_arc.read().unwrap();
let BackendStorage::Cuda(c) = &g.inner else {
panic!("not cuda")
};
c.buffer() as *const _ as usize
};
let inputs = vec![Arc::clone(&src_arc)];
let mut outputs = vec![Arc::clone(&dst_arc)];
copy_from_cuda_wrapper(&inputs, &mut outputs, &[], &OpParams::None)
.expect("copy_from_cuda_wrapper (write-into CUDA branch)");
let after_ptr = {
let g = dst_arc.read().unwrap();
let BackendStorage::Cuda(c) = &g.inner else {
panic!("not cuda")
};
c.buffer() as *const _ as usize
};
assert_eq!(
before_ptr, after_ptr,
"write-into must preserve the output's buffer identity (device address)"
);
let new_bytes = {
let g = dst_arc.read().unwrap();
let BackendStorage::Cuda(c) = &g.inner else {
panic!("not cuda")
};
c.to_cpu_bytes().expect("d2h new")
};
let old_bytes = {
let g = src_arc.read().unwrap();
let BackendStorage::Cuda(c) = &g.inner else {
panic!("not cuda")
};
let copied = c
.slot_copy_to_new(0, c.len_bytes())
.expect("slot_copy_to_new (old allocate-and-replace path)");
copied.to_cpu_bytes().expect("d2h old")
};
assert_eq!(
new_bytes, old_bytes,
"write-into result must byte-match the old slot_copy_to_new result"
);
assert_eq!(new_bytes, src_bytes, "and both must match the source bytes");
}
}