Skip to content

Fix YaRN RoPE bugs in model builder and add parity tests - #2076

Merged
kunal-vaishnavi merged 3 commits into
mainfrom
pr/fix-yarn-config-resolution
Apr 20, 2026
Merged

Fix YaRN RoPE bugs in model builder and add parity tests#2076
kunal-vaishnavi merged 3 commits into
mainfrom
pr/fix-yarn-config-resolution

Conversation

@titaiwangms

@titaiwangms Ti-Tai Wang (titaiwangms) commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes four bugs in the ModelBuilder's YaRN RoPE configuration resolution that caused completely wrong cos/sin caches, producing garbage output for ALL YaRN-based models (Ministral-3-3B, GPT-OSS-20B, and any model using beta_fast/beta_slow in rope_scaling).

Also adds comprehensive parity tests for 3 YaRN configurations, consolidates mscale logic, and excludes src/python/py/models/ from lintrunner (legacy formatting conflicts).

Bugs Fixed

Bug 1: hasattr on dict for original_max_position_embeddings

hasattr(config.rope_scaling, 'original_max_position_embeddings') always returns False for dicts. Fixed to use isinstance(Mapping) + in operator.

Bug 2: rope_theta fallback

Models that store rope_theta only in rope_scaling dict (not as a top-level config attribute) fell through to default theta=10000. Added fallback chain: config.rope_thetaconfig.rope_embedding_baseconfig.rope_scaling["rope_theta"]10000.

Bug 3: YaRN mscale override

Always computed mscale from factor via make_mscale(), ignoring explicit mscale=1.0 in config. Ministral-3-3B sets mscale=1.0 but was getting mscale≈1.277. Now respects the config value when > 0.

Bug 4: inv_freq double-inversion

make_inv_freq_rescaled_with_ntk computed 1/(factor * inv_freq) which double-inverts since inv_freq is already 1/pos_freqs, giving pos_freqs/factor (wrong). Fixed to inv_freq / factor.

Review Feedback Addressed

  • Changed isinstance(rope_scaling, dict)isinstance(rope_scaling, Mapping) in 2 locations
  • Consolidated inline mscale override logic into make_mscale(mscale, config_mscale=0) — config override is now handled centrally with backward-compatible default
  • Removed make_mscale default parameter values (callers pass explicitly)
  • Added mscale_all_dim documentation comment
  • Tests drive real builder code (make_rope_init) instead of reimplementing logic inline
  • Added GPT-OSS-20B test config per reviewer request

Tests Added

Added test/python/test_yarn_rope_parity.py with 16 tests covering 3 YaRN configurations:

Ministral-3-3B (factor=16, theta=1M, explicit mscale=1.0):

  1. test_ministral_3b_cos_sin_match — end-to-end parity with HF reference
  2. test_bug_a_hasattr_on_dict — dict key access + wrong fallback guard
  3. test_bug_b_rope_theta_fallback — theta from rope_scaling + default guard
  4. test_bug_c_mscale_override — explicit mscale via make_rope_init()
  5. test_mscale_fallback_when_absent.get("mscale", 0) fallback
  6. test_mscale_all_dim_formula — mscale_all_dim computation
  7. test_bug_d_inv_freq_no_double_inversion — NTK scaling vs HF reference
  8. test_full_cache_length — parity at 2048 positions
  9. test_mapping_isinstance_with_frozen_dict — MappingProxyType support

Synthetic YaRN (factor=40, theta=10K, no explicit mscale):
10. test_yarn_no_mscale_cos_sin_match — parity with HF reference
11. test_yarn_no_mscale_uses_computed_value — computed mscale from factor
12. test_different_yarn_configs_produce_different_caches — configs produce distinct outputs

GPT-OSS-20B (factor=4, theta=500K, top-level rope_theta, no explicit mscale):
13. test_gptoss_20b_cos_sin_match — end-to-end parity with HF reference
14. test_gptoss_20b_top_level_rope_theta — verifies top-level theta is used
15. test_gptoss_20b_computed_mscale — computed mscale from factor=4
16. test_gptoss_20b_full_cache_length — parity at 2048 positions

Impact

  • Fixes ALL models using YaRN RoPE (beta_fast/beta_slow in rope_scaling)
  • Verified: cos/sin caches match HF transformers reference (atol=1e-5)
  • Verified: Ministral-3-3B generates correct output ("The capital of France is Paris.")

Files Changed

  • .lintrunner.toml — exclude src/python/py/models/** from lintrunner (legacy formatting)
  • src/python/py/models/builders/base.py — 4 bug fixes + Mapping import + mscale consolidation
  • test/python/test_yarn_rope_parity.py — 16 parity tests for 3 YaRN configs (new file)

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

Fixes YaRN RoPE configuration resolution in the Python ModelBuilder to prevent incorrect rotary cos/sin cache generation (which can produce unusable outputs for YaRN-based models).

Changes:

  • Fixes original_max_position_embeddings detection when rope_scaling is stored as a dict-like object.
  • Adds rope_theta fallback to read from rope_scaling when not present as a top-level config field.
  • Respects explicit YaRN mscale from config and corrects NTK rescaling math to avoid double-inversion.

Comment thread src/python/py/models/builders/base.py
Comment thread src/python/py/models/builders/base.py
@titaiwangms
Ti-Tai Wang (titaiwangms) force-pushed the pr/fix-yarn-config-resolution branch 2 times, most recently from dbb6147 to d786f35 Compare April 9, 2026 20:28
@titaiwangms
Ti-Tai Wang (titaiwangms) marked this pull request as ready for review April 9, 2026 21:00
@titaiwangms Ti-Tai Wang (titaiwangms) changed the title Fix 4 ModelBuilder config resolution bugs for YaRN RoPE models Fix YaRN RoPE bugs in model builder and add parity tests Apr 9, 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 2 out of 2 changed files in this pull request and generated 5 comments.

Comment thread test/python/test_yarn_rope_parity.py Outdated
Comment thread test/python/test_yarn_rope_parity.py Outdated
Comment thread test/python/test_yarn_rope_parity.py Outdated
Comment thread test/python/test_yarn_rope_parity.py Outdated
Comment thread test/python/test_yarn_rope_parity.py Outdated
Comment thread test/python/test_yarn_rope_parity.py
Comment thread src/python/py/models/builders/base.py Outdated
Comment thread src/python/py/models/builders/base.py Outdated
@titaiwangms
Ti-Tai Wang (titaiwangms) force-pushed the pr/fix-yarn-config-resolution branch from 81b1c13 to f24ee36 Compare April 15, 2026 19:59

@titaiwangms Ti-Tai Wang (titaiwangms) left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reverted all cosmetic spacing changes in base.py. Only functional YaRN fixes remain — diff is now 36 changed lines (29+, 7-) vs ~487 before. Thanks for the catch!

Squashed into single clean commit: f24ee36

@tianleiwu Tianlei Wu (tianleiwu) 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.

Review Summary

All four YaRN RoPE bug fixes are correct and well-tested. The fixes are minimal, targeted, and produce cos/sin caches matching the HuggingFace transformers reference.

Positives:

  • Using collections.abc.Mapping instead of isinstance(_, dict) correctly handles MappingProxyType, ordered dicts, and other Mapping implementations.
  • The rope_theta fallback chain covers all known config layouts.
  • Bug 4 (inv_freq double-inversion) fix is mathematically clean: inv_freq / factor correctly gives 1/(factor * pos_freqs) matching HF.
  • Test suite is comprehensive: 11 tests with HF reference parity, guard assertions preventing false passes, and real builder code exercised via make_rope_init.

One suggestion on make_mscale generalization (see inline comment). Two nitpicks on test file (below).

Test nitpicks (non-blocking):

  1. _make_builder_cos_sin uses object.__new__(Model) and manually initializes attributes. If make_rope_init starts depending on new attributes from __init__, tests will fail with opaque AttributeErrors. Consider adding a comment listing assumed attributes.
  2. YARN_NO_MSCALE_CONFIG docstring says "Modeled after DeepSeek-V2 style YaRN scaling" but DeepSeek-V2 uses longrope/su-type RoPE, not yarn. The inline comment ("Synthetic YaRN config") is accurate — consider aligning the docstring.

Comment thread src/python/py/models/builders/base.py

@titaiwangms Ti-Tai Wang (titaiwangms) left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review feedback!

Tianlei Wu (@tianleiwu) — Good suggestion on the mscale_all_dim formula. We went ahead and implemented it in this PR since it was straightforward: make_mscale() now accepts config_mscale_all_dim and uses the full HF formula (get_mscale(factor, mscale) / get_mscale(factor, mscale_all_dim)) when both are provided. Added a test covering equal, different, and fallback cases. Backward compatible — existing callers default to config_mscale_all_dim=0.

kunal-vaishnavi — Confirmed: all cosmetic spacing changes have been reverted. Only functional YaRN RoPE fixes remain (36→40 changed lines in base.py). The make_mscale consolidation you suggested is also in place with config_mscale parameter.

@titaiwangms
Ti-Tai Wang (titaiwangms) force-pushed the pr/fix-yarn-config-resolution branch from f24ee36 to 71a0f29 Compare April 15, 2026 22:12
@titaiwangms

Copy link
Copy Markdown
Contributor Author

I constantly found CI failures that are not from this PR but they are required. How did people merge their PR in genai? Or I miss something.

Fix four bugs in the YaRN RoPE implementation in base.py:

1. Use isinstance(Mapping) instead of hasattr() for dict-based
   rope_scaling — hasattr always returns False for dict keys
2. Resolve rope_theta from rope_scaling when top-level attr is absent
3. Add config_mscale parameter to make_mscale() so explicit mscale
   values (e.g. Ministral-3-3B's mscale=1.0) override computed values
4. Fix inv_freq double-inversion: use inv_freq/factor instead of
   1/(factor*inv_freq)

Add 11 parity tests comparing builder cos/sin caches against HuggingFace
reference implementation for two YaRN configurations (Ministral-3-3B
and a DeepSeek-V2 style config).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@titaiwangms
Ti-Tai Wang (titaiwangms) force-pushed the pr/fix-yarn-config-resolution branch from 71a0f29 to a959221 Compare April 15, 2026 22:31
Comment thread src/python/py/models/builders/base.py
Comment thread test/python/test_yarn_rope_parity.py
Ti-Tai Wang (titaiwangms) added a commit that referenced this pull request Apr 16, 2026
Add Mistral3TextModel builder for mistralai/Ministral-3-3B-Instruct-2512:
- VLM text decoder with FP8 dequantization (weight * scale_inv)
- Patches genai_config.json with image_token_id from HF config
- Excludes embedding layer (handled by separate embedding model)
- Registers Mistral3ForConditionalGeneration in builder.py dispatch

Also includes from PR #2076 (base branch):
- Fix 4 YaRN RoPE bugs (hasattr, theta fallback, mscale, inv_freq)
- isinstance(Mapping) for robust rope_scaling checks
- Consolidated make_mscale() with config_mscale parameter
- 12 YaRN RoPE parity tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@titaiwangms
Ti-Tai Wang (titaiwangms) force-pushed the pr/fix-yarn-config-resolution branch from b2c21e0 to a959221 Compare April 16, 2026 19:57
Ti-Tai Wang (titaiwangms) added a commit that referenced this pull request Apr 16, 2026
Add Mistral3TextModel builder for mistralai/Ministral-3-3B-Instruct-2512:
- VLM text decoder with FP8 dequantization (weight * scale_inv)
- Patches genai_config.json with image_token_id from HF config
- Excludes embedding layer (handled by separate embedding model)
- Registers Mistral3ForConditionalGeneration in builder.py dispatch

Also includes from PR #2076 (base branch):
- Fix 4 YaRN RoPE bugs (hasattr, theta fallback, mscale, inv_freq)
- isinstance(Mapping) for robust rope_scaling checks
- Consolidated make_mscale() with config_mscale parameter
- 12 YaRN RoPE parity tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add src/python/py/models/** to exclude_patterns for both RUFF and
RUFF-FORMAT linters in .lintrunner.toml. This prevents the linter
from introducing formatting-only changes to model builder files,
keeping PR diffs focused on functional changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Convert lambda assignment to a proper function definition to satisfy
ruff E731. Add required blank lines around nested def per ruff-format.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ti-Tai Wang (titaiwangms) added a commit that referenced this pull request Apr 17, 2026
Add Mistral3TextModel builder for mistralai/Ministral-3-3B-Instruct-2512:
- VLM text decoder with FP8 dequantization (weight * scale_inv)
- Patches genai_config.json with image_token_id from HF config
- Excludes embedding layer (handled by separate embedding model)
- Registers Mistral3ForConditionalGeneration in builder.py dispatch

Also includes from PR #2076 (base branch):
- Fix 4 YaRN RoPE bugs (hasattr, theta fallback, mscale, inv_freq)
- isinstance(Mapping) for robust rope_scaling checks
- Consolidated make_mscale() with config_mscale parameter
- 12 YaRN RoPE parity tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kunal-vaishnavi
kunal-vaishnavi enabled auto-merge (squash) April 18, 2026 00:22
@kunal-vaishnavi
kunal-vaishnavi merged commit b297562 into main Apr 20, 2026
15 of 19 checks passed
@kunal-vaishnavi
kunal-vaishnavi deleted the pr/fix-yarn-config-resolution branch April 20, 2026 19:09
Ti-Tai Wang (titaiwangms) added a commit that referenced this pull request Apr 26, 2026
## Summary

Adds Mistral3/Pixtral VLM support to onnxruntime-genai with multi-image
inference. Includes C++ image processor, PixtralVisionState for
per-image vision processing, Python export support, and comprehensive
tests.

## Changes

### C++ Runtime
- **Mistral3 image processor** — `[IMG]`/`[IMG_BREAK]`/`[IMG_END]` token
expansion based on image resolution and patch geometry, multi-image
support
- **PixtralVisionState** — per-image vision processing loop with bounds
checks and overflow guard; slices from padded batch tensor using
`image_sizes` metadata from ort-extensions `PixtralImageSizes` op
- **Virtual `SetExtraInputs`** — proper polymorphic dispatch for vision
state subclasses
- **`IsPixtralFamily()` model type detection** — enables
Pixtral-specific codepath
- **`processor_config.json`** — with `PixtralImageSizes` preprocessing
step
- **`context_length` / `max_length` separation** — `context_length`
controls KV cache allocation while `max_length` controls generation
stopping, preventing premature EOS with large image token counts
- **INT32 `input_ids`** — token IDs above 32767 (Pixtral `[IMG]`=128011)
require int32

### Python Export Support
- Mistral3 model classes (`Mistral3Config`,
`Mistral3ForConditionalGeneration`)
- FP8 dtype promotion for checkpoint loading
- `get_user_content()` handler for Mistral3 prompt formatting

## Multi-Image Architecture

Pixtral uses dynamic image sizes (28×28 to 1540×1540) so images can't be
batched in the vision encoder. `PixtralVisionState` processes each image
individually by:
1. Reading `image_sizes` tensor from ort-extensions `PixtralImageSizes`
op (provides per-image H×W)
2. Slicing the padded `[N, C, max_H, max_W]` batch tensor to extract
each image's actual pixels
3. Running vision encoder on each image separately
4. Concatenating vision embeddings for the decoder

## Dependencies

- **onnxruntime-extensions PR #1050** — `PixtralImageSizes` custom op
for image size metadata
- **PR #2076** — YaRN RoPE parity fixes (merged) ✅

## Testing

- 5 multi-image token expansion tests (various image sizes and counts)
- Virtual dispatch verification tests for `SetExtraInputs`
- YaRN RoPE parity tests (from merged #2076)
- E2E verified: multi-image (fish.jpg + challenge.jpg) correctly
describes both images

---------

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.

4 participants