ONE - On-device Neural Engine
Loading...
Searching...
No Matches
TestUtils.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
3 * Copyright 2017 The TensorFlow Authors. All Rights Reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#ifndef LUCI_INTERPRETER_KERNELS_TESTUTILS_H
19#define LUCI_INTERPRETER_KERNELS_TESTUTILS_H
20
21#include "luci_interpreter/core/Tensor.h"
23
24#include <type_traits>
25#include <limits> // std::numeric_limits
26
27#include <gtest/gtest.h>
28#include <gmock/gmock.h>
29
30namespace luci_interpreter
31{
32namespace kernels
33{
34namespace testing
35{
36
37template <typename T>
38std::vector<T> quantize(const float *data, size_t num_elements, float scale, int32_t zero_point);
39
40template <DataType DT>
41Tensor makeInputTensor(const Shape &shape, const std::vector<typename DataTypeImpl<DT>::Type> &data,
42 IMemoryManager *memory_manager)
43{
44 Tensor tensor(DT, shape, {}, "");
45 memory_manager->allocate_memory(tensor);
46 tensor.writeData(data.data(), data.size() * sizeof(typename DataTypeImpl<DT>::Type));
47 return tensor;
48}
49
60template <DataType DT>
61Tensor makeInputTensor(const Shape &shape, float scale, int32_t zero_point,
62 const std::vector<float> &data, IMemoryManager *memory_manager)
63{
64 using NativeT = typename DataTypeImpl<DT>::Type;
65 Tensor tensor(DT, shape, {{scale}, {zero_point}}, "");
66 std::vector<NativeT> quantized_data =
67 quantize<NativeT>(data.data(), data.size(), scale, zero_point);
68 memory_manager->allocate_memory(tensor);
69 tensor.writeData(quantized_data.data(), quantized_data.size() * sizeof(NativeT));
70 return tensor;
71}
72
84template <DataType DT>
85Tensor makeInputTensor(const Shape &shape, const std::vector<float> &scales,
86 const std::vector<int32_t> &zero_points, int quantized_dimension,
87 const std::vector<float> &data, IMemoryManager *memory_manager)
88{
89 using NativeT = typename DataTypeImpl<DT>::Type;
90 assert(quantized_dimension < shape.num_dims());
91 Tensor tensor(DT, shape, {scales, zero_points, quantized_dimension}, "");
92
93 // quantize_dimension breaks shape into two parts:
94 // inner dimensions that contains continuous data with one quantization type
95 // outer dimensions that contains other dimensions
96 size_t outer_dims_size = 1;
97 int32_t quant_dim_size = shape.dim(quantized_dimension);
98 size_t inner_dims_size = 1;
99 assert(quant_dim_size == scales.size());
100 assert(quant_dim_size == zero_points.size());
101
102 for (int i = 0; i < quantized_dimension; ++i)
103 outer_dims_size *= shape.dim(i);
104 for (int i = quantized_dimension + 1; i < shape.num_dims(); ++i)
105 inner_dims_size *= shape.dim(i);
106
107 assert(shape.num_elements() == outer_dims_size * quant_dim_size * inner_dims_size);
108
109 std::vector<NativeT> quantized_data;
110 quantized_data.reserve(shape.num_elements());
111 for (size_t outer_it = 0; outer_it < outer_dims_size; ++outer_it)
112 for (int32_t channel = 0; channel < quant_dim_size; ++channel)
113 {
114 int32_t zero_point = zero_points[channel];
115 float scale = scales[channel];
116 size_t offset = inner_dims_size * (quant_dim_size * outer_it + channel);
117 std::vector<NativeT> part_quantized_data =
118 quantize<NativeT>(data.data() + offset, inner_dims_size, scale, zero_point);
119 quantized_data.insert(quantized_data.end(), part_quantized_data.begin(),
120 part_quantized_data.end());
121 }
122 assert(quantized_data.size() == shape.num_elements());
123 memory_manager->allocate_memory(tensor);
124 tensor.writeData(quantized_data.data(), quantized_data.size() * sizeof(NativeT));
125 return tensor;
126}
127
128Tensor makeOutputTensor(DataType element_type);
129Tensor makeOutputTensor(DataType element_type, float scale, int32_t zero_point);
130
131std::vector<int32_t> extractTensorShape(const Tensor &tensor);
132
133// Returns the corresponding DataType given the type T.
134template <typename T> constexpr DataType getElementType()
135{
136 if (std::is_same<T, float>::value)
137 return DataType::FLOAT32;
138 if (std::is_same<T, double>::value)
139 return DataType::FLOAT64;
140 if (std::is_same<T, uint8_t>::value)
141 return DataType::U8;
142 if (std::is_same<T, uint16_t>::value)
143 return DataType::U16;
144 if (std::is_same<T, uint32_t>::value)
145 return DataType::U32;
146 if (std::is_same<T, uint64_t>::value)
147 return DataType::U64;
148 if (std::is_same<T, int8_t>::value)
149 return DataType::S8;
150 if (std::is_same<T, int16_t>::value)
151 return DataType::S16;
152 if (std::is_same<T, int32_t>::value)
153 return DataType::S32;
154 if (std::is_same<T, int64_t>::value)
155 return DataType::S64;
156 if (std::is_same<T, bool>::value)
157 return DataType::BOOL;
158 return DataType::Unknown;
159}
160
161template <typename T> std::vector<T> extractTensorData(const Tensor &tensor)
162{
163 const auto *data_ptr = tensor.data<T>();
164 return std::vector<T>(data_ptr, data_ptr + tensor.shape().num_elements());
165}
166
167std::vector<float> dequantizeTensorData(const Tensor &tensor);
168
169// Array version of `::testing::FloatNear` matcher.
170::testing::Matcher<std::vector<float>> FloatArrayNear(const std::vector<float> &values,
171 float max_abs_error = 1.0e-5f);
172
173template <typename T>
174std::vector<T> quantize(const float *data, size_t num_elements, float scale, int32_t zero_point)
175{
176 static_assert(std::is_integral<T>::value, "Integral type expected.");
177
178 float q_min{}, q_max{};
179 if (std::is_signed<T>::value)
180 {
181 q_min = -std::numeric_limits<T>::max();
182 q_max = std::numeric_limits<T>::max();
183 }
184 else
185 {
186 q_min = 0;
187 q_max = std::numeric_limits<T>::max();
188 }
189
190 std::vector<T> q;
191 for (size_t i = 0; i < num_elements; ++i)
192 {
193 const auto &f = data[i];
194 q.push_back(static_cast<T>(
195 std::max<float>(q_min, std::min<float>(q_max, std::round(zero_point + (f / scale))))));
196 }
197 return q;
198}
199
200template <typename T>
201std::vector<float> dequantize(const T *data, size_t num_elements, float scale, int32_t zero_point)
202{
203 static_assert(std::is_integral<T>::value, "Integral type expected.");
204 std::vector<float> f;
205 for (size_t i = 0; i < num_elements; ++i)
206 {
207 const T &q = data[i];
208 f.push_back(scale * (q - zero_point));
209 }
210 return f;
211}
212
213// NOTE Returns scale and zero point for _asymmetric_ range (both signed and unsigned).
214template <typename T> std::pair<float, int32_t> quantizationParams(float f_min, float f_max)
215{
216 static_assert(std::is_integral<T>::value, "Integral type expected.");
217 int32_t zero_point = 0;
218 float scale = 0;
219 const T qmin = std::numeric_limits<T>::lowest();
220 const T qmax = std::numeric_limits<T>::max();
221 const float qmin_double = qmin;
222 const float qmax_double = qmax;
223 // 0 should always be a representable value. Let's assume that the initial
224 // min,max range contains 0.
225 assert(f_max >= 0);
226 assert(f_min <= 0);
227 if (f_min == f_max)
228 {
229 // Special case where the min,max range is a point. Should be {0}.
230 assert(f_max == 0);
231 assert(f_min == 0);
232 return {scale, zero_point};
233 }
234
235 // General case.
236 //
237 // First determine the scale.
238 scale = (f_max - f_min) / (qmax_double - qmin_double);
239
240 // Zero-point computation.
241 // First the initial floating-point computation. The zero-point can be
242 // determined from solving an affine equation for any known pair
243 // (real value, corresponding quantized value).
244 // We know two such pairs: (rmin, qmin) and (rmax, qmax).
245 // The arithmetic error on the zero point computed from either pair
246 // will be roughly machine_epsilon * (sum of absolute values of terms)
247 // so we want to use the variant that adds the smaller terms.
248 const float zero_point_from_min = qmin_double - f_min / scale;
249 const float zero_point_from_max = qmax_double - f_max / scale;
250
251 const float zero_point_from_min_error = std::abs(qmin_double) + std::abs(f_min / scale);
252
253 const float zero_point_from_max_error = std::abs(qmax_double) + std::abs(f_max / scale);
254
255 const float zero_point_double = zero_point_from_min_error < zero_point_from_max_error
256 ? zero_point_from_min
257 : zero_point_from_max;
258
259 // Now we need to nudge the zero point to be an integer
260 // (our zero points are integer, and this is motivated by the requirement
261 // to be able to represent the real value "0" exactly as a quantized value,
262 // which is required in multiple places, for example in Im2col with SAME
263 // padding).
264
265 T nudged_zero_point = 0;
266 if (zero_point_double < qmin_double)
267 {
268 nudged_zero_point = qmin;
269 }
270 else if (zero_point_double > qmax_double)
271 {
272 nudged_zero_point = qmax;
273 }
274 else
275 {
276 nudged_zero_point = static_cast<T>(std::round(zero_point_double));
277 }
278
279 // The zero point should always be in the range of quantized value,
280 // // [qmin, qmax].
281 assert(qmax >= nudged_zero_point);
282 assert(qmin <= nudged_zero_point);
283 zero_point = nudged_zero_point;
284 // finally, return the values
285 return {scale, zero_point};
286}
287
288inline float getTolerance(float min, float max, int quantize_steps)
289{
290 return ((max - min) / quantize_steps);
291}
292
293} // namespace testing
294} // namespace kernels
295} // namespace luci_interpreter
296
297#endif // LUCI_INTERPRETER_KERNELS_TESTUTILS_H
virtual void allocate_memory(luci_interpreter::Tensor &tensor)=0
int32_t dim(int i) const
Definition Tensor.h:41
int32_t num_elements() const
Definition Tensor.h:53
int num_dims() const
Definition Tensor.h:39
__global uchar * offset(const Image *img, int x, int y)
Definition helpers.h:540
const T * data(const std::vector< T, Alloc > &v)
Tensor makeOutputTensor(DataType element_type)
Definition TestUtils.cpp:32
std::vector< float > dequantize(const T *data, size_t num_elements, float scale, int32_t zero_point)
Definition TestUtils.h:201
std::vector< int32_t > extractTensorShape(const Tensor &tensor)
float getTolerance(float min, float max, int quantize_steps)
Definition TestUtils.h:288
constexpr DataType getElementType()
Definition TestUtils.h:134
std::vector< float > dequantizeTensorData(const Tensor &tensor)
Definition TestUtils.cpp:39
Matcher< std::vector< float > > FloatArrayNear(const std::vector< float > &values, float max_abs_error)
std::vector< T > quantize(const float *data, size_t num_elements, float scale, int32_t zero_point)
Definition TestUtils.h:174
std::pair< float, int32_t > quantizationParams(float f_min, float f_max)
Definition TestUtils.h:214
std::vector< T > extractTensorData(const Tensor &tensor)
Definition TestUtils.h:161
Tensor makeInputTensor(const Shape &shape, const std::vector< typename DataTypeImpl< DT >::Type > &data, IMemoryManager *memory_manager)
Definition TestUtils.h:41
DataType
"scalar" value type
Definition DataType.h:32
C++ scalar type corresponding to each DataType.
Definition DataType.h:58