use onnx_runtime_ep_api::{EpError, Kernel, KernelFactory, Result, TensorMut, TensorView};
use onnx_runtime_ir::{DataType, Node, compute_contiguous_strides};
use super::{check_arity, elem_size, to_dense_bytes, write_dense_bytes};
use crate::strided::{next_index, numel};
pub struct WhereKernel;
pub struct WhereFactory;
impl KernelFactory for WhereFactory {
fn create(&self, _node: &Node, _shapes: &[Vec<usize>]) -> Result<Box<dyn Kernel>> {
Ok(Box::new(WhereKernel))
}
}
fn effective_strides(src_shape: &[usize], out_shape: &[usize]) -> Result<Vec<i64>> {
let out_rank = out_shape.len();
let src_strides = compute_contiguous_strides(src_shape);
let mut eff = vec![0i64; out_rank];
for (axis, e) in eff.iter_mut().enumerate() {
let src_axis = axis as isize - (out_rank as isize - src_shape.len() as isize);
if src_axis < 0 {
continue;
}
let src_axis = src_axis as usize;
let src_dim = src_shape[src_axis];
if src_dim == out_shape[axis] {
*e = src_strides[src_axis];
} else if src_dim == 1 {
*e = 0;
} else {
return Err(EpError::KernelFailed(format!(
"Where: operand shape {src_shape:?} is not broadcast-compatible with output \
shape {out_shape:?}. WHY: axis {axis} has extent {src_dim}, expected 1 or {}. \
HOW: reshape the operand to broadcast against the output.",
out_shape[axis]
)));
}
}
Ok(eff)
}
impl Kernel for WhereKernel {
fn execute(&self, inputs: &[TensorView], outputs: &mut [TensorMut]) -> Result<()> {
check_arity("Where", inputs, outputs, 3, 3, 1)?;
let (cond, x, y) = (&inputs[0], &inputs[1], &inputs[2]);
if cond.dtype != DataType::Bool {
return Err(EpError::KernelFailed(format!(
"Where: condition must be Bool, got {:?}. WHY: `Where` selects on a boolean \
mask. HOW: pass a Bool condition tensor.",
cond.dtype
)));
}
let dtype = outputs[0].dtype;
if x.dtype != dtype || y.dtype != dtype {
return Err(EpError::KernelFailed(format!(
"Where: branch dtypes {:?}/{:?} must match output dtype {:?}. WHY: both branches \
feed one typed output. HOW: cast x and y to the output dtype.",
x.dtype, y.dtype, dtype
)));
}
let esize = elem_size(dtype)?;
let out_shape = outputs[0].shape.to_vec();
let out_rank = out_shape.len();
let cond_eff = effective_strides(cond.shape, &out_shape)?;
let x_eff = effective_strides(x.shape, &out_shape)?;
let y_eff = effective_strides(y.shape, &out_shape)?;
let cond_bytes = to_dense_bytes(cond)?;
let x_bytes = to_dense_bytes(x)?;
let y_bytes = to_dense_bytes(y)?;
let n = numel(&out_shape);
let mut out = vec![0u8; n * esize];
if n == 0 {
return write_dense_bytes(&mut outputs[0], &out);
}
let mut idx = vec![0usize; out_rank];
let mut w = 0usize;
loop {
let (mut c_off, mut x_off, mut y_off) = (0i64, 0i64, 0i64);
for d in 0..out_rank {
let i = idx[d] as i64;
c_off += cond_eff[d] * i;
x_off += x_eff[d] * i;
y_off += y_eff[d] * i;
}
let take_x = cond_bytes[c_off as usize] != 0;
let (src, off) = if take_x {
(&x_bytes, x_off as usize)
} else {
(&y_bytes, y_off as usize)
};
let start = off * esize;
out[w..w + esize].copy_from_slice(&src[start..start + esize]);
w += esize;
if !next_index(&out_shape, &mut idx) {
break;
}
}
write_dense_bytes(&mut outputs[0], &out)
}
fn supports_strided_input(&self, _input_idx: usize) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernels::testutil::Owned;
fn run(cond: &Owned, x: &Owned, y: &Owned, out: &mut Owned) {
WhereKernel
.execute(&[cond.view(), x.view(), y.view()], &mut [out.view_mut()])
.unwrap();
}
#[test]
fn where_same_shape() {
let c = Owned::bool_(&[4], &[true, false, true, false]);
let x = Owned::f32(&[4], &[1., 2., 3., 4.]);
let y = Owned::f32(&[4], &[10., 20., 30., 40.]);
let mut out = Owned::zeros_f32(&[4]);
run(&c, &x, &y, &mut out);
assert_eq!(out.to_f32(), vec![1., 20., 3., 40.]);
}
#[test]
fn where_broadcasts_condition() {
let c = Owned::bool_(&[2, 1], &[true, false]);
let x = Owned::f32(&[2, 2], &[1., 2., 3., 4.]);
let y = Owned::f32(&[], &[0.]);
let mut out = Owned::zeros_f32(&[2, 2]);
run(&c, &x, &y, &mut out);
assert_eq!(out.to_f32(), vec![1., 2., 0., 0.]);
}
#[test]
fn where_int64_dtype_agnostic() {
let c = Owned::bool_(&[3], &[false, true, false]);
let x = Owned::i64(&[3], &[1, 2, 3]);
let y = Owned::i64(&[3], &[7, 8, 9]);
let mut out = Owned::zeros(DataType::Int64, &[3]);
run(&c, &x, &y, &mut out);
assert_eq!(out.to_i64(), vec![7, 2, 9]);
}
#[test]
fn where_broadcasts_branches() {
let c = Owned::bool_(&[2, 2], &[true, false, false, true]);
let x = Owned::f32(&[1, 2], &[1., 2.]);
let y = Owned::f32(&[2, 1], &[10., 20.]);
let mut out = Owned::zeros_f32(&[2, 2]);
run(&c, &x, &y, &mut out);
assert_eq!(out.to_f32(), vec![1., 10., 20., 2.]);
}
#[test]
fn where_bf16_preserves_selected_bits() {
let c = Owned::bool_(&[3], &[true, false, true]);
let x = Owned::bf16(&[3], &[1., -2., 3.]);
let y = Owned::bf16(&[3], &[4., 5., -6.]);
let mut out = Owned::zeros(onnx_runtime_ir::DataType::BFloat16, &[3]);
run(&c, &x, &y, &mut out);
assert_eq!(
out.to_u16_bits(),
vec![x.to_u16_bits()[0], y.to_u16_bits()[1], x.to_u16_bits()[2]]
);
}
}