Skip to content

feat(models): add NVIDIA Cosmos3-Edge vision-language model - #448

Open
justinchuby wants to merge 2 commits into
mainfrom
feat/cosmos3-edge
Open

feat(models): add NVIDIA Cosmos3-Edge vision-language model#448
justinchuby wants to merge 2 commits into
mainfrom
feat/cosmos3-edge

Conversation

@justinchuby

@justinchuby justinchuby commented Aug 2, 2026

Copy link
Copy Markdown
Member

Summary

Adds the full NVIDIA Cosmos3-Edge vision-language model
(nvidia/Cosmos3-Edge, Cosmos3EdgeForConditionalGeneration) to mobius as a
LLaVA-style 3-model onnxruntime-genai split (decoder + vision_encoder +
embedding), building on the text reasoner backbone from the first commit.

Architecture

  • decoder — grouped-query-attention text reasoner with a non-gated
    squared-ReLU FFN
    (hidden_act="relu2", up_proj → relu2 → down_proj) and
    3D multimodal RoPE (mrope_section=[24, 20, 20]); takes inputs_embeds.
  • vision_encoder — SigLIP vision tower + new
    Cosmos3EdgeMultiModalProjector (pre-shuffle LayerNorm → 2x2 pixel-shuffle
    linear_fc1 → GELU → linear_fc2).
  • embedding — token embedding + image-feature fusion at image_token_id=19.

Built through a new Cosmos3EdgeVLTask (cosmos3-edge-vl). The decoder-only
text reasoner remains available as cosmos3_edge_text.

Weight routing (preprocess_weights)

Single HF checkpoint → three sub-models:

  • model.visual.* / model.projector.* → vision (SigLIP mlp.fc1/fc2up_proj/down_proj)
  • embed_tokens → embedding
  • top-level text tower (layers.* / norm / lm_head) → decoder (self_attn.to_{q,k,v,out}{q,k,v,o}_proj)
  • k_norm_und_for_gen (generator-tower key-norm) → dropped

Verified against the real nvidia/Cosmos3-Edge safetensors index: every produced
weight key lands on a graph initializer, and every weight-bearing initializer is
covered (only computed RoPE/const tensors are unmatched, as expected).

Confidence

L1 (graph-build) only. NVIDIA does not publish modeling code for cosmos3_edge
(not in transformers, no remote-code module), so the exact pixel-shuffle ordering
and numerical parity are unverifiable; L4/L5 parity is deferred. The
cosmos3_omni variants (Cosmos3-Nano/-Super) are two-tower diffusion world
models tracked separately.

Testing

  • tests/build_graph_test.py -k cosmos3 — all pass (text + VLM 3-model split).
  • Full build_graph_test.py + model_coverage_test.py + cli_test.py suites pass.
  • ruff format + ruff check clean.

🤖 Do not merge — awaiting review.

Add support for the text reasoner (language tower) of the cosmos3_edge
vision-language checkpoint (nvidia/Cosmos3-Edge,
Cosmos3EdgeForConditionalGeneration).

The language tower is a standard grouped-query-attention decoder with two
Cosmos-specific traits handled here:

- Non-gated squared-ReLU FFN (hidden_act="relu2",
  down_proj(relu2(up_proj(x)))), mapped onto the existing FCMLP component
  (Nemotron precedent) instead of the GLU-style gated MLP.
- 3D multimodal RoPE (mrope_section=[24, 20, 20]); for text-only inference
  the three sections are identical, reducing to standard 1D RoPE.

preprocess_weights renames the self_attn.to_{q,k,v,out} projections to the
q/k/v/o_proj component names, nests the top-level text tower (layers.*,
embed_tokens, norm) under model., keeps lm_head at the top level, and drops
the vision encoder (model.visual.*), the multimodal projector
(model.projector.*), and the per-layer k_norm_und_for_gen key-norm — the
latter being a two-tower (Mixture-of-Transformers) artifact that normalizes
the understanding tower's keys for the generator (diffusion) tower and is
not applied in the reasoner's own causal self-attention.

Registered as cosmos3_edge / cosmos3_edge_text and exported from
models/__init__.py. L1 graph-build verified via the parametrized
CAUSAL_LM_CONFIGS matrix; end-to-end build from the real config.json
produces a 28-layer GQA decoder with the expected non-gated relu2 FFN.
L4/L5 numerical parity is deferred (NVIDIA's custom edge modeling code is
not in transformers), recorded in _COVERAGE_SKIP. The cosmos3_omni
diffusion world-model variants (Cosmos3-Nano/-Super) are out of scope for
this decoder-only path.

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

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 13491cd7fd7a96

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.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 13491cd7fd7a96

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 support in mobius for NVIDIA Cosmos3-Edge’s decoder-only text reasoner backbone by introducing a dedicated model class that reuses the existing CausalLMModel stack while swapping in a non-gated squared-ReLU FFN and applying Cosmos-specific weight-key remapping.

Changes:

  • Added Cosmos3EdgeTextModel with FCMLP (non-gated relu2) and a preprocess_weights() mapping to rename attention projections, nest the text tower under model., and drop vision/projector + k_norm_und_for_gen weights.
  • Registered cosmos3_edge / cosmos3_edge_text in the model registry and exported the model from mobius.models.
  • Added L1 graph-build coverage via tests/_test_configs.py, and recorded L4/L5 waiver rationale in tests/model_coverage_test.py; documented in CHANGELOG.md.

Reviewed changes

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

Show a summary per file
File Description
tests/model_coverage_test.py Records Cosmos3-Edge as L1-only coverage with explicit L4/L5 parity rationale.
tests/_test_configs.py Adds cosmos3_edge to the causal LM config matrix (relu2 + mRoPE section) for L1 graph-build.
src/mobius/models/cosmos.py Implements the Cosmos3-Edge text reasoner backbone model + weight preprocessing rules.
src/mobius/models/init.py Exports Cosmos3EdgeTextModel.
src/mobius/_registry.py Registers cosmos3_edge / cosmos3_edge_text to the new model and adds default IDs.
CHANGELOG.md Documents the new model support and scope limitations.

Extend the cosmos3_edge support from a text-only reasoner to the full
vision-language model (nvidia/Cosmos3-Edge,
Cosmos3EdgeForConditionalGeneration) as a LLaVA-style 3-model
onnxruntime-genai split (decoder + vision_encoder + embedding):

- decoder: squared-ReLU GQA text reasoner taking inputs_embeds, built
  with 3D multimodal RoPE (mrope_section=[24,20,20]).
- vision_encoder: SigLIP vision tower + new Cosmos3EdgeMultiModalProjector
  (pre-shuffle LayerNorm -> 2x2 pixel-shuffle -> linear_fc1 -> GELU ->
  linear_fc2).
- embedding: token embedding + image-feature fusion at image_token_id=19.

preprocess_weights routes the single HF checkpoint to the three
sub-models: model.visual.* / model.projector.* -> vision (SigLIP
mlp.fc1/fc2 -> up_proj/down_proj), embed_tokens -> embedding, the
top-level text tower -> decoder (self_attn.to_{q,k,v,out} ->
{q,k,v,o}_proj), and drops the generator-tower k_norm_und_for_gen
key-norm. Built via a new Cosmos3EdgeVLTask ("cosmos3-edge-vl").

A vision config hook reconstructs image_size from num_patches and pulls
the projector's merger_intermediate_size from projector_config. The
decoder-only text reasoner remains available as cosmos3_edge_text.

L1 graph-build tested only: NVIDIA publishes no modeling code for
cosmos3_edge (not in transformers, no remote-code module), so exact
pixel-shuffle ordering and numerical parity are unverifiable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: justinchuby <justinchuby@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 3, 2026 04:10
@justinchuby justinchuby changed the title feat(models): add NVIDIA Cosmos 3 Edge text reasoner backbone feat(models): add NVIDIA Cosmos3-Edge vision-language model Aug 3, 2026

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

Suppressed comments (3)

src/mobius/_configs/per_model/_cosmos3_edge_vision.py:40

  • Using round(sqrt(num_patches)) can silently produce the wrong grid for non-square num_patches values, yielding an inconsistent image_size and downstream shape errors. Using math.isqrt() and validating grid*grid == num_patches makes this fail fast with a clear error.
        patch_size = getattr(hf_vision, "patch_size", None) or fields.get("patch_size")
        if num_patches is not None and patch_size is not None:
            grid = round(math.sqrt(num_patches))
            fields["image_size"] = grid * patch_size

src/mobius/components/_multimodal.py:159

  • Cosmos3EdgeMultiModalProjector implicitly assumes spatial_merge_size > 0 and that grid_size is divisible by spatial_merge_size. If either is violated, the Reshape in forward will fail at runtime with a hard-to-debug shape error. Add explicit validation in init with a clear ValueError.
        self._grid = grid_size
        self._ms = spatial_merge_size
        self._vision_hidden = vision_hidden_size
        merged_dim = vision_hidden_size * spatial_merge_size * spatial_merge_size
        # Pre-shuffle LayerNorm over the raw vision hidden size.

src/mobius/components/init.py:177

  • There are multiple adjacent import blocks from mobius.components._multimodal. Consolidating them into a single grouped import reduces duplication and avoids isort/ruff churn when adding/removing exports.
from mobius.components._multimodal import (
    Cosmos3EdgeMultiModalProjector as Cosmos3EdgeMultiModalProjector,
)
from mobius.components._multimodal import (
    Gemma3MultiModalProjector as Gemma3MultiModalProjector,

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