use std::collections::HashMap;
use std::sync::Arc;
use crate::Layout;
use crate::tensor::backend::{Backend, ComputeFor};
use crate::tensor::definitions::NumberLike;
use crate::tensor::graph::{TensorGraphBaked, TensorGraphCacheNode, TensorGraphEdge};
use crate::tensor::ops::def_op::OpKind;
use crate::tensor::planner::{ComputeKind, OutputKind, OwnedComputeKind};
use crate::tensor::storage::TensorData;
#[inline]
fn strip_tensor<T: Copy>(tensor: TensorData<T>) -> Vec<T> {
if let Ok(v) = Arc::try_unwrap(tensor.storage.buffer) {
v
} else {
unreachable!("cannot strip a tensor that is being used!")
}
}
#[inline]
fn alloc_vec<T: Default + Clone>(len: usize) -> Vec<T> {
let mut output_buffer = Vec::with_capacity(len);
output_buffer.resize(len, T::default());
output_buffer
}
#[inline]
fn build_inputs<T: Clone>(
computation_cache: &HashMap<usize, TensorData<T>>,
ids: &[usize],
) -> Vec<TensorData<T>> {
ids.iter()
.map(|&id| computation_cache.get(&id).unwrap().clone())
.collect()
}
fn execute_output<T: NumberLike + ComputeFor<B>, B: Backend>(
op: &OpKind<T>,
layout: &Layout,
output: &OutputKind,
resolved_inputs: &[usize],
computation_cache: &mut HashMap<usize, TensorData<T>>,
) -> TensorData<T> {
match output {
OutputKind::Allocate(len) => {
let output_buffer = alloc_vec(*len);
let inputs = build_inputs(computation_cache, resolved_inputs);
B::compute(op, output_buffer, layout, &inputs)
}
OutputKind::Buffer(id) => {
let reused = computation_cache.remove(id).unwrap();
let output_buffer = strip_tensor(reused);
let inputs = build_inputs(computation_cache, resolved_inputs);
B::compute(op, output_buffer, layout, &inputs)
}
OutputKind::InPlaceIdx(idx) => {
let inputs: Vec<TensorData<T>> = resolved_inputs
.iter()
.enumerate()
.map(|(i, id)| {
if i == *idx {
computation_cache.remove(id).unwrap()
} else {
computation_cache.get(id).unwrap().clone()
}
})
.collect();
B::compute_inplace(op, layout, inputs, *idx)
}
OutputKind::Reference(idx) => {
let inputs = build_inputs(computation_cache, resolved_inputs);
B::compute_inplace(op, layout, inputs, *idx)
}
}
}
pub(crate) enum StepRef<'a, T, B: Backend> {
Leaf {
edge: &'a TensorGraphEdge<T, B>,
},
Op {
id: usize,
op: &'a OpKind<T>,
layout: &'a Layout,
output: &'a OutputKind,
resolved_inputs: &'a [usize],
dealloc_after: &'a [usize],
},
CachedOp {
cache: &'a TensorGraphCacheNode<T, B>,
output: &'a OutputKind,
resolved_inputs: &'a [usize],
dealloc_after: &'a [usize],
},
Baked {
baked: &'a TensorGraphBaked<T, B>,
resolved_inputs: &'a Vec<usize>,
dealloc_after: &'a Vec<usize>,
},
}
#[inline]
pub(crate) fn borrowed_step<'a, T, B: Backend>(
step: &'a ComputeKind<'a, T, B>,
) -> StepRef<'a, T, B> {
match step {
ComputeKind::Leaf { edge } => StepRef::Leaf { edge },
ComputeKind::Op {
node,
output,
resolved_inputs,
dealloc_after,
} => StepRef::Op {
id: node.id,
op: &node.op,
layout: &node.layout,
output,
resolved_inputs,
dealloc_after,
},
ComputeKind::CachedOp {
cache,
output,
resolved_inputs,
dealloc_after,
} => StepRef::CachedOp {
cache,
output,
resolved_inputs,
dealloc_after,
},
ComputeKind::Baked {
baked,
resolved_inputs,
dealloc_after,
} => StepRef::Baked {
baked,
resolved_inputs,
dealloc_after,
},
}
}
#[inline]
pub(crate) fn owned_step<T, B: Backend>(step: &OwnedComputeKind<T, B>) -> StepRef<'_, T, B> {
match step {
OwnedComputeKind::Leaf { edge } => StepRef::Leaf { edge },
OwnedComputeKind::Op {
node,
output,
resolved_inputs,
dealloc_after,
} => StepRef::Op {
id: node.id,
op: &node.op,
layout: &node.layout,
output,
resolved_inputs,
dealloc_after,
},
OwnedComputeKind::CachedOp {
cache,
output,
resolved_inputs,
dealloc_after,
} => StepRef::CachedOp {
cache,
output,
resolved_inputs,
dealloc_after,
},
OwnedComputeKind::Baked {
baked,
resolved_inputs,
dealloc_after,
} => StepRef::Baked {
baked,
resolved_inputs,
dealloc_after,
},
}
}
pub(crate) fn run_plan<'a, T: NumberLike + ComputeFor<B> + 'a, B: Backend + 'a>(
steps: &mut (dyn Iterator<Item = StepRef<'a, T, B>> + 'a),
root_id: usize,
external_inputs: Vec<(usize, TensorData<T>)>,
) -> TensorData<T> {
let mut computation_cache: HashMap<usize, TensorData<T>> = HashMap::new();
for (id, input) in external_inputs.into_iter() {
computation_cache.insert(id, input);
}
for step in steps {
match step {
StepRef::Leaf { edge } => {
computation_cache.insert(edge.id, edge.data.clone());
}
StepRef::Op {
id,
op,
layout,
output,
resolved_inputs,
dealloc_after,
..
} => {
let result =
execute_output(op, layout, output, resolved_inputs, &mut computation_cache);
computation_cache.insert(id, result);
for &dealloc_id in dealloc_after {
computation_cache.remove(&dealloc_id);
}
}
StepRef::CachedOp {
cache,
output,
resolved_inputs,
dealloc_after,
} => {
if cache.is_cache_filled() {
computation_cache
.insert(cache.get_node().id, cache.cache.get().unwrap().clone());
match output {
OutputKind::Allocate(_) | OutputKind::Reference(_) => {}
OutputKind::Buffer(id) => {
computation_cache.remove(id);
}
OutputKind::InPlaceIdx(idx) => {
computation_cache.remove(&resolved_inputs[*idx]);
}
}
for &dealloc_id in dealloc_after {
computation_cache.remove(&dealloc_id);
}
continue;
}
let node = cache.get_node();
let result = execute_output(
&node.op,
&node.layout,
output,
resolved_inputs,
&mut computation_cache,
);
let _ = cache.cache.set(result.clone());
computation_cache.insert(node.id, result);
for &dealloc_id in dealloc_after {
computation_cache.remove(&dealloc_id);
}
}
StepRef::Baked {
baked,
resolved_inputs,
dealloc_after,
} => {
let inputs: Vec<(usize, TensorData<T>)> = baked
.inputs_ids
.iter()
.zip(resolved_inputs.iter())
.map(|(&slot_id, &outer_id)| {
(slot_id, computation_cache.get(&outer_id).unwrap().clone())
})
.collect();
let result = run_plan(
&mut baked.plan.plan.iter().map(owned_step),
baked.plan.root_id,
inputs,
);
computation_cache.insert(baked.id, result);
for &dealloc_id in dealloc_after {
computation_cache.remove(&dealloc_id);
}
}
}
}
computation_cache.remove(&root_id).unwrap()
}