1use std::collections::BTreeMap;
17
18use crate::tensor_graph::front::TensorOpSpec;
19use crate::tensor_graph::fuse::{binary, class, gates_rows, gemv, leaves, Weights};
20use crate::tensor_graph::graph::{Access, Dst as IrDst, Fused, Op, RowSel as IrRowSel, Sym, TensorGraph};
21use crate::tensor_graph::shim::{Shim, Support};
22use crate::tensor_graph::symbols::ClassId;
23use crate::tensor_recognize::qwen::{Activation, GateAct, RowIndex, Window};
24use crate::tensor_recognize::RowReduceKind;
25
26use super::lane::kernels;
27use super::lane::plan::{
28 attended_span, Advance, ConvPart, Dst, GemmPart, Launch, Part, PreNorm, RopeSide, Router, RowSel, SharedExperts, StepIo, GEMV_ROWS,
29 QBLOCK,
30};
31use super::lane::weights::{Placement, Rows};
32use super::lane_shim::{chunks_apart, LaneShim, Latent};
33use super::qwen_emit::Q8MatKind;
34
35pub struct Link<'a> {
38 pub g: &'a TensorGraph,
39 pub constants: &'a BTreeMap<String, Placement>,
40 pub shim: &'a LaneShim,
41 pub io: &'a StepIo,
42}
43
44fn dst(d: &IrDst) -> Dst {
45 Dst {
46 buf: d.buf.clone(),
47 row: d.row.as_ref().map(|(sel, pitch)| {
48 let sel = match sel {
49 IrRowSel::Param(p) => RowSel::Param(p.clone()),
50 IrRowSel::Fixed(n) => RowSel::Fixed(*n),
51 };
52 (sel, *pitch)
53 }),
54 unit: d.unit.map(|u| Part { pitch: u.pitch, offset: u.offset }),
55 }
56}
57
58fn part(a: Access) -> Option<Part> {
59 match a {
60 Access::Whole => None,
61 Access::Strided(s) => Some(Part { pitch: s.pitch, offset: s.offset }),
62 }
63}
64
65fn row_sel(index: &RowIndex, name: Option<&String>) -> Result<RowSel, String> {
67 match index {
68 RowIndex::Named(_) => name.cloned().map(RowSel::Param).ok_or_else(|| "a row index with no operand".to_string()),
69 RowIndex::Literal(n) => Ok(RowSel::Fixed(*n as usize)),
70 }
71}
72
73impl Link<'_> {
74 fn numerics(&self) -> Q8MatKind {
75 self.shim.knobs.activations
76 }
77
78 fn rows_of(&self, t: &str) -> Option<&Rows> {
79 match self.constants.get(t) {
80 Some(Placement::Rows(r)) => Some(r),
81 _ => None,
82 }
83 }
84
85 fn values(&self, t: &str) -> bool {
86 matches!(self.constants.get(t), Some(Placement::Values(_)))
87 }
88
89 pub fn op(&self, op: &Op) -> Result<Vec<(Launch, Vec<usize>)>, String> {
91 let whole = |l: Launch| vec![(l, covered(op))];
92 match &op.sym {
93 Sym::View { offset } => Ok(whole(Launch::View { src: op.ins[0].clone(), offset: *offset, out: op.out.clone() })),
94 Sym::Quantize(_) => {
95 let n = self.g.tensors.get(&op.ins[0]).map(|t| t.len()).ok_or("a quantize of an unknown tensor")?;
96 Ok(whole(Launch::Quant { x: op.ins[0].clone(), n }))
97 }
98 Sym::Prim(spec) => {
99 let mut l = self.prim(op, spec)?;
100 self.place(op, &mut l);
101 Ok(whole(l))
102 }
103 Sym::Fused(f) => match self.shim.supports(f.class) {
104 Support::Native => self.class_launches(op, f),
105 Support::Decompose => f.parts.iter().map(|p| self.op(p)).collect::<Result<Vec<_>, _>>().map(|v| v.concat()),
106 Support::Refuse(why) => Err(format!("{}: {why}", f.class)),
107 },
108 }
109 }
110
111 pub fn whole(&self, op: &Op) -> Result<Vec<(Launch, Vec<usize>)>, String> {
115 let Sym::Fused(f) = &op.sym else { return self.op(op) };
116 self.shim.fits(op, self.g)?;
117 self.class_launches(op, f)
118 }
119
120 fn class_launches(&self, op: &Op, f: &Fused) -> Result<Vec<(Launch, Vec<usize>)>, String> {
123 match f.class {
124 ClassId::Mla => self.latent(op, f),
125 ClassId::AttnOutputGate if gates_rows(&f.parts) => self.gated_rows(op, f, false),
126 ClassId::GluActMulQuant => match &f.parts[0].sym {
127 Sym::Fused(gated) if gated.class == ClassId::AttnOutputGate && gates_rows(&gated.parts) => self.gated_rows(op, gated, true),
128 _ => self.class_launch(op, f).map(|l| vec![(l, covered(op))]),
129 },
130 _ => self.class_launch(op, f).map(|l| {
131 let ops = match l {
132 Launch::GemmParts { .. } | Launch::ConvParts { .. } => f.parts.iter().flat_map(covered).collect(),
133 _ => covered(op),
134 };
135 vec![(l, ops)]
136 }),
137 }
138 }
139
140 fn gated_rows(&self, op: &Op, f: &Fused, quant: bool) -> Result<Vec<(Launch, Vec<usize>)>, String> {
147 let (product, rows) = f.parts.split_last().ok_or("a gated attention of no parts")?;
148 let gated = binary(product).ok_or("an attention's gated product")?;
149 let act = gated.gate.ok_or("a gate's activation")?;
150 let plain = !quant || !self.only_projections(&op.out);
152 let mut launches = Vec::new();
153 for set in rows {
154 let Some(TensorOpSpec::SetRow(sr)) = set.prim() else { continue };
155 let attention = rows.iter().find(|p| p.out == set.ins[1]).ok_or("a row set from no attention")?;
156 let index = row_sel(&sr.index, set.ins.get(2))?;
157 let RowSel::Fixed(row) = index else { return Err("a batch's attention set at a row the step names".into()) };
158 let mut l = self.one(attention)?;
159 let Launch::Attention { gate, goff, quant: q, f32, .. } = &mut l else { return Err("a gated attention".into()) };
160 (*gate, *goff, *q, *f32) = (Some((gated.b.clone(), act)), (row - 1) * sr.cols, quant, plain);
161 launches.push((l, covered(attention)));
162 let set_row = Launch::SetRow { table: op.out.clone(), index, src: set.ins[1].clone(), cols: sr.cols, count: sr.count, seq: None, f32: plain, quant };
163 launches.push((set_row, covered(set)));
164 }
165 let last = launches.iter().rposition(|(l, _)| matches!(l, Launch::Attention { .. })).ok_or("a batch's gate over no attention")?;
168 let counted: Vec<usize> = launches.iter().flat_map(|(_, ops)| ops.clone()).collect();
169 launches[last].1.extend(covered(op).into_iter().filter(|k| !counted.contains(k)));
170 launches[last].1.sort_unstable();
171 Ok(launches)
172 }
173
174 fn place(&self, op: &Op, l: &mut Launch) {
176 if let Some(p) = part(op.access[0]) {
177 if let Launch::Norm { part, .. } | Launch::Rope { part, .. } | Launch::HeadGemv { part, .. } = l {
178 *part = Some(p);
179 }
180 }
181 if let (Launch::Attention { vstride, voff, .. }, Some(Access::Strided(s))) = (&mut *l, op.access.get(2)) {
182 (*vstride, *voff) = (s.pitch, s.offset);
183 }
184 if op.dst.own().is_some() {
185 return;
186 }
187 let to = dst(&op.dst);
188 match l {
189 Launch::Gemv { y, .. } | Launch::GemvF32 { y, .. } | Launch::Rope { y, .. } | Launch::Moe { y, .. } | Launch::Join { y, .. } | Launch::HeadGemv { y, .. } => *y = to,
190 Launch::Norm { into, .. } => *into = Some(to),
191 _ => {}
192 }
193 }
194
195 fn prim(&self, op: &Op, spec: &TensorOpSpec) -> Result<Launch, String> {
197 let (ins, out) = (&op.ins, op.out.clone());
198 let numerics = self.numerics();
199 let (knobs, batch) = (&self.shim.knobs, self.shim.batch);
200 let refuse = |why: &str| Err(why.to_string());
201 Ok(match spec {
202 TensorOpSpec::Matmul(m) => {
203 if m.m == 0 {
204 return refuse("a matmul of no rows");
205 }
206 let w = &ins[1];
207 if let Some(r) = self.rows_of(w) {
208 if !r.transposed || r.rows != m.n || r.cols != m.k {
209 return refuse("the weight's checkpoint rows are not the projection's outputs");
210 }
211 let rows = kernels::gemm_blk_rows(kernels::format_arg(r.ty), numerics == Q8MatKind::TwoPassInt8);
212 let tiles = m.n.div_ceil(kernels::GEMM_TILE_N) * m.m.div_ceil(rows);
213 let tail = match kernels::gemm_blk_tail(tiles, knobs.chip.sms, m.k.div_ceil(128)) {
214 (whole, split) if split > 1 && m.m > GEMV_ROWS && numerics != Q8MatKind::Dequant && !batch => Some((whole, split)),
215 _ => None,
216 };
217 Launch::Gemv {
218 x: ins[0].clone(),
219 w: w.clone(),
220 format: r.ty,
221 bias: None,
222 y: Dst::to(&out),
223 k: m.k,
224 n: m.n,
225 m: m.m,
226 per_warp: 1,
227 f32: true,
228 quant: false,
229 tail,
230 batch,
231 }
232 } else if self.values(w) {
233 Launch::GemvF32 { x: ins[0].clone(), w: w.clone(), bias: None, y: Dst::to(&out), k: m.k, n: m.n, m: m.m, split: 1, batch }
234 } else {
235 return refuse("a matmul by a computed matrix has no kernel");
236 }
237 }
238 TensorOpSpec::Elementwise(e) => Launch::Binary {
239 op: e.op,
240 a: ins[0].clone(),
241 b: ins[1].clone(),
242 gate: None,
243 row: e.row,
244 out,
245 f32: true,
246 quant: false,
247 n: e.n,
248 },
249 TensorOpSpec::SwiGlu(s) => Launch::SwiGlu { g: ins[0].clone(), u: ins[1].clone(), out, f32: true, quant: false, n: s.total },
250 TensorOpSpec::FusedRow(r) => {
251 if r.reduce != RowReduceKind::RmsNorm {
252 return refuse("a softmax or layernorm row has no kernel in the lane yet");
253 }
254 if r.bias.is_some() {
255 return refuse("an RMSNorm with a bias");
256 }
257 let mut it = ins.iter().skip(1);
258 let w = r.weight.map(|_| it.next().expect("the weight operand").clone());
259 let add = r.residual.map(|_| it.next().expect("the residual operand").clone());
260 Launch::Norm {
261 x: ins[0].clone(),
262 part: None,
263 add,
264 pre: None,
265 sum: None,
266 w,
267 out,
268 into: None,
269 f32: true,
270 quant: false,
271 rows: r.rows,
272 cols: r.cols,
273 eps: r.eps,
274 }
275 }
276 TensorOpSpec::Rope(r) => {
277 if r.hd > 1024 || 2 * r.half > r.hd || r.rows == 0 || r.h % r.rows != 0 {
278 return refuse("a head wider than 1024, a rotation wider than the head, or rows that do not divide the heads");
279 }
280 let pos = match &r.from {
281 Some((RowIndex::Named(_), _)) => Some(ins[3].clone()),
282 Some((RowIndex::Literal(_), _)) => return refuse("a rotation from a literal position"),
283 None => None,
284 };
285 Launch::Rope {
286 x: ins[0].clone(),
287 part: None,
288 norm: None,
289 cos: ins[1].clone(),
290 sin: ins[2].clone(),
291 pos,
292 y: Dst::to(&out),
293 heads: r.h,
294 per_row: r.h / r.rows,
295 hd: r.hd,
296 half: r.half,
297 }
298 }
299 TensorOpSpec::Silu(s) => Launch::Unary { act: Activation::Silu, x: ins[0].clone(), y: out, n: s.n },
300 TensorOpSpec::Sigmoid(s) => Launch::Unary { act: Activation::Sigmoid, x: ins[0].clone(), y: out, n: s.n },
301 TensorOpSpec::Gelu(s) => Launch::Unary { act: Activation::Gelu, x: ins[0].clone(), y: out, n: s.n },
302 TensorOpSpec::Scale(s) => Launch::Scale { x: ins[0].clone(), y: out, n: s.n, factor: s.factor.get() },
303 TensorOpSpec::Decay(d) => {
304 if d.rows == 0 || d.n % d.rows != 0 {
305 return refuse("a decay whose rows do not divide its heads");
306 }
307 Launch::Decay { ar: ins[0].clone(), dt: ins[1].clone(), al: ins[2].clone(), y: out, n: d.n, heads: d.n / d.rows }
308 }
309 TensorOpSpec::L2Norm(l) => Launch::L2Norm { x: ins[0].clone(), y: out, rows: l.rows, cols: l.cols, eps: l.eps },
310 TensorOpSpec::CausalConv(c) => Launch::Conv {
311 x: ins[0].clone(),
312 w: ins[1].clone(),
313 ring: ins[2].clone(),
314 y: out,
315 chan: c.chan,
316 taps: c.taps,
317 rows: c.rows,
318 silu: false,
319 l2: None,
320 },
321 TensorOpSpec::GatedDelta(d) => {
322 if d.gate == GateAct::Sigmoid {
323 return refuse("a gated delta gated by a sigmoid (`with sigmoid gate Z`)");
324 }
325 if d.d > 1024 || d.d % 32 != 0 {
326 return refuse("a delta-rule head dim past 1024 or not a multiple of 32");
327 }
328 Launch::Delta {
329 q: ins[0].clone(),
330 k: ins[1].clone(),
331 v: ins[2].clone(),
332 z: ins[3].clone(),
333 b: ins[4].clone(),
334 a: ins[5].clone(),
335 st: ins[6].clone(),
336 gn: ins[7].clone(),
337 out,
338 f32: true,
339 quant: false,
340 hv: d.hv,
341 hk: d.hk,
342 d: d.d,
343 rows: d.rows,
344 eps: d.eps,
345 seqs: 1,
346 }
347 }
348 TensorOpSpec::Softcap(_) => return refuse("a soft-capped tensor (`capped at C`)"),
349 TensorOpSpec::Attention(a) => {
350 if a.softcap.is_some() {
351 return refuse("a soft-capped attention (`capped at C`)");
352 }
353 if a.kv == 0 || a.h % a.kv != 0 {
354 return refuse("query heads that do not group evenly over the key/value heads");
355 }
356 if a.hd % 4 != 0 || a.vd % 2 != 0 {
357 return refuse("a key head not a whole number of four-value words, or a value head of an odd width");
358 }
359 if matches!(a.window, Some(Window::Chunked(_))) {
360 return refuse("a chunked-local attention — the lane's mask keeps a window's last positions");
361 }
362 let window = a.window.map(Window::span);
363 let span = attended_span(a.l, window);
364 let acc = kernels::accumulator(kernels::mode(numerics)).0;
365 let Some(per_block) = kernels::attn_heads_per_block(a.h / a.kv, a.hd, a.vd, span, knobs.chip.sms, knobs.chip.smem_kib_per_block * 1024, acc)
366 else {
367 return refuse("a head wider than one block's shared memory holds");
368 };
369 let splits = kernels::attn_splits(a.h / per_block, span, knobs.chip.sms);
370 let mut rest = ins[3..].iter();
371 let pos = match &a.pos {
372 Some(RowIndex::Named(_)) => rest.next().cloned(),
373 Some(RowIndex::Literal(_)) => return refuse("an attention bounded by a literal position"),
374 None => None,
375 };
376 Launch::Attention {
377 q: ins[0].clone(),
378 kc: ins[1].clone(),
379 vc: ins[2].clone(),
380 pos,
381 out,
382 heads: a.h,
383 group: a.h / a.kv,
384 hd: a.hd,
385 vd: a.vd,
386 len: a.l,
387 rows: a.rows,
388 window,
389 sinks: a.sinks.map(|_| rest.next().cloned().expect("the sinks operand")),
390 vstride: a.kv * a.vd,
391 voff: 0,
392 scale: a.scale.map_or(1.0 / (a.hd as f64).sqrt(), |s| s.get()),
393 per_block,
394 splits,
395 parallel: chunks_apart(splits, a.rows, a.h / per_block, knobs.chip.sms),
396 split: None,
397 shared: None,
398 seq: None,
399 f32: true,
400 quant: false,
401 gate: None,
402 goff: 0,
403 }
404 }
405 TensorOpSpec::Moe(m) => {
406 if !m.weighs_outputs_by_softmax() {
407 return refuse("an expert mixture whose router is not a softmax weighting its experts' outputs — scored by sigmoids, biased in its choice, limited to groups, scaled, or weighting its inputs");
408 }
409 let ok = |t: &str, rows: usize, cols: usize, transposed: bool| self.rows_of(t).is_some_and(|r| r.rows == rows && r.cols == cols && r.transposed == transposed);
410 let router = if ok(&ins[1], m.ne, m.d, true) {
411 Router::Rows(self.rows_of(&ins[1]).expect("rows").ty)
412 } else if self.values(&ins[1]) {
413 Router::F32
414 } else {
415 return refuse("an expert mixture whose router is neither quantized checkpoint rows nor values");
416 };
417 if !(ok(&ins[2], m.ne * m.dff, m.d, false) && ok(&ins[3], m.ne * m.dff, m.d, false) && ok(&ins[4], m.ne * m.d, m.dff, false)) {
418 return refuse("an expert mixture whose experts are not quantized checkpoint rows");
419 }
420 let mut rest = ins[5..].iter();
421 let mut constant = |b: bool| -> Result<Option<String>, String> {
422 match b.then(|| rest.next().cloned()).flatten() {
423 Some(n) if self.values(&n) => Ok(Some(n)),
424 Some(_) => Err("an expert mixture whose biases are not constant values".to_string()),
425 None => Ok(None),
426 }
427 };
428 let rb = constant(m.router_bias.is_some())?;
429 let biases = [constant(m.biases[0].is_some())?, constant(m.biases[1].is_some())?, constant(m.biases[2].is_some())?];
430 if m.k == 0 || m.k > 32 || m.ne > 8192 {
431 return refuse("more than 32 experts kept, or more than 8192 experts");
432 }
433 Launch::Moe {
434 x: ins[0].clone(),
435 wr: ins[1].clone(),
436 wg: ins[2].clone(),
437 wu: ins[3].clone(),
438 wd: ins[4].clone(),
439 formats: [2, 3, 4].map(|k| self.rows_of(&ins[k]).expect("rows").ty),
440 y: Dst::to(&out),
441 d: m.d,
442 dff: m.dff,
443 experts: m.ne,
444 k: m.k,
445 router,
446 rb,
447 biases,
448 renormalize: m.renormalize,
449 act: m.act,
450 shared: None,
451 per_warp: 1,
452 rows: 1,
453 router_split: 1,
454 batch,
455 }
456 }
457 TensorOpSpec::RowGather(r) => {
458 let table = ins[0].clone();
459 match (row_sel(&r.index, ins.get(1))?, self.constants.get(&table)) {
460 (RowSel::Param(p), Some(Placement::Rows(rr))) if !rr.transposed && rr.rows == r.rows && rr.cols == r.cols => {
461 Launch::Gather { table, index: p, out, cols: r.cols, rows: 1, format: Some(rr.ty), scale: None }
462 }
463 (RowSel::Param(p), Some(Placement::Values(_))) => Launch::Gather { table, index: p, out, cols: r.cols, rows: 1, format: None, scale: None },
464 (RowSel::Fixed(row), None) if row >= 1 && row <= r.rows => {
467 Launch::Slice { x: table, y: out, groups: 1, every: r.rows * r.cols, from: (row - 1) * r.cols, width: r.cols }
468 }
469 _ => return refuse("a row of a constant table at a literal index, or of a table in no form a row reads"),
470 }
471 }
472 TensorOpSpec::RowsGather(r) => {
473 let table = ins[0].clone();
474 let format = match self.constants.get(&table) {
475 Some(Placement::Rows(rr)) if !rr.transposed && rr.rows == r.rows && rr.cols == r.cols => Some(rr.ty),
476 Some(Placement::Values(_)) => None,
477 _ => return refuse("rows gathered from a table that is not a constant"),
478 };
479 Launch::Gather { table, index: ins[1].clone(), out, cols: r.cols, rows: r.count, format, scale: None }
480 }
481 TensorOpSpec::ArgMax(a) => Launch::ArgMax { x: ins[0].clone(), out, n: a.n, advance: None, rows: 1 },
482 TensorOpSpec::SetRow(r) => {
483 Launch::SetRow { table: ins[0].clone(), index: row_sel(&r.index, ins.get(2))?, src: ins[1].clone(), cols: r.cols, count: r.count, seq: None, f32: true, quant: false }
484 }
485 TensorOpSpec::Transpose(_) | TensorOpSpec::FullAttention(_) => return refuse("a transpose or an unmasked block attention has no kernel yet"),
486 TensorOpSpec::IntRequant(_) | TensorOpSpec::IntClamp(_) | TensorOpSpec::Relu(_) | TensorOpSpec::LutAttention(_) => {
487 return refuse("a requant, a clamp, a ReLU or a LUT attention has no kernel yet")
488 }
489 TensorOpSpec::HeadSlice(h) => Launch::Slice { x: ins[0].clone(), y: out, groups: h.n / h.every, every: h.every, from: h.from, width: h.width },
490 TensorOpSpec::Join(j) => Launch::Join { a: ins[0].clone(), b: ins[1].clone(), y: Dst::to(&out), rows: j.rows, heads: j.heads, ha: j.wa / j.heads, hb: j.wb / j.heads, yz: 0 },
491 TensorOpSpec::HeadMatmul(m) => {
492 let Some(r) = self.rows_of(&ins[1]) else { return refuse("a per-head matmul by a matrix that is not checkpoint rows") };
493 let rows_out = match (r.transposed, r.rows, r.cols) {
494 (true, rows, cols) if rows == m.heads * m.b && cols == m.a => true,
495 (false, rows, cols) if rows == m.heads * m.a && cols == m.b => false,
496 _ => return refuse("a per-head matmul whose checkpoint rows are not each head's outputs or inputs"),
497 };
498 if m.a % QBLOCK != 0 || m.b % QBLOCK != 0 {
499 return refuse("a per-head matmul whose heads are not whole 32-value blocks");
500 }
501 Launch::HeadGemv {
502 x: ins[0].clone(),
503 part: None,
504 w: ins[1].clone(),
505 format: r.ty,
506 y: Dst::to(&out),
507 m: m.m,
508 heads: m.heads,
509 a: m.a,
510 b: m.b,
511 rows_out,
512 shared: false,
513 upto: None,
514 batch,
515 }
516 }
517 })
518 }
519
520 fn one(&self, op: &Op) -> Result<Launch, String> {
522 let mut v = self.op(op)?;
523 match v.len() {
524 1 => Ok(v.remove(0).0),
525 n => Err(format!("a member the lane runs as one launch links to {n}")),
526 }
527 }
528
529 fn class_launch(&self, op: &Op, f: &Fused) -> Result<Launch, String> {
531 let parts = &f.parts;
532 let mut l = match f.class {
533 ClassId::AddNorm => {
534 let mut l = self.one(&parts[1])?;
535 let Launch::Norm { x, add, sum, .. } = &mut l else { return Err("an add and norm over a norm".into()) };
536 let (a, b) = (parts[0].ins[0].clone(), parts[0].ins[1].clone());
537 (*x, *add, *sum) = (a, Some(b), op.keeps.first().cloned());
538 l
539 }
540 ClassId::NormThenAdd => {
541 let (inner, outer) = (&parts[0], &parts[1]);
542 let Launch::Norm { w, eps, .. } = self.one(inner)? else { return Err("a sandwich's inner norm".into()) };
543 let mut l = self.one(outer)?;
544 let Launch::Norm { x, add, pre, part, .. } = &mut l else { return Err("a sandwich's outer norm".into()) };
545 (*x, *add, *pre) = (op.ins[0].clone(), Some(op.ins[1].clone()), Some(PreNorm { w, eps }));
546 *part = self::part(op.access[0]);
547 l
548 }
549 ClassId::GemvNormEpilogue | ClassId::EmbedNorm | ClassId::MoeDeferredFinalize => {
551 let mut launches = parts.iter().map(|p| self.one(p));
552 let first = launches.next().ok_or("a fold of no parts")??;
553 let mut l = launches.try_fold(first, |first, then| Ok::<_, String>(Launch::Fold { first: Box::new(first), then: Box::new(then?) }))?;
554 if let (Launch::Fold { first, .. }, Some(p)) = (&mut l, gemv(&parts[0], self.g)) {
556 if let Launch::Gemv { per_warp, .. } = first.as_mut() {
557 *per_warp = self.shim.per_warp(&p);
558 }
559 }
560 l
561 }
562 ClassId::RowWriteQuant => {
564 let mut l = self.one(&parts[0])?;
565 let Launch::SetRow { f32, quant, .. } = &mut l else { return Err("a row set that carries its quantized form".into()) };
566 (*f32, *quant) = (!self.only_projections(&op.out), true);
567 l
568 }
569 ClassId::AttnOutputGate => {
571 let mut l = self.one(&parts[0])?;
572 let gated = binary(&parts[1]).ok_or("an attention's gated product")?;
573 let Launch::Attention { out, gate, .. } = &mut l else { return Err("a gated attention".into()) };
574 (*out, *gate) = (op.out.clone(), Some((gated.b, gated.gate.ok_or("a gate's activation")?)));
575 l
576 }
577 ClassId::GemmBias => {
578 let mut l = self.one(&parts[0])?;
579 let (Launch::Gemv { bias, y, .. } | Launch::GemvF32 { bias, y, .. }) = &mut l else { return Err("a bias on a product".into()) };
580 (*bias, *y) = (Some(op.ins[2].clone()), Dst::to(&op.out));
581 l
582 }
583 ClassId::QkNormRope => {
584 let Launch::Norm { w: Some(w), eps, .. } = self.one(&parts[0])? else { return Err("a rotation's per-head norm".into()) };
585 let mut l = self.one(&parts[1])?;
586 let Launch::Rope { x, norm, .. } = &mut l else { return Err("a normed rotation".into()) };
587 (*x, *norm) = (op.ins[0].clone(), Some((w, eps)));
588 l
589 }
590 ClassId::ConvBiasSilu | ClassId::ConvSiluL2norm => {
591 let mut l = self.one(&parts[0])?;
592 let Launch::Conv { y, silu, l2, .. } = &mut l else { return Err("a convolution's epilogue".into()) };
593 (*y, *silu) = (op.out.clone(), true);
594 if f.class == ClassId::ConvSiluL2norm {
595 let Some(TensorOpSpec::L2Norm(n)) = parts[1].prim() else { return Err("a convolution's L2 norm".into()) };
596 *l2 = Some((n.cols, n.eps));
597 }
598 l
599 }
600 ClassId::ConvParts => {
601 let mut convs = Vec::new();
602 for p in parts {
603 let Launch::Conv { x, w, ring, y, chan, taps, l2, .. } = self.one(p)? else { return Err("a part of a launch of convolutions".into()) };
604 convs.push((ConvPart { x, w, ring, y, chan, l2: l2.is_some() }, taps, l2));
605 }
606 let taps = convs[0].1;
607 let norm = convs.iter().find_map(|c| c.2);
608 let (block, eps) = match norm {
609 Some((d, e)) => (d, Some(e)),
610 None => (256, None),
611 };
612 Launch::ConvParts { parts: convs.into_iter().map(|c| c.0).collect(), taps, block, eps, seqs: 1 }
613 }
614 ClassId::GdnGating => {
615 let sigmoid = parts.iter().find(|p| matches!(p.prim(), Some(TensorOpSpec::Sigmoid(_)))).ok_or("the write strength")?;
616 let decay = parts.iter().find(|p| matches!(p.prim(), Some(TensorOpSpec::Decay(_)))).ok_or("the forget gate")?;
617 let Some(TensorOpSpec::Decay(d)) = decay.prim() else { unreachable!() };
618 Launch::Gates {
619 br: sigmoid.ins[0].clone(),
620 ar: decay.ins[0].clone(),
621 dt: decay.ins[1].clone(),
622 al: decay.ins[2].clone(),
623 beta: sigmoid.out.clone(),
624 decay: decay.out.clone(),
625 n: d.n,
626 heads: d.n / d.rows,
627 }
628 }
629 ClassId::GdnGateProjections => {
630 let gating = parts.iter().find(|p| class(p) == Some(ClassId::GdnGating)).ok_or("the gates")?;
631 let Launch::Gates { br, ar, dt, al, beta, decay, heads, .. } = self.one(gating)? else { return Err("the gates".into()) };
632 let product = |t: &str| parts.iter().find(|p| p.out == t).and_then(|p| gemv(p, self.g)).ok_or("a gate's product");
633 let (pb, pa) = (product(&br)?, product(&ar)?);
634 Launch::GateProj { x: pb.x, wb: pb.w, wa: pa.w, dt, al, beta, decay, k: pb.k, heads, parts: 1 }
635 }
636 ClassId::GluActMul => {
637 let act = &parts[0];
638 let gate = match act.prim() {
639 Some(TensorOpSpec::Silu(_)) => Activation::Silu,
640 Some(TensorOpSpec::Sigmoid(_)) => Activation::Sigmoid,
641 Some(TensorOpSpec::Gelu(_)) => Activation::Gelu,
642 _ => return Err("a gate's activation".into()),
643 };
644 let mut l = self.one(&parts[1])?;
645 let Launch::Binary { a, b, gate: g, .. } = &mut l else { return Err("a gated product".into()) };
646 let other = if *a == act.out { b.clone() } else { a.clone() };
647 (*a, *b, *g) = (other, act.ins[0].clone(), Some(gate));
648 l
649 }
650 ClassId::GemvGateUpGlu => {
651 let glu = parts.last().ok_or("a gated pair's activation")?;
652 let (g, u, act) = match glu.prim() {
653 Some(TensorOpSpec::SwiGlu(_)) => (glu.ins[0].clone(), glu.ins[1].clone(), None),
654 _ => match self.one(glu)? {
655 Launch::Binary { a, b, gate: Some(act), .. } => (b, a, Some(act)),
656 _ => return Err("a gated pair's activation".into()),
657 },
658 };
659 let product = |t: &str| -> Result<Launch, String> { self.one(parts.iter().find(|p| p.out == t).ok_or("a gated pair's product")?) };
660 let (Launch::Gemv { x, w: wg, format: fg, bias: bg, k, n, m, tail, batch, .. }, Launch::Gemv { w: wu, format: fu, bias: bu, .. }) = (product(&g)?, product(&u)?) else {
661 return Err("a gated pair of products by checkpoint rows".into());
662 };
663 let gemv_rows = m <= GEMV_ROWS || self.numerics() == Q8MatKind::Dequant || batch;
664 let paired = !gemv_rows && fg == fu && n % (kernels::GEMM_TILE_N / 2) == 0;
665 let tail = tail.filter(|_| paired).map(|(whole, split)| (2 * whole, split));
666 Launch::GemvGlu { x, wg, wu, formats: [fg, fu], biases: [bg, bu], act, out: op.out.clone(), f32: true, quant: false, k, n, m, per_warp: 1, tail, batch }
667 }
668 ClassId::SharedExpertFusion => {
669 let mixture = parts.iter().find(|p| matches!(p.prim(), Some(TensorOpSpec::Moe(_)))).ok_or("the mixture")?;
670 let glu = parts.iter().find(|p| class(p) == Some(ClassId::GemvGateUpGlu)).ok_or("the shared expert's gated pair")?;
671 let down = parts.iter().find(|p| gemv(p, self.g).is_some_and(|d| d.x == glu.out)).ok_or("the shared expert's down product")?;
672 let Launch::GemvGlu { wg, wu, formats: [fg, fu], n: sdff, .. } = self.one(glu)? else { return Err("the shared expert's gated pair".into()) };
673 let Launch::Gemv { w: wd, format: fd, .. } = self.one(down)? else { return Err("the shared expert's down product".into()) };
674 let mut l = self.one(mixture)?;
675 let Launch::Moe { y, shared, .. } = &mut l else { return Err("a mixture".into()) };
676 (*y, *shared) = (Dst::to(&op.out), Some(SharedExperts { wg, wu, wd, formats: [fg, fu, fd], dff: sdff }));
677 l
678 }
679 ClassId::MoeTokenSort => {
680 let mixtures: Vec<&Op> = parts.iter().filter(|p| matches!(p.prim(), Some(TensorOpSpec::Moe(_)))).collect();
681 let first = mixtures.first().ok_or("a chunk's mixtures")?;
682 let src = parts.iter().find(|p| matches!(p.sym, Sym::View { .. })).map(|v| v.ins[0].clone()).ok_or("the chunk's rows")?;
683 let mut l = self.prim(first, first.prim().expect("a mixture"))?;
684 let Launch::Moe { x, y, rows, .. } = &mut l else { return Err("a mixture".into()) };
685 (*x, *y, *rows) = (src, Dst::to(&op.out), mixtures.len());
686 l
687 }
688 ClassId::EmbedScale => {
689 let Some(TensorOpSpec::Scale(s)) = parts[1].prim() else { return Err("an embedding's scale".into()) };
690 let mut l = self.one(&parts[0])?;
691 let Launch::Gather { out, scale, .. } = &mut l else { return Err("a gather".into()) };
692 (*out, *scale) = (op.out.clone(), Some(s.factor.get()));
693 l
694 }
695 ClassId::RopeQkOneLaunch => {
696 let mut sides = Vec::new();
697 let mut shared = None;
698 for p in parts {
699 let Launch::Rope { x, part, norm, y, heads, per_row, cos, sin, pos, hd, half } = self.one(p)? else { return Err("a side of a launch of rotations".into()) };
700 shared.get_or_insert((cos, sin, pos, hd, half));
701 sides.push(RopeSide { x, part, norm, y, heads, per_row, yz: 0 });
702 }
703 let (cos, sin, pos, hd, half) = shared.ok_or("a launch of rotations")?;
704 Launch::RopeParts { sides, cos, sin, pos, hd, half, seqs: false }
705 }
706 ClassId::FusedSampling => {
707 let mut l = self.one(&parts[0])?;
708 let Launch::ArgMax { out, advance, .. } = &mut l else { return Err("an arg-max".into()) };
709 let seqs: Vec<(&str, &str)> = std::iter::once((self.io.next.as_str(), self.io.position.as_str()))
710 .chain(self.io.more.iter().map(|q| (q.next.as_str(), q.position.as_str())))
711 .collect();
712 let item = seqs.iter().position(|(next, _)| next == out).ok_or("an arg-max that picks no sequence's token")?;
713 *advance = Some(Advance { token: self.io.token.clone(), position: seqs[item].1.to_string(), item });
714 l
715 }
716 ClassId::NormQuant
717 | ClassId::AddNormQuant
718 | ClassId::GluActMulQuant
719 | ClassId::ElementwiseQuant
720 | ClassId::GdnOutputQuant
721 | ClassId::GemvQuant
722 | ClassId::AttnOutputQuant => {
723 let mut l = self.one(&parts[0])?;
724 let only = self.only_projections(&op.out);
725 match &mut l {
726 Launch::Norm { f32, quant, .. }
727 | Launch::Binary { f32, quant, .. }
728 | Launch::SwiGlu { f32, quant, .. }
729 | Launch::Delta { f32, quant, .. }
730 | Launch::GemvGlu { f32, quant, .. }
731 | Launch::Gemv { f32, quant, .. }
732 | Launch::Attention { f32, quant, .. } => (*quant, *f32) = (true, !only),
733 Launch::Fold { then, .. } => match then.as_mut() {
734 Launch::Norm { f32, quant, .. } => (*quant, *f32) = (true, !only),
735 _ => return Err("a fold that quantizes its norm's result".into()),
736 },
737 _ => return Err("a producer that quantizes in its epilogue".into()),
738 }
739 l
740 }
741 ClassId::MergedQkv | ClassId::QkvHeadEpilogue => {
742 let members: Vec<&Op> = if f.class == ClassId::QkvHeadEpilogue { vec![op] } else { parts.iter().collect() };
743 let (mut products, mut sides) = (Vec::new(), Vec::new());
744 for m in members {
745 let product = match &m.sym {
746 Sym::Fused(e) if e.class == ClassId::QkvHeadEpilogue => {
747 sides.push(self.one(&e.parts[1])?);
748 &e.parts[0]
749 }
750 _ => m,
751 };
752 products.push(gemv(product, self.g).ok_or("products of one activation")?);
753 }
754 let first = &products[0];
755 let Weights::Rows(format) = first.weights else { return Err("products by checkpoint rows".into()) };
756 let gemm: Vec<GemmPart> = products.iter().map(|p| GemmPart { w: p.w.clone(), bias: p.bias.clone(), y: dst(&p.dst), n: p.n }).collect();
757 let projections = if self.shim.on_tensor_cores(first) {
758 let n: usize = gemm.iter().map(|p| p.n).sum();
759 let rows = kernels::gemm_blk_rows(kernels::format_arg(format), self.numerics() == Q8MatKind::TwoPassInt8);
760 let tiles = n.div_ceil(kernels::GEMM_TILE_N) * first.m.div_ceil(rows);
761 let tail = match kernels::gemm_blk_tail(tiles, self.shim.knobs.chip.sms, first.k.div_ceil(128)) {
762 (whole, split) if split > 1 => Some((whole, split)),
763 _ => None,
764 };
765 Launch::GemmParts { x: first.x.clone(), format, k: first.k, m: first.m, parts: gemm, tail, per_warp: 1, batch: false }
766 } else {
767 Launch::GemmParts { x: first.x.clone(), format, k: first.k, m: first.m, parts: gemm, tail: None, per_warp: self.shim.per_warp(first), batch: self.shim.batch }
768 };
769 if sides.is_empty() {
770 projections
771 } else {
772 let mut turned = Vec::new();
773 let mut shared = None;
774 for side in sides {
775 let Launch::Rope { x, part, norm, y, heads, per_row, cos, sin, pos, hd, half } = side else { return Err("a part's heads turned in its epilogue".into()) };
776 shared.get_or_insert((cos, sin, pos, hd, half));
777 turned.push(RopeSide { x, part, norm, y, heads, per_row, yz: 0 });
778 }
779 let (cos, sin, pos, hd, half) = shared.expect("a rotation");
780 Launch::Fold { first: Box::new(projections), then: Box::new(Launch::RopeParts { sides: turned, cos, sin, pos, hd, half, seqs: false }) }
781 }
782 }
783 other => return Err(format!("the lane links `{other}` whole and has no launch for it")),
784 };
785 self.place(op, &mut l);
786 Ok(l)
787 }
788
789 fn only_projections(&self, x: &str) -> bool {
792 let mut names = vec![x.to_string()];
793 let mut seen: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
794 let mut readers: Vec<(&Op, String)> = Vec::new();
795 while let Some(t) = names.pop() {
796 if !seen.insert(t.clone()) {
797 continue;
798 }
799 for op in self.g.ops.iter().filter(|o| o.ins.contains(&t)) {
800 match &op.sym {
801 Sym::View { offset } if offset % QBLOCK == 0 => names.push(op.out.clone()),
802 Sym::Fused(f) if f.class == ClassId::RowWriteQuant => {
805 if op.ins.get(1) == Some(&t) {
806 names.push(op.out.clone());
807 }
808 }
809 _ => readers.push((op, t.clone())),
810 }
811 }
812 }
813 readers.iter().all(|(op, t)| {
814 let projected = op.ins.iter().enumerate().any(|(k, n)| n == t && self.shim.activation(op, k, self.g).is_some());
815 let f32_router = leaves((*op).clone()).iter().any(|l| matches!(l.prim(), Some(TensorOpSpec::Moe(_))) && self.values(&l.ins[1]));
816 projected && !f32_router && !matches!(op.sym, Sym::Quantize(_))
817 })
818 }
819
820 fn latent(&self, op: &Op, f: &Fused) -> Result<Vec<(Launch, Vec<usize>)>, String> {
823 let found = leaves(op.clone());
824 let attn = found.iter().find(|l| matches!(l.prim(), Some(TensorOpSpec::Attention(_)))).ok_or("a latent attention")?;
825 let Some(TensorOpSpec::Attention(a)) = attn.prim() else { unreachable!() };
826 let join = found.iter().find(|l| matches!(l.prim(), Some(TensorOpSpec::Join(_))) && l.out == attn.ins[0]).ok_or("the attention's joined query")?;
827 let heads_product = |x: &str| found.iter().find(|l| matches!(l.prim(), Some(TensorOpSpec::HeadMatmul(_))) && l.ins[0] == x);
828 let absorb = found.iter().find(|l| matches!(l.prim(), Some(TensorOpSpec::HeadMatmul(_))) && l.out == join.ins[0]).ok_or("the query's absorption")?;
829 let slice = found.iter().find(|l| l.out == absorb.ins[0]).ok_or("the query's unrotated part")?;
830 let expand = heads_product(&attn.out).ok_or("the value half")?;
831 let (Some(TensorOpSpec::HeadMatmul(ab)), Some(TensorOpSpec::HeadMatmul(ex))) = (absorb.prim(), expand.prim()) else { unreachable!() };
832 let (Some(rk), Some(rv)) = (self.rows_of(&absorb.ins[1]), self.rows_of(&expand.ins[1])) else { return Err("the expansion's checkpoint rows".into()) };
833 let pos = match &a.pos {
834 Some(RowIndex::Named(_)) => Some(attn.ins[3].clone()),
835 _ => None,
836 };
837 let latent = Latent {
838 qr: slice.ins[0].clone(),
839 qpp: join.ins[1].clone(),
840 kc: attn.ins[1].clone(),
841 pos,
842 heads: a.h,
843 kw: a.hd,
844 lora: a.vd,
845 nope: ab.a,
846 len: a.l,
847 rows: a.rows,
848 scale: a.scale.map_or(1.0 / (a.hd as f64).sqrt(), |s| s.get()),
849 wkb: absorb.ins[1].clone(),
850 fk: rk.ty,
851 kro: rk.transposed,
852 wvb: expand.ins[1].clone(),
853 fv: rv.ty,
854 vd: ex.b,
855 ct: f.parts.iter().map(|p| p.out.clone()).find(|o| *o == op.out).unwrap_or_else(|| op.out.clone()),
856 };
857 let [kh, vh, attend] = self.shim.latent_launches(&latent).ok_or_else(|| "a latent head wider than one block's shared memory holds".to_string())?;
858 let values = found.iter().find(|l| l.out == attn.ins[2]).ok_or("the values' slice")?;
859 let (keys_ops, values_ops) = (covered(absorb), covered(values));
860 let rest: Vec<usize> = covered(op).into_iter().filter(|k| !keys_ops.contains(k) && !values_ops.contains(k)).collect();
861 Ok(vec![(kh, keys_ops), (vh, values_ops), (attend, rest)])
862 }
863}
864
865fn covered(op: &Op) -> Vec<usize> {
867 let mut c = op.covers.clone();
868 c.sort_unstable();
869 c
870}
871
872pub fn link(g: &TensorGraph, constants: &BTreeMap<String, Placement>, shim: &LaneShim, io: &StepIo) -> Result<Vec<(Launch, Vec<usize>)>, (usize, String)> {
875 let l = Link { g, constants, shim, io };
876 let mut out = Vec::new();
877 for (k, op) in g.ops.iter().enumerate() {
878 out.extend(l.op(op).map_err(|why| (k, why))?);
879 }
880 Ok(out)
881}