Imported Upstream version 1.12.0
[platform/core/ml/nnfw.git] / tests / nnfw_api / src / GenModelTest.h
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #ifndef __NNFW_API_TEST_GEN_MODEL_TEST_H__
18 #define __NNFW_API_TEST_GEN_MODEL_TEST_H__
19
20 #include <gtest/gtest.h>
21 #include <nnfw_internal.h>
22
23 #include <fstream>
24 #include <string>
25 #include <unordered_map>
26
27 #include "CircleGen.h"
28 #include "fixtures.h"
29
30 inline size_t sizeOfNnfwType(NNFW_TYPE type)
31 {
32   switch (type)
33   {
34     case NNFW_TYPE_TENSOR_BOOL:
35     case NNFW_TYPE_TENSOR_UINT8:
36     case NNFW_TYPE_TENSOR_QUANT8_ASYMM:
37     case NNFW_TYPE_TENSOR_QUANT8_ASYMM_SIGNED:
38       return 1;
39     case NNFW_TYPE_TENSOR_FLOAT32:
40     case NNFW_TYPE_TENSOR_INT32:
41       return 4;
42     case NNFW_TYPE_TENSOR_INT64:
43       return 8;
44     default:
45       throw std::runtime_error{"Invalid tensor type"};
46   }
47 }
48
49 // TODO Unify this with `SessionObject` in `fixtures.h`
50 struct SessionObjectGeneric
51 {
52   nnfw_session *session = nullptr;
53   std::vector<std::vector<uint8_t>> inputs;
54   std::vector<std::vector<uint8_t>> outputs;
55 };
56
57 struct TestCaseData
58 {
59   /**
60    * @brief A vector of input buffers
61    */
62   std::vector<std::vector<uint8_t>> inputs;
63
64   /**
65    * @brief A vector of output buffers
66    */
67   std::vector<std::vector<uint8_t>> outputs;
68
69   /**
70    * @brief Append vector data to inputs
71    *
72    * @tparam T Data type
73    * @param data vector data array
74    */
75   template <typename T> TestCaseData &addInput(const std::vector<T> &data)
76   {
77     addData(inputs, data);
78     return *this;
79   }
80
81   /**
82    * @brief Append vector data to inputs
83    *
84    * @tparam T Data type
85    * @param data vector data array
86    */
87   template <typename T> TestCaseData &addOutput(const std::vector<T> &data)
88   {
89     addData(outputs, data);
90     return *this;
91   }
92
93   /**
94    * @brief Call this when @c nnfw_run() for this test case is expected to be failed
95    */
96   TestCaseData &expectFailRun()
97   {
98     _expected_fail_run = true;
99     return *this;
100   }
101   bool expected_fail_run() const { return _expected_fail_run; }
102
103 private:
104   template <typename T>
105   static void addData(std::vector<std::vector<uint8_t>> &dest, const std::vector<T> &data)
106   {
107     size_t size = data.size() * sizeof(T);
108     dest.emplace_back();
109     dest.back().resize(size);
110     std::memcpy(dest.back().data(), data.data(), size);
111   }
112
113   bool _expected_fail_run = false;
114 };
115
116 template <>
117 inline void TestCaseData::addData<bool>(std::vector<std::vector<uint8_t>> &dest,
118                                         const std::vector<bool> &data)
119 {
120   size_t size = data.size() * sizeof(uint8_t);
121   dest.emplace_back();
122   dest.back().resize(size);
123   std::transform(data.cbegin(), data.cend(), dest.back().data(),
124                  [](bool b) { return static_cast<uint8_t>(b); });
125 }
126
127 /**
128  * @brief Create a TestCaseData with a uniform type
129  *
130  * A helper function for generating test cases that has the same data type for model inputs/outputs.
131  *
132  * @tparam T Uniform tensor type
133  * @param inputs Inputs tensor buffers
134  * @param outputs Output tensor buffers
135  * @return TestCaseData Generated test case data
136  */
137 template <typename T>
138 static TestCaseData uniformTCD(const std::vector<std::vector<T>> &inputs,
139                                const std::vector<std::vector<T>> &outputs)
140 {
141   TestCaseData ret;
142   for (const auto &data : inputs)
143     ret.addInput(data);
144   for (const auto &data : outputs)
145     ret.addOutput(data);
146   return ret;
147 }
148
149 /**
150  * @brief A test configuration class
151  */
152 class GenModelTestContext
153 {
154 public:
155   GenModelTestContext(CircleBuffer &&cbuf) : _cbuf{std::move(cbuf)}, _backends{"cpu"} {}
156
157   /**
158    * @brief  Return circle buffer
159    *
160    * @return CircleBuffer& the circle buffer
161    */
162   const CircleBuffer &cbuf() const { return _cbuf; }
163
164   /**
165    * @brief Return test cases
166    *
167    * @return std::vector<TestCaseData>& the test cases
168    */
169   const std::vector<TestCaseData> &test_cases() const { return _test_cases; }
170
171   /**
172    * @brief Return backends
173    *
174    * @return const std::vector<std::string>& the backends to be tested
175    */
176   const std::vector<std::string> &backends() const { return _backends; }
177
178   /**
179    * @brief Return test is defined to fail on model load
180    *
181    * @return bool test is defined to fail on model load
182    */
183   bool expected_fail_model_load() const { return _expected_fail_model_load; }
184
185   /**
186    * @brief Return test is defined to fail on compile
187    *
188    * @return bool test is defined to fail on compile
189    */
190   bool expected_fail_compile() const { return _expected_fail_compile; }
191
192   /**
193    * @brief Set the output buffer size of specified output tensor
194    *        Note that output tensor size of a model with dynamic tensor is calculated while
195    *        running the model.
196    *        Therefore, before runniing the model, the sufficient size of buffer should
197    *        be prepared by calling this method.
198    *        The size does not need to be the exact size.
199    */
200   void output_sizes(uint32_t ind, size_t size) { _output_sizes[ind] = size; }
201
202   size_t output_sizes(uint32_t ind) const { return _output_sizes.at(ind); }
203
204   bool hasOutputSizes(uint32_t ind) const { return _output_sizes.find(ind) != _output_sizes.end(); }
205
206   /**
207    * @brief Add a test case
208    *
209    * @param tc the test case to be added
210    */
211   void addTestCase(const TestCaseData &tc) { _test_cases.emplace_back(tc); }
212
213   /**
214    * @brief Add a test case
215    *
216    * @param tc the test case to be added
217    */
218   void setBackends(const std::vector<std::string> &backends)
219   {
220     _backends.clear();
221
222     for (auto backend : backends)
223     {
224 #ifdef TEST_ACL_BACKEND
225       if (backend == "acl_cl" || backend == "acl_neon")
226       {
227         _backends.push_back(backend);
228       }
229 #endif
230       if (backend == "cpu" || backend == "ruy")
231       {
232         _backends.push_back(backend);
233       }
234 #ifdef TEST_XNNPACK_BACKEND
235       if (backend == "xnnpack")
236       {
237         _backends.push_back(backend);
238       }
239 #endif
240     }
241   }
242
243   /**
244    * @brief Expect failure while model load
245    */
246   void expectFailModelLoad() { _expected_fail_model_load = true; }
247
248   /**
249    * @brief Expect failure while compiling
250    */
251   void expectFailCompile() { _expected_fail_compile = true; }
252
253   /**
254    * @brief Expect failure while execution
255    */
256   void expectFailExecution() { _expected_fail_execution = true; }
257
258 private:
259   CircleBuffer _cbuf;
260   std::vector<TestCaseData> _test_cases;
261   std::vector<std::string> _backends;
262   std::unordered_map<uint32_t, size_t> _output_sizes;
263   bool _expected_fail_model_load{false};
264   bool _expected_fail_compile{false};
265   bool _expected_fail_execution{false};
266 };
267
268 /**
269  * @brief Generated Model test fixture for a one time inference
270  *
271  * This fixture is for one-time inference test with variety of generated models.
272  * It is the test maker's responsiblity to create @c _context which contains
273  * test body, which are generated circle buffer, model input data and output data and
274  * backend list to be tested.
275  * The rest(calling API functions for execution) is done by @c Setup and @c TearDown .
276  *
277  */
278 class GenModelTest : public ::testing::Test
279 {
280 protected:
281   void SetUp() override
282   { // DO NOTHING
283   }
284
285   void TearDown() override
286   {
287     for (std::string backend : _context->backends())
288     {
289       // NOTE If we can prepare many times for one model loading on same session,
290       //      we can move nnfw_create_session to SetUp and
291       //      nnfw_load_circle_from_buffer to outside forloop
292       NNFW_ENSURE_SUCCESS(nnfw_create_session(&_so.session));
293       auto &cbuf = _context->cbuf();
294       auto model_load_result =
295         nnfw_load_circle_from_buffer(_so.session, cbuf.buffer(), cbuf.size());
296       if (_context->expected_fail_model_load())
297       {
298         ASSERT_NE(model_load_result, NNFW_STATUS_NO_ERROR);
299         std::cerr << "Failed model loading as expected." << std::endl;
300         NNFW_ENSURE_SUCCESS(nnfw_close_session(_so.session));
301         continue;
302       }
303       NNFW_ENSURE_SUCCESS(model_load_result);
304       NNFW_ENSURE_SUCCESS(nnfw_set_available_backends(_so.session, backend.data()));
305
306       if (_context->expected_fail_compile())
307       {
308         ASSERT_NE(nnfw_prepare(_so.session), NNFW_STATUS_NO_ERROR);
309
310         NNFW_ENSURE_SUCCESS(nnfw_close_session(_so.session));
311         continue;
312       }
313       NNFW_ENSURE_SUCCESS(nnfw_prepare(_so.session));
314
315       // In/Out buffer settings
316       uint32_t num_inputs;
317       NNFW_ENSURE_SUCCESS(nnfw_input_size(_so.session, &num_inputs));
318       _so.inputs.resize(num_inputs);
319       for (uint32_t ind = 0; ind < _so.inputs.size(); ind++)
320       {
321         nnfw_tensorinfo ti;
322         NNFW_ENSURE_SUCCESS(nnfw_input_tensorinfo(_so.session, ind, &ti));
323         uint64_t input_elements = num_elems(&ti);
324         _so.inputs[ind].resize(input_elements * sizeOfNnfwType(ti.dtype));
325         if (_so.inputs[ind].size() == 0)
326         {
327           // Optional inputs
328           ASSERT_EQ(nnfw_set_input(_so.session, ind, ti.dtype, nullptr, 0), NNFW_STATUS_NO_ERROR);
329         }
330         else
331         {
332           ASSERT_EQ(nnfw_set_input(_so.session, ind, ti.dtype, _so.inputs[ind].data(),
333                                    _so.inputs[ind].size()),
334                     NNFW_STATUS_NO_ERROR);
335         }
336       }
337
338       uint32_t num_outputs;
339       NNFW_ENSURE_SUCCESS(nnfw_output_size(_so.session, &num_outputs));
340       _so.outputs.resize(num_outputs);
341       for (uint32_t ind = 0; ind < _so.outputs.size(); ind++)
342       {
343         nnfw_tensorinfo ti;
344         NNFW_ENSURE_SUCCESS(nnfw_output_tensorinfo(_so.session, ind, &ti));
345
346         auto size = 0;
347         {
348           if (_context->hasOutputSizes(ind))
349           {
350             size = _context->output_sizes(ind);
351           }
352           else
353           {
354             uint64_t output_elements = num_elems(&ti);
355             size = output_elements * sizeOfNnfwType(ti.dtype);
356           }
357           _so.outputs[ind].resize(size);
358         }
359
360         ASSERT_EQ(nnfw_set_output(_so.session, ind, ti.dtype, _so.outputs[ind].data(),
361                                   _so.outputs[ind].size()),
362                   NNFW_STATUS_NO_ERROR);
363       }
364
365       // Set input values, run, and check output values
366       for (auto &test_case : _context->test_cases())
367       {
368         auto &ref_inputs = test_case.inputs;
369         auto &ref_outputs = test_case.outputs;
370         ASSERT_EQ(_so.inputs.size(), ref_inputs.size());
371         for (uint32_t i = 0; i < _so.inputs.size(); i++)
372         {
373           // Fill the values
374           ASSERT_EQ(_so.inputs[i].size(), ref_inputs[i].size());
375           memcpy(_so.inputs[i].data(), ref_inputs[i].data(), ref_inputs[i].size());
376         }
377
378         if (test_case.expected_fail_run())
379         {
380           ASSERT_NE(nnfw_run(_so.session), NNFW_STATUS_NO_ERROR);
381           continue;
382         }
383
384         NNFW_ENSURE_SUCCESS(nnfw_run(_so.session));
385
386         ASSERT_EQ(_so.outputs.size(), ref_outputs.size());
387         for (uint32_t i = 0; i < _so.outputs.size(); i++)
388         {
389           nnfw_tensorinfo ti;
390           NNFW_ENSURE_SUCCESS(nnfw_output_tensorinfo(_so.session, i, &ti));
391
392           // Check output tensor values
393           auto &ref_output = ref_outputs[i];
394           auto &output = _so.outputs[i];
395           ASSERT_EQ(output.size(), ref_output.size());
396
397           switch (ti.dtype)
398           {
399             case NNFW_TYPE_TENSOR_BOOL:
400               compareBuffersExactBool(ref_output, output, i);
401               break;
402             case NNFW_TYPE_TENSOR_UINT8:
403             case NNFW_TYPE_TENSOR_QUANT8_ASYMM:
404               compareBuffersExact<uint8_t>(ref_output, output, i);
405               break;
406             case NNFW_TYPE_TENSOR_QUANT8_ASYMM_SIGNED:
407               compareBuffersExact<int8_t>(ref_output, output, i);
408               break;
409             case NNFW_TYPE_TENSOR_INT32:
410               compareBuffersExact<int32_t>(ref_output, output, i);
411               break;
412             case NNFW_TYPE_TENSOR_FLOAT32:
413               // TODO better way for handling FP error?
414               for (uint32_t e = 0; e < ref_output.size() / sizeof(float); e++)
415               {
416                 float refval = reinterpret_cast<const float *>(ref_output.data())[e];
417                 float val = reinterpret_cast<const float *>(output.data())[e];
418                 EXPECT_NEAR(refval, val, 0.001) << "Output #" << i << ", Element Index : " << e;
419               }
420               break;
421             case NNFW_TYPE_TENSOR_INT64:
422               compareBuffersExact<int64_t>(ref_output, output, i);
423               break;
424             default:
425               throw std::runtime_error{"Invalid tensor type"};
426           }
427           // TODO Add shape comparison
428         }
429       }
430
431       NNFW_ENSURE_SUCCESS(nnfw_close_session(_so.session));
432     }
433   }
434
435 private:
436   template <typename T>
437   void compareBuffersExact(const std::vector<uint8_t> &ref_buf, const std::vector<uint8_t> &act_buf,
438                            uint32_t index)
439   {
440     for (uint32_t e = 0; e < ref_buf.size() / sizeof(T); e++)
441     {
442       T ref = reinterpret_cast<const T *>(ref_buf.data())[e];
443       T act = reinterpret_cast<const T *>(act_buf.data())[e];
444       EXPECT_EQ(ref, act) << "Output #" << index << ", Element Index : " << e;
445     }
446   }
447
448   void compareBuffersExactBool(const std::vector<uint8_t> &ref_buf,
449                                const std::vector<uint8_t> &act_buf, uint32_t index)
450   {
451     for (uint32_t e = 0; e < ref_buf.size() / sizeof(uint8_t); e++)
452     {
453       uint8_t ref_raw = reinterpret_cast<const uint8_t *>(ref_buf.data())[e];
454       bool ref = (ref_raw != 0 ? true : false);
455       uint8_t act_raw = reinterpret_cast<const uint8_t *>(act_buf.data())[e];
456       bool act = (act_raw != 0 ? true : false);
457       EXPECT_EQ(ref, act) << "Output #" << index << ", Element Index : " << e;
458     }
459   }
460
461 protected:
462   SessionObjectGeneric _so;
463   std::unique_ptr<GenModelTestContext> _context;
464 };
465
466 #endif // __NNFW_API_TEST_GEN_MODEL_TEST_H__