1use std::collections::{BTreeMap, BTreeSet};
13
14use crate::tensor_recognize::qwen::{Activation, MoeAct, RowIndex};
15use crate::tensor_recognize::{ElementwiseOp, Epsilon, RowReduceKind};
16
17use super::facts::{self, Slots};
18use super::front::TensorOpSpec;
19use super::graph::{Access, Dst, Fused, Op, Part, Role, RowSel, Storage, Strided, Sym, TensorGraph};
20use super::shim::{Linking, Position, Shim, Support};
21use super::symbols::ClassId;
22
23#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct Fired {
26 pub rule: &'static str,
27 pub class: ClassId,
28 pub covers: Vec<usize>,
29}
30
31#[derive(Clone, Debug)]
33pub struct Optimized {
34 pub graph: TensorGraph,
35 pub fired: Vec<Fired>,
36 pub report: Report,
37}
38
39impl Optimized {
40 pub fn fired(&self, rule: &str) -> usize {
42 self.fired.iter().filter(|f| f.rule == rule).count()
43 }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
48pub enum Outcome {
49 Fired,
50 Refused(String),
52 Held,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct Decision {
59 pub rule: &'static str,
60 pub class: ClassId,
61 pub covers: Vec<usize>,
62 pub outcome: Outcome,
63}
64
65#[derive(Clone, Debug, Default, PartialEq, Eq)]
68pub struct Report {
69 pub decisions: Vec<Decision>,
70}
71
72impl Report {
73 pub fn lines(&self) -> Vec<String> {
75 self.decisions
76 .iter()
77 .map(|d| {
78 let outcome = match &d.outcome {
79 Outcome::Fired => "fired".to_string(),
80 Outcome::Refused(why) => format!("refused: {why}"),
81 Outcome::Held => "held".to_string(),
82 };
83 format!("{} {} {:?} {outcome}", d.rule, d.class, d.covers)
84 })
85 .collect()
86 }
87
88 pub fn digest(&self) -> String {
90 crate::crypto_accel::sha256(self.lines().join("\n").as_bytes()).iter().map(|b| format!("{b:02x}")).collect()
91 }
92
93 pub fn of(&self, rule: &str) -> Vec<&Decision> {
95 self.decisions.iter().filter(|d| d.rule == rule).collect()
96 }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101pub enum RuleKind {
102 Group,
104 Place,
106}
107
108pub struct ChipRule<'a> {
110 pub name: &'static str,
111 pub builds: &'static [ClassId],
112 pub apply: Box<dyn Fn(&mut Rewriter) + 'a>,
113}
114
115pub struct Rule {
117 pub name: &'static str,
118 pub kind: RuleKind,
119 pub builds: &'static [ClassId],
121 pub apply: fn(&mut Rewriter),
122}
123
124pub struct Rewriter<'a> {
127 pub s: Slots,
128 pub g: &'a TensorGraph,
129 pub shim: &'a dyn Shim,
130 pub fired: Vec<Fired>,
131 pub rule: &'static str,
132 pub building: ClassId,
134 pub report: Vec<Decision>,
135}
136
137impl Rewriter<'_> {
138 pub fn allows(&mut self, op: &Op) -> bool {
141 let answer = super::shim::allows(self.shim, op, self.g);
142 let native = answer.is_native();
143 if !native {
144 let why = match answer {
145 super::shim::Allow::Refuse(why) => why,
146 _ => format!("the chip runs `{}` as its parts", class(op).map_or(op.kind(), |c| c.id())),
147 };
148 let mut covers: Vec<usize> = leaves(op.clone()).iter().flat_map(|l| l.covers.clone()).collect();
149 covers.sort_unstable();
150 let decision = Decision { rule: self.rule, class: class(op).unwrap_or(self.building), covers, outcome: Outcome::Refused(why) };
151 if self.report.last() != Some(&decision) {
152 self.report.push(decision);
153 }
154 }
155 native
156 }
157
158 pub fn carried(&self, t: &str) -> bool {
160 self.g.io.as_ref().is_some_and(|io| io.names().contains(&t))
161 }
162
163 pub fn observed(&self, t: &str) -> bool {
164 self.g.tensors.get(t).is_some_and(|x| x.observed) || self.carried(t)
165 }
166
167 pub fn record(&mut self, rule: &'static str, class: ClassId, at: usize) {
168 let mut covers = self.s.get(at).covers.clone();
169 covers.sort_unstable();
170 self.report.push(Decision { rule, class, covers: covers.clone(), outcome: Outcome::Fired });
171 self.fired.push(Fired { rule, class, covers });
172 }
173
174 fn dry(&mut self, apply: &dyn Fn(&mut Rewriter), shim: &dyn Shim, then: &dyn Fn(ClassId) -> Outcome) {
178 let mut scratch = Rewriter { s: self.s.clone(), g: self.g, shim, fired: Vec::new(), rule: self.rule, building: self.building, report: Vec::new() };
179 apply(&mut scratch);
180 self.report.extend(scratch.report.into_iter().map(|d| match d.outcome {
181 Outcome::Fired => Decision { outcome: then(d.class), ..d },
182 _ => d,
183 }));
184 }
185}
186
187#[derive(Clone, Copy, Debug, PartialEq, Eq)]
191pub enum Weights {
192 Rows(crate::model::quant::GgmlType),
193 Values,
194}
195
196pub fn weights(g: &TensorGraph, w: &str) -> Option<Weights> {
198 match g.tensors.get(w).map(|t| (&t.role, &t.storage)) {
199 Some((Role::Constant, Storage::Rows { format, .. })) => Some(Weights::Rows(*format)),
200 Some((Role::Constant, Storage::Values)) => Some(Weights::Values),
201 _ => None,
202 }
203}
204
205pub fn class(op: &Op) -> Option<ClassId> {
207 match &op.sym {
208 Sym::Fused(f) => Some(f.class),
209 _ => None,
210 }
211}
212
213fn part_of(a: Access) -> Option<Part> {
214 match a {
215 Access::Whole => None,
216 Access::Strided(s) => Some(Part { pitch: s.pitch, offset: s.offset }),
217 }
218}
219
220#[derive(Clone, Debug)]
223pub struct NormForm {
224 pub x: String,
225 pub part: Option<Part>,
226 pub add: Option<String>,
227 pub pre: bool,
228 pub sum: bool,
229 pub w: Option<String>,
230 pub out: String,
231 pub own: bool,
232 pub rows: usize,
233 pub cols: usize,
234 pub eps: Epsilon,
235}
236
237pub fn norm(op: &Op) -> Option<NormForm> {
239 let rms = |spec: &TensorOpSpec| match spec {
240 TensorOpSpec::FusedRow(r) if r.reduce == RowReduceKind::RmsNorm && r.bias.is_none() => Some(r.clone()),
241 _ => None,
242 };
243 match &op.sym {
244 Sym::Prim(spec) => {
245 let r = rms(spec)?;
246 let mut k = 1;
247 let w = r.weight.map(|_| {
248 k += 1;
249 op.ins[k - 1].clone()
250 });
251 let add = r.residual.map(|_| op.ins[k].clone());
252 Some(NormForm {
253 x: op.ins[0].clone(),
254 part: part_of(op.access[0]),
255 add,
256 pre: false,
257 sum: false,
258 w,
259 out: op.out.clone(),
260 own: op.dst.own().is_some(),
261 rows: r.rows,
262 cols: r.cols,
263 eps: r.eps,
264 })
265 }
266 Sym::Fused(f) if f.class == ClassId::AddNorm || f.class == ClassId::NormThenAdd => {
267 let found = leaves(op.clone());
268 let last = found.last()?;
269 let r = rms(last.prim()?)?;
270 let w = r.weight.map(|_| last.ins[1].clone());
271 let sum = !op.keeps.is_empty();
272 let (x, add) = (op.ins[0].clone(), op.ins[1].clone());
275 Some(NormForm {
276 x,
277 part: part_of(op.access[0]),
278 add: Some(add),
279 pre: f.class == ClassId::NormThenAdd,
280 sum,
281 w,
282 out: op.out.clone(),
283 own: op.dst.own().is_some(),
284 rows: r.rows,
285 cols: r.cols,
286 eps: r.eps,
287 })
288 }
289 _ => None,
290 }
291}
292
293#[derive(Clone, Debug)]
295pub struct BinaryForm {
296 pub op: ElementwiseOp,
297 pub a: String,
298 pub b: String,
299 pub gate: Option<Activation>,
300 pub row: Option<usize>,
301 pub out: String,
302 pub n: usize,
303}
304
305pub fn binary(op: &Op) -> Option<BinaryForm> {
306 match &op.sym {
307 Sym::Prim(TensorOpSpec::Elementwise(e)) if op.dst.own().is_some() => {
308 Some(BinaryForm { op: e.op, a: op.ins[0].clone(), b: op.ins[1].clone(), gate: None, row: e.row, out: op.out.clone(), n: e.n })
309 }
310 Sym::Fused(f) if f.class == ClassId::GluActMul && op.dst.own().is_some() => {
311 let found = leaves(op.clone());
312 let act = activation(found.first()?)?;
313 let TensorOpSpec::Elementwise(e) = found.last()?.prim()? else { return None };
314 Some(BinaryForm { op: e.op, a: op.ins[0].clone(), b: op.ins[1].clone(), gate: Some(act), row: e.row, out: op.out.clone(), n: e.n })
315 }
316 _ => None,
317 }
318}
319
320pub fn activation(op: &Op) -> Option<Activation> {
322 match op.prim()? {
323 TensorOpSpec::Silu(_) => Some(Activation::Silu),
324 TensorOpSpec::Sigmoid(_) => Some(Activation::Sigmoid),
325 TensorOpSpec::Gelu(_) => Some(Activation::Gelu),
326 _ => None,
327 }
328}
329
330#[derive(Clone, Debug)]
333pub struct GemvForm {
334 pub x: String,
335 pub w: String,
336 pub weights: Weights,
337 pub bias: Option<String>,
338 pub dst: Dst,
339 pub k: usize,
340 pub n: usize,
341 pub m: usize,
342}
343
344pub fn gemv(op: &Op, g: &TensorGraph) -> Option<GemvForm> {
345 let lifted = |p: &Op| match p.prim()? {
346 TensorOpSpec::Matmul(m) => Some(m.clone()),
347 _ => None,
348 };
349 let (m, bias) = match &op.sym {
350 Sym::Prim(_) => (lifted(op)?, None),
351 Sym::Fused(f) if f.class == ClassId::GemmBias => (lifted(leaves(op.clone()).first()?)?, Some(op.ins[2].clone())),
352 _ => return None,
353 };
354 let w = op.ins[1].clone();
355 Some(GemvForm { x: op.ins[0].clone(), weights: weights(g, &w)?, w, bias, dst: op.dst.clone(), k: m.k, n: m.n, m: m.m })
356}
357
358#[derive(Clone, Debug)]
361pub struct RopeForm {
362 pub x: String,
363 pub part: Option<Part>,
364 pub norm: Option<(String, Epsilon)>,
365 pub cos: String,
366 pub sin: String,
367 pub pos: Option<String>,
368 pub dst: Dst,
369 pub heads: usize,
370 pub per_row: usize,
371 pub hd: usize,
372 pub half: usize,
373}
374
375pub fn rope(op: &Op) -> Option<RopeForm> {
376 let (spec, norm, at) = match &op.sym {
377 Sym::Prim(TensorOpSpec::Rope(r)) => (r.clone(), None, 0),
378 Sym::Fused(f) if f.class == ClassId::QkNormRope => {
379 let found = leaves(op.clone());
380 let TensorOpSpec::FusedRow(n) = found.first()?.prim()? else { return None };
381 let TensorOpSpec::Rope(r) = found.last()?.prim()? else { return None };
382 (r.clone(), Some((op.ins[1].clone(), n.eps)), 1)
383 }
384 _ => return None,
385 };
386 let pos = spec.from.as_ref().and_then(|(p, _)| match p {
387 RowIndex::Named(_) => Some(op.ins[at + 3].clone()),
388 RowIndex::Literal(_) => None,
389 });
390 Some(RopeForm {
391 x: op.ins[0].clone(),
392 part: part_of(op.access[0]),
393 norm,
394 cos: op.ins[at + 1].clone(),
395 sin: op.ins[at + 2].clone(),
396 pos,
397 dst: op.dst.clone(),
398 heads: spec.h,
399 per_row: spec.h / spec.rows.max(1),
400 hd: spec.hd,
401 half: spec.half,
402 })
403}
404
405#[derive(Clone, Debug)]
407pub struct SliceForm {
408 pub x: String,
409 pub y: String,
410 pub groups: usize,
411 pub every: usize,
412 pub from: usize,
413 pub width: usize,
414}
415
416pub fn slice(op: &Op) -> Option<SliceForm> {
417 if op.dst.own().is_none() {
418 return None;
419 }
420 match op.prim()? {
421 TensorOpSpec::HeadSlice(h) => Some(SliceForm { x: op.ins[0].clone(), y: op.out.clone(), groups: h.n / h.every, every: h.every, from: h.from, width: h.width }),
422 TensorOpSpec::RowGather(r) => match r.index {
423 RowIndex::Literal(row) => Some(SliceForm {
424 x: op.ins[0].clone(),
425 y: op.out.clone(),
426 groups: 1,
427 every: r.rows * r.cols,
428 from: (row as usize - 1) * r.cols,
429 width: r.cols,
430 }),
431 RowIndex::Named(_) => None,
432 },
433 _ => None,
434 }
435}
436
437pub fn unit_input(op: &Op) -> Option<(String, usize, usize)> {
441 if let Some(n) = norm(op) {
442 return (n.part.is_none() && n.add.as_deref() != Some(n.x.as_str())).then_some((n.x, n.rows, n.cols));
443 }
444 if let Some(r) = rope(op) {
445 return r.part.is_none().then_some((r.x, r.heads, r.hd));
446 }
447 if let Some(TensorOpSpec::HeadMatmul(m)) = op.prim() {
448 return (op.access[0] == Access::Whole).then(|| (op.ins[0].clone(), m.m * m.heads, m.a));
449 }
450 None
451}
452
453pub fn unit_output(op: &Op) -> Option<(usize, usize)> {
456 if let Some(n) = norm(op) {
457 return Some((n.rows, n.cols));
458 }
459 if let Some(r) = rope(op) {
460 return Some((r.heads, r.hd));
461 }
462 if let Some(TensorOpSpec::HeadMatmul(m)) = op.prim() {
463 return Some((m.m * m.heads, m.b));
464 }
465 None
466}
467
468pub fn written(op: &Op, g: &TensorGraph) -> Option<(usize, usize)> {
471 if let Some(p) = gemv(op, g) {
472 return Some((p.n * p.m, p.n));
473 }
474 if let Some(r) = rope(op) {
475 return Some((r.heads * r.hd, r.per_row * r.hd));
476 }
477 if let Some(n) = norm(op) {
478 return Some((n.rows * n.cols, n.cols));
479 }
480 match op.prim()? {
481 TensorOpSpec::Moe(m) => Some((m.d, m.d)),
482 TensorOpSpec::Join(j) => Some((j.rows * (j.wa + j.wb), j.wa + j.wb)),
483 TensorOpSpec::HeadMatmul(m) => Some((m.m * m.heads * m.b, m.heads * m.b)),
484 _ => match class(op) {
485 Some(ClassId::SharedExpertFusion) | Some(ClassId::MoeTokenSort) => {
486 let t = &g.tensors[&op.out];
487 Some((t.len(), t.cols))
488 }
489 _ => None,
490 },
491 }
492}
493
494pub fn set_dst(op: &mut Op, dst: Dst) {
499 if let Sym::Fused(f) = &mut op.sym {
500 if let Some(part) = f.parts.iter_mut().rev().find(|p| p.out == op.out) {
501 set_dst(part, dst.clone());
502 }
503 }
504 op.dst = dst;
505}
506
507pub fn read_in_place(op: &mut Op, from: &str, to: &str, access: Access) {
509 if let Sym::Fused(f) = &mut op.sym {
510 for part in &mut f.parts {
511 read_in_place(part, from, to, access);
512 }
513 }
514 for k in 0..op.ins.len() {
515 if op.ins[k] == from {
516 op.ins[k] = to.to_string();
517 op.access[k] = access;
518 }
519 }
520}
521
522pub fn leaves(op: Op) -> Vec<Op> {
526 match op.sym {
527 Sym::Fused(f) => f.parts.into_iter().flat_map(leaves).collect(),
528 _ => vec![op],
529 }
530}
531
532pub fn realizes(op: &Op, g: &TensorGraph) -> BTreeSet<ClassId> {
541 let mut out = BTreeSet::new();
542 if let Sym::Fused(f) = &op.sym {
543 out.insert(f.class);
544 for part in &f.parts {
545 out.extend(realizes(part, g));
546 }
547 match f.class {
548 ClassId::MoeTokenSort => {
549 out.insert(ClassId::MoeGatherInOperand);
550 }
551 ClassId::SharedExpertFusion => {
552 out.insert(ClassId::RouterSharedGateMerge);
553 }
554 _ => {}
555 }
556 let widths: Vec<usize> = f.parts.iter().filter_map(|p| gemv(p, g)).map(|p| p.n).collect();
557 let pair = f.class == ClassId::MergedQkv && widths.len() == 2 && widths[0] == widths[1];
558 if pair || f.class == ClassId::GemvGateUpGlu {
559 out.insert(ClassId::MergedGateUp);
560 }
561 }
562 for (k, a) in op.access.iter().enumerate() {
563 if let Access::Strided(_) = a {
564 let values = matches!(op.prim(), Some(TensorOpSpec::Attention(_))) && k == 2;
565 out.insert(if values { ClassId::StridedValues } else { ClassId::StridedRead });
566 }
567 }
568 if op.dst.row.is_some() {
569 out.insert(ClassId::RowWrite);
570 if let Some(ro) = rope(op) {
571 out.insert(if ro.norm.is_some() { ClassId::QkNormRopeKvAppend } else { ClassId::RopeKvAppend });
572 }
573 }
574 if op.dst.unit.is_some() {
575 out.insert(ClassId::JoinInPlace);
576 }
577 if let Some(TensorOpSpec::GatedDelta(_)) = op.prim() {
578 out.insert(ClassId::GatedNorm);
579 }
580 out
581}
582
583impl Rewriter<'_> {
584 pub fn fuse(&mut self, rule: &'static str, class: ClassId, at: usize, gone: &[usize], ins: Vec<(String, Access)>, out: String, dst: Dst) {
588 let mut slots: Vec<usize> = std::iter::once(at).chain(gone.iter().copied()).collect();
590 slots.sort_unstable();
591 let parts: Vec<Op> = slots.into_iter().map(|k| self.s.take(k).expect("a live slot")).collect();
592 let mut covers: Vec<usize> = parts.iter().flat_map(|o| o.covers.clone()).collect();
593 covers.sort_unstable();
594 let inside: BTreeSet<String> = parts.iter().flat_map(|p| leaves(p.clone())).map(|l| l.out).collect();
596 let keeps: Vec<String> = inside
597 .iter()
598 .filter(|t| **t != out && (self.observed(t) || !self.s.readers(t).is_empty()))
599 .cloned()
600 .collect();
601 let (names, access): (Vec<String>, Vec<Access>) = ins.into_iter().unzip();
602 let op = Op { sym: Sym::Fused(Fused { class, parts }), ins: names, access, out, dst, keeps, covers };
603 self.s.put(at, op);
604 self.record(rule, class, at);
605 }
606
607 pub fn absorb(&mut self, rule: &'static str, class: ClassId, at: usize, gone: &[usize]) {
610 let mut covers = self.s.get(at).covers.clone();
611 for &g in gone {
612 covers.extend(self.s.take(g).expect("a live slot").covers);
613 }
614 covers.sort_unstable();
615 self.s.get_mut(at).covers = covers;
616 self.record(rule, class, at);
617 }
618
619 pub fn admits(&mut self, op: &Op) -> bool {
621 self.allows(op)
622 }
623}
624
625pub fn candidate(class: ClassId, parts: Vec<Op>, ins: Vec<String>, out: &str, dst: Dst) -> Op {
628 let n = ins.len();
629 Op {
630 sym: Sym::Fused(Fused { class, parts }),
631 ins,
632 access: vec![Access::Whole; n],
633 out: out.to_string(),
634 dst,
635 keeps: Vec::new(),
636 covers: Vec::new(),
637 }
638}
639
640fn add_norm(r: &mut Rewriter) {
644 for i in 0..r.s.len() {
645 let Some(b) = r.s.slot(i).and_then(binary) else { continue };
646 if b.op != ElementwiseOp::Add || b.gate.is_some() || b.row.is_some() {
647 continue;
648 }
649 let readers = r.s.readers(&b.out);
650 let Some(&j) = readers.first() else { continue };
651 let Some(n) = norm(r.s.get(j)) else { continue };
652 if n.x != b.out || n.part.is_some() || n.add.is_some() || n.rows * n.cols != b.n || class(r.s.get(j)).is_some() {
653 continue;
654 }
655 let cand = candidate(ClassId::AddNorm, vec![r.s.get(i).clone(), r.s.get(j).clone()], vec![b.a.clone(), b.b.clone()], &n.out, r.s.get(j).dst.clone());
656 if !r.admits(&cand) {
657 continue;
658 }
659 let mut ins = vec![(b.a.clone(), r.s.get(i).access[0]), (b.b.clone(), r.s.get(i).access[1])];
660 ins.extend(n.w.iter().map(|w| (w.clone(), Access::Whole)));
661 let (out, dst) = (n.out.clone(), r.s.get(j).dst.clone());
662 r.fuse("add_norm", ClassId::AddNorm, j, &[i], ins, out, dst);
663 }
664}
665
666fn sandwich_norm(r: &mut Rewriter) {
671 sandwich_norms(r);
672 norm_then_adds(r);
673}
674
675fn plain_norm(r: &Rewriter, t: &str, j: usize, rows: usize, cols: usize, later: bool) -> Option<usize> {
678 let i = r.s.producer(t)?;
679 let n = norm(r.s.get(i))?;
680 let fits = n.out == t && n.part.is_none() && n.add.is_none() && !n.pre && !n.sum && n.own && class(r.s.get(i)).is_none() && n.rows == rows && n.cols == cols;
681 let readers = r.s.readers(t);
682 let read = readers.first() == Some(&j) && (readers.len() == 1 || (later && !r.carried(t)));
683 (fits && read).then_some(i)
684}
685
686fn norm_then_adds(r: &mut Rewriter) {
687 for j in 0..r.s.len() {
688 let Some(b) = r.s.slot(j).and_then(binary) else { continue };
689 if b.op != ElementwiseOp::Add || b.gate.is_some() || b.row.is_some() || r.s.get(j).dst.own().is_none() {
690 continue;
691 }
692 let shape = |t: &str| r.s.producer(t).and_then(|i| norm(r.s.get(i))).map(|n| (n.rows, n.cols)).filter(|(rows, cols)| rows * cols == b.n);
693 let found = [(b.b.clone(), b.a.clone()), (b.a.clone(), b.b.clone())]
694 .into_iter()
695 .find_map(|(normed, kept)| shape(&normed).and_then(|(rows, cols)| plain_norm(r, &normed, j, rows, cols, true)).map(|i| (i, kept)));
696 let Some((i, kept)) = found else { continue };
697 let inner = norm(r.s.get(i)).expect("a plain norm");
698 if kept == inner.out || !r.s.untouched(&inner.x, i, j, false, &[]) {
699 continue;
700 }
701 let cand = candidate(ClassId::NormThenAdd, vec![r.s.get(i).clone(), r.s.get(j).clone()], vec![kept.clone(), inner.x.clone()], &b.out, r.s.get(j).dst.clone());
702 if !r.admits(&cand) {
703 continue;
704 }
705 let kept_access = if kept == b.a { r.s.get(j).access[0] } else { r.s.get(j).access[1] };
706 let mut ins = vec![(kept, kept_access), (inner.x.clone(), r.s.get(i).access[0])];
707 ins.extend(inner.w.iter().map(|w| (w.clone(), Access::Whole)));
708 let (out, dst) = (b.out.clone(), r.s.get(j).dst.clone());
709 r.fuse("sandwich_norm", ClassId::NormThenAdd, j, &[i], ins, out, dst);
710 }
711}
712
713fn sandwich_norms(r: &mut Rewriter) {
714 for j in 0..r.s.len() {
715 let Some(outer) = r.s.slot(j).and_then(norm) else { continue };
716 let Some(add) = outer.add.clone() else { continue };
717 if outer.pre {
718 continue;
719 }
720 let plain = |r: &Rewriter, t: &str| plain_norm(r, t, j, outer.rows, outer.cols, false);
721 let (i, kept) = match (plain(r, &add), plain(r, &outer.x)) {
722 (Some(i), _) => (i, outer.x.clone()),
723 (None, Some(i)) if outer.part.is_none() => (i, add.clone()),
724 _ => continue,
725 };
726 let inner = norm(r.s.get(i)).expect("a plain norm");
727 if !r.s.untouched(&inner.x, i, j, false, &[]) {
728 continue;
729 }
730 let cand = candidate(ClassId::NormThenAdd, vec![r.s.get(i).clone(), r.s.get(j).clone()], vec![kept.clone(), inner.x.clone()], &outer.out, r.s.get(j).dst.clone());
731 if !r.admits(&cand) {
732 continue;
733 }
734 let kept_access = if kept == outer.x { r.s.get(j).access[0] } else { Access::Whole };
736 let mut ins = vec![(kept, kept_access), (inner.x.clone(), r.s.get(i).access[0])];
737 ins.extend(inner.w.iter().map(|w| (w.clone(), Access::Whole)));
738 ins.extend(outer.w.iter().map(|w| (w.clone(), Access::Whole)));
739 let (out, dst) = (outer.out.clone(), r.s.get(j).dst.clone());
740 r.fuse("sandwich_norm", ClassId::NormThenAdd, j, &[i], ins, out, dst);
741 }
742}
743
744fn gemv_bias(r: &mut Rewriter) {
746 for j in 0..r.s.len() {
747 let Some(b) = r.s.slot(j).and_then(binary) else { continue };
748 if b.op != ElementwiseOp::Add || b.gate.is_some() {
749 continue;
750 }
751 let Some(i) = r.s.producer(&b.a) else { continue };
752 let Some(p) = gemv(r.s.get(i), r.g) else { continue };
753 let constant = weights(r.g, &b.b) == Some(Weights::Values) && r.g.tensors.get(&b.b).map(|t| t.len()) == Some(b.row.unwrap_or(b.n));
754 let fits = p.bias.is_none() && p.dst.row.is_none() && b.row.unwrap_or(b.n) == p.n;
755 if !(constant && fits && r.s.readers(&b.a) == [j]) {
756 continue;
757 }
758 let cand = candidate(ClassId::GemmBias, vec![r.s.get(i).clone(), r.s.get(j).clone()], vec![p.x.clone(), p.w.clone(), b.b.clone()], &b.out, Dst::to(&b.out));
759 if !r.admits(&cand) {
760 continue;
761 }
762 let ins = vec![(p.x.clone(), r.s.get(i).access[0]), (p.w.clone(), Access::Whole), (b.b.clone(), Access::Whole)];
763 let out = b.out.clone();
764 r.fuse("gemv_bias", ClassId::GemmBias, i, &[j], ins, out.clone(), Dst::to(&out));
765 }
766}
767
768fn head_norm_rope(r: &mut Rewriter) {
770 for j in 0..r.s.len() {
771 let Some(ro) = r.s.slot(j).and_then(rope) else { continue };
772 if ro.norm.is_some() || class(r.s.get(j)).is_some() {
773 continue;
774 }
775 let Some(i) = r.s.producer(&ro.x) else { continue };
776 let Some(n) = norm(r.s.get(i)) else { continue };
777 let fits = n.part.is_none() && n.add.is_none() && n.w.is_some() && n.own && class(r.s.get(i)).is_none() && n.rows == ro.heads && n.cols == ro.hd;
778 if !(fits && r.s.readers(&ro.x) == [j]) {
779 continue;
780 }
781 let cand = candidate(ClassId::QkNormRope, vec![r.s.get(i).clone(), r.s.get(j).clone()], vec![n.x.clone()], &ro.dst.buf, ro.dst.clone());
782 if !r.admits(&cand) {
783 continue;
784 }
785 let rope_op = r.s.get(j).clone();
786 let mut ins = vec![(n.x.clone(), r.s.get(i).access[0]), (n.w.clone().expect("a weight"), Access::Whole)];
787 ins.extend(rope_op.ins[1..].iter().map(|t| (t.clone(), Access::Whole)));
788 r.fuse("head_norm_rope", ClassId::QkNormRope, j, &[i], ins, rope_op.out.clone(), rope_op.dst.clone());
789 }
790}
791
792fn slice_read(r: &mut Rewriter) {
795 for i in 0..r.s.len() {
796 let Some(sl) = r.s.slot(i).and_then(slice) else { continue };
797 let readers = r.s.readers(&sl.y);
798 let [j] = readers[..] else { continue };
799 let untouched = r.s.untouched(&sl.x, i, j, false, &[]);
800 if !(unit_input(r.s.get(j)) == Some((sl.y.clone(), sl.groups, sl.width)) && untouched) {
801 continue;
802 }
803 let mut cand = r.s.get(j).clone();
804 read_in_place(&mut cand, &sl.y, &sl.x, Access::Strided(Strided { pitch: sl.every, offset: sl.from, width: sl.width }));
805 if !r.admits(&cand) {
806 continue;
807 }
808 r.s.put(j, cand);
809 r.absorb("slice_read", ClassId::StridedRead, j, &[i]);
810 }
811}
812
813fn row_write(r: &mut Rewriter) {
816 for j in 0..r.s.len() {
817 let Some(Sym::Prim(TensorOpSpec::SetRow(sr))) = r.s.slot(j).map(|o| &o.sym) else { continue };
818 let set = r.s.get(j).clone();
819 let (table, src) = (set.ins[0].clone(), set.ins[1].clone());
820 let index = match sr.index {
821 RowIndex::Named(_) => RowSel::Param(set.ins[2].clone()),
822 RowIndex::Literal(n) => RowSel::Fixed(n as usize),
823 };
824 let (cols, count) = (sr.cols, sr.count);
825 let Some(i) = r.s.producer(&src) else { continue };
826 let writes_rows = written(r.s.get(i), r.g) == Some((count * cols, cols));
827 let untouched = r.s.untouched(&table, i, j, true, &[]);
828 if !(writes_rows && untouched && r.s.readers(&src) == [j]) {
829 continue;
830 }
831 let mut cand = r.s.get(i).clone();
832 set_dst(&mut cand, Dst { buf: table.clone(), row: Some((index, cols)), unit: None });
833 if !r.admits(&cand) {
834 continue;
835 }
836 r.s.put(i, cand);
837 r.absorb("row_write", ClassId::RowWrite, i, &[j]);
838 }
839}
840
841fn join_in_place(r: &mut Rewriter) {
844 for j in 0..r.s.len() {
845 let Some(TensorOpSpec::Join(jn)) = r.s.slot(j).and_then(|o| o.prim()) else { continue };
846 let join_op = r.s.get(j).clone();
847 let (a, b, y) = (join_op.ins[0].clone(), join_op.ins[1].clone(), join_op.dst.clone());
848 let (ha, hb) = (jn.wa / jn.heads, jn.wb / jn.heads);
849 let (Some(pa), Some(pb)) = (r.s.producer(&a), r.s.producer(&b)) else { continue };
850 let units = jn.rows * jn.heads;
851 let len = |t: &str| r.g.tensors.get(t).map(|t| t.len());
852 let fits = pa != pb
853 && y.unit.is_none()
854 && len(&a) == Some(units * ha)
855 && len(&b) == Some(units * hb)
856 && r.s.readers(&a) == [j]
857 && r.s.readers(&b) == [j];
858 let row = match &y.row {
859 Some((RowSel::Param(p), _)) => Some(p.clone()),
860 _ => None,
861 };
862 let lo = pa.min(pb);
863 let untouched = r.s.live().filter(|(k, _)| *k >= lo && *k < j).all(|(k, o)| {
864 let other = k != pa && k != pb;
865 let (rd, wr) = (super::facts::reads(o), super::facts::writes(o));
866 !rd.contains(&y.buf.as_str()) && (!other || !wr.contains(&y.buf.as_str())) && row.as_deref().is_none_or(|p| !wr.contains(&p))
867 });
868 if !(fits && untouched) {
869 continue;
870 }
871 let (mut ca, mut cb) = (r.s.get(pa).clone(), r.s.get(pb).clone());
872 set_dst(&mut ca, Dst { unit: Some(Strided { pitch: ha + hb, offset: 0, width: ha }), ..y.clone() });
873 set_dst(&mut cb, Dst { unit: Some(Strided { pitch: ha + hb, offset: ha, width: hb }), ..y.clone() });
874 if !(r.admits(&ca) && r.admits(&cb)) {
875 continue;
876 }
877 r.s.put(pa, ca);
878 r.s.put(pb, cb);
879 r.absorb("join_in_place", ClassId::JoinInPlace, pa.max(pb), &[j]);
880 }
881}
882
883#[derive(Clone, Debug)]
886pub struct ConvForm {
887 pub x: String,
888 pub w: String,
889 pub ring: String,
890 pub y: String,
891 pub chan: usize,
892 pub taps: usize,
893 pub rows: usize,
894 pub silu: bool,
895 pub l2: Option<(usize, Epsilon)>,
896}
897
898pub fn conv(op: &Op) -> Option<ConvForm> {
899 let lifted = |p: &Op| match p.prim()? {
900 TensorOpSpec::CausalConv(c) => Some(c.clone()),
901 _ => None,
902 };
903 let (c, silu, l2) = match &op.sym {
904 Sym::Prim(_) => (lifted(op)?, false, None),
905 Sym::Fused(f) if f.class == ClassId::ConvBiasSilu => (lifted(leaves(op.clone()).first()?)?, true, None),
906 Sym::Fused(f) if f.class == ClassId::ConvSiluL2norm => {
907 let found = leaves(op.clone());
908 let TensorOpSpec::L2Norm(l) = found.last()?.prim()? else { return None };
909 (lifted(found.first()?)?, true, Some((l.cols, l.eps)))
910 }
911 _ => return None,
912 };
913 Some(ConvForm { x: op.ins[0].clone(), w: op.ins[1].clone(), ring: op.ins[2].clone(), y: op.out.clone(), chan: c.chan, taps: c.taps, rows: c.rows, silu, l2 })
914}
915
916fn conv_silu(r: &mut Rewriter) {
918 for i in 0..r.s.len() {
919 let Some(c) = r.s.slot(i).and_then(conv) else { continue };
920 if c.silu {
921 continue;
922 }
923 let readers = r.s.readers(&c.y);
924 let [j] = readers[..] else { continue };
925 if activation(r.s.get(j)) != Some(Activation::Silu) {
926 continue;
927 }
928 let out = r.s.get(j).out.clone();
929 let cand = candidate(ClassId::ConvBiasSilu, vec![r.s.get(i).clone(), r.s.get(j).clone()], vec![c.x.clone(), c.w.clone(), c.ring.clone()], &out, Dst::to(&out));
930 if !r.admits(&cand) {
931 continue;
932 }
933 let ins = vec![(c.x, r.s.get(i).access[0]), (c.w, Access::Whole), (c.ring, Access::Whole)];
934 r.fuse("conv_silu", ClassId::ConvBiasSilu, i, &[j], ins, out.clone(), Dst::to(&out));
935 }
936}
937
938fn conv_silu_l2(r: &mut Rewriter) {
940 for i in 0..r.s.len() {
941 let Some(c) = r.s.slot(i).and_then(conv) else { continue };
942 if !c.silu || c.l2.is_some() {
943 continue;
944 }
945 let readers = r.s.readers(&c.y);
946 let [k] = readers[..] else { continue };
947 let Some(TensorOpSpec::L2Norm(l)) = r.s.get(k).prim() else { continue };
948 if l.rows * l.cols != c.chan * c.rows {
949 continue;
950 }
951 let out = r.s.get(k).out.clone();
952 let cand = candidate(ClassId::ConvSiluL2norm, vec![r.s.get(i).clone(), r.s.get(k).clone()], vec![c.x.clone(), c.w.clone(), c.ring.clone()], &out, Dst::to(&out));
953 if !r.admits(&cand) {
954 continue;
955 }
956 let ins = vec![(c.x, r.s.get(i).access[0]), (c.w, Access::Whole), (c.ring, Access::Whole)];
957 r.fuse("conv_silu_l2", ClassId::ConvSiluL2norm, i, &[k], ins, out.clone(), Dst::to(&out));
958 }
959}
960
961fn conv_parts(r: &mut Rewriter) {
964 let convs: Vec<usize> = r.s.live().filter(|(_, o)| conv(o).is_some()).map(|(i, _)| i).collect();
965 for (at, &i) in convs.iter().enumerate() {
966 if r.s.slot(i).and_then(conv).is_none() {
967 continue;
968 }
969 let mut group = vec![i];
970 for &j in &convs[at + 1..] {
971 if r.s.slot(j).and_then(conv).is_none() || r.s.crosses(i, j, &group) {
972 continue;
973 }
974 let members: Vec<Op> = group.iter().chain([&j]).map(|&k| r.s.get(k).clone()).collect();
975 let ins: Vec<String> = members.iter().flat_map(|m| m.ins.iter().cloned()).collect();
976 let out = members[0].out.clone();
977 if r.admits(&candidate(ClassId::ConvParts, members, ins, &out, Dst::to(&out))) {
978 group.push(j);
979 }
980 }
981 if group.len() < 2 {
982 continue;
983 }
984 let members: Vec<Op> = group.iter().map(|&k| r.s.get(k).clone()).collect();
985 let ins: Vec<(String, Access)> = members.iter().flat_map(|m| m.ins.iter().cloned().zip(m.access.iter().copied())).collect();
986 let out = members[0].out.clone();
987 r.fuse("conv_parts", ClassId::ConvParts, i, &group[1..], ins, out.clone(), Dst::to(&out));
988 }
989}
990
991fn gates(r: &mut Rewriter) {
993 for j in 0..r.s.len() {
994 let Some(TensorOpSpec::GatedDelta(_)) = r.s.slot(j).and_then(|o| o.prim()) else { continue };
995 let (b, a) = (r.s.get(j).ins[4].clone(), r.s.get(j).ins[5].clone());
996 let (Some(pb), Some(pa)) = (r.s.producer(&b), r.s.producer(&a)) else { continue };
997 let (Some(TensorOpSpec::Sigmoid(sg)), Some(TensorOpSpec::Decay(dc))) = (r.s.get(pb).prim().cloned(), r.s.get(pa).prim().cloned()) else { continue };
998 if sg.n != dc.n || r.s.readers(&b) != [j] || r.s.readers(&a) != [j] {
999 continue;
1000 }
1001 let (keep, gone) = (pb.max(pa), pb.min(pa));
1002 let (sop, dop) = (r.s.get(pb).clone(), r.s.get(pa).clone());
1003 let ins: Vec<(String, Access)> = [&sop, &dop].iter().flat_map(|o| o.ins.iter().cloned().zip(o.access.iter().copied())).collect();
1004 let cand = candidate(ClassId::GdnGating, vec![sop, dop], ins.iter().map(|(t, _)| t.clone()).collect(), &b, Dst::to(&b));
1005 if !r.admits(&cand) {
1006 continue;
1007 }
1008 r.fuse("gates", ClassId::GdnGating, keep, &[gone], ins, b.clone(), Dst::to(&b));
1009 }
1010}
1011
1012fn gate_projections(r: &mut Rewriter) {
1015 for g in 0..r.s.len() {
1016 let Some(op) = r.s.slot(g) else { continue };
1017 if class(op) != Some(ClassId::GdnGating) {
1018 continue;
1019 }
1020 let Sym::Fused(f) = &op.sym else { continue };
1021 let (Some(TensorOpSpec::Sigmoid(_)), Some(TensorOpSpec::Decay(dc))) = (f.parts[0].prim(), f.parts[1].prim()) else { continue };
1022 let (br, ar) = (f.parts[0].ins[0].clone(), f.parts[1].ins[0].clone());
1023 let heads = dc.n / dc.rows.max(1);
1024 if dc.n != heads || r.s.readers(&br) != [g] || r.s.readers(&ar) != [g] {
1025 continue;
1026 }
1027 let (Some(ib), Some(ia)) = (r.s.producer(&br), r.s.producer(&ar)) else { continue };
1028 let projection = |i: usize| -> Option<(String, String, usize)> {
1029 let p = gemv(r.s.get(i), r.g)?;
1030 (p.bias.is_none() && p.dst.own().is_some() && p.n == heads && p.m == 1).then(|| (p.x, p.w, p.k))
1031 };
1032 let (Some((xb, _, kb)), Some((xa, _, ka))) = (projection(ib), projection(ia)) else { continue };
1033 if xb != xa || kb != ka || !r.s.untouched(&xb, ib.min(ia), g, false, &[]) {
1034 continue;
1035 }
1036 let members = vec![r.s.get(ib).clone(), r.s.get(ia).clone(), r.s.get(g).clone()];
1037 let mut ins: Vec<(String, Access)> = vec![(xb.clone(), r.s.get(ib).access[0]), (r.s.get(ib).ins[1].clone(), Access::Whole), (r.s.get(ia).ins[1].clone(), Access::Whole)];
1038 ins.extend(f.parts[1].ins[1..].iter().map(|t| (t.clone(), Access::Whole)));
1039 let out = r.s.get(g).out.clone();
1040 let cand = candidate(ClassId::GdnGateProjections, members, ins.iter().map(|(t, _)| t.clone()).collect(), &out, Dst::to(&out));
1041 if !r.admits(&cand) {
1042 continue;
1043 }
1044 r.fuse("gate_projections", ClassId::GdnGateProjections, g, &[ib, ia], ins, out.clone(), Dst::to(&out));
1045 }
1046}
1047
1048fn act_gate(r: &mut Rewriter) {
1051 for j in 0..r.s.len() {
1052 let Some(bf) = r.s.slot(j).and_then(binary) else { continue };
1053 if bf.op != ElementwiseOp::Mul || bf.gate.is_some() || bf.row.is_some() || class(r.s.get(j)).is_some() {
1054 continue;
1055 }
1056 let gated = [(bf.b.clone(), bf.a.clone(), 0usize), (bf.a.clone(), bf.b.clone(), 1usize)].into_iter().find_map(|(act_out, other, other_k)| {
1057 let i = r.s.producer(&act_out)?;
1058 activation(r.s.get(i))?;
1059 (r.s.readers(&act_out) == [j] && class(r.s.get(i)).is_none()).then_some((i, other, other_k))
1060 });
1061 let Some((i, other, other_k)) = gated else { continue };
1062 let x = r.s.get(i).ins[0].clone();
1063 let ins = vec![(other.clone(), r.s.get(j).access[other_k]), (x.clone(), r.s.get(i).access[0])];
1064 let cand = candidate(ClassId::GluActMul, vec![r.s.get(i).clone(), r.s.get(j).clone()], vec![other, x], &bf.out, Dst::to(&bf.out));
1065 if !r.admits(&cand) {
1066 continue;
1067 }
1068 r.fuse("act_gate", ClassId::GluActMul, j, &[i], ins, bf.out.clone(), Dst::to(&bf.out));
1069 }
1070}
1071
1072fn attn_output_gate(r: &mut Rewriter) {
1078 for j in 0..r.s.len() {
1079 let Some(op) = r.s.slot(j) else { continue };
1080 if class(op) != Some(ClassId::GluActMul) || op.access.iter().any(|a| *a != Access::Whole) {
1081 continue;
1082 }
1083 let Some(bf) = binary(op).filter(|b| b.op == ElementwiseOp::Mul && b.row.is_none()) else { continue };
1084 match r.s.producer(&bf.a) {
1085 Some(i) if attention(r.s.get(i)) => {
1086 if r.s.readers(&bf.a) == [j] && !r.observed(&bf.a) && !r.s.sinks_past(i, j, &[]) {
1087 pair(r, "attn_output_gate", ClassId::AttnOutputGate, i, j, j);
1088 }
1089 }
1090 _ => {
1091 let gate_len = r.g.tensors.get(&bf.b).map(|t| t.len());
1092 if let Some(mut members) = attended_rows(r, &bf.a, j).filter(|_| gate_len == Some(bf.n)) {
1093 members.push(j);
1094 group_at_last(r, "attn_output_gate", ClassId::AttnOutputGate, members);
1095 }
1096 }
1097 }
1098 }
1099}
1100
1101pub fn gates_rows(parts: &[Op]) -> bool {
1104 parts.iter().any(|p| matches!(p.prim(), Some(TensorOpSpec::SetRow(_))))
1105}
1106
1107fn attention(op: &Op) -> bool {
1109 matches!(op.prim(), Some(TensorOpSpec::Attention(_))) && op.dst.own().is_some()
1110}
1111
1112fn attended_rows(r: &Rewriter, table: &str, j: usize) -> Option<Vec<usize>> {
1117 let t = r.g.tensors.get(table)?;
1118 if t.carried || r.observed(table) {
1119 return None;
1120 }
1121 let sets: Vec<usize> = r.s.live().filter(|(_, op)| facts::writes(op).contains(&table)).map(|(k, _)| k).collect();
1122 let (mut rows, mut members) = (BTreeSet::new(), Vec::new());
1123 for &k in &sets {
1124 let set = r.s.get(k);
1125 let Some(TensorOpSpec::SetRow(sr)) = set.prim() else { return None };
1126 let RowIndex::Literal(row) = sr.index else { return None };
1127 if (sr.rows, sr.cols, sr.count) != (t.rows, t.cols, 1) || set.ins[0] != table || set.dst.own().is_none() {
1128 return None;
1129 }
1130 let src = &set.ins[1];
1131 let i = r.s.producer(src).filter(|&i| attention(r.s.get(i)) && r.s.readers(src) == [k] && !r.observed(src))?;
1132 if !rows.insert(row) {
1133 return None;
1134 }
1135 members.extend([i, k]);
1136 }
1137 let whole = rows.len() == t.rows && rows.iter().copied().eq(1..=t.rows as i64);
1138 let touched = r.s.readers(table).iter().all(|k| *k == j || sets.contains(k));
1139 (whole && touched).then_some(members)
1140}
1141
1142fn glu_pair(r: &mut Rewriter) {
1145 for j in 0..r.s.len() {
1146 let Some(op) = r.s.slot(j) else { continue };
1147 let (g, u, len) = match op.prim() {
1148 Some(TensorOpSpec::SwiGlu(sw)) if op.dst.own().is_some() => (op.ins[0].clone(), op.ins[1].clone(), sw.total),
1149 _ => match binary(op) {
1150 Some(b) if b.op == ElementwiseOp::Mul && b.gate.is_some() && b.row.is_none() => (b.b, b.a, b.n),
1151 _ => continue,
1152 },
1153 };
1154 let out = op.out.clone();
1155 let (Some(pg), Some(pu)) = (r.s.producer(&g), r.s.producer(&u)) else { continue };
1156 let (Some(fg), Some(fu)) = (gemv(r.s.get(pg), r.g), gemv(r.s.get(pu), r.g)) else { continue };
1157 let fits = pg != pu
1158 && fg.x == fu.x
1159 && (fg.k, fg.n, fg.m) == (fu.k, fu.n, fu.m)
1160 && fg.m * fg.n == len
1161 && fg.dst.own().is_some()
1162 && fu.dst.own().is_some()
1163 && r.s.untouched(&fg.x, pg.min(pu), j, false, &[])
1164 && r.s.readers(&g) == [j]
1165 && r.s.readers(&u) == [j];
1166 if !fits {
1167 continue;
1168 }
1169 let members = vec![r.s.get(pg).clone(), r.s.get(pu).clone(), r.s.get(j).clone()];
1170 let mut ins: Vec<(String, Access)> = vec![(fg.x.clone(), r.s.get(pg).access[0]), (fg.w.clone(), Access::Whole), (fu.w.clone(), Access::Whole)];
1171 ins.extend(fg.bias.iter().chain(fu.bias.iter()).map(|b| (b.clone(), Access::Whole)));
1172 let cand = candidate(ClassId::GemvGateUpGlu, members, ins.iter().map(|(t, _)| t.clone()).collect(), &out, Dst::to(&out));
1173 if !r.admits(&cand) {
1174 continue;
1175 }
1176 r.fuse("glu_pair", ClassId::GemvGateUpGlu, j, &[pg, pu], ins, out.clone(), Dst::to(&out));
1177 }
1178}
1179
1180#[derive(Clone, Debug)]
1183pub struct MoeForm {
1184 pub x: String,
1185 pub dst: Dst,
1186 pub d: usize,
1187 pub k: usize,
1188 pub act: MoeAct,
1189 pub rows: usize,
1190 pub shared: bool,
1191}
1192
1193pub fn moe(op: &Op) -> Option<MoeForm> {
1194 let (m, shared, rows) = match &op.sym {
1195 Sym::Prim(TensorOpSpec::Moe(m)) => (m.clone(), false, 1),
1196 Sym::Fused(f) if f.class == ClassId::SharedExpertFusion || f.class == ClassId::MoeTokenSort => {
1197 let found = leaves(op.clone());
1198 let m = found.iter().find_map(|p| match p.prim()? {
1199 TensorOpSpec::Moe(m) => Some(m.clone()),
1200 _ => None,
1201 })?;
1202 match f.class {
1203 ClassId::SharedExpertFusion => (m, true, 1),
1204 _ => (m, false, found.iter().filter(|p| matches!(p.prim(), Some(TensorOpSpec::Moe(_)))).count()),
1205 }
1206 }
1207 _ => return None,
1208 };
1209 Some(MoeForm { x: op.ins[0].clone(), dst: op.dst.clone(), d: m.d, k: m.k, act: m.act, rows, shared })
1210}
1211
1212fn shared_expert(r: &mut Rewriter) {
1215 for j in 0..r.s.len() {
1216 let Some(bf) = r.s.slot(j).and_then(binary) else { continue };
1217 if bf.op != ElementwiseOp::Add || bf.gate.is_some() || bf.row.is_some() || class(r.s.get(j)).is_some() {
1218 continue;
1219 }
1220 let parts = [(bf.a.clone(), bf.b.clone()), (bf.b.clone(), bf.a.clone())].into_iter().find_map(|(mixed, shared)| {
1221 let (pm, pd) = (r.s.producer(&mixed)?, r.s.producer(&shared)?);
1222 moe(r.s.get(pm)).filter(|m| !m.shared).map(|_| (mixed, shared, pm, pd))
1223 });
1224 let Some((mixed, shared, pm, pd)) = parts else { continue };
1225 let mf = moe(r.s.get(pm)).expect("a mixture");
1226 let Some(down) = gemv(r.s.get(pd), r.g) else { continue };
1227 let h = down.x.clone();
1228 let Some(pg) = r.s.producer(&h) else { continue };
1229 if class(r.s.get(pg)) != Some(ClassId::GemvGateUpGlu) {
1230 continue;
1231 }
1232 let glu = r.s.get(pg).clone();
1233 let Sym::Fused(gf) = &glu.sym else { continue };
1234 let first = gf.parts.iter().find_map(|p| gemv(p, r.g));
1235 let Some(gm) = first else { continue };
1236 let fits = glu.ins[0] == mf.x
1237 && (gm.k, gm.n, down.n, down.m * down.n) == (mf.d, down.k, mf.d, bf.n)
1238 && gm.m == down.m
1239 && mf.dst.own().is_some()
1240 && down.dst.own().is_some()
1241 && r.s.readers(&mixed) == [j]
1242 && r.s.readers(&shared) == [j]
1243 && r.s.readers(&h) == [pd]
1244 && r.s.untouched(&mf.x, pm.min(pg), j, false, &[]);
1245 if !fits {
1246 continue;
1247 }
1248 let members = vec![r.s.get(pm).clone(), r.s.get(pg).clone(), r.s.get(pd).clone(), r.s.get(j).clone()];
1249 let mut ins: Vec<(String, Access)> = r.s.get(pm).ins.iter().cloned().zip(r.s.get(pm).access.iter().copied()).collect();
1250 ins.extend(glu.ins[1..].iter().map(|t| (t.clone(), Access::Whole)));
1251 ins.push((down.w.clone(), Access::Whole));
1252 let cand = candidate(ClassId::SharedExpertFusion, members, ins.iter().map(|(t, _)| t.clone()).collect(), &bf.out, Dst::to(&bf.out));
1253 if !r.admits(&cand) {
1254 continue;
1255 }
1256 r.fuse("shared_expert", ClassId::SharedExpertFusion, j, &[pm, pg, pd], ins, bf.out.clone(), Dst::to(&bf.out));
1257 }
1258}
1259
1260fn moe_rows(r: &mut Rewriter) {
1263 let mut groups: BTreeMap<(String, String), Vec<(usize, usize, usize)>> = BTreeMap::new();
1264 for (i, op) in r.s.live() {
1265 let Some(mf) = moe(op) else { continue };
1266 if mf.rows != 1 || mf.shared {
1267 continue;
1268 }
1269 let Some((RowSel::Fixed(at), cols)) = &mf.dst.row else { continue };
1270 if mf.dst.unit.is_some() {
1271 continue;
1272 }
1273 let Some(v) = r.s.producer(&mf.x) else { continue };
1274 let Sym::View { offset } = r.s.get(v).sym else { continue };
1275 if *cols == mf.d && offset % mf.d == 0 && *at == offset / mf.d + 1 {
1276 groups.entry((r.s.get(v).ins[0].clone(), mf.dst.buf.clone())).or_default().push((i, v, offset / mf.d));
1277 }
1278 }
1279 for ((src, table), mut members) in groups {
1280 members.sort_by_key(|&(_, _, row)| row);
1281 let rows = members.len();
1282 let params = |i: usize| {
1284 let op = r.s.get(i);
1285 (op.prim().map(|p| format!("{p:?}")).unwrap_or_default(), op.ins[1..].to_vec())
1286 };
1287 let first = params(members[0].0);
1288 let alike = members.iter().enumerate().all(|(n, &(i, _, row))| row == n && {
1289 let (p, ins) = params(i);
1290 let (fp, fins) = &first;
1291 ins == *fins && strip_symbols(&p) == strip_symbols(fp)
1292 });
1293 let lo = members.iter().map(|m| m.0.min(m.1)).min().unwrap_or(0);
1294 let hi = members.iter().map(|m| m.0).max().unwrap_or(0);
1295 let ours: Vec<usize> = members.iter().flat_map(|&(i, v, _)| [i, v]).collect();
1296 let untouched = r.s.untouched(&table, lo, hi, true, &ours) && r.s.untouched(&src, lo, hi, false, &ours);
1297 let only_theirs = members.iter().all(|&(i, v, _)| r.s.readers(&r.s.get(v).out) == [i]);
1298 if rows < 2 || !alike || !untouched || !only_theirs {
1299 continue;
1300 }
1301 let gone: Vec<usize> = ours.iter().copied().filter(|&g| g != hi).collect();
1302 let mut ins: Vec<(String, Access)> = vec![(src.clone(), Access::Whole)];
1303 ins.extend(r.s.get(hi).ins[1..].iter().map(|t| (t.clone(), Access::Whole)));
1304 let members_ops: Vec<Op> = ours.iter().map(|&k| r.s.get(k).clone()).collect();
1305 let cand = candidate(ClassId::MoeTokenSort, members_ops, ins.iter().map(|(t, _)| t.clone()).collect(), &table, Dst::to(&table));
1306 if !r.admits(&cand) {
1307 continue;
1308 }
1309 r.fuse("moe_rows", ClassId::MoeTokenSort, hi, &gone, ins, table.clone(), Dst::to(&table));
1310 }
1311}
1312
1313fn strip_symbols(text: &str) -> String {
1315 let mut out = String::new();
1316 let mut rest = text;
1317 while let Some(i) = rest.find("Symbol(") {
1318 out.push_str(&rest[..i]);
1319 rest = &rest[i..];
1320 let end = rest.find(')').map_or(rest.len(), |e| e + 1);
1321 rest = &rest[end..];
1322 }
1323 out.push_str(rest);
1324 out
1325}
1326
1327fn gather_scale(r: &mut Rewriter) {
1329 for j in 0..r.s.len() {
1330 let Some(TensorOpSpec::Scale(_)) = r.s.slot(j).and_then(|o| o.prim()) else { continue };
1331 let x = r.s.get(j).ins[0].clone();
1332 let Some(i) = r.s.producer(&x) else { continue };
1333 let gather = match r.s.get(i).prim() {
1334 Some(TensorOpSpec::RowGather(rg)) => matches!(rg.index, RowIndex::Named(_)) && weights(r.g, &r.s.get(i).ins[0]).is_some(),
1335 Some(TensorOpSpec::RowsGather(_)) => weights(r.g, &r.s.get(i).ins[0]).is_some(),
1336 _ => false,
1337 };
1338 if !gather || r.s.readers(&x) != [j] {
1339 continue;
1340 }
1341 let out = r.s.get(j).out.clone();
1342 let ins: Vec<(String, Access)> = r.s.get(i).ins.iter().cloned().zip(r.s.get(i).access.iter().copied()).collect();
1343 let cand = candidate(ClassId::EmbedScale, vec![r.s.get(i).clone(), r.s.get(j).clone()], ins.iter().map(|(t, _)| t.clone()).collect(), &out, Dst::to(&out));
1344 if !r.admits(&cand) {
1345 continue;
1346 }
1347 r.fuse("gather_scale", ClassId::EmbedScale, i, &[j], ins, out.clone(), Dst::to(&out));
1348 }
1349}
1350
1351fn strided_values(r: &mut Rewriter) {
1353 for j in 0..r.s.len() {
1354 let Some(TensorOpSpec::Attention(a)) = r.s.slot(j).and_then(|o| o.prim()).cloned() else { continue };
1355 let vc = r.s.get(j).ins[2].clone();
1356 let Some(i) = r.s.producer(&vc) else { continue };
1357 let Some(sl) = slice(r.s.get(i)) else { continue };
1358 let group = a.h / a.kv.max(1);
1359 if sl.width != a.h / group * a.vd || r.s.readers(&vc) != [j] || !r.s.untouched(&sl.x, i, j, false, &[]) {
1360 continue;
1361 }
1362 let mut cand = r.s.get(j).clone();
1363 read_in_place(&mut cand, &vc, &sl.x, Access::Strided(Strided { pitch: sl.every, offset: sl.from, width: sl.width }));
1364 if !r.admits(&cand) {
1365 continue;
1366 }
1367 r.s.put(j, cand);
1368 r.absorb("strided_values", ClassId::StridedValues, j, &[i]);
1369 }
1370}
1371
1372fn rope_parts(r: &mut Rewriter) {
1376 for i in 0..r.s.len() {
1377 let Some(first) = r.s.slot(i).and_then(rope) else { continue };
1378 let mut group = vec![i];
1379 for j in i + 1..r.s.len() {
1380 let Some(ro) = r.s.slot(j).and_then(rope) else { continue };
1381 let fits = ro.cos == first.cos && ro.sin == first.sin && ro.pos == first.pos && ro.hd == first.hd && ro.half == first.half;
1382 if !fits || r.s.crosses(i, j, &[]) {
1383 continue;
1384 }
1385 let members: Vec<Op> = group.iter().chain([&j]).map(|&k| r.s.get(k).clone()).collect();
1386 let ins: Vec<String> = members.iter().flat_map(|m| m.ins.iter().cloned()).collect();
1387 let (out, dst) = (members[0].out.clone(), members[0].dst.clone());
1388 if r.admits(&candidate(ClassId::RopeQkOneLaunch, members, ins, &out, dst)) {
1389 group.push(j);
1390 }
1391 }
1392 if group.len() < 2 {
1393 continue;
1394 }
1395 let members: Vec<Op> = group.iter().map(|&k| r.s.get(k).clone()).collect();
1396 let ins: Vec<(String, Access)> = members.iter().flat_map(|m| m.ins.iter().cloned().zip(m.access.iter().copied())).collect();
1397 let (out, dst) = (members[0].out.clone(), members[0].dst.clone());
1398 let cand = candidate(ClassId::RopeQkOneLaunch, members, ins.iter().map(|(t, _)| t.clone()).collect(), &out, dst.clone());
1399 if !r.admits(&cand) {
1400 continue;
1401 }
1402 r.fuse("rope_parts", ClassId::RopeQkOneLaunch, i, &group[1..], ins, out, dst);
1403 }
1404}
1405
1406fn argmax_advance(r: &mut Rewriter) {
1408 let Some(io) = r.g.io.clone() else { return };
1409 let nexts: Vec<String> = std::iter::once(io.next.clone()).chain(io.more.iter().map(|q| q.next.clone())).collect();
1410 for i in 0..r.s.len() {
1411 let Some(TensorOpSpec::ArgMax(_)) = r.s.slot(i).and_then(|o| o.prim()) else { continue };
1412 if !nexts.contains(&r.s.get(i).out) {
1413 continue;
1414 }
1415 let op = r.s.get(i).clone();
1416 let ins: Vec<(String, Access)> = op.ins.iter().cloned().zip(op.access.iter().copied()).collect();
1417 r.fuse("argmax_advance", ClassId::FusedSampling, i, &[], ins, op.out.clone(), op.dst.clone());
1418 }
1419}
1420
1421fn projects(op: &Op, g: &TensorGraph) -> Option<(String, usize, usize)> {
1424 if let Some(p) = gemv(op, g) {
1425 return Some((p.x, p.k, p.m));
1426 }
1427 let Sym::Fused(_) = &op.sym else { return None };
1428 let products: Vec<GemvForm> = leaves(op.clone()).iter().filter_map(|l| gemv(l, g)).collect();
1429 let first = products.first()?;
1430 let one = products.iter().all(|p| (&p.x, p.k, p.m) == (&first.x, first.k, first.m));
1431 (one && op.ins.first() == Some(&first.x)).then(|| (first.x.clone(), first.k, first.m))
1432}
1433
1434fn merged_projections(r: &mut Rewriter) {
1439 for i in 0..r.s.len() {
1440 let Some(first) = r.s.slot(i).and_then(|o| projects(o, r.g)) else { continue };
1441 let x = first.0.clone();
1442 if !r.admits(&candidate(ClassId::MergedQkv, vec![r.s.get(i).clone()], vec![x.clone()], &r.s.get(i).out, r.s.get(i).dst.clone())) {
1443 continue;
1444 }
1445 let mut group = vec![i];
1446 for j in i + 1..r.s.len() {
1447 let Some(op) = r.s.slot(j) else { continue };
1448 if super::facts::writes(op).contains(&x.as_str()) {
1449 break;
1450 }
1451 if projects(op, r.g).as_ref() != Some(&first) {
1452 continue;
1453 }
1454 let written: Vec<&str> = super::facts::writes(op);
1455 let read: Vec<&str> = super::facts::reads(op);
1456 let crossed = (i + 1..j).filter(|b| !group.contains(b)).filter_map(|b| r.s.slot(b)).any(|between| {
1457 let (br, bw) = (super::facts::reads(between), super::facts::writes(between));
1458 written.iter().any(|t| br.contains(t) || bw.contains(t)) || read.iter().any(|t| bw.contains(t))
1459 });
1460 if crossed {
1461 continue;
1462 }
1463 let members: Vec<Op> = group.iter().chain([&j]).map(|&k| r.s.get(k).clone()).collect();
1464 if r.admits(&candidate(ClassId::MergedQkv, members, vec![x.clone()], &r.s.get(i).out, r.s.get(i).dst.clone())) {
1465 group.push(j);
1466 }
1467 }
1468 if group.len() < 2 {
1469 continue;
1470 }
1471 let members: Vec<Op> = group.iter().map(|&k| r.s.get(k).clone()).collect();
1472 let mut ins: Vec<(String, Access)> = vec![(x.clone(), r.s.get(i).access[0])];
1473 for m in &members {
1474 for (t, a) in m.ins.iter().zip(&m.access).skip(1) {
1475 if !ins.iter().any(|(u, _)| u == t) {
1476 ins.push((t.clone(), *a));
1477 }
1478 }
1479 }
1480 let (out, dst) = (members[0].out.clone(), members[0].dst.clone());
1481 r.fuse("merged_projections", ClassId::MergedQkv, i, &group[1..], ins, out, dst);
1482 }
1483}
1484
1485fn pointwise(op: &Op) -> Option<usize> {
1488 if op.dst.own().is_none() || op.access.iter().any(|a| *a != Access::Whole) {
1489 return None;
1490 }
1491 match op.prim()? {
1492 TensorOpSpec::Elementwise(e) if e.row.is_none() => Some(e.n),
1493 TensorOpSpec::Silu(u) => Some(u.n),
1494 TensorOpSpec::Sigmoid(u) => Some(u.n),
1495 TensorOpSpec::Gelu(u) => Some(u.n),
1496 TensorOpSpec::Relu(u) => Some(u.n),
1497 TensorOpSpec::Scale(u) => Some(u.n),
1498 _ => None,
1499 }
1500}
1501
1502fn scale_of(op: &Op) -> Option<(String, usize)> {
1504 match op.prim()? {
1505 TensorOpSpec::Scale(sc) if op.access[0] == Access::Whole && op.dst.own().is_some() => Some((op.ins[0].clone(), sc.n)),
1506 _ => None,
1507 }
1508}
1509
1510fn operands(op: &Op) -> Vec<(String, Access)> {
1512 op.ins.iter().cloned().zip(op.access.iter().copied()).collect()
1513}
1514
1515fn external(members: &[&Op]) -> Vec<(String, Access)> {
1517 let made: BTreeSet<&str> = members.iter().map(|m| m.out.as_str()).collect();
1518 let mut ins: Vec<(String, Access)> = Vec::new();
1519 for m in members {
1520 for (t, a) in operands(m) {
1521 if !made.contains(t.as_str()) && !ins.iter().any(|(n, _)| *n == t) {
1522 ins.push((t, a));
1523 }
1524 }
1525 }
1526 ins
1527}
1528
1529fn pair(r: &mut Rewriter, rule: &'static str, class: ClassId, producer: usize, consumer: usize, anchor: usize) -> bool {
1532 let (p, c) = (r.s.get(producer).clone(), r.s.get(consumer).clone());
1533 let ins = external(&[&p, &c]);
1534 let names: Vec<String> = ins.iter().map(|(t, _)| t.clone()).collect();
1535 if !r.admits(&candidate(class, vec![p, c.clone()], names, &c.out, c.dst.clone())) {
1536 return false;
1537 }
1538 let gone = if anchor == producer { consumer } else { producer };
1539 r.fuse(rule, class, anchor, &[gone], ins, c.out.clone(), c.dst.clone());
1540 true
1541}
1542
1543fn norm_scale(r: &mut Rewriter) {
1545 for j in 0..r.s.len() {
1546 let Some((x, n)) = r.s.slot(j).and_then(scale_of) else { continue };
1547 let Some(i) = r.s.producer(&x) else { continue };
1548 let Some(nf) = norm(r.s.get(i)) else { continue };
1549 let plain = class(r.s.get(i)).is_none() && nf.own && nf.part.is_none() && nf.add.is_none() && nf.rows * nf.cols == n;
1550 if plain && r.s.readers(&x) == [j] && !r.s.crosses(i, j, &[]) {
1551 pair(r, "norm_scale", ClassId::NormScale, i, j, i);
1552 }
1553 }
1554}
1555
1556fn gemm_scale(r: &mut Rewriter) {
1558 for j in 0..r.s.len() {
1559 let Some(op) = r.s.slot(j) else { continue };
1560 let Some((x, n)) = scale_of(op).or_else(|| column_scale(r, op)) else { continue };
1561 let Some(i) = r.s.producer(&x) else { continue };
1562 let Some(p) = gemv(r.s.get(i), r.g) else { continue };
1563 if !(p.dst.own().is_some() && p.n * p.m == n && r.s.readers(&x) == [j] && !r.s.crosses(i, j, &[])) {
1564 continue;
1565 }
1566 if !pair(r, "gemm_scale", ClassId::GemmScale, i, j, i) {
1567 continue;
1568 }
1569 let y = r.s.get(i).out.clone();
1571 let Some(&k) = r.s.readers(&y).first().filter(|_| r.s.readers(&y).len() == 1) else { continue };
1572 let Some(b) = binary(r.s.get(k)) else { continue };
1573 let bias = if b.a == y { &b.b } else { &b.a };
1574 let constant = weights(r.g, bias) == Some(Weights::Values) && r.g.tensors.get(bias).map(|t| t.len()) == Some(b.row.unwrap_or(b.n));
1575 if b.op == ElementwiseOp::Add && b.gate.is_none() && (b.row.is_some() || b.n == n) && b.row.unwrap_or(p.n) == p.n && constant && !r.s.crosses(i, k, &[]) {
1576 pair(r, "gemm_scale", ClassId::GemmScale, i, k, i);
1577 }
1578 }
1579}
1580
1581fn column_scale(r: &Rewriter, op: &Op) -> Option<(String, usize)> {
1584 let b = binary(op)?;
1585 if b.op != ElementwiseOp::Mul || b.gate.is_some() || op.dst.own().is_none() || op.access.iter().any(|a| *a != Access::Whole) {
1586 return None;
1587 }
1588 let constant = |t: &str, len: usize| weights(r.g, t) == Some(Weights::Values) && r.g.tensors.get(t).map(|t| t.len()) == Some(len);
1589 if constant(&b.b, b.row.unwrap_or(b.n)) {
1590 Some((b.a.clone(), b.n))
1591 } else if b.row.is_none() && constant(&b.a, b.n) {
1592 Some((b.b.clone(), b.n))
1593 } else {
1594 None
1595 }
1596}
1597
1598fn affine_input_fold(r: &mut Rewriter) {
1600 for i in 0..r.s.len() {
1601 let Some(op) = r.s.slot(i) else { continue };
1602 let Some((_, n)) = scale_of(op) else { continue };
1603 let s = op.out.clone();
1604 let [j] = r.s.readers(&s)[..] else { continue };
1605 let Some(p) = gemv(r.s.get(j), r.g) else { continue };
1606 if p.x == s && r.s.get(j).access[0] == Access::Whole && p.k * p.m == n && !r.s.sinks_past(i, j, &[]) {
1607 pair(r, "affine_input_fold", ClassId::AffineInputFold, i, j, j);
1608 }
1609 }
1610}
1611
1612fn relu_sqr(r: &mut Rewriter) {
1614 for j in 0..r.s.len() {
1615 let Some(b) = r.s.slot(j).and_then(binary) else { continue };
1616 if b.op != ElementwiseOp::Mul || b.gate.is_some() || b.row.is_some() || b.a != b.b || class(r.s.get(j)).is_some() {
1617 continue;
1618 }
1619 let Some(i) = r.s.producer(&b.a) else { continue };
1620 let relu = matches!(r.s.get(i).prim(), Some(TensorOpSpec::Relu(u)) if u.n == b.n) && pointwise(r.s.get(i)).is_some();
1621 if relu && r.s.readers(&b.a) == [j] && !r.s.crosses(i, j, &[]) {
1622 pair(r, "relu_sqr", ClassId::ReluSqr, i, j, i);
1623 }
1624 }
1625}
1626
1627fn region_member(op: &Op, g: &TensorGraph) -> Option<(usize, super::graph::Elem)> {
1631 if op.dst.own().is_none() || op.access.iter().any(|a| *a != Access::Whole) || class(op).is_some() {
1632 return None;
1633 }
1634 let n = match op.prim()? {
1635 TensorOpSpec::Elementwise(e) => e.n,
1636 _ => pointwise(op)?,
1637 };
1638 Some((n, g.tensors.get(&op.out)?.elem))
1639}
1640
1641fn elementwise_regions(r: &mut Rewriter) {
1645 for y in (0..r.s.len()).rev() {
1646 let Some(sink) = r.s.slot(y) else { continue };
1647 let Some(form) = region_member(sink, r.g) else { continue };
1648 let mut members: Vec<usize> = vec![y];
1649 let mut joins = false;
1650 loop {
1651 let mut grew = false;
1652 for m in members.clone() {
1653 let mut fed = 0;
1654 for t in r.s.get(m).ins.clone() {
1655 let Some(p) = r.s.producer(&t) else { continue };
1656 if members.contains(&p) {
1657 fed += 1;
1658 continue;
1659 }
1660 let fits = region_member(r.s.get(p), r.g) == Some(form) && !r.carried(&t);
1661 if fits && !r.s.sinks_past(p, y, &members) {
1662 members.push(p);
1663 fed += 1;
1664 grew = true;
1665 }
1666 }
1667 joins |= fed >= 2;
1668 }
1669 if !grew {
1670 break;
1671 }
1672 }
1673 if members.len() < 3 || !joins {
1674 continue;
1675 }
1676 members.sort_unstable();
1677 let ops: Vec<Op> = members.iter().map(|&k| r.s.get(k).clone()).collect();
1678 let ins = external(&ops.iter().collect::<Vec<_>>());
1679 let names: Vec<String> = ins.iter().map(|(t, _)| t.clone()).collect();
1680 let (out, dst) = (r.s.get(y).out.clone(), r.s.get(y).dst.clone());
1681 if !r.admits(&candidate(ClassId::ElementwiseRegions, ops, names, &out, dst.clone())) {
1682 continue;
1683 }
1684 let gone: Vec<usize> = members.iter().copied().filter(|&k| k != y).collect();
1685 r.fuse("elementwise_regions", ClassId::ElementwiseRegions, y, &gone, ins, out, dst);
1686 }
1687}
1688
1689fn elementwise_chains(r: &mut Rewriter) {
1692 for i in 0..r.s.len() {
1693 let Some(first) = r.s.slot(i) else { continue };
1694 let Some(TensorOpSpec::Elementwise(e0)) = first.prim() else { continue };
1695 if pointwise(first).is_none() {
1696 continue;
1697 }
1698 let n = e0.n;
1699 let mut chain = vec![i];
1700 let mut cur = first.out.clone();
1701 let mut k = i;
1702 loop {
1703 let Some(next) = (k + 1..r.s.len()).find(|&b| r.s.slot(b).is_some()) else { break };
1704 let op = r.s.get(next);
1705 let carried = match op.prim() {
1706 Some(TensorOpSpec::Elementwise(e)) if e.n == n && pointwise(op).is_some() => (op.ins[0] == cur) ^ (op.ins[1] == cur),
1707 Some(TensorOpSpec::Silu(_)) if pointwise(op).is_some() => op.ins[0] == cur,
1708 _ => false,
1709 };
1710 if !carried || class(op).is_some() || r.carried(&cur) || r.s.readers(&cur) != [next] {
1711 break;
1712 }
1713 chain.push(next);
1714 cur = op.out.clone();
1715 k = next;
1716 }
1717 if chain.len() < 2 {
1718 continue;
1719 }
1720 let ops: Vec<Op> = chain.iter().map(|&k| r.s.get(k).clone()).collect();
1721 let ins = external(&ops.iter().collect::<Vec<_>>());
1722 let names: Vec<String> = ins.iter().map(|(t, _)| t.clone()).collect();
1723 let last = ops.last().expect("a chain").clone();
1724 if !r.admits(&candidate(ClassId::ElementwiseChains, ops, names, &last.out, last.dst.clone())) {
1725 continue;
1726 }
1727 r.fuse("elementwise_chains", ClassId::ElementwiseChains, i, &chain[1..], ins, last.out, last.dst);
1728 }
1729}
1730
1731fn recompute_fanout(r: &mut Rewriter) {
1734 for i in 0..r.s.len() {
1735 let Some(op) = r.s.slot(i) else { continue };
1736 let cheap = matches!(op.prim(), Some(TensorOpSpec::Silu(_) | TensorOpSpec::Sigmoid(_) | TensorOpSpec::Gelu(_) | TensorOpSpec::Relu(_) | TensorOpSpec::Scale(_)));
1737 if !cheap || pointwise(op).is_none() || r.observed(&op.out) {
1738 continue;
1739 }
1740 let producer = op.clone();
1741 let readers = r.s.readers(&producer.out);
1742 let fits = readers.len() >= 2
1743 && readers.iter().all(|&k| class(r.s.get(k)).is_none() && pointwise(r.s.get(k)).is_some() && r.s.untouched(&producer.ins[0], i, k, false, &[]));
1744 if !fits {
1745 continue;
1746 }
1747 let candidates: Vec<Op> = readers
1748 .iter()
1749 .map(|&k| {
1750 let c = r.s.get(k).clone();
1751 let ins = external(&[&producer, &c]);
1752 candidate(ClassId::RecomputeFanout, vec![producer.clone(), c.clone()], ins.into_iter().map(|(t, _)| t).collect(), &c.out, c.dst.clone())
1753 })
1754 .collect();
1755 if !candidates.iter().all(|c| r.admits(c)) {
1756 continue;
1757 }
1758 for k in readers {
1759 let c = r.s.take(k).expect("a live slot");
1760 let ins = external(&[&producer, &c]);
1761 let mut covers: Vec<usize> = producer.covers.iter().chain(&c.covers).copied().collect();
1762 covers.sort_unstable();
1763 let (names, access): (Vec<String>, Vec<Access>) = ins.into_iter().unzip();
1764 let fused = Op {
1765 sym: Sym::Fused(Fused { class: ClassId::RecomputeFanout, parts: vec![producer.clone(), c.clone()] }),
1766 ins: names,
1767 access,
1768 out: c.out.clone(),
1769 dst: c.dst.clone(),
1770 keeps: Vec::new(),
1771 covers,
1772 };
1773 r.s.put(k, fused);
1774 r.record("recompute_fanout", ClassId::RecomputeFanout, k);
1775 }
1776 r.s.take(i);
1777 }
1778}
1779
1780fn dual_norm(r: &mut Rewriter) {
1782 let plain = |op: &Op| norm(op).filter(|n| class(op).is_none() && n.own && n.part.is_none() && n.add.is_none());
1783 for i in 0..r.s.len() {
1784 let Some(a) = r.s.slot(i).and_then(plain) else { continue };
1785 for j in i + 1..r.s.len() {
1786 let Some(b) = r.s.slot(j).and_then(plain) else { continue };
1787 if (b.rows, b.cols) != (a.rows, a.cols) || facts::reads(r.s.get(j)).contains(&a.out.as_str()) || r.s.crosses(i, j, &[]) {
1788 continue;
1789 }
1790 let ops = vec![r.s.get(i).clone(), r.s.get(j).clone()];
1791 let ins = external(&ops.iter().collect::<Vec<_>>());
1792 let names: Vec<String> = ins.iter().map(|(t, _)| t.clone()).collect();
1793 let dst = r.s.get(i).dst.clone();
1794 if r.admits(&candidate(ClassId::DualNorm, ops, names, &a.out, dst.clone())) {
1795 r.fuse("dual_norm", ClassId::DualNorm, i, &[j], ins, a.out.clone(), dst);
1796 }
1797 break;
1798 }
1799 }
1800}
1801
1802fn embed_norm(r: &mut Rewriter) {
1805 for i in 0..r.s.len() {
1806 let Some(op) = r.s.slot(i) else { continue };
1807 let gather = match op.prim() {
1808 Some(TensorOpSpec::RowsGather(_)) => true,
1809 Some(TensorOpSpec::RowGather(rg)) => matches!(rg.index, RowIndex::Named(_)),
1810 _ => class(op) == Some(ClassId::EmbedScale),
1811 };
1812 let table = op.ins[0].clone();
1813 if !gather || op.dst.own().is_none() || weights(r.g, &table).is_none() {
1814 continue;
1815 }
1816 let x = op.out.clone();
1817 let Some(&j) = r.s.readers(&x).first() else { continue };
1818 let Some(n) = norm(r.s.get(j)) else { continue };
1819 if class(r.s.get(j)).is_some() || n.x != x || !n.own || n.part.is_some() || n.add.is_some() || r.s.crosses(i, j, &[]) {
1820 continue;
1821 }
1822 let ops = vec![r.s.get(i).clone(), r.s.get(j).clone()];
1823 let ins = external(&ops.iter().collect::<Vec<_>>());
1824 let names: Vec<String> = ins.iter().map(|(t, _)| t.clone()).collect();
1825 let dst = r.s.get(j).dst.clone();
1826 if r.admits(&candidate(ClassId::EmbedNorm, ops, names, &n.out, dst.clone())) {
1827 r.fuse("embed_norm", ClassId::EmbedNorm, i, &[j], ins, n.out.clone(), dst);
1828 }
1829 }
1830}
1831
1832fn gemv_norm_epilogue(r: &mut Rewriter) {
1836 norm_epilogues(r, "gemv_norm_epilogue", ClassId::GemvNormEpilogue, |m| m == 1);
1837}
1838
1839fn prefill_norm_epilogues(r: &mut Rewriter) {
1841 norm_epilogues(r, "prefill_norm_epilogues", ClassId::PrefillNormEpilogues, |m| m > 1);
1842}
1843
1844fn norm_epilogues(r: &mut Rewriter, rule: &'static str, builds: ClassId, rows: fn(usize) -> bool) {
1846 for j in 0..r.s.len() {
1847 let Some(op) = r.s.slot(j) else { continue };
1848 if !matches!(class(op), Some(ClassId::AddNorm | ClassId::NormThenAdd)) {
1849 continue;
1850 }
1851 let summands = [op.ins[0].clone(), op.ins[1].clone()];
1852 for t in summands {
1853 let Some(i) = r.s.producer(&t) else { continue };
1854 let Some(p) = gemv(r.s.get(i), r.g) else { continue };
1855 if !rows(p.m) || p.dst.own().is_none() || r.s.readers(&t) != [j] || r.s.sinks_past(i, j, &[]) {
1856 continue;
1857 }
1858 if pair(r, rule, builds, i, j, j) {
1859 break;
1860 }
1861 }
1862 }
1863}
1864
1865fn qkv_head_epilogue(r: &mut Rewriter) {
1868 for j in 0..r.s.len() {
1869 let Some(op) = r.s.slot(j) else { continue };
1870 let Some(ro) = rope(op).filter(|ro| ro.part.is_none()) else { continue };
1871 let Some(i) = r.s.producer(&ro.x) else { continue };
1872 let Some(p) = gemv(r.s.get(i), r.g) else { continue };
1873 if p.dst.own().is_some() && r.s.readers(&ro.x) == [j] && !r.s.sinks_past(i, j, &[]) {
1874 pair(r, "qkv_head_epilogue", ClassId::QkvHeadEpilogue, i, j, j);
1875 }
1876 }
1877}
1878
1879fn cross_layer_launch(r: &mut Rewriter) {
1882 let row = |op: &Op| match op.prim()? {
1883 TensorOpSpec::SetRow(s) if op.access.iter().all(|a| *a == Access::Whole) => Some(match &s.index {
1884 RowIndex::Named(_) => op.ins[2].clone(),
1885 RowIndex::Literal(n) => format!("#{n}"),
1886 }),
1887 _ => None,
1888 };
1889 for i in 0..r.s.len() {
1890 let Some(at) = r.s.slot(i).and_then(row) else { continue };
1891 let mut group = vec![i];
1892 for j in i + 1..r.s.len() {
1893 if r.s.slot(j).and_then(row).as_ref() == Some(&at) && !r.s.crosses(i, j, &group) {
1894 let ops: Vec<Op> = group.iter().chain([&j]).map(|&k| r.s.get(k).clone()).collect();
1895 let names: Vec<String> = external(&ops.iter().collect::<Vec<_>>()).into_iter().map(|(t, _)| t).collect();
1896 let (out, dst) = (ops[0].out.clone(), ops[0].dst.clone());
1897 if r.admits(&candidate(ClassId::CrossLayerLaunch, ops, names, &out, dst)) {
1898 group.push(j);
1899 }
1900 }
1901 }
1902 if group.len() < 2 {
1903 continue;
1904 }
1905 let ops: Vec<Op> = group.iter().map(|&k| r.s.get(k).clone()).collect();
1906 let ins = external(&ops.iter().collect::<Vec<_>>());
1907 let (out, dst) = (ops[0].out.clone(), ops[0].dst.clone());
1908 r.fuse("cross_layer_launch", ClassId::CrossLayerLaunch, i, &group[1..], ins, out, dst);
1909 }
1910}
1911
1912fn read_only_within(r: &Rewriter, op: &Op, within: &[usize]) -> bool {
1914 facts::writes(op).into_iter().all(|t| !r.observed(t) && r.s.readers(t).iter().all(|k| within.contains(k)))
1915}
1916
1917fn group_at_last(r: &mut Rewriter, rule: &'static str, class: ClassId, mut members: Vec<usize>) -> bool {
1920 members.sort_unstable();
1921 members.dedup();
1922 let at = *members.last().expect("members");
1923 if members.iter().any(|&m| m != at && r.s.sinks_past(m, at, &members)) {
1924 return false;
1925 }
1926 let ops: Vec<Op> = members.iter().map(|&k| r.s.get(k).clone()).collect();
1927 let ins = external(&ops.iter().collect::<Vec<_>>());
1928 let names: Vec<String> = ins.iter().map(|(t, _)| t.clone()).collect();
1929 let (out, dst) = (r.s.get(at).out.clone(), r.s.get(at).dst.clone());
1930 if !r.admits(&candidate(class, ops, names, &out, dst.clone())) {
1931 return false;
1932 }
1933 let gone: Vec<usize> = members.iter().copied().filter(|&k| k != at).collect();
1934 r.fuse(rule, class, at, &gone, ins, out, dst);
1935 true
1936}
1937
1938fn moe_deferred_finalize(r: &mut Rewriter) {
1942 let mixture = |r: &Rewriter, t: &str, reader: usize| {
1943 let i = r.s.producer(t)?;
1944 moe(r.s.get(i)).filter(|m| m.dst.own().is_some() && r.s.readers(t) == [reader]).map(|_| i)
1945 };
1946 for j in 0..r.s.len() {
1947 let Some(op) = r.s.slot(j) else { continue };
1948 if class(op) != Some(ClassId::AddNorm) {
1949 continue;
1950 }
1951 for t in [op.ins[0].clone(), op.ins[1].clone()] {
1952 if let Some(i) = mixture(r, &t, j) {
1953 if !r.s.sinks_past(i, j, &[]) && pair(r, "moe_deferred_finalize", ClassId::MoeDeferredFinalize, i, j, j) {
1954 break;
1955 }
1956 continue;
1957 }
1958 let Some(i) = r.s.producer(&t).filter(|_| r.s.readers(&t) == [j]) else { continue };
1960 let Some(b) = binary(r.s.get(i)) else { continue };
1961 if b.op != ElementwiseOp::Add || b.gate.is_some() || b.row.is_some() || class(r.s.get(i)).is_some() {
1962 continue;
1963 }
1964 let Some(m) = [&b.a, &b.b].into_iter().find_map(|x| mixture(r, x, i)) else { continue };
1965 if group_at_last(r, "moe_deferred_finalize", ClassId::MoeDeferredFinalize, vec![m, i, j]) {
1966 break;
1967 }
1968 }
1969 }
1970}
1971
1972fn mla_unabsorb_in_fold(r: &mut Rewriter) {
1975 for i in 0..r.s.len() {
1976 let Some(TensorOpSpec::Attention(a)) = r.s.slot(i).and_then(|o| o.prim()).cloned() else { continue };
1977 if a.kv != 1 || a.vd >= a.hd || r.s.get(i).dst.own().is_none() {
1978 continue;
1979 }
1980 let o = r.s.get(i).out.clone();
1981 let [j] = r.s.readers(&o)[..] else { continue };
1982 let Some(TensorOpSpec::HeadMatmul(c)) = r.s.get(j).prim() else { continue };
1983 let fits = r.s.get(j).ins[0] == o && r.s.get(j).access[0] == Access::Whole && c.heads == a.h && c.a == a.vd && r.s.get(j).dst.own().is_some();
1984 if fits && !r.s.crosses(i, j, &[]) {
1985 pair(r, "mla_unabsorb_in_fold", ClassId::MlaUnabsorbInFold, i, j, i);
1986 }
1987 }
1988}
1989
1990fn mla_prologue(r: &mut Rewriter) {
1994 for at in 0..r.s.len() {
1995 let Some(op) = r.s.slot(at) else { continue };
1996 let latent = leaves(op.clone()).into_iter().find_map(|l| match l.prim() {
1997 Some(TensorOpSpec::Attention(a)) if a.kv == 1 && a.vd < a.hd => Some((l.ins[0].clone(), l.ins[1].clone())),
1998 _ => None,
1999 });
2000 let Some((q, kc)) = latent else { continue };
2001 let mut members: Vec<usize> = r.s.live().filter(|(k, o)| *k < at && (o.dst.buf == q || o.dst.buf == kc) && o.dst.own().is_none()).map(|(k, _)| k).collect();
2003 if members.is_empty() {
2004 continue;
2005 }
2006 loop {
2008 let mut grew = false;
2009 for m in members.clone() {
2010 for t in r.s.get(m).ins.clone() {
2011 let Some(p) = r.s.maker(&t) else { continue };
2012 if members.contains(&p) || p > at {
2013 continue;
2014 }
2015 let within: Vec<usize> = members.iter().copied().chain([p, at]).collect();
2016 if read_only_within(r, r.s.get(p), &within) {
2017 members.push(p);
2018 grew = true;
2019 }
2020 }
2021 }
2022 if !grew {
2023 break;
2024 }
2025 }
2026 let ops: Vec<&Op> = members.iter().map(|&k| r.s.get(k)).collect();
2027 let projects = ops.iter().any(|o| gemv(o, r.g).is_some() || class(o) == Some(ClassId::MergedQkv));
2028 let absorbs = ops.iter().any(|o| leaves((*o).clone()).iter().any(|l| matches!(l.prim(), Some(TensorOpSpec::HeadMatmul(_)))));
2029 let rotates = ops.iter().any(|o| leaves((*o).clone()).iter().any(|l| matches!(l.prim(), Some(TensorOpSpec::Rope(_)))));
2030 if projects && absorbs && rotates {
2031 group_at_last(r, "mla_prologue", ClassId::MlaPrologue, members);
2032 }
2033 }
2034}
2035
2036fn delta_inputs(r: &Rewriter, j: usize) -> Option<(usize, Vec<usize>, Option<Vec<usize>>)> {
2040 let Some(TensorOpSpec::GatedDelta(d)) = r.s.slot(j)?.prim() else { return None };
2041 let op = r.s.get(j);
2042 let convs: BTreeSet<usize> = op.ins[0..3].iter().map(|t| r.s.maker(t)).collect::<Option<_>>()?;
2043 if !convs.iter().all(|&c| conv(r.s.get(c)).is_some() || class(r.s.get(c)) == Some(ClassId::ConvParts)) {
2044 return None;
2045 }
2046 let gating = r.s.maker(&op.ins[4])?;
2047 if r.s.maker(&op.ins[5]) != Some(gating) || !matches!(class(r.s.get(gating)), Some(ClassId::GdnGating | ClassId::GdnGateProjections)) {
2048 return None;
2049 }
2050 let mut read: Vec<String> = convs
2051 .iter()
2052 .flat_map(|&c| leaves(r.s.get(c).clone()))
2053 .filter(|l| matches!(l.prim(), Some(TensorOpSpec::CausalConv(_))))
2054 .map(|l| l.ins[0].clone())
2055 .collect();
2056 read.push(op.ins[3].clone());
2057 let products: Option<BTreeSet<usize>> = read.iter().map(|t| r.s.maker(t)).collect();
2058 Some((d.rows, convs.into_iter().chain([gating]).collect(), products.map(|p| p.into_iter().collect())))
2059}
2060
2061fn made_for(r: &Rewriter, members: &[usize], j: usize) -> bool {
2063 let within: Vec<usize> = members.iter().copied().chain([j]).collect();
2064 members.iter().all(|&k| read_only_within(r, r.s.get(k), &within))
2065}
2066
2067fn gdn_input_epilogue(r: &mut Rewriter) {
2070 for j in 0..r.s.len() {
2071 let Some((rows, prep, Some(products))) = delta_inputs(r, j) else { continue };
2072 let one_activation = products.iter().filter_map(|&p| gemv(r.s.get(p), r.g)).map(|p| p.x).collect::<BTreeSet<_>>().len() == 1;
2073 let members: Vec<usize> = prep.into_iter().chain(products.iter().copied()).collect();
2074 if rows == 1 && one_activation && products.iter().all(|&p| gemv(r.s.get(p), r.g).is_some()) && made_for(r, &members, j) {
2075 group_at_last(r, "gdn_input_epilogue", ClassId::GdnInputEpilogue, members);
2076 }
2077 }
2078}
2079
2080fn gdn_post_conv_prep(r: &mut Rewriter) {
2083 for j in 0..r.s.len() {
2084 let Some((rows, prep, _)) = delta_inputs(r, j) else { continue };
2085 if rows > 1 && made_for(r, &prep, j) {
2086 group_at_last(r, "gdn_post_conv_prep", ClassId::GdnPostConvPrep, prep);
2087 }
2088 }
2089}
2090
2091fn gdn_decode_whole(r: &mut Rewriter) {
2094 for j in 0..r.s.len() {
2095 let Some((rows, prep, _)) = delta_inputs(r, j) else { continue };
2096 if rows == 1 && made_for(r, &prep, j) {
2097 group_at_last(r, "gdn_decode_whole", ClassId::GdnDecodeWhole, prep.into_iter().chain([j]).collect());
2098 }
2099 }
2100}
2101
2102fn row_wise(op: &Op) -> bool {
2106 if op.dst.row.is_some() || !op.state_inputs().is_empty() {
2107 return false;
2108 }
2109 if matches!(class(op), Some(ClassId::MoeTokenSort | ClassId::MoeDeferredFinalize)) {
2110 return true;
2111 }
2112 leaves(op.clone()).iter().all(|l| {
2113 matches!(
2114 l.prim(),
2115 Some(
2116 TensorOpSpec::Matmul(_)
2117 | TensorOpSpec::Elementwise(_)
2118 | TensorOpSpec::FusedRow(_)
2119 | TensorOpSpec::Rope(_)
2120 | TensorOpSpec::Silu(_)
2121 | TensorOpSpec::Sigmoid(_)
2122 | TensorOpSpec::Gelu(_)
2123 | TensorOpSpec::Relu(_)
2124 | TensorOpSpec::Scale(_)
2125 | TensorOpSpec::SwiGlu(_)
2126 | TensorOpSpec::HeadMatmul(_)
2127 | TensorOpSpec::Attention(_)
2128 | TensorOpSpec::Moe(_)
2129 | TensorOpSpec::Join(_)
2130 | TensorOpSpec::L2Norm(_)
2131 | TensorOpSpec::HeadSlice(_)
2132 )
2133 ) || matches!(l.sym, Sym::Quantize(_))
2134 })
2135}
2136
2137fn last_layer_rows(r: &mut Rewriter) {
2141 for v in 0..r.s.len() {
2142 let Some(view) = r.s.slot(v) else { continue };
2143 let Sym::View { offset } = view.sym else { continue };
2144 let table = view.ins[0].clone();
2145 let Some(t) = r.g.tensors.get(&table) else { continue };
2146 if t.rows < 2 || offset % t.cols != 0 {
2147 continue;
2148 }
2149 let rows = t.rows;
2150 let Some(first) = r.s.producer(&table) else { continue };
2151 let mut members: Vec<usize> = vec![v];
2152 let mut frontier = vec![first];
2153 while let Some(p) = frontier.pop() {
2154 if members.contains(&p) {
2155 continue;
2156 }
2157 let op = r.s.get(p);
2158 let same_rows = r.g.tensors.get(&op.out).is_none_or(|x| x.rows == rows);
2159 let uncarried = facts::writes(op).into_iter().all(|t| r.g.tensors.get(t).is_none_or(|x| !x.carried));
2160 let within: Vec<usize> = members.iter().copied().chain([p]).collect();
2161 if !(row_wise(op) && same_rows && uncarried && read_only_within(r, op, &within)) {
2162 continue;
2163 }
2164 members.push(p);
2165 for t in op.ins.clone() {
2166 if let Some(q) = r.s.producer(&t) {
2167 frontier.push(q);
2168 }
2169 }
2170 }
2171 let attends = members.iter().any(|&m| leaves(r.s.get(m).clone()).iter().any(|l| matches!(l.prim(), Some(TensorOpSpec::Attention(_)))));
2172 if attends && members.len() > 2 {
2173 group_at_last(r, "last_layer_rows", ClassId::LastLayerRows, members);
2174 }
2175 }
2176}
2177
2178fn normed_rotation(op: &Op) -> Option<(String, String, Option<String>)> {
2181 let found = leaves(op.clone());
2182 let normed = found.iter().any(|l| matches!(l.prim(), Some(TensorOpSpec::FusedRow(_))));
2183 let ro = found.iter().find(|l| matches!(l.prim(), Some(TensorOpSpec::Rope(_))))?;
2184 normed.then(|| (ro.ins[1].clone(), ro.ins[2].clone(), ro.ins.get(3).cloned()))
2185}
2186
2187fn output_gate(r: &Rewriter, op: &Op) -> Option<String> {
2190 if let Sym::Fused(f) = &op.sym {
2191 if f.class == ClassId::AttnOutputGate {
2192 return binary(f.parts.last()?).filter(|b| b.gate.is_some()).map(|b| b.b);
2193 }
2194 }
2195 let [gk] = r.s.readers(&op.out)[..] else { return None };
2196 let b = binary(r.s.get(gk)).filter(|b| b.gate.is_some())?;
2197 Some(if b.a == op.out { b.b } else { b.a })
2198}
2199
2200fn qk_norm_rope_gate_split(r: &mut Rewriter) {
2203 for at in 0..r.s.len() {
2204 let Some(op) = r.s.slot(at) else { continue };
2205 let Some(attn) = leaves(op.clone()).into_iter().find(|l| matches!(l.prim(), Some(TensorOpSpec::Attention(_)))) else { continue };
2206 let q = attn.ins[0].clone();
2207 let Some(gate) = output_gate(r, op) else { continue };
2208 let (Some(p), Some(qk)) = (r.s.maker(&gate), r.s.maker(&q)) else { continue };
2209 let Some(gp) = gemv(r.s.get(p), r.g) else { continue };
2210 let query_x = leaves(r.s.get(qk).clone()).into_iter().find_map(|l| match l.prim() {
2211 Some(TensorOpSpec::Matmul(_)) => Some(l.ins[0].clone()),
2212 _ => None,
2213 });
2214 if normed_rotation(r.s.get(qk)).is_none() || query_x.as_deref() != Some(gp.x.as_str()) || p > qk || r.s.sinks_past(p, qk, &[]) {
2215 continue;
2216 }
2217 pair(r, "qk_norm_rope_gate_split", ClassId::QkNormRopeGateSplit, p, qk, qk);
2218 }
2219}
2220
2221fn horizontal_norm_rope_insert(r: &mut Rewriter) {
2224 for i in 0..r.s.len() {
2225 let Some(tables) = r.s.slot(i).and_then(normed_rotation) else { continue };
2226 let mut group = vec![i];
2227 for j in i + 1..r.s.len() {
2228 if r.s.slot(j).and_then(normed_rotation).as_ref() != Some(&tables) || r.s.crosses(i, j, &group) {
2229 continue;
2230 }
2231 let ops: Vec<Op> = group.iter().chain([&j]).map(|&k| r.s.get(k).clone()).collect();
2232 let names: Vec<String> = external(&ops.iter().collect::<Vec<_>>()).into_iter().map(|(t, _)| t).collect();
2233 let (out, dst) = (ops[0].out.clone(), ops[0].dst.clone());
2234 if r.admits(&candidate(ClassId::HorizontalNormRopeInsert, ops, names, &out, dst)) {
2235 group.push(j);
2236 }
2237 }
2238 let inserts = group.iter().any(|&k| leaves(r.s.get(k).clone()).iter().any(|l| l.dst.row.is_some()));
2239 if group.len() < 2 || !inserts {
2240 continue;
2241 }
2242 let ops: Vec<Op> = group.iter().map(|&k| r.s.get(k).clone()).collect();
2243 let ins = external(&ops.iter().collect::<Vec<_>>());
2244 let (out, dst) = (ops[0].out.clone(), ops[0].dst.clone());
2245 r.fuse("horizontal_norm_rope_insert", ClassId::HorizontalNormRopeInsert, i, &group[1..], ins, out, dst);
2246 }
2247}
2248
2249pub const RULES: &[Rule] = &[
2251 Rule { name: "add_norm", kind: RuleKind::Group, builds: &[ClassId::AddNorm], apply: add_norm },
2252 Rule { name: "sandwich_norm", kind: RuleKind::Group, builds: &[ClassId::NormThenAdd], apply: sandwich_norm },
2253 Rule { name: "gemv_bias", kind: RuleKind::Group, builds: &[ClassId::GemmBias], apply: gemv_bias },
2254 Rule { name: "gemm_scale", kind: RuleKind::Group, builds: &[ClassId::GemmScale], apply: gemm_scale },
2255 Rule { name: "affine_input_fold", kind: RuleKind::Group, builds: &[ClassId::AffineInputFold], apply: affine_input_fold },
2256 Rule { name: "gemv_norm_epilogue", kind: RuleKind::Group, builds: &[ClassId::GemvNormEpilogue], apply: gemv_norm_epilogue },
2257 Rule { name: "prefill_norm_epilogues", kind: RuleKind::Group, builds: &[ClassId::PrefillNormEpilogues], apply: prefill_norm_epilogues },
2258 Rule { name: "norm_scale", kind: RuleKind::Group, builds: &[ClassId::NormScale], apply: norm_scale },
2259 Rule { name: "head_norm_rope", kind: RuleKind::Group, builds: &[ClassId::QkNormRope], apply: head_norm_rope },
2260 Rule { name: "slice_read", kind: RuleKind::Place, builds: &[ClassId::StridedRead], apply: slice_read },
2261 Rule { name: "row_write", kind: RuleKind::Place, builds: &[ClassId::RowWrite], apply: row_write },
2262 Rule { name: "qkv_head_epilogue", kind: RuleKind::Group, builds: &[ClassId::QkvHeadEpilogue], apply: qkv_head_epilogue },
2263 Rule { name: "join_in_place", kind: RuleKind::Place, builds: &[ClassId::JoinInPlace], apply: join_in_place },
2264 Rule { name: "conv_silu", kind: RuleKind::Group, builds: &[ClassId::ConvBiasSilu], apply: conv_silu },
2265 Rule { name: "conv_silu_l2", kind: RuleKind::Group, builds: &[ClassId::ConvSiluL2norm], apply: conv_silu_l2 },
2266 Rule { name: "conv_parts", kind: RuleKind::Group, builds: &[ClassId::ConvParts], apply: conv_parts },
2267 Rule { name: "gates", kind: RuleKind::Group, builds: &[ClassId::GdnGating], apply: gates },
2268 Rule { name: "gate_projections", kind: RuleKind::Group, builds: &[ClassId::GdnGateProjections], apply: gate_projections },
2269 Rule { name: "gdn_input_epilogue", kind: RuleKind::Group, builds: &[ClassId::GdnInputEpilogue], apply: gdn_input_epilogue },
2270 Rule { name: "gdn_post_conv_prep", kind: RuleKind::Group, builds: &[ClassId::GdnPostConvPrep], apply: gdn_post_conv_prep },
2271 Rule { name: "gdn_decode_whole", kind: RuleKind::Group, builds: &[ClassId::GdnDecodeWhole], apply: gdn_decode_whole },
2272 Rule { name: "act_gate", kind: RuleKind::Group, builds: &[ClassId::GluActMul], apply: act_gate },
2273 Rule { name: "attn_output_gate", kind: RuleKind::Group, builds: &[ClassId::AttnOutputGate], apply: attn_output_gate },
2274 Rule { name: "glu_pair", kind: RuleKind::Group, builds: &[ClassId::GemvGateUpGlu], apply: glu_pair },
2275 Rule { name: "relu_sqr", kind: RuleKind::Group, builds: &[ClassId::ReluSqr], apply: relu_sqr },
2276 Rule { name: "elementwise_regions", kind: RuleKind::Group, builds: &[ClassId::ElementwiseRegions], apply: elementwise_regions },
2277 Rule { name: "elementwise_chains", kind: RuleKind::Group, builds: &[ClassId::ElementwiseChains], apply: elementwise_chains },
2278 Rule { name: "recompute_fanout", kind: RuleKind::Group, builds: &[ClassId::RecomputeFanout], apply: recompute_fanout },
2279 Rule { name: "shared_expert", kind: RuleKind::Group, builds: &[ClassId::SharedExpertFusion], apply: shared_expert },
2280 Rule { name: "moe_rows", kind: RuleKind::Group, builds: &[ClassId::MoeTokenSort], apply: moe_rows },
2281 Rule { name: "moe_deferred_finalize", kind: RuleKind::Group, builds: &[ClassId::MoeDeferredFinalize], apply: moe_deferred_finalize },
2282 Rule { name: "gather_scale", kind: RuleKind::Group, builds: &[ClassId::EmbedScale], apply: gather_scale },
2283 Rule { name: "embed_norm", kind: RuleKind::Group, builds: &[ClassId::EmbedNorm], apply: embed_norm },
2284 Rule { name: "strided_values", kind: RuleKind::Place, builds: &[ClassId::StridedValues], apply: strided_values },
2285 Rule { name: "mla_unabsorb_in_fold", kind: RuleKind::Group, builds: &[ClassId::MlaUnabsorbInFold], apply: mla_unabsorb_in_fold },
2286 Rule { name: "dual_norm", kind: RuleKind::Group, builds: &[ClassId::DualNorm], apply: dual_norm },
2287 Rule { name: "cross_layer_launch", kind: RuleKind::Group, builds: &[ClassId::CrossLayerLaunch], apply: cross_layer_launch },
2288 Rule { name: "rope_parts", kind: RuleKind::Group, builds: &[ClassId::RopeQkOneLaunch], apply: rope_parts },
2289 Rule { name: "qk_norm_rope_gate_split", kind: RuleKind::Group, builds: &[ClassId::QkNormRopeGateSplit], apply: qk_norm_rope_gate_split },
2290 Rule { name: "horizontal_norm_rope_insert", kind: RuleKind::Group, builds: &[ClassId::HorizontalNormRopeInsert], apply: horizontal_norm_rope_insert },
2291 Rule { name: "mla_prologue", kind: RuleKind::Group, builds: &[ClassId::MlaPrologue], apply: mla_prologue },
2292 Rule { name: "last_layer_rows", kind: RuleKind::Group, builds: &[ClassId::LastLayerRows], apply: last_layer_rows },
2293];
2294
2295pub const ADVANCE: Rule = Rule { name: "argmax_advance", kind: RuleKind::Group, builds: &[ClassId::FusedSampling], apply: argmax_advance };
2297
2298pub const LATE: &[Rule] = &[Rule { name: "merged_projections", kind: RuleKind::Group, builds: &[ClassId::MergedQkv], apply: merged_projections }];
2300
2301fn views(g: &TensorGraph) -> Vec<Op> {
2305 let mut last_reader: BTreeMap<&str, usize> = BTreeMap::new();
2306 for (k, op) in g.ops.iter().enumerate() {
2307 for x in &op.ins {
2308 last_reader.insert(x.as_str(), k);
2309 }
2310 }
2311 g.ops
2312 .iter()
2313 .enumerate()
2314 .map(|(i, op)| {
2315 let Some(TensorOpSpec::RowGather(rg)) = op.prim() else { return op.clone() };
2316 let RowIndex::Literal(row) = rg.index else { return op.clone() };
2317 let table = &op.ins[0];
2318 let computed = g.tensors.get(table).is_some_and(|t| t.role != Role::Constant);
2319 if !computed || row < 1 || row as usize > rg.rows {
2320 return op.clone();
2321 }
2322 let last = last_reader.get(op.out.as_str()).copied().unwrap_or(i);
2323 let rewritten = g.ops[i + 1..last.max(i + 1)].iter().any(|o| o.state_inputs().into_iter().any(|k| o.ins[k] == *table));
2324 if rewritten {
2325 return op.clone();
2326 }
2327 Op { sym: Sym::View { offset: (row as usize - 1) * rg.cols }, ..op.clone() }
2328 })
2329 .collect()
2330}
2331
2332pub fn optimize(g: &TensorGraph, shim: &dyn Shim, held: &[&str]) -> Optimized {
2336 let lifted = if shim.supports(ClassId::StridedRead) == Support::Native { views(g) } else { g.ops.clone() };
2338 let mut r = Rewriter { s: Slots::new(lifted), g, shim, fired: Vec::new(), rule: "", building: ADVANCE.builds[0], report: Vec::new() };
2339 let chip = shim.rules();
2340 let unlinked = |builds: &[ClassId]| {
2341 builds.iter().find_map(|c| match shim.supports(*c) {
2342 Support::Native => None,
2343 Support::Decompose => Some(format!("the chip runs `{c}` as its parts")),
2344 Support::Refuse(why) => Some(format!("{c}: {why}")),
2345 })
2346 };
2347 let step = |r: &mut Rewriter, name: &'static str, builds: &[ClassId], apply: &dyn Fn(&mut Rewriter)| {
2348 r.rule = name;
2349 r.building = builds[0];
2350 if held.contains(&name) {
2351 r.dry(apply, shim, &|_| Outcome::Held);
2352 } else if let Some(why) = unlinked(builds) {
2353 r.dry(apply, &Linking { base: shim, classes: builds }, &|_| Outcome::Refused(why.clone()));
2354 } else {
2355 apply(r);
2356 }
2357 };
2358 let at = |r: &mut Rewriter, position: Position| {
2359 for (_, rule) in chip.iter().filter(|(p, _)| *p == position) {
2360 step(r, rule.name, rule.builds, &*rule.apply);
2361 }
2362 };
2363 for rule in RULES {
2364 at(&mut r, Position::Before(rule.name));
2365 step(&mut r, rule.name, rule.builds, &rule.apply);
2366 at(&mut r, Position::After(rule.name));
2367 }
2368 r.rule = ADVANCE.name;
2369 r.building = ADVANCE.builds[0];
2370 if unlinked(ADVANCE.builds).is_none() {
2371 (ADVANCE.apply)(&mut r);
2372 }
2373 r.rule = "fused_quant";
2374 r.building = ClassId::GemvQuant;
2375 let fold = !held.contains(&"fused_quant");
2376 if !fold {
2377 r.dry(&|r: &mut Rewriter| super::numerics::quantize(r, true), shim, &|_| Outcome::Held);
2378 }
2379 super::numerics::quantize(&mut r, fold);
2380 for rule in LATE {
2381 at(&mut r, Position::Before(rule.name));
2382 step(&mut r, rule.name, rule.builds, &rule.apply);
2383 at(&mut r, Position::After(rule.name));
2384 }
2385 at(&mut r, Position::End);
2386 let ops = r.s.into_ops();
2387 Optimized { graph: TensorGraph { ops, ..g.clone() }, fired: r.fired, report: Report { decisions: r.report } }
2388}
2389
2390pub fn steps(shim: &dyn Shim) -> Vec<&'static str> {
2395 let chip = shim.rules();
2396 let at = |p: Position| chip.iter().filter(move |(q, _)| *q == p).map(|(_, rule)| rule.name).collect::<Vec<_>>();
2397 let mut out = Vec::new();
2398 let rules = |list: &[Rule], out: &mut Vec<&'static str>| {
2399 for rule in list {
2400 out.extend(at(Position::Before(rule.name)));
2401 out.push(rule.name);
2402 out.extend(at(Position::After(rule.name)));
2403 }
2404 };
2405 rules(RULES, &mut out);
2406 out.push("fused_quant");
2407 rules(LATE, &mut out);
2408 out.extend(at(Position::End));
2409 out
2410}
2411
2412pub fn rule_names() -> Vec<&'static str> {
2414 RULES.iter().chain(LATE).map(|r| r.name).collect()
2415}
2416
2417pub fn classes_built() -> BTreeMap<&'static str, &'static [ClassId]> {
2419 RULES.iter().chain(LATE).chain([&ADVANCE]).map(|r| (r.name, r.builds)).collect()
2420}