Skip to content

Add OpenBMB MiniCPM-V 4.6 support - #464

Open
justinchuby wants to merge 4 commits into
mainfrom
justinchuby-add-minicpm-v-4-6
Open

Add OpenBMB MiniCPM-V 4.6 support#464
justinchuby wants to merge 4 commits into
mainfrom
justinchuby-add-minicpm-v-4-6

Conversation

@justinchuby

@justinchuby justinchuby commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

  • add native minicpmv4_6 support for openbmb/MiniCPM-V-4.6 / Transformers 5.7+
  • implement packed variable-resolution SigLIP2 vision with exact nearest-position lookup, ragged overview/slice batching, inserted window-attention merger, final MLP merger, and both 16x/4x modes
  • reuse the Qwen3.5 mixed DeltaNet/full-attention decoder, fuse image and video placeholders, and export standardized decoder, vision_encoder, and embedding models
  • add config extraction, registry/CLI integration, weight routing, ORT GenAI metadata, tokenizer/processor artifact handling, and README coverage
  • commit genuine L4 image-prefill and deterministic 30-token L5 image-conditioned generation goldens
  • keep packed-coordinate computation on CUDA and add BF16-safe float boundaries around ReduceMean / Compress

Validation

Core confidence levels

  • L1 graph build/checker/output-shape coverage: 4 passed
  • L2 real config + YAML schema/model coverage: 237 schema tests and 926 model coverage tests passed
  • L3 synthetic vision parity: exact fp32 parity for uniform grids, non-uniform ragged grids, and 4x mode (rtol=atol=1e-4)
  • L3 real-weight stage parity: nonzero 1024x1024 image with non-uniform overview/slice grids passed at rtol=atol=1e-3
  • L4 image prefill golden: passed on CPU and CUDA
  • L5 deterministic 30-token image-conditioned generation golden: passed on CPU and CUDA with exact token match; output describes the supplied fluffy animal image
  • image and video placeholder mixing test: passed
  • targeted suite: 1,017 passed / 217 skipped; lintrunner -a clean; three independent reviews found no significant issues

DRYness and optimized graph audit

The implementation reuses existing public building blocks wherever semantics match:

  • Qwen3.5 hybrid decoder: Qwen35TextModel plus the shared hybrid cache/task machinery
  • SigLIP-style blocks: shared VisionEncoderLayer, VisionAttention, LayerNorm, Linear, and activation helpers
  • standard three-model wiring: VisionLanguageTask, build_decoder_from_embeds, and build_embedding_from_features
  • only MiniCPM-specific operations remain local: NaViT packed coordinates, ragged padded grids, the inserted window-attention merger, the final iterative merger, and the dual image/video placeholder mask

The existing InputMixer was intentionally not reused: it accepts one token ID and rank-3 per-batch vision features, while MiniCPM has two placeholder IDs sharing one rank-2 packed feature stream. Adapting it would add shape/expand/gather nodes and obscure the exact HF ordering; the MiniCPM embedding graph is already only 12 nodes.

Weighted, CUDA-optimized FP16 graph evidence:

Graph Nodes Identity Transpose Cast Reshape Unused inputs
decoder 1,149 0 36 128 156 0
vision_encoder 619 0 5 2 17 0
embedding 12 0 0 1 0 0

Initializer folding reduces unweighted vision transposes from 175 to 5; the remaining five are architectural raster/window permutations. Decoder transposes/reshapes are Qwen3.5 attention/DeltaNet layouts. The 128 FP16 decoder casts come from the shared numerically-stable Qwen3.5 DeltaNet implementation. BF16 vision retains six casts because ORT requires FLOAT boundaries for ReduceMean and Compress; FP16 now emits only two metadata casts after gating those compatibility casts to BF16.

CUDA (RTX A1000, driver 573.44, CUDA 12.8, ORT 1.28.0)

Runtime setup was verified with torch 2.11.0+cu128, torch.cuda.is_available() == True, and CUDAExecutionProvider present. Profiles reject silent CPU fallback.

FP32 CPU vs CUDA, nonzero 1024x1024 image, ragged grids [[32,32], [40,24] x6]:

  • vision: 542 CUDA nodes / 0 CPU nodes; max abs 0.00485945, mean abs 0.000139655, cosine 0.999999404, no NaNs
  • embedding: 12 CUDA nodes / 0 CPU nodes; exact match, no NaNs
  • decoder: 862 CUDA nodes / 37 metadata CPU nodes (Concat x36, Sub x1), 0 memcpy events; logits max abs 0.471408, mean abs 0.00219277, cosine 0.999997973, identical argmax 760, no NaNs
  • present KV / recurrent state outputs were compared individually; all cosine values were at least 0.9999992, no NaNs

Memcpy audit:

  • vision: 23 dynamic-shape metadata transfers (int64 scalars/vectors only), 944 us total; no CPU compute nodes remain after replacing ArgMax with GreaterOrEqual + ReduceSum
  • embedding: 0 memcpy
  • decoder: 0 memcpy

Reduced precision on CUDA:

  • FP16 vision: max abs 0.0691971, mean abs 0.000724234, cosine 0.999961913, no NaNs
  • FP16 embedding: max abs 0.0691971, mean abs 0.000666107, cosine 0.999961913, no NaNs
  • FP16 last-token logits: max abs 0.0500653, mean abs 0.00798651, cosine 0.999991179, identical argmax 760, no NaNs
  • BF16 vision and embedding execute successfully after the ReduceMean / Compress fixes, with no NaNs. BF16 ONNX vision vs per-unit HF BF16 CUDA: max abs 0.265625, mean abs 0.00541074, cosine 0.99873763.

Exact commands:

lintrunner -a
# ok No lint issues.

$env:MOBIUS_TEST_DEVICE='cuda'
python -m pytest tests\integration_test.py -m integration -k minicpmv4_6_real_weight -q --tb=short
# 1 passed, 122 deselected

$env:MOBIUS_TEST_DEVICE='cuda'
python -m pytest tests\e2e_golden_test.py -m "golden or generation" -k minicpm -q --tb=short
# 2 passed, 377 deselected

$env:MOBIUS_TEST_DEVICE='cuda'
python -m pytest tests\e2e_golden_test.py -m generation -v --models minicpmv4_6 --timeout=300 --junitxml=junit-l5.xml --cov=src --cov-report=xml --cov-branch --tb=short
# exact CI command reproduced locally: 1 passed, 145 skipped, 233 deselected in 201.34s

mobius build --model openbmb/MiniCPM-V-4.6 --dtype f16 --ep cuda --runtime ort-genai <output>
# exit 0

mobius build --model openbmb/MiniCPM-V-4.6 --dtype bf16 --ep cuda --runtime ort-genai <output>
# exit 0

Stage parity/profiling used OnnxModelSession(..., device="cuda", enable_profiling=True) for vision_encoder, embedding, and decoder, with a hard assertion that each stage had CUDA node placement.

CI status

All completed lint, analysis, platform, L1, L3, architecture, benchmark, and fast-integration jobs pass. The initial L4 runner was cancelled and the initial L5 runner disappeared without a failed step or downloadable test log. Failed-job reruns were requested; the self-hosted A10 jobs remained queued without assigned runners. The exact L5 CI command passes locally on CUDA in 201.34 seconds.

Runtime limitations / waivers

  • Native ORT GenAI image processor/generation: current ort-extensions has no MiniCPM adaptive slicing + horizontal NaViT packing transform and its config schema does not recognize target_sizes. The export preserves preprocessor_config.json; the full image pipeline and generation are validated directly through ONNX Runtime CUDA. Runtime callers must inject HF-preprocessed tensors until native processor support lands.
  • ORT GenAI CUDA model loader: onnxruntime-genai-cuda 0.15.2 currently crashes with Windows access violation -1073741819 while loading the FP16 hybrid package. Raw ORT CUDA executes all three stages and exact L5 generation successfully, so this is isolated to the GenAI hybrid runtime path.
  • BF16 decoder: ORT 1.28 has no CPU or CUDA provider for the hybrid CausalConvWithState node in BF16 (Provider type ... is not set). BF16 vision/embedding are validated; FP16 is the supported end-to-end reduced-precision CUDA path.
  • Foundry Local: the Foundry Local runtime is not installed in this environment.
  • Olive quantization: Olive is not installed in this environment.

Implement the packed variable-resolution SigLIP2 vision tower, ragged crop batching, 16x/4x mergers, Qwen3.5 hybrid decoder integration, image/video token mixing, config extraction, standardized three-model export, and ORT GenAI metadata. Add synthetic and real-weight parity tests plus committed image prefill and deterministic generation goldens.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
@justinchuby
justinchuby requested review from a team and a lite review from Copilot August 8, 2026 00:48
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 78437cdcd34f4b

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 60 60 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 68 68 +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 54 54 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 62 62 +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 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 98 98 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 60 60 +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 62 62 +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 431 431 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 166 166 +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.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 78437cdcd34f4b

Model Sub-model Changes Status
bert (feature-extraction) model 0
falcon model 0
gemma2 model 0
gemma4 (gemma4) decoder 0
gemma4 (gemma4) embedding 0
gemma4 (gemma4) vision_encoder 0
gemma4_text 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_encoder 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)

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 first-class support for OpenBMB MiniCPM‑V‑4.6 to mobius’ multimodal pipeline by introducing a native model implementation, a dedicated 3-model Vision-Language task layout (decoder/vision_encoder/embedding), registry/config wiring, ORT GenAI export integration, and new integration + golden coverage.

Changes:

  • Introduce MiniCPMV46ForConditionalGeneration (packed NaViT/SigLIP2 vision + Qwen3.5 hybrid decoder) plus unit tests and weight-routing.
  • Add MiniCPMVLTask and register the new minicpmv4_6 model_type across configs, registry, and ORT GenAI export behavior.
  • Add new L4/L5 test case + golden artifacts, plus ORT GenAI package-load and real-weight vision parity integration tests.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/ort_genai_test.py Adds ORT GenAI integration test to verify MiniCPM-V-4.6 package loads.
tests/integration_test.py Adds real-weight SigLIP2/merger vision parity test for MiniCPM‑V‑4.6.
tests/_test_configs.py Adds tiny MiniCPM‑V‑4.6 VL config for graph-build coverage.
testdata/golden/vision-language/minicpm-v-4_6.json Adds L4 golden logits/tokens snapshot for MiniCPM‑V‑4.6.
testdata/golden/vision-language/minicpm-v-4_6_generation.json Adds L5 deterministic generation golden for MiniCPM‑V‑4.6.
testdata/cases/vision-language/minicpm-v-4_6.yaml Adds L4/L5 golden case definition for MiniCPM‑V‑4.6.
src/mobius/tasks/_vision_language_3model.py Adds MiniCPMVLTask and packed target_sizes vision I/O contract.
src/mobius/tasks/init.py Exposes MiniCPMVLTask and registers it in task registry.
src/mobius/models/minicpmv4_6.py Implements MiniCPM‑V‑4.6 model (vision tower + mergers + hybrid decoder + embedding fusion).
src/mobius/models/minicpmv4_6_test.py Adds synthetic vision parity + embedding placeholder-mixing tests.
src/mobius/models/init.py Exports MiniCPMV46ForConditionalGeneration.
src/mobius/integrations/ort_genai/auto_export.py Adds MiniCPM-specific ORT GenAI handling (no image_processor.json; preserve HF preprocessor config; adjust vision input mapping).
src/mobius/_registry.py Registers minicpmv4_6 model_type and default model id.
src/mobius/_configs/per_model/_minicpmv4_6_vision.py Adds per-model vision-config extraction hook for the composite MiniCPM config.
src/mobius/_configs/per_model/init.py Imports the new MiniCPM vision extractor to register hooks.
src/mobius/_configs/_sub_configs.py Extends VisionConfig with MiniCPM merger parameters.
src/mobius/_configs/_base.py Adds video_token_id and downsample_mode to the shared architecture config.
README.md Updates supported Multimodal model list to include MiniCPM‑V 4.6.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/mobius/tasks/_vision_language_3model.py
Comment thread src/mobius/_configs/per_model/_minicpmv4_6_vision.py Outdated
justinchuby and others added 3 commits August 7, 2026 18:47
Keep packed patch coordinate mapping on CUDA by replacing ArgMax with a segment-count reduction. Cast through float for ReduceMean and Compress so BF16 vision graphs load and run while restoring the model dtype at each boundary.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Emit float compatibility casts around ReduceMean and Compress only for BF16, where ORT requires them. Keep native FP16 kernels on the fast path and remove four unnecessary casts without changing outputs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Fall back to the architecture's 2x2 merger kernels when Hugging Face explicitly supplies null values, and add a regression test for both window and final merger fields.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
@justinchuby

Copy link
Copy Markdown
Member Author

Looks like we need compress and argmax in ORT cuda

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.

2 participants