1use smallvec::{SmallVec, smallvec};
11
12use crate::custom::CustomCall;
13use crate::shape::{Shape, Strides};
14use crate::view::{self, DimSlice};
15use crate::{Array, Category, DType, Error, Result};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19#[non_exhaustive]
20pub enum UnaryOp {
21 Negative,
23 Abs,
25 Exp,
27 Log,
29 Sqrt,
31 Sin,
33 Cos,
35 Tanh,
37 Sigmoid,
39 Conjugate,
41 ErfInv,
43}
44
45impl UnaryOp {
46 pub(crate) fn is_transcendental(self) -> bool {
48 !matches!(self, Self::Negative | Self::Abs | Self::Conjugate)
49 }
50
51 pub(crate) fn name(self) -> &'static str {
52 match self {
53 Self::Negative => "negative",
54 Self::Abs => "abs",
55 Self::Exp => "exp",
56 Self::Log => "log",
57 Self::Sqrt => "sqrt",
58 Self::Sin => "sin",
59 Self::Cos => "cos",
60 Self::Tanh => "tanh",
61 Self::Sigmoid => "sigmoid",
62 Self::Conjugate => "conjugate",
63 Self::ErfInv => "erfinv",
64 }
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70#[non_exhaustive]
71pub enum BinaryOp {
72 Add,
74 Subtract,
76 Multiply,
78 Divide,
80 Maximum,
82 Minimum,
84 LogAddExp,
86 Equal,
88 NotEqual,
90 Less,
92 LessEqual,
94 Greater,
96 GreaterEqual,
98}
99
100impl BinaryOp {
101 pub(crate) fn is_comparison(self) -> bool {
103 matches!(
104 self,
105 Self::Equal
106 | Self::NotEqual
107 | Self::Less
108 | Self::LessEqual
109 | Self::Greater
110 | Self::GreaterEqual
111 )
112 }
113
114 pub(crate) fn name(self) -> &'static str {
115 match self {
116 Self::Add => "add",
117 Self::Subtract => "subtract",
118 Self::Multiply => "multiply",
119 Self::Divide => "divide",
120 Self::Maximum => "maximum",
121 Self::Minimum => "minimum",
122 Self::LogAddExp => "logaddexp",
123 Self::Equal => "equal",
124 Self::NotEqual => "not_equal",
125 Self::Less => "less",
126 Self::LessEqual => "less_equal",
127 Self::Greater => "greater",
128 Self::GreaterEqual => "greater_equal",
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
135#[non_exhaustive]
136pub enum ReduceOp {
137 Sum,
139 Prod,
141 Max,
143 Min,
145 And,
147 Or,
149}
150
151impl ReduceOp {
152 pub(crate) fn name(self) -> &'static str {
153 match self {
154 Self::Sum => "sum",
155 Self::Prod => "prod",
156 Self::Max => "max",
157 Self::Min => "min",
158 Self::And => "all",
159 Self::Or => "any",
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
166#[non_exhaustive]
167pub enum ArgReduceOp {
168 Max,
170 Min,
172}
173
174impl ArgReduceOp {
175 pub(crate) fn name(self) -> &'static str {
176 match self {
177 Self::Max => "argmax",
178 Self::Min => "argmin",
179 }
180 }
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
185#[non_exhaustive]
186pub enum ScanOp {
187 Sum,
189 Prod,
191 Max,
193 Min,
195 LogAddExp,
197}
198
199impl ScanOp {
200 pub(crate) fn name(self) -> &'static str {
201 match self {
202 Self::Sum => "cumsum",
203 Self::Prod => "cumprod",
204 Self::Max => "cummax",
205 Self::Min => "cummin",
206 Self::LogAddExp => "logcumsumexp",
207 }
208 }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
213#[non_exhaustive]
214pub enum ScatterReduce {
215 None,
217 Sum,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Hash)]
223#[non_exhaustive]
224pub enum Primitive {
225 Unary(UnaryOp),
227 Binary(BinaryOp),
229 Select,
231 AsType(DType),
233 Reshape(Vec<usize>),
235 Transpose(Vec<usize>),
237 Slice {
239 start: Vec<usize>,
241 step: Vec<isize>,
243 len: Vec<usize>,
245 },
246 Broadcast(Vec<usize>),
248 StopGradient,
250 SliceUpdate {
253 start: Vec<usize>,
255 step: Vec<isize>,
257 len: Vec<usize>,
259 },
260 Reduce {
262 op: ReduceOp,
264 axes: Vec<usize>,
266 },
267 ArgReduce {
269 op: ArgReduceOp,
271 axis: usize,
273 },
274 GatherAxis {
279 axis: usize,
281 },
282 ScatterAxis {
286 axis: usize,
288 reduce: ScatterReduce,
290 },
291 Sort {
293 axis: usize,
295 },
296 ArgSort {
298 axis: usize,
300 },
301 Scan {
303 op: ScanOp,
305 axis: usize,
307 reverse: bool,
309 inclusive: bool,
312 },
313 Concatenate {
315 axis: usize,
317 },
318 Matmul,
321 AddMM {
324 alpha: u32,
326 beta: u32,
328 },
329 Quantize {
332 group_size: usize,
334 bits: u8,
336 },
337 Dequantize {
339 group_size: usize,
341 bits: u8,
343 },
344 QuantizedMatmul {
347 group_size: usize,
349 bits: u8,
351 transpose: bool,
353 },
354 Fast(crate::fast::imp::FastOp),
356 Compiled(crate::fused::Fused),
359 RandomBits {
362 shape: Vec<usize>,
364 width: u8,
366 },
367 Custom(CustomCall),
371 MetalKernel(crate::kernel::KernelCall),
373 Copy,
376 Depends {
381 n_deps: usize,
383 },
384 #[cfg(test)]
386 AddMultiply,
387}
388
389#[derive(Debug, Clone, PartialEq, Eq)]
391pub(crate) struct OutputSpec {
392 pub(crate) dtype: DType,
393 pub(crate) shape: Shape,
394 pub(crate) strides: Option<Strides>,
396}
397
398impl Primitive {
399 pub(crate) fn addmm_factors(&self) -> (f32, f32) {
401 match self {
402 Self::AddMM { alpha, beta } => (f32::from_bits(*alpha), f32::from_bits(*beta)),
403 _ => (1.0, 0.0),
404 }
405 }
406
407 pub fn name(&self) -> &'static str {
409 match self {
410 Self::Unary(op) => op.name(),
411 Self::Binary(op) => op.name(),
412 Self::Select => "where",
413 Self::AsType(_) => "astype",
414 Self::Reshape(_) => "reshape",
415 Self::Transpose(_) => "transpose",
416 Self::Slice { .. } => "slice",
417 Self::Broadcast(_) => "broadcast_to",
418 Self::StopGradient => "stop_gradient",
419 Self::SliceUpdate { .. } => "slice_update",
420 Self::Reduce { op, .. } => op.name(),
421 Self::ArgReduce { op, .. } => op.name(),
422 Self::GatherAxis { .. } => "take_along_axis",
423 Self::ScatterAxis {
424 reduce: ScatterReduce::None,
425 ..
426 } => "put_along_axis",
427 Self::ScatterAxis { .. } => "scatter_add_along_axis",
428 Self::Concatenate { .. } => "concatenate",
429 Self::Sort { .. } => "sort",
430 Self::ArgSort { .. } => "argsort",
431 Self::Scan { op, .. } => op.name(),
432 Self::Matmul => "matmul",
433 Self::AddMM { .. } => "addmm",
434 Self::RandomBits { .. } => "random_bits",
435 Self::Fast(op) => op.name(),
436 Self::Compiled(_) => "compiled",
437 Self::Quantize { .. } => "quantize",
438 Self::Dequantize { .. } => "dequantize",
439 Self::QuantizedMatmul { .. } => "quantized_matmul",
440 Self::Custom(_) => "custom_function",
441 Self::MetalKernel(_) => "metal_kernel",
442 Self::Copy => "copy",
443 Self::Depends { .. } => "depends",
444 #[cfg(test)]
445 Self::AddMultiply => "add_multiply",
446 }
447 }
448
449 pub(crate) fn output_specs(&self, inputs: &[Array]) -> Result<SmallVec<[OutputSpec; 1]>> {
451 let computed = |dtype: DType, shape: &[usize]| OutputSpec {
452 dtype,
453 shape: shape.into(),
454 strides: None,
455 };
456 let spec = match self {
457 Self::Unary(op) => {
458 let [a] = self.arity::<1>(inputs)?;
459 let supported = match op {
461 UnaryOp::Negative => a.dtype() != DType::Bool,
462 UnaryOp::Abs | UnaryOp::Conjugate => true,
463 _ => a.dtype().category() == Category::Float,
464 };
465 if !supported {
466 return Err(Error::unsupported_dtype(op.name(), a.dtype()));
467 }
468 let dtype = match (op, a.dtype()) {
469 (UnaryOp::Abs, DType::Complex64) => DType::F32,
470 (_, d) => d,
471 };
472 computed(dtype, a.dims())
473 }
474 Self::Binary(op) => {
475 let [a, b] = self.arity::<2>(inputs)?;
476 self.same_shape_and_dtype(a, b)?;
477 let category = a.dtype().category();
478 let unsupported = match op {
479 BinaryOp::LogAddExp => category != Category::Float,
480 BinaryOp::Divide => !matches!(category, Category::Float | Category::Complex),
481 BinaryOp::Maximum
482 | BinaryOp::Minimum
483 | BinaryOp::Less
484 | BinaryOp::LessEqual
485 | BinaryOp::Greater
486 | BinaryOp::GreaterEqual => category == Category::Complex,
487 _ => false,
488 };
489 if unsupported {
490 return Err(Error::unsupported_dtype(op.name(), a.dtype()));
491 }
492 computed(
493 if op.is_comparison() {
494 DType::Bool
495 } else {
496 a.dtype()
497 },
498 a.dims(),
499 )
500 }
501 Self::Select => {
502 let [c, a, b] = self.arity::<3>(inputs)?;
503 self.same_shape_and_dtype(a, b)?;
504 if c.dtype() != DType::Bool || c.dims() != a.dims() {
505 return Err(Error::InvalidArgument(
506 "where: condition must be a bool array of the same shape".into(),
507 ));
508 }
509 computed(a.dtype(), a.dims())
510 }
511 Self::AsType(dtype) => {
512 let [a] = self.arity::<1>(inputs)?;
513 computed(*dtype, a.dims())
514 }
515 Self::Reshape(shape) => {
516 let [a] = self.arity::<1>(inputs)?;
517 if crate::shape::checked_size(shape)? != a.size() {
518 return Err(Error::InvalidArgument(format!(
519 "reshape: cannot reshape {} elements into shape {shape:?}",
520 a.size()
521 )));
522 }
523 OutputSpec {
524 dtype: a.dtype(),
525 shape: shape.as_slice().into(),
526 strides: view::nocopy_reshape(a.dims(), a.strides(), shape),
527 }
528 }
529 Self::Transpose(axes) => {
530 let [a] = self.arity::<1>(inputs)?;
531 let (shape, strides) = view::permute(a.dims(), a.strides(), axes)?;
532 OutputSpec {
533 dtype: a.dtype(),
534 shape,
535 strides: Some(strides),
536 }
537 }
538 Self::Slice { len, .. } => {
539 let [a] = self.arity::<1>(inputs)?;
540 let dims = self.dim_slices(a.ndim())?;
541 if !region_fits(&dims, a.dims()) {
542 return Err(Error::InvalidArgument(format!(
543 "slice: region {dims:?} does not fit shape {:?}",
544 a.dims()
545 )));
546 }
547 let (strides, _) = view::slice_geometry(a.strides(), &dims);
548 OutputSpec {
549 dtype: a.dtype(),
550 shape: len.as_slice().into(),
551 strides: Some(strides),
552 }
553 }
554 Self::Broadcast(shape) => {
555 let [a] = self.arity::<1>(inputs)?;
556 let strides = view::broadcast_strides(a.dims(), a.strides(), shape)?;
557 OutputSpec {
558 dtype: a.dtype(),
559 shape: shape.as_slice().into(),
560 strides: Some(strides),
561 }
562 }
563 Self::Copy => {
564 let [a] = self.arity::<1>(inputs)?;
565 computed(a.dtype(), a.dims())
566 }
567 Self::Depends { n_deps } => {
568 if inputs.len() != n_deps + 1 {
569 return Err(Error::InvalidArgument(format!(
570 "depends: {} dependencies expected, got {}",
571 n_deps,
572 inputs.len().saturating_sub(1)
573 )));
574 }
575 let a = &inputs[0];
576 OutputSpec {
577 dtype: a.dtype(),
578 shape: a.dims().into(),
579 strides: Some(a.strides().into()),
580 }
581 }
582 Self::StopGradient => {
583 let [a] = self.arity::<1>(inputs)?;
584 OutputSpec {
585 dtype: a.dtype(),
586 shape: a.dims().into(),
587 strides: Some(a.strides().into()),
588 }
589 }
590 Self::SliceUpdate { len, .. } => {
591 let [base, update] = self.arity::<2>(inputs)?;
592 let dims = self.dim_slices(base.ndim())?;
593 if !region_fits(&dims, base.dims()) || update.dims() != len.as_slice() {
594 return Err(Error::InvalidArgument(format!(
595 "slice_update: region {dims:?} does not fit shape {:?} with update {:?}",
596 base.dims(),
597 update.dims()
598 )));
599 }
600 if base.dtype() != update.dtype() {
601 return Err(Error::dtype_mismatch(
602 "slice_update",
603 base.dtype(),
604 update.dtype(),
605 ));
606 }
607 computed(base.dtype(), base.dims())
608 }
609 Self::Reduce { op, axes } => {
610 let [a] = self.arity::<1>(inputs)?;
611 let shape = self.reduced_shape(a.dims(), axes)?;
612 if !axes.windows(2).all(|w| w[0] < w[1]) {
613 return Err(Error::InvalidArgument(format!(
614 "{}: axes {axes:?} must be sorted and unique",
615 self.name()
616 )));
617 }
618 let supported = match op {
619 ReduceOp::And | ReduceOp::Or => a.dtype() == DType::Bool,
620 ReduceOp::Max | ReduceOp::Min => a.dtype() != DType::Complex64,
621 ReduceOp::Sum | ReduceOp::Prod => true,
622 };
623 if !supported {
624 return Err(Error::unsupported_dtype(self.name(), a.dtype()));
625 }
626 let ordered = matches!(op, ReduceOp::Max | ReduceOp::Min);
627 if ordered && axes.iter().any(|&d| a.dims()[d] == 0) {
628 return Err(Error::InvalidArgument(format!(
629 "{}: cannot reduce a zero-length axis (no identity)",
630 self.name()
631 )));
632 }
633 computed(a.dtype(), &shape)
634 }
635 Self::ArgReduce { axis, .. } => {
636 let [a] = self.arity::<1>(inputs)?;
637 if a.dtype() == DType::Complex64 {
638 return Err(Error::unsupported_dtype(self.name(), a.dtype()));
639 }
640 computed(DType::U32, &self.reduced_shape(a.dims(), &[*axis])?)
641 }
642 Self::GatherAxis { axis } => {
643 let [src, indices] = self.arity::<2>(inputs)?;
644 self.indexed(src, indices, *axis)?;
645 computed(src.dtype(), indices.dims())
646 }
647 Self::ScatterAxis { axis, .. } => {
648 let [dst, indices, updates] = self.arity::<3>(inputs)?;
649 self.indexed(dst, indices, *axis)?;
650 if updates.dims() != indices.dims() {
651 return Err(Error::shape_mismatch(
652 self.name(),
653 indices.dims(),
654 updates.dims(),
655 ));
656 }
657 if updates.dtype() != dst.dtype() {
658 return Err(Error::dtype_mismatch(
659 self.name(),
660 dst.dtype(),
661 updates.dtype(),
662 ));
663 }
664 computed(dst.dtype(), dst.dims())
665 }
666 Self::Sort { axis } | Self::ArgSort { axis } | Self::Scan { axis, .. } => {
667 let [a] = self.arity::<1>(inputs)?;
668 if *axis >= a.ndim() {
669 return Err(Error::InvalidArgument(format!(
670 "{}: axis {axis} out of range for {} dimensions",
671 self.name(),
672 a.ndim()
673 )));
674 }
675 let ordered = matches!(
676 self,
677 Self::Sort { .. }
678 | Self::ArgSort { .. }
679 | Self::Scan {
680 op: ScanOp::Max | ScanOp::Min,
681 ..
682 }
683 );
684 let float_only = matches!(
685 self,
686 Self::Scan {
687 op: ScanOp::LogAddExp,
688 ..
689 }
690 );
691 let category = a.dtype().category();
692 if (ordered && category == crate::Category::Complex)
693 || (float_only && category != crate::Category::Float)
694 {
695 return Err(Error::unsupported_dtype(self.name(), a.dtype()));
696 }
697 let dtype = match self {
698 Self::ArgSort { .. } => DType::U32,
699 _ => a.dtype(),
700 };
701 computed(dtype, a.dims())
702 }
703 Self::Concatenate { axis } => {
704 let first = inputs.first().ok_or_else(|| {
705 Error::InvalidArgument("concatenate: no arrays to join".into())
706 })?;
707 if *axis >= first.ndim() {
708 return Err(Error::InvalidArgument(format!(
709 "concatenate: axis {axis} out of range for {} dimensions",
710 first.ndim()
711 )));
712 }
713 let mut shape: Shape = first.dims().into();
714 for a in &inputs[1..] {
715 let fits = a.ndim() == first.ndim()
716 && a.dims()
717 .iter()
718 .zip(first.dims())
719 .enumerate()
720 .all(|(d, (x, y))| d == *axis || x == y);
721 if !fits {
722 return Err(Error::shape_mismatch("concatenate", first.dims(), a.dims()));
723 }
724 if a.dtype() != first.dtype() {
725 return Err(Error::dtype_mismatch(
726 "concatenate",
727 first.dtype(),
728 a.dtype(),
729 ));
730 }
731 shape[*axis] = shape[*axis].checked_add(a.dims()[*axis]).ok_or_else(|| {
732 Error::InvalidArgument("concatenate: the joined size overflows".into())
733 })?;
734 }
735 computed(first.dtype(), &shape)
736 }
737 Self::Matmul | Self::AddMM { .. } => {
738 let (a, b) = match self {
739 Self::Matmul => {
740 let [a, b] = self.arity::<2>(inputs)?;
741 (a, b)
742 }
743 _ => {
744 let [a, b, _] = self.arity::<3>(inputs)?;
745 (a, b)
746 }
747 };
748 let (sa, sb) = (a.dims(), b.dims());
749 let n = sa.len();
750 let valid =
751 n >= 2 && sb.len() == n && sa[..n - 2] == sb[..n - 2] && sa[n - 1] == sb[n - 2];
752 if !valid {
753 return Err(Error::shape_mismatch(self.name(), sa, sb));
754 }
755 if a.dtype() != b.dtype() {
756 return Err(Error::dtype_mismatch(self.name(), a.dtype(), b.dtype()));
757 }
758 if !matches!(a.dtype().category(), Category::Float | Category::Complex) {
759 return Err(Error::unsupported_dtype(self.name(), a.dtype()));
760 }
761 let mut shape: Shape = sa.into();
762 shape[n - 1] = sb[n - 1];
763 if let Self::AddMM { .. } = self {
764 let c = &inputs[2];
765 if c.dims() != shape.as_slice() || c.dtype() != a.dtype() {
766 return Err(Error::InvalidArgument(format!(
767 "addmm: c must be {} of shape {shape:?}, got {} {:?}",
768 a.dtype(),
769 c.dtype(),
770 c.dims()
771 )));
772 }
773 }
774 computed(a.dtype(), &shape)
775 }
776 Self::RandomBits { shape, width } => {
777 let [keys] = self.arity::<1>(inputs)?;
778 if keys.dtype() != DType::U32 || keys.dims().last() != Some(&2) {
779 return Err(Error::InvalidArgument(format!(
780 "random_bits: keys must be uint32 of shape [..., 2], got {} {:?}",
781 keys.dtype(),
782 keys.dims()
783 )));
784 }
785 let dtype = match width {
786 1 => DType::U8,
787 2 => DType::U16,
788 4 => DType::U32,
789 _ => {
790 return Err(Error::InvalidArgument(format!(
791 "random_bits: width must be 1, 2 or 4, got {width}"
792 )));
793 }
794 };
795 let out: Vec<usize> = keys.dims()[..keys.ndim() - 1]
796 .iter()
797 .chain(shape)
798 .copied()
799 .collect();
800 computed(dtype, &out)
801 }
802 Self::Fast(op) => {
803 return Ok(op
804 .outputs(inputs)?
805 .into_iter()
806 .map(|(dtype, shape)| computed(dtype, &shape))
807 .collect());
808 }
809 Self::Compiled(fused) => {
810 let dtypes: Vec<DType> = inputs.iter().map(Array::dtype).collect();
811 if dtypes != fused.inputs() {
812 return Err(Error::InvalidArgument(format!(
813 "compiled: inputs {dtypes:?}, expected {:?}",
814 fused.inputs()
815 )));
816 }
817 let shape = inputs[0].dims();
818 if let Some(other) = inputs.iter().find(|a| a.dims() != shape) {
819 return Err(Error::shape_mismatch("compiled", shape, other.dims()));
820 }
821 return Ok(fused
822 .dtypes()
823 .into_iter()
824 .map(|d| computed(d, shape))
825 .collect());
826 }
827 Self::Quantize { group_size, bits } => {
828 let [w] = self.arity::<1>(inputs)?;
829 crate::quantized::check_params(self.name(), *group_size, u32::from(*bits))?;
830 let n = w.ndim();
831 if !matches!(w.dtype(), DType::F16 | DType::BF16 | DType::F32) {
832 return Err(Error::unsupported_dtype("quantize", w.dtype()));
833 }
834 if n < 2 || w.dims()[n - 1] % group_size != 0 {
835 return Err(Error::InvalidArgument(format!(
836 "quantize: needs at least 2 dimensions with the last divisible by \
837 the group size {group_size}, got shape {:?}",
838 w.dims()
839 )));
840 }
841 let last = w.dims()[n - 1];
842 let with_last = |d: usize| {
843 let mut s: Shape = w.dims().into();
844 s[n - 1] = d;
845 s
846 };
847 let packed = computed(DType::U32, &with_last(last * usize::from(*bits) / 32));
848 let groups = computed(w.dtype(), &with_last(last / group_size));
849 return Ok(smallvec![packed, groups.clone(), groups]);
850 }
851 Self::Dequantize { group_size, bits } => {
852 let [w, scales, _] = self.arity::<3>(inputs)?;
853 crate::quantized::check_params(self.name(), *group_size, u32::from(*bits))?;
854 let mut shape: Shape = w.dims().into();
855 let n = shape.len();
856 shape[n - 1] = self.quantized_columns(w, &inputs[1..], *group_size, *bits)?;
857 computed(scales.dtype(), &shape)
858 }
859 Self::QuantizedMatmul {
860 group_size,
861 bits,
862 transpose,
863 } => {
864 let [x, w, scales, _] = self.arity::<4>(inputs)?;
865 crate::quantized::check_params(self.name(), *group_size, u32::from(*bits))?;
866 let columns = self.quantized_columns(w, &inputs[2..], *group_size, *bits)?;
867 let rows = w.dims()[w.ndim() - 2];
868 let (inner, outer) = if *transpose {
869 (columns, rows)
870 } else {
871 (rows, columns)
872 };
873 let (xn, wn) = (x.ndim(), w.ndim());
874 let batch_ok = wn == 2 || (xn == wn && x.dims()[..xn - 2] == w.dims()[..wn - 2]);
875 if xn == 0 || x.dims()[xn - 1] != inner || !batch_ok {
876 return Err(Error::shape_mismatch(
877 "quantized_matmul",
878 x.dims(),
879 w.dims(),
880 ));
881 }
882 if x.dtype() != scales.dtype() {
883 return Err(Error::dtype_mismatch(
884 "quantized_matmul",
885 x.dtype(),
886 scales.dtype(),
887 ));
888 }
889 let mut shape: Shape = x.dims().into();
890 shape[xn - 1] = outer;
891 computed(x.dtype(), &shape)
892 }
893 Self::MetalKernel(call) => {
894 return Ok(call
895 .outputs
896 .iter()
897 .map(|(dtype, shape)| computed(*dtype, shape))
898 .collect());
899 }
900 Self::Custom(call) => {
901 let outputs = inputs.get(call.primals..).unwrap_or_default();
902 if outputs.is_empty() {
903 return Err(Error::InvalidArgument(
904 "custom_function: the function must return at least one array".into(),
905 ));
906 }
907 return Ok(outputs
908 .iter()
909 .map(|a| OutputSpec {
910 dtype: a.dtype(),
911 shape: a.dims().into(),
912 strides: Some(a.strides().into()),
913 })
914 .collect());
915 }
916 #[cfg(test)]
917 Self::AddMultiply => {
918 let [a, b] = self.arity::<2>(inputs)?;
919 self.same_shape_and_dtype(a, b)?;
920 let spec = computed(a.dtype(), a.dims());
921 return Ok(smallvec![spec.clone(), spec]);
922 }
923 };
924 Ok(smallvec![spec])
925 }
926
927 pub(crate) fn dim_slices(&self, ndim: usize) -> Result<Vec<DimSlice>> {
930 match self {
931 Self::Slice { start, step, len } | Self::SliceUpdate { start, step, len }
932 if start.len() == ndim && step.len() == ndim && len.len() == ndim =>
933 {
934 Ok((0..ndim)
935 .map(|i| DimSlice {
936 start: start[i],
937 step: step[i],
938 len: len[i],
939 })
940 .collect())
941 }
942 _ => Err(Error::InvalidArgument(
943 "slice: one start, step and length per dimension".into(),
944 )),
945 }
946 }
947
948 fn quantized_columns(
952 &self,
953 w: &Array,
954 groups: &[Array],
955 group_size: usize,
956 bits: u8,
957 ) -> Result<usize> {
958 let (scales, biases) = (&groups[0], &groups[1]);
959 let n = w.ndim();
960 let invalid =
961 |what: String| Err(Error::InvalidArgument(format!("{}: {what}", self.name())));
962 if w.dtype() != DType::U32 {
963 return invalid(format!("packed weights must be uint32, got {}", w.dtype()));
964 }
965 if !matches!(scales.dtype(), DType::F16 | DType::BF16 | DType::F32)
966 || biases.dtype() != scales.dtype()
967 || biases.dims() != scales.dims()
968 {
969 return invalid(format!(
970 "scales and biases must share a float dtype and shape, got {} {:?} and {} {:?}",
971 scales.dtype(),
972 scales.dims(),
973 biases.dtype(),
974 biases.dims()
975 ));
976 }
977 let packed = w.dims().last().copied().unwrap_or(0) * 32;
978 let groups = scales.dims().last().copied().unwrap_or(0) * group_size;
979 if n < 2
980 || scales.ndim() != n
981 || w.dims()[..n - 1] != scales.dims()[..n - 1]
982 || packed != groups * usize::from(bits)
983 {
984 return invalid(format!(
985 "packed weights {:?} and scales {:?} do not match group size {group_size} \
986 and {bits} bits",
987 w.dims(),
988 scales.dims()
989 ));
990 }
991 Ok(groups)
992 }
993
994 fn indexed(&self, array: &Array, indices: &Array, axis: usize) -> Result<()> {
997 if !matches!(
998 indices.dtype().category(),
999 crate::Category::Signed | crate::Category::Unsigned
1000 ) {
1001 return Err(Error::InvalidArgument(format!(
1002 "{}: indices must be integers, got {}",
1003 self.name(),
1004 indices.dtype()
1005 )));
1006 }
1007 let fits = indices.ndim() == array.ndim()
1008 && axis < array.ndim()
1009 && indices
1010 .dims()
1011 .iter()
1012 .zip(array.dims())
1013 .enumerate()
1014 .all(|(d, (i, a))| d == axis || i == a);
1015 if !fits {
1016 return Err(Error::InvalidArgument(format!(
1017 "{}: indices of shape {:?} do not fit an array of shape {:?} along axis {axis}",
1018 self.name(),
1019 indices.dims(),
1020 array.dims()
1021 )));
1022 }
1023 if array.dims()[axis] == 0 && indices.size() > 0 {
1024 return Err(Error::InvalidArgument(format!(
1025 "{}: cannot index an empty axis",
1026 self.name()
1027 )));
1028 }
1029 Ok(())
1030 }
1031
1032 fn reduced_shape(&self, shape: &[usize], axes: &[usize]) -> Result<Shape> {
1034 let mut out: Shape = shape.into();
1035 for &axis in axes {
1036 *out.get_mut(axis).ok_or_else(|| {
1037 Error::InvalidArgument(format!(
1038 "{}: axis {axis} out of range for shape {shape:?}",
1039 self.name()
1040 ))
1041 })? = 1;
1042 }
1043 Ok(out)
1044 }
1045
1046 fn arity<'a, const N: usize>(&self, inputs: &'a [Array]) -> Result<&'a [Array; N]> {
1047 inputs.try_into().map_err(|_| {
1048 Error::InvalidArgument(format!(
1049 "{} takes {N} inputs, got {}",
1050 self.name(),
1051 inputs.len()
1052 ))
1053 })
1054 }
1055
1056 fn same_shape_and_dtype(&self, a: &Array, b: &Array) -> Result<()> {
1057 if a.dims() != b.dims() {
1058 return Err(Error::shape_mismatch(self.name(), a.dims(), b.dims()));
1059 }
1060 if a.dtype() != b.dtype() {
1061 return Err(Error::dtype_mismatch(self.name(), a.dtype(), b.dtype()));
1062 }
1063 Ok(())
1064 }
1065}
1066
1067pub(crate) fn copy(x: &Array) -> Result<Array> {
1069 Ok(Array::from_op(Primitive::Copy, vec![x.clone()])?.swap_remove(0))
1070}
1071
1072pub(crate) fn depends(x: &Array, deps: &[Array]) -> Result<Array> {
1074 let mut inputs = Vec::with_capacity(deps.len() + 1);
1075 inputs.push(x.clone());
1076 inputs.extend_from_slice(deps);
1077 let primitive = Primitive::Depends { n_deps: deps.len() };
1078 Ok(Array::from_op(primitive, inputs)?.swap_remove(0))
1079}
1080
1081pub(crate) fn region_fits(dims: &[DimSlice], shape: &[usize]) -> bool {
1085 dims.len() == shape.len()
1086 && dims.iter().zip(shape).all(|(d, &n)| {
1087 if d.step == 0 {
1088 return false;
1089 }
1090 if d.len == 0 {
1091 return d.start <= n;
1092 }
1093 let last = isize::try_from(d.len - 1)
1094 .ok()
1095 .and_then(|steps| steps.checked_mul(d.step))
1096 .and_then(|span| isize::try_from(d.start).ok()?.checked_add(span));
1097 d.start < n && last.is_some_and(|last| (0..n as isize).contains(&last))
1098 })
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103 use std::collections::hash_map::DefaultHasher;
1104 use std::hash::{Hash, Hasher};
1105
1106 use super::*;
1107
1108 fn hash(p: &Primitive) -> u64 {
1109 let mut h = DefaultHasher::new();
1110 p.hash(&mut h);
1111 h.finish()
1112 }
1113
1114 #[test]
1115 fn equal_primitives_compare_and_hash_equal() {
1116 let add = Primitive::Binary(BinaryOp::Add);
1117 assert_eq!(add, Primitive::Binary(BinaryOp::Add));
1118 assert_eq!(hash(&add), hash(&Primitive::Binary(BinaryOp::Add)));
1119 assert_ne!(add, Primitive::Binary(BinaryOp::Multiply));
1120 let s = |start: Vec<usize>| Primitive::Slice {
1121 start,
1122 step: vec![1],
1123 len: vec![2],
1124 };
1125 assert_eq!(s(vec![1]), s(vec![1]));
1126 assert_ne!(s(vec![1]), s(vec![2]));
1127 }
1128}