use crate::{Dispatch, HwdnaError, DotKernel, MatMulKernel};
#[allow(dead_code)]
use crate::Backend;
pub enum GraphNode {
Dot {
dispatch: Dispatch<DotKernel>,
input_a: NodeId,
input_b: NodeId,
},
MatMul {
dispatch: Dispatch<MatMulKernel>,
input_a: NodeId,
input_b: NodeId,
n: usize,
},
FusedDotClampReduce {
dispatch_a: Dispatch<DotKernel>,
dispatch_b: Dispatch<DotKernel>,
},
}
pub type NodeId = usize;
pub enum ComputeResult {
Scalar(f64),
Vector(Vec<f64>),
Matrix(Vec<f64>, usize),
}
pub struct ExecutionPlan {
steps: Vec<ExecStep>,
}
#[allow(dead_code)]
enum ExecStep {
Dot { a_idx: usize, b_idx: usize },
MatMul { a_idx: usize, b_idx: usize, n: usize, out_idx: usize },
FusedDotClampReduce { a_idx: usize, b_idx: usize, lo: f64, hi: f64 },
}
pub struct ComputeGraph {
nodes: Vec<GraphNode>,
inputs: Vec<Vec<f64>>,
#[allow(dead_code)]
backend: Backend,
}
impl ComputeGraph {
pub fn new() -> Self {
Self { nodes: Vec::new(), inputs: Vec::new(), backend: Backend::Auto }
}
pub fn add_input(&mut self, data: Vec<f64>) -> NodeId {
let id = self.inputs.len();
self.inputs.push(data);
id
}
pub fn add_node(&mut self, node: GraphNode) -> NodeId {
let id = self.nodes.len();
self.nodes.push(node);
id
}
pub fn schedule(&self) -> ExecutionPlan {
let mut steps = Vec::new();
for (i, node) in self.nodes.iter().enumerate() {
match node {
GraphNode::Dot { .. } => {
steps.push(ExecStep::Dot { a_idx: i, b_idx: i });
}
GraphNode::MatMul { n, .. } => {
steps.push(ExecStep::MatMul { a_idx: i, b_idx: i, n: *n, out_idx: i });
}
GraphNode::FusedDotClampReduce { .. } => {
steps.push(ExecStep::FusedDotClampReduce { a_idx: i, b_idx: i, lo: 0.0, hi: 1.0 });
}
}
}
ExecutionPlan { steps }
}
pub fn execute(&mut self, plan: &ExecutionPlan) -> Result<Vec<ComputeResult>, HwdnaError> {
let mut results = Vec::with_capacity(plan.steps.len());
for step in &plan.steps {
match step {
ExecStep::Dot { a_idx, .. } => {
if let GraphNode::Dot { ref mut dispatch, input_a, input_b } = &mut self.nodes[*a_idx] {
let a = &self.inputs[*input_a];
let b = &self.inputs[*input_b];
let r = dispatch.compute(a, b);
results.push(ComputeResult::Scalar(r));
}
}
ExecStep::MatMul { a_idx, n, .. } => {
if let GraphNode::MatMul { ref mut dispatch, input_a, input_b, .. } = &mut self.nodes[*a_idx] {
let a = &self.inputs[*input_a];
let b = &self.inputs[*input_b];
let mut c = vec![0.0; n * n];
dispatch.compute(a, b, &mut c, *n);
results.push(ComputeResult::Matrix(c, *n));
}
}
ExecStep::FusedDotClampReduce { a_idx, .. } => {
if let GraphNode::FusedDotClampReduce { .. } = &mut self.nodes[*a_idx] {
results.push(ComputeResult::Scalar(0.0));
}
}
}
}
Ok(results)
}
pub fn to_dot(&self) -> String {
let mut dot = String::from("digraph ComputeGraph {\n");
dot.push_str(" rankdir=LR;\n");
dot.push_str(" node [shape=box, style=rounded];\n");
for (i, input) in self.inputs.iter().enumerate() {
dot.push_str(&format!(" input_{} [label=\"input({})\"];\n", i, input.len()));
}
for (i, node) in self.nodes.iter().enumerate() {
match node {
GraphNode::Dot { input_a, input_b, .. } => {
dot.push_str(&format!(" node_{} [label=\"Dot\"];\n", i));
dot.push_str(&format!(" input_{} -> node_{};\n", input_a, i));
dot.push_str(&format!(" input_{} -> node_{};\n", input_b, i));
}
GraphNode::MatMul { input_a, input_b, n, .. } => {
dot.push_str(&format!(" node_{} [label=\"MatMul({}x{})\"];\n", i, n, n));
dot.push_str(&format!(" input_{} -> node_{};\n", input_a, i));
dot.push_str(&format!(" input_{} -> node_{};\n", input_b, i));
}
GraphNode::FusedDotClampReduce { .. } => {
dot.push_str(&format!(" node_{} [label=\"FusedDotClampReduce\"];\n", i));
}
}
}
dot.push_str("}\n");
dot
}
}
pub fn fuse_dots(dispatches: Vec<Dispatch<DotKernel>>) -> Dispatch<DotKernel> {
dispatches.into_iter().next().unwrap_or_else(|| Dispatch::new("fused", Vec::new()))
}