1use rlx_ir::infer::GraphExt;
26use rlx_ir::op::{Activation, BinaryOp, CmpOp, ReduceOp};
27use rlx_ir::{DType, Graph, NodeId, Op, Philox4x32, Shape};
28use std::collections::HashMap;
29use std::io::{self, Write};
30
31pub mod bytes;
32pub mod dataflow;
33pub mod demo;
34pub mod guard;
35#[cfg(feature = "decompose")]
39pub mod kernels;
40#[cfg(feature = "allocate")]
41pub mod layers;
42pub mod live;
43pub mod motifs;
44pub mod online;
45pub mod optimize;
46pub mod parity;
47#[cfg(feature = "parquet")]
48pub mod parquet_sink;
49pub mod probe;
50pub mod shapes;
51#[cfg(feature = "decompose")]
52pub mod svd;
53pub mod timing;
54
55#[derive(Clone, Copy, Debug)]
59pub struct StatConfig {
60 pub hist_bins: usize,
62 pub hist_min: f32,
64 pub hist_max: f32,
65 pub hist_normalize: bool,
69 pub per_channel: bool,
71 pub per_position: bool,
74 pub adjacency: bool,
77}
78
79impl Default for StatConfig {
80 fn default() -> Self {
81 Self {
82 hist_bins: 32,
83 hist_min: -6.0,
84 hist_max: 6.0,
85 hist_normalize: true,
86 per_channel: true,
87 per_position: true,
88 adjacency: true,
89 }
90 }
91}
92
93#[derive(Clone, Debug)]
95pub struct StatSpec {
96 pub out_idx: usize,
99 pub site: String,
101 pub role: &'static str,
103 pub stat: &'static str,
106 pub len: usize,
109 pub numel: usize,
111 pub flops: u64,
114}
115
116pub fn write_site_costs(path: &str, specs: &[StatSpec]) -> io::Result<()> {
119 let mut seen: HashMap<String, u64> = HashMap::new();
120 for s in specs {
121 seen.entry(s.site.clone()).or_insert(s.flops);
122 }
123 let mut f = io::BufWriter::new(std::fs::File::create(path)?);
124 writeln!(f, "site,flops")?;
125 for (site, flops) in seen {
126 writeln!(f, "{},{}", site.replace(',', "_"), flops)?;
127 }
128 f.flush()
129}
130
131fn reduce_scalar(g: &mut Graph, t: NodeId, op: ReduceOp) -> NodeId {
133 let rank = g.shape(t).rank();
134 g.reduce(
135 t,
136 op,
137 (0..rank).collect(),
138 false,
139 Shape::new(&[1], DType::F32),
140 )
141}
142
143fn build_tensor_stats(g: &mut Graph, t: NodeId, cfg: &StatConfig) -> Vec<(NodeId, &'static str)> {
145 let tshape = g.shape(t).clone();
146 let rank = tshape.rank();
147 let mut out: Vec<(NodeId, &'static str)> = Vec::new();
148
149 out.push((reduce_scalar(g, t, ReduceOp::Min), "min"));
150 out.push((reduce_scalar(g, t, ReduceOp::Max), "max"));
151 out.push((reduce_scalar(g, t, ReduceOp::Mean), "mean"));
152
153 let abs = g.activation(Activation::Abs, t, tshape.clone());
155 out.push((reduce_scalar(g, abs, ReduceOp::Sum), "l1"));
156
157 let sq = g.add_node(Op::Binary(BinaryOp::Mul), vec![t, t], tshape.clone());
159 out.push((reduce_scalar(g, sq, ReduceOp::Sum), "sumsq"));
160
161 let zero = g.full(&[1], 0.0, DType::F32);
163 let ne = g.add_node(
164 Op::Compare(CmpOp::Ne),
165 vec![t, zero],
166 tshape.clone().with_dtype(DType::Bool),
167 );
168 let ne_f = g.add_node(Op::Cast { to: DType::F32 }, vec![ne], tshape.clone());
169 out.push((reduce_scalar(g, ne_f, ReduceOp::Sum), "nnz"));
170
171 let one = Shape::new(&[1], DType::F32);
176 let mean1 = reduce_scalar(g, t, ReduceOp::Mean);
177 let cen = g.add_node(Op::Binary(BinaryOp::Sub), vec![t, mean1], tshape.clone()); let c2 = g.add_node(Op::Binary(BinaryOp::Mul), vec![cen, cen], tshape.clone());
179 let m2 = reduce_scalar(g, c2, ReduceOp::Mean);
180 let c4 = g.add_node(Op::Binary(BinaryOp::Mul), vec![c2, c2], tshape.clone());
181 let m4 = reduce_scalar(g, c4, ReduceOp::Mean);
182 let m2sq = g.add_node(Op::Binary(BinaryOp::Mul), vec![m2, m2], one.clone());
183 let eps4 = g.full(&[1], 1e-20, DType::F32);
184 let kden = g.add_node(Op::Binary(BinaryOp::Add), vec![m2sq, eps4], one.clone());
185 let kurt = g.add_node(Op::Binary(BinaryOp::Div), vec![m4, kden], one.clone());
186 out.push((kurt, "kurtosis"));
187
188 if cfg.hist_bins > 0 {
196 let hist_node = if cfg.hist_normalize {
197 let absn = g.activation(Activation::Abs, t, tshape.clone());
198 let scale = reduce_scalar(g, absn, ReduceOp::Max); let eps = g.full(&[1], 1e-12, DType::F32);
200 let denom = g.add_node(
201 Op::Binary(BinaryOp::Add),
202 vec![scale, eps],
203 Shape::new(&[1], DType::F32),
204 );
205 let normed = g.add_node(Op::Binary(BinaryOp::Div), vec![t, denom], tshape.clone());
206 g.histogram(normed, cfg.hist_bins, -1.0, 1.0)
207 } else {
208 g.histogram(t, cfg.hist_bins, cfg.hist_min, cfg.hist_max)
209 };
210 out.push((hist_node, "hist"));
211 }
212
213 let numel = tshape.num_elements().unwrap_or(0);
219 let k = 16usize.min(numel);
220 if k > 0 {
221 let flat = g.reshape_(t, vec![numel as i64]);
222 let idx: Vec<i64> = (0..k).map(|j| (j * numel / k) as i64).collect();
223 let data: Vec<u8> = idx.iter().flat_map(|v| v.to_le_bytes()).collect();
224 let idx_node = g.add_node(Op::Constant { data }, vec![], Shape::new(&[k], DType::I64));
225 let sig = g.gather_(flat, idx_node, 0);
226 out.push((sig, "elemsig"));
227 }
228
229 if rank >= 2 && cfg.per_channel {
230 let absc = g.activation(Activation::Abs, t, tshape.clone());
232 let c = tshape.dim(rank - 1).unwrap_static();
233 let chan = g.reduce(
234 absc,
235 ReduceOp::Max,
236 (0..rank - 1).collect(),
237 false,
238 Shape::new(&[c], DType::F32),
239 );
240 let cmax = reduce_scalar(g, chan, ReduceOp::Max);
245 let cmean = reduce_scalar(g, chan, ReduceOp::Mean);
246 let epsc = g.full(&[1], 1e-20, DType::F32);
247 let cden = g.add_node(
248 Op::Binary(BinaryOp::Add),
249 vec![cmean, epsc],
250 Shape::new(&[1], DType::F32),
251 );
252 let cout = g.add_node(
253 Op::Binary(BinaryOp::Div),
254 vec![cmax, cden],
255 Shape::new(&[1], DType::F32),
256 );
257 out.push((chan, "chan_maxabs"));
258 out.push((cout, "chan_outlier"));
259 }
260
261 if rank >= 2 && cfg.per_position {
262 let sqp = g.add_node(Op::Binary(BinaryOp::Mul), vec![t, t], tshape.clone());
264 let c = tshape.dim(rank - 1).unwrap_static();
265 let rows = tshape.num_elements().unwrap_or(c) / c;
266 let pos = g.reduce(
267 sqp,
268 ReduceOp::Sum,
269 vec![rank - 1],
270 false,
271 Shape::new(&[rows], DType::F32),
272 );
273 out.push((pos, "pos_sumsq"));
274 }
275
276 if rank == 2 && cfg.adjacency {
277 let rows = tshape.dim(0).unwrap_static();
281 let d = tshape.dim(1).unwrap_static();
282 if rows >= 2 {
283 let pair = Shape::new(&[rows - 1, d], DType::F32);
284 let prev = g.narrow_(t, 0, 0, rows - 1);
285 let next = g.narrow_(t, 0, 1, rows - 1);
286 let diff = g.add_node(Op::Binary(BinaryOp::Sub), vec![next, prev], pair.clone());
287 let sq = g.add_node(Op::Binary(BinaryOp::Mul), vec![diff, diff], pair);
288 let adj = g.reduce(
289 sq,
290 ReduceOp::Sum,
291 vec![1],
292 false,
293 Shape::new(&[rows - 1], DType::F32),
294 );
295 out.push((adj, "adj_sumsq"));
296 }
297 }
298
299 out
300}
301
302pub fn inject_matmul_stats(graph: &Graph, cfg: &StatConfig) -> (Graph, Vec<StatSpec>) {
307 inject_matmul_stats_filtered(graph, cfg, &|_lhs_numel, _out_numel| true)
308}
309
310pub fn inject_matmul_stats_filtered(
317 graph: &Graph,
318 cfg: &StatConfig,
319 keep: &dyn Fn(usize, usize) -> bool,
320) -> (Graph, Vec<StatSpec>) {
321 let mut g = Graph::new(&graph.name);
322 let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
323 let mut matmuls: Vec<(String, NodeId, NodeId, NodeId)> = Vec::new();
325 let mut dq_matmuls: Vec<(String, NodeId, NodeId)> = Vec::new();
329
330 for node in graph.nodes() {
331 let inputs: Vec<NodeId> = node.inputs.iter().map(|i| id_map[i]).collect();
332 let new_id = g.add_node(node.op.clone(), inputs.clone(), node.shape.clone());
333 id_map.insert(node.id, new_id);
334 if matches!(node.op, Op::MatMul) {
335 let site = match &node.name {
338 Some(n) => format!("{n}#{}", node.id.0),
339 None => format!("matmul#{}", node.id.0),
340 };
341 matmuls.push((site, inputs[0], inputs[1], new_id));
342 }
343 if matches!(node.op, Op::DequantMatMul { .. }) {
344 let site = match &node.name {
345 Some(n) => format!("{n}#{}", node.id.0),
346 None => format!("dqmm#{}", node.id.0),
347 };
348 dq_matmuls.push((site, inputs[0], new_id));
349 }
350 }
351
352 let mut outputs: Vec<NodeId> = graph.outputs.iter().map(|i| id_map[i]).collect();
353 let mut specs: Vec<StatSpec> = Vec::new();
354
355 for (site, lhs, rhs, out_t) in matmuls {
356 let ls = g.shape(lhs).clone();
358 let os = g.shape(out_t).clone();
359 if !keep(
360 ls.num_elements().unwrap_or(0),
361 os.num_elements().unwrap_or(0),
362 ) {
363 continue;
364 }
365 let m = ls.dim(0).unwrap_static();
366 let kk = ls.dim(ls.rank() - 1).unwrap_static();
367 let n = os.dim(os.rank() - 1).unwrap_static();
368 let flops = 2 * m as u64 * kk as u64 * n as u64;
369 for (role, t) in [("lhs", lhs), ("rhs", rhs), ("out", out_t)] {
370 let numel = g.shape(t).num_elements().unwrap_or(0);
371 for (node, stat) in build_tensor_stats(&mut g, t, cfg) {
372 let len = g.shape(node).num_elements().unwrap_or(1);
373 specs.push(StatSpec {
374 out_idx: outputs.len(),
375 site: site.clone(),
376 role,
377 stat,
378 len,
379 numel,
380 flops,
381 });
382 outputs.push(node);
383 }
384 }
385 }
386
387 for (site, lhs, out_t) in dq_matmuls {
389 let ls = g.shape(lhs).clone();
390 let os = g.shape(out_t).clone();
391 if !keep(
392 ls.num_elements().unwrap_or(0),
393 os.num_elements().unwrap_or(0),
394 ) {
395 continue;
396 }
397 let m = ls.dim(0).unwrap_static();
398 let kk = ls.dim(ls.rank() - 1).unwrap_static();
399 let n = os.dim(os.rank() - 1).unwrap_static();
400 let flops = 2 * m as u64 * kk as u64 * n as u64;
401 for (role, t) in [("lhs", lhs), ("out", out_t)] {
402 let numel = g.shape(t).num_elements().unwrap_or(0);
403 for (node, stat) in build_tensor_stats(&mut g, t, cfg) {
404 let len = g.shape(node).num_elements().unwrap_or(1);
405 specs.push(StatSpec {
406 out_idx: outputs.len(),
407 site: site.clone(),
408 role,
409 stat,
410 len,
411 numel,
412 flops,
413 });
414 outputs.push(node);
415 }
416 }
417 }
418
419 g.set_outputs(outputs);
420 (g, specs)
421}
422
423fn fakequant(g: &mut Graph, a: NodeId, per_channel: bool) -> NodeId {
432 let ashape = g.shape(a).clone();
433 let rank = ashape.rank();
434 if rank == 0 {
435 return a;
436 }
437 let last = rank - 1;
438 let dims: Vec<usize> = (0..rank).map(|i| ashape.dim(i).unwrap_static()).collect();
439 let (axes, mut rdims): (Vec<usize>, Vec<usize>) = if per_channel {
442 let mut rd = dims.clone();
443 (0..last).for_each(|i| rd[i] = 1);
444 ((0..last).collect(), rd)
445 } else {
446 let mut rd = dims.clone();
447 rd[last] = 1;
448 (vec![last], rd)
449 };
450 if axes.is_empty() {
451 rdims = dims.clone();
452 }
453 let rshape = Shape::new(&rdims, DType::F32);
454 let scalar = |g: &mut Graph, v: f32| {
455 g.add_node(
456 Op::Constant {
457 data: v.to_le_bytes().to_vec(),
458 },
459 vec![],
460 Shape::new(&[1], DType::F32),
461 )
462 };
463
464 let abs = g.add_node(Op::Activation(Activation::Abs), vec![a], ashape.clone());
465 let amax = g.reduce(abs, ReduceOp::Max, axes, true, rshape.clone());
466 let inv = g.add_node(
467 Op::Activation(Activation::Recip),
468 vec![amax],
469 rshape.clone(),
470 ); let norm = g.add_node(Op::Binary(BinaryOp::Mul), vec![a, inv], ashape.clone()); let c127 = scalar(g, 127.0);
473 let up = g.add_node(Op::Binary(BinaryOp::Mul), vec![norm, c127], ashape.clone());
474 let r = g.add_node(Op::Activation(Activation::Round), vec![up], ashape.clone());
475 let rc = g.add_node(
476 Op::Clamp {
477 min: -127.0,
478 max: 127.0,
479 },
480 vec![r],
481 ashape.clone(),
482 );
483 let c_inv = scalar(g, 1.0 / 127.0);
484 let back = g.add_node(Op::Binary(BinaryOp::Mul), vec![rc, c_inv], ashape.clone()); g.add_node(Op::Binary(BinaryOp::Mul), vec![back, amax], ashape.clone()) }
487
488pub fn inject_activation_fakequant(graph: &Graph, per_channel: bool) -> Graph {
499 let mut g = Graph::new(&graph.name);
500 let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
501 for node in graph.nodes() {
502 let inputs: Vec<NodeId> = node.inputs.iter().map(|i| id_map[i]).collect();
503 let new_id = if matches!(node.op, Op::MatMul) && !inputs.is_empty() {
504 let fq = fakequant(&mut g, inputs[0], per_channel);
505 let mut mm = inputs.clone();
506 mm[0] = fq;
507 g.add_node(Op::MatMul, mm, node.shape.clone())
508 } else {
509 g.add_node(node.op.clone(), inputs, node.shape.clone())
510 };
511 id_map.insert(node.id, new_id);
512 }
513 let outputs: Vec<NodeId> = graph.outputs.iter().map(|i| id_map[i]).collect();
514 g.set_outputs(outputs);
515 g
516}
517
518pub struct ResidualSpec {
520 pub a_idx: usize,
522 pub b_idx: usize,
523 pub order: usize,
525}
526
527pub fn inject_residual_stats(graph: &Graph) -> (Graph, Vec<ResidualSpec>) {
534 let mut g = Graph::new(&graph.name);
535 let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
536 let mut adds: Vec<(NodeId, NodeId)> = Vec::new();
537 for node in graph.nodes() {
538 let inputs: Vec<NodeId> = node.inputs.iter().map(|i| id_map[i]).collect();
539 let new_id = g.add_node(node.op.clone(), inputs.clone(), node.shape.clone());
540 id_map.insert(node.id, new_id);
541 if matches!(node.op, Op::Binary(BinaryOp::Add)) && inputs.len() == 2 {
542 adds.push((inputs[0], inputs[1]));
543 }
544 }
545 let mut outputs: Vec<NodeId> = graph.outputs.iter().map(|i| id_map[i]).collect();
546 let mut specs = Vec::new();
547 for (order, (a, b)) in adds.into_iter().enumerate() {
548 let ash = g.shape(a).clone();
549 let bsh = g.shape(b).clone();
550 let sqa = g.add_node(Op::Binary(BinaryOp::Mul), vec![a, a], ash);
551 let ssa = reduce_scalar(&mut g, sqa, ReduceOp::Sum);
552 let sqb = g.add_node(Op::Binary(BinaryOp::Mul), vec![b, b], bsh);
553 let ssb = reduce_scalar(&mut g, sqb, ReduceOp::Sum);
554 specs.push(ResidualSpec {
555 a_idx: outputs.len(),
556 b_idx: outputs.len() + 1,
557 order,
558 });
559 outputs.push(ssa);
560 outputs.push(ssb);
561 }
562 g.set_outputs(outputs);
563 (g, specs)
564}
565
566pub fn skip_residual_blocks(graph: &Graph, skip_orders: &[usize]) -> Graph {
573 let mut g = Graph::new(&graph.name);
574 let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
575 let mut order = 0usize;
576 for node in graph.nodes() {
577 let inputs: Vec<NodeId> = node.inputs.iter().map(|i| id_map[i]).collect();
578 if matches!(node.op, Op::Binary(BinaryOp::Add)) && inputs.len() == 2 {
579 let this = order;
580 order += 1;
581 if skip_orders.contains(&this) {
582 id_map.insert(node.id, inputs[0]);
585 continue;
586 }
587 }
588 let new_id = g.add_node(node.op.clone(), inputs, node.shape.clone());
589 id_map.insert(node.id, new_id);
590 }
591 let outputs: Vec<NodeId> = graph.outputs.iter().map(|i| id_map[i]).collect();
592 g.set_outputs(outputs);
593 g
594}
595
596#[cfg(test)]
597mod fakequant_tests {
598 use super::*;
599 use rlx_runtime::{CompileOptions, Device, Session};
600
601 #[test]
602 fn fakequant_subgraph_matches_per_row_int8() {
603 let (m, k) = (3usize, 8usize);
604 let mut g = Graph::new("fq");
605 let x = g.input("x", Shape::new(&[m, k], DType::F32));
606 let fq = fakequant(&mut g, x, false);
607 g.set_outputs(vec![fq]);
608 let mut o = CompileOptions::default();
609 o.fusion_opts.skip_fusion = true;
610 let mut c = Session::new(Device::Cpu).compile_with(g, &o);
611 let xd: Vec<f32> = (0..m * k).map(|i| i as f32 * 0.37 - 1.5).collect();
612 let got = c.run(&[("x", &xd)]).remove(0);
613 let mut want = xd.clone();
615 for r in 0..m {
616 let row = &mut want[r * k..(r + 1) * k];
617 let amax = row.iter().fold(0f32, |a, &v| a.max(v.abs()));
618 let s = if amax < 1e-20 { 1.0 } else { amax / 127.0 };
619 for v in row.iter_mut() {
620 *v = (*v / s).round().clamp(-127.0, 127.0) * s;
621 }
622 }
623 let num: f32 = got
624 .iter()
625 .zip(&want)
626 .map(|(a, b)| (a - b).powi(2))
627 .sum::<f32>()
628 .sqrt();
629 let den: f32 = want.iter().map(|v| v * v).sum::<f32>().sqrt() + 1e-9;
630 assert!(
631 num / den < 0.01,
632 "fakequant vs per-row int8 rel-err {}",
633 num / den
634 );
635 }
636}
637
638fn reduce_axis(g: &mut Graph, t: NodeId, axis: usize, op: ReduceOp) -> (NodeId, usize) {
642 let dims: Vec<usize> = {
643 let s = g.shape(t);
644 (0..s.rank()).map(|i| s.dim(i).unwrap_static()).collect()
645 };
646 let out_dims: Vec<usize> = dims
647 .iter()
648 .enumerate()
649 .filter(|(i, _)| *i != axis)
650 .map(|(_, &d)| d)
651 .collect();
652 let len: usize = out_dims.iter().product::<usize>().max(1);
653 let out_shape = Shape::new(&out_dims, DType::F32);
654 (g.reduce(t, op, vec![axis], false, out_shape), len)
655}
656
657pub fn inject_infer_stats(graph: &Graph, _cfg: &StatConfig) -> (Graph, Vec<StatSpec>) {
665 let mut g = Graph::new(&graph.name);
666 let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
667 let mut taps: Vec<(&'static str, String, NodeId, usize)> = Vec::new();
669
670 for node in graph.nodes() {
671 let inputs: Vec<NodeId> = node.inputs.iter().map(|i| id_map[i]).collect();
672 let new_id = g.add_node(node.op.clone(), inputs.clone(), node.shape.clone());
673 id_map.insert(node.id, new_id);
674 match &node.op {
675 Op::Softmax { .. } => {
676 let site = match &node.name {
677 Some(n) => format!("{n}#{}", node.id.0),
678 None => format!("softmax#{}", node.id.0),
679 };
680 taps.push(("softmax", site, new_id, 0));
681 }
682 Op::GroupedMatMul if node.inputs.len() >= 3 => {
687 let site = format!("gmm#{}", node.id.0);
688 let w = &graph.node(node.inputs[1]).shape; let experts = w.dim(0).unwrap_static();
690 taps.push(("topk", site, inputs[2], experts)); }
692 Op::TopK { .. } => {
693 let site = match &node.name {
694 Some(n) => format!("{n}#{}", node.id.0),
695 None => format!("topk#{}", node.id.0),
696 };
697 let e = &graph.node(node.inputs[0]).shape;
699 let experts = e.dim(e.rank() - 1).unwrap_static();
700 taps.push(("topk", site, new_id, experts));
701 }
702 _ => {}
703 }
704 }
705
706 let mut outputs: Vec<NodeId> = graph.outputs.iter().map(|i| id_map[i]).collect();
707 let mut specs: Vec<StatSpec> = Vec::new();
708 let mut emit = |g: &mut Graph,
709 outs: &mut Vec<NodeId>,
710 node,
711 site: &str,
712 role: &'static str,
713 stat: &'static str,
714 numel: usize| {
715 let len = g.shape(node).num_elements().unwrap_or(1);
716 specs.push(StatSpec {
717 out_idx: outs.len(),
718 site: site.into(),
719 role,
720 stat,
721 len,
722 numel,
723 flops: 0,
724 });
725 outs.push(node);
726 };
727
728 for (kind, site, node, experts) in taps {
729 let rank = g.shape(node).rank();
730 let numel = g.shape(node).num_elements().unwrap_or(0);
731 match kind {
732 "softmax" if rank >= 2 => {
733 let (qmax, _) = reduce_axis(&mut g, node, rank - 1, ReduceOp::Max);
734 emit(
735 &mut g,
736 &mut outputs,
737 qmax,
738 &site,
739 "attn",
740 "attn_qmax",
741 numel,
742 );
743 let (krecv, _) = reduce_axis(&mut g, node, rank - 2, ReduceOp::Sum);
744 emit(
745 &mut g,
746 &mut outputs,
747 krecv,
748 &site,
749 "attn",
750 "attn_krecv",
751 numel,
752 );
753 }
754 "topk" if experts > 0 => {
755 let load = g.histogram(node, experts, 0.0, experts as f32);
757 emit(
758 &mut g,
759 &mut outputs,
760 load,
761 &site,
762 "route",
763 "route_load",
764 numel,
765 );
766 }
767 _ => {}
768 }
769 }
770
771 g.set_outputs(outputs);
772 (g, specs)
773}
774
775pub fn inject_attention_stats(graph: &Graph, _cfg: &StatConfig) -> (Graph, Vec<StatSpec>) {
781 use rlx_autodiff::decompose_backward_kernels::attention_softmax_weights;
782 use rlx_ir::op::MaskKind;
783
784 let mut g = Graph::new(&graph.name);
785 let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
786 let mut attns: Vec<(
788 String,
789 NodeId,
790 NodeId,
791 NodeId,
792 Option<NodeId>,
793 usize,
794 usize,
795 MaskKind,
796 )> = Vec::new();
797
798 for node in graph.nodes() {
799 let inputs: Vec<NodeId> = node.inputs.iter().map(|i| id_map[i]).collect();
800 let new_id = g.add_node(node.op.clone(), inputs.clone(), node.shape.clone());
801 id_map.insert(node.id, new_id);
802 if let Op::Attention {
803 num_heads,
804 head_dim,
805 mask_kind,
806 ..
807 } = &node.op
808 {
809 let site = match &node.name {
810 Some(n) => format!("{n}#{}", node.id.0),
811 None => format!("attn#{}", node.id.0),
812 };
813 let mask = if matches!(mask_kind, MaskKind::Custom | MaskKind::Bias) {
814 inputs.get(3).copied()
815 } else {
816 None
817 };
818 attns.push((
819 site, inputs[0], inputs[1], inputs[2], mask, *num_heads, *head_dim, *mask_kind,
820 ));
821 }
822 }
823
824 let mut outputs: Vec<NodeId> = graph.outputs.iter().map(|i| id_map[i]).collect();
825 let mut specs: Vec<StatSpec> = Vec::new();
826 for (site, q, k, v, mask, nh, hd, mk) in attns {
827 let mask_shape = mask.map(|m| g.shape(m).clone());
828 let w = attention_softmax_weights(&mut g, q, k, v, nh, hd, mk, mask, mask_shape.as_ref());
829 let rank = g.shape(w).rank(); let numel = g.shape(w).num_elements().unwrap_or(0);
831 let (qmax, _) = reduce_axis(&mut g, w, rank - 1, ReduceOp::Max);
833 specs.push(StatSpec {
834 out_idx: outputs.len(),
835 site: site.clone(),
836 role: "attn",
837 stat: "attn_qmax",
838 len: g.shape(qmax).num_elements().unwrap_or(1),
839 numel,
840 flops: 0,
841 });
842 outputs.push(qmax);
843 let (krecv, _) = reduce_axis(&mut g, w, rank - 2, ReduceOp::Sum);
845 specs.push(StatSpec {
846 out_idx: outputs.len(),
847 site,
848 role: "attn",
849 stat: "attn_krecv",
850 len: g.shape(krecv).num_elements().unwrap_or(1),
851 numel,
852 flops: 0,
853 });
854 outputs.push(krecv);
855 }
856
857 g.set_outputs(outputs);
858 (g, specs)
859}
860
861#[derive(Clone, Copy, Debug, PartialEq, Eq)]
866pub enum Dist {
867 Gaussian,
869 Uniform,
871 Sparse90,
873 LowRank,
875 Outlier,
877 Quantized,
879 Coherent,
882}
883
884impl Dist {
885 pub const ALL: [Dist; 7] = [
886 Dist::Gaussian,
887 Dist::Uniform,
888 Dist::Sparse90,
889 Dist::LowRank,
890 Dist::Outlier,
891 Dist::Quantized,
892 Dist::Coherent,
893 ];
894
895 pub fn name(self) -> &'static str {
896 match self {
897 Dist::Gaussian => "gaussian",
898 Dist::Uniform => "uniform",
899 Dist::Sparse90 => "sparse90",
900 Dist::LowRank => "lowrank",
901 Dist::Outlier => "outlier",
902 Dist::Quantized => "quantized",
903 Dist::Coherent => "coherent",
904 }
905 }
906}
907
908#[inline]
909fn u01(rng: &mut Philox4x32) -> f32 {
910 rng.next_u32() as f32 / u32::MAX as f32
911}
912
913pub fn sample(dist: Dist, rows: usize, cols: usize, seed: u64) -> Vec<f32> {
916 let n = rows * cols;
917 let mut rng = Philox4x32::new(seed);
918 let mut base = vec![0f32; n];
919 rng.fill_normal(&mut base); let mut mask = Philox4x32::new(seed ^ 0x9E37_79B9_7F4A_7C15);
921
922 match dist {
923 Dist::Gaussian => base,
924 Dist::Uniform => (0..n).map(|_| 2.0 * u01(&mut rng) - 1.0).collect(),
925 Dist::Sparse90 => base
926 .iter()
927 .map(|&x| {
928 if mask.next_u32().is_multiple_of(10) {
929 x
930 } else {
931 0.0
932 }
933 })
934 .collect(),
935 Dist::LowRank => {
936 let r = 4usize;
937 let mut u = vec![0f32; rows * r];
938 let mut v = vec![0f32; r * cols];
939 rng.fill_normal(&mut u);
940 mask.fill_normal(&mut v);
941 let scale = 1.0 / (r as f32).sqrt();
942 let mut out = vec![0f32; n];
943 for i in 0..rows {
944 for j in 0..cols {
945 let mut acc = 0f32;
946 for k in 0..r {
947 acc += u[i * r + k] * v[k * cols + j];
948 }
949 out[i * cols + j] = acc * scale;
950 }
951 }
952 out
953 }
954 Dist::Outlier => base
955 .iter()
956 .map(|&x| {
957 if mask.next_u32().is_multiple_of(100) {
958 x * 30.0
959 } else {
960 x
961 }
962 })
963 .collect(),
964 Dist::Quantized => base.iter().map(|&x| (x * 2.0).round() / 2.0).collect(),
965 Dist::Coherent => {
966 let mut out = vec![0f32; n];
968 let mut row = vec![0f32; cols];
969 rng.fill_normal(&mut row); let mut step = vec![0f32; cols];
971 for i in 0..rows {
972 if i > 0 {
973 mask.fill_normal(&mut step);
974 for j in 0..cols {
975 row[j] += 0.15 * step[j];
976 }
977 }
978 out[i * cols..(i + 1) * cols].copy_from_slice(&row);
979 }
980 out
981 }
982 }
983}
984
985pub struct Recorder<W: Write> {
992 w: W,
993}
994
995impl Recorder<io::BufWriter<std::fs::File>> {
996 pub fn create(path: &str) -> io::Result<Self> {
998 let f = std::fs::File::create(path)?;
999 Self::new(io::BufWriter::new(f))
1000 }
1001}
1002
1003impl<W: Write> Recorder<W> {
1004 pub fn new(mut w: W) -> io::Result<Self> {
1005 writeln!(
1009 w,
1010 "run_id,step,backend,dist,M,K,N,numel,site,role,stat,idx,value"
1011 )?;
1012 Ok(Self { w })
1013 }
1014
1015 #[allow(clippy::too_many_arguments)]
1019 pub fn record(
1020 &mut self,
1021 run_id: u64,
1022 step: u64,
1023 backend: &str,
1024 dist: &str,
1025 m: usize,
1026 k: usize,
1027 n: usize,
1028 specs: &[StatSpec],
1029 outs: &[Vec<f32>],
1030 ) -> io::Result<()> {
1031 for spec in specs {
1032 let data = &outs[spec.out_idx];
1033 let site = spec.site.replace(',', "_");
1034 for (idx, &val) in data.iter().enumerate() {
1035 writeln!(
1036 self.w,
1037 "{run_id},{step},{backend},{dist},{m},{k},{n},{},{site},{},{},{idx},{val}",
1038 spec.numel, spec.role, spec.stat,
1039 )?;
1040 }
1041 }
1042 Ok(())
1043 }
1044
1045 pub fn flush(&mut self) -> io::Result<()> {
1046 self.w.flush()
1047 }
1048}