-
Notifications
You must be signed in to change notification settings - Fork 391
Add Qwen3.5 hybrid model support #3592
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
69f0de1
64aad8b
c5ddc81
6f4e457
4f1a13f
58cfb0c
a8d98f6
584aa12
b68153a
c99df00
bc2002a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -734,9 +734,22 @@ class VLMPipeline::VLMPipelineImpl : public VLMPipelineBase{ | |
| ov::Tensor new_atten_mask = ov::Tensor{ov::element::i64, { 1, history_size + inputs_embeds_size }}; | ||
| std::fill_n(new_atten_mask.data<int64_t>(), new_atten_mask.get_size(), 1); | ||
|
|
||
| ov::Tensor position_ids; | ||
| // Only compute and pass position_ids if the language model accepts them. | ||
| // Hybrid models (e.g. Qwen3.5) compute rotary embeddings internally. | ||
| std::optional<ov::Tensor> position_ids; | ||
| std::optional<int64_t> rope_delta; | ||
| std::tie(position_ids, rope_delta) = m_inputs_embedder->get_position_ids(inputs_embeds_size, history_size); | ||
| bool has_position_ids_input = false; | ||
| for (const auto& input : m_language.get_compiled_model().inputs()) { | ||
| if (input.get_any_name() == "position_ids") { | ||
| has_position_ids_input = true; | ||
| break; | ||
| } | ||
| } | ||
|
Comment on lines
+741
to
+747
|
||
| if (has_position_ids_input) { | ||
| auto [pos_ids, delta] = m_inputs_embedder->get_position_ids(inputs_embeds_size, history_size); | ||
| position_ids = std::move(pos_ids); | ||
| rope_delta = delta; | ||
| } | ||
|
Comment on lines
+737
to
+752
|
||
|
|
||
| const auto& lm_extra_inputs = m_inputs_embedder->get_lm_extra_inputs(); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,185 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copyright (C) 2023-2026 Intel Corporation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "visual_language/qwen3_5/classes.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "visual_language/qwen2vl/classes.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "utils.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace ov::genai { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::unordered_map<std::string, ov::Tensor> g_empty_extra_inputs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } // namespace | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| InputsEmbedderQwen3_5::InputsEmbedderQwen3_5( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const VLMConfig& vlm_config, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::filesystem::path& model_dir, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::string& device, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ov::AnyMap device_config) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : InputsEmbedderQwen3VL(vlm_config, model_dir, device, device_config) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| InputsEmbedderQwen3_5::InputsEmbedderQwen3_5( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const VLMConfig& vlm_config, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ModelsMap& models_map, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const Tokenizer& tokenizer, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::filesystem::path& config_dir_path, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::string& device, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ov::AnyMap device_config) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : InputsEmbedderQwen3VL(vlm_config, models_map, tokenizer, config_dir_path, device, device_config) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::pair<ov::Tensor, ov::Tensor> InputsEmbedderQwen3_5::run_video_image_embeddings_merger( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::vector<EncodedImage>& images, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::vector<size_t>& images_sequence, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::vector<EncodedVideo>& videos, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const std::vector<size_t>& videos_sequence | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto [reordered_image_embeds, reordered_images_grid_thw] = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qwen2_vl_utils::reorder_image_embeds_and_grid_thw(images, images_sequence); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto [reordered_video_embeds, reordered_videos_grid_thw] = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qwen2_vl_utils::reorder_video_embeds_and_grid_thw(videos, videos_sequence); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ov::Tensor concatenated_embeds = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qwen2_vl_utils::concatenate_video_image_embeds(reordered_video_embeds, reordered_image_embeds); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::vector<std::array<size_t, 3>> combined_grid_thw; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| combined_grid_thw.insert(combined_grid_thw.end(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reordered_videos_grid_thw.begin(), reordered_videos_grid_thw.end()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| combined_grid_thw.insert(combined_grid_thw.end(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reordered_images_grid_thw.begin(), reordered_images_grid_thw.end()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add interpolated position embeddings (reused from Qwen3-VL parent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!combined_grid_thw.empty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ov::Tensor pos_embeds = get_interpolated_pos_embeds(combined_grid_thw); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| float* concat_data = concatenated_embeds.data<float>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const float* pos_data = pos_embeds.data<const float>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (size_t i = 0; i < concatenated_embeds.get_size(); ++i) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| concat_data[i] += pos_data[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+55
to
+61
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ov::Tensor rotary_pos_emb = get_rotary_pos_emb(combined_grid_thw); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CircularBufferQueueElementGuard<ov::InferRequest> infer_request_guard( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this->m_ireq_queue_vision_embeddings_merger.get()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ov::InferRequest& merger = infer_request_guard.get(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| merger.set_tensor("hidden_states", concatenated_embeds); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (m_with_cu_seqlens_input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| merger.set_tensor("cu_seq_lens", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qwen2_vl_utils::get_cu_seqlens(reordered_images_grid_thw, reordered_videos_grid_thw)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| merger.set_tensor("attention_mask", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qwen2_vl_utils::get_attention_mask(reordered_images_grid_thw, reordered_videos_grid_thw)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| merger.set_tensor("rotary_pos_emb", rotary_pos_emb); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| merger.infer(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Qwen3.5 merger outputs only "last_hidden_state" (no deepstack_feature_lists) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ov::Tensor vision_embeds = merger.get_tensor("last_hidden_state"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto vision_embeds_shape = vision_embeds.get_shape(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t video_tokens = calc_vec_tokens_num(reordered_videos_grid_thw); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t image_tokens = calc_vec_tokens_num(reordered_images_grid_thw); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t total_tokens = video_tokens + image_tokens; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t video_token_count = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (total_tokens > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| video_token_count = vision_embeds_shape[0] * video_tokens / total_tokens; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t image_token_count = vision_embeds_shape[0] - video_token_count; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ov::Tensor video_embeds{vision_embeds.get_element_type(), {video_token_count, vision_embeds_shape[1]}}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ov::Tensor image_embeds{vision_embeds.get_element_type(), {image_token_count, vision_embeds_shape[1]}}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::memcpy(video_embeds.data(), vision_embeds.data(), video_embeds.get_byte_size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::memcpy(image_embeds.data(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static_cast<uint8_t*>(vision_embeds.data()) + video_embeds.get_byte_size(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+86
to
+102
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t video_tokens = calc_vec_tokens_num(reordered_videos_grid_thw); | |
| size_t image_tokens = calc_vec_tokens_num(reordered_images_grid_thw); | |
| size_t total_tokens = video_tokens + image_tokens; | |
| size_t video_token_count = 0; | |
| if (total_tokens > 0) { | |
| video_token_count = vision_embeds_shape[0] * video_tokens / total_tokens; | |
| } | |
| size_t image_token_count = vision_embeds_shape[0] - video_token_count; | |
| ov::Tensor video_embeds{vision_embeds.get_element_type(), {video_token_count, vision_embeds_shape[1]}}; | |
| ov::Tensor image_embeds{vision_embeds.get_element_type(), {image_token_count, vision_embeds_shape[1]}}; | |
| std::memcpy(video_embeds.data(), vision_embeds.data(), video_embeds.get_byte_size()); | |
| std::memcpy(image_embeds.data(), | |
| static_cast<uint8_t*>(vision_embeds.data()) + video_embeds.get_byte_size(), | |
| OPENVINO_ASSERT(vision_embeds_shape.size() == 2 || vision_embeds_shape.size() == 3, | |
| "Expected merger output 'last_hidden_state' to have rank 2 [tokens, hidden] " | |
| "or rank 3 [batch, tokens, hidden], but got rank ", | |
| vision_embeds_shape.size()); | |
| ov::Tensor normalized_vision_embeds; | |
| ov::Shape normalized_vision_embeds_shape; | |
| if (vision_embeds_shape.size() == 3) { | |
| OPENVINO_ASSERT(vision_embeds_shape[0] == 1, | |
| "Expected merger output 'last_hidden_state' batch dimension to be 1, but got ", | |
| vision_embeds_shape[0]); | |
| normalized_vision_embeds_shape = {vision_embeds_shape[1], vision_embeds_shape[2]}; | |
| normalized_vision_embeds = ov::Tensor{vision_embeds.get_element_type(), normalized_vision_embeds_shape}; | |
| std::memcpy(normalized_vision_embeds.data(), vision_embeds.data(), normalized_vision_embeds.get_byte_size()); | |
| } else { | |
| normalized_vision_embeds = vision_embeds; | |
| normalized_vision_embeds_shape = vision_embeds_shape; | |
| } | |
| size_t video_tokens = calc_vec_tokens_num(reordered_videos_grid_thw); | |
| size_t image_tokens = calc_vec_tokens_num(reordered_images_grid_thw); | |
| size_t total_tokens = video_tokens + image_tokens; | |
| size_t video_token_count = 0; | |
| if (total_tokens > 0) { | |
| video_token_count = normalized_vision_embeds_shape[0] * video_tokens / total_tokens; | |
| } | |
| size_t image_token_count = normalized_vision_embeds_shape[0] - video_token_count; | |
| ov::Tensor video_embeds{normalized_vision_embeds.get_element_type(), {video_token_count, normalized_vision_embeds_shape[1]}}; | |
| ov::Tensor image_embeds{normalized_vision_embeds.get_element_type(), {image_token_count, normalized_vision_embeds_shape[1]}}; | |
| std::memcpy(video_embeds.data(), normalized_vision_embeds.data(), video_embeds.get_byte_size()); | |
| std::memcpy(image_embeds.data(), | |
| static_cast<uint8_t*>(normalized_vision_embeds.data()) + video_embeds.get_byte_size(), |
Copilot
AI
Apr 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Splitting vision_embeds by proportional ratio can silently produce incorrect boundaries (and rounding artifacts) if the merger output token dimension differs from video_tokens + image_tokens or if the model changes tokenization behavior. If the output is expected to preserve token count/order, split deterministically using video_tokens and image_tokens (and validate vision_embeds_shape[0] == total_tokens); otherwise, this needs an explicit, model-defined mapping rather than a proportional guess.
Copilot
AI
Apr 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InputsEmbedderQwen3_5 inherits from InputsEmbedderQwen3VL, but these overrides explicitly call InputsEmbedderQwen2VL, which bypasses any Qwen3VL-specific start_chat/finish_chat behavior (if implemented). Prefer calling InputsEmbedderQwen3VL::start_chat/finish_chat, or remove these overrides entirely if no behavior change is required.
| InputsEmbedderQwen2VL::start_chat(system_message); | |
| } | |
| void InputsEmbedderQwen3_5::finish_chat() { | |
| InputsEmbedderQwen2VL::finish_chat(); | |
| InputsEmbedderQwen3VL::start_chat(system_message); | |
| } | |
| void InputsEmbedderQwen3_5::finish_chat() { | |
| InputsEmbedderQwen3VL::finish_chat(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| // Copyright (C) 2023-2026 Intel Corporation | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <filesystem> | ||
|
|
||
| #include "visual_language/vlm_config.hpp" | ||
| #include "visual_language/vision_encoder.hpp" | ||
| #include "visual_language/inputs_embedder.hpp" | ||
| #include "visual_language/qwen3_vl/classes.hpp" | ||
|
Comment on lines
+4
to
+11
|
||
|
|
||
| namespace ov::genai { | ||
|
|
||
| /// Qwen3.5 reuses Qwen3-VL vision encoder unchanged. | ||
| class VisionEncoderQwen3_5 : public VisionEncoderQwen3VL { | ||
| public: | ||
| using VisionEncoderQwen3VL::VisionEncoderQwen3VL; | ||
| }; | ||
|
|
||
| /// Qwen3.5 InputsEmbedder. | ||
| /// Inherits Qwen3-VL position-interpolation and video-timestamp handling. | ||
| /// Overrides deepstack / visual_pos_masks handling because the Qwen3.5 LLM | ||
| /// does not consume those extra inputs and the merger model does not produce | ||
| /// deepstack_feature_lists. | ||
| class InputsEmbedderQwen3_5 : public InputsEmbedderQwen3VL { | ||
| public: | ||
| InputsEmbedderQwen3_5( | ||
| const VLMConfig& vlm_config, | ||
| const std::filesystem::path& model_dir, | ||
| const std::string& device, | ||
| const ov::AnyMap device_config); | ||
|
|
||
| InputsEmbedderQwen3_5( | ||
| const VLMConfig& vlm_config, | ||
| const ModelsMap& models_map, | ||
| const Tokenizer& tokenizer, | ||
| const std::filesystem::path& config_dir_path, | ||
| const std::string& device, | ||
| const ov::AnyMap device_config); | ||
|
Comment on lines
+28
to
+40
|
||
|
|
||
| ov::Tensor get_inputs_embeds( | ||
| const std::string& prompt, | ||
| const std::vector<ov::genai::EncodedImage>& images, | ||
| const std::vector<ov::genai::EncodedVideo>& videos, | ||
| ov::genai::VLMPerfMetrics& metrics, | ||
| bool recalculate_merged_embeddings = true, | ||
| const std::vector<size_t>& image_sequence = {}, | ||
| const std::vector<size_t>& videos_sequence = {}, | ||
| const std::vector<std::pair<std::size_t, std::size_t>>& history_vision_count = {}) override; | ||
|
|
||
| /// Qwen3.5 LLM has no extra inputs (no deepstack / visual_pos_masks). | ||
| const std::unordered_map<std::string, ov::Tensor>& get_lm_extra_inputs() const override; | ||
|
|
||
| void start_chat(const std::string& system_message) override; | ||
| void finish_chat() override; | ||
|
|
||
| protected: | ||
| std::pair<ov::Tensor, ov::Tensor> run_video_image_embeddings_merger( | ||
| const std::vector<EncodedImage>& images, | ||
| const std::vector<size_t>& images_sequence, | ||
| const std::vector<EncodedVideo>& videos, | ||
| const std::vector<size_t>& videos_sequence) override; | ||
| }; | ||
|
|
||
| } // namespace ov::genai | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,7 @@ enum class VLMModelType { | |||||
| QWEN2_VL, | ||||||
| QWEN2_5_VL, | ||||||
| QWEN3_VL, | ||||||
| QWEN3_5, | ||||||
|
||||||
| QWEN3_5, | |
| QWEN3_5_VL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New QWEN3_5 model branch is introduced here, but there is no corresponding functional coverage in the existing VLM pipeline test suite (e.g.,
tests/python_tests/test_vlm_pipeline.pyenumerates supported tiny-random VLMs and currently has no Qwen3.5 entry). Please add at least one test case exercising this code path (including the hybrid behavior where the LM may omitposition_idsand Qwen3.5 returns empty extra inputs).