use rlx_ir::op::{Activation, BinaryOp};
use rlx_ir::{DType, Graph, NodeId, Op, Philox4x32, Shape};
use rlx_opscope::dataflow::repeated_flow_patterns;
use rlx_opscope::shapes::{DEFAULT_RIDGE, gemm_shape_histogram, op_costs, roofline_class};
use rlx_opscope::{Recorder, StatConfig, inject_infer_stats, inject_matmul_stats};
use rlx_runtime::{Device, Session};
const V: usize = 256; const SQ: usize = 32; const D: usize = 64; const DFF: usize = 256; const L: usize = 4;
fn sh(d: &[usize]) -> Shape {
Shape::new(d, DType::F32)
}
fn causal_mask(g: &mut Graph) -> NodeId {
let mut m = vec![0f32; SQ * SQ];
for i in 0..SQ {
for j in 0..SQ {
if j > i {
m[i * SQ + j] = -1e9; }
}
}
let data: Vec<u8> = m.iter().flat_map(|v| v.to_le_bytes()).collect();
g.add_node(Op::Constant { data }, vec![], sh(&[SQ, SQ]))
}
fn scalar_const(g: &mut Graph, v: f32) -> NodeId {
g.add_node(
Op::Constant {
data: v.to_le_bytes().to_vec(),
},
vec![],
sh(&[1]),
)
}
fn named_matmul(g: &mut Graph, a: NodeId, b: NodeId, out: Shape, name: &str) -> NodeId {
let id = g.matmul(a, b, out);
g.node_mut(id).name = Some(name.into());
id
}
fn attention(g: &mut Graph, x: NodeId, mask: NodeId, l: usize) -> NodeId {
let (s, dd) = (sh(&[SQ, D]), sh(&[D, D]));
let wq = g.param(format!("L{l}.wq"), dd.clone());
let wk = g.param(format!("L{l}.wk"), dd.clone());
let wv = g.param(format!("L{l}.wv"), dd.clone());
let wo = g.param(format!("L{l}.wo"), dd);
let q = named_matmul(g, x, wq, s.clone(), &format!("L{l}.q"));
let k = named_matmul(g, x, wk, s.clone(), &format!("L{l}.k"));
let vv = named_matmul(g, x, wv, s.clone(), &format!("L{l}.v"));
let kt = g.add_node(Op::Transpose { perm: vec![1, 0] }, vec![k], sh(&[D, SQ]));
let scores = named_matmul(g, q, kt, sh(&[SQ, SQ]), &format!("L{l}.qk"));
let scale = scalar_const(g, 1.0 / (D as f32).sqrt());
let scores = g.add_node(
Op::Binary(BinaryOp::Mul),
vec![scores, scale],
sh(&[SQ, SQ]),
);
let scores = g.add_node(Op::Binary(BinaryOp::Add), vec![scores, mask], sh(&[SQ, SQ]));
let p = g.softmax(scores, -1, sh(&[SQ, SQ]));
let ctx = named_matmul(g, p, vv, s.clone(), &format!("L{l}.av"));
let o = named_matmul(g, ctx, wo, s.clone(), &format!("L{l}.o"));
g.add_node(Op::Binary(BinaryOp::Add), vec![x, o], s) }
fn ffn(g: &mut Graph, x: NodeId, l: usize) -> NodeId {
let s = sh(&[SQ, D]);
let w1 = g.param(format!("L{l}.f1"), sh(&[D, DFF]));
let w2 = g.param(format!("L{l}.f2"), sh(&[DFF, D]));
let h = named_matmul(g, x, w1, sh(&[SQ, DFF]), &format!("L{l}.ffn_up"));
let h = g.activation(Activation::Silu, h, sh(&[SQ, DFF]));
let y = named_matmul(g, h, w2, s.clone(), &format!("L{l}.ffn_down"));
g.add_node(Op::Binary(BinaryOp::Add), vec![x, y], s)
}
fn build_llm() -> Graph {
let mut g = Graph::new("tiny_llm");
let mut x = g.input("x", sh(&[SQ, D]));
let mask = causal_mask(&mut g);
for l in 0..L {
x = attention(&mut g, x, mask, l);
x = ffn(&mut g, x, l);
}
let wlm = g.param("lm_head", sh(&[D, V]));
let logits = named_matmul(&mut g, x, wlm, sh(&[SQ, V]), "lm_head");
g.set_outputs(vec![logits]);
g
}
fn mean(v: &[f32]) -> f32 {
v.iter().sum::<f32>() / v.len().max(1) as f32
}
fn main() -> std::io::Result<()> {
let out = std::env::args()
.nth(1)
.unwrap_or_else(|| "opscope_llm.csv".into());
let g = build_llm();
println!(
"tiny_llm: {L} layers, seq {SQ}, dim {D}, vocab {V} — {} nodes\n",
g.nodes().len()
);
let costs = op_costs(&g);
let (tf, tb) = (
costs.iter().map(|c| c.flops).sum::<u64>(),
costs.iter().map(|c| c.bytes).sum::<u64>(),
);
let (mut mem, mut comp) = (0u64, 0u64);
for c in &costs {
match roofline_class(c, DEFAULT_RIDGE) {
"memory-bound" => mem += c.flops,
"compute-bound" => comp += c.flops,
_ => {}
}
}
println!(
"[Tier3] {:.3} GFLOP, {:.2} MB, {:.1} FLOP/byte — {:.0}% memory-bound",
tf as f64 / 1e9,
tb as f64 / 1e6,
tf as f64 / tb.max(1) as f64,
mem as f64 / (mem + comp).max(1) as f64 * 100.0
);
print!("[Tier3] hot GEMM shapes: ");
for ((m, k, n), (ct, _)) in gemm_shape_histogram(&costs).iter().take(4) {
print!("{m}×{k}×{n}(×{ct}) ");
}
println!("\n");
let pats = repeated_flow_patterns(&g, 3, 5, 2);
println!("[struct] top repeated dataflow blocks (fusion candidates):");
for p in pats.iter().take(3) {
println!(" ×{} d{} {}", p.count, p.depth, p.tree);
}
println!();
let cfg = StatConfig::default();
let (g1, mm_specs) = inject_matmul_stats(&g, &cfg);
let (g2, inf_specs) = inject_infer_stats(&g1, &cfg);
let mut c = Session::new(Device::Cpu).compile(g2);
let mut rng = Philox4x32::new(0x11CE);
for node in g.nodes() {
if let Op::Param { name } = &node.op {
let dims: Vec<usize> = (0..node.shape.rank())
.map(|i| node.shape.dim(i).unwrap_static())
.collect();
let numel: usize = dims.iter().product();
let mut w = vec![0f32; numel];
rng.fill_normal(&mut w);
let scale = (2.0 / dims[0] as f32).sqrt();
for v in &mut w {
*v *= scale;
}
c.set_param(name, &w);
}
}
let mut x = vec![0f32; SQ * D];
rng.fill_normal(&mut x);
let outs = c.run(&[("x", &x)]);
println!("[Tier1] causal attention (per layer): peak mass + where attention lands");
let mut layer = 0;
for spec in inf_specs.iter().filter(|s| s.stat == "attn_qmax") {
let qmax = &outs[spec.out_idx];
let krecv_spec = inf_specs
.iter()
.find(|s| s.stat == "attn_krecv" && s.site == spec.site)
.unwrap();
let krecv = &outs[krecv_spec.out_idx];
let early = SQ / 4;
let early_mass: f32 =
krecv[..early].iter().sum::<f32>() / krecv.iter().sum::<f32>().max(1.0);
println!(
" {:<8} mean peak {:.2} first {early} keys get {:.0}% of attention (causal skew)",
spec.site,
mean(qmax),
early_mass * 100.0
);
layer += 1;
if layer >= L {
break;
}
}
println!();
let mut rec = Recorder::create(&out)?;
rec.record(0, 0, "cpu", "llm", SQ, D, V, &mm_specs, &outs)?;
rec.flush()?;
println!(
"[value] wrote {} matmul-site sketches → {out}",
mm_specs.len()
);
println!(" next: opscope-mine {out} | opscope-plan {out}");
Ok(())
}