24b378e241b4d6b7ac5e881aa35969bbaf6c22d5
[tidl/tidl-api.git] / tidl_api / src / subgraph_runtime.cpp
1 /******************************************************************************
2  * Copyright (c) 2019 Texas Instruments Incorporated - http://www.ti.com/
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *      * Redistributions of source code must retain the above copyright
8  *        notice, this list of conditions and the following disclaimer.
9  *      * Redistributions in binary form must reproduce the above copyright
10  *        notice, this list of conditions and the following disclaimer in the
11  *        documentation and/or other materials provided with the distribution.
12  *      * Neither the name of Texas Instruments Incorporated nor the
13  *        names of its contributors may be used to endorse or promote products
14  *        derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
26  *  THE POSSIBILITY OF SUCH DAMAGE.
27  *****************************************************************************/
29 #include <pthread.h>
30 #define LOKI_PTHREAD_H
31 #include <loki/Singleton.h>
33 #include "util.h"
34 #include "subgraph_runtime.h"
35 #include "subgraph_runtime_impl.h"
38 #if 0
39 // Auto-generated code from Relay/TVM compilation step after
40 // partitioning and lowering to backend implementation
42 void TVM_TidlFunction(int total_subgraphs, int subgraph_id,
43                      int num_input_tensors, int num_output_tensors,
44                      PackedArgs args)
45 {
46   float** in_data  = new float*[num_inputs_per_inference * batch_size];
47   float** out_data = new float*[num_outputs_per_inference * batch_size];
49   for (in j = 0; j < batch_size; j++)
50   {
51     for (int i = 0; i < num_inputs_per_inference + num_outputs_per_inference;
52          i++)
53       if (i < num_inputs_per_inference)
54         in_data[j * num_inputs_per_inference + i] = args.data[i][j];
55       else
56         out_data[j * num_outpus_per_inference + i - num_inputs_per_inference]
57                                                   = args.data[i][j];
58   }
60   // call into this function in libtidl.so
61   // dlopen("libtidl_api.so")
62   // TidlFunc = dlsym("TidlRunSubgraph");
63   (*TidlFunc)(total_subgraphs, subgraph_id, batch_size
64               num_inputs_per_inference, num_outputs_per_inference,
65               in_data, out_data);
67   delete [] in_data;
68   delete [] out_data;
69 }
70 #endif
73 // Singleton ResM .cpp
74 using namespace tidl;
76 int TidlGetPreferredBatchSize(int total_subgraphs)
77 {
78   ResM& res = ResM::Instance(total_subgraphs);
79   return res.GetNumEs();
80 }
82 void TidlInitSubgraph(int total_subgraphs, int subgraph_id)
83 {
84   ResM& res = ResM::Instance(total_subgraphs);
85   res.InitSubgraph(subgraph_id);
86 }
89 void TidlRunSubgraph(int total_subgraphs,
90                      int subgraph_id,
91                      int batch_size,
92                      int num_inputs_per_inference,
93                      int num_outputs_per_inference,
94                      float **input_tensors,
95                      float **output_tensors
96                     )
97 {
98   ResM& res = ResM::Instance(total_subgraphs);
99   res.InitSubgraph(subgraph_id);
100   int num_eops = res.GetNumEOPs(subgraph_id);
101   if (num_eops > batch_size)  num_eops = batch_size;
102   std::vector<ExecutionObjectPipeline*> eops(num_eops);
103   for (int i = 0; i < num_eops; i++)
104     eops[i] = res.GetEOP(subgraph_id);
105   const SubgraphDataConv& in_conv  = res.GetInConv(subgraph_id);
106   const SubgraphDataConv& out_conv = res.GetOutConv(subgraph_id);
108   std::vector<std::vector<float *>> in_data_v(batch_size),
109                                     out_data_v(batch_size);
110   for (int frame_idx = 0; frame_idx < batch_size; frame_idx++)
111   {
112     for (int i = 0; i < num_inputs_per_inference; i++)
113       in_data_v[frame_idx].emplace_back(input_tensors[
114                                     frame_idx * num_inputs_per_inference + i]);
115     for (int i = 0; i < num_outputs_per_inference; i++)
116       out_data_v[frame_idx].emplace_back(output_tensors[
117                                     frame_idx * num_inputs_per_inference + i]);
118   }
120   // Process batch_size frames with available eops in pipelined manner
121   // additional num_eops iterations to flush the pipeline (epilogue)
122   for (int frame_idx = 0; frame_idx < batch_size + num_eops; frame_idx++)
123   {
124     ExecutionObjectPipeline *eop = eops[frame_idx % num_eops];
126     if (eop->ProcessFrameWait())
127     {
128       const uint8_t *out_data = (const uint8_t*) eop->GetOutputBufferPtr();
129       out_conv.ScaleDequant(out_data, out_data_v[frame_idx - num_eops]);
130     }
132     if (frame_idx < batch_size)
133     {
134        uint8_t *in_data = (uint8_t *) eop->GetInputBufferPtr();
135       in_conv.ScaleQuant(in_data_v[frame_idx], in_data);
136       eop->ProcessFrameStartAsync();
137     }
138   }
140   for (int i = 0; i < num_eops; i++)
141       res.FreeEOP(subgraph_id, eops[i]);
145 typedef Loki::SingletonHolder <tidl::ResM, Loki::CreateUsingNew,
146 Loki::DefaultLifetime, Loki::ClassLevelLockable> tidlSingleResM;
148 ResM::ResM() : enable_trace_m(false), num_subgraphs_m(0),
149                num_lg2_dsps_used_m(0), eops_m(nullptr)
153 ResM::~ResM()
155   if (eops_m != nullptr)
156   {
157     for (const ResEOP& res_eop : *eops_m)
158     {
159       if (res_eop.eops != nullptr)
160       {
161         for (const ExecutionObjectPipeline* eop : *(res_eop.eops))
162         {
163           free(eop->GetInputBufferPtr());
164           free(eop->GetOutputBufferPtr());
165           delete eop;
166         }
167       }
168     }
169     delete eops_m;
170     eops_m = nullptr;
171   }
173   for (const Executor* e : es_m)
174     if (e != nullptr) delete e;
175   for (const Executor* e : e2s_m)
176     if (e != nullptr) delete e;
177   for (SubgraphDataConv *dc : in_conv_m)
178     if (dc != nullptr) delete dc;
179   for (SubgraphDataConv *dc : out_conv_m)
180     if (dc != nullptr) delete dc;
183 ResM& ResM::Instance(uint32_t total_num_subgraphs)
185   ResM& res = tidlSingleResM::Instance();
186   res.Init(total_num_subgraphs);
187   return res;
190 void ResM::Init(uint32_t num_subgraphs)
192   std::lock_guard<std::mutex> lock(mutex_init_m);
194   if (num_subgraphs_m == 0)
195   {
196     num_subgraphs_m = num_subgraphs;
198     if (getenv("TIDL_SUBGRAPH_TRACE") != nullptr)  enable_trace_m = true;
200     // Allocating resources
201     num_eves_m = Executor::GetNumDevices(DeviceType::EVE);
202     num_dsps_m = Executor::GetNumDevices(DeviceType::DSP);
204     assert(num_eves_m > 0 || num_dsps_m > 0);
205     assert(num_subgraphs_m <= num_eves_m || num_subgraphs_m <= num_dsps_m);
206     num_es_per_subgraph_m = num_eves_m / num_subgraphs_m;
207     if (num_eves_m == 0)
208       num_es_per_subgraph_m = num_dsps_m / num_subgraphs_m;
210     cs_m.resize(num_subgraphs_m);
211     es_m.resize(num_subgraphs_m, nullptr);
212     e2s_m.resize(num_subgraphs_m, nullptr);
213     eops_m = new std::vector<ResEOP>(num_subgraphs_m);
214     in_conv_m.resize(num_subgraphs_m, nullptr);
215     out_conv_m.resize(num_subgraphs_m, nullptr);
216   }
220 void ResM::InitSubgraph(uint32_t subgraph_id)
222   assert(subgraph_id < num_subgraphs_m);
223   ResEOP& res_eop = (*eops_m)[subgraph_id];
225   std::unique_lock<std::mutex> lock(res_eop.mutex_eops);
227   // Constructing EOPs if not already constructed
228   if (res_eop.eops == nullptr)
229   {
230     if (enable_trace_m)
231       printf("Subgraph %d: initialing E/EOPs with %d cores\n",
232              subgraph_id, num_es_per_subgraph_m);
234     // Read config file
235     std::string cfg_file = "subgraph" + std::to_string(subgraph_id) + ".cfg";
236     bool status = cs_m[subgraph_id].ReadFromFile(cfg_file);
237     assert(status);
239     // Read the network
240     sTIDL_Network_t *net = new sTIDL_Network_t;
241     status = ReadNetworkBinary(cs_m[subgraph_id].netBinFile,
242                                reinterpret_cast<char *>(net));
243     assert(status);
245     // Get data conversion info from configuration
246     // Get input/output tensors dimensions from network
247     // Construct data converters at the subgraph boundaries
248     std::vector<int> inDims, outDims;
249     for (int32_t layer = 0; layer < net->numLayers; layer++)
250     {
251       if (net->TIDLLayers[layer].layerType != (int32_t) TIDL_DataLayer)
252         continue;
253       if (net->TIDLLayers[layer].numInBufs <= 0)
254       {
255         for (int d = 0; d < 4; d++)
256           inDims.push_back(net->TIDLLayers[layer].outData[0].dimValues[d]);
257       }
258       if (net->TIDLLayers[layer].numOutBufs <= 0)
259       {
260         for (int d = 0; d < 4; d++)
261           outDims.push_back(net->TIDLLayers[layer].inData[0].dimValues[d]);
262       }
263     }
264     assert(cs_m[subgraph_id].inIsNCHW.size() * 4 == inDims.size());
265     assert(cs_m[subgraph_id].outIsNCHW.size() * 4 == outDims.size());
266     std::vector<bool> inIsSigned, outIsSigned, inIsNCHW, outIsNCHW;
267     for (int v : cs_m[subgraph_id].inIsSigned)  inIsSigned.push_back(v != 0);
268     for (int v : cs_m[subgraph_id].inIsNCHW)    inIsNCHW.push_back(v != 0);
269     for (int v : cs_m[subgraph_id].outIsSigned) outIsSigned.push_back(v != 0);
270     for (int v : cs_m[subgraph_id].outIsNCHW)   outIsNCHW.push_back(v != 0);
271     in_conv_m[subgraph_id] = new SubgraphDataConv(
272                                                cs_m[subgraph_id].inConvType,
273                                                inIsSigned,
274                                                cs_m[subgraph_id].inScaleF2Q,
275                                                inIsNCHW,
276                                                inDims);
277     out_conv_m[subgraph_id] = new SubgraphDataConv(
278                                                cs_m[subgraph_id].outConvType,
279                                                outIsSigned,
280                                                cs_m[subgraph_id].outScaleF2Q,
281                                                outIsNCHW,
282                                                outDims);
284     // Check if last few layers can be offloaded to DSPs
285     //       and DSPs are available
286     DeviceIds e_ids, e2_ids;
287     for (uint32_t i = 0; i < num_es_per_subgraph_m; i++)
288       e_ids.insert(static_cast<DeviceId>(
289                                subgraph_id * num_es_per_subgraph_m + i));
290     // uint32_t num_dsps_used = 0;
291     if (num_eves_m > 0 && num_dsps_m > 0 && ! cs_m[subgraph_id].runFullNet)
292     {
293       int32_t start_layer = net->numLayers -1;
294       int32_t end_layer = 0;
295       if (net->TIDLLayers[start_layer].layerType == (int32_t) TIDL_DataLayer)
296         start_layer -= 1;
297       if (net->TIDLLayers[end_layer].layerType == (int32_t) TIDL_DataLayer)
298         end_layer += 1;
299       int32_t i = start_layer;
300       for ( ; i > end_layer; i--)
301       {
302         int32_t layer_type = net->TIDLLayers[i].layerType;
303         if (layer_type != (int32_t) TIDL_SoftMaxLayer &&
304             layer_type != (int32_t) TIDL_InnerProductLayer &&
305             layer_type != (int32_t) TIDL_PoolingLayer)
306           break;
307       }
308       i += 1;
309       if (i <= start_layer)
310       {
311         if (num_lg2_dsps_used_m < num_dsps_m)
312         {
313           if (enable_trace_m)
314             printf("Subgraph %d: assign layers %d to %d to group 2 for DSP\n",
315                    subgraph_id, i, start_layer);
316           while (i <= start_layer)
317             cs_m[subgraph_id].layerIndex2LayerGroupId[i++] = 2;
318           e2_ids.insert(static_cast<DeviceId>(num_lg2_dsps_used_m));
319           num_lg2_dsps_used_m += 1;
320           if (num_subgraphs_m == 1)  // Allocate all dsps if only one subgraph
321           {
322             while (num_lg2_dsps_used_m < num_dsps_m)
323               e2_ids.insert(static_cast<DeviceId>(num_lg2_dsps_used_m++));
324           }
325         }
326       }
327       delete net;
328     }
330     if (e2_ids.empty())
331       cs_m[subgraph_id].runFullNet = true;
332     cs_m[subgraph_id].enableApiTrace = enable_trace_m;
334     // Constructing Es and EOPs, each subgraph -> num_eves_per_subgraph_m EOPs
335     res_eop.eops = new std::vector<ExecutionObjectPipeline*>;
336     uint32_t buffer_factor = 2;  // double buffering factor
337     if (num_eves_m > 0)
338     {
339       es_m[subgraph_id]  = new Executor(DeviceType::EVE, e_ids,
340                                         cs_m[subgraph_id], 1);
341       if (! e2_ids.empty())
342       {
343         e2s_m[subgraph_id] = new Executor(DeviceType::DSP, e2_ids,
344                                           cs_m[subgraph_id], 2);
345         for (uint32_t j = 0; j < buffer_factor; j++)
346           for (uint32_t i = 0; i < num_es_per_subgraph_m; i++)
347             res_eop.eops->emplace_back(new ExecutionObjectPipeline(
348                                   {(*es_m[subgraph_id])[i],
349                                    (*e2s_m[subgraph_id])[i % e2_ids.size()]}));
350       }
351       else
352       {
353         for (uint32_t j = 0; j < buffer_factor; j++)
354           for (uint32_t i = 0; i < num_es_per_subgraph_m; i++)
355             res_eop.eops->emplace_back(new ExecutionObjectPipeline(
356                                                    {(*es_m[subgraph_id])[i]}));
357       }
358     }
359     else
360     {
361       es_m[subgraph_id]  = new Executor(DeviceType::DSP, e_ids,
362                                         cs_m[subgraph_id], 1);
363       for (uint32_t j = 0; j < buffer_factor; j++)
364         for (uint32_t i = 0; i < num_es_per_subgraph_m; i++)
365           res_eop.eops->emplace_back(new ExecutionObjectPipeline(
366                                                    {(*es_m[subgraph_id])[i]}));
367     }
369     if (enable_trace_m)
370       printf("Subgraph %d: Allocating input/output buffers for %d EOPs\n",
371              subgraph_id, res_eop.eops->size());
372     // Allocate input/output buffers
373     for (auto eop : *(res_eop.eops))
374     {
375       size_t in_size  = eop->GetInputBufferSizeInBytes();
376       size_t out_size = eop->GetOutputBufferSizeInBytes();
377       void*  in_ptr   = malloc(in_size);
378       void*  out_ptr  = malloc(out_size);
379       assert(in_ptr != nullptr && out_ptr != nullptr);
381       ArgInfo in(in_ptr, in_size);
382       ArgInfo out(out_ptr, out_size);
383       eop->SetInputOutputBuffer(in, out);
384     }
386     res_eop.free_eop_index = 0;
387     res_eop.is_used.resize(res_eop.eops->size(), false);
388   }
391 uint32_t ResM::GetNumEOPs(uint32_t subgraph_id)
393   assert(subgraph_id < num_subgraphs_m);
394   ResEOP& res_eop = (*eops_m)[subgraph_id];
395   assert (res_eop.eops != nullptr);
397   return res_eop.eops->size();
400 ExecutionObjectPipeline* ResM::GetEOP(uint32_t subgraph_id)
402   assert(subgraph_id < num_subgraphs_m);
403   ResEOP& res_eop = (*eops_m)[subgraph_id];
404   assert(res_eop.eops != nullptr);
406   std::unique_lock<std::mutex> lock(res_eop.mutex_eops);
408   // Return an available EOP (round robin allocation)
409   uint32_t curr_eop = res_eop.free_eop_index;
410   res_eop.cv_eops.wait(lock, [this, subgraph_id, curr_eop]{
411            return this->eops_m->at(subgraph_id).is_used[curr_eop] == false; });
412   res_eop.is_used[curr_eop] = true;
413   res_eop.free_eop_index = (curr_eop + 1) % res_eop.eops->size();
414   if (enable_trace_m)
415     printf("Subgraph %d: return EOP %d for GetEOP()\n", subgraph_id, curr_eop);
416   return res_eop.eops->at(curr_eop);
419 void ResM::FreeEOP(uint32_t subgraph_id, ExecutionObjectPipeline* eop)
421   assert(subgraph_id < num_subgraphs_m);
422   ResEOP& res_eop = (*eops_m)[subgraph_id];
423   assert(res_eop.eops != nullptr);
425   {
426     std::unique_lock<std::mutex> lock(res_eop.mutex_eops);
427     for (uint32_t i = 0; i < res_eop.is_used.size(); i++)
428       if (res_eop.eops->at(i) == eop)
429       {
430         res_eop.is_used[i] = false;
431         if (enable_trace_m)
432           printf("Subgraph %d: FreeEOP %d\n", subgraph_id, i);
433         break;
434       }
435   }
436   res_eop.cv_eops.notify_all();
439 Configuration& ResM::GetConfiguration(uint32_t subgraph_id)
441   assert(subgraph_id < num_subgraphs_m);
442   assert((*eops_m)[subgraph_id].eops != nullptr);
443   return cs_m[subgraph_id];
446 const SubgraphDataConv& ResM::GetInConv(uint32_t subgraph_id)
448   assert(subgraph_id < num_subgraphs_m);
449   assert(in_conv_m[subgraph_id] != nullptr);
450   return *in_conv_m[subgraph_id];
453 const SubgraphDataConv& ResM::GetOutConv(uint32_t subgraph_id)
455   assert(subgraph_id < num_subgraphs_m);
456   assert(out_conv_m[subgraph_id] != nullptr);
457   return *out_conv_m[subgraph_id];