use std::marker::PhantomData;
use tenferro_ops::broadcast::{broadcast_shape, broadcast_shapes};
use tenferro_tensor::{
CompareDir, DType, Engine, Error, GatherConfig, Result, Tensor, TensorRead, TensorScalar,
TensorValue, TypedTensor,
};
use super::{
scalar_tensor, zero_pad_config, CompositeBinary, CompositeOps, CompositeReduce, CompositeUnary,
};
use crate::typed_tensor::{broadcast_error, broadcast_to_in_read, ReadInput};
pub(crate) fn borrowed(tensor: &Tensor) -> ReadInput<'_> {
ReadInput::Borrowed(TensorRead::from_tensor(tensor))
}
pub(crate) fn typed_borrowed<T: TensorScalar>(tensor: &TypedTensor<T>) -> ReadInput<'_> {
ReadInput::Borrowed(T::tensor_read(tensor))
}
pub(crate) fn run_session_composite<'a>(
session: &mut dyn Engine,
composite: impl FnOnce(&mut SessionComposite<'_, 'a>) -> Result<ReadInput<'a>>,
) -> Result<Tensor> {
let mut ops = SessionComposite::new(session);
let out = composite(&mut ops)?;
ops.finish(out)
}
pub(crate) struct SessionComposite<'s, 'a> {
session: &'s mut dyn Engine,
inputs: PhantomData<ReadInput<'a>>,
}
impl<'s, 'a> SessionComposite<'s, 'a> {
fn new(session: &'s mut dyn Engine) -> Self {
Self {
session,
inputs: PhantomData,
}
}
fn finish(&mut self, value: ReadInput<'_>) -> Result<Tensor> {
match value {
ReadInput::Owned(tensor) => Ok(tensor),
ReadInput::Value(value) => {
if value.as_tensor().is_some() {
Ok(value.into_tensor()?)
} else {
self.session.to_contiguous_read(value.tensor_read())
}
}
ReadInput::Borrowed(read) => self.session.to_contiguous_read(read),
}
}
fn upload(&mut self, tensor: &Tensor) -> Result<ReadInput<'static>> {
Ok(ReadInput::Owned(
self.session
.upload_host_tensor(TensorRead::from_tensor(tensor))?,
))
}
}
impl<'a> CompositeOps for SessionComposite<'_, 'a> {
type Value = ReadInput<'a>;
type Error = Error;
fn dtype(&self, value: &ReadInput<'a>) -> DType {
value.tensor_read().dtype()
}
fn shape(&self, value: &ReadInput<'a>) -> Result<Vec<usize>> {
Ok(value.tensor_read().shape().to_vec())
}
fn scalar(&mut self, dtype: DType, value: f64) -> Result<ReadInput<'a>> {
self.upload(&scalar_tensor(dtype, value)?)
}
fn unary(&mut self, op: CompositeUnary, value: &ReadInput<'a>) -> Result<ReadInput<'a>> {
let input = value.tensor_read();
let session = &mut *self.session;
let out = match op {
CompositeUnary::Neg => session.neg_read(input),
CompositeUnary::Exp => session.exp_read(input),
CompositeUnary::Log => session.log_read(input),
CompositeUnary::Log1p => session.log1p_read(input),
CompositeUnary::Tanh => session.tanh_read(input),
CompositeUnary::Erf => session.erf_read(input),
CompositeUnary::Rsqrt => session.rsqrt_read(input),
}?;
Ok(ReadInput::Owned(out))
}
fn binary(
&mut self,
op: CompositeBinary,
lhs: &ReadInput<'a>,
rhs: &ReadInput<'a>,
) -> Result<ReadInput<'a>> {
let (lhs, rhs) = (lhs.tensor_read(), rhs.tensor_read());
let shape = broadcast_shape(lhs.shape(), rhs.shape()).map_err(broadcast_error)?;
let lhs = broadcast_to_in_read(lhs, &shape, self.session)?;
let rhs = broadcast_to_in_read(rhs, &shape, self.session)?;
let (lhs, rhs) = (lhs.tensor_read(), rhs.tensor_read());
let session = &mut *self.session;
let out = match op {
CompositeBinary::Add => session.add_read(lhs, rhs),
CompositeBinary::Sub => session.sub_read(lhs, rhs),
CompositeBinary::Mul => session.mul_read(lhs, rhs),
CompositeBinary::Div => session.div_read(lhs, rhs),
CompositeBinary::Maximum => session.maximum_read(lhs, rhs),
}?;
Ok(ReadInput::Owned(out))
}
fn compare(
&mut self,
lhs: &ReadInput<'a>,
rhs: &ReadInput<'a>,
dir: CompareDir,
) -> Result<ReadInput<'a>> {
let (lhs, rhs) = (lhs.tensor_read(), rhs.tensor_read());
let shape = broadcast_shape(lhs.shape(), rhs.shape()).map_err(broadcast_error)?;
let lhs = broadcast_to_in_read(lhs, &shape, self.session)?;
let rhs = broadcast_to_in_read(rhs, &shape, self.session)?;
let out = self
.session
.compare_read(lhs.tensor_read(), rhs.tensor_read(), &dir)?;
Ok(ReadInput::Owned(out))
}
fn select(
&mut self,
condition: &ReadInput<'a>,
on_true: &ReadInput<'a>,
on_false: &ReadInput<'a>,
) -> Result<ReadInput<'a>> {
let (condition, on_true, on_false) = (
condition.tensor_read(),
on_true.tensor_read(),
on_false.tensor_read(),
);
let shape = broadcast_shapes([condition.shape(), on_true.shape(), on_false.shape()])
.map_err(broadcast_error)?;
let condition = broadcast_to_in_read(condition, &shape, self.session)?;
let on_true = broadcast_to_in_read(on_true, &shape, self.session)?;
let on_false = broadcast_to_in_read(on_false, &shape, self.session)?;
let out = self.session.select_read(
condition.tensor_read(),
on_true.tensor_read(),
on_false.tensor_read(),
)?;
Ok(ReadInput::Owned(out))
}
fn reduce(
&mut self,
op: CompositeReduce,
value: &ReadInput<'a>,
axes: &[usize],
) -> Result<ReadInput<'a>> {
let input = value.tensor_read();
let out = match op {
CompositeReduce::Sum => self.session.reduce_sum_read(input, axes),
CompositeReduce::Max => self.session.reduce_max_read(input, axes),
CompositeReduce::SumSquares => self.session.reduce_sum_squares_read(input, axes),
}?;
Ok(ReadInput::Owned(out))
}
fn broadcast_in_dim(
&mut self,
value: &ReadInput<'a>,
shape: &[usize],
dims: &[usize],
) -> Result<ReadInput<'a>> {
record_broadcast_route(BroadcastRoute::Materializing);
let out = self
.session
.broadcast_in_dim_read(value.tensor_read(), shape, dims)?;
Ok(ReadInput::Owned(out))
}
fn broadcast_in_dim_owned(
&mut self,
value: ReadInput<'a>,
shape: &[usize],
dims: &[usize],
) -> Result<ReadInput<'a>> {
record_broadcast_route(BroadcastRoute::View);
Ok(match value {
ReadInput::Borrowed(read) => {
ReadInput::Borrowed(read.broadcast_in_dim_view(shape, dims)?)
}
ReadInput::Owned(tensor) => ReadInput::Value(
TensorValue::from_tensor(tensor).broadcast_in_dim_view(shape, dims)?,
),
ReadInput::Value(value) => ReadInput::Value(value.broadcast_in_dim_view(shape, dims)?),
})
}
fn reshape(&mut self, value: &ReadInput<'a>, shape: &[usize]) -> Result<ReadInput<'a>> {
let out = self.session.reshape_read(value.tensor_read(), shape)?;
Ok(ReadInput::Owned(out))
}
fn concatenate(&mut self, values: &[&ReadInput<'a>], axis: usize) -> Result<ReadInput<'a>> {
let reads: Vec<TensorRead<'_>> = values.iter().map(|value| value.tensor_read()).collect();
Ok(ReadInput::Owned(
self.session.concatenate_read(&reads, axis)?,
))
}
fn pad(
&mut self,
value: &ReadInput<'a>,
low: &[usize],
high: &[usize],
) -> Result<ReadInput<'a>> {
let out = self
.session
.pad_read(value.tensor_read(), &zero_pad_config(low, high))?;
Ok(ReadInput::Owned(out))
}
fn gather(
&mut self,
operand: &ReadInput<'a>,
indices: &ReadInput<'a>,
config: GatherConfig,
) -> Result<ReadInput<'a>> {
Ok(ReadInput::Owned(self.session.gather_read(
operand.tensor_read(),
indices.tensor_read(),
&config,
)?))
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BroadcastRoute {
View,
Materializing,
}
static OBSERVE_BROADCAST_ROUTES: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
thread_local! {
static BROADCAST_ROUTES: std::cell::RefCell<Vec<BroadcastRoute>> =
const { std::cell::RefCell::new(Vec::new()) };
}
pub(crate) fn set_broadcast_route_observation(enabled: bool) {
if !enabled {
let _ = take_broadcast_routes();
}
OBSERVE_BROADCAST_ROUTES.store(enabled, std::sync::atomic::Ordering::Relaxed);
}
fn record_broadcast_route(route: BroadcastRoute) {
if OBSERVE_BROADCAST_ROUTES.load(std::sync::atomic::Ordering::Relaxed) {
BROADCAST_ROUTES.with(|routes| routes.borrow_mut().push(route));
}
}
pub(crate) fn take_broadcast_routes() -> Vec<BroadcastRoute> {
BROADCAST_ROUTES.with(|routes| std::mem::take(&mut *routes.borrow_mut()))
}