1use crate::cast::Canon;
27use crate::kernels::Operand;
28use crate::ops::{
29 self, add, arange, astype, broadcast_to, cos, divide, equal, exp, expand_dims, filled, less,
30 log, max, mean, multiply, reshape, sin, slice, sqrt, squeeze, stop_gradient, subtract, sum,
31 where_,
32};
33use crate::primitive::Primitive;
34use crate::{
35 Array, Category, DType, Device, DeviceKind, Error, Result, SliceArg, Stream, default_stream,
36 set_default_stream,
37};
38use crate::{stack, zeros};
39
40pub use crate::kernel::{KernelCall, KernelConfig, MetalKernel, TemplateArg};
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44#[non_exhaustive]
45pub enum FastOp {
46 RmsNorm {
48 eps: u32,
50 },
51 AddRmsNorm {
53 eps: u32,
55 },
56 RmsNormVjp {
58 eps: u32,
60 },
61 LayerNorm {
64 eps: u32,
66 },
67 LayerNormVjp {
69 eps: u32,
71 },
72 Rope {
74 dims: usize,
76 traditional: bool,
78 base: u32,
80 scale: u32,
82 forward: bool,
84 },
85 NormRope {
89 eps: Option<u32>,
91 dims: usize,
93 traditional: bool,
95 base: u32,
97 scale: u32,
99 offset: usize,
101 cache: bool,
103 },
104 DepthwiseConv1d {
106 stride: usize,
108 padding: usize,
110 dilation: usize,
112 },
113 PagedAttention {
115 scale: u32,
117 },
118 PagedWrite,
120 Softmax,
122 LogSumExp,
124 CrossEntropy,
126 CrossEntropyVjp,
128 Attention {
131 scale: u32,
133 causal: bool,
135 mask: bool,
137 sinks: bool,
139 },
140 GatedDelta {
143 steps: bool,
145 },
146 GatedDeltaSlots,
149 GatherMm,
152 GatherQmm {
157 group_size: usize,
159 bits: u8,
161 transpose: bool,
163 },
164}
165
166#[derive(Debug, Clone, Copy)]
168#[non_exhaustive]
169pub enum AttentionMask<'a> {
170 None,
172 Causal,
174 Array(&'a Array),
177}
178
179fn float(bits: u32) -> f32 {
180 f32::from_bits(bits)
181}
182
183fn check_inputs(inputs: &[&Array]) -> Result<()> {
184 match inputs.iter().find_map(|a| a.error()) {
185 Some(err) => Err(err),
186 None => Ok(()),
187 }
188}
189
190fn scalar(value: f64, dtype: DType) -> Result<Array> {
192 filled(&[], dtype, |_| Canon::Float(value))
193}
194
195pub(crate) fn on_gpu(dtype: DType, size: usize) -> bool {
197 default_stream().device.kind == DeviceKind::Gpu
198 && matches!(dtype, DType::F16 | DType::BF16 | DType::F32)
199 && size > 0
200 && size < u32::MAX as usize
201}
202
203pub(crate) fn record_or_fallback(op: FastOp, inputs: Vec<Array>) -> Result<Vec<Array>> {
206 if on_gpu(inputs[0].dtype(), inputs[0].size()) {
207 Array::from_op(Primitive::Fast(op), inputs)
208 } else {
209 op.fallback(&inputs)
210 }
211}
212
213fn one(op: FastOp, inputs: Vec<Array>) -> Result<Array> {
214 Ok(record_or_fallback(op, inputs)?.swap_remove(0))
215}
216
217fn floating(op: &'static str, dtype: DType) -> Result<()> {
218 match dtype.category() {
219 Category::Float => Ok(()),
220 _ => Err(Error::UnsupportedDType { op, dtype }),
221 }
222}
223
224fn check_param(op: &'static str, what: &str, p: &Array, x: &Array) -> Result<()> {
226 if p.ndim() != 1 || p.size() != x.shape()[x.ndim() - 1] {
227 return Err(Error::InvalidArgument(format!(
228 "{op}: {what} must be 1-D with {} elements, got shape {:?}",
229 x.shape()[x.ndim() - 1],
230 p.shape()
231 )));
232 }
233 Ok(())
234}
235
236pub fn rms_norm(x: &Array, weight: Option<&Array>, eps: f32) -> Result<Array> {
240 check_inputs(&[x])?;
241 if x.ndim() == 0 {
242 return Err(Error::InvalidArgument(
243 "rms_norm: the input needs at least one dimension".into(),
244 ));
245 }
246 let dtype = match weight {
247 Some(w) => {
248 check_inputs(&[w])?;
249 check_param("rms_norm", "weight", w, x)?;
250 crate::promote_types(x.dtype(), w.dtype())
251 }
252 None => x.dtype(),
253 };
254 floating("rms_norm", dtype)?;
255 let w = match weight {
256 Some(w) => astype(w, dtype)?,
257 None => scalar(1.0, dtype)?,
258 };
259 let op = FastOp::RmsNorm { eps: eps.to_bits() };
260 one(op, vec![astype(x, dtype)?, w])
261}
262
263pub fn add_rms_norm(
268 x: &Array,
269 residual: &Array,
270 weight: Option<&Array>,
271 eps: f32,
272) -> Result<(Array, Array)> {
273 check_inputs(&[x, residual])?;
274 let dtype = crate::promote_types(x.dtype(), residual.dtype());
275 let fused = x.shape() == residual.shape()
276 && x.ndim() > 0
277 && dtype.category() == Category::Float
278 && weight.is_none_or(|w| {
279 w.error().is_none()
280 && w.ndim() == 1
281 && w.size() == x.shape()[x.ndim() - 1]
282 && crate::promote_types(dtype, w.dtype()) == dtype
283 });
284 if !fused {
285 let h = add(x, residual)?;
286 let y = rms_norm(&h, weight, eps)?;
287 return Ok((h, y));
288 }
289 let w = match weight {
290 Some(w) => astype(w, dtype)?,
291 None => scalar(1.0, dtype)?,
292 };
293 let op = FastOp::AddRmsNorm { eps: eps.to_bits() };
294 let mut out = record_or_fallback(op, vec![astype(x, dtype)?, astype(residual, dtype)?, w])?;
295 let y = out.pop().expect("two outputs");
296 Ok((out.pop().expect("two outputs"), y))
297}
298
299pub fn layer_norm(
302 x: &Array,
303 weight: Option<&Array>,
304 bias: Option<&Array>,
305 eps: f32,
306) -> Result<Array> {
307 check_inputs(&[x])?;
308 if x.ndim() == 0 {
309 return Err(Error::InvalidArgument(
310 "layer_norm: the input needs at least one dimension".into(),
311 ));
312 }
313 let mut dtype = x.dtype();
314 for (p, what) in [(weight, "weight"), (bias, "bias")] {
315 if let Some(p) = p {
316 check_inputs(&[p])?;
317 check_param("layer_norm", what, p, x)?;
318 dtype = crate::promote_types(dtype, p.dtype());
319 }
320 }
321 floating("layer_norm", dtype)?;
322 let w = match weight {
323 Some(w) => astype(w, dtype)?,
324 None => scalar(1.0, dtype)?,
325 };
326 let b = match bias {
327 Some(b) => astype(b, dtype)?,
328 None => scalar(0.0, dtype)?,
329 };
330 let op = FastOp::LayerNorm { eps: eps.to_bits() };
331 one(op, vec![astype(x, dtype)?, w, b])
332}
333
334pub fn rope(
340 x: &Array,
341 dims: usize,
342 traditional: bool,
343 base: Option<f32>,
344 scale: f32,
345 offset: &Array,
346 freqs: Option<&Array>,
347) -> Result<Array> {
348 check_inputs(&[x, offset])?;
349 let invalid = |msg: String| Err(Error::InvalidArgument(format!("rope: {msg}")));
350 if x.ndim() < 3 {
351 return invalid(format!(
352 "the input needs at least 3 dimensions, got {:?}",
353 x.shape()
354 ));
355 }
356 floating("rope", x.dtype())?;
357 if offset.ndim() > 1 || (offset.size() != 1 && offset.size() != x.shape()[0]) {
358 return invalid(format!(
359 "offset must be a scalar or have {} elements, got shape {:?}",
360 x.shape()[0],
361 offset.shape()
362 ));
363 }
364 if !matches!(
365 offset.dtype().category(),
366 Category::Signed | Category::Unsigned
367 ) {
368 return invalid(format!("offset must be an integer, got {}", offset.dtype()));
369 }
370 if dims == 0 || !dims.is_multiple_of(2) || dims > x.shape()[x.ndim() - 1] {
371 return invalid(format!(
372 "dims must be even, positive and at most {}, got {dims}",
373 x.shape()[x.ndim() - 1]
374 ));
375 }
376 let offset = match offset.dtype().size() {
377 4 => offset.clone(),
378 _ => astype(offset, DType::I32)?,
379 };
380 let mut inputs = vec![x.clone(), offset];
381 let base = match (base, freqs) {
382 (Some(b), None) => b,
383 (None, Some(f)) => {
384 check_inputs(&[f])?;
385 if f.ndim() != 1 || f.size() != dims / 2 {
386 return invalid(format!(
387 "freqs must be 1-D with {} elements, got shape {:?}",
388 dims / 2,
389 f.shape()
390 ));
391 }
392 inputs.push(astype(f, DType::F32)?);
393 1.0
394 }
395 _ => return invalid("exactly one of base and freqs must be given".into()),
396 };
397 let op = FastOp::Rope {
398 dims,
399 traditional,
400 base: base.to_bits(),
401 scale: scale.to_bits(),
402 forward: true,
403 };
404 one(op, inputs)
405}
406
407pub fn cross_entropy(logits: &Array, targets: &Array) -> Result<Array> {
411 check_inputs(&[logits, targets])?;
412 if logits.ndim() == 0 || targets.shape() != &logits.shape()[..logits.ndim() - 1] {
413 return Err(Error::InvalidArgument(format!(
414 "cross_entropy: targets of shape {:?} do not match logits of shape {:?}",
415 targets.shape(),
416 logits.shape()
417 )));
418 }
419 floating("cross_entropy", logits.dtype())?;
420 if !matches!(
421 targets.dtype().category(),
422 Category::Signed | Category::Unsigned
423 ) {
424 return Err(Error::InvalidArgument(format!(
425 "cross_entropy: targets must be integer class indices, got {}",
426 targets.dtype()
427 )));
428 }
429 one(
430 FastOp::CrossEntropy,
431 vec![logits.clone(), astype(targets, DType::I32)?],
432 )
433}
434
435fn update_last(base: &Array, update: &Array, start: usize, step: isize) -> Result<Array> {
438 let n = base.ndim();
439 let mut starts = vec![0; n];
440 starts[n - 1] = start;
441 let mut steps = vec![1; n];
442 steps[n - 1] = step;
443 let primitive = Primitive::SliceUpdate {
444 start: starts,
445 step: steps,
446 len: update.shape().to_vec(),
447 };
448 Ok(Array::from_op(primitive, vec![base.clone(), update.clone()])?.swap_remove(0))
449}
450
451fn last(x: &Array, start: usize, step: isize, len: usize) -> Result<Array> {
453 let mut args = vec![SliceArg::full(); x.ndim()];
454 let stop = start as isize + step * len as isize;
455 args[x.ndim() - 1] = SliceArg::new(Some(start as isize), Some(stop), step);
456 slice(x, &args)
457}
458
459fn rsqrt(x: &Array) -> Result<Array> {
461 divide(1.0, sqrt(x)?)
462}
463
464fn softmax_graph(x: &Array) -> Result<Array> {
466 let xf = if x.dtype() == DType::F32 {
467 x.clone()
468 } else {
469 astype(x, DType::F32)?
470 };
471 let m = stop_gradient(&max(&xf, &[-1], true)?)?;
472 let e = exp(&subtract(&xf, &m)?)?;
473 astype(÷(&e, sum(&e, &[-1], true)?)?, x.dtype())
474}
475
476fn logsumexp_graph(x: &Array) -> Result<Array> {
478 let m = stop_gradient(&max(x, &[-1], true)?)?;
479 let out = add(log(&sum(&exp(&subtract(x, &m)?)?, &[-1], true)?)?, &m)?;
480 where_(equal(&ops::abs(&m)?, f64::INFINITY)?, &m, &out)
481}
482
483fn one_hot(y: &Array, classes: usize, dtype: DType) -> Result<Array> {
485 let positions = arange(0.0, classes as f64, 1.0, y.dtype())?;
486 equal(&expand_dims(y, -1)?, &positions)?.astype(dtype)
487}
488
489fn wrap(y: &Array, classes: usize) -> Result<Array> {
491 where_(less(y, 0)?, add(y, classes as f64)?, y)
492}
493
494impl FastOp {
495 pub(crate) fn name(&self) -> &'static str {
496 match self {
497 Self::RmsNorm { .. } => "rms_norm",
498 Self::AddRmsNorm { .. } => "add_rms_norm",
499 Self::RmsNormVjp { .. } => "rms_norm_vjp",
500 Self::LayerNorm { .. } => "layer_norm",
501 Self::LayerNormVjp { .. } => "layer_norm_vjp",
502 Self::Rope { .. } => "rope",
503 Self::NormRope { .. } => "norm_rope",
504 Self::DepthwiseConv1d { .. } => "conv1d",
505 Self::PagedAttention { .. } => "paged_attention",
506 Self::PagedWrite => "paged_write",
507 Self::Softmax => "softmax",
508 Self::LogSumExp => "logsumexp",
509 Self::CrossEntropy => "cross_entropy",
510 Self::CrossEntropyVjp => "cross_entropy_vjp",
511 Self::Attention { .. } => "scaled_dot_product_attention",
512 Self::GatedDelta { .. } | Self::GatedDeltaSlots => "gated_delta",
513 Self::GatherQmm { .. } => "gather_qmm",
514 Self::GatherMm => "gather_mm",
515 }
516 }
517
518 pub(crate) fn outputs(&self, inputs: &[Array]) -> Result<Vec<(DType, Vec<usize>)>> {
520 let arity = match self {
521 Self::Softmax | Self::LogSumExp => 1,
522 Self::RmsNorm { .. } | Self::CrossEntropy | Self::DepthwiseConv1d { .. } => 2,
523 Self::PagedAttention { .. } => 5,
524 Self::PagedWrite => 3,
525 Self::AddRmsNorm { .. } | Self::RmsNormVjp { .. } | Self::LayerNorm { .. } => 3,
526 Self::LayerNormVjp { .. } | Self::CrossEntropyVjp => 4,
527 Self::Rope { .. } => inputs.len().clamp(2, 3),
528 Self::NormRope { cache, .. } => inputs
529 .len()
530 .clamp(2 + usize::from(*cache), 3 + usize::from(*cache)),
531 Self::Attention { mask, sinks, .. } => 3 + usize::from(*mask) + usize::from(*sinks),
532 Self::GatedDelta { .. } => 6,
533 Self::GatedDeltaSlots => 7,
534 Self::GatherQmm { .. } => inputs.len().clamp(6, 7),
535 Self::GatherMm => inputs.len().clamp(4, 5),
536 };
537 if inputs.len() != arity || inputs[0].ndim() == 0 {
538 return Err(Error::InvalidArgument(format!(
539 "{}: {arity} inputs expected, got {}",
540 self.name(),
541 inputs.len()
542 )));
543 }
544 let x = &inputs[0];
545 let like = |a: &Array| (a.dtype(), a.shape().to_vec());
546 Ok(match self {
547 Self::RmsNorm { .. }
548 | Self::LayerNorm { .. }
549 | Self::Rope { .. }
550 | Self::NormRope { .. }
551 | Self::Softmax => {
552 vec![like(x)]
553 }
554 Self::LogSumExp => {
555 let mut shape = x.shape().to_vec();
556 *shape.last_mut().expect("1-D") = 1;
557 vec![(x.dtype(), shape)]
558 }
559 Self::AddRmsNorm { .. } => vec![like(x), like(x)],
560 Self::PagedAttention { .. } | Self::PagedWrite => vec![like(x)],
561 Self::DepthwiseConv1d {
562 stride,
563 padding,
564 dilation,
565 } => {
566 let (l, k) = (x.shape()[1], inputs[1].shape()[1]);
567 let l_out = (l + 2 * padding - dilation * (k - 1) - 1) / stride + 1;
568 vec![(x.dtype(), vec![x.shape()[0], l_out, x.shape()[2]])]
569 }
570 Self::RmsNormVjp { .. } => vec![like(x), like(&inputs[1])],
571 Self::LayerNormVjp { .. } => vec![like(x), like(&inputs[1]), like(&inputs[2])],
572 Self::CrossEntropy => vec![(DType::F32, inputs[1].shape().to_vec())],
573 Self::CrossEntropyVjp => vec![like(x)],
574 Self::Attention { .. } => {
575 let mut shape = x.shape().to_vec();
576 shape[3] = inputs[2].shape()[3];
577 vec![(x.dtype(), shape)]
578 }
579 Self::GatedDeltaSlots => {
580 vec![(x.dtype(), inputs[2].shape().to_vec()), like(&inputs[5])]
581 }
582 Self::GatedDelta { steps } => {
583 let state = match steps {
584 false => inputs[5].shape().to_vec(),
585 true => {
586 let mut shape = inputs[5].shape().to_vec();
587 shape.insert(1, x.shape()[1]);
588 shape
589 }
590 };
591 vec![
592 (x.dtype(), inputs[2].shape().to_vec()),
593 (inputs[5].dtype(), state),
594 ]
595 }
596 Self::GatherMm => {
597 let b = inputs[1].shape();
598 let mut shape = inputs[2].shape().to_vec();
599 shape.extend([x.shape()[x.ndim() - 2], b[b.len() - 1]]);
600 vec![(x.dtype(), shape)]
601 }
602 Self::GatherQmm {
603 bits, transpose, ..
604 } => {
605 let w = inputs[1].shape();
606 let n = match transpose {
607 true => w[w.len() - 2],
608 false => w[w.len() - 1] * 32 / usize::from(*bits),
609 };
610 let mut shape = inputs[4].shape().to_vec();
611 shape.extend([x.shape()[x.ndim() - 2], n]);
612 vec![(x.dtype(), shape)]
613 }
614 })
615 }
616
617 pub(crate) fn fallback(&self, inputs: &[Array]) -> Result<Vec<Array>> {
619 let x = &inputs[0];
620 let f32x = || astype(x, DType::F32);
621 let out = match self {
622 Self::RmsNorm { eps } => {
623 let xf = f32x()?;
624 let n = rsqrt(&add(mean(&multiply(&xf, &xf)?, &[-1], true)?, float(*eps))?)?;
625 let y = multiply(&xf, &n)?.astype(x.dtype())?;
626 let w = &inputs[1];
627 vec![if w.ndim() == 0 { y } else { multiply(&y, w)? }]
628 }
629 Self::AddRmsNorm { eps } => {
630 let h = add(x, &inputs[1])?;
631 let op = Self::RmsNorm { eps: *eps };
632 let y = record_or_fallback(op, vec![h.clone(), inputs[2].clone()])?;
633 vec![h, y.into_iter().next().expect("one output")]
634 }
635 Self::LayerNorm { eps } => {
636 let xf = f32x()?;
637 let xc = subtract(&xf, mean(&xf, &[-1], true)?)?;
638 let v = mean(&multiply(&xc, &xc)?, &[-1], true)?;
639 let mut y = multiply(&xc, rsqrt(&add(&v, float(*eps))?)?)?.astype(x.dtype())?;
640 if inputs[1].ndim() != 0 {
641 y = multiply(&y, &inputs[1])?;
642 }
643 if inputs[2].ndim() != 0 {
644 y = add(&y, &inputs[2])?;
645 }
646 vec![y]
647 }
648 Self::RmsNormVjp { eps } => {
649 let (w, g) = (&inputs[1], &inputs[2]);
650 let n = rsqrt(&add(mean(&multiply(x, x)?, &[-1], true)?, float(*eps))?)?;
651 let n3 = multiply(&multiply(&n, &n)?, &n)?;
652 let gw = multiply(g, w)?;
653 let t = mean(&multiply(&gw, x)?, &[-1], true)?;
654 let dx = subtract(multiply(&gw, &n)?, multiply(multiply(x, &t)?, &n3)?)?;
655 let dw = match w.ndim() {
656 0 => scalar(0.0, w.dtype())?,
657 _ => sum_rows(&multiply(g, multiply(x, &n)?)?)?,
658 };
659 vec![dx, dw]
660 }
661 Self::LayerNormVjp { eps } => {
662 let (w, b, g) = (&inputs[1], &inputs[2], &inputs[3]);
663 let mu = mean(x, &[-1], true)?;
664 let xc = subtract(x, &mu)?;
665 let var = mean(&multiply(&xc, &xc)?, &[-1], true)?;
666 let n = rsqrt(&add(&var, float(*eps))?)?;
667 let n3 = multiply(&multiply(&n, &n)?, &n)?;
668 let wg = multiply(w, g)?;
669 let mwg = mean(&wg, &[-1], true)?;
670 let mwgxc = mean(&multiply(&wg, &xc)?, &[-1], true)?;
671 let dx = subtract(
672 multiply(subtract(&wg, &mwg)?, &n)?,
673 multiply(multiply(&xc, &mwgxc)?, &n3)?,
674 )?;
675 let dw = match w.ndim() {
676 0 => scalar(0.0, w.dtype())?,
677 _ => sum_rows(&multiply(g, multiply(&xc, &n)?)?)?,
678 };
679 let db = match b.ndim() {
680 0 => scalar(0.0, b.dtype())?,
681 _ => sum_rows(g)?,
682 };
683 vec![dx, dw, db]
684 }
685 Self::Attention {
686 scale,
687 causal,
688 mask,
689 sinks,
690 } => vec![attention_graph(
691 inputs,
692 float(*scale),
693 *causal,
694 *mask,
695 *sinks,
696 )?],
697 Self::Rope {
698 dims,
699 traditional,
700 base,
701 scale,
702 forward,
703 } => vec![rope_graph(
704 inputs,
705 *dims,
706 *traditional,
707 float(*base),
708 float(*scale),
709 *forward,
710 )?],
711 Self::NormRope {
712 eps,
713 dims,
714 traditional,
715 base,
716 scale,
717 offset,
718 cache,
719 } => {
720 let at = usize::from(*cache);
721 let (x, w) = (&inputs[at], &inputs[at + 1]);
722 let mut h = x.clone();
723 if let Some(eps) = eps {
724 let op = Self::RmsNorm { eps: *eps };
725 h = record_or_fallback(op, vec![h, w.clone()])?.swap_remove(0);
726 }
727 let mut rope_inputs = vec![h, Array::scalar(*offset as i32)];
728 rope_inputs.extend(inputs.get(at + 2).cloned());
729 let op = Self::Rope {
730 dims: *dims,
731 traditional: *traditional,
732 base: *base,
733 scale: *scale,
734 forward: true,
735 };
736 let r = record_or_fallback(op, rope_inputs)?.swap_remove(0);
737 vec![match cache {
738 true => {
739 let mut region = vec![SliceArg::full(); 4];
740 let end = (offset + r.shape()[2]) as isize;
741 region[2] = SliceArg::new(Some(*offset as isize), Some(end), 1);
742 crate::slice_update(&inputs[0], &r, ®ion)?
743 }
744 false => r,
745 }]
746 }
747 Self::DepthwiseConv1d {
748 stride,
749 padding,
750 dilation,
751 } => {
752 let c = x.shape()[2];
753 vec![crate::conv::general(
754 x,
755 &inputs[1],
756 (*stride, *padding, *dilation, c),
757 )?]
758 }
759 Self::PagedAttention { scale } => vec![paged_attention_graph(inputs, float(*scale))?],
760 Self::PagedWrite => vec![paged_write_graph(inputs)?],
761 Self::Softmax => vec![softmax_graph(x)?],
762 Self::GatedDelta { steps } => gated_delta_graph(inputs, *steps)?,
763 Self::GatedDeltaSlots => {
764 let (pool, slots) = (&inputs[5], &inputs[6]);
766 let states = crate::take(pool, slots, Some(0))?;
767 let mut args = inputs[..5].to_vec();
768 args.push(states);
769 let mut out = gated_delta_graph(&args, false)?;
770 let new = out.pop().expect("two outputs");
771 let at = broadcast_to(&reshape(slots, &[-1, 1, 1, 1])?, new.shape())?;
772 let pool = crate::put_along_axis(pool, &at, &new, 0)?;
773 vec![out.pop().expect("two outputs"), pool]
774 }
775 Self::GatherMm => {
776 let rows = |a: &Array, indices: &Array| -> Result<Array> {
777 let nd = a.ndim();
778 let shape = [-1, a.shape()[nd - 2] as isize, a.shape()[nd - 1] as isize];
779 crate::take(&reshape(a, &shape)?, indices, Some(0))
780 };
781 vec![crate::matmul(
782 &rows(x, &inputs[2])?,
783 &rows(&inputs[1], &inputs[3])?,
784 )?]
785 }
786 Self::GatherQmm {
787 group_size,
788 bits,
789 transpose,
790 } => vec![gather_qmm_graph(
791 inputs,
792 *group_size,
793 u32::from(*bits),
794 *transpose,
795 )?],
796 Self::LogSumExp => vec![logsumexp_graph(x)?],
797 Self::CrossEntropy => {
798 let classes = x.shape()[x.ndim() - 1];
799 let y = wrap(&inputs[1], classes)?;
800 let hit = one_hot(&y, classes, DType::Bool)?;
801 let score = sum(&where_(&hit, x, 0.0)?, &[-1], false)?;
802 let lse = squeeze(&logsumexp_graph(x)?, &[-1])?;
803 vec![subtract(&lse, &score)?.astype(DType::F32)?]
804 }
805 Self::CrossEntropyVjp => {
806 let classes = x.shape()[x.ndim() - 1];
807 let (y, loss, g) = (wrap(&inputs[1], classes)?, &inputs[2], &inputs[3]);
808 let hit = one_hot(&y, classes, DType::Bool)?;
809 let score = sum(&where_(&hit, x, 0.0)?, &[-1], false)?;
810 let lse = add(loss, score.astype(DType::F32)?)?;
811 let p = exp(&subtract(&f32x()?, expand_dims(&lse, -1)?)?)?;
812 let gx = multiply(
813 expand_dims(g, -1)?,
814 subtract(&p, one_hot(&y, classes, DType::F32)?)?,
815 )?;
816 vec![gx.astype(x.dtype())?]
817 }
818 };
819 Ok(out)
820 }
821
822 pub(crate) fn vjp(
824 &self,
825 inputs: &[Array],
826 outputs: &[Array],
827 cotangents: &[Array],
828 wanted: &[bool],
829 ) -> Result<Vec<Option<Array>>> {
830 let g = &cotangents[0];
831 let pick = |all: Vec<Array>| -> Vec<Option<Array>> {
832 all.into_iter()
833 .zip(wanted)
834 .map(|(a, &w)| w.then_some(a))
835 .collect()
836 };
837 match self {
838 Self::RmsNorm { eps } => {
839 let op = Self::RmsNormVjp { eps: *eps };
840 let grads =
841 record_or_fallback(op, vec![inputs[0].clone(), inputs[1].clone(), g.clone()])?;
842 Ok(pick(grads))
843 }
844 Self::LayerNorm { eps } => {
845 let op = Self::LayerNormVjp { eps: *eps };
846 let mut all = inputs.to_vec();
847 all.push(g.clone());
848 Ok(pick(record_or_fallback(op, all)?))
849 }
850 Self::Rope { forward, .. } => {
851 if wanted[1..].iter().any(|&w| w) {
852 return Err(Error::InvalidArgument(
853 "rope: no gradient with respect to the offset or frequencies".into(),
854 ));
855 }
856 let Self::Rope {
857 dims,
858 traditional,
859 base,
860 scale,
861 ..
862 } = *self
863 else {
864 unreachable!()
865 };
866 let inverse = Self::Rope {
867 dims,
868 traditional,
869 base,
870 scale,
871 forward: !forward,
872 };
873 let mut all = inputs.to_vec();
874 all[0] = g.clone();
875 let mut out = vec![Some(one(inverse, all)?)];
876 out.extend(std::iter::repeat_n(None, inputs.len() - 1));
877 Ok(out)
878 }
879 Self::Softmax => {
880 let s = &outputs[0];
881 let sg = multiply(s, g)?;
882 let dx = subtract(&sg, multiply(s, sum(&sg, &[-1], true)?)?)?;
883 Ok(vec![Some(dx)])
884 }
885 Self::LogSumExp => {
886 let s = one(Self::Softmax, vec![inputs[0].clone()])?;
887 Ok(vec![Some(multiply(g, &s)?)])
888 }
889 Self::CrossEntropy => {
890 if wanted[1] {
891 return Err(Error::InvalidArgument(
892 "cross_entropy: no gradient with respect to the targets".into(),
893 ));
894 }
895 let all = vec![
896 inputs[0].clone(),
897 inputs[1].clone(),
898 outputs[0].clone(),
899 g.clone(),
900 ];
901 Ok(vec![Some(one(Self::CrossEntropyVjp, all)?), None])
902 }
903 Self::AddRmsNorm { .. }
904 | Self::NormRope { .. }
905 | Self::PagedAttention { .. }
906 | Self::PagedWrite
907 | Self::DepthwiseConv1d { .. }
908 | Self::RmsNormVjp { .. }
909 | Self::LayerNormVjp { .. }
910 | Self::CrossEntropyVjp
911 | Self::Attention { .. }
912 | Self::GatedDelta { .. }
913 | Self::GatedDeltaSlots
914 | Self::GatherQmm { .. }
915 | Self::GatherMm => self.fallback_vjp(inputs, cotangents, wanted),
916 }
917 }
918
919 fn fallback_vjp(
921 &self,
922 inputs: &[Array],
923 cotangents: &[Array],
924 wanted: &[bool],
925 ) -> Result<Vec<Option<Array>>> {
926 let active: Vec<usize> = (0..inputs.len())
927 .filter(|&i| wanted[i] && inputs[i].dtype().category() == Category::Float)
928 .collect();
929 let primals: Vec<Array> = active.iter().map(|&i| inputs[i].clone()).collect();
930 let f = |ps: &[Array]| {
931 let mut all = inputs.to_vec();
932 for (p, &i) in ps.iter().zip(&active) {
933 all[i] = p.clone();
934 }
935 self.fallback(&all)
936 };
937 let (_, grads) = crate::vjp(f, &primals, cotangents)?;
938 let mut out = vec![None; inputs.len()];
939 for (g, &i) in grads.into_iter().zip(&active) {
940 out[i] = Some(g);
941 }
942 Ok(out)
943 }
944
945 pub(crate) fn jvp(&self, inputs: &[Array], tangents: &[Option<Array>]) -> Result<Vec<Array>> {
947 let active: Vec<usize> = (0..inputs.len())
948 .filter(|&i| tangents[i].is_some() && inputs[i].dtype().category() == Category::Float)
949 .collect();
950 let primals: Vec<Array> = active.iter().map(|&i| inputs[i].clone()).collect();
951 let ts: Vec<Array> = active
952 .iter()
953 .map(|&i| tangents[i].clone().expect("active"))
954 .collect();
955 let f = |ps: &[Array]| {
956 let mut all = inputs.to_vec();
957 for (p, &i) in ps.iter().zip(&active) {
958 all[i] = p.clone();
959 }
960 self.fallback(&all)
961 };
962 Ok(crate::jvp(f, &primals, &ts)?.1)
963 }
964
965 pub(crate) fn vmap(
969 &self,
970 inputs: &[Array],
971 axes: &[Option<usize>],
972 ) -> Result<(Vec<Array>, usize)> {
973 let rest_unbatched = axes[1..].iter().all(Option::is_none);
974 let front = |i: usize| -> Result<Array> {
975 let a = &inputs[i];
976 match axes[i] {
977 Some(ax) => crate::batching::move_axis(a, ax, 0),
978 None => {
979 let b = inputs
980 .iter()
981 .zip(axes)
982 .find_map(|(a, ax)| ax.map(|ax| a.shape()[ax]))
983 .expect("one batched input");
984 let mut shape = vec![b];
985 shape.extend_from_slice(a.shape());
986 broadcast_to(&expand_dims(a, 0)?, &shape)
987 }
988 }
989 };
990 let simple = match self {
991 Self::RmsNorm { .. } | Self::LayerNorm { .. } | Self::Softmax | Self::LogSumExp => {
992 rest_unbatched
993 }
994 Self::Rope { .. } => rest_unbatched && inputs[1].size() == 1,
995 Self::CrossEntropy => true,
996 _ => false,
997 };
998 if simple {
999 let mut all = inputs.to_vec();
1000 all[0] = front(0)?;
1001 if *self == Self::CrossEntropy {
1002 all[1] = front(1)?;
1003 }
1004 return Ok((record_or_fallback(self.clone(), all)?, 0));
1005 }
1006 let f = |xs: &[Array]| self.fallback(xs);
1007 Ok((crate::transforms::batch(&f, inputs, axes)?, 0))
1008 }
1009
1010 pub(crate) fn eval_cpu(&self, operands: &[Operand<'_>]) -> Result<Vec<crate::array::Data>> {
1012 let leaves = operands
1013 .iter()
1014 .map(|(meta, data)| {
1015 let buffer = crate::kernels::contiguous(meta, data)?;
1016 Array::from_buffer(buffer, meta.dtype, &meta.shape)
1017 })
1018 .collect::<Result<Vec<_>>>()?;
1019 let previous = default_stream();
1020 set_default_stream(Stream::default_for(Device::cpu()));
1021 let outputs = self.fallback(&leaves);
1022 set_default_stream(previous);
1023 let outputs = outputs?;
1024 crate::eval(&outputs)?;
1025 outputs
1026 .iter()
1027 .map(|a| {
1028 let data = a.data().expect("evaluated");
1029 Ok(crate::array::Data::new(crate::kernels::contiguous(
1030 a.out(),
1031 data,
1032 )?))
1033 })
1034 .collect()
1035 }
1036}
1037
1038fn sum_rows(g: &Array) -> Result<Array> {
1040 let axes: Vec<isize> = (0..g.ndim() as isize - 1).collect();
1041 sum(g, &axes, false)
1042}
1043
1044fn rope_graph(
1046 inputs: &[Array],
1047 dims: usize,
1048 traditional: bool,
1049 base: f32,
1050 scale: f32,
1051 forward: bool,
1052) -> Result<Array> {
1053 let x = &inputs[0];
1054 let shape: Vec<isize> = x.shape().iter().map(|&d| d as isize).collect();
1055 let nd = x.ndim();
1056 let (b, t, d) = (x.shape()[0], x.shape()[nd - 2], x.shape()[nd - 1]);
1057 let x4 = reshape(x, &[b as isize, -1, t as isize, d as isize])?;
1058 let dtype = x.dtype();
1059 let half = dims / 2;
1060 let offset = &inputs[1];
1061 let offset = if offset.size() > 1 {
1062 reshape(offset, &[-1, 1, 1])?
1063 } else {
1064 reshape(offset, &[])?
1065 };
1066 let positions = multiply(
1067 add(arange(0.0, t as f64, 1.0, DType::F32)?, &offset)?,
1068 f64::from(scale),
1069 )?;
1070 let inv_freqs = match inputs.get(2) {
1071 Some(f) => divide(1.0, f)?,
1072 None => exp(&multiply(
1073 arange(0.0, -(half as f64), -1.0, DType::F32)?,
1074 f64::from(base.ln() / half as f32),
1075 )?)?,
1076 };
1077 let theta = multiply(expand_dims(&positions, -1)?, &inv_freqs)?;
1078 let (c, s) = (cos(&theta)?.astype(dtype)?, sin(&theta)?.astype(dtype)?);
1079 let (start2, step) = if traditional { (1, 2) } else { (half, 1) };
1080 let x1 = last(&x4, 0, step, half)?;
1081 let x2 = last(&x4, start2, step, half)?;
1082 let (r1, r2) = if forward {
1083 (
1084 subtract(multiply(&x1, &c)?, multiply(&x2, &s)?)?,
1085 add(multiply(&x1, &s)?, multiply(&x2, &c)?)?,
1086 )
1087 } else {
1088 (
1089 add(multiply(&x2, &s)?, multiply(&x1, &c)?)?,
1090 subtract(multiply(&x2, &c)?, multiply(&x1, &s)?)?,
1091 )
1092 };
1093 let full = |r: Array| broadcast_to(&r, x1.shape());
1094 let out = update_last(&x4, &full(r1)?, 0, step)?;
1095 let out = update_last(&out, &full(r2)?, start2, step)?;
1096 reshape(&out, &shape)
1097}
1098
1099#[derive(Debug, Clone, Copy)]
1101pub struct Rotation<'a> {
1102 pub dims: usize,
1104 pub traditional: bool,
1106 pub base: Option<f32>,
1108 pub scale: f32,
1110 pub freqs: Option<&'a Array>,
1112}
1113
1114const NORM_ROPE_MAX: usize = 1024;
1116
1117pub fn norm_rope(
1130 x: &Array,
1131 norm: Option<(&Array, f32)>,
1132 rotation: Rotation<'_>,
1133 offset: usize,
1134 cache: Option<&Array>,
1135) -> Result<Array> {
1136 check_inputs(&[x])?;
1137 let mut inputs = Vec::with_capacity(4);
1138 let mut fused = x.ndim() == 4
1139 && x.dtype().category() == Category::Float
1140 && x.shape()[3] <= NORM_ROPE_MAX
1141 && offset + x.shape()[2] <= i32::MAX as usize;
1142 if let Some(c) = cache {
1143 check_inputs(&[c])?;
1144 let s = x.shape();
1145 let fits = c.ndim() == 4
1146 && c.shape()[..2] == s[..2]
1147 && c.shape()[3] == s[3]
1148 && c.shape()[2] >= offset + s[2];
1149 if !fits {
1150 return Err(Error::InvalidArgument(format!(
1151 "norm_rope: a cache of shape {:?} cannot take {:?} at position {offset}",
1152 c.shape(),
1153 s
1154 )));
1155 }
1156 fused &= c.dtype() == x.dtype();
1157 inputs.push(c.clone());
1158 }
1159 if let Some((w, _)) = norm {
1160 check_inputs(&[w])?;
1161 fused &= w.dtype() == x.dtype() && w.ndim() == 1 && w.size() == x.shape()[x.ndim() - 1];
1162 }
1163 let freqs_valid = rotation
1164 .freqs
1165 .is_none_or(|f| f.error().is_none() && f.ndim() == 1 && f.size() == rotation.dims / 2);
1166 let dims_valid = rotation.dims > 0
1167 && rotation.dims.is_multiple_of(2)
1168 && x.ndim() > 0
1169 && rotation.dims <= x.shape()[x.ndim() - 1];
1170 if !fused || !freqs_valid || !dims_valid || rotation.base.is_some() == rotation.freqs.is_some()
1171 {
1172 return norm_rope_graph(x, norm, rotation, offset, cache);
1174 }
1175 inputs.push(x.clone());
1176 inputs.push(match norm {
1177 Some((w, _)) => w.clone(),
1178 None => scalar(1.0, x.dtype())?,
1179 });
1180 if let Some(f) = rotation.freqs {
1181 inputs.push(astype(f, DType::F32)?);
1182 }
1183 let op = FastOp::NormRope {
1184 eps: norm.map(|(_, eps)| eps.to_bits()),
1185 dims: rotation.dims,
1186 traditional: rotation.traditional,
1187 base: rotation.base.unwrap_or(1.0).to_bits(),
1188 scale: rotation.scale.to_bits(),
1189 offset,
1190 cache: cache.is_some(),
1191 };
1192 one(op, inputs)
1193}
1194
1195fn norm_rope_graph(
1197 x: &Array,
1198 norm: Option<(&Array, f32)>,
1199 rotation: Rotation<'_>,
1200 offset: usize,
1201 cache: Option<&Array>,
1202) -> Result<Array> {
1203 let x = match norm {
1204 Some((w, eps)) => rms_norm(x, Some(w), eps)?,
1205 None => x.clone(),
1206 };
1207 let r = rope(
1208 &x,
1209 rotation.dims,
1210 rotation.traditional,
1211 rotation.base,
1212 rotation.scale,
1213 &Array::scalar(offset as i32),
1214 rotation.freqs,
1215 )?;
1216 match cache {
1217 Some(c) => {
1218 let mut region = vec![SliceArg::full(); 4];
1219 region[2] = SliceArg::new(
1220 Some(offset as isize),
1221 Some((offset + r.shape()[2]) as isize),
1222 1,
1223 );
1224 crate::slice_update(c, &r, ®ion)
1225 }
1226 None => Ok(r),
1227 }
1228}
1229
1230pub fn paged_attention(
1246 q: &Array,
1247 k_cache: &Array,
1248 v_cache: &Array,
1249 block_tables: &Array,
1250 lengths: &Array,
1251 scale: f32,
1252) -> Result<Array> {
1253 check_inputs(&[q, k_cache, v_cache, block_tables, lengths])?;
1254 let invalid = |msg: String| Err(Error::InvalidArgument(format!("paged_attention: {msg}")));
1255 floating("paged_attention", q.dtype())?;
1256 if q.ndim() != 4 || k_cache.ndim() != 4 || k_cache.shape() != v_cache.shape() {
1257 return invalid(format!(
1258 "q must be [B, Hq, L, D] and the caches [blocks, Hkv, block_size, D], got {:?}, \
1259 {:?}, {:?}",
1260 q.shape(),
1261 k_cache.shape(),
1262 v_cache.shape()
1263 ));
1264 }
1265 let (b, hq, d) = (q.shape()[0], q.shape()[1], q.shape()[3]);
1266 let hkv = k_cache.shape()[1];
1267 if k_cache.shape()[3] != d || hkv == 0 || hq % hkv != 0 {
1268 return invalid(format!(
1269 "q's heads ({hq}) must be a multiple of the caches' ({hkv}), with head size {d}"
1270 ));
1271 }
1272 let integer = |a: &Array| matches!(a.dtype().category(), Category::Signed | Category::Unsigned);
1273 if block_tables.ndim() != 2 || block_tables.shape()[0] != b || !integer(block_tables) {
1274 return invalid(format!(
1275 "block_tables must be [{b}, max_blocks] integers, got {} {:?}",
1276 block_tables.dtype(),
1277 block_tables.shape()
1278 ));
1279 }
1280 if lengths.shape() != [b] || !integer(lengths) {
1281 return invalid(format!(
1282 "lengths must be [{b}] integers, got {} {:?}",
1283 lengths.dtype(),
1284 lengths.shape()
1285 ));
1286 }
1287 let dtype = q.dtype();
1288 let inputs = vec![
1289 q.clone(),
1290 astype(k_cache, dtype)?,
1291 astype(v_cache, dtype)?,
1292 astype(block_tables, DType::I32)?,
1293 astype(lengths, DType::I32)?,
1294 ];
1295 let op = FastOp::PagedAttention {
1296 scale: scale.to_bits(),
1297 };
1298 let small = k_cache.size() < u32::MAX as usize;
1300 if matches!(d, 64 | 128 | 256) && k_cache.shape()[2].is_power_of_two() && small {
1301 one(op, inputs)
1302 } else {
1303 Ok(op.fallback(&inputs)?.swap_remove(0))
1304 }
1305}
1306
1307pub fn paged_write(cache: &Array, new: &Array, slots: &Array) -> Result<Array> {
1312 check_inputs(&[cache, new, slots])?;
1313 let invalid = |msg: String| Err(Error::InvalidArgument(format!("paged_write: {msg}")));
1314 let fits = cache.ndim() == 4
1315 && new.ndim() == 3
1316 && new.shape()[1] == cache.shape()[1]
1317 && new.shape()[2] == cache.shape()[3];
1318 if !fits || slots.shape() != [new.shape().first().copied().unwrap_or(0)] {
1319 return invalid(format!(
1320 "cache must be [blocks, Hkv, block_size, D], new [T, Hkv, D] and slots [T], got \
1321 {:?}, {:?}, {:?}",
1322 cache.shape(),
1323 new.shape(),
1324 slots.shape()
1325 ));
1326 }
1327 if !matches!(
1328 slots.dtype().category(),
1329 Category::Signed | Category::Unsigned
1330 ) {
1331 return invalid(format!("slots must be integers, got {}", slots.dtype()));
1332 }
1333 let inputs = vec![
1334 cache.clone(),
1335 astype(new, cache.dtype())?,
1336 astype(slots, DType::I32)?,
1337 ];
1338 one(FastOp::PagedWrite, inputs)
1339}
1340
1341fn paged_keys(cache: &Array, tables: &Array) -> Result<Array> {
1343 let (b, max_blocks) = (tables.shape()[0], tables.shape()[1]);
1344 let (hkv, bs, d) = (cache.shape()[1], cache.shape()[2], cache.shape()[3]);
1345 let gathered = crate::take(cache, &reshape(tables, &[-1])?, Some(0))?; let gathered = reshape(
1347 &gathered,
1348 &[
1349 b as isize,
1350 max_blocks as isize,
1351 hkv as isize,
1352 bs as isize,
1353 d as isize,
1354 ],
1355 )?;
1356 let gathered = crate::transpose(&gathered, &[0, 2, 1, 3, 4])?;
1357 reshape(
1358 &gathered,
1359 &[
1360 b as isize,
1361 hkv as isize,
1362 (max_blocks * bs) as isize,
1363 d as isize,
1364 ],
1365 )
1366}
1367
1368fn paged_attention_graph(inputs: &[Array], scale: f32) -> Result<Array> {
1371 let [q, k_cache, v_cache, tables, lengths] = inputs else {
1372 unreachable!("five inputs")
1373 };
1374 let (b, lq) = (q.shape()[0], q.shape()[2]);
1375 let (k, v) = (paged_keys(k_cache, tables)?, paged_keys(v_cache, tables)?);
1376 let s = k.shape()[2];
1377 let positions = reshape(
1379 &arange(0.0, s as f64, 1.0, DType::I32)?,
1380 &[1, 1, 1, s as isize],
1381 )?;
1382 let queries = reshape(
1383 &arange(0.0, lq as f64, 1.0, DType::I32)?,
1384 &[1, 1, lq as isize, 1],
1385 )?;
1386 let end = add(
1387 reshape(lengths, &[b as isize, 1, 1, 1])?,
1388 subtract(&queries, (lq - 1) as f64)?,
1389 )?;
1390 let mask = less(&positions, &end)?;
1391 scaled_dot_product_attention(q, &k, &v, scale, AttentionMask::Array(&mask), None)
1392}
1393
1394fn paged_write_graph(inputs: &[Array]) -> Result<Array> {
1397 let [cache, new, slots] = inputs else {
1398 unreachable!("three inputs")
1399 };
1400 let (blocks, hkv, bs, d) = (
1401 cache.shape()[0],
1402 cache.shape()[1],
1403 cache.shape()[2],
1404 cache.shape()[3],
1405 );
1406 let rows = reshape(
1407 &crate::transpose(cache, &[0, 2, 1, 3])?,
1408 &[(blocks * bs) as isize, hkv as isize, d as isize],
1409 )?;
1410 let at = broadcast_to(&reshape(slots, &[-1, 1, 1])?, new.shape())?;
1411 let rows = crate::put_along_axis(&rows, &at, new, 0)?;
1412 let rows = reshape(
1413 &rows,
1414 &[blocks as isize, bs as isize, hkv as isize, d as isize],
1415 )?;
1416 crate::transpose(&rows, &[0, 2, 1, 3])
1417}
1418
1419pub fn scaled_dot_product_attention(
1428 q: &Array,
1429 k: &Array,
1430 v: &Array,
1431 scale: f32,
1432 mask: AttentionMask<'_>,
1433 sinks: Option<&Array>,
1434) -> Result<Array> {
1435 check_inputs(&[q, k, v])?;
1436 let invalid = |msg: String| {
1437 Err(Error::InvalidArgument(format!(
1438 "scaled_dot_product_attention: {msg}"
1439 )))
1440 };
1441 if [q, k, v].iter().any(|a| a.ndim() != 4) {
1442 return invalid(format!(
1443 "q, k and v must be 4-D, got {:?}, {:?} and {:?}",
1444 q.shape(),
1445 k.shape(),
1446 v.shape()
1447 ));
1448 }
1449 let (qs, ks, vs) = (q.shape(), k.shape(), v.shape());
1450 if ks[0] != qs[0] || vs[0] != qs[0] || qs[3] != ks[3] || ks[1] != vs[1] || ks[2] != vs[2] {
1451 return invalid(format!("incompatible shapes {qs:?}, {ks:?} and {vs:?}"));
1452 }
1453 if ks[1] == 0 || qs[1] % ks[1] != 0 {
1454 return invalid(format!(
1455 "the query heads ({}) must be a multiple of the key heads ({})",
1456 qs[1], ks[1]
1457 ));
1458 }
1459 let dtype = crate::promote_types(crate::promote_types(q.dtype(), k.dtype()), v.dtype());
1460 floating("scaled_dot_product_attention", dtype)?;
1461 let mut inputs = vec![astype(q, dtype)?, astype(k, dtype)?, astype(v, dtype)?];
1462 let (causal, has_mask) = match mask {
1463 AttentionMask::None => (false, false),
1464 AttentionMask::Causal => (true, false),
1465 AttentionMask::Array(m) => {
1466 check_inputs(&[m])?;
1467 if m.ndim() > 4 {
1468 return invalid(format!(
1469 "the mask must have at most 4 dimensions, got {:?}",
1470 m.shape()
1471 ));
1472 }
1473 let m = if m.dtype() == DType::Bool {
1474 m.clone()
1475 } else if crate::promote_types(m.dtype(), dtype) == dtype {
1476 astype(m, dtype)?
1477 } else {
1478 return invalid(format!("a {} mask does not promote to {dtype}", m.dtype()));
1479 };
1480 inputs.push(broadcast_to(&m, &[qs[0], qs[1], qs[2], ks[2]])?);
1481 (false, true)
1482 }
1483 };
1484 if let Some(s) = sinks {
1485 check_inputs(&[s])?;
1486 if s.ndim() != 1 || s.size() != qs[1] || crate::promote_types(s.dtype(), dtype) != dtype {
1487 return invalid(format!(
1488 "sinks must be [{}] of a type promoting to {dtype}",
1489 qs[1]
1490 ));
1491 }
1492 inputs.push(astype(s, dtype)?);
1493 }
1494 let op = FastOp::Attention {
1495 scale: scale.to_bits(),
1496 causal,
1497 mask: has_mask,
1498 sinks: sinks.is_some(),
1499 };
1500 if on_gpu(dtype, q.size()) && attention_kernel(&inputs, causal, has_mask) {
1501 Ok(Array::from_op(Primitive::Fast(op), inputs)?.swap_remove(0))
1502 } else {
1503 Ok(op.fallback(&inputs)?.swap_remove(0))
1504 }
1505}
1506
1507fn attention_kernel(inputs: &[Array], causal: bool, has_mask: bool) -> bool {
1510 let (q, k, v) = (&inputs[0], &inputs[1], &inputs[2]);
1511 let (lq, lk, d, dv) = (q.shape()[2], k.shape()[2], q.shape()[3], v.shape()[3]);
1512 let gqa = q.shape()[1] / k.shape()[1];
1513 if lk == 0 || lq == 0 {
1514 return false;
1515 }
1516 if lq > 8 {
1517 let head =
1518 (d == dv && matches!(d, 64 | 72 | 80 | 96 | 128 | 192 | 256)) || (d, dv) == (96, 64);
1519 let causal_ok = !causal || lq <= lk;
1520 let scores = q.shape()[0] * q.shape()[1] * lq * lk * q.dtype().size();
1527 let huge = scores > 4 << 30;
1528 let slow = match d {
1529 192 => !huge,
1530 256 => !(huge || (causal && lq >= 1024 && lk <= 8192)),
1531 _ => false,
1532 };
1533 head && causal_ok && !slow
1534 } else {
1535 let head = (d == dv && matches!(d, 64 | 96 | 128 | 256 | 512))
1536 || (d, dv) == (192, 128)
1537 || (d, dv) == (96, 64);
1538 let d512 = d != 512 || (lq == 1 && gqa == 8 && !has_mask && lk >= 1024);
1539 head && lq <= lk && lq * gqa <= 32 && d512
1540 }
1541}
1542
1543fn attention_graph(
1545 inputs: &[Array],
1546 scale: f32,
1547 causal: bool,
1548 has_mask: bool,
1549 has_sinks: bool,
1550) -> Result<Array> {
1551 let (q, k, v) = (&inputs[0], &inputs[1], &inputs[2]);
1552 let dtype = q.dtype();
1553 let (b, hq, lq) = (q.shape()[0], q.shape()[1], q.shape()[2]);
1554 let (hkv, lk) = (k.shape()[1], k.shape()[2]);
1555 let rep = hq / hkv;
1556 let i = |n: usize| n as isize;
1557 let mut q = multiply(q, f64::from(scale))?;
1558 let (mut k, mut v) = (k.clone(), v.clone());
1559 if rep > 1 {
1560 q = reshape(&q, &[i(b), i(hkv), i(rep), i(lq), i(q.shape()[3])])?;
1561 k = expand_dims(&k, 2)?;
1562 v = expand_dims(&v, 2)?;
1563 }
1564 let n = k.ndim();
1565 let mut axes: Vec<usize> = (0..n).collect();
1566 axes.swap(n - 2, n - 1);
1567 let mut scores = crate::matmul(&q, &crate::transpose(&k, &axes)?)?;
1568 let mask = if causal {
1569 let offset = lk as f64 - lq as f64;
1570 let qi = expand_dims(&arange(offset, offset + lq as f64, 1.0, DType::I32)?, 1)?;
1571 let ki = expand_dims(&arange(0.0, lk as f64, 1.0, DType::I32)?, 0)?;
1572 Some(crate::greater_equal(&qi, &ki)?)
1573 } else if has_mask {
1574 let m = &inputs[3];
1575 Some(if rep > 1 {
1576 reshape(m, &[i(b), i(hkv), i(rep), i(lq), i(lk)])?
1577 } else {
1578 m.clone()
1579 })
1580 } else {
1581 None
1582 };
1583 if let Some(m) = mask {
1584 scores = if m.dtype() == DType::Bool {
1585 let lowest = match dtype {
1586 DType::F16 => f64::from(crate::f16::MIN),
1587 DType::BF16 => f64::from(crate::bf16::MIN),
1588 DType::F64 => f64::MIN,
1589 _ => f64::from(f32::MIN),
1590 };
1591 where_(&m, &scores, lowest)?
1592 } else {
1593 add(&scores, &m)?
1594 };
1595 }
1596 let probs = if has_sinks {
1597 let s = astype(&scores, DType::F32)?;
1599 let sink = astype(inputs.last().expect("sinks"), DType::F32)?;
1600 let shape: Vec<isize> = if rep > 1 {
1601 vec![1, i(hkv), i(rep), 1, 1]
1602 } else {
1603 vec![1, i(hq), 1, 1]
1604 };
1605 let sink = reshape(&sink, &shape)?;
1606 let m = stop_gradient(&crate::maximum(&max(&s, &[-1], true)?, &sink)?)?;
1607 let e = exp(&subtract(&s, &m)?)?;
1608 let denom = add(sum(&e, &[-1], true)?, exp(&subtract(&sink, &m)?)?)?;
1609 astype(÷(&e, &denom)?, dtype)?
1610 } else if on_gpu(dtype, scores.size()) {
1611 crate::softmax(&scores, &[-1])?
1612 } else {
1613 softmax_graph(&scores)?
1614 };
1615 let out = crate::matmul(&probs, &v)?;
1616 if rep > 1 {
1617 reshape(&out, &[i(b), i(hq), i(lq), i(v.shape()[n - 1])])
1618 } else {
1619 Ok(out)
1620 }
1621}
1622
1623pub fn gated_delta(
1653 q: &Array,
1654 k: &Array,
1655 v: &Array,
1656 g: &Array,
1657 beta: &Array,
1658 state: &Array,
1659) -> Result<(Array, Array)> {
1660 gated_delta_op(q, k, v, g, beta, state, false)
1661}
1662
1663pub fn gated_delta_steps(
1669 q: &Array,
1670 k: &Array,
1671 v: &Array,
1672 g: &Array,
1673 beta: &Array,
1674 state: &Array,
1675) -> Result<(Array, Array)> {
1676 gated_delta_op(q, k, v, g, beta, state, true)
1677}
1678
1679pub fn gated_delta_slots(
1685 q: &Array,
1686 k: &Array,
1687 v: &Array,
1688 g: &Array,
1689 beta: &Array,
1690 pool: &Array,
1691 slots: &Array,
1692) -> Result<(Array, Array)> {
1693 check_inputs(&[pool, slots])?;
1694 let b = q.shape().first().copied().unwrap_or(0);
1695 let fits = pool.ndim() == 4
1696 && slots.shape() == [b]
1697 && matches!(
1698 slots.dtype().category(),
1699 Category::Signed | Category::Unsigned
1700 );
1701 if !fits {
1702 return Err(Error::InvalidArgument(format!(
1703 "gated_delta_slots: pool must be [slots, Hv, Dv, Dk] and slots [{b}] integers, got \
1704 {:?} and {} {:?}",
1705 pool.shape(),
1706 slots.dtype(),
1707 slots.shape()
1708 )));
1709 }
1710 let one = crate::take(pool, &reshape(&astype(slots, DType::I32)?, &[-1])?, Some(0))?;
1712 gated_delta_op(q, k, v, g, beta, &one, false)?;
1713 let inputs = vec![
1714 q.clone(),
1715 k.clone(),
1716 v.clone(),
1717 g.clone(),
1718 beta.clone(),
1719 pool.clone(),
1720 astype(slots, DType::I32)?,
1721 ];
1722 let kernel = on_gpu(q.dtype(), q.size())
1723 && q.dtype() == v.dtype()
1724 && q.shape()[3].is_multiple_of(32)
1725 && [g, beta, pool]
1726 .iter()
1727 .all(|a| matches!(a.dtype(), DType::F16 | DType::BF16 | DType::F32));
1728 let op = FastOp::GatedDeltaSlots;
1729 let mut out = if kernel {
1730 Array::from_op(Primitive::Fast(op), inputs)?
1731 } else {
1732 op.fallback(&inputs)?
1733 };
1734 let pool = out.pop().expect("two outputs");
1735 Ok((out.pop().expect("two outputs"), pool))
1736}
1737
1738fn gated_delta_op(
1739 q: &Array,
1740 k: &Array,
1741 v: &Array,
1742 g: &Array,
1743 beta: &Array,
1744 state: &Array,
1745 steps: bool,
1746) -> Result<(Array, Array)> {
1747 check_inputs(&[q, k, v, g, beta, state])?;
1748 let op = "gated_delta";
1749 for a in [q, k, v, g, beta, state] {
1750 floating(op, a.dtype())?;
1751 }
1752 let invalid = |msg: String| Err(Error::InvalidArgument(format!("{op}: {msg}")));
1753 if q.ndim() != 4 || q.shape() != k.shape() || v.ndim() != 4 {
1754 return invalid(format!(
1755 "q and k must be [B, T, Hk, Dk] and v [B, T, Hv, Dv], got {:?}, {:?}, {:?}",
1756 q.shape(),
1757 k.shape(),
1758 v.shape()
1759 ));
1760 }
1761 let (b, t, hk, dk) = (q.shape()[0], q.shape()[1], q.shape()[2], q.shape()[3]);
1762 let (hv, dv) = (v.shape()[2], v.shape()[3]);
1763 if v.shape()[..2] != [b, t] || hk == 0 || hv % hk != 0 {
1764 return invalid(format!(
1765 "v must be [{b}, {t}, Hv, Dv] with Hv a multiple of {hk}, got {:?}",
1766 v.shape()
1767 ));
1768 }
1769 if g.shape() != [b, t, hv] || beta.shape() != [b, t, hv] {
1770 return invalid(format!(
1771 "g and beta must be [{b}, {t}, {hv}], got {:?} and {:?}",
1772 g.shape(),
1773 beta.shape()
1774 ));
1775 }
1776 if state.shape() != [b, hv, dv, dk] {
1777 return invalid(format!(
1778 "state must be [{b}, {hv}, {dv}, {dk}], got {:?}",
1779 state.shape()
1780 ));
1781 }
1782 let inputs = vec![
1783 q.clone(),
1784 k.clone(),
1785 v.clone(),
1786 g.clone(),
1787 beta.clone(),
1788 state.clone(),
1789 ];
1790 let kernel = on_gpu(q.dtype(), q.size())
1791 && q.dtype() == v.dtype()
1792 && dk % 32 == 0
1793 && [g, beta, state]
1794 .iter()
1795 .all(|a| matches!(a.dtype(), DType::F16 | DType::BF16 | DType::F32));
1796 let op = FastOp::GatedDelta { steps };
1797 let mut out = if kernel {
1798 Array::from_op(Primitive::Fast(op), inputs)?
1799 } else {
1800 op.fallback(&inputs)?
1801 };
1802 let s = out.pop().expect("two outputs");
1803 Ok((out.pop().expect("two outputs"), s))
1804}
1805
1806fn gated_delta_graph(inputs: &[Array], steps: bool) -> Result<Vec<Array>> {
1809 let [q, k, v, g, beta, state] = inputs else {
1810 unreachable!("six inputs")
1811 };
1812 let (b, t, hk, dk) = (q.shape()[0], q.shape()[1], q.shape()[2], q.shape()[3]);
1813 let (hv, dv) = (v.shape()[2], v.shape()[3]);
1814 let heads = |x: &Array| -> Result<Array> {
1816 if hv == hk {
1817 return Ok(x.clone());
1818 }
1819 let wide = broadcast_to(&expand_dims(x, 3)?, &[b, t, hk, hv / hk, dk])?;
1820 reshape(&wide, &[b as isize, t as isize, hv as isize, dk as isize])
1821 };
1822 let wide = |x: &Array| match x.dtype() {
1824 DType::F64 => Ok(x.clone()),
1825 _ => astype(x, DType::F32),
1826 };
1827 let (qh, kh) = (heads(&wide(q)?)?, heads(&wide(k)?)?);
1828 let (v, g, beta) = (&wide(v)?, &wide(g)?, &wide(beta)?);
1829 let at = |x: &Array, i: usize| -> Result<Array> {
1830 let mut args = vec![SliceArg::full(); x.ndim()];
1831 args[1] = SliceArg::new(Some(i as isize), Some(i as isize + 1), 1);
1832 squeeze(&slice(x, &args)?, &[1])
1833 };
1834 let mut s = wide(state)?;
1835 let mut ys = Vec::with_capacity(t);
1836 let mut states = Vec::with_capacity(if steps { t } else { 0 });
1837 for i in 0..t {
1838 let (qt, kt, vt) = (at(&qh, i)?, expand_dims(&at(&kh, i)?, 2)?, at(v, i)?);
1839 let decay = reshape(&at(g, i)?, &[b as isize, hv as isize, 1, 1])?;
1840 s = multiply(&s, &decay)?;
1841 let kv_mem = sum(&multiply(&s, &kt)?, &[-1], false)?;
1842 let delta = multiply(subtract(&vt, &kv_mem)?, expand_dims(&at(beta, i)?, -1)?)?;
1843 s = add(&s, multiply(&kt, expand_dims(&delta, -1)?)?)?;
1844 let y = sum(&multiply(&s, expand_dims(&qt, 2)?)?, &[-1], false)?;
1845 ys.push(astype(&y, q.dtype())?);
1846 if steps {
1847 states.push(astype(&s, state.dtype())?);
1848 }
1849 }
1850 let y = match ys.is_empty() {
1851 true => zeros(&[b, 0, hv, dv], q.dtype())?,
1852 false => stack(&ys, 1)?,
1853 };
1854 let s = match (steps, states.is_empty()) {
1855 (false, _) => astype(&s, state.dtype())?,
1856 (true, false) => stack(&states, 1)?,
1857 (true, true) => zeros(&[b, 0, hv, dv, dk], state.dtype())?,
1858 };
1859 Ok(vec![y, s])
1860}
1861
1862fn gather_qmm_graph(
1865 inputs: &[Array],
1866 group_size: usize,
1867 bits: u32,
1868 transpose: bool,
1869) -> Result<Array> {
1870 let (lhs, rhs) = (&inputs[4], &inputs[5]);
1871 let rows = |a: &Array, indices: &Array| -> Result<Array> {
1872 let nd = a.ndim();
1873 let flat = reshape(
1874 a,
1875 &[-1, a.shape()[nd - 2] as isize, a.shape()[nd - 1] as isize],
1876 )?;
1877 crate::take(&flat, indices, Some(0))
1878 };
1879 crate::quantized_matmul(
1880 &rows(&inputs[0], lhs)?,
1881 &rows(&inputs[1], rhs)?,
1882 &rows(&inputs[2], rhs)?,
1883 &rows(&inputs[3], rhs)?,
1884 transpose,
1885 group_size,
1886 bits,
1887 )
1888}