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