Skip to content

Commit e992272

Browse files
preetha-inteljatinwadhwa921
authored andcommitted
Support for bounded dynamic model (#701)
* Refactored the code for reshape feature * Refactor the inference logic accomodating bounded dimensions * Fix lint issues * Refactor OV shapes classification to be a part of bindings struct * Refactor the provider options key verification for python interface * Restrict removal of model proto when CPU fallback is enabled and fix unit test failures --------- Co-authored-by: jatinwadhwa921 <[email protected]>
1 parent 7f7f45b commit e992272

17 files changed

+445
-196
lines changed

onnxruntime/core/providers/openvino/backend_manager.cc

Lines changed: 110 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ BackendManager::BackendManager(SessionContext& session_context,
7070
// Save the indexes of graph inputs among fused_node's inputDefs
7171
// (which also contains initializers).
7272
for (uint32_t index = 0; const auto& node : subgraph.GetInputs()) {
73+
if (subgraph.GetGraph().GetConsumerNodes(node->Name()).size() == 0) {
74+
continue; // Skip if the input is a dangling node
75+
}
7376
subgraph_context_.input_names.insert({node->Name(), index++});
7477
}
7578

@@ -110,7 +113,7 @@ BackendManager::BackendManager(SessionContext& session_context,
110113
subgraph_context_.has_dynamic_input_shape = true;
111114
LOGS_DEFAULT(INFO) << "[OpenVINO-EP] Model has symbolic input dims";
112115
if (cpu_or_gpu || (npu && session_context_.enable_causallm) &&
113-
!session_context_.disable_dynamic_shapes) {
116+
!session_context_.disable_dynamic_shapes) {
114117
LOGS_DEFAULT(INFO) << "[OpenVINO-EP] Starting backend initialization. "
115118
<< "Creating backend Dynamic Shapes";
116119
try {
@@ -291,24 +294,83 @@ bool BackendManager::ModelHasBatchedInputs(const ONNX_NAMESPACE::ModelProto& mod
291294
}
292295

293296
bool BackendManager::ModelHasSymbolicInputDims(const onnxruntime::GraphViewer& subgraph) const {
294-
bool has_sym_dims = false;
295-
auto graph_inputs = subgraph.GetInputs();
296-
for (auto input : graph_inputs) {
297+
const auto& graph_inputs = subgraph.GetInputs();
298+
299+
// First validate shapes if provided by user
300+
bool shapes_valid = true;
301+
if (!session_context_.reshape.empty()) {
302+
try {
303+
ValidateInputShapes(session_context_.reshape, graph_inputs);
304+
} catch (const std::exception& e) {
305+
LOGS_DEFAULT(ERROR) << "[OpenVINO-EP] Shape validation failed: " << e.what();
306+
session_context_.reshape.clear(); // Clear the shape map as it's invalid
307+
shapes_valid = false;
308+
}
309+
}
310+
311+
// Count dynamic inputs and check if reshape covers all of them
312+
size_t dynamic_input_count = 0;
313+
bool all_dynamic_inputs_covered = true;
314+
315+
for (const auto* input : graph_inputs) {
316+
// Skip dangling inputs (no consumers)
317+
if (subgraph.GetGraph().GetConsumerNodes(input->Name()).empty()) {
318+
continue;
319+
}
320+
321+
// Check if input has dynamic dimensions
322+
bool has_dynamic_dim = false;
323+
324+
// Case 1: Completely undefined shape
297325
if (input->Shape() == nullptr) {
298-
has_sym_dims = true;
299-
break;
326+
has_dynamic_dim = true;
300327
}
301-
for (auto& dim : input->Shape()->dim()) {
302-
if (dim.value_case() != dim.kDimValue) {
303-
has_sym_dims = true;
304-
break;
328+
// Case 2: Shape defined but with symbolic dimensions
329+
else {
330+
for (const auto& dim : input->Shape()->dim()) {
331+
if (dim.value_case() != dim.kDimValue) {
332+
has_dynamic_dim = true;
333+
break;
334+
}
305335
}
306336
}
307-
if (has_sym_dims) {
308-
break;
337+
338+
// If dynamic, count it and check if reshape covers it
339+
if (has_dynamic_dim) {
340+
dynamic_input_count++;
341+
342+
// Check if this dynamic input is covered by reshape input
343+
if (!session_context_.reshape.empty() &&
344+
session_context_.reshape.find(input->Name()) == session_context_.reshape.end()) {
345+
all_dynamic_inputs_covered = false;
346+
LOGS_DEFAULT(WARNING) << "[OpenVINO-EP] reshape_input is provided but doesn't cover dynamic input: "
347+
<< input->Name();
348+
}
309349
}
310350
}
311-
return has_sym_dims;
351+
352+
const bool has_symbolic_dims = (dynamic_input_count > 0);
353+
354+
// Early return if no reshape input provided
355+
if (session_context_.reshape.empty()) {
356+
return has_symbolic_dims; // Return based on whether model has symbolic dims
357+
}
358+
359+
// For dynamic models with incomplete reshape coverage, clear shapes
360+
if (has_symbolic_dims && !all_dynamic_inputs_covered) {
361+
session_context_.reshape.clear();
362+
LOGS_DEFAULT(WARNING) << "reshape_input does not cover all dynamic dimensions, "
363+
<< "ignoring all provided shapes";
364+
return true; // Model is dynamic
365+
}
366+
367+
// If shapes are valid with complete coverage for dynamic model, treat as concrete
368+
if (has_symbolic_dims && shapes_valid && all_dynamic_inputs_covered) {
369+
LOGS_DEFAULT(INFO) << "All dynamic dimensions successfully covered by reshape_input";
370+
return false; // Model is now effectively static with concrete shapes
371+
}
372+
373+
return has_symbolic_dims; // Return dynamic status based on symbolic dimensions
312374
}
313375

314376
// Check to see if the graph is QDQ
@@ -386,7 +448,7 @@ BackendManager::GetModelProtoFromFusedNode(const onnxruntime::Node& fused_node,
386448
const auto& onnx_model_path_name = subgraph.ModelPath();
387449
// QDQ stripping enabled only for the NPU and experimentally on the GPU
388450
if ((session_context_.device_type.find("NPU") != std::string::npos ||
389-
session_context_.device_type.find("GPU") != std::string::npos) &&
451+
session_context_.device_type.find("GPU") != std::string::npos) &&
390452
(enable_ovep_qdq_optimizer || session_context_.so_share_ep_contexts)) {
391453
std::unique_ptr<onnxruntime::Model> model;
392454
Status status = CreateModelWithStrippedQDQNodes(subgraph, logger, session_context_.so_share_ep_contexts, enable_ovep_qdq_optimizer, model, shared_context_.shared_weights);
@@ -480,6 +542,40 @@ BackendManager::ReWriteBatchDimWithOne(const ONNX_NAMESPACE::ModelProto& model_p
480542
return model_copy;
481543
}
482544

545+
void BackendManager::ValidateInputShapes(const reshape_t& shapes,
546+
const std::vector<const NodeArg*>& graph_inputs) const {
547+
for (const auto& [tensor_name, requested_shape] : shapes) {
548+
// Find matching input in graph
549+
const NodeArg* graph_input = nullptr;
550+
for (const auto* input : graph_inputs) {
551+
if (input->Name() == tensor_name) {
552+
graph_input = input;
553+
break;
554+
}
555+
}
556+
557+
if (!graph_input) {
558+
ORT_THROW("Input '" + tensor_name + "' specified in reshape_input does not exist in the graph");
559+
}
560+
561+
const ONNX_NAMESPACE::TensorShapeProto* graph_shape = graph_input->Shape();
562+
if (!graph_shape) {
563+
ORT_THROW("Graph input '" + tensor_name + "' has no shape information");
564+
}
565+
566+
// Check dimensions count matches
567+
size_t graph_dim_count = graph_shape->dim_size();
568+
size_t requested_dim_count = requested_shape.get_max_shape().size();
569+
570+
if (graph_dim_count != requested_dim_count) {
571+
ORT_THROW("Dimensions mismatch for input '" + tensor_name +
572+
"': graph expects " + std::to_string(graph_dim_count) +
573+
" dimensions but reshape_input specifies " +
574+
std::to_string(requested_dim_count) + " dimensions");
575+
}
576+
}
577+
}
578+
483579
void BackendManager::Compute(OrtKernelContext* context) {
484580
Ort::KernelContext ctx(context);
485581
std::chrono::high_resolution_clock::time_point start_compute, end_compute;

onnxruntime/core/providers/openvino/backend_manager.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ class BackendManager {
3939
const logging::Logger& logger) const;
4040

4141
bool ModelHasSymbolicInputDims(const onnxruntime::GraphViewer& subgraph) const;
42+
std::unordered_set<std::string> IdentifyDynamicInputs(const onnxruntime::GraphViewer& subgraph,
43+
const std::vector<const NodeArg*>& graph_inputs) const;
4244
bool ModelHasBatchedInputs(const ONNX_NAMESPACE::ModelProto& model_proto) const;
45+
void ValidateInputShapes(const reshape_t& shapes,
46+
const std::vector<const NodeArg*>& graph_inputs) const;
4347

4448
std::shared_ptr<ONNX_NAMESPACE::ModelProto>
4549
ReWriteBatchDimWithOne(const ONNX_NAMESPACE::ModelProto& model_proto);

onnxruntime/core/providers/openvino/backend_utils.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ CreateOVModel(std::string&& model,
146146
try {
147147
auto ov_model = OVCore::Get()->ReadModel(std::move(model), session_context.onnx_model_path_name.string());
148148

149+
if (!session_context.reshape.empty()) {
150+
LOGS_DEFAULT(INFO) << log_tag << "Reshaping the ov tensor to specified shape";
151+
ov_model->reshape(session_context.reshape);
152+
}
149153
// Check for Constant Folding
150154
if ((session_context.device_type != "NPU") && !session_context.is_wholly_supported_graph) {
151155
ov::pass::ConstantFolding pass_const_obj;

0 commit comments

Comments
 (0)