Skip to content

Onboard Qwen2.5-Omni Thinker model - #193

Open
rui-ren wants to merge 11 commits into
mainfrom
ruiren/onboard-qwen25-omni
Open

Onboard Qwen2.5-Omni Thinker model#193
rui-ren wants to merge 11 commits into
mainfrom
ruiren/onboard-qwen25-omni

Conversation

@rui-ren

@rui-ren rui-ren commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Adds Qwen2.5-Omni Thinker support with:

  • Packed audio encoder
  • Omni vision encoder
  • Audio, image, and video embedding fusion
  • MRoPE text decoder
  • Dedicated four-model export task
  • Hugging Face config extraction and weight routing
  • Graph and CLI regression tests

@github-actions

github-actions Bot commented Apr 23, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing cef62325ca65be

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 413 413 +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.

@codecov

codecov Bot commented Apr 23, 2026

Copy link
Copy Markdown

The author of this PR, rui-ren, 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/models/qwen25_omni.py Fixed
Comment thread src/mobius/models/qwen25_omni.py Fixed
Comment thread src/mobius/models/qwen25_omni.py Fixed
Comment thread src/mobius/models/qwen25_omni.py Fixed
@github-actions

github-actions Bot commented Apr 23, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing cef62325ca65be

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)

Comment thread src/mobius/models/qwen25_omni.py Fixed
Comment thread src/mobius/models/qwen25_omni.py Fixed
@rui-ren

rui-ren commented Apr 28, 2026

Copy link
Copy Markdown
Contributor Author

@copilot can you please use this skill of adding-a-new-model to review this PR, and provide good feedback of this PR.

Currently I am only working on the thinker component, please provide good feedback of this.

Copilot AI commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

@copilot can you please use this skill of adding-a-new-model to review this PR, and provide good feedback of this PR.

Currently I am only working on the thinker component, please provi...

Reviewed the thinker work at d5a4ec9 using the adding-a-new-model checklist. Focused feedback:

  • Blocking: src/mobius/models/qwen25_omni.py does not import/compile yet (python -m py_compile ... fails on an unmatched ) at line 302), so the registry import cannot work.
  • Blocking: Qwen25OmniThinkerForConditionalGeneration is still pass. Since it is registered with task="speech-language", it needs at least the audio_tower, embedding, and decoder attributes expected by SpeechLanguageTask; otherwise component validation will fail.
  • The decoder currently references DecoderLayer, RMSNorm, and initialize_rope without importing them.
  • The embedding path has several correctness issues: inputs_ids vs input_ids, _image_token_id vs _image_token_ids, returning input_embeds instead of inputs_embeds, and audio.image_token_id is accessed even when config.audio may be None.
  • The task split does not yet match the intended thinker shape if image support is included. SpeechLanguageTask only wires audio_encoder -> embedding -> decoder; it does not build or pass a vision_encoder/image_features model. Either keep this first PR audio-only or add a dedicated Omni thinker task/component spec for audio_encoder, vision_encoder, embedding, and decoder.
  • Weight loading is missing. Qwen Omni HF checkpoints will likely need thinker. prefix stripping and routing similar to Qwen3ASRForConditionalGeneration.preprocess_weights (thinker.model.embed_tokens to embedding, decoder layers/norm/lm_head to decoder, audio/vision towers to their submodules).
  • Checklist gaps before merge: export the model from models/__init__.py, add class metadata/docstring on the registered class, add a tiny qwen2_5_omni L1 build config/test, and then run the targeted build graph test once dependencies are available.

rui-ren and others added 3 commits May 11, 2026 11:03
Pick up the partial Qwen2.5-Omni onboarding (#193) and bring it to a
state where the file imports cleanly, the full pytest suite collects
without errors, and the registry-completeness test passes.

Concrete fixes:

* Syntax: removed stray ")" at end of Qwen25OmniDecoderModel that
  broke every test that imported the module (65 collection errors on
  plain main).
* Missing imports: RMSNorm, DecoderLayer, initialize_rope were
  referenced by Qwen25OmniDecoderModel but not imported. Also
  consolidated the OpBuilder import (was using the internal
  onnxscript._internal.builder module).
* Typos in Qwen25OmniEmbeddingModel:
  - Used audio.image_token_id (image_token_id lives on VisionConfig).
  - self._image_token_ids plural typo where _image_token_id was meant.
  - "return input_embeds" referenced a non-existent local; renamed.
  - Forward arg "inputs_ids" -> "input_ids".
* Composite class implemented: Qwen25OmniThinkerForConditionalGeneration
  was just `pass`. Now mirrors the gemma4 multi-encoder layout — owns
  decoder, embedding, vision_encoder, and audio_tower (named to match
  what SpeechLanguageTask looks up). forward() raises
  NotImplementedError because this is a multi-output split needing a
  dedicated task. preprocess_weights maps HF
  thinker.audio_tower.* / thinker.visual.* / thinker.model.* /
  thinker.lm_head.* onto the new module structure, drops Talker /
  codec-head keys, and applies the standard embed/lm_head weight
  tying.

Test infrastructure:

* Added qwen2_5_omni to _KNOWN_UNTESTED_MODEL_TYPES with a comment
  explaining the situation: the audio + vision + text 4-model split
  needs a dedicated task (like Gemma4Task); the existing
  speech-language task only drives audio + text. When that task is
  added in a follow-up, this entry should move into a dedicated graph
  test (mirroring test_gemma4_any_to_any_graph).

Also cleaned up: unused n_window lookup, unused dataclasses import,
trailing blank lines, and audio-attention docstring punctuation that
ruff flagged.

Tests: full tests/build_graph_test.py + src/mobius/ suite passes
(2767 passed, 43 skipped). Ruff check + format clean on touched files.

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

Copy link
Copy Markdown
Member

@copilot update to the latest and ensure the implementation is clean and correct

Copilot AI added 2 commits July 13, 2026 02:47
Signed-off-by: GitHub <noreply@github.com>
Add a dedicated four-model task and align audio, vision, decoder, configuration, and weight routing with the current Hugging Face Thinker implementation. Cover the nested config and complete graph package with focused regression tests.

Signed-off-by: GitHub <noreply@github.com>
@justinchuby
justinchuby marked this pull request as ready for review July 13, 2026 02:53
@justinchuby
justinchuby requested review from a team and Copilot July 13, 2026 02:53

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

This PR onboards the Qwen2.5-Omni (Thinker) architecture into mobius, implementing a four-model ONNX split (audio encoder, vision encoder, embedding, decoder) and wiring it into the registry/config extraction paths.

Changes:

  • Added Qwen25OmniThinkerForConditionalGeneration + Qwen25OmniTask to build a 4-part ModelPackage (audio_encoder, vision_encoder, embedding, decoder).
  • Implemented new Omni audio encoder components and a generic Conv1d building block.
  • Extended config extraction to pull Omni’s nested thinker_config.vision_config (including video_token_id) and registered the new model/task in the public APIs and registry.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/build_graph_test.py Adds a graph-build test for the 4-model package and marks the model type as “specialized” for registry completeness.
src/mobius/tasks/_qwen25_omni.py New task to build the Omni 4-model split (audio/vision/embedding/decoder).
src/mobius/tasks/init.py Exposes Qwen25OmniTask and registers the task name qwen25-omni.
src/mobius/models/qwen25_omni.py New Thinker model implementation (audio + vision + embedding fusion + decoder) and HF weight routing.
src/mobius/models/qwen25_omni_test.py Unit tests for nested-config extraction and preprocess_weights() routing rules.
src/mobius/models/init.py Exports Qwen25OmniThinkerForConditionalGeneration.
src/mobius/components/_qwen25_omni_audio.py Adds Omni packed audio attention + encoder layer components.
src/mobius/components/_conv.py Adds a reusable Conv1d component.
src/mobius/components/init.py Exposes Omni audio components via the public components API.
src/mobius/_registry.py Registers model type qwen2_5_omni with task qwen25-omni.
src/mobius/_configs/per_model/_qwen25_omni_vision.py Adds a per-model hook to extract vision config from thinker_config.
src/mobius/_configs/per_model/init.py Imports the new Omni vision extractor hook.
src/mobius/_configs/_sub_configs.py Adds video_token_id to VisionConfig.
src/mobius/_configs/_extractors.py Lifts video_token_id as a shared vision field.
src/mobius/_configs/_base.py Adds top-level video_token_id and recognizes qwen2_5_omni_text for config extraction.

Comment on lines +85 to +89
q = op.Unsqueeze(op.Transpose(q, perm=[1, 0, 2]), [0])
k = op.Unsqueeze(op.Transpose(k, perm=[1, 0, 2]), [0])
v = op.Unsqueeze(op.Transpose(v, perm=[1, 0, 2]), [0])
attn_output = op.Attention(
q,
Comment on lines +14 to +17
import onnx_ir as ir
from onnxscript import nn
from onnxscript._internal import builder

Comment on lines +40 to +48
def build(self, module: nn.Module, config: ArchitectureConfig) -> ModelPackage:
self._validate_components(module)
models = {
"audio_encoder": self._build_audio(module.audio_encoder, config),
"vision_encoder": self._build_vision(module.vision_encoder, config),
"embedding": self._build_embedding(module.embedding, config),
"decoder": build_decoder_from_embeds(module.decoder, config, mrope=True),
}
return ModelPackage(models, config=config)
Comment on lines +58 to +62
input_features = builder.input(
"input_features",
dtype=ir.DataType.FLOAT,
shape=[num_chunks, n_mels, chunk_len],
)
Comment on lines +432 to +435
- ``decoder``: Qwen2.5 text decoder taking ``inputs_embeds``
- ``vision_encoder``: Qwen2.5-VL ViT (pixel_values + grid_thw → image features)
- ``audio_tower``: 2x Conv1d + transformer audio tower (mel → audio features)
- ``embedding``: word embedding + multimodal feature fusion
Comment thread tests/build_graph_test.py
Comment on lines 5003 to +5007
"fun_asr",
"mms",
"qwen3_asr",
"qwen3_forced_aligner",
"qwen2_5_omni",

Copilot AI commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

@copilot update to the latest and ensure the implementation is clean and correct

Updated onto latest main and completed the clean Thinker implementation in 5ca65be: dedicated four-model task, HF-aligned packed audio/vision/decoder behavior, nested config extraction, checkpoint routing, and regression coverage. Full suite: 3,124 passed, 43 skipped; audio/vision/decoder synthetic parity also passes.

Copilot AI changed the title Onboard qwen25-omni model Onboard Qwen2.5-Omni Thinker model Jul 13, 2026
Copilot AI requested a review from justinchuby July 13, 2026 03:07
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.

4 participants