1use crate::cast::Canon;
25use crate::kernels::Operand;
26use crate::ops::{
27 self, add, arange, astype, broadcast_to, cos, divide, equal, exp, expand_dims, filled, less,
28 log, max, mean, multiply, reshape, sin, slice, sqrt, squeeze, stop_gradient, subtract, sum,
29 where_,
30};
31use crate::primitive::Primitive;
32use crate::{
33 Array, Category, DType, Device, DeviceKind, Error, Result, SliceArg, Stream, default_stream,
34 set_default_stream,
35};
36use crate::{stack, zeros};
37
38pub use crate::kernel::{KernelCall, KernelConfig, MetalKernel, TemplateArg};
39
40#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42#[non_exhaustive]
43pub enum FastOp {
44 RmsNorm {
46 eps: u32,
48 },
49 RmsNormVjp {
51 eps: u32,
53 },
54 LayerNorm {
57 eps: u32,
59 },
60 LayerNormVjp {
62 eps: u32,
64 },
65 Rope {
67 dims: usize,
69 traditional: bool,
71 base: u32,
73 scale: u32,
75 forward: bool,
77 },
78 Softmax,
80 LogSumExp,
82 CrossEntropy,
84 CrossEntropyVjp,
86 Attention {
89 scale: u32,
91 causal: bool,
93 mask: bool,
95 sinks: bool,
97 },
98 GatedDelta,
101 GatherMm,
104 GatherQmm {
109 group_size: usize,
111 bits: u8,
113 transpose: bool,
115 },
116}
117
118#[derive(Debug, Clone, Copy)]
120#[non_exhaustive]
121pub enum AttentionMask<'a> {
122 None,
124 Causal,
126 Array(&'a Array),
129}
130
131fn float(bits: u32) -> f32 {
132 f32::from_bits(bits)
133}
134
135fn check_inputs(inputs: &[&Array]) -> Result<()> {
136 match inputs.iter().find_map(|a| a.error()) {
137 Some(err) => Err(err),
138 None => Ok(()),
139 }
140}
141
142fn scalar(value: f64, dtype: DType) -> Result<Array> {
144 filled(&[], dtype, |_| Canon::Float(value))
145}
146
147pub(crate) fn on_gpu(dtype: DType, size: usize) -> bool {
149 default_stream().device.kind == DeviceKind::Gpu
150 && matches!(dtype, DType::F16 | DType::BF16 | DType::F32)
151 && size > 0
152 && size < u32::MAX as usize
153}
154
155pub(crate) fn record_or_fallback(op: FastOp, inputs: Vec<Array>) -> Result<Vec<Array>> {
158 if on_gpu(inputs[0].dtype(), inputs[0].size()) {
159 Array::from_op(Primitive::Fast(op), inputs)
160 } else {
161 op.fallback(&inputs)
162 }
163}
164
165fn one(op: FastOp, inputs: Vec<Array>) -> Result<Array> {
166 Ok(record_or_fallback(op, inputs)?.swap_remove(0))
167}
168
169fn floating(op: &'static str, dtype: DType) -> Result<()> {
170 match dtype.category() {
171 Category::Float => Ok(()),
172 _ => Err(Error::UnsupportedDType { op, dtype }),
173 }
174}
175
176fn check_param(op: &'static str, what: &str, p: &Array, x: &Array) -> Result<()> {
178 if p.ndim() != 1 || p.size() != x.shape()[x.ndim() - 1] {
179 return Err(Error::InvalidArgument(format!(
180 "{op}: {what} must be 1-D with {} elements, got shape {:?}",
181 x.shape()[x.ndim() - 1],
182 p.shape()
183 )));
184 }
185 Ok(())
186}
187
188pub fn rms_norm(x: &Array, weight: Option<&Array>, eps: f32) -> Result<Array> {
192 check_inputs(&[x])?;
193 if x.ndim() == 0 {
194 return Err(Error::InvalidArgument(
195 "rms_norm: the input needs at least one dimension".into(),
196 ));
197 }
198 let dtype = match weight {
199 Some(w) => {
200 check_inputs(&[w])?;
201 check_param("rms_norm", "weight", w, x)?;
202 crate::promote_types(x.dtype(), w.dtype())
203 }
204 None => x.dtype(),
205 };
206 floating("rms_norm", dtype)?;
207 let w = match weight {
208 Some(w) => astype(w, dtype)?,
209 None => scalar(1.0, dtype)?,
210 };
211 let op = FastOp::RmsNorm { eps: eps.to_bits() };
212 one(op, vec![astype(x, dtype)?, w])
213}
214
215pub fn layer_norm(
218 x: &Array,
219 weight: Option<&Array>,
220 bias: Option<&Array>,
221 eps: f32,
222) -> Result<Array> {
223 check_inputs(&[x])?;
224 if x.ndim() == 0 {
225 return Err(Error::InvalidArgument(
226 "layer_norm: the input needs at least one dimension".into(),
227 ));
228 }
229 let mut dtype = x.dtype();
230 for (p, what) in [(weight, "weight"), (bias, "bias")] {
231 if let Some(p) = p {
232 check_inputs(&[p])?;
233 check_param("layer_norm", what, p, x)?;
234 dtype = crate::promote_types(dtype, p.dtype());
235 }
236 }
237 floating("layer_norm", dtype)?;
238 let w = match weight {
239 Some(w) => astype(w, dtype)?,
240 None => scalar(1.0, dtype)?,
241 };
242 let b = match bias {
243 Some(b) => astype(b, dtype)?,
244 None => scalar(0.0, dtype)?,
245 };
246 let op = FastOp::LayerNorm { eps: eps.to_bits() };
247 one(op, vec![astype(x, dtype)?, w, b])
248}
249
250pub fn rope(
256 x: &Array,
257 dims: usize,
258 traditional: bool,
259 base: Option<f32>,
260 scale: f32,
261 offset: &Array,
262 freqs: Option<&Array>,
263) -> Result<Array> {
264 check_inputs(&[x, offset])?;
265 let invalid = |msg: String| Err(Error::InvalidArgument(format!("rope: {msg}")));
266 if x.ndim() < 3 {
267 return invalid(format!(
268 "the input needs at least 3 dimensions, got {:?}",
269 x.shape()
270 ));
271 }
272 floating("rope", x.dtype())?;
273 if offset.ndim() > 1 || (offset.size() != 1 && offset.size() != x.shape()[0]) {
274 return invalid(format!(
275 "offset must be a scalar or have {} elements, got shape {:?}",
276 x.shape()[0],
277 offset.shape()
278 ));
279 }
280 if !matches!(
281 offset.dtype().category(),
282 Category::Signed | Category::Unsigned
283 ) {
284 return invalid(format!("offset must be an integer, got {}", offset.dtype()));
285 }
286 if dims == 0 || !dims.is_multiple_of(2) || dims > x.shape()[x.ndim() - 1] {
287 return invalid(format!(
288 "dims must be even, positive and at most {}, got {dims}",
289 x.shape()[x.ndim() - 1]
290 ));
291 }
292 let offset = match offset.dtype().size() {
293 4 => offset.clone(),
294 _ => astype(offset, DType::I32)?,
295 };
296 let mut inputs = vec![x.clone(), offset];
297 let base = match (base, freqs) {
298 (Some(b), None) => b,
299 (None, Some(f)) => {
300 check_inputs(&[f])?;
301 if f.ndim() != 1 || f.size() != dims / 2 {
302 return invalid(format!(
303 "freqs must be 1-D with {} elements, got shape {:?}",
304 dims / 2,
305 f.shape()
306 ));
307 }
308 inputs.push(astype(f, DType::F32)?);
309 1.0
310 }
311 _ => return invalid("exactly one of base and freqs must be given".into()),
312 };
313 let op = FastOp::Rope {
314 dims,
315 traditional,
316 base: base.to_bits(),
317 scale: scale.to_bits(),
318 forward: true,
319 };
320 one(op, inputs)
321}
322
323pub fn cross_entropy(logits: &Array, targets: &Array) -> Result<Array> {
327 check_inputs(&[logits, targets])?;
328 if logits.ndim() == 0 || targets.shape() != &logits.shape()[..logits.ndim() - 1] {
329 return Err(Error::InvalidArgument(format!(
330 "cross_entropy: targets of shape {:?} do not match logits of shape {:?}",
331 targets.shape(),
332 logits.shape()
333 )));
334 }
335 floating("cross_entropy", logits.dtype())?;
336 if !matches!(
337 targets.dtype().category(),
338 Category::Signed | Category::Unsigned
339 ) {
340 return Err(Error::InvalidArgument(format!(
341 "cross_entropy: targets must be integer class indices, got {}",
342 targets.dtype()
343 )));
344 }
345 one(
346 FastOp::CrossEntropy,
347 vec![logits.clone(), astype(targets, DType::I32)?],
348 )
349}
350
351fn update_last(base: &Array, update: &Array, start: usize, step: isize) -> Result<Array> {
354 let n = base.ndim();
355 let mut starts = vec![0; n];
356 starts[n - 1] = start;
357 let mut steps = vec![1; n];
358 steps[n - 1] = step;
359 let primitive = Primitive::SliceUpdate {
360 start: starts,
361 step: steps,
362 len: update.shape().to_vec(),
363 };
364 Ok(Array::from_op(primitive, vec![base.clone(), update.clone()])?.swap_remove(0))
365}
366
367fn last(x: &Array, start: usize, step: isize, len: usize) -> Result<Array> {
369 let mut args = vec![SliceArg::full(); x.ndim()];
370 let stop = start as isize + step * len as isize;
371 args[x.ndim() - 1] = SliceArg::new(Some(start as isize), Some(stop), step);
372 slice(x, &args)
373}
374
375fn rsqrt(x: &Array) -> Result<Array> {
377 divide(1.0, sqrt(x)?)
378}
379
380fn softmax_graph(x: &Array) -> Result<Array> {
382 let xf = if x.dtype() == DType::F32 {
383 x.clone()
384 } else {
385 astype(x, DType::F32)?
386 };
387 let m = stop_gradient(&max(&xf, &[-1], true)?)?;
388 let e = exp(&subtract(&xf, &m)?)?;
389 astype(÷(&e, sum(&e, &[-1], true)?)?, x.dtype())
390}
391
392fn logsumexp_graph(x: &Array) -> Result<Array> {
394 let m = stop_gradient(&max(x, &[-1], true)?)?;
395 let out = add(log(&sum(&exp(&subtract(x, &m)?)?, &[-1], true)?)?, &m)?;
396 where_(equal(&ops::abs(&m)?, f64::INFINITY)?, &m, &out)
397}
398
399fn one_hot(y: &Array, classes: usize, dtype: DType) -> Result<Array> {
401 let positions = arange(0.0, classes as f64, 1.0, y.dtype())?;
402 equal(&expand_dims(y, -1)?, &positions)?.astype(dtype)
403}
404
405fn wrap(y: &Array, classes: usize) -> Result<Array> {
407 where_(less(y, 0)?, add(y, classes as f64)?, y)
408}
409
410impl FastOp {
411 pub(crate) fn name(&self) -> &'static str {
412 match self {
413 Self::RmsNorm { .. } => "rms_norm",
414 Self::RmsNormVjp { .. } => "rms_norm_vjp",
415 Self::LayerNorm { .. } => "layer_norm",
416 Self::LayerNormVjp { .. } => "layer_norm_vjp",
417 Self::Rope { .. } => "rope",
418 Self::Softmax => "softmax",
419 Self::LogSumExp => "logsumexp",
420 Self::CrossEntropy => "cross_entropy",
421 Self::CrossEntropyVjp => "cross_entropy_vjp",
422 Self::Attention { .. } => "scaled_dot_product_attention",
423 Self::GatedDelta => "gated_delta",
424 Self::GatherQmm { .. } => "gather_qmm",
425 Self::GatherMm => "gather_mm",
426 }
427 }
428
429 pub(crate) fn outputs(&self, inputs: &[Array]) -> Result<Vec<(DType, Vec<usize>)>> {
431 let arity = match self {
432 Self::Softmax | Self::LogSumExp => 1,
433 Self::RmsNorm { .. } | Self::CrossEntropy => 2,
434 Self::RmsNormVjp { .. } | Self::LayerNorm { .. } => 3,
435 Self::LayerNormVjp { .. } | Self::CrossEntropyVjp => 4,
436 Self::Rope { .. } => inputs.len().clamp(2, 3),
437 Self::Attention { mask, sinks, .. } => 3 + usize::from(*mask) + usize::from(*sinks),
438 Self::GatedDelta => 6,
439 Self::GatherQmm { .. } => inputs.len().clamp(6, 7),
440 Self::GatherMm => inputs.len().clamp(4, 5),
441 };
442 if inputs.len() != arity || inputs[0].ndim() == 0 {
443 return Err(Error::InvalidArgument(format!(
444 "{}: {arity} inputs expected, got {}",
445 self.name(),
446 inputs.len()
447 )));
448 }
449 let x = &inputs[0];
450 let like = |a: &Array| (a.dtype(), a.shape().to_vec());
451 Ok(match self {
452 Self::RmsNorm { .. } | Self::LayerNorm { .. } | Self::Rope { .. } | Self::Softmax => {
453 vec![like(x)]
454 }
455 Self::LogSumExp => {
456 let mut shape = x.shape().to_vec();
457 *shape.last_mut().expect("1-D") = 1;
458 vec![(x.dtype(), shape)]
459 }
460 Self::RmsNormVjp { .. } => vec![like(x), like(&inputs[1])],
461 Self::LayerNormVjp { .. } => vec![like(x), like(&inputs[1]), like(&inputs[2])],
462 Self::CrossEntropy => vec![(DType::F32, inputs[1].shape().to_vec())],
463 Self::CrossEntropyVjp => vec![like(x)],
464 Self::Attention { .. } => {
465 let mut shape = x.shape().to_vec();
466 shape[3] = inputs[2].shape()[3];
467 vec![(x.dtype(), shape)]
468 }
469 Self::GatedDelta => vec![(x.dtype(), inputs[2].shape().to_vec()), like(&inputs[5])],
470 Self::GatherMm => {
471 let b = inputs[1].shape();
472 let mut shape = inputs[2].shape().to_vec();
473 shape.extend([x.shape()[x.ndim() - 2], b[b.len() - 1]]);
474 vec![(x.dtype(), shape)]
475 }
476 Self::GatherQmm {
477 bits, transpose, ..
478 } => {
479 let w = inputs[1].shape();
480 let n = match transpose {
481 true => w[w.len() - 2],
482 false => w[w.len() - 1] * 32 / usize::from(*bits),
483 };
484 let mut shape = inputs[4].shape().to_vec();
485 shape.extend([x.shape()[x.ndim() - 2], n]);
486 vec![(x.dtype(), shape)]
487 }
488 })
489 }
490
491 pub(crate) fn fallback(&self, inputs: &[Array]) -> Result<Vec<Array>> {
493 let x = &inputs[0];
494 let f32x = || astype(x, DType::F32);
495 let out = match self {
496 Self::RmsNorm { eps } => {
497 let xf = f32x()?;
498 let n = rsqrt(&add(mean(&multiply(&xf, &xf)?, &[-1], true)?, float(*eps))?)?;
499 let y = multiply(&xf, &n)?.astype(x.dtype())?;
500 let w = &inputs[1];
501 vec![if w.ndim() == 0 { y } else { multiply(&y, w)? }]
502 }
503 Self::LayerNorm { eps } => {
504 let xf = f32x()?;
505 let xc = subtract(&xf, mean(&xf, &[-1], true)?)?;
506 let v = mean(&multiply(&xc, &xc)?, &[-1], true)?;
507 let mut y = multiply(&xc, rsqrt(&add(&v, float(*eps))?)?)?.astype(x.dtype())?;
508 if inputs[1].ndim() != 0 {
509 y = multiply(&y, &inputs[1])?;
510 }
511 if inputs[2].ndim() != 0 {
512 y = add(&y, &inputs[2])?;
513 }
514 vec![y]
515 }
516 Self::RmsNormVjp { eps } => {
517 let (w, g) = (&inputs[1], &inputs[2]);
518 let n = rsqrt(&add(mean(&multiply(x, x)?, &[-1], true)?, float(*eps))?)?;
519 let n3 = multiply(&multiply(&n, &n)?, &n)?;
520 let gw = multiply(g, w)?;
521 let t = mean(&multiply(&gw, x)?, &[-1], true)?;
522 let dx = subtract(multiply(&gw, &n)?, multiply(multiply(x, &t)?, &n3)?)?;
523 let dw = match w.ndim() {
524 0 => scalar(0.0, w.dtype())?,
525 _ => sum_rows(&multiply(g, multiply(x, &n)?)?)?,
526 };
527 vec![dx, dw]
528 }
529 Self::LayerNormVjp { eps } => {
530 let (w, b, g) = (&inputs[1], &inputs[2], &inputs[3]);
531 let mu = mean(x, &[-1], true)?;
532 let xc = subtract(x, &mu)?;
533 let var = mean(&multiply(&xc, &xc)?, &[-1], true)?;
534 let n = rsqrt(&add(&var, float(*eps))?)?;
535 let n3 = multiply(&multiply(&n, &n)?, &n)?;
536 let wg = multiply(w, g)?;
537 let mwg = mean(&wg, &[-1], true)?;
538 let mwgxc = mean(&multiply(&wg, &xc)?, &[-1], true)?;
539 let dx = subtract(
540 multiply(subtract(&wg, &mwg)?, &n)?,
541 multiply(multiply(&xc, &mwgxc)?, &n3)?,
542 )?;
543 let dw = match w.ndim() {
544 0 => scalar(0.0, w.dtype())?,
545 _ => sum_rows(&multiply(g, multiply(&xc, &n)?)?)?,
546 };
547 let db = match b.ndim() {
548 0 => scalar(0.0, b.dtype())?,
549 _ => sum_rows(g)?,
550 };
551 vec![dx, dw, db]
552 }
553 Self::Attention {
554 scale,
555 causal,
556 mask,
557 sinks,
558 } => vec![attention_graph(
559 inputs,
560 float(*scale),
561 *causal,
562 *mask,
563 *sinks,
564 )?],
565 Self::Rope {
566 dims,
567 traditional,
568 base,
569 scale,
570 forward,
571 } => vec![rope_graph(
572 inputs,
573 *dims,
574 *traditional,
575 float(*base),
576 float(*scale),
577 *forward,
578 )?],
579 Self::Softmax => vec![softmax_graph(x)?],
580 Self::GatedDelta => gated_delta_graph(inputs)?,
581 Self::GatherMm => {
582 let rows = |a: &Array, indices: &Array| -> Result<Array> {
583 let nd = a.ndim();
584 let shape = [-1, a.shape()[nd - 2] as isize, a.shape()[nd - 1] as isize];
585 crate::take(&reshape(a, &shape)?, indices, Some(0))
586 };
587 vec![crate::matmul(
588 &rows(x, &inputs[2])?,
589 &rows(&inputs[1], &inputs[3])?,
590 )?]
591 }
592 Self::GatherQmm {
593 group_size,
594 bits,
595 transpose,
596 } => vec![gather_qmm_graph(
597 inputs,
598 *group_size,
599 u32::from(*bits),
600 *transpose,
601 )?],
602 Self::LogSumExp => vec![logsumexp_graph(x)?],
603 Self::CrossEntropy => {
604 let classes = x.shape()[x.ndim() - 1];
605 let y = wrap(&inputs[1], classes)?;
606 let hit = one_hot(&y, classes, DType::Bool)?;
607 let score = sum(&where_(&hit, x, 0.0)?, &[-1], false)?;
608 let lse = squeeze(&logsumexp_graph(x)?, &[-1])?;
609 vec![subtract(&lse, &score)?.astype(DType::F32)?]
610 }
611 Self::CrossEntropyVjp => {
612 let classes = x.shape()[x.ndim() - 1];
613 let (y, loss, g) = (wrap(&inputs[1], classes)?, &inputs[2], &inputs[3]);
614 let hit = one_hot(&y, classes, DType::Bool)?;
615 let score = sum(&where_(&hit, x, 0.0)?, &[-1], false)?;
616 let lse = add(loss, score.astype(DType::F32)?)?;
617 let p = exp(&subtract(&f32x()?, expand_dims(&lse, -1)?)?)?;
618 let gx = multiply(
619 expand_dims(g, -1)?,
620 subtract(&p, one_hot(&y, classes, DType::F32)?)?,
621 )?;
622 vec![gx.astype(x.dtype())?]
623 }
624 };
625 Ok(out)
626 }
627
628 pub(crate) fn vjp(
630 &self,
631 inputs: &[Array],
632 outputs: &[Array],
633 cotangents: &[Array],
634 wanted: &[bool],
635 ) -> Result<Vec<Option<Array>>> {
636 let g = &cotangents[0];
637 let pick = |all: Vec<Array>| -> Vec<Option<Array>> {
638 all.into_iter()
639 .zip(wanted)
640 .map(|(a, &w)| w.then_some(a))
641 .collect()
642 };
643 match self {
644 Self::RmsNorm { eps } => {
645 let op = Self::RmsNormVjp { eps: *eps };
646 let grads =
647 record_or_fallback(op, vec![inputs[0].clone(), inputs[1].clone(), g.clone()])?;
648 Ok(pick(grads))
649 }
650 Self::LayerNorm { eps } => {
651 let op = Self::LayerNormVjp { eps: *eps };
652 let mut all = inputs.to_vec();
653 all.push(g.clone());
654 Ok(pick(record_or_fallback(op, all)?))
655 }
656 Self::Rope { forward, .. } => {
657 if wanted[1..].iter().any(|&w| w) {
658 return Err(Error::InvalidArgument(
659 "rope: no gradient with respect to the offset or frequencies".into(),
660 ));
661 }
662 let Self::Rope {
663 dims,
664 traditional,
665 base,
666 scale,
667 ..
668 } = *self
669 else {
670 unreachable!()
671 };
672 let inverse = Self::Rope {
673 dims,
674 traditional,
675 base,
676 scale,
677 forward: !forward,
678 };
679 let mut all = inputs.to_vec();
680 all[0] = g.clone();
681 let mut out = vec![Some(one(inverse, all)?)];
682 out.extend(std::iter::repeat_n(None, inputs.len() - 1));
683 Ok(out)
684 }
685 Self::Softmax => {
686 let s = &outputs[0];
687 let sg = multiply(s, g)?;
688 let dx = subtract(&sg, multiply(s, sum(&sg, &[-1], true)?)?)?;
689 Ok(vec![Some(dx)])
690 }
691 Self::LogSumExp => {
692 let s = one(Self::Softmax, vec![inputs[0].clone()])?;
693 Ok(vec![Some(multiply(g, &s)?)])
694 }
695 Self::CrossEntropy => {
696 if wanted[1] {
697 return Err(Error::InvalidArgument(
698 "cross_entropy: no gradient with respect to the targets".into(),
699 ));
700 }
701 let all = vec![
702 inputs[0].clone(),
703 inputs[1].clone(),
704 outputs[0].clone(),
705 g.clone(),
706 ];
707 Ok(vec![Some(one(Self::CrossEntropyVjp, all)?), None])
708 }
709 Self::RmsNormVjp { .. }
710 | Self::LayerNormVjp { .. }
711 | Self::CrossEntropyVjp
712 | Self::Attention { .. }
713 | Self::GatedDelta
714 | Self::GatherQmm { .. }
715 | Self::GatherMm => self.fallback_vjp(inputs, cotangents, wanted),
716 }
717 }
718
719 fn fallback_vjp(
721 &self,
722 inputs: &[Array],
723 cotangents: &[Array],
724 wanted: &[bool],
725 ) -> Result<Vec<Option<Array>>> {
726 let active: Vec<usize> = (0..inputs.len())
727 .filter(|&i| wanted[i] && inputs[i].dtype().category() == Category::Float)
728 .collect();
729 let primals: Vec<Array> = active.iter().map(|&i| inputs[i].clone()).collect();
730 let f = |ps: &[Array]| {
731 let mut all = inputs.to_vec();
732 for (p, &i) in ps.iter().zip(&active) {
733 all[i] = p.clone();
734 }
735 self.fallback(&all)
736 };
737 let (_, grads) = crate::vjp(f, &primals, cotangents)?;
738 let mut out = vec![None; inputs.len()];
739 for (g, &i) in grads.into_iter().zip(&active) {
740 out[i] = Some(g);
741 }
742 Ok(out)
743 }
744
745 pub(crate) fn jvp(&self, inputs: &[Array], tangents: &[Option<Array>]) -> Result<Vec<Array>> {
747 let active: Vec<usize> = (0..inputs.len())
748 .filter(|&i| tangents[i].is_some() && inputs[i].dtype().category() == Category::Float)
749 .collect();
750 let primals: Vec<Array> = active.iter().map(|&i| inputs[i].clone()).collect();
751 let ts: Vec<Array> = active
752 .iter()
753 .map(|&i| tangents[i].clone().expect("active"))
754 .collect();
755 let f = |ps: &[Array]| {
756 let mut all = inputs.to_vec();
757 for (p, &i) in ps.iter().zip(&active) {
758 all[i] = p.clone();
759 }
760 self.fallback(&all)
761 };
762 Ok(crate::jvp(f, &primals, &ts)?.1)
763 }
764
765 pub(crate) fn vmap(
769 &self,
770 inputs: &[Array],
771 axes: &[Option<usize>],
772 ) -> Result<(Vec<Array>, usize)> {
773 let rest_unbatched = axes[1..].iter().all(Option::is_none);
774 let front = |i: usize| -> Result<Array> {
775 let a = &inputs[i];
776 match axes[i] {
777 Some(ax) => crate::batching::move_axis(a, ax, 0),
778 None => {
779 let b = inputs
780 .iter()
781 .zip(axes)
782 .find_map(|(a, ax)| ax.map(|ax| a.shape()[ax]))
783 .expect("one batched input");
784 let mut shape = vec![b];
785 shape.extend_from_slice(a.shape());
786 broadcast_to(&expand_dims(a, 0)?, &shape)
787 }
788 }
789 };
790 let simple = match self {
791 Self::RmsNorm { .. } | Self::LayerNorm { .. } | Self::Softmax | Self::LogSumExp => {
792 rest_unbatched
793 }
794 Self::Rope { .. } => rest_unbatched && inputs[1].size() == 1,
795 Self::CrossEntropy => true,
796 _ => false,
797 };
798 if simple {
799 let mut all = inputs.to_vec();
800 all[0] = front(0)?;
801 if *self == Self::CrossEntropy {
802 all[1] = front(1)?;
803 }
804 return Ok((record_or_fallback(self.clone(), all)?, 0));
805 }
806 let f = |xs: &[Array]| self.fallback(xs);
807 Ok((crate::transforms::batch(&f, inputs, axes)?, 0))
808 }
809
810 pub(crate) fn eval_cpu(&self, operands: &[Operand<'_>]) -> Result<Vec<crate::array::Data>> {
812 let leaves = operands
813 .iter()
814 .map(|(meta, data)| {
815 let buffer = crate::kernels::contiguous(meta, data)?;
816 Array::from_buffer(buffer, meta.dtype, &meta.shape)
817 })
818 .collect::<Result<Vec<_>>>()?;
819 let previous = default_stream();
820 set_default_stream(Stream::default_for(Device::cpu()));
821 let outputs = self.fallback(&leaves);
822 set_default_stream(previous);
823 let outputs = outputs?;
824 crate::eval(&outputs)?;
825 outputs
826 .iter()
827 .map(|a| {
828 let data = a.data().expect("evaluated");
829 Ok(crate::array::Data::new(crate::kernels::contiguous(
830 a.out(),
831 data,
832 )?))
833 })
834 .collect()
835 }
836}
837
838fn sum_rows(g: &Array) -> Result<Array> {
840 let axes: Vec<isize> = (0..g.ndim() as isize - 1).collect();
841 sum(g, &axes, false)
842}
843
844fn rope_graph(
846 inputs: &[Array],
847 dims: usize,
848 traditional: bool,
849 base: f32,
850 scale: f32,
851 forward: bool,
852) -> Result<Array> {
853 let x = &inputs[0];
854 let shape: Vec<isize> = x.shape().iter().map(|&d| d as isize).collect();
855 let nd = x.ndim();
856 let (b, t, d) = (x.shape()[0], x.shape()[nd - 2], x.shape()[nd - 1]);
857 let x4 = reshape(x, &[b as isize, -1, t as isize, d as isize])?;
858 let dtype = x.dtype();
859 let half = dims / 2;
860 let offset = &inputs[1];
861 let offset = if offset.size() > 1 {
862 reshape(offset, &[-1, 1, 1])?
863 } else {
864 reshape(offset, &[])?
865 };
866 let positions = multiply(
867 add(arange(0.0, t as f64, 1.0, DType::F32)?, &offset)?,
868 f64::from(scale),
869 )?;
870 let inv_freqs = match inputs.get(2) {
871 Some(f) => divide(1.0, f)?,
872 None => exp(&multiply(
873 arange(0.0, -(half as f64), -1.0, DType::F32)?,
874 f64::from(base.ln() / half as f32),
875 )?)?,
876 };
877 let theta = multiply(expand_dims(&positions, -1)?, &inv_freqs)?;
878 let (c, s) = (cos(&theta)?.astype(dtype)?, sin(&theta)?.astype(dtype)?);
879 let (start2, step) = if traditional { (1, 2) } else { (half, 1) };
880 let x1 = last(&x4, 0, step, half)?;
881 let x2 = last(&x4, start2, step, half)?;
882 let (r1, r2) = if forward {
883 (
884 subtract(multiply(&x1, &c)?, multiply(&x2, &s)?)?,
885 add(multiply(&x1, &s)?, multiply(&x2, &c)?)?,
886 )
887 } else {
888 (
889 add(multiply(&x2, &s)?, multiply(&x1, &c)?)?,
890 subtract(multiply(&x2, &c)?, multiply(&x1, &s)?)?,
891 )
892 };
893 let full = |r: Array| broadcast_to(&r, x1.shape());
894 let out = update_last(&x4, &full(r1)?, 0, step)?;
895 let out = update_last(&out, &full(r2)?, start2, step)?;
896 reshape(&out, &shape)
897}
898
899pub fn scaled_dot_product_attention(
908 q: &Array,
909 k: &Array,
910 v: &Array,
911 scale: f32,
912 mask: AttentionMask<'_>,
913 sinks: Option<&Array>,
914) -> Result<Array> {
915 check_inputs(&[q, k, v])?;
916 let invalid = |msg: String| {
917 Err(Error::InvalidArgument(format!(
918 "scaled_dot_product_attention: {msg}"
919 )))
920 };
921 if [q, k, v].iter().any(|a| a.ndim() != 4) {
922 return invalid(format!(
923 "q, k and v must be 4-D, got {:?}, {:?} and {:?}",
924 q.shape(),
925 k.shape(),
926 v.shape()
927 ));
928 }
929 let (qs, ks, vs) = (q.shape(), k.shape(), v.shape());
930 if ks[0] != qs[0] || vs[0] != qs[0] || qs[3] != ks[3] || ks[1] != vs[1] || ks[2] != vs[2] {
931 return invalid(format!("incompatible shapes {qs:?}, {ks:?} and {vs:?}"));
932 }
933 if ks[1] == 0 || qs[1] % ks[1] != 0 {
934 return invalid(format!(
935 "the query heads ({}) must be a multiple of the key heads ({})",
936 qs[1], ks[1]
937 ));
938 }
939 let dtype = crate::promote_types(crate::promote_types(q.dtype(), k.dtype()), v.dtype());
940 floating("scaled_dot_product_attention", dtype)?;
941 let mut inputs = vec![astype(q, dtype)?, astype(k, dtype)?, astype(v, dtype)?];
942 let (causal, has_mask) = match mask {
943 AttentionMask::None => (false, false),
944 AttentionMask::Causal => (true, false),
945 AttentionMask::Array(m) => {
946 check_inputs(&[m])?;
947 if m.ndim() > 4 {
948 return invalid(format!(
949 "the mask must have at most 4 dimensions, got {:?}",
950 m.shape()
951 ));
952 }
953 let m = if m.dtype() == DType::Bool {
954 m.clone()
955 } else if crate::promote_types(m.dtype(), dtype) == dtype {
956 astype(m, dtype)?
957 } else {
958 return invalid(format!("a {} mask does not promote to {dtype}", m.dtype()));
959 };
960 inputs.push(broadcast_to(&m, &[qs[0], qs[1], qs[2], ks[2]])?);
961 (false, true)
962 }
963 };
964 if let Some(s) = sinks {
965 check_inputs(&[s])?;
966 if s.ndim() != 1 || s.size() != qs[1] || crate::promote_types(s.dtype(), dtype) != dtype {
967 return invalid(format!(
968 "sinks must be [{}] of a type promoting to {dtype}",
969 qs[1]
970 ));
971 }
972 inputs.push(astype(s, dtype)?);
973 }
974 let op = FastOp::Attention {
975 scale: scale.to_bits(),
976 causal,
977 mask: has_mask,
978 sinks: sinks.is_some(),
979 };
980 if on_gpu(dtype, q.size()) && attention_kernel(&inputs, causal, has_mask) {
981 Ok(Array::from_op(Primitive::Fast(op), inputs)?.swap_remove(0))
982 } else {
983 Ok(op.fallback(&inputs)?.swap_remove(0))
984 }
985}
986
987fn attention_kernel(inputs: &[Array], causal: bool, has_mask: bool) -> bool {
990 let (q, k, v) = (&inputs[0], &inputs[1], &inputs[2]);
991 let (lq, lk, d, dv) = (q.shape()[2], k.shape()[2], q.shape()[3], v.shape()[3]);
992 let gqa = q.shape()[1] / k.shape()[1];
993 if lk == 0 || lq == 0 {
994 return false;
995 }
996 if lq > 8 {
997 let head =
998 (d == dv && matches!(d, 64 | 72 | 80 | 96 | 128 | 192 | 256)) || (d, dv) == (96, 64);
999 let causal_ok = !causal || lq <= lk;
1000 let slow = match d {
1003 192 => true,
1004 256 => !(q.dtype() != DType::F32 && causal && lq >= 2048 && lq == lk),
1005 _ => false,
1006 };
1007 head && causal_ok && !slow
1008 } else {
1009 let head = (d == dv && matches!(d, 64 | 96 | 128 | 256 | 512))
1010 || (d, dv) == (192, 128)
1011 || (d, dv) == (96, 64);
1012 let d512 = d != 512 || (lq == 1 && gqa == 8 && !has_mask && lk >= 1024);
1013 head && lq <= lk && lq * gqa <= 32 && d512
1014 }
1015}
1016
1017fn attention_graph(
1019 inputs: &[Array],
1020 scale: f32,
1021 causal: bool,
1022 has_mask: bool,
1023 has_sinks: bool,
1024) -> Result<Array> {
1025 let (q, k, v) = (&inputs[0], &inputs[1], &inputs[2]);
1026 let dtype = q.dtype();
1027 let (b, hq, lq) = (q.shape()[0], q.shape()[1], q.shape()[2]);
1028 let (hkv, lk) = (k.shape()[1], k.shape()[2]);
1029 let rep = hq / hkv;
1030 let i = |n: usize| n as isize;
1031 let mut q = multiply(q, f64::from(scale))?;
1032 let (mut k, mut v) = (k.clone(), v.clone());
1033 if rep > 1 {
1034 q = reshape(&q, &[i(b), i(hkv), i(rep), i(lq), i(q.shape()[3])])?;
1035 k = expand_dims(&k, 2)?;
1036 v = expand_dims(&v, 2)?;
1037 }
1038 let n = k.ndim();
1039 let mut axes: Vec<usize> = (0..n).collect();
1040 axes.swap(n - 2, n - 1);
1041 let mut scores = crate::matmul(&q, &crate::transpose(&k, &axes)?)?;
1042 let mask = if causal {
1043 let offset = lk as f64 - lq as f64;
1044 let qi = expand_dims(&arange(offset, offset + lq as f64, 1.0, DType::I32)?, 1)?;
1045 let ki = expand_dims(&arange(0.0, lk as f64, 1.0, DType::I32)?, 0)?;
1046 Some(crate::greater_equal(&qi, &ki)?)
1047 } else if has_mask {
1048 let m = &inputs[3];
1049 Some(if rep > 1 {
1050 reshape(m, &[i(b), i(hkv), i(rep), i(lq), i(lk)])?
1051 } else {
1052 m.clone()
1053 })
1054 } else {
1055 None
1056 };
1057 if let Some(m) = mask {
1058 scores = if m.dtype() == DType::Bool {
1059 let lowest = match dtype {
1060 DType::F16 => f64::from(crate::f16::MIN),
1061 DType::BF16 => f64::from(crate::bf16::MIN),
1062 DType::F64 => f64::MIN,
1063 _ => f64::from(f32::MIN),
1064 };
1065 where_(&m, &scores, lowest)?
1066 } else {
1067 add(&scores, &m)?
1068 };
1069 }
1070 let probs = if has_sinks {
1071 let s = astype(&scores, DType::F32)?;
1073 let sink = astype(inputs.last().expect("sinks"), DType::F32)?;
1074 let shape: Vec<isize> = if rep > 1 {
1075 vec![1, i(hkv), i(rep), 1, 1]
1076 } else {
1077 vec![1, i(hq), 1, 1]
1078 };
1079 let sink = reshape(&sink, &shape)?;
1080 let m = stop_gradient(&crate::maximum(&max(&s, &[-1], true)?, &sink)?)?;
1081 let e = exp(&subtract(&s, &m)?)?;
1082 let denom = add(sum(&e, &[-1], true)?, exp(&subtract(&sink, &m)?)?)?;
1083 astype(÷(&e, &denom)?, dtype)?
1084 } else if on_gpu(dtype, scores.size()) {
1085 crate::softmax(&scores, &[-1])?
1086 } else {
1087 softmax_graph(&scores)?
1088 };
1089 let out = crate::matmul(&probs, &v)?;
1090 if rep > 1 {
1091 reshape(&out, &[i(b), i(hq), i(lq), i(v.shape()[n - 1])])
1092 } else {
1093 Ok(out)
1094 }
1095}
1096
1097pub fn gated_delta(
1127 q: &Array,
1128 k: &Array,
1129 v: &Array,
1130 g: &Array,
1131 beta: &Array,
1132 state: &Array,
1133) -> Result<(Array, Array)> {
1134 check_inputs(&[q, k, v, g, beta, state])?;
1135 let op = "gated_delta";
1136 for a in [q, k, v, g, beta, state] {
1137 floating(op, a.dtype())?;
1138 }
1139 let invalid = |msg: String| Err(Error::InvalidArgument(format!("{op}: {msg}")));
1140 if q.ndim() != 4 || q.shape() != k.shape() || v.ndim() != 4 {
1141 return invalid(format!(
1142 "q and k must be [B, T, Hk, Dk] and v [B, T, Hv, Dv], got {:?}, {:?}, {:?}",
1143 q.shape(),
1144 k.shape(),
1145 v.shape()
1146 ));
1147 }
1148 let (b, t, hk, dk) = (q.shape()[0], q.shape()[1], q.shape()[2], q.shape()[3]);
1149 let (hv, dv) = (v.shape()[2], v.shape()[3]);
1150 if v.shape()[..2] != [b, t] || hk == 0 || hv % hk != 0 {
1151 return invalid(format!(
1152 "v must be [{b}, {t}, Hv, Dv] with Hv a multiple of {hk}, got {:?}",
1153 v.shape()
1154 ));
1155 }
1156 if g.shape() != [b, t, hv] || beta.shape() != [b, t, hv] {
1157 return invalid(format!(
1158 "g and beta must be [{b}, {t}, {hv}], got {:?} and {:?}",
1159 g.shape(),
1160 beta.shape()
1161 ));
1162 }
1163 if state.shape() != [b, hv, dv, dk] {
1164 return invalid(format!(
1165 "state must be [{b}, {hv}, {dv}, {dk}], got {:?}",
1166 state.shape()
1167 ));
1168 }
1169 let inputs = vec![
1170 q.clone(),
1171 k.clone(),
1172 v.clone(),
1173 g.clone(),
1174 beta.clone(),
1175 state.clone(),
1176 ];
1177 let kernel = on_gpu(q.dtype(), q.size())
1178 && q.dtype() == v.dtype()
1179 && dk % 32 == 0
1180 && [g, beta, state]
1181 .iter()
1182 .all(|a| matches!(a.dtype(), DType::F16 | DType::BF16 | DType::F32));
1183 let mut out = if kernel {
1184 Array::from_op(Primitive::Fast(FastOp::GatedDelta), inputs)?
1185 } else {
1186 FastOp::GatedDelta.fallback(&inputs)?
1187 };
1188 let s = out.pop().expect("two outputs");
1189 Ok((out.pop().expect("two outputs"), s))
1190}
1191
1192fn gated_delta_graph(inputs: &[Array]) -> Result<Vec<Array>> {
1195 let [q, k, v, g, beta, state] = inputs else {
1196 unreachable!("six inputs")
1197 };
1198 let (b, t, hk, dk) = (q.shape()[0], q.shape()[1], q.shape()[2], q.shape()[3]);
1199 let (hv, dv) = (v.shape()[2], v.shape()[3]);
1200 let heads = |x: &Array| -> Result<Array> {
1202 if hv == hk {
1203 return Ok(x.clone());
1204 }
1205 let wide = broadcast_to(&expand_dims(x, 3)?, &[b, t, hk, hv / hk, dk])?;
1206 reshape(&wide, &[b as isize, t as isize, hv as isize, dk as isize])
1207 };
1208 let wide = |x: &Array| match x.dtype() {
1210 DType::F64 => Ok(x.clone()),
1211 _ => astype(x, DType::F32),
1212 };
1213 let (qh, kh) = (heads(&wide(q)?)?, heads(&wide(k)?)?);
1214 let (v, g, beta) = (&wide(v)?, &wide(g)?, &wide(beta)?);
1215 let at = |x: &Array, i: usize| -> Result<Array> {
1216 let mut args = vec![SliceArg::full(); x.ndim()];
1217 args[1] = SliceArg::new(Some(i as isize), Some(i as isize + 1), 1);
1218 squeeze(&slice(x, &args)?, &[1])
1219 };
1220 let mut s = wide(state)?;
1221 let mut ys = Vec::with_capacity(t);
1222 for i in 0..t {
1223 let (qt, kt, vt) = (at(&qh, i)?, expand_dims(&at(&kh, i)?, 2)?, at(v, i)?);
1224 let decay = reshape(&at(g, i)?, &[b as isize, hv as isize, 1, 1])?;
1225 s = multiply(&s, &decay)?;
1226 let kv_mem = sum(&multiply(&s, &kt)?, &[-1], false)?;
1227 let delta = multiply(subtract(&vt, &kv_mem)?, expand_dims(&at(beta, i)?, -1)?)?;
1228 s = add(&s, multiply(&kt, expand_dims(&delta, -1)?)?)?;
1229 let y = sum(&multiply(&s, expand_dims(&qt, 2)?)?, &[-1], false)?;
1230 ys.push(astype(&y, q.dtype())?);
1231 }
1232 let y = match ys.is_empty() {
1233 true => zeros(&[b, 0, hv, dv], q.dtype())?,
1234 false => stack(&ys, 1)?,
1235 };
1236 Ok(vec![y, astype(&s, state.dtype())?])
1237}
1238
1239fn gather_qmm_graph(
1242 inputs: &[Array],
1243 group_size: usize,
1244 bits: u32,
1245 transpose: bool,
1246) -> Result<Array> {
1247 let (lhs, rhs) = (&inputs[4], &inputs[5]);
1248 let rows = |a: &Array, indices: &Array| -> Result<Array> {
1249 let nd = a.ndim();
1250 let flat = reshape(
1251 a,
1252 &[-1, a.shape()[nd - 2] as isize, a.shape()[nd - 1] as isize],
1253 )?;
1254 crate::take(&flat, indices, Some(0))
1255 };
1256 crate::quantized_matmul(
1257 &rows(&inputs[0], lhs)?,
1258 &rows(&inputs[1], rhs)?,
1259 &rows(&inputs[2], rhs)?,
1260 &rows(&inputs[3], rhs)?,
1261 transpose,
1262 group_size,
1263 bits,
1264 )
1265}