1use crate::k_quants::{
10 self, BlockQ2K, BlockQ3K, BlockQ4_0, BlockQ4_1, BlockQ4K, BlockQ5_0, BlockQ5_1, BlockQ5K,
11 BlockQ6K, BlockQ8_0, BlockQ8_1, BlockQ8K, GgmlType,
12};
13use fuel_ir::quantized::GgmlDType;
14use fuel_ir::{HostBuffer, Result};
15use half::{bf16, f16};
16use std::borrow::Cow;
17
18pub trait QuantizedType: Send + Sync {
19 fn dtype(&self) -> GgmlDType;
20 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()>;
21 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()>;
22 fn dequantize(&self, elem_count: usize) -> Result<HostBuffer>;
23 fn storage_size_in_bytes(&self) -> usize;
24 fn as_ptr(&self) -> *const u8;
25 fn block_size(&self) -> usize;
26 #[allow(clippy::wrong_self_convention)]
27 fn from_float(&mut self, xs: &[f32]);
28 #[allow(clippy::wrong_self_convention)]
29 fn from_float_imatrix(&mut self, xs: &[f32], imatrix_weights: &[f32], n_per_row: usize);
30 fn size(&self) -> usize;
31}
32
33impl<T: GgmlType + Send + Sync> QuantizedType for Vec<T> {
34 fn matmul_t(&self, mkn: (usize, usize, usize), lhs: &[f32], dst: &mut [f32]) -> Result<()> {
35 k_quants::matmul(mkn, lhs, self.as_slice(), dst)
36 }
37 fn matmul_t_f16(&self, mkn: (usize, usize, usize), lhs: &[f16], dst: &mut [f16]) -> Result<()> {
38 k_quants::matmul_f16(mkn, lhs, self.as_slice(), dst)
39 }
40 fn size(&self) -> usize {
41 self.len() * core::mem::size_of::<T>()
42 }
43 fn from_float(&mut self, xs: &[f32]) {
44 T::from_float(xs, self)
45 }
46 fn from_float_imatrix(&mut self, xs: &[f32], imatrix_weights: &[f32], n_per_row: usize) {
47 T::from_float_imatrix(xs, self, imatrix_weights, n_per_row)
48 }
49 fn dtype(&self) -> GgmlDType {
50 T::DTYPE
51 }
52 fn block_size(&self) -> usize {
53 T::BLCK_SIZE
54 }
55 fn dequantize(&self, elem_count: usize) -> Result<HostBuffer> {
56 T::DTYPE.check_dequant_count("dequantize", elem_count, self.storage_size_in_bytes())?;
60 let mut ys = vec![0.0f32; elem_count];
61 T::to_float(self.as_slice(), &mut ys);
62 Ok(HostBuffer::F32(ys))
63 }
64 fn storage_size_in_bytes(&self) -> usize {
65 self.len() * std::mem::size_of::<T>()
66 }
67 fn as_ptr(&self) -> *const u8 {
68 self.as_ptr() as *const u8
69 }
70}
71
72fn blocks_from_bytes<T: GgmlType>(data: &[u8]) -> Result<Vec<T>> {
80 let size = std::mem::size_of::<T>();
81 if !data.len().is_multiple_of(size) {
82 return Err(fuel_ir::Error::Msg(format!(
83 "cpu_from_data: {} bytes is not a whole number of {:?} blocks ({size} bytes each)",
84 data.len(),
85 T::DTYPE,
86 ))
87 .bt());
88 }
89 Ok(data
90 .chunks_exact(size)
91 .map(bytemuck::pod_read_unaligned::<T>)
94 .collect())
95}
96
97pub fn cpu_zeros(dtype: GgmlDType, elem_count: usize) -> Box<dyn QuantizedType> {
98 match dtype {
99 GgmlDType::F32 => Box::new(vec![f32::zeros(); elem_count]),
100 GgmlDType::F16 => Box::new(vec![f16::zeros(); elem_count]),
101 GgmlDType::BF16 => Box::new(vec![bf16::zeros(); elem_count]),
102 GgmlDType::Q4_0 => Box::new(vec![BlockQ4_0::zeros(); elem_count / BlockQ4_0::BLCK_SIZE]),
103 GgmlDType::Q4_1 => Box::new(vec![BlockQ4_1::zeros(); elem_count / BlockQ4_1::BLCK_SIZE]),
104 GgmlDType::Q5_0 => Box::new(vec![BlockQ5_0::zeros(); elem_count / BlockQ5_0::BLCK_SIZE]),
105 GgmlDType::Q5_1 => Box::new(vec![BlockQ5_1::zeros(); elem_count / BlockQ5_1::BLCK_SIZE]),
106 GgmlDType::Q8_0 => Box::new(vec![BlockQ8_0::zeros(); elem_count / BlockQ8_0::BLCK_SIZE]),
107 GgmlDType::Q8_1 => Box::new(vec![BlockQ8_1::zeros(); elem_count / BlockQ8_1::BLCK_SIZE]),
108 GgmlDType::Q2K => Box::new(vec![BlockQ2K::zeros(); elem_count / BlockQ2K::BLCK_SIZE]),
109 GgmlDType::Q3K => Box::new(vec![BlockQ3K::zeros(); elem_count / BlockQ3K::BLCK_SIZE]),
110 GgmlDType::Q4K => Box::new(vec![BlockQ4K::zeros(); elem_count / BlockQ4K::BLCK_SIZE]),
111 GgmlDType::Q5K => Box::new(vec![BlockQ5K::zeros(); elem_count / BlockQ5K::BLCK_SIZE]),
112 GgmlDType::Q6K => Box::new(vec![BlockQ6K::zeros(); elem_count / BlockQ6K::BLCK_SIZE]),
113 GgmlDType::Q8K => Box::new(vec![BlockQ8K::zeros(); elem_count / BlockQ8K::BLCK_SIZE]),
114 }
115}
116
117pub fn cpu_from_data(dtype: GgmlDType, data: Cow<'_, [u8]>) -> Result<Box<dyn QuantizedType>> {
121 let data: &[u8] = &data;
122 Ok(match dtype {
123 GgmlDType::F32 => Box::new(blocks_from_bytes::<f32>(data)?),
124 GgmlDType::F16 => Box::new(blocks_from_bytes::<f16>(data)?),
125 GgmlDType::BF16 => Box::new(blocks_from_bytes::<bf16>(data)?),
126 GgmlDType::Q4_0 => Box::new(blocks_from_bytes::<BlockQ4_0>(data)?),
127 GgmlDType::Q4_1 => Box::new(blocks_from_bytes::<BlockQ4_1>(data)?),
128 GgmlDType::Q5_0 => Box::new(blocks_from_bytes::<BlockQ5_0>(data)?),
129 GgmlDType::Q5_1 => Box::new(blocks_from_bytes::<BlockQ5_1>(data)?),
130 GgmlDType::Q8_0 => Box::new(blocks_from_bytes::<BlockQ8_0>(data)?),
131 GgmlDType::Q8_1 => Box::new(blocks_from_bytes::<BlockQ8_1>(data)?),
132 GgmlDType::Q2K => Box::new(blocks_from_bytes::<BlockQ2K>(data)?),
133 GgmlDType::Q3K => Box::new(blocks_from_bytes::<BlockQ3K>(data)?),
134 GgmlDType::Q4K => Box::new(blocks_from_bytes::<BlockQ4K>(data)?),
135 GgmlDType::Q5K => Box::new(blocks_from_bytes::<BlockQ5K>(data)?),
136 GgmlDType::Q6K => Box::new(blocks_from_bytes::<BlockQ6K>(data)?),
137 GgmlDType::Q8K => Box::new(blocks_from_bytes::<BlockQ8K>(data)?),
138 })
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 fn f32_bytes(xs: &[f32]) -> Vec<u8> {
146 xs.iter().flat_map(|x| x.to_le_bytes()).collect()
147 }
148
149 fn dequant(q: &dyn QuantizedType, n: usize) -> Vec<f32> {
150 q.dequantize(n).unwrap().as_slice::<f32>().unwrap().to_vec()
151 }
152
153 #[test]
158 fn owned_f32_bytes_round_trip() {
159 let xs = [1.5_f32, -2.0, 3.25];
160 let q = cpu_from_data(GgmlDType::F32, Cow::Owned(f32_bytes(&xs))).unwrap();
161 assert_eq!(dequant(q.as_ref(), 3), xs);
162 }
163
164 #[test]
165 fn owned_q4_0_blocks_round_trip() {
166 let mut bytes = Vec::new();
170 for (d, q) in [(1.0_f32, 0x3A_u8), (0.5, 0x81)] {
171 bytes.extend_from_slice(&f16::from_f32(d).to_le_bytes());
172 bytes.extend(std::iter::repeat_n(q, 16));
173 }
174 let back = cpu_from_data(GgmlDType::Q4_0, Cow::Owned(bytes)).unwrap();
175 let want: Vec<f32> = [2.0, -5.0, -3.5, 0.0]
178 .iter()
179 .flat_map(|&v| std::iter::repeat_n(v, 16))
180 .collect();
181 assert_eq!(dequant(back.as_ref(), 64), want);
182 }
183
184 #[test]
186 fn dequantize_declines_a_count_the_blocks_cannot_supply() {
187 let q = cpu_zeros(GgmlDType::Q4_0, 64); assert_eq!(dequant(q.as_ref(), 64), vec![0.0; 64]);
189 let too_many = q.dequantize(96).expect_err("3 blocks from 2").to_string();
190 assert!(
191 too_many.contains("96 elements need 54 bytes of Q4_0 blocks, but the buffer holds 36"),
192 "{too_many}"
193 );
194 let partial = q.dequantize(40).expect_err("a partial block").to_string();
195 assert!(
196 partial.contains("element count 40 is not a multiple of Q4_0's block size 32"),
197 "{partial}"
198 );
199 }
200
201 #[test]
202 fn a_partial_trailing_block_is_declined() {
203 let err = cpu_from_data(GgmlDType::Q4_0, Cow::Owned(vec![0_u8; 18 + 5]))
204 .err()
205 .expect("a partial block must be declined")
206 .to_string();
207 assert!(
208 err.contains("23 bytes is not a whole number of Q4_0 blocks (18 bytes each)"),
209 "wrong decline: {err}"
210 );
211 }
212
213 #[test]
215 fn misaligned_bytes_are_accepted() {
216 let xs = [0.5_f32, 7.0, -1.25, 2.0];
217 let body = f32_bytes(&xs);
218 let mut buf = vec![0_u8; 3 + body.len()];
219 let off = (0..3)
220 .find(|o| !(buf.as_ptr() as usize + o).is_multiple_of(std::mem::align_of::<f32>()))
221 .expect("one of three offsets is misaligned for f32");
222 buf[off..off + body.len()].copy_from_slice(&body);
223 let bytes = &buf[off..off + body.len()];
224 assert!(!(bytes.as_ptr() as usize).is_multiple_of(std::mem::align_of::<f32>()));
225 let q = cpu_from_data(GgmlDType::F32, Cow::Borrowed(bytes)).unwrap();
226 assert_eq!(dequant(q.as_ref(), 4), xs);
227 }
228}