pipeline_modeling_tool.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. #include <iostream>
  2. #include <fstream>
  3. #include <thread>
  4. #include <exception>
  5. #include <unordered_map>
  6. #include <vector>
  7. #include <opencv2/gapi.hpp>
  8. #include <opencv2/highgui.hpp> // cv::CommandLineParser
  9. #include <opencv2/core/utils/filesystem.hpp>
  10. #if defined(_WIN32)
  11. #define NOMINMAX
  12. #include <windows.h>
  13. #undef NOMINMAX
  14. #endif
  15. #include "pipeline_modeling_tool/dummy_source.hpp"
  16. #include "pipeline_modeling_tool/utils.hpp"
  17. #include "pipeline_modeling_tool/pipeline_builder.hpp"
  18. enum class AppMode {
  19. REALTIME,
  20. BENCHMARK
  21. };
  22. static AppMode strToAppMode(const std::string& mode_str) {
  23. if (mode_str == "realtime") {
  24. return AppMode::REALTIME;
  25. } else if (mode_str == "benchmark") {
  26. return AppMode::BENCHMARK;
  27. } else {
  28. throw std::logic_error("Unsupported AppMode: " + mode_str +
  29. "\nPlease chose between: realtime and benchmark");
  30. }
  31. }
  32. enum class WaitMode {
  33. BUSY,
  34. SLEEP
  35. };
  36. static WaitMode strToWaitMode(const std::string& mode_str) {
  37. if (mode_str == "sleep") {
  38. return WaitMode::SLEEP;
  39. } else if (mode_str == "busy") {
  40. return WaitMode::BUSY;
  41. } else {
  42. throw std::logic_error("Unsupported wait mode: " + mode_str +
  43. "\nPlease chose between: busy (default) and sleep");
  44. }
  45. }
  46. template <typename T>
  47. T read(const cv::FileNode& node) {
  48. return static_cast<T>(node);
  49. }
  50. static cv::FileNode check_and_get_fn(const cv::FileNode& fn,
  51. const std::string& field,
  52. const std::string& uplvl) {
  53. const bool is_map = fn.isMap();
  54. if (!is_map || fn[field].empty()) {
  55. throw std::logic_error(uplvl + " must contain field: " + field);
  56. }
  57. return fn[field];
  58. }
  59. static cv::FileNode check_and_get_fn(const cv::FileStorage& fs,
  60. const std::string& field,
  61. const std::string& uplvl) {
  62. auto fn = fs[field];
  63. if (fn.empty()) {
  64. throw std::logic_error(uplvl + " must contain field: " + field);
  65. }
  66. return fn;
  67. }
  68. template <typename T, typename FileT>
  69. T check_and_read(const FileT& f,
  70. const std::string& field,
  71. const std::string& uplvl) {
  72. auto fn = check_and_get_fn(f, field, uplvl);
  73. return read<T>(fn);
  74. }
  75. template <typename T>
  76. cv::optional<T> readOpt(const cv::FileNode& fn) {
  77. return fn.empty() ? cv::optional<T>() : cv::optional<T>(read<T>(fn));
  78. }
  79. template <typename T>
  80. std::vector<T> readList(const cv::FileNode& fn,
  81. const std::string& field,
  82. const std::string& uplvl) {
  83. auto fn_field = check_and_get_fn(fn, field, uplvl);
  84. if (!fn_field.isSeq()) {
  85. throw std::logic_error(field + " in " + uplvl + " must be a sequence");
  86. }
  87. std::vector<T> vec;
  88. for (auto iter : fn_field) {
  89. vec.push_back(read<T>(iter));
  90. }
  91. return vec;
  92. }
  93. template <typename T>
  94. std::vector<T> readVec(const cv::FileNode& fn,
  95. const std::string& field,
  96. const std::string& uplvl) {
  97. auto fn_field = check_and_get_fn(fn, field, uplvl);
  98. std::vector<T> vec;
  99. fn_field >> vec;
  100. return vec;
  101. }
  102. static int strToPrecision(const std::string& precision) {
  103. static std::unordered_map<std::string, int> str_to_precision = {
  104. {"U8", CV_8U}, {"FP32", CV_32F}, {"FP16", CV_16F}
  105. };
  106. auto it = str_to_precision.find(precision);
  107. if (it == str_to_precision.end()) {
  108. throw std::logic_error("Unsupported precision: " + precision);
  109. }
  110. return it->second;
  111. }
  112. template <>
  113. OutputDescr read<OutputDescr>(const cv::FileNode& fn) {
  114. auto dims = readVec<int>(fn, "dims", "output");
  115. auto str_prec = check_and_read<std::string>(fn, "precision", "output");
  116. return OutputDescr{dims, strToPrecision(str_prec)};
  117. }
  118. template <>
  119. Edge read<Edge>(const cv::FileNode& fn) {
  120. auto from = check_and_read<std::string>(fn, "from", "edge");
  121. auto to = check_and_read<std::string>(fn, "to", "edge");
  122. auto splitNameAndPort = [](const std::string& str) {
  123. auto pos = str.find(':');
  124. auto name =
  125. pos == std::string::npos ? str : std::string(str.c_str(), pos);
  126. size_t port =
  127. pos == std::string::npos ? 0 : std::atoi(str.c_str() + pos + 1);
  128. return std::make_pair(name, port);
  129. };
  130. auto p1 = splitNameAndPort(from);
  131. auto p2 = splitNameAndPort(to);
  132. return Edge{Edge::P{p1.first, p1.second}, Edge::P{p2.first, p2.second}};
  133. }
  134. static std::string getModelsPath() {
  135. static char* models_path_c = std::getenv("PIPELINE_MODELS_PATH");
  136. static std::string models_path = models_path_c ? models_path_c : ".";
  137. return models_path;
  138. }
  139. template <>
  140. ModelPath read<ModelPath>(const cv::FileNode& fn) {
  141. using cv::utils::fs::join;
  142. if (!fn["xml"].empty() && !fn["bin"].empty()) {
  143. return ModelPath{LoadPath{join(getModelsPath(), fn["xml"].string()),
  144. join(getModelsPath(), fn["bin"].string())}};
  145. } else if (!fn["blob"].empty()){
  146. return ModelPath{ImportPath{join(getModelsPath(), fn["blob"].string())}};
  147. } else {
  148. const std::string emsg = R""""(
  149. Path to OpenVINO model must be specified in either of two formats:
  150. 1.
  151. xml: path to *.xml
  152. bin: path to *.bin
  153. 2.
  154. blob: path to *.blob
  155. )"""";
  156. throw std::logic_error(emsg);
  157. }
  158. }
  159. static PLMode strToPLMode(const std::string& mode_str) {
  160. if (mode_str == "streaming") {
  161. return PLMode::STREAMING;
  162. } else if (mode_str == "regular") {
  163. return PLMode::REGULAR;
  164. } else {
  165. throw std::logic_error("Unsupported PLMode: " + mode_str +
  166. "\nPlease chose between: streaming and regular");
  167. }
  168. }
  169. static cv::gapi::ie::InferMode strToInferMode(const std::string& infer_mode) {
  170. if (infer_mode == "async") {
  171. return cv::gapi::ie::InferMode::Async;
  172. } else if (infer_mode == "sync") {
  173. return cv::gapi::ie::InferMode::Sync;
  174. } else {
  175. throw std::logic_error("Unsupported Infer mode: " + infer_mode +
  176. "\nPlease chose between: async and sync");
  177. }
  178. }
  179. template <>
  180. CallParams read<CallParams>(const cv::FileNode& fn) {
  181. auto name =
  182. check_and_read<std::string>(fn, "name", "node");
  183. // FIXME: Impossible to read size_t due OpenCV limitations.
  184. auto call_every_nth_opt = readOpt<int>(fn["call_every_nth"]);
  185. auto call_every_nth = call_every_nth_opt.value_or(1);
  186. if (call_every_nth <= 0) {
  187. throw std::logic_error(
  188. name + " call_every_nth must be greater than zero\n"
  189. "Current call_every_nth: " + std::to_string(call_every_nth));
  190. }
  191. return CallParams{std::move(name), static_cast<size_t>(call_every_nth)};
  192. }
  193. template <typename V>
  194. std::map<std::string, V> readMap(const cv::FileNode& fn) {
  195. std::map<std::string, V> map;
  196. for (auto item : fn) {
  197. map.emplace(item.name(), read<V>(item));
  198. }
  199. return map;
  200. }
  201. template <>
  202. InferParams read<InferParams>(const cv::FileNode& fn) {
  203. auto name =
  204. check_and_read<std::string>(fn, "name", "node");
  205. InferParams params;
  206. params.path = read<ModelPath>(fn);
  207. params.device = check_and_read<std::string>(fn, "device", name);
  208. params.input_layers = readList<std::string>(fn, "input_layers", name);
  209. params.output_layers = readList<std::string>(fn, "output_layers", name);
  210. params.config = readMap<std::string>(fn["config"]);
  211. auto out_prec_str = readOpt<std::string>(fn["output_precision"]);
  212. if (out_prec_str.has_value()) {
  213. params.out_precision =
  214. cv::optional<int>(strToPrecision(out_prec_str.value()));
  215. }
  216. return params;
  217. }
  218. template <>
  219. DummyParams read<DummyParams>(const cv::FileNode& fn) {
  220. auto name =
  221. check_and_read<std::string>(fn, "name", "node");
  222. DummyParams params;
  223. params.time = check_and_read<double>(fn, "time", name);
  224. if (params.time < 0) {
  225. throw std::logic_error(name + " time must be positive");
  226. }
  227. params.output = check_and_read<OutputDescr>(fn, "output", name);
  228. return params;
  229. }
  230. static std::vector<std::string> parseExecList(const std::string& exec_list) {
  231. std::vector<std::string> pl_types;
  232. std::stringstream ss(exec_list);
  233. std::string pl_type;
  234. while (getline(ss, pl_type, ',')) {
  235. pl_types.push_back(pl_type);
  236. }
  237. return pl_types;
  238. }
  239. static void loadConfig(const std::string& filename,
  240. std::map<std::string, std::string>& config) {
  241. cv::FileStorage fs(filename, cv::FileStorage::READ);
  242. if (!fs.isOpened()) {
  243. throw std::runtime_error("Failed to load config: " + filename);
  244. }
  245. cv::FileNode root = fs.root();
  246. for (auto it = root.begin(); it != root.end(); ++it) {
  247. auto device = *it;
  248. if (!device.isMap()) {
  249. throw std::runtime_error("Failed to parse config: " + filename);
  250. }
  251. for (auto item : device) {
  252. config.emplace(item.name(), item.string());
  253. }
  254. }
  255. }
  256. int main(int argc, char* argv[]) {
  257. #if defined(_WIN32)
  258. timeBeginPeriod(1);
  259. #endif
  260. try {
  261. const std::string keys =
  262. "{ h help | | Print this help message. }"
  263. "{ cfg | | Path to the config which is either"
  264. " YAML file or string. }"
  265. "{ load_config | | Optional. Path to XML/YAML/JSON file"
  266. " to load custom IE parameters. }"
  267. "{ cache_dir | | Optional. Enables caching of loaded models"
  268. " to specified directory. }"
  269. "{ log_file | | Optional. If file is specified, app will"
  270. " dump expanded execution information. }"
  271. "{ pl_mode | streaming | Optional. Pipeline mode: streaming/regular"
  272. " if it's specified will be applied for"
  273. " every pipeline. }"
  274. "{ qc | 1 | Optional. Calculated automatically by G-API"
  275. " if set to 0. If it's specified will be"
  276. " applied for every pipeline. }"
  277. "{ app_mode | realtime | Application mode (realtime/benchmark). }"
  278. "{ drop_frames | false | Drop frames if they come earlier than pipeline is completed. }"
  279. "{ exec_list | | A comma-separated list of pipelines that"
  280. " will be executed. Spaces around commas"
  281. " are prohibited. }"
  282. "{ infer_mode | async | OpenVINO inference mode (async/sync). }";
  283. cv::CommandLineParser cmd(argc, argv, keys);
  284. if (cmd.has("help")) {
  285. cmd.printMessage();
  286. return 0;
  287. }
  288. const auto cfg = cmd.get<std::string>("cfg");
  289. const auto load_config = cmd.get<std::string>("load_config");
  290. const auto cached_dir = cmd.get<std::string>("cache_dir");
  291. const auto log_file = cmd.get<std::string>("log_file");
  292. const auto cmd_pl_mode = strToPLMode(cmd.get<std::string>("pl_mode"));
  293. const auto qc = cmd.get<int>("qc");
  294. const auto app_mode = strToAppMode(cmd.get<std::string>("app_mode"));
  295. const auto exec_str = cmd.get<std::string>("exec_list");
  296. const auto infer_mode = strToInferMode(cmd.get<std::string>("infer_mode"));
  297. const auto drop_frames = cmd.get<bool>("drop_frames");
  298. cv::FileStorage fs;
  299. if (cfg.empty()) {
  300. throw std::logic_error("Config must be specified via --cfg option");
  301. }
  302. // NB: *.yml
  303. if (cfg.size() < 5) {
  304. throw std::logic_error("--cfg string must contain at least 5 symbols"
  305. " to determine if it's a file (*.yml) a or string");
  306. }
  307. if (cfg.substr(cfg.size() - 4, cfg.size()) == ".yml") {
  308. if (!fs.open(cfg, cv::FileStorage::READ)) {
  309. throw std::logic_error("Failed to open config file: " + cfg);
  310. }
  311. } else {
  312. fs = cv::FileStorage(cfg, cv::FileStorage::FORMAT_YAML |
  313. cv::FileStorage::MEMORY);
  314. }
  315. std::map<std::string, std::string> gconfig;
  316. if (!load_config.empty()) {
  317. loadConfig(load_config, gconfig);
  318. }
  319. // NB: Takes priority over config from file
  320. if (!cached_dir.empty()) {
  321. gconfig =
  322. std::map<std::string, std::string>{{"CACHE_DIR", cached_dir}};
  323. }
  324. auto opt_work_time_ms = readOpt<double>(fs["work_time"]);
  325. cv::optional<int64_t> opt_work_time_mcs;
  326. if (opt_work_time_ms) {
  327. const double work_time_ms = opt_work_time_ms.value();
  328. if (work_time_ms < 0) {
  329. throw std::logic_error("work_time must be positive");
  330. }
  331. opt_work_time_mcs = cv::optional<int64_t>(utils::ms_to_mcs(work_time_ms));
  332. }
  333. auto pipelines_fn = check_and_get_fn(fs, "Pipelines", "Config");
  334. if (!pipelines_fn.isMap()) {
  335. throw std::logic_error("Pipelines field must be a map");
  336. }
  337. auto exec_list = !exec_str.empty() ? parseExecList(exec_str)
  338. : pipelines_fn.keys();
  339. std::vector<Pipeline::Ptr> pipelines;
  340. pipelines.reserve(exec_list.size());
  341. // NB: Build pipelines based on config information
  342. PipelineBuilder builder;
  343. for (const auto& name : exec_list) {
  344. const auto& pl_fn = check_and_get_fn(pipelines_fn, name, "Pipelines");
  345. builder.setName(name);
  346. StopCriterion::Ptr stop_criterion;
  347. auto opt_num_iters = readOpt<int>(pl_fn["num_iters"]);
  348. // NB: num_iters for specific pipeline takes priority over global work_time.
  349. if (opt_num_iters) {
  350. stop_criterion.reset(new NumItersCriterion(opt_num_iters.value()));
  351. } else if (opt_work_time_mcs) {
  352. stop_criterion.reset(new ElapsedTimeCriterion(opt_work_time_mcs.value()));
  353. } else {
  354. throw std::logic_error(
  355. "Failed: Pipeline " + name + " doesn't have stop criterion!\n"
  356. "Please specify either work_time: <value> in the config root"
  357. " or num_iters: <value> for specific pipeline.");
  358. }
  359. builder.setStopCriterion(std::move(stop_criterion));
  360. // NB: Set source
  361. {
  362. const auto& src_fn = check_and_get_fn(pl_fn, "source", name);
  363. auto src_name =
  364. check_and_read<std::string>(src_fn, "name", "source");
  365. auto latency =
  366. check_and_read<double>(src_fn, "latency", "source");
  367. auto output =
  368. check_and_read<OutputDescr>(src_fn, "output", "source");
  369. // NB: In case BENCHMARK mode sources work with zero latency.
  370. if (app_mode == AppMode::BENCHMARK) {
  371. latency = 0.0;
  372. }
  373. const auto wait_mode =
  374. strToWaitMode(readOpt<std::string>(src_fn["wait_mode"]).value_or("busy"));
  375. auto wait_strategy = (wait_mode == WaitMode::SLEEP) ? utils::sleep : utils::busyWait;
  376. auto src = std::make_shared<DummySource>(
  377. utils::double_ms_t{latency}, output, drop_frames, std::move(wait_strategy));
  378. builder.setSource(src_name, src);
  379. }
  380. const auto& nodes_fn = check_and_get_fn(pl_fn, "nodes", name);
  381. if (!nodes_fn.isSeq()) {
  382. throw std::logic_error("nodes in " + name + " must be a sequence");
  383. }
  384. for (auto node_fn : nodes_fn) {
  385. auto call_params = read<CallParams>(node_fn);
  386. auto node_type =
  387. check_and_read<std::string>(node_fn, "type", "node");
  388. if (node_type == "Dummy") {
  389. builder.addDummy(call_params, read<DummyParams>(node_fn));
  390. } else if (node_type == "Infer") {
  391. auto infer_params = read<InferParams>(node_fn);
  392. try {
  393. utils::mergeMapWith(infer_params.config, gconfig);
  394. } catch (std::exception& e) {
  395. std::stringstream ss;
  396. ss << "Failed to merge global and local config for Infer node: "
  397. << call_params.name << std::endl << e.what();
  398. throw std::logic_error(ss.str());
  399. }
  400. infer_params.mode = infer_mode;
  401. builder.addInfer(call_params, infer_params);
  402. } else {
  403. throw std::logic_error("Unsupported node type: " + node_type);
  404. }
  405. }
  406. const auto edges_fn = check_and_get_fn(pl_fn, "edges", name);
  407. if (!edges_fn.isSeq()) {
  408. throw std::logic_error("edges in " + name + " must be a sequence");
  409. }
  410. for (auto edge_fn : edges_fn) {
  411. auto edge = read<Edge>(edge_fn);
  412. builder.addEdge(edge);
  413. }
  414. auto cfg_pl_mode = readOpt<std::string>(pl_fn["mode"]);
  415. // NB: Pipeline mode from config takes priority over cmd.
  416. auto pl_mode = cfg_pl_mode.has_value()
  417. ? strToPLMode(cfg_pl_mode.value()) : cmd_pl_mode;
  418. // NB: Using drop_frames with streaming pipelines will lead to
  419. // incorrect performance results.
  420. if (drop_frames && pl_mode == PLMode::STREAMING) {
  421. throw std::logic_error(
  422. "--drop_frames option is supported only for pipelines in \"regular\" mode");
  423. }
  424. builder.setMode(pl_mode);
  425. // NB: Queue capacity from config takes priority over cmd.
  426. auto config_qc = readOpt<int>(pl_fn["queue_capacity"]);
  427. auto queue_capacity = config_qc.has_value() ? config_qc.value() : qc;
  428. // NB: 0 is special constant that means
  429. // queue capacity should be calculated automatically.
  430. if (queue_capacity != 0) {
  431. builder.setQueueCapacity(queue_capacity);
  432. }
  433. auto dump = readOpt<std::string>(pl_fn["dump"]);
  434. if (dump) {
  435. builder.setDumpFilePath(dump.value());
  436. }
  437. pipelines.emplace_back(builder.build());
  438. }
  439. // NB: Compille pipelines
  440. for (size_t i = 0; i < pipelines.size(); ++i) {
  441. pipelines[i]->compile();
  442. }
  443. // NB: Execute pipelines
  444. std::vector<std::exception_ptr> eptrs(pipelines.size(), nullptr);
  445. std::vector<std::thread> threads(pipelines.size());
  446. for (size_t i = 0; i < pipelines.size(); ++i) {
  447. threads[i] = std::thread([&, i]() {
  448. try {
  449. pipelines[i]->run();
  450. } catch (...) {
  451. eptrs[i] = std::current_exception();
  452. }
  453. });
  454. }
  455. std::ofstream file;
  456. if (!log_file.empty()) {
  457. file.open(log_file);
  458. }
  459. for (size_t i = 0; i < threads.size(); ++i) {
  460. threads[i].join();
  461. }
  462. for (size_t i = 0; i < threads.size(); ++i) {
  463. if (eptrs[i] != nullptr) {
  464. try {
  465. std::rethrow_exception(eptrs[i]);
  466. } catch (std::exception& e) {
  467. throw std::logic_error(pipelines[i]->name() + " failed: " + e.what());
  468. }
  469. }
  470. if (file.is_open()) {
  471. file << pipelines[i]->report().toStr(true) << std::endl;
  472. }
  473. std::cout << pipelines[i]->report().toStr() << std::endl;
  474. }
  475. } catch (const std::exception& e) {
  476. std::cout << e.what() << std::endl;
  477. throw;
  478. }
  479. return 0;
  480. }