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::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 #[cfg(test)]
375 AddMultiply,
376}
377
378#[derive(Debug, Clone, PartialEq, Eq)]
380pub(crate) struct OutputSpec {
381 pub(crate) dtype: DType,
382 pub(crate) shape: Shape,
383 pub(crate) strides: Option<Strides>,
385}
386
387impl Primitive {
388 pub(crate) fn addmm_factors(&self) -> (f32, f32) {
390 match self {
391 Self::AddMM { alpha, beta } => (f32::from_bits(*alpha), f32::from_bits(*beta)),
392 _ => (1.0, 0.0),
393 }
394 }
395
396 pub fn name(&self) -> &'static str {
398 match self {
399 Self::Unary(op) => op.name(),
400 Self::Binary(op) => op.name(),
401 Self::Select => "where",
402 Self::AsType(_) => "astype",
403 Self::Reshape(_) => "reshape",
404 Self::Transpose(_) => "transpose",
405 Self::Slice { .. } => "slice",
406 Self::Broadcast(_) => "broadcast_to",
407 Self::StopGradient => "stop_gradient",
408 Self::SliceUpdate { .. } => "slice_update",
409 Self::Reduce { op, .. } => op.name(),
410 Self::ArgReduce { op, .. } => op.name(),
411 Self::GatherAxis { .. } => "take_along_axis",
412 Self::ScatterAxis {
413 reduce: ScatterReduce::None,
414 ..
415 } => "put_along_axis",
416 Self::ScatterAxis { .. } => "scatter_add_along_axis",
417 Self::Concatenate { .. } => "concatenate",
418 Self::Sort { .. } => "sort",
419 Self::ArgSort { .. } => "argsort",
420 Self::Scan { op, .. } => op.name(),
421 Self::Matmul => "matmul",
422 Self::AddMM { .. } => "addmm",
423 Self::RandomBits { .. } => "random_bits",
424 Self::Fast(op) => op.name(),
425 Self::Compiled(_) => "compiled",
426 Self::Quantize { .. } => "quantize",
427 Self::Dequantize { .. } => "dequantize",
428 Self::QuantizedMatmul { .. } => "quantized_matmul",
429 Self::Custom(_) => "custom_function",
430 Self::MetalKernel(_) => "metal_kernel",
431 #[cfg(test)]
432 Self::AddMultiply => "add_multiply",
433 }
434 }
435
436 pub(crate) fn output_specs(&self, inputs: &[Array]) -> Result<SmallVec<[OutputSpec; 1]>> {
438 let computed = |dtype: DType, shape: &[usize]| OutputSpec {
439 dtype,
440 shape: shape.into(),
441 strides: None,
442 };
443 let spec = match self {
444 Self::Unary(op) => {
445 let [a] = self.arity::<1>(inputs)?;
446 let dtype = match (op, a.dtype()) {
447 (UnaryOp::Abs, DType::Complex64) => DType::F32,
448 (_, d) => d,
449 };
450 computed(dtype, a.shape())
451 }
452 Self::Binary(op) => {
453 let [a, b] = self.arity::<2>(inputs)?;
454 self.same_shape_and_dtype(a, b)?;
455 if *op == BinaryOp::LogAddExp && a.dtype().category() != Category::Float {
456 return Err(Error::UnsupportedDType {
457 op: op.name(),
458 dtype: a.dtype(),
459 });
460 }
461 computed(
462 if op.is_comparison() {
463 DType::Bool
464 } else {
465 a.dtype()
466 },
467 a.shape(),
468 )
469 }
470 Self::Select => {
471 let [c, a, b] = self.arity::<3>(inputs)?;
472 self.same_shape_and_dtype(a, b)?;
473 if c.dtype() != DType::Bool || c.shape() != a.shape() {
474 return Err(Error::InvalidArgument(
475 "where: condition must be a bool array of the same shape".into(),
476 ));
477 }
478 computed(a.dtype(), a.shape())
479 }
480 Self::AsType(dtype) => {
481 let [a] = self.arity::<1>(inputs)?;
482 computed(*dtype, a.shape())
483 }
484 Self::Reshape(shape) => {
485 let [a] = self.arity::<1>(inputs)?;
486 OutputSpec {
487 dtype: a.dtype(),
488 shape: shape.as_slice().into(),
489 strides: view::nocopy_reshape(a.shape(), a.strides(), shape),
490 }
491 }
492 Self::Transpose(axes) => {
493 let [a] = self.arity::<1>(inputs)?;
494 let (shape, strides) = view::permute(a.shape(), a.strides(), axes)?;
495 OutputSpec {
496 dtype: a.dtype(),
497 shape,
498 strides: Some(strides),
499 }
500 }
501 Self::Slice { len, .. } => {
502 let [a] = self.arity::<1>(inputs)?;
503 let (strides, _) = view::slice_geometry(a.strides(), &self.dim_slices(a.ndim())?);
504 OutputSpec {
505 dtype: a.dtype(),
506 shape: len.as_slice().into(),
507 strides: Some(strides),
508 }
509 }
510 Self::Broadcast(shape) => {
511 let [a] = self.arity::<1>(inputs)?;
512 let strides = view::broadcast_strides(a.shape(), a.strides(), shape)?;
513 OutputSpec {
514 dtype: a.dtype(),
515 shape: shape.as_slice().into(),
516 strides: Some(strides),
517 }
518 }
519 Self::StopGradient => {
520 let [a] = self.arity::<1>(inputs)?;
521 OutputSpec {
522 dtype: a.dtype(),
523 shape: a.shape().into(),
524 strides: Some(a.strides().into()),
525 }
526 }
527 Self::SliceUpdate { len, .. } => {
528 let [base, update] = self.arity::<2>(inputs)?;
529 let dims = self.dim_slices(base.ndim())?;
530 let in_bounds = dims.iter().zip(base.shape()).all(|(d, &n)| {
531 let last = d.start as isize + (d.len as isize - 1) * d.step;
532 d.len == 0 || (d.start < n && (0..n as isize).contains(&last))
533 });
534 if !in_bounds || update.shape() != len.as_slice() {
535 return Err(Error::InvalidArgument(format!(
536 "slice_update: region {dims:?} does not fit shape {:?} with update {:?}",
537 base.shape(),
538 update.shape()
539 )));
540 }
541 if base.dtype() != update.dtype() {
542 return Err(Error::DTypeMismatch {
543 op: "slice_update",
544 lhs: base.dtype(),
545 rhs: update.dtype(),
546 });
547 }
548 computed(base.dtype(), base.shape())
549 }
550 Self::Reduce { axes, .. } => {
551 let [a] = self.arity::<1>(inputs)?;
552 computed(a.dtype(), &self.reduced_shape(a.shape(), axes)?)
553 }
554 Self::ArgReduce { axis, .. } => {
555 let [a] = self.arity::<1>(inputs)?;
556 computed(DType::U32, &self.reduced_shape(a.shape(), &[*axis])?)
557 }
558 Self::GatherAxis { axis } => {
559 let [src, indices] = self.arity::<2>(inputs)?;
560 self.indexed(src, indices, *axis)?;
561 computed(src.dtype(), indices.shape())
562 }
563 Self::ScatterAxis { axis, .. } => {
564 let [dst, indices, updates] = self.arity::<3>(inputs)?;
565 self.indexed(dst, indices, *axis)?;
566 if updates.shape() != indices.shape() {
567 return Err(Error::ShapeMismatch {
568 op: self.name(),
569 lhs: indices.shape().to_vec(),
570 rhs: updates.shape().to_vec(),
571 });
572 }
573 if updates.dtype() != dst.dtype() {
574 return Err(Error::DTypeMismatch {
575 op: self.name(),
576 lhs: dst.dtype(),
577 rhs: updates.dtype(),
578 });
579 }
580 computed(dst.dtype(), dst.shape())
581 }
582 Self::Sort { axis } | Self::ArgSort { axis } | Self::Scan { axis, .. } => {
583 let [a] = self.arity::<1>(inputs)?;
584 if *axis >= a.ndim() {
585 return Err(Error::InvalidArgument(format!(
586 "{}: axis {axis} out of range for {} dimensions",
587 self.name(),
588 a.ndim()
589 )));
590 }
591 let ordered = matches!(
592 self,
593 Self::Sort { .. }
594 | Self::ArgSort { .. }
595 | Self::Scan {
596 op: ScanOp::Max | ScanOp::Min,
597 ..
598 }
599 );
600 let float_only = matches!(
601 self,
602 Self::Scan {
603 op: ScanOp::LogAddExp,
604 ..
605 }
606 );
607 let category = a.dtype().category();
608 if (ordered && category == crate::Category::Complex)
609 || (float_only && category != crate::Category::Float)
610 {
611 return Err(Error::UnsupportedDType {
612 op: self.name(),
613 dtype: a.dtype(),
614 });
615 }
616 let dtype = match self {
617 Self::ArgSort { .. } => DType::U32,
618 _ => a.dtype(),
619 };
620 computed(dtype, a.shape())
621 }
622 Self::Concatenate { axis } => {
623 let first = inputs.first().ok_or_else(|| {
624 Error::InvalidArgument("concatenate: no arrays to join".into())
625 })?;
626 if *axis >= first.ndim() {
627 return Err(Error::InvalidArgument(format!(
628 "concatenate: axis {axis} out of range for {} dimensions",
629 first.ndim()
630 )));
631 }
632 let mut shape: Shape = first.shape().into();
633 for a in &inputs[1..] {
634 let fits = a.ndim() == first.ndim()
635 && a.shape()
636 .iter()
637 .zip(first.shape())
638 .enumerate()
639 .all(|(d, (x, y))| d == *axis || x == y);
640 if !fits {
641 return Err(Error::ShapeMismatch {
642 op: "concatenate",
643 lhs: first.shape().to_vec(),
644 rhs: a.shape().to_vec(),
645 });
646 }
647 if a.dtype() != first.dtype() {
648 return Err(Error::DTypeMismatch {
649 op: "concatenate",
650 lhs: first.dtype(),
651 rhs: a.dtype(),
652 });
653 }
654 shape[*axis] += a.shape()[*axis];
655 }
656 computed(first.dtype(), &shape)
657 }
658 Self::Matmul | Self::AddMM { .. } => {
659 let (a, b) = match self {
660 Self::Matmul => {
661 let [a, b] = self.arity::<2>(inputs)?;
662 (a, b)
663 }
664 _ => {
665 let [a, b, _] = self.arity::<3>(inputs)?;
666 (a, b)
667 }
668 };
669 let (sa, sb) = (a.shape(), b.shape());
670 let n = sa.len();
671 let valid =
672 n >= 2 && sb.len() == n && sa[..n - 2] == sb[..n - 2] && sa[n - 1] == sb[n - 2];
673 if !valid {
674 return Err(Error::ShapeMismatch {
675 op: self.name(),
676 lhs: sa.to_vec(),
677 rhs: sb.to_vec(),
678 });
679 }
680 if a.dtype() != b.dtype() {
681 return Err(Error::DTypeMismatch {
682 op: self.name(),
683 lhs: a.dtype(),
684 rhs: b.dtype(),
685 });
686 }
687 let mut shape: Shape = sa.into();
688 shape[n - 1] = sb[n - 1];
689 if let Self::AddMM { .. } = self {
690 let c = &inputs[2];
691 if c.shape() != shape.as_slice() || c.dtype() != a.dtype() {
692 return Err(Error::InvalidArgument(format!(
693 "addmm: c must be {} of shape {shape:?}, got {} {:?}",
694 a.dtype(),
695 c.dtype(),
696 c.shape()
697 )));
698 }
699 }
700 computed(a.dtype(), &shape)
701 }
702 Self::RandomBits { shape, width } => {
703 let [keys] = self.arity::<1>(inputs)?;
704 if keys.dtype() != DType::U32 || keys.shape().last() != Some(&2) {
705 return Err(Error::InvalidArgument(format!(
706 "random_bits: keys must be uint32 of shape [..., 2], got {} {:?}",
707 keys.dtype(),
708 keys.shape()
709 )));
710 }
711 let dtype = match width {
712 1 => DType::U8,
713 2 => DType::U16,
714 4 => DType::U32,
715 _ => {
716 return Err(Error::InvalidArgument(format!(
717 "random_bits: width must be 1, 2 or 4, got {width}"
718 )));
719 }
720 };
721 let out: Vec<usize> = keys.shape()[..keys.ndim() - 1]
722 .iter()
723 .chain(shape)
724 .copied()
725 .collect();
726 computed(dtype, &out)
727 }
728 Self::Fast(op) => {
729 return Ok(op
730 .outputs(inputs)?
731 .into_iter()
732 .map(|(dtype, shape)| computed(dtype, &shape))
733 .collect());
734 }
735 Self::Compiled(fused) => {
736 let dtypes: Vec<DType> = inputs.iter().map(Array::dtype).collect();
737 if dtypes != fused.inputs() {
738 return Err(Error::InvalidArgument(format!(
739 "compiled: inputs {dtypes:?}, expected {:?}",
740 fused.inputs()
741 )));
742 }
743 let shape = inputs[0].shape();
744 if let Some(other) = inputs.iter().find(|a| a.shape() != shape) {
745 return Err(Error::ShapeMismatch {
746 op: "compiled",
747 lhs: shape.to_vec(),
748 rhs: other.shape().to_vec(),
749 });
750 }
751 return Ok(fused
752 .dtypes()
753 .into_iter()
754 .map(|d| computed(d, shape))
755 .collect());
756 }
757 Self::Quantize { group_size, bits } => {
758 let [w] = self.arity::<1>(inputs)?;
759 let n = w.ndim();
760 if !matches!(w.dtype(), DType::F16 | DType::BF16 | DType::F32) {
761 return Err(Error::UnsupportedDType {
762 op: "quantize",
763 dtype: w.dtype(),
764 });
765 }
766 if n < 2 || w.shape()[n - 1] % group_size != 0 {
767 return Err(Error::InvalidArgument(format!(
768 "quantize: needs at least 2 dimensions with the last divisible by \
769 the group size {group_size}, got shape {:?}",
770 w.shape()
771 )));
772 }
773 let last = w.shape()[n - 1];
774 let with_last = |d: usize| {
775 let mut s: Shape = w.shape().into();
776 s[n - 1] = d;
777 s
778 };
779 let packed = computed(DType::U32, &with_last(last * usize::from(*bits) / 32));
780 let groups = computed(w.dtype(), &with_last(last / group_size));
781 return Ok(smallvec![packed, groups.clone(), groups]);
782 }
783 Self::Dequantize { group_size, bits } => {
784 let [w, scales, _] = self.arity::<3>(inputs)?;
785 let mut shape: Shape = w.shape().into();
786 let n = shape.len();
787 shape[n - 1] = self.quantized_columns(w, &inputs[1..], *group_size, *bits)?;
788 computed(scales.dtype(), &shape)
789 }
790 Self::QuantizedMatmul {
791 group_size,
792 bits,
793 transpose,
794 } => {
795 let [x, w, scales, _] = self.arity::<4>(inputs)?;
796 let columns = self.quantized_columns(w, &inputs[2..], *group_size, *bits)?;
797 let rows = w.shape()[w.ndim() - 2];
798 let (inner, outer) = if *transpose {
799 (columns, rows)
800 } else {
801 (rows, columns)
802 };
803 let (xn, wn) = (x.ndim(), w.ndim());
804 let batch_ok = wn == 2 || (xn == wn && x.shape()[..xn - 2] == w.shape()[..wn - 2]);
805 if xn == 0 || x.shape()[xn - 1] != inner || !batch_ok {
806 return Err(Error::ShapeMismatch {
807 op: "quantized_matmul",
808 lhs: x.shape().to_vec(),
809 rhs: w.shape().to_vec(),
810 });
811 }
812 if x.dtype() != scales.dtype() {
813 return Err(Error::DTypeMismatch {
814 op: "quantized_matmul",
815 lhs: x.dtype(),
816 rhs: scales.dtype(),
817 });
818 }
819 let mut shape: Shape = x.shape().into();
820 shape[xn - 1] = outer;
821 computed(x.dtype(), &shape)
822 }
823 Self::MetalKernel(call) => {
824 return Ok(call
825 .outputs
826 .iter()
827 .map(|(dtype, shape)| computed(*dtype, shape))
828 .collect());
829 }
830 Self::Custom(call) => {
831 let outputs = inputs.get(call.primals..).unwrap_or_default();
832 if outputs.is_empty() {
833 return Err(Error::InvalidArgument(
834 "custom_function: the function must return at least one array".into(),
835 ));
836 }
837 return Ok(outputs
838 .iter()
839 .map(|a| OutputSpec {
840 dtype: a.dtype(),
841 shape: a.shape().into(),
842 strides: Some(a.strides().into()),
843 })
844 .collect());
845 }
846 #[cfg(test)]
847 Self::AddMultiply => {
848 let [a, b] = self.arity::<2>(inputs)?;
849 self.same_shape_and_dtype(a, b)?;
850 let spec = computed(a.dtype(), a.shape());
851 return Ok(smallvec![spec.clone(), spec]);
852 }
853 };
854 Ok(smallvec![spec])
855 }
856
857 pub(crate) fn dim_slices(&self, ndim: usize) -> Result<Vec<DimSlice>> {
860 match self {
861 Self::Slice { start, step, len } | Self::SliceUpdate { start, step, len }
862 if start.len() == ndim && step.len() == ndim && len.len() == ndim =>
863 {
864 Ok((0..ndim)
865 .map(|i| DimSlice {
866 start: start[i],
867 step: step[i],
868 len: len[i],
869 })
870 .collect())
871 }
872 _ => Err(Error::InvalidArgument(
873 "slice: one start, step and length per dimension".into(),
874 )),
875 }
876 }
877
878 fn quantized_columns(
882 &self,
883 w: &Array,
884 groups: &[Array],
885 group_size: usize,
886 bits: u8,
887 ) -> Result<usize> {
888 let (scales, biases) = (&groups[0], &groups[1]);
889 let n = w.ndim();
890 let invalid =
891 |what: String| Err(Error::InvalidArgument(format!("{}: {what}", self.name())));
892 if w.dtype() != DType::U32 {
893 return invalid(format!("packed weights must be uint32, got {}", w.dtype()));
894 }
895 if !matches!(scales.dtype(), DType::F16 | DType::BF16 | DType::F32)
896 || biases.dtype() != scales.dtype()
897 || biases.shape() != scales.shape()
898 {
899 return invalid(format!(
900 "scales and biases must share a float dtype and shape, got {} {:?} and {} {:?}",
901 scales.dtype(),
902 scales.shape(),
903 biases.dtype(),
904 biases.shape()
905 ));
906 }
907 let packed = w.shape().last().copied().unwrap_or(0) * 32;
908 let groups = scales.shape().last().copied().unwrap_or(0) * group_size;
909 if n < 2
910 || scales.ndim() != n
911 || w.shape()[..n - 1] != scales.shape()[..n - 1]
912 || packed != groups * usize::from(bits)
913 {
914 return invalid(format!(
915 "packed weights {:?} and scales {:?} do not match group size {group_size} \
916 and {bits} bits",
917 w.shape(),
918 scales.shape()
919 ));
920 }
921 Ok(groups)
922 }
923
924 fn indexed(&self, array: &Array, indices: &Array, axis: usize) -> Result<()> {
927 if !matches!(
928 indices.dtype().category(),
929 crate::Category::Signed | crate::Category::Unsigned
930 ) {
931 return Err(Error::InvalidArgument(format!(
932 "{}: indices must be integers, got {}",
933 self.name(),
934 indices.dtype()
935 )));
936 }
937 let fits = indices.ndim() == array.ndim()
938 && axis < array.ndim()
939 && indices
940 .shape()
941 .iter()
942 .zip(array.shape())
943 .enumerate()
944 .all(|(d, (i, a))| d == axis || i == a);
945 if !fits {
946 return Err(Error::InvalidArgument(format!(
947 "{}: indices of shape {:?} do not fit an array of shape {:?} along axis {axis}",
948 self.name(),
949 indices.shape(),
950 array.shape()
951 )));
952 }
953 if array.shape()[axis] == 0 && indices.size() > 0 {
954 return Err(Error::InvalidArgument(format!(
955 "{}: cannot index an empty axis",
956 self.name()
957 )));
958 }
959 Ok(())
960 }
961
962 fn reduced_shape(&self, shape: &[usize], axes: &[usize]) -> Result<Shape> {
964 let mut out: Shape = shape.into();
965 for &axis in axes {
966 *out.get_mut(axis).ok_or_else(|| {
967 Error::InvalidArgument(format!(
968 "{}: axis {axis} out of range for shape {shape:?}",
969 self.name()
970 ))
971 })? = 1;
972 }
973 Ok(out)
974 }
975
976 fn arity<'a, const N: usize>(&self, inputs: &'a [Array]) -> Result<&'a [Array; N]> {
977 inputs.try_into().map_err(|_| {
978 Error::InvalidArgument(format!(
979 "{} takes {N} inputs, got {}",
980 self.name(),
981 inputs.len()
982 ))
983 })
984 }
985
986 fn same_shape_and_dtype(&self, a: &Array, b: &Array) -> Result<()> {
987 if a.shape() != b.shape() {
988 return Err(Error::ShapeMismatch {
989 op: self.name(),
990 lhs: a.shape().to_vec(),
991 rhs: b.shape().to_vec(),
992 });
993 }
994 if a.dtype() != b.dtype() {
995 return Err(Error::DTypeMismatch {
996 op: self.name(),
997 lhs: a.dtype(),
998 rhs: b.dtype(),
999 });
1000 }
1001 Ok(())
1002 }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007 use std::collections::hash_map::DefaultHasher;
1008 use std::hash::{Hash, Hasher};
1009
1010 use super::*;
1011
1012 fn hash(p: &Primitive) -> u64 {
1013 let mut h = DefaultHasher::new();
1014 p.hash(&mut h);
1015 h.finish()
1016 }
1017
1018 #[test]
1019 fn equal_primitives_compare_and_hash_equal() {
1020 let add = Primitive::Binary(BinaryOp::Add);
1021 assert_eq!(add, Primitive::Binary(BinaryOp::Add));
1022 assert_eq!(hash(&add), hash(&Primitive::Binary(BinaryOp::Add)));
1023 assert_ne!(add, Primitive::Binary(BinaryOp::Multiply));
1024 let s = |start: Vec<usize>| Primitive::Slice {
1025 start,
1026 step: vec![1],
1027 len: vec![2],
1028 };
1029 assert_eq!(s(vec![1]), s(vec![1]));
1030 assert_ne!(s(vec![1]), s(vec![2]));
1031 }
1032}