Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9fc0e2a
deprecate export_pytorch_via_onnx
echarlaix May 21, 2026
9a2f46f
remove optimum-onnx dep
echarlaix May 21, 2026
8c5c5fb
style
echarlaix May 21, 2026
7439b8b
remove redundant postprocess_past_key_values
echarlaix May 21, 2026
3f3b218
onnx to ov config
echarlaix May 26, 2026
6f235d5
remove unused
echarlaix May 27, 2026
a959574
style
echarlaix May 27, 2026
e0a04ce
moving utils
echarlaix May 27, 2026
289617f
move configs
echarlaix May 27, 2026
3de4f6a
rename for clarity
echarlaix May 27, 2026
b820ff5
fix imports
echarlaix May 27, 2026
802397c
fix
echarlaix May 27, 2026
67fc3b9
fix
echarlaix May 27, 2026
2c574e8
deprecate ONNX_SUPPORTED_ARCHITECTURES
echarlaix May 27, 2026
397ef78
remove unnecessary OVMistralDummyPastKeyValuesGenerator
echarlaix May 27, 2026
a272f93
add missing
echarlaix May 27, 2026
16926be
add missing patched_gemma4_clippable_linear_forward
echarlaix May 27, 2026
63b3269
fix
echarlaix May 27, 2026
7366916
postprocess_past_key_values
echarlaix May 28, 2026
7b4510d
add donut swin config
echarlaix May 28, 2026
116b0d0
fix speecht5
echarlaix May 28, 2026
4af9cff
add TrOCROpenVINOConfig
echarlaix May 28, 2026
fd705dd
remove onnx
echarlaix May 28, 2026
008cf69
style
echarlaix May 28, 2026
b46c5fc
protobuf
echarlaix May 28, 2026
fe6792a
remove opset
echarlaix May 28, 2026
1f98e9e
fix
echarlaix May 28, 2026
b18eb3f
style
echarlaix May 28, 2026
6404ebe
fix timm export
echarlaix May 29, 2026
89ccfbf
fix
echarlaix May 29, 2026
c423c91
remove unecessary eager_mask_without_vmap
echarlaix May 29, 2026
4907bb4
remove patching
echarlaix May 29, 2026
7e5185b
remove noop_bfloat16_casting
echarlaix May 29, 2026
1770c90
clean PatchingSpec
echarlaix Jun 2, 2026
db9d6d2
Update optimum/exporters/openvino/base.py
echarlaix Jun 2, 2026
003695c
Update optimum/exporters/openvino/base.py
echarlaix Jun 2, 2026
0568cb1
Update optimum/exporters/openvino/convert.py
echarlaix Jun 2, 2026
3f47ab1
remove onnx reference
echarlaix Jun 2, 2026
2a5ce6a
remove patched_speecht5_prenet_forward
echarlaix Jun 2, 2026
6f99775
rename onnx to openvino
echarlaix Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion optimum/exporters/openvino/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import optimum.exporters.openvino.model_configs

from .__main__ import main_export
from .convert import export, export_from_model, export_models, export_pytorch_via_onnx
from .convert import export, export_from_model, export_models
from .stateful import ensure_stateful_is_available, patch_stateful


Expand Down
6 changes: 3 additions & 3 deletions optimum/exporters/openvino/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from transformers.utils import is_torch_available

from openvino import Core, Type, save_model
from optimum.exporters.onnx.base import OnnxConfig
from optimum.exporters.openvino.base import OpenVINOConfig
from optimum.exporters.tasks import TasksManager
from optimum.intel.utils.import_utils import (
DIFFUSERS_IMPORT_ERROR,
Expand Down Expand Up @@ -227,7 +227,7 @@ def main_export(
local_files_only: bool = False,
token: Optional[Union[bool, str]] = None,
model_kwargs: Optional[Dict[str, Any]] = None,
custom_export_configs: Optional[Dict[str, "OnnxConfig"]] = None,
custom_export_configs: Optional[Dict[str, "OpenVINOConfig"]] = None,
fn_get_submodels: Optional[Callable] = None,
ov_config: "OVConfig" = None,
stateful: bool = True,
Expand Down Expand Up @@ -283,7 +283,7 @@ def main_export(
the export. This argument should be used along the `custom_export_configs` argument
in case, for example, the model inputs/outputs are changed (for example, if
`model_kwargs={"output_attentions": True}` is passed).
custom_export_configs (`Optional[Dict[str, OnnxConfig]]`, defaults to `None`):
custom_export_configs (`Optional[Dict[str, OpenVINOConfig]]`, defaults to `None`):
Experimental usage: override the default export config used for the given model. This argument may be useful for advanced users that desire a finer-grained control on the export. An example is available [here](https://huggingface.co/docs/optimum/main/en/exporters/onnx/usage_guides/export_a_model).
fn_get_submodels (`Optional[Callable]`, defaults to `None`):
Experimental usage: Override the default submodels that are used at the export. This is
Expand Down
107 changes: 107 additions & 0 deletions optimum/exporters/openvino/_traceable_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Copyright 2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations

import logging
from typing import Any

import torch


logger = logging.getLogger(__name__)


# Simply removing the nn.Module, same as in https://github.com/huggingface/transformers/pull/35873
class TraceableCache:
"""Base, abstract class for all caches. The actual data structure is specific to each subclass."""

def __init__(self):
super().__init__()

def update(
self,
key_states: torch.Tensor,
value_states: torch.Tensor,
layer_idx: int,
cache_kwargs: dict[str, Any] | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Updates the cache with the new `key_states` and `value_states` for the layer `layer_idx`.

Parameters:
key_states (`torch.Tensor`):
The new key states to cache.
value_states (`torch.Tensor`):
The new value states to cache.
layer_idx (`int`):
The index of the layer to cache the states for.
cache_kwargs (`Dict[str, Any]`, `optional`):
Additional arguments for the cache subclass. These are specific to each subclass and allow new types of
cache to be created.

Return:
A tuple containing the updated key and value states.
"""
raise NotImplementedError("Make sure to implement `update` in a subclass.")

def get_seq_length(self, layer_idx: int | None = 0) -> int:
"""Returns the sequence length of the cached states. A layer index can be optionally passed."""
# TODO: deprecate this function in favor of `cache_position`
raise NotImplementedError("Make sure to implement `get_seq_length` in a subclass.")

# Deprecate in favor of max-cache-shape because we want to be specific by what we mean with "max_length"
# Prev some cache objects didn't have "max_length" (SlidingWindowCache or SinkCache) because the cache object technically handles
# infinite amount of tokens. In the codebase what we really need to check is the max capacity of certain cache instances, so
# we change naming to be more explicit
def get_max_length(self) -> int | None:
logger.warning_once(
"`get_max_cache()` is deprecated for all Cache classes. Use `get_max_cache_shape()` instead. "
"Calling `get_max_cache()` will raise error from v4.48"
)
return self.get_max_cache_shape()

def get_max_cache_shape(self) -> int | None:
"""Returns the maximum sequence length (i.e. max capacity) of the cache object."""
raise NotImplementedError("Make sure to implement `get_max_cache_shape` in a subclass.")

def get_usable_length(self, new_seq_length: int, layer_idx: int | None = 0) -> int:
"""Given the sequence length of the new inputs, returns the usable length of the cache."""
# Cache without size limit -> all cache is usable
# Cache with size limit -> if the length cache plus the length of the new inputs is larger the maximum cache
# length, we will need to evict part of the cache (and thus not all cache is usable)
max_length = self.get_max_cache_shape()
previous_seq_length = self.get_seq_length(layer_idx)
if max_length is not None and previous_seq_length + new_seq_length > max_length:
return max_length - new_seq_length
return previous_seq_length

def reorder_cache(self, beam_idx: torch.LongTensor):
"""Reorders the cache for beam search, given the selected beam indices."""
for layer_idx in range(len(self.key_cache)):
if self.key_cache[layer_idx] != []:
device = self.key_cache[layer_idx].device
self.key_cache[layer_idx] = self.key_cache[layer_idx].index_select(0, beam_idx.to(device))
if self.value_cache[layer_idx] != []:
device = self.value_cache[layer_idx].device
self.value_cache[layer_idx] = self.value_cache[layer_idx].index_select(0, beam_idx.to(device))

@property
def seen_tokens(self):
logger.warning_once(
"The `seen_tokens` attribute is deprecated and will be removed in v4.41. Use the `cache_position` "
"model input instead."
)
if hasattr(self, "_seen_tokens"):
return self._seen_tokens
else:
return None
175 changes: 175 additions & 0 deletions optimum/exporters/openvino/_traceable_decorator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Copyright 2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import warnings
from collections import defaultdict
from functools import wraps

from transformers.utils.generic import logger


try:
# transformers>=5.2
from transformers.utils.output_capturing import _CAN_RECORD_REGISTRY, OutputRecorder
except ImportError:
from transformers.utils.generic import _CAN_RECORD_REGISTRY, OutputRecorder


# This is a fixed version of transformers.utils.generic.check_model_inputs
# that fixes issues related to export and tracing
# - adds support for positional args (use_cache), without which use_cache end up being passed twice
# - fixes issue with default capture_flags being None for some models
def traceable_check_model_inputs(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
use_cache = (
kwargs["use_cache"] if kwargs.get("use_cache") is not None else getattr(self.config, "use_cache", None)
)
if use_cache is not None:
if getattr(self, "gradient_checkpointing", False) and self.training and use_cache:
logger.warning_once(
"`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`."
)
use_cache = False

# Prevent passing use_cache twice
if "use_cache" in func.__code__.co_varnames:
use_cache_idx = func.__code__.co_varnames.index("use_cache") - 1 # minus 1 for 'self'
if len(args) > use_cache_idx:
args = list(args)
args[use_cache_idx] = use_cache
args = tuple(args)
else:
kwargs["use_cache"] = use_cache

return_dict = kwargs.pop("return_dict", None)
if return_dict is None:
return_dict = getattr(self.config, "return_dict", True)

all_args = kwargs.copy()
if "kwargs" in all_args:
for k, v in all_args["kwargs"].items():
all_args[k] = v

capture_flags = _CAN_RECORD_REGISTRY.get(str(self.__class__)) or {} # there is a weak ref for executorch

recordable_keys = {
f"output_{k}": all_args.get(
f"output_{k}",
getattr(
self.config,
f"output_{k}",
all_args.get("output_attentions", getattr(self.config, "output_attentions", False)),
),
)
for k in capture_flags
}

# We let cross attentions to be saved separately because some models add `cross-attn` layer
# when certain conditions are met. Let's output cross attention if attentions are requested (for BC)
if "output_attentions" in recordable_keys:
recordable_keys["output_cross_attentions"] = recordable_keys["output_attentions"]

collected_outputs = defaultdict(tuple)
monkey_patched_layers = []

# Check attention implementation is properly set for capturing attention outputs
if recordable_keys.get("output_attentions", False):
supported_attn = ["eager", "eager_paged", "flex_attention"]
config_attn = getattr(self.config, "_attn_implementation", None)
sub_configs = [getattr(self.config, key, None) for key in self.config.sub_configs]
sub_configs_attn = [
getattr(config, "_attn_implementation", None) for config in sub_configs if config is not None
]
if config_attn not in supported_attn or any(attn not in supported_attn for attn in sub_configs_attn):
warnings.warn(
f"`output_attentions=True` is not supported with `attn_implementation` other than {supported_attn}. "
"Please use `model.set_attn_implementation('eager')` to enable capturing attention outputs.",
UserWarning,
stacklevel=2,
)

def make_capture_wrapper(module, orig_forward, key, index):
@wraps(orig_forward)
def wrapped_forward(*args, **kwargs):
if key == "hidden_states" and len(collected_outputs[key]) == 0:
collected_outputs[key] += (args[0],)
output = orig_forward(*args, **kwargs)
if not isinstance(output, tuple):
collected_outputs[key] += (output,)
elif output[index] is not None:
if key not in collected_outputs:
collected_outputs[key] = (output[index],)
else:
collected_outputs[key] += (output[index],)
return output

return wrapped_forward

if any(recordable_keys.values()):
capture_tasks = []
for key, layer_specs in capture_flags.items():
if not recordable_keys.get(f"output_{key}", False):
continue
if not isinstance(layer_specs, list):
layer_specs = [layer_specs]
for specs in layer_specs:
if not isinstance(specs, OutputRecorder):
index = 0 if "hidden_states" in key else 1
class_name = None if not isinstance(specs, str) else specs
target_class = specs if not isinstance(specs, str) else None
specs = OutputRecorder(target_class=target_class, index=index, class_name=class_name)
capture_tasks.append((key, specs))

for name, module in self.named_modules():
for key, specs in capture_tasks:
# The second check is for multimodals where only backbone layer suffix is available
if (specs.target_class is not None and isinstance(module, specs.target_class)) or (
specs.class_name is not None and name.endswith(specs.class_name)
):
if specs.layer_name is not None and specs.layer_name not in name:
continue
# Monkey patch forward
original_forward = module.forward
module.forward = make_capture_wrapper(module, original_forward, key, specs.index)
monkey_patched_layers.append((module, original_forward))

outputs = func(self, *args, **kwargs)
# Restore original forward methods
for module, original_forward in monkey_patched_layers:
module.forward = original_forward

# Inject collected outputs into model output
for key in collected_outputs:
if key == "hidden_states":
if hasattr(outputs, "vision_hidden_states"):
collected_outputs[key] = collected_outputs[key][:-1]
collected_outputs[key] += (outputs.vision_hidden_states,)
elif hasattr(outputs, "last_hidden_state"):
collected_outputs[key] = collected_outputs[key][:-1]
collected_outputs[key] += (outputs.last_hidden_state,)

outputs[key] = collected_outputs[key]
elif key == "attentions":
if isinstance(capture_flags[key], list) and len(capture_flags[key]) == 2:
outputs[key] = collected_outputs[key][0::2]
outputs["cross_" + key] = collected_outputs[key][1::2]
else:
outputs[key] = collected_outputs[key]
else:
outputs[key] = collected_outputs[key]
if return_dict is False:
outputs = outputs.to_tuple()
return outputs

return wrapper
Loading
Loading