use crate::support::{clamp, requantize};
use crate::types::{Dims, FcParams, PerChannelQuantParams, PerTensorQuantParams, Result};
pub fn fully_connected_s8(
fc_params: &FcParams,
quant_params: &PerTensorQuantParams,
input_dims: &Dims,
input: &[i8],
filter_dims: &Dims,
kernel: &[i8],
bias: Option<&[i32]>,
output_dims: &Dims,
output: &mut [i8],
) -> Result<()> {
let batches = input_dims.n as usize;
let accum_depth = filter_dims.n as usize;
let output_depth = output_dims.c as usize;
for b in 0..batches {
let input_batch = &input[b * accum_depth..(b + 1) * accum_depth];
let output_batch = &mut output[b * output_depth..(b + 1) * output_depth];
for out_c in 0..output_depth {
let mut acc: i32 = match bias {
Some(b_slice) => b_slice[out_c],
None => 0,
};
let kernel_row = &kernel[out_c * accum_depth..(out_c + 1) * accum_depth];
for i in 0..accum_depth {
let lhs = input_batch[i] as i32 + fc_params.input_offset;
let rhs = kernel_row[i] as i32 + fc_params.filter_offset;
acc += lhs * rhs;
}
acc = requantize(acc, quant_params.multiplier, quant_params.shift);
acc += fc_params.output_offset;
acc = clamp(acc, fc_params.activation.min, fc_params.activation.max);
output_batch[out_c] = acc as i8;
}
}
Ok(())
}
pub fn fully_connected_per_channel_s8(
fc_params: &FcParams,
quant_params: &PerChannelQuantParams,
input_dims: &Dims,
input: &[i8],
filter_dims: &Dims,
kernel: &[i8],
bias: Option<&[i32]>,
output_dims: &Dims,
output: &mut [i8],
) -> Result<()> {
let batches = input_dims.n as usize;
let accum_depth = filter_dims.n as usize;
let output_depth = output_dims.c as usize;
for b in 0..batches {
let input_batch = &input[b * accum_depth..(b + 1) * accum_depth];
let output_batch = &mut output[b * output_depth..(b + 1) * output_depth];
for out_c in 0..output_depth {
let mut acc: i32 = match bias {
Some(b_slice) => b_slice[out_c],
None => 0,
};
let kernel_row = &kernel[out_c * accum_depth..(out_c + 1) * accum_depth];
for i in 0..accum_depth {
let lhs = input_batch[i] as i32 + fc_params.input_offset;
let rhs = kernel_row[i] as i32 + fc_params.filter_offset;
acc += lhs * rhs;
}
let mult = quant_params.multiplier[out_c];
let shift = quant_params.shift[out_c];
acc = requantize(acc, mult, shift);
acc += fc_params.output_offset;
acc = clamp(acc, fc_params.activation.min, fc_params.activation.max);
output_batch[out_c] = acc as i8;
}
}
Ok(())
}
pub fn fully_connected_s16(
fc_params: &FcParams,
quant_params: &PerTensorQuantParams,
input_dims: &Dims,
input: &[i16],
filter_dims: &Dims,
kernel: &[i8],
bias: Option<&[i64]>,
output_dims: &Dims,
output: &mut [i16],
) -> Result<()> {
let batches = input_dims.n as usize;
let accum_depth = filter_dims.n as usize;
let output_depth = output_dims.c as usize;
for b in 0..batches {
let input_batch = &input[b * accum_depth..(b + 1) * accum_depth];
let output_batch = &mut output[b * output_depth..(b + 1) * output_depth];
for out_c in 0..output_depth {
let mut acc: i64 = match bias {
Some(b_slice) => b_slice[out_c],
None => 0,
};
let kernel_row = &kernel[out_c * accum_depth..(out_c + 1) * accum_depth];
for i in 0..accum_depth {
let lhs = input_batch[i] as i64;
let rhs = kernel_row[i] as i64;
acc += lhs * rhs;
}
let req = requantize(
(acc >> 15) as i32,
quant_params.multiplier,
quant_params.shift,
);
let final_val = clamp(req, fc_params.activation.min, fc_params.activation.max);
output_batch[out_c] = final_val as i16;
}
}
Ok(())
}
pub fn batch_matmul_s8(
fc_params: &FcParams,
quant_params: &PerTensorQuantParams,
lhs_dims: &Dims,
input_lhs: &[i8],
rhs_dims: &Dims,
input_rhs: &[i8],
output_dims: &Dims,
output: &mut [i8],
) -> Result<()> {
let batches = output_dims.n as usize;
let rows = lhs_dims.h as usize;
let cols = rhs_dims.c as usize;
let accum_dim = lhs_dims.w as usize;
for b in 0..batches {
let lhs_b_idx = if lhs_dims.n == 1 { 0 } else { b };
let rhs_b_idx = if rhs_dims.n == 1 { 0 } else { b };
for i in 0..rows {
for j in 0..cols {
let mut acc: i32 = 0;
for k in 0..accum_dim {
let lhs_idx = (lhs_b_idx * rows + i) * accum_dim + k;
let rhs_idx = (rhs_b_idx * accum_dim + k) * cols + j;
let lhs_val = input_lhs[lhs_idx] as i32 + fc_params.input_offset;
let rhs_val = input_rhs[rhs_idx] as i32 + fc_params.filter_offset;
acc += lhs_val * rhs_val;
}
let req = requantize(acc, quant_params.multiplier, quant_params.shift);
let final_val = clamp(
req + fc_params.output_offset,
fc_params.activation.min,
fc_params.activation.max,
);
let out_idx = (b * rows + i) * cols + j;
if out_idx < output.len() {
output[out_idx] = final_val as i8;
}
}
}
}
Ok(())
}
pub fn batch_matmul_s16(
fc_params: &FcParams,
quant_params: &PerTensorQuantParams,
lhs_dims: &Dims,
input_lhs: &[i16],
rhs_dims: &Dims,
input_rhs: &[i16],
output_dims: &Dims,
output: &mut [i16],
) -> Result<()> {
let batches = output_dims.n as usize;
let rows = lhs_dims.h as usize;
let cols = rhs_dims.c as usize;
let accum_dim = lhs_dims.w as usize;
for b in 0..batches {
let lhs_b_idx = if lhs_dims.n == 1 { 0 } else { b };
let rhs_b_idx = if rhs_dims.n == 1 { 0 } else { b };
for i in 0..rows {
for j in 0..cols {
let mut acc: i64 = 0;
for k in 0..accum_dim {
let lhs_idx = (lhs_b_idx * rows + i) * accum_dim + k;
let rhs_idx = (rhs_b_idx * accum_dim + k) * cols + j;
let lhs_val = input_lhs[lhs_idx] as i64;
let rhs_val = input_rhs[rhs_idx] as i64;
acc += lhs_val * rhs_val;
}
let req = requantize(
(acc >> 15) as i32,
quant_params.multiplier,
quant_params.shift,
);
let final_val = clamp(req, fc_params.activation.min, fc_params.activation.max);
let out_idx = (b * rows + i) * cols + j;
if out_idx < output.len() {
output[out_idx] = final_val as i16;
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Activation;
#[test]
fn test_fully_connected_s8() {
let fc_params = FcParams {
input_offset: 0,
filter_offset: 0,
output_offset: 0,
activation: Activation::int8_unconstrained(),
};
let quant_params = PerTensorQuantParams::new(1073741824, 0); let input_dims = Dims::new(1, 1, 1, 3);
let input = [2i8, 4i8, 6i8];
let filter_dims = Dims::new(3, 1, 1, 2); let kernel = [
1i8, 2i8, 3i8, 4i8, 5i8, 6i8, ];
let bias = [0i32, 0i32];
let output_dims = Dims::new(1, 1, 1, 2);
let mut output = [0i8; 2];
fully_connected_s8(
&fc_params,
&quant_params,
&input_dims,
&input,
&filter_dims,
&kernel,
Some(&bias),
&output_dims,
&mut output,
)
.unwrap();
assert_eq!(output[0], 14);
assert_eq!(output[1], 32);
}
#[test]
fn test_batch_matmul_s8() {
let fc_params = FcParams {
input_offset: 0,
filter_offset: 0,
output_offset: 0,
activation: Activation::int8_unconstrained(),
};
let quant_params = PerTensorQuantParams::new(1073741824, 0);
let lhs_dims = Dims::new(1, 2, 2, 0); let input_lhs = [1i8, 2i8, 3i8, 4i8];
let rhs_dims = Dims::new(1, 2, 0, 2); let input_rhs = [5i8, 6i8, 7i8, 8i8];
let output_dims = Dims::new(1, 2, 0, 2);
let mut output = [0i8; 4];
batch_matmul_s8(
&fc_params,
&quant_params,
&lhs_dims,
&input_lhs,
&rhs_dims,
&input_rhs,
&output_dims,
&mut output,
)
.unwrap();
assert_eq!(output, [10, 11, 22, 25]);
}
}