use std::boxed::Box;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock};
use crate::Dimension;
use crate::tensor::backend::{Backend, ComputeFor};
use crate::tensor::definitions::NumberLike;
use crate::tensor::errors::OpError;
use crate::tensor::executor::{borrowed_step, run_plan};
use crate::tensor::mem_formats::layout::Layout;
use crate::tensor::ops::compute_layout;
use crate::tensor::ops::def_op::OpKind;
use crate::tensor::ops::fusion::try_fuse;
use crate::tensor::planner::{OwnedCorePlan, plan_computation};
use crate::tensor::storage::TensorData;
use crate::tensor::traits::{Numeric, Promising};
static NEXT_ID: AtomicUsize = const { AtomicUsize::new(0) };
pub enum NodeKind<T, B: Backend> {
Edge(Arc<TensorGraphEdge<T, B>>),
Cache(Arc<TensorGraphCacheNode<T, B>>),
Node(Arc<TensorGraphNode<T, B>>),
Slot(Arc<TensorGraphSlot<T, B>>),
Baked(Arc<TensorGraphBaked<T, B>>),
}
impl<T, B: Backend> Clone for NodeKind<T, B> {
fn clone(&self) -> Self {
match self {
NodeKind::Edge(e) => NodeKind::Edge(e.clone()),
NodeKind::Cache(c) => NodeKind::Cache(c.clone()),
NodeKind::Node(n) => NodeKind::Node(n.clone()),
NodeKind::Slot(s) => NodeKind::Slot(s.clone()),
NodeKind::Baked(c) => NodeKind::Baked(c.clone()),
}
}
}
impl<T: Debug, B: Backend> Debug for NodeKind<T, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NodeKind::Edge(e) => f.debug_tuple("Edge").field(e).finish(),
NodeKind::Cache(c) => f.debug_tuple("Cache").field(c).finish(),
NodeKind::Node(n) => f.debug_tuple("Node").field(n).finish(),
NodeKind::Slot(s) => f.debug_tuple("Slot").field(s).finish(),
NodeKind::Baked(c) => f.debug_tuple("Baked").field(c).finish(),
}
}
}
pub(crate) fn get_inputs_layout<T: NumberLike, B: Backend>(
inputs: &[NodeKind<T, B>],
) -> Box<[&Layout]> {
inputs
.iter()
.map(|node| match &node {
NodeKind::Edge(edge) => edge.get().layout(),
NodeKind::Node(node) => &node.layout,
NodeKind::Cache(cache) => &cache.get_node().layout,
NodeKind::Slot(slot) => &slot.layout,
NodeKind::Baked(baked) => &baked.layout,
})
.collect()
}
pub struct TensorGraphEdge<T, B: Backend> {
pub(crate) id: usize,
pub(crate) data: TensorData<T>,
marker: PhantomData<B>,
}
impl<T, B: Backend> TensorGraphEdge<T, B> {
pub fn from_tensor_data(data: TensorData<T>) -> Self {
Self {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
data,
marker: PhantomData {},
}
}
pub fn get(&self) -> &TensorData<T> {
&self.data
}
#[inline]
pub(crate) fn layout(&self) -> &Layout {
self.data.layout()
}
}
impl<T: Clone, B: Backend> Promising for TensorGraphEdge<T, B> {
type Output = T;
#[inline]
fn compute(&self) -> TensorData<T> {
self.data.clone()
}
}
impl<T, B: Backend> Debug for TensorGraphEdge<T, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TensorGraphEdge {{ id: {}, data: [...] }}", self.id)
}
}
pub struct TensorGraphNode<T, B: Backend> {
pub(crate) id: usize,
pub(crate) op: OpKind<T>,
pub(crate) inputs: Box<[NodeKind<T, B>]>,
pub(crate) layout: Layout,
marker: PhantomData<B>,
}
#[allow(private_bounds)]
impl<T: Numeric, B: Backend> TensorGraphNode<T, B> {
pub fn new(op: OpKind<T>, inputs: Box<[NodeKind<T, B>]>) -> Result<Self, OpError> {
let fused = try_fuse(op, inputs);
let layouts = get_inputs_layout(&fused.inputs);
let layout = compute_layout(&fused.op, &layouts);
if let Err(err) = layout {
return Err(err);
}
let unchecked_layout = unsafe { layout.unwrap_unchecked() };
Ok(Self {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
op: fused.op,
inputs: fused.inputs,
layout: unchecked_layout,
marker: PhantomData {},
})
}
pub fn with_layout(op: OpKind<T>, inputs: Box<[NodeKind<T, B>]>, layout: Layout) -> Self {
let fused = try_fuse(op, inputs);
Self {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
op: fused.op,
inputs: fused.inputs,
layout,
marker: PhantomData {},
}
}
}
impl<T, B: Backend> TensorGraphNode<T, B> {
#[inline]
pub(crate) fn layout(&self) -> &Layout {
&self.layout
}
}
impl<T: NumberLike + ComputeFor<B>, B: Backend> Promising for TensorGraphNode<T, B> {
type Output = T;
fn compute(&self) -> TensorData<T> {
let plan = plan_computation(self);
debug_assert!(plan.external_inputs.is_empty());
run_plan(
&mut plan.plan.iter().map(borrowed_step),
plan.root_id,
Vec::new(),
)
}
}
impl<T: Debug, B: Backend> Debug for TensorGraphNode<T, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"TensorGraphNode {{ id: {:?}, op: {:?}, inputs: [...] }}",
self.id, self.op
)
}
}
pub struct TensorGraphCacheNode<T, B: Backend> {
node: TensorGraphNode<T, B>,
pub(crate) cache: OnceLock<TensorData<T>>,
}
#[allow(private_bounds)]
impl<T: Numeric, B: Backend> TensorGraphCacheNode<T, B> {
pub fn new(op: OpKind<T>, inputs: Box<[NodeKind<T, B>]>) -> Result<Self, OpError> {
let node = TensorGraphNode::new(op, inputs);
match node {
Ok(node) => Ok(Self {
node,
cache: OnceLock::new(),
}),
Err(err) => Err(err),
}
}
}
impl<T, B: Backend> TensorGraphCacheNode<T, B> {
pub fn get_node(&self) -> &TensorGraphNode<T, B> {
&self.node
}
pub fn is_cache_filled(&self) -> bool {
self.cache.get().is_some()
}
pub fn get_cache(&self) -> Option<&TensorData<T>> {
self.cache.get()
}
#[inline]
pub(crate) fn layout(&self) -> &Layout {
&self.node.layout
}
}
impl<T: NumberLike + ComputeFor<B>, B: Backend> Promising for TensorGraphCacheNode<T, B> {
type Output = T;
fn compute(&self) -> TensorData<T> {
self.cache.get_or_init(|| self.node.compute()).clone()
}
}
impl<T: Debug, B: Backend> Debug for TensorGraphCacheNode<T, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"TensorGraphNode {{ id: {:?}, op: {:?}, inputs: [...], cached: {} }}",
self.node.id,
self.node.op,
self.is_cache_filled()
)
}
}
pub struct TensorGraphSlot<T, B: Backend> {
pub(crate) id: usize,
pub(crate) layout: Layout,
marker: PhantomData<(T, B)>,
}
impl<T, B: Backend> TensorGraphSlot<T, B> {
#[inline]
pub(crate) fn new(layout: Layout) -> Self {
Self {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
layout,
marker: PhantomData {},
}
}
#[inline]
pub(crate) fn layout(&self) -> &Layout {
&self.layout
}
}
impl<T: Debug, B: Backend> Debug for TensorGraphSlot<T, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TensorGraphSlot {{ id: {}, }}", self.id)
}
}
pub struct TensorGraphBaked<T, B: Backend> {
pub(crate) id: usize,
pub(crate) inputs: Box<[NodeKind<T, B>]>,
pub(crate) inputs_ids: Box<[usize]>,
pub(crate) plan: Arc<OwnedCorePlan<T, B>>,
layout: Layout,
}
impl<T: PartialEq + Clone, B: Backend> TensorGraphBaked<T, B> {
pub(crate) fn from_node(
plan: &Arc<OwnedCorePlan<T, B>>,
inputs: Box<[NodeKind<T, B>]>,
inputs_ids: Box<[usize]>,
layout: &Layout,
) -> Self {
Self {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
inputs,
inputs_ids,
plan: plan.clone(),
layout: layout.clone(),
}
}
}
impl<T, B: Backend> TensorGraphBaked<T, B> {
#[inline]
pub(crate) fn layout(&self) -> &Layout {
&self.layout
}
}
impl<T: Debug, B: Backend> Debug for TensorGraphBaked<T, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TensorGraphBaked {{ id: {:?}, plan: [...] }}", self.id)
}
}