Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/source/openvino/models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ Here is the list of the supported architectures :
- XLM
- XLM-RoBERTa
- XVERSE
- Youtu-VL
- Zamba2

## [Diffusers](https://huggingface.co/docs/diffusers/index)
Expand Down
52 changes: 52 additions & 0 deletions optimum/exporters/openvino/input_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,58 @@ def generate(self, input_name: str, framework: str = "pt", int_dtype: str = "int
)


class DummyYoutuVLVisionEmbedInputGenerator(DummyVisionInputGenerator):
# Dummy inputs for the SigLIP2-based youtu_vl vision tower + patch merger exported as a single model.
# The window_index / cu_seqlens / rotary_pos_emb are precomputed at runtime from `spatial_shapes`
# (their construction contains dynamic-length python loops that are not traceable), so the exported
# model receives the already-materialized `attention_mask`, `window_attention_mask`, `window_index`
# and `rotary_pos_emb` tensors together with the patchified `pixel_values`.
SUPPORTED_INPUT_NAMES = (
"pixel_values",
"attention_mask",
"window_attention_mask",
"window_index",
"rotary_pos_emb",
)

def __init__(
self,
task: str,
normalized_config: NormalizedVisionConfig,
batch_size: int = 1,
**kwargs,
):
self.batch_size = batch_size
config = normalized_config.config
self.hidden_size = config.hidden_size
self.num_heads = config.num_attention_heads
self.num_channels = config.num_channels
self.patch_size = config.patch_size
self.spatial_merge_size = 2
self.spatial_merge_unit = self.spatial_merge_size * self.spatial_merge_size
# A small square grid that is divisible by the spatial merge size.
self.grid_h = 4
self.grid_w = 4
self.in_features = self.num_channels * self.patch_size * self.patch_size

def generate(self, input_name: str, framework: str = "pt", int_dtype: str = "int64", float_dtype: str = "fp32"):
seq_len = self.batch_size * self.grid_h * self.grid_w

if input_name == "pixel_values":
return self.random_float_tensor([seq_len, self.in_features], framework=framework, dtype=float_dtype)

if input_name in ["attention_mask", "window_attention_mask"]:
return self.random_mask_tensor([1, seq_len, seq_len], framework=framework, dtype=float_dtype)

if input_name == "rotary_pos_emb":
dim = self.hidden_size // self.num_heads // 2
return self.random_float_tensor([seq_len, dim], framework=framework, dtype=float_dtype)

if input_name == "window_index":
hidden_size = seq_len // self.spatial_merge_unit
return self.random_int_tensor([hidden_size], max_value=hidden_size)


class Qwen3ASRDummySeq2SeqPastKeyValuesGenerator(DummySeq2SeqPastKeyValuesGenerator):
"""Custom KV cache generator for Qwen3-ASR with GQA (num_key_value_heads != num_attention_heads).
Qwen3-ASR has no cross-attention, so only self-attention KV cache is generated (2 per layer)."""
Expand Down
132 changes: 132 additions & 0 deletions optimum/exporters/openvino/model_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
DummyVideoChatFlashQwenProjectorInputGenerator,
DummyVisionPositionIdsInputGenerator,
DummyVisionPositionIdsPhi4InputGenerator,
DummyYoutuVLVisionEmbedInputGenerator,
Eagle3DummyGenerator,
Eagle3VLMDummyGenerator,
FunASRDummyAudioInputGenerator,
Expand Down Expand Up @@ -187,6 +188,8 @@
SpeechT5ModelPatcher,
VideoChatFlashQwenVisionEmbeddingModelPatcher,
XverseModelPatcher,
YoutuVLLanguageModelPatcher,
YoutuVLVisionEmbMergerPatcher,
Zamba2ModelPatcher,
_get_model_attribute,
)
Expand Down Expand Up @@ -4587,6 +4590,135 @@ class DeepseekOpenVINOConfig(MiniCPM3OpenVINOConfig):
_MODEL_PATCHER = DeepseekPatcher


@register_in_tasks_manager(
"youtu_vl", *["text-generation", "text-generation-with-past"], library_name="transformers"
)
class YoutuVLTextOpenVINOConfig(TextDecoderWithPositionIdsOpenVINOConfig):
# Text backbone of youtu_vl: a DeepSeek-V3-style multi-latent-attention (MLA) decoder.
# The KV cache stores key states of dim `qk_nope_head_dim + qk_rope_head_dim` and value states
# of dim `v_head_dim` per attention head, matching the MiniCPM3/DeepSeek MLA cache layout.
MIN_TRANSFORMERS_VERSION = "4.53.0"
DUMMY_INPUT_GENERATOR_CLASSES = (DummyTextInputGenerator, OVMiniCPM3DummyPastKeyValuesGenerator)
DUMMY_PKV_GENERATOR_CLASS = OVMiniCPM3DummyPastKeyValuesGenerator
NORMALIZED_CONFIG_CLASS = NormalizedTextConfig
_MODEL_PATCHER = YoutuVLLanguageModelPatcher


class YoutuVLConfigBehavior(str, enum.Enum):
LANGUAGE = "language"
VISION_EMBEDDINGS = "vision_embeddings"
TEXT_EMBEDDINGS = "text_embeddings"


@register_in_tasks_manager("youtu_vl", *["image-text-to-text"], library_name="transformers")
class YoutuVLOpenVINOConfig(BaseVLMOpenVINOConfig):
# tencent/Youtu-VL-4B-Instruct: a VLM combining a SigLIP2 windowed vision tower + patch merger
# with a DeepSeek-V3-style MLA text backbone. The config is flat (text parameters live at the
# top level, so there is no `text_config`); the language behavior therefore uses the original
# config directly with the `youtu_vl` text-generation export config registered above.
MIN_TRANSFORMERS_VERSION = "4.53.0"
SUPPORTED_BEHAVIORS = [model_type.value for model_type in YoutuVLConfigBehavior]
NORMALIZED_CONFIG_CLASS = NormalizedVisionConfig
DUMMY_INPUT_GENERATOR_CLASSES = (DummyYoutuVLVisionEmbedInputGenerator,)

def __init__(
self,
config: "PretrainedConfig",
task: str = "feature-extraction",
int_dtype: str = "int64",
float_dtype: str = "fp32",
behavior: YoutuVLConfigBehavior = YoutuVLConfigBehavior.VISION_EMBEDDINGS,
preprocessors: Optional[List[Any]] = None,
**kwargs,
):
super().__init__(
config=config,
task=task,
int_dtype=int_dtype,
float_dtype=float_dtype,
preprocessors=preprocessors,
)
self._behavior = behavior
self._orig_config = config
if self._behavior == YoutuVLConfigBehavior.VISION_EMBEDDINGS and hasattr(config, "vision_config"):
self._config = config.vision_config
self._normalized_config = self.NORMALIZED_CONFIG_CLASS(self._config)

@staticmethod
def get_model_for_behavior(model, behavior: Union[str, YoutuVLConfigBehavior]):
if isinstance(behavior, str) and not isinstance(behavior, YoutuVLConfigBehavior):
behavior = YoutuVLConfigBehavior(behavior)

if behavior == YoutuVLConfigBehavior.LANGUAGE:
return model

if behavior == YoutuVLConfigBehavior.VISION_EMBEDDINGS:
return model

if behavior == YoutuVLConfigBehavior.TEXT_EMBEDDINGS:
text_embedding = model.get_input_embeddings()
text_embedding.config = model.config
return text_embedding

def with_behavior(
self,
behavior: Union[str, YoutuVLConfigBehavior],
):
if isinstance(behavior, str) and not isinstance(behavior, YoutuVLConfigBehavior):
behavior = YoutuVLConfigBehavior(behavior)

if behavior == YoutuVLConfigBehavior.TEXT_EMBEDDINGS:
return get_vlm_text_embeddings_config(
"youtu_vl",
self._orig_config,
self.int_dtype,
self.float_dtype,
)

if behavior == YoutuVLConfigBehavior.LANGUAGE:
return get_vlm_text_generation_config(
"youtu_vl",
self._orig_config,
self.int_dtype,
self.float_dtype,
model_patcher=YoutuVLLanguageModelPatcher,
)

if behavior == YoutuVLConfigBehavior.VISION_EMBEDDINGS:
return self.__class__(
self._orig_config,
task=self.task,
int_dtype=self.int_dtype,
float_dtype=self.float_dtype,
behavior=behavior,
preprocessors=self._preprocessors,
)

def patch_model_for_export(self, model: PreTrainedModel, model_kwargs: Optional[Dict[str, Any]] = None):
model_kwargs = model_kwargs or {}
if self._behavior == YoutuVLConfigBehavior.VISION_EMBEDDINGS:
return YoutuVLVisionEmbMergerPatcher(self, model, model_kwargs)
return super().patch_model_for_export(model, model_kwargs)

@property
def inputs(self) -> Dict[str, Dict[int, str]]:
if self._behavior == YoutuVLConfigBehavior.VISION_EMBEDDINGS:
return {
"pixel_values": {0: "sequence_length"},
"attention_mask": {1: "sequence_length", 2: "sequence_length"},
"window_attention_mask": {1: "sequence_length", 2: "sequence_length"},
"window_index": {0: "unit_sequence_length"},
"rotary_pos_emb": {0: "sequence_length"},
}
return {}

@property
def outputs(self) -> Dict[str, Dict[int, str]]:
if self._behavior == YoutuVLConfigBehavior.VISION_EMBEDDINGS:
return {"last_hidden_state": {0: "seq_len"}}
return {}


@register_in_tasks_manager("got_ocr2", *["image-to-text", "image-text-to-text"], library_name="transformers")
class GotOCR2OpenVINOConfig(BaseVLMOpenVINOConfig):
MAX_TRANSFORMERS_VERSION = "4.57.6"
Expand Down
Loading
Loading