#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Val(pub u32);
pub type W = usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Rows {
Tokens,
Seqs,
Markers,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Shape {
pub rows: Rows,
pub width: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Epilogue {
None,
Gelu,
Relu,
Accumulate,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Op {
Embed {
table: W,
out: Val,
},
LayerNorm {
x: Val,
w: W,
b: Option<W>,
eps: f64,
out: Val,
},
Gemm {
a: Val,
w: W,
b: Option<W>,
epilogue: Epilogue,
out: Val,
},
Rope {
qkv: Val,
theta: f64,
},
Attention {
qkv: Val,
window: Option<usize>,
out: Val,
},
GeGlu {
x: Val,
out: Val,
},
AddType {
h: Val,
table: W,
},
GatherMarkers {
h: Val,
out: Val,
},
ActFeatures {
h: Val,
logits: Val,
out: Val,
},
}
impl Op {
#[must_use]
pub fn uses(&self) -> (Vec<Val>, Vec<Val>) {
match *self {
Op::Embed { out, .. } => (vec![], vec![out]),
Op::LayerNorm { x, out, .. } | Op::GeGlu { x, out } => (vec![x], vec![out]),
Op::Gemm { a, epilogue, out, .. } => {
if epilogue == Epilogue::Accumulate {
(vec![a, out], vec![out])
} else {
(vec![a], vec![out])
}
}
Op::Rope { qkv, .. } => (vec![qkv], vec![qkv]),
Op::Attention { qkv, out, .. } => (vec![qkv], vec![out]),
Op::AddType { h, .. } => (vec![h], vec![h]),
Op::GatherMarkers { h, out } => (vec![h], vec![out]),
Op::ActFeatures { h, logits, out } => (vec![h, logits], vec![out]),
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Graph {
pub vals: Vec<Shape>,
pub ops: Vec<Op>,
pub logits: Option<Val>,
pub act: Option<Val>,
}
impl Graph {
pub fn val(&mut self, rows: Rows, width: usize) -> Val {
self.vals.push(Shape { rows, width });
Val(u32::try_from(self.vals.len() - 1).expect("fewer than 2^32 values"))
}
#[must_use]
pub fn shape(&self, v: Val) -> Shape {
self.vals[v.0 as usize]
}
pub fn push(&mut self, op: Op) {
self.ops.push(op);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Layout {
pub offsets: Vec<usize>,
pub len: usize,
}
pub const ALIGN: usize = 16;
#[must_use]
pub fn layout(graph: &Graph, rows: impl Fn(Rows) -> usize) -> Layout {
let n = graph.vals.len();
let mut live = vec![(usize::MAX, 0usize); n];
for (i, op) in graph.ops.iter().enumerate() {
let (r, w) = op.uses();
for v in r.into_iter().chain(w) {
let l = &mut live[v.0 as usize];
l.0 = l.0.min(i);
l.1 = l.1.max(i);
}
}
for v in [graph.logits, graph.act].into_iter().flatten() {
let l = &mut live[v.0 as usize];
l.0 = l.0.min(graph.ops.len());
l.1 = usize::MAX;
}
let size: Vec<usize> =
graph.vals.iter().map(|s| (rows(s.rows) * s.width).next_multiple_of(ALIGN)).collect();
let mut order: Vec<usize> = (0..n).filter(|&v| live[v].0 != usize::MAX).collect();
order.sort_by_key(|&v| (std::cmp::Reverse(size[v]), v));
let mut offsets = vec![0usize; n];
let mut placed: Vec<usize> = Vec::new();
let mut len = 0;
for v in order {
let (a, b) = live[v];
let mut taken: Vec<(usize, usize)> = placed
.iter()
.filter(|&&u| live[u].0 <= b && a <= live[u].1)
.map(|&u| (offsets[u], offsets[u] + size[u]))
.collect();
taken.sort_unstable();
let mut at = 0;
for (lo, hi) in taken {
if at + size[v] <= lo {
break;
}
at = at.max(hi);
}
offsets[v] = at;
len = len.max(at + size[v]);
placed.push(v);
}
Layout { offsets, len }
}
#[cfg(test)]
mod tests {
use super::*;
fn chain(width: usize, steps: usize) -> Graph {
let mut g = Graph::default();
let mut x = g.val(Rows::Tokens, width);
g.push(Op::Embed { table: 0, out: x });
for _ in 0..steps {
let y = g.val(Rows::Tokens, width);
g.push(Op::LayerNorm { x, w: 1, b: None, eps: 1e-5, out: y });
x = y;
}
g.logits = Some(x);
g
}
#[test]
fn a_chain_needs_two_buffers() {
let g = chain(10, 20);
let l = layout(&g, |_| 7);
let one = (7 * 10usize).next_multiple_of(ALIGN);
assert_eq!(l.len, 2 * one);
for (i, op) in g.ops.iter().enumerate().skip(1) {
let Op::LayerNorm { x, out, .. } = *op else { unreachable!() };
assert_ne!(l.offsets[x.0 as usize], l.offsets[out.0 as usize], "op {i} aliases");
}
}
#[test]
fn live_values_never_overlap() {
let mut g = Graph::default();
let h = g.val(Rows::Tokens, 8);
g.push(Op::Embed { table: 0, out: h });
for i in 0..6 {
let a = g.val(Rows::Tokens, 8 * (i % 3 + 1));
let b = g.val(Rows::Seqs, 3);
g.push(Op::Gemm { a: h, w: 0, b: None, epilogue: Epilogue::None, out: a });
g.push(Op::GatherMarkers { h: a, out: b });
g.push(Op::Gemm { a, w: 0, b: None, epilogue: Epilogue::Accumulate, out: h });
}
g.act = Some(h);
let rows = |r| match r {
Rows::Tokens => 33,
Rows::Seqs => 5,
Rows::Markers => 9,
};
let l = layout(&g, rows);
let mut live = vec![(usize::MAX, 0); g.vals.len()];
for (i, op) in g.ops.iter().enumerate() {
let (r, w) = op.uses();
for v in r.into_iter().chain(w) {
let x = &mut live[v.0 as usize];
*x = (x.0.min(i), x.1.max(i));
}
}
live[h.0 as usize].1 = usize::MAX;
for u in 0..g.vals.len() {
for v in 0..u {
let time = live[u].0 <= live[v].1 && live[v].0 <= live[u].1;
let su = rows(g.vals[u].rows) * g.vals[u].width;
let sv = rows(g.vals[v].rows) * g.vals[v].width;
let space = l.offsets[u] < l.offsets[v] + sv && l.offsets[v] < l.offsets[u] + su;
assert!(!(time && space), "values {u} and {v} overlap");
assert!(l.offsets[u] + su <= l.len);
}
}
assert!(l.len < g.vals.iter().map(|s| rows(s.rows) * s.width).sum::<usize>());
}
}