1use crate::cast::Canon;
5use crate::indexing::stack;
6use crate::kernels::Operand;
7use crate::ops::imp::zeros;
8use crate::ops::imp::{
9 self as ops, add, arange, astype, broadcast_to, cos, divide, equal, exp, expand_dims, filled,
10 less, log, max, mean, multiply, reshape, sin, slice, sqrt, squeeze, stop_gradient, subtract,
11 sum, where_,
12};
13use crate::primitive::Primitive;
14
15use super::plan::{self, GroupKind, MaskKind, Route, RouteKey, Target};
16use crate::{
17 Array, Category, DType, Device, DeviceKind, Error, Result, SliceArg, Stream, default_stream,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22#[non_exhaustive]
23pub enum FastOp {
24 RmsNorm {
26 eps: u32,
28 },
29 AddRmsNorm {
31 eps: u32,
33 },
34 RmsNormVjp {
36 eps: u32,
38 },
39 LayerNorm {
42 eps: u32,
44 },
45 LayerNormVjp {
47 eps: u32,
49 },
50 Rope {
52 dims: usize,
54 traditional: bool,
56 base: u32,
58 scale: u32,
60 forward: bool,
62 },
63 NormRope {
67 eps: Option<u32>,
69 dims: usize,
71 traditional: bool,
73 base: u32,
75 scale: u32,
77 offset: usize,
79 cache: bool,
81 },
82 DepthwiseConv1d {
84 stride: usize,
86 padding: usize,
88 dilation: usize,
90 },
91 PagedAttention {
93 scale: u32,
95 },
96 PagedWrite,
98 Softmax,
100 LogSumExp,
102 CrossEntropy,
104 CrossEntropyVjp,
106 Attention {
109 scale: u32,
111 causal: bool,
113 mask: bool,
115 sinks: bool,
117 passes: u8,
120 blocks: u32,
122 gqa_kernel: bool,
124 tk1: bool,
126 },
127 GatedDelta {
130 steps: bool,
132 },
133 GatedDeltaSlots,
136 GatherMm,
139 GatherQmm {
144 group_size: usize,
146 bits: u8,
148 transpose: bool,
150 },
151}
152
153#[derive(Debug, Clone, Copy, Default)]
155#[non_exhaustive]
156pub enum AttentionMask<'a> {
157 #[default]
159 None,
160 Causal,
162 Array(&'a Array),
165}
166
167fn float(bits: u32) -> f32 {
168 f32::from_bits(bits)
169}
170
171fn scalar(value: f64, dtype: DType) -> Result<Array> {
173 filled(&[], dtype, |_| Canon::Float(value))
174}
175
176pub(crate) fn on_gpu(dtype: DType, size: usize) -> bool {
178 default_stream().device.kind == DeviceKind::Gpu
179 && matches!(dtype, DType::F16 | DType::BF16 | DType::F32)
180 && size > 0
181 && size < u32::MAX as usize
182}
183
184pub(crate) fn record_or_fallback(op: FastOp, inputs: Vec<Array>) -> Result<Vec<Array>> {
187 if on_gpu(inputs[0].dtype(), inputs[0].size()) {
188 Array::from_op(Primitive::Fast(op), inputs)
189 } else {
190 op.fallback(&inputs)
191 }
192}
193
194fn one(op: FastOp, inputs: Vec<Array>) -> Result<Array> {
195 Ok(record_or_fallback(op, inputs)?.swap_remove(0))
196}
197
198fn floating(op: &'static str, dtype: DType) -> Result<()> {
199 match dtype.category() {
200 Category::Float => Ok(()),
201 _ => Err(Error::unsupported_dtype(op, dtype)),
202 }
203}
204
205fn check_param(op: &'static str, what: &str, p: &Array, x: &Array) -> Result<()> {
207 if p.ndim() != 1 || p.size() != x.dims()[x.ndim() - 1] {
208 return Err(Error::InvalidArgument(format!(
209 "{op}: {what} must be 1-D with {} elements, got shape {:?}",
210 x.dims()[x.ndim() - 1],
211 p.dims()
212 )));
213 }
214 Ok(())
215}
216
217pub(crate) fn rms_norm(x: &Array, weight: Option<&Array>, eps: f32) -> Result<Array> {
219 if x.ndim() == 0 {
220 return Err(Error::InvalidArgument(
221 "rms_norm: the input needs at least one dimension".into(),
222 ));
223 }
224 let dtype = match weight {
225 Some(w) => {
226 check_param("rms_norm", "weight", w, x)?;
227 crate::promote_types(x.dtype(), w.dtype())
228 }
229 None => x.dtype(),
230 };
231 floating("rms_norm", dtype)?;
232 let w = match weight {
233 Some(w) => astype(w, dtype)?,
234 None => scalar(1.0, dtype)?,
235 };
236 let op = FastOp::RmsNorm { eps: eps.to_bits() };
237 one(op, vec![astype(x, dtype)?, w])
238}
239
240pub(crate) fn add_rms_norm(
242 x: &Array,
243 residual: &Array,
244 weight: Option<&Array>,
245 eps: f32,
246) -> Result<(Array, Array)> {
247 let dtype = crate::promote_types(x.dtype(), residual.dtype());
248 let fused = x.dims() == residual.dims()
249 && x.ndim() > 0
250 && dtype.category() == Category::Float
251 && weight.is_none_or(|w| {
252 w.slot_error().is_none()
253 && w.ndim() == 1
254 && w.size() == x.dims()[x.ndim() - 1]
255 && crate::promote_types(dtype, w.dtype()) == dtype
256 });
257 if !fused {
258 let h = add(x, residual)?;
259 let y = rms_norm(&h, weight, eps)?;
260 return Ok((h, y));
261 }
262 let w = match weight {
263 Some(w) => astype(w, dtype)?,
264 None => scalar(1.0, dtype)?,
265 };
266 let op = FastOp::AddRmsNorm { eps: eps.to_bits() };
267 let mut out = record_or_fallback(op, vec![astype(x, dtype)?, astype(residual, dtype)?, w])?;
268 let y = out.pop().expect("two outputs");
269 Ok((out.pop().expect("two outputs"), y))
270}
271
272pub(crate) fn layer_norm(
274 x: &Array,
275 weight: Option<&Array>,
276 bias: Option<&Array>,
277 eps: f32,
278) -> Result<Array> {
279 if x.ndim() == 0 {
280 return Err(Error::InvalidArgument(
281 "layer_norm: the input needs at least one dimension".into(),
282 ));
283 }
284 let mut dtype = x.dtype();
285 for (p, what) in [(weight, "weight"), (bias, "bias")] {
286 if let Some(p) = p {
287 check_param("layer_norm", what, p, x)?;
288 dtype = crate::promote_types(dtype, p.dtype());
289 }
290 }
291 floating("layer_norm", dtype)?;
292 let w = match weight {
293 Some(w) => astype(w, dtype)?,
294 None => scalar(1.0, dtype)?,
295 };
296 let b = match bias {
297 Some(b) => astype(b, dtype)?,
298 None => scalar(0.0, dtype)?,
299 };
300 let op = FastOp::LayerNorm { eps: eps.to_bits() };
301 one(op, vec![astype(x, dtype)?, w, b])
302}
303
304pub(crate) fn rope(
306 x: &Array,
307 dims: usize,
308 traditional: bool,
309 base: Option<f32>,
310 scale: f32,
311 offset: &Array,
312 freqs: Option<&Array>,
313) -> Result<Array> {
314 let invalid = |msg: String| Err(Error::InvalidArgument(format!("rope: {msg}")));
315 if x.ndim() < 3 {
316 return invalid(format!(
317 "the input needs at least 3 dimensions, got {:?}",
318 x.dims()
319 ));
320 }
321 floating("rope", x.dtype())?;
322 if offset.ndim() > 1 || (offset.size() != 1 && offset.size() != x.dims()[0]) {
323 return invalid(format!(
324 "offset must be a scalar or have {} elements, got shape {:?}",
325 x.dims()[0],
326 offset.dims()
327 ));
328 }
329 if !matches!(
330 offset.dtype().category(),
331 Category::Signed | Category::Unsigned
332 ) {
333 return invalid(format!("offset must be an integer, got {}", offset.dtype()));
334 }
335 if dims == 0 || !dims.is_multiple_of(2) || dims > x.dims()[x.ndim() - 1] {
336 return invalid(format!(
337 "dims must be even, positive and at most {}, got {dims}",
338 x.dims()[x.ndim() - 1]
339 ));
340 }
341 let offset = match offset.dtype().size() {
342 4 => offset.clone(),
343 _ => astype(offset, DType::I32)?,
344 };
345 let mut inputs = vec![x.clone(), offset];
346 let base = match (base, freqs) {
347 (Some(b), None) => b,
348 (None, Some(f)) => {
349 if f.ndim() != 1 || f.size() != dims / 2 {
350 return invalid(format!(
351 "freqs must be 1-D with {} elements, got shape {:?}",
352 dims / 2,
353 f.dims()
354 ));
355 }
356 inputs.push(astype(f, DType::F32)?);
357 1.0
358 }
359 _ => return invalid("exactly one of base and freqs must be given".into()),
360 };
361 let op = FastOp::Rope {
362 dims,
363 traditional,
364 base: base.to_bits(),
365 scale: scale.to_bits(),
366 forward: true,
367 };
368 one(op, inputs)
369}
370
371pub(crate) fn cross_entropy(logits: &Array, targets: &Array) -> Result<Array> {
373 if logits.ndim() == 0 || targets.dims() != &logits.dims()[..logits.ndim() - 1] {
374 return Err(Error::InvalidArgument(format!(
375 "cross_entropy: targets of shape {:?} do not match logits of shape {:?}",
376 targets.dims(),
377 logits.dims()
378 )));
379 }
380 floating("cross_entropy", logits.dtype())?;
381 if !matches!(
382 targets.dtype().category(),
383 Category::Signed | Category::Unsigned
384 ) {
385 return Err(Error::InvalidArgument(format!(
386 "cross_entropy: targets must be integer class indices, got {}",
387 targets.dtype()
388 )));
389 }
390 one(
391 FastOp::CrossEntropy,
392 vec![logits.clone(), astype(targets, DType::I32)?],
393 )
394}
395
396fn update_last(base: &Array, update: &Array, start: usize, step: isize) -> Result<Array> {
399 let n = base.ndim();
400 let mut starts = vec![0; n];
401 starts[n - 1] = start;
402 let mut steps = vec![1; n];
403 steps[n - 1] = step;
404 let primitive = Primitive::SliceUpdate {
405 start: starts,
406 step: steps,
407 len: update.dims().to_vec(),
408 };
409 Ok(Array::from_op(primitive, vec![base.clone(), update.clone()])?.swap_remove(0))
410}
411
412fn last(x: &Array, start: usize, step: isize, len: usize) -> Result<Array> {
414 let mut args = vec![SliceArg::full(); x.ndim()];
415 let stop = start as isize + step * len as isize;
416 args[x.ndim() - 1] = SliceArg::new(Some(start as isize), Some(stop), step);
417 slice(x, &args)
418}
419
420fn rsqrt(x: &Array) -> Result<Array> {
422 divide(1.0, sqrt(x)?)
423}
424
425fn softmax_graph(x: &Array) -> Result<Array> {
427 let xf = if x.dtype() == DType::F32 {
428 x.clone()
429 } else {
430 astype(x, DType::F32)?
431 };
432 let m = stop_gradient(&max(&xf, &[-1], true)?)?;
433 let e = exp(&subtract(&xf, &m)?)?;
434 astype(÷(&e, sum(&e, &[-1], true)?)?, x.dtype())
435}
436
437fn logsumexp_graph(x: &Array) -> Result<Array> {
439 let m = stop_gradient(&max(x, &[-1], true)?)?;
440 let out = add(log(&sum(&exp(&subtract(x, &m)?)?, &[-1], true)?)?, &m)?;
441 where_(equal(&ops::abs(&m)?, f64::INFINITY)?, &m, &out)
442}
443
444fn one_hot(y: &Array, classes: usize, dtype: DType) -> Result<Array> {
446 let positions = arange(0.0, classes as f64, 1.0, y.dtype())?;
447 equal(&expand_dims(y, -1)?, &positions)?.try_astype(dtype)
448}
449
450fn wrap(y: &Array, classes: usize) -> Result<Array> {
452 where_(less(y, 0)?, add(y, classes as f64)?, y)
453}
454
455impl FastOp {
456 pub(crate) fn name(&self) -> &'static str {
457 match self {
458 Self::RmsNorm { .. } => "rms_norm",
459 Self::AddRmsNorm { .. } => "add_rms_norm",
460 Self::RmsNormVjp { .. } => "rms_norm_vjp",
461 Self::LayerNorm { .. } => "layer_norm",
462 Self::LayerNormVjp { .. } => "layer_norm_vjp",
463 Self::Rope { .. } => "rope",
464 Self::NormRope { .. } => "norm_rope",
465 Self::DepthwiseConv1d { .. } => "conv1d",
466 Self::PagedAttention { .. } => "paged_attention",
467 Self::PagedWrite => "paged_write",
468 Self::Softmax => "softmax",
469 Self::LogSumExp => "logsumexp",
470 Self::CrossEntropy => "cross_entropy",
471 Self::CrossEntropyVjp => "cross_entropy_vjp",
472 Self::Attention { .. } => "scaled_dot_product_attention",
473 Self::GatedDelta { .. } | Self::GatedDeltaSlots => "gated_delta",
474 Self::GatherQmm { .. } => "gather_qmm",
475 Self::GatherMm => "gather_mm",
476 }
477 }
478
479 pub(crate) fn outputs(&self, inputs: &[Array]) -> Result<Vec<(DType, Vec<usize>)>> {
481 let arity = match self {
482 Self::Softmax | Self::LogSumExp => 1,
483 Self::RmsNorm { .. } | Self::CrossEntropy | Self::DepthwiseConv1d { .. } => 2,
484 Self::PagedAttention { .. } => 5,
485 Self::PagedWrite => 3,
486 Self::AddRmsNorm { .. } | Self::RmsNormVjp { .. } | Self::LayerNorm { .. } => 3,
487 Self::LayerNormVjp { .. } | Self::CrossEntropyVjp => 4,
488 Self::Rope { .. } => inputs.len().clamp(2, 3),
489 Self::NormRope { cache, .. } => inputs
490 .len()
491 .clamp(2 + usize::from(*cache), 3 + usize::from(*cache)),
492 Self::Attention { mask, sinks, .. } => 3 + usize::from(*mask) + usize::from(*sinks),
493 Self::GatedDelta { .. } => 6,
494 Self::GatedDeltaSlots => 7,
495 Self::GatherQmm { .. } => inputs.len().clamp(6, 7),
496 Self::GatherMm => inputs.len().clamp(4, 5),
497 };
498 if inputs.len() != arity || inputs[0].ndim() == 0 {
499 return Err(Error::InvalidArgument(format!(
500 "{}: {arity} inputs expected, got {}",
501 self.name(),
502 inputs.len()
503 )));
504 }
505 self.check(inputs)?;
506 let x = &inputs[0];
507 let like = |a: &Array| (a.dtype(), a.dims().to_vec());
508 Ok(match self {
509 Self::RmsNorm { .. }
510 | Self::LayerNorm { .. }
511 | Self::Rope { .. }
512 | Self::NormRope { .. }
513 | Self::Softmax => {
514 vec![like(x)]
515 }
516 Self::LogSumExp => {
517 let mut shape = x.dims().to_vec();
518 *shape.last_mut().expect("1-D") = 1;
519 vec![(x.dtype(), shape)]
520 }
521 Self::AddRmsNorm { .. } => vec![like(x), like(x)],
522 Self::PagedAttention { .. } | Self::PagedWrite => vec![like(x)],
523 Self::DepthwiseConv1d {
524 stride,
525 padding,
526 dilation,
527 } => {
528 let (l, k) = (x.dims()[1], inputs[1].dims()[1]);
529 let l_out = (l + 2 * padding - dilation * (k - 1) - 1) / stride + 1;
530 vec![(x.dtype(), vec![x.dims()[0], l_out, x.dims()[2]])]
531 }
532 Self::RmsNormVjp { .. } => vec![like(x), like(&inputs[1])],
533 Self::LayerNormVjp { .. } => vec![like(x), like(&inputs[1]), like(&inputs[2])],
534 Self::CrossEntropy => vec![(DType::F32, inputs[1].dims().to_vec())],
535 Self::CrossEntropyVjp => vec![like(x)],
536 Self::Attention { .. } => {
537 let mut shape = x.dims().to_vec();
538 shape[3] = inputs[2].dims()[3];
539 vec![(x.dtype(), shape)]
540 }
541 Self::GatedDeltaSlots => {
542 vec![(x.dtype(), inputs[2].dims().to_vec()), like(&inputs[5])]
543 }
544 Self::GatedDelta { steps } => {
545 let state = match steps {
546 false => inputs[5].dims().to_vec(),
547 true => {
548 let mut shape = inputs[5].dims().to_vec();
549 shape.insert(1, x.dims()[1]);
550 shape
551 }
552 };
553 vec![
554 (x.dtype(), inputs[2].dims().to_vec()),
555 (inputs[5].dtype(), state),
556 ]
557 }
558 Self::GatherMm => {
559 let b = inputs[1].dims();
560 let mut shape = inputs[2].dims().to_vec();
561 shape.extend([x.dims()[x.ndim() - 2], b[b.len() - 1]]);
562 vec![(x.dtype(), shape)]
563 }
564 Self::GatherQmm {
565 bits, transpose, ..
566 } => {
567 let w = inputs[1].dims();
568 let n = match transpose {
569 true => w[w.len() - 2],
570 false => w[w.len() - 1] * 32 / usize::from(*bits),
571 };
572 let mut shape = inputs[4].dims().to_vec();
573 shape.extend([x.dims()[x.ndim() - 2], n]);
574 vec![(x.dtype(), shape)]
575 }
576 })
577 }
578
579 fn check(&self, inputs: &[Array]) -> Result<()> {
584 let name = self.name();
585 let invalid = |msg: String| Err(Error::InvalidArgument(format!("{name}: {msg}")));
586 let x = &inputs[0];
587 let kernel_float = |a: &Array| matches!(a.dtype(), DType::F16 | DType::BF16 | DType::F32);
588 let integer = |a: &Array| matches!(a.dtype(), DType::I32 | DType::U32);
589 let last = |a: &Array| a.dims().last().copied().unwrap_or(0);
590 let param = |p: &Array| {
592 p.dtype() == x.dtype() && (p.ndim() == 0 || (p.ndim() == 1 && p.size() == last(x)))
593 };
594 let like = |a: &Array| a.dtype() == x.dtype() && a.dims() == x.dims();
595 if !kernel_float(x) {
596 return Err(Error::unsupported_dtype(name, x.dtype()));
597 }
598 let rotation = |dims: usize, x: &Array, freqs: Option<&Array>| {
599 let d = last(x);
600 dims > 0
601 && dims.is_multiple_of(2)
602 && dims <= d
603 && freqs.is_none_or(|f| {
604 f.dtype() == DType::F32 && f.ndim() == 1 && f.size() == dims / 2
605 })
606 };
607 let ok = match self {
608 Self::RmsNorm { .. } | Self::LayerNorm { .. } => inputs[1..].iter().all(param),
609 Self::AddRmsNorm { .. } => like(&inputs[1]) && param(&inputs[2]),
610 Self::RmsNormVjp { .. } => param(&inputs[1]) && like(&inputs[2]),
611 Self::LayerNormVjp { .. } => param(&inputs[1]) && param(&inputs[2]) && like(&inputs[3]),
612 Self::Softmax | Self::LogSumExp => true,
613 Self::Rope { dims, .. } => {
614 let offset = &inputs[1];
615 x.ndim() >= 3
616 && integer(offset)
617 && offset.ndim() <= 1
618 && (offset.size() == 1 || offset.size() == x.dims()[0])
619 && rotation(*dims, x, inputs.get(2))
620 }
621 Self::NormRope {
622 eps,
623 dims,
624 offset,
625 cache,
626 ..
627 } => {
628 let at = usize::from(*cache);
629 let (y, w) = (&inputs[at], &inputs[at + 1]);
630 let s = y.dims();
631 let Some(end) = offset.checked_add(s.get(2).copied().unwrap_or(0)) else {
632 return invalid(format!("position {offset} overflows"));
633 };
634 let fits = y.ndim() == 4
635 && y.dtype() == x.dtype()
636 && s[3] <= NORM_ROPE_MAX
637 && end <= i32::MAX as usize;
638 let cache_fits = !cache
639 || (fits
640 && x.ndim() == 4
641 && x.dims()[..2] == s[..2]
642 && x.dims()[3] == s[3]
643 && x.dims()[2] >= end);
644 let weight = || {
645 w.dtype() == x.dtype()
646 && match eps {
647 Some(_) => w.ndim() == 1 && w.size() == s[3],
648 None => w.ndim() == 0,
649 }
650 };
651 fits && cache_fits && weight() && rotation(*dims, y, inputs.get(at + 2))
652 }
653 Self::DepthwiseConv1d {
654 stride,
655 padding,
656 dilation,
657 } => {
658 let w = &inputs[1];
659 let span = w
660 .dims()
661 .get(1)
662 .and_then(|&k| k.checked_sub(1))
663 .and_then(|k| dilation.checked_mul(k)?.checked_add(1));
664 let padded = padding
665 .checked_mul(2)
666 .and_then(|p| p.checked_add(x.dims().get(1).copied()?));
667 x.ndim() == 3
668 && w.ndim() == 3
669 && w.dtype() == x.dtype()
670 && w.dims()[0] == x.dims()[2]
671 && w.dims()[2] == 1
672 && *stride > 0
673 && *dilation > 0
674 && matches!((span, padded), (Some(s), Some(p)) if p >= s)
675 }
676 Self::PagedAttention { .. } => {
677 let [q, k, v, tables, lengths] = inputs else {
678 unreachable!("five inputs")
679 };
680 let (b, hq, d) = (q.dims().first(), q.dims().get(1), q.dims().get(3));
681 q.ndim() == 4
682 && k.ndim() == 4
683 && k.dims() == v.dims()
684 && k.dtype() == q.dtype()
685 && v.dtype() == q.dtype()
686 && Some(&k.dims()[3]) == d
687 && k.dims()[1] > 0
688 && hq.is_some_and(|h| h % k.dims()[1] == 0)
689 && matches!(d, Some(64 | 128 | 256))
690 && k.dims()[2].is_power_of_two()
691 && k.size() < u32::MAX as usize
692 && tables.ndim() == 2
693 && tables.dims().first() == b
694 && tables.dtype() == DType::I32
695 && lengths.ndim() == 1
696 && lengths.dims().first() == b
697 && lengths.dtype() == DType::I32
698 }
699 Self::PagedWrite => {
700 let [cache, new, slots] = inputs else {
701 unreachable!("three inputs")
702 };
703 cache.ndim() == 4
704 && new.ndim() == 3
705 && new.dtype() == cache.dtype()
706 && new.dims()[1] == cache.dims()[1]
707 && new.dims()[2] == cache.dims()[3]
708 && slots.dims() == [new.dims()[0]]
709 && slots.dtype() == DType::I32
710 }
711 Self::CrossEntropy | Self::CrossEntropyVjp => {
712 let rows = &x.dims()[..x.ndim() - 1];
713 let targets = &inputs[1];
714 let rest = inputs[2..]
715 .iter()
716 .all(|a| a.dtype() == DType::F32 && a.dims() == rows);
717 targets.dtype() == DType::I32 && targets.dims() == rows && rest
718 }
719 Self::Attention { mask, sinks, .. } => {
720 let (q, k, v) = (&inputs[0], &inputs[1], &inputs[2]);
721 if [q, k, v]
722 .iter()
723 .any(|a| a.ndim() != 4 || a.dtype() != x.dtype())
724 {
725 return invalid("q, k and v must be 4-D arrays of one dtype".into());
726 }
727 let (qs, ks, vs) = (q.dims(), k.dims(), v.dims());
728 let shapes = ks[0] == qs[0]
729 && vs[0] == qs[0]
730 && qs[3] == ks[3]
731 && ks[1] == vs[1]
732 && ks[2] == vs[2]
733 && ks[1] > 0
734 && qs[1] % ks[1] == 0;
735 let mask_ok = !mask
736 || (inputs[3].dims() == [qs[0], qs[1], qs[2], ks[2]]
737 && matches!(inputs[3].dtype(), d if d == DType::Bool || d == x.dtype()));
738 let sinks_ok = !sinks || {
739 let s = inputs.last().expect("sinks");
740 s.ndim() == 1 && s.size() == qs[1] && s.dtype() == x.dtype()
741 };
742 shapes && mask_ok && sinks_ok && attention_config(self, inputs)
743 }
744 Self::GatedDelta { .. } | Self::GatedDeltaSlots => {
745 let (q, k, v, g, beta) =
746 (&inputs[0], &inputs[1], &inputs[2], &inputs[3], &inputs[4]);
747 if q.ndim() != 4 || v.ndim() != 4 || q.dims() != k.dims() {
748 return invalid("q and k must be [B, T, Hk, Dk] and v [B, T, Hv, Dv]".into());
749 }
750 let (b, t, hk, dk) = (q.dims()[0], q.dims()[1], q.dims()[2], q.dims()[3]);
751 let (hv, dv) = (v.dims()[2], v.dims()[3]);
752 let state = match self {
753 Self::GatedDeltaSlots => {
754 let (pool, slots) = (&inputs[5], &inputs[6]);
755 pool.ndim() == 4
756 && pool.dims()[1..] == [hv, dv, dk]
757 && slots.dims() == [b]
758 && slots.dtype() == DType::I32
759 && kernel_float(pool)
760 }
761 _ => inputs[5].dims() == [b, hv, dv, dk] && kernel_float(&inputs[5]),
762 };
763 k.dtype() == q.dtype()
764 && v.dtype() == q.dtype()
765 && v.dims()[..2] == [b, t]
766 && hk > 0
767 && hv % hk == 0
768 && dk.is_multiple_of(32)
769 && g.dims() == [b, t, hv]
770 && beta.dims() == [b, t, hv]
771 && kernel_float(g)
772 && kernel_float(beta)
773 && state
774 }
775 Self::GatherMm | Self::GatherQmm { .. } => {
776 let (w, lhs, rhs) = match self {
777 Self::GatherMm => (&inputs[1], &inputs[2], &inputs[3]),
778 _ => (&inputs[1], &inputs[4], &inputs[5]),
779 };
780 if x.ndim() < 2 || w.ndim() < 2 {
781 return invalid("operands must be [..., M, K] and [..., K, N]".into());
782 }
783 let (m, k) = (x.dims()[x.ndim() - 2], last(x));
784 let inner = match self {
785 Self::GatherQmm {
786 group_size,
787 bits,
788 transpose,
789 } => {
790 crate::quantized::check_params(name, *group_size, u32::from(*bits))?;
791 let (scales, biases) = (&inputs[2], &inputs[3]);
792 let n = w.ndim();
793 let packing = scales.dtype() == x.dtype()
794 && biases.dtype() == x.dtype()
795 && biases.dims() == scales.dims()
796 && w.dtype() == DType::U32
797 && scales.ndim() == n
798 && w.dims()[..n - 1] == scales.dims()[..n - 1]
799 && last(w).checked_mul(32)
800 == last(scales)
801 .checked_mul(*group_size)
802 .and_then(|c| c.checked_mul(usize::from(*bits)));
803 let columns = last(scales) * group_size;
805 (packing && *transpose).then_some(columns)
806 }
807 _ => (w.dtype() == x.dtype()).then_some(w.dims()[w.ndim() - 2]),
808 };
809 let offsets = match inputs.get(if *self == Self::GatherMm { 4 } else { 6 }) {
810 None => true,
811 Some(o) => {
812 let experts: usize = w.dims()[..w.ndim() - 2].iter().product();
813 x.ndim() == 3
814 && m == 1
815 && rhs.ndim() == 1
816 && o.dtype() == DType::I32
817 && o.dims() == [experts]
818 }
819 };
820 inner == Some(k)
821 && lhs.dtype() == DType::U32
822 && rhs.dtype() == DType::U32
823 && lhs.dims() == rhs.dims()
824 && offsets
825 }
826 };
827 if ok {
828 Ok(())
829 } else {
830 invalid(format!(
831 "invalid inputs {:?}",
832 inputs
833 .iter()
834 .map(|a| (a.dtype(), a.dims()))
835 .collect::<Vec<_>>()
836 ))
837 }
838 }
839
840 pub(crate) fn fallback(&self, inputs: &[Array]) -> Result<Vec<Array>> {
842 let x = &inputs[0];
843 let f32x = || astype(x, DType::F32);
844 let out = match self {
845 Self::RmsNorm { eps } => {
846 let xf = f32x()?;
847 let n = rsqrt(&add(mean(&multiply(&xf, &xf)?, &[-1], true)?, float(*eps))?)?;
848 let y = multiply(&xf, &n)?.try_astype(x.dtype())?;
849 let w = &inputs[1];
850 vec![if w.ndim() == 0 { y } else { multiply(&y, w)? }]
851 }
852 Self::AddRmsNorm { eps } => {
853 let h = add(x, &inputs[1])?;
854 let op = Self::RmsNorm { eps: *eps };
855 let y = record_or_fallback(op, vec![h.clone(), inputs[2].clone()])?;
856 vec![h, y.into_iter().next().expect("one output")]
857 }
858 Self::LayerNorm { eps } => {
859 let xf = f32x()?;
860 let xc = subtract(&xf, mean(&xf, &[-1], true)?)?;
861 let v = mean(&multiply(&xc, &xc)?, &[-1], true)?;
862 let mut y = multiply(&xc, rsqrt(&add(&v, float(*eps))?)?)?.try_astype(x.dtype())?;
863 if inputs[1].ndim() != 0 {
864 y = multiply(&y, &inputs[1])?;
865 }
866 if inputs[2].ndim() != 0 {
867 y = add(&y, &inputs[2])?;
868 }
869 vec![y]
870 }
871 Self::RmsNormVjp { eps } => {
872 let (w, g) = (&inputs[1], &inputs[2]);
873 let n = rsqrt(&add(mean(&multiply(x, x)?, &[-1], true)?, float(*eps))?)?;
874 let n3 = multiply(&multiply(&n, &n)?, &n)?;
875 let gw = multiply(g, w)?;
876 let t = mean(&multiply(&gw, x)?, &[-1], true)?;
877 let dx = subtract(multiply(&gw, &n)?, multiply(multiply(x, &t)?, &n3)?)?;
878 let dw = match w.ndim() {
879 0 => scalar(0.0, w.dtype())?,
880 _ => sum_rows(&multiply(g, multiply(x, &n)?)?)?,
881 };
882 vec![dx, dw]
883 }
884 Self::LayerNormVjp { eps } => {
885 let (w, b, g) = (&inputs[1], &inputs[2], &inputs[3]);
886 let mu = mean(x, &[-1], true)?;
887 let xc = subtract(x, &mu)?;
888 let var = mean(&multiply(&xc, &xc)?, &[-1], true)?;
889 let n = rsqrt(&add(&var, float(*eps))?)?;
890 let n3 = multiply(&multiply(&n, &n)?, &n)?;
891 let wg = multiply(w, g)?;
892 let mwg = mean(&wg, &[-1], true)?;
893 let mwgxc = mean(&multiply(&wg, &xc)?, &[-1], true)?;
894 let dx = subtract(
895 multiply(subtract(&wg, &mwg)?, &n)?,
896 multiply(multiply(&xc, &mwgxc)?, &n3)?,
897 )?;
898 let dw = match w.ndim() {
899 0 => scalar(0.0, w.dtype())?,
900 _ => sum_rows(&multiply(g, multiply(&xc, &n)?)?)?,
901 };
902 let db = match b.ndim() {
903 0 => scalar(0.0, b.dtype())?,
904 _ => sum_rows(g)?,
905 };
906 vec![dx, dw, db]
907 }
908 Self::Attention {
909 scale,
910 causal,
911 mask,
912 sinks,
913 ..
914 } => vec![attention_graph(
915 inputs,
916 float(*scale),
917 *causal,
918 *mask,
919 *sinks,
920 None,
921 None,
922 )?],
923 Self::Rope {
924 dims,
925 traditional,
926 base,
927 scale,
928 forward,
929 } => vec![rope_graph(
930 inputs,
931 *dims,
932 *traditional,
933 float(*base),
934 float(*scale),
935 *forward,
936 )?],
937 Self::NormRope {
938 eps,
939 dims,
940 traditional,
941 base,
942 scale,
943 offset,
944 cache,
945 } => {
946 let at = usize::from(*cache);
947 let (x, w) = (&inputs[at], &inputs[at + 1]);
948 let mut h = x.clone();
949 if let Some(eps) = eps {
950 let op = Self::RmsNorm { eps: *eps };
951 h = record_or_fallback(op, vec![h, w.clone()])?.swap_remove(0);
952 }
953 let mut rope_inputs = vec![h, Array::from(*offset as i32)];
954 rope_inputs.extend(inputs.get(at + 2).cloned());
955 let op = Self::Rope {
956 dims: *dims,
957 traditional: *traditional,
958 base: *base,
959 scale: *scale,
960 forward: true,
961 };
962 let r = record_or_fallback(op, rope_inputs)?.swap_remove(0);
963 vec![match cache {
964 true => {
965 let mut region = vec![SliceArg::full(); 4];
966 let end = (offset + r.dims()[2]) as isize;
967 region[2] = SliceArg::new(Some(*offset as isize), Some(end), 1);
968 crate::ops::imp::slice_update(&inputs[0], &r, ®ion)?
969 }
970 false => r,
971 }]
972 }
973 Self::DepthwiseConv1d {
974 stride,
975 padding,
976 dilation,
977 } => {
978 let c = x.dims()[2];
979 vec![crate::conv::general(
980 x,
981 &inputs[1],
982 (*stride, *padding, *dilation, c),
983 )?]
984 }
985 Self::PagedAttention { scale } => vec![paged_attention_graph(inputs, float(*scale))?],
986 Self::PagedWrite => vec![paged_write_graph(inputs)?],
987 Self::Softmax => vec![softmax_graph(x)?],
988 Self::GatedDelta { steps } => gated_delta_graph(inputs, *steps)?,
989 Self::GatedDeltaSlots => {
990 let (pool, slots) = (&inputs[5], &inputs[6]);
992 let states = crate::indexing::take(pool, slots, Some(0))?;
993 let mut args = inputs[..5].to_vec();
994 args.push(states);
995 let mut out = gated_delta_graph(&args, false)?;
996 let new = out.pop().expect("two outputs");
997 let at = broadcast_to(&reshape(slots, &[-1, 1, 1, 1])?, new.dims())?;
998 let pool = crate::indexing::put_along_axis(pool, &at, &new, 0)?;
999 vec![out.pop().expect("two outputs"), pool]
1000 }
1001 Self::GatherMm => {
1002 let rows = |a: &Array, indices: &Array| -> Result<Array> {
1003 let nd = a.ndim();
1004 let shape = [-1, a.dims()[nd - 2] as isize, a.dims()[nd - 1] as isize];
1005 crate::indexing::take(&reshape(a, &shape)?, indices, Some(0))
1006 };
1007 vec![crate::ops::imp::matmul(
1008 &rows(x, &inputs[2])?,
1009 &rows(&inputs[1], &inputs[3])?,
1010 )?]
1011 }
1012 Self::GatherQmm {
1013 group_size,
1014 bits,
1015 transpose,
1016 } => vec![gather_qmm_graph(
1017 inputs,
1018 *group_size,
1019 u32::from(*bits),
1020 *transpose,
1021 )?],
1022 Self::LogSumExp => vec![logsumexp_graph(x)?],
1023 Self::CrossEntropy => {
1024 let classes = x.dims()[x.ndim() - 1];
1025 let y = wrap(&inputs[1], classes)?;
1026 let hit = one_hot(&y, classes, DType::Bool)?;
1027 let score = sum(&where_(&hit, x, 0.0)?, &[-1], false)?;
1028 let lse = squeeze(&logsumexp_graph(x)?, &[-1])?;
1029 vec![subtract(&lse, &score)?.try_astype(DType::F32)?]
1030 }
1031 Self::CrossEntropyVjp => {
1032 let classes = x.dims()[x.ndim() - 1];
1033 let (y, loss, g) = (wrap(&inputs[1], classes)?, &inputs[2], &inputs[3]);
1034 let hit = one_hot(&y, classes, DType::Bool)?;
1035 let score = sum(&where_(&hit, x, 0.0)?, &[-1], false)?;
1036 let lse = add(loss, score.try_astype(DType::F32)?)?;
1037 let p = exp(&subtract(&f32x()?, expand_dims(&lse, -1)?)?)?;
1038 let gx = multiply(
1039 expand_dims(g, -1)?,
1040 subtract(&p, one_hot(&y, classes, DType::F32)?)?,
1041 )?;
1042 vec![gx.try_astype(x.dtype())?]
1043 }
1044 };
1045 Ok(out)
1046 }
1047
1048 pub(crate) fn vjp(
1050 &self,
1051 inputs: &[Array],
1052 outputs: &[Array],
1053 cotangents: &[Array],
1054 wanted: &[bool],
1055 ) -> Result<Vec<Option<Array>>> {
1056 let g = &cotangents[0];
1057 let pick = |all: Vec<Array>| -> Vec<Option<Array>> {
1058 all.into_iter()
1059 .zip(wanted)
1060 .map(|(a, &w)| w.then_some(a))
1061 .collect()
1062 };
1063 match self {
1064 Self::RmsNorm { eps } => {
1065 let op = Self::RmsNormVjp { eps: *eps };
1066 let grads =
1067 record_or_fallback(op, vec![inputs[0].clone(), inputs[1].clone(), g.clone()])?;
1068 Ok(pick(grads))
1069 }
1070 Self::LayerNorm { eps } => {
1071 let op = Self::LayerNormVjp { eps: *eps };
1072 let mut all = inputs.to_vec();
1073 all.push(g.clone());
1074 Ok(pick(record_or_fallback(op, all)?))
1075 }
1076 Self::Rope { forward, .. } => {
1077 if wanted[1..].iter().any(|&w| w) {
1078 return Err(Error::InvalidArgument(
1079 "rope: no gradient with respect to the offset or frequencies".into(),
1080 ));
1081 }
1082 let Self::Rope {
1083 dims,
1084 traditional,
1085 base,
1086 scale,
1087 ..
1088 } = *self
1089 else {
1090 unreachable!()
1091 };
1092 let inverse = Self::Rope {
1093 dims,
1094 traditional,
1095 base,
1096 scale,
1097 forward: !forward,
1098 };
1099 let mut all = inputs.to_vec();
1100 all[0] = g.clone();
1101 let mut out = vec![Some(one(inverse, all)?)];
1102 out.extend(std::iter::repeat_n(None, inputs.len() - 1));
1103 Ok(out)
1104 }
1105 Self::Softmax => {
1106 let s = &outputs[0];
1107 let sg = multiply(s, g)?;
1108 let dx = subtract(&sg, multiply(s, sum(&sg, &[-1], true)?)?)?;
1109 Ok(vec![Some(dx)])
1110 }
1111 Self::LogSumExp => {
1112 let s = one(Self::Softmax, vec![inputs[0].clone()])?;
1113 Ok(vec![Some(multiply(g, &s)?)])
1114 }
1115 Self::CrossEntropy => {
1116 if wanted[1] {
1117 return Err(Error::InvalidArgument(
1118 "cross_entropy: no gradient with respect to the targets".into(),
1119 ));
1120 }
1121 let all = vec![
1122 inputs[0].clone(),
1123 inputs[1].clone(),
1124 outputs[0].clone(),
1125 g.clone(),
1126 ];
1127 Ok(vec![Some(one(Self::CrossEntropyVjp, all)?), None])
1128 }
1129 Self::AddRmsNorm { .. }
1130 | Self::NormRope { .. }
1131 | Self::PagedAttention { .. }
1132 | Self::PagedWrite
1133 | Self::DepthwiseConv1d { .. }
1134 | Self::RmsNormVjp { .. }
1135 | Self::LayerNormVjp { .. }
1136 | Self::CrossEntropyVjp
1137 | Self::Attention { .. }
1138 | Self::GatedDelta { .. }
1139 | Self::GatedDeltaSlots
1140 | Self::GatherQmm { .. }
1141 | Self::GatherMm => self.fallback_vjp(inputs, cotangents, wanted),
1142 }
1143 }
1144
1145 fn fallback_vjp(
1147 &self,
1148 inputs: &[Array],
1149 cotangents: &[Array],
1150 wanted: &[bool],
1151 ) -> Result<Vec<Option<Array>>> {
1152 let active: Vec<usize> = (0..inputs.len())
1153 .filter(|&i| wanted[i] && inputs[i].dtype().category() == Category::Float)
1154 .collect();
1155 let primals: Vec<Array> = active.iter().map(|&i| inputs[i].clone()).collect();
1156 let f = |ps: &[Array]| {
1157 let mut all = inputs.to_vec();
1158 for (p, &i) in ps.iter().zip(&active) {
1159 all[i] = p.clone();
1160 }
1161 self.fallback(&all)
1162 };
1163 let (_, grads) = crate::vjp_flat(f, &primals, cotangents)?;
1164 let mut out = vec![None; inputs.len()];
1165 for (g, &i) in grads.into_iter().zip(&active) {
1166 out[i] = Some(g);
1167 }
1168 Ok(out)
1169 }
1170
1171 pub(crate) fn jvp(&self, inputs: &[Array], tangents: &[Option<Array>]) -> Result<Vec<Array>> {
1173 let active: Vec<usize> = (0..inputs.len())
1174 .filter(|&i| tangents[i].is_some() && inputs[i].dtype().category() == Category::Float)
1175 .collect();
1176 let primals: Vec<Array> = active.iter().map(|&i| inputs[i].clone()).collect();
1177 let ts: Vec<Array> = active
1178 .iter()
1179 .map(|&i| tangents[i].clone().expect("active"))
1180 .collect();
1181 let f = |ps: &[Array]| {
1182 let mut all = inputs.to_vec();
1183 for (p, &i) in ps.iter().zip(&active) {
1184 all[i] = p.clone();
1185 }
1186 self.fallback(&all)
1187 };
1188 Ok(crate::jvp_flat(f, &primals, &ts)?.1)
1189 }
1190
1191 pub(crate) fn vmap(
1195 &self,
1196 inputs: &[Array],
1197 axes: &[Option<usize>],
1198 ) -> Result<(Vec<Array>, usize)> {
1199 let rest_unbatched = axes[1..].iter().all(Option::is_none);
1200 let front = |i: usize| -> Result<Array> {
1201 let a = &inputs[i];
1202 match axes[i] {
1203 Some(ax) => crate::batching::move_axis(a, ax, 0),
1204 None => {
1205 let b = inputs
1206 .iter()
1207 .zip(axes)
1208 .find_map(|(a, ax)| ax.map(|ax| a.dims()[ax]))
1209 .expect("one batched input");
1210 let mut shape = vec![b];
1211 shape.extend_from_slice(a.dims());
1212 broadcast_to(&expand_dims(a, 0)?, &shape)
1213 }
1214 }
1215 };
1216 let simple = match self {
1217 Self::RmsNorm { .. } | Self::LayerNorm { .. } | Self::Softmax | Self::LogSumExp => {
1218 rest_unbatched
1219 }
1220 Self::Rope { .. } => rest_unbatched && inputs[1].size() == 1,
1221 Self::CrossEntropy => true,
1222 _ => false,
1223 };
1224 if simple {
1225 let mut all = inputs.to_vec();
1226 all[0] = front(0)?;
1227 if *self == Self::CrossEntropy {
1228 all[1] = front(1)?;
1229 }
1230 return Ok((record_or_fallback(self.clone(), all)?, 0));
1231 }
1232 let f = |xs: &[Array]| self.fallback(xs);
1233 Ok((crate::transforms::batch(&f, inputs, axes)?, 0))
1234 }
1235
1236 pub(crate) fn eval_cpu(&self, operands: &[Operand<'_>]) -> Result<Vec<crate::array::Data>> {
1238 run_on_cpu(operands, |leaves| self.fallback(leaves))
1239 }
1240}
1241
1242pub(crate) fn run_on_cpu(
1248 operands: &[Operand<'_>],
1249 f: impl FnOnce(&[Array]) -> Result<Vec<Array>>,
1250) -> Result<Vec<crate::array::Data>> {
1251 let leaves = operands
1252 .iter()
1253 .map(|(meta, data)| {
1254 let buffer = crate::kernels::contiguous(meta, data)?;
1255 Array::from_buffer(buffer, meta.dtype, &meta.shape)
1256 })
1257 .collect::<Result<Vec<_>>>()?;
1258 let outputs = {
1259 let _cpu = crate::device::StreamGuard::set(Stream::default_for(Device::cpu()));
1260 f(&leaves)?
1261 };
1262 crate::eval(&outputs)?;
1263 outputs
1264 .iter()
1265 .map(|a| {
1266 let data = a.data().expect("evaluated");
1267 Ok(crate::array::Data::new(crate::kernels::contiguous(
1268 a.out(),
1269 data,
1270 )?))
1271 })
1272 .collect()
1273}
1274
1275#[cfg(test)]
1276mod tests {
1277 use super::*;
1278
1279 #[test]
1280 fn a_panicking_cpu_fallback_restores_the_default_stream() {
1281 let gpu = Stream::default_for(Device::gpu(0));
1282 crate::set_default_stream(gpu);
1283 let x = Array::from(1.0f32);
1284 let data = x.data().expect("host data").clone();
1285 let operands = [(x.out(), &data)];
1286 let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1287 run_on_cpu(&operands, |_| -> Result<Vec<Array>> {
1288 assert_eq!(default_stream().device, Device::cpu(), "runs on the CPU");
1289 panic!("fallback hook panics");
1290 })
1291 }));
1292 assert!(caught.is_err());
1293 assert_eq!(default_stream(), gpu, "the default stream is restored");
1294 crate::set_default_stream(Stream::default_for(Device::cpu()));
1295 }
1296}
1297
1298fn sum_rows(g: &Array) -> Result<Array> {
1300 if g.ndim() <= 1 {
1301 return Ok(g.clone());
1303 }
1304 let axes: Vec<isize> = (0..g.ndim() as isize - 1).collect();
1305 sum(g, &axes, false)
1306}
1307
1308fn rope_graph(
1310 inputs: &[Array],
1311 dims: usize,
1312 traditional: bool,
1313 base: f32,
1314 scale: f32,
1315 forward: bool,
1316) -> Result<Array> {
1317 let x = &inputs[0];
1318 let shape: Vec<isize> = x.dims().iter().map(|&d| d as isize).collect();
1319 let nd = x.ndim();
1320 let (b, t, d) = (x.dims()[0], x.dims()[nd - 2], x.dims()[nd - 1]);
1321 let x4 = reshape(x, &[b as isize, -1, t as isize, d as isize])?;
1322 let dtype = x.dtype();
1323 let half = dims / 2;
1324 let offset = &inputs[1];
1325 let offset = if offset.size() > 1 {
1326 reshape(offset, &[-1, 1, 1])?
1327 } else {
1328 reshape(offset, &[])?
1329 };
1330 let positions = multiply(
1331 add(arange(0.0, t as f64, 1.0, DType::F32)?, &offset)?,
1332 f64::from(scale),
1333 )?;
1334 let inv_freqs = match inputs.get(2) {
1335 Some(f) => divide(1.0, f)?,
1336 None => exp(&multiply(
1337 arange(0.0, -(half as f64), -1.0, DType::F32)?,
1338 f64::from(base.ln() / half as f32),
1339 )?)?,
1340 };
1341 let theta = multiply(expand_dims(&positions, -1)?, &inv_freqs)?;
1342 let (c, s) = (
1343 cos(&theta)?.try_astype(dtype)?,
1344 sin(&theta)?.try_astype(dtype)?,
1345 );
1346 let (start2, step) = if traditional { (1, 2) } else { (half, 1) };
1347 let x1 = last(&x4, 0, step, half)?;
1348 let x2 = last(&x4, start2, step, half)?;
1349 let (r1, r2) = if forward {
1350 (
1351 subtract(multiply(&x1, &c)?, multiply(&x2, &s)?)?,
1352 add(multiply(&x1, &s)?, multiply(&x2, &c)?)?,
1353 )
1354 } else {
1355 (
1356 add(multiply(&x2, &s)?, multiply(&x1, &c)?)?,
1357 subtract(multiply(&x2, &c)?, multiply(&x1, &s)?)?,
1358 )
1359 };
1360 let full = |r: Array| broadcast_to(&r, x1.dims());
1361 let out = update_last(&x4, &full(r1)?, 0, step)?;
1362 let out = update_last(&out, &full(r2)?, start2, step)?;
1363 reshape(&out, &shape)
1364}
1365
1366#[derive(Debug, Clone, Copy)]
1368pub struct Rotation<'a> {
1369 pub dims: i32,
1371 pub traditional: bool,
1373 pub base: Option<f32>,
1375 pub scale: f32,
1377 pub freqs: Option<&'a Array>,
1379}
1380
1381const NORM_ROPE_MAX: usize = 1024;
1383
1384pub(crate) fn norm_rope(
1386 x: &Array,
1387 norm: Option<(&Array, f32)>,
1388 rotation: Rotation<'_>,
1389 offset: usize,
1390 cache: Option<&Array>,
1391) -> Result<Array> {
1392 let rdims = usize::try_from(rotation.dims).map_err(|_| {
1393 Error::InvalidArgument(format!(
1394 "norm_rope: dims must not be negative, got {}",
1395 rotation.dims
1396 ))
1397 })?;
1398 let mut inputs = Vec::with_capacity(4);
1399 let mut fused = x.ndim() == 4
1400 && x.dtype().category() == Category::Float
1401 && x.dims()[3] <= NORM_ROPE_MAX
1402 && offset + x.dims()[2] <= i32::MAX as usize;
1403 if let Some(c) = cache {
1404 let s = x.dims();
1405 let fits = c.ndim() == 4
1406 && c.dims()[..2] == s[..2]
1407 && c.dims()[3] == s[3]
1408 && c.dims()[2] >= offset + s[2];
1409 if !fits {
1410 return Err(Error::InvalidArgument(format!(
1411 "norm_rope: a cache of shape {:?} cannot take {:?} at position {offset}",
1412 c.dims(),
1413 s
1414 )));
1415 }
1416 fused &= c.dtype() == x.dtype();
1417 inputs.push(c.clone());
1418 }
1419 if let Some((w, _)) = norm {
1420 fused &= w.dtype() == x.dtype() && w.ndim() == 1 && w.size() == x.dims()[x.ndim() - 1];
1421 }
1422 let freqs_valid = rotation
1423 .freqs
1424 .is_none_or(|f| f.slot_error().is_none() && f.ndim() == 1 && f.size() == rdims / 2);
1425 let dims_valid =
1426 rdims > 0 && rdims.is_multiple_of(2) && x.ndim() > 0 && rdims <= x.dims()[x.ndim() - 1];
1427 if !fused || !freqs_valid || !dims_valid || rotation.base.is_some() == rotation.freqs.is_some()
1428 {
1429 return norm_rope_graph(x, norm, rotation, offset, cache);
1431 }
1432 inputs.push(x.clone());
1433 inputs.push(match norm {
1434 Some((w, _)) => w.clone(),
1435 None => scalar(1.0, x.dtype())?,
1436 });
1437 if let Some(f) = rotation.freqs {
1438 inputs.push(astype(f, DType::F32)?);
1439 }
1440 let op = FastOp::NormRope {
1441 eps: norm.map(|(_, eps)| eps.to_bits()),
1442 dims: rdims,
1443 traditional: rotation.traditional,
1444 base: rotation.base.unwrap_or(1.0).to_bits(),
1445 scale: rotation.scale.to_bits(),
1446 offset,
1447 cache: cache.is_some(),
1448 };
1449 one(op, inputs)
1450}
1451
1452fn norm_rope_graph(
1454 x: &Array,
1455 norm: Option<(&Array, f32)>,
1456 rotation: Rotation<'_>,
1457 offset: usize,
1458 cache: Option<&Array>,
1459) -> Result<Array> {
1460 let rdims = usize::try_from(rotation.dims).map_err(|_| {
1461 Error::InvalidArgument(format!(
1462 "norm_rope: dims must not be negative, got {}",
1463 rotation.dims
1464 ))
1465 })?;
1466 let x = match norm {
1467 Some((w, eps)) => rms_norm(x, Some(w), eps)?,
1468 None => x.clone(),
1469 };
1470 let r = rope(
1471 &x,
1472 rdims,
1473 rotation.traditional,
1474 rotation.base,
1475 rotation.scale,
1476 &Array::from(offset as i32),
1477 rotation.freqs,
1478 )?;
1479 match cache {
1480 Some(c) => {
1481 let mut region = vec![SliceArg::full(); 4];
1482 region[2] = SliceArg::new(
1483 Some(offset as isize),
1484 Some((offset + r.dims()[2]) as isize),
1485 1,
1486 );
1487 crate::ops::imp::slice_update(c, &r, ®ion)
1488 }
1489 None => Ok(r),
1490 }
1491}
1492
1493pub(crate) fn paged_attention(
1495 q: &Array,
1496 k_cache: &Array,
1497 v_cache: &Array,
1498 block_tables: &Array,
1499 lengths: &Array,
1500 scale: f32,
1501) -> Result<Array> {
1502 let invalid = |msg: String| Err(Error::InvalidArgument(format!("paged_attention: {msg}")));
1503 floating("paged_attention", q.dtype())?;
1504 if q.ndim() != 4 || k_cache.ndim() != 4 || k_cache.dims() != v_cache.dims() {
1505 return invalid(format!(
1506 "q must be [B, Hq, L, D] and the caches [blocks, Hkv, block_size, D], got {:?}, \
1507 {:?}, {:?}",
1508 q.dims(),
1509 k_cache.dims(),
1510 v_cache.dims()
1511 ));
1512 }
1513 let (b, hq, d) = (q.dims()[0], q.dims()[1], q.dims()[3]);
1514 let hkv = k_cache.dims()[1];
1515 if k_cache.dims()[3] != d || hkv == 0 || hq % hkv != 0 {
1516 return invalid(format!(
1517 "q's heads ({hq}) must be a multiple of the caches' ({hkv}), with head size {d}"
1518 ));
1519 }
1520 let integer = |a: &Array| matches!(a.dtype().category(), Category::Signed | Category::Unsigned);
1521 if block_tables.ndim() != 2 || block_tables.dims()[0] != b || !integer(block_tables) {
1522 return invalid(format!(
1523 "block_tables must be [{b}, max_blocks] integers, got {} {:?}",
1524 block_tables.dtype(),
1525 block_tables.dims()
1526 ));
1527 }
1528 if lengths.dims() != [b] || !integer(lengths) {
1529 return invalid(format!(
1530 "lengths must be [{b}] integers, got {} {:?}",
1531 lengths.dtype(),
1532 lengths.dims()
1533 ));
1534 }
1535 if k_cache.dims()[0] == 0 {
1536 return invalid("the caches hold no blocks".into());
1537 }
1538 let dtype = q.dtype();
1539 let inputs = vec![
1540 q.clone(),
1541 astype(k_cache, dtype)?,
1542 astype(v_cache, dtype)?,
1543 astype(block_tables, DType::I32)?,
1544 astype(lengths, DType::I32)?,
1545 ];
1546 let op = FastOp::PagedAttention {
1547 scale: scale.to_bits(),
1548 };
1549 let small = k_cache.size() < u32::MAX as usize;
1551 if matches!(d, 64 | 128 | 256) && k_cache.dims()[2].is_power_of_two() && small {
1552 one(op, inputs)
1553 } else {
1554 Ok(op.fallback(&inputs)?.swap_remove(0))
1555 }
1556}
1557
1558pub(crate) fn paged_write(cache: &Array, new: &Array, slots: &Array) -> Result<Array> {
1560 let invalid = |msg: String| Err(Error::InvalidArgument(format!("paged_write: {msg}")));
1561 let fits = cache.ndim() == 4
1562 && new.ndim() == 3
1563 && new.dims()[1] == cache.dims()[1]
1564 && new.dims()[2] == cache.dims()[3];
1565 if !fits || slots.dims() != [new.dims().first().copied().unwrap_or(0)] {
1566 return invalid(format!(
1567 "cache must be [blocks, Hkv, block_size, D], new [T, Hkv, D] and slots [T], got \
1568 {:?}, {:?}, {:?}",
1569 cache.dims(),
1570 new.dims(),
1571 slots.dims()
1572 ));
1573 }
1574 if !matches!(
1575 slots.dtype().category(),
1576 Category::Signed | Category::Unsigned
1577 ) {
1578 return invalid(format!("slots must be integers, got {}", slots.dtype()));
1579 }
1580 let inputs = vec![
1581 cache.clone(),
1582 astype(new, cache.dtype())?,
1583 astype(slots, DType::I32)?,
1584 ];
1585 one(FastOp::PagedWrite, inputs)
1586}
1587
1588fn paged_keys(cache: &Array, tables: &Array) -> Result<Array> {
1591 let (b, max_blocks) = (tables.dims()[0], tables.dims()[1]);
1592 let (blocks, hkv, bs, d) = (
1593 cache.dims()[0],
1594 cache.dims()[1],
1595 cache.dims()[2],
1596 cache.dims()[3],
1597 );
1598 let last = i32::try_from(blocks.saturating_sub(1)).unwrap_or(i32::MAX);
1599 let ids = ops::minimum(ops::maximum(reshape(tables, &[-1])?, 0i32)?, last)?;
1600 let gathered = crate::indexing::take(cache, &ids, Some(0))?; let gathered = reshape(
1602 &gathered,
1603 &[
1604 b as isize,
1605 max_blocks as isize,
1606 hkv as isize,
1607 bs as isize,
1608 d as isize,
1609 ],
1610 )?;
1611 let gathered = crate::ops::imp::transpose(&gathered, &[0, 2, 1, 3, 4])?;
1612 reshape(
1613 &gathered,
1614 &[
1615 b as isize,
1616 hkv as isize,
1617 (max_blocks * bs) as isize,
1618 d as isize,
1619 ],
1620 )
1621}
1622
1623fn paged_attention_graph(inputs: &[Array], scale: f32) -> Result<Array> {
1626 let [q, k_cache, v_cache, tables, lengths] = inputs else {
1627 unreachable!("five inputs")
1628 };
1629 let (b, lq) = (q.dims()[0], q.dims()[2]);
1630 let (k, v) = (paged_keys(k_cache, tables)?, paged_keys(v_cache, tables)?);
1631 let s = k.dims()[2];
1632 let capacity = i32::try_from(s).unwrap_or(i32::MAX);
1635 let lengths = &ops::minimum(ops::maximum(lengths, 0i32)?, capacity)?;
1636 let positions = reshape(
1638 &arange(0.0, s as f64, 1.0, DType::I32)?,
1639 &[1, 1, 1, s as isize],
1640 )?;
1641 let queries = reshape(
1642 &arange(0.0, lq as f64, 1.0, DType::I32)?,
1643 &[1, 1, lq as isize, 1],
1644 )?;
1645 let end = add(
1646 reshape(lengths, &[b as isize, 1, 1, 1])?,
1647 subtract(&queries, (lq - 1) as f64)?,
1648 )?;
1649 let mask = less(&positions, &end)?;
1650 let options = crate::fast::AttentionOptions::default().mask(&mask);
1651 scaled_dot_product_attention(q, &k, &v, scale, &options)
1652}
1653
1654fn paged_write_graph(inputs: &[Array]) -> Result<Array> {
1657 let [cache, new, slots] = inputs else {
1658 unreachable!("three inputs")
1659 };
1660 let (blocks, hkv, bs, d) = (
1661 cache.dims()[0],
1662 cache.dims()[1],
1663 cache.dims()[2],
1664 cache.dims()[3],
1665 );
1666 let rows = reshape(
1667 &crate::ops::imp::transpose(cache, &[0, 2, 1, 3])?,
1668 &[(blocks * bs) as isize, hkv as isize, d as isize],
1669 )?;
1670 let end = i32::try_from(blocks * bs)
1671 .map_err(|_| Error::InvalidArgument("paged_write: more than 2^31 slots".into()))?;
1672 let slots = where_(
1673 less(slots, 0i32)?,
1674 end,
1675 where_(less(slots, end)?, slots, end)?,
1676 )?;
1677 let scratch = zeros(&[1, hkv, d], rows.dtype())?;
1678 let rows = crate::indexing::concatenate(&[rows, scratch], 0)?;
1679 let at = broadcast_to(&reshape(&slots, &[-1, 1, 1])?, new.dims())?;
1680 let rows = crate::indexing::put_along_axis(&rows, &at, new, 0)?;
1681 let rows = slice(
1682 &rows,
1683 &[
1684 SliceArg::new(Some(0), Some(end as isize), 1),
1685 SliceArg::full(),
1686 SliceArg::full(),
1687 ],
1688 )?;
1689 let rows = reshape(
1690 &rows,
1691 &[blocks as isize, bs as isize, hkv as isize, d as isize],
1692 )?;
1693 crate::ops::imp::transpose(&rows, &[0, 2, 1, 3])
1694}
1695
1696pub(crate) fn attention_key(
1699 qs: &[usize],
1700 ks: &[usize],
1701 vs: &[usize],
1702 dtype: DType,
1703 options: &crate::fast::AttentionOptions<'_>,
1704 target: Target,
1705) -> Result<RouteKey> {
1706 let invalid = |msg: String| {
1707 Err(Error::InvalidArgument(format!(
1708 "scaled_dot_product_attention: {msg}"
1709 )))
1710 };
1711 if [qs, ks, vs].iter().any(|s| s.len() != 4) {
1712 return invalid(format!(
1713 "q, k and v must be 4-D, got {qs:?}, {ks:?} and {vs:?}"
1714 ));
1715 }
1716 if ks[0] != qs[0] || vs[0] != qs[0] || qs[3] != ks[3] || ks[1] != vs[1] || ks[2] != vs[2] {
1717 return invalid(format!("incompatible shapes {qs:?}, {ks:?} and {vs:?}"));
1718 }
1719 if ks[1] == 0 || !qs[1].is_multiple_of(ks[1]) {
1720 return invalid(format!(
1721 "the query heads ({}) must be a multiple of the key heads ({})",
1722 qs[1], ks[1]
1723 ));
1724 }
1725 floating("scaled_dot_product_attention", dtype)?;
1726 let (mask, bool_mask) = match options.mask {
1727 AttentionMask::None => (MaskKind::None, false),
1728 AttentionMask::Causal => (MaskKind::Causal, false),
1729 AttentionMask::Array(m) => {
1730 if m.ndim() > 4 {
1731 return invalid(format!(
1732 "the mask must have at most 4 dimensions, got {:?}",
1733 m.dims()
1734 ));
1735 }
1736 if m.dtype() != DType::Bool && crate::promote_types(m.dtype(), dtype) != dtype {
1737 return invalid(format!("a {} mask does not promote to {dtype}", m.dtype()));
1738 }
1739 let full = [qs[0], qs[1], qs[2], ks[2]];
1740 let fits = m
1741 .dims()
1742 .iter()
1743 .rev()
1744 .zip(full.iter().rev())
1745 .all(|(&a, &b)| a == b || a == 1);
1746 if !fits {
1747 return invalid(format!(
1748 "the mask {:?} does not broadcast to {full:?}",
1749 m.dims()
1750 ));
1751 }
1752 (MaskKind::Array, m.dtype() == DType::Bool)
1753 }
1754 };
1755 if let Some(s) = options.sinks
1756 && (s.ndim() != 1 || s.size() != qs[1] || crate::promote_types(s.dtype(), dtype) != dtype)
1757 {
1758 return invalid(format!(
1759 "sinks must be [{}] of a type promoting to {dtype}",
1760 qs[1]
1761 ));
1762 }
1763 let window = match options.window {
1764 Some(w) if w < 1 => return invalid(format!("the window must be at least 1, got {w}")),
1765 Some(w) => Some(w as usize),
1766 None => None,
1767 };
1768 Ok(RouteKey {
1769 b: qs[0],
1770 hq: qs[1],
1771 hkv: ks[1],
1772 lq: qs[2],
1773 lk: ks[2],
1774 d: qs[3],
1775 dv: vs[3],
1776 dtype,
1777 mask,
1778 bool_mask,
1779 sinks: options.sinks.is_some(),
1780 window,
1781 cap: options.max_score_bytes,
1782 target,
1783 })
1784}
1785
1786fn attention_target(dtype: DType, size: usize) -> Target {
1788 if on_gpu(dtype, size.max(1)) {
1789 Target::Gpu {
1790 class: crate::gpu::class(),
1791 }
1792 } else {
1793 Target::Cpu
1794 }
1795}
1796
1797pub(crate) fn plan_attention(
1799 q: &[i32],
1800 k: &[i32],
1801 v: &[i32],
1802 dtype: DType,
1803 options: &crate::fast::AttentionOptions<'_>,
1804) -> Result<crate::fast::AttentionPlan> {
1805 let dims = |s: &[i32]| -> Result<Vec<usize>> {
1806 s.iter()
1807 .map(|&d| {
1808 usize::try_from(d).map_err(|_| {
1809 Error::InvalidArgument(format!(
1810 "plan_attention: dimensions must not be negative, got {s:?}"
1811 ))
1812 })
1813 })
1814 .collect()
1815 };
1816 let (qs, ks, vs) = (dims(q)?, dims(k)?, dims(v)?);
1817 let size = qs.iter().product();
1818 let key = attention_key(&qs, &ks, &vs, dtype, options, attention_target(dtype, size))?;
1819 let plan = plan::plan(&key)?;
1820 let as_i32 = |n: usize| i32::try_from(n).unwrap_or(i32::MAX);
1821 let kernel = match &plan.route {
1822 Route::Vector(groups) => {
1823 let (mut passes, mut blocks) = (1u8, 1usize);
1824 for g in groups {
1825 if let GroupKind::Vector {
1826 passes: p,
1827 blocks: b,
1828 ..
1829 } = g.kind
1830 {
1831 passes = passes.max(p);
1832 blocks = blocks.max(b);
1833 }
1834 }
1835 crate::fast::AttentionKernel::Vector {
1836 passes,
1837 blocks: as_i32(blocks),
1838 dispatches: as_i32(groups.len()),
1839 }
1840 }
1841 Route::Tiled => crate::fast::AttentionKernel::Tiled,
1842 Route::Composed { rows } if *rows < key.lq => {
1843 crate::fast::AttentionKernel::ComposedBlocks {
1844 rows: as_i32(*rows),
1845 }
1846 }
1847 Route::Composed { .. } => crate::fast::AttentionKernel::Composed,
1848 };
1849 Ok(crate::fast::AttentionPlan {
1850 kernel,
1851 scratch_bytes: plan.scratch,
1852 max_intermediate_bytes: plan.max_intermediate,
1853 capped: plan.capped,
1854 })
1855}
1856
1857pub(crate) fn scaled_dot_product_attention(
1860 q: &Array,
1861 k: &Array,
1862 v: &Array,
1863 scale: f32,
1864 options: &crate::fast::AttentionOptions<'_>,
1865) -> Result<Array> {
1866 if [q, k, v].iter().any(|a| a.ndim() != 4) {
1867 return Err(Error::InvalidArgument(format!(
1868 "scaled_dot_product_attention: q, k and v must be 4-D, got {:?}, {:?} and {:?}",
1869 q.dims(),
1870 k.dims(),
1871 v.dims()
1872 )));
1873 }
1874 let dtype = crate::promote_types(crate::promote_types(q.dtype(), k.dtype()), v.dtype());
1875 let key = attention_key(
1876 q.dims(),
1877 k.dims(),
1878 v.dims(),
1879 dtype,
1880 options,
1881 attention_target(dtype, q.size()),
1882 )?;
1883 let plan = plan::plan(&key)?;
1884 let (qs, ks) = (q.dims(), k.dims());
1885 let (q, k, v) = (astype(q, dtype)?, astype(k, dtype)?, astype(v, dtype)?);
1886 let mask = match options.mask {
1887 AttentionMask::Array(m) => {
1888 let m = if m.dtype() == DType::Bool {
1889 m.clone()
1890 } else {
1891 astype(m, dtype)?
1892 };
1893 Some(broadcast_to(&m, &[qs[0], qs[1], qs[2], ks[2]])?)
1894 }
1895 _ => None,
1896 };
1897 let sinks = match options.sinks {
1898 Some(s) => Some(astype(s, dtype)?),
1899 None => None,
1900 };
1901 let call = Call {
1902 key: &key,
1903 scale,
1904 q: &q,
1905 k: &k,
1906 v: &v,
1907 mask: mask.as_ref(),
1908 sinks: sinks.as_ref(),
1909 };
1910 match &plan.route {
1911 Route::Tiled => call.fused(0, key.lq, key.lk, Kernel::Tiled),
1912 Route::Vector(groups) => {
1913 if let [g] = groups.as_slice()
1914 && g.start == 0
1915 && g.end == key.lq
1916 && g.keys == key.lk
1917 {
1918 return call.group(g);
1919 }
1920 let outs = groups
1921 .iter()
1922 .map(|g| call.group(g))
1923 .collect::<Result<Vec<_>>>()?;
1924 crate::indexing::concatenate(&outs, 2)
1925 }
1926 Route::Composed { rows } => {
1927 let rows = (*rows).max(1);
1928 let mut outs: Vec<Array> = Vec::with_capacity(key.lq.div_ceil(rows));
1929 let mut start = 0;
1930 while start < key.lq {
1931 let end = (start + rows).min(key.lq);
1932 let after = outs.last().cloned();
1935 outs.push(call.composed(start, end, after)?);
1936 start = end;
1937 }
1938 if outs.len() == 1 {
1939 Ok(outs.pop().expect("one block"))
1940 } else {
1941 crate::indexing::concatenate(&outs, 2)
1942 }
1943 }
1944 }
1945}
1946
1947#[derive(Clone, Copy)]
1949enum Kernel {
1950 Tiled,
1951 Vector {
1952 passes: u8,
1953 blocks: usize,
1954 gqa_kernel: bool,
1955 tk1: bool,
1956 },
1957}
1958
1959struct Call<'a> {
1961 key: &'a RouteKey,
1962 scale: f32,
1963 q: &'a Array,
1964 k: &'a Array,
1965 v: &'a Array,
1966 mask: Option<&'a Array>,
1968 sinks: Option<&'a Array>,
1969}
1970
1971impl Call<'_> {
1972 fn slices(
1974 &self,
1975 rows: (usize, usize),
1976 keys: (usize, usize),
1977 ) -> Result<(Array, Array, Array, Option<Array>)> {
1978 let range = |(a, b): (usize, usize)| SliceArg::new(Some(a as isize), Some(b as isize), 1);
1979 let whole_q = rows == (0, self.key.lq);
1980 let whole_k = keys == (0, self.key.lk);
1981 let full = SliceArg::full;
1982 let q = match whole_q {
1983 true => self.q.clone(),
1984 false => slice(self.q, &[full(), full(), range(rows), full()])?,
1985 };
1986 let (k, v) = match whole_k {
1987 true => (self.k.clone(), self.v.clone()),
1988 false => (
1989 slice(self.k, &[full(), full(), range(keys), full()])?,
1990 slice(self.v, &[full(), full(), range(keys), full()])?,
1991 ),
1992 };
1993 let mask = match self.mask {
1994 Some(m) if !(whole_q && whole_k) => {
1995 Some(slice(m, &[full(), full(), range(rows), range(keys)])?)
1996 }
1997 other => other.cloned(),
1998 };
1999 Ok((q, k, v, mask))
2000 }
2001
2002 fn fused(&self, start: usize, end: usize, keys: usize, kernel: Kernel) -> Result<Array> {
2005 let (q, k, v, mask) = self.slices((start, end), (0, keys))?;
2006 let (passes, blocks, gqa_kernel, tk1) = match kernel {
2007 Kernel::Tiled => (0, 1, false, false),
2008 Kernel::Vector {
2009 passes,
2010 blocks,
2011 gqa_kernel,
2012 tk1,
2013 } => (passes, blocks, gqa_kernel, tk1),
2014 };
2015 let op = FastOp::Attention {
2016 scale: self.scale.to_bits(),
2017 causal: self.key.mask == MaskKind::Causal,
2018 mask: mask.is_some(),
2019 sinks: self.sinks.is_some(),
2020 passes,
2021 blocks: blocks as u32,
2022 gqa_kernel,
2023 tk1,
2024 };
2025 let mut inputs = vec![q, k, v];
2026 inputs.extend(mask);
2027 inputs.extend(self.sinks.cloned());
2028 Ok(Array::from_op(Primitive::Fast(op), inputs)?.swap_remove(0))
2029 }
2030
2031 fn group(&self, g: &plan::Group) -> Result<Array> {
2033 match g.kind {
2034 GroupKind::Vector {
2035 passes,
2036 blocks,
2037 gqa_kernel,
2038 tk1,
2039 } => self.fused(
2040 g.start,
2041 g.end,
2042 g.keys,
2043 Kernel::Vector {
2044 passes,
2045 blocks,
2046 gqa_kernel,
2047 tk1,
2048 },
2049 ),
2050 GroupKind::Composed => {
2051 let (q, k, v, mask) = self.slices((g.start, g.end), (0, g.keys))?;
2052 let mut inputs = vec![q, k, v];
2053 let has_mask = mask.is_some();
2054 inputs.extend(mask);
2055 inputs.extend(self.sinks.cloned());
2056 attention_graph(
2057 &inputs,
2058 self.scale,
2059 self.key.mask == MaskKind::Causal,
2060 has_mask,
2061 self.sinks.is_some(),
2062 None,
2063 None,
2064 )
2065 }
2066 }
2067 }
2068
2069 fn composed(&self, start: usize, end: usize, after: Option<Array>) -> Result<Array> {
2072 let key = self.key;
2073 let causal = key.mask == MaskKind::Causal;
2074 let hi = match causal {
2076 true => (key.lk + end).saturating_sub(key.lq).clamp(1, key.lk),
2077 false => key.lk,
2078 };
2079 let lo = match key.window {
2081 Some(w) => (start + key.lk).saturating_sub(key.lq + w - 1).min(hi - 1),
2082 None => 0,
2083 };
2084 let offset = (start + key.lk) as isize - key.lq as isize - lo as isize;
2085 let (mut q, k, v, mask) = self.slices((start, end), (lo, hi))?;
2086 if let Some(prev) = after {
2087 q = crate::primitive::depends(&q, &[prev])?;
2088 }
2089 let mut inputs = vec![q, k, v];
2090 let has_mask = mask.is_some();
2091 inputs.extend(mask);
2092 inputs.extend(self.sinks.cloned());
2093 attention_graph(
2094 &inputs,
2095 self.scale,
2096 causal,
2097 has_mask,
2098 self.sinks.is_some(),
2099 Some(offset),
2100 key.window,
2101 )
2102 }
2103}
2104
2105fn attention_config(op: &FastOp, inputs: &[Array]) -> bool {
2108 let FastOp::Attention {
2109 causal,
2110 mask,
2111 sinks,
2112 passes,
2113 blocks,
2114 gqa_kernel,
2115 tk1,
2116 ..
2117 } = *op
2118 else {
2119 return false;
2120 };
2121 let (q, k, v) = (&inputs[0], &inputs[1], &inputs[2]);
2122 let (lq, lk, d, dv) = (q.dims()[2], k.dims()[2], q.dims()[3], v.dims()[3]);
2123 let gqa = q.dims()[1] / k.dims()[1];
2124 if lq == 0 || lk == 0 {
2125 return false;
2126 }
2127 match passes {
2128 0 => plan::tiled_head(d, dv) && (!causal || lq <= lk) && !gqa_kernel && !tk1,
2129 1 | 2 => {
2130 let vector = lq <= 8 && lq <= lk && plan::vector_head(d, dv) && (d != 512 || lq == 1);
2131 let two = passes == 1
2132 || ((1..=1024).contains(&blocks)
2133 && 32 * gqa * lq <= 1024
2134 && (!gqa_kernel
2135 || (lq == 1
2136 && !mask
2137 && !sinks
2138 && d == dv
2139 && ((gqa == 8 && (d == 64 || d == 128))
2140 || ((gqa == 12 || gqa == 16) && d == 128))))
2141 && (!tk1 || (!gqa_kernel && plan::tk1_variant(q.dtype(), d, dv))));
2142 vector && two && (passes == 2 || (!gqa_kernel && !tk1))
2143 }
2144 _ => false,
2145 }
2146}
2147
2148fn attention_graph(
2154 inputs: &[Array],
2155 scale: f32,
2156 causal: bool,
2157 has_mask: bool,
2158 has_sinks: bool,
2159 offset: Option<isize>,
2160 window: Option<usize>,
2161) -> Result<Array> {
2162 let (q, k, v) = (&inputs[0], &inputs[1], &inputs[2]);
2163 let dtype = q.dtype();
2164 let (b, hq, lq) = (q.dims()[0], q.dims()[1], q.dims()[2]);
2165 let (hkv, lk) = (k.dims()[1], k.dims()[2]);
2166 let rep = hq / hkv;
2167 let i = |n: usize| n as isize;
2168 let mut q = multiply(q, f64::from(scale))?;
2169 let (mut k, mut v) = (k.clone(), v.clone());
2170 if rep > 1 {
2171 q = reshape(&q, &[i(b), i(hkv), i(rep), i(lq), i(q.dims()[3])])?;
2172 k = expand_dims(&k, 2)?;
2173 v = expand_dims(&v, 2)?;
2174 }
2175 let n = k.ndim();
2176 let mut axes: Vec<usize> = (0..n).collect();
2177 axes.swap(n - 2, n - 1);
2178 let mut scores = crate::ops::imp::matmul(&q, &crate::ops::imp::transpose(&k, &axes)?)?;
2179 let mask = if causal {
2180 let offset = offset.unwrap_or(lk as isize - lq as isize) as f64;
2181 let qi = expand_dims(&arange(offset, offset + lq as f64, 1.0, DType::I32)?, 1)?;
2182 let ki = expand_dims(&arange(0.0, lk as f64, 1.0, DType::I32)?, 0)?;
2183 let seen = crate::ops::imp::greater_equal(&qi, &ki)?;
2184 Some(match window {
2185 Some(w) => multiply(
2187 &seen,
2188 &crate::ops::imp::greater(&ki, &subtract(&qi, w as f64)?)?,
2189 )?,
2190 None => seen,
2191 })
2192 } else if has_mask {
2193 let m = &inputs[3];
2194 Some(if rep > 1 {
2195 reshape(m, &[i(b), i(hkv), i(rep), i(lq), i(lk)])?
2196 } else {
2197 m.clone()
2198 })
2199 } else {
2200 None
2201 };
2202 if let Some(m) = mask {
2203 scores = if m.dtype() == DType::Bool {
2204 let lowest = match dtype {
2205 DType::F16 => f64::from(crate::f16::MIN),
2206 DType::BF16 => f64::from(crate::bf16::MIN),
2207 DType::F64 => f64::MIN,
2208 _ => f64::from(f32::MIN),
2209 };
2210 where_(&m, &scores, lowest)?
2211 } else {
2212 add(&scores, &m)?
2213 };
2214 }
2215 let probs = if has_sinks {
2216 let s = astype(&scores, DType::F32)?;
2218 let sink = astype(inputs.last().expect("sinks"), DType::F32)?;
2219 let shape: Vec<isize> = if rep > 1 {
2220 vec![1, i(hkv), i(rep), 1, 1]
2221 } else {
2222 vec![1, i(hq), 1, 1]
2223 };
2224 let sink = reshape(&sink, &shape)?;
2225 let m = stop_gradient(&crate::ops::imp::maximum(&max(&s, &[-1], true)?, &sink)?)?;
2226 let e = exp(&subtract(&s, &m)?)?;
2227 let denom = add(sum(&e, &[-1], true)?, exp(&subtract(&sink, &m)?)?)?;
2228 astype(÷(&e, &denom)?, dtype)?
2229 } else if on_gpu(dtype, scores.size()) {
2230 crate::ops::imp::softmax(&scores, &[-1])?
2231 } else {
2232 softmax_graph(&scores)?
2233 };
2234 let out = crate::ops::imp::matmul(&probs, &v)?;
2235 if rep > 1 {
2236 reshape(&out, &[i(b), i(hq), i(lq), i(v.dims()[n - 1])])
2237 } else {
2238 Ok(out)
2239 }
2240}
2241
2242pub(crate) fn gated_delta(
2244 q: &Array,
2245 k: &Array,
2246 v: &Array,
2247 g: &Array,
2248 beta: &Array,
2249 state: &Array,
2250) -> Result<(Array, Array)> {
2251 gated_delta_op(q, k, v, g, beta, state, false)
2252}
2253
2254pub(crate) fn gated_delta_steps(
2256 q: &Array,
2257 k: &Array,
2258 v: &Array,
2259 g: &Array,
2260 beta: &Array,
2261 state: &Array,
2262) -> Result<(Array, Array)> {
2263 gated_delta_op(q, k, v, g, beta, state, true)
2264}
2265
2266pub(crate) fn gated_delta_slots(
2268 q: &Array,
2269 k: &Array,
2270 v: &Array,
2271 g: &Array,
2272 beta: &Array,
2273 pool: &Array,
2274 slots: &Array,
2275) -> Result<(Array, Array)> {
2276 let b = q.dims().first().copied().unwrap_or(0);
2277 let fits = pool.ndim() == 4
2278 && slots.dims() == [b]
2279 && matches!(
2280 slots.dtype().category(),
2281 Category::Signed | Category::Unsigned
2282 );
2283 if !fits {
2284 return Err(Error::InvalidArgument(format!(
2285 "gated_delta_slots: pool must be [slots, Hv, Dv, Dk] and slots [{b}] integers, got \
2286 {:?} and {} {:?}",
2287 pool.dims(),
2288 slots.dtype(),
2289 slots.dims()
2290 )));
2291 }
2292 let one = crate::indexing::take(pool, &reshape(&astype(slots, DType::I32)?, &[-1])?, Some(0))?;
2294 let _validated = gated_delta_op(q, k, v, g, beta, &one, false)?;
2295 let inputs = vec![
2296 q.clone(),
2297 k.clone(),
2298 v.clone(),
2299 g.clone(),
2300 beta.clone(),
2301 pool.clone(),
2302 astype(slots, DType::I32)?,
2303 ];
2304 let kernel = on_gpu(q.dtype(), q.size())
2305 && q.dtype() == v.dtype()
2306 && q.dims()[3].is_multiple_of(32)
2307 && [g, beta, pool]
2308 .iter()
2309 .all(|a| matches!(a.dtype(), DType::F16 | DType::BF16 | DType::F32));
2310 let op = FastOp::GatedDeltaSlots;
2311 let mut out = if kernel {
2312 Array::from_op(Primitive::Fast(op), inputs)?
2313 } else {
2314 op.fallback(&inputs)?
2315 };
2316 let pool = out.pop().expect("two outputs");
2317 Ok((out.pop().expect("two outputs"), pool))
2318}
2319
2320fn gated_delta_op(
2321 q: &Array,
2322 k: &Array,
2323 v: &Array,
2324 g: &Array,
2325 beta: &Array,
2326 state: &Array,
2327 steps: bool,
2328) -> Result<(Array, Array)> {
2329 let op = "gated_delta";
2330 for a in [q, k, v, g, beta, state] {
2331 floating(op, a.dtype())?;
2332 }
2333 let invalid = |msg: String| Err(Error::InvalidArgument(format!("{op}: {msg}")));
2334 if q.ndim() != 4 || q.dims() != k.dims() || v.ndim() != 4 {
2335 return invalid(format!(
2336 "q and k must be [B, T, Hk, Dk] and v [B, T, Hv, Dv], got {:?}, {:?}, {:?}",
2337 q.dims(),
2338 k.dims(),
2339 v.dims()
2340 ));
2341 }
2342 let (b, t, hk, dk) = (q.dims()[0], q.dims()[1], q.dims()[2], q.dims()[3]);
2343 let (hv, dv) = (v.dims()[2], v.dims()[3]);
2344 if v.dims()[..2] != [b, t] || hk == 0 || hv % hk != 0 {
2345 return invalid(format!(
2346 "v must be [{b}, {t}, Hv, Dv] with Hv a multiple of {hk}, got {:?}",
2347 v.dims()
2348 ));
2349 }
2350 if g.dims() != [b, t, hv] || beta.dims() != [b, t, hv] {
2351 return invalid(format!(
2352 "g and beta must be [{b}, {t}, {hv}], got {:?} and {:?}",
2353 g.dims(),
2354 beta.dims()
2355 ));
2356 }
2357 if state.dims() != [b, hv, dv, dk] {
2358 return invalid(format!(
2359 "state must be [{b}, {hv}, {dv}, {dk}], got {:?}",
2360 state.dims()
2361 ));
2362 }
2363 let inputs = vec![
2364 q.clone(),
2365 k.clone(),
2366 v.clone(),
2367 g.clone(),
2368 beta.clone(),
2369 state.clone(),
2370 ];
2371 let kernel = on_gpu(q.dtype(), q.size())
2372 && q.dtype() == v.dtype()
2373 && dk % 32 == 0
2374 && [g, beta, state]
2375 .iter()
2376 .all(|a| matches!(a.dtype(), DType::F16 | DType::BF16 | DType::F32));
2377 let op = FastOp::GatedDelta { steps };
2378 let mut out = if kernel {
2379 Array::from_op(Primitive::Fast(op), inputs)?
2380 } else {
2381 op.fallback(&inputs)?
2382 };
2383 let s = out.pop().expect("two outputs");
2384 Ok((out.pop().expect("two outputs"), s))
2385}
2386
2387fn gated_delta_graph(inputs: &[Array], steps: bool) -> Result<Vec<Array>> {
2390 let [q, k, v, g, beta, state] = inputs else {
2391 unreachable!("six inputs")
2392 };
2393 let (b, t, hk, dk) = (q.dims()[0], q.dims()[1], q.dims()[2], q.dims()[3]);
2394 let (hv, dv) = (v.dims()[2], v.dims()[3]);
2395 let heads = |x: &Array| -> Result<Array> {
2397 if hv == hk {
2398 return Ok(x.clone());
2399 }
2400 let wide = broadcast_to(&expand_dims(x, 3)?, &[b, t, hk, hv / hk, dk])?;
2401 reshape(&wide, &[b as isize, t as isize, hv as isize, dk as isize])
2402 };
2403 let wide = |x: &Array| match x.dtype() {
2405 DType::F64 => Ok(x.clone()),
2406 _ => astype(x, DType::F32),
2407 };
2408 let (qh, kh) = (heads(&wide(q)?)?, heads(&wide(k)?)?);
2409 let (v, g, beta) = (&wide(v)?, &wide(g)?, &wide(beta)?);
2410 let at = |x: &Array, i: usize| -> Result<Array> {
2411 let mut args = vec![SliceArg::full(); x.ndim()];
2412 args[1] = SliceArg::new(Some(i as isize), Some(i as isize + 1), 1);
2413 squeeze(&slice(x, &args)?, &[1])
2414 };
2415 let mut s = wide(state)?;
2416 let mut ys = Vec::with_capacity(t);
2417 let mut states = Vec::with_capacity(if steps { t } else { 0 });
2418 for i in 0..t {
2419 let (qt, kt, vt) = (at(&qh, i)?, expand_dims(&at(&kh, i)?, 2)?, at(v, i)?);
2420 let decay = reshape(&at(g, i)?, &[b as isize, hv as isize, 1, 1])?;
2421 s = multiply(&s, &decay)?;
2422 let kv_mem = sum(&multiply(&s, &kt)?, &[-1], false)?;
2423 let delta = multiply(subtract(&vt, &kv_mem)?, expand_dims(&at(beta, i)?, -1)?)?;
2424 s = add(&s, multiply(&kt, expand_dims(&delta, -1)?)?)?;
2425 let y = sum(&multiply(&s, expand_dims(&qt, 2)?)?, &[-1], false)?;
2426 ys.push(astype(&y, q.dtype())?);
2427 if steps {
2428 states.push(astype(&s, state.dtype())?);
2429 }
2430 }
2431 let y = match ys.is_empty() {
2432 true => zeros(&[b, 0, hv, dv], q.dtype())?,
2433 false => stack(&ys, 1)?,
2434 };
2435 let s = match (steps, states.is_empty()) {
2436 (false, _) => astype(&s, state.dtype())?,
2437 (true, false) => stack(&states, 1)?,
2438 (true, true) => zeros(&[b, 0, hv, dv, dk], state.dtype())?,
2439 };
2440 Ok(vec![y, s])
2441}
2442
2443fn gather_qmm_graph(
2446 inputs: &[Array],
2447 group_size: usize,
2448 bits: u32,
2449 transpose: bool,
2450) -> Result<Array> {
2451 let (lhs, rhs) = (&inputs[4], &inputs[5]);
2452 let rows = |a: &Array, indices: &Array| -> Result<Array> {
2453 let nd = a.ndim();
2454 let flat = reshape(
2455 a,
2456 &[-1, a.dims()[nd - 2] as isize, a.dims()[nd - 1] as isize],
2457 )?;
2458 crate::indexing::take(&flat, indices, Some(0))
2459 };
2460 crate::quantized::quantized_matmul(
2461 &rows(&inputs[0], lhs)?,
2462 &rows(&inputs[1], rhs)?,
2463 &rows(&inputs[2], rhs)?,
2464 &rows(&inputs[3], rhs)?,
2465 transpose,
2466 group_size,
2467 bits,
2468 )
2469}