Skip to main content

logicaffeine_compile/tensor_recognize/
qwen.rs

1//! The Qwen-family recognizers — the ops beyond matmul / elementwise / fused-row / swiglu that an authored
2//! transformer forward is made of, each lifted from the EXACT statement shape its parser sugar desugars to
3//! (`parser/mod.rs`, the `desugar_tensor_*` family) into a typed spec.
4//!
5//! Shared the way [`super`] is: the CUDA orchestrator's stage walk adapts these specs into its tuples, and
6//! the spatial front end's whole-program walk composes them. Recognition is structural — no markers, no
7//! naming conventions — and every recognizer here is keyed on its own sugar's body, so a composite block is
8//! never claimed as one of its parts and a neighbouring sugar with the same skeleton (silu / gelu, rope /
9//! transpose) is told apart by the sub-expression that differs. Each answers `None` for anything else.
10
11use std::collections::{HashMap, HashSet};
12
13use logicaffeine_language::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt};
14
15use super::{as_binop, as_ident, as_index, Epsilon, Factor};
16use crate::intern::{Interner, Symbol};
17
18/// `Qr = rope(Q, cos, sin)` — RoPE-NEOX over `h` heads of `hd` dims, rotating each head's leading `2·half`
19/// dims as `(i, i+half)` pairs and passing the remaining `hd − 2·half` through (Qwen3.8's partial rotary;
20/// `hd == 2·half` is the classic full form). `hd` is read off the pass-through loop's bound.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct RopeSpec {
23    pub q: Symbol,
24    pub rc: Symbol,
25    pub rs: Symbol,
26    pub out: Symbol,
27    pub h: usize,
28    pub half: usize,
29    pub hd: usize,
30    /// The tables' rows: 1 — every head turns by the one row of angles — or one per row of `Q`, head `v` turning by
31    /// table row `v / (h / rows)`. With [`from`](Self::from), the rows of `Q`.
32    pub rows: usize,
33    /// `from position P`: the tables are a cache of angles for this many positions, and row `r` of `Q` (0-based) turns
34    /// by cache row `P + r` (1-based) — `P` checked to keep the block's rows among the cache's.
35    pub from: Option<(RowIndex, usize)>,
36}
37
38/// `Y = silu(X)` — `x / (1 + e^-x)` over `n` entries.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct SiluSpec {
41    pub x: Symbol,
42    pub out: Symbol,
43    pub n: usize,
44}
45
46/// `Y = sigmoid(X)` — `1 / (1 + e^-x)` over `n` entries.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct SigmoidSpec {
49    pub x: Symbol,
50    pub out: Symbol,
51    pub n: usize,
52}
53
54/// `Y = gelu(X)` — ggml's tanh approximation in its one-exponential form `x / (1 + e^-2z)`,
55/// `z = √(2/π)·(x + 0.044715·x³)`, over `n` entries.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct GeluSpec {
58    pub x: Symbol,
59    pub out: Symbol,
60    pub n: usize,
61}
62
63/// `Y = X scaled by C` — `x·C` over `n` entries.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct ScaleSpec {
66    pub x: Symbol,
67    pub out: Symbol,
68    pub n: usize,
69    pub factor: Factor,
70}
71
72/// `Y = X capped at C` — `C·tanh(x/C)` over `n` entries (Gemma 2's logits).
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct SoftcapSpec {
75    pub x: Symbol,
76    pub out: Symbol,
77    pub n: usize,
78    pub cap: Factor,
79}
80
81/// `A = decay(Ar, Dt, Alog)` — Gated DeltaNet's per-head forget gate `(1 + e^(Ar+Dt))^Alog` over `n` heads.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct DecaySpec {
84    pub ar: Symbol,
85    pub dt: Symbol,
86    pub alog: Symbol,
87    pub out: Symbol,
88    pub n: usize,
89    /// The rows of `Ar`: 1, or a block of rows decayed by one bias and rate per head, `n / rows` heads to a row.
90    pub rows: usize,
91}
92
93/// `Y = l2norm(X)` — per-row `X / sqrt(Σ X² + eps)` over a `rows × cols` view (the `across H heads` form is
94/// `rows == H`, `cols == |X| / H`), with the program's epsilon.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct L2NormSpec {
97    pub x: Symbol,
98    pub out: Symbol,
99    pub rows: usize,
100    pub cols: usize,
101    pub eps: Epsilon,
102}
103
104/// Where a row sugar's row comes from: a NAMED scalar (the data-indexed case — a token id, a cache position,
105/// an arg-max) or a literal. An arbitrary expression is not lifted; the walk lists the statement instead.
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum RowIndex {
108    Named(Symbol),
109    Literal(i64),
110}
111
112/// `x = row i of T` — the embedding lookup: row `i` (1-based) of a `rows × cols` table, as `1 × cols`.
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct RowGatherSpec {
115    pub table: Symbol,
116    pub index: RowIndex,
117    pub out: Symbol,
118    pub rows: usize,
119    pub cols: usize,
120}
121
122/// `X = rows I of T` — the block gather: row `k` of `X` is row `I[k]` (1-based) of a `rows × cols` table, for each of
123/// `I`'s `count` indices.
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct RowsGatherSpec {
126    pub table: Symbol,
127    pub indices: Symbol,
128    pub out: Symbol,
129    pub rows: usize,
130    pub cols: usize,
131    pub count: usize,
132}
133
134/// `t = argmax of X` — the 1-based index of the first maximal element of `X`'s `n` elements, an Int.
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct ArgMaxSpec {
137    pub x: Symbol,
138    pub out: Symbol,
139    pub n: usize,
140}
141
142/// `Set row i of T to S` — the KV-cache append: row `i` (1-based) of a `rows × cols` table overwritten by
143/// the `cols` elements of `S`. `T` is read AND written: carried state.
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct SetRowSpec {
146    pub table: Symbol,
147    pub index: RowIndex,
148    pub source: Symbol,
149    pub rows: usize,
150    pub cols: usize,
151    /// The rows written: 1 for `Set row`, `R` for the block `Set rows from P`, rows `P … P + R − 1`.
152    pub count: usize,
153}
154
155/// `C = full attention of Q over K and V with H heads` — bidirectional multi-head self-attention over `t`
156/// positions; `Q`/`K`/`V`/`C` are `t × (h·hd)`. Every query row attends to every position, unmasked.
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct FullAttentionSpec {
159    pub q: Symbol,
160    pub k: Symbol,
161    pub v: Symbol,
162    pub out: Symbol,
163    pub h: usize,
164    pub hd: usize,
165    pub t: usize,
166}
167
168/// `Kt = transpose(X)` — an `rows × cols` tensor becomes `cols × rows` (`Kt[j·rows + i] = X[i·cols + j]`).
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct TransposeSpec {
171    pub x: Symbol,
172    pub out: Symbol,
173    pub rows: usize,
174    pub cols: usize,
175}
176
177/// `P = the first K of every N of X` / `the last K of every N of X` — one part of every group of `every` consecutive
178/// elements of `X`'s `n`: the `width` elements from offset `from` of each group, in order (`P[g·width + i] =
179/// X[g·every + from + i]`). A head's part of a row of heads, or a part of every cached row.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct HeadSliceSpec {
182    pub x: Symbol,
183    pub out: Symbol,
184    pub every: usize,
185    pub from: usize,
186    pub width: usize,
187    pub n: usize,
188}
189
190/// `J = A joined with B [across H heads]` — `rows` rows of `heads` heads, each head's part of `A` then its part of `B`:
191/// `A` is `wa` wide and `B` `wb` wide per row, a head's parts `wa / heads` and `wb / heads` wide. One head joins the rows.
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct JoinSpec {
194    pub a: Symbol,
195    pub b: Symbol,
196    pub out: Symbol,
197    pub rows: usize,
198    pub heads: usize,
199    pub wa: usize,
200    pub wb: usize,
201}
202
203/// `Y = causal conv(X, WC, CS)` — the depthwise causal conv1d over `chan` channels with a `taps`-deep decode
204/// ring `CS` (shifted up, appended with `X`, reduced against `WC`). `CS` is read AND written: carried state.
205#[derive(Debug, Clone, PartialEq, Eq)]
206pub struct CausalConvSpec {
207    pub x: Symbol,
208    pub wc: Symbol,
209    pub cs: Symbol,
210    pub out: Symbol,
211    pub taps: usize,
212    pub chan: usize,
213    /// The rows of `X`: 1, or a block of rows entering the ring one after another.
214    pub rows: usize,
215}
216
217/// `O = gated delta(Q, K, V, Z, B, A, S, GN)` — the Gated DeltaNet step over `hv` value heads reading `hk`
218/// key heads at head dim `d`: `u = S·k`, `S ← α·S + β·(v − α·u)·kᵀ`, `o = S·q`, RMSNorm'd, scaled by `GN`,
219/// gated by `silu(z)`, the norm taking the program's epsilon. `S` (`st`) is read AND written: the carried memory.
220#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct GatedDeltaSpec {
222    pub q: Symbol,
223    pub k: Symbol,
224    pub v: Symbol,
225    pub z: Symbol,
226    pub b: Symbol,
227    pub a: Symbol,
228    pub st: Symbol,
229    pub gn: Symbol,
230    pub out: Symbol,
231    pub hv: usize,
232    pub hk: usize,
233    pub d: usize,
234    /// The rows: 1, or a block of tokens each taking the step in turn against the one memory.
235    pub rows: usize,
236    /// The epsilon of the gated output norm.
237    pub eps: Epsilon,
238    /// What gates the normed head: `silu(z)` (`with gate Z`) or `σ(z)` (`with sigmoid gate Z`).
239    pub gate: GateAct,
240}
241
242/// The activation of a Gated DeltaNet's output gate.
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
244pub enum GateAct {
245    Silu,
246    Sigmoid,
247}
248
249/// The positions of the `n` an attention query attends that it keeps.
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
251pub enum Window {
252    /// The last `W`: `max(1, n − W + 1) ..= n` (`within a window of W`).
253    Sliding(usize),
254    /// Those of the query's own chunk of `C`, Llama 4's chunked-local attention: `⌊(n − 1)/C⌋·C + 1 ..= n`
255    /// (`within chunks of C`).
256    Chunked(usize),
257}
258
259impl Window {
260    /// The first position kept, 1-based, by a query attending `n` positions.
261    pub fn first(self, n: usize) -> usize {
262        match self {
263            Window::Sliding(w) => n.saturating_sub(w - 1).max(1),
264            Window::Chunked(c) => (n - 1) / c * c + 1,
265        }
266    }
267
268    /// The most positions a query keeps.
269    pub fn span(self) -> usize {
270        match self {
271            Window::Sliding(w) | Window::Chunked(w) => w,
272        }
273    }
274
275    /// The clause the language writes it as, after an attention's length.
276    pub fn clause(self) -> String {
277        match self {
278            Window::Sliding(w) => format!(" within a window of {w}"),
279            Window::Chunked(c) => format!(" within chunks of {c}"),
280        }
281    }
282}
283
284/// `ctx = attention(q over kc, vc)` — single-query multi-head GQA attention: `q` is `1 × (h·hd)`, `kc` is `l × (kv·hd)`
285/// and `vc` `l × (kv·vd)` (the cached keys/values), query head `g` attends to kv head `g / (h/kv)` and writes `vd`
286/// values. `pos` is the position bound of the masked `up to P` form (the KV cache as carried state); `None` is the
287/// unmasked form that attends to every cached row.
288#[derive(Debug, Clone, PartialEq, Eq)]
289pub struct AttentionSpec {
290    pub q: Symbol,
291    pub kc: Symbol,
292    pub vc: Symbol,
293    pub out: Symbol,
294    pub h: usize,
295    pub kv: usize,
296    pub hd: usize,
297    /// The value head width: `hd`, or narrower (`V` holding `kv·vd` columns).
298    pub vd: usize,
299    /// The score scale a program gives (`scaled by S`); `None` is the default `1/√hd`, which a given scale equal to
300    /// it also lifts as.
301    pub scale: Option<Factor>,
302    /// The `capped at C` form's `C`: every scaled score `s` taken as `C·tanh(s/C)` before the softmax (Gemma 2).
303    pub softcap: Option<Factor>,
304    pub l: usize,
305    /// The masked `up to P` form's bound: attend over the first `P` of the `l` cached positions. For a block of `rows`
306    /// query rows (`causal attention … from P`) it is the FIRST row's: row `r` (0-based) attends `P + r` positions.
307    pub pos: Option<RowIndex>,
308    /// Query rows: 1 for the single-query form, `T` for a causal block.
309    pub rows: usize,
310    /// The positions a query keeps of the `n` it attends: the last `W` (`within a window of W`), or those of its own chunk
311    /// of `C` (`within chunks of C`).
312    pub window: Option<Window>,
313    /// The `with sinks S` form's `S`: a logit per query head joining its softmax, taking no value.
314    pub sinks: Option<Symbol>,
315}
316
317/// A recognized **Mixture-of-Experts FFN** — `Let out be moe of x with router wr gate wg up wu down wd
318/// choosing K of E`. Lifted from the desugared block the SAME way `recognize_matmul`/`recognize_rope` lift
319/// theirs — purely structural, no markers, no naming conventions: the OPERAND tensors are the indexed reads
320/// NOT `Let`-bound inside the block (the temps are), and the dims fall straight out of the weight index
321/// arithmetic (`wr[(t-1)·E+e]` yields E; `wg[((e-1)·dff+(o-1))·d+t]` yields d and dff); `out` is the tensor the
322/// weighted accumulation writes.
323#[derive(Debug, Clone, PartialEq, Eq)]
324pub struct MoeSpec {
325    pub out: Symbol,
326    pub x: Symbol,
327    pub wr: Symbol,
328    pub wg: Symbol,
329    pub wu: Symbol,
330    pub wd: Symbol,
331    pub d: usize,
332    pub dff: usize,
333    pub ne: usize,
334    pub k: usize,
335    /// The router's bias, added to each logit before the softmax (`router Wr plus Br`).
336    pub router_bias: Option<Symbol>,
337    /// The experts' gate, up and down biases (`gate Wg plus Bg`, …), each a row per expert.
338    pub biases: [Option<Symbol>; 3],
339    /// The kept experts' weights are renormalized to sum to one; `false` (`without renormalizing`) weighs each by its
340    /// softmax over every expert.
341    pub renormalize: bool,
342    /// The experts' gated activation.
343    pub act: MoeAct,
344    /// How the experts are scored: the softmax over every logit, or each logit's own sigmoid.
345    pub score: MoeScore,
346    /// A kept expert's weight scales the input it reads, and its output joins the sum unweighted (Llama 4).
347    pub weigh_inputs: bool,
348    /// The selection bias (`biased by B`): each expert chosen by its score plus `B[e]`, weighed by its score alone.
349    pub selection_bias: Option<Symbol>,
350    /// The group limit (`within the top G of N groups`).
351    pub groups: Option<MoeGroups>,
352    /// The routed scale (`scaled by S`) as its bits: every kept weight times `S`, after the renormalization.
353    pub scale: Option<u64>,
354}
355
356/// A mixture's experts in `count` groups of `ne / count`, each scored by the sum of its two best selection scores (its
357/// one where a group holds one expert), the experts chosen only among the top `kept` groups.
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
359pub struct MoeGroups {
360    pub count: usize,
361    pub kept: usize,
362}
363
364impl MoeSpec {
365    /// The routed scale, one when the mixture has none.
366    pub fn scale(&self) -> f64 {
367        self.scale.map_or(1.0, f64::from_bits)
368    }
369
370    /// Whether the router is a softmax over every expert weighting the kept experts' outputs — chosen by their scores
371    /// alone, among every expert, unscaled: the routing a lowering that knows no other computes.
372    pub fn weighs_outputs_by_softmax(&self) -> bool {
373        self.score == MoeScore::Softmax && self.selection_bias.is_none() && self.groups.is_none() && self.scale.is_none() && !self.weigh_inputs
374    }
375
376    /// Whether the renormalizing sum's clamp at 2⁻¹⁴ can bind: a softmax's kept weights sum to at least `K/E`, so only a
377    /// sigmoid's — or a softmax over 16,384 experts or more — can fall below it.
378    pub fn sum_can_clamp(&self) -> bool {
379        self.renormalize && (self.score == MoeScore::Sigmoid || self.ne >= 1 << 14)
380    }
381}
382
383/// A mixture's gated activation: SwiGLU, or gpt-oss's SwiGLU with the gate clamped to at most `limit`, the up to
384/// `[−limit, limit]`, and the sigmoid sharpened by `slope` (both kept as their bits, so the spec stays `Eq`).
385#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
386pub enum MoeAct {
387    Swiglu,
388    ClampedSwiglu { limit: u64, slope: u64 },
389}
390
391/// The activation a unary elementwise map applies: the three sugars that share the one-loop `Push f(X[i])`
392/// skeleton and differ only in `f`.
393#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
394pub enum Activation {
395    Silu,
396    Sigmoid,
397    Gelu,
398}
399
400impl Activation {
401    /// `x · σ(u(x))` — the input itself gated by a sigmoid of some function of it (silu: `u = x`; gelu:
402    /// `u = 2z`). Sigmoid is the gate alone.
403    pub fn is_self_gated(self) -> bool {
404        matches!(self, Activation::Silu | Activation::Gelu)
405    }
406}
407
408/// The one-loop unary map `Y[i] = f(X[i])` over `n` entries, with `f` classified exactly.
409#[derive(Debug, Clone, PartialEq, Eq)]
410pub struct UnaryMapSpec {
411    pub act: Activation,
412    pub x: Symbol,
413    pub out: Symbol,
414    pub n: usize,
415}
416
417const SQRT_2_OVER_PI: f64 = 0.797_884_560_802_865_4;
418const GELU_K: f64 = 0.044_715;
419
420// ── expression and statement views ─────────────────────────────────────────────────────────────────────
421
422fn as_int(e: &Expr) -> Option<i64> {
423    match e {
424        Expr::Literal(Literal::Number(n)) => Some(*n),
425        _ => None,
426    }
427}
428
429fn as_float(e: &Expr) -> Option<f64> {
430    match e {
431        Expr::Literal(Literal::Float(f)) => Some(*f),
432        _ => None,
433    }
434}
435
436fn is_float(e: &Expr, want: f64) -> bool {
437    as_float(e)
438        .map(|f| (f - want).abs() <= 1e-12 * want.abs().max(1.0))
439        .unwrap_or(false)
440}
441
442fn is_e(e: &Expr) -> bool {
443    is_float(e, std::f64::consts::E)
444}
445
446fn is_int(e: &Expr, want: i64) -> bool {
447    as_int(e) == Some(want)
448}
449
450fn is_new(e: &Expr) -> bool {
451    matches!(e, Expr::New { .. })
452}
453
454fn is_ident(e: &Expr, s: Symbol) -> bool {
455    as_ident(e) == Some(s)
456}
457
458fn want(ok: bool) -> Option<()> {
459    ok.then_some(())
460}
461
462/// `coll[idx]` with a named collection.
463fn indexed<'a>(e: &'a Expr<'a>) -> Option<(Symbol, &'a Expr<'a>)> {
464    let (c, i) = as_index(e)?;
465    Some((as_ident(c)?, i))
466}
467
468/// `X[iv]` — a named collection read at exactly the loop variable.
469fn read_at(e: &Expr, iv: Symbol) -> Option<Symbol> {
470    let (c, i) = indexed(e)?;
471    is_ident(i, iv).then_some(c)
472}
473
474/// `X[i]·X[i]` — a square of one element read.
475fn square_of(e: &Expr) -> Option<Symbol> {
476    let (l, r) = as_binop(e, BinaryOpKind::Multiply)?;
477    let (x, _) = indexed(l)?;
478    let (y, _) = indexed(r)?;
479    (x == y).then_some(x)
480}
481
482/// A counted loop: its variable, its literal bound, whether the bound is inclusive (`<=`), and its body.
483struct Loop<'a> {
484    iv: Symbol,
485    bound: i64,
486    inclusive: bool,
487    body: &'a [Stmt<'a>],
488}
489
490fn as_loop<'a>(s: &'a Stmt<'a>) -> Option<Loop<'a>> {
491    let Stmt::While { cond, body, .. } = s else {
492        return None;
493    };
494    let Expr::BinaryOp { op, left, right } = *cond else {
495        return None;
496    };
497    let iv = as_ident(left)?;
498    let bound = as_int(right)?;
499    let inclusive = match op {
500        BinaryOpKind::LtEq => true,
501        BinaryOpKind::Lt => false,
502        _ => return None,
503    };
504    Some(Loop {
505        iv,
506        bound,
507        inclusive,
508        body: *body,
509    })
510}
511
512/// A sequential reader over a statement list — each desugar lays its statements out in a fixed order, and a
513/// matcher consumes them in that order, refusing at the first statement that is not the expected one.
514#[derive(Clone)]
515struct Cursor<'a> {
516    stmts: &'a [Stmt<'a>],
517    at: usize,
518}
519
520impl<'a> Cursor<'a> {
521    fn new(stmts: &'a [Stmt<'a>], at: usize) -> Self {
522        Cursor { stmts, at }
523    }
524    fn next(&mut self) -> Option<&'a Stmt<'a>> {
525        let s = self.stmts.get(self.at)?;
526        self.at += 1;
527        Some(s)
528    }
529    /// `Let var be <value>`.
530    fn let_(&mut self) -> Option<(Symbol, &'a Expr<'a>)> {
531        match self.next()? {
532            Stmt::Let { var, value, .. } => Some((*var, *value)),
533            _ => None,
534        }
535    }
536    /// `Let var be <int literal>`.
537    fn int_let(&mut self, value: i64) -> Option<Symbol> {
538        let (var, v) = self.let_()?;
539        is_int(v, value).then_some(var)
540    }
541    /// `Let var be <float literal>`.
542    fn float_let(&mut self, value: f64) -> Option<Symbol> {
543        let (var, v) = self.let_()?;
544        is_float(v, value).then_some(var)
545    }
546    /// `Let var be a new Seq`.
547    fn new_let(&mut self) -> Option<Symbol> {
548        let (var, v) = self.let_()?;
549        is_new(v).then_some(var)
550    }
551    /// A counted loop over `iv` with the given bound form.
552    fn loop_(&mut self, iv: Symbol, inclusive: bool) -> Option<Loop<'a>> {
553        let l = as_loop(self.next()?)?;
554        (l.iv == iv && l.inclusive == inclusive).then_some(l)
555    }
556    /// `Push <value> to <collection>`.
557    fn push(&mut self) -> Option<(&'a Expr<'a>, Symbol)> {
558        match self.next()? {
559            Stmt::Push { value, collection } => Some((*value, as_ident(collection)?)),
560            _ => None,
561        }
562    }
563    /// `Set target to <value>`.
564    fn set(&mut self) -> Option<(Symbol, &'a Expr<'a>)> {
565        match self.next()? {
566            Stmt::Set { target, value } => Some((*target, *value)),
567            _ => None,
568        }
569    }
570    /// `Set item <index> of <collection> to <value>`.
571    fn set_index(&mut self) -> Option<(Symbol, &'a Expr<'a>, &'a Expr<'a>)> {
572        match self.next()? {
573            Stmt::SetIndex {
574                collection,
575                index,
576                value,
577            } => Some((as_ident(collection)?, *index, *value)),
578            _ => None,
579        }
580    }
581}
582
583/// Every `Let var be a new Seq` in a statement list — the binder each tensor desugar opens with — as
584/// `(position, var)`; a matcher reads the sugar's body from the statement after it.
585fn anchors<'a>(body: &'a [Stmt<'a>]) -> impl Iterator<Item = (usize, Symbol)> + 'a {
586    body.iter().enumerate().filter_map(|(i, s)| match s {
587        Stmt::Let { var, value, .. } if is_new(value) => Some((i, *var)),
588        _ => None,
589    })
590}
591
592/// Try `f` on the statement list and on every nested block (through `Splice`, loops and branches), first
593/// match wins — so a recognizer accepts a whole program, one top-level statement, or a bare desugar body.
594fn find_in_bodies<'a, T, F>(stmts: &'a [Stmt<'a>], f: F) -> Option<T>
595where
596    F: Fn(&'a [Stmt<'a>]) -> Option<T> + Copy,
597{
598    if let Some(t) = f(stmts) {
599        return Some(t);
600    }
601    for s in stmts {
602        let found = match s {
603            Stmt::Splice { body } | Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
604                find_in_bodies(body, f)
605            }
606            Stmt::If {
607                then_block,
608                else_block,
609                ..
610            } => find_in_bodies(then_block, f)
611                .or_else(|| else_block.and_then(|eb| find_in_bodies(eb, f))),
612            _ => None,
613        };
614        if found.is_some() {
615            return found;
616        }
617    }
618    None
619}
620
621// ── the one-loop unary maps: silu / sigmoid / gelu ─────────────────────────────────────────────────────
622
623/// `1 + E^(0 − X[iv])` → `X`.
624fn sigmoid_denominator(e: &Expr, iv: Symbol) -> Option<Symbol> {
625    let (one, pow) = as_binop(e, BinaryOpKind::Add)?;
626    want(is_float(one, 1.0))?;
627    let (base, expo) = as_binop(pow, BinaryOpKind::Pow)?;
628    want(is_e(base))?;
629    let (zero, xi) = as_binop(expo, BinaryOpKind::Subtract)?;
630    want(is_float(zero, 0.0))?;
631    read_at(xi, iv)
632}
633
634/// `1 + E^(0 − 2·z)` with `z = √(2/π)·(X[iv] + 0.044715·(X[iv]·(X[iv]·X[iv])))` → `X`.
635fn gelu_denominator(e: &Expr, iv: Symbol) -> Option<Symbol> {
636    let (one, pow) = as_binop(e, BinaryOpKind::Add)?;
637    want(is_float(one, 1.0))?;
638    let (base, expo) = as_binop(pow, BinaryOpKind::Pow)?;
639    want(is_e(base))?;
640    let (zero, two_z) = as_binop(expo, BinaryOpKind::Subtract)?;
641    want(is_float(zero, 0.0))?;
642    let (two, z) = as_binop(two_z, BinaryOpKind::Multiply)?;
643    want(is_float(two, 2.0))?;
644    let (c, inner) = as_binop(z, BinaryOpKind::Multiply)?;
645    want(is_float(c, SQRT_2_OVER_PI))?;
646    let (x1, kx3) = as_binop(inner, BinaryOpKind::Add)?;
647    let x = read_at(x1, iv)?;
648    let (k, x3) = as_binop(kx3, BinaryOpKind::Multiply)?;
649    want(is_float(k, GELU_K))?;
650    let (xa, xx) = as_binop(x3, BinaryOpKind::Multiply)?;
651    let (xb, xc) = as_binop(xx, BinaryOpKind::Multiply)?;
652    want(read_at(xa, iv) == Some(x) && read_at(xb, iv) == Some(x) && read_at(xc, iv) == Some(x))?;
653    Some(x)
654}
655
656/// The pushed value of a unary map, classified: `X[iv] / (1 + e^-X[iv])` (silu), `1 / (1 + e^-X[iv])`
657/// (sigmoid), `X[iv] / (1 + e^-2z)` (gelu).
658fn classify_unary(value: &Expr, iv: Symbol) -> Option<(Activation, Symbol)> {
659    let (num, den) = as_binop(value, BinaryOpKind::Divide)?;
660    if is_float(num, 1.0) {
661        return sigmoid_denominator(den, iv).map(|x| (Activation::Sigmoid, x));
662    }
663    let x = read_at(num, iv)?;
664    if sigmoid_denominator(den, iv) == Some(x) {
665        return Some((Activation::Silu, x));
666    }
667    if gelu_denominator(den, iv) == Some(x) {
668        return Some((Activation::Gelu, x));
669    }
670    None
671}
672
673fn match_unary_map<'a>(body: &'a [Stmt<'a>]) -> Option<UnaryMapSpec> {
674    for (i, out) in anchors(body) {
675        let mut c = Cursor::new(body, i + 1);
676        let Some(iv) = c.int_let(1) else { continue };
677        let Some(l) = c.loop_(iv, true) else { continue };
678        let Some((value, into)) = Cursor::new(l.body, 0).push() else {
679            continue;
680        };
681        if into != out {
682            continue;
683        }
684        if let Some((act, x)) = classify_unary(value, iv) {
685            return Some(UnaryMapSpec {
686                act,
687                x,
688                out,
689                n: l.bound as usize,
690            });
691        }
692    }
693    None
694}
695
696/// Recognize the one-loop unary map `Let Y be silu/sigmoid/gelu of X` anywhere in the statement list.
697pub fn recognize_unary_map(stmts: &[Stmt]) -> Option<UnaryMapSpec> {
698    find_in_bodies(stmts, match_unary_map)
699}
700
701/// Recognize `Let Y be silu of X` — `Push X[i] / (1 + E^(0 − X[i]))` in one counted loop.
702pub fn recognize_silu(stmts: &[Stmt]) -> Option<SiluSpec> {
703    recognize_unary_map(stmts)
704        .filter(|m| m.act == Activation::Silu)
705        .map(|m| SiluSpec {
706            x: m.x,
707            out: m.out,
708            n: m.n,
709        })
710}
711
712/// Recognize `Let Y be sigmoid of X` — `Push 1 / (1 + E^(0 − X[i]))` in one counted loop.
713pub fn recognize_sigmoid(stmts: &[Stmt]) -> Option<SigmoidSpec> {
714    recognize_unary_map(stmts)
715        .filter(|m| m.act == Activation::Sigmoid)
716        .map(|m| SigmoidSpec {
717            x: m.x,
718            out: m.out,
719            n: m.n,
720        })
721}
722
723/// Recognize `Let Y be gelu of X` — `Push X[i] / (1 + E^(0 − 2z))` with the 0.044715 cubic in `z`.
724pub fn recognize_gelu(stmts: &[Stmt]) -> Option<GeluSpec> {
725    recognize_unary_map(stmts)
726        .filter(|m| m.act == Activation::Gelu)
727        .map(|m| GeluSpec {
728            x: m.x,
729            out: m.out,
730            n: m.n,
731        })
732}
733
734/// `x` soft-capped at `c` as the sugar writes it (`capped at C`): `c·(1 − 2/(E^(x·(2/c)) + 1))`. The capped expression
735/// and the cap.
736fn as_capped<'a>(e: &'a Expr<'a>) -> Option<(&'a Expr<'a>, f64)> {
737    let (c, rest) = as_binop(e, BinaryOpKind::Multiply)?;
738    let c = as_float(c).filter(|c| c.is_finite() && *c > 0.0)?;
739    let (one, fraction) = as_binop(rest, BinaryOpKind::Subtract)?;
740    let (two, den) = as_binop(fraction, BinaryOpKind::Divide)?;
741    let (pow, one_more) = as_binop(den, BinaryOpKind::Add)?;
742    let (base, exponent) = as_binop(pow, BinaryOpKind::Pow)?;
743    let (x, k) = as_binop(exponent, BinaryOpKind::Multiply)?;
744    let exact = |e: &Expr, v: f64| as_float(e).is_some_and(|f| f.to_bits() == v.to_bits());
745    (exact(one, 1.0) && exact(two, 2.0) && exact(one_more, 1.0) && exact(base, std::f64::consts::E) && exact(k, 2.0 / c))
746        .then_some((x, c))
747}
748
749/// The one-loop `Let Y be X capped at C` — `Push` of `X[i]` capped, [`as_capped`]'s form.
750fn match_softcap<'a>(body: &'a [Stmt<'a>]) -> Option<SoftcapSpec> {
751    for (i, out) in anchors(body) {
752        let mut c = Cursor::new(body, i + 1);
753        let Some(iv) = c.int_let(1) else { continue };
754        let Some(l) = c.loop_(iv, true) else { continue };
755        let Some((value, into)) = Cursor::new(l.body, 0).push() else {
756            continue;
757        };
758        if into != out {
759            continue;
760        }
761        let Some((xi, cap)) = as_capped(value) else { continue };
762        if let Some(x) = read_at(xi, iv) {
763            return Some(SoftcapSpec { x, out, n: l.bound as usize, cap: Factor::new(cap) });
764        }
765    }
766    None
767}
768
769/// Recognize `Let Y be X capped at C` anywhere in the statement list.
770pub fn recognize_softcap(stmts: &[Stmt]) -> Option<SoftcapSpec> {
771    find_in_bodies(stmts, match_softcap)
772}
773
774/// The one-loop `Let Y be X scaled by C` — `Push X[i] · C`, `C` a number literal.
775fn match_scale<'a>(body: &'a [Stmt<'a>]) -> Option<ScaleSpec> {
776    for (i, out) in anchors(body) {
777        let mut c = Cursor::new(body, i + 1);
778        let Some(iv) = c.int_let(1) else { continue };
779        let Some(l) = c.loop_(iv, true) else { continue };
780        let Some((value, into)) = Cursor::new(l.body, 0).push() else {
781            continue;
782        };
783        if into != out {
784            continue;
785        }
786        let Some((xi, factor)) = as_binop(value, BinaryOpKind::Multiply) else { continue };
787        if let (Some(x), Some(factor)) = (read_at(xi, iv), as_float(factor)) {
788            return Some(ScaleSpec { x, out, n: l.bound as usize, factor: Factor::new(factor) });
789        }
790    }
791    None
792}
793
794/// Recognize `Let Y be X scaled by C` anywhere in the statement list.
795pub fn recognize_scale(stmts: &[Stmt]) -> Option<ScaleSpec> {
796    find_in_bodies(stmts, match_scale)
797}
798
799// ── the integer maps: requant / clamp / relu ───────────────────────────────────────────────────────────
800
801/// `Y = X times M shifted right by S` — an int8 model's requant `(x · M) >> S` over `n` Int entries, an arithmetic
802/// shift of the exact product.
803#[derive(Debug, Clone, PartialEq, Eq)]
804pub struct IntRequantSpec {
805    pub x: Symbol,
806    pub out: Symbol,
807    pub n: usize,
808    pub m: i64,
809    pub s: u32,
810}
811
812/// `Y = X clamped between LO and HI` — saturation, `min(max(x, LO), HI)` over `n` Int entries, `LO ≤ HI`.
813#[derive(Debug, Clone, PartialEq, Eq)]
814pub struct IntClampSpec {
815    pub x: Symbol,
816    pub out: Symbol,
817    pub n: usize,
818    pub lo: i64,
819    pub hi: i64,
820}
821
822/// `Y = relu of X` — `max(x, 0)` over `n` entries, in `X`'s element kind (`int`).
823#[derive(Debug, Clone, PartialEq, Eq)]
824pub struct ReluSpec {
825    pub x: Symbol,
826    pub out: Symbol,
827    pub n: usize,
828    pub int: bool,
829}
830
831/// The integer map a one-loop push applies.
832enum IntMap {
833    Requant { m: i64, s: u32 },
834    Clamp { lo: i64, hi: i64 },
835    Relu { int: bool },
836}
837
838/// `name(args…)` with `name` resolving to `want`.
839fn call_of<'a>(e: &'a Expr<'a>, interner: &Interner, want: &str) -> Option<&'a [&'a Expr<'a>]> {
840    match e {
841        Expr::Call { function, args } if interner.resolve(*function) == want => Some(args),
842        _ => None,
843    }
844}
845
846/// The pushed value of a one-loop integer map, classified: `(X[iv] * M) >> S` (requant), `min(max(X[iv], LO), HI)`
847/// (clamp) and `max(X[iv], 0)` (ReLU over Int) or `max(X[iv], 0.0)` (over Real).
848fn classify_int_map<'a>(value: &'a Expr<'a>, iv: Symbol, interner: &Interner) -> Option<(IntMap, Symbol)> {
849    if let Some((prod, shift)) = as_binop(value, BinaryOpKind::Shr) {
850        let s = u32::try_from(as_int(shift)?).ok().filter(|s| *s <= 63)?;
851        let (xi, m) = as_binop(prod, BinaryOpKind::Multiply)?;
852        return Some((IntMap::Requant { m: as_int(m)?, s }, read_at(xi, iv)?));
853    }
854    if let Some([lower, hi]) = call_of(value, interner, "min") {
855        let [xi, lo] = call_of(lower, interner, "max")? else {
856            return None;
857        };
858        let (lo, hi) = (as_int(lo)?, as_int(hi)?);
859        want(lo <= hi)?;
860        return Some((IntMap::Clamp { lo, hi }, read_at(xi, iv)?));
861    }
862    let [xi, zero] = call_of(value, interner, "max")? else {
863        return None;
864    };
865    let int = if is_int(zero, 0) {
866        true
867    } else if is_float(zero, 0.0) {
868        false
869    } else {
870        return None;
871    };
872    Some((IntMap::Relu { int }, read_at(xi, iv)?))
873}
874
875/// The one-loop map `Let Y be … X …` its sugar desugars to: the output's `New`, `Let p be 1`, then `While p ≤ n:
876/// Push map(X[p]) to Y; Set p to p + 1`.
877fn match_int_map<'a>(body: &'a [Stmt<'a>], interner: &Interner) -> Option<(IntMap, Symbol, Symbol, usize)> {
878    for (i, out) in anchors(body) {
879        let mut c = Cursor::new(body, i + 1);
880        let Some(iv) = c.int_let(1) else { continue };
881        let Some(l) = c.loop_(iv, true) else { continue };
882        let Some((value, into)) = Cursor::new(l.body, 0).push() else {
883            continue;
884        };
885        if into != out || l.body.len() != 2 {
886            continue;
887        }
888        if let Some((map, x)) = classify_int_map(value, iv, interner) {
889            return Some((map, x, out, l.bound as usize));
890        }
891    }
892    None
893}
894
895fn recognize_int_map(stmts: &[Stmt], interner: &Interner) -> Option<(IntMap, Symbol, Symbol, usize)> {
896    find_in_bodies(stmts, |b| match_int_map(b, interner))
897}
898
899/// Recognize `Let Y be X times M shifted right by S` — `Push (X[p] * M) shifted right by S` in one counted loop.
900pub fn recognize_int_requant(stmts: &[Stmt], interner: &Interner) -> Option<IntRequantSpec> {
901    match recognize_int_map(stmts, interner)? {
902        (IntMap::Requant { m, s }, x, out, n) => Some(IntRequantSpec { x, out, n, m, s }),
903        _ => None,
904    }
905}
906
907/// Recognize `Let Y be X clamped between LO and HI` — `Push min(max(X[p], LO), HI)` in one counted loop.
908pub fn recognize_int_clamp(stmts: &[Stmt], interner: &Interner) -> Option<IntClampSpec> {
909    match recognize_int_map(stmts, interner)? {
910        (IntMap::Clamp { lo, hi }, x, out, n) => Some(IntClampSpec { x, out, n, lo, hi }),
911        _ => None,
912    }
913}
914
915/// Recognize `Let Y be relu of X` — `Push max(X[p], 0)` in one counted loop, over Int or Real.
916pub fn recognize_relu(stmts: &[Stmt], interner: &Interner) -> Option<ReluSpec> {
917    match recognize_int_map(stmts, interner)? {
918        (IntMap::Relu { int }, x, out, n) => Some(ReluSpec { x, out, n, int }),
919        _ => None,
920    }
921}
922
923// ── decay ──────────────────────────────────────────────────────────────────────────────────────────────
924
925/// `Push Pow(Add(1, Pow(E, Ar[i] + Dt[i])), Alog[i])` → `(ar, dt, alog)`.
926/// `(1 + E^(Ar[at] + Dt[i]))^Alog[i]` — `at` the element `Ar` is read at (`i` itself, or `row_item_at`'s). Returns
927/// `(Ar, Dt, Alog)`.
928fn decay_push(
929    value: &Expr,
930    iv: Symbol,
931    ar_at: &dyn Fn(&Expr) -> bool,
932) -> Option<(Symbol, Symbol, Symbol)> {
933    let (base, rate) = as_binop(value, BinaryOpKind::Pow)?;
934    let alog = read_at(rate, iv)?;
935    let (one, pow) = as_binop(base, BinaryOpKind::Add)?;
936    want(is_float(one, 1.0))?;
937    let (e, u) = as_binop(pow, BinaryOpKind::Pow)?;
938    want(is_e(e))?;
939    let (ar_i, dt_i) = as_binop(u, BinaryOpKind::Add)?;
940    let (ar, at) = indexed(ar_i)?;
941    want(ar_at(at))?;
942    Some((ar, read_at(dt_i, iv)?, alog))
943}
944
945/// `(row − 1)·width + i` — element `i` of row `row` of a block `width` wide. Returns `width`.
946fn row_item_at(e: &Expr, row: Symbol, i: Symbol) -> Option<i64> {
947    let (base, i_id) = as_binop(e, BinaryOpKind::Add)?;
948    want(is_ident(i_id, i))?;
949    let (row_less_one, width) = as_binop(base, BinaryOpKind::Multiply)?;
950    let (r, one) = as_binop(row_less_one, BinaryOpKind::Subtract)?;
951    want(is_ident(r, row) && is_int(one, 1))?;
952    as_int(width)
953}
954
955fn match_decay<'a>(body: &'a [Stmt<'a>]) -> Option<DecaySpec> {
956    for (i, out) in anchors(body) {
957        let mut c = Cursor::new(body, i + 1);
958        let Some(iv) = c.int_let(1) else { continue };
959        let Some(l) = c.loop_(iv, true) else { continue };
960        let mut lb = Cursor::new(l.body, 0);
961        match lb.push() {
962            // One row: `Push … Ar[i] …` over the row.
963            Some((value, into)) => {
964                if into != out {
965                    continue;
966                }
967                if let Some((ar, dt, alog)) = decay_push(value, iv, &|at| is_ident(at, iv)) {
968                    return Some(DecaySpec {
969                        ar,
970                        dt,
971                        alog,
972                        out,
973                        n: l.bound as usize,
974                        rows: 1,
975                    });
976                }
977            }
978            // A block: the row loop around the head loop, `Ar` read at `(row − 1)·heads + i`.
979            None => {
980                let spec = (|| {
981                    let mut rb = Cursor::new(l.body, 0);
982                    let hv = rb.int_let(1)?;
983                    let heads = rb.loop_(hv, true)?;
984                    let (inc, _) = rb.set()?;
985                    want(inc == iv && rb.next().is_none())?;
986                    let mut hb = Cursor::new(heads.body, 0);
987                    let (value, into) = hb.push()?;
988                    want(into == out)?;
989                    let ar_at = |at: &Expr| row_item_at(at, iv, hv) == Some(heads.bound);
990                    let (ar, dt, alog) = decay_push(value, hv, &ar_at)?;
991                    Some(DecaySpec {
992                        ar,
993                        dt,
994                        alog,
995                        out,
996                        n: (l.bound * heads.bound) as usize,
997                        rows: l.bound as usize,
998                    })
999                })();
1000                if spec.is_some() {
1001                    return spec;
1002                }
1003            }
1004        }
1005    }
1006    None
1007}
1008
1009/// Recognize `Let A be decay of Ar with bias Dt rate Alog` — `Push (1 + E^(Ar[i]+Dt[i]))^Alog[i]`.
1010pub fn recognize_decay(stmts: &[Stmt]) -> Option<DecaySpec> {
1011    find_in_bodies(stmts, match_decay)
1012}
1013
1014// ── l2norm ─────────────────────────────────────────────────────────────────────────────────────────────
1015
1016fn match_l2norm<'a>(body: &'a [Stmt<'a>]) -> Option<L2NormSpec> {
1017    for (i, out) in anchors(body) {
1018        let mut c = Cursor::new(body, i + 1);
1019        let Some(r) = c.int_let(1) else { continue };
1020        let Some(rows) = c.loop_(r, true) else {
1021            continue;
1022        };
1023        if let Some(spec) = l2norm_row(rows.body, r, out).map(|(x, cols, eps)| L2NormSpec {
1024            x,
1025            out,
1026            rows: rows.bound as usize,
1027            cols,
1028            eps: Epsilon::new(eps),
1029        }) {
1030            return Some(spec);
1031        }
1032    }
1033    None
1034}
1035
1036/// One row of the l2norm desugar: `base = (r−1)·cols`, the sum of squares over `cols`, `denom = sqrt(ss + eps)`,
1037/// then the normalize loop `Push X[base+q] / denom`. Returns `(x, cols, eps)`.
1038fn l2norm_row<'a>(body: &'a [Stmt<'a>], r: Symbol, out: Symbol) -> Option<(Symbol, usize, f64)> {
1039    let mut c = Cursor::new(body, 0);
1040    let (base, base_v) = c.let_()?;
1041    let (r_minus, cols_e) = as_binop(base_v, BinaryOpKind::Multiply)?;
1042    let (r_id, one) = as_binop(r_minus, BinaryOpKind::Subtract)?;
1043    want(is_ident(r_id, r) && is_int(one, 1))?;
1044    let cols = as_int(cols_e)?;
1045    let ss = c.float_let(0.0)?;
1046    let p = c.int_let(1)?;
1047    let ss_loop = c.loop_(p, true)?;
1048    want(ss_loop.bound == cols)?;
1049    let (ss_target, ss_v) = Cursor::new(ss_loop.body, 0).set()?;
1050    want(ss_target == ss)?;
1051    let (ss_id, sq) = as_binop(ss_v, BinaryOpKind::Add)?;
1052    want(is_ident(ss_id, ss))?;
1053    let x = square_of(sq)?;
1054    let (denom, call) = c.let_()?;
1055    let Expr::Call { args, .. } = call else {
1056        return None;
1057    };
1058    let [arg] = args.as_slice() else { return None };
1059    let (ss_id, eps) = as_binop(arg, BinaryOpKind::Add)?;
1060    want(is_ident(ss_id, ss))?;
1061    let eps = as_float(eps).filter(|e| *e > 0.0)?;
1062    let q = c.int_let(1)?;
1063    let n_loop = c.loop_(q, true)?;
1064    want(n_loop.bound == cols)?;
1065    let (value, into) = Cursor::new(n_loop.body, 0).push()?;
1066    want(into == out)?;
1067    let (xq, den) = as_binop(value, BinaryOpKind::Divide)?;
1068    want(is_ident(den, denom))?;
1069    let (x2, idx) = indexed(xq)?;
1070    let (base_id, q_id) = as_binop(idx, BinaryOpKind::Add)?;
1071    want(x2 == x && is_ident(base_id, base) && is_ident(q_id, q))?;
1072    Some((x, cols as usize, eps))
1073}
1074
1075/// Recognize `Let Y be l2norm of X [across H heads]` — the per-row `sqrt` denominator bound in a `Let` and
1076/// the `X[base+q] / denom` normalize loop.
1077pub fn recognize_l2norm(stmts: &[Stmt]) -> Option<L2NormSpec> {
1078    find_in_bodies(stmts, match_l2norm)
1079}
1080
1081// ── transpose ──────────────────────────────────────────────────────────────────────────────────────────
1082
1083fn match_transpose<'a>(body: &'a [Stmt<'a>]) -> Option<TransposeSpec> {
1084    for (i, out) in anchors(body) {
1085        let mut c = Cursor::new(body, i + 1);
1086        let Some(tj) = c.int_let(0) else { continue };
1087        let Some(outer) = c.loop_(tj, false) else {
1088            continue;
1089        };
1090        let cols = outer.bound;
1091        let mut ob = Cursor::new(outer.body, 0);
1092        let Some(ti) = ob.int_let(0) else { continue };
1093        let Some(inner) = ob.loop_(ti, false) else {
1094            continue;
1095        };
1096        let Some((value, into)) = Cursor::new(inner.body, 0).push() else {
1097            continue;
1098        };
1099        if into != out {
1100            continue;
1101        }
1102        let Some((x, idx)) = indexed(value) else {
1103            continue;
1104        };
1105        // X[ti·cols + tj + 1]
1106        let shape = (|| {
1107            let (sum, one) = as_binop(idx, BinaryOpKind::Add)?;
1108            want(is_int(one, 1))?;
1109            let (prod, tj_id) = as_binop(sum, BinaryOpKind::Add)?;
1110            want(is_ident(tj_id, tj))?;
1111            let (ti_id, cols_e) = as_binop(prod, BinaryOpKind::Multiply)?;
1112            want(is_ident(ti_id, ti) && is_int(cols_e, cols))
1113        })();
1114        if shape.is_some() {
1115            return Some(TransposeSpec {
1116                x,
1117                out,
1118                rows: inner.bound as usize,
1119                cols: cols as usize,
1120            });
1121        }
1122    }
1123    None
1124}
1125
1126/// Recognize `Let Kt be transpose of X` — the `cols`-outer / `rows`-inner loop nest pushing `X[ti·cols+tj+1]`.
1127pub fn recognize_transpose(stmts: &[Stmt]) -> Option<TransposeSpec> {
1128    find_in_bodies(stmts, match_transpose)
1129}
1130
1131// ── head slices and joins ──────────────────────────────────────────────────────────────────────────────
1132
1133/// `Let iv be 1. While iv ≤ width: Push <value> to out. Set iv to iv + 1.` — one part's copy: `(value, iv, width)`.
1134fn part_copy<'a>(c: &mut Cursor<'a>, out: Symbol) -> Option<(&'a Expr<'a>, Symbol, i64)> {
1135    let iv = c.int_let(1)?;
1136    let l = c.loop_(iv, true)?;
1137    let mut b = Cursor::new(l.body, 0);
1138    let (value, into) = b.push()?;
1139    want(into == out && matches!(b.set(), Some((inc, _)) if inc == iv) && b.next().is_none() && l.bound >= 1)?;
1140    Some((value, iv, l.bound))
1141}
1142
1143/// `T[(r·stride + h·width) + iv]` — element `iv` (1-based) of head `h`'s part, `width` wide, of row `r`. Returns `T`.
1144fn part_read(e: &Expr, r: Symbol, stride: i64, h: Symbol, width: i64, iv: Symbol) -> Option<Symbol> {
1145    let (t, idx) = indexed(e)?;
1146    let (at, iv_id) = as_binop(idx, BinaryOpKind::Add)?;
1147    let (row, part) = as_binop(at, BinaryOpKind::Add)?;
1148    let (r_id, s) = as_binop(row, BinaryOpKind::Multiply)?;
1149    let (h_id, w) = as_binop(part, BinaryOpKind::Multiply)?;
1150    want(is_ident(iv_id, iv) && is_ident(r_id, r) && is_int(s, stride) && is_ident(h_id, h) && is_int(w, width))?;
1151    Some(t)
1152}
1153
1154fn match_head_slice<'a>(body: &'a [Stmt<'a>]) -> Option<HeadSliceSpec> {
1155    for (i, out) in anchors(body) {
1156        let found = (|| {
1157            let mut c = Cursor::new(body, i + 1);
1158            let g = c.int_let(0)?;
1159            let groups = c.loop_(g, false)?;
1160            let mut gb = Cursor::new(groups.body, 0);
1161            let (value, iv, width) = part_copy(&mut gb, out)?;
1162            want(matches!(gb.set(), Some((inc, _)) if inc == g) && gb.next().is_none() && groups.bound >= 1)?;
1163            // `X[(g·N + iv) + from]`: the group's `N` elements, `from` past their first.
1164            let (x, idx) = indexed(value)?;
1165            let (at, from) = as_binop(idx, BinaryOpKind::Add)?;
1166            let (row, iv_id) = as_binop(at, BinaryOpKind::Add)?;
1167            let (g_id, every) = as_binop(row, BinaryOpKind::Multiply)?;
1168            want(is_ident(iv_id, iv) && is_ident(g_id, g))?;
1169            let (every, from) = (as_int(every)?, as_int(from)?);
1170            want(from >= 0 && from + width <= every)?;
1171            Some(HeadSliceSpec {
1172                x,
1173                out,
1174                every: every as usize,
1175                from: from as usize,
1176                width: width as usize,
1177                n: (groups.bound * every) as usize,
1178            })
1179        })();
1180        if found.is_some() {
1181            return found;
1182        }
1183    }
1184    None
1185}
1186
1187/// Recognize `Let P be the first K of every N of X` / `the last K of every N of X` — a loop over the groups, each
1188/// pushing its `K` elements `X[(g·N + i) + from]`.
1189pub fn recognize_head_slice(stmts: &[Stmt]) -> Option<HeadSliceSpec> {
1190    find_in_bodies(stmts, match_head_slice)
1191}
1192
1193fn match_join<'a>(body: &'a [Stmt<'a>]) -> Option<JoinSpec> {
1194    for (i, out) in anchors(body) {
1195        let found = (|| {
1196            let mut c = Cursor::new(body, i + 1);
1197            let r = c.int_let(0)?;
1198            let rows = c.loop_(r, false)?;
1199            let mut rb = Cursor::new(rows.body, 0);
1200            let h = rb.int_let(0)?;
1201            let heads = rb.loop_(h, false)?;
1202            want(matches!(rb.set(), Some((inc, _)) if inc == r) && rb.next().is_none())?;
1203            // Each head: its part of `A`, then its part of `B`, each read at its row and head.
1204            let mut hb = Cursor::new(heads.body, 0);
1205            let (va, ia, ha) = part_copy(&mut hb, out)?;
1206            let (vb, ib, hw) = part_copy(&mut hb, out)?;
1207            want(matches!(hb.set(), Some((inc, _)) if inc == h) && hb.next().is_none())?;
1208            let a = part_read(va, r, ha * heads.bound, h, ha, ia)?;
1209            let b = part_read(vb, r, hw * heads.bound, h, hw, ib)?;
1210            want(rows.bound >= 1 && heads.bound >= 1)?;
1211            Some(JoinSpec {
1212                a,
1213                b,
1214                out,
1215                rows: rows.bound as usize,
1216                heads: heads.bound as usize,
1217                wa: (ha * heads.bound) as usize,
1218                wb: (hw * heads.bound) as usize,
1219            })
1220        })();
1221        if found.is_some() {
1222            return found;
1223        }
1224    }
1225    None
1226}
1227
1228/// Recognize `Let J be A joined with B [across H heads]` — a loop over the rows and in it a loop over the heads, each
1229/// head pushing its part of `A` (`A[(r·wa + h·ha) + i]`) then its part of `B`.
1230pub fn recognize_join(stmts: &[Stmt]) -> Option<JoinSpec> {
1231    find_in_bodies(stmts, match_join)
1232}
1233
1234// ── the row sugars: gather, arg-max, scatter ───────────────────────────────────────────────────────────
1235
1236/// `Let at be <index>. Require that at ≥ 1 and at ≤ rows.` — the checked row index every row sugar opens
1237/// with. Returns `(at, index, rows)`.
1238fn checked_row_index<'a>(c: &mut Cursor<'a>) -> Option<(Symbol, RowIndex, usize)> {
1239    let (at, value) = c.let_()?;
1240    let index = match value {
1241        Expr::Identifier(s) => RowIndex::Named(*s),
1242        Expr::Literal(Literal::Number(n)) => RowIndex::Literal(*n),
1243        _ => return None,
1244    };
1245    let Stmt::RuntimeAssert {
1246        condition,
1247        hard: true,
1248        ..
1249    } = c.next()?
1250    else {
1251        return None;
1252    };
1253    let (lo, hi) = as_binop(condition, BinaryOpKind::And)?;
1254    let (lo_v, one) = as_binop(lo, BinaryOpKind::GtEq)?;
1255    let (hi_v, rows) = as_binop(hi, BinaryOpKind::LtEq)?;
1256    want(is_ident(lo_v, at) && is_int(one, 1) && is_ident(hi_v, at))?;
1257    let rows = as_int(rows)?;
1258    want(rows >= 1)?;
1259    Some((at, index, rows as usize))
1260}
1261
1262/// `((at − 1)·cols + j) + 1` — the flat item of element `j` of row `at`. Returns `cols`.
1263fn row_item(e: &Expr, at: Symbol, j: Symbol) -> Option<i64> {
1264    let (sum, one) = as_binop(e, BinaryOpKind::Add)?;
1265    want(is_int(one, 1))?;
1266    let (base, j_id) = as_binop(sum, BinaryOpKind::Add)?;
1267    want(is_ident(j_id, j))?;
1268    let (at_less_one, cols) = as_binop(base, BinaryOpKind::Multiply)?;
1269    let (at_id, one) = as_binop(at_less_one, BinaryOpKind::Subtract)?;
1270    want(is_ident(at_id, at) && is_int(one, 1))?;
1271    as_int(cols)
1272}
1273
1274fn match_row_gather<'a>(body: &'a [Stmt<'a>]) -> Option<RowGatherSpec> {
1275    let mut c = Cursor::new(body, 0);
1276    let (at, index, rows) = checked_row_index(&mut c)?;
1277    let out = c.new_let()?;
1278    let j = c.int_let(0)?;
1279    let l = c.loop_(j, false)?;
1280    want(c.next().is_none())?;
1281    let mut lb = Cursor::new(l.body, 0);
1282    let (value, into) = lb.push()?;
1283    want(into == out)?;
1284    let (table, item) = indexed(value)?;
1285    let cols = row_item(item, at, j)?;
1286    want(cols == l.bound && cols >= 1)?;
1287    let (inc, _) = lb.set()?;
1288    want(inc == j && lb.next().is_none())?;
1289    Some(RowGatherSpec {
1290        table,
1291        index,
1292        out,
1293        rows,
1294        cols: cols as usize,
1295    })
1296}
1297
1298/// Recognize `Let x be row i of T`.
1299pub fn recognize_row_gather(stmts: &[Stmt]) -> Option<RowGatherSpec> {
1300    find_in_bodies(stmts, match_row_gather)
1301}
1302
1303fn match_rows_gather<'a>(body: &'a [Stmt<'a>]) -> Option<RowsGatherSpec> {
1304    let mut c = Cursor::new(body, 0);
1305    let out = c.new_let()?;
1306    let k = c.int_let(1)?;
1307    let outer = c.loop_(k, true)?;
1308    want(c.next().is_none())?;
1309    // Per index: `Let at be I[k]`, its range check, then the row's copy.
1310    let mut rb = Cursor::new(outer.body, 0);
1311    let (at, value) = rb.let_()?;
1312    let (indices, k_at) = indexed(value)?;
1313    want(is_ident(k_at, k))?;
1314    let Stmt::RuntimeAssert {
1315        condition,
1316        hard: true,
1317        ..
1318    } = rb.next()?
1319    else {
1320        return None;
1321    };
1322    let (lo, hi) = as_binop(condition, BinaryOpKind::And)?;
1323    let (lo_v, one) = as_binop(lo, BinaryOpKind::GtEq)?;
1324    let (hi_v, rows) = as_binop(hi, BinaryOpKind::LtEq)?;
1325    want(is_ident(lo_v, at) && is_int(one, 1) && is_ident(hi_v, at))?;
1326    let rows = as_int(rows)?;
1327    let j = rb.int_let(0)?;
1328    let copy = rb.loop_(j, false)?;
1329    let mut cb = Cursor::new(copy.body, 0);
1330    let (read, into) = cb.push()?;
1331    want(into == out)?;
1332    let (table, item) = indexed(read)?;
1333    let cols = row_item(item, at, j)?;
1334    want(cols == copy.bound && cols >= 1 && rows >= 1 && table != indices)?;
1335    let (inc_j, _) = cb.set()?;
1336    want(inc_j == j && cb.next().is_none())?;
1337    let (inc_k, _) = rb.set()?;
1338    want(inc_k == k && rb.next().is_none())?;
1339    Some(RowsGatherSpec {
1340        table,
1341        indices,
1342        out,
1343        rows: rows as usize,
1344        cols: cols as usize,
1345        count: outer.bound as usize,
1346    })
1347}
1348
1349/// Recognize `Let X be rows I of T`.
1350pub fn recognize_rows_gather(stmts: &[Stmt]) -> Option<RowsGatherSpec> {
1351    find_in_bodies(stmts, match_rows_gather)
1352}
1353
1354fn match_set_row<'a>(body: &'a [Stmt<'a>]) -> Option<SetRowSpec> {
1355    let mut c = Cursor::new(body, 0);
1356    let (at, index, rows) = checked_row_index(&mut c)?;
1357    let j = c.int_let(0)?;
1358    let l = c.loop_(j, false)?;
1359    want(c.next().is_none())?;
1360    let mut lb = Cursor::new(l.body, 0);
1361    let (table, item, value) = lb.set_index()?;
1362    // The item's `(at − 1)·cols` is the table's row width; the loop writes `count` whole rows from `at` on, and the
1363    // check keeps the last of them in the table (`at ≤ rows − count + 1`).
1364    let cols = row_item(item, at, j)?;
1365    want(cols >= 1 && l.bound >= cols && l.bound % cols == 0)?;
1366    let count = l.bound / cols;
1367    let (source, at_j) = indexed(value)?;
1368    let (j_id, one) = as_binop(at_j, BinaryOpKind::Add)?;
1369    want(is_ident(j_id, j) && is_int(one, 1) && source != table)?;
1370    let (inc, _) = lb.set()?;
1371    want(inc == j && lb.next().is_none())?;
1372    Some(SetRowSpec {
1373        table,
1374        index,
1375        source,
1376        rows: rows + count as usize - 1,
1377        cols: cols as usize,
1378        count: count as usize,
1379    })
1380}
1381
1382/// Recognize `Set row i of T to S`.
1383pub fn recognize_set_row(stmts: &[Stmt]) -> Option<SetRowSpec> {
1384    find_in_bodies(stmts, match_set_row)
1385}
1386
1387fn match_argmax<'a>(body: &'a [Stmt<'a>]) -> Option<ArgMaxSpec> {
1388    let mut c = Cursor::new(body, 0);
1389    let (best, first) = c.let_()?;
1390    let (x, one) = indexed(first)?;
1391    want(is_int(one, 1))?;
1392    let at = c.int_let(1)?;
1393    let i = c.int_let(2)?;
1394    let l = c.loop_(i, true)?;
1395    let (out, result) = c.let_()?;
1396    want(is_ident(result, at) && c.next().is_none())?;
1397    let mut lb = Cursor::new(l.body, 0);
1398    let Stmt::If {
1399        cond,
1400        then_block,
1401        else_block: None,
1402    } = lb.next()?
1403    else {
1404        return None;
1405    };
1406    let (read, cur) = as_binop(cond, BinaryOpKind::Gt)?;
1407    want(read_at(read, i) == Some(x) && is_ident(cur, best))?;
1408    let mut tb = Cursor::new(then_block, 0);
1409    let (b, bv) = tb.set()?;
1410    want(b == best && read_at(bv, i) == Some(x))?;
1411    let (a, av) = tb.set()?;
1412    want(a == at && is_ident(av, i) && tb.next().is_none())?;
1413    let (inc, _) = lb.set()?;
1414    want(inc == i && lb.next().is_none() && l.bound >= 1)?;
1415    Some(ArgMaxSpec {
1416        x,
1417        out,
1418        n: l.bound as usize,
1419    })
1420}
1421
1422/// Recognize `Let t be argmax of X`.
1423pub fn recognize_argmax(stmts: &[Stmt]) -> Option<ArgMaxSpec> {
1424    find_in_bodies(stmts, match_argmax)
1425}
1426
1427// ── full attention ─────────────────────────────────────────────────────────────────────────────────────
1428
1429/// Every collection a statement list READS by index that it does not itself bind, in first-read order.
1430fn read_collections(body: &[Stmt], locals: &HashSet<Symbol>, out: &mut Vec<Symbol>) {
1431    fn ex(e: &Expr, locals: &HashSet<Symbol>, out: &mut Vec<Symbol>) {
1432        match e {
1433            Expr::Index { collection, index } => {
1434                if let Expr::Identifier(s) = &**collection {
1435                    if !locals.contains(s) && !out.contains(s) {
1436                        out.push(*s);
1437                    }
1438                }
1439                ex(index, locals, out);
1440            }
1441            Expr::BinaryOp { left, right, .. } => {
1442                ex(left, locals, out);
1443                ex(right, locals, out);
1444            }
1445            Expr::Call { args, .. } => args.iter().for_each(|a| ex(a, locals, out)),
1446            _ => {}
1447        }
1448    }
1449    for s in body {
1450        match s {
1451            Stmt::Let { value, .. } | Stmt::Set { value, .. } | Stmt::Push { value, .. } => {
1452                ex(value, locals, out)
1453            }
1454            Stmt::SetIndex { index, value, .. } => {
1455                ex(index, locals, out);
1456                ex(value, locals, out);
1457            }
1458            Stmt::While { body, .. } => read_collections(body, locals, out),
1459            _ => {}
1460        }
1461    }
1462}
1463
1464/// Every name a statement list binds with `Let`, at any depth.
1465fn let_bound(body: &[Stmt], into: &mut HashSet<Symbol>) {
1466    for s in body {
1467        match s {
1468            Stmt::Let { var, .. } => {
1469                into.insert(*var);
1470            }
1471            Stmt::While { body, .. } => let_bound(body, into),
1472            _ => {}
1473        }
1474    }
1475}
1476
1477/// The full-attention desugar: zero-fill the output, then a query-row loop over a head loop that builds a
1478/// fresh per-head score row, takes its max (`max(·,·)`), its exp-sum, and writes the weighted V sum. The
1479/// decode `attention` desugar differs in the one place that matters: it `Let`s a GQA k-head
1480/// (`head / group`) and reads a single query row — so neither recognizer can claim the other's program.
1481fn match_full_attention<'a>(body: &'a [Stmt<'a>]) -> Option<FullAttentionSpec> {
1482    let mut c = Cursor::new(body, 0);
1483    let out = c.new_let()?;
1484    let ozi = c.int_let(1)?;
1485    let fill = c.loop_(ozi, true)?;
1486    let (pushed, into) = Cursor::new(fill.body, 0).push()?;
1487    want(into == out && is_float(pushed, 0.0))?;
1488    let row = c.int_let(1)?;
1489    let rows = c.loop_(row, true)?;
1490    want(c.next().is_none())?;
1491    let t = rows.bound;
1492    let mut rb = Cursor::new(rows.body, 0);
1493    let head = rb.int_let(1)?;
1494    let heads = rb.loop_(head, true)?;
1495    let h = heads.bound;
1496    // The per-head body: a fresh score row, the score loop (its inner loop's bound is the head dim), the
1497    // max, the exp-sum, the weighted V loop — and no GQA `Let … be head / group`.
1498    let hb = heads.body;
1499    want(!hb.iter().any(|st| {
1500        matches!(
1501            st,
1502            Stmt::Let {
1503                value: Expr::BinaryOp {
1504                    op: BinaryOpKind::Divide,
1505                    ..
1506                },
1507                ..
1508            }
1509        )
1510    }))?;
1511    let mut hc = Cursor::new(hb, 0);
1512    hc.new_let()?;
1513    let j = hc.int_let(1)?;
1514    let scores = hc.loop_(j, true)?;
1515    want(scores.bound == t)?;
1516    let hd = scores
1517        .body
1518        .iter()
1519        .find_map(|st| as_loop(st).map(|l| l.bound))?;
1520    let uses_max = |b: &[Stmt]| {
1521        b.iter().any(|st| matches!(st, Stmt::While { body, .. } if body.iter().any(|x| matches!(x, Stmt::Set { value: Expr::Call { .. }, .. }))))
1522    };
1523    want(uses_max(hb))?;
1524    want(t >= 1 && h >= 1 && hd >= 1 && fill.bound == t * h * hd)?;
1525    let mut locals = HashSet::new();
1526    let_bound(body, &mut locals);
1527    let mut reads = Vec::new();
1528    read_collections(body, &locals, &mut reads);
1529    reads.retain(|r| *r != out);
1530    let [q, k, v] = reads.as_slice() else {
1531        return None;
1532    };
1533    Some(FullAttentionSpec {
1534        q: *q,
1535        k: *k,
1536        v: *v,
1537        out,
1538        h: h as usize,
1539        hd: hd as usize,
1540        t: t as usize,
1541    })
1542}
1543
1544/// Recognize `Let C be full attention of Q over K and V with H heads`.
1545pub fn recognize_full_attention(stmts: &[Stmt]) -> Option<FullAttentionSpec> {
1546    find_in_bodies(stmts, match_full_attention)
1547}
1548
1549// ── rope ───────────────────────────────────────────────────────────────────────────────────────────────
1550
1551/// `Q[..]·T1[..] ∘ Q[..]·T2[..]` — a rotation term: the two `Q` reads and the two table reads, in order.
1552/// `rh·hd + i + shift + 1` — the 1-based item of element `i + shift` of head `rh`. Returns `hd`.
1553fn head_item(e: &Expr, rh: Symbol, i: Symbol, shift: i64) -> Option<i64> {
1554    let (sum, one) = as_binop(e, BinaryOpKind::Add)?;
1555    want(is_int(one, 1))?;
1556    let sum = if shift == 0 {
1557        sum
1558    } else {
1559        let (s, by) = as_binop(sum, BinaryOpKind::Add)?;
1560        want(is_int(by, shift))?;
1561        s
1562    };
1563    let (base, i_id) = as_binop(sum, BinaryOpKind::Add)?;
1564    want(is_ident(i_id, i))?;
1565    let (rh_id, hd) = as_binop(base, BinaryOpKind::Multiply)?;
1566    want(is_ident(rh_id, rh))?;
1567    as_int(hd)
1568}
1569
1570/// A rope table's 1-based index: `ri + 1` — one row for every head (`None`) — or `(rh / per_row)·half + (ri + 1)` — a
1571/// row per row of `Q`, `per_row` heads to a row (`Some(per_row)`). Reading a cache from the checked position `at`, the
1572/// only form is `((at − 1) + rh / per_row)·half + (ri + 1)`.
1573fn rope_table_index(
1574    e: &Expr,
1575    rh: Symbol,
1576    ri: Symbol,
1577    half: i64,
1578    at: Option<Symbol>,
1579) -> Option<Option<i64>> {
1580    let within = |x: &Expr| {
1581        as_binop(x, BinaryOpKind::Add).is_some_and(|(r, one)| is_ident(r, ri) && is_int(one, 1))
1582    };
1583    if within(e) {
1584        return at.is_none().then_some(None);
1585    }
1586    let (row_base, rest) = as_binop(e, BinaryOpKind::Add)?;
1587    want(within(rest))?;
1588    let (row, by) = as_binop(row_base, BinaryOpKind::Multiply)?;
1589    want(is_int(by, half))?;
1590    let row = match at {
1591        None => row,
1592        Some(at) => {
1593            let (at_less_one, row) = as_binop(row, BinaryOpKind::Add)?;
1594            let (at_id, one) = as_binop(at_less_one, BinaryOpKind::Subtract)?;
1595            want(is_ident(at_id, at) && is_int(one, 1))?;
1596            row
1597        }
1598    };
1599    let (rh_id, per_row) = as_binop(row, BinaryOpKind::Divide)?;
1600    want(is_ident(rh_id, rh))?;
1601    let per_row = as_int(per_row)?;
1602    want(per_row >= 1)?;
1603    Some(Some(per_row))
1604}
1605
1606/// `Q[a]·T1[t] ∘ Q[b]·T2[t]` — head `rh`'s rotation of the pair `(ri, ri + half)`, every index the desugar's: `a` and `b`
1607/// the pair's items ([`head_item`]) and `t` the table index ([`rope_table_index`]), the same for both tables. Returns
1608/// `(Q, T1, T2, hd, per_row)`.
1609fn rotation_terms<'a>(
1610    value: &'a Expr<'a>,
1611    op: BinaryOpKind,
1612    rh: Symbol,
1613    ri: Symbol,
1614    half: i64,
1615    at: Option<Symbol>,
1616) -> Option<(Symbol, Symbol, Symbol, i64, Option<i64>)> {
1617    let (m1, m2) = as_binop(value, op)?;
1618    let (qa, t1) = as_binop(m1, BinaryOpKind::Multiply)?;
1619    let (qb, t2) = as_binop(m2, BinaryOpKind::Multiply)?;
1620    let (q1, a) = indexed(qa)?;
1621    let (q2, b) = indexed(qb)?;
1622    want(q1 == q2)?;
1623    let hd = head_item(a, rh, ri, 0)?;
1624    want(head_item(b, rh, ri, half)? == hd)?;
1625    let (c1, i1) = indexed(t1)?;
1626    let (c2, i2) = indexed(t2)?;
1627    let per_row = rope_table_index(i1, rh, ri, half, at)?;
1628    want(rope_table_index(i2, rh, ri, half, at)? == per_row)?;
1629    Some((q1, c1, c2, hd, per_row))
1630}
1631
1632fn match_rope<'a>(body: &'a [Stmt<'a>]) -> Option<RopeSpec> {
1633    for (i, out) in anchors(body) {
1634        let mut c = Cursor::new(body, i + 1);
1635        let Some(rh) = c.int_let(0) else { continue };
1636        let Some(outer) = c.loop_(rh, false) else {
1637            continue;
1638        };
1639        // A cache read from a position opens with that position, checked: `Let at be P`, then its range assertion — so a
1640        // checked index before the anchor is tried as the rope's position first, and as nothing of the rope's after.
1641        let checked = i
1642            .checked_sub(2)
1643            .and_then(|k| checked_row_index(&mut Cursor::new(body, k)));
1644        let try_spec = |checked: Option<(Symbol, RowIndex, usize)>| {
1645            let at = checked.map(|(at, _, _)| at);
1646            let mut ob = Cursor::new(outer.body, 0);
1647            let ri = ob.int_let(0)?;
1648            let loop1 = ob.loop_(ri, false)?;
1649            let half = loop1.bound;
1650            let (push1, into1) = Cursor::new(loop1.body, 0).push()?;
1651            want(into1 == out)?;
1652            let (q, rc, rs, hd, per_row) =
1653                rotation_terms(push1, BinaryOpKind::Subtract, rh, ri, half, at)?;
1654            let (reset, zero) = ob.set()?;
1655            want(reset == ri && is_int(zero, 0))?;
1656            let loop2 = ob.loop_(ri, false)?;
1657            want(loop2.bound == half)?;
1658            let (push2, into2) = Cursor::new(loop2.body, 0).push()?;
1659            want(into2 == out)?;
1660            want(
1661                rotation_terms(push2, BinaryOpKind::Add, rh, ri, half, at)?
1662                    == (q, rs, rc, hd, per_row),
1663            )?;
1664            let rp = ob.int_let(2 * half)?;
1665            let loop3 = ob.loop_(rp, false)?;
1666            let (push3, into3) = Cursor::new(loop3.body, 0).push()?;
1667            let (q3, item) = indexed(push3)?;
1668            want(
1669                into3 == out && q3 == q && head_item(item, rh, rp, 0)? == hd && loop3.bound == hd,
1670            )?;
1671            let h = outer.bound;
1672            let rows = match per_row {
1673                None => 1,
1674                Some(p) => {
1675                    want(h % p == 0)?;
1676                    h / p
1677                }
1678            };
1679            // The check keeps the block's last row among the cache's: `at ≤ cache − rows + 1`.
1680            let from = checked.map(|(_, index, last)| (index, last + rows as usize - 1));
1681            Some(RopeSpec {
1682                q,
1683                rc,
1684                rs,
1685                out,
1686                h: h as usize,
1687                half: half as usize,
1688                hd: hd as usize,
1689                rows: rows as usize,
1690                from,
1691            })
1692        };
1693        let spec = checked
1694            .and_then(|c| try_spec(Some(c)))
1695            .or_else(|| try_spec(None));
1696        if spec.is_some() {
1697            return spec;
1698        }
1699    }
1700    None
1701}
1702
1703/// Recognize `Let Qr be rope of Q with cos RC and sin RS [from position P] [across H heads]` — per head, the two
1704/// rotation loops over `half` and the pass-through loop up to `hd`, after the position's check when there is one.
1705pub fn recognize_rope(stmts: &[Stmt]) -> Option<RopeSpec> {
1706    find_in_bodies(stmts, match_rope)
1707}
1708
1709// ── causal conv ────────────────────────────────────────────────────────────────────────────────────────
1710
1711/// The single step's three loops from `c` — the ring shift, the append of `X`, the tap reduce into `Y` — over rows
1712/// `chan` wide, `X` and `Y` indexed through `at` (the loop variable itself, or row `row` of a block). Returns
1713/// `(X, WC, CS, taps)`.
1714fn conv_step<'a>(
1715    c: &mut Cursor<'a>,
1716    out: Symbol,
1717    chan: i64,
1718    at: &dyn Fn(&Expr, Symbol) -> bool,
1719) -> Option<(Symbol, Symbol, Symbol, i64)> {
1720    // 1. shift the ring up one row: CS[(t1−1)·chan + c1] ← CS[t1·chan + c1]
1721    let t1 = c.int_let(1)?;
1722    let shift = c.loop_(t1, true)?;
1723    let mut sb = Cursor::new(shift.body, 0);
1724    let c1 = sb.int_let(1)?;
1725    let shift_inner = sb.loop_(c1, true)?;
1726    want(shift_inner.bound == chan)?;
1727    let (cs, _, moved) = Cursor::new(shift_inner.body, 0).set_index()?;
1728    want(indexed(moved)?.0 == cs)?;
1729    // 2. this token becomes the newest row: CS[(taps−1)·chan + c2] ← X[at]
1730    let c2 = c.int_let(1)?;
1731    let append = c.loop_(c2, true)?;
1732    want(append.bound == chan)?;
1733    let (cs2, _, newest) = Cursor::new(append.body, 0).set_index()?;
1734    want(cs2 == cs)?;
1735    let (x, x_at) = indexed(newest)?;
1736    want(at(x_at, c2))?;
1737    // 3. Y[at] = Σ_t CS[t,c3]·WC[t,c3]
1738    let c3 = c.int_let(1)?;
1739    let reduce = c.loop_(c3, true)?;
1740    want(reduce.bound == chan)?;
1741    let mut rb = Cursor::new(reduce.body, 0);
1742    let acc = rb.float_let(0.0)?;
1743    let t2 = rb.int_let(1)?;
1744    let taps_loop = rb.loop_(t2, true)?;
1745    let taps = taps_loop.bound;
1746    want(shift.bound == taps - 1)?;
1747    let (acc_target, acc_v) = Cursor::new(taps_loop.body, 0).set()?;
1748    want(acc_target == acc)?;
1749    let (acc_id, term) = as_binop(acc_v, BinaryOpKind::Add)?;
1750    want(is_ident(acc_id, acc))?;
1751    let (cs_read, wc_read) = as_binop(term, BinaryOpKind::Multiply)?;
1752    want(indexed(cs_read)?.0 == cs)?;
1753    let wc = indexed(wc_read)?.0;
1754    let (out2, idx, stored) = rb.set_index()?;
1755    want(out2 == out && at(idx, c3) && is_ident(stored, acc))?;
1756    Some((x, wc, cs, taps))
1757}
1758
1759fn match_causal_conv<'a>(body: &'a [Stmt<'a>]) -> Option<CausalConvSpec> {
1760    for (i, out) in anchors(body) {
1761        let mut c = Cursor::new(body, i + 1);
1762        let Some(zi) = c.int_let(1) else { continue };
1763        let Some(zero_fill) = c.loop_(zi, true) else {
1764            continue;
1765        };
1766        let filled = zero_fill.bound;
1767        let spec = (|| {
1768            let (zero, into) = Cursor::new(zero_fill.body, 0).push()?;
1769            want(into == out && is_float(zero, 0.0))?;
1770            let mut block = c.clone();
1771            // One row: the step's loops follow the zero fill.
1772            if let Some((x, wc, cs, taps)) = conv_step(&mut c, out, filled, &|e, v| is_ident(e, v))
1773            {
1774                return Some(CausalConvSpec {
1775                    x,
1776                    wc,
1777                    cs,
1778                    out,
1779                    taps: taps as usize,
1780                    chan: filled as usize,
1781                    rows: 1,
1782                });
1783            }
1784            // A block: the step inside a row loop, `X` and `Y` read and written at `(row − 1)·chan + c`.
1785            let row = block.int_let(1)?;
1786            let rows = block.loop_(row, true)?;
1787            want(rows.bound > 1 && filled % rows.bound == 0)?;
1788            let chan = filled / rows.bound;
1789            let mut rb = Cursor::new(rows.body, 0);
1790            let (x, wc, cs, taps) = conv_step(&mut rb, out, chan, &|e, v| {
1791                row_item_at(e, row, v) == Some(chan)
1792            })?;
1793            let (inc, _) = rb.set()?;
1794            want(inc == row && rb.next().is_none())?;
1795            Some(CausalConvSpec {
1796                x,
1797                wc,
1798                cs,
1799                out,
1800                taps: taps as usize,
1801                chan: chan as usize,
1802                rows: rows.bound as usize,
1803            })
1804        })();
1805        if spec.is_some() {
1806            return spec;
1807        }
1808    }
1809    None
1810}
1811
1812/// Recognize `Let Y be causal conv of X with weight WC state CS over T taps` — the three loops: the ring
1813/// shift `CS[(t−1)·chan+c] ← CS[t·chan+c]`, the append of `X`, and the tap reduce against `WC`.
1814pub fn recognize_causal_conv(stmts: &[Stmt]) -> Option<CausalConvSpec> {
1815    find_in_bodies(stmts, match_causal_conv)
1816}
1817
1818// ── gated delta ────────────────────────────────────────────────────────────────────────────────────────
1819
1820/// `Set acc to acc + S[..]·K[..]` — a matvec accumulation step; returns the two reads, each as its
1821/// collection and index.
1822fn matvec_step<'a>(
1823    s: Option<&'a Stmt<'a>>,
1824    acc: Symbol,
1825) -> Option<((Symbol, &'a Expr<'a>), (Symbol, &'a Expr<'a>))> {
1826    let Stmt::Set { target, value } = s? else {
1827        return None;
1828    };
1829    want(*target == acc)?;
1830    let (acc_id, term) = as_binop(value, BinaryOpKind::Add)?;
1831    want(is_ident(acc_id, acc))?;
1832    let (l, r) = as_binop(term, BinaryOpKind::Multiply)?;
1833    Some((indexed(l)?, indexed(r)?))
1834}
1835
1836/// `base + i` — a head-relative read index.
1837fn offset_by(idx: &Expr, base: Symbol, i: Symbol) -> bool {
1838    as_binop(idx, BinaryOpKind::Add)
1839        .map(|(b, x)| is_ident(b, base) && is_ident(x, i))
1840        .unwrap_or(false)
1841}
1842
1843/// `V / (1 + E^(0 − V))` where `V = Z[i]` and both reads' `i` satisfy `at` — the silu gate of the head; returns `Z`.
1844fn silu_of_read(e: &Expr, at: &dyn Fn(&Expr) -> bool) -> Option<Symbol> {
1845    let (num, den) = as_binop(e, BinaryOpKind::Divide)?;
1846    let (z, i) = indexed(num)?;
1847    let (one, pow) = as_binop(den, BinaryOpKind::Add)?;
1848    want(is_float(one, 1.0))?;
1849    let (base, expo) = as_binop(pow, BinaryOpKind::Pow)?;
1850    want(is_e(base))?;
1851    let (zero, again) = as_binop(expo, BinaryOpKind::Subtract)?;
1852    let (z2, i2) = indexed(again)?;
1853    want(is_float(zero, 0.0) && z2 == z && at(i) && at(i2))?;
1854    Some(z)
1855}
1856
1857/// `1 / (1 + E^(0 − z[i]))` — the sigmoid of an element read where `at` says.
1858fn sigmoid_of_read(e: &Expr, at: &dyn Fn(&Expr) -> bool) -> Option<Symbol> {
1859    let (num, den) = as_binop(e, BinaryOpKind::Divide)?;
1860    want(is_float(num, 1.0))?;
1861    let (one, pow) = as_binop(den, BinaryOpKind::Add)?;
1862    want(is_float(one, 1.0))?;
1863    let (base, expo) = as_binop(pow, BinaryOpKind::Pow)?;
1864    want(is_e(base))?;
1865    let (zero, read) = as_binop(expo, BinaryOpKind::Subtract)?;
1866    let (z, i) = indexed(read)?;
1867    want(is_float(zero, 0.0) && at(i))?;
1868    Some(z)
1869}
1870
1871/// `(head − 1) · per` — a head's base in a run of `per`-wide heads.
1872fn head_times(e: &Expr, head: Symbol, per: i64) -> bool {
1873    as_binop(e, BinaryOpKind::Multiply).is_some_and(|(h, w)| {
1874        as_binop(h, BinaryOpKind::Subtract)
1875            .is_some_and(|(h, one)| is_ident(h, head) && is_int(one, 1))
1876            && is_int(w, per)
1877    })
1878}
1879
1880/// The Gated DeltaNet step's head loop from `c` — the key/value bases, the gates, the memory update and the gated norm —
1881/// writing `out`, which the zero fill made `filled` long. A block's step runs inside a row loop: `row` is its
1882/// `(rq, rv, rh)` bases, added to the key base and to the gates' index.
1883fn gd_heads<'a>(
1884    c: &mut Cursor<'a>,
1885    out: Symbol,
1886    row: Option<(Symbol, Symbol, Symbol)>,
1887    filled: i64,
1888) -> Option<GatedDeltaSpec> {
1889    let head = c.int_let(1)?;
1890    let head_read = |e: &Expr| -> Option<Symbol> {
1891        let (coll, at) = indexed(e)?;
1892        match row {
1893            None => is_ident(at, head).then_some(coll),
1894            Some((_, _, rh)) => {
1895                let (rh_id, head_id) = as_binop(at, BinaryOpKind::Add)?;
1896                (is_ident(rh_id, rh) && is_ident(head_id, head)).then_some(coll)
1897            }
1898        }
1899    };
1900    let heads = c.loop_(head, true)?;
1901    let hv = heads.bound;
1902    let mut hb = Cursor::new(heads.body, 0);
1903    // Let kb be ((head − 1) mod hk) · d — the key head this value head reads.
1904    let (kb, kb_v) = hb.let_()?;
1905    let kb_v = match row {
1906        None => kb_v,
1907        Some((rq, _, _)) => {
1908            let (rq_id, rest) = as_binop(kb_v, BinaryOpKind::Add)?;
1909            want(is_ident(rq_id, rq))?;
1910            rest
1911        }
1912    };
1913    let (modulo, d_e) = as_binop(kb_v, BinaryOpKind::Multiply)?;
1914    let d = as_int(d_e)?;
1915    let (head_minus, hk_e) = as_binop(modulo, BinaryOpKind::Modulo)?;
1916    let hk = as_int(hk_e)?;
1917    let (head_id, one) = as_binop(head_minus, BinaryOpKind::Subtract)?;
1918    want(is_ident(head_id, head) && is_int(one, 1) && filled == hv * d)?;
1919    let (vb, vb_v) = hb.let_()?;
1920    let vb_v = match row {
1921        None => vb_v,
1922        Some((_, rv, _)) => {
1923            let (rv_id, rest) = as_binop(vb_v, BinaryOpKind::Add)?;
1924            want(is_ident(rv_id, rv))?;
1925            rest
1926        }
1927    };
1928    want(head_times(vb_v, head, d))?;
1929    let (_sb, sb_v) = hb.let_()?;
1930    want(head_times(sb_v, head, d * d))?;
1931    let (alpha, alpha_v) = hb.let_()?;
1932    let a = head_read(alpha_v)?;
1933    let (beta, beta_v) = hb.let_()?;
1934    let b = head_read(beta_v)?;
1935    // 1. u = S·k
1936    let uvec = hb.new_let()?;
1937    let ua = hb.int_let(1)?;
1938    let u_loop = hb.loop_(ua, true)?;
1939    want(u_loop.bound == d)?;
1940    let mut ub_c = Cursor::new(u_loop.body, 0);
1941    let uacc = ub_c.float_let(0.0)?;
1942    let ub = ub_c.int_let(1)?;
1943    let u_inner = ub_c.loop_(ub, true)?;
1944    want(u_inner.bound == d)?;
1945    let ((st, _), (k, k_idx)) = matvec_step(u_inner.body.first(), uacc)?;
1946    want(offset_by(k_idx, kb, ub))?;
1947    let (pushed, into) = ub_c.push()?;
1948    want(is_ident(pushed, uacc) && into == uvec)?;
1949    // 2. S = α·S + β·(v − α·u)·kᵀ
1950    let sa = hb.int_let(1)?;
1951    let s_loop = hb.loop_(sa, true)?;
1952    want(s_loop.bound == d)?;
1953    let mut sc = Cursor::new(s_loop.body, 0);
1954    let (coef, coef_v) = sc.let_()?;
1955    let (beta_id, err) = as_binop(coef_v, BinaryOpKind::Multiply)?;
1956    want(is_ident(beta_id, beta))?;
1957    let (v_read, alpha_u) = as_binop(err, BinaryOpKind::Subtract)?;
1958    let (v, v_idx) = indexed(v_read)?;
1959    want(offset_by(v_idx, vb, sa))?;
1960    let (alpha_id, u_read) = as_binop(alpha_u, BinaryOpKind::Multiply)?;
1961    want(is_ident(alpha_id, alpha) && indexed(u_read)?.0 == uvec)?;
1962    let sbi = sc.int_let(1)?;
1963    let s_inner = sc.loop_(sbi, true)?;
1964    want(s_inner.bound == d)?;
1965    let (st2, _, update) = Cursor::new(s_inner.body, 0).set_index()?;
1966    want(st2 == st)?;
1967    let (decayed, corrected) = as_binop(update, BinaryOpKind::Add)?;
1968    let (alpha_id, st_read) = as_binop(decayed, BinaryOpKind::Multiply)?;
1969    want(is_ident(alpha_id, alpha) && indexed(st_read)?.0 == st)?;
1970    let (coef_id, k_read) = as_binop(corrected, BinaryOpKind::Multiply)?;
1971    want(is_ident(coef_id, coef) && indexed(k_read)?.0 == k)?;
1972    // 3. o = S·q, read from the updated memory
1973    let ovec = hb.new_let()?;
1974    let oa = hb.int_let(1)?;
1975    let o_loop = hb.loop_(oa, true)?;
1976    want(o_loop.bound == d)?;
1977    let mut ob_c = Cursor::new(o_loop.body, 0);
1978    let oacc = ob_c.float_let(0.0)?;
1979    let ob = ob_c.int_let(1)?;
1980    let o_inner = ob_c.loop_(ob, true)?;
1981    want(o_inner.bound == d)?;
1982    let ((st3, _), (q, q_idx)) = matvec_step(o_inner.body.first(), oacc)?;
1983    want(st3 == st && offset_by(q_idx, kb, ob))?;
1984    let (pushed, into) = ob_c.push()?;
1985    let (oacc_id, _scale) = as_binop(pushed, BinaryOpKind::Multiply)?;
1986    want(is_ident(oacc_id, oacc) && into == ovec)?;
1987    // 4. RMSNorm of the ungated head, scale by GN, gate by silu(z)
1988    let ssq = hb.float_let(0.0)?;
1989    let ga = hb.int_let(1)?;
1990    let gate_loop = hb.loop_(ga, true)?;
1991    want(gate_loop.bound == d)?;
1992    let (den, den_v) = hb.let_()?;
1993    let Expr::Call { args, .. } = den_v else {
1994        return None;
1995    };
1996    let [arg] = args.as_slice() else { return None };
1997    let (mean, eps) = as_binop(arg, BinaryOpKind::Add)?;
1998    let (ssq_id, d_f) = as_binop(mean, BinaryOpKind::Divide)?;
1999    want(is_ident(ssq_id, ssq) && is_float(d_f, d as f64))?;
2000    let eps = as_float(eps).filter(|e| *e > 0.0)?;
2001    let na = hb.int_let(1)?;
2002    let norm_loop = hb.loop_(na, true)?;
2003    want(norm_loop.bound == d)?;
2004    let (out2, idx, stored) = Cursor::new(norm_loop.body, 0).set_index()?;
2005    want(out2 == out)?;
2006    let (vb_id, na_id) = as_binop(idx, BinaryOpKind::Add)?;
2007    want(is_ident(vb_id, vb) && is_ident(na_id, na))?;
2008    let (normed, gate) = as_binop(stored, BinaryOpKind::Multiply)?;
2009    let at = |i: &Expr| offset_by(i, vb, na);
2010    let (z, gate) = match silu_of_read(gate, &at) {
2011        Some(z) => (z, GateAct::Silu),
2012        None => (sigmoid_of_read(gate, &at)?, GateAct::Sigmoid),
2013    };
2014    let (gn_read, o_over_den) = as_binop(normed, BinaryOpKind::Multiply)?;
2015    let gn = read_at(gn_read, na)?;
2016    let (o_read, den_id) = as_binop(o_over_den, BinaryOpKind::Divide)?;
2017    want(indexed(o_read)?.0 == ovec && is_ident(den_id, den))?;
2018    Some(GatedDeltaSpec {
2019        q,
2020        k,
2021        v,
2022        z,
2023        b,
2024        a,
2025        st,
2026        gn,
2027        out,
2028        hv: hv as usize,
2029        hk: hk as usize,
2030        d: d as usize,
2031        rows: 1,
2032        eps: Epsilon::new(eps),
2033        gate,
2034    })
2035}
2036
2037fn match_gated_delta<'a>(body: &'a [Stmt<'a>]) -> Option<GatedDeltaSpec> {
2038    for (i, out) in anchors(body) {
2039        let mut c = Cursor::new(body, i + 1);
2040        let Some(zi) = c.int_let(1) else { continue };
2041        let Some(zero_fill) = c.loop_(zi, true) else {
2042            continue;
2043        };
2044        let spec = (|| {
2045            let (zero, into) = Cursor::new(zero_fill.body, 0).push()?;
2046            want(into == out && is_float(zero, 0.0))?;
2047            // One row: the head loop follows the zero fill.
2048            if let Some(spec) = gd_heads(&mut c.clone(), out, None, zero_fill.bound) {
2049                return Some(spec);
2050            }
2051            // A block: a row loop binding the row's bases `(row − 1)·HK·D`, `(row − 1)·HV·D` and `(row − 1)·HV`, then the
2052            // head loop over them.
2053            let row = c.int_let(1)?;
2054            let rows = c.loop_(row, true)?;
2055            want(rows.bound > 1 && zero_fill.bound % rows.bound == 0)?;
2056            let mut rb = Cursor::new(rows.body, 0);
2057            let (rq, rq_v) = rb.let_()?;
2058            let (rv, rv_v) = rb.let_()?;
2059            let (rh, rh_v) = rb.let_()?;
2060            let spec = gd_heads(
2061                &mut rb,
2062                out,
2063                Some((rq, rv, rh)),
2064                zero_fill.bound / rows.bound,
2065            )?;
2066            let (inc, _) = rb.set()?;
2067            want(inc == row && rb.next().is_none())?;
2068            let base = |e: &Expr, per: usize| {
2069                as_binop(e, BinaryOpKind::Multiply).is_some_and(|(r, w)| {
2070                    as_binop(r, BinaryOpKind::Subtract)
2071                        .is_some_and(|(r, one)| is_ident(r, row) && is_int(one, 1))
2072                        && is_int(w, per as i64)
2073                })
2074            };
2075            want(
2076                base(rq_v, spec.hk * spec.d) && base(rv_v, spec.hv * spec.d) && base(rh_v, spec.hv),
2077            )?;
2078            Some(GatedDeltaSpec {
2079                rows: rows.bound as usize,
2080                ..spec
2081            })
2082        })();
2083        if spec.is_some() {
2084            return spec;
2085        }
2086    }
2087    None
2088}
2089
2090/// Recognize `Let O be gated delta of Q over K and V with gate Z beta B decay A state S norm GN with HV heads
2091/// HK groups` — the `head` loop opened by the `(head−1) mod HK` key-head binding, the in-place `S` update,
2092/// and the gated norm that writes the head.
2093pub fn recognize_gated_delta(stmts: &[Stmt]) -> Option<GatedDeltaSpec> {
2094    find_in_bodies(stmts, match_gated_delta)
2095}
2096
2097// ── attention ──────────────────────────────────────────────────────────────────────────────────────────
2098
2099/// Does a block mask with the `-1e30` sentinel — the top-K argmax only the `moe` desugar performs?
2100fn masks(body: &[Stmt]) -> bool {
2101    body.iter().any(|s| match s {
2102        Stmt::SetIndex { value, .. } => {
2103            matches!(value, Expr::Literal(Literal::Float(f)) if *f < -1.0e29)
2104        }
2105        Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => masks(body),
2106        Stmt::If {
2107            then_block,
2108            else_block,
2109            ..
2110        } => masks(then_block) || else_block.map(masks).unwrap_or(false),
2111        _ => false,
2112    })
2113}
2114
2115/// Every `Let`-bound symbol in a block, recursively — the block's temporaries.
2116fn collect_lets(body: &[Stmt], out: &mut HashSet<Symbol>) {
2117    for s in body {
2118        match s {
2119            Stmt::Let { var, .. } => {
2120                out.insert(*var);
2121            }
2122            Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => {
2123                collect_lets(body, out)
2124            }
2125            Stmt::If {
2126                then_block,
2127                else_block,
2128                ..
2129            } => {
2130                collect_lets(then_block, out);
2131                if let Some(eb) = else_block {
2132                    collect_lets(eb, out);
2133                }
2134            }
2135            _ => {}
2136        }
2137    }
2138}
2139
2140/// The literal bound of a `While v ≤ N` loop.
2141fn attention_while_bound(cond: &Expr) -> Option<i64> {
2142    if let Expr::BinaryOp {
2143        op: BinaryOpKind::LtEq,
2144        right,
2145        ..
2146    } = cond
2147    {
2148        return as_int(right);
2149    }
2150    None
2151}
2152
2153/// An attention's operands `(Q, K, V)`: the query and the keys, the first two tensors its block indexes from outside in
2154/// program order, and the values, the one tensor the loop writing the output weighs — the keys again when a tensor is
2155/// both (a latent cache read as its keys and its values).
2156fn attention_qkv(body: &[Stmt], locals: &HashSet<Symbol>) -> Option<(Symbol, Symbol, Symbol)> {
2157    fn values(body: &[Stmt], locals: &HashSet<Symbol>) -> Option<Symbol> {
2158        body.iter().find_map(|st| match st {
2159            Stmt::While { body: wb, .. } if wb.iter().any(|s| matches!(s, Stmt::SetIndex { .. })) => {
2160                let mut read = Vec::new();
2161                attention_operands(wb, locals, &mut read);
2162                match read[..] {
2163                    [v] => Some(v),
2164                    _ => None,
2165                }
2166            }
2167            Stmt::While { body: wb, .. } | Stmt::Repeat { body: wb, .. } | Stmt::Splice { body: wb } => values(wb, locals),
2168            _ => None,
2169        })
2170    }
2171    let mut ops = Vec::new();
2172    attention_operands(body, locals, &mut ops);
2173    let v = values(body, locals)?;
2174    match ops[..] {
2175        [q, k, third, ..] if third == v => Some((q, k, v)),
2176        [q, k] if k == v => Some((q, k, v)),
2177        _ => None,
2178    }
2179}
2180
2181/// The externally-indexed operands of an attention desugar, in program order (`Q, K, V`).
2182fn attention_operands(body: &[Stmt], locals: &HashSet<Symbol>, out: &mut Vec<Symbol>) {
2183    fn ex(e: &Expr, locals: &HashSet<Symbol>, out: &mut Vec<Symbol>) {
2184        match e {
2185            Expr::Index { collection, index } => {
2186                if let Expr::Identifier(s) = &**collection {
2187                    if !locals.contains(s) && !out.contains(s) {
2188                        out.push(*s);
2189                    }
2190                }
2191                ex(index, locals, out);
2192            }
2193            Expr::BinaryOp { left, right, .. } => {
2194                ex(left, locals, out);
2195                ex(right, locals, out);
2196            }
2197            Expr::Call { args, .. } => {
2198                for a in args {
2199                    ex(a, locals, out);
2200                }
2201            }
2202            _ => {}
2203        }
2204    }
2205    for s in body {
2206        match s {
2207            Stmt::Set { value, .. } => ex(value, locals, out),
2208            Stmt::SetIndex { index, value, .. } => {
2209                ex(index, locals, out);
2210                ex(value, locals, out);
2211            }
2212            Stmt::Push { value, .. } => ex(value, locals, out),
2213            Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => {
2214                attention_operands(body, locals, out)
2215            }
2216            Stmt::If {
2217                cond,
2218                then_block,
2219                else_block,
2220            } => {
2221                ex(cond, locals, out);
2222                attention_operands(then_block, locals, out);
2223                if let Some(eb) = else_block {
2224                    attention_operands(eb, locals, out);
2225                }
2226            }
2227            _ => {}
2228        }
2229    }
2230}
2231
2232/// `((n − 1)/C)·C + 1` over the attended length `n`: the first position of a query's chunk of `C`.
2233fn chunk_start(value: &Expr, bound: &Expr) -> Option<usize> {
2234    let (scaled, one) = as_binop(value, BinaryOpKind::Add)?;
2235    let (whole, c) = as_binop(scaled, BinaryOpKind::Multiply)?;
2236    let (before, c_again) = as_binop(whole, BinaryOpKind::Divide)?;
2237    let (n, one_again) = as_binop(before, BinaryOpKind::Subtract)?;
2238    let c = as_int(c)?;
2239    (c > 0 && as_int(c_again) == Some(c) && as_int(one) == Some(1) && as_int(one_again) == Some(1) && same_length(n, bound))
2240        .then_some(c as usize)
2241}
2242
2243/// Whether `a` and `b` name the same attended length: one scalar, or one literal.
2244fn same_length(a: &Expr, b: &Expr) -> bool {
2245    match (a, b) {
2246        (Expr::Identifier(x), Expr::Identifier(y)) => x == y,
2247        _ => as_int(a).is_some() && as_int(a) == as_int(b),
2248    }
2249}
2250
2251/// The head loop of an attention desugar, as its recognizers read it.
2252struct Heads<'a> {
2253    /// The query heads: the loop's bound.
2254    h: usize,
2255    /// The query heads per key/value head: the `kvh` divisor.
2256    group: usize,
2257    /// The positions attended: the score loop's bound.
2258    bound: &'a Expr<'a>,
2259    /// The head dimension: the score loop's inner bound.
2260    hd: usize,
2261    /// The value head width: the weighted sum's bound.
2262    vd: usize,
2263    /// The score scale, when it is not `1/√hd`: the factor each score is pushed times.
2264    scale: Option<Factor>,
2265    /// The cap each scaled score is pushed under, when it is capped.
2266    softcap: Option<Factor>,
2267    /// The window, when the head loop binds one.
2268    window: Option<Window>,
2269    /// The sinks' tensor, when the softmax's maximum starts from one.
2270    sinks: Option<Symbol>,
2271}
2272
2273/// The loops of an attention desugar's heads, whatever its softmax: the head loop (the `While` among `body`'s statements
2274/// whose body Lets the per-head `scores`), its body, its bound, the `kvh` divisor, and the score loop's bound and inner
2275/// bound.
2276struct HeadLoops<'a> {
2277    /// The head loop's body.
2278    body: &'a [Stmt<'a>],
2279    /// The query heads: the loop's bound.
2280    h: usize,
2281    /// The query heads per key/value head: the `kvh` divisor.
2282    group: usize,
2283    /// The positions attended: the score loop's bound.
2284    bound: &'a Expr<'a>,
2285    /// The head dimension: the score loop's inner bound.
2286    hd: usize,
2287}
2288
2289fn head_loops<'a>(body: &'a [Stmt<'a>]) -> Option<HeadLoops<'a>> {
2290    let head = body.iter().find_map(|st| match st {
2291        Stmt::While { cond, body: wb, .. }
2292            if wb
2293                .iter()
2294                .any(|s| matches!(s, Stmt::Let { value, .. } if is_new(value))) =>
2295        {
2296            Some((cond, wb))
2297        }
2298        _ => None,
2299    })?;
2300    let h = attention_while_bound(head.0)? as usize;
2301    let hbody = head.1;
2302    let group = hbody.iter().find_map(|st| match st {
2303        Stmt::Let {
2304            value:
2305                Expr::BinaryOp {
2306                    op: BinaryOpKind::Divide,
2307                    right,
2308                    ..
2309                },
2310            ..
2311        } => as_int(right).map(|g| g as usize),
2312        _ => None,
2313    })?;
2314    let (bound, hd) = hbody.iter().find_map(|st| match st {
2315        Stmt::While {
2316            cond:
2317                Expr::BinaryOp {
2318                    op: BinaryOpKind::LtEq,
2319                    right,
2320                    ..
2321                },
2322            body: sb,
2323            ..
2324        } => sb
2325            .iter()
2326            .find_map(|s| {
2327                if let Stmt::While { cond: ic, .. } = s {
2328                    attention_while_bound(ic)
2329                } else {
2330                    None
2331                }
2332            })
2333            .map(|hd| (*right, hd as usize)),
2334        _ => None,
2335    })?;
2336    Some(HeadLoops { body: hbody, h, group, bound, hd })
2337}
2338
2339/// The head loop of a float attention desugar among `body`'s statements, with its value width, its score scale, its
2340/// window and its sinks.
2341fn attention_heads<'a>(body: &'a [Stmt<'a>]) -> Option<Heads<'a>> {
2342    let HeadLoops { body: hbody, h, group, bound, hd } = head_loops(body)?;
2343    // The weighted sum writes the output: its loop runs over the value head width.
2344    let vd = hbody.iter().find_map(|st| match st {
2345        Stmt::While { cond, body: wb, .. } if wb.iter().any(|s| matches!(s, Stmt::SetIndex { .. })) => {
2346            attention_while_bound(cond)
2347        }
2348        _ => None,
2349    })? as usize;
2350    // Each score is pushed times the scale, capped where the program caps it.
2351    let (pushed, softcap) = hbody.iter().find_map(|st| match st {
2352        Stmt::While { body: sb, .. } => sb.iter().find_map(|s| match s {
2353            Stmt::Push { value, .. } => {
2354                let (scaled, cap) = match as_capped(value) {
2355                    Some((scaled, c)) => (scaled, Some(Factor::new(c))),
2356                    None => (*value, None),
2357                };
2358                as_binop(scaled, BinaryOpKind::Multiply).and_then(|(_, f)| as_float(f)).map(|f| (f, cap))
2359            }
2360            _ => None,
2361        }),
2362        _ => None,
2363    })?;
2364    let scale = (pushed.to_bits() != (1.0f64 / (hd as f64).sqrt()).to_bits()).then(|| Factor::new(pushed));
2365    // A window binds its first position in the head loop, and the score loop starts there: `max(1, n − (W − 1))` for
2366    // the last `W`, `((n − 1)/C)·C + 1` for a chunk of `C`.
2367    let window = hbody.iter().find_map(|st| match st {
2368        Stmt::Let { value: Expr::Call { args, .. }, .. } => match args.as_slice() {
2369            [one, Expr::BinaryOp { op: BinaryOpKind::Subtract, left, right }] if as_int(one) == Some(1) && same_length(left, bound) => {
2370                as_int(right).map(|k| Window::Sliding(k as usize + 1))
2371            }
2372            _ => None,
2373        },
2374        Stmt::Let { value, .. } => chunk_start(value, bound).map(Window::Chunked),
2375        _ => None,
2376    });
2377    // Sinks start the softmax's maximum at `max(scores[1], S[head])`.
2378    let sinks = hbody.iter().find_map(|st| match st {
2379        Stmt::Let { value: Expr::Call { args, .. }, .. } => match args.as_slice() {
2380            [first, sink] => {
2381                let (_, one) = as_index(first)?;
2382                let (s, at) = as_index(sink)?;
2383                (as_int(one) == Some(1) && matches!(at, Expr::Identifier(_))).then_some(as_ident(s)?)
2384            }
2385            _ => None,
2386        },
2387        _ => None,
2388    });
2389    Some(Heads { h, group, bound, hd, vd, scale, softcap, window, sinks })
2390}
2391
2392/// Recognize the single-query GQA `attention` block. The desugar is a `Splice` that: Lets the output `Seq`
2393/// (first `New`), zero-fills it, then a head-loop (`While` ≤ H whose body Lets a `scores` `New(Seq)`) computes
2394/// `scores = Q·K / √HD`, a softmax (`Pow(E, …)`), and the weighted sum of `V`, `SetIndex`ing the output. It
2395/// does NOT mask with the `-1e30` sentinel (that's `moe`). Dims come from the loop bounds (H = head-loop
2396/// bound, L = score-loop bound, HD = score-inner bound) and the `kvh` divisor (`group = H/KV`); the three
2397/// externally-indexed operands, in program order, are `Q, K, V`. The masked `up to P` form sets `pos`.
2398pub fn recognize_attention(stmts: &[Stmt]) -> Option<AttentionSpec> {
2399    for s in stmts {
2400        let Stmt::Splice { body } = s else { continue };
2401        if masks(body) || !exp_softmax(body) {
2402            continue;
2403        }
2404        // Output = the first Let with a New(Seq) value.
2405        let out = body.iter().find_map(|st| match st {
2406            Stmt::Let { var, value, .. } if is_new(value) => Some(*var),
2407            _ => None,
2408        })?;
2409        // Head-loop = the While whose body Lets a New(Seq) (the per-head `scores`); group = the `kvh` divisor; the score
2410        // loop runs over the cache — to the literal L, or (the masked `up to P` form) to a length bound at the top.
2411        let Heads { h, group, bound, hd, vd, scale, softcap, window, sinks } = attention_heads(body)?;
2412        let kv = if group > 0 { h / group } else { h };
2413        let (l, pos) = match bound {
2414            Expr::Identifier(len) => match masked_length(body, *len) {
2415                Some(masked) => masked,
2416                None => continue,
2417            },
2418            other => (as_int(other).unwrap_or(0) as usize, None),
2419        };
2420        let mut locals = HashSet::new();
2421        collect_lets(body, &mut locals);
2422        let Some((q, kc, vc)) = attention_qkv(body, &locals) else { continue };
2423        if h == 0 || hd == 0 || vd == 0 || l == 0 {
2424            continue;
2425        }
2426        return Some(AttentionSpec {
2427            q,
2428            kc,
2429            vc,
2430            out,
2431            h,
2432            kv,
2433            hd,
2434            vd,
2435            scale,
2436            softcap,
2437            l,
2438            pos,
2439            rows: 1,
2440            window,
2441            sinks,
2442        });
2443    }
2444    None
2445}
2446
2447/// Recognize `Let C be causal attention of Q over K and V with H heads KV groups from P` — a block of `T` query rows:
2448/// the `Let from be P` and its block check `from ≥ 1 and from + (T − 1) ≤ L` at the top, the output's zero fill, then a
2449/// row loop (to `T`) whose body binds the row's length `from + row − 1` and runs the single-query head loop over it.
2450pub fn recognize_causal_attention(stmts: &[Stmt]) -> Option<AttentionSpec> {
2451    for s in stmts {
2452        let Stmt::Splice { body } = s else { continue };
2453        if masks(body) || !exp_softmax(body) {
2454            continue;
2455        }
2456        let Some(out) = body.iter().find_map(|st| match st {
2457            Stmt::Let { var, value, .. } if is_new(value) => Some(*var),
2458            _ => None,
2459        }) else {
2460            continue;
2461        };
2462        // The row loop: a top-level While whose body holds the head loop.
2463        let Some((rows, rbody)) = body.iter().find_map(|st| match st {
2464            Stmt::While { cond, body: wb, .. } if attention_heads(wb).is_some() => {
2465                Some((attention_while_bound(cond)? as usize, wb))
2466            }
2467            _ => None,
2468        }) else {
2469            continue;
2470        };
2471        let Some(Heads { h, group, bound: Expr::Identifier(row_len), hd, vd, scale, softcap, window, sinks }) = attention_heads(rbody) else {
2472            continue;
2473        };
2474        let kv = if group > 0 { h / group } else { h };
2475        // The row's length is `from + row − 1`; `from` is bound to `P` at the top and the block checked against `L`.
2476        let from = rbody.iter().find_map(|st| match st {
2477            Stmt::Let {
2478                var,
2479                value:
2480                    Expr::BinaryOp {
2481                        op: BinaryOpKind::Subtract,
2482                        left:
2483                            Expr::BinaryOp {
2484                                op: BinaryOpKind::Add,
2485                                left: Expr::Identifier(f),
2486                                ..
2487                            },
2488                        ..
2489                    },
2490                ..
2491            } if var == row_len => Some(*f),
2492            _ => None,
2493        });
2494        let Some(from) = from else { continue };
2495        let Some(pos) = body.iter().find_map(|st| match st {
2496            Stmt::Let { var, value, .. } if *var == from => match value {
2497                Expr::Identifier(p) => Some(RowIndex::Named(*p)),
2498                other => as_int(other).map(RowIndex::Literal),
2499            },
2500            _ => None,
2501        }) else {
2502            continue;
2503        };
2504        let l = body.iter().find_map(|st| match st {
2505            Stmt::RuntimeAssert {
2506                condition:
2507                    Expr::BinaryOp {
2508                        op: BinaryOpKind::And,
2509                        right:
2510                            Expr::BinaryOp {
2511                                op: BinaryOpKind::LtEq,
2512                                left:
2513                                    Expr::BinaryOp {
2514                                        op: BinaryOpKind::Add,
2515                                        left: Expr::Identifier(f),
2516                                        right: span,
2517                                    },
2518                                right: cap,
2519                            },
2520                        ..
2521                    },
2522                ..
2523            } if *f == from && as_int(span) == Some(rows as i64 - 1) => as_int(cap),
2524            _ => None,
2525        });
2526        let Some(l) = l.filter(|l| *l > 0) else {
2527            continue;
2528        };
2529        let mut locals = HashSet::new();
2530        collect_lets(body, &mut locals);
2531        let Some((q, kc, vc)) = attention_qkv(body, &locals) else { continue };
2532        if h == 0 || hd == 0 || vd == 0 || rows < 2 {
2533            continue;
2534        }
2535        return Some(AttentionSpec {
2536            q,
2537            kc,
2538            vc,
2539            out,
2540            h,
2541            kv,
2542            hd,
2543            vd,
2544            scale,
2545            softcap,
2546            l: l as usize,
2547            pos: Some(pos),
2548            rows,
2549            window,
2550            sinks,
2551        });
2552    }
2553    None
2554}
2555
2556/// The masked attention's bound: `Let len be P` and the check `len ≥ 1 and len ≤ L` at the top of the block —
2557/// `(L, P)`, with `P` a named scalar or a literal (an expression bound is not a lifted form).
2558fn masked_length(body: &[Stmt], len: Symbol) -> Option<(usize, Option<RowIndex>)> {
2559    let value = body.iter().find_map(|s| match s {
2560        Stmt::Let { var, value, .. } if *var == len => Some(*value),
2561        _ => None,
2562    })?;
2563    let pos = match value {
2564        Expr::Identifier(p) => RowIndex::Named(*p),
2565        other => RowIndex::Literal(as_int(other)?),
2566    };
2567    let rows = body.iter().find_map(|s| match s {
2568        Stmt::RuntimeAssert {
2569            condition:
2570                Expr::BinaryOp {
2571                    op: BinaryOpKind::And,
2572                    right,
2573                    ..
2574                },
2575            ..
2576        } => match right {
2577            Expr::BinaryOp {
2578                op: BinaryOpKind::LtEq,
2579                left: Expr::Identifier(v),
2580                right: r,
2581            } if *v == len => as_int(r),
2582            _ => None,
2583        },
2584        _ => None,
2585    })?;
2586    (rows > 0).then_some((rows as usize, Some(pos)))
2587}
2588
2589// ── lut attention: the int8 model's softmax ───────────────────────────────────────────────────────────
2590
2591/// `Let C be lut attention of Q over K and V with H heads KV groups [up to P] with table E and scale M shifted right
2592/// by S` — single-query attention over Int tensors whose softmax is an exp table: per head, scores `q · k_j`, their
2593/// maximum, weights `E[clamp(((max − s_j) · M) >> S, 0, table_len − 1)]` and the context `⌊Σ w_j · v_j / Σ w_j⌋`.
2594/// The shapes and `pos` are [`AttentionSpec`]'s.
2595#[derive(Debug, Clone, PartialEq, Eq)]
2596pub struct LutAttentionSpec {
2597    pub q: Symbol,
2598    pub kc: Symbol,
2599    pub vc: Symbol,
2600    pub table: Symbol,
2601    pub out: Symbol,
2602    pub h: usize,
2603    pub kv: usize,
2604    pub hd: usize,
2605    pub l: usize,
2606    pub table_len: usize,
2607    pub pos: Option<RowIndex>,
2608    pub m: i64,
2609    pub s: u32,
2610}
2611
2612/// Whether any expression a statement list holds, at any depth, satisfies `f`.
2613fn any_expr(body: &[Stmt], f: &dyn Fn(&Expr) -> bool) -> bool {
2614    fn ex(e: &Expr, f: &dyn Fn(&Expr) -> bool) -> bool {
2615        f(e) || match e {
2616            Expr::BinaryOp { left, right, .. } => ex(left, f) || ex(right, f),
2617            Expr::Index { collection, index } => ex(collection, f) || ex(index, f),
2618            Expr::Call { args, .. } => args.iter().any(|a| ex(a, f)),
2619            _ => false,
2620        }
2621    }
2622    body.iter().any(|s| match s {
2623        Stmt::Let { value, .. } | Stmt::Set { value, .. } | Stmt::Push { value, .. } => ex(value, f),
2624        Stmt::SetIndex { index, value, .. } => ex(index, f) || ex(value, f),
2625        Stmt::While { cond, body, .. } => ex(cond, f) || any_expr(body, f),
2626        Stmt::Repeat { body, .. } | Stmt::Splice { body } => any_expr(body, f),
2627        Stmt::If {
2628            cond,
2629            then_block,
2630            else_block,
2631            ..
2632        } => ex(cond, f) || any_expr(then_block, f) || else_block.is_some_and(|eb| any_expr(eb, f)),
2633        _ => false,
2634    })
2635}
2636
2637/// A float softmax's exponent `E^(…)` somewhere in the block — the float attention forms' own signature.
2638fn exp_softmax(body: &[Stmt]) -> bool {
2639    any_expr(body, &|e| {
2640        as_binop(e, BinaryOpKind::Pow).is_some_and(|(base, _)| is_e(base))
2641    })
2642}
2643
2644/// `E[min(max(((mx − sc[j]) · M) >> S, 0), N − 1) + 1]` → `(E, M, S, N)`: the LUT weight read.
2645fn lut_read<'a>(e: &'a Expr<'a>, interner: &Interner) -> Option<(Symbol, i64, u32, usize)> {
2646    let (table, at) = indexed(e)?;
2647    let (idx, one) = as_binop(at, BinaryOpKind::Add)?;
2648    want(is_int(one, 1))?;
2649    let [lower, last] = call_of(idx, interner, "min")? else {
2650        return None;
2651    };
2652    let [shifted, zero] = call_of(lower, interner, "max")? else {
2653        return None;
2654    };
2655    want(is_int(zero, 0))?;
2656    let (prod, shift) = as_binop(shifted, BinaryOpKind::Shr)?;
2657    let (diff, m) = as_binop(prod, BinaryOpKind::Multiply)?;
2658    as_binop(diff, BinaryOpKind::Subtract)?;
2659    let s = u32::try_from(as_int(shift)?).ok().filter(|s| *s <= 63)?;
2660    let n = usize::try_from(as_int(last)? + 1).ok().filter(|n| *n >= 1)?;
2661    Some((table, as_int(m)?, s, n))
2662}
2663
2664/// The LUT weight read a block binds, at any depth.
2665fn find_lut_read<'a>(body: &'a [Stmt<'a>], interner: &Interner) -> Option<(Symbol, i64, u32, usize)> {
2666    body.iter().find_map(|s| match s {
2667        Stmt::Let { value, .. } => lut_read(value, interner),
2668        Stmt::While { body, .. } | Stmt::Repeat { body, .. } => find_lut_read(body, interner),
2669        _ => None,
2670    })
2671}
2672
2673/// Whether a block writes an element by floor division, at any depth — the integer softmax's normalization.
2674fn floor_divides(body: &[Stmt]) -> bool {
2675    body.iter().any(|s| match s {
2676        Stmt::SetIndex { value, .. } => as_binop(value, BinaryOpKind::FloorDivide).is_some(),
2677        Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => floor_divides(body),
2678        _ => false,
2679    })
2680}
2681
2682/// Recognize the `lut attention` block: a `Splice` holding the LUT weight read and a floor-division write, its head
2683/// loop and cache bound the single-query attention's (`attention_heads`, `masked_length`). Its operands, the
2684/// tensors it reads by index and does not bind, are `Q, K, E, V` in program order; the table is the one the LUT read
2685/// names.
2686pub fn recognize_lut_attention(stmts: &[Stmt], interner: &Interner) -> Option<LutAttentionSpec> {
2687    for s in stmts {
2688        let Stmt::Splice { body } = s else { continue };
2689        let Some((table, m, shift, table_len)) = find_lut_read(body, interner) else {
2690            continue;
2691        };
2692        if !floor_divides(body) || exp_softmax(body) {
2693            continue;
2694        }
2695        let Some(out) = body.iter().find_map(|st| match st {
2696            Stmt::Let { var, value, .. } if is_new(value) => Some(*var),
2697            _ => None,
2698        }) else {
2699            continue;
2700        };
2701        let Some(HeadLoops { h, group, bound, hd, .. }) = head_loops(body) else {
2702            continue;
2703        };
2704        if group == 0 || h % group != 0 {
2705            continue;
2706        }
2707        let (l, pos) = match bound {
2708            Expr::Identifier(len) => match masked_length(body, *len) {
2709                Some(masked) => masked,
2710                None => continue,
2711            },
2712            other => (as_int(other).unwrap_or(0) as usize, None),
2713        };
2714        let mut locals = HashSet::new();
2715        collect_lets(body, &mut locals);
2716        let mut ops: Vec<Symbol> = Vec::new();
2717        attention_operands(body, &locals, &mut ops);
2718        ops.retain(|o| *o != table);
2719        if ops.len() != 3 || h == 0 || hd == 0 || l == 0 {
2720            continue;
2721        }
2722        return Some(LutAttentionSpec {
2723            q: ops[0],
2724            kc: ops[1],
2725            vc: ops[2],
2726            table,
2727            out,
2728            h,
2729            kv: h / group,
2730            hd,
2731            l,
2732            table_len,
2733            pos,
2734            m,
2735            s: shift,
2736        });
2737    }
2738    None
2739}
2740
2741// ── moe ────────────────────────────────────────────────────────────────────────────────────────────────
2742
2743fn moe_ident(e: &Expr) -> Option<Symbol> {
2744    match e {
2745        Expr::Identifier(s) => Some(*s),
2746        _ => None,
2747    }
2748}
2749fn moe_num(e: &Expr) -> Option<i64> {
2750    match e {
2751        Expr::Literal(Literal::Number(n)) => Some(*n),
2752        _ => None,
2753    }
2754}
2755/// The distinct identifier symbols an index expression mentions (its loop-var arity distinguishes the
2756/// operands: `x[t]` = 1, `wr[(t-1)·E+e]` = 2, `wg[((e-1)·dff+(o-1))·d+t]` = 3).
2757fn moe_idx_vars(e: &Expr, out: &mut HashSet<Symbol>) {
2758    match e {
2759        Expr::Identifier(s) => {
2760            out.insert(*s);
2761        }
2762        Expr::BinaryOp { left, right, .. } => {
2763            moe_idx_vars(left, out);
2764            moe_idx_vars(right, out);
2765        }
2766        _ => {}
2767    }
2768}
2769/// `E` from a router index `(t-1)·E + e` = `Add(Mul(_, E), _)`.
2770fn moe_ne_from(idx: &Expr) -> Option<usize> {
2771    if let Expr::BinaryOp {
2772        op: BinaryOpKind::Add,
2773        left,
2774        ..
2775    } = idx
2776    {
2777        if let Expr::BinaryOp {
2778            op: BinaryOpKind::Multiply,
2779            right,
2780            ..
2781        } = &**left
2782        {
2783            return moe_num(right).map(|n| n as usize);
2784        }
2785    }
2786    None
2787}
2788/// `(d, dff)` from a gate index `((e-1)·dff + (o-1))·d + t` = `Add(Mul(Add(Mul(_, dff), _), d), _)`.
2789fn moe_d_dff_from(idx: &Expr) -> Option<(usize, usize)> {
2790    if let Expr::BinaryOp {
2791        op: BinaryOpKind::Add,
2792        left,
2793        ..
2794    } = idx
2795    {
2796        if let Expr::BinaryOp {
2797            op: BinaryOpKind::Multiply,
2798            left: inner,
2799            right: d,
2800        } = &**left
2801        {
2802            if let Expr::BinaryOp {
2803                op: BinaryOpKind::Add,
2804                left: mul2,
2805                ..
2806            } = &**inner
2807            {
2808                if let Expr::BinaryOp {
2809                    op: BinaryOpKind::Multiply,
2810                    right: dff,
2811                    ..
2812                } = &**mul2
2813                {
2814                    return Some((moe_num(d)? as usize, moe_num(dff)? as usize));
2815                }
2816            }
2817        }
2818    }
2819    None
2820}
2821
2822/// Recognize a MoE block anywhere in the statement list (through `Splice`). `None` if not the `moe` shape.
2823pub fn recognize_moe(stmts: &[Stmt]) -> Option<MoeSpec> {
2824    // The MoE desugar is the ONLY block that masks its router probs with a large negative sentinel via
2825    // `SetIndex` — the top-K argmax. Find that block's body.
2826    fn find_body<'a, 'b>(stmts: &'b [Stmt<'a>]) -> Option<&'b [Stmt<'a>]> {
2827        for s in stmts {
2828            if let Stmt::Splice { body } = s {
2829                if masks(body) {
2830                    return Some(body);
2831                }
2832                if let Some(b) = find_body(body) {
2833                    return Some(b);
2834                }
2835            }
2836        }
2837        None
2838    }
2839    let body = find_body(stmts)?;
2840
2841    // Temps are Let-bound inside the block; operands are the tensors indexed but bound OUTSIDE.
2842    let mut locals = HashSet::new();
2843    collect_lets(body, &mut locals);
2844
2845    // Every operand index access, in program order: (operand symbol, its index expression).
2846    let mut accesses: Vec<(Symbol, &Expr)> = Vec::new();
2847    fn walk_idx<'a, 'b>(
2848        body: &'b [Stmt<'a>],
2849        locals: &HashSet<Symbol>,
2850        out: &mut Vec<(Symbol, &'b Expr<'a>)>,
2851    ) {
2852        fn expr<'a, 'b>(
2853            e: &'b Expr<'a>,
2854            locals: &HashSet<Symbol>,
2855            out: &mut Vec<(Symbol, &'b Expr<'a>)>,
2856        ) {
2857            if let Expr::Index { collection, index } = e {
2858                if let Some(s) = moe_ident(collection) {
2859                    if !locals.contains(&s) {
2860                        out.push((s, index));
2861                    }
2862                }
2863                expr(index, locals, out);
2864            }
2865            if let Expr::BinaryOp { left, right, .. } = e {
2866                expr(left, locals, out);
2867                expr(right, locals, out);
2868            }
2869        }
2870        for s in body {
2871            match s {
2872                Stmt::Set { value, .. } => expr(value, locals, out),
2873                Stmt::SetIndex { index, value, .. } => {
2874                    expr(index, locals, out);
2875                    expr(value, locals, out);
2876                }
2877                Stmt::Push { value, .. } => expr(value, locals, out),
2878                Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => {
2879                    walk_idx(body, locals, out)
2880                }
2881                Stmt::If {
2882                    cond,
2883                    then_block,
2884                    else_block,
2885                } => {
2886                    expr(cond, locals, out);
2887                    walk_idx(then_block, locals, out);
2888                    if let Some(eb) = else_block {
2889                        walk_idx(eb, locals, out);
2890                    }
2891                }
2892                _ => {}
2893            }
2894        }
2895    }
2896    walk_idx(body, &locals, &mut accesses);
2897
2898    // Classify operands by index arity: x=1, wr=2, {wg,wu,wd}=3 (in first-appearance order).
2899    let arity = |idx: &Expr| {
2900        let mut v = HashSet::new();
2901        moe_idx_vars(idx, &mut v);
2902        v.len()
2903    };
2904    let mut x = None;
2905    let mut wr = None;
2906    let mut wr_idx = None;
2907    let mut three: Vec<(Symbol, &Expr)> = Vec::new();
2908    let mut seen3 = HashSet::new();
2909    for &(s, idx) in &accesses {
2910        match arity(idx) {
2911            1 => {
2912                if x.is_none() {
2913                    x = Some(s);
2914                }
2915            }
2916            2 => {
2917                if wr.is_none() {
2918                    wr = Some(s);
2919                    wr_idx = Some(idx);
2920                }
2921            }
2922            3 => {
2923                if seen3.insert(s) {
2924                    three.push((s, idx));
2925                }
2926            }
2927            _ => {}
2928        }
2929    }
2930    let (x, wr) = (x?, wr?);
2931    let ne = moe_ne_from(wr_idx?)?;
2932    let (wg, wg_idx) = three.first().copied()?;
2933    let (wu, _) = three.get(1).copied()?;
2934    let (wd, _) = three.get(2).copied()?;
2935    let (d, dff) = moe_d_dff_from(wg_idx)?;
2936
2937    // `out` = the tensor written by the weighted accumulation `SetIndex out[o] = out[o] + w[e]·dacc`.
2938    fn find_out(body: &[Stmt]) -> Option<Symbol> {
2939        for s in body {
2940            match s {
2941                Stmt::SetIndex {
2942                    collection, value, ..
2943                } => {
2944                    if let (
2945                        Some(c),
2946                        Expr::BinaryOp {
2947                            op: BinaryOpKind::Add,
2948                            left,
2949                            ..
2950                        },
2951                    ) = (moe_ident(collection), value)
2952                    {
2953                        if let Expr::Index { collection: lc, .. } = &**left {
2954                            if moe_ident(lc) == Some(c) {
2955                                return Some(c);
2956                            }
2957                        }
2958                    }
2959                }
2960                Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => {
2961                    if let Some(o) = find_out(body) {
2962                        return Some(o);
2963                    }
2964                }
2965                Stmt::If {
2966                    then_block,
2967                    else_block,
2968                    ..
2969                } => {
2970                    if let Some(o) =
2971                        find_out(then_block).or_else(|| else_block.and_then(|eb| find_out(eb)))
2972                    {
2973                        return Some(o);
2974                    }
2975                }
2976                _ => {}
2977            }
2978        }
2979        None
2980    }
2981    let out = find_out(body)?;
2982
2983    // `k` = the bound of the round loop: the `While` whose body masks a score with the negative sentinel and writes the
2984    // weight it picked (`W[b] = P[b]`) — a group limit's rounds mask their groups and write no weight.
2985    fn find_k(body: &[Stmt]) -> Option<usize> {
2986        for s in body {
2987            if let Stmt::While { cond, body: wb, .. } = s {
2988                let masks_here = wb.iter().any(|s| matches!(s, Stmt::SetIndex { value: Expr::Literal(Literal::Float(f)), .. } if *f < -1.0e29))
2989                    && wb.iter().any(|s| matches!(s, Stmt::SetIndex { value: Expr::Index { .. }, .. }));
2990                if masks_here {
2991                    if let Expr::BinaryOp {
2992                        op: BinaryOpKind::LtEq,
2993                        right,
2994                        ..
2995                    } = cond
2996                    {
2997                        return moe_num(right).map(|n| n as usize);
2998                    }
2999                }
3000            }
3001            if let Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } = s
3002            {
3003                if let Some(k) = find_k(body) {
3004                    return Some(k);
3005                }
3006            }
3007        }
3008        None
3009    }
3010    let k = find_k(body)?;
3011    let (score, weigh_inputs) = moe_routing(body);
3012    let (router_bias, biases, act) = moe_options(body, &locals, wg, wu);
3013    // The renormalization divides each kept weight by their sum: `W[i] = W[i] / sum`, in a loop of its own.
3014    fn renormalizes(body: &[Stmt], locals: &HashSet<Symbol>) -> bool {
3015        body.iter().any(|s| match s {
3016            Stmt::While { body, .. } => body.iter().any(|s| {
3017                matches!(s, Stmt::SetIndex { collection, value: Expr::BinaryOp { op: BinaryOpKind::Divide, left, right }, .. }
3018                    if as_index(left).and_then(|(c, _)| as_ident(c)) == as_ident(collection)
3019                        && as_ident(right).is_some_and(|r| locals.contains(&r)))
3020            }),
3021            _ => false,
3022        })
3023    }
3024    let renormalize = renormalizes(body, &locals);
3025    let (selection_bias, groups, scale) = moe_choice(body, &locals);
3026
3027    Some(MoeSpec {
3028        out,
3029        x,
3030        wr,
3031        wg,
3032        wu,
3033        wd,
3034        d,
3035        dff,
3036        ne,
3037        k,
3038        router_bias,
3039        biases,
3040        renormalize,
3041        act,
3042        score,
3043        weigh_inputs,
3044        selection_bias,
3045        groups,
3046        scale,
3047    })
3048}
3049
3050/// A mixture's choice, read off its desugar: the selection bias is the operand added to a score as it is copied for the
3051/// choosing (`P[i] + B[i]`, pushed); the group limit's rounds are the loop that marks a group chosen with `−2e30`, its
3052/// bound `G`, the bound of the scan inside it `N`; the routed scale is the literal a weight is multiplied by in place
3053/// (`W[i] = W[i]·S`).
3054fn moe_choice(body: &[Stmt], locals: &HashSet<Symbol>) -> (Option<Symbol>, Option<MoeGroups>, Option<u64>) {
3055    fn bound(cond: &Expr) -> Option<usize> {
3056        match cond {
3057            Expr::BinaryOp { op: BinaryOpKind::LtEq, right, .. } => moe_num(right).and_then(|n| usize::try_from(n).ok()),
3058            _ => None,
3059        }
3060    }
3061    fn walk(body: &[Stmt], locals: &HashSet<Symbol>, found: &mut (Option<Symbol>, Option<MoeGroups>, Option<u64>)) {
3062        for s in body {
3063            match s {
3064                Stmt::Push { value, collection } if moe_ident(collection).is_some_and(|c| locals.contains(&c)) => {
3065                    if let Some((score, bias)) = as_binop(value, BinaryOpKind::Add) {
3066                        let local_score = as_index(score).and_then(|(c, _)| as_ident(c)).is_some_and(|c| locals.contains(&c));
3067                        let operand = as_index(bias).and_then(|(c, _)| as_ident(c)).filter(|b| !locals.contains(b));
3068                        if let (true, Some(b)) = (local_score, operand) {
3069                            found.0 = Some(b);
3070                        }
3071                    }
3072                }
3073                Stmt::SetIndex { collection, value, .. } => {
3074                    if let Some((read, by)) = as_binop(value, BinaryOpKind::Multiply) {
3075                        if let (Some((c, _)), Some(v)) = (as_index(read), as_float(by)) {
3076                            if as_ident(c).is_some() && as_ident(c) == moe_ident(collection) {
3077                                found.2 = Some(v.to_bits());
3078                            }
3079                        }
3080                    }
3081                }
3082                Stmt::While { cond, body: wb, .. } => {
3083                    let marks = wb.iter().any(|s| matches!(s, Stmt::SetIndex { value: Expr::Literal(Literal::Float(f)), .. } if *f < -1.5e30));
3084                    if marks {
3085                        let count = wb.iter().find_map(|s| match s {
3086                            Stmt::While { cond, .. } => bound(cond),
3087                            _ => None,
3088                        });
3089                        if let (Some(kept), Some(count)) = (bound(cond), count) {
3090                            found.1 = Some(MoeGroups { count, kept });
3091                        }
3092                    }
3093                    walk(wb, locals, found);
3094                }
3095                Stmt::Repeat { body, .. } | Stmt::Splice { body } => walk(body, locals, found),
3096                Stmt::If { then_block, else_block, .. } => {
3097                    walk(then_block, locals, found);
3098                    if let Some(eb) = else_block {
3099                        walk(eb, locals, found);
3100                    }
3101                }
3102                _ => {}
3103            }
3104        }
3105    }
3106    let mut found = (None, None, None);
3107    walk(body, locals, &mut found);
3108    found
3109}
3110
3111/// How a mixture scores its experts.
3112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3113pub enum MoeScore {
3114    /// The softmax over every router logit.
3115    Softmax,
3116    /// Each logit's own sigmoid (`by sigmoid`).
3117    Sigmoid,
3118}
3119
3120/// A mixture's scoring and whether a kept expert's weight scales its input (`weighting the inputs`), read off its
3121/// desugar: the sigmoid's score is `1 / (1 + e^(0 − L[e]))`, the one push of the block whose value divides the literal
3122/// one (the softmax divides an exponential by the sum); the weighted inputs mark each kept expert in a selection, the
3123/// one `SetIndex` of the block that writes the literal one.
3124fn moe_routing(body: &[Stmt]) -> (MoeScore, bool) {
3125    fn walk(body: &[Stmt], sigmoid: &mut bool, selects: &mut bool) {
3126        for s in body {
3127            match s {
3128                Stmt::Push { value: Expr::BinaryOp { op: BinaryOpKind::Divide, left, .. }, .. } if as_float(left) == Some(1.0) => {
3129                    *sigmoid = true
3130                }
3131                Stmt::SetIndex { value, .. } if as_float(value) == Some(1.0) => *selects = true,
3132                Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => walk(body, sigmoid, selects),
3133                _ => {}
3134            }
3135        }
3136    }
3137    let (mut sigmoid, mut selects) = (false, false);
3138    walk(body, &mut sigmoid, &mut selects);
3139    (if sigmoid { MoeScore::Sigmoid } else { MoeScore::Softmax }, selects)
3140}
3141
3142/// A mixture's options, read off its desugar: the router's bias is the operand its logit push adds (`acc + Br[e]`), the
3143/// gate's and the up's the operands their pushes add to the accumulator of `wg` and of `wu` (`acc + Bg[…]`, the `acc`
3144/// summing `x[t]·Wg[…]`), the down's the operand its weighted accumulation adds to the expert's sum
3145/// (`out[o] + w[e]·(acc + Bd[…])`); the activation is clamped when the gate it sharpens is `min(g, L)` (`A·min(g, L)`).
3146fn moe_options(body: &[Stmt], locals: &HashSet<Symbol>, wg: Symbol, wu: Symbol) -> (Option<Symbol>, [Option<Symbol>; 3], MoeAct) {
3147    /// `acc + B[i]` or `W[e]·acc + B[i]`: the accumulator, `B`, and whether `i` is a plain loop variable (the router's
3148    /// logit `e`) rather than an expert's offset.
3149    fn added(e: &Expr, locals: &HashSet<Symbol>) -> Option<(Symbol, Symbol, bool)> {
3150        let (acc, read) = as_binop(e, BinaryOpKind::Add)?;
3151        let acc = as_ident(acc).or_else(|| as_binop(acc, BinaryOpKind::Multiply).and_then(|(_, a)| as_ident(a)))?;
3152        let (b, at) = as_index(read)?;
3153        let b = as_ident(b).filter(|b| !locals.contains(b))?;
3154        Some((acc, b, as_ident(at).is_some()))
3155    }
3156    /// `acc + v·W[i]` (either factor order): the weight `W` an accumulation reads.
3157    fn summed_weight(e: &Expr) -> Option<Symbol> {
3158        let (_, term) = as_binop(e, BinaryOpKind::Add)?;
3159        let (l, r) = as_binop(term, BinaryOpKind::Multiply)?;
3160        [r, l].into_iter().find_map(|f| as_index(f).and_then(|(w, _)| as_ident(w)))
3161    }
3162    fn clamp_of(e: &Expr) -> Option<(f64, f64)> {
3163        match e {
3164            Expr::BinaryOp { op: BinaryOpKind::Multiply, left, right } => {
3165                if let (Some(slope), Expr::Call { args, .. }) = (as_float(left), &**right) {
3166                    if let [_, limit] = args.as_slice() {
3167                        if let Some(limit) = as_float(limit) {
3168                            return Some((limit, slope));
3169                        }
3170                    }
3171                }
3172                clamp_of(left).or_else(|| clamp_of(right))
3173            }
3174            Expr::BinaryOp { left, right, .. } => clamp_of(left).or_else(|| clamp_of(right)),
3175            Expr::Call { args, .. } => args.iter().find_map(|a| clamp_of(a)),
3176            _ => None,
3177        }
3178    }
3179    #[derive(Default)]
3180    struct Found {
3181        pushes: Vec<(Symbol, Symbol, bool)>,
3182        /// Each accumulator and the weight it sums.
3183        sums: HashMap<Symbol, Symbol>,
3184        down: Option<Symbol>,
3185        clamp: Option<(f64, f64)>,
3186    }
3187    fn walk(body: &[Stmt], locals: &HashSet<Symbol>, found: &mut Found) {
3188        for s in body {
3189            match s {
3190                Stmt::Push { value, .. } => found.pushes.extend(added(value, locals)),
3191                Stmt::SetIndex { value, .. } => {
3192                    if let Some((_, weighted)) = as_binop(value, BinaryOpKind::Add) {
3193                        if let Some((_, sum)) = as_binop(weighted, BinaryOpKind::Multiply) {
3194                            if let Some((_, b, _)) = added(sum, locals) {
3195                                found.down = Some(b);
3196                            }
3197                        }
3198                    }
3199                }
3200                Stmt::Set { target, value } => {
3201                    if let Some(w) = summed_weight(value) {
3202                        found.sums.insert(*target, w);
3203                    }
3204                    if found.clamp.is_none() {
3205                        found.clamp = clamp_of(value);
3206                    }
3207                }
3208                Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Splice { body } => walk(body, locals, found),
3209                _ => {}
3210            }
3211        }
3212    }
3213    let mut found = Found::default();
3214    walk(body, locals, &mut found);
3215    let router_bias = found.pushes.iter().find(|(_, _, plain)| *plain).map(|(_, b, _)| *b);
3216    let bias_of = |w: Symbol| {
3217        found.pushes.iter().find(|(acc, _, plain)| !*plain && found.sums.get(acc) == Some(&w)).map(|(_, b, _)| *b)
3218    };
3219    let act = match found.clamp {
3220        Some((limit, slope)) => MoeAct::ClampedSwiglu { limit: limit.to_bits(), slope: slope.to_bits() },
3221        None => MoeAct::Swiglu,
3222    };
3223    (router_bias, [bias_of(wg), bias_of(wu), found.down], act)
3224}
3225
3226#[cfg(test)]
3227#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/tensor_recognize/qwen/tests.rs"]
3228mod tests;