Add TIDL_SUBGRAPH_NUM_EVES env var
[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 }
88 void TidlFreeSubgraph(int total_subgraphs, int subgraph_id)
89 {
90   ResM& res = ResM::Instance(total_subgraphs);
91   res.FreeSubgraph(subgraph_id);
92 }
94 void TidlRunSubgraph(int total_subgraphs,
95                      int subgraph_id,
96                      int batch_size,
97                      int num_inputs_per_inference,
98                      int num_outputs_per_inference,
99                      float **input_tensors,
100                      float **output_tensors
101                     )
103   ResM& res = ResM::Instance(total_subgraphs);
104   res.InitSubgraph(subgraph_id);
105   int num_eops = res.GetNumEOPs(subgraph_id);
106   if (num_eops > batch_size)  num_eops = batch_size;
107   std::vector<ExecutionObjectPipeline*> eops(num_eops);
108   for (int i = 0; i < num_eops; i++)
109     eops[i] = res.GetEOP(subgraph_id);
110   const SubgraphDataConv& in_conv  = res.GetInConv(subgraph_id);
111   const SubgraphDataConv& out_conv = res.GetOutConv(subgraph_id);
113   std::vector<std::vector<float *>> in_data_v(batch_size),
114                                     out_data_v(batch_size);
115   for (int frame_idx = 0; frame_idx < batch_size; frame_idx++)
116   {
117     for (int i = 0; i < num_inputs_per_inference; i++)
118       in_data_v[frame_idx].emplace_back(input_tensors[
119                                     frame_idx * num_inputs_per_inference + i]);
120     for (int i = 0; i < num_outputs_per_inference; i++)
121       out_data_v[frame_idx].emplace_back(output_tensors[
122                                     frame_idx * num_inputs_per_inference + i]);
123   }
125   // Process batch_size frames with available eops in pipelined manner
126   // additional num_eops iterations to flush the pipeline (epilogue)
127   for (int frame_idx = 0; frame_idx < batch_size + num_eops; frame_idx++)
128   {
129     ExecutionObjectPipeline *eop = eops[frame_idx % num_eops];
131     if (eop->ProcessFrameWait())
132     {
133       const uint8_t *out_data = (const uint8_t*) eop->GetOutputBufferPtr();
134       out_conv.ScaleDequant(out_data, out_data_v[frame_idx - num_eops]);
135     }
137     if (frame_idx < batch_size)
138     {
139        uint8_t *in_data = (uint8_t *) eop->GetInputBufferPtr();
140       in_conv.ScaleQuant(in_data_v[frame_idx], in_data);
141       eop->ProcessFrameStartAsync();
142     }
143   }
145   for (int i = 0; i < num_eops; i++)
146       res.FreeEOP(subgraph_id, eops[i]);
150 typedef Loki::SingletonHolder <tidl::ResM, Loki::CreateUsingNew,
151 Loki::DefaultLifetime, Loki::ClassLevelLockable> tidlSingleResM;
153 ResM::ResM() : enable_trace_m(false), num_subgraphs_m(0),
154                num_lg2_dsps_used_m(0), eops_m(nullptr)
158 ResM::~ResM()
160   for (uint32_t i = 0; i < num_subgraphs_m; i++)
161     FreeSubgraph(i);
163   delete eops_m;
164   eops_m = nullptr;
167 void ResM::FreeSubgraph(uint32_t subgraph_id)
169   assert(subgraph_id < num_subgraphs_m);
171   if (eops_m != nullptr)
172   {
173     ResEOP& res_eop = (*eops_m)[subgraph_id];
174     if (res_eop.eops != nullptr)
175     {
176       for (const ExecutionObjectPipeline* eop : *(res_eop.eops))
177       {
178         free(eop->GetInputBufferPtr());
179         free(eop->GetOutputBufferPtr());
180         delete eop;
181       }
182       delete res_eop.eops;
183       res_eop.eops = nullptr;
184     }
185   }
187   delete es_m[subgraph_id];
188   es_m[subgraph_id] = nullptr;
190   delete e2s_m[subgraph_id];
191   e2s_m[subgraph_id] = nullptr;
193   delete in_conv_m[subgraph_id];
194   in_conv_m[subgraph_id] = nullptr;
196   delete out_conv_m[subgraph_id];
197   out_conv_m[subgraph_id] = nullptr;
200 ResM& ResM::Instance(uint32_t total_num_subgraphs)
202   ResM& res = tidlSingleResM::Instance();
203   res.Init(total_num_subgraphs);
204   return res;
207 void ResM::Init(uint32_t num_subgraphs)
209   std::lock_guard<std::mutex> lock(mutex_init_m);
211   if (num_subgraphs_m == 0)
212   {
213     num_subgraphs_m = num_subgraphs;
215     if (getenv("TIDL_SUBGRAPH_TRACE") != nullptr)  enable_trace_m = true;
217     // Allocating resources
218     num_eves_m = Executor::GetNumDevices(DeviceType::EVE);
219     num_dsps_m = Executor::GetNumDevices(DeviceType::DSP);
221     char *env_subgraph_num_eves = getenv("TIDL_SUBGRAPH_NUM_EVES");
222     if (env_subgraph_num_eves != nullptr)
223     {
224         uint32_t subgraph_num_eves = atoi(env_subgraph_num_eves);
225         if (subgraph_num_eves > 0 && subgraph_num_eves < num_eves_m)
226             num_eves_m = subgraph_num_eves;
227         if (subgraph_num_eves > 0 && subgraph_num_eves < num_dsps_m)
228             num_dsps_m = subgraph_num_eves;
229     }
231     assert(num_eves_m > 0 || num_dsps_m > 0);
232     assert(num_subgraphs_m <= num_eves_m || num_subgraphs_m <= num_dsps_m);
233     num_es_per_subgraph_m = num_eves_m / num_subgraphs_m;
234     if (num_eves_m == 0)
235       num_es_per_subgraph_m = num_dsps_m / num_subgraphs_m;
237     cs_m.resize(num_subgraphs_m);
238     es_m.resize(num_subgraphs_m, nullptr);
239     e2s_m.resize(num_subgraphs_m, nullptr);
240     eops_m = new std::vector<ResEOP>(num_subgraphs_m);
241     in_conv_m.resize(num_subgraphs_m, nullptr);
242     out_conv_m.resize(num_subgraphs_m, nullptr);
243   }
247 void ResM::InitSubgraph(uint32_t subgraph_id)
249   assert(subgraph_id < num_subgraphs_m);
250   ResEOP& res_eop = (*eops_m)[subgraph_id];
252   std::unique_lock<std::mutex> lock(res_eop.mutex_eops);
254   // Constructing EOPs if not already constructed
255   if (res_eop.eops == nullptr)
256   {
257     if (enable_trace_m)
258       printf("Subgraph %d: initialing E/EOPs with %d cores\n",
259              subgraph_id, num_es_per_subgraph_m);
261     // Read config file
262     std::string cfg_file = "subgraph" + std::to_string(subgraph_id) + ".cfg";
263     char *subgraph_dir = getenv("TIDL_SUBGRAPH_DIR");
264     if (subgraph_dir != nullptr)
265       cfg_file = std::string(subgraph_dir) + "/" + cfg_file;
266     cs_m[subgraph_id].isSubgraphCfg = true;
267     bool status = cs_m[subgraph_id].ReadFromFile(cfg_file);
268     assert(status);
270     // Read the network
271     sTIDL_Network_t *net = new sTIDL_Network_t;
272     status = ReadNetworkBinary(cs_m[subgraph_id].netBinFile,
273                                reinterpret_cast<char *>(net));
274     assert(status);
276     // Get data conversion info from configuration
277     // Get input/output tensors dimensions from network
278     // Construct data converters at the subgraph boundaries
279     std::vector<int> inDims, outDims;
280     for (int32_t layer = 0; layer < net->numLayers; layer++)
281     {
282       if (net->TIDLLayers[layer].layerType != (int32_t) TIDL_DataLayer)
283         continue;
284       if (net->TIDLLayers[layer].numInBufs <= 0)
285       {
286         for (int d = 0; d < 4; d++)
287           inDims.push_back(net->TIDLLayers[layer].outData[0].dimValues[d]);
288       }
289       if (net->TIDLLayers[layer].numOutBufs <= 0)
290       {
291         for (int d = 0; d < 4; d++)
292           outDims.push_back(net->TIDLLayers[layer].inData[0].dimValues[d]);
293       }
294     }
295     assert(cs_m[subgraph_id].inIsNCHW.size() * 4 == inDims.size());
296     assert(cs_m[subgraph_id].outIsNCHW.size() * 4 == outDims.size());
297     std::vector<bool> inIsSigned, outIsSigned, inIsNCHW, outIsNCHW;
298     for (int v : cs_m[subgraph_id].inIsSigned)  inIsSigned.push_back(v != 0);
299     for (int v : cs_m[subgraph_id].inIsNCHW)    inIsNCHW.push_back(v != 0);
300     for (int v : cs_m[subgraph_id].outIsSigned) outIsSigned.push_back(v != 0);
301     for (int v : cs_m[subgraph_id].outIsNCHW)   outIsNCHW.push_back(v != 0);
302     in_conv_m[subgraph_id] = new SubgraphDataConv(
303                                                cs_m[subgraph_id].inConvType,
304                                                inIsSigned,
305                                                cs_m[subgraph_id].inScaleF2Q,
306                                                inIsNCHW,
307                                                inDims);
308     out_conv_m[subgraph_id] = new SubgraphDataConv(
309                                                cs_m[subgraph_id].outConvType,
310                                                outIsSigned,
311                                                cs_m[subgraph_id].outScaleF2Q,
312                                                outIsNCHW,
313                                                outDims);
315     // Check if last few layers can be offloaded to DSPs
316     //       and DSPs are available
317     DeviceIds e_ids, e2_ids;
318     for (uint32_t i = 0; i < num_es_per_subgraph_m; i++)
319       e_ids.insert(static_cast<DeviceId>(
320                                subgraph_id * num_es_per_subgraph_m + i));
321     // uint32_t num_dsps_used = 0;
322     if (num_eves_m > 0 && num_dsps_m > 0 && ! cs_m[subgraph_id].runFullNet)
323     {
324       if (cs_m[subgraph_id].layerIndex2LayerGroupId.empty())
325       {
326         int32_t start_layer = net->numLayers -1;
327         int32_t end_layer = 0;
328         if (net->TIDLLayers[start_layer].layerType == (int32_t) TIDL_DataLayer)
329           start_layer -= 1;
330         if (net->TIDLLayers[end_layer].layerType == (int32_t) TIDL_DataLayer)
331           end_layer += 1;
332         int32_t i = start_layer;
333         for ( ; i > end_layer; i--)
334         {
335           int32_t layer_type = net->TIDLLayers[i].layerType;
336           if (layer_type != (int32_t) TIDL_SoftMaxLayer &&
337               layer_type != (int32_t) TIDL_InnerProductLayer &&
338               layer_type != (int32_t) TIDL_PoolingLayer)
339             break;
340         }
341         i += 1;
342         if (i <= start_layer)
343         {
344           if (num_lg2_dsps_used_m < num_dsps_m)
345           {
346             if (enable_trace_m)
347               printf("Subgraph %d: assign layers %d to %d to group 2 for DSP\n",
348                      subgraph_id, i, start_layer);
349             while (i <= start_layer)
350               cs_m[subgraph_id].layerIndex2LayerGroupId[i++] = 2;
351           }
352         }
353       }
354       else
355       {
356         if (enable_trace_m)
357           printf("Subgraph %d: using layer2group map in config file for DSP\n",
358                  subgraph_id);
359       }
361       if (! cs_m[subgraph_id].layerIndex2LayerGroupId.empty())
362       {
363         e2_ids.insert(static_cast<DeviceId>(num_lg2_dsps_used_m));
364         num_lg2_dsps_used_m += 1;
365         if (num_subgraphs_m == 1)  // Allocate all dsps if only one subgraph
366         {
367           while (num_lg2_dsps_used_m < num_dsps_m)
368             e2_ids.insert(static_cast<DeviceId>(num_lg2_dsps_used_m++));
369         }
370       }
371     }
372     delete net;
374     if (e2_ids.empty())
375       cs_m[subgraph_id].runFullNet = true;
376     cs_m[subgraph_id].enableApiTrace = enable_trace_m;
378     // Constructing Es and EOPs, each subgraph -> num_eves_per_subgraph_m EOPs
379     res_eop.eops = new std::vector<ExecutionObjectPipeline*>;
380     uint32_t buffer_factor = 2;  // double buffering factor
381     if (num_eves_m > 0)
382     {
383       es_m[subgraph_id]  = new Executor(DeviceType::EVE, e_ids,
384                                         cs_m[subgraph_id], 1);
385       if (! e2_ids.empty())
386       {
387         e2s_m[subgraph_id] = new Executor(DeviceType::DSP, e2_ids,
388                                           cs_m[subgraph_id], 2);
389         for (uint32_t j = 0; j < buffer_factor; j++)
390           for (uint32_t i = 0; i < num_es_per_subgraph_m; i++)
391             res_eop.eops->emplace_back(new ExecutionObjectPipeline(
392                                   {(*es_m[subgraph_id])[i],
393                                    (*e2s_m[subgraph_id])[i % e2_ids.size()]}));
394       }
395       else
396       {
397         for (uint32_t j = 0; j < buffer_factor; j++)
398           for (uint32_t i = 0; i < num_es_per_subgraph_m; i++)
399             res_eop.eops->emplace_back(new ExecutionObjectPipeline(
400                                                    {(*es_m[subgraph_id])[i]}));
401       }
402     }
403     else
404     {
405       es_m[subgraph_id]  = new Executor(DeviceType::DSP, e_ids,
406                                         cs_m[subgraph_id], 1);
407       for (uint32_t j = 0; j < buffer_factor; j++)
408         for (uint32_t i = 0; i < num_es_per_subgraph_m; i++)
409           res_eop.eops->emplace_back(new ExecutionObjectPipeline(
410                                                    {(*es_m[subgraph_id])[i]}));
411     }
413     if (enable_trace_m)
414       printf("Subgraph %d: Allocating input/output buffers for %d EOPs\n",
415              subgraph_id, res_eop.eops->size());
416     // Allocate input/output buffers
417     for (auto eop : *(res_eop.eops))
418     {
419       size_t in_size  = eop->GetInputBufferSizeInBytes();
420       size_t out_size = eop->GetOutputBufferSizeInBytes();
421       void*  in_ptr   = malloc(in_size);
422       void*  out_ptr  = malloc(out_size);
423       assert(in_ptr != nullptr && out_ptr != nullptr);
425       ArgInfo in(in_ptr, in_size);
426       ArgInfo out(out_ptr, out_size);
427       eop->SetInputOutputBuffer(in, out);
428     }
430     res_eop.free_eop_index = 0;
431     res_eop.is_used.resize(res_eop.eops->size(), false);
432   }
435 uint32_t ResM::GetNumEOPs(uint32_t subgraph_id)
437   assert(subgraph_id < num_subgraphs_m);
438   ResEOP& res_eop = (*eops_m)[subgraph_id];
439   assert (res_eop.eops != nullptr);
441   return res_eop.eops->size();
444 ExecutionObjectPipeline* ResM::GetEOP(uint32_t subgraph_id)
446   assert(subgraph_id < num_subgraphs_m);
447   ResEOP& res_eop = (*eops_m)[subgraph_id];
448   assert(res_eop.eops != nullptr);
450   std::unique_lock<std::mutex> lock(res_eop.mutex_eops);
452   // Return an available EOP (round robin allocation)
453   uint32_t curr_eop = res_eop.free_eop_index;
454   res_eop.cv_eops.wait(lock, [this, subgraph_id, curr_eop]{
455            return this->eops_m->at(subgraph_id).is_used[curr_eop] == false; });
456   res_eop.is_used[curr_eop] = true;
457   res_eop.free_eop_index = (curr_eop + 1) % res_eop.eops->size();
458   if (enable_trace_m)
459     printf("Subgraph %d: return EOP %d for GetEOP()\n", subgraph_id, curr_eop);
460   return res_eop.eops->at(curr_eop);
463 void ResM::FreeEOP(uint32_t subgraph_id, ExecutionObjectPipeline* eop)
465   assert(subgraph_id < num_subgraphs_m);
466   ResEOP& res_eop = (*eops_m)[subgraph_id];
467   assert(res_eop.eops != nullptr);
469   {
470     std::unique_lock<std::mutex> lock(res_eop.mutex_eops);
471     for (uint32_t i = 0; i < res_eop.is_used.size(); i++)
472       if (res_eop.eops->at(i) == eop)
473       {
474         res_eop.is_used[i] = false;
475         if (enable_trace_m)
476           printf("Subgraph %d: FreeEOP %d\n", subgraph_id, i);
477         break;
478       }
479   }
480   res_eop.cv_eops.notify_all();
483 Configuration& ResM::GetConfiguration(uint32_t subgraph_id)
485   assert(subgraph_id < num_subgraphs_m);
486   assert((*eops_m)[subgraph_id].eops != nullptr);
487   return cs_m[subgraph_id];
490 const SubgraphDataConv& ResM::GetInConv(uint32_t subgraph_id)
492   assert(subgraph_id < num_subgraphs_m);
493   assert(in_conv_m[subgraph_id] != nullptr);
494   return *in_conv_m[subgraph_id];
497 const SubgraphDataConv& ResM::GetOutConv(uint32_t subgraph_id)
499   assert(subgraph_id < num_subgraphs_m);
500   assert(out_conv_m[subgraph_id] != nullptr);
501   return *out_conv_m[subgraph_id];