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