Skip to content

Add Pixtral/Ministral3 VLM support: dynamic vision, FP8 dequant, PatchMerger fix, MHA - #130

Merged
titaiwangms merged 3 commits into
mainfrom
fix-pixtral-dynamic-vision
Apr 10, 2026
Merged

Add Pixtral/Ministral3 VLM support: dynamic vision, FP8 dequant, PatchMerger fix, MHA#130
titaiwangms merged 3 commits into
mainfrom
fix-pixtral-dynamic-vision

Conversation

@titaiwangms

@titaiwangms titaiwangms commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Pixtral/Ministral3 VLM support with 3-model split (vision/embedding/decoder) for onnxruntime-genai.

Key Changes

Vision encoder (_pixtral_vision.py)

  • PixtralVisionTower: Conv2d patch embedding → RMSNorm → 24-layer transformer with 2D RoPE
  • PixtralRoPE2D: Precomputed 2D rotary position embeddings over spatial grid
  • PixtralAttention: Bidirectional MHA with 2D RoPE (com.microsoft.MultiHeadAttention; op.Attention fallback for onnx-standard EP)
  • Mistral3PatchMerger: Spatial 2×2 patch merging matching HF F.unfold dim-major ordering
  • Mistral3MultiModalProjector: norm → merge → GELU MLP projection

PatchMerger fix (cosine sim 0.007 → 0.999973)

The original transpose permutation [0,1,3,2,4,5] produced patch-major ordering, but HF F.unfold produces dim-major [D, ms_h, ms_w]. The learned merging_layer weights expect HF ordering. Fixed to [0,1,3,5,2,4].

FP8 weight dequantization (_weight_loading.py)

  • Detects FP8 weights, dequantizes via fp8.to(bf16) * scale
  • Always returns new dict (non-mutating, addresses review comment)
  • Docstring updated to list all removed suffixes

Attention scale optimization (_attention.py)

  • Moved attn_scale Mul before RoPE (scaling commutes with rotation) to preserve RotaryEmbedding → Attention pattern for GQA rewrite rule matching

Task and infrastructure

  • PixtralVLTask: Dynamic H×W vision input with Squeeze for rank-2 output
  • Registered as pixtral-vl task in _vision_language_3model.py
  • Olive-recipe optimize.py integration verified

Testing

  • Cosine sim vs HuggingFace PyTorch: 0.999973
  • E2E model-mm.py with fish.jpg and challenge.jpg: correct descriptions
  • Olive-recipe pipeline export + E2E verified
  • test_patch_merger_matches_hf_unfold_ordering: regression test against HF F.unfold
  • All 1261 unit tests pass, lintrunner clean

Files Changed

  • src/mobius/components/_pixtral_vision.py — PatchMerger fix + MHA
  • src/mobius/components/_pixtral_vision_test.py — F.unfold ordering test
  • src/mobius/tasks/_vision_language_3model.py — Squeeze vision output
  • src/mobius/_weight_loading.py — Docstring + mutation fix
  • src/mobius/components/_attention.py — attn_scale before RoPE

@titaiwangms
titaiwangms requested review from a team and Copilot April 8, 2026 23:17
@titaiwangms
titaiwangms marked this pull request as draft April 8, 2026 23:17
Comment thread src/mobius/_registry.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds Pixtral/Ministral-3 VLM enablement across the export pipeline by (1) introducing a Pixtral-specific VLM task with dynamic vision input resolution, (2) supporting direct loading of FP8 HuggingFace checkpoints via dequantization during weight download, and (3) adding Ministral3/Mistral4 YaRN RoPE llama_4_attn_scale handling and applying it in attention.

Changes:

  • Add PixtralVLTask and register pixtral/mistral3 to use it for dynamic-resolution vision export.
  • Extend YarnRope to optionally return (cos, sin, attn_scale) and apply attn_scale to queries in Attention.
  • Add _dequantize_fp8_weights() to dequantize FP8 checkpoint weights at download time, with new unit tests.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/mobius/tasks/_vision_language_3model.py Adds PixtralVLTask overriding the vision model input shape to symbolic H/W for dynamic resolution.
src/mobius/tasks/__init__.py Exports PixtralVLTask and registers a new task name (pixtral-vl).
src/mobius/components/_rotary_embedding.py Updates RoPE APIs/docs to allow optional attn_scale; implements YaRN llama_4_scaling_beta scaling output.
src/mobius/components/_rotary_embedding_test.py Adds unit tests for YaRN returning 2-tuple vs 3-tuple and RoPE application compatibility.
src/mobius/components/_attention.py Applies llama_4_attn_scale to query states when present in position_embeddings.
src/mobius/_weight_loading.py Adds FP8 dequantization during weight download and filters auxiliary FP8 scale tensors.
src/mobius/_weight_loading_test.py Adds unit tests for FP8 dequantization behavior and key derivation.
src/mobius/_registry.py Routes pixtral/mistral3 model types to the new pixtral-vl task.

Comment thread src/mobius/_weight_loading.py Outdated
Comment thread src/mobius/_weight_loading.py
Comment thread src/mobius/_weight_loading_test.py Outdated
@titaiwangms
titaiwangms force-pushed the fix-pixtral-dynamic-vision branch from 30a5634 to fdb1bc2 Compare April 8, 2026 23:33
@github-actions

github-actions Bot commented Apr 8, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing db7c0cafd16c76

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 61 61 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 66 66 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 107 107 +0.0%
gpt2 model_size_bytes 388 KB 388 KB +0.0%
gpt2 num_nodes 53 53 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 61 61 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 58 58 +0.0%
mamba (ssm-text-generation) model_size_bytes 360 KB 360 KB +0.0%
mamba (ssm-text-generation) num_nodes 103 103 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 59 59 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 56 56 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 61 61 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 58 58 +0.0%
qwen3_5_moe (hybrid-text-generation) model_size_bytes 506 KB 506 KB +0.0%
qwen3_5_moe (hybrid-text-generation) num_nodes 275 275 +0.0%
qwen3_5_text (hybrid-text-generation) model_size_bytes 458 KB 458 KB +0.0%
qwen3_5_text (hybrid-text-generation) num_nodes 129 129 +0.0%
qwen3_5_vl (hybrid-qwen-vl) model_size_bytes 977 KB 977 KB +0.0%
qwen3_5_vl (hybrid-qwen-vl) num_nodes 409 409 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 174 174 +0.0%
whisper (speech-to-text) model_size_bytes 1008 KB 1008 KB +0.0%
whisper (speech-to-text) num_nodes 128 128 +0.0%

No performance regressions.

Comment thread src/mobius/_registry.py Outdated
@github-actions

github-actions Bot commented Apr 8, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing db7c0cafd16c76

Model Sub-model Changes Status
bert (feature-extraction) model 0
falcon model 0
gemma2 model 0
gpt2 model 0
llama model 0
llama (static-cache) model 0
mamba (ssm-text-generation) model 0
phi3 model 0
phi3 (static-cache) model 0
qwen model 0
qwen (static-cache) model 0
qwen2 model 0
qwen2 (static-cache) model 0
qwen2_moe model 0
qwen2_moe (static-cache) model 0
qwen3 model 0
qwen3 (static-cache) model 0
qwen3_5_moe (hybrid-text-generation) model 0
qwen3_5_text (hybrid-text-generation) model 0
qwen3_5_vl (hybrid-qwen-vl) decoder 0
qwen3_5_vl (hybrid-qwen-vl) embedding 0
qwen3_5_vl (hybrid-qwen-vl) vision 0
qwen3_moe model 0
qwen3_moe (static-cache) model 0
qwen3_next (hybrid-text-generation) model 0
t5 (seq2seq) decoder 0
t5 (seq2seq) encoder 0
whisper (speech-to-text) decoder 0
whisper (speech-to-text) encoder 0

No architecture changes detected.


Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed)

@titaiwangms
titaiwangms force-pushed the fix-pixtral-dynamic-vision branch from fdb1bc2 to 52387fc Compare April 8, 2026 23:38
@titaiwangms
titaiwangms marked this pull request as ready for review April 9, 2026 21:35
@titaiwangms
titaiwangms force-pushed the fix-pixtral-dynamic-vision branch from c4ac85c to c714125 Compare April 9, 2026 21:37
@codecov

codecov Bot commented Apr 9, 2026

Copy link
Copy Markdown

The author of this PR, titaiwangms, is not an activated member of this organization on Codecov.
Please activate this user on Codecov to display this PR comment.
Coverage data is still being uploaded to Codecov.io for purposes of overall coverage calculations.
Please don't hesitate to email us at support@codecov.io with any questions.

Comment thread src/mobius/_weight_loading.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 9 out of 9 changed files in this pull request and generated 2 comments.

Comment thread src/mobius/components/_attention.py Outdated
Comment thread src/mobius/tasks/__init__.py
…_scale

- Dynamic Pixtral vision export via PixtralVLTask
- FP8 dequantization for HF checkpoints
- llama_4_attn_scale for long-context attention
- FP16 dtype mismatch fix
- LlavaEmbedding zero-padding for text-only mode
- Add vision parity test and golden test case
- 8 unit tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@titaiwangms
titaiwangms force-pushed the fix-pixtral-dynamic-vision branch from 0f1129b to 2897b81 Compare April 10, 2026 00:04
Comment thread testdata/cases/vision-language/ministral-3-3b.yaml

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 11 out of 11 changed files in this pull request and generated 2 comments.

Comment thread src/mobius/tasks/_vision_language_3model.py
Comment thread src/mobius/_weight_loading.py Outdated
@justinchuby

Copy link
Copy Markdown
Member

Only comments are LazyTensor + Referece json files, which can also be a follow up if needed

Comment thread src/mobius/models/llava.py
…sion output

Three fixes for the Pixtral vision model:

1. Mistral3PatchMerger transpose perm [0,1,3,2,4,5] (patch-major)
   changed to [0,1,3,5,2,4] (dim-major) to match HuggingFace's
   F.unfold element ordering. The learned merging_layer weights expect
   HF's ordering — wrong permutation caused cosine sim 0.007 vs HF.

2. PixtralAttention: use com.microsoft.MultiHeadAttention for all EPs
   except onnx-standard (which falls back to standard Attention since
   MHA has no function body for InlinePass decomposition).

3. PixtralVLTask._build_vision: Squeeze(axis=0) on vision output to
   produce rank-2 [num_patches, hidden] — ort-genai expects rank-2.

Also addresses PR review comments:
- _weight_loading: always return new dict from _dequantize_fp8_weights,
  update docstring to list all removed suffixes (.input_scale)
- _attention: move attn_scale Mul before RoPE to preserve
  RotaryEmbedding -> Attention pattern for GQA rewrite rule

Results:
- Cosine sim vs HuggingFace: 0.999973 (was 0.007)
- E2E model-mm.py: fish.jpg and challenge.jpg produce correct descriptions
- Olive-recipe pipeline verified (optimize.py export + E2E)
- All 1261 unit tests pass, lintrunner clean

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@titaiwangms titaiwangms changed the title Add Pixtral/Ministral3 VLM support: dynamic vision, FP8 dequant, attn_scale Add Pixtral/Ministral3 VLM support: dynamic vision, FP8 dequant, PatchMerger fix, MHA Apr 10, 2026
@titaiwangms
titaiwangms enabled auto-merge (squash) April 10, 2026 21:29
@titaiwangms
titaiwangms merged commit ec3455b into main Apr 10, 2026
14 of 20 checks passed
@titaiwangms
titaiwangms deleted the fix-pixtral-dynamic-vision branch April 10, 2026 21:32
@justinchuby

Copy link
Copy Markdown
Member

@titaiwangms if you have a Olive-recipe e2e, please feel free to add it to examples/olive/ as a demo. Thanks!

@justinchuby

justinchuby commented Apr 10, 2026

Copy link
Copy Markdown
Member

@titaiwangms CI failing: https://github.com/onnxruntime/mobius/actions/runs/24267296245/job/70864946872?pr=157

is there a way to run the new ort test without saving model to disk?

@titaiwangms

Copy link
Copy Markdown
Contributor Author

@titaiwangms CI failing: https://github.com/onnxruntime/mobius/actions/runs/24267296245/job/70864946872?pr=157

is there a way to run the new ort test without saving model to disk?

let me fix it.

titaiwangms added a commit that referenced this pull request Apr 30, 2026
…161)

## Summary

Add an end-to-end olive-recipe demo for Ministral-3-3B VLM and enhance
the mobius genai integration with Pixtral processor config support and
tokenizer class remapping.

## Changes

### Core: genai integration enhancements
(`src/mobius/integrations/ort_genai/`)
- **`_write_processor_config`**: Enhanced to generate full
ORT-extensions image transform pipeline for VL models. Dispatches by
model type (Pixtral: longest_edge resize; generic: smart_resize).
Derives normalization params from HF processor config.
- **`_fix_tokenizer_config`**: New function with
`_TOKENIZER_CLASS_REMAP` dict to remap unsupported tokenizer classes
(e.g. `TokenizersBackend` → `LlamaTokenizer`). Called automatically from
`write_ort_genai_config()`.
- 4 new tests (31 total passing)

### Example: olive-recipe demo (`examples/olive/ministral-3-3b-vlm/`)
- **optimize.py**: Pure mobius export (`build()` → `save()` →
`write_ort_genai_config()`) with optional Olive quantization via
`--olive-config`
- **inference.py**: ORT GenAI multimodal inference (text-only,
image+text, interactive)
- **eval.py**: AI2D benchmark evaluation (ONNX vs PyTorch comparison)
- Olive configs for CPU (INT4) and CUDA (FP16) quantization
- README with setup, export, inference, and evaluation instructions

### Skills
- New `olive-recipe` skill documenting the mobius + Olive hybrid
pipeline pattern
- Updated `ort-genai-config` skill with Pixtral processor config and
tokenizer remap info

## References
- [Issue #158](#158)
- [PR #130](#130)
(Pixtral/Ministral3 VLM support)
- [olive-recipes PR
#352](microsoft/olive-recipes#352)
- [onnxruntime-genai PR
#2077](microsoft/onnxruntime-genai#2077)

Closes #158

---------

Signed-off-by: Ti-Tai Wang <titaiwang@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

Pixtral vision model: ORT fused RotaryEmbedding op produces wrong features for 2D spatial RoPE

3 participants