mcbench: Multicore benchmark with minimal overhead
[tidl/tidl-api.git] / examples / mcbench / main.cpp
1 /******************************************************************************
2  * Copyright (c) 2018, 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  *****************************************************************************/
28 #include <signal.h>
29 #include <iostream>
30 #include <iomanip>
31 #include <fstream>
32 #include <cassert>
33 #include <string>
34 #include <functional>
35 #include <algorithm>
36 #include <time.h>
37 #include <unistd.h>
39 #include <queue>
40 #include <vector>
41 #include <cstdio>
42 #include <chrono>
44 #include "executor.h"
45 #include "execution_object.h"
46 #include "execution_object_pipeline.h"
47 #include "configuration.h"
48 #include "../segmentation/object_classes.h"
49 #include "../common/utils.h"
50 #include "../common/video_utils.h"
52 using namespace std;
53 using namespace tidl;
54 using namespace cv;
57 #define NUM_VIDEO_FRAMES  100
58 #define DEFAULT_CONFIG    "../test/testvecs/config/mcbench/tidl_config_j11_v2.txt"
60 bool RunConfiguration(const cmdline_opts_t& opts);
61 Executor* CreateExecutor(DeviceType dt, uint32_t num, const Configuration& c,
62                          int layers_group_id);
63 bool CreateExecutionObjectPipelines(uint32_t num_eves, uint32_t num_dsps,
64                                     Configuration& configuration, 
65                                     uint32_t num_layers_groups,
66                                     Executor*& e_eve, Executor*& e_dsp,
67                                   std::vector<ExecutionObjectPipeline*>& eops);
69 void AllocateMemory(const std::vector<ExecutionObjectPipeline*>& eops);
71 bool ReadFrame(ExecutionObjectPipeline& eop, uint32_t frame_idx,
72                const Configuration& c, const cmdline_opts_t& opts, char *input_frames_buffer);
73 static void DisplayHelp();
76 int main(int argc, char *argv[])
77 {
78     // Catch ctrl-c to ensure a clean exit
79     signal(SIGABRT, exit);
80     signal(SIGTERM, exit);
82     // If there are no devices capable of offloading TIDL on the SoC, exit
83     uint32_t num_eves = Executor::GetNumDevices(DeviceType::EVE);
84     uint32_t num_dsps = Executor::GetNumDevices(DeviceType::DSP);
85     if ((num_eves == 0) || (num_dsps == 0))
86     {
87         cout << "mcbench requires EVE and/or DSP for execution." << endl;
88         return EXIT_SUCCESS;
89     }
91     cout << "CMDLINE: ";
92     for(int i = 0; i < argc; ++i) cout << argv[i] << " ";
93     cout << endl;
95     // Process arguments
96     cmdline_opts_t opts;
97     opts.config = DEFAULT_CONFIG;
98     opts.num_eves = 0;
99     opts.num_dsps = 2;
100     if (! ProcessArgs(argc, argv, opts))
101     {
102         DisplayHelp();
103         exit(EXIT_SUCCESS);
104     }
105     assert((opts.num_dsps + opts.num_eves) != 0);
107     if (opts.num_frames == 0)
108         opts.num_frames = NUM_VIDEO_FRAMES;
110     // Run network
111     bool status = RunConfiguration(opts);
113     if (!status)
114     {
115         cout << "mcbench FAILED" << endl;
116         return EXIT_FAILURE;
117     }
119     cout << "mcbench PASSED" << endl;
120     return EXIT_SUCCESS;
123 bool RunConfiguration(const cmdline_opts_t& opts)
125     // Read the TI DL configuration file
126     Configuration c;
127     std::string config_file = opts.config;
128     std::string inputFile;
130     bool status = c.ReadFromFile(config_file);
131     if (!status)
132     {
133         cerr << "Error in configuration file: " << config_file << endl;
134         return false;
135     }
136     c.enableApiTrace = opts.verbose;
138     if (opts.input_file.empty())
139         inputFile   = c.inData;
140     else
141         inputFile = opts.input_file;
143     int channel_size = c.inWidth * c.inHeight;
144     int frame_size = c.inNumChannels * channel_size;
146     c.numFrames = GetBinaryFileSize (inputFile) / frame_size;
148     cout << "Input: " << inputFile << " frames:" << c.numFrames << endl;
150     // Read input file into memory buffer
151     char *input_frame_buffer = new char[c.numFrames * frame_size]();
152     ifstream ifs(inputFile, ios::binary);
153     ifs.read(input_frame_buffer, channel_size * 3);
154     if(!ifs.good()) {
155        std::cout << "Invalid File input:" << inputFile << std::endl;
156        return false;    
157     }
159     try
160     {
161         // Construct ExecutionObjectPipeline that utilizes multiple
162         // ExecutionObjects to process a single frame, each ExecutionObject
163         // processes one layerGroup of the network
164         //
165         // Pipeline depth can enable more optimized pipeline execution:
166         // Given one EVE and one DSP as an example, with different
167         //     pipeline_depth, we have different execution behavior:
168         // If pipeline_depth is set to 1,
169         //    we create one EOP: eop0 (eve0, dsp0)
170         //    pipeline execution of multiple frames over time is as follows:
171         //    --------------------- time ------------------->
172         //    eop0: [eve0...][dsp0]
173         //    eop0:                [eve0...][dsp0]
174         //    eop0:                               [eve0...][dsp0]
175         //    eop0:                                              [eve0...][dsp0]
176         // If pipeline_depth is set to 2,
177         //    we create two EOPs: eop0 (eve0, dsp0), eop1(eve0, dsp0)
178         //    pipeline execution of multiple frames over time is as follows:
179         //    --------------------- time ------------------->
180         //    eop0: [eve0...][dsp0]
181         //    eop1:          [eve0...][dsp0]
182         //    eop0:                   [eve0...][dsp0]
183         //    eop1:                            [eve0...][dsp0]
184         // Additional benefit of setting pipeline_depth to 2 is that
185         //    it can also overlap host ReadFrame() with device processing:
186         //    --------------------- time ------------------->
187         //    eop0: [RF][eve0...][dsp0]
188         //    eop1:     [RF]     [eve0...][dsp0]
189         //    eop0:                    [RF][eve0...][dsp0]
190         //    eop1:                             [RF][eve0...][dsp0]
191         Executor *e_eve = NULL;
192         Executor *e_dsp = NULL;
193         std::vector<ExecutionObjectPipeline *> eops;
194         if (! CreateExecutionObjectPipelines(opts.num_eves, opts.num_dsps, c,
195                                         opts.num_layers_groups, e_eve, e_dsp, eops))
196             return false;
197         uint32_t num_eops = eops.size();
198         // Allocate input/output memory for each EOP
199         AllocateMemory(eops);
201         chrono::time_point<chrono::steady_clock> tloop0, tloop1;
202         tloop0 = chrono::steady_clock::now();
204         // Process frames with available eops in a pipelined manner
205         // additional num_eops iterations to flush pipeline (epilogue)
206         for (uint32_t frame_idx = 0;
207              frame_idx < opts.num_frames + num_eops; frame_idx++)
208         {
209             ExecutionObjectPipeline* eop = eops[frame_idx % num_eops];
211             // Wait for previous frame on the same eop to finish processing
212             if (eop->ProcessFrameWait())
213             {
214                 if(opts.verbose)
215                   ReportTime(eop);
216             }
218             // Read a frame and start processing it with current eo
219             if (ReadFrame(*eop, frame_idx, c, opts, input_frame_buffer))
220                 eop->ProcessFrameStartAsync();
221         }
223         tloop1 = chrono::steady_clock::now();
224         chrono::duration<float> elapsed = tloop1 - tloop0;
225         cout << "Loop total time: "
226                   << setw(6) << setprecision(4)
227                   << (elapsed.count() * 1000) << "ms" << endl;
228         cout << "FPS:" << opts.num_frames / elapsed.count() << endl;
230         FreeMemory(eops);
231         for (auto eop : eops)  delete eop;
232         delete e_eve;
233         delete e_dsp;
234     }
235     catch (tidl::Exception &e)
236     {
237         cerr << e.what() << endl;
238         status = false;
239     }
240     delete [] input_frame_buffer;
241     return status;
244 // Create an Executor with the specified type and number of EOs
245 Executor* CreateExecutor(DeviceType dt, uint32_t num, const Configuration& c,
246                          int layers_group_id)
248     if (num == 0) return nullptr;
250     DeviceIds ids;
251     for (uint32_t i = 0; i < num; i++)
252         ids.insert(static_cast<DeviceId>(i));
254     return new Executor(dt, ids, c, layers_group_id);
257 bool CreateExecutionObjectPipelines(uint32_t num_eves, uint32_t num_dsps,
258                                     Configuration& configuration, 
259                                     uint32_t num_layers_groups,
260                                     Executor*& e_eve, Executor*& e_dsp,
261                                     std::vector<ExecutionObjectPipeline*>& eops)
263     DeviceIds ids_eve, ids_dsp;
264     for (uint32_t i = 0; i < num_eves; i++)
265         ids_eve.insert(static_cast<DeviceId>(i));
266     for (uint32_t i = 0; i < num_dsps; i++)
267         ids_dsp.insert(static_cast<DeviceId>(i));
268     const uint32_t buffer_factor = 2;
270     switch(num_layers_groups)
271     {
272     case 1: // Single layers group
273         e_eve = num_eves == 0 ? nullptr :
274                 new Executor(DeviceType::EVE, ids_eve, configuration);
275         e_dsp = num_dsps == 0 ? nullptr :
276                 new Executor(DeviceType::DSP, ids_dsp, configuration);
278         // Construct ExecutionObjectPipeline with single Execution Object to
279         // process each frame. This is parallel processing of frames with
280         // as many DSP and EVE cores that we have on hand.
281         // If buffer_factor == 2, duplicating EOPs for double buffering
282         // and overlapping host pre/post-processing with device processing
283         for (uint32_t j = 0; j < buffer_factor; j++)
284         {
285             for (uint32_t i = 0; i < num_eves; i++)
286                 eops.push_back(new ExecutionObjectPipeline({(*e_eve)[i]}));
287             for (uint32_t i = 0; i < num_dsps; i++)
288                 eops.push_back(new ExecutionObjectPipeline({(*e_dsp)[i]}));
289         }
290         break;
292     case 2: // Two layers group
293         // Create Executors with the approriate core type, number of cores
294         // and configuration specified
295         // EVE will run layersGroupId 1 in the network, while
296         // DSP will run layersGroupId 2 in the network
297         e_eve = num_eves == 0 ? nullptr :
298                 new Executor(DeviceType::EVE, ids_eve, configuration, 1);
299         e_dsp = num_dsps == 0 ? nullptr :
300                 new Executor(DeviceType::DSP, ids_dsp, configuration, 2);
302         // Construct ExecutionObjectPipeline that utilizes multiple
303         // ExecutionObjects to process a single frame, each ExecutionObject
304         // processes one layerGroup of the network
305         // If buffer_factor == 2, duplicating EOPs for pipelining at
306         // EO level rather than at EOP level, in addition to double buffering
307         // and overlapping host pre/post-processing with device processing
308         for (uint32_t j = 0; j < buffer_factor; j++)
309         {
310             for (uint32_t i = 0; i < std::max(num_eves, num_dsps); i++)
311                 eops.push_back(new ExecutionObjectPipeline(
312                                 {(*e_eve)[i%num_eves], (*e_dsp)[i%num_dsps]}));
313         }
314         break;
316     default:
317         std::cout << "Layers groups can be either 1 or 2!" << std::endl;
318         return false;
319         break;
320     }
322     return true;
325 bool ReadFrame(ExecutionObjectPipeline& eop, uint32_t frame_idx,
326                const Configuration& c, const cmdline_opts_t& opts, char *input_frames_buffer)
328     if ((uint32_t)frame_idx >= opts.num_frames)
329         return false;
331     eop.SetFrameIndex(frame_idx);
333     char*  frame_buffer = eop.GetInputBufferPtr();
334     assert (frame_buffer != nullptr);
335     int channel_size = c.inWidth * c.inHeight;
336     char *bgr_frames_input = input_frames_buffer + (frame_idx % c.numFrames) * channel_size * 3;
337     memcpy(frame_buffer,                bgr_frames_input + 0, channel_size);
338     memcpy(frame_buffer + 1*channel_size, bgr_frames_input + 1 * channel_size, channel_size);
339     memcpy(frame_buffer + 2*channel_size, bgr_frames_input + 2 * channel_size, channel_size);
340     return true;
343 void DisplayHelp()
345     std::cout <<
346     "Usage: mcbench\n"
347     "  Will run partitioned mcbench network to perform "
348     "multi-objects detection\n"
349     "  and classification.  First part of network "
350     "(layersGroupId 1) runs on EVE,\n"
351     "  second part (layersGroupId 2) runs on DSP.\n"
352     "  Use -c to run a different segmentation network.  Default is jdetnet.\n"
353     "Optional arguments:\n"
354     " -c <config>          Valid configs: ../test/testvecs/config/infer/... files \n"
355     " -d <number>          Number of dsp cores to use\n"
356     " -e <number>          Number of eve cores to use\n"
357     " -g <1|2>             Number of layer groups (layer group <=> consecutive group of layers)\n"
358     " -f <number>          Number of frames to process\n"
359     " -r <number>          Keep repeating specified number of frames from input file\n"
360     " -i <image>           Path to the image file as input\n"
361     "                      Default are 9 frames in testvecs\n"
362     " -i camera<number>    Use camera as input\n"
363     "                      video input port: /dev/video<number>\n"
364     " -i <name>.{mp4,mov,avi}  Use video file as input\n"
365     " -w <number>          Output image/video width\n"
366     " -v                   Verbose output during execution\n"
367     " -h                   Help\n";