Add Gemma4 multimodal support (vision + audio) - #2103
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds end-to-end Gemma4 multimodal (vision + optional audio) support to ONNX Runtime GenAI, including preprocessing, pipeline/runtime updates, and model registration/config plumbing.
Changes:
- Register Gemma4 model types/processors and extend config to cover new inputs/IDs (e.g.,
pixel_position_ids, audio-related token IDs). - Extend multimodal pipeline/runtime to support Gemma4 decoder requirements (
input_idsalongsideinputs_embeds), optional/empty audio features, and speech output reshaping. - Enhance runtime primitives (KV cache per-layer
head_dim,WindowedPositionInputsint64 support).
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/models/position_inputs.cpp | Allow WindowedPositionInputs to accept int64 position_ids/attention_mask in addition to int32. |
| src/models/multi_modal_features.h | Add APIs to allocate empty feature tensors and reshape features. |
| src/models/multi_modal_features.cpp | Implement empty allocation + reshape and tweak batch-dim behavior for 3D tensors. |
| src/models/multi_modal.h | Add optional decoder input_ids support (plus a new include). |
| src/models/multi_modal.cpp | Pipeline updates for optional decoder input_ids, empty-audio fallback, and speech output reshape (3D→2D). |
| src/models/model_type.h | Register gemma4_text as LLM and gemma4 as MMM. |
| src/models/model.h | Include Gemma4 processor header. |
| src/models/model.cpp | Enable MMM speech session based on speech.filename; register Gemma4 processor in factory. |
| src/models/kv_cache.h | Add empty_pasts_ for per-layer empty past tensors. |
| src/models/kv_cache.cpp | Auto-detect per-layer head_dim (Gemma4 256/512), handle unconstrained cache length, and sync per-layer shapes in shared-buffer mode. |
| src/models/gemma4_multimodal_processor.h | New Gemma4 multimodal processor interface. |
| src/models/gemma4_multimodal_processor.cpp | New preprocessing for Gemma4 vision (incl. patch trimming + pixel position ids) and audio (mel features + prompt expansion). |
| src/config.h | Add pixel_position_ids naming, plus new token ID fields (audio_token_id, boa_token_id). |
| src/config.cpp | Parse pixel_position_ids, audio_token_id, boa_token_id from config JSON. |
| examples/python/common.py | Extend example structured user content to include {"type": "audio"} entries. |
| cmake/deps.txt | Bump onnxruntime-extensions dependency SHA. |
apsonawane
enabled auto-merge (squash)
April 28, 2026 23:46
justinchuby
approved these changes
May 4, 2026
This was referenced May 4, 2026
hanbitmyths
pushed a commit
that referenced
this pull request
May 15, 2026
## Problem PR #2103 (_Add Gemma4 multimodal support_) introduced an optional `decoder_input_ids_` in `DecoderState` for models like Gemma4 that require `input_ids` alongside `inputs_embeds`. The guard condition was: ```cpp if (model_.session_info_.HasInput(model_.config_->model.decoder.inputs.input_ids)) { ``` However, `model_.session_info_` is a **combined** `SessionInfo` aggregating inputs from ALL sessions — decoder, embedding, vision, and speech. Since the **embedding** session always takes `input_ids` as its primary input, `HasInput("input_ids")` returns `true` for every VLM that has an embedding model, even when the decoder ONNX itself has no `input_ids` input. ## Impact Any VLM using the 3-model pipeline (embedding + decoder) where the **decoder does not have `input_ids`** will fail at inference with: ``` RuntimeError: Invalid input name: input_ids ``` Confirmed broken: **mistral3** (Ministral-3B / Pixtral family), whose decoder only accepts `inputs_embeds`. ## Fix Scope the check to a **decoder-only** `SessionInfo` so it only fires when the decoder ONNX itself declares `input_ids` as an input: ```cpp // Use a decoder-only SessionInfo to avoid false positives from the embedding session SessionInfo decoder_only_info; decoder_only_info.Add(*model_.decoder_session_); if (decoder_only_info.HasInput(model_.config_->model.decoder.inputs.input_ids)) { decoder_input_ids_ = std::make_unique<DefaultInputIDs>(*this); decoder_input_ids_->Add(); } ``` Gemma4 is unaffected — its decoder ONNX genuinely has `input_ids` as an input, so the narrowed check still fires correctly for that model. ## Verification Tested with the exported Ministral-3B-Instruct-2512 (mistral3 type, 3-model VLM): - `generator.set_inputs(inputs)` no longer throws - Token generation succeeds end-to-end Regressed by: #2103 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Akshay Sonawane <asonawane@microsoft.com>
baijumeswani
pushed a commit
that referenced
this pull request
May 19, 2026
…2167) ## Summary Make the speech sub-model optional in `PhiMultiModalProcessor`, mirroring the pattern already used by `Gemma4MultiModalProcessor` (added in #2103). A `phi4mm` `genai_config.json` with the `speech` block cleared (`filename` and `config_filename` both empty / removed) can now load vision-only and skip the speech ONNX entirely, reducing peak memory usage for image-only use cases. ## Problem PR #2103 added auto-detection of `speech.filename` in `model.cpp` and allocation of an empty `audio_features` tensor in `multi_modal.cpp` when the embedding ONNX requires it. However, `PhiMultiModalProcessor` itself was not gated and still unconditionally: 1. Calls `session_info.GetInputDataType(config.model.speech.inputs.audio_embeds)` (and `audio_sizes`) in the initializer list. `SessionInfo` aggregates inputs only from sessions that were actually loaded — with no speech session, this throws: "Model input was not found: audio_embeds" 2. Calls `OrtxCreateSpeechFeatureExtractor(audio_processor_, "")` with an empty `speech.config_filename`, which fails to open the file. 3. Registers `AudioEmbeds` / `AudioAttentionMask` / `AudioSizes` mappings against an unconfigured speech section. The net effect is that even though `model.cpp` correctly skips loading `speech.onnx`, the processor construction in `MultiModalProcessor`'s factory still throws during `og.Model(...)`, blocking vision-only phi4mm loads. ## Fix Apply the exact same gating pattern that `Gemma4MultiModalProcessor` already uses: - Add a `bool has_speech_{false}` member to `PhiMultiModalProcessor`. - Move all audio-related construction (audio dtype queries, `OrtxCreateSpeechFeatureExtractor`, three `AddMapping` calls) inside an `if (!speech.config_filename.empty() && fs::exists(...))` block that sets `has_speech_ = true` on success. - Throw a precise error if `speech.filename` is set but the audio processor config file is missing on disk. - In `Process()`, throw a clear user-facing error if `payload.audios` is supplied while `has_speech_` is `false`. - Gate the audio feature extraction and the audio output emit block on `payload.audios && has_speech_`. No changes to vision-only image processing or to the `ProcessImageAudioPrompt` helper — the existing `if (audio_sizes)` guards inside it correctly produce `num_audios == 0` on the vision-only path, so `audio_projection_mode` resolves to `1` (Vision, language) as expected. ## Usage To run phi4mm vision-only: ```json "speech": { "filename": "", "config_filename": "" } (or remove the speech block entirely from genai_config.json). The speech ONNX and audio_processor_config.json no longer need to be present on disk. --------- Co-authored-by: Bollavaram <manasab@amd.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds end-to-end support for Google Gemma 4 multimodal models in ORT GenAI, covering text-only (
gemma4_text), vision-language, and any-to-any (vision + audio + text) variants.Changes
Model registration
gemma4_textas LLM andgemma4as MMM (multi-modal model)speech.filenamein genai_config — no separategemma4_any_to_anytype neededGemma4MultiModalProcessorin the processor factoryGemma4 multimodal processor (
gemma4_multimodal_processor.cpp/h)Gemma4ImageTransform(onnxruntime-extensions), trims padded patches to actual count usingnum_soft_tokensfrom preprocessor, producespixel_values+pixel_position_idsGemma4LogMel, computesaudio_sizesfor the pipeline, generatesinput_features_mask(all-True for single-clip inference), and expands<|audio|>placeholder tokens in the prompt<|image|>and<|audio|>tokens from the chat template into the correct number of soft tokens before encoding. Handles template-inserted tokens and auto-insertion when no template is availableKV cache — per-layer head_dim (
kv_cache.cpp/h)head_dimacross layers from ONNX session input shapes (Gemma4 uses 256 for sliding-window layers, 512 for global attention layers)empty_pasts_with correct head dimensionslayer_shapes_[i][2] == 0(unconstrained) in Update to avoid zero-size allocationlayer_shapes_sequence dimension forpast_present_share_buffermodePosition inputs — int64 support (
position_inputs.cpp)WindowedPositionInputsnow supports bothint32_tandint64_tforposition_idsandattention_maskMulti-modal pipeline (
multi_modal.cpp/h)decoder_input_ids_for models requiringinput_idsalongsideinputs_embedsaudio_featurestensor when embedding model requires it but no speech session exists (AllocateEmptyFeatures)ReshapeFeatures) before passing to embedding modelnum_audio_tokens_MultiModalFeatures (
multi_modal_features.cpp/h)AllocateEmptyFeatures()— pre-allocates empty tensor for optional inputsReshapeFeatures()— in-place reshape with data copy and state pointer updatebatch_size <= 0support — skip batch dimension for 3D model outputsConfig (
config.h/cpp)pixel_position_idsto vision inputsaudio_token_idandboa_token_idto model configPixelPositionIdsNamedefault constantExample script (
common.py){"type": "audio"}entries for Gemma-style structured content inget_user_contentTesting
Tested with Gemma4 E2B model exported via mobius:
past_present_share_buffer=falseWindowedPositionInputs