1use heapless::Vec as HVec;
6use serde::{Deserialize, Serialize};
7
8pub const MAX_TENSOR_SIZE: usize = 16 * 1024;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum QuantizationType {
14 Int8,
16 Int4,
18 Binary,
20 Fixed16,
22}
23
24impl QuantizationType {
25 pub const fn bits(&self) -> usize {
27 match self {
28 Self::Int8 => 8,
29 Self::Int4 => 4,
30 Self::Binary => 1,
31 Self::Fixed16 => 16,
32 }
33 }
34
35 pub const fn compression_ratio(&self) -> usize {
37 32 / self.bits()
38 }
39}
40
41#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
43pub struct QuantParams {
44 pub scale: f32,
46 pub zero_point: f32,
48 pub min_val: f32,
50 pub max_val: f32,
52}
53
54impl Default for QuantParams {
55 fn default() -> Self {
56 Self {
57 scale: 1.0 / 127.0,
58 zero_point: 0.0,
59 min_val: -1.0,
60 max_val: 1.0,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct QuantizedTensor<const N: usize> {
68 pub data: HVec<u8, N>,
70 pub shape: [usize; 4],
72 pub ndim: usize,
74 pub quant_type: QuantizationType,
76 pub params: QuantParams,
78}
79
80impl<const N: usize> QuantizedTensor<N> {
81 pub fn from_f32(data: &[f32], shape: &[usize], quant_type: QuantizationType) -> crate::Result<Self> {
83 if data.is_empty() {
84 return Err(crate::Error::QuantizationError("Empty data"));
85 }
86
87 let mut min_val = f32::MAX;
89 let mut max_val = f32::MIN;
90 for &v in data {
91 if v < min_val { min_val = v; }
92 if v > max_val { max_val = v; }
93 }
94
95 let params = match quant_type {
96 QuantizationType::Int8 => {
97 let scale = (max_val - min_val) / 255.0;
98 let zero_point = -min_val / scale - 128.0;
99 QuantParams { scale, zero_point, min_val, max_val }
100 }
101 QuantizationType::Int4 => {
102 let scale = (max_val - min_val) / 15.0;
103 let zero_point = -min_val / scale - 8.0;
104 QuantParams { scale, zero_point, min_val, max_val }
105 }
106 QuantizationType::Binary => {
107 QuantParams {
108 scale: 1.0,
109 zero_point: 0.0,
110 min_val: -1.0,
111 max_val: 1.0,
112 }
113 }
114 QuantizationType::Fixed16 => {
115 let scale = (max_val - min_val) / 65535.0;
116 QuantParams { scale, zero_point: min_val, min_val, max_val }
117 }
118 };
119
120 let quantized_data = Self::quantize_data(data, quant_type, ¶ms)?;
121
122 let mut shape_arr = [0usize; 4];
123 let ndim = shape.len().min(4);
124 for (i, &s) in shape.iter().take(4).enumerate() {
125 shape_arr[i] = s;
126 }
127
128 Ok(Self {
129 data: quantized_data,
130 shape: shape_arr,
131 ndim,
132 quant_type,
133 params,
134 })
135 }
136
137 fn quantize_data(data: &[f32], quant_type: QuantizationType, params: &QuantParams) -> crate::Result<HVec<u8, N>> {
138 let mut result = HVec::new();
139
140 match quant_type {
141 QuantizationType::Int8 => {
142 for &v in data {
143 let q = ((v - params.min_val) / params.scale).round() as i16;
144 let q = q.clamp(-128, 127) as i8;
145 result.push(q as u8).map_err(|_| crate::Error::BufferOverflow)?;
146 }
147 }
148 QuantizationType::Int4 => {
149 for chunk in data.chunks(2) {
151 let v0 = ((chunk[0] - params.min_val) / params.scale).round() as i8;
152 let v1 = if chunk.len() > 1 {
153 ((chunk[1] - params.min_val) / params.scale).round() as i8
154 } else {
155 0
156 };
157 let v0 = (v0.clamp(-8, 7) + 8) as u8;
158 let v1 = (v1.clamp(-8, 7) + 8) as u8;
159 let packed = (v0 & 0x0F) | ((v1 & 0x0F) << 4);
160 result.push(packed).map_err(|_| crate::Error::BufferOverflow)?;
161 }
162 }
163 QuantizationType::Binary => {
164 for chunk in data.chunks(8) {
166 let mut byte = 0u8;
167 for (i, &v) in chunk.iter().enumerate() {
168 if v >= 0.0 {
169 byte |= 1 << i;
170 }
171 }
172 result.push(byte).map_err(|_| crate::Error::BufferOverflow)?;
173 }
174 }
175 QuantizationType::Fixed16 => {
176 for &v in data {
177 let q = ((v - params.min_val) / params.scale).round() as u16;
178 result.push((q >> 8) as u8).map_err(|_| crate::Error::BufferOverflow)?;
179 result.push((q & 0xFF) as u8).map_err(|_| crate::Error::BufferOverflow)?;
180 }
181 }
182 }
183
184 Ok(result)
185 }
186
187 pub fn numel(&self) -> usize {
189 self.shape[..self.ndim].iter().product()
190 }
191
192 pub fn compressed_size(&self) -> usize {
194 self.data.len()
195 }
196
197 pub fn memory_savings(&self) -> f32 {
199 let fp32_size = self.numel() * 4;
200 1.0 - (self.compressed_size() as f32 / fp32_size as f32)
201 }
202}
203
204#[inline(never)] pub fn matmul_int8(
210 weights: &[i8],
211 _weight_params: &QuantParams,
212 input: &[i8],
213 _input_params: &QuantParams,
214 output: &mut [i32],
215 out_dim: usize,
216 in_dim: usize,
217) {
218 debug_assert_eq!(weights.len(), out_dim * in_dim);
219 debug_assert_eq!(input.len(), in_dim);
220 debug_assert_eq!(output.len(), out_dim);
221
222 for i in 0..out_dim {
223 let mut acc: i32 = 0;
224 let row_start = i * in_dim;
225
226 let chunks = in_dim / 4;
228 for j in 0..chunks {
229 let idx = j * 4;
230 acc += weights[row_start + idx] as i32 * input[idx] as i32;
231 acc += weights[row_start + idx + 1] as i32 * input[idx + 1] as i32;
232 acc += weights[row_start + idx + 2] as i32 * input[idx + 2] as i32;
233 acc += weights[row_start + idx + 3] as i32 * input[idx + 3] as i32;
234 }
235
236 for j in (chunks * 4)..in_dim {
238 acc += weights[row_start + j] as i32 * input[j] as i32;
239 }
240
241 output[i] = acc;
242 }
243}
244
245#[inline]
247pub fn dequantize_accumulator(
248 acc: i32,
249 weight_params: &QuantParams,
250 input_params: &QuantParams,
251) -> f32 {
252 acc as f32 * weight_params.scale * input_params.scale
253}
254
255#[inline]
259pub fn binary_xnor_popcount(a: &[u8], b: &[u8]) -> i32 {
260 debug_assert_eq!(a.len(), b.len());
261
262 let mut count: i32 = 0;
263 for (&x, &y) in a.iter().zip(b.iter()) {
264 let xnor = !(x ^ y);
266 count += xnor.count_ones() as i32;
267 }
268
269 let total_bits = (a.len() * 8) as i32;
273 count * 2 - total_bits
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn test_int8_quantization() {
282 let data = [-1.0f32, -0.5, 0.0, 0.5, 1.0];
283 let tensor: QuantizedTensor<64> = QuantizedTensor::from_f32(
284 &data,
285 &[5],
286 QuantizationType::Int8
287 ).unwrap();
288
289 assert_eq!(tensor.numel(), 5);
290 assert_eq!(tensor.compressed_size(), 5);
291 assert!(tensor.memory_savings() > 0.7); }
293
294 #[test]
295 fn test_binary_xnor() {
296 let a = [0b11110000u8, 0b10101010];
297 let b = [0b11110000u8, 0b10101010];
298
299 let result = binary_xnor_popcount(&a, &b);
301 assert_eq!(result, 16); }
303
304 #[test]
305 fn test_int4_packing() {
306 let data = [0.0f32, 0.5, -0.5, 1.0];
307 let tensor: QuantizedTensor<64> = QuantizedTensor::from_f32(
308 &data,
309 &[4],
310 QuantizationType::Int4
311 ).unwrap();
312
313 assert_eq!(tensor.compressed_size(), 2);
315 }
316}