1use 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#[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 pub rows: usize,
33 pub from: Option<(RowIndex, usize)>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct SiluSpec {
41 pub x: Symbol,
42 pub out: Symbol,
43 pub n: usize,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct SigmoidSpec {
49 pub x: Symbol,
50 pub out: Symbol,
51 pub n: usize,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct GeluSpec {
58 pub x: Symbol,
59 pub out: Symbol,
60 pub n: usize,
61}
62
63#[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#[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#[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 pub rows: usize,
91}
92
93#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum RowIndex {
108 Named(Symbol),
109 Literal(i64),
110}
111
112#[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#[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#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct ArgMaxSpec {
137 pub x: Symbol,
138 pub out: Symbol,
139 pub n: usize,
140}
141
142#[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 pub count: usize,
153}
154
155#[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#[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#[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#[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#[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 pub rows: usize,
215}
216
217#[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 pub rows: usize,
236 pub eps: Epsilon,
238 pub gate: GateAct,
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
244pub enum GateAct {
245 Silu,
246 Sigmoid,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
251pub enum Window {
252 Sliding(usize),
254 Chunked(usize),
257}
258
259impl Window {
260 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 pub fn span(self) -> usize {
270 match self {
271 Window::Sliding(w) | Window::Chunked(w) => w,
272 }
273 }
274
275 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#[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 pub vd: usize,
299 pub scale: Option<Factor>,
302 pub softcap: Option<Factor>,
304 pub l: usize,
305 pub pos: Option<RowIndex>,
308 pub rows: usize,
310 pub window: Option<Window>,
313 pub sinks: Option<Symbol>,
315}
316
317#[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 pub router_bias: Option<Symbol>,
337 pub biases: [Option<Symbol>; 3],
339 pub renormalize: bool,
342 pub act: MoeAct,
344 pub score: MoeScore,
346 pub weigh_inputs: bool,
348 pub selection_bias: Option<Symbol>,
350 pub groups: Option<MoeGroups>,
352 pub scale: Option<u64>,
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
359pub struct MoeGroups {
360 pub count: usize,
361 pub kept: usize,
362}
363
364impl MoeSpec {
365 pub fn scale(&self) -> f64 {
367 self.scale.map_or(1.0, f64::from_bits)
368 }
369
370 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 pub fn sum_can_clamp(&self) -> bool {
379 self.renormalize && (self.score == MoeScore::Sigmoid || self.ne >= 1 << 14)
380 }
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
386pub enum MoeAct {
387 Swiglu,
388 ClampedSwiglu { limit: u64, slope: u64 },
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
394pub enum Activation {
395 Silu,
396 Sigmoid,
397 Gelu,
398}
399
400impl Activation {
401 pub fn is_self_gated(self) -> bool {
404 matches!(self, Activation::Silu | Activation::Gelu)
405 }
406}
407
408#[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
420fn 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
462fn 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
468fn read_at(e: &Expr, iv: Symbol) -> Option<Symbol> {
470 let (c, i) = indexed(e)?;
471 is_ident(i, iv).then_some(c)
472}
473
474fn 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
482struct 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#[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 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 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 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 fn new_let(&mut self) -> Option<Symbol> {
548 let (var, v) = self.let_()?;
549 is_new(v).then_some(var)
550 }
551 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 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 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 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
583fn 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
592fn 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
621fn 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
634fn 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
656fn 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
696pub fn recognize_unary_map(stmts: &[Stmt]) -> Option<UnaryMapSpec> {
698 find_in_bodies(stmts, match_unary_map)
699}
700
701pub 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
712pub 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
723pub 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
734fn 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
749fn 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
769pub fn recognize_softcap(stmts: &[Stmt]) -> Option<SoftcapSpec> {
771 find_in_bodies(stmts, match_softcap)
772}
773
774fn 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
794pub fn recognize_scale(stmts: &[Stmt]) -> Option<ScaleSpec> {
796 find_in_bodies(stmts, match_scale)
797}
798
799#[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#[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#[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
831enum IntMap {
833 Requant { m: i64, s: u32 },
834 Clamp { lo: i64, hi: i64 },
835 Relu { int: bool },
836}
837
838fn 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
846fn 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
875fn 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
899pub 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
907pub 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
915pub 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
923fn 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
945fn 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 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 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
1009pub fn recognize_decay(stmts: &[Stmt]) -> Option<DecaySpec> {
1011 find_in_bodies(stmts, match_decay)
1012}
1013
1014fn 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
1036fn 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
1075pub fn recognize_l2norm(stmts: &[Stmt]) -> Option<L2NormSpec> {
1078 find_in_bodies(stmts, match_l2norm)
1079}
1080
1081fn 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 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
1126pub fn recognize_transpose(stmts: &[Stmt]) -> Option<TransposeSpec> {
1128 find_in_bodies(stmts, match_transpose)
1129}
1130
1131fn 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
1143fn 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 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
1187pub 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 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
1228pub fn recognize_join(stmts: &[Stmt]) -> Option<JoinSpec> {
1231 find_in_bodies(stmts, match_join)
1232}
1233
1234fn 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
1262fn 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
1298pub 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 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
1349pub 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 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
1382pub 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
1422pub fn recognize_argmax(stmts: &[Stmt]) -> Option<ArgMaxSpec> {
1424 find_in_bodies(stmts, match_argmax)
1425}
1426
1427fn 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
1464fn 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
1477fn 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 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
1544pub fn recognize_full_attention(stmts: &[Stmt]) -> Option<FullAttentionSpec> {
1546 find_in_bodies(stmts, match_full_attention)
1547}
1548
1549fn 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
1570fn 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
1606fn 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 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 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
1703pub fn recognize_rope(stmts: &[Stmt]) -> Option<RopeSpec> {
1706 find_in_bodies(stmts, match_rope)
1707}
1708
1709fn 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 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 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 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 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 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
1812pub fn recognize_causal_conv(stmts: &[Stmt]) -> Option<CausalConvSpec> {
1815 find_in_bodies(stmts, match_causal_conv)
1816}
1817
1818fn 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
1836fn 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
1843fn 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
1857fn 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
1871fn 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
1880fn 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, 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 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 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 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 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 if let Some(spec) = gd_heads(&mut c.clone(), out, None, zero_fill.bound) {
2049 return Some(spec);
2050 }
2051 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
2090pub fn recognize_gated_delta(stmts: &[Stmt]) -> Option<GatedDeltaSpec> {
2094 find_in_bodies(stmts, match_gated_delta)
2095}
2096
2097fn 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
2115fn 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
2140fn 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
2153fn 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
2181fn 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
2232fn 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
2243fn 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
2251struct Heads<'a> {
2253 h: usize,
2255 group: usize,
2257 bound: &'a Expr<'a>,
2259 hd: usize,
2261 vd: usize,
2263 scale: Option<Factor>,
2265 softcap: Option<Factor>,
2267 window: Option<Window>,
2269 sinks: Option<Symbol>,
2271}
2272
2273struct HeadLoops<'a> {
2277 body: &'a [Stmt<'a>],
2279 h: usize,
2281 group: usize,
2283 bound: &'a Expr<'a>,
2285 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
2339fn attention_heads<'a>(body: &'a [Stmt<'a>]) -> Option<Heads<'a>> {
2342 let HeadLoops { body: hbody, h, group, bound, hd } = head_loops(body)?;
2343 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 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 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 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
2392pub 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 let out = body.iter().find_map(|st| match st {
2406 Stmt::Let { var, value, .. } if is_new(value) => Some(*var),
2407 _ => None,
2408 })?;
2409 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
2447pub 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 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 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
2556fn 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#[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
2612fn 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
2637fn 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
2644fn 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
2664fn 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
2673fn 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
2682pub 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
2741fn 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}
2755fn 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}
2769fn 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}
2788fn 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
2822pub fn recognize_moe(stmts: &[Stmt]) -> Option<MoeSpec> {
2824 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 let mut locals = HashSet::new();
2843 collect_lets(body, &mut locals);
2844
2845 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 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 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 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 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
3050fn 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3113pub enum MoeScore {
3114 Softmax,
3116 Sigmoid,
3118}
3119
3120fn 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
3142fn moe_options(body: &[Stmt], locals: &HashSet<Symbol>, wg: Symbol, wu: Symbol) -> (Option<Symbol>, [Option<Symbol>; 3], MoeAct) {
3147 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 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 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;