[tools] new api and support dynamic models in python benchmark app (#8582)

* Preprocessing API - base classes

Includes API definition for trivial mean/scale operations (which don't require layout)

Mean/scale with 'layout' support will be done under separate task together
 with Layout

Current test code coverage: 100%

* Python bindings for base preprocessing API

* remove pre_post_process directory from ngraph/core

* remove files from ngraph/python dir

* move pyngraph pre_post_process files from ngraph/python to runtime

* remove pre_post_process test from CMakeList

* move include to the header

* update include path for pre_post_process

* style fix

* bind InputTensorInfo::set_layout

* cleaned test_preprocess

* fix test expected output

* remove duplicate test

* update description of set_element_type

* fix style

* move preprocess from pyngraph to pyopenvino/graph

* update test_preprocess imports and remove unnecessary test

* remove duplicate import

* update custom method

* update test

* update test

* create decorator that changes Node into Output<Node>

* create function that cast Node to Output<Node>

* update test_preprocess to use decorator for custom function

* change _cast_to_output -> _from_node

* style fix

* add tests fro scale and mean with vector input

* style fix

* add docstring for custom_preprocess_function

* bind InputInfo network method

* style fix

* bind OutputInfo

* fix description of preprocess submodule

* fix style

* update copyright year

* bind OutputTensorInfo

* bind OutputNetworkInfo and InputNetworkInfo

* Bind exec core ov (#50)

* Output const node python tests (#52)

* add python bindings tests for Output<const ov::None>

* add proper tests

* add new line

* rename ie_version to version

* Pszmel/bind infer request (#51)

* remove set_batch, get_blob and set_blob

* update InferRequest class

* change InferenceEngine::InferRequest to ov::runtime::InferRequest

* update set_callback body

* update bindings to reflect ov::runtime::InferRequest

* bind set_input_tensor and get_input_tensor

* style fix

* clen ie_infer_queue.cpp

* Bind exec core ov (#50)

* bind core, exec_net classes

* rm unused function

* add new line

* rename ie_infer_request -> infer_request

* update imports

* update __init__.py

* update ie_api.py

* Replace old containers with the new one

* create impl for create_infer_request

* comment out infer_queue to avoid errors with old infer_request

* update infer_request bind to reflect new infer_request api

* comment out inpuit_info from ie_network to avoid errors with old containers

* Register new containers and comment out InferQueue

* update infer request tests

* style fix

* remove unused imports

* remove unused imports and 2 methods

* add tests to cover all new methods from infer_request

* style fix

* add test

* remove registration of InferResults

* update name of exception_ptr parameter

* update the loops that iterate through inputs and outputs

* clean setCustomCallbacks

* style fix

* add Tensor import

* style fix

* update infer and normalize_inputs

* style fix

* rename startTime and endTime

* Create test for mixed keys as infer arguments

* update infer function

* update return type of infer

Co-authored-by: Bartek Szmelczynski <bartosz.szmelczynski@intel.com>

* fix get_version

* fix opaque issue

* some cosmetic changes

* fix codestyle in tests

* make tests green

* Extend python InferRequest

* Extend python Function

* Change return value of infer call

* Fix missing precisions conversions in CPU plugin

* Rework of runtime for new tests

* Fixed onnx reading in python tests

* Edit compatibility tests

* Edit tests

* Add FLOAT_LIKE xfails

* bind ColorFormat and ResizeAlgorithm

* clean imports

* fix typo

* [Python API] bind ProfilingInfo (#55)

* bind ProfilingInfo

* Add tests

* Fix code style

* Add property

* fix codestyle

* Infer new request method (#56)

* fix conflicts, add infer_new_request function

* remove redundant functions, fix style

* revert the unwanted changes

* revert removal of the Blob

* revert removal of isTblob

* add add_extension from path

* codestyle

* add PostProcessSteps to init

* bind PreProcessSteps

* create additional tests

* fix win build

* add inputs-outputs to function

* update infer queue

* fix code style

* Hot-fix CPU plugin with precision

* fix start_async

* add performance hint to time infer (#8480)

* Updated common migration pipeline (#8176)

* Updated common migration pipeline

* Fixed merge issue

* Added new model and extended example

* Fixed typo

* Added v10-v11 comparison

* Avoid redundant graph nodes scans (#8415)

* Refactor work with env variables (#8208)

* del MO_ROOT

* del MO_ROOT from common_utils.py

* add MO_PATH to common_utils.py

* change mo_path

* [IE Sample Scripts] Use cmake to build samples (#8442)

* Use cmake to build samples

* Add the option to set custom build output folder

* Remove opset8 from compatibility ngraph python API (#8452)

* [GPU] OneDNN gpu submodule update to version 2.5 (#8449)

* [GPU] OneDNN gpu submodule update to version 2.5

* [GPU] Updated onednn submodule and added layout optimizer fix

* Install rules for static libraries case (#8384)

* Proper cmake install for static libraries case

* Added an ability to skip template plugin

* Added install rules for VPU / GPU

* Install more libraries

* Fixed absolute TBB include paths

* Disable GNA

* Fixed issue with linker

* Some fixes

* Fixed linkage issues in tests

* Disabled some tests

* Updated CI pipelines

* Fixed Windows linkage

* Fixed custom_opset test for static casr

* Fixed CVS-70313

* Continue on error

* Fixed clanf-format

* Try to fix Windows linker

* Fixed compilation

* Disable samples

* Fixed samples build with THREADING=SEQ

* Fixed link error on Windows

* Fixed ieFuncTests

* Added static Azure CI

* Revert "Fixed link error on Windows"

This reverts commit 78cca36fd2.

* Merge static and dynamic linux pipelines

* Fixed Azure

* fix codestyle

* rename all methods in this class to snake_case

* some updates

* code style

* fix code style in tests

* update statistics reporting

* update filling inputs

* change ngraph.Type to ov.Type

* fix typo

* save work

* save work

* save work

* compute latency in callback

* save work

* Fix get_idle_request

* save work

* fix latency

* Fix code style

* update AppInputInfo

* add iteration to PatrialShape

* fix rebasing

* bind result::get_layout()

* correct mistakes

* fix setup

* use parameters/results instead inputs/outputs

* move _from_node to node_output.hpp

* add read_model from buffer

* update imports

* revert package struct

* add new line

* remove bad quotes

* update imports

* style fix

* add new line

* Fix preprocessing

* rename functin args

* set NCHW layout to image as default

* Fix input fillings

* remove Type import

* update tests

* style fix

* test clean

* remove blank line

* Add tensor_shape

* fix comments

* update PrePostProcessor init and build methods

* create test with model update tests with new PrePostProcessor init and build

* Change filling inputs

* fix preprocessing

* basic support dynamic shapes

* fix legacy mode

* rename ie to core

* fix cpp code style

* fix input files parsing

* fix binary filling

* support dynamic batch size

* process images with original shapes if no tensor shapes were given

* fix fps and number of iterations

* Add new metrics

* support pass path to folder into input mapping

* add pcseq flag

* fix resolving conflicts

* dump statistic per group

* check for compatibility with partial shape

* revert statistic report names

* code refactoring

* update parameters

* enable legacy_mode if data size less than nireq

* add serialize to offline_transformations

* Fix preprocessing import

* change log output due to ci parsing

* fix layout

* allow to pass batch size with undefined layout

* add serializer

* fix comments from jiwaszki

* Fix latency parsing for ci

* code style

* rename tensor_shape to data_shape

* add message if image is processed with original shape

* fix syntax warning

* remove default legacy_mode if requests cover all data

* rewrite all file parsing

* fix preprocessing

* Fix preprocessing #2

* Use layout instead str

* Fix file extensions

* Fix image sizes filling

* sort input files

* [Python API] quick fix of packaging

* update tests

* fix setup.py

* small fix

* small fixes according to comments

* skip mo frontend tests

* full mode is default for dynamic models only

* backward compatibility

* Fix package

* set layout in runtime

* static mode for dynamic models with all equal data shapes

* use get_tensor instead set_tensor in legacy mode

* benchmarking dynamic model available in full mode only

* fix layout detection

* use batch_size * iteration instead processed_frames in legacy mode

* fix tensor naming

* represent --inference_only

* refactoring main loop

* Fix number of iterations for full mode

Co-authored-by: Michael Nosov <mikhail.nosov@intel.com>
Co-authored-by: pszmel <piotr.szmelczynski@intel.com>
Co-authored-by: Bartek Szmelczynski <bartosz.szmelczynski@intel.com>
Co-authored-by: Anastasia Kuporosova <anastasia.kuporosova@intel.com>
Co-authored-by: jiwaszki <jan.iwaszkiewicz@intel.com>
Co-authored-by: Victor Kuznetsov <victor.kuznetsov@intel.com>
Co-authored-by: Ilya Churaev <ilya.churaev@intel.com>
Co-authored-by: Tomasz Jankowski <tomasz1.jankowski@intel.com>
Co-authored-by: Dmitry Pigasin <dmitry.pigasin@intel.com>
Co-authored-by: Artur Kulikowski <artur.kulikowski@intel.com>
Co-authored-by: Ilya Znamenskiy <ilya.znamenskiy@intel.com>
Co-authored-by: Ilya Lavrenov <ilya.lavrenov@intel.com>
This commit is contained in:
9 files changed
+1065 -613

No files matched your search

@@ -192,6 +192,25 @@ void regclass_graph_PartialShape(py::module m) {
},
py::is_operator());
shape.def("__len__", [](const ov::PartialShape& self) {
return self.size();
});
shape.def("__setitem__", [](ov::PartialShape& self, size_t key, ov::Dimension& d) {
self[key] = d;
});
shape.def("__getitem__", [](const ov::PartialShape& self, size_t key) {
return self[key];
});
shape.def(
"__iter__",
[](ov::PartialShape& self) {
return py::make_iterator(self.begin(), self.end());
},
py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */
shape.def("__str__", [](const ov::PartialShape& self) -> std::string {
std::stringstream ss;
ss << self;
@@ -11,6 +11,7 @@
#include <sstream>
#include <string>
#include "openvino/core/dimension.hpp" // ov::Dimension
#include "pyopenvino/graph/shape.hpp"
namespace py = pybind11;
@@ -24,7 +25,13 @@ void regclass_graph_Shape(py::module m) {
shape.def("__len__", [](const ov::Shape& v) {
return v.size();
});
shape.def("__getitem__", [](const ov::Shape& v, int key) {
shape.def("__setitem__", [](ov::Shape& self, size_t key, size_t d) {
self[key] = d;
});
shape.def("__setitem__", [](ov::Shape& self, size_t key, ov::Dimension d) {
self[key] = d.get_length();
});
shape.def("__getitem__", [](const ov::Shape& v, size_t key) {
return v[key];
});
@@ -4,7 +4,8 @@
import os
from datetime import datetime
from math import ceil
from openvino.inference_engine import IENetwork, IECore, get_version, StatusCode
from typing import Union
from openvino.runtime import Core, get_version, AsyncInferQueue
from .utils.constants import MULTI_DEVICE_NAME, HETERO_DEVICE_NAME, CPU_DEVICE_NAME, GPU_DEVICE_NAME, XML_EXTENSION, BIN_EXTENSION
from .utils.logging import logger
@@ -15,31 +16,33 @@ def percentile(values, percent):
return values[ceil(len(values) * percent / 100) - 1]
class Benchmark:
def __init__(self, device: str, number_infer_requests: int = None, number_iterations: int = None,
duration_seconds: int = None, api_type: str = 'async'):
def __init__(self, device: str, number_infer_requests: int = 0, number_iterations: int = None,
duration_seconds: int = None, api_type: str = 'async', inference_only = None):
self.device = device
self.ie = IECore()
self.nireq = number_infer_requests
self.core = Core()
self.nireq = number_infer_requests if api_type == 'async' else 1
self.niter = number_iterations
self.duration_seconds = get_duration_seconds(duration_seconds, self.niter, self.device)
self.api_type = api_type
self.inference_only = inference_only
self.latency_groups = []
def __del__(self):
del self.ie
del self.core
def add_extension(self, path_to_extension: str=None, path_to_cldnn_config: str=None):
if path_to_cldnn_config:
self.ie.set_config({'CONFIG_FILE': path_to_cldnn_config}, GPU_DEVICE_NAME)
self.core.set_config({'CONFIG_FILE': path_to_cldnn_config}, GPU_DEVICE_NAME)
logger.info(f'GPU extensions is loaded {path_to_cldnn_config}')
if path_to_extension:
self.ie.add_extension(extension_path=path_to_extension, device_name=CPU_DEVICE_NAME)
self.core.add_extension(extension_path=path_to_extension)
logger.info(f'CPU extensions is loaded {path_to_extension}')
def get_version_info(self) -> str:
logger.info(f"InferenceEngine:\n{'': <9}{'API version':.<24} {get_version()}")
version_string = 'Device info\n'
for device, version in self.ie.get_versions(self.device).items():
for device, version in self.core.get_versions(self.device).items():
version_string += f"{'': <9}{device}\n"
version_string += f"{'': <9}{version.description:.<24}{' version'} {version.major}.{version.minor}\n"
version_string += f"{'': <9}{'Build':.<24} {version.build_number}\n"
@@ -47,119 +50,161 @@ class Benchmark:
def set_config(self, config = {}):
for device in config.keys():
self.ie.set_config(config[device], device)
self.core.set_config(config[device], device)
def set_cache_dir(self, cache_dir: str):
self.ie.set_config({'CACHE_DIR': cache_dir}, '')
self.core.set_config({'CACHE_DIR': cache_dir}, '')
def read_network(self, path_to_model: str):
def read_model(self, path_to_model: str):
model_filename = os.path.abspath(path_to_model)
head, ext = os.path.splitext(model_filename)
weights_filename = os.path.abspath(head + BIN_EXTENSION) if ext == XML_EXTENSION else ""
ie_network = self.ie.read_network(model_filename, weights_filename)
return ie_network
return self.core.read_model(model_filename, weights_filename)
def load_network(self, ie_network: IENetwork, config = {}):
exe_network = self.ie.load_network(ie_network,
self.device,
config=config,
num_requests=1 if self.api_type == 'sync' else self.nireq or 0)
# Number of requests
self.nireq = len(exe_network.requests)
return exe_network
def load_network_from_file(self, path_to_model: str, config = {}):
exe_network = self.ie.load_network(path_to_model,
self.device,
config=config,
num_requests=1 if self.api_type == 'sync' else self.nireq or 0)
# Number of requests
self.nireq = len(exe_network.requests)
return exe_network
def import_network(self, path_to_file : str, config = {}):
exe_network = self.ie.import_network(model_file=path_to_file,
device_name=self.device,
config=config,
num_requests=1 if self.api_type == 'sync' else self.nireq or 0)
# Number of requests
self.nireq = len(exe_network.requests)
return exe_network
def first_infer(self, exe_network):
infer_request = exe_network.requests[0]
# warming up - out of scope
def create_infer_requests(self, exe_network):
if self.api_type == 'sync':
infer_request.infer()
requests = [exe_network.create_infer_request()]
else:
infer_request.async_infer()
status = infer_request.wait()
if status != StatusCode.OK:
raise Exception(f"Wait for all requests is failed with status code {status}!")
return infer_request.latency
requests = AsyncInferQueue(exe_network, self.nireq)
self.nireq = len(requests)
return requests
def infer(self, exe_network, batch_size, latency_percentile, progress_bar=None):
def first_infer(self, requests):
if self.api_type == 'sync':
requests[0].infer()
return requests[0].latency
else:
id = requests.get_idle_request_id()
requests.start_async()
requests.wait_all()
return requests[id].latency
def update_progress_bar(self, progress_bar, exec_time, progress_count):
if self.duration_seconds:
# calculate how many progress intervals are covered by current iteration.
# depends on the current iteration time and time of each progress interval.
# Previously covered progress intervals must be skipped.
progress_interval_time = self.duration_seconds / progress_bar.total_num
new_progress = int(exec_time / progress_interval_time - progress_count)
progress_bar.add_progress(new_progress)
progress_count += new_progress
elif self.niter:
progress_bar.add_progress(1)
return progress_count
def sync_inference(self, request, data_queue, progress_bar):
progress_count = 0
infer_requests = exe_network.requests
start_time = datetime.utcnow()
exec_time = 0
iteration = 0
times = []
in_fly = set()
# Start inference & calculate performance
# to align number if iterations to guarantee that last infer requests are executed in the same conditions **/
start_time = datetime.utcnow()
while (self.niter and iteration < self.niter) or \
(self.duration_seconds and exec_time < self.duration_seconds) or \
(self.api_type == 'async' and iteration % self.nireq):
if self.api_type == 'sync':
infer_requests[0].infer()
times.append(infer_requests[0].latency)
else:
infer_request_id = exe_network.get_idle_request_id()
if infer_request_id < 0:
status = exe_network.wait(num_requests=1)
if status != StatusCode.OK:
raise Exception("Wait for idle request failed!")
infer_request_id = exe_network.get_idle_request_id()
if infer_request_id < 0:
raise Exception("Invalid request id!")
if infer_request_id in in_fly:
times.append(infer_requests[infer_request_id].latency)
else:
in_fly.add(infer_request_id)
infer_requests[infer_request_id].async_infer()
(self.duration_seconds and exec_time < self.duration_seconds):
if self.inference_only == False:
request.set_input_tensors(data_queue.get_next_input())
request.infer()
times.append(request.latency)
iteration += 1
exec_time = (datetime.utcnow() - start_time).total_seconds()
if progress_bar:
if self.duration_seconds:
# calculate how many progress intervals are covered by current iteration.
# depends on the current iteration time and time of each progress interval.
# Previously covered progress intervals must be skipped.
progress_interval_time = self.duration_seconds / progress_bar.total_num
new_progress = int(exec_time / progress_interval_time - progress_count)
progress_bar.add_progress(new_progress)
progress_count += new_progress
elif self.niter:
progress_bar.add_progress(1)
# wait the latest inference executions
status = exe_network.wait()
if status != StatusCode.OK:
raise Exception(f"Wait for all requests is failed with status code {status}!")
progress_count = self.update_progress_bar(progress_bar, exec_time, progress_count)
total_duration_sec = (datetime.utcnow() - start_time).total_seconds()
return sorted(times), total_duration_sec, iteration
def async_inference_only(self, infer_queue, progress_bar):
progress_count = 0
exec_time = 0
iteration = 0
times = []
in_fly = set()
start_time = datetime.utcnow()
while (self.niter and iteration < self.niter) or \
(self.duration_seconds and exec_time < self.duration_seconds) or \
(iteration % self.nireq):
idle_id = infer_queue.get_idle_request_id()
if idle_id in in_fly:
times.append(infer_queue[idle_id].latency)
else:
in_fly.add(idle_id)
infer_queue.start_async()
iteration += 1
exec_time = (datetime.utcnow() - start_time).total_seconds()
if progress_bar:
progress_count = self.update_progress_bar(progress_bar, exec_time, progress_count)
infer_queue.wait_all()
total_duration_sec = (datetime.utcnow() - start_time).total_seconds()
for infer_request_id in in_fly:
times.append(infer_requests[infer_request_id].latency)
times.sort()
latency_ms = percentile(times, latency_percentile)
fps = batch_size * 1000 / latency_ms if self.api_type == 'sync' else batch_size * iteration / total_duration_sec
times.append(infer_queue[infer_request_id].latency)
return sorted(times), total_duration_sec, iteration
def async_inference_full_mode(self, infer_queue, data_queue, progress_bar, pcseq):
progress_count = 0
processed_frames = 0
exec_time = 0
iteration = 0
times = []
num_groups = len(self.latency_groups)
in_fly = set()
start_time = datetime.utcnow()
while (self.niter and iteration < self.niter) or \
(self.duration_seconds and exec_time < self.duration_seconds) or \
(iteration % num_groups):
processed_frames += data_queue.get_next_batch_size()
idle_id = infer_queue.get_idle_request_id()
if idle_id in in_fly:
times.append(infer_queue[idle_id].latency)
if pcseq:
self.latency_groups[infer_queue.userdata[idle_id]].times.append(infer_queue[idle_id].latency)
else:
in_fly.add(idle_id)
group_id = data_queue.current_group_id
infer_queue[idle_id].set_input_tensors(data_queue.get_next_input())
infer_queue.start_async(userdata=group_id)
iteration += 1
exec_time = (datetime.utcnow() - start_time).total_seconds()
if progress_bar:
progress_count = self.update_progress_bar(progress_bar, exec_time, progress_count)
infer_queue.wait_all()
total_duration_sec = (datetime.utcnow() - start_time).total_seconds()
for infer_request_id in in_fly:
times.append(infer_queue[infer_request_id].latency)
return sorted(times), total_duration_sec, processed_frames, iteration
def main_loop(self, requests, data_queue, batch_size, latency_percentile, progress_bar, pcseq):
if self.api_type == 'sync':
times, total_duration_sec, iteration = self.sync_inference(requests[0], data_queue, progress_bar)
elif self.inference_only:
times, total_duration_sec, iteration = self.async_inference_only(requests, progress_bar)
fps = len(batch_size) * iteration / total_duration_sec
else:
times, total_duration_sec, processed_frames, iteration = self.async_inference_full_mode(requests, data_queue, progress_bar, pcseq)
fps = processed_frames / total_duration_sec
median_latency_ms = percentile(times, latency_percentile)
avg_latency_ms = sum(times) / len(times)
min_latency_ms = times[0]
max_latency_ms = times[-1]
if self.api_type == 'sync':
fps = len(batch_size) * 1000 / median_latency_ms
if pcseq:
for group in self.latency_groups:
if group.times:
group.times.sort()
group.avg = sum(group.times) / len(group.times)
group.min = group.times[0]
group.max = group.times[-1]
if progress_bar:
progress_bar.finish()
return fps, latency_ms, total_duration_sec, iteration
return fps, median_latency_ms, avg_latency_ms, min_latency_ms, max_latency_ms, total_duration_sec, iteration
@@ -9,13 +9,14 @@ from openvino.tools.benchmark.benchmark import Benchmark
from openvino.tools.benchmark.parameters import parse_args
from openvino.tools.benchmark.utils.constants import MULTI_DEVICE_NAME, HETERO_DEVICE_NAME, CPU_DEVICE_NAME, \
GPU_DEVICE_NAME, MYRIAD_DEVICE_NAME, GNA_DEVICE_NAME, BLOB_EXTENSION
from openvino.tools.benchmark.utils.inputs_filling import set_inputs
from openvino.tools.benchmark.utils.inputs_filling import get_input_data
from openvino.tools.benchmark.utils.logging import logger
from openvino.tools.benchmark.utils.progress_bar import ProgressBar
from openvino.tools.benchmark.utils.utils import next_step, get_number_iterations, process_precision, \
from openvino.tools.benchmark.utils.utils import next_step, get_number_iterations, pre_post_processing, \
process_help_inference_string, print_perf_counters, dump_exec_graph, get_duration_in_milliseconds, \
get_command_line_arguments, parse_nstreams_value_per_device, parse_devices, get_inputs_info, \
print_inputs_and_outputs_info, get_batch_size, load_config, dump_config
print_inputs_and_outputs_info, get_batch_size, load_config, dump_config, get_latency_groups, \
check_for_static
from openvino.tools.benchmark.utils.statistics_report import StatisticsReport, averageCntReport, detailedCntReport
@@ -55,13 +56,13 @@ def run(args):
if ext == BLOB_EXTENSION:
is_network_compiled = True
print("Network is compiled")
print("Model is compiled")
# ------------------------------ 2. Loading Inference Engine ---------------------------------------------------
next_step(step_id=2)
benchmark = Benchmark(args.target_device, args.number_infer_requests,
args.number_iterations, args.time, args.api_type)
args.number_iterations, args.time, args.api_type, args.inference_only)
## CPU (MKLDNN) extensions
if CPU_DEVICE_NAME in device_name and args.path_to_extension:
@@ -134,7 +135,7 @@ def run(args):
key = get_device_type_from_name(device) + "_THROUGHPUT_STREAMS"
if device in device_number_streams.keys():
## set to user defined value
supported_config_keys = benchmark.ie.get_metric(device, 'SUPPORTED_CONFIG_KEYS')
supported_config_keys = benchmark.core.get_metric(device, 'SUPPORTED_CONFIG_KEYS')
if key not in supported_config_keys:
raise Exception(f"Device {device} doesn't support config key '{key}'! " +
"Please specify -nstreams for correct devices in format <dev1>:<nstreams1>,<dev2>:<nstreams2>")
@@ -188,7 +189,7 @@ def run(args):
if args.number_threads and is_flag_set_in_command_line("nthreads"):
config[device]['GNA_LIB_N_THREADS'] = str(args.number_threads)
else:
supported_config_keys = benchmark.ie.get_metric(device, 'SUPPORTED_CONFIG_KEYS')
supported_config_keys = benchmark.core.get_metric(device, 'SUPPORTED_CONFIG_KEYS')
if 'CPU_THREADS_NUM' in supported_config_keys and args.number_threads and is_flag_set_in_command_line("nthreads"):
config[device]['CPU_THREADS_NUM'] = str(args.number_threads)
if 'CPU_THROUGHPUT_STREAMS' in supported_config_keys and args.number_streams and is_flag_set_in_command_line("streams"):
@@ -198,7 +199,6 @@ def run(args):
perf_counts = perf_counts
benchmark.set_config(config)
batch_size = args.batch_size
if args.cache_dir:
benchmark.set_cache_dir(args.cache_dir)
@@ -206,36 +206,37 @@ def run(args):
load_from_file_enabled = is_flag_set_in_command_line('load_from_file') or is_flag_set_in_command_line('lfile')
if load_from_file_enabled and not is_network_compiled:
next_step()
print("Skipping the step for loading network from file")
print("Skipping the step for loading model from file")
next_step()
print("Skipping the step for loading network from file")
print("Skipping the step for loading model from file")
next_step()
print("Skipping the step for loading network from file")
print("Skipping the step for loading model from file")
# --------------------- 7. Loading the model to the device -------------------------------------------------
next_step()
start_time = datetime.utcnow()
exe_network = benchmark.load_network(args.path_to_model)
exe_network = benchmark.core.compile_model(args.path_to_model)
duration_ms = f"{(datetime.utcnow() - start_time).total_seconds() * 1000:.2f}"
logger.info(f"Load network took {duration_ms} ms")
logger.info(f"Compile model took {duration_ms} ms")
if statistics:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
('load network time (ms)', duration_ms)
])
app_inputs_info, _ = get_inputs_info(args.shape, args.layout, args.batch_size, args.input_scale, args.input_mean, exe_network.input_info)
if batch_size == 0:
batch_size = 1
app_inputs_info, _ = get_inputs_info(args.shape, args.data_shape, args.layout, args.batch_size, args.input_scale, args.input_mean, exe_network.get_runtime_function().get_parameters())
batch_size = get_batch_size(app_inputs_info)
if batch_size.is_dynamic and benchmark.api_type == 'sync':
raise Exception("Dynamic batch size is supported only in async mode")
elif not is_network_compiled:
# --------------------- 4. Read the Intermediate Representation of the network -----------------------------
next_step()
start_time = datetime.utcnow()
ie_network = benchmark.read_network(args.path_to_model)
topology_name = ie_network.name
function = benchmark.read_model(args.path_to_model)
topology_name = function.get_name()
duration_ms = f"{(datetime.utcnow() - start_time).total_seconds() * 1000:.2f}"
logger.info(f"Read network took {duration_ms} ms")
logger.info(f"Read model took {duration_ms} ms")
if statistics:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
@@ -245,15 +246,15 @@ def run(args):
# --------------------- 5. Resizing network to match image sizes and given batch ---------------------------
next_step()
app_inputs_info, reshape = get_inputs_info(args.shape, args.layout, args.batch_size, args.input_scale, args.input_mean, ie_network.input_info)
app_inputs_info, reshape = get_inputs_info(args.shape, args.data_shape, args.layout, args.batch_size, args.input_scale, args.input_mean, function.get_parameters())
if reshape:
start_time = datetime.utcnow()
shapes = { k : v.shape for k,v in app_inputs_info.items() }
shapes = { info.name : info.partial_shape for info in app_inputs_info }
logger.info(
'Reshaping network: {}'.format(', '.join("'{}': {}".format(k, v) for k, v in shapes.items())))
ie_network.reshape(shapes)
'Reshaping model: {}'.format(', '.join("'{}': {}".format(k, str(v)) for k, v in shapes.items())))
function.reshape(shapes)
duration_ms = f"{(datetime.utcnow() - start_time).total_seconds() * 1000:.2f}"
logger.info(f"Reshape network took {duration_ms} ms")
logger.info(f"Reshape model took {duration_ms} ms")
if statistics:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
@@ -261,23 +262,25 @@ def run(args):
])
# use batch size according to provided layout and shapes
batch_size = get_batch_size(app_inputs_info) if args.layout else ie_network.batch_size
batch_size = get_batch_size(app_inputs_info)
if batch_size.is_dynamic and benchmark.api_type == 'sync':
raise Exception("Dynamic batch size is supported only in async mode")
logger.info(f'Network batch size: {batch_size}')
# --------------------- 6. Configuring inputs and outputs of the model --------------------------------------------------
next_step()
process_precision(ie_network, app_inputs_info, args.input_precision, args.output_precision, args.input_output_precision)
print_inputs_and_outputs_info(ie_network)
pre_post_processing(function, app_inputs_info, args.input_precision, args.output_precision, args.input_output_precision)
print_inputs_and_outputs_info(function)
# --------------------- 7. Loading the model to the device -------------------------------------------------
next_step()
start_time = datetime.utcnow()
exe_network = benchmark.load_network(ie_network)
exe_network = benchmark.core.compile_model(function, benchmark.device)
duration_ms = f"{(datetime.utcnow() - start_time).total_seconds() * 1000:.2f}"
logger.info(f"Load network took {duration_ms} ms")
logger.info(f"Compile model took {duration_ms} ms")
if statistics:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
@@ -295,24 +298,26 @@ def run(args):
next_step()
start_time = datetime.utcnow()
exe_network = benchmark.import_network(args.path_to_model)
exe_network = benchmark.core.import_model(args.path_to_model)
duration_ms = f"{(datetime.utcnow() - start_time).total_seconds() * 1000:.2f}"
logger.info(f"Import network took {duration_ms} ms")
logger.info(f"Import model took {duration_ms} ms")
if statistics:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
('import network time (ms)', duration_ms)
])
app_inputs_info, _ = get_inputs_info(args.shape, args.layout, args.batch_size, args.input_scale, args.input_mean, exe_network.input_info)
if batch_size == 0:
batch_size = 1
app_inputs_info, _ = get_inputs_info(args.shape, args.data_shape, args.layout, args.batch_size, args.input_scale, args.input_mean, exe_network.get_runtime_function().get_parameters())
batch_size = get_batch_size(app_inputs_info)
if batch_size.is_dynamic and benchmark.api_type == 'sync':
raise Exception("Dynamic batch size is supported only in async mode")
# --------------------- 8. Querying optimal runtime parameters --------------------------------------------------
next_step()
if is_flag_set_in_command_line('hint'):
## actual device-deduced settings for the hint
for device in devices:
keys = benchmark.ie.get_metric(device, 'SUPPORTED_CONFIG_KEYS')
keys = benchmark.core.get_metric(device, 'SUPPORTED_CONFIG_KEYS')
logger.info(f'DEVICE: {device}')
for k in keys:
logger.info(f' {k} , {exe_network.get_config(k)}')
@@ -320,17 +325,23 @@ def run(args):
# Update number of streams
for device in device_number_streams.keys():
key = get_device_type_from_name(device) + '_THROUGHPUT_STREAMS'
device_number_streams[device] = benchmark.ie.get_config(device, key)
device_number_streams[device] = benchmark.core.get_config(device, key)
# Number of requests
infer_requests = exe_network.requests
# Iteration limit
benchmark.niter = get_number_iterations(benchmark.niter, benchmark.nireq, args.api_type)
# ------------------------------------ 9. Creating infer requests and filling input blobs ----------------------
# ------------------------------------ 9. Creating infer requests and preparing input data ----------------------
next_step()
# Create infer requests
start_time = datetime.utcnow()
requests = benchmark.create_infer_requests(exe_network)
duration_ms = f"{(datetime.utcnow() - start_time).total_seconds() * 1000:.2f}"
logger.info(f"Create {benchmark.nireq} infer requests took {duration_ms} ms")
if statistics:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
('create infer requests time (ms)', duration_ms)
])
# Prepare input data
paths_to_input = list()
if args.paths_to_input:
for path in args.paths_to_input:
@@ -338,7 +349,39 @@ def run(args):
paths_to_input.extend(path)
else:
paths_to_input.append(os.path.abspath(*path))
set_inputs(paths_to_input, batch_size, app_inputs_info, infer_requests)
data_queue = get_input_data(paths_to_input, app_inputs_info)
static_mode = check_for_static(app_inputs_info)
if not static_mode and benchmark.api_type == 'sync':
raise Exception("Benchmarking of the model with dynamic shapes is available for async API only."
"Please use -api async -nstreams 1 -nireq 1 to emulate sync behavior.")
if benchmark.inference_only == None:
if static_mode:
benchmark.inference_only = True
else:
benchmark.inference_only = False
elif benchmark.inference_only and not static_mode:
raise Exception("Benchmarking dynamic model available with input filling in measurement loop only!")
benchmark.latency_groups = get_latency_groups(app_inputs_info)
if len(benchmark.latency_groups) > 1:
logger.info(f"Defined {len(benchmark.latency_groups)} tensor groups:")
for group in benchmark.latency_groups:
print(f"\t{str(group)}")
# Iteration limit
benchmark.niter = get_number_iterations(benchmark.niter, benchmark.nireq, max(len(info.shapes) for info in app_inputs_info), benchmark.api_type)
# Set input tensors before first inference
for request in requests:
data_tensors = data_queue.get_next_input()
for port, data_tensor in data_tensors.items():
input_tensor = request.get_input_tensor(port)
input_tensor.shape = data_tensor.shape
input_tensor.data[:] = data_tensor.data
if statistics:
statistics.add_parameters(StatisticsReport.Category.RUNTIME_CONFIG,
@@ -346,9 +389,10 @@ def run(args):
('topology', topology_name),
('target device', device_name),
('API', args.api_type),
('inference_only', benchmark.inference_only),
('precision', "UNSPECIFIED"),
('batch size', str(batch_size)),
('number of iterations', str(benchmark.niter) if benchmark.niter else "0"),
('number of iterations', str(benchmark.niter)),
('number of parallel infer requests', str(benchmark.nireq)),
('duration (ms)', str(get_duration_in_milliseconds(benchmark.duration_seconds))),
])
@@ -370,14 +414,19 @@ def run(args):
progress_bar = ProgressBar(progress_bar_total_count, args.stream_output, args.progress) if args.progress else None
duration_ms = f"{benchmark.first_infer(exe_network):.2f}"
duration_ms = f"{benchmark.first_infer(requests):.2f}"
logger.info(f"First inference took {duration_ms} ms")
if statistics:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
('first inference time (ms)', duration_ms)
])
fps, latency_ms, total_duration_sec, iteration = benchmark.infer(exe_network, batch_size, args.latency_percentile, progress_bar)
pcseq = args.pcseq
if static_mode or len(benchmark.latency_groups) == 1:
pcseq = False
fps, median_latency_ms, avg_latency_ms, min_latency_ms, max_latency_ms, total_duration_sec, iteration = benchmark.main_loop(requests, data_queue, batch_size, args.latency_percentile, progress_bar, pcseq)
# ------------------------------------ 11. Dumping statistics report -------------------------------------------
next_step()
@@ -391,12 +440,12 @@ def run(args):
if perf_counts:
perfs_count_list = []
for ni in range(int(benchmark.nireq)):
perfs_count_list.append(exe_network.requests[ni].get_perf_counts())
for request in requests:
perfs_count_list.append(request.profiling_info)
if args.perf_counts:
print_perf_counters(perfs_count_list)
if statistics:
statistics.dump_performance_counters(perfs_count_list)
statistics.dump_performance_counters(perfs_count_list)
if statistics:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
@@ -405,30 +454,74 @@ def run(args):
('total number of iterations', str(iteration)),
])
if MULTI_DEVICE_NAME not in device_name:
if args.latency_percentile == 50:
latency_prefix = None
if args.latency_percentile == 50 and static_mode:
#latency_prefix = 'median latency (ms)'
latency_prefix = 'latency (ms)'
else:
latency_prefix = 'latency (' + args.latency_percentile + ' percentile) (ms)'
elif args.latency_percentile != 50:
latency_prefix = 'latency (' + str(args.latency_percentile) + ' percentile) (ms)'
if latency_prefix:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
(latency_prefix, f'{median_latency_ms:.2f}'),
])
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
(latency_prefix, f'{latency_ms:.2f}'),
("avg latency", f'{avg_latency_ms:.2f}'),
])
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
("min latency", f'{min_latency_ms:.2f}'),
])
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
("max latency", f'{max_latency_ms:.2f}'),
])
if pcseq:
for group in benchmark.latency_groups:
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
("group", str(group)),
])
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
("avg latency", f'{group.avg:.2f}'),
])
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
("min latency", f'{group.min:.2f}'),
])
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
("max latency", f'{group.max:.2f}'),
])
statistics.add_parameters(StatisticsReport.Category.EXECUTION_RESULTS,
[
('throughput', f'{fps:.2f}'),
])
statistics.dump()
if statistics:
statistics.dump()
print(f'Count: {iteration} iterations')
print(f'Duration: {get_duration_in_milliseconds(total_duration_sec):.2f} ms')
if MULTI_DEVICE_NAME not in device_name:
if args.latency_percentile == 50:
print(f'Latency: {latency_ms:.2f} ms')
else:
print(f'Latency ({args.latency_percentile} percentile): {latency_ms:.2f} ms')
print('Latency:')
if args.latency_percentile == 50 and static_mode:
print(f'Median: {median_latency_ms:.2f} ms')
elif args.latency_percentile != 50:
print(f'({args.latency_percentile} percentile): {median_latency_ms:.2f} ms')
print(f'AVG: {avg_latency_ms:.2f} ms')
print(f'MIN: {min_latency_ms:.2f} ms')
print(f'MAX: {max_latency_ms:.2f} ms')
if pcseq:
print("Latency for each data shape group: ")
for group in benchmark.latency_groups:
print(f"{str(group)}")
print(f'AVG: {group.avg:.2f} ms')
print(f'MIN: {group.min:.2f} ms')
print(f'MAX: {group.max:.2f} ms')
print(f'Throughput: {fps:.2f} FPS')
del exe_network
@@ -35,7 +35,7 @@ def parse_args():
help='Optional. '
'Path to a folder with images and/or binaries or to specific image or binary file.'
'It is also allowed to map files to network inputs: '
'input_1:file_1,file_2,input_4:file_4 input_2:file_3')
'input_1:file_1/dir1,file_2/dir2,input_4:file_4/dir4 input_2:file_3/dir3')
args.add_argument('-m', '--path_to_model', type=str, required=True,
help='Required. Path to an .xml/.onnx file with a trained model or '
'to a .blob file with a trained compiled model.')
@@ -60,9 +60,9 @@ def parse_args():
args.add_argument('-niter', '--number_iterations', type=check_positive, required=False, default=None,
help='Optional. Number of iterations. '
'If not specified, the number of iterations is calculated depending on a device.')
args.add_argument('-nireq', '--number_infer_requests', type=check_positive, required=False, default=None,
args.add_argument('-nireq', '--number_infer_requests', type=check_positive, required=False, default=0,
help='Optional. Number of infer requests. Default value is determined automatically for device.')
args.add_argument('-b', '--batch_size', type=int, required=False, default=0,
args.add_argument('-b', '--batch_size', type=str, required=False, default='',
help='Optional. ' +
'Batch size value. ' +
'If not specified, the batch size value is determined from Intermediate Representation')
@@ -77,7 +77,13 @@ def parse_args():
'Show progress bar (can affect performance measurement). Default values is \'False\'.')
args.add_argument('-shape', type=str, required=False, default='',
help='Optional. '
'Set shape for input. For example, "input1[1,3,224,224],input2[1,4]" or "[1,3,224,224]" in case of one input size.')
'Set shape for input. For example, "input1[1,3,224,224],input2[1,4]" or "[1,3,224,224]" in case of one input size.'
'This parameter affect model Parameter shape, can be dynamic. For dynamic dimesions use symbol `?`, `-1` or range `low.. up`.')
args.add_argument('-data_shape', type=str, required=False, default='',
help='Optional. '
'Optional if network shapes are all static (original ones or set by -shape).'
'Required if at least one input shape is dynamic and input images are not provided.'
'Set shape for input tensors. For example, "input1[1,3,224,224],input2[1,4]" or "[1,3,224,224] in case of one input size.')
args.add_argument('-layout', type=str, required=False, default='',
help='Optional. '
'Prompts how network layouts should be treated by application. '
@@ -110,6 +116,11 @@ def parse_args():
help='Optional. Path to a file where to store executable graph information serialized.')
args.add_argument('-pc', '--perf_counts', type=str2bool, required=False, default=False, nargs='?', const=True,
help='Optional. Report performance counters.', )
args.add_argument('-pcseq', '--pcseq', type=str2bool, required=False, default=False, nargs='?', const=True,
help='Optional. Report latencies for each shape in -data_shape sequence.', )
args.add_argument('-inference_only', '--inference_only', type=str2bool, required=False, default=None, nargs='?', const=True,
help='Optional. If true inputs filling only once before measurements (default for static models), '
'else inputs filling is included into loop measurement (default for dynamic models)', )
args.add_argument('-report_type', '--report_type', type=str, required=False,
choices=['no_counters', 'average_counters', 'detailed_counters'],
help="Optional. Enable collecting statistics report. \"no_counters\" report contains "
@@ -15,8 +15,8 @@ XML_EXTENSION = '.xml'
BIN_EXTENSION = '.bin'
BLOB_EXTENSION = '.blob'
IMAGE_EXTENSIONS = ['JPEG', 'JPG', 'PNG', 'BMP']
BINARY_EXTENSIONS = ['BIN']
IMAGE_EXTENSIONS = ['.jpeg', '.jpg', '.png', '.bmp']
BINARY_EXTENSIONS = ['.bin']
DEVICE_DURATION_IN_SECS = {
CPU_DEVICE_NAME: 60,
@@ -5,333 +5,374 @@ import os
import cv2
import re
import numpy as np
from glob import glob
from collections import defaultdict
from pathlib import Path
from itertools import chain
from openvino.runtime import Tensor, PartialShape
from .constants import IMAGE_EXTENSIONS, BINARY_EXTENSIONS
from .logging import logger
def set_inputs(paths_to_input, batch_size, app_input_info, requests):
requests_input_data = get_inputs(paths_to_input, batch_size, app_input_info, requests)
for i in range(len(requests)):
inputs = requests[i].input_blobs
for k, v in requests_input_data[i].items():
if k not in inputs.keys():
raise Exception(f"No input with name {k} found!")
inputs[k].buffer[:] = v
class DataQueue:
def __init__(self, input_data: dict, batch_sizes: list):
self.input_data = input_data
self.sizes_map = {}
for name, tensors in input_data.items():
self.sizes_map[name] = len(tensors)
self.index_map = defaultdict.fromkeys(input_data.keys(), 0)
self.batch_sizes = batch_sizes
self.size = len(batch_sizes)
self.current_group_id = 0
def get_next_input(self):
data = {}
for input_name, input_tensors in self.input_data.items():
data[input_name] = input_tensors[self.index_map[input_name]]
self.index_map[input_name] = (self.index_map[input_name] + 1) % self.sizes_map[input_name]
self.current_group_id = (self.current_group_id + 1) % self.size
return data
def get_next_batch_size(self):
return self.batch_sizes[self.current_group_id]
def get_inputs(paths_to_input, batch_size, app_input_info, requests):
input_file_mapping = parse_paths_to_input(paths_to_input)
check_input_file_mapping(input_file_mapping, app_input_info)
input_image_sizes = {}
for key in sorted(app_input_info.keys()):
info = app_input_info[key]
if info.is_image:
input_image_sizes[key] = (info.width, info.height)
images_count = len(input_image_sizes.keys())
binaries_count = len(app_input_info) - images_count
image_files = list()
binary_files = list()
if paths_to_input and not input_file_mapping:
image_files = get_files_by_extensions(paths_to_input, IMAGE_EXTENSIONS)
binary_files = get_files_by_extensions(paths_to_input, BINARY_EXTENSIONS)
if input_file_mapping and len(input_file_mapping) < len(app_input_info):
not_provided_inputs = set(app_input_info) - set(input_file_mapping)
logger.warning("No input files were given for the inputs: "
f"{', '.join(not_provided_inputs)}. This inputs will be filled with random values!")
elif (len(image_files) == 0) and (len(binary_files) == 0):
logger.warning("No input files were given: all inputs will be filled with random values!")
else:
binary_to_be_used = binaries_count * batch_size * len(requests)
if binary_to_be_used > 0 and len(binary_files) == 0:
logger.warning(f"No supported binary inputs found! "
f"Please check your file extensions: {','.join(BINARY_EXTENSIONS)}")
elif binary_to_be_used > len(binary_files):
logger.warning(
f"Some binary input files will be duplicated: "
f"{binary_to_be_used} files are required, "
f"but only {len(binary_files)} were provided")
elif binary_to_be_used < len(binary_files):
logger.warning(
f"Some binary input files will be ignored: only {binary_to_be_used} "
f"files are required from {len(binary_files)}")
images_to_be_used = images_count * batch_size * len(requests)
if images_to_be_used > 0 and len(image_files) == 0:
logger.warning(f"No supported image inputs found! Please check your "
f"file extensions: {','.join(IMAGE_EXTENSIONS)}")
elif images_to_be_used > len(image_files):
logger.warning(
f"Some image input files will be duplicated: {images_to_be_used} "
f"files are required, but only {len(image_files)} were provided")
elif images_to_be_used < len(image_files):
logger.warning(
f"Some image input files will be ignored: only {images_to_be_used} "
f"files are required from {len(image_files)}")
requests_input_data = []
for request_id in range(0, len(requests)):
logger.info(f"Infer Request {request_id} filling")
input_data = {}
keys = list(sorted(app_input_info.keys()))
for key in keys:
info = app_input_info[key]
if info.is_image:
# input is image
if key in input_file_mapping:
input_data[key] = fill_blob_with_image(input_file_mapping[key], request_id, batch_size,
keys.index(key), len(keys), info, from_map=True)
continue
if len(image_files) > 0:
input_data[key] = fill_blob_with_image(image_files, request_id, batch_size, keys.index(key),
len(keys), info)
continue
# input is binary
if len(binary_files) or key in input_file_mapping:
if key in input_file_mapping:
input_data[key] = fill_blob_with_binary(input_file_mapping[key], request_id, batch_size,
keys.index(key), len(keys), info, from_map=True)
continue
input_data[key] = fill_blob_with_binary(binary_files, request_id, batch_size, keys.index(key),
len(keys), info)
continue
# most likely input is image info
if info.is_image_info and len(input_image_sizes) == 1:
image_size = input_image_sizes[list(input_image_sizes.keys()).pop()]
logger.info("Fill input '" + key + "' with image size " + str(image_size[0]) + "x" +
str(image_size[1]))
input_data[key] = fill_blob_with_image_info(image_size, info)
continue
# fill with random data
logger.info(f"Fill input '{key}' with random values "
f"({'image' if info.is_image else 'some binary data'} is expected)")
input_data[key] = fill_blob_with_random(info)
requests_input_data.append(input_data)
return requests_input_data
def get_group_batch_sizes(app_input_info):
batch_sizes = []
niter = max(len(info.shapes) for info in app_input_info)
for i in range(niter):
batch_size = 0
for info in app_input_info:
batch_index = info.layout.get_index_by_name('N') if info.layout.has_name('N') else -1
if batch_index != -1:
shape = info.shapes[i % len(info.shapes)]
if batch_size == 0:
batch_size = shape[batch_index]
elif batch_size != shape[batch_index]:
raise Exception("Can't deterimine batch size: batch is different for different inputs!")
if batch_size == 0:
batch_size = 1
batch_sizes.append(batch_size)
return batch_sizes
def get_files_by_extensions(paths_to_input, extensions):
if len(paths_to_input) == 1:
files = [file for file in paths_to_input[0].split(",") if file]
if all(get_extension(file) in extensions for file in files):
check_files_exist(files)
return files
return get_files_by_extensions_for_directory_or_list_of_files(paths_to_input, extensions)
def get_files_by_extensions_for_directory_or_list_of_files(paths_to_input, extensions):
input_files = list()
for path_to_input in paths_to_input:
if os.path.isfile(path_to_input):
files = [os.path.normpath(path_to_input)]
def get_batch_sizes_per_input_map(app_input_info):
batch_sizes_map = {}
for info in app_input_info:
if info.layout.has_name('N'):
if info.is_dynamic:
batch_sizes_map[info.name] = info.getDimentionsByLayout('N')
else:
batch_sizes_map[info.name] = [len(info.getDimentionByLayout('N'))]
else:
path = os.path.join(path_to_input, '*')
files = glob(path, recursive=True)
for file in files:
file_extension = get_extension(file)
if file_extension in extensions:
input_files.append(file)
input_files.sort()
return input_files
batch_sizes_map[info.name] = [1] * len(info.shapes)
return batch_sizes_map
def get_extension(file_path):
return file_path.split(".")[-1].upper()
def get_input_data(paths_to_input, app_input_info):
image_mapping, binary_mapping = get_input_file_mappings(paths_to_input, app_input_info)
image_sizes = get_image_sizes(app_input_info)
batch_sizes_map = get_batch_sizes_per_input_map(app_input_info)
images_to_be_used_map = {input_name: len(images) for input_name, images in image_mapping.items()}
binaries_to_be_used_map = {input_name: len(binaries) for input_name, binaries in binary_mapping.items()}
for info in app_input_info:
if info.shapes:
total_frames = np.sum(batch_sizes_map[info.name])
if info.name in image_mapping:
if images_to_be_used_map[info.name] > total_frames and images_to_be_used_map[info.name] % total_frames != 0:
images_to_be_used_map[info.name] = images_to_be_used_map[info.name] - images_to_be_used_map[info.name] % total_frames
logger.warning(f"Number of provided images for input '{info.name}' is not a multiple of the number of "
f"provided data shapes. Only {images_to_be_used_map[info.name]} images will be processed for this input.")
elif images_to_be_used_map[info.name] < total_frames:
logger.warning(f"Some images will be dublicated: {total_frames} is required, "
f"but only {images_to_be_used_map[info.name]} were provided.")
elif info.name in binary_mapping:
if binaries_to_be_used_map[info.name] > total_frames and binaries_to_be_used_map[info.name] % total_frames != 0:
binaries_to_be_used_map[info.name] = binaries_to_be_used_map - binaries_to_be_used_map % total_frames
logger.warning(f"Number of provided binaries for input '{info.name}' is not a multiple of the number of "
f"provided data shapes. Only {binaries_to_be_used_map[info.name]} binaries will be processed for this input.")
elif binaries_to_be_used_map[info.name] < total_frames:
logger.warning(f"Some binaries will be dublicated: {total_frames} is required, "
f"but only {images_to_be_used_map[info.name]} were provided.")
else:
logger.warning(f"No input files were given for input '{info.name}'!. This input will be filled with random values!")
else:
if info.name in image_mapping:
logger.info(f"Images given for input '{info.name}' will be processed with original shapes.")
else:
raise Exception(f"Input {info.name} is dynamic. Provide data shapes!")
data = {}
for port, info in enumerate(app_input_info):
if info.name in image_mapping:
data[port] = get_image_tensors(image_mapping[info.name][:images_to_be_used_map[info.name]], info, batch_sizes_map[info.name])
elif info.name in binary_mapping:
data[port] = get_binary_tensors(binary_mapping[info.name][:binaries_to_be_used_map[info.name]], info, batch_sizes_map[info.name])
elif info.is_image_info and len(image_sizes) == 1:
image_size = image_sizes[0]
logger.info(f"Create input tensors for input '{info.name}' with image sizes: {image_size}")
data[port] = get_image_info_tensors(image_size, info)
else:
logger.info(f"Fill input '{info.name}' with random values ")
data[port] = fill_tensors_with_random(info)
return DataQueue(data, get_group_batch_sizes(app_input_info))
def fill_blob_with_image(image_paths, request_id, batch_size, input_id, input_size, info, from_map=False):
shape = info.shape
images = np.ndarray(shape)
if from_map:
image_index = request_id * batch_size
else:
image_index = request_id * batch_size * input_size + input_id
def get_image_tensors(image_paths, info, batch_sizes):
processed_frames = 0
widthes = info.widthes if info.is_dynamic else [info.width]
heights = info.heights if info.is_dynamic else [info.height]
tensors = []
process_with_original_shapes = False
num_shapes = len(info.shapes)
if num_shapes == 0:
process_with_original_shapes = True
num_images = len(image_paths)
niter = max(num_shapes, num_images)
for i in range(niter):
shape = list(info.shapes[i % num_shapes]) if num_shapes else []
dtype = get_dtype(info.element_type.get_type_name())[0]
images = np.ndarray(shape=shape, dtype=dtype)
image_index = processed_frames
current_batch_size = 1 if process_with_original_shapes else batch_sizes[i % num_shapes]
for b in range(current_batch_size):
image_index %= num_images
image_filename = image_paths[image_index]
logger.info(f'Prepare image {image_filename}')
image = cv2.imread(image_filename)
if process_with_original_shapes:
logger.info(f'Image will be processed with original shape - {image.shape[:-1]}')
elif info.layout.has_name('H') and info.layout.has_name('W'):
new_im_size = (widthes[i % num_shapes], heights[i % num_shapes])
if image.shape[:-1] != new_im_size:
logger.warning(f"Image is resized from ({image.shape[:-1]}) to ({new_im_size})")
image = cv2.resize(image, new_im_size)
scale_mean = (not np.array_equal(info.scale, (1.0, 1.0, 1.0)) or not np.array_equal(info.mean, (0.0, 0.0, 0.0)))
if info.scale or info.mean:
blue, green, red = cv2.split(image)
if info.mean:
blue = np.subtract(blue, info.mean[0])
green = np.subtract(green, info.mean[1])
red = np.subtract(red, info.mean[2])
if info.scale:
blue = np.divide(blue, info.scale[0])
green = np.divide(green, info.scale[1])
red = np.divide(red, info.scale[2])
image = cv2.merge([blue, green, red])
for b in range(batch_size):
image_index %= len(image_paths)
image_filename = image_paths[image_index]
logger.info(f'Prepare image {image_filename}')
image = cv2.imread(image_filename)
new_im_size = tuple((info.width, info.height))
if image.shape[:-1] != new_im_size:
logger.warning(f"Image is resized from ({image.shape[:-1]}) to ({new_im_size})")
image = cv2.resize(image, new_im_size)
if str(info.layout) in ['[N,C,H,W]', '[C,H,W]']:
image = image.transpose((2, 0, 1))
if scale_mean:
blue, green, red = cv2.split(image)
blue = np.subtract(blue, info.mean[0])
blue = np.divide(blue, info.scale[0])
green = np.subtract(green, info.mean[1])
green = np.divide(green, info.scale[1])
red = np.subtract(red, info.mean[2])
red = np.divide(red, info.scale[2])
image = cv2.merge([blue, green, red])
if process_with_original_shapes:
if len(info.partial_shape) == 4:
image = np.expand_dims(image, 0)
p_shape = PartialShape(image.shape)
if info.partial_shape.compatible(p_shape):
info.data_shapes.append(p_shape.to_shape())
else:
raise Exception(f"Data shape '{str(p_shape)}' provided for input '{info.name}' "
f"is not compatible with partial shape '{str(info.partial_shape)}' for this input.")
tensors.append(Tensor(image.astype(dtype)))
else:
try:
images[b] = image
except ValueError:
#raise Exception(f"Image shape {image.shape} is not compatible with input shape {shape}. "
#f"Try to provide layout for input '{info.name}'.")
# backward compatibility
# will be removed
logger.warning(f"Image shape {image.shape} is not compatible with input shape {shape}. "
f"Input '{info.name}' will be filled with random values!")
return fill_tensors_with_random(info)
if info.layout in ['NCHW', 'CHW']:
image = image.transpose((2, 0, 1))
images[b] = image
if from_map:
image_index += 1
else:
image_index += input_size
return images
processed_frames += current_batch_size
if not process_with_original_shapes:
tensors.append(Tensor(images))
return tensors
def get_dtype(precision):
format_map = {
'FP32' : (np.float32, np.finfo(np.float32).min, np.finfo(np.float32).max),
'I32' : (np.int32, np.iinfo(np.int32).min, np.iinfo(np.int32).max),
'I64' : (np.int64, np.iinfo(np.int64).min, np.iinfo(np.int64).max),
'FP16' : (np.float16, np.finfo(np.float16).min, np.finfo(np.float16).max),
'I16' : (np.int16, np.iinfo(np.int16).min, np.iinfo(np.int16).max),
'U16' : (np.uint16, np.iinfo(np.uint16).min, np.iinfo(np.uint16).max),
'I8' : (np.int8, np.iinfo(np.int8).min, np.iinfo(np.int8).max),
'U8' : (np.uint8, np.iinfo(np.uint8).min, np.iinfo(np.uint8).max),
'BOOL' : (np.uint8, 0, 1),
'f32' : (np.float32, np.finfo(np.float32).min, np.finfo(np.float32).max),
'i32' : (np.int32, np.iinfo(np.int32).min, np.iinfo(np.int32).max),
'i64' : (np.int64, np.iinfo(np.int64).min, np.iinfo(np.int64).max),
'fp16' : (np.float16, np.finfo(np.float16).min, np.finfo(np.float16).max),
'i16' : (np.int16, np.iinfo(np.int16).min, np.iinfo(np.int16).max),
'u16' : (np.uint16, np.iinfo(np.uint16).min, np.iinfo(np.uint16).max),
'i8' : (np.int8, np.iinfo(np.int8).min, np.iinfo(np.int8).max),
'u8' : (np.uint8, np.iinfo(np.uint8).min, np.iinfo(np.uint8).max),
'boolean' : (np.uint8, 0, 1),
}
if precision in format_map.keys():
return format_map[precision]
raise Exception("Can't find data type for precision: " + precision)
def fill_blob_with_binary(binary_paths, request_id, batch_size, input_id, input_size, info, from_map=False):
binaries = np.ndarray(info.shape)
shape = info.shape.copy()
if 'N' in info.layout:
shape[info.layout.index('N')] = 1
if from_map:
binary_index = request_id * batch_size
else:
binary_index = request_id * batch_size * input_size + input_id
dtype = get_dtype(info.precision)[0]
for b in range(batch_size):
binary_index %= len(binary_paths)
binary_filename = binary_paths[binary_index]
logger.info("Prepare binary file " + binary_filename)
binary_file_size = os.path.getsize(binary_filename)
blob_size = dtype().nbytes * int(np.prod(shape))
if blob_size != binary_file_size:
raise Exception(
f"File {binary_filename} contains {binary_file_size} bytes but network expects {blob_size}")
binaries[b] = np.reshape(np.fromfile(binary_filename, dtype), shape)
def get_binary_tensors(binary_paths, info, batch_sizes):
num_shapes = len(info.shapes)
num_binaries = len(binary_paths)
niter = max(num_shapes, num_binaries)
processed_frames = 0
tensors = []
for i in range(niter):
shape_id = i % num_shapes
dtype = get_dtype(info.element_type.get_type_name())[0]
shape = list(info.shapes[shape_id])
binaries = np.ndarray(shape=shape, dtype=dtype)
if info.layout.has_name('N'):
shape[info.layout.get_index_by_name('N')] = 1
binary_index = processed_frames
current_batch_size = batch_sizes[shape_id]
for b in range(current_batch_size):
binary_index %= num_binaries
binary_filename = binary_paths[binary_index]
logger.info("Prepare binary file " + binary_filename)
binary_file_size = os.path.getsize(binary_filename)
blob_size = dtype().nbytes * int(np.prod(shape))
if blob_size != binary_file_size:
raise Exception(
f"File {binary_filename} contains {binary_file_size} bytes but network expects {blob_size}")
binaries[b] = np.reshape(np.fromfile(binary_filename, dtype), shape)
if from_map:
binary_index += 1
else:
binary_index += input_size
return binaries
processed_frames += current_batch_size
tensors.append(Tensor(binaries))
return tensors
def fill_blob_with_image_info(image_size, layer):
shape = layer.shape
im_info = np.ndarray(shape)
for b in range(shape[0]):
for i in range(shape[1]):
im_info[b][i] = image_size[i] if i in [0, 1] else 1
def get_image_sizes(app_input_info):
image_sizes = []
for info in app_input_info:
if info.is_image:
if info.is_static:
image_sizes.append((info.width, info.height))
else:
info_image_sizes = []
for w, h in zip(info.widthes, info.heights):
info_image_sizes.append((w, h))
image_sizes.append(info_image_sizes)
return image_sizes
return im_info
def fill_blob_with_random(layer):
dtype, rand_min, rand_max = get_dtype(layer.precision)
def get_image_info_tensors(image_sizes, layer):
im_infos = []
for shape, image_size in zip(layer.shapes, image_sizes):
im_info = np.ndarray(shape, dtype=get_dtype(layer.element_type.get_type_name())[0])
for b in range(shape[0]):
for i in range(shape[1]):
im_info[b][i] = image_size if i in [0, 1] else 1
im_infos.append(Tensor(im_info))
return im_infos
def fill_tensors_with_random(layer):
dtype, rand_min, rand_max = get_dtype(layer.element_type.get_type_name())
# np.random.uniform excludes high: add 1 to have it generated
if np.dtype(dtype).kind in ['i', 'u', 'b']:
rand_max += 1
rs = np.random.RandomState(np.random.MT19937(np.random.SeedSequence(0)))
if layer.shape:
return rs.uniform(rand_min, rand_max, layer.shape).astype(dtype)
return (dtype)(rs.uniform(rand_min, rand_max))
input_tensors = []
for shape in layer.shapes:
if shape:
input_tensors.append(Tensor(rs.uniform(rand_min, rand_max, list(shape)).astype(dtype)))
else:
input_tensors.append(Tensor(rs.uniform(rand_min, rand_max)))
return input_tensors
def parse_paths_to_input(paths_to_inputs):
input_dicts_list = [parse_path(path) for path in paths_to_inputs]
inputs = defaultdict(list)
for input_dict in input_dicts_list:
for input_name, input_files in input_dict.items():
inputs[input_name] += input_files
return {input_: files for input_, files in inputs.items() if files}
def get_input_file_mappings(paths_to_inputs, app_input_info):
image_dicts_list = []
binary_dicts_list = []
for path in paths_to_inputs:
image_dict, binary_dict = parse_path(path, app_input_info)
image_dicts_list.append(image_dict)
binary_dicts_list.append(binary_dict)
def merge_dicts(dicts_list):
merged = defaultdict(list)
for dict in dicts_list:
for k,v in dict.items():
merged[k] += v
return merged
def remove_empty_items(dict):
return {k: sorted(v) for k,v in dict.items() if v}
return remove_empty_items(merge_dicts(image_dicts_list)), remove_empty_items(merge_dicts(binary_dicts_list))
def parse_path(path):
def parse_path(path, app_input_info):
"""
Parse "input_1:file1,file2,input_2:file3" into a dict
Parse "input_1:file1/dir1,file2/dir2,input_2:file3/dir3 or file1/dir1,file2/dir2" into two dicts - with binary files and with images
"""
inputs = re.findall(r"([^,]\w+):", path)
input_files = [file for file in re.split(r"[^,]\w+:", path) if file]
return {
input_: files.strip(",").split(",") for input_, files in zip(inputs, input_files)
}
def check_input_file_mapping(input_file_mapping, app_input_info):
check_inputs(app_input_info, input_file_mapping)
check_input_file_mapping_files_exists(input_file_mapping)
check_files_extensions(app_input_info, input_file_mapping)
def check_inputs(app_input_info, input_file_mapping):
wrong_inputs = [
input_ for input_ in input_file_mapping if input_ not in app_input_info
]
if wrong_inputs:
input_names = sorted(list(info.name for info in app_input_info))
parsed_names = re.findall(r"([^,]\w+):", path)
wrong_names = list(name for name in parsed_names if name not in input_names)
if wrong_names:
raise Exception(
f"Wrong input mapping! Cannot find inputs: {wrong_inputs}. "
f"Available inputs: {list(app_input_info)}. "
f"Wrong input mapping! Cannot find inputs: {wrong_names}. "
f"Available inputs: {input_names}. "
"Please check `-i` input data"
)
input_pathes = [path for path in re.split(r"[^,]\w+:", path) if path]
input_path_mapping = defaultdict(list)
# input mapping is used
if parsed_names:
input_path_mapping = {input_: files.strip(",").split(",") for input_, files in zip(parsed_names, input_pathes)}
else:
input_files = list()
_input_pathes = input_pathes[0].strip(",").split(",")
for _input_path in _input_pathes:
input_path = Path(_input_path)
if input_path.exists():
if input_path.is_dir():
input_files += list(str(file_path) for file_path in input_path.iterdir())
elif input_path.is_file:
input_files.append(str(input_path))
else:
raise Exception(f"Path '{str(input_path)}' doesn't exist \n {str(input_path)}")
num_files, num_inputs = len(input_files), len(app_input_info)
if num_inputs > 1:
logger.warning(f"Model has {num_inputs} inputs. It's recommended to use name mapping to specify parameters for each input.")
if num_files > num_inputs and num_files % num_inputs != 0:
input_files = input_files[:num_files - num_files % num_inputs]
logger.warning(f"Number of provided input files '{num_files}' is not a multiple of the number of "
f"model inputs. Only {len(input_files)} files fill be used.")
num_files = len(input_files)
for i in range(num_files):
input_path_mapping[input_names[i % num_inputs]].append(input_files[i])
def check_input_file_mapping_files_exists(input_file_mapping):
check_files_exist(chain.from_iterable(input_file_mapping.values()))
def check_files_exist(input_files_list):
not_files = [
file for file in input_files_list if not Path(file).is_file()
]
if not_files:
not_files = ",\n".join(not_files)
raise Exception(
f"Inputs are not files or does not exist!\n {not_files}"
)
def check_files_extensions(app_input_info, input_file_mapping):
unsupported_files = []
for input_, files in input_file_mapping.items():
info = app_input_info[input_]
proper_extentions = IMAGE_EXTENSIONS if info.is_image else BINARY_EXTENSIONS
unsupported = "\n".join(
[file for file in files if Path(file).suffix.upper().strip(".") not in proper_extentions]
)
if unsupported:
unsupported_files.append(unsupported)
images_mapping = defaultdict(list)
binary_mapping = defaultdict(list)
unsupported_files = list()
for input_name, _input_pathes in input_path_mapping.items():
for _input_path in _input_pathes:
input_path = Path(_input_path)
if input_path.exists():
files = list()
if input_path.is_dir():
files = input_path.iterdir()
elif input_path.is_file:
files = [input_path]
for file in files:
if file.suffix.lower() in IMAGE_EXTENSIONS:
images_mapping[input_name].append(str(file))
elif file.suffix.lower() in BINARY_EXTENSIONS:
binary_mapping[input_name].append(str(file))
else:
unsupported_files.append(str(file))
else:
raise Exception(f"Path for input '{input_name}' doesn't exist \n {str(input_path)}")
if unsupported_files:
unsupported_files = "\n".join(unsupported_files)
raise Exception(
f"This files has unsupported extensions: {unsupported_files}.\n"
f"Supported extentions:\nImages: {IMAGE_EXTENSIONS}\nBinary: {BINARY_EXTENSIONS}"
)
logger.warning(f"This files has unsupported extensions and will be ignored: {unsupported_files}.\n"
f"Supported extentions:\nImages: {IMAGE_EXTENSIONS}\nBinary: {BINARY_EXTENSIONS}")
return images_mapping, binary_mapping
@@ -1,6 +1,7 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from datetime import timedelta
import os
import sys
from enum import Enum
@@ -58,49 +59,50 @@ class StatisticsReport:
logger.info(f"Statistics report is stored to {f.name}")
def dump_performance_counters_request(self, f, perf_counts):
total = 0
total_cpu = 0
def dump_performance_counters_request(self, f, prof_info):
total = timedelta()
total_cpu = timedelta()
f.write(self.csv_separator.join(['layerName', 'execStatus', 'layerType', 'execType', 'realTime (ms)', 'cpuTime (ms)\n']))
for k, v in sorted(perf_counts.items(), key=lambda x: x[1]['execution_index']):
f.write(self.csv_separator.join([k, v['status'], v['layer_type'], v['exec_type'], str(v['real_time']/1000.0), str(v['cpu_time']/1000.0)]))
for pi in prof_info:
f.write(self.csv_separator.join([pi.node_name, str(pi.status), pi.node_type, pi.exec_type, str(pi.real_time/1000.0), str(pi.cpu_time/1000.0)]))
f.write('\n')
total += v['real_time']
total_cpu += v['cpu_time']
total += pi.real_time
total_cpu += pi.cpu_time
f.write(self.csv_separator.join(['Total','','','',str(total/1000.0),str(total_cpu/1000.0)]))
f.write('\n\n')
def dump_performance_counters(self, perf_counts):
def dump_performance_counters(self, prof_info_list):
if self.config.report_type == '' or self.config.report_type == noCntReport:
logger.info("Statistics collecting for performance counters was not requested. No reports are dumped.")
return
if not perf_counts:
if not prof_info_list:
logger.info('Performance counters are empty. No reports are dumped.')
return
filename = os.path.join(self.config.report_folder, f'benchmark_{self.config.report_type}_report.csv')
with open(filename, 'w') as f:
if self.config.report_type == detailedCntReport:
for pc in perf_counts:
self.dump_performance_counters_request(f, pc)
for prof_info in prof_info_list:
self.dump_performance_counters_request(f, prof_info)
elif self.config.report_type == averageCntReport:
def get_average_performance_counters(perf_counts):
performance_counters_avg = {}
def get_average_performance_counters(prof_info_list):
performance_counters_avg = []
## iterate over each processed infer request and handle its PM data
for i in range(0, len(perf_counts)):
## iterate over each layer from sorted vector and add required PM data to the per-layer maps
for k in perf_counts[0].keys():
if k not in performance_counters_avg.keys():
performance_counters_avg[k] = perf_counts[i][k]
for prof_info in prof_info_list:
for pi in prof_info:
item = next((x for x in performance_counters_avg if x.node_name == pi.node_name), None)
if item:
item.real_time += pi.real_time
item.cpu_time += pi.cpu_time
else:
performance_counters_avg[k]['real_time'] += perf_counts[i][k]['real_time']
performance_counters_avg[k]['cpu_time'] += perf_counts[i][k]['cpu_time']
for _, v in performance_counters_avg.items():
v['real_time'] /= len(perf_counts)
v['cpu_time'] /= len(perf_counts)
performance_counters_avg.append(pi)
for pi in performance_counters_avg:
pi.real_time /= len(prof_info_list)
pi.cpu_time /= len(prof_info_list)
return performance_counters_avg
self.dump_performance_counters_request(f, get_average_performance_counters(perf_counts))
self.dump_performance_counters_request(f, get_average_performance_counters(prof_info_list))
else:
raise Exception('PM data can only be collected for average or detailed report types')
@@ -1,7 +1,12 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from openvino.inference_engine import IENetwork,IECore
from collections import defaultdict
import datetime
from openvino.runtime import Core, Function, PartialShape, Dimension, Layout
from openvino.runtime.impl import Type
from openvino.preprocess import PrePostProcessor
from openvino.offline_transformations_pybind import serialize
from .constants import DEVICE_DURATION_IN_SECS, UNKNOWN_DEVICE_TYPE, \
CPU_DEVICE_NAME, GPU_DEVICE_NAME
@@ -31,7 +36,7 @@ def next_step(additional_info='', step_id=0):
6: "Configuring input of the model",
7: "Loading the model to the device",
8: "Querying optimal runtime parameters",
9: "Creating infer requests and filling input blobs with images",
9: "Creating infer requests and preparing input data",
10: "Measuring performance",
11: "Dumping statistics report",
}
@@ -48,52 +53,71 @@ def next_step(additional_info='', step_id=0):
step_info_template = step_info_template.format(next_step.step_id, len(step_names), step_name)
print(step_info_template)
def process_precision(ie_network: IENetwork, app_inputs_info, input_precision: str, output_precision: str, input_output_precision: str):
def get_element_type(precision):
format_map = {
'FP32' : Type.f32,
'I32' : Type.i32,
'I64' : Type.i64,
'FP16' : Type.f16,
'I16' : Type.i16,
'U16' : Type.u16,
'I8' : Type.i8,
'U8' : Type.u8,
'BOOL' : Type.boolean,
}
if precision in format_map.keys():
return format_map[precision]
raise Exception("Can't find openvino element type for precision: " + precision)
def pre_post_processing(function: Function, app_inputs_info, input_precision: str, output_precision: str, input_output_precision: str):
pre_post_processor = PrePostProcessor(function)
if input_precision:
_configure_network_inputs(ie_network, app_inputs_info, input_precision)
element_type = get_element_type(input_precision)
for i in range(len(function.inputs)):
pre_post_processor.input(i).tensor().set_element_type(element_type)
app_inputs_info[i].element_type = element_type
if output_precision:
_configure_network_outputs(ie_network, output_precision)
element_type = get_element_type(output_precision)
for i in range(len(function.outputs)):
pre_post_processor.output(i).tensor().set_element_type(element_type)
user_precision_map = {}
if input_output_precision:
_configure_network_inputs_and_outputs(ie_network, input_output_precision)
input_info = ie_network.input_info
for key in app_inputs_info.keys():
## if precision for input set by user, then set it to app_inputs
## if it an image, set U8
if input_precision or (input_output_precision and key in input_output_precision.keys()):
app_inputs_info[key].precision = input_info[key].precision
elif app_inputs_info[key].is_image:
app_inputs_info[key].precision = 'U8'
input_info[key].precision = 'U8'
user_precision_map = _parse_arg_map(input_output_precision)
input_names = get_input_output_names(function.get_parameters())
output_names = get_input_output_names(function.get_results())
for node_name, precision in user_precision_map.items():
user_precision_map[node_name] = get_element_type(precision)
for name, element_type in user_precision_map.items():
if name in input_names:
port = input_names.index(name)
app_inputs_info[port].element_type = element_type
pre_post_processor.input(port).tensor().set_element_type(element_type)
elif name in output_names:
port = output_names.index(name)
pre_post_processor.output(port).tensor().set_element_type(element_type)
else:
raise Exception(f"Node '{name}' does not exist in network")
def _configure_network_inputs(ie_network: IENetwork, app_inputs_info, input_precision: str):
input_info = ie_network.input_info
# update app_inputs_info
if not input_precision:
inputs = function.inputs
for i in range(len(inputs)):
if app_inputs_info[i].name in user_precision_map.keys():
app_inputs_info[i].element_type = user_precision_map[app_inputs_info[i].name]
elif app_inputs_info[i].is_image:
app_inputs_info[i].element_type = Type.u8
pre_post_processor.input(i).tensor().set_element_type(Type.u8)
else:
app_inputs_info[i].element_type = inputs[i].get_element_type()
for key in input_info.keys():
app_inputs_info[key].precision = input_precision
input_info[key].precision = input_precision
# set layout for model input
for port, info in enumerate(app_inputs_info):
pre_post_processor.input(port).network().set_layout(info.layout)
def _configure_network_outputs(ie_network: IENetwork, output_precision: str):
output_info = ie_network.outputs
function = pre_post_processor.build()
for key in output_info.keys():
output_info[key].precision = output_precision
def _configure_network_inputs_and_outputs(ie_network: IENetwork, input_output_precision: str):
if not input_output_precision:
raise Exception("Input/output precision is empty")
user_precision_map = _parse_arg_map(input_output_precision)
input_info = ie_network.input_info
output_info = ie_network.outputs
for key, value in user_precision_map.items():
if key in input_info:
input_info[key].precision = value
elif key in output_info:
output_info[key].precision = value
else:
raise Exception(f"Element '{key}' does not exist in network")
def _parse_arg_map(arg_map: str):
arg_map = arg_map.replace(" ", "")
@@ -106,28 +130,54 @@ def _parse_arg_map(arg_map: str):
return parsed_map
def print_inputs_and_outputs_info(ie_network: IENetwork):
input_info = ie_network.input_info
for key in input_info.keys():
tensor_desc = input_info[key].tensor_desc
logger.info(f"Network input '{key}' precision {tensor_desc.precision}, "
f"dimensions ({tensor_desc.layout}): "
f"{' '.join(str(x) for x in tensor_desc.dims)}")
output_info = ie_network.outputs
for key in output_info.keys():
info = output_info[key]
logger.info(f"Network output '{key}' precision {info.precision}, "
f"dimensions ({info.layout}): "
f"{' '.join(str(x) for x in info.shape)}")
def get_number_iterations(number_iterations: int, nireq: int, api_type: str):
def get_precision(element_type: Type):
format_map = {
'f32' : 'FP32',
'i32' : 'I32',
'i64' : 'I64',
'f16' : 'FP16',
'i16' : 'I16',
'u16' : 'U16',
'i8' : 'I8',
'u8' : 'U8',
'boolean' : 'BOOL',
}
if element_type.get_type_name() in format_map.keys():
return format_map[element_type.get_type_name()]
raise Exception("Can't find precision for openvino element type: " + str(element_type))
def print_inputs_and_outputs_info(function: Function):
parameters = function.get_parameters()
input_names = get_input_output_names(parameters)
for i in range(len(parameters)):
logger.info(f"Network input '{input_names[i]}' precision {get_precision(parameters[i].get_element_type())}, "
f"dimensions ({str(parameters[i].get_layout())}): "
f"{' '.join(str(x) for x in parameters[i].get_partial_shape())}")
results = function.get_results()
output_names = get_input_output_names(results)
results = function.get_results()
for i in range(len(results)):
logger.info(f"Network output '{output_names[i]}' precision {get_precision(results[i].get_element_type())}, "
f"dimensions ({str(results[i].get_layout())}): "
f"{' '.join(str(x) for x in results[i].get_output_partial_shape(0))}")
def get_number_iterations(number_iterations: int, nireq: int, num_shapes: int, api_type: str):
niter = number_iterations
if api_type == 'async' and niter:
niter = int((niter + nireq - 1) / nireq) * nireq
if number_iterations != niter:
logger.warning('Number of iterations was aligned by request number '
f'from {number_iterations} to {niter} using number of requests {nireq}')
if num_shapes > nireq:
niter = int(((niter + num_shapes -1) / num_shapes) * num_shapes)
if number_iterations != niter:
logger.warning('Number of iterations was aligned by number of input shapes '
f'from {number_iterations} to {niter} using number of possible input shapes {num_shapes}')
else:
niter = int((niter + nireq - 1) / nireq) * nireq
if number_iterations != niter:
logger.warning('Number of iterations was aligned by request number '
f'from {number_iterations} to {niter} using number of requests {nireq}')
return niter
@@ -142,6 +192,32 @@ def get_duration_seconds(time, number_iterations, device):
return 0
class LatencyGroup:
def __init__(self, input_names, input_shapes):
self.input_names = input_names
self.input_shapes = input_shapes
self.times = list()
self.avg = 0.
self.min = 0.
self.max = 0.
def __str__(self):
return str().join(f"{name}: {str(shape)} " for name, shape in zip(self.input_names, self.input_shapes))
def get_latency_groups(app_input_info):
num_groups = max(len(info.shapes) for info in app_input_info)
latency_groups = []
for i in range(num_groups):
names = list()
shapes = list()
for info in app_input_info:
names.append(info.name)
shapes.append(info.shapes[i % len(info.shapes)])
latency_groups.append(LatencyGroup(names, shapes))
return latency_groups
def get_duration_in_milliseconds(duration):
return duration * 1000
@@ -159,6 +235,14 @@ def get_duration_in_secs(target_device):
return duration
def check_for_static(app_input_info):
is_static = True
for info in app_input_info:
if info.is_dynamic:
return False
return is_static
def parse_devices(device_string):
if device_string in ['MULTI', 'HETERO']:
return list()
@@ -206,6 +290,8 @@ def process_help_inference_string(benchmark_app, device_number_streams):
if device_ss:
output_string += ' using ' + device_ss
output_string += f', inference only: {benchmark_app.inference_only}'
limits = ''
if benchmark_app.niter and not benchmark_app.duration_seconds:
@@ -219,35 +305,33 @@ def process_help_inference_string(benchmark_app, device_number_streams):
return output_string
def dump_exec_graph(exe_network, exec_graph_path):
try:
exec_graph_info = exe_network.get_exec_graph_info()
exec_graph_info.serialize(exec_graph_path)
logger.info(f'Executable graph is stored to {exec_graph_path}')
del exec_graph_info
except Exception as e:
logger.exception(e)
def dump_exec_graph(exe_network, model_path, weight_path = None):
if not weight_path:
weight_path = model_path[:model_path.find(".xml")] + ".bin"
serialize(exe_network.get_runtime_function(), model_path, weight_path)
def print_perf_counters(perf_counts_list):
max_layer_name = 30
for ni in range(len(perf_counts_list)):
perf_counts = perf_counts_list[ni]
total_time = 0
total_time_cpu = 0
total_time = datetime.timedelta()
total_time_cpu = datetime.timedelta()
logger.info(f"Performance counts for {ni}-th infer request")
for layer, stats in sorted(perf_counts.items(), key=lambda x: x[1]['execution_index']):
max_layer_name = 30
print(f"{layer[:max_layer_name - 4] + '...' if (len(layer) >= max_layer_name) else layer:<30}"
f"{stats['status']:<15}"
f"{'layerType: ' + str(stats['layer_type']):<30}"
f"{'realTime: ' + str(stats['real_time']):<20}"
f"{'cpu: ' + str(stats['cpu_time']):<20}"
f"{'execType: ' + str(stats['exec_type']):<20}")
total_time += stats['real_time']
total_time_cpu += stats['cpu_time']
for pi in perf_counts:
print(f"{pi.node_name[:max_layer_name - 4] + '...' if (len(pi.node_name) >= max_layer_name) else pi.node_name:<30}"
f"{str(pi.status):<15}"
f"{'layerType: ' + pi.node_type:<30}"
f"{'realTime: ' + str(pi.real_time):<20}"
f"{'cpu: ' + str(pi.cpu_time):<20}"
f"{'execType: ' + pi.exec_type:<20}")
total_time += pi.real_time
total_time_cpu += pi.cpu_time
print(f'Total time: {total_time} microseconds')
print(f'Total CPU time: {total_time_cpu} microseconds\n')
def get_command_line_arguments(argv):
parameters = []
arg_name = ''
@@ -260,17 +344,49 @@ def get_command_line_arguments(argv):
arg_value = ''
else:
if arg[0] == '-':
if arg_name is not '':
if arg_name != '':
parameters.append((arg_name, arg_value))
arg_value = ''
arg_name = arg
else:
arg_value = arg
if arg_name is not '':
if arg_name != '':
parameters.append((arg_name, arg_value))
return parameters
def parse_input_parameters(parameter_string, input_info):
def get_input_output_names(nodes):
return [node.friendly_name for node in nodes]
def get_data_shapes_map(data_shape_string, input_names):
# Parse parameter string like "input0[shape1][shape2],input1[shape1]" or "[shape1][shape2]" (applied to all inputs)
return_value = {}
if data_shape_string:
data_shape_string += ','
matches = re.findall(r'(.*?\[.*?\]),', data_shape_string)
if matches:
for match in matches:
input_name = match[:match.find('[')]
shapes = re.findall(r'\[(.*?)\]', match[len(input_name):])
if input_name:
return_value[input_name] = list(parse_partial_shape(shape_str) for shape_str in shapes)
else:
data_shapes = list(parse_partial_shape(shape_str) for shape_str in shapes)
num_inputs, num_shapes = len(input_names), len(data_shapes)
if num_shapes != 1 and num_shapes % num_inputs != 0:
raise Exception(f"Number of provided data_shapes is not a multiple of the number of model inputs!")
return_value = defaultdict(list)
for i in range(max(num_shapes, num_inputs)):
return_value[input_names[i % num_inputs]].append(data_shapes[i % num_shapes])
return return_value
else:
raise Exception(f"Can't parse input parameter: {data_shape_string}")
return return_value
def parse_input_parameters(parameter_string, input_names):
# Parse parameter string like "input0[value0],input1[value1]" or "[value]" (applied to all inputs)
return_value = {}
if parameter_string:
@@ -281,12 +397,13 @@ def parse_input_parameters(parameter_string, input_info):
if input_name != '':
return_value[input_name] = value
else:
return_value = { k:value for k in input_info.keys() }
return_value = { k:value for k in input_names }
break
else:
raise Exception(f"Can't parse input parameter: {parameter_string}")
return return_value
def parse_scale_or_mean(parameter_string, input_info):
# Parse parameter string like "input0[value0],input1[value1]" or "[value]" (applied to all inputs)
return_value = {}
@@ -299,122 +416,239 @@ def parse_scale_or_mean(parameter_string, input_info):
if input_name != '':
return_value[input_name] = f_value
else:
print("input_info: ", input_info)
for name, description in input_info.items():
if description.is_image:
return_value[name] = f_value
for input in input_info:
if input.is_image:
return_value[input.name] = f_value
else:
raise Exception(f"Can't parse input parameter: {parameter_string}")
return return_value
class InputInfo:
class AppInputInfo:
def __init__(self):
self.precision = None
self.layout = ""
self.shape = []
self.element_type = None
self.layout = Layout()
self.partial_shape = None
self.data_shapes = []
self.scale = []
self.mean = []
self.name = None
@property
def is_image(self):
if self.layout not in [ "NCHW", "NHWC", "CHW", "HWC" ]:
if str(self.layout) not in [ "[N,C,H,W]", "[N,H,W,C]", "[C,H,W]", "[H,W,C]" ]:
return False
return self.channels == 3
@property
def is_image_info(self):
if self.layout != "NC":
if str(self.layout) != "[N,C]":
return False
return self.channels >= 2
return self.channels.relaxes(Dimension(2))
def getDimentionByLayout(self, character):
if character not in self.layout:
raise Exception(f"Error: Can't get {character} from layout {self.layout}")
return self.shape[self.layout.index(character)]
if self.layout.has_name(character):
return self.partial_shape[self.layout.get_index_by_name(character)]
else:
return Dimension(0)
def getDimentionsByLayout(self, character):
if self.layout.has_name(character):
d_index = self.layout.get_index_by_name(character)
dims = []
for shape in self.data_shapes:
dims.append(shape[d_index])
return dims
else:
return [0] * len(self.data_shapes)
@property
def shapes(self):
if self.is_static:
return [self.partial_shape.to_shape()]
else:
return self.data_shapes
@property
def width(self):
return self.getDimentionByLayout("W")
return len(self.getDimentionByLayout("W"))
@property
def widthes(self):
return self.getDimentionsByLayout("W")
@property
def height(self):
return self.getDimentionByLayout("H")
return len(self.getDimentionByLayout("H"))
@property
def heights(self):
return self.getDimentionsByLayout("H")
@property
def channels(self):
return self.getDimentionByLayout("C")
@property
def batch(self):
return self.getDimentionByLayout("N")
def is_static(self):
return self.partial_shape.is_static
@property
def depth(self):
return self.getDimentionByLayout("D")
def is_dynamic(self):
return self.partial_shape.is_dynamic
def get_inputs_info(shape_string, layout_string, batch_size, scale_string, mean_string, input_info):
shape_map = parse_input_parameters(shape_string, input_info)
layout_map = parse_input_parameters(layout_string, input_info)
def parse_partial_shape(shape_str):
dims = []
for dim in shape_str.split(','):
if '.. ' in dim:
range = list(int(d) for d in dim.split('..'))
assert len(range) == 2
dims.append(Dimension(range))
elif dim == '?':
dims.append(Dimension())
else:
dims.append(Dimension(int(dim)))
return PartialShape(dims)
def parse_batch_size(batch_size_str):
if batch_size_str:
error_message = f"Can't parse batch size '{batch_size_str}'"
dims = batch_size_str.split("..")
if len(dims) > 2:
raise Exception(error_message)
elif len(dims) == 2:
range = []
for d in dims:
if d.isnumeric():
range.append(int(d))
else:
raise Exception(error_message)
return Dimension(*range)
else:
if dims[0].lstrip("-").isnumeric():
return Dimension(int(dims[0]))
elif dims[0] == "?":
return Dimension()
else:
raise Exception(error_message)
else:
return Dimension(0)
def get_inputs_info(shape_string, data_shape_string, layout_string, batch_size, scale_string, mean_string, parameters):
input_names = get_input_output_names(parameters)
shape_map = parse_input_parameters(shape_string, input_names)
data_shape_map = get_data_shapes_map(data_shape_string, input_names)
layout_map = parse_input_parameters(layout_string, input_names)
batch_size = parse_batch_size(batch_size)
reshape = False
info_map = {}
for name, descriptor in input_info.items():
info = InputInfo()
# Precision
info.precision = descriptor.precision
input_info = []
for i in range(len(parameters)):
info = AppInputInfo()
# Input name
info.name = input_names[i]
# Shape
if name in shape_map.keys():
parsed_shape = [int(dim) for dim in shape_map[name].split(',')]
info.shape = parsed_shape
if info.name in shape_map.keys():
info.partial_shape = parse_partial_shape(shape_map[info.name])
reshape = True
else:
info.shape = descriptor.input_data.shape
info.partial_shape = parameters[i].get_partial_shape()
# Layout
info.layout = layout_map[name].upper() if name in layout_map.keys() else descriptor.tensor_desc.layout
if info.name in layout_map.keys():
info.layout = Layout(layout_map[info.name])
elif parameters[i].get_layout() != Layout():
info.layout = parameters[i].get_layout()
else:
image_colors_dim = Dimension(3)
shape = info.partial_shape
num_dims = len(shape)
if num_dims == 4:
if(shape[1]) == image_colors_dim:
info.layout = Layout("NCHW")
elif(shape[3] == image_colors_dim):
info.layout = Layout("NHWC")
elif num_dims == 3:
if(shape[0]) == image_colors_dim:
info.layout = Layout("CHW")
elif(shape[2] == image_colors_dim):
info.layout = Layout("HWC")
# Update shape with batch if needed
if batch_size != 0:
batch_index = info.layout.index('N') if 'N' in info.layout else -1
if batch_index != -1 and info.shape[batch_index] != batch_size:
info.shape[batch_index] = batch_size
reshape = True
info_map[name] = info
if batch_size.is_static and data_shape_map:
logger.warning(f"Batch size will be ignored. Provide batch deminsion in data_shape parameter.")
else:
batch_index = -1
if info.layout.has_name('N'):
batch_index = info.layout.get_index_by_name('N')
elif info.layout == Layout():
supposed_batch = info.partial_shape[0]
if supposed_batch.is_dynamic or supposed_batch in [0, 1]:
logger.warning(f"Batch dimension is not specified in layout. "
"The first dimension will be interpreted as batch size.")
batch_index = 0
info.layout = Layout("N...")
if batch_index != -1 and info.partial_shape[batch_index] != batch_size:
info.partial_shape[batch_index] = batch_size
reshape = True
elif batch_index == -1:
raise Exception(f"Batch dimension is not specified for this model!")
# Data shape
if info.name in data_shape_map.keys() and info.is_dynamic:
for p_shape in data_shape_map[info.name]:
if p_shape.is_dynamic:
raise Exception(f"Data shape always should be static, {str(p_shape)} is dynamic.")
elif info.partial_shape.compatible(p_shape):
info.data_shapes.append(p_shape.to_shape())
else:
raise Exception(f"Data shape '{str(p_shape)}' provided for input '{info.name}' "
f"is not compatible with partial shape '{str(info.partial_shape)}' for this input.")
elif info.name in data_shape_map.keys():
logger.warning(f"Input '{info.name}' has static shape. Provided data shapes for this input will be ignored.")
input_info.append(info)
# Update scale, mean
scale_map = parse_scale_or_mean(scale_string, info_map)
mean_map = parse_scale_or_mean(mean_string, info_map)
scale_map = parse_scale_or_mean(scale_string, input_info)
mean_map = parse_scale_or_mean(mean_string, input_info)
for name, descriptor in info_map.items():
if descriptor.is_image:
descriptor.scale = np.ones(3)
descriptor.mean = np.zeros(3)
for input in input_info:
if input.name in scale_map:
input.scale = scale_map[input.name]
if input.name in mean_map:
input.mean = mean_map[input.name]
if name in scale_map:
descriptor.scale = scale_map[name]
if name in mean_map:
descriptor.mean = mean_map[name]
return input_info, reshape
return info_map, reshape
def get_batch_size(inputs_info):
batch_size = 0
for _, info in inputs_info.items():
batch_index = info.layout.index('N') if 'N' in info.layout else -1
null_dimension = Dimension(0)
batch_size = null_dimension
for info in inputs_info:
batch_index = info.layout.get_index_by_name('N') if info.layout.has_name('N') else -1
if batch_index != -1:
if batch_size == 0:
batch_size = info.shape[batch_index]
elif batch_size != info.shape[batch_index]:
if batch_size == null_dimension:
batch_size = info.partial_shape[batch_index]
elif batch_size != info.partial_shape[batch_index]:
raise Exception("Can't deterimine batch size: batch is different for different inputs!")
if batch_size == 0:
batch_size = 1
if batch_size == null_dimension:
batch_size = Dimension(1)
return batch_size
def show_available_devices():
ie = IECore()
print("\nAvailable target devices: ", (" ".join(ie.available_devices)))
print("\nAvailable target devices: ", (" ".join(Core().available_devices)))
def dump_config(filename, config):
with open(filename, 'w') as f:
json.dump(config, f, indent=4)
def load_config(filename, config):
with open(filename) as f:
config.update(json.load(f))