1use crate::ops::{astype, broadcast_to, expand_dims, squeeze};
27use crate::primitive::Primitive;
28use crate::{Array, Category, Error, Result, promote_types, view};
29
30fn check_params(op: &str, group_size: usize, bits: u32) -> Result<u8> {
32 if !matches!(group_size, 32 | 64 | 128) {
33 return Err(Error::InvalidArgument(format!(
34 "{op}: group size {group_size} is not supported (32, 64 or 128)"
35 )));
36 }
37 match bits {
38 1..=6 | 8 => Ok(bits as u8),
39 _ => Err(Error::InvalidArgument(format!(
40 "{op}: {bits} bits are not supported (1, 2, 3, 4, 5, 6 or 8)"
41 ))),
42 }
43}
44
45fn check_inputs(inputs: &[&Array]) -> Result<()> {
46 match inputs.iter().find_map(|a| a.error()) {
47 Some(err) => Err(err),
48 None => Ok(()),
49 }
50}
51
52pub fn quantize(w: &Array, group_size: usize, bits: u32) -> Result<(Array, Array, Array)> {
58 check_inputs(&[w])?;
59 let bits = check_params("quantize", group_size, bits)?;
60 let mut out = Array::from_op(Primitive::Quantize { group_size, bits }, vec![w.clone()])?;
61 let biases = out.pop().expect("three outputs");
62 let scales = out.pop().expect("three outputs");
63 Ok((out.pop().expect("three outputs"), scales, biases))
64}
65
66pub fn dequantize(
68 w: &Array,
69 scales: &Array,
70 biases: &Array,
71 group_size: usize,
72 bits: u32,
73) -> Result<Array> {
74 check_inputs(&[w, scales, biases])?;
75 let bits = check_params("dequantize", group_size, bits)?;
76 let inputs = vec![w.clone(), scales.clone(), biases.clone()];
77 Ok(Array::from_op(Primitive::Dequantize { group_size, bits }, inputs)?.swap_remove(0))
78}
79
80#[allow(clippy::too_many_arguments)]
88pub fn quantized_matmul(
89 x: &Array,
90 w: &Array,
91 scales: &Array,
92 biases: &Array,
93 transpose: bool,
94 group_size: usize,
95 bits: u32,
96) -> Result<Array> {
97 check_inputs(&[x, w, scales, biases])?;
98 let bits = check_params("quantized_matmul", group_size, bits)?;
99 let dtype = promote_types(promote_types(x.dtype(), scales.dtype()), biases.dtype());
100 if dtype.category() != Category::Float || dtype.size() > 4 {
101 return Err(Error::UnsupportedDType {
102 op: "quantized_matmul",
103 dtype,
104 });
105 }
106 let (x, scales, biases) = (
107 astype(x, dtype)?,
108 astype(scales, dtype)?,
109 astype(biases, dtype)?,
110 );
111 let vector = x.ndim() == 1;
112 let mut x = if vector { expand_dims(&x, 0)? } else { x };
113 let (mut w, mut scales, mut biases) = (w.clone(), scales, biases);
114 let (xn, wn) = (x.ndim(), w.ndim());
115 if wn > 2 && xn >= 2 {
116 let batch = view::broadcast_shapes(
119 "quantized_matmul",
120 &x.shape()[..xn - 2],
121 &w.shape()[..wn - 2],
122 )?;
123 let full = |a: &Array| -> Result<Array> {
124 let mut shape = batch.to_vec();
125 shape.extend_from_slice(&a.shape()[a.ndim() - 2..]);
126 broadcast_to(a, &shape)
127 };
128 x = full(&x)?;
129 w = full(&w)?;
130 scales = full(&scales)?;
131 biases = full(&biases)?;
132 }
133 let primitive = Primitive::QuantizedMatmul {
134 group_size,
135 bits,
136 transpose,
137 };
138 let out = Array::from_op(primitive, vec![x, w, scales, biases])?.swap_remove(0);
139 if vector {
140 squeeze(&out, &[-2])
141 } else {
142 Ok(out)
143 }
144}
145
146#[allow(clippy::too_many_arguments)]
173pub fn gather_qmm(
174 x: &Array,
175 w: &Array,
176 scales: &Array,
177 biases: &Array,
178 lhs_indices: Option<&Array>,
179 rhs_indices: Option<&Array>,
180 transpose: bool,
181 group_size: usize,
182 bits: u32,
183 sorted_indices: bool,
184) -> Result<Array> {
185 check_inputs(&[x, w, scales, biases])?;
186 let bits = check_params("gather_qmm", group_size, bits)?;
187 let invalid = |msg: String| Err(Error::InvalidArgument(format!("gather_qmm: {msg}")));
188 let dtype = promote_types(promote_types(x.dtype(), scales.dtype()), biases.dtype());
189 if dtype.category() != Category::Float || dtype.size() > 4 {
190 return Err(Error::UnsupportedDType {
191 op: "gather_qmm",
192 dtype,
193 });
194 }
195 if x.ndim() < 2 || w.ndim() < 2 || scales.shape() != biases.shape() {
196 return invalid(format!(
197 "x must be [..., M, K] and w [..., N, K'], got {:?} and {:?}",
198 x.shape(),
199 w.shape()
200 ));
201 }
202 let (x, scales, biases) = (
203 astype(x, dtype)?,
204 astype(scales, dtype)?,
205 astype(biases, dtype)?,
206 );
207 let (lhs, rhs) = gather_indices("gather_qmm", &x, w, lhs_indices, rhs_indices)?;
208 let mut inputs = vec![x.clone(), w.clone(), scales, biases, lhs, rhs.clone()];
209 if let Some(offsets) = sorted_offsets(&x, w, &rhs, lhs_indices.is_none() && sorted_indices)? {
210 inputs.push(offsets);
211 }
212 let op = crate::fast::FastOp::GatherQmm {
213 group_size,
214 bits,
215 transpose,
216 };
217 let kernel = transpose && crate::fast::on_gpu(dtype, x.size());
218 let out = if kernel {
219 Array::from_op(Primitive::Fast(op), inputs)?.swap_remove(0)
220 } else {
221 op.fallback(&inputs)?.swap_remove(0)
222 };
223 Ok(out)
224}
225
226fn gather_indices(
229 op: &'static str,
230 a: &Array,
231 b: &Array,
232 lhs: Option<&Array>,
233 rhs: Option<&Array>,
234) -> Result<(Array, Array)> {
235 let indices = |x: &Array, given: Option<&Array>| -> Result<Array> {
236 let batch = &x.shape()[..x.ndim() - 2];
237 match given {
238 Some(i)
239 if i.dtype().category() == Category::Float || i.dtype() == crate::DType::Bool =>
240 {
241 Err(Error::InvalidArgument(format!(
242 "{op}: indices must be integers"
243 )))
244 }
245 Some(i) => astype(i, crate::DType::U32),
246 None => {
247 let n: usize = batch.iter().product();
248 let all = crate::arange(0.0, n as f64, 1.0, crate::DType::U32)?;
249 all.reshape(&batch.iter().map(|&d| d as isize).collect::<Vec<_>>())
250 }
251 }
252 };
253 let (l, r) = (indices(a, lhs)?, indices(b, rhs)?);
254 let batch = view::broadcast_shapes(op, l.shape(), r.shape())?;
255 Ok((broadcast_to(&l, &batch)?, broadcast_to(&r, &batch)?))
256}
257
258fn sorted_offsets(a: &Array, b: &Array, rhs: &Array, sorted: bool) -> Result<Option<Array>> {
262 let m = a.shape()[a.ndim() - 2];
263 if !(sorted && m == 1 && a.ndim() == 3 && rhs.ndim() == 1) {
264 return Ok(None);
265 }
266 let experts: usize = b.shape()[..b.ndim() - 2].iter().product();
267 let ones = crate::ones(rhs.shape(), crate::DType::I32)?;
268 let zeros = crate::zeros(&[experts], crate::DType::I32)?;
269 let counts = crate::scatter_add_along_axis(&zeros, rhs, &ones, 0)?;
270 Ok(Some(crate::cumsum(&counts, Some(0), false, false)?))
271}
272
273pub fn gather_mm(
293 a: &Array,
294 b: &Array,
295 lhs_indices: Option<&Array>,
296 rhs_indices: Option<&Array>,
297 sorted_indices: bool,
298) -> Result<Array> {
299 check_inputs(&[a, b])?;
300 let dtype = promote_types(a.dtype(), b.dtype());
301 if dtype.category() != Category::Float {
302 return Err(Error::UnsupportedDType {
303 op: "gather_mm",
304 dtype,
305 });
306 }
307 if a.ndim() < 2 || b.ndim() < 2 || a.shape()[a.ndim() - 1] != b.shape()[b.ndim() - 2] {
308 return Err(Error::InvalidArgument(format!(
309 "gather_mm: a must be [..., M, K] and b [..., K, N], got {:?} and {:?}",
310 a.shape(),
311 b.shape()
312 )));
313 }
314 let (a, b) = (astype(a, dtype)?, astype(b, dtype)?);
315 let (lhs, rhs) = gather_indices("gather_mm", &a, &b, lhs_indices, rhs_indices)?;
316 let mut inputs = vec![a.clone(), b.clone(), lhs, rhs.clone()];
317 if let Some(offsets) = sorted_offsets(&a, &b, &rhs, lhs_indices.is_none() && sorted_indices)? {
318 inputs.push(offsets);
319 }
320 Ok(crate::fast::record_or_fallback(crate::fast::FastOp::GatherMm, inputs)?.swap_remove(0))
321}