Skip to content

Some fixes for NemotronH - #110

Merged
gramalingam merged 23 commits into
mainfrom
rama/chunkscan
Apr 12, 2026
Merged

Some fixes for NemotronH#110
gramalingam merged 23 commits into
mainfrom
rama/chunkscan

Conversation

@gramalingam

@gramalingam gramalingam commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator
  • Fix some issues in HF transformer side that causes some numeric divergence with the ONNX implementation
  • Add a couple of versions (using scan and chunked-SSD) of Mamba2Block (for dealing with sequence of length > 1).
  • A few refinements to example script to run model

gramalingam and others added 11 commits April 2, 2026 00:57
Refactor Mamba2Block to support arbitrary sequence lengths via an ONNX
Scan op that iterates token-by-token over the conv + SSM recurrence.

Key changes:
- Use scan_input_axes/scan_output_axes to iterate over axis 1 directly,
  eliminating pre/post Transpose ops.
- Build Scan body via GraphBuilder.subgraph() with a _scan_body method,
  using the child builder's scoped value names to avoid SSA collisions
  (replaces manual rename_subgraph_values).
- Pre-realize conv1d/ssm parameters on the parent builder so the Scan
  body can reference them as implicit inputs.
- Simplify op.Constant(value_ints=[...]) to plain [...] in Split, Slice,
  Reshape, and Concat arguments.
- Remove explicit shapes from Scan body ir.Values (inference fills them).
- Batch input projection (in_proj) and output projection (out_proj) over
  the full sequence outside the Scan loop.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
HuggingFace's _init_weights() unconditionally overwrites dt_bias with
torch.rand() after loading checkpoint weights. Add _fix_nemotron_h_dt_bias()
to reload the correct values from safetensors when running --compare-hf.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Add MOBIUS_MAMBA_SCAN flag (default True). When False, Mamba2Block uses
the original single-token forward pass without Scan, useful for debugging
numerical divergence between the two paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Implement the chunked Structured State Space Duality algorithm for
parallel multi-token processing in Mamba2Block, gated behind the
flags.mamba_scan feature flag (default True).

Key changes:
- Mamba2Block._forward_chunked_ssd: processes all tokens in parallel
  within chunks of chunk_size (default 256), propagating SSM state
  across chunk boundaries via cumulative matrix products.
- dt clamp (time_step_min=0.001) after softplus in both chunked SSD
  and single-token Mamba2Scan paths, matching HF behavior.
- NemotronHConfig: added mamba_time_step_min field.
- Example script: config attribute name fixes for NemotronH.

Standalone Mamba2Block verified to match PyTorch reference within
~4e-7 for all n_groups (1,2,4,8). Full-model prefill shows growing
divergence (compound FP32 amplification across 21 Mamba layers),
with top tokens matching 24/26 positions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Change flags.mamba_scan from bool to str with three modes:

- 'chunked_ssd' (default): chunked SSD parallel algorithm
- 'scan': ONNX Scan op token-by-token iteration
- 'single': single-token path (seq_len must be 1)

The Scan-based path (from rama/mamba branch) is integrated alongside
the chunked SSD and single-token paths.  All three are selectable via
MOBIUS_MAMBA_SCAN env var.  Backwards compatible: 1/true -> chunked_ssd,
0/false -> single.

Add _env_str helper to _flags.py for string-valued flags with choices
and boolean-alias backwards compatibility.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
HF's _init_weights also reinitializes out_proj.weight in every Mamba2
layer with kaiming_uniform_ scaled by 1/sqrt(num_hidden_layers) when
rescale_prenorm_residual is True. This overwrites trained checkpoint
weights, causing massive ONNX-vs-HF divergence.

Extended _fix_nemotron_h_dt_bias() to reload both dt_bias and
out_proj.weight from the safetensors checkpoint (42 params total:
21 dt_bias + 21 out_proj.weight).

With both fixes, full-model comparison improves from 0/26 to 25/26
token matches (max diff 7.2, down from 25.0).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
When no --prompt is given, the script now enters an interactive loop
prompting for queries. Type 'quit' or Ctrl-D to stop. The ONNX session
is reused across all queries. With --compare-hf, HF comparison runs
after all interactive queries are collected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Tokens are now printed as they are generated instead of waiting
until the end. Makes slow generation much more usable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Default 1.2 to prevent repetitive loops in greedy decoding. Uses the
same algorithm as HF's RepetitionPenaltyLogitsProcessor: divide
positive logits by penalty, multiply negative logits by penalty.
Both ONNX and HF paths use the same --repetition-penalty value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
@github-actions

github-actions Bot commented Apr 7, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing dd4bed8b43f0a1

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 99 -3.9% 🟢
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 61 61 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 58 58 +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.

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

Note

Copilot was unable to run its full agentic suite in this review.

Improves NemotronH/Mamba2 correctness and usability by aligning ONNX behavior more closely with HuggingFace, adding multi-token Mamba2 execution strategies, and refining the Nemotron example generation script.

Changes:

  • Add time_step_min support to Mamba2 SSM step to reduce numeric divergence vs HF.
  • Implement multi-token Mamba2Block paths (chunked-SSD and ONNX Scan) and dispatch via a new MOBIUS_MAMBA_SCAN flag.
  • Enhance the Nemotron text-generation example (interactive mode, repetition penalty, HF comparison fixes).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/build_graph_test.py Enables graph test coverage for mamba2 by removing it from the skip list.
src/mobius/models/nemotron_h.py Plumbs mamba_time_step_min into the Mamba2Block construction.
src/mobius/components/_ssm.py Adds time_step_min parameter and clamps dt accordingly.
src/mobius/components/_mamba_block.py Adds chunked-SSD + Scan multi-token implementations and flag-based dispatch.
src/mobius/_flags.py Introduces mamba_scan flag parsing from environment with validated choices.
src/mobius/_configs.py Adds config field for mamba_time_step_min and HF config mapping.
examples/nemotron_3_nano_text_generation.py Improves script UX and correctness (interactive prompts, repetition penalty, HF weight patching).

Comment thread src/mobius/components/_mamba_block.py
Comment thread src/mobius/components/_mamba_block.py Outdated
Comment thread src/mobius/components/_mamba_block.py Outdated
Comment thread src/mobius/_flags.py Outdated
Comment thread examples/nemotron_3_nano_text_generation.py Outdated
@github-actions

github-actions Bot commented Apr 7, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing dd4bed8b43f0a1

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 21 🟡
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
mamba (ssm-text-generation) / model — 21 change(s)

Op summary: 103 → 99 nodes

--- base
+++ head
@@ -2,13 +2,9 @@
 RMSNormalization
 Transpose
 MatMul
-Constant
 Split
 Transpose
 Concat
-Constant
-Constant
-Constant
 Slice
 Conv
 Unsqueeze

Removed nodes:

  • - Constant
  • - Constant
  • - Constant
  • - Constant

Modified attributes:

  • node[65] Transpose: perm: [0, 2, 1] → [1, 0]

Connectivity changes:

  • node[1] RMSNormalization: input_ids [33, 6] → [35, 6]
  • node[3] MatMul: input_ids [34, 35] → [36, 37]
  • node[18] Transpose: input_ids [13] → [18]
  • node[19] MatMul: input_ids [51, 52] → [53, 56]
  • node[28] Unsqueeze: input_ids [61, 16] → [54, 11]
  • node[36] Squeeze: input_ids [67, 19] → [55, 9]
  • node[38] Mul: input_ids [72, 2] → [73, 75]
  • node[40] Squeeze: input_ids [57, 19] → [49, 9]
  • node[45] Mul: input_ids [12, 80] → [40, 82]
  • node[65] Transpose: input_ids [104] → [30]
  • node[75] Unsqueeze: input_ids [114, 16] → [107, 11]
  • node[83] Squeeze: input_ids [120, 19] → [108, 9]
  • node[85] Mul: input_ids [125, 4] → [126, 128]
  • node[87] Squeeze: input_ids [110, 19] → [103, 9]
  • node[92] Mul: input_ids [26, 133] → [94, 135]

Initializer changes:

  • initializer count 28 → 30

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

gramalingam and others added 2 commits April 7, 2026 20:10
Replace the out_proj.weight reload workaround with a simpler config fix:
set rescale_prenorm_residual=False before from_pretrained. This prevents
HF's _init_weights from corrupting out_proj.weight with kaiming_uniform_
reinit (a training-time GPT-2 residual scaling flag, not needed for
inference). The dt_bias reload is still needed (separate unconditional
bug in _init_weights).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Split the monolithic 1047-line Mamba2Block class into:

- _mamba_block.py: Mamba2BlockBase (shared __init__), Mamba2BlockSingle
  (single-token path), and Mamba2Block factory function
- _mamba_block_scan.py: Mamba2BlockScan (ONNX Scan token-by-token)
- _mamba_block_chunked.py: Mamba2BlockChunkedSSD (parallel chunked SSD)

The factory function reads flags.mamba_scan at construction time and
returns the matching subclass. Zero caller changes needed — all 4
consumers (bamba, granitemoehybrid, mamba, nemotron_h) use the factory
exactly like the old class.

Design follows my/mamba-2-design.md Option B+D:
- Subclass over boolean flags (no mode dispatch in forward())
- _realize_submodule is a single staticmethod on Mamba2BlockBase
- Chunked SSD helpers (_segment_sum, _segment_sum_dynamic) move to
  the chunked file

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
@gramalingam gramalingam changed the title [DRAFT] Some fixes for NemotronH Some fixes for NemotronH Apr 7, 2026
- Change mamba_scan flag default from 'single' to 'chunked_ssd' and
  align the docstring to label chunked_ssd as the default mode
- Update example docstring to reflect that multi-token Mamba2 modes
  now exist (removes outdated 'only support single-token decode')
- Align _scan_body parameter names with subgraph input dict keys
  for consistency (conv_state_in→conv_state, xbc_in→xbc_t, etc.)

The _realize_submodule duplicate issue was already resolved by the
prior refactoring commit (single staticmethod on Mamba2BlockBase).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
@justinchuby

Copy link
Copy Markdown
Member

Tests are failing

gramalingam and others added 4 commits April 8, 2026 23:25
- Add noqa: N802 to Mamba2Block factory function (intentional PascalCase)
- Add per-file-ignores for N803/N806 in _mamba_block_chunked.py (SSM
  math notation: A, B, C, D, H, N variables)
- Fix E402 in example: move mobius imports above warnings.filterwarnings
- Add noqa: N806 for _CORRUPTED_KEYS constant-like variable
- Add noqa: RUF069 for intentional float equality check (penalty == 1.0)
- Auto-fix import sorting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
The causal conv Pad in Mamba2BlockChunkedSSD used a float32 constant
for the pad value, causing an ORT type error when the model is built
with f16 or bf16. Fix by using CastLike to match the pad constant to
the input tensor dtype. Internal chunked SSD Pads (on explicitly
float32 tensors) keep explicit float32 constants.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
The default was mistakenly changed to 'chunked_ssd' when addressing
PR review feedback. Restore 'single' as the intended default and
update the docstring to list it first with the (default) label.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
@justinchuby

Copy link
Copy Markdown
Member

Can com.microsoft.LinearAttention Express Mamba2 SSD?

TL;DR: Yes — with update_rule="gated" — and it would collapse all three forward modes in this PR into a single op call.


The LinearAttention Op

com.microsoft.LinearAttention (ORT ContribOps, opset 1) is a unified recurrent-attention op that supports both decode (T=1) and prefill (T>1) in one call, with a chunk-parallel implementation for GPU efficiency. Its update_rule="gated" recurrence is:

S_t = exp(g_t) * S_{t-1} + k_t ⊗ v_t
o_t = scale * q_t^T S_t

Inputs: query, key, value, [past_state], [decay], [beta]
Outputs: output, present_state

The decay input supports per-head scalar shape (B, T, H_kv) — exactly what Mamba2 needs.


Mamba2 SSD Recurrence (from Mamba2Scan.forward)

dA_t = exp(A * dt_t)                     # scalar per head
h_t  = dA_t * h_{t-1} + dt_t * B_t ⊗ x_t   # outer product, B_t: d_state
y_t  = einsum(h_t, C_t) + D * x_t       # readout + skip

where A_log is (num_heads,), dt_t is per-head, B_t / C_t are (n_groups, d_state) expanded to all heads.


Exact Tensor Mapping

Mamba2 tensor LinearAttention arg Packed shape Notes
C_mat (readout) query (B, T, H_q × d_state) q_num_heads = num_heads
B_mat (input matrix) key (B, T, H_kv × d_state) kv_num_heads = n_groups
dt * x (discretized) value (B, T, H_kv × d_head) absorb dt into value
A * dt (log-decay) decay (B, T, H_kv) per-head scalar
ssm_state past_state (B, H, d_state, d_head) requires transpose (see below)

Attributes: update_rule="gated", q_num_heads=num_heads, kv_num_heads=n_groups, scale=1.0

The n_groups > 1 case maps naturally onto LinearAttention's GQA grouping (q_num_heads / kv_num_heads = heads_per_group).


Three Items Outside the Op

  1. dt discretizationdt = softplus(dt_proj(x) + dt_bias) is computed before calling LA; dt * x is passed as value and A * dt as decay. No structural change needed.

  2. D skip connectiony += D * x is a plain Add after the LA output. One line.

  3. State shape transpose — Current Mamba2 state is (B, H, d_head, d_state) but LA expects (B, H, d_k, d_v) = (B, H, d_state, d_head). Need Transpose([0,1,3,2]) on past_state input and present_state output. Minor.


What This Means for This PR

This PR implements three modes — Mamba2BlockSingle (T=1), Mamba2BlockScan (ONNX Scan, sequential), Mamba2BlockChunkedSSD (parallel within chunks) — because ORT doesn't have a native op that handles both decode and prefill. LinearAttention removes that need entirely. A single LA call handles both paths, and ORT's implementation is chunk-parallel for prefill internally.

The entire Mamba2BlockChunkedSSD._chunked_ssd() method (~180 ONNX ops: segment_sum, CumSum, Trilu, cross-chunk propagation, 5-stage SSD) would collapse to:

# Outside: compute dt, pack tensors
decay = op.Mul(a_2d, dt)                      # A * dt, shape (B,T,H_kv)
value = op.Mul(x_disc_packed, dt_packed)      # dt * x, shape (B,T,H_kv*d_head)

# The op
output, new_state = op.LinearAttention(
    C_packed,                     # query  (B,T,H_q*d_state)
    B_packed,                     # key    (B,T,H_kv*d_state)
    value,                        # value  (B,T,H_kv*d_head)
    past_state_transposed,        # (B,H_kv,d_state,d_head)
    decay,                        # (B,T,H_kv) — per-head scalar decay
    domain="com.microsoft",
    update_rule="gated",
    q_num_heads=self.num_heads,
    kv_num_heads=self.n_groups,
    scale=1.0,
)

# Outside: D skip
y = op.Add(output, op.Mul(D_skip, x))

Recommendation

Consider gating this PR on LinearAttention availability. Since LinearAttention is already in com.microsoft ContribOps (confirmed in ContribOperators.md), adding a rewrite rule that replaces the Mamba2BlockChunkedSSD ONNX subgraph with a single LinearAttention node (similar to how GQA rewrites work for attention) would:

  1. Remove ~300 lines of chunked SSD ops from the ONNX graph
  2. Let ORT choose the optimal chunk size at runtime
  3. Unify decode and prefill into one code path

If LinearAttention isn't available in the minimum supported ORT version, the current ONNX-native fallback in this PR is still valuable — they can coexist as primary and fallback paths.


Investigated by reading the PR diff, Mamba2Scan.forward() in _ssm.py, Mamba2BlockChunkedSSD._chunked_ssd(), and the LinearAttention spec in ContribOperators.md.

gramalingam and others added 3 commits April 9, 2026 21:00
Switch NemotronH from create_attention_bias (float additive mask) to
create_padding_mask (bool mask). ORT 1.24.4's opset 24 Attention op
does not support bfloat16 for the mask/bias input, causing a type error:
'Type parameter (T) of Optype (Add) bound to different types
(tensor(bfloat16) and tensor(float))'.

The bool padding mask works with all dtypes (f32, f16, bf16) because:
- Attention op already uses is_causal=1 for causal masking
- The bool mask only encodes padding information
- This also enables Flash Attention eligibility in ORT

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
NemotronH does not use rotary position embeddings — its HuggingFace
reference (NemotronHAttention.forward) applies no rotary encoding.
The model relies on Mamba layers' inherent position-awareness for
sequence ordering.

The ONNX model was incorrectly applying DefaultRope(theta=10000)
because ArchitectureConfig.from_transformers() defaults rope_type
to 'default' when the HF config doesn't specify one.  This caused
~0.12 RMS divergence at the first attention layer (layer 12),
growing to >1.0 by the final attention layer (layer 32) during
prefill.

After this fix, all 57 generated tokens match HF exactly with
max logit diff of 5.7e-5 (pure FP32 rounding noise).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Map --device to the corresponding EP name so the built model uses
EP-specific contrib ops (e.g. GroupQueryAttention on CPU/CUDA).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>

# Conflicts:
#	examples/nemotron_3_nano_text_generation.py
@gramalingam
gramalingam enabled auto-merge (squash) April 10, 2026 14:53
@codecov

codecov Bot commented Apr 10, 2026

Copy link
Copy Markdown

The author of this PR, gramalingam, 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.

@gramalingam

Copy link
Copy Markdown
Collaborator Author

Can com.microsoft.LinearAttention Express Mamba2 SSD?

Thanks for the suggestion. This version is working correctly at last. I suggest merging it. I will look at using LinearAttention as a follow-up PR. (Sometime copilots do a few dumb things that make it hard to get back to an earlier state. Better to get this version in, so that I can fallback to this easily.)

A second follow-on relates to the issue that was causing the numeric-precision: this model doesn't use RoPE (in HF), its config doesn't have rope_type, but we end up using a default RoPE instead of no RoPE. Not sure if we need to change anything about the way the default works in this setting. The current approach (the model class explicitly makes that decision) might be fine, but worth thinking about.

The mamba2 test config had intermediate_size=128 (from TINY_INTERMEDIATE
default) but num_heads=4 * head_dim=16 = 64. Mamba2 requires
d_inner = num_heads * head_dim, so the GatedRMSNorm received inputs
with incompatible shapes ([batch,64] vs [batch,128]), causing shape
inference to fail and the ONNX checker to report missing shape fields.

Fix: add explicit intermediate_size=64 to the mamba2 test config.
Also add __post_init__ validation to Mamba2Config to catch this
constraint violation early.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
gramalingam added a commit that referenced this pull request Apr 11, 2026
- Change mamba_scan flag default from 'single' to 'chunked_ssd' and
  align the docstring to label chunked_ssd as the default mode
- Update example docstring to reflect that multi-token Mamba2 modes
  now exist (removes outdated 'only support single-token decode')
- Align _scan_body parameter names with subgraph input dict keys
  for consistency (conv_state_in→conv_state, xbc_in→xbc_t, etc.)

The _realize_submodule duplicate issue was already resolved by the
prior refactoring commit (single staticmethod on Mamba2BlockBase).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: G Ramalingam <grama@microsoft.com>
@gramalingam
gramalingam disabled auto-merge April 12, 2026 01:12
@gramalingam
gramalingam merged commit 7c1972d into main Apr 12, 2026
19 of 22 checks passed
@gramalingam
gramalingam deleted the rama/chunkscan branch April 12, 2026 01:13
justinchuby added a commit that referenced this pull request Apr 22, 2026
`_extract_rope_config()` never returned `None`, so NoPE models
(NemotronH, GraniteMoeHybrid, GPT-2/BERT/OPT families) silently received
`rope_type='default'` and had spurious rotary ops injected into their
ONNX graphs. The existing NemotronH fix (#110) is a model-class
workaround; this PR fixes the root cause in the config system.

### Changes

- **`_extract_rope_config()`** returns `None` unless the HF config has a
real RoPE signal: `rope_parameters`, `rope_scaling`, or legacy
`rotary_dim` / `rotary_pct` / `rotary_emb_base`. `rope_theta` alone is
dead data (NemotronH carries it without using RoPE) and is deliberately
not treated as a signal.
- **`ArchitectureConfig.from_transformers()`** propagates `None` to both
the `rope` sub-config and every flat RoPE field when the model is NoPE.
`dataclasses.replace(rope_config, ...)` call sites now guard against
`None`.
- **`ArchitectureConfig.rope_type`** default is `None` (the structural
NoPE signal). `rope_theta` / `partial_rotary_factor` keep inert numeric
defaults so `ArchitectureConfig(rope_type="default", ...)` still works
for direct construction in tests.
- **`initialize_rope()`** returns `None` when `rope_type is None` and
`mrope_section is None`.
- **`TextModel.forward()`** guards `self.rotary_emb(...)` and passes
`position_embeddings=None` down when RoPE is absent.
- **`Attention.__init__`** tolerates `partial_rotary_factor=None`
(treated as the inert 1.0) so NoPE-routed attention doesn't crash on
`math.isclose(None, 1.0)`.

### Tests

- New: `test_from_transformers_nope_model_has_none_rope`,
`test_from_transformers_legacy_rotary_dim_enables_rope`,
`test_rope_theta_alone_is_not_a_rope_signal`, `test_nope_returns_none`
(initialize_rope).
- Existing fakes in `_config_resolver_test.py` / `_configs_test.py` now
include `rope_parameters={"rope_type": "default"}` to match how real HF
`PretrainedConfig.__post_init__` populates the field.
- `make_config()` test helper opts into `rope_type="default"` so
component tests keep the RoPE code path. Direct `BambaConfig` /
`JambaConfig` / `Gemma2Config` test constructors and
`deepseek_ocr2.py`'s internal `ArchitectureConfig` opt in explicitly.

### Intentionally out of scope

- Simplifying the NemotronH / GraniteMoeHybrid text-model workarounds —
they use bespoke layer types (`NemotronHMambaLayer`, etc.) and can't
drop straight onto `TextModel`. They remain correct and now coexist with
a structural defense in the config layer.
- Phase 2 (calling `validate()` in the build path, nullable MoE
sub-config, deprecating flat RoPE fields) and Phase 3 (feature-group
sub-configs, per-model config classes) from the issue.

### Example

```python
# NemotronH config → no RoPE signal → structurally NoPE
class FakeNemotronH:
    model_type = "nemotron_h"
    rope_theta = 10_000.0  # dead data, ignored
    # no rope_parameters, no rope_scaling, no rotary_*
    ...

cfg = ArchitectureConfig.from_transformers(FakeNemotronH())
assert cfg.rope is None
assert cfg.rope_type is None
assert initialize_rope(cfg) is None  # TextModel now skips RoPE automatically
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: justinchuby <11205048+justinchuby@users.noreply.github.com>
Co-authored-by: Justin Chu <justinchuby@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.

3 participants