1use crate::cast::Canon;
26use crate::kernels::Operand;
27use crate::ops::{
28 self, add, arange, astype, broadcast_to, cos, divide, equal, exp, expand_dims, filled, less,
29 log, max, mean, multiply, reshape, sin, slice, sqrt, squeeze, stop_gradient, subtract, sum,
30 where_,
31};
32use crate::primitive::Primitive;
33use crate::{
34 Array, Category, DType, Device, DeviceKind, Error, Result, SliceArg, Stream, default_stream,
35 set_default_stream,
36};
37use crate::{stack, zeros};
38
39pub use crate::kernel::{KernelCall, KernelConfig, MetalKernel, TemplateArg};
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
43#[non_exhaustive]
44pub enum FastOp {
45 RmsNorm {
47 eps: u32,
49 },
50 AddRmsNorm {
52 eps: u32,
54 },
55 RmsNormVjp {
57 eps: u32,
59 },
60 LayerNorm {
63 eps: u32,
65 },
66 LayerNormVjp {
68 eps: u32,
70 },
71 Rope {
73 dims: usize,
75 traditional: bool,
77 base: u32,
79 scale: u32,
81 forward: bool,
83 },
84 NormRope {
88 eps: Option<u32>,
90 dims: usize,
92 traditional: bool,
94 base: u32,
96 scale: u32,
98 offset: usize,
100 cache: bool,
102 },
103 DepthwiseConv1d {
105 stride: usize,
107 padding: usize,
109 dilation: usize,
111 },
112 Softmax,
114 LogSumExp,
116 CrossEntropy,
118 CrossEntropyVjp,
120 Attention {
123 scale: u32,
125 causal: bool,
127 mask: bool,
129 sinks: bool,
131 },
132 GatedDelta {
135 steps: bool,
137 },
138 GatherMm,
141 GatherQmm {
146 group_size: usize,
148 bits: u8,
150 transpose: bool,
152 },
153}
154
155#[derive(Debug, Clone, Copy)]
157#[non_exhaustive]
158pub enum AttentionMask<'a> {
159 None,
161 Causal,
163 Array(&'a Array),
166}
167
168fn float(bits: u32) -> f32 {
169 f32::from_bits(bits)
170}
171
172fn check_inputs(inputs: &[&Array]) -> Result<()> {
173 match inputs.iter().find_map(|a| a.error()) {
174 Some(err) => Err(err),
175 None => Ok(()),
176 }
177}
178
179fn scalar(value: f64, dtype: DType) -> Result<Array> {
181 filled(&[], dtype, |_| Canon::Float(value))
182}
183
184pub(crate) fn on_gpu(dtype: DType, size: usize) -> bool {
186 default_stream().device.kind == DeviceKind::Gpu
187 && matches!(dtype, DType::F16 | DType::BF16 | DType::F32)
188 && size > 0
189 && size < u32::MAX as usize
190}
191
192pub(crate) fn record_or_fallback(op: FastOp, inputs: Vec<Array>) -> Result<Vec<Array>> {
195 if on_gpu(inputs[0].dtype(), inputs[0].size()) {
196 Array::from_op(Primitive::Fast(op), inputs)
197 } else {
198 op.fallback(&inputs)
199 }
200}
201
202fn one(op: FastOp, inputs: Vec<Array>) -> Result<Array> {
203 Ok(record_or_fallback(op, inputs)?.swap_remove(0))
204}
205
206fn floating(op: &'static str, dtype: DType) -> Result<()> {
207 match dtype.category() {
208 Category::Float => Ok(()),
209 _ => Err(Error::UnsupportedDType { op, dtype }),
210 }
211}
212
213fn check_param(op: &'static str, what: &str, p: &Array, x: &Array) -> Result<()> {
215 if p.ndim() != 1 || p.size() != x.shape()[x.ndim() - 1] {
216 return Err(Error::InvalidArgument(format!(
217 "{op}: {what} must be 1-D with {} elements, got shape {:?}",
218 x.shape()[x.ndim() - 1],
219 p.shape()
220 )));
221 }
222 Ok(())
223}
224
225pub fn rms_norm(x: &Array, weight: Option<&Array>, eps: f32) -> Result<Array> {
229 check_inputs(&[x])?;
230 if x.ndim() == 0 {
231 return Err(Error::InvalidArgument(
232 "rms_norm: the input needs at least one dimension".into(),
233 ));
234 }
235 let dtype = match weight {
236 Some(w) => {
237 check_inputs(&[w])?;
238 check_param("rms_norm", "weight", w, x)?;
239 crate::promote_types(x.dtype(), w.dtype())
240 }
241 None => x.dtype(),
242 };
243 floating("rms_norm", dtype)?;
244 let w = match weight {
245 Some(w) => astype(w, dtype)?,
246 None => scalar(1.0, dtype)?,
247 };
248 let op = FastOp::RmsNorm { eps: eps.to_bits() };
249 one(op, vec![astype(x, dtype)?, w])
250}
251
252pub fn add_rms_norm(
257 x: &Array,
258 residual: &Array,
259 weight: Option<&Array>,
260 eps: f32,
261) -> Result<(Array, Array)> {
262 check_inputs(&[x, residual])?;
263 let dtype = crate::promote_types(x.dtype(), residual.dtype());
264 let fused = x.shape() == residual.shape()
265 && x.ndim() > 0
266 && dtype.category() == Category::Float
267 && weight.is_none_or(|w| {
268 w.error().is_none()
269 && w.ndim() == 1
270 && w.size() == x.shape()[x.ndim() - 1]
271 && crate::promote_types(dtype, w.dtype()) == dtype
272 });
273 if !fused {
274 let h = add(x, residual)?;
275 let y = rms_norm(&h, weight, eps)?;
276 return Ok((h, y));
277 }
278 let w = match weight {
279 Some(w) => astype(w, dtype)?,
280 None => scalar(1.0, dtype)?,
281 };
282 let op = FastOp::AddRmsNorm { eps: eps.to_bits() };
283 let mut out = record_or_fallback(op, vec![astype(x, dtype)?, astype(residual, dtype)?, w])?;
284 let y = out.pop().expect("two outputs");
285 Ok((out.pop().expect("two outputs"), y))
286}
287
288pub fn layer_norm(
291 x: &Array,
292 weight: Option<&Array>,
293 bias: Option<&Array>,
294 eps: f32,
295) -> Result<Array> {
296 check_inputs(&[x])?;
297 if x.ndim() == 0 {
298 return Err(Error::InvalidArgument(
299 "layer_norm: the input needs at least one dimension".into(),
300 ));
301 }
302 let mut dtype = x.dtype();
303 for (p, what) in [(weight, "weight"), (bias, "bias")] {
304 if let Some(p) = p {
305 check_inputs(&[p])?;
306 check_param("layer_norm", what, p, x)?;
307 dtype = crate::promote_types(dtype, p.dtype());
308 }
309 }
310 floating("layer_norm", dtype)?;
311 let w = match weight {
312 Some(w) => astype(w, dtype)?,
313 None => scalar(1.0, dtype)?,
314 };
315 let b = match bias {
316 Some(b) => astype(b, dtype)?,
317 None => scalar(0.0, dtype)?,
318 };
319 let op = FastOp::LayerNorm { eps: eps.to_bits() };
320 one(op, vec![astype(x, dtype)?, w, b])
321}
322
323pub fn rope(
329 x: &Array,
330 dims: usize,
331 traditional: bool,
332 base: Option<f32>,
333 scale: f32,
334 offset: &Array,
335 freqs: Option<&Array>,
336) -> Result<Array> {
337 check_inputs(&[x, offset])?;
338 let invalid = |msg: String| Err(Error::InvalidArgument(format!("rope: {msg}")));
339 if x.ndim() < 3 {
340 return invalid(format!(
341 "the input needs at least 3 dimensions, got {:?}",
342 x.shape()
343 ));
344 }
345 floating("rope", x.dtype())?;
346 if offset.ndim() > 1 || (offset.size() != 1 && offset.size() != x.shape()[0]) {
347 return invalid(format!(
348 "offset must be a scalar or have {} elements, got shape {:?}",
349 x.shape()[0],
350 offset.shape()
351 ));
352 }
353 if !matches!(
354 offset.dtype().category(),
355 Category::Signed | Category::Unsigned
356 ) {
357 return invalid(format!("offset must be an integer, got {}", offset.dtype()));
358 }
359 if dims == 0 || !dims.is_multiple_of(2) || dims > x.shape()[x.ndim() - 1] {
360 return invalid(format!(
361 "dims must be even, positive and at most {}, got {dims}",
362 x.shape()[x.ndim() - 1]
363 ));
364 }
365 let offset = match offset.dtype().size() {
366 4 => offset.clone(),
367 _ => astype(offset, DType::I32)?,
368 };
369 let mut inputs = vec![x.clone(), offset];
370 let base = match (base, freqs) {
371 (Some(b), None) => b,
372 (None, Some(f)) => {
373 check_inputs(&[f])?;
374 if f.ndim() != 1 || f.size() != dims / 2 {
375 return invalid(format!(
376 "freqs must be 1-D with {} elements, got shape {:?}",
377 dims / 2,
378 f.shape()
379 ));
380 }
381 inputs.push(astype(f, DType::F32)?);
382 1.0
383 }
384 _ => return invalid("exactly one of base and freqs must be given".into()),
385 };
386 let op = FastOp::Rope {
387 dims,
388 traditional,
389 base: base.to_bits(),
390 scale: scale.to_bits(),
391 forward: true,
392 };
393 one(op, inputs)
394}
395
396pub fn cross_entropy(logits: &Array, targets: &Array) -> Result<Array> {
400 check_inputs(&[logits, targets])?;
401 if logits.ndim() == 0 || targets.shape() != &logits.shape()[..logits.ndim() - 1] {
402 return Err(Error::InvalidArgument(format!(
403 "cross_entropy: targets of shape {:?} do not match logits of shape {:?}",
404 targets.shape(),
405 logits.shape()
406 )));
407 }
408 floating("cross_entropy", logits.dtype())?;
409 if !matches!(
410 targets.dtype().category(),
411 Category::Signed | Category::Unsigned
412 ) {
413 return Err(Error::InvalidArgument(format!(
414 "cross_entropy: targets must be integer class indices, got {}",
415 targets.dtype()
416 )));
417 }
418 one(
419 FastOp::CrossEntropy,
420 vec![logits.clone(), astype(targets, DType::I32)?],
421 )
422}
423
424fn update_last(base: &Array, update: &Array, start: usize, step: isize) -> Result<Array> {
427 let n = base.ndim();
428 let mut starts = vec![0; n];
429 starts[n - 1] = start;
430 let mut steps = vec![1; n];
431 steps[n - 1] = step;
432 let primitive = Primitive::SliceUpdate {
433 start: starts,
434 step: steps,
435 len: update.shape().to_vec(),
436 };
437 Ok(Array::from_op(primitive, vec![base.clone(), update.clone()])?.swap_remove(0))
438}
439
440fn last(x: &Array, start: usize, step: isize, len: usize) -> Result<Array> {
442 let mut args = vec![SliceArg::full(); x.ndim()];
443 let stop = start as isize + step * len as isize;
444 args[x.ndim() - 1] = SliceArg::new(Some(start as isize), Some(stop), step);
445 slice(x, &args)
446}
447
448fn rsqrt(x: &Array) -> Result<Array> {
450 divide(1.0, sqrt(x)?)
451}
452
453fn softmax_graph(x: &Array) -> Result<Array> {
455 let xf = if x.dtype() == DType::F32 {
456 x.clone()
457 } else {
458 astype(x, DType::F32)?
459 };
460 let m = stop_gradient(&max(&xf, &[-1], true)?)?;
461 let e = exp(&subtract(&xf, &m)?)?;
462 astype(÷(&e, sum(&e, &[-1], true)?)?, x.dtype())
463}
464
465fn logsumexp_graph(x: &Array) -> Result<Array> {
467 let m = stop_gradient(&max(x, &[-1], true)?)?;
468 let out = add(log(&sum(&exp(&subtract(x, &m)?)?, &[-1], true)?)?, &m)?;
469 where_(equal(&ops::abs(&m)?, f64::INFINITY)?, &m, &out)
470}
471
472fn one_hot(y: &Array, classes: usize, dtype: DType) -> Result<Array> {
474 let positions = arange(0.0, classes as f64, 1.0, y.dtype())?;
475 equal(&expand_dims(y, -1)?, &positions)?.astype(dtype)
476}
477
478fn wrap(y: &Array, classes: usize) -> Result<Array> {
480 where_(less(y, 0)?, add(y, classes as f64)?, y)
481}
482
483impl FastOp {
484 pub(crate) fn name(&self) -> &'static str {
485 match self {
486 Self::RmsNorm { .. } => "rms_norm",
487 Self::AddRmsNorm { .. } => "add_rms_norm",
488 Self::RmsNormVjp { .. } => "rms_norm_vjp",
489 Self::LayerNorm { .. } => "layer_norm",
490 Self::LayerNormVjp { .. } => "layer_norm_vjp",
491 Self::Rope { .. } => "rope",
492 Self::NormRope { .. } => "norm_rope",
493 Self::DepthwiseConv1d { .. } => "conv1d",
494 Self::Softmax => "softmax",
495 Self::LogSumExp => "logsumexp",
496 Self::CrossEntropy => "cross_entropy",
497 Self::CrossEntropyVjp => "cross_entropy_vjp",
498 Self::Attention { .. } => "scaled_dot_product_attention",
499 Self::GatedDelta { .. } => "gated_delta",
500 Self::GatherQmm { .. } => "gather_qmm",
501 Self::GatherMm => "gather_mm",
502 }
503 }
504
505 pub(crate) fn outputs(&self, inputs: &[Array]) -> Result<Vec<(DType, Vec<usize>)>> {
507 let arity = match self {
508 Self::Softmax | Self::LogSumExp => 1,
509 Self::RmsNorm { .. } | Self::CrossEntropy | Self::DepthwiseConv1d { .. } => 2,
510 Self::AddRmsNorm { .. } | Self::RmsNormVjp { .. } | Self::LayerNorm { .. } => 3,
511 Self::LayerNormVjp { .. } | Self::CrossEntropyVjp => 4,
512 Self::Rope { .. } => inputs.len().clamp(2, 3),
513 Self::NormRope { cache, .. } => inputs
514 .len()
515 .clamp(2 + usize::from(*cache), 3 + usize::from(*cache)),
516 Self::Attention { mask, sinks, .. } => 3 + usize::from(*mask) + usize::from(*sinks),
517 Self::GatedDelta { .. } => 6,
518 Self::GatherQmm { .. } => inputs.len().clamp(6, 7),
519 Self::GatherMm => inputs.len().clamp(4, 5),
520 };
521 if inputs.len() != arity || inputs[0].ndim() == 0 {
522 return Err(Error::InvalidArgument(format!(
523 "{}: {arity} inputs expected, got {}",
524 self.name(),
525 inputs.len()
526 )));
527 }
528 let x = &inputs[0];
529 let like = |a: &Array| (a.dtype(), a.shape().to_vec());
530 Ok(match self {
531 Self::RmsNorm { .. }
532 | Self::LayerNorm { .. }
533 | Self::Rope { .. }
534 | Self::NormRope { .. }
535 | Self::Softmax => {
536 vec![like(x)]
537 }
538 Self::LogSumExp => {
539 let mut shape = x.shape().to_vec();
540 *shape.last_mut().expect("1-D") = 1;
541 vec![(x.dtype(), shape)]
542 }
543 Self::AddRmsNorm { .. } => vec![like(x), like(x)],
544 Self::DepthwiseConv1d {
545 stride,
546 padding,
547 dilation,
548 } => {
549 let (l, k) = (x.shape()[1], inputs[1].shape()[1]);
550 let l_out = (l + 2 * padding - dilation * (k - 1) - 1) / stride + 1;
551 vec![(x.dtype(), vec![x.shape()[0], l_out, x.shape()[2]])]
552 }
553 Self::RmsNormVjp { .. } => vec![like(x), like(&inputs[1])],
554 Self::LayerNormVjp { .. } => vec![like(x), like(&inputs[1]), like(&inputs[2])],
555 Self::CrossEntropy => vec![(DType::F32, inputs[1].shape().to_vec())],
556 Self::CrossEntropyVjp => vec![like(x)],
557 Self::Attention { .. } => {
558 let mut shape = x.shape().to_vec();
559 shape[3] = inputs[2].shape()[3];
560 vec![(x.dtype(), shape)]
561 }
562 Self::GatedDelta { steps } => {
563 let state = match steps {
564 false => inputs[5].shape().to_vec(),
565 true => {
566 let mut shape = inputs[5].shape().to_vec();
567 shape.insert(1, x.shape()[1]);
568 shape
569 }
570 };
571 vec![
572 (x.dtype(), inputs[2].shape().to_vec()),
573 (inputs[5].dtype(), state),
574 ]
575 }
576 Self::GatherMm => {
577 let b = inputs[1].shape();
578 let mut shape = inputs[2].shape().to_vec();
579 shape.extend([x.shape()[x.ndim() - 2], b[b.len() - 1]]);
580 vec![(x.dtype(), shape)]
581 }
582 Self::GatherQmm {
583 bits, transpose, ..
584 } => {
585 let w = inputs[1].shape();
586 let n = match transpose {
587 true => w[w.len() - 2],
588 false => w[w.len() - 1] * 32 / usize::from(*bits),
589 };
590 let mut shape = inputs[4].shape().to_vec();
591 shape.extend([x.shape()[x.ndim() - 2], n]);
592 vec![(x.dtype(), shape)]
593 }
594 })
595 }
596
597 pub(crate) fn fallback(&self, inputs: &[Array]) -> Result<Vec<Array>> {
599 let x = &inputs[0];
600 let f32x = || astype(x, DType::F32);
601 let out = match self {
602 Self::RmsNorm { eps } => {
603 let xf = f32x()?;
604 let n = rsqrt(&add(mean(&multiply(&xf, &xf)?, &[-1], true)?, float(*eps))?)?;
605 let y = multiply(&xf, &n)?.astype(x.dtype())?;
606 let w = &inputs[1];
607 vec![if w.ndim() == 0 { y } else { multiply(&y, w)? }]
608 }
609 Self::AddRmsNorm { eps } => {
610 let h = add(x, &inputs[1])?;
611 let op = Self::RmsNorm { eps: *eps };
612 let y = record_or_fallback(op, vec![h.clone(), inputs[2].clone()])?;
613 vec![h, y.into_iter().next().expect("one output")]
614 }
615 Self::LayerNorm { eps } => {
616 let xf = f32x()?;
617 let xc = subtract(&xf, mean(&xf, &[-1], true)?)?;
618 let v = mean(&multiply(&xc, &xc)?, &[-1], true)?;
619 let mut y = multiply(&xc, rsqrt(&add(&v, float(*eps))?)?)?.astype(x.dtype())?;
620 if inputs[1].ndim() != 0 {
621 y = multiply(&y, &inputs[1])?;
622 }
623 if inputs[2].ndim() != 0 {
624 y = add(&y, &inputs[2])?;
625 }
626 vec![y]
627 }
628 Self::RmsNormVjp { eps } => {
629 let (w, g) = (&inputs[1], &inputs[2]);
630 let n = rsqrt(&add(mean(&multiply(x, x)?, &[-1], true)?, float(*eps))?)?;
631 let n3 = multiply(&multiply(&n, &n)?, &n)?;
632 let gw = multiply(g, w)?;
633 let t = mean(&multiply(&gw, x)?, &[-1], true)?;
634 let dx = subtract(multiply(&gw, &n)?, multiply(multiply(x, &t)?, &n3)?)?;
635 let dw = match w.ndim() {
636 0 => scalar(0.0, w.dtype())?,
637 _ => sum_rows(&multiply(g, multiply(x, &n)?)?)?,
638 };
639 vec![dx, dw]
640 }
641 Self::LayerNormVjp { eps } => {
642 let (w, b, g) = (&inputs[1], &inputs[2], &inputs[3]);
643 let mu = mean(x, &[-1], true)?;
644 let xc = subtract(x, &mu)?;
645 let var = mean(&multiply(&xc, &xc)?, &[-1], true)?;
646 let n = rsqrt(&add(&var, float(*eps))?)?;
647 let n3 = multiply(&multiply(&n, &n)?, &n)?;
648 let wg = multiply(w, g)?;
649 let mwg = mean(&wg, &[-1], true)?;
650 let mwgxc = mean(&multiply(&wg, &xc)?, &[-1], true)?;
651 let dx = subtract(
652 multiply(subtract(&wg, &mwg)?, &n)?,
653 multiply(multiply(&xc, &mwgxc)?, &n3)?,
654 )?;
655 let dw = match w.ndim() {
656 0 => scalar(0.0, w.dtype())?,
657 _ => sum_rows(&multiply(g, multiply(&xc, &n)?)?)?,
658 };
659 let db = match b.ndim() {
660 0 => scalar(0.0, b.dtype())?,
661 _ => sum_rows(g)?,
662 };
663 vec![dx, dw, db]
664 }
665 Self::Attention {
666 scale,
667 causal,
668 mask,
669 sinks,
670 } => vec![attention_graph(
671 inputs,
672 float(*scale),
673 *causal,
674 *mask,
675 *sinks,
676 )?],
677 Self::Rope {
678 dims,
679 traditional,
680 base,
681 scale,
682 forward,
683 } => vec![rope_graph(
684 inputs,
685 *dims,
686 *traditional,
687 float(*base),
688 float(*scale),
689 *forward,
690 )?],
691 Self::NormRope {
692 eps,
693 dims,
694 traditional,
695 base,
696 scale,
697 offset,
698 cache,
699 } => {
700 let at = usize::from(*cache);
701 let (x, w) = (&inputs[at], &inputs[at + 1]);
702 let mut h = x.clone();
703 if let Some(eps) = eps {
704 let op = Self::RmsNorm { eps: *eps };
705 h = record_or_fallback(op, vec![h, w.clone()])?.swap_remove(0);
706 }
707 let mut rope_inputs = vec![h, Array::scalar(*offset as i32)];
708 rope_inputs.extend(inputs.get(at + 2).cloned());
709 let op = Self::Rope {
710 dims: *dims,
711 traditional: *traditional,
712 base: *base,
713 scale: *scale,
714 forward: true,
715 };
716 let r = record_or_fallback(op, rope_inputs)?.swap_remove(0);
717 vec![match cache {
718 true => {
719 let mut region = vec![SliceArg::full(); 4];
720 let end = (offset + r.shape()[2]) as isize;
721 region[2] = SliceArg::new(Some(*offset as isize), Some(end), 1);
722 crate::slice_update(&inputs[0], &r, ®ion)?
723 }
724 false => r,
725 }]
726 }
727 Self::DepthwiseConv1d {
728 stride,
729 padding,
730 dilation,
731 } => {
732 let c = x.shape()[2];
733 vec![crate::conv::general(
734 x,
735 &inputs[1],
736 (*stride, *padding, *dilation, c),
737 )?]
738 }
739 Self::Softmax => vec![softmax_graph(x)?],
740 Self::GatedDelta { steps } => gated_delta_graph(inputs, *steps)?,
741 Self::GatherMm => {
742 let rows = |a: &Array, indices: &Array| -> Result<Array> {
743 let nd = a.ndim();
744 let shape = [-1, a.shape()[nd - 2] as isize, a.shape()[nd - 1] as isize];
745 crate::take(&reshape(a, &shape)?, indices, Some(0))
746 };
747 vec![crate::matmul(
748 &rows(x, &inputs[2])?,
749 &rows(&inputs[1], &inputs[3])?,
750 )?]
751 }
752 Self::GatherQmm {
753 group_size,
754 bits,
755 transpose,
756 } => vec![gather_qmm_graph(
757 inputs,
758 *group_size,
759 u32::from(*bits),
760 *transpose,
761 )?],
762 Self::LogSumExp => vec![logsumexp_graph(x)?],
763 Self::CrossEntropy => {
764 let classes = x.shape()[x.ndim() - 1];
765 let y = wrap(&inputs[1], classes)?;
766 let hit = one_hot(&y, classes, DType::Bool)?;
767 let score = sum(&where_(&hit, x, 0.0)?, &[-1], false)?;
768 let lse = squeeze(&logsumexp_graph(x)?, &[-1])?;
769 vec![subtract(&lse, &score)?.astype(DType::F32)?]
770 }
771 Self::CrossEntropyVjp => {
772 let classes = x.shape()[x.ndim() - 1];
773 let (y, loss, g) = (wrap(&inputs[1], classes)?, &inputs[2], &inputs[3]);
774 let hit = one_hot(&y, classes, DType::Bool)?;
775 let score = sum(&where_(&hit, x, 0.0)?, &[-1], false)?;
776 let lse = add(loss, score.astype(DType::F32)?)?;
777 let p = exp(&subtract(&f32x()?, expand_dims(&lse, -1)?)?)?;
778 let gx = multiply(
779 expand_dims(g, -1)?,
780 subtract(&p, one_hot(&y, classes, DType::F32)?)?,
781 )?;
782 vec![gx.astype(x.dtype())?]
783 }
784 };
785 Ok(out)
786 }
787
788 pub(crate) fn vjp(
790 &self,
791 inputs: &[Array],
792 outputs: &[Array],
793 cotangents: &[Array],
794 wanted: &[bool],
795 ) -> Result<Vec<Option<Array>>> {
796 let g = &cotangents[0];
797 let pick = |all: Vec<Array>| -> Vec<Option<Array>> {
798 all.into_iter()
799 .zip(wanted)
800 .map(|(a, &w)| w.then_some(a))
801 .collect()
802 };
803 match self {
804 Self::RmsNorm { eps } => {
805 let op = Self::RmsNormVjp { eps: *eps };
806 let grads =
807 record_or_fallback(op, vec![inputs[0].clone(), inputs[1].clone(), g.clone()])?;
808 Ok(pick(grads))
809 }
810 Self::LayerNorm { eps } => {
811 let op = Self::LayerNormVjp { eps: *eps };
812 let mut all = inputs.to_vec();
813 all.push(g.clone());
814 Ok(pick(record_or_fallback(op, all)?))
815 }
816 Self::Rope { forward, .. } => {
817 if wanted[1..].iter().any(|&w| w) {
818 return Err(Error::InvalidArgument(
819 "rope: no gradient with respect to the offset or frequencies".into(),
820 ));
821 }
822 let Self::Rope {
823 dims,
824 traditional,
825 base,
826 scale,
827 ..
828 } = *self
829 else {
830 unreachable!()
831 };
832 let inverse = Self::Rope {
833 dims,
834 traditional,
835 base,
836 scale,
837 forward: !forward,
838 };
839 let mut all = inputs.to_vec();
840 all[0] = g.clone();
841 let mut out = vec![Some(one(inverse, all)?)];
842 out.extend(std::iter::repeat_n(None, inputs.len() - 1));
843 Ok(out)
844 }
845 Self::Softmax => {
846 let s = &outputs[0];
847 let sg = multiply(s, g)?;
848 let dx = subtract(&sg, multiply(s, sum(&sg, &[-1], true)?)?)?;
849 Ok(vec![Some(dx)])
850 }
851 Self::LogSumExp => {
852 let s = one(Self::Softmax, vec![inputs[0].clone()])?;
853 Ok(vec![Some(multiply(g, &s)?)])
854 }
855 Self::CrossEntropy => {
856 if wanted[1] {
857 return Err(Error::InvalidArgument(
858 "cross_entropy: no gradient with respect to the targets".into(),
859 ));
860 }
861 let all = vec![
862 inputs[0].clone(),
863 inputs[1].clone(),
864 outputs[0].clone(),
865 g.clone(),
866 ];
867 Ok(vec![Some(one(Self::CrossEntropyVjp, all)?), None])
868 }
869 Self::AddRmsNorm { .. }
870 | Self::NormRope { .. }
871 | Self::DepthwiseConv1d { .. }
872 | Self::RmsNormVjp { .. }
873 | Self::LayerNormVjp { .. }
874 | Self::CrossEntropyVjp
875 | Self::Attention { .. }
876 | Self::GatedDelta { .. }
877 | Self::GatherQmm { .. }
878 | Self::GatherMm => self.fallback_vjp(inputs, cotangents, wanted),
879 }
880 }
881
882 fn fallback_vjp(
884 &self,
885 inputs: &[Array],
886 cotangents: &[Array],
887 wanted: &[bool],
888 ) -> Result<Vec<Option<Array>>> {
889 let active: Vec<usize> = (0..inputs.len())
890 .filter(|&i| wanted[i] && inputs[i].dtype().category() == Category::Float)
891 .collect();
892 let primals: Vec<Array> = active.iter().map(|&i| inputs[i].clone()).collect();
893 let f = |ps: &[Array]| {
894 let mut all = inputs.to_vec();
895 for (p, &i) in ps.iter().zip(&active) {
896 all[i] = p.clone();
897 }
898 self.fallback(&all)
899 };
900 let (_, grads) = crate::vjp(f, &primals, cotangents)?;
901 let mut out = vec![None; inputs.len()];
902 for (g, &i) in grads.into_iter().zip(&active) {
903 out[i] = Some(g);
904 }
905 Ok(out)
906 }
907
908 pub(crate) fn jvp(&self, inputs: &[Array], tangents: &[Option<Array>]) -> Result<Vec<Array>> {
910 let active: Vec<usize> = (0..inputs.len())
911 .filter(|&i| tangents[i].is_some() && inputs[i].dtype().category() == Category::Float)
912 .collect();
913 let primals: Vec<Array> = active.iter().map(|&i| inputs[i].clone()).collect();
914 let ts: Vec<Array> = active
915 .iter()
916 .map(|&i| tangents[i].clone().expect("active"))
917 .collect();
918 let f = |ps: &[Array]| {
919 let mut all = inputs.to_vec();
920 for (p, &i) in ps.iter().zip(&active) {
921 all[i] = p.clone();
922 }
923 self.fallback(&all)
924 };
925 Ok(crate::jvp(f, &primals, &ts)?.1)
926 }
927
928 pub(crate) fn vmap(
932 &self,
933 inputs: &[Array],
934 axes: &[Option<usize>],
935 ) -> Result<(Vec<Array>, usize)> {
936 let rest_unbatched = axes[1..].iter().all(Option::is_none);
937 let front = |i: usize| -> Result<Array> {
938 let a = &inputs[i];
939 match axes[i] {
940 Some(ax) => crate::batching::move_axis(a, ax, 0),
941 None => {
942 let b = inputs
943 .iter()
944 .zip(axes)
945 .find_map(|(a, ax)| ax.map(|ax| a.shape()[ax]))
946 .expect("one batched input");
947 let mut shape = vec![b];
948 shape.extend_from_slice(a.shape());
949 broadcast_to(&expand_dims(a, 0)?, &shape)
950 }
951 }
952 };
953 let simple = match self {
954 Self::RmsNorm { .. } | Self::LayerNorm { .. } | Self::Softmax | Self::LogSumExp => {
955 rest_unbatched
956 }
957 Self::Rope { .. } => rest_unbatched && inputs[1].size() == 1,
958 Self::CrossEntropy => true,
959 _ => false,
960 };
961 if simple {
962 let mut all = inputs.to_vec();
963 all[0] = front(0)?;
964 if *self == Self::CrossEntropy {
965 all[1] = front(1)?;
966 }
967 return Ok((record_or_fallback(self.clone(), all)?, 0));
968 }
969 let f = |xs: &[Array]| self.fallback(xs);
970 Ok((crate::transforms::batch(&f, inputs, axes)?, 0))
971 }
972
973 pub(crate) fn eval_cpu(&self, operands: &[Operand<'_>]) -> Result<Vec<crate::array::Data>> {
975 let leaves = operands
976 .iter()
977 .map(|(meta, data)| {
978 let buffer = crate::kernels::contiguous(meta, data)?;
979 Array::from_buffer(buffer, meta.dtype, &meta.shape)
980 })
981 .collect::<Result<Vec<_>>>()?;
982 let previous = default_stream();
983 set_default_stream(Stream::default_for(Device::cpu()));
984 let outputs = self.fallback(&leaves);
985 set_default_stream(previous);
986 let outputs = outputs?;
987 crate::eval(&outputs)?;
988 outputs
989 .iter()
990 .map(|a| {
991 let data = a.data().expect("evaluated");
992 Ok(crate::array::Data::new(crate::kernels::contiguous(
993 a.out(),
994 data,
995 )?))
996 })
997 .collect()
998 }
999}
1000
1001fn sum_rows(g: &Array) -> Result<Array> {
1003 let axes: Vec<isize> = (0..g.ndim() as isize - 1).collect();
1004 sum(g, &axes, false)
1005}
1006
1007fn rope_graph(
1009 inputs: &[Array],
1010 dims: usize,
1011 traditional: bool,
1012 base: f32,
1013 scale: f32,
1014 forward: bool,
1015) -> Result<Array> {
1016 let x = &inputs[0];
1017 let shape: Vec<isize> = x.shape().iter().map(|&d| d as isize).collect();
1018 let nd = x.ndim();
1019 let (b, t, d) = (x.shape()[0], x.shape()[nd - 2], x.shape()[nd - 1]);
1020 let x4 = reshape(x, &[b as isize, -1, t as isize, d as isize])?;
1021 let dtype = x.dtype();
1022 let half = dims / 2;
1023 let offset = &inputs[1];
1024 let offset = if offset.size() > 1 {
1025 reshape(offset, &[-1, 1, 1])?
1026 } else {
1027 reshape(offset, &[])?
1028 };
1029 let positions = multiply(
1030 add(arange(0.0, t as f64, 1.0, DType::F32)?, &offset)?,
1031 f64::from(scale),
1032 )?;
1033 let inv_freqs = match inputs.get(2) {
1034 Some(f) => divide(1.0, f)?,
1035 None => exp(&multiply(
1036 arange(0.0, -(half as f64), -1.0, DType::F32)?,
1037 f64::from(base.ln() / half as f32),
1038 )?)?,
1039 };
1040 let theta = multiply(expand_dims(&positions, -1)?, &inv_freqs)?;
1041 let (c, s) = (cos(&theta)?.astype(dtype)?, sin(&theta)?.astype(dtype)?);
1042 let (start2, step) = if traditional { (1, 2) } else { (half, 1) };
1043 let x1 = last(&x4, 0, step, half)?;
1044 let x2 = last(&x4, start2, step, half)?;
1045 let (r1, r2) = if forward {
1046 (
1047 subtract(multiply(&x1, &c)?, multiply(&x2, &s)?)?,
1048 add(multiply(&x1, &s)?, multiply(&x2, &c)?)?,
1049 )
1050 } else {
1051 (
1052 add(multiply(&x2, &s)?, multiply(&x1, &c)?)?,
1053 subtract(multiply(&x2, &c)?, multiply(&x1, &s)?)?,
1054 )
1055 };
1056 let full = |r: Array| broadcast_to(&r, x1.shape());
1057 let out = update_last(&x4, &full(r1)?, 0, step)?;
1058 let out = update_last(&out, &full(r2)?, start2, step)?;
1059 reshape(&out, &shape)
1060}
1061
1062#[derive(Debug, Clone, Copy)]
1064pub struct Rotation<'a> {
1065 pub dims: usize,
1067 pub traditional: bool,
1069 pub base: Option<f32>,
1071 pub scale: f32,
1073 pub freqs: Option<&'a Array>,
1075}
1076
1077const NORM_ROPE_MAX: usize = 1024;
1079
1080pub fn norm_rope(
1093 x: &Array,
1094 norm: Option<(&Array, f32)>,
1095 rotation: Rotation<'_>,
1096 offset: usize,
1097 cache: Option<&Array>,
1098) -> Result<Array> {
1099 check_inputs(&[x])?;
1100 let mut inputs = Vec::with_capacity(4);
1101 let mut fused = x.ndim() == 4
1102 && x.dtype().category() == Category::Float
1103 && x.shape()[3] <= NORM_ROPE_MAX
1104 && offset + x.shape()[2] <= i32::MAX as usize;
1105 if let Some(c) = cache {
1106 check_inputs(&[c])?;
1107 let s = x.shape();
1108 let fits = c.ndim() == 4
1109 && c.shape()[..2] == s[..2]
1110 && c.shape()[3] == s[3]
1111 && c.shape()[2] >= offset + s[2];
1112 if !fits {
1113 return Err(Error::InvalidArgument(format!(
1114 "norm_rope: a cache of shape {:?} cannot take {:?} at position {offset}",
1115 c.shape(),
1116 s
1117 )));
1118 }
1119 fused &= c.dtype() == x.dtype();
1120 inputs.push(c.clone());
1121 }
1122 if let Some((w, _)) = norm {
1123 check_inputs(&[w])?;
1124 fused &= w.dtype() == x.dtype() && w.ndim() == 1 && w.size() == x.shape()[x.ndim() - 1];
1125 }
1126 let freqs_valid = rotation
1127 .freqs
1128 .is_none_or(|f| f.error().is_none() && f.ndim() == 1 && f.size() == rotation.dims / 2);
1129 let dims_valid = rotation.dims > 0
1130 && rotation.dims.is_multiple_of(2)
1131 && x.ndim() > 0
1132 && rotation.dims <= x.shape()[x.ndim() - 1];
1133 if !fused || !freqs_valid || !dims_valid || rotation.base.is_some() == rotation.freqs.is_some()
1134 {
1135 return norm_rope_graph(x, norm, rotation, offset, cache);
1137 }
1138 inputs.push(x.clone());
1139 inputs.push(match norm {
1140 Some((w, _)) => w.clone(),
1141 None => scalar(1.0, x.dtype())?,
1142 });
1143 if let Some(f) = rotation.freqs {
1144 inputs.push(astype(f, DType::F32)?);
1145 }
1146 let op = FastOp::NormRope {
1147 eps: norm.map(|(_, eps)| eps.to_bits()),
1148 dims: rotation.dims,
1149 traditional: rotation.traditional,
1150 base: rotation.base.unwrap_or(1.0).to_bits(),
1151 scale: rotation.scale.to_bits(),
1152 offset,
1153 cache: cache.is_some(),
1154 };
1155 one(op, inputs)
1156}
1157
1158fn norm_rope_graph(
1160 x: &Array,
1161 norm: Option<(&Array, f32)>,
1162 rotation: Rotation<'_>,
1163 offset: usize,
1164 cache: Option<&Array>,
1165) -> Result<Array> {
1166 let x = match norm {
1167 Some((w, eps)) => rms_norm(x, Some(w), eps)?,
1168 None => x.clone(),
1169 };
1170 let r = rope(
1171 &x,
1172 rotation.dims,
1173 rotation.traditional,
1174 rotation.base,
1175 rotation.scale,
1176 &Array::scalar(offset as i32),
1177 rotation.freqs,
1178 )?;
1179 match cache {
1180 Some(c) => {
1181 let mut region = vec![SliceArg::full(); 4];
1182 region[2] = SliceArg::new(
1183 Some(offset as isize),
1184 Some((offset + r.shape()[2]) as isize),
1185 1,
1186 );
1187 crate::slice_update(c, &r, ®ion)
1188 }
1189 None => Ok(r),
1190 }
1191}
1192
1193pub fn scaled_dot_product_attention(
1202 q: &Array,
1203 k: &Array,
1204 v: &Array,
1205 scale: f32,
1206 mask: AttentionMask<'_>,
1207 sinks: Option<&Array>,
1208) -> Result<Array> {
1209 check_inputs(&[q, k, v])?;
1210 let invalid = |msg: String| {
1211 Err(Error::InvalidArgument(format!(
1212 "scaled_dot_product_attention: {msg}"
1213 )))
1214 };
1215 if [q, k, v].iter().any(|a| a.ndim() != 4) {
1216 return invalid(format!(
1217 "q, k and v must be 4-D, got {:?}, {:?} and {:?}",
1218 q.shape(),
1219 k.shape(),
1220 v.shape()
1221 ));
1222 }
1223 let (qs, ks, vs) = (q.shape(), k.shape(), v.shape());
1224 if ks[0] != qs[0] || vs[0] != qs[0] || qs[3] != ks[3] || ks[1] != vs[1] || ks[2] != vs[2] {
1225 return invalid(format!("incompatible shapes {qs:?}, {ks:?} and {vs:?}"));
1226 }
1227 if ks[1] == 0 || qs[1] % ks[1] != 0 {
1228 return invalid(format!(
1229 "the query heads ({}) must be a multiple of the key heads ({})",
1230 qs[1], ks[1]
1231 ));
1232 }
1233 let dtype = crate::promote_types(crate::promote_types(q.dtype(), k.dtype()), v.dtype());
1234 floating("scaled_dot_product_attention", dtype)?;
1235 let mut inputs = vec![astype(q, dtype)?, astype(k, dtype)?, astype(v, dtype)?];
1236 let (causal, has_mask) = match mask {
1237 AttentionMask::None => (false, false),
1238 AttentionMask::Causal => (true, false),
1239 AttentionMask::Array(m) => {
1240 check_inputs(&[m])?;
1241 if m.ndim() > 4 {
1242 return invalid(format!(
1243 "the mask must have at most 4 dimensions, got {:?}",
1244 m.shape()
1245 ));
1246 }
1247 let m = if m.dtype() == DType::Bool {
1248 m.clone()
1249 } else if crate::promote_types(m.dtype(), dtype) == dtype {
1250 astype(m, dtype)?
1251 } else {
1252 return invalid(format!("a {} mask does not promote to {dtype}", m.dtype()));
1253 };
1254 inputs.push(broadcast_to(&m, &[qs[0], qs[1], qs[2], ks[2]])?);
1255 (false, true)
1256 }
1257 };
1258 if let Some(s) = sinks {
1259 check_inputs(&[s])?;
1260 if s.ndim() != 1 || s.size() != qs[1] || crate::promote_types(s.dtype(), dtype) != dtype {
1261 return invalid(format!(
1262 "sinks must be [{}] of a type promoting to {dtype}",
1263 qs[1]
1264 ));
1265 }
1266 inputs.push(astype(s, dtype)?);
1267 }
1268 let op = FastOp::Attention {
1269 scale: scale.to_bits(),
1270 causal,
1271 mask: has_mask,
1272 sinks: sinks.is_some(),
1273 };
1274 if on_gpu(dtype, q.size()) && attention_kernel(&inputs, causal, has_mask) {
1275 Ok(Array::from_op(Primitive::Fast(op), inputs)?.swap_remove(0))
1276 } else {
1277 Ok(op.fallback(&inputs)?.swap_remove(0))
1278 }
1279}
1280
1281fn attention_kernel(inputs: &[Array], causal: bool, has_mask: bool) -> bool {
1284 let (q, k, v) = (&inputs[0], &inputs[1], &inputs[2]);
1285 let (lq, lk, d, dv) = (q.shape()[2], k.shape()[2], q.shape()[3], v.shape()[3]);
1286 let gqa = q.shape()[1] / k.shape()[1];
1287 if lk == 0 || lq == 0 {
1288 return false;
1289 }
1290 if lq > 8 {
1291 let head =
1292 (d == dv && matches!(d, 64 | 72 | 80 | 96 | 128 | 192 | 256)) || (d, dv) == (96, 64);
1293 let causal_ok = !causal || lq <= lk;
1294 let slow = match d {
1297 192 => true,
1298 256 => !(q.dtype() != DType::F32 && causal && lq >= 2048 && lq == lk),
1299 _ => false,
1300 };
1301 head && causal_ok && !slow
1302 } else {
1303 let head = (d == dv && matches!(d, 64 | 96 | 128 | 256 | 512))
1304 || (d, dv) == (192, 128)
1305 || (d, dv) == (96, 64);
1306 let d512 = d != 512 || (lq == 1 && gqa == 8 && !has_mask && lk >= 1024);
1307 head && lq <= lk && lq * gqa <= 32 && d512
1308 }
1309}
1310
1311fn attention_graph(
1313 inputs: &[Array],
1314 scale: f32,
1315 causal: bool,
1316 has_mask: bool,
1317 has_sinks: bool,
1318) -> Result<Array> {
1319 let (q, k, v) = (&inputs[0], &inputs[1], &inputs[2]);
1320 let dtype = q.dtype();
1321 let (b, hq, lq) = (q.shape()[0], q.shape()[1], q.shape()[2]);
1322 let (hkv, lk) = (k.shape()[1], k.shape()[2]);
1323 let rep = hq / hkv;
1324 let i = |n: usize| n as isize;
1325 let mut q = multiply(q, f64::from(scale))?;
1326 let (mut k, mut v) = (k.clone(), v.clone());
1327 if rep > 1 {
1328 q = reshape(&q, &[i(b), i(hkv), i(rep), i(lq), i(q.shape()[3])])?;
1329 k = expand_dims(&k, 2)?;
1330 v = expand_dims(&v, 2)?;
1331 }
1332 let n = k.ndim();
1333 let mut axes: Vec<usize> = (0..n).collect();
1334 axes.swap(n - 2, n - 1);
1335 let mut scores = crate::matmul(&q, &crate::transpose(&k, &axes)?)?;
1336 let mask = if causal {
1337 let offset = lk as f64 - lq as f64;
1338 let qi = expand_dims(&arange(offset, offset + lq as f64, 1.0, DType::I32)?, 1)?;
1339 let ki = expand_dims(&arange(0.0, lk as f64, 1.0, DType::I32)?, 0)?;
1340 Some(crate::greater_equal(&qi, &ki)?)
1341 } else if has_mask {
1342 let m = &inputs[3];
1343 Some(if rep > 1 {
1344 reshape(m, &[i(b), i(hkv), i(rep), i(lq), i(lk)])?
1345 } else {
1346 m.clone()
1347 })
1348 } else {
1349 None
1350 };
1351 if let Some(m) = mask {
1352 scores = if m.dtype() == DType::Bool {
1353 let lowest = match dtype {
1354 DType::F16 => f64::from(crate::f16::MIN),
1355 DType::BF16 => f64::from(crate::bf16::MIN),
1356 DType::F64 => f64::MIN,
1357 _ => f64::from(f32::MIN),
1358 };
1359 where_(&m, &scores, lowest)?
1360 } else {
1361 add(&scores, &m)?
1362 };
1363 }
1364 let probs = if has_sinks {
1365 let s = astype(&scores, DType::F32)?;
1367 let sink = astype(inputs.last().expect("sinks"), DType::F32)?;
1368 let shape: Vec<isize> = if rep > 1 {
1369 vec![1, i(hkv), i(rep), 1, 1]
1370 } else {
1371 vec![1, i(hq), 1, 1]
1372 };
1373 let sink = reshape(&sink, &shape)?;
1374 let m = stop_gradient(&crate::maximum(&max(&s, &[-1], true)?, &sink)?)?;
1375 let e = exp(&subtract(&s, &m)?)?;
1376 let denom = add(sum(&e, &[-1], true)?, exp(&subtract(&sink, &m)?)?)?;
1377 astype(÷(&e, &denom)?, dtype)?
1378 } else if on_gpu(dtype, scores.size()) {
1379 crate::softmax(&scores, &[-1])?
1380 } else {
1381 softmax_graph(&scores)?
1382 };
1383 let out = crate::matmul(&probs, &v)?;
1384 if rep > 1 {
1385 reshape(&out, &[i(b), i(hq), i(lq), i(v.shape()[n - 1])])
1386 } else {
1387 Ok(out)
1388 }
1389}
1390
1391pub fn gated_delta(
1421 q: &Array,
1422 k: &Array,
1423 v: &Array,
1424 g: &Array,
1425 beta: &Array,
1426 state: &Array,
1427) -> Result<(Array, Array)> {
1428 gated_delta_op(q, k, v, g, beta, state, false)
1429}
1430
1431pub fn gated_delta_steps(
1437 q: &Array,
1438 k: &Array,
1439 v: &Array,
1440 g: &Array,
1441 beta: &Array,
1442 state: &Array,
1443) -> Result<(Array, Array)> {
1444 gated_delta_op(q, k, v, g, beta, state, true)
1445}
1446
1447fn gated_delta_op(
1448 q: &Array,
1449 k: &Array,
1450 v: &Array,
1451 g: &Array,
1452 beta: &Array,
1453 state: &Array,
1454 steps: bool,
1455) -> Result<(Array, Array)> {
1456 check_inputs(&[q, k, v, g, beta, state])?;
1457 let op = "gated_delta";
1458 for a in [q, k, v, g, beta, state] {
1459 floating(op, a.dtype())?;
1460 }
1461 let invalid = |msg: String| Err(Error::InvalidArgument(format!("{op}: {msg}")));
1462 if q.ndim() != 4 || q.shape() != k.shape() || v.ndim() != 4 {
1463 return invalid(format!(
1464 "q and k must be [B, T, Hk, Dk] and v [B, T, Hv, Dv], got {:?}, {:?}, {:?}",
1465 q.shape(),
1466 k.shape(),
1467 v.shape()
1468 ));
1469 }
1470 let (b, t, hk, dk) = (q.shape()[0], q.shape()[1], q.shape()[2], q.shape()[3]);
1471 let (hv, dv) = (v.shape()[2], v.shape()[3]);
1472 if v.shape()[..2] != [b, t] || hk == 0 || hv % hk != 0 {
1473 return invalid(format!(
1474 "v must be [{b}, {t}, Hv, Dv] with Hv a multiple of {hk}, got {:?}",
1475 v.shape()
1476 ));
1477 }
1478 if g.shape() != [b, t, hv] || beta.shape() != [b, t, hv] {
1479 return invalid(format!(
1480 "g and beta must be [{b}, {t}, {hv}], got {:?} and {:?}",
1481 g.shape(),
1482 beta.shape()
1483 ));
1484 }
1485 if state.shape() != [b, hv, dv, dk] {
1486 return invalid(format!(
1487 "state must be [{b}, {hv}, {dv}, {dk}], got {:?}",
1488 state.shape()
1489 ));
1490 }
1491 let inputs = vec![
1492 q.clone(),
1493 k.clone(),
1494 v.clone(),
1495 g.clone(),
1496 beta.clone(),
1497 state.clone(),
1498 ];
1499 let kernel = on_gpu(q.dtype(), q.size())
1500 && q.dtype() == v.dtype()
1501 && dk % 32 == 0
1502 && [g, beta, state]
1503 .iter()
1504 .all(|a| matches!(a.dtype(), DType::F16 | DType::BF16 | DType::F32));
1505 let op = FastOp::GatedDelta { steps };
1506 let mut out = if kernel {
1507 Array::from_op(Primitive::Fast(op), inputs)?
1508 } else {
1509 op.fallback(&inputs)?
1510 };
1511 let s = out.pop().expect("two outputs");
1512 Ok((out.pop().expect("two outputs"), s))
1513}
1514
1515fn gated_delta_graph(inputs: &[Array], steps: bool) -> Result<Vec<Array>> {
1518 let [q, k, v, g, beta, state] = inputs else {
1519 unreachable!("six inputs")
1520 };
1521 let (b, t, hk, dk) = (q.shape()[0], q.shape()[1], q.shape()[2], q.shape()[3]);
1522 let (hv, dv) = (v.shape()[2], v.shape()[3]);
1523 let heads = |x: &Array| -> Result<Array> {
1525 if hv == hk {
1526 return Ok(x.clone());
1527 }
1528 let wide = broadcast_to(&expand_dims(x, 3)?, &[b, t, hk, hv / hk, dk])?;
1529 reshape(&wide, &[b as isize, t as isize, hv as isize, dk as isize])
1530 };
1531 let wide = |x: &Array| match x.dtype() {
1533 DType::F64 => Ok(x.clone()),
1534 _ => astype(x, DType::F32),
1535 };
1536 let (qh, kh) = (heads(&wide(q)?)?, heads(&wide(k)?)?);
1537 let (v, g, beta) = (&wide(v)?, &wide(g)?, &wide(beta)?);
1538 let at = |x: &Array, i: usize| -> Result<Array> {
1539 let mut args = vec![SliceArg::full(); x.ndim()];
1540 args[1] = SliceArg::new(Some(i as isize), Some(i as isize + 1), 1);
1541 squeeze(&slice(x, &args)?, &[1])
1542 };
1543 let mut s = wide(state)?;
1544 let mut ys = Vec::with_capacity(t);
1545 let mut states = Vec::with_capacity(if steps { t } else { 0 });
1546 for i in 0..t {
1547 let (qt, kt, vt) = (at(&qh, i)?, expand_dims(&at(&kh, i)?, 2)?, at(v, i)?);
1548 let decay = reshape(&at(g, i)?, &[b as isize, hv as isize, 1, 1])?;
1549 s = multiply(&s, &decay)?;
1550 let kv_mem = sum(&multiply(&s, &kt)?, &[-1], false)?;
1551 let delta = multiply(subtract(&vt, &kv_mem)?, expand_dims(&at(beta, i)?, -1)?)?;
1552 s = add(&s, multiply(&kt, expand_dims(&delta, -1)?)?)?;
1553 let y = sum(&multiply(&s, expand_dims(&qt, 2)?)?, &[-1], false)?;
1554 ys.push(astype(&y, q.dtype())?);
1555 if steps {
1556 states.push(astype(&s, state.dtype())?);
1557 }
1558 }
1559 let y = match ys.is_empty() {
1560 true => zeros(&[b, 0, hv, dv], q.dtype())?,
1561 false => stack(&ys, 1)?,
1562 };
1563 let s = match (steps, states.is_empty()) {
1564 (false, _) => astype(&s, state.dtype())?,
1565 (true, false) => stack(&states, 1)?,
1566 (true, true) => zeros(&[b, 0, hv, dv, dk], state.dtype())?,
1567 };
1568 Ok(vec![y, s])
1569}
1570
1571fn gather_qmm_graph(
1574 inputs: &[Array],
1575 group_size: usize,
1576 bits: u32,
1577 transpose: bool,
1578) -> Result<Array> {
1579 let (lhs, rhs) = (&inputs[4], &inputs[5]);
1580 let rows = |a: &Array, indices: &Array| -> Result<Array> {
1581 let nd = a.ndim();
1582 let flat = reshape(
1583 a,
1584 &[-1, a.shape()[nd - 2] as isize, a.shape()[nd - 1] as isize],
1585 )?;
1586 crate::take(&flat, indices, Some(0))
1587 };
1588 crate::quantized_matmul(
1589 &rows(&inputs[0], lhs)?,
1590 &rows(&inputs[1], rhs)?,
1591 &rows(&inputs[2], rhs)?,
1592 &rows(&inputs[3], rhs)?,
1593 transpose,
1594 group_size,
1595 bits,
1596 )
1597}