1use std::iter::once;
2
3use crate::{
4 array,
5 error::Exception,
6 module::{Module, ModuleParameters, Param},
7 ops::indexing::IndexOp,
8 ops::{self, dequantize, quantized_matmul, zeros},
9 quantization::Quantizable,
10 random::uniform,
11 Array,
12};
13use mlx_internal_macros::{Buildable, Builder};
14use mlx_macros::ModuleParameters;
15
16use crate::nn::{Embedding, Linear};
17
18pub fn quantize<M>(
26 module: M,
27 group_size: impl Into<Option<i32>>,
28 bits: impl Into<Option<i32>>,
29) -> Result<M::Quantized, M::QuantizationError>
30where
31 M: Quantizable,
32{
33 let group_size = group_size.into().unwrap_or(M::DEFAULT_GROUP_SIZE);
34 let bits = bits.into().unwrap_or(M::DEFAULT_BITS);
35 module.try_into_quantized(group_size, bits)
36}
37
38#[derive(Debug, Clone, Builder)]
40#[builder(
41 root = crate,
42 build_with = build_quantized_embedding,
43 err = Exception,
44)]
45pub struct QuantizedEmbeddingBuilder {
46 pub embedding_count: i32,
48
49 pub dimensions: i32,
51
52 #[builder(optional, default = QuantizedEmbedding::DEFAULT_GROUP_SIZE)]
54 pub group_size: i32,
55
56 #[builder(optional, default = QuantizedEmbedding::DEFAULT_BITS)]
58 pub bits: i32,
59}
60
61#[derive(Debug, Clone, ModuleParameters, Buildable)]
63#[module(root = crate)]
64#[buildable(root = crate)]
65pub struct QuantizedEmbedding {
66 pub group_size: i32,
68
69 pub bits: i32,
71
72 #[param]
74 pub scales: Param<Array>,
75
76 #[param]
78 pub biases: Param<Array>,
79
80 #[param]
82 pub inner: Embedding,
83}
84
85impl QuantizedEmbeddingBuilder {
86 pub fn build_with_embedding(
88 self,
89 embedding: Embedding,
90 ) -> Result<QuantizedEmbedding, Exception> {
91 let weight = embedding.weight.value;
92 self.build_with_weight(weight)
93 }
94
95 pub fn build_with_weight(self, weight: Array) -> Result<QuantizedEmbedding, Exception> {
97 let group_size = self.group_size;
98 let bits = self.bits;
99 build_quantized_embedding_inner(weight, group_size, bits)
100 }
101}
102
103fn build_quantized_embedding_inner(
104 weight: Array,
105 group_size: i32,
106 bits: i32,
107) -> Result<QuantizedEmbedding, Exception> {
108 let (quantized_weight, scales, biases) = ops::quantize(
109 &weight,
110 ops::QuantizeOptions {
111 group_size: Some(group_size),
112 bits: Some(bits),
113 ..Default::default()
114 },
115 )?;
116 let biases = biases.expect("affine quantization returns biases");
117
118 let inner = Embedding {
119 weight: Param::new(quantized_weight),
120 };
121
122 let mut qe = QuantizedEmbedding {
123 group_size,
124 bits,
125 scales: Param::new(scales),
126 biases: Param::new(biases),
127 inner,
128 };
129
130 qe.freeze_parameters(true);
132
133 Ok(qe)
134}
135
136fn build_quantized_embedding(
137 builder: QuantizedEmbeddingBuilder,
138) -> Result<QuantizedEmbedding, Exception> {
139 let embedding_count = builder.embedding_count;
140 let dims = builder.dimensions;
141
142 let scale = array!(f32::sqrt(1.0 / (dims as f32)));
143 let weight = crate::random::normal::<f32>(&[embedding_count, dims], None, None, None)? * &scale;
145
146 builder.build_with_weight(weight)
147}
148
149impl QuantizedEmbedding {
150 pub const DEFAULT_GROUP_SIZE: i32 = 64;
152
153 pub const DEFAULT_BITS: i32 = 4;
155
156 pub fn try_from_embedding(
164 embedding: Embedding,
165 group_size: impl Into<Option<i32>>,
166 bits: impl Into<Option<i32>>,
167 ) -> Result<Self, Exception> {
168 let group_size = group_size.into().unwrap_or(Self::DEFAULT_GROUP_SIZE);
169 let bits = bits.into().unwrap_or(Self::DEFAULT_BITS);
170 build_quantized_embedding_inner(embedding.weight.value, group_size, bits)
171 }
172
173 pub fn as_linear(&self, x: impl AsRef<Array>) -> Result<Array, Exception> {
178 quantized_matmul(
179 x.as_ref(),
180 &self.inner.weight,
181 &self.scales,
182 self.biases.as_ref(),
183 ops::QuantizedMatmulOptions {
184 transpose: true,
185 group_size: Some(self.group_size),
186 bits: Some(self.bits),
187 ..Default::default()
188 },
189 )
190 .map_err(Into::into)
191 }
192}
193
194impl TryFrom<Embedding> for QuantizedEmbedding {
195 type Error = Exception;
196
197 fn try_from(embedding: Embedding) -> Result<Self, Self::Error> {
198 Self::try_from_embedding(embedding, None, None)
199 }
200}
201
202impl Module<&Array> for QuantizedEmbedding {
203 type Error = Exception;
204 type Output = Array;
205
206 fn forward(&mut self, x: &Array) -> Result<Array, Self::Error> {
207 let s = x.shape();
208 let x = x.flatten(None, None)?;
209 let w = self.inner.weight.index(&x);
210 let scales = self.scales.index(&x);
211 let biases = self.biases.index(&x);
212
213 let out = dequantize(
214 &w,
215 &scales,
216 &biases,
217 ops::DequantizeOptions {
218 group_size: Some(self.group_size),
219 bits: Some(self.bits),
220 ..Default::default()
221 },
222 )?;
223
224 let ret_shape = s.iter().copied().chain(once(-1)).collect::<Vec<_>>();
225 out.reshape(&ret_shape)
226 }
227
228 fn training_mode(&mut self, mode: bool) {
229 self.inner.training_mode(mode);
230 }
231}
232
233#[derive(Debug, Clone, Builder)]
235#[builder(
236 root = crate,
237 build_with = build_quantized_linear,
238 err = Exception,
239)]
240pub struct QuantizedLinearBuilder {
241 pub input_dims: i32,
243
244 pub output_dims: i32,
246
247 #[builder(optional, default = QuantizedLinear::DEFAULT_GROUP_SIZE)]
249 pub group_size: i32,
250
251 #[builder(optional, default = QuantizedLinear::DEFAULT_BITS)]
253 pub bits: i32,
254
255 #[builder(optional, default = Linear::DEFAULT_BIAS)]
257 pub bias: bool,
258}
259
260impl QuantizedLinearBuilder {
261 pub fn build_with_linear(self, other: Linear) -> Result<QuantizedLinear, Exception> {
263 self.build_with_weight_and_bias(other.weight.value, other.bias.value)
264 }
265
266 fn build_with_weight_and_bias(
267 self,
268 weight: Array,
269 bias: Option<Array>,
270 ) -> Result<QuantizedLinear, Exception> {
271 build_quantized_linear_inner(weight, bias, self.group_size, self.bits)
272 }
273}
274
275fn build_quantized_linear_inner(
276 weight: Array,
277 bias: Option<Array>,
278 group_size: i32,
279 bits: i32,
280) -> Result<QuantizedLinear, Exception> {
281 let (quantized_weight, scales, biases) = ops::quantize(
282 &weight,
283 ops::QuantizeOptions {
284 group_size: Some(group_size),
285 bits: Some(bits),
286 ..Default::default()
287 },
288 )?;
289 let biases = biases.expect("affine quantization returns biases");
290
291 let inner = Linear {
292 weight: Param::new(quantized_weight),
293 bias: Param::new(bias),
294 };
295
296 let mut ql = QuantizedLinear {
297 group_size,
298 bits,
299 scales: Param::new(scales),
300 biases: Param::new(biases),
301 inner,
302 };
303
304 ql.freeze_parameters(true);
306
307 Ok(ql)
308}
309
310pub fn build_quantized_linear(
312 builder: QuantizedLinearBuilder,
313) -> Result<QuantizedLinear, Exception> {
314 let input_dims = builder.input_dims;
315 let output_dims = builder.output_dims;
316 let scale = f32::sqrt(1.0 / (input_dims as f32));
317 let weight = uniform::<_, f32>(-scale, scale, &[output_dims, input_dims], None)?;
318
319 let bias = if builder.bias {
320 Some(zeros::<f32>(&[output_dims])?)
321 } else {
322 None
323 };
324
325 builder.build_with_weight_and_bias(weight, bias)
326}
327
328#[derive(Debug, Clone, ModuleParameters, Buildable)]
337#[module(root = crate)]
338#[buildable(root = crate)]
339pub struct QuantizedLinear {
340 pub group_size: i32,
342
343 pub bits: i32,
345
346 #[param]
348 pub scales: Param<Array>,
349
350 #[param]
352 pub biases: Param<Array>,
353
354 #[param]
356 pub inner: Linear,
357}
358
359impl QuantizedLinear {
360 pub const DEFAULT_GROUP_SIZE: i32 = 64;
362
363 pub const DEFAULT_BITS: i32 = 4;
365
366 pub fn try_from_linear(
374 linear: Linear,
375 group_size: impl Into<Option<i32>>,
376 bits: impl Into<Option<i32>>,
377 ) -> Result<Self, Exception> {
378 let group_size = group_size.into().unwrap_or(Self::DEFAULT_GROUP_SIZE);
379 let bits = bits.into().unwrap_or(Self::DEFAULT_BITS);
380 build_quantized_linear_inner(linear.weight.value, linear.bias.value, group_size, bits)
381 }
382}
383
384impl TryFrom<Linear> for QuantizedLinear {
385 type Error = Exception;
386
387 fn try_from(linear: Linear) -> Result<Self, Self::Error> {
388 Self::try_from_linear(linear, None, None)
389 }
390}
391
392impl Module<&Array> for QuantizedLinear {
393 type Error = Exception;
394 type Output = Array;
395
396 fn forward(&mut self, x: &Array) -> Result<Array, Self::Error> {
397 let mut x = quantized_matmul(
398 x,
399 &self.inner.weight,
400 &self.scales,
401 self.biases.as_ref(),
402 ops::QuantizedMatmulOptions {
403 transpose: true,
404 group_size: Some(self.group_size),
405 bits: Some(self.bits),
406 ..Default::default()
407 },
408 )?;
409 if let Some(bias) = &self.inner.bias.value {
410 x = x.add(bias)?;
411 }
412 Ok(x)
413 }
414
415 fn training_mode(&mut self, mode: bool) {
416 self.inner.training_mode(mode);
417 }
418}