1use std::collections::BTreeSet;
28
29use crate::codegen_cuda::knobs::{CachePrecision, CudaKnobs, StatePrecision};
30use crate::codegen_cuda::qwen_emit::Q8MatKind;
31use crate::tensor_graph::facts;
32use crate::tensor_graph::fuse::{class, conv, gemv, leaves, moe, norm, rope, slice, unit_output, ChipRule, ConvForm, GemvForm, Rewriter, Weights};
33use crate::tensor_graph::front::TensorOpSpec;
34use crate::tensor_graph::graph::{Access, Op, QuantFormat, RowSel, Storage, Sym, TensorGraph};
35use crate::tensor_graph::numerics::{holds_accumulator, AttentionNumerics, Block, Bound, Contract, Keep, Scale, Scheme, Taken};
36use crate::tensor_graph::shim::{Position, Shim, Support};
37use crate::tensor_graph::symbols::ClassId;
38use crate::tensor_recognize::qwen::{GateAct, MoeAct, RowIndex};
39use crate::tensor_recognize::ElementwiseOp;
40
41use super::lane::kernels;
42use super::lane::plan::{attended_span, Dst as LaneDst, Launch, Part as LanePart, Split, GEMV_ROWS, QBLOCK};
43
44const CONV_PARTS: usize = 4;
46
47const ROPE_SIDES: usize = 2;
49
50#[derive(Clone, Debug)]
53pub struct LaneShim {
54 pub knobs: CudaKnobs,
55 pub batch: bool,
56 pub held: Vec<&'static str>,
57}
58
59impl LaneShim {
60 pub fn per_warp(&self, p: &GemvForm) -> usize {
63 match p.weights {
64 Weights::Rows(format) if !self.held.contains(&"rows_a_warp") => kernels::rows_a_warp(format, p.n, p.k, p.m),
65 _ => 1,
66 }
67 }
68
69 fn moe_down_launch(op: &Op, g: &TensorGraph) -> Option<(usize, usize)> {
72 let m = moe(op)?;
73 let dff = leaves(op.clone()).iter().find_map(|p| match p.prim()? {
74 TensorOpSpec::Moe(s) => Some(s.dff),
75 _ => None,
76 })?;
77 let sdff = match &op.sym {
78 Sym::Fused(f) if f.class == ClassId::SharedExpertFusion => gemv(f.parts.get(2)?, g)?.k,
79 _ => 0,
80 };
81 Some(kernels::moe_down_launch(m.d, dff, m.k, sdff))
82 }
83
84 pub fn on_tensor_cores(&self, p: &GemvForm) -> bool {
87 p.m > GEMV_ROWS && !self.batch && self.knobs.activations != Q8MatKind::Dequant && matches!(p.weights, Weights::Rows(_))
88 }
89
90 fn lands_whole(p: &GemvForm) -> bool {
92 p.dst.unit.is_none() && p.dst.row.as_ref().map_or(true, |(_, pitch)| *pitch == p.n)
93 }
94
95 fn rows_product(op: &Op, g: &TensorGraph) -> Option<GemvForm> {
97 gemv(op, g).filter(|p| matches!(p.weights, Weights::Rows(_)))
98 }
99
100 fn part_product(op: &Op, g: &TensorGraph) -> Option<GemvForm> {
103 match &op.sym {
104 Sym::Fused(f) if f.class == ClassId::QkvHeadEpilogue => Self::rows_product(&f.parts[0], g),
105 _ => Self::rows_product(op, g),
106 }
107 }
108
109 fn turned_alike(members: &[Op]) -> bool {
113 let sides: Vec<_> = members
114 .iter()
115 .filter_map(|m| match &m.sym {
116 Sym::Fused(f) if f.class == ClassId::QkvHeadEpilogue => rope(&f.parts[1]),
117 _ => None,
118 })
119 .collect();
120 sides.len() <= ROPE_SIDES
121 && sides.iter().all(|s| {
122 (&s.cos, &s.sin, &s.pos, s.hd, s.half, s.norm.is_some()) == (&sides[0].cos, &sides[0].sin, &sides[0].pos, sides[0].hd, sides[0].half, sides[0].norm.is_some())
123 })
124 }
125
126 fn gemv_mergeable(&self, p: &GemvForm) -> bool {
129 (p.m <= GEMV_ROWS || self.batch) && Self::lands_whole(p) && p.n % self.per_warp(p) == 0
130 }
131
132 fn gemv_in_parts(&self, members: &[Op], g: &TensorGraph) -> bool {
135 let products: Option<Vec<GemvForm>> = members.iter().map(|m| Self::part_product(m, g)).collect();
136 let Some(products) = products else { return false };
137 let first = &products[0];
138 products.len() <= kernels::GEMM_PARTS
139 && Self::turned_alike(members)
140 && products.iter().all(|p| {
141 self.gemv_mergeable(p)
142 && p.weights == first.weights
143 && (p.x.as_str(), p.k, p.m) == (first.x.as_str(), first.k, first.m)
144 && self.per_warp(p) == self.per_warp(first)
145 })
146 }
147
148 fn gemm_parts(&self, members: &[Op], g: &TensorGraph) -> bool {
150 let products: Option<Vec<GemvForm>> = members.iter().map(|m| Self::rows_product(m, g)).collect();
151 let Some(products) = products else { return false };
152 let first = &products[0];
153 products.len() <= kernels::GEMM_PARTS
154 && products.iter().all(|p| {
155 self.on_tensor_cores(p)
156 && Self::lands_whole(p)
157 && p.n % kernels::GEMM_TILE_N == 0
158 && p.weights == first.weights
159 && (p.x.as_str(), p.k, p.m) == (first.x.as_str(), first.k, first.m)
160 })
161 }
162}
163
164fn embedded_norm(op: &Op) -> Option<crate::tensor_graph::fuse::NormForm> {
166 match &op.sym {
167 Sym::Fused(f) if f.class == ClassId::EmbedNorm => norm(f.parts.last()?),
168 _ => None,
169 }
170}
171
172impl Shim for LaneShim {
173 fn name(&self) -> String {
174 format!("cuda-lane:{}:{:?}", self.knobs.chip.name, self.knobs.activations)
175 }
176
177 fn supports(&self, class: ClassId) -> Support {
178 use ClassId::*;
179 use Support::{Decompose, Native, Refuse};
180 let epilogue = if self.knobs.fuse_quant { Native } else { Decompose };
181 match class {
182 NormWeight | AddNorm | NormThenAdd | GatedNorm | EmbedScale => Native,
183 QkNormRope | QkNormRopeKvAppend | RopeKvAppend | RopeQkOneLaunch | Mla => Native,
184 FlashDecode | FlashPrefill | AttentionVariantsInline | AttnLseMerge => Native,
185 GluActMul | GemvGateUpGlu | GemmBias | MergedQkv | MergedGateUp | GemmDequantInkernel => Native,
186 RouterTopk | MoeTokenSort | MoeGateUpAct | MoeDownCombine | SharedExpertFusion => Native,
187 GdnStep | ConvBiasSilu | ConvParts | ConvSiluL2norm | GdnGateProjections | GdnGating => Native,
188 FusedSampling | StridedRead | RowWrite | JoinInPlace | StridedValues | CudaGraphs => Native,
189 NormQuant | AddNormQuant | GluActMulQuant | ElementwiseQuant | GdnOutputQuant | GemvQuant => epilogue,
190 GemvNormEpilogue => Native,
191 EmbedNorm => Native,
192 NormScale | NormWeightOffset | DualNorm | PrefillNormEpilogues | AttnResidualNorm => Decompose,
193 QkvHeadEpilogue => Native,
194 RecomputeFanout | QkNormRopeGateSplit | HorizontalNormRopeInsert | CrossLayerLaunch => Decompose,
195 AttnOutputQuant => epilogue,
196 AttnOutputGate | RowWriteQuant => Native,
197 MlaPrologue | MlaUnabsorbInFold | ElementwiseChains | ElementwiseRegions => Decompose,
198 MaskedExpertActivation | GemmScale | AffineInputFold | RouterSharedGateMerge | MoeGatherInOperand => Decompose,
199 MoeDeferredFinalize => Native,
200 GdnInputEpilogue | GdnDecodeWhole | GdnPostConvPrep | LastLayerRows => Decompose,
201 ReluSqr => Refuse("the lane links no ReLU"),
202 Softcap => Refuse("the lane's attention takes no soft cap"),
203 MixedBatchAttention => Refuse("the lane attends a batch's sequences each at its own position, none mid-prefill"),
204 SparseAttention => Refuse("the lane's attention reads every cached row of its span"),
205 GroupedTopk => Refuse("the lane routes by one top-k over every expert"),
206 Sampling => Refuse("the lane picks the next token by arg-max"),
207 KvCacheQuant => Refuse("the lane stores its caches in f32 or f16"),
208 HadamardRotation => Refuse("the lane rotates no cache"),
209 CompensatedResidual => Refuse("the lane sums its residual in f32, uncompensated"),
210 HostResidentKv => Refuse("the lane keeps its caches on the device; only matrices move to host memory"),
211 AttnGqaRowPacking => Refuse("the lane's decode attention runs a head's dots on the CUDA cores"),
212 MoeDirectPairs => Refuse("the lane sorts a chunk's rows by expert"),
213 PersistentLayerMegakernel => Refuse("the lane replays a graph of a launch an op"),
214 AllreduceNorm => Refuse("the lane runs on one device"),
215 }
216 }
217
218 fn fits(&self, op: &Op, g: &TensorGraph) -> Result<(), String> {
219 holds_accumulator(op, g, 32)?;
220 let refuse = |why: &str| Err(why.to_string());
221 for leaf in leaves(op.clone()) {
222 match leaf.prim() {
223 Some(TensorOpSpec::Attention(a)) if a.softcap.is_some() => return refuse("the lane's attention takes no soft cap"),
224 Some(TensorOpSpec::Softcap(_)) => return refuse("the lane links no soft-capped tensor"),
225 Some(TensorOpSpec::GatedDelta(g)) if g.gate == GateAct::Sigmoid => {
226 return refuse("the lane's gated delta gates by SiLU, not a sigmoid")
227 }
228 _ => {}
229 }
230 if let Some(u) = &leaf.dst.unit {
231 if !unit_output(&leaf).is_some_and(|(_, width)| width == u.width) {
232 return refuse("the lane writes a part of every unit of a wider tensor only from a norm, a rotation or a per-head product");
233 }
234 }
235 for access in &leaf.access {
236 let Access::Strided(s) = access else { continue };
237 match leaf.prim() {
238 Some(TensorOpSpec::HeadMatmul(_)) if s.pitch % QBLOCK != 0 || s.offset % QBLOCK != 0 => {
239 return refuse("a per-head product reads a part in place only at whole 32-value blocks");
240 }
241 Some(TensorOpSpec::Attention(_)) if s.pitch % 2 != 0 || s.offset % 2 != 0 => {
242 return refuse("an attention reads its values in place a pair at a time, from an even place");
243 }
244 _ => {}
245 }
246 }
247 }
248 if moe(op).is_some() && matches!(op.dst.row, Some((RowSel::Param(_), _))) {
249 return refuse("a mixture writes its result at a fixed row, never at one a parameter names");
250 }
251 let Sym::Fused(f) = &op.sym else { return Ok(()) };
252 match f.class {
253 ClassId::NormThenAdd if norm(op).is_none() => Err("the lane's norm-then-add kernel norms the sum it writes".to_string()),
254 ClassId::GemvNormEpilogue => {
255 let (Some(p), Some(n)) = (gemv(&f.parts[0], g), norm(&f.parts[1])) else {
256 return refuse("a projection's epilogue norms the add of its result");
257 };
258 let rows = matches!(p.weights, Weights::Rows(ty) if ty == crate::model::quant::GgmlType::Q8_0 || kernels::decodes(ty));
259 let one_gemv = rows && p.m == 1 && !self.batch && p.dst.own().is_some();
260 let whole_row = n.part.is_none() && n.rows == p.m && n.cols == p.n && f.parts[1].dst.own().is_some();
261 if !(one_gemv && whole_row) {
262 return refuse("a projection norms in its last block the one row it writes whole, a GEMV of a step's one row by checkpoint rows");
263 }
264 let block = match p.weights {
266 Weights::Rows(crate::model::quant::GgmlType::Q8_0) => kernels::norm_threads(n.cols),
267 _ => kernels::gemv_blk_norm_threads(self.per_warp(&p)),
268 };
269 if kernels::block_holds_its_norm(block, n.cols) {
270 Ok(())
271 } else {
272 refuse("a projection norms in its last block only where its block is at least the norm's width")
273 }
274 }
275 ClassId::MoeDeferredFinalize => {
276 let (Some(m), Some(n)) = (moe(&f.parts[0]), f.parts.last().and_then(norm)) else {
277 return refuse("a mixture's last step norms the add of its result");
278 };
279 let summed = f.parts.len() == 2 || f.parts[1].prim().is_some_and(|p| matches!(p, TensorOpSpec::Elementwise(e) if e.op == ElementwiseOp::Add));
280 let whole_rows = n.part.is_none() && n.rows == m.rows && n.cols == m.d && n.own && m.dst.own().is_some();
281 if !(summed && whole_rows) {
282 return refuse("a mixture, alone or summed with another array, adds and norms in its last step the whole rows it writes");
283 }
284 if m.rows == 1 && !self.batch {
287 let Some((_, threads)) = Self::moe_down_launch(&f.parts[0], g) else {
288 return refuse("a decode mixture's down projection reads its experts' widths from its parts");
289 };
290 if !kernels::block_holds_its_norm(threads, m.d) {
291 return refuse("a decode mixture norms in its last down block only where the block is at least the norm's width");
292 }
293 }
294 Ok(())
295 }
296 ClassId::EmbedNorm => {
297 let gathered = |o: &Op| match o.prim() {
298 Some(TensorOpSpec::RowsGather(_) | TensorOpSpec::RowGather(_)) => true,
299 _ => class(o) == Some(ClassId::EmbedScale),
300 };
301 match embedded_norm(op) {
302 Some(n) if gathered(&f.parts[0]) && n.own => Ok(()),
303 _ => refuse("a gather norms in its own blocks the whole rows it gathers"),
304 }
305 }
306 ClassId::QkvHeadEpilogue => {
307 let (Some(p), Some(ro)) = (Self::rows_product(&f.parts[0], g), rope(&f.parts[1])) else {
308 return refuse("a projection's epilogue turns the heads of its result");
309 };
310 let one_row = p.m == 1 && !self.batch && self.gemv_mergeable(&p) && p.dst.own().is_some();
311 let its_heads = ro.part.is_none() && ro.heads == ro.per_row && p.n == ro.heads * ro.hd && ro.hd <= kernels::ROPE_HEAD;
312 if one_row && its_heads {
313 Ok(())
314 } else {
315 refuse("a projection turns in its epilogue the heads of the one row it writes whole, a GEMV of a step's one row")
316 }
317 }
318 ClassId::ConvSiluL2norm => {
319 let found = leaves(op.clone());
320 let chan = found.iter().find_map(|p| match p.prim()? {
321 TensorOpSpec::CausalConv(c) => Some(c.chan),
322 _ => None,
323 });
324 let cols = found.iter().find_map(|p| match p.prim()? {
325 TensorOpSpec::L2Norm(l) => Some(l.cols),
326 _ => None,
327 });
328 match (chan, cols) {
329 (Some(chan), Some(cols)) if cols <= 1024 && cols % 32 == 0 && chan % cols == 0 => Ok(()),
330 _ => refuse("a convolution's epilogue norms heads of at most 1024 values, whole warps, that divide its channels"),
331 }
332 }
333 ClassId::GemvGateUpGlu => {
334 let products: Vec<GemvForm> = f.parts.iter().filter_map(|p| gemv(p, g)).collect();
335 let (Some(gate), Some(up)) = (products.first(), products.get(1)) else {
336 return refuse("a gated pair is two products");
337 };
338 if !matches!((gate.weights, up.weights), (Weights::Rows(_), Weights::Rows(_))) {
339 return refuse("the lane pairs a gate and an up product only over checkpoint rows");
340 }
341 let gemv_rows = gate.m <= GEMV_ROWS || self.knobs.activations == Q8MatKind::Dequant || self.batch;
342 let paired = !gemv_rows && gate.weights == up.weights && gate.n % (kernels::GEMM_TILE_N / 2) == 0;
343 if gemv_rows || paired {
344 Ok(())
345 } else {
346 refuse("a gated pair on the tensor cores pairs tiles of one format, the hidden width whole pair tiles")
347 }
348 }
349 ClassId::ConvParts => {
350 let members: Option<Vec<ConvForm>> = f.parts.iter().map(conv).collect();
351 let Some(members) = members else { return refuse("a launch of convolutions takes convolutions") };
352 let normed: BTreeSet<_> = members.iter().filter_map(|c| c.l2.map(|(cols, eps)| (cols, eps.get().to_bits()))).collect();
353 let one = members.len() <= CONV_PARTS
354 && members.iter().all(|c| c.rows == 1 && c.silu && c.taps == members[0].taps)
355 && normed.len() <= 1;
356 if one {
357 Ok(())
358 } else {
359 refuse("one launch convolves a step's one row through the SiLU in at most four parts of one length of taps, the normed ones over heads of one width")
360 }
361 }
362 ClassId::GdnGateProjections => {
363 let by_values = f.parts.iter().filter_map(|p| gemv(p, g)).all(|p| p.weights == Weights::Values);
364 if by_values {
365 Ok(())
366 } else {
367 refuse("the lane takes a delta rule's gates from its activation only by matrices it holds as values")
368 }
369 }
370 ClassId::SharedExpertFusion => {
371 let found = leaves(op.clone());
372 let swiglu = found.iter().any(|p| matches!(p.prim(), Some(TensorOpSpec::SwiGlu(_))));
373 let biased = found.iter().any(|p| matches!(p.prim(), Some(TensorOpSpec::Elementwise(e)) if e.op == ElementwiseOp::Add && e.row.is_some()));
374 let rows = found.iter().filter_map(|p| gemv(p, g)).all(|p| matches!(p.weights, Weights::Rows(_)) && p.m == 1);
375 match moe(op) {
376 Some(m) if m.k < 32 && m.rows == 1 && m.act == MoeAct::Swiglu && swiglu && !biased && rows => Ok(()),
377 _ => refuse("a mixture folds its unbiased SwiGLU shared expert, a step's one row of checkpoint rows, into at most 32 warps of kept experts"),
378 }
379 }
380 ClassId::RopeQkOneLaunch => {
381 let sides: Option<Vec<_>> = f.parts.iter().map(rope).collect();
382 let Some(sides) = sides else { return refuse("a launch of rotations takes rotations") };
383 let one = sides.len() <= ROPE_SIDES
384 && sides.iter().all(|s| s.heads == s.per_row && s.norm.is_some() == sides[0].norm.is_some());
385 if one {
386 Ok(())
387 } else {
388 refuse("one launch rotates at most a query and a key of a step's one row, both normed or neither")
389 }
390 }
391 ClassId::AttnOutputGate => {
394 let attentions: Vec<_> = f.parts.iter().filter_map(|p| match p.prim() {
395 Some(TensorOpSpec::Attention(a)) => Some(a),
396 _ => None,
397 }).collect();
398 if !attentions.is_empty() && attentions.iter().all(|a| a.rows <= GEMV_ROWS && a.vd % 2 == 0) {
399 Ok(())
400 } else {
401 refuse("an attention gates its output only in the decode step's, a short chunk's and a batch's sequences' kernels, its heads whole pairs")
402 }
403 }
404 ClassId::RowWriteQuant => match f.parts[0].prim() {
406 Some(TensorOpSpec::SetRow(r)) if r.cols % QBLOCK == 0 => Ok(()),
407 _ => refuse("a row set carries its rows' quantized form only where they are whole 32-value blocks"),
408 },
409 ClassId::GluActMulQuant if class(&f.parts[0]) == Some(ClassId::AttnOutputGate) => match &f.parts[0].sym {
412 Sym::Fused(gated) => {
413 let whole = gated.parts.iter().all(|p| match p.prim() {
414 Some(TensorOpSpec::Attention(a)) => a.vd % 32 == 0,
415 Some(TensorOpSpec::SetRow(r)) => r.cols % QBLOCK == 0,
416 _ => true,
417 });
418 if whole {
419 Ok(())
420 } else {
421 refuse("a gated attention quantizes its output in its store only where its heads, and a batch's rows, are whole 32-value groups")
422 }
423 }
424 _ => refuse("a gated attention is a fold of its attention and its gate"),
425 },
426 ClassId::AttnOutputQuant => match f.parts[0].prim() {
427 Some(TensorOpSpec::Attention(a)) if a.rows <= GEMV_ROWS && a.vd % 32 == 0 => Ok(()),
430 _ => refuse("an attention quantizes its output only in the decode step's and a short chunk's kernels, its heads whole 32-value groups"),
431 },
432 ClassId::NormQuant if norm(&f.parts[0]).or_else(|| embedded_norm(&f.parts[0])).is_none() => {
433 refuse("the lane's norm quantizes its result only from an RMS norm")
434 }
435 ClassId::GemvQuant => match gemv(&f.parts[0], g) {
436 Some(p) if self.per_warp(&p) == 4 && p.n % QBLOCK == 0 && p.dst.own().is_some() => Ok(()),
437 _ => refuse("a product quantizes its outputs only four rows a warp, whole blocks of them"),
438 },
439 ClassId::GluActMulQuant if class(&f.parts[0]) == Some(ClassId::GemvGateUpGlu) => {
440 let hidden = match &f.parts[0].sym {
441 Sym::Fused(glu) => glu.parts.iter().find_map(|p| gemv(p, g)).map(|p| p.n),
442 _ => None,
443 };
444 match hidden {
445 Some(n) if n % QBLOCK == 0 => Ok(()),
446 _ => refuse("a gated pair quantizes its hidden values in whole 32-value blocks"),
447 }
448 }
449 ClassId::MergedQkv => {
450 if self.gemm_parts(&f.parts, g) || (!self.held.contains(&"gemv_parts") && self.gemv_in_parts(&f.parts, g)) {
452 Ok(())
453 } else {
454 refuse("products of one activation are one launch in parts: at most four, one format — on the tensor cores whole 128-output tiles, in the GEMV one number of rows a warp")
455 }
456 }
457 _ => Ok(()),
458 }
459 }
460
461 fn activation(&self, consumer: &Op, operand: usize, g: &TensorGraph) -> Option<QuantFormat> {
462 if let Sym::Fused(f) = &consumer.sym {
464 use ClassId::*;
465 if matches!(f.class, NormQuant | AddNormQuant | GluActMulQuant | ElementwiseQuant | GdnOutputQuant | GemvQuant | AttnOutputQuant) {
466 return self.activation(&f.parts[0], operand, g);
467 }
468 if matches!(f.class, GemvNormEpilogue | QkvHeadEpilogue | MoeDeferredFinalize) {
470 return self.activation(&f.parts[0], operand, g).filter(|_| operand < f.parts[0].ins.len());
471 }
472 }
473 let format = match self.knobs.activations {
474 Q8MatKind::Dequant => return None,
475 Q8MatKind::Int8Act => QuantFormat::OneWord,
476 Q8MatKind::TwoPassInt8 => QuantFormat::TwoWords,
477 };
478 let projected = operand == 0
479 && (Self::rows_product(consumer, g).is_some()
480 || moe(consumer).is_some()
481 || matches!(class(consumer), Some(ClassId::GemvGateUpGlu | ClassId::MergedQkv)));
482 projected.then_some(format)
483 }
484
485 fn rules(&self) -> Vec<(Position, ChipRule<'_>)> {
486 vec![
487 (Position::Before("add_norm"), ChipRule { name: "latent_expanded", builds: &[ClassId::Mla], apply: Box::new(move |r| self.latent_expanded(r)) }),
488 (Position::Before("merged_projections"), ChipRule { name: "gemv_parts", builds: &[ClassId::MergedQkv], apply: Box::new(move |r| self.gemv_parts(r)) }),
489 ]
490 }
491
492 fn numerics(&self) -> Option<Contract> {
493 let activations = match self.knobs.activations {
494 Q8MatKind::Dequant => None,
495 Q8MatKind::Int8Act => Some(Scheme::of(QuantFormat::OneWord)),
496 Q8MatKind::TwoPassInt8 => Some(Scheme::of(QuantFormat::TwoWords)),
497 };
498 let cache = match self.knobs.cache_precision {
499 CachePrecision::F32 => Keep::F32,
500 CachePrecision::F16 => Keep::F16,
501 };
502 let state = match self.knobs.recurrent_state {
503 StatePrecision::F32 => Keep::F32,
504 StatePrecision::Bf16 => Keep::Bf16,
505 };
506 let point = format!("the lane: {:?} activations, {:?} cache, {:?} state", self.knobs.activations, self.knobs.cache_precision, self.knobs.recurrent_state);
507 Some(Contract { point, activations, cache, state, attention: Self::attention(self.knobs.activations), fused: Bound::EXACT, accumulator_bits: 32 })
508 }
509}
510
511impl LaneShim {
512 pub fn attention(activations: Q8MatKind) -> Option<AttentionNumerics> {
518 let row = |words| Taken::Quantized(Scheme { block: Block::Row, scale: Scale::MaxOver127, words });
519 match activations {
520 Q8MatKind::Dequant => None,
521 Q8MatKind::Int8Act => Some(AttentionNumerics { queries: Taken::Kept(Keep::F16), keys_values: Taken::Kept(Keep::F16), weights: Taken::Kept(Keep::F16) }),
522 Q8MatKind::TwoPassInt8 => Some(AttentionNumerics { queries: row(2), keys_values: row(2), weights: row(3) }),
523 }
524 }
525
526 fn gemv_parts(&self, r: &mut Rewriter) {
532 for i in 0..r.s.len() {
533 let Some(first) = r.s.slot(i).and_then(|o| Self::part_product(o, r.g)).filter(|p| self.gemv_mergeable(p)) else { continue };
534 let mut group = vec![i];
535 for j in i + 1..r.s.len() {
536 let Some(op) = r.s.slot(j) else { continue };
537 if group.len() == kernels::GEMM_PARTS || facts::writes(op).contains(&first.x.as_str()) {
538 break;
539 }
540 let members: Vec<Op> = group.iter().map(|&k| r.s.get(k).clone()).chain([op.clone()]).collect();
541 let fits = Self::part_product(op, r.g).is_some() && self.gemv_in_parts(&members, r.g);
542 if !fits {
543 continue;
544 }
545 let (written, read) = (facts::writes(op), facts::reads(op));
546 let crossed = (i + 1..j).filter(|b| !group.contains(b)).filter_map(|b| r.s.slot(b)).any(|between| {
547 let (br, bw) = (facts::reads(between), facts::writes(between));
548 written.iter().any(|t| br.contains(t) || bw.contains(t)) || read.iter().any(|t| bw.contains(t))
549 });
550 if !crossed {
551 group.push(j);
552 }
553 }
554 if group.len() < 2 {
555 continue;
556 }
557 let members: Vec<Op> = group.iter().map(|&k| r.s.get(k).clone()).collect();
558 let mut ins: Vec<(String, Access)> = vec![(first.x.clone(), members[0].access[0])];
559 ins.extend(members.iter().flat_map(|m| m.ins[1..].iter().map(|t| (t.clone(), Access::Whole))));
560 let (out, dst) = (members[0].out.clone(), members[0].dst.clone());
561 r.fuse("gemv_parts", ClassId::MergedQkv, i, &group[1..], ins, out, dst);
562 }
563 }
564
565 fn latent_expanded(&self, r: &mut Rewriter) {
573 let numerics = self.knobs.activations;
574 if numerics == Q8MatKind::Dequant || self.batch {
575 return;
576 }
577 let whole = |op: &Op| op.access.iter().all(|a| *a == Access::Whole);
578 let rows_of = |t: &str| match r.g.tensors.get(t).map(|t| &t.storage) {
579 Some(Storage::Rows { format, transposed }) => Some((*format, *transposed)),
580 _ => None,
581 };
582 let mut found = Vec::new();
583 for i in 0..r.s.len() {
584 let Some(op) = r.s.slot(i) else { continue };
585 let Some(TensorOpSpec::Attention(a)) = op.prim() else { continue };
586 if a.sinks.is_some() || a.window.is_some() || a.kv != 1 || a.rows <= GEMV_ROWS || a.vd >= a.hd || !whole(op) {
587 continue;
588 }
589 let (q, kc, vc, ol) = (op.ins[0].clone(), op.ins[1].clone(), op.ins[2].clone(), op.out.clone());
590 let pos = match &a.pos {
591 Some(RowIndex::Named(_)) => Some(op.ins[3].clone()),
592 Some(RowIndex::Literal(_)) => continue,
593 None => None,
594 };
595 let (heads, kw, lora, len, rows, scale) = (a.h, a.hd, a.vd, a.l, a.rows, a.scale.map_or(1.0 / (a.hd as f64).sqrt(), |s| s.get()));
596 let rope = kw - lora;
597 let Some(vi) = r.s.producer(&vc) else { continue };
599 if !slice(r.s.get(vi)).is_some_and(|v| v.x == kc && v.every == kw && v.from == 0 && v.width == lora) || r.s.readers(&vc) != [i] {
600 continue;
601 }
602 let Some(ji) = r.s.producer(&q) else { continue };
604 let join = r.s.get(ji);
605 let Some(TensorOpSpec::Join(j)) = join.prim() else { continue };
606 if join.dst.own() != Some(q.as_str()) || !whole(join) || (j.rows, j.heads, j.wa / j.heads, j.wb / j.heads) != (rows, heads, lora, rope) || r.s.readers(&q) != [i] {
607 continue;
608 }
609 let (ql, qpp) = (join.ins[0].clone(), join.ins[1].clone());
610 let Some(ai) = r.s.producer(&ql) else { continue };
611 let absorb = r.s.get(ai);
612 let Some(TensorOpSpec::HeadMatmul(m)) = absorb.prim() else { continue };
613 if absorb.dst.own() != Some(ql.as_str()) || !whole(absorb) || (m.m, m.heads, m.b) != (rows, heads, lora) || r.s.readers(&ql) != [ji] {
614 continue;
615 }
616 let (qn, wkb, nope) = (absorb.ins[0].clone(), absorb.ins[1].clone(), m.a);
617 let Some((fk, kro)) = rows_of(&wkb) else { continue };
618 let qk = nope + rope;
619 let Some(qi) = r.s.producer(&qn) else { continue };
620 let Some(qs) = slice(r.s.get(qi)).filter(|s| (s.groups, s.every, s.from, s.width) == (rows * heads, qk, 0, nope)) else { continue };
621 if r.s.readers(&qn) != [ai] {
622 continue;
623 }
624 let qr = qs.x;
625 let Some(ri) = r.s.producer(&qpp) else { continue };
626 let rot = r.s.get(ri);
627 if !matches!(rot.prim(), Some(TensorOpSpec::Rope(_))) || rot.dst.own() != Some(qpp.as_str()) || !whole(rot) || r.s.readers(&qpp) != [ji] {
628 continue;
629 }
630 let rotated_from_query = r.s.producer(&rot.ins[0]).and_then(|e| slice(r.s.get(e))).is_some_and(|e| e.x == qr && (e.every, e.from, e.width) == (qk, nope, rope));
631 if !rotated_from_query {
632 continue;
633 }
634 let [ci] = r.s.readers(&ol)[..] else { continue };
636 let expand = r.s.get(ci);
637 let Some(TensorOpSpec::HeadMatmul(c)) = expand.prim() else { continue };
638 let Some((fv, true)) = rows_of(&expand.ins[1]) else { continue };
639 let Some(ct) = expand.dst.own().map(str::to_string) else { continue };
640 if !whole(expand) || (c.m, c.heads, c.a) != (rows, heads, lora) {
641 continue;
642 }
643 let latent = Latent {
644 qr,
645 qpp,
646 kc,
647 pos,
648 heads,
649 kw,
650 lora,
651 nope,
652 len,
653 rows,
654 scale,
655 wkb,
656 fk,
657 kro,
658 wvb: expand.ins[1].clone(),
659 fv,
660 vd: c.b,
661 ct,
662 };
663 if self.latent_launches(&latent).is_some_and(|[kh, vh, attend]| attend.attends_in_tiles(numerics) && kh.heads_in_tiles(numerics) && vh.heads_in_tiles(numerics)) {
664 found.push((i, [qi, ai, ji, vi, ci], expand.dst.clone()));
665 }
666 }
667 for (i, gone, dst) in found {
668 let members: Vec<Op> = std::iter::once(i).chain(gone).map(|k| r.s.get(k).clone()).collect();
669 let made: Vec<&str> = members.iter().map(|m| m.out.as_str()).collect();
670 let mut ins: Vec<(String, Access)> = Vec::new();
671 let mut parts = members.clone();
672 parts.sort_by_key(|p| p.covers.first().copied().unwrap_or(0));
673 for p in &parts {
674 for (t, a) in p.ins.iter().zip(&p.access) {
675 if !made.contains(&t.as_str()) && !ins.iter().any(|(n, _)| n == t) {
676 ins.push((t.clone(), *a));
677 }
678 }
679 }
680 r.fuse("latent_expanded", ClassId::Mla, i, &gone, ins, dst.buf.clone(), dst);
681 }
682 }
683
684 pub fn latent_launches(&self, l: &Latent) -> Option<[Launch; 3]> {
688 let numerics = self.knobs.activations;
689 let acc = kernels::accumulator(kernels::mode(numerics)).0;
690 let sms = self.knobs.chip.sms;
691 let qk = l.nope + (l.kw - l.lora);
692 let upto = l.pos.clone().map(|p| (p, l.rows - 1));
693 let half = |w: &str, format, b, rows_out, y: &str| Launch::HeadGemv {
694 x: l.kc.clone(),
695 part: Some(LanePart { pitch: l.kw, offset: 0 }),
696 w: w.to_string(),
697 format,
698 y: LaneDst::to(y),
699 m: l.len,
700 heads: l.heads,
701 a: l.lora,
702 b,
703 rows_out,
704 shared: true,
705 upto: upto.clone(),
706 batch: false,
707 };
708 let (keys, values) = (format!("{}.keys", l.ct), format!("{}.values", l.ct));
709 let kh = half(&l.wkb, l.fk, l.nope, !l.kro, &keys);
710 let vh = half(&l.wvb, l.fv, l.vd, true, &values);
711 let span = attended_span(l.len, None);
712 let per_block = kernels::attn_heads_per_block(1, qk, l.vd, span, sms, self.knobs.chip.smem_kib_per_block * 1024, acc)?;
713 let splits = kernels::attn_splits(l.heads / per_block, span, sms);
714 let attend = Launch::Attention {
715 q: l.qr.clone(),
716 kc: keys,
717 vc: values,
718 pos: l.pos.clone(),
719 sinks: None,
720 out: l.ct.clone(),
721 heads: l.heads,
722 group: 1,
723 hd: qk,
724 vd: l.vd,
725 len: l.len,
726 rows: l.rows,
727 window: None,
728 vstride: l.heads * l.vd,
729 voff: 0,
730 scale: l.scale,
731 per_block,
732 splits,
733 parallel: chunks_apart(splits, l.rows, l.heads / per_block, sms),
734 split: Some(Split {
735 own: l.nope,
736 q_pitch: qk,
737 q_tail: (l.qpp.clone(), LanePart { pitch: l.kw - l.lora, offset: 0 }),
738 k_tail: (l.kc.clone(), LanePart { pitch: l.kw, offset: l.lora }),
739 }),
740 shared: None,
741 seq: None,
742 f32: true,
743 quant: false,
744 gate: None,
745 goff: 0,
746 };
747 Some([kh, vh, attend])
748 }
749}
750
751pub struct Latent {
756 pub qr: String,
757 pub qpp: String,
758 pub kc: String,
759 pub pos: Option<String>,
760 pub heads: usize,
761 pub kw: usize,
762 pub lora: usize,
763 pub nope: usize,
764 pub len: usize,
765 pub rows: usize,
766 pub scale: f64,
767 pub wkb: String,
768 pub fk: crate::model::quant::GgmlType,
769 pub kro: bool,
770 pub wvb: String,
771 pub fv: crate::model::quant::GgmlType,
772 pub vd: usize,
773 pub ct: String,
774}
775
776pub fn chunks_apart(splits: usize, rows: usize, head_blocks: usize, sms: usize) -> bool {
779 splits > 1 && rows * head_blocks < 2 * sms
780}