Skip to content

CVS-175736 - [OVEP] Optimize Stateful Path: use output-to-input strategy to get the pairs of KV name - #845

Merged
MayureshV1 merged 10 commits into
intel:ovep-developfrom
Kotomi-Du:optimize_stateful_path
Nov 15, 2025
Merged

CVS-175736 - [OVEP] Optimize Stateful Path: use output-to-input strategy to get the pairs of KV name#845
MayureshV1 merged 10 commits into
intel:ovep-developfrom
Kotomi-Du:optimize_stateful_path

Conversation

@Kotomi-Du

@Kotomi-Du Kotomi-Du commented Nov 6, 2025

Copy link
Copy Markdown

Description

It is hardcoded for the path of identifying KV pairs. This PR is to make it general to fit most of the cases. It is a follow up of #821

Here is the strategy:

  1. check output name with "present" to get output_tensor list
  2. extract the pattern, e.g. key_***_%d; value_***_%d from output tesnor
  3. apply extracted pattern for input name to get input_tensor list.

Jira Ticket :

https://jira.devtools.intel.com/browse/CVS-175736

Go to new ABI?

Yes

Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc Outdated
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR optimizes the stateful path handling in OpenVINO by implementing a dynamic pattern extraction strategy to identify KV (key-value) pairs, replacing the previous hardcoded approach. The strategy extracts patterns from output tensors containing "present" and applies them to identify corresponding input tensors.

Key Changes:

  • Introduced regex-based pattern extraction from output tensor names to dynamically identify KV pairs
  • Added fallback mechanism using substring matching when pattern extraction yields no results
  • Refactored PatchStatefulDecoder to use the new pattern-based extraction functions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc Outdated
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc Outdated
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc Outdated
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc Outdated
if (name.starts_with("present")) {
key_value_output_names.push_back(name);
std::smatch match;
if (std::regex_match(name, match, present_pattern)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a regular expression here? It seems like it can be simplified to a regular string manipulation: we already know that the string starts with "present" and we can find where the last underscore is, gathering the substring.
This is also a micro-optimization in terms of performance, since C++ regexps are compiled at runtime and are known for their bad performance.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc
auto [key_value_output_names, extracted_patterns] = ExtractKVPatternsFromOutputs(model);
auto [key_value_input_names, not_kv_inputs] = ExtractInputKVTensors(model, extracted_patterns);

std::cout << key_value_input_names.size() << ";" << key_value_output_names.size() << std::endl;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like a debug statement here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

}

if (key_value_input_names.size() != key_value_output_names.size()) {
std::cout << "found different sizes btween key_value_input_names and key_value_output_names, they couldn't be paired" << std::endl;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this one should be a runtime exception of some sort. I don't think we'd ever want to hit this state, return, and have the rest of the stateful flow continue on.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated


std::cout << key_value_input_names.size() << ";" << key_value_output_names.size() << std::endl;
if (key_value_input_names.empty() || key_value_output_names.empty()) {
std::cout << "no key_value_input_names or key_value_output_names found" << std::endl;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for here as below -- I think there should be a runtime exception thrown here. I don't think we'd ever intend for the stateful flow to get enabled, and not identify pairs of tensors to perform a make_stateful transformation on.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

// https://github.com/huggingface/optimum-intel/blob/main/optimum/exporters/openvino/stateful.py#L281
void PatchStatefulDecoder(std::shared_ptr<ov::Model> model) {
// Helper function to extract KV patterns from output names dynamically
std::pair<std::vector<std::string>, std::vector<std::string>> ExtractKVPatternsFromOutputs(const std::shared_ptr<ov::Model>& model) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function returns two std::vectors only to check that the first one is non-empty and the second one is used as a sort of lookup table. Therefore, it can return std::optional<T> instead.

@Kotomi-Du Kotomi-Du Nov 10, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each element in key_value_output_names will be passed as information for making stateful, it cannot be switched to std::optional.

It is updated to std::optional for patterns now.

void PatchStatefulDecoder(std::shared_ptr<ov::Model> model) {
// Helper function to extract KV patterns from output names dynamically
std::pair<std::vector<std::string>, std::vector<std::string>> ExtractKVPatternsFromOutputs(const std::shared_ptr<ov::Model>& model) {
std::set<std::string> unique_patterns;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider switching to std::unordered_set<T> if you don't need the values to be sorted.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

}
}
}
std::vector<std::string> extracted_patterns(unique_patterns.begin(), unique_patterns.end());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to construct a std::vector here? Would it be possible to return the set directly?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated it to std::optional<std::pair<std::string, std::string>> now.

Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc
@Kotomi-Du
Kotomi-Du requested a review from Copilot November 12, 2025 18:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc
Comment thread onnxruntime/core/providers/openvino/ov_stateful_patch_utils.cc Outdated

@RyanMetcalfeInt8 RyanMetcalfeInt8 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@RyanMetcalfeInt8

Copy link
Copy Markdown

@MayureshV1 -- can you please take a look and merge if you are okay with this?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

size_t last_underscore_pos = name.rfind('_');
// Extract pattern between "present_" and the last underscore
if (last_underscore_pos != std::string::npos && last_underscore_pos > prefix_len) {
std::string pattern = name.substr(prefix_len, last_underscore_pos - prefix_len);

Copilot AI Nov 14, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern extraction assumes the format 'present__' but doesn't validate that the suffix after the last underscore is actually numeric. This could lead to incorrect pattern extraction if output names have different formats (e.g., 'present_key_cross_layer_0'). Consider validating the numeric suffix before extracting the pattern.

Copilot uses AI. Check for mistakes.
Comment on lines +166 to +168
if (unique_patterns.size() > 2) {
ORT_THROW("More than two unique KV patterns found in output names.");
}

Copilot AI Nov 14, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded limit of 2 patterns contradicts the PR's goal of making the code more general. Consider either removing this restriction or making it configurable, as models with different architectures might legitimately have more than two KV pattern types.

Suggested change
if (unique_patterns.size() > 2) {
ORT_THROW("More than two unique KV patterns found in output names.");
}

Copilot uses AI. Check for mistakes.

@gblong1 gblong1 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@MayureshV1
MayureshV1 merged commit d0bac3e into intel:ovep-develop Nov 15, 2025
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants