Update the logic of benchmark app property setting (#16427)

* 1. refine the logic to ov::device::properties setting.
2. the config overrides will be performed if same config setting is came from CMD line.-a

Signed-off-by: Wang, Yang <yang4.wang@intel.com>

* Update configuration sample file within README.md.

* Update.

* Update.

* 1. Update configuration example file within REAMDME.md for Python version.
2. implement the config DEVICE_PROPERTIES value convertation between the string type and dictionary of Python type.
3. Update the configuration file loading and dumping logic.

Signed-off-by: Wang, Yang <yang4.wang@intel.com>

* Update.

* Update.

* Update.

* Update.

* Update.

* 1. Enable configs to be interchangeable between C++ and Python.
2. Update perf_count showing logic.

Signed-off-by: Wang, Yang <yang4.wang@intel.com>

* Revert the logic of showing show performance counters.

* Update help msg for loading config option.

---------

Signed-off-by: Wang, Yang <yang4.wang@intel.com>
This commit is contained in:
Wang, Yang
2023-04-12 23:32:54 +08:00
committed by GitHub
parent c283d21215
commit 86d7c97fa9
9 changed files with 158 additions and 168 deletions

View File

@@ -217,27 +217,18 @@ Running the application with the ``-h`` or ``--help`` option yields the followin
-exec_graph_path Optional. Path to a file where to store executable graph information serialized.
-dump_config Optional. Path to JSON file to dump IE parameters, which were set by application.
-load_config Optional. Path to JSON file to load custom IE parameters. Please note, command line parameters have higher priority then parameters from configuration file.
Example 1: a simple JSON file for HW device with primary properties.
{
"CPU": {"NUM_STREAMS": "3", "PERF_COUNT": "NO"}
}
Example 2: a simple JSON file for meta device(AUTO/MULTI) with HW device properties.
{
"AUTO": {
"PERFORMANCE_HINT": "",
"PERF_COUNT": "NO",
"DEVICE_PROPERTIES": {
"CPU": {
"INFERENCE_PRECISION_HINT": "f32",
"NUM_STREAMS": "3"
},
"GPU": {
"INFERENCE_PRECISION_HINT": "f32",
"NUM_STREAMS": "5"
}
}
}
}
Example 1: a simple JSON file for HW device with primary properties.
{
"CPU": {"NUM_STREAMS": "3", "PERF_COUNT": "NO"}
}
Example 2: a simple JSON file for meta device(AUTO/MULTI) with HW device properties.
{
"AUTO": {
"PERFORMANCE_HINT": "THROUGHPUT",
"PERF_COUNT": "NO",
"DEVICE_PROPERTIES": "{CPU:{INFERENCE_PRECISION_HINT:f32,NUM_STREAMS:3},GPU:{INFERENCE_PRECISION_HINT:f32,NUM_STREAMS:5}}"
}
}
Running the application with the empty list of options yields the usage message given above and an error message.

View File

@@ -245,18 +245,10 @@ static const char load_config_message[] =
"properties.\n"
" {\n"
" \"AUTO\": {\n"
" \"PERFORMANCE_HINT\": \"\",\n"
" \"PERFORMANCE_HINT\": \"THROUGHPUT\",\n"
" \"PERF_COUNT\": \"NO\",\n"
" \"DEVICE_PROPERTIES\": {\n"
" \"CPU\": {\n"
" \"INFERENCE_PRECISION_HINT\": \"f32\",\n"
" \"NUM_STREAMS\": \"3\"\n"
" },\n"
" \"GPU\": {\n"
" \"INFERENCE_PRECISION_HINT\": \"f32\",\n"
" \"NUM_STREAMS\": \"5\"\n"
" }\n"
" }\n"
" \"DEVICE_PROPERTIES\": "
"\"{CPU:{INFERENCE_PRECISION_HINT:f32,NUM_STREAMS:3},GPU:{INFERENCE_PRECISION_HINT:f32,NUM_STREAMS:5}}\"\n"
" }\n"
" }";

View File

@@ -162,7 +162,6 @@ void setDeviceProperty(ov::Core& core,
std::string& device,
ov::AnyMap& device_config,
const std::pair<std::string, ov::Any>& property,
std::map<std::string, bool>& is_dev_set_property,
const std::pair<std::string, ov::Any>& config = {}) {
auto supported_properties = core.get_property(device, ov::supported_properties);
auto supported = [&](const std::string& key) {
@@ -179,18 +178,7 @@ void setDeviceProperty(ov::Core& core,
if (device_property.first.empty())
return;
if (device_config.find(device) == device_config.end() || // device properties not existed
(config.first.empty() && // not setting default value to property
(!FLAGS_load_config.empty() &&
is_dev_set_property[device]))) { // device properties loaded from file and overwrite is not happened
is_dev_set_property[device] = false;
device_config.erase(device);
device_config.insert(ov::device::properties(device, device_property));
} else {
auto& properties = device_config[device].as<ov::AnyMap>();
// property present in device properties has higher priority than property set with default value
properties.emplace(device_property);
}
update_device_properties_setting(device, device_config, device_property);
}
void warn_if_no_batch(const benchmark_app::InputsInfo& first_inputs) {
@@ -285,10 +273,6 @@ int main(int argc, char* argv[]) {
// Parse devices
auto devices = parse_devices(device_name);
std::map<std::string, bool> is_dev_set_property = {};
// initialize flags to ensure ov::device::properties should only be set once per device.
for (auto& dev : devices)
is_dev_set_property[dev] = true;
// Parse nstreams per device
std::map<std::string, std::string> device_nstreams = parse_value_per_device(devices, FLAGS_nstreams);
std::map<std::string, std::string> device_infer_precision =
@@ -296,10 +280,8 @@ int main(int argc, char* argv[]) {
// Load device config file if specified
std::map<std::string, ov::AnyMap> config;
bool is_load_config = false;
if (!FLAGS_load_config.empty()) {
load_config(FLAGS_load_config, config);
is_load_config = true;
}
/** This vector stores paths to the processed images with input names**/
@@ -377,20 +359,22 @@ int main(int argc, char* argv[]) {
// Update config per device according to command line parameters
for (auto& device : devices) {
auto& device_config = config[device];
// high-level performance modes
if (!device_config.count(ov::hint::performance_mode.name())) {
device_config.emplace(ov::hint::performance_mode(get_performance_hint(device, core)));
auto ov_perf_hint = get_performance_hint(device, core);
if (isFlagSetInCommandLine("hint")) {
// apply command line hint setting and override if hint exists
device_config[ov::hint::performance_mode.name()] = ov_perf_hint;
} else {
// keep hint setting in the config if no hint setting from command line
device_config.emplace(ov::hint::performance_mode(ov_perf_hint));
}
auto ov_perf_hint = device_config.at(ov::hint::performance_mode.name()).as<ov::hint::PerformanceMode>();
if (FLAGS_nireq != 0)
device_config.emplace(ov::hint::num_requests(unsigned(FLAGS_nireq)));
device_config[ov::hint::num_requests.name()] = unsigned(FLAGS_nireq);
// Set performance counter
if (isFlagSetInCommandLine("pc")) {
// set to user defined value
device_config.emplace(ov::enable_profiling(FLAGS_pc));
device_config[ov::enable_profiling.name()] = FLAGS_pc;
} else if (device_config.count(ov::enable_profiling.name()) &&
(device_config.at(ov::enable_profiling.name()).as<bool>())) {
slog::warn << "Performance counters for " << device
@@ -399,18 +383,18 @@ int main(int argc, char* argv[]) {
FLAGS_report_type == sortDetailedCntReport) {
slog::warn << "Turn on performance counters for " << device << " device since report type is "
<< FLAGS_report_type << "." << slog::endl;
device_config.emplace(ov::enable_profiling(true));
device_config[ov::enable_profiling.name()] = true;
} else if (!FLAGS_exec_graph_path.empty()) {
slog::warn << "Turn on performance counters for " << device << " device due to execution graph dumping."
<< slog::endl;
device_config.emplace(ov::enable_profiling(true));
device_config[ov::enable_profiling.name()] = true;
} else if (!FLAGS_pcsort.empty()) {
slog::warn << "Turn on sorted performance counters for " << device << " device since pcsort value is "
<< FLAGS_pcsort << "." << slog::endl;
device_config.emplace(ov::enable_profiling(true));
device_config[ov::enable_profiling.name()] = true;
} else {
// set to default value
device_config.emplace(ov::enable_profiling(FLAGS_pc));
device_config[ov::enable_profiling.name()] = FLAGS_pc;
}
perf_counts = (device_config.at(ov::enable_profiling.name()).as<bool>()) ? true : perf_counts;
@@ -437,9 +421,7 @@ int main(int argc, char* argv[]) {
key = ov::num_streams.name();
update_device_config_for_virtual_device(it_device_nstreams->second,
device_config,
ov::num_streams,
is_dev_set_property,
is_load_config);
ov::num_streams);
} else {
throw std::logic_error("Device " + device + " doesn't support config key '" + key + "' " +
"and '" + ov::num_streams.name() + "'!" +
@@ -472,7 +454,6 @@ int main(int argc, char* argv[]) {
hwdevice,
device_config,
ov::num_streams(ov::streams::AUTO),
is_dev_set_property,
std::make_pair(key, value));
}
}
@@ -492,9 +473,7 @@ int main(int argc, char* argv[]) {
} else if (is_virtual_device(device)) {
update_device_config_for_virtual_device(it_device_infer_precision->second,
device_config,
ov::hint::inference_precision,
is_dev_set_property,
is_load_config);
ov::hint::inference_precision);
} else {
throw std::logic_error("Device " + device + " doesn't support config key '" +
ov::hint::inference_precision.name() + "'! " +
@@ -520,13 +499,13 @@ int main(int argc, char* argv[]) {
: ov::affinity(fix_pin_option(FLAGS_pin));
if (supported(property_name) || device_name == "AUTO") {
// create nthreads/pin primary property for HW device or AUTO if -d is AUTO directly.
device_config.emplace(property);
device_config[property.first] = property.second;
} else if (is_virtual) {
// Create secondary property of -nthreads/-pin only for CPU if CPU device appears in the devices
// list specified by -d.
for (auto& device : hardware_devices) {
if (device == "CPU")
setDeviceProperty(core, device, device_config, property, is_dev_set_property);
setDeviceProperty(core, device, device_config, property);
}
}
};

View File

@@ -121,6 +121,41 @@ bool is_virtual_device_found(const std::vector<std::string>& device_names) {
return false;
}
void update_device_properties_setting(const std::string& device_name,
ov::AnyMap& config,
std::pair<std::string, ov::Any> device_property) {
// overriding if property {key, value} is already existed in config["DEVICE_PROPERTIES"][device_name],
// if not, insert this {key, value} into config["DEVICE_PROPERTIES"][device_name].
// check and create property {"DEVICE_PROPERTIES": ov::AnyMap{hw_device, ov::AnyMap{}}} if not exist in config
if (config.find(ov::device::properties.name()) == config.end()) {
config[ov::device::properties.name()] = ov::AnyMap{};
config[ov::device::properties.name()].as<ov::AnyMap>().insert({device_name, ov::AnyMap{device_property}});
return;
}
// because of legacy API 1.0. eg the config from JSON file.
if (config[ov::device::properties.name()].is<std::string>()) {
config[ov::device::properties.name()] = config[ov::device::properties.name()].as<ov::AnyMap>();
}
auto& device_properties = config[ov::device::properties.name()].as<ov::AnyMap>();
if (device_properties.find(device_name) == device_properties.end()) {
device_properties.insert({device_name, ov::AnyMap{device_property}});
return;
}
// because of legacy API 1.0. eg the config from JSON file.
if (device_properties[device_name].is<std::string>()) {
device_properties[device_name] = device_properties[device_name].as<ov::AnyMap>();
}
auto& secondary_property = device_properties[device_name].as<ov::AnyMap>();
// overwrite if this config existed
secondary_property.erase(device_property.first);
secondary_property.insert(device_property);
}
std::vector<std::string> parse_devices(const std::string& device_string) {
std::string comma_separated_devices = device_string;
auto colon = comma_separated_devices.find(":");
@@ -184,9 +219,7 @@ void parse_value_for_virtual_device(const std::string& device, std::map<std::str
template <typename T>
void update_device_config_for_virtual_device(const std::string& value,
ov::AnyMap& device_config,
ov::Property<T, ov::PropertyMutability::RW> property,
std::map<std::string, bool>& is_dev_set_property,
bool is_load_config) {
ov::Property<T, ov::PropertyMutability::RW> property) {
// check if the element contains the hardware device property
if (split(value, ':').size() == 1) {
device_config[property.name()] = value;
@@ -198,52 +231,21 @@ void update_device_config_for_virtual_device(const std::string& value,
for (const auto& it : devices_property) {
const auto& device_name = it.first;
const auto& device_value = it.second;
if (device_config.find(ov::device::properties.name()) == device_config.end() ||
(is_load_config && is_dev_set_property[device_name])) {
// Create ov::device::properties with ov::num_stream/ov::hint::inference_precision and
// 1. Insert this ov::device::properties into device config if this
// ov::device::properties isn't existed. Otherwise,
// 2. Replace the existed ov::device::properties within device config.
is_dev_set_property[device_name] = false;
device_config.erase(device_name);
device_config[ov::device::properties.name()] = ov::AnyMap{};
auto& secondary_property = device_config.at(ov::device::properties.name()).as<ov::AnyMap>();
secondary_property[device_name] = ov::AnyMap{{property.name(), device_value}};
} else {
auto& secondary_property = device_config.at(ov::device::properties.name()).as<ov::AnyMap>();
if (secondary_property.count(device_name)) {
auto& device_property = secondary_property.at(device_name).as<ov::AnyMap>();
device_property.emplace(property(device_value));
} else {
secondary_property[device_name] = ov::AnyMap{{property.name(), device_value}};
}
}
update_device_properties_setting(device_name, device_config, property(device_value));
}
}
}
void update_device_config_for_virtual_device(const std::string& value,
ov::AnyMap& device_config,
ov::Property<ov::streams::Num, ov::PropertyMutability::RW> property,
std::map<std::string, bool>& is_dev_set_property,
bool is_load_config) {
return update_device_config_for_virtual_device<ov::streams::Num>(value,
device_config,
property,
is_dev_set_property,
is_load_config);
ov::Property<ov::streams::Num, ov::PropertyMutability::RW> property) {
return update_device_config_for_virtual_device<ov::streams::Num>(value, device_config, property);
}
void update_device_config_for_virtual_device(const std::string& value,
ov::AnyMap& device_config,
ov::Property<ov::element::Type, ov::PropertyMutability::RW> property,
std::map<std::string, bool>& is_dev_set_property,
bool is_load_config) {
return update_device_config_for_virtual_device<ov::element::Type>(value,
device_config,
property,
is_dev_set_property,
is_load_config);
ov::Property<ov::element::Type, ov::PropertyMutability::RW> property) {
return update_device_config_for_virtual_device<ov::element::Type>(value, device_config, property);
}
std::map<std::string, std::string> parse_value_per_device(const std::vector<std::string>& devices,

View File

@@ -60,6 +60,9 @@ using PartialShapes = std::map<std::string, ngraph::PartialShape>;
bool is_virtual_device(const std::string& device_name);
bool is_virtual_device_found(const std::vector<std::string>& device_names);
void update_device_properties_setting(const std::string& device_name,
ov::AnyMap& config,
std::pair<std::string, ov::Any> device_property);
std::vector<std::string> parse_devices(const std::string& device_string);
uint32_t device_default_device_duration_in_seconds(const std::string& device);
std::map<std::string, std::string> parse_value_per_device(const std::vector<std::string>& devices,
@@ -68,9 +71,7 @@ void parse_value_for_virtual_device(const std::string& device, std::map<std::str
template <typename T>
void update_device_config_for_virtual_device(const std::string& value,
ov::AnyMap& device_config,
ov::Property<T, ov::PropertyMutability::RW> property,
std::map<std::string, bool>& is_dev_set_property,
bool is_load_config = false);
ov::Property<T, ov::PropertyMutability::RW> property);
std::string get_shapes_string(const benchmark_app::PartialShapes& shapes);
size_t get_batch_size(const benchmark_app::InputsInfo& inputs_info);
std::vector<std::string> split(const std::string& s, char delim);

View File

@@ -306,26 +306,17 @@ Running the application with the ``-h`` or ``--help`` option yields the followin
Optional. Path to JSON file to load custom OpenVINO parameters.
Please note, command line parameters have higher priority then parameters from configuration file.
Example 1: a simple JSON file for HW device with primary properties.
{
"CPU": {"NUM_STREAMS": "3", "PERF_COUNT": "NO"}
}
{
"CPU": {"NUM_STREAMS": "3", "PERF_COUNT": "NO"}
}
Example 2: a simple JSON file for meta device(AUTO/MULTI) with HW device properties.
{
"AUTO": {
"PERFORMANCE_HINT": "",
"PERF_COUNT": "NO",
"DEVICE_PROPERTIES": {
"CPU": {
"INFERENCE_PRECISION_HINT": "f32",
"NUM_STREAMS": "3"
},
"GPU": {
"INFERENCE_PRECISION_HINT": "f32",
"NUM_STREAMS": "5"
}
}
}
}
{
"AUTO": {
"PERFORMANCE_HINT": "THROUGHPUT",
"PERF_COUNT": "NO",
"DEVICE_PROPERTIES": "{CPU:{INFERENCE_PRECISION_HINT:f32,NUM_STREAMS:3},GPU:{INFERENCE_PRECISION_HINT:f32,NUM_STREAMS:5}}"
}
}
Running the application with the empty list of options yields the usage message given above and an error message.

View File

@@ -71,15 +71,12 @@ def main():
device_name = args.target_device
devices = parse_devices(device_name)
is_dev_set_property = {device: True for device in devices}
device_number_streams = parse_value_per_device(devices, args.number_streams, "nstreams")
device_infer_precision = parse_value_per_device(devices, args.infer_precision, "infer_precision")
config = {}
is_load_config = False
if args.load_config:
load_config(args.load_config, config)
is_load_config = True
if is_network_compiled:
logger.info("Model is compiled")
@@ -108,7 +105,7 @@ def main():
# --------------------- 3. Setting device configuration --------------------------------------------------------
next_step()
def get_performance_hint(device) -> properties.hint.PerformanceMode:
def set_performance_hint(device):
perf_hint = properties.hint.PerformanceMode.UNDEFINED
supported_properties = benchmark.core.get_property(device, properties.supported_properties())
if properties.hint.performance_mode() in supported_properties:
@@ -128,9 +125,10 @@ def main():
perf_hint = properties.hint.PerformanceMode.THROUGHPUT if benchmark.api_type == "async" else properties.hint.PerformanceMode.LATENCY
logger.warning(f"Performance hint was not explicitly specified in command line. " +
f"Device({device}) performance hint will be set to {perf_hint}.")
config[device][properties.hint.performance_mode()] = perf_hint
else:
logger.warning(f"Device {device} does not support performance hint property(-hint).")
return perf_hint
def get_device_type_from_name(name) :
new_name = str(name)
@@ -169,10 +167,7 @@ def main():
config[device] = {}
## high-level performance modes
if properties.hint.performance_mode() not in config[device].keys():
config[device][properties.hint.performance_mode()] = get_performance_hint(device)
perf_hint = config[device][properties.hint.performance_mode()]
set_performance_hint(device)
if is_flag_set_in_command_line('nireq'):
config[device][properties.hint.num_requests()] = str(args.number_infer_requests)
@@ -205,14 +200,6 @@ def main():
## insert or append property into hw device properties list
def update_configs(hw_device, property_name, property_value):
(key, value) = properties.device.properties({hw_device:{property_name:property_value}})
is_set_streams_auto = property_name == properties.num_streams() and property_value == properties.streams.Num.AUTO
if not is_set_streams_auto and is_load_config and is_dev_set_property[hw_device] and hw_device in config[device].keys():
# overwrite the device properties loaded from configuration file if
# 1. not setting 'NUM_STREAMS' to default value 'AUTO',
# 2. enable loading device properties from configuration file,
# 3. device properties in config[device] is loaded from configuration file, and never setting device properties before
is_dev_set_property[hw_device] = False
del config[device][key]
# add property into hw device properties list.
if key not in config[device].keys():
config[device][key] = value
@@ -221,10 +208,10 @@ def main():
if hw_device not in current_config.keys():
current_config.update(value.get())
else:
current_device_config = current_config[hw_device].get()
current_device_config = current_config[hw_device]
for prop in value.get().items():
current_device_config.update(prop[1].get())
current_config[hw_device].set(current_device_config)
current_device_config.update(prop[1])
current_config[hw_device].update(current_device_config)
config[device][key].set(current_config)
def update_device_config_for_virtual_device(value, config, key):
@@ -326,7 +313,6 @@ def main():
if device in device_number_streams.keys():
del device_number_streams[device]
perf_counts = perf_counts
device_config = {}
for device in config:
if benchmark.device.find(device) == 0:
@@ -466,7 +452,7 @@ def main():
if k == properties.device.properties():
for device_key in value.keys():
logger.info(f' {device_key}:')
for k2, value2 in value.get(device_key).get().items():
for k2, value2 in value.get(device_key).items():
if k2 not in skip_keys:
logger.info(f' {k2}: {value2}')
else:

View File

@@ -189,18 +189,9 @@ def parse_args():
"Example 2: a simple JSON file for meta device(AUTO/MULTI) with HW device properties.\n"
" {\n"
" \"AUTO\": {\n"
" \"PERFORMANCE_HINT\": \"\",\n"
" \"PERFORMANCE_HINT\": \"THROUGHPUT\",\n"
" \"PERF_COUNT\": \"NO\",\n"
" \"DEVICE_PROPERTIES\": {\n"
" \"CPU\": {\n"
" \"INFERENCE_PRECISION_HINT\": \"f32\",\n"
" \"NUM_STREAMS\": \"3\"\n"
" },\n"
" \"GPU\": {\n"
" \"INFERENCE_PRECISION_HINT\": \"f32\",\n"
" \"NUM_STREAMS\": \"5\"\n"
" }\n"
" }\n"
" \"DEVICE_PROPERTIES\": \"{CPU:{INFERENCE_PRECISION_HINT:f32,NUM_STREAMS:3},GPU:{INFERENCE_PRECISION_HINT:f32,NUM_STREAMS:5}}\"\n"
" }\n"
" }")
parsed_args = parser.parse_args()

View File

@@ -4,7 +4,7 @@
from collections import defaultdict
from datetime import timedelta
import enum
from openvino.runtime import Core, Model, PartialShape, Dimension, Layout, Type, serialize, properties
from openvino.runtime import Core, Model, PartialShape, Dimension, Layout, Type, serialize, properties, OVAny
from openvino.preprocess import PrePostProcessor
from .constants import DEVICE_DURATION_IN_SECS, UNKNOWN_DEVICE_TYPE, \
@@ -758,12 +758,64 @@ def show_available_devices():
print("\nAvailable target devices: ", (" ".join(Core().available_devices)))
def device_properties_to_string(config):
ret = "{"
for k, v in config.items():
if isinstance(v, dict):
sub_str = "{"
for sk, sv in v.items():
if isinstance(sv, bool):
sv = "YES" if sv else "NO"
if isinstance(sv, properties.Affinity):
sv = sv.name
sub_str += "{0}:{1},".format(sk, sv)
sub_str = sub_str[:-1]
sub_str += "}"
ret += "{0}:{1},".format(k, sub_str)
else:
ret += "{0}:{1},".format(k, v)
ret = ret[:-1]
ret += "}"
return ret
def string_to_device_properties(device_properties_str):
ret = {}
if not device_properties_str:
return ret
if not device_properties_str.startswith("{") or not device_properties_str.endswith("}"):
raise Exception(
"Failed to parse device properties. Value of device properties should be started with '{' and ended with '}'."
"They are actually {} and {}".format(device_properties_str[0], device_properties_str[-1]))
pattern = r'(\w+):({.+?}|[^,}]+)'
pairs = re.findall(pattern, device_properties_str)
for key, value in pairs:
if value.startswith("{") and value.endswith("}"):
value = value[1:-1]
nested_pairs = re.findall(pattern, value)
nested_dict = {}
for nested_key, nested_value in nested_pairs:
nested_dict[nested_key] = nested_value
value = nested_dict
ret[key] = value
return ret
def dump_config(filename, config):
json_config = {}
for device_name, device_config in config.items():
json_config[device_name] = {}
for key, value in device_config.items():
value_string = value.name if isinstance(value, properties.hint.PerformanceMode) else str(value)
if isinstance(value, OVAny) and (isinstance(value.value, dict)):
value_string = device_properties_to_string(value.get())
elif isinstance(value, (properties.hint.PerformanceMode, properties.Affinity)):
value_string = value.name
elif isinstance(value, OVAny):
value_string = str(value.value)
else:
value_string = str(value)
if isinstance(value, bool):
value_string = "YES" if value else "NO"
json_config[device_name][key] = value_string
with open(filename, 'w') as f:
@@ -776,4 +828,9 @@ def load_config(filename, config):
for device in original_config:
config[device] = {}
for property_name in original_config[device]:
config[device][property_name] = original_config[device][property_name]
property_value = original_config[device][property_name]
if property_name == properties.device.properties():
property_value = string_to_device_properties(property_value)
elif property_value in ("YES", "NO"):
property_value = True if property_value == "YES" else False
config[device][property_name] = OVAny(property_value)