edgefirst_decoder/per_scale/
helper.rs1use crate::schema::SchemaV2;
11use crate::{DecoderError, DecoderResult};
12use edgefirst_tensor::{Quantization as TQ, TensorDyn};
13
14pub fn apply_schema_quant(schema: &SchemaV2, tensors: &mut [&mut TensorDyn]) -> DecoderResult<()> {
29 for logical in &schema.outputs {
30 for child in &logical.outputs {
32 if let Some(q) = child.quantization.as_ref() {
33 attach_per_tensor_quant_by_shape(tensors, &child.shape, q)?;
34 }
35 }
36 if logical.outputs.is_empty() {
38 if let Some(q) = logical.quantization.as_ref() {
39 attach_per_tensor_quant_by_shape(tensors, &logical.shape, q)?;
40 }
41 }
42 }
43 Ok(())
44}
45
46fn attach_per_tensor_quant_by_shape(
47 tensors: &mut [&mut TensorDyn],
48 expected_shape: &[usize],
49 schema_q: &crate::schema::Quantization,
50) -> DecoderResult<()> {
51 if schema_q.scale.is_empty() {
55 return Err(DecoderError::InvalidShape(format!(
56 "apply_schema_quant: schema declares quantization for shape \
57 {expected_shape:?} but `scale` is empty"
58 )));
59 }
60 if !schema_q.is_per_tensor() {
61 return Ok(());
64 }
65 let scale = schema_q.scale[0];
66 let zp = schema_q.zero_point_at(0);
67
68 for t in tensors.iter_mut() {
69 if t.shape() == expected_shape {
70 macro_rules! try_attach {
72 ($ty:ty) => {
73 if let Some(inner) = t.as_typed_mut::<$ty>() {
74 let tq = TQ::per_tensor(scale, zp);
75 inner.set_quantization(tq).map_err(|e| {
76 DecoderError::Internal(format!(
77 "apply_schema_quant set_quantization failed: {e}"
78 ))
79 })?;
80 return Ok(());
81 }
82 };
83 }
84 try_attach!(i8);
85 try_attach!(u8);
86 try_attach!(i16);
87 try_attach!(u16);
88 return Ok(());
90 }
91 }
92 Err(DecoderError::InvalidShape(format!(
93 "apply_schema_quant: no tensor matches {expected_shape:?}"
94 )))
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::schema::SchemaV2;
101 use edgefirst_tensor::{Tensor, TensorMemory};
102
103 #[test]
104 fn applies_quant_to_int8_by_shape() {
105 let json =
107 edgefirst_bench::testdata::read_to_string("per_scale/synthetic_yolov8n_schema.json");
108 let schema: SchemaV2 = serde_json::from_str(&json).unwrap();
109
110 let t = Tensor::<i8>::new(&[1, 80, 80, 64], Some(TensorMemory::Mem), None).unwrap();
113 assert!(t.quantization().is_none(), "fresh tensor has no quant");
114 let mut td: TensorDyn = t.into();
115 let mut tensors: Vec<&mut TensorDyn> = vec![&mut td];
116
117 apply_schema_quant(&schema, &mut tensors).unwrap_err();
118 }
121
122 #[test]
123 fn applies_quant_to_full_yolov8_input_set() {
124 let json =
125 edgefirst_bench::testdata::read_to_string("per_scale/synthetic_yolov8n_schema.json");
126 let schema: SchemaV2 = serde_json::from_str(&json).unwrap();
127
128 let shapes_int8 = [
130 vec![1, 80, 80, 64],
131 vec![1, 80, 80, 80],
132 vec![1, 80, 80, 32],
133 vec![1, 40, 40, 64],
134 vec![1, 40, 40, 80],
135 vec![1, 40, 40, 32],
136 vec![1, 20, 20, 64],
137 vec![1, 20, 20, 80],
138 vec![1, 20, 20, 32],
139 vec![1, 160, 160, 32],
140 ];
141 let mut owned: Vec<TensorDyn> = shapes_int8
142 .iter()
143 .map(|s| {
144 Tensor::<i8>::new(s, Some(TensorMemory::Mem), None)
145 .unwrap()
146 .into()
147 })
148 .collect();
149 let mut refs: Vec<&mut TensorDyn> = owned.iter_mut().collect();
150
151 apply_schema_quant(&schema, &mut refs).unwrap();
152
153 for td in &owned {
155 if let Some(t) = td.as_typed::<i8>() {
156 assert!(
157 t.quantization().is_some(),
158 "tensor missing quant after apply"
159 );
160 }
161 }
162 }
163
164 #[test]
165 fn empty_scale_errors_instead_of_silently_skipping() {
166 use crate::schema::{DType, Quantization};
170 let bad_q = Quantization {
171 scale: vec![],
172 zero_point: None,
173 axis: None,
174 dtype: Some(DType::Int8),
175 };
176 let mut td: TensorDyn = Tensor::<i8>::new(&[1, 2, 3], Some(TensorMemory::Mem), None)
177 .unwrap()
178 .into();
179 let mut refs: Vec<&mut TensorDyn> = vec![&mut td];
180
181 let err = attach_per_tensor_quant_by_shape(&mut refs, &[1, 2, 3], &bad_q)
182 .expect_err("empty scale must be rejected");
183 match err {
184 DecoderError::InvalidShape(msg) => {
185 assert!(msg.contains("`scale` is empty"), "msg = {msg}")
186 }
187 other => panic!("unexpected error: {other:?}"),
188 }
189 }
190
191 #[test]
192 fn skips_float_tensors_silently() {
193 let json =
194 edgefirst_bench::testdata::read_to_string("per_scale/synthetic_yolov8n_schema.json");
195 let schema: SchemaV2 = serde_json::from_str(&json).unwrap();
196
197 let shape = vec![1, 80, 80, 64];
199 let t = Tensor::<f32>::new(&shape, Some(TensorMemory::Mem), None).unwrap();
200 let mut td: TensorDyn = t.into();
201 let mut refs: Vec<&mut TensorDyn> = vec![&mut td];
202
203 let r = apply_schema_quant(&schema, &mut refs);
208 assert!(r.is_err());
209 assert_eq!(td.dtype(), edgefirst_tensor::DType::F32, "unexpected dtype");
211 }
212}