1use crate::support::{clamp, requantize};
4use crate::types::{ConvParams, Dims, FcParams, PerTensorQuantParams, Result};
5
6#[inline]
10pub const fn unpack_s4_pair(packed_byte: i8) -> (i8, i8) {
11 let b = packed_byte as i32;
12 let low = (b << 28) >> 28;
13 let high = b >> 4;
14 (low as i8, high as i8)
15}
16
17#[inline]
19pub const fn pack_s4_pair(low: i8, high: i8) -> i8 {
20 ((low as u8 & 0x0F) | ((high as u8 & 0x0F) << 4)) as i8
21}
22
23pub fn fully_connected_s4(
27 fc_params: &FcParams,
28 quant_params: &PerTensorQuantParams,
29 input_dims: &Dims,
30 input: &[i8],
31 filter_dims: &Dims,
32 packed_kernel: &[i8],
33 bias: Option<&[i32]>,
34 output_dims: &Dims,
35 output: &mut [i8],
36) -> Result<()> {
37 let batches = input_dims.n as usize;
38 let accum_depth = filter_dims.n as usize;
39 let output_depth = output_dims.c as usize;
40
41 let packed_cols = (accum_depth + 1) / 2;
42
43 for b in 0..batches {
44 let input_batch = &input[b * accum_depth..(b + 1) * accum_depth];
45 let output_batch = &mut output[b * output_depth..(b + 1) * output_depth];
46
47 for out_c in 0..output_depth {
48 let mut acc: i32 = match bias {
49 Some(b_slice) => b_slice[out_c],
50 None => 0,
51 };
52
53 let kernel_row_packed = &packed_kernel[out_c * packed_cols..(out_c + 1) * packed_cols];
54
55 for p in 0..packed_cols {
56 let (w0, w1) = unpack_s4_pair(kernel_row_packed[p]);
57 let idx0 = p * 2;
58 if idx0 < accum_depth {
59 let lhs0 = input_batch[idx0] as i32 + fc_params.input_offset;
60 acc += lhs0 * (w0 as i32);
61 }
62 let idx1 = idx0 + 1;
63 if idx1 < accum_depth {
64 let lhs1 = input_batch[idx1] as i32 + fc_params.input_offset;
65 acc += lhs1 * (w1 as i32);
66 }
67 }
68
69 acc = requantize(acc, quant_params.multiplier, quant_params.shift);
70 acc += fc_params.output_offset;
71 acc = clamp(acc, fc_params.activation.min, fc_params.activation.max);
72
73 output_batch[out_c] = acc as i8;
74 }
75 }
76 Ok(())
77}
78
79pub fn convolve_s4(
81 conv_params: &ConvParams,
82 quant_params: &PerTensorQuantParams,
83 input_dims: &Dims,
84 input: &[i8],
85 filter_dims: &Dims,
86 packed_kernel: &[i8],
87 bias: Option<&[i32]>,
88 output_dims: &Dims,
89 output: &mut [i8],
90) -> Result<()> {
91 let input_batches = input_dims.n as usize;
92 let input_h = input_dims.h as usize;
93 let input_w = input_dims.w as usize;
94 let input_c = input_dims.c as usize;
95
96 let kernel_h = filter_dims.h as usize;
97 let kernel_w = filter_dims.w as usize;
98 let kernel_c = filter_dims.c as usize;
99
100 let output_h = output_dims.h as usize;
101 let output_w = output_dims.w as usize;
102 let output_c = output_dims.c as usize;
103
104 let kernel_spatial_c = kernel_h * kernel_w * kernel_c;
105 let packed_kernel_cols = (kernel_spatial_c + 1) / 2;
106
107 for b in 0..input_batches {
108 for out_y in 0..output_h {
109 let base_y = out_y as i32 * conv_params.stride.h - conv_params.padding.h;
110 for out_x in 0..output_w {
111 let base_x = out_x as i32 * conv_params.stride.w - conv_params.padding.w;
112
113 for out_c in 0..output_c {
114 let mut acc: i32 = match bias {
115 Some(b_slice) => b_slice[out_c],
116 None => 0,
117 };
118
119 let ker_packed_row = &packed_kernel
120 [out_c * packed_kernel_cols..(out_c + 1) * packed_kernel_cols];
121 let mut k_flat_idx = 0usize;
122
123 for ky in 0..kernel_h {
124 let in_y = base_y + ky as i32 * conv_params.dilation.h;
125 for kx in 0..kernel_w {
126 let in_x = base_x + kx as i32 * conv_params.dilation.w;
127 for ic in 0..kernel_c {
128 if in_y >= 0
129 && in_y < input_dims.h
130 && in_x >= 0
131 && in_x < input_dims.w
132 {
133 let in_idx = ((b * input_h + in_y as usize) * input_w
134 + in_x as usize)
135 * input_c
136 + ic;
137
138 let packed_byte = ker_packed_row[k_flat_idx / 2];
139 let weight = if k_flat_idx % 2 == 0 {
140 unpack_s4_pair(packed_byte).0
141 } else {
142 unpack_s4_pair(packed_byte).1
143 };
144
145 let lhs = input[in_idx] as i32 + conv_params.input_offset;
146 acc += lhs * (weight as i32);
147 }
148 k_flat_idx += 1;
149 }
150 }
151 }
152
153 acc = requantize(acc, quant_params.multiplier, quant_params.shift);
154 acc += conv_params.output_offset;
155 acc = clamp(acc, conv_params.activation.min, conv_params.activation.max);
156
157 let out_idx = ((b * output_h + out_y) * output_w + out_x) * output_c + out_c;
158 output[out_idx] = acc as i8;
159 }
160 }
161 }
162 }
163
164 Ok(())
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use crate::types::Activation;
171
172 #[test]
173 fn test_s4_packing() {
174 let (low, high) = (-5i8, 7i8);
175 let packed = pack_s4_pair(low, high);
176 let (unpacked_low, unpacked_high) = unpack_s4_pair(packed);
177 assert_eq!(unpacked_low, -5);
178 assert_eq!(unpacked_high, 7);
179 }
180
181 #[test]
182 fn test_fully_connected_s4() {
183 let fc_params = FcParams {
184 input_offset: 0,
185 filter_offset: 0,
186 output_offset: 0,
187 activation: Activation::int8_unconstrained(),
188 };
189 let quant_params = PerTensorQuantParams::new(1073741824, 0); let input_dims = Dims::new(1, 1, 1, 2);
191 let input = [4i8, 6i8];
192
193 let filter_dims = Dims::new(2, 1, 1, 1);
194 let packed_kernel = [pack_s4_pair(2i8, 3i8)]; let output_dims = Dims::new(1, 1, 1, 1);
196 let mut output = [0i8; 1];
197
198 fully_connected_s4(
199 &fc_params,
200 &quant_params,
201 &input_dims,
202 &input,
203 &filter_dims,
204 &packed_kernel,
205 None,
206 &output_dims,
207 &mut output,
208 )
209 .unwrap();
210
211 assert_eq!(output[0], 13);
213 }
214}