use crate::{
DType,
graph::{ClassId, Graph},
runtime::ShapeId,
shape::Dim,
slab::Slab,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct MatMul {
pub(crate) a: ClassId,
pub(crate) b: ClassId,
pub(crate) out: ClassId,
pub(crate) m: Dim,
pub(crate) n: Dim,
pub(crate) k: Dim,
pub(crate) acc_dtype: DType,
pub(crate) in_dtype: DType,
}
impl Graph {
pub(crate) fn match_matmul(&self, cid: ClassId, shapes: &Slab<ShapeId, Vec<Dim>>) -> Option<MatMul> {
let out_shape = &shapes[self.classes[cid].shape];
if out_shape.len() != 2 {
return None;
}
let [m, n] = [out_shape[0], out_shape[1]];
let (prod, k) = self.reduce_add_last(cid, shapes)?;
let (ea, eb) = self.mul_of(prod)?;
let (a, a3) = self.expand_src(ea, shapes)?;
let (bt, b3) = self.expand_src(eb, shapes)?;
if a3 != [m, 1, k] || b3 != [1, n, k] {
return None;
}
let b = self.transpose_src(bt)?;
if shapes[self.classes[b].shape] != [k, n] {
return None;
}
let in_dtype = self.classes[a].dtype;
if self.classes[b].dtype != in_dtype {
return None;
}
Some(MatMul { a, b, out: cid, m, n, k, acc_dtype: self.classes[cid].dtype, in_dtype })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
DType,
graph::{EClass, Node, NodeData},
kernel::BOp,
slab::SlabId,
};
use std::collections::BTreeSet;
fn class(graph: &mut Graph, shapes: &mut Slab<ShapeId, Vec<Dim>>, node: Node, shape: Vec<Dim>) -> ClassId {
let sid = shapes.push(shape);
let nid = graph.nodes.push(NodeData { node, class_of: ClassId::NULL });
let cid = graph.classes.push(EClass { nodes: vec![nid], shape: sid, dtype: DType::F32 });
graph.nodes[nid].class_of = cid;
cid
}
fn leaf(graph: &mut Graph, shapes: &mut Slab<ShapeId, Vec<Dim>>, shape: Vec<Dim>) -> ClassId {
let cid = class(graph, shapes, Node::Leaf { dtype: DType::F32, leaf_id: graph.max_leaf_id }, shape);
graph.max_leaf_id += 1;
cid
}
fn matmul_graph(m: Dim, n: Dim, k: Dim) -> (Graph, Slab<ShapeId, Vec<Dim>>, BTreeSet<ClassId>) {
let mut graph = Graph::new();
let mut shapes = Slab::new();
let a = leaf(&mut graph, &mut shapes, vec![m, k]);
let b = leaf(&mut graph, &mut shapes, vec![k, n]);
let s_m1k = shapes.push(vec![m, 1, k]);
let s_1nk = shapes.push(vec![1, n, k]);
let s_mnk = shapes.push(vec![m, n, k]);
let ra = class(&mut graph, &mut shapes, Node::Reshape { x: a, shape: s_m1k }, vec![m, 1, k]);
let bt = class(&mut graph, &mut shapes, Node::Permute { x: b, axes: vec![1, 0].into_boxed_slice() }, vec![n, k]);
let rb = class(&mut graph, &mut shapes, Node::Reshape { x: bt, shape: s_1nk }, vec![1, n, k]);
let ea = class(&mut graph, &mut shapes, Node::Expand { x: ra, shape: s_mnk }, vec![m, n, k]);
let eb = class(&mut graph, &mut shapes, Node::Expand { x: rb, shape: s_mnk }, vec![m, n, k]);
let mul = class(&mut graph, &mut shapes, Node::Binary { x: ea, y: eb, bop: BOp::Mul }, vec![m, n, k]);
let cast = class(&mut graph, &mut shapes, Node::Cast { x: mul, dtype: DType::F32 }, vec![m, n, k]);
let out =
class(&mut graph, &mut shapes, Node::Reduce { x: cast, bop: BOp::Add, axes: vec![2].into_boxed_slice() }, vec![m, n]);
let outputs: BTreeSet<ClassId> = [out].into();
(graph, shapes, outputs)
}
#[test]
fn matches_matmul() {
let (graph, shapes, outputs) = matmul_graph(2, 3, 4);
let out = outputs.iter().next().copied().unwrap();
let mm = graph.match_matmul(out, &shapes).unwrap();
assert_eq!(mm.m, 2);
assert_eq!(mm.n, 3);
assert_eq!(mm.k, 4);
}
#[test]
fn does_not_match_reduce_over_first_axis() {
let (mut graph, mut shapes, _) = matmul_graph(2, 3, 4);
let out = class(
&mut graph,
&mut shapes,
Node::Reduce { x: ClassId::from(6), bop: BOp::Add, axes: vec![0].into_boxed_slice() },
vec![3, 4],
);
assert!(graph.match_matmul(out, &shapes).is_none());
}
#[test]
fn matches_matmul_with_folded_a_reshape() {
let (mut graph, mut shapes, _) = matmul_graph(2, 3, 4);
let folded_a = leaf(&mut graph, &mut shapes, vec![2, 1, 4]);
let m = 2;
let n = 3;
let k = 4;
let s_mnk = shapes.push(vec![m, n, k]);
let ea = class(&mut graph, &mut shapes, Node::Expand { x: folded_a, shape: s_mnk }, vec![m, n, k]);
let mul = class(&mut graph, &mut shapes, Node::Binary { x: ea, y: ClassId::from(6), bop: BOp::Mul }, vec![m, n, k]);
let cast = class(&mut graph, &mut shapes, Node::Cast { x: mul, dtype: DType::F32 }, vec![m, n, k]);
let out =
class(&mut graph, &mut shapes, Node::Reduce { x: cast, bop: BOp::Add, axes: vec![2].into_boxed_slice() }, vec![m, n]);
let mm = graph.match_matmul(out, &shapes).unwrap();
assert_eq!(mm.a, folded_a);
assert_eq!(mm.m, m);
assert_eq!(mm.n, n);
assert_eq!(mm.k, k);
}
}