Skip to content

Commit a6359ee

Browse files
[OVEP] Support for providing layout to input/output to OpenVINO (#767)
* [OVEP] Support for providing layout to input/output to OpenVINO * [OVEP] Minor bug fixes for layout feature
1 parent a780d5b commit a6359ee

File tree

9 files changed

+132
-3
lines changed

9 files changed

+132
-3
lines changed

onnxruntime/core/providers/openvino/backend_utils.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ CreateOVModel(std::string&& model,
150150
LOGS_DEFAULT(INFO) << log_tag << "Reshaping the ov tensor to specified shape";
151151
ov_model->reshape(session_context.reshape);
152152
}
153+
154+
if (!session_context.layout.empty()) {
155+
LOGS_DEFAULT(INFO) << log_tag << "Setting the ov tensor layout to specified layout";
156+
ov_model = Set_Layout(ov_model, session_context.layout);
157+
}
153158
// Check for Constant Folding
154159
if ((session_context.device_type != "NPU") && !session_context.is_wholly_supported_graph) {
155160
ov::pass::ConstantFolding pass_const_obj;
@@ -199,6 +204,41 @@ GetOutputTensor(Ort::KernelContext& context,
199204
return context.GetOutput(index, output_shape);
200205
}
201206

207+
std::shared_ptr<OVNetwork> Set_Layout(std::shared_ptr<OVNetwork> ov_model, const layout_t& layout) {
208+
ov::preprocess::PrePostProcessor preproc(ov_model);
209+
210+
const auto& inputs = ov_model->inputs();
211+
const auto& outputs = ov_model->outputs();
212+
213+
auto find_tensor_index = [](const std::vector<ov::Output<ov::Node>>& tensors, const std::string& name) -> std::optional<size_t> {
214+
for (size_t i = 0; i < tensors.size(); ++i) {
215+
const auto& tensor = tensors[i];
216+
if (tensor.get_any_name() == name || tensor.get_tensor().get_names().count(name) > 0) {
217+
return i;
218+
}
219+
}
220+
return std::nullopt;
221+
};
222+
223+
for (const auto& [tensor_name, layout_value] : layout) {
224+
bool tensor_found = false;
225+
226+
if (auto input_idx = find_tensor_index(inputs, tensor_name)) {
227+
preproc.input(*input_idx).tensor().set_layout(layout_value);
228+
tensor_found = true;
229+
} else if (auto output_idx = find_tensor_index(outputs, tensor_name)) {
230+
preproc.output(*output_idx).tensor().set_layout(layout_value);
231+
tensor_found = true;
232+
}
233+
234+
if (!tensor_found) {
235+
LOGS_DEFAULT(WARNING) << "Tensor '" << tensor_name << "' not found in model inputs or outputs";
236+
}
237+
}
238+
239+
return preproc.build();
240+
}
241+
202242
int GetFirstAvailableDevice(SessionContext& session_context) {
203243
int i = 0;
204244
// Get the first available VAD-M device and set the device to busy

onnxruntime/core/providers/openvino/backend_utils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ int GetFirstAvailableDevice(SessionContext& session_context);
7979

8080
void FillOutputsWithConstantData(std::shared_ptr<ov::Node> node, Ort::UnownedValue& out_tensor);
8181

82+
std::shared_ptr<OVNetwork> Set_Layout(std::shared_ptr<OVNetwork> ov_model, const layout_t& layout);
83+
8284
template <typename T>
8385
void FillOutputHelper(Ort::UnownedValue& out_tensor, std::shared_ptr<ov::Node> node);
8486

onnxruntime/core/providers/openvino/backends/basic_backend.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ BasicBackend::BasicBackend(std::unique_ptr<ONNX_NAMESPACE::ModelProto>& model_pr
9898
!subgraph_context_.has_dynamic_input_shape &&
9999
!session_context_.so_context_enable &&
100100
session_context_.reshape.empty() &&
101+
session_context_.layout.empty() &&
101102
!enable_causallm &&
102103
!eligible_for_cpu_fallback &&
103104
auto_unified_compile);

onnxruntime/core/providers/openvino/contexts.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SharedContext : public WeakSingleton<SharedContext> {
7070

7171
using config_t = std::map<std::string, ov::AnyMap>;
7272
using reshape_t = std::map<std::string, ov::PartialShape>;
73+
using layout_t = std::map<std::string, ov::Layout>;
7374

7475
struct ProviderInfo {
7576
std::string device_type{""}; // [device_type]: Overrides the accelerator hardware type and
@@ -88,6 +89,7 @@ struct ProviderInfo {
8889
// (GPU) feature. If blob files are already present,
8990
// it will be directly loaded.
9091
reshape_t reshape{}; // Used for reshaping the ov input tensor shape at runtime.
92+
layout_t layout{}; // Used for specifying the ov input/output tensor layout at runtime.
9193
std::string model_priority{"DEFAULT"}; // High-level OpenVINO model priority hint
9294
// Defines what model should be provided with more performant
9395
// bounded resource first
@@ -110,7 +112,7 @@ struct ProviderInfo {
110112
const ConfigOptions* config_options{NULL};
111113
const std::unordered_set<std::string> valid_provider_keys = {"device_type", "device_id", "device_luid", "cache_dir", "precision",
112114
"load_config", "context", "num_of_threads", "model_priority", "num_streams", "enable_opencl_throttling", "enable_qdq_optimizer",
113-
"enable_causallm", "disable_dynamic_shapes", "reshape_input"};
115+
"enable_causallm", "disable_dynamic_shapes", "reshape_input", "layout"};
114116
};
115117

116118
// Holds context applicable to the entire EP instance.

onnxruntime/core/providers/openvino/openvino_parser_utils.cc

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,5 +236,79 @@ ov::Dimension OpenVINOParserUtils::ParseDimensionRange(const std::string& range_
236236
return ov::Dimension(range_start, range_end);
237237
}
238238

239+
layout_t OpenVINOParserUtils::ParseLayout(const std::string& layout_definition) {
240+
layout_t parsed_layout_map;
241+
242+
// Return empty map for empty input
243+
if (layout_definition.empty()) {
244+
ORT_THROW("Empty layout definition provided in layout parameter");
245+
}
246+
247+
// Regular expression for parsing layout definitions
248+
const std::regex layout_pattern(R"(([^\[\],]+)\s*\[(.*?)\])"); // e.g. "input_1[NC],data[CHW]"
249+
250+
// Find all tensor layout definitions using regex
251+
auto layout_begin = std::sregex_iterator(
252+
layout_definition.begin(),
253+
layout_definition.end(),
254+
layout_pattern);
255+
auto layout_end = std::sregex_iterator();
256+
257+
// If no matches found, throw error
258+
if (layout_begin == layout_end) {
259+
ORT_THROW("Invalid layout definition format: " + layout_definition);
260+
}
261+
262+
// Process each tensor definition
263+
for (std::sregex_iterator i = std::move(layout_begin); i != layout_end; ++i) {
264+
std::smatch layout_match = *i;
265+
266+
// Extract tensor name and trim whitespace
267+
std::string tensor_name = layout_match[1].str(); // Group 1: tensor name e.g. "input_1"
268+
tensor_name = TrimWhitespace(tensor_name);
269+
270+
if (tensor_name.empty()) {
271+
ORT_THROW("Empty tensor name provided in layout parameter");
272+
}
273+
274+
// Extract dimensions string
275+
std::string dimensions_str = layout_match[2].str(); // Group 2: dimensions string [e.g. "NC", "CHW"]
276+
277+
if (!Check_Valid_Layout(dimensions_str, tensor_name)) {
278+
ORT_THROW("Invalid dimensions string provided in layout parameter");
279+
}
280+
281+
// Store parsed shape in result map
282+
parsed_layout_map[tensor_name] = ov::Layout(dimensions_str);
283+
}
284+
285+
return parsed_layout_map;
286+
}
287+
288+
bool OpenVINOParserUtils::Check_Valid_Layout(const std::string& layout_str, const std::string& tensor_name) {
289+
// Check if the layout string is empty
290+
if (layout_str.empty()) {
291+
return false;
292+
}
293+
294+
std::unordered_set<char> seen_alphabets;
295+
for (char c : layout_str) {
296+
if (std::isalpha(c)) {
297+
char upper_c = static_cast<char>(std::toupper(c)); // Convert to uppercase for case-insensitive comparison
298+
if (seen_alphabets.find(upper_c) != seen_alphabets.end()) {
299+
ORT_THROW("Repeated Dim '" + std::string(1, c) +
300+
"' found in layout dimensions for tensor '" + tensor_name + "'");
301+
}
302+
seen_alphabets.insert(upper_c);
303+
} else if (c != '?') {
304+
// Only '?' is allowed as non-alphabetic character
305+
ORT_THROW("Invalid character '" + std::string(1, c) +
306+
"' found in layout dimensions for tensor '" + tensor_name + "'");
307+
}
308+
}
309+
310+
return true;
311+
}
312+
239313
} // namespace openvino_ep
240314
} // namespace onnxruntime

onnxruntime/core/providers/openvino/openvino_parser_utils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ class OpenVINOParserUtils {
1818
std::string& device_type,
1919
const std::string& option_name);
2020
static reshape_t ParseInputShape(const std::string& reshape_input_definition);
21+
static layout_t ParseLayout(const std::string& layout_definition);
2122
static std::string TrimWhitespace(const std::string& str);
2223
static ov::Dimension ParseDimensionRange(const std::string& range_str, const std::string& tensor_name);
24+
static bool Check_Valid_Layout(const std::string& layout_str, const std::string& tensor_name);
2325
};
2426

2527
} // namespace openvino_ep

onnxruntime/core/providers/openvino/openvino_provider_factory.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ static void ParseProviderInfo(const ProviderOptions& provider_options,
232232
pi.reshape = OpenVINOParserUtils::ParseInputShape(provider_options.at("reshape_input"));
233233
}
234234

235+
if (provider_options.contains("layout")) {
236+
pi.layout = OpenVINOParserUtils::ParseLayout(provider_options.at("layout"));
237+
}
238+
235239
if (provider_options.contains("load_config")) {
236240
auto parse_config = [&](const std::string& config_str) -> std::map<std::string, ov::AnyMap> {
237241
// If the config string is empty, return an empty map and skip processing

onnxruntime/test/perftest/command_args_parser.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ ABSL_FLAG(std::string, i, "",
6060
" [OpenVINO only] [num_of_threads]: Overrides the accelerator hardware type and precision with these values at runtime.\n"
6161
" [OpenVINO only] [cache_dir]: Explicitly specify the path to dump and load the blobs(Model caching) or cl_cache (Kernel Caching) files feature. If blob files are already present, it will be directly loaded.\n"
6262
" [OpenVINO only] [enable_opencl_throttling]: Enables OpenCL queue throttling for GPU device(Reduces the CPU Utilization while using GPU) \n"
63-
" [Example] [For OpenVINO EP] -e openvino -i \"device_type|CPU num_of_threads|5 enable_opencl_throttling|true cache_dir|\"<path>\"\"\n"
63+
" [OpenVINO only] [reshape_input]: Sets model input shapes with support for bounded dynamic dimensions using 'min..max' syntax (e.g., [1..10,3,224,224]) \n"
64+
" [OpenVINO only] [layout]: Specifies the layout for inputs/outputs to interpret tensor dimensions correctly. \n"
65+
" [Example] [For OpenVINO EP] -e openvino -i \"device_type|CPU num_of_threads|5 enable_opencl_throttling|true reshape_input|<input_name>[1,3,60,60..100] layout|<input_name>[NCHW] cache_dir|\"<path>\"\"\n"
6466
"\n"
6567
" [QNN only] [backend_type]: QNN backend type. E.g., 'cpu', 'htp'. Mutually exclusive with 'backend_path'.\n"
6668
" [QNN only] [backend_path]: QNN backend path. E.g., '/folderpath/libQnnHtp.so', '/winfolderpath/QnnHtp.dll'. Mutually exclusive with 'backend_type'.\n"

onnxruntime/test/perftest/ort_test_session.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,12 +906,14 @@ select from 'TF8', 'TF16', 'UINT8', 'FLOAT', 'ITENSOR'. \n)");
906906
ov_options[key] = value;
907907
} else if (key == "reshape_input") {
908908
ov_options[key] = value;
909+
} else if (key == "layout") {
910+
ov_options[key] = value;
909911
} else {
910912
ORT_THROW(
911913
"[ERROR] [OpenVINO] wrong key type entered. Choose from the following runtime key options that are available for OpenVINO."
912914
" ['device_type', 'device_id', 'num_of_threads', 'load_config', 'cache_dir', 'num_streams', "
913915
"'enable_opencl_throttling', 'disable_dynamic_shapes', 'enable_qdq_optimizer',"
914-
" 'enable_causallm', 'model_priority'] \n");
916+
" 'enable_causallm', 'reshape_input', 'layout', 'model_priority'] \n");
915917
}
916918
}
917919
session_options.AppendExecutionProvider_OpenVINO_V2(ov_options);

0 commit comments

Comments
 (0)