[UnitTest/Model] Revise model and add its unittests
[platform/adaptation/npu/trix-engine.git] / src / core / ne-handler.cc
1 /**
2  * Proprietary
3  * Copyright (C) 2020 Samsung Electronics
4  * Copyright (C) 2020 Dongju Chae <dongju.chae@samsung.com>
5  */
6 /**
7  * @file ne-host-handler.cc
8  * @date 03 Apr 2020
9  * @brief Implementation of APIs to access NPU from Host
10  * @see https://code.sec.samsung.net/confluence/display/ODLC/2020+Overall+Software+Stack
11  * @author Dongju Chae <dongju.chae@samsung.com>
12  * @bug No known bugs except for NYI items
13  */
14
15 #include <npubinfmt.h>
16 #include <NPUdrvAPI.h>
17 #include <CommPlugin.h>
18
19 #include "ne-utils.h"
20 #include "ne-mem.h"
21 #include "ne-scheduler.h"
22 #include "ne-handler.h"
23
24 #include <string.h>
25 #include <assert.h>
26
27 #include <condition_variable>
28 #include <functional>
29 #include <atomic>
30 #include <map>
31
32 #define TAG _N2
33
34 #define INIT_HOST_HANDLER(handler, dev) \
35   Device *tdev = static_cast <Device *> (dev); \
36   if (tdev == nullptr) return -EINVAL; \
37   HostHandler *handler = tdev->getHostHandler (); \
38   if (handler == nullptr) return -EINVAL;
39
40 /** @brief device class. it contains all related instances */
41 class Device {
42   public:
43     /** @brief Factory method to create a trinity device dependong on dev type */
44     static Device *createInstance (dev_type device_type, int device_id);
45
46     /** @brief constructor of device */
47     Device (dev_type type, int id, bool need_model = true)
48       : comm_(CommPlugin::getCommPlugin()), type_ (type), id_ (id),
49         need_model_ (true), mode_ (NPUASYNC_WAIT), initialized_ (ATOMIC_FLAG_INIT) {}
50
51     /** @brief destructor of device */
52     virtual ~Device () {}
53
54     /** @brief initialization */
55     int init () {
56       if (!initialized_.test_and_set()) {
57         /** create the corresponding driver API */
58         api_ = DriverAPI::createDriverAPI (type_, id_);
59         if (api_.get() == nullptr) {
60           initialized_.clear();
61           logerr (TAG, "Failed to create driver API\n");
62           return -EINVAL;
63         }
64
65         handler_.reset (new HostHandler (this));
66         scheduler_.reset (new Scheduler (api_.get()));
67         mem_ = MemAllocator::createInstance (api_.get());
68       }
69
70       return 0;
71     }
72
73     HostHandler *getHostHandler () { return handler_.get(); }
74     dev_type getType () { return type_; }
75     int getID () { return id_; }
76     bool needModel () { return need_model_; }
77     void setAsyncMode (npu_async_mode mode) { mode_ = mode; }
78
79     HWmem * allocMemory () { return mem_->allocMemory (); }
80     void deallocMemory (int dmabuf_fd) { mem_->deallocMemory (dmabuf_fd); }
81
82     /** it stops all requests in this device (choose wait or force) */
83     int stop (bool force_stop) {
84       Request *req = new Request (NPUINPUT_STOP);
85       req->setForceStop (force_stop);
86       return scheduler_->submitRequest (req);
87     }
88
89     virtual Model * registerModel (const generic_buffer *model) = 0;
90     virtual int run (npu_input_opmode opmode, const Model *model,
91         const input_buffers *input, npuOutputNotify cb, void *cb_data,
92         uint64_t *sequence) = 0;
93
94   protected:
95     /** the device instance has ownership of all related components */
96     std::unique_ptr<DriverAPI>    api_;       /**< device api */
97     std::unique_ptr<MemAllocator> mem_;       /**< memory allocator */
98     std::unique_ptr<HostHandler>  handler_;   /**< host handler */
99     std::unique_ptr<Scheduler>    scheduler_; /**< scheduler */
100
101     CommPlugin& comm_;                        /**< plugin communicator */
102
103     dev_type type_;                           /**< device type */
104     int id_;                                  /**< device id */
105     bool need_model_;                         /**< indicates whether the device needs model */
106     npu_async_mode mode_;                     /**< async run mode */
107
108   private:
109     std::atomic_flag initialized_;
110 };
111
112 /** @brief Trinity Vision (TRIV) classs */
113 class TrinityVision : public Device {
114   public:
115     TrinityVision (int id) : Device (NPUCOND_TRIV_CONN_SOCIP, id) {}
116     ~TrinityVision () {}
117
118     static size_t manipulateData (const Model *model, uint32_t idx, bool is_input,
119         void *dst, void *src, size_t size);
120
121     Model * registerModel (const generic_buffer *model_buf) {
122       Model *model = mem_->allocModel ();
123       if (model == nullptr) {
124         logerr (TAG, "Failed to allocate model\n");
125         return nullptr;
126       }
127
128       int status;
129       if (model_buf->type == BUFFER_DMABUF) {
130         model->setDmabuf (model_buf->dmabuf);
131         model->setOffset (model_buf->offset);
132         model->setSize (model_buf->size);
133       } else {
134         status = model->alloc (model_buf->size);
135         if (status != 0) {
136           logerr (TAG, "Failed to allocate model: %d\n", status);
137           goto delete_exit;
138         }
139
140         status = comm_.extractGenericBuffer (model_buf, model->getData(), nullptr);
141         if (status != 0) {
142           logerr (TAG, "Failed to extract generic buffer: %d\n", status);
143           goto delete_exit;
144         }
145       }
146
147       status = model->setMetadata (model->getData());
148       if (status != 0)
149         goto delete_exit;
150
151       model_config_t config;
152       config.dmabuf_id = model->getDmabuf();
153       config.program_size = model->getMetadata()->getProgramSize();
154       config.program_offset_addr = model->getOffset() + model->getMetadata()->getMetaSize();
155       config.weight_offset_addr = config.program_offset_addr + config.program_size;
156
157       status = api_->setModel (&config);
158       if (status != 0)
159         goto delete_exit;
160
161       return model;
162
163 delete_exit:
164       delete model;
165       return nullptr;
166     }
167
168     Buffer * prepareInputBuffers (const Model *model, const input_buffers *input) {
169       const Metadata *meta = model->getMetadata();
170       const generic_buffer *first = &input->bufs[0];
171
172       if (meta->getInputNum() != input->num_buffers)
173         return nullptr;
174
175       Buffer * buffer = mem_->allocBuffer ();
176       if (buffer != nullptr) {
177         if (first->type == BUFFER_DMABUF) {
178           buffer->setDmabuf (first->dmabuf);
179           buffer->setOffset (first->offset);
180           buffer->setSize (meta->getBufferSize());
181         } else {
182           int status = buffer->alloc (meta->getBufferSize ());
183           if (status != 0) {
184             logerr (TAG, "Failed to allocate buffer: %d\n", status);
185             delete buffer;
186             return nullptr;
187           }
188         }
189       }
190
191       try {
192         buffer->createTensors (meta);
193       } catch (std::bad_alloc& bad) {
194         logerr (TAG, "Failed to allocate buffer: No enough memory\n");
195         delete buffer;
196         buffer = nullptr;
197       } catch (std::exception& exp) {
198         logerr (TAG, "Failed to allocate buffer: %s\n", exp.what());
199         delete buffer;
200         buffer = nullptr;
201       }
202       return buffer;
203     }
204
205     int run (npu_input_opmode opmode, const Model *model,
206         const input_buffers *input, npuOutputNotify cb, void *cb_data,
207         uint64_t *sequence) {
208       if (opmode != NPUINPUT_HOST)
209         return -EINVAL;
210
211       Buffer *buffer = prepareInputBuffers (model, input);
212       if (buffer == nullptr)
213         return -EINVAL;
214
215       for (uint32_t idx = 0; idx < input->num_buffers; idx++) {
216         auto func = std::bind (TrinityVision::manipulateData, model, idx, true,
217             std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
218         int status = comm_.extractGenericBuffer (&input->bufs[idx],
219             buffer->getInputTensor(idx)->getData(), func);
220         if (status != 0) {
221           logerr (TAG, "Failed to feed input buffer: %d\n", status);
222           return status;
223         }
224       }
225
226       /** this device uses CMA buffer */
227
228       Request *req = new Request (opmode);
229       req->setModel (model);
230       req->setBuffer (buffer);
231       req->setCallback (std::bind (&TrinityVision::callback, this, req, cb, cb_data));
232
233       if (sequence)
234         *sequence = req->getID();
235
236       return scheduler_->submitRequest (req);
237     }
238
239     void callback (Request *req, npuOutputNotify cb, void *cb_data) {
240       const Model *model = req->getModel ();
241       Buffer *buffer = req->getBuffer ();
242       output_buffers output = {
243         .num_buffers = buffer->getOutputNum ()
244       };
245
246       for (uint32_t idx = 0; idx < output.num_buffers; idx++) {
247         uint32_t output_tensor_size = model->getOutputTensorSize (idx);
248
249         output.bufs[idx].type = BUFFER_MAPPED;
250         output.bufs[idx].size = output_tensor_size;
251         /** user needs to free this */
252         output.bufs[idx].addr = malloc (output_tensor_size);
253
254         auto func = std::bind (TrinityVision::manipulateData, model, idx, false,
255             std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
256         int status = comm_.insertGenericBuffer (buffer->getOutputTensor(idx)->getData(),
257             &output.bufs[idx], func);
258         if (status != 0) {
259           logerr (TAG, "Failed to return output buffer: %d\n", status);
260         }
261       }
262
263       cb (&output, req->getID(), cb_data);
264     }
265 };
266
267 /** @brief Trinity Vision2 (TRIV2) classs */
268 class TrinityVision2 : public Device {
269   public:
270     TrinityVision2 (int id) : Device (NPUCOND_TRIV2_CONN_SOCIP, id) {}
271     ~TrinityVision2 () {}
272
273     static size_t manipulateData (const Model *model, uint32_t idx, bool is_input,
274         void *dst, void *src, size_t size) {
275       memcpy (dst, src, size);
276       return size;
277     }
278
279     Model * registerModel (const generic_buffer *model_buf) {
280       /** TODO: model's weight values are stored in segments */
281       return nullptr;
282     }
283
284     int run (npu_input_opmode opmode, const Model *model,
285         const input_buffers *input, npuOutputNotify cb, void *cb_data,
286         uint64_t *sequence) {
287       if (opmode != NPUINPUT_HOST && opmode != NPUINPUT_HW_RECURRING)
288         return -EINVAL;
289
290       /** this device uses segment table */
291
292       Request *req = new Request (opmode);
293       req->setModel (model);
294 #if 0
295       req->setSegmentTable (segt);
296 #endif
297       req->setCallback (std::bind (&TrinityVision2::callback, this, req, cb, cb_data));
298
299       if (sequence)
300         *sequence = req->getID();
301
302       return scheduler_->submitRequest (req);
303     }
304
305     void callback (Request *req, npuOutputNotify cb, void *cb_data) {
306     }
307 };
308
309 /** @brief Trinity Asr (TRIA) classs */
310 class TrinityAsr : public Device {
311   public:
312     TrinityAsr (int id) : Device (NPUCOND_TRIA_CONN_SOCIP, id, false) {}
313     ~TrinityAsr () {}
314
315     static size_t manipulateData (const Model *model, uint32_t idx, bool is_input,
316         void *dst, void *src, size_t size) {
317       memcpy (dst, src, size);
318       return size;
319     }
320
321     Model * registerModel (const generic_buffer *model_buf) { return nullptr; }
322
323     int run (npu_input_opmode opmode, const Model *model,
324         const input_buffers *input, npuOutputNotify cb, void *cb_data,
325         uint64_t *sequence) {
326       if (opmode != NPUINPUT_HOST)
327         return -EINVAL;
328
329       /** ASR does not require model and support only a single tensor */
330       const generic_buffer *first_buf = &input->bufs[0];
331       Buffer * buffer = mem_->allocBuffer ();
332       int status;
333       if (first_buf->type == BUFFER_DMABUF) {
334         buffer->setDmabuf (first_buf->dmabuf);
335         buffer->setOffset (first_buf->offset);
336         buffer->setSize (first_buf->size);
337       } else {
338         status = buffer->alloc (first_buf->size);
339         if (status != 0) {
340           delete buffer;
341           return status;
342         }
343       }
344       buffer->createTensors ();
345
346       status = comm_.extractGenericBuffer (first_buf,
347           buffer->getInputTensor(0)->getData(), nullptr);
348       if (status != 0)
349         return status;
350
351       Request *req = new Request (opmode);
352       req->setBuffer (buffer);
353       req->setCallback (std::bind (&TrinityAsr::callback, this, req, cb, cb_data));
354
355       if (sequence)
356         *sequence = req->getID();
357
358       return scheduler_->submitRequest (req);
359     }
360
361     void callback (Request *req, npuOutputNotify cb, void *cb_data) {
362     }
363 };
364
365 #ifdef ENABLE_MANIP
366
367 #define do_quantized_memcpy(type) do {\
368     idx = 0;\
369     if (quant) {\
370       while (idx < num_elems) {\
371           val = ((type *) src)[idx];\
372           val = val / _scale;\
373           val += _zero_point;\
374           val = (val > 255.0) ? 255.0 : 0.0;\
375           ((uint8_t *) dst)[idx++] = (uint8_t) val;\
376       }\
377     } else {\
378       while (idx < num_elems) {\
379           val = *(uint8_t *) src;\
380           val -= _zero_point;\
381           val *= _scale;\
382           ((type *) dst)[idx++] = (type) val;\
383           dst = (void*)(((uint8_t *) dst) + data_size);\
384           src = (void*)(((uint8_t *) src) + 1);\
385       }\
386     }\
387   } while (0)
388
389 /**
390  * @brief memcpy during quantization
391  */
392 static void memcpy_with_quant (bool quant, data_type type, float scale, uint32_t zero_point,
393     void *dst, const void *src, uint32_t num_elems)
394 {
395   double _scale = (double) scale;
396   double _zero_point = (double) zero_point;
397   double val;
398   uint32_t data_size = get_data_size (type);
399   uint32_t idx;
400
401   switch (type) {
402     case DATA_TYPE_INT8:
403       do_quantized_memcpy (int8_t);
404       break;
405     case DATA_TYPE_UINT8:
406       do_quantized_memcpy (uint8_t);
407       break;
408     case DATA_TYPE_INT16:
409       do_quantized_memcpy (int16_t);
410       break;
411     case DATA_TYPE_UINT16:
412       do_quantized_memcpy (uint16_t);
413       break;
414     case DATA_TYPE_INT32:
415       do_quantized_memcpy (int32_t);
416       break;
417     case DATA_TYPE_UINT32:
418       do_quantized_memcpy (uint32_t);
419       break;
420     case DATA_TYPE_INT64:
421       do_quantized_memcpy (int64_t);
422       break;
423     case DATA_TYPE_UINT64:
424       do_quantized_memcpy (uint64_t);
425       break;
426     case DATA_TYPE_FLOAT32:
427       do_quantized_memcpy (float);
428       break;
429     case DATA_TYPE_FLOAT64:
430       do_quantized_memcpy (double);
431       break;
432     default:
433       logerr (TAG, "Unsupported datatype %d\n", type);
434   }
435 }
436
437 /**
438  * @brief perform data manipulation
439  * @param[in] model model instance
440  * @param[in] idx tensor index
441  * @param[in] is_input indicate it's input manipulation
442  * @param[out] dst destination buffer
443  * @param[in] src source buffer (feature map)
444  * @param[in] size size to be copied
445  * @return size of memory copy if no error, otherwise zero
446  *
447  * @note the input data format should be NHWC
448  * @detail rules for the memory address of activations in NPU HW.
449  *         (https://code.sec.samsung.net/confluence/pages/viewpage.action?pageId=146491864)
450  *
451  * 1) Special case (depth == 3)
452  * - addr(x,y,z) = addr(0,0,0) + (z) + 3 * (x + width * y)
453  *
454  * 2) Common case
455  * - addr(x,y,z) = addr(0,0,0) + (z % MPA_L) + MPA_L * (x + width * (y + height * (z / MPA_L)))
456  *
457  * Thus, if depth is not a multiple of MPA_L (i.e., 64), zero padding is required
458  */
459 size_t
460 TrinityVision::manipulateData (const Model *model, uint32_t idx, bool is_input,
461     void *dst, void *src, size_t size)
462 {
463   const Metadata *meta = model->getMetadata();
464   const tensor_data_info* info;
465   const uint32_t *dims;
466   uint32_t zero_point;
467   float scale;
468
469   /** extract required information from the metadata */
470   if (is_input) {
471     if (idx >= meta->getInputNum()) {
472       logerr (TAG, "Wrong information for input tensors in metadata\n");
473       return 0;
474     }
475
476     info = model->getInputDataInfo (idx);
477     dims = meta->getInputDims (idx);
478     zero_point = meta->getInputQuantZero (idx);
479     scale = meta->getInputQuantScale (idx);
480   } else {
481     if (idx >= meta->getOutputNum()) {
482       logerr (TAG, "Wrong information for output tensors in metadata\n");
483       return 0;
484     }
485
486     info = model->getOutputDataInfo (idx);
487     dims = meta->getOutputDims (idx);
488     zero_point = meta->getOutputQuantZero (idx);
489     scale = meta->getOutputQuantScale (idx);
490   }
491
492   if (info == nullptr) {
493     logerr (TAG, "Unmatched tensors info\n");
494     return 0;
495   }
496
497   uint32_t batch = dims[0];
498   uint32_t height = dims[1];
499   uint32_t width = dims[2];
500   uint32_t depth = dims[3];
501
502   uint32_t data_size = get_data_size (info->type);
503   if (data_size == 0) {
504     logerr (TAG, "Invalid data size\n");
505     return 0;
506   }
507
508   bool need_quantization = false;
509   /**
510    * note that we assume DATA_TYPE_SRNPU is the smallest data type that we consider.
511    * Also, DATA_TYPE_SRNPU and uint8_t may be regarded as the same in the view of apps.
512    */
513   if (info->type != DATA_TYPE_SRNPU) {
514     assert (data_size >= get_data_size (DATA_TYPE_SRNPU));
515
516     if (data_size > get_data_size (DATA_TYPE_SRNPU) ||
517         !(zero_point == DEFAULT_ZERO_POINT && scale == DEFAULT_SCALE))
518       need_quantization = true;
519   }
520
521   /** check data manipulation is required */
522   if (depth != 3 && depth != 64 && info->layout != DATA_LAYOUT_SRNPU) {
523     uint32_t MPA_L = DATA_GRANULARITY;
524     uint32_t n, h, w, d;
525     uint32_t std_offset;  /* standard offset in NHWC data format */
526     uint32_t npu_offset;  /* npu offset in NPU HW data format*/
527     uint32_t src_offset;
528     uint32_t dst_offset;
529     uint32_t slice_size;
530
531     /* @todo we currently support only NHWC */
532     if (info->layout != DATA_LAYOUT_NHWC) {
533       logerr (TAG, "data manipulation is supported for NHWC only\n");
534       return -EINVAL;
535     }
536
537     for (n = 0; n < batch; n++) {
538       for (h = 0; h < height; h++) {
539         for (w = 0; w < width; w++) {
540           for (d = 0; d < depth; d += MPA_L) {
541             std_offset = d + depth * (w + width * (h + n * height));
542             npu_offset = MPA_L * (w + width * (h + (n + d / MPA_L) * height));
543             slice_size = (depth - d >= MPA_L) ? MPA_L : depth - d;
544
545             if (is_input) {
546               src_offset = std_offset * data_size;
547               dst_offset = npu_offset;
548             } else {
549               src_offset = npu_offset;
550               dst_offset = std_offset * data_size;
551             }
552
553             /* if depth is not a multiple of MPA_L, add zero paddings (not exact values) */
554             if (need_quantization) {
555               memcpy_with_quant (is_input, info->type, scale, zero_point,
556                   static_cast<char*>(dst) + dst_offset,
557                   static_cast<char*>(src) + src_offset,
558                   slice_size);
559             } else {
560               memcpy (
561                   static_cast<char*>(dst) + dst_offset,
562                   static_cast<char*>(src) + src_offset,
563                   slice_size);
564             }
565           }
566         }
567       }
568     }
569   } else if (need_quantization) {
570     /** depth == 3 || depth == 64; special cases which can directly copy input tensor data */
571     if (is_input)
572       size = size / data_size;
573
574     memcpy_with_quant (is_input, info->type, scale, zero_point,
575         dst, src, size);
576   } else {
577     memcpy (dst, src, size);
578   }
579
580   return 0;
581 }
582
583 #else
584
585 size_t
586 TrinityVision::manipulateData (const Model *model, uint32_t idx, bool is_input,
587     void *dst, void *src, size_t size)
588 {
589   memcpy (dst, src, size);
590   return size;
591 }
592
593 #endif
594
595 /**
596  * @brief create device instance depending on device type and id
597  * @param[in] type device type
598  * @param[in] id device id
599  * @return device instance
600  */
601 Device *
602 Device::createInstance (dev_type type, int id)
603 {
604   Device *device = nullptr;
605
606   switch (type & DEVICETYPE_MASK) {
607     case DEVICETYPE_TRIV:
608       device = new TrinityVision (id);
609       break;
610     case DEVICETYPE_TRIV2:
611       device = new TrinityVision2 (id);
612       break;
613     case DEVICETYPE_TRIA:
614       device = new TrinityAsr (id);
615       break;
616     default:
617       break;
618   }
619
620   if (device != nullptr && device->init () != 0) {
621     delete device;
622     device = nullptr;
623   }
624
625   return device;
626 }
627
628 /** @brief host handler constructor */
629 HostHandler::HostHandler (Device *device)
630   : device_(device),
631     /* ignored as we don't use double buffering anymore, but for backward-compatibility */
632     async_mode_ (NPUASYNC_WAIT)
633 {
634 }
635
636 /** @brief host handler destructor */
637 HostHandler::~HostHandler ()
638 {
639 }
640
641 /**
642  * @brief register model from generic buffer
643  * @param[in] model_buf model buffer
644  * @param[out] modelid model id
645  * @return 0 if no error. otherwise a negative errno
646  */
647 int
648 HostHandler::registerModel (generic_buffer *model_buf, uint32_t *modelid)
649 {
650   Model *model = device_->registerModel (model_buf);
651   if (model == nullptr) {
652     logerr (TAG, "Failed to register model\n");
653     return -EINVAL;
654   }
655
656   int status = models_.insert (model->getID(), model);
657   if (status != 0) {
658     logerr (TAG, "Failed to insert model id\n");
659     delete model;
660     return status;
661   }
662
663   *modelid = model->getID();
664   return 0;
665 }
666
667 /**
668  * @brief remove the registered model
669  * @param[in] modelid model id
670  * @return 0 if no error. otherwise a negative errno
671  */
672 int
673 HostHandler::unregisterModel (uint32_t modelid)
674 {
675   return models_.remove (modelid);
676 }
677
678 /**
679  * @brief remove all registered models
680  * @return 0
681  */
682 int
683 HostHandler::unregisterModels ()
684 {
685   models_.clear ();
686   return 0;
687 }
688
689 /**
690  * @brief Set the data layout for input/output tensors
691  * @param[in] modelid The ID of model whose layouts are set
692  * @param[in] in the layout/type info for input tensors
693  * @param[in] out the layout/type info for output tensors
694  * @return @c 0 if no error. otherwise a negative error value
695  * @note if this function is not called, default layout/type will be used.
696  */
697 int
698 HostHandler::setDataInfo (uint32_t modelid, tensors_data_info *in,
699     tensors_data_info *out)
700 {
701   Model *model = models_.find (modelid);
702   if (model == nullptr)
703     return -ENOENT;
704
705   return model->setDataInfo (in, out);
706 }
707
708 /**
709  * @brief Set the inference constraint for next NPU inferences
710  * @param[in] modelid The target model id
711  * @param[in] constraint inference constraint (e.g., timeout, priority)
712  * @return @c 0 if no error. otherwise a negative error value
713  * @note If this function is not called, default values are used.
714  */
715 int
716 HostHandler::setConstraint (uint32_t modelid, npuConstraint constraint)
717 {
718   Model *model = models_.find (modelid);
719   if (model == nullptr)
720     return -ENOENT;
721
722   model->setConstraint (constraint);
723
724   return 0;
725 }
726
727 /**
728  * @brief find and return model instance
729  * @param[in] modelid model id
730  * @return model instance if found. otherwise nullptr
731  */
732 Model *
733 HostHandler::getModel (uint32_t modelid)
734 {
735   return models_.find (modelid);
736 }
737
738 /** @brief dummay callback for runSync. */
739 class callbackSync {
740   public:
741     callbackSync (output_buffers *output) : output_(output), done_(false) {}
742
743     static void callback (output_buffers *output, uint64_t sequence, void *data) {
744       callbackSync *sync = static_cast<callbackSync *>(data);
745       sync->callback (output, sequence);
746     }
747
748     void callback (output_buffers *output, uint64_t sequence) {
749       /** just copy internal variables of output buffers */
750       memcpy (output_, output, sizeof (output_buffers));
751       done_ = true;
752       cv_.notify_one ();
753     }
754
755     void wait () {
756       std::unique_lock<std::mutex> lock (m_);
757       cv_.wait (lock, [this]() { return done_; });
758     }
759
760   private:
761     std::mutex m_;
762     std::condition_variable cv_;
763     output_buffers *output_;
764     bool done_;
765 };
766
767 /**
768  * @brief Execute inference. Wait (block) until the output is available.
769  * @param[in] modelid The model to be inferred.
770  * @param[in] input The input data to be inferred.
771  * @param[out] output The output result.
772  * @return @c 0 if no error. otherwise a negative error value
773  */
774 int
775 HostHandler::runSync (uint32_t modelid, const input_buffers *input,
776     output_buffers *output)
777 {
778   callbackSync sync (output);
779   int status = runAsync (modelid, input, callbackSync::callback,
780       static_cast <void*> (&sync), NPUASYNC_DROP_OLD, nullptr);
781   if (status == 0) {
782     /** sync needs to wait callback */
783     sync.wait ();
784   }
785   return status;
786 }
787
788 /**
789  * @brief Invoke NPU inference. Unblocking call.
790  * @param[in] modelid The model to be inferred.
791  * @param[in] input The input data to be inferred.
792  * @param[in] cb The output buffer handler.
793  * @param[in] cb_data The data given as a parameter to the runNPU_async call.
794  * @param[in] mode Configures how this operation works.
795  * @param[out] sequence The sequence number returned with runNPU_async.
796  * @return @c 0 if no error. otherwise a negative error value
797  */
798 int
799 HostHandler::runAsync (uint32_t modelid, const input_buffers *input,
800     npuOutputNotify cb, void *cb_data, npu_async_mode mode, uint64_t *sequence)
801 {
802   Model *model = nullptr;
803
804   if (device_->needModel()) {
805     model = getModel (modelid);
806     if (model == nullptr)
807       return -ENOENT;
808   }
809
810   device_->setAsyncMode (mode);
811   return device_->run (NPUINPUT_HOST, model, input, cb, cb_data, sequence);
812 }
813
814 /**
815  * @brief get number of available devices
816  * @param[in] type device type
817  * @return number of devices
818  */
819 int
820 HostHandler::getNumDevices (dev_type type)
821 {
822   return DriverAPI::getNumDevices (type);
823 }
824
825 /**
826  * @brief get device instance
827  * @param[out] dev device instance
828  * @param[in] type device type
829  * @param[in] id device id
830  * @return 0 if no error. otherwise a negative errno
831  */
832 int
833 HostHandler::getDevice (npudev_h *dev, dev_type type, uint32_t id)
834 {
835   int num_devices = getNumDevices (type);
836
837   /** check the validity of device id */
838   if (!(num_devices > 0 && id < static_cast<uint32_t>(num_devices))) {
839     logerr (TAG, "Invalid arguments provided\n");
840     return -ENODEV;
841   }
842
843   Device *device = Device::createInstance (type, id);
844   if (device == nullptr) {
845     logerr (TAG, "Failed to create a device with the given type\n");
846     return -EINVAL;
847   }
848
849   *dev = device;
850   /** This is just for backward-compatility; we don't guarantee its corresness */
851   latest_dev_ = *dev;
852
853   return 0;
854 }
855
856 /**
857  * @brief allocate generic buffer (just for users)
858  * @param[out] buffer buffer instance
859  * @return 0 if no error. otherwise a negative errno
860  */
861 int
862 HostHandler::allocGenericBuffer (generic_buffer *buffer)
863 {
864   if (buffer == NULL)
865     return -EINVAL;
866
867   if (buffer->size > UINT32_MAX) {
868     logerr (TAG, "Don't support such a large size");
869     return -ENOMEM;
870   }
871
872   if (buffer->type == BUFFER_FILE) {
873     /* nothing to do */
874     if (buffer->filepath == nullptr)
875       return -EINVAL;
876   } else {
877     /* now, npu-engine always provides dmabuf-based allocation */
878     HWmem *hwmem = device_->allocMemory ();
879     if (hwmem == nullptr || hwmem->alloc (buffer->size) < 0)
880       return -ENOMEM;
881
882     buffer->dmabuf = hwmem->getDmabuf();
883     buffer->offset = hwmem->getOffset();
884     buffer->addr = hwmem->getData();
885   }
886   return 0;
887 }
888
889 /**
890  * @brief deallocate generic buffer (just for users)
891  * @param[in] buffer buffer instance
892  * @return 0 if no error. otherwise a negative errno
893  */
894 int
895 HostHandler::deallocGenericBuffer (generic_buffer *buffer)
896 {
897   if (buffer == NULL)
898     return -EINVAL;
899
900   if (buffer->type != BUFFER_FILE)
901     device_->deallocMemory (buffer->dmabuf);
902
903   return 0;
904 }
905
906 /**
907  * @brief allocate multiple generic buffers (just for users)
908  * @param[out] buffers multi-buffer instance
909  * @return 0 if no error. otherwise a negative errno
910  */
911 int
912 HostHandler::allocGenericBuffer (generic_buffers *buffers)
913 {
914   if (buffers == NULL || buffers->num_buffers < 1)
915     return -EINVAL;
916
917   buffer_types type = buffers->bufs[0].type;
918   if (type == BUFFER_FILE)
919     return 0;
920
921   uint64_t total_size = 0;
922   for (uint32_t idx = 0; idx < buffers->num_buffers; idx++)
923     total_size += buffers->bufs[idx].size;
924
925   uint64_t first_size = buffers->bufs[0].size;
926   buffers->bufs[0].size = total_size;
927   int status = allocGenericBuffer (&buffers->bufs[0]);
928   if (status != 0)
929     return status;
930
931   uint64_t offset = first_size;
932   for (uint32_t idx = 1; idx < buffers->num_buffers; idx++) {
933     buffers->bufs[idx].dmabuf = buffers->bufs[0].dmabuf;
934     buffers->bufs[idx].offset = buffers->bufs[0].offset + offset;
935     buffers->bufs[idx].type = type;
936
937     offset += buffers->bufs[idx].size;
938   }
939
940   return 0;
941 }
942
943 /**
944  * @brief deallocate multiple generic buffers (just for users)
945  * @param[in] buffers multi-buffer instance
946  * @return 0 if no error. otherwise a negative errno
947  */
948 int
949 HostHandler::deallocGenericBuffer (generic_buffers *buffers)
950 {
951   if (buffers == NULL || buffers->num_buffers < 1)
952     return -EINVAL;
953
954   return deallocGenericBuffer (&buffers->bufs[0]);
955 }
956
957 /** just for backward-compatability */
958 npudev_h HostHandler::latest_dev_ = nullptr;
959
960 /** implementation of libnpuhost APIs */
961
962 /**
963  * @brief Returns the number of available NPU devices.
964  * @return @c The number of NPU devices.
965  * @retval 0 if no NPU devices available. if positive (number of NPUs) if NPU devices available. otherwise, a negative error value.
966  * @note the caller should call putNPUdevice() to release the device handle
967  */
968 int getnumNPUdeviceByType (dev_type type)
969 {
970   return HostHandler::getNumDevices (type);
971 }
972
973 /**
974  * @brief Returns the handle of the chosen NPU devices.
975  * @param[out] dev The NPU device handle
976  * @param[in] id The NPU id to get the handle. 0 <= id < getnumNPUdeviceByType().
977  * @return @c 0 if no error. otherwise a negative error value
978  * @note the caller should call putNPUdevice() to release the device handle
979  */
980 int getNPUdeviceByType (npudev_h *dev, dev_type type, uint32_t id)
981 {
982   return HostHandler::getDevice (dev, type, id);
983 }
984
985 /**
986  * @brief Returns the handle of an NPU device meeting the condition
987  * @param[out] dev The NPU device handle
988  * @param[in] cond The condition for device search.
989  * @return @c 0 if no error. otherwise a negative error value
990  * @note the caller should call putNPUdevice() to release the device handle
991  * @note it's not supported yet
992  */
993 int getNPUdeviceByCondition(npudev_h *dev, const npucondition *cond)
994 {
995   /** not implmeneted yet */
996   return getNPUdeviceByType (dev, NPUCOND_TRIV_CONN_SOCIP, 0);
997 }
998
999 /**
1000  * @brief release the NPU device instance obtained by getDevice ()
1001  * @param[in] dev the NPU device handle
1002  */
1003 void putNPUdevice (npudev_h dev)
1004 {
1005   if (dev != nullptr)
1006     delete static_cast<Device *> (dev);
1007 }
1008
1009 /**
1010  * @brief Send the NN model to NPU.
1011  * @param[in] dev The NPU device handle
1012  * @param[in] modelfile The filepath to the compiled NPU NN model in any buffer_type
1013  * @param[out] modelid The modelid allocated for this instance of NN model.
1014  * @return @c 0 if no error. otherwise a negative error value
1015  *
1016  * @detail For ASR devices, which do not accept models, but have models
1017  *         embedded in devices, you do not need to call register and
1018  *         register calls for ASR are ignored.
1019  *
1020  * @todo Add a variation: in-memory model register.
1021  */
1022 int registerNPUmodel (npudev_h dev, generic_buffer *modelfile, uint32_t *modelid)
1023 {
1024   INIT_HOST_HANDLER (host_handler, dev);
1025
1026   return host_handler->registerModel (modelfile, modelid);
1027 }
1028
1029 /**
1030  * @brief Remove the NN model from NPU
1031  * @param[in] dev The NPU device handle
1032  * @param[in] modelid The model to be removed from the NPU.
1033  * @return @c 0 if no error. otherwise a negative error value
1034  * @detail This may incur some latency with memory compatcion.
1035  */
1036 int unregisterNPUmodel(npudev_h dev, uint32_t modelid)
1037 {
1038   INIT_HOST_HANDLER (host_handler, dev);
1039
1040   return host_handler->unregisterModel (modelid);
1041 }
1042
1043 /**
1044  * @brief Remove all NN models from NPU
1045  * @param[in] dev The NPU device handle
1046  * @return @c 0 if no error. otherwise a negative error value
1047  */
1048 int unregisterNPUmodel_all(npudev_h dev)
1049 {
1050   INIT_HOST_HANDLER (host_handler, dev);
1051
1052   return host_handler->unregisterModels ();
1053 }
1054
1055 /**
1056  * @brief [OPTIONAL] Set the data layout for input/output tensors
1057  * @param[in] dev The NPU device handle
1058  * @param[in] modelid The ID of model whose layouts are set
1059  * @param[in] info_in the layout/type info for input tensors
1060  * @param[in] info_out the layout/type info for output tensors
1061  * @return @c 0 if no error. otherwise a negative error value
1062  * @note if this function is not called, default layout/type will be used.
1063  */
1064 int setNPU_dataInfo(npudev_h dev, uint32_t modelid,
1065     tensors_data_info *info_in, tensors_data_info *info_out)
1066 {
1067   INIT_HOST_HANDLER (host_handler, dev);
1068
1069   return host_handler->setDataInfo (modelid, info_in, info_out);
1070 }
1071
1072 /**
1073  * @brief [OPTIONAL] Set the inference constraint for next NPU inferences
1074  * @param[in] dev The NPU device handle
1075  * @param[in] modelid The target model id
1076  * @param[in] constraint inference constraint (e.g., timeout, priority)
1077  * @return @c 0 if no error. otherwise a negative error value
1078  * @note If this function is not called, default values are used.
1079  */
1080 int setNPU_constraint(npudev_h dev, uint32_t modelid, npuConstraint constraint)
1081 {
1082   INIT_HOST_HANDLER (host_handler, dev);
1083
1084   return host_handler->setConstraint (modelid, constraint);
1085 }
1086
1087 /**
1088  * @brief Execute inference. Wait (block) until the output is available.
1089  * @param[in] dev The NPU device handle
1090  * @param[in] modelid The model to be inferred.
1091  * @param[in] input The input data to be inferred.
1092  * @param[out] output The output result. The caller MUST allocate appropriately before calling this.
1093  * @return @c 0 if no error. otherwise a negative error value
1094  *
1095  * @detail This is a syntactic sugar of runNPU_async().
1096  *         CAUTION: There is a memcpy for the output buffer.
1097  */
1098 int runNPU_sync(npudev_h dev, uint32_t modelid, const input_buffers *input,
1099     output_buffers *output)
1100 {
1101   INIT_HOST_HANDLER (host_handler, dev);
1102
1103   return host_handler->runSync (modelid, input, output);
1104 }
1105
1106 /**
1107  * @brief Invoke NPU inference. Unblocking call.
1108  * @param[in] dev The NPU device handle
1109  * @param[in] modelid The model to be inferred.
1110  * @param[in] input The input data to be inferred.
1111  * @param[in] cb The output buffer handler.
1112  * @param[out] sequence The sequence number returned with runNPU_async.
1113  * @param[in] data The data given as a parameter to the runNPU_async call.
1114  * @param[in] mode Configures how this operation works.
1115  * @return @c 0 if no error. otherwise a negative error value
1116  */
1117 int runNPU_async(npudev_h dev, uint32_t modelid, const input_buffers *input,
1118     npuOutputNotify cb, uint64_t *sequence, void *data,
1119     npu_async_mode mode)
1120 {
1121   INIT_HOST_HANDLER (host_handler, dev);
1122
1123   return host_handler->runAsync (modelid, input, cb, data, mode, sequence);
1124 }
1125
1126 /**
1127  * @brief Allocate a buffer for NPU model with the requested buffer type.
1128  * @param[in] dev The NPU device handle
1129  * @param[in/out] Buffer the buffer pointer where memory is allocated.
1130  * @return 0 if no error, otherwise a negative errno.
1131  */
1132 int allocNPU_modelBuffer (npudev_h dev, generic_buffer *buffer)
1133 {
1134   INIT_HOST_HANDLER (host_handler, dev);
1135
1136   return host_handler->allocGenericBuffer (buffer);
1137 }
1138
1139 /**
1140  * @brief Free the buffer and remove the address mapping.
1141  * @param[in] dev The NPU device handle
1142  * @param[in] buffer the model buffer
1143  * @return 0 if no error, otherwise a negative errno.
1144  */
1145 int cleanNPU_modelBuffer (npudev_h dev, generic_buffer *buffer)
1146 {
1147   INIT_HOST_HANDLER (host_handler, dev);
1148
1149   return host_handler->deallocGenericBuffer (buffer);
1150 }
1151
1152 /**
1153  * @brief Allocate a buffer for NPU input with the requested buffer type.
1154  * @param[in] dev The NPU device handle
1155  * @param[in/out] Buffer the buffer pointer where memory is allocated.
1156  * @return 0 if no error, otherwise a negative errno.
1157  * @note please utilize allocInputBuffers() for multiple input tensors because subsequent
1158  *       calls of allocInputBuffer() don't gurantee contiguous allocations between them.
1159  */
1160 int allocNPU_inputBuffer (npudev_h dev, generic_buffer *buffer)
1161 {
1162   INIT_HOST_HANDLER (host_handler, dev);
1163
1164   return host_handler->allocGenericBuffer (buffer);
1165 }
1166
1167 /**
1168  * @brief Free the buffer and remove the address mapping.
1169  * @param[in] dev The NPU device handle
1170  * @param[in] buffer the input buffer
1171  * @return 0 if no error, otherwise a negative errno.
1172  */
1173 int cleanNPU_inputBuffer (npudev_h dev, generic_buffer *buffer)
1174 {
1175   INIT_HOST_HANDLER (host_handler, dev);
1176
1177   return host_handler->deallocGenericBuffer (buffer);
1178 }
1179
1180 /**
1181  * @brief Allocate input buffers, which have multiple instances of generic_buffer
1182  * @param[in] dev The NPU device handle
1183  * @param[in/out] input input buffers.
1184  * @return 0 if no error, otherwise a negative errno.
1185  * @note it reuses allocInputBuffer().
1186  * @details in case of BUFFER_DMABUF, this function can be used to gurantee physically-contiguous
1187  *          memory mapping for multiple tensors (in a single inference, not batch size).
1188  */
1189 int allocNPU_inputBuffers (npudev_h dev, input_buffers * input)
1190 {
1191   INIT_HOST_HANDLER (host_handler, dev);
1192
1193   return host_handler->allocGenericBuffer (input);
1194 }
1195
1196 /**
1197  * @brief Free input buffers allocated by allocInputBuffers().
1198  * @param[in] dev The NPU device handle
1199  * @param[in/out] input input buffers.
1200  * @note it reuses cleanInputbuffer().
1201  * @return 0 if no error, otherwise a negative errno.
1202  */
1203 int cleanNPU_inputBuffers (npudev_h dev, input_buffers * input)
1204 {
1205   INIT_HOST_HANDLER (host_handler, dev);
1206
1207   return host_handler->deallocGenericBuffer (input);
1208 }
1209
1210 /**
1211  * @brief Get metadata for NPU model
1212  * @param[in] model The path of model binary file
1213  * @param[in] need_extra whether you want to extract the extra data in metadata
1214  * @return the metadata structure to be filled if no error, otherwise nullptr
1215  *
1216  * @note For most npu-engine users, the extra data is not useful because it will be
1217  *       used for second-party users (e.g., compiler, simulator).
1218  *       Also, the caller needs to free the metadata.
1219  *
1220  * @note the caller needs to free the metadata
1221  */
1222 npubin_meta * getNPUmodel_metadata (const char *model, bool need_extra)
1223 {
1224   npubin_meta *meta;
1225   FILE *fp;
1226   size_t ret;
1227
1228   if (!model)
1229     return nullptr;
1230
1231   fp = fopen (model, "rb");
1232   if (!fp) {
1233     logerr (TAG, "Failed to open the model binary: %d\n", -errno);
1234     return nullptr;
1235   }
1236
1237   meta = (npubin_meta *) malloc (NPUBIN_META_SIZE);
1238   if (!meta) {
1239     logerr (TAG, "Failed to allocate metadata\n");
1240     goto exit_err;
1241   }
1242
1243   ret = fread (meta, 1, NPUBIN_META_SIZE, fp);
1244   if (ret != NPUBIN_META_SIZE) {
1245     logerr (TAG, "Failed to read the metadata\n");
1246     goto exit_free;
1247   }
1248
1249   if (!CHECK_NPUBIN (meta->magiccode)) {
1250     logerr (TAG, "Invalid metadata provided\n");
1251     goto exit_free;
1252   }
1253
1254   if (need_extra && NPUBIN_META_EXTRA (meta->magiccode) > 0) {
1255     npubin_meta *new_meta;
1256
1257     new_meta = (npubin_meta *) realloc (meta, NPUBIN_META_TOTAL_SIZE(meta->magiccode));
1258     if (!new_meta) {
1259       logerr (TAG, "Failed to allocate extra metadata\n");
1260       goto exit_free;
1261     }
1262
1263     ret = fread (new_meta->reserved_extra, 1, NPUBIN_META_EXTRA_SIZE (meta->magiccode), fp);
1264     if (ret != NPUBIN_META_EXTRA_SIZE (meta->magiccode)) {
1265       logerr (TAG, "Invalid extra metadata provided\n");
1266       free (new_meta);
1267       goto exit_err;
1268     }
1269
1270     meta = new_meta;
1271   }
1272
1273   fclose (fp);
1274
1275   return meta;
1276
1277 exit_free:
1278   free (meta);
1279 exit_err:
1280   fclose (fp);
1281
1282   return nullptr;
1283 }
1284
1285 /** deprecated buffer APIs; please use the above APIs */
1286
1287 /**
1288  * @brief Returns the number of NPU devices (TRIV).
1289  */
1290 int getnumNPUdevice (void)
1291 {
1292   logwarn (TAG, "deprecated. Please use getnumNPUdeviceByType ()\n");
1293   return getnumNPUdeviceByType (NPUCOND_TRIV_CONN_SOCIP);
1294 }
1295
1296 /**
1297  * @brief Returns the list of ASR devices (TRIA)
1298  */
1299 int getnumASRdevice (void)
1300 {
1301   logwarn (TAG, "deprecated. Please use getnumNPUdeviceByType ()\n");
1302   return getnumNPUdeviceByType (NPUCOND_TRIA_CONN_SOCIP);
1303 }
1304
1305 /**
1306  * @brief Returns the handle of the chosen TRIV device.
1307  */
1308 int getNPUdevice (npudev_h *dev, uint32_t id)
1309 {
1310   logwarn (TAG, "deprecated. Please use getNPUdeviceByType ()\n");
1311   return getNPUdeviceByType (dev, NPUCOND_TRIV_CONN_SOCIP, id);
1312 }
1313
1314 /**
1315  * @brief Returns the handle of the chosen TRIA device.
1316  */
1317 int getASRdevice (npudev_h *dev, uint32_t id)
1318 {
1319   logwarn (TAG, "deprecated. Please use getNPUdeviceByType ()\n");
1320   return getNPUdeviceByType (dev, NPUCOND_TRIA_CONN_SOCIP, id);
1321 }
1322
1323 /** @brief deprecated */
1324 int allocModelBuffer (generic_buffer *buffer)
1325 {
1326   logwarn (TAG, "deprecated. Please use allocNPU_modelBuffer\n");
1327   return allocNPU_modelBuffer (HostHandler::getLatestDevice(), buffer);
1328 }
1329
1330 /** @brief deprecated */
1331 int cleanModelBuffer (generic_buffer *buffer)
1332 {
1333   logwarn (TAG, "deprecated. Please use cleanNPU_modelBuffer\n");
1334   return allocNPU_modelBuffer (HostHandler::getLatestDevice(), buffer);
1335 }
1336
1337 /** @brief deprecated */
1338 int allocInputBuffer (generic_buffer *buffer)
1339 {
1340   logwarn (TAG, "deprecated. Please use allocNPU_inputBuffer\n");
1341   return allocNPU_inputBuffer (HostHandler::getLatestDevice(), buffer);
1342 }
1343
1344 /** @brief deprecated */
1345 int cleanInputBuffer (generic_buffer *buffer)
1346 {
1347   logwarn (TAG, "deprecated. Please use cleanNPU_inputBuffer\n");
1348   return cleanNPU_inputBuffer (HostHandler::getLatestDevice(), buffer);
1349 }
1350
1351 /** @brief deprecated */
1352 int allocInputBuffers (input_buffers * input)
1353 {
1354   logwarn (TAG, "deprecated. Please use allocNPU_inputBuffers\n");
1355   return allocNPU_inputBuffers (HostHandler::getLatestDevice(), input);
1356 }
1357
1358 /** @brief deprecated */
1359 int cleanInputBuffers (input_buffers * input)
1360 {
1361   logwarn (TAG, "deprecated. Please use cleanNPU_inputBuffers\n");
1362   return cleanNPU_inputBuffers (HostHandler::getLatestDevice(), input);
1363 }
1364
1365 /** @brief deprecated */
1366 int allocNPUBuffer (uint64_t size, buffer_types type,
1367     const char * filepath, generic_buffer *buffer)
1368 {
1369   if (buffer) {
1370     buffer->size = size;
1371     buffer->type = type;
1372     buffer->filepath = filepath;
1373   }
1374
1375   logwarn (TAG, "deprecated. Please use allocNPU_* APIs\n");
1376   return allocModelBuffer (buffer);
1377 }
1378
1379 /** @brief deprecated */
1380 int cleanNPUBuffer (generic_buffer * buffer)
1381 {
1382   logwarn (TAG, "deprecated. Please use cleanNPU_* APIs\n");
1383   return cleanModelBuffer (buffer);
1384 }