Skip to main content

logicaffeine_compile/tensor_graph/
fuse.rs

1//! The shared optimizer: one table of fusion rules over the one IR, decided once for every chip.
2//!
3//! Each rule is legal by dataflow alone — a producer's only reader, nothing between that writes what it reads — and
4//! commits only when the chip's shim runs the result whole. Rules run in a fixed order, each to a fixed point over the
5//! slots in turn, and any can be held back. A rule's result is a fused symbol defined by its parts, a part read in place
6//! (a strided access), or a result landing in rows or units of another tensor (a destination): the meaning never changes,
7//! and `tensor_graph::eval` computes the same values from the rewritten graph as from the lifted one.
8//!
9//! The rules keep the names and the order of the lane's table, so a compile answers `fired("add_norm")` as it always
10//! has, and each fused op sits at the slot the lane's launch sits at, so the launch order does not move.
11
12use 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/// A rule that fired: its name, the class it built, and the lifted ops the result computes.
24#[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/// A graph after the shared rules, every rule that fired on it, and the report of every decision.
32#[derive(Clone, Debug)]
33pub struct Optimized {
34    pub graph: TensorGraph,
35    pub fired: Vec<Fired>,
36    pub report: Report,
37}
38
39impl Optimized {
40    /// How many times `rule` fired.
41    pub fn fired(&self, rule: &str) -> usize {
42        self.fired.iter().filter(|f| f.rule == rule).count()
43    }
44}
45
46/// What became of a place a rule could fire.
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub enum Outcome {
49    Fired,
50    /// The chip does not run the result whole, and why.
51    Refused(String),
52    /// The compile held the rule back.
53    Held,
54}
55
56/// One place a rule could fire — the class it builds over the lifted ops it would compute — and what became of it.
57#[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/// Every decision a compile made, in the order the table made them: the fusions a model received on a chip, and every
66/// one it did not with the reason.
67#[derive(Clone, Debug, Default, PartialEq, Eq)]
68pub struct Report {
69    pub decisions: Vec<Decision>,
70}
71
72impl Report {
73    /// Each decision as one line: the rule, the class, the lifted ops, and what became of it.
74    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    /// The report's sha256, as 64 lowercase hex digits: what an artifact records beside its program's identity.
89    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    /// The decisions about `rule`.
94    pub fn of(&self, rule: &str) -> Vec<&Decision> {
95        self.decisions.iter().filter(|d| d.rule == rule).collect()
96    }
97}
98
99/// What a rule does.
100#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101pub enum RuleKind {
102    /// Several ops as one.
103    Group,
104    /// A part read in place, or a result written in place.
105    Place,
106}
107
108/// A chip's own rule: its name, the classes it builds, and what it does — reading the chip's own configuration.
109pub struct ChipRule<'a> {
110    pub name: &'static str,
111    pub builds: &'static [ClassId],
112    pub apply: Box<dyn Fn(&mut Rewriter) + 'a>,
113}
114
115/// One rule of the table.
116pub struct Rule {
117    pub name: &'static str,
118    pub kind: RuleKind,
119    /// The classes it builds.
120    pub builds: &'static [ClassId],
121    pub apply: fn(&mut Rewriter),
122}
123
124/// The graph being rewritten: its slots, the lifted graph's tensors, the chip's shim, what has fired, the rule running
125/// and every decision made.
126pub 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    /// The class the running rule builds, for a candidate that is a placement on a lifted op.
133    pub building: ClassId,
134    pub report: Vec<Decision>,
135}
136
137impl Rewriter<'_> {
138    /// Whether the chip runs `op` whole: the shim reads the op against the graph's tensors. A candidate it does not run is
139    /// reported refused, with the chip's reason.
140    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    /// The tensors the graph's host reads across steps: they stay whole wherever they are computed.
159    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    /// Where `apply` would fire on the graph as it stands under `shim`, without firing it: each firing reported as `then`
175    /// says — held, or refused because the chip does not link the class whole — and each refusal it meets reported as it
176    /// is.
177    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// ── Forms: what each op is, as the rules read it ───────────────────────────────────────────────────────
188
189/// A matrix product's weights: rows of a checkpoint tensor in a block format, or values.
190#[derive(Clone, Copy, Debug, PartialEq, Eq)]
191pub enum Weights {
192    Rows(crate::model::quant::GgmlType),
193    Values,
194}
195
196/// The weights of a product by `w`, when `w` is a constant whose storage says how it is read.
197pub 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
205/// A fused op's class, if any.
206pub 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/// A norm: the input it reads (in place as a part of a wider tensor, or whole), the operand it adds first, the norm it
221/// takes of that operand, whether it writes the sum on, its weight, rows and columns, epsilon, and where it lands.
222#[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
237/// The RMS norm an op is: a lifted norm without a bias, or a norm a rule built.
238pub 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            // The norm's input and the operand it adds: an add-then-norm's two summands, a sandwich's kept side and its
273            // inner norm's input.
274            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/// The elementwise op an op is: its operator, operands, the gate folded in, the row a bias is broadcast over, its length.
294#[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
320/// The activation a lifted unary op applies.
321pub 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/// A matrix product by a constant: its activation, weights and how they are stored, its bias, where it lands, and its
331/// shape (`k` in, `n` out, `m` rows).
332#[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/// A rotation: its input (whole or as a part of every unit of a wider tensor), the per-head norm folded in, its tables,
359/// its position, where it lands, and its heads (`per_row` a row), head width and rotated half.
360#[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/// A head slice, or a row of a table taken as a copy: `y[g·width + i] = x[g·every + from + i]` over `groups` groups.
406#[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
437/// The input an op reads unit by unit and its units, `(x, units, width)`, for an op that can read them as a part of
438/// every unit of a wider tensor: a norm (not of what it adds), a rotation, a per-head product — none reading in place
439/// already.
440pub 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
453/// The units an op writes its result in, `(units, width)`, when it can write them as a part of every unit of a wider
454/// tensor: a norm, a rotation, a per-head product.
455pub 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
468/// The elements a row-writing producer writes, and the row width it writes them in: a matrix product, a rotation, a
469/// mixture, a norm, a join, a per-head product.
470pub 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
494// ── Placements, carried down to the parts ──────────────────────────────────────────────────────────────
495
496/// Land `op`'s result where `dst` says — on the op, and on the part of a fused op that computes its result, so the
497/// placement survives the op being fused again.
498pub 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
507/// Read `to` with `access` wherever `op` — or any part of it — reads `from`.
508pub 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
522// ── Building fused ops ─────────────────────────────────────────────────────────────────────────────────
523
524/// The lifted ops a slot computes, as leaves: each member's in turn, in the order the members run.
525pub 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
532/// Every class `op` realizes: its own and each member's; the placements its reads and its result take — a part read in
533/// place (an attention's values read in its key rows), a result written into rows of a table or into units of a wider
534/// tensor — and the composites they make: a rotation landing in a cache's rows appends to it (`rope_kv_append`, and
535/// `qk_norm_rope_kv_append` with its per-head norm folded in), a gated delta rule norms its output gated by the SiLU of
536/// its gate (`gated_norm`), a gate and an up product of one activation run as one product (`merged_gate_up`: two
537/// products side by side of one width, or a gated pair), a token-sorted mixture gathers each expert's rows inside its
538/// product (`moe_gather_in_operand`), and a mixture with its shared expert folded in computes the router and the shared
539/// expert's gate in one op (`router_shared_gate_merge`).
540pub 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    /// Fold the ops of `gone` into the op at `at` as one op of `class` defined by those ops as they stand — a member a
585    /// rule built keeps its own class and parts — in the order their slots run, its operands `ins` (with their accesses), landing
586    /// where `dst` says, keeping every result computed inside that anything else reads; one firing of `rule`.
587    pub fn fuse(&mut self, rule: &'static str, class: ClassId, at: usize, gone: &[usize], ins: Vec<(String, Access)>, out: String, dst: Dst) {
588        // The members in the order their slots run.
589        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        // What the parts compute that something outside them reads, or the host does: it outlives the op.
595        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    /// Fold the slots of `gone` into the op at `at`, its symbol and operands unchanged: a placement, whose meaning the
608    /// op's new access or destination carries.
609    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    /// Whether the candidate `op` would run whole on the chip.
620    pub fn admits(&mut self, op: &Op) -> bool {
621        self.allows(op)
622    }
623}
624
625/// A candidate op for the shim to judge: `class` over `parts` as they stand — each a lifted op or one a rule built —
626/// so the shim reads every member as it is.
627pub 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
640// ── The rules ──────────────────────────────────────────────────────────────────────────────────────────
641
642/// A residual add whose sum an RMS norm reads first runs inside the norm, which writes the sum on for its later readers.
643fn 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
666/// A norm of a sum whose added operand is itself a plain norm's whole output, read by nothing else: the first norm runs
667/// inside the second, its operand normed in the same block. And a plain norm whose whole output is one summand of an
668/// add no norm follows, read first by the add: the add in the norm's row op, the sum its result, the norm's output
669/// written on for any op after the add that reads it.
670fn sandwich_norm(r: &mut Rewriter) {
671    sandwich_norms(r);
672    norm_then_adds(r);
673}
674
675/// A plain norm of the sum's shape, `t` its whole output, read by op `j` — and, where `later` allows it, by ops after `j`,
676/// for which the fold writes it on: its slot.
677fn 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        // The kept side keeps its access; the inner norm's input is read whole.
735        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
744/// A constant bias added to a product's every row, the add's one input, inside the product.
745fn 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
768/// A per-head RMS norm read only by a rotation runs inside the rotation.
769fn 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
792/// A part of every unit of a tensor read only by an op that reads its input unit by unit is read where it lies, the
793/// slice no op.
794fn 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
813/// A value read only by the row write(s) that put it in a table is written straight into the table's rows by the op
814/// that computes it.
815fn 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
841/// The two halves of a join, each read only by it, are written by their producers straight into their parts of every
842/// unit of the joined tensor — of the table's rows, when the join itself lands in one.
843fn 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/// A convolution: its input, weights, ring, output, channels, taps, rows, whether a SiLU and a per-head L2 norm follow
884/// inside it.
885#[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
916/// A causal convolution's SiLU, read only by it, in the convolution's epilogue.
917fn 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
938/// The per-head L2 norm after a convolution's SiLU, read only by it, in the convolution's epilogue.
939fn 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
961/// A step's causal convolutions as one op over their channels, each a convolution moved up to the first one's place only
962/// where nothing between them writes what it reads or touches what it writes; the chip says which it takes together.
963fn 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
991/// Gated DeltaNet's write strength and forget gate in one op, at the later gate's place.
992fn 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
1012/// A step's two Gated DeltaNet gates and the two value-weight products of one activation row they read, read by nothing
1013/// else — no bias, each whole, one length of `k` — as one op at the gates' place. The activation must still hold there.
1014fn 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
1048/// An activation (an output gate's sigmoid, a GeLU or SiLU gate) inside the product it gates: either factor may be the
1049/// activation's, since the product is the same with the operands swapped.
1050fn 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
1072/// An attention's whole result, read by nothing but its product with a gate's activation (a gated attention's output
1073/// gate) and unseen by the host, gated in the attention's place: the gate applied as the attention stores its output. A
1074/// batch's attentions — one a sequence, each result set whole into its row of one table that nothing but those sets and
1075/// the product touches, every row of which the product gates — are gated with their sets in the product's place: each
1076/// attention gating its row by the gate's row as it stores it, into its row of the product.
1077fn 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
1101/// Whether a gated attention's parts gate a batch's rows (`attn_output_gate`): its attentions' results set into rows of
1102/// the table its product gates, not one attention's result gated whole.
1103pub fn gates_rows(parts: &[Op]) -> bool {
1104    parts.iter().any(|p| matches!(p.prim(), Some(TensorOpSpec::SetRow(_))))
1105}
1106
1107/// Whether `op` is a lifted attention writing its whole result into a buffer of its own.
1108fn attention(op: &Op) -> bool {
1109    matches!(op.prim(), Some(TensorOpSpec::Attention(_))) && op.dst.own().is_some()
1110}
1111
1112/// The attentions and row sets that fill `table` — each of its rows set whole, once, by a literal index, from the result
1113/// of an attention only that set reads and the host does not see — when nothing but those sets writes the table and
1114/// nothing but they and slot `j` reads it, and neither the host nor a later step sees it: each attention and its set, in
1115/// the order the sets run.
1116fn 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
1142/// A gated pair: two products of one activation and the gate between them (a SwiGLU, or a gate inside its product), as
1143/// one op at the gate's place. The activation must still hold there.
1144fn 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/// A mixture of experts, its shared expert folded in or not, and its rows: the lifted mixture, one a rule built, or a
1181/// block's mixtures as one.
1182#[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
1212/// A mixture's shared SwiGLU expert, read only by its sum with the mixture, inside the mixture: its gate and up rows
1213/// beside the kept experts', its down row summed after theirs.
1214fn 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
1260/// A block's mixtures — one a row of a stacked tensor, read through a view, writing the same row of a table — as one
1261/// mixture over the rows, each routed on its own, each expert's weights read once for every row that chose it.
1262fn 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        // Every mixture alike but for its activation and destination.
1283        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
1313/// A spec's debug text without the symbols it names: what two mixtures of different tensors share.
1314fn 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
1327/// A scale of gathered rows (an embedding's `√d`) inside the gather.
1328fn 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
1351/// An attention whose values are a part of every row of a table (a latent cache's latent) reads them where they lie.
1352fn 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
1372/// A step's rotations of its one row by one table at one position — normed alike or not at all, one head width — as one
1373/// op over their heads, at the first one's place: each later rotation moves up only where nothing between them writes
1374/// what it reads, or reads or writes what it writes.
1375fn 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
1406/// The step's arg-max moves the token, the position and the generated run on.
1407fn 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
1421/// The activation, depth and rows an op projects: a product's, or a fold's whose every product reads that one activation
1422/// at that one depth — a projection with its heads' epilogues, or several — first among its inputs.
1423fn 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
1434/// A step's products of one activation's rows by matrices — each landing in a buffer of its own or a table's rows its
1435/// width wide, or folded with the epilogues of its heads — as one op over their outputs side by side, at the first one's
1436/// place. Nothing between may write the activation, or touch what a later member writes; the chip says which products
1437/// it takes together.
1438fn 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
1485/// The element count of a pointwise op computing its result whole: a lifted elementwise op over whole operands, or a
1486/// lifted activation or scale.
1487fn 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
1502/// A lifted scale of a whole tensor: its input, factor and length.
1503fn 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
1510/// An op's operands with their accesses.
1511fn operands(op: &Op) -> Vec<(String, Access)> {
1512    op.ins.iter().cloned().zip(op.access.iter().copied()).collect()
1513}
1514
1515/// The operands of `members` that none of them computes, each once, in the order the members read them.
1516fn 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
1529/// Fuse `producer` and `consumer` — the consumer reading the producer's result, which nothing else reads — into one op
1530/// of `class` at the slot `anchor` names, when the chip runs it whole.
1531fn 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
1543/// A scalar scale of a plain norm's whole result, which nothing else reads, in the norm's epilogue.
1544fn 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
1556/// A scalar scale of a product's whole result, which nothing else reads, in the product's epilogue.
1557fn 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        // A bias after the scale, a constant row added to its every row, joins the epilogue.
1570        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
1581/// A product of a whole tensor by a constant row, each column its own factor — over every row of the tensor or its one
1582/// row — as a scale: the scaled tensor and its length.
1583fn 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
1598/// A scalar scale of a product's activation, which nothing else reads, folded into the product's input.
1599fn 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
1612/// A ReLU squared: the ReLU's result, read only by the product of it with itself, inside that product.
1613fn 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
1627/// What a region member computes: `n` values of one element type, each from its operands' values at its place — a
1628/// pointwise op, or an elementwise op combining every row of its first operand with one row of its second (a bias
1629/// over a block).
1630fn 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
1641/// A region of pointwise ops of one length and element type feeding one op — some op of it joining two of the
1642/// region's results — as one op at the last one's place: each member moved down past nothing that writes what it reads
1643/// or touches what it writes, a member's result read after the region written out beside it.
1644fn 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
1689/// A run of elementwise ops of one length in consecutive slots, each carrying the one before's result as exactly one
1690/// operand — or a SiLU of it — that nothing else reads, as one op at the first one's place.
1691fn 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
1731/// A cheap pointwise op read by several pointwise ops, recomputed inside each — its result never stored — where it is
1732/// not observed or carried and each reader can take it where it stands.
1733fn 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
1780/// Two independent plain norms of one row shape, the later moved up to the earlier's place, as one op.
1781fn 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
1802/// The embedding's gathered rows and the first norm of them, the norm moved up to the gather's place; the rows stay
1803/// written for their later readers.
1804fn 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
1832/// A product of a step's one row whose whole result is one summand of a residual add and the norm of the sum — or the
1833/// input of a norm whose result is that summand (a post-norm before the add, `sandwich_norm`'s) — read by nothing else,
1834/// run in the add-and-norm's place: the projection with its post-norm, the residual and the next norm in its epilogue.
1835fn gemv_norm_epilogue(r: &mut Rewriter) {
1836    norm_epilogues(r, "gemv_norm_epilogue", ClassId::GemvNormEpilogue, |m| m == 1);
1837}
1838
1839/// [`gemv_norm_epilogue`] over a chunk's rows: the norms in the epilogue of a product of several.
1840fn prefill_norm_epilogues(r: &mut Rewriter) {
1841    norm_epilogues(r, "prefill_norm_epilogues", ClassId::PrefillNormEpilogues, |m| m > 1);
1842}
1843
1844/// A product of `rows` rows read only by an add-and-norm (or a norm-then-add), folded into it as `builds`.
1845fn 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
1865/// A product whose whole result is the input of a rotation — its per-head norm folded in or none — read by nothing else,
1866/// run in the rotation's place: the projection with the norm, the rotation and its cache append in its epilogue.
1867fn 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
1879/// Rows written into tables at one row, each write moved up to the first one's place past nothing that touches what it
1880/// reads or writes, as one op: every layer's cache insert in one launch.
1881fn 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
1912/// Whether every tensor `op` writes is read only by the slots of `within`.
1913fn 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
1917/// Fold `members` into one op of `class` at the last one's slot, its result the anchor's, when each member moves down
1918/// to the anchor past nothing that sees the difference and the chip runs the result whole.
1919fn 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
1938/// A mixture's whole result — or its sum with its shared expert's product, where the shared expert stays apart (a
1939/// chunk's token-sorted mixture) — one summand of a residual add and the norm of the sum, each read by nothing else,
1940/// combined in the add-and-norm's place: the combine folded into the next add and norm.
1941fn 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            // The mixture summed with its shared expert's product first.
1959            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
1972/// A latent attention's attended latent, read only by the product that expands it into each head's values, expanded in
1973/// the attention's place: the values un-absorbed in the attention's fold.
1974fn 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
1990/// Everything a latent attention's query and cache row are made by in its step — the projection of the layer's
1991/// activation, the latent's norm, the rotations, the cache append and the query's absorption — read by nothing but
1992/// itself and the attention, as one op before the attention.
1993fn 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        // What writes the query and the cache in this step.
2002        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        // Everything they read that is made only for them.
2007        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
2036/// A gated delta rule's inputs as its step makes them, with its rows: the convolutions of its query, key and values and
2037/// its two gates, and — where an op makes each — the products of the layer's activation those convolutions read and its
2038/// output gate is.
2039fn 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
2061/// Whether every op of `members` is read by nothing but the others and the op at `j`.
2062fn 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
2067/// A one-row gated delta rule's every input projection — query, key, values, output gate, both gates — with its
2068/// convolutions, SiLU, L2 norms and gates in the projections' epilogue, as one op.
2069fn 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
2080/// A chunk's gated delta inputs past their projections — the convolutions with their SiLU and L2 norms, and the gates —
2081/// as one op.
2082fn 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
2091/// A one-row gated delta rule's whole step between its projections — the convolutions of its query, key and values
2092/// with their SiLU and L2 norms, its two gates, the update of its memory and its gated norm — as one op.
2093fn 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
2102/// Whether an op computes each row of its result from that row of its activations alone (an attention: each query
2103/// row from its row and the whole cache; a token-sorted mixture: each row routed on its own, its views a row each),
2104/// writing no state and no rows of a table.
2105fn 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
2137/// A chunk's output row read from its last layer's result: every op of the layer's tail that only that row reads —
2138/// row by row from the attention on, each read by nothing but the tail and writing no state the step carries, so the
2139/// cache writes stay before it — computes with it, as one op at the row's place.
2140fn 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
2178/// The rotation leaf of an op that rotates normed heads — a per-head norm and a rotation, alone or with the product
2179/// before them — and its tables and position.
2180fn 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
2187/// The gate a gated attention's result is multiplied by — the activation's input in the gated product of the attention
2188/// `op` computes: the product folded into the attention's store, which gates its first operand, or the op's one reader.
2189fn 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
2200/// A gated attention's gate — a product of the activation its query is projected from, read after the attention — in the
2201/// op that projects, norms and rotates the query: the split, the norms, the partial rotation and the gate as one op.
2202fn 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
2221/// A layer's normed rotations by one table at one position — its query and key branches, one inserting its row into the
2222/// cache — as one op over the row, the later moved up to the first one's place.
2223fn 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
2249/// The rule table, in the order the rules apply.
2250pub 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
2295/// The rule that always closes the table: the arg-max that picks the next token moves the step on.
2296pub const ADVANCE: Rule = Rule { name: "argmax_advance", kind: RuleKind::Group, builds: &[ClassId::FusedSampling], apply: argmax_advance };
2297
2298/// The rules that apply once every product's activation is quantized where it will be read.
2299pub const LATE: &[Rule] = &[Rule { name: "merged_projections", kind: RuleKind::Group, builds: &[ClassId::MergedQkv], apply: merged_projections }];
2300
2301/// The lifted ops with every row of a computed table taken at a literal index read in place — a view into the table —
2302/// unless the table is written in place before the row's last reader, where the row is copied where the program takes
2303/// it.
2304fn 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
2332/// `g` with the shared rules but those `held` applied under `shim`, every firing, and the report of every decision. A
2333/// rule held, or one building a class the chip does not link whole, is run on a copy of the graph to report where it
2334/// would have fired.
2335pub fn optimize(g: &TensorGraph, shim: &dyn Shim, held: &[&str]) -> Optimized {
2336    // A row of a computed table read in place is a part read in place: only a chip that links one takes the view.
2337    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
2390/// Every step a compile under `shim` can hold, in the order it runs them: each shared rule with the chip's own rules
2391/// before and after it, the numerics fold (`fused_quant`, a producer taking the quantize of its result), the late rules
2392/// with theirs, then the chip's closing rules. Holding every step from one on leaves the graph as the steps before it
2393/// make it.
2394pub 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
2412/// The names of the shared rules a compile can hold back, in the order they apply.
2413pub fn rule_names() -> Vec<&'static str> {
2414    RULES.iter().chain(LATE).map(|r| r.name).collect()
2415}
2416
2417/// The classes the table builds, by rule.
2418pub fn classes_built() -> BTreeMap<&'static str, &'static [ClassId]> {
2419    RULES.iter().chain(LATE).chain([&ADVANCE]).map(|r| (r.name, r.builds)).collect()
2420}