Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
43c7e8e
first commit to update modelbuilder
xadupre Apr 10, 2026
bd2366e
lint
xadupre Apr 10, 2026
fb7f0c3
dependency
xadupre Apr 10, 2026
b6a8cc8
fix import issues
xadupre Apr 10, 2026
4306fb1
Potential fix for pull request finding 'CodeQL / Empty except'
xadupre Apr 10, 2026
0518fde
fix
xadupre Apr 10, 2026
d6950e8
fix import issues
xadupre Apr 10, 2026
19d40c2
ls
xadupre Apr 10, 2026
04cfac0
Potential fix for pull request finding 'CodeQL / Signature mismatch i…
xadupre Apr 10, 2026
bf8f708
fix: handle None data_path in conftest, fix CodeQL signature mismatch…
Copilot Apr 10, 2026
52364b4
lint
xadupre Apr 10, 2026
e9b5000
Update test/python/models/fast/test_random_granite.py
xadupre Apr 14, 2026
c0bccd4
Update test/python/models/fast/test_random_gemma.py
xadupre Apr 14, 2026
d41f845
Update test/python/models/fast/test_random_gemma3.py
xadupre Apr 14, 2026
b787806
Update .github/workflows/modelbuilder_fast_tests.yml
xadupre Apr 14, 2026
5d6f67e
Update src/python/py/models/builders/base.py
xadupre Apr 14, 2026
0b169f8
address review: restore formatting, fix gemma3 VLM model_type, remove…
Copilot Apr 14, 2026
ccee173
fix: add comment for FP16 CPU GQA exclusion, rename config var in wor…
Copilot Apr 14, 2026
e069371
Merge branch 'main' of https://github.com/microsoft/onnxruntime-genai…
xadupre Apr 20, 2026
0a5351f
fix style
xadupre Apr 20, 2026
56a4bab
Apply suggestion from @kunal-vaishnavi
xadupre Apr 20, 2026
1962cd8
fix
xadupre Apr 20, 2026
3db7e03
Merge branch 'xadupre/mbext' of https://github.com/microsoft/onnxrunt…
xadupre Apr 20, 2026
0c71fa6
fix gqa
xadupre Apr 20, 2026
960c525
rename ExtTestCase into ModelBuilderTestCase
xadupre Apr 20, 2026
49997a8
fix CodeQL: remove incorrect Model inheritance from Ministral3VisionE…
Copilot Apr 20, 2026
02b1186
remove unnecessary tests
xadupre Apr 20, 2026
40af7a8
rename a file
xadupre Apr 20, 2026
90b0d77
use gqa fir wen35
xadupre Apr 20, 2026
2c5f71f
add og tests
xadupre Apr 20, 2026
263033e
nemotron_h
xadupre Apr 20, 2026
3681b46
Potential fix for pull request finding 'CodeQL / Potentially uninitia…
xadupre Apr 20, 2026
29b111e
remove undesired changes
xadupre Apr 20, 2026
a691f94
fix
xadupre Apr 20, 2026
7cfd93e
fix nemotron model type
xadupre Apr 20, 2026
67df491
style
xadupre Apr 20, 2026
3bb036c
merge
xadupre Apr 21, 2026
7240182
remove transformers 4.57
xadupre Apr 21, 2026
c556000
zero
xadupre Apr 21, 2026
7356997
Update src/python/py/models/builders/mistral.py
xadupre Apr 21, 2026
f5bb55e
add onnxscript
xadupre Apr 21, 2026
63e622b
Merge branch 'xadupre/mbext' of https://github.com/microsoft/onnxrunt…
xadupre Apr 21, 2026
2c91a48
fix
xadupre Apr 21, 2026
5fdef15
fix: use os.path.join for cross-platform paths; remove unreachable co…
Copilot Apr 21, 2026
e918502
fix: cleanup path should use 'output' not 'output_dir' in get_dirs
Copilot Apr 21, 2026
c14803a
big refactoring
xadupre Apr 21, 2026
e6475b3
Merge branch 'xadupre/mbext' of https://github.com/microsoft/onnxrunt…
xadupre Apr 21, 2026
ccc8118
fixes
xadupre Apr 21, 2026
799963d
fix
xadupre Apr 21, 2026
60002b7
refactor: replace PvVersion class with packaging.version.Version
Copilot Apr 21, 2026
2194011
fix
xadupre Apr 21, 2026
7233274
style
xadupre Apr 21, 2026
b251a1e
style
xadupre Apr 21, 2026
f869a6d
fix import
xadupre Apr 21, 2026
a6a6dcb
crash
xadupre Apr 21, 2026
bc1c9ce
fix
xadupre Apr 21, 2026
347b8da
fix
xadupre Apr 21, 2026
b3d133b
donotremove
xadupre Apr 21, 2026
1ac7e60
fix
xadupre Apr 21, 2026
7918593
add missing patch
xadupre Apr 21, 2026
fb4010c
more tests
xadupre Apr 21, 2026
0ec2f77
fix
xadupre Apr 22, 2026
a194ffc
revert
xadupre Apr 22, 2026
423ec5f
better
xadupre Apr 22, 2026
15cc2b2
nemotron
xadupre Apr 22, 2026
076ad4a
remove a decorator
xadupre Apr 22, 2026
913dd2e
add more comments
xadupre Apr 22, 2026
34bf6c3
removing unused imports
xadupre Apr 22, 2026
7fd7752
remove unused imports
xadupre Apr 22, 2026
012ad6d
Potential fix for pull request finding 'CodeQL / Overwriting attribut…
xadupre Apr 23, 2026
4b25fbd
switch to 1.26
xadupre Apr 23, 2026
0ec2843
Merge branch 'xadupre/mbext' of https://github.com/microsoft/onnxrunt…
xadupre Apr 23, 2026
4ea3aaf
use transformers 5.6
xadupre Apr 23, 2026
9d103cb
mistral
xadupre Apr 23, 2026
84c7135
Merge branch 'main' of https://github.com/microsoft/onnxruntime-genai…
xadupre Apr 23, 2026
00117a1
fix name
xadupre Apr 23, 2026
aa6596d
Merge branch 'main' of https://github.com/microsoft/onnxruntime-genai…
xadupre Apr 24, 2026
132f1a1
Merge remote-tracking branch 'origin/main' into xadupre/mbext
Copilot Apr 28, 2026
79e7fdb
remove local_functions.py and use nightly ORT in fast tests workflow
Copilot Apr 28, 2026
d603515
use nightly onnxruntime-genai in fast tests workflow alongside nightl…
Copilot Apr 28, 2026
1a7cd8a
Merge branch 'main' into xadupre/mbext
xadupre Apr 29, 2026
18109bf
fix nemotronh
xadupre Apr 29, 2026
c0f11e6
Initialize q/k/v path
hanbitmyths May 1, 2026
aaa7684
Merge branch 'main' of https://github.com/microsoft/onnxruntime-genai…
xadupre May 4, 2026
76e6c7d
Merge branch 'main' of https://github.com/microsoft/onnxruntime-genai…
xadupre May 5, 2026
e4c3cad
Merge branch 'main' of https://github.com/microsoft/onnxruntime-genai…
xadupre May 6, 2026
273d3fb
Merge branch 'main' - add LFM2 support and resolve builder.py conflict
Copilot May 7, 2026
fae7874
Merge branch 'main' of https://github.com/microsoft/onnxruntime-genai…
xadupre May 12, 2026
8790306
Merge branch 'main' into xadupre/mbext - resolve requirements.txt con…
Copilot May 18, 2026
f977a04
Merge branch 'main' into xadupre/mbext - resolve base.py conflict
Copilot May 20, 2026
daab5e0
remove extra-index-url
xadupre May 20, 2026
2b694c6
Merge branch 'main' into xadupre/mbext - resolve builder.py conflict …
Copilot May 22, 2026
d7b7be8
Merge branch 'main' into xadupre/mbext - resolve conflicts (add Qwen3…
Copilot May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/modelbuilder_fast_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Fast Tests

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

permissions:
contents: read

jobs:
tests:
name: ci (${{ matrix.os }}, py${{ matrix.python-version }}, torch-${{ matrix.torch-version || 'stable' }}, transformers-${{ matrix.transformers-version || 'latest' }})
runs-on: ${{ matrix.os }}
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.13"]
transformers-version: ["4.57", "5.5"]
Comment thread
xadupre marked this conversation as resolved.
Outdated
torch-version: ["2.11"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
Comment thread
xadupre marked this conversation as resolved.
Outdated

# we install torch first to avoid downloading any CUDA dependency

- name: Install nightly pytorch (cpu)
if: matrix.torch-version == 'nightly'
run: pip install --pre --upgrade torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cpu

Comment thread
xadupre marked this conversation as resolved.
- name: Install pytorch 2.11 (Linux/Windows)
if: matrix.torch-version == '2.11' && matrix.os != 'macos-latest'
run: pip install torch==2.11.0+cpu torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

- name: Install dependencies
run: pip install torch "transformers==${{ matrix.transformers-version }}" tokenizers pandas openpyxl pytest huggingface_hub onnx onnxscript optree onnxruntime onnxruntime-genai peft py-cpuinfo pytest-cov pytest-subtests tabulate

- name: Run fast tests
run: DOCLEAN=1 PYTHONPATH=./src/python/py pytest test/python/models/fast -v --cov=modelbuilder --cov-report=xml --cov-report=term-missing
Comment thread
xadupre marked this conversation as resolved.
Outdated
Comment thread
xadupre marked this conversation as resolved.
Outdated

# Code to upload coverage report.
# - name: Upload coverage reports to Codecov
# uses: codecov/codecov-action@v5
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# flags: modelbuilder-fast-tests

- name: Upload stats artifact
if: always()
uses: actions/upload-artifact@v4
Comment thread
xadupre marked this conversation as resolved.
with:
name: stats-${{ matrix.os }}-py${{ matrix.python-version }}-torch${{ matrix.torch-version }}-transformers${{ matrix.transformers-version }}
path: stats/
if-no-files-found: ignore
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ src/csharp/AssemblyInfo.cs
*.csv
.idea
cache_dir
dump_models
stats
example-models
*.onnx
*.onnx.data
Expand Down
200 changes: 151 additions & 49 deletions src/python/py/models/builder.py

Large diffs are not rendered by default.

54 changes: 1 addition & 53 deletions src/python/py/models/builders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,5 @@
# Portions of this file consist of AI generated content.
# -------------------------------------------------------------------------
from .base import Model
from .chatglm import ChatGLMModel
from .ernie import ErnieModel
from .gemma import Gemma2Model, Gemma3Model, GemmaModel
from .gptoss import GPTOSSModel
from .granite import GraniteModel
from .internlm import InternLM2Model
from .llama import LlamaModel
from .mistral import MistralModel
from .nemotron import NemotronModel
from .olmo import OLMoModel
from .phi import (
Phi3MiniLongRoPEModel,
Phi3MiniModel,
Phi3MoELongRoPEModel,
Phi3SmallLongRoPEModel,
Phi3SmallModel,
Phi3VModel,
Phi4MMModel,
PhiModel,
)
from .qwen import Qwen3Model, Qwen3VLTextModel, Qwen25VLTextModel, Qwen35TextModel, QwenModel
from .smollm import SmolLM3Model
from .whisper import WhisperModel

__all__ = [
"ChatGLMModel",
"ErnieModel",
"GPTOSSModel",
"Gemma2Model",
"Gemma3Model",
"GemmaModel",
"GraniteModel",
"InternLM2Model",
"LlamaModel",
"MistralModel",
"Model",
"NemotronModel",
"OLMoModel",
"Phi3MiniLongRoPEModel",
"Phi3MiniModel",
"Phi3MoELongRoPEModel",
"Phi3SmallLongRoPEModel",
"Phi3SmallModel",
"Phi3VModel",
"Phi4MMModel",
"PhiModel",
"Qwen3Model",
"Qwen3VLTextModel",
"Qwen25VLTextModel",
"Qwen35TextModel",
"QwenModel",
"SmolLM3Model",
"WhisperModel",
]
__all__ = ["Model"]
676 changes: 415 additions & 261 deletions src/python/py/models/builders/base.py

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/python/py/models/builders/ernie.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ def __init__(self, config, io_dtype, onnx_dtype, ep, cache_dir, extra_options):
# Ernie uses interleaved rotary position embeddings.
self.rope_attrs["interleaved"] = 1

# Ernie4_5Config stores rope_theta inside a `rope_parameters` dict rather
# than as a top-level attribute. The base Model.__init__ falls back to
# 10000 when `config.rope_theta` is absent, so we patch the value here.
if not hasattr(config, "rope_theta") and hasattr(config, "rope_parameters"):
Comment thread
xadupre marked this conversation as resolved.
Outdated
rope_theta = config.rope_parameters.get("rope_theta")
if rope_theta is not None:
self.rope_attrs["theta"] = rope_theta

# Ernie uses a `compression_ratio` for its RoPE scaling.
# The original RoPE logic in ernie is: position_ids / compression_ratio,
# which is equivalent to scaling the frequencies (inv_freq) by 1 / compression_ratio.
Expand Down
43 changes: 38 additions & 5 deletions src/python/py/models/builders/gemma.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,25 @@ class Gemma3Model(Gemma2Model):
def __init__(self, config, io_dtype, onnx_dtype, ep, cache_dir, extra_options):
super().__init__(config, io_dtype, onnx_dtype, ep, cache_dir, extra_options)

self.rope_local_theta = config.rope_local_base_freq
# Remember whether this was originally a conditional-generation config
# (Gemma3ForConditionalGeneration) before builder.py overrides model_type
# to "gemma3_text". load_weights needs to know which HF class to instantiate.
self._original_architecture = config.architectures[0]

if hasattr(config, "rope_local_base_freq"):
# Older transformers: rope_local_base_freq and rope_theta are top-level fields
self.rope_local_theta = config.rope_local_base_freq
elif hasattr(config, "rope_parameters") and isinstance(config.rope_parameters, dict):
# Newer transformers (v5+): rope info lives in a nested rope_parameters dict
sliding_params = config.rope_parameters.get("sliding_attention", {})
self.rope_local_theta = sliding_params.get("rope_theta", 10000.0)
# Update the global theta (full-attention layers) which the base class
# could not infer because config.rope_theta is absent in this format.
full_params = config.rope_parameters.get("full_attention", {})
self.rope_attrs["theta"] = full_params.get("rope_theta", self.rope_attrs["theta"])
else:
# Default local RoPE theta matching Gemma3's original rope_local_base_freq
self.rope_local_theta = 10000.0
self.make_rotary_embedding_multi_cache()

def is_local(self, layer_id):
Expand All @@ -136,7 +154,7 @@ def make_attention_init(self):
super().make_attention_init()

def make_rotary_embedding_multi_cache(self):
self.cos_cache_global_name, self.sin_cache_global_name = "cos_cache_global", "sin_cache_global"
self.cos_cache_global_name, self.sin_cache_global_name = ("cos_cache_global", "sin_cache_global")
super().make_rotary_embedding_caches(
cos_cache_name=self.cos_cache_global_name, sin_cache_name=self.sin_cache_global_name
)
Expand All @@ -145,16 +163,31 @@ def make_rotary_embedding_multi_cache(self):
self.rope_attrs["create_caches"] = True
self.rope_attrs["theta"] = self.rope_local_theta

self.cos_cache_local_name, self.sin_cache_local_name = "cos_cache_local", "sin_cache_local"
self.cos_cache_local_name, self.sin_cache_local_name = ("cos_cache_local", "sin_cache_local")
super().make_rotary_embedding_caches(
cos_cache_name=self.cos_cache_local_name, sin_cache_name=self.sin_cache_local_name
)

def load_weights(self, input_path):
Comment thread
xadupre marked this conversation as resolved.
# Gemma3ForConditionalGeneration (VLM) does not accept the
# ``num_hidden_layers`` keyword argument that the base class would
# normally forward to ``AutoModelForCausalLM.from_pretrained``.
# Load it directly here instead.
if self._original_architecture == "Gemma3ForConditionalGeneration":
if self.quant_type is not None or input_path.endswith(".gguf"):
return super().load_weights(input_path)
from transformers import Gemma3ForConditionalGeneration as _HFModel

return _HFModel.from_pretrained(
self.model_name_or_path, cache_dir=self.cache_dir, token=self.hf_token, trust_remote_code=self.hf_remote
)
return super().load_weights(input_path)

def make_rotary_embedding_caches(self, **kwargs):
cos_cache_name = kwargs.get(
"cos_cache_name", self.cos_cache_global_name if self.window_size == -1 else self.cos_cache_local_name
"cos_cache_name", (self.cos_cache_global_name if self.window_size == -1 else self.cos_cache_local_name)
)
sin_cache_name = kwargs.get(
"sin_cache_name", self.sin_cache_global_name if self.window_size == -1 else self.sin_cache_local_name
"sin_cache_name", (self.sin_cache_global_name if self.window_size == -1 else self.sin_cache_local_name)
)
return super().make_rotary_embedding_caches(cos_cache_name=cos_cache_name, sin_cache_name=sin_cache_name)
38 changes: 28 additions & 10 deletions src/python/py/models/builders/gptoss.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,14 @@ def make_layernorm(self, layer_id, layernorm, skip, simple, location):
super().make_layernorm(layer_id, layernorm, skip, simple, location)

def make_rotary_embedding_caches_from_scratch(self):
inv_freq = self.rope_attrs["theta"] ** (torch.arange(0, self.head_size, 2, dtype=torch.float) / self.head_size)
inv_freq = 1.0 / (
Comment thread
xadupre marked this conversation as resolved.
Outdated
self.rope_attrs["theta"] ** (torch.arange(0, self.head_size, 2, dtype=torch.float) / self.head_size)
)
inv_freq = self.make_inv_freq_rescaled(inv_freq)

t = torch.arange(self.rope_attrs["cache_length"], dtype=torch.float32)
freqs = torch.einsum("i,j->ij", t, inv_freq)
cos_cache, sin_cache = freqs.cos() * self.rope_attrs["mscale"], freqs.sin() * self.rope_attrs["mscale"]
cos_cache, sin_cache = (freqs.cos() * self.rope_attrs["mscale"], freqs.sin() * self.rope_attrs["mscale"])
return cos_cache, sin_cache

def make_attention(self, layer_id, attention, root_input, **kwargs):
Expand Down Expand Up @@ -702,41 +704,57 @@ def has_quark_experts(self, experts):

def combine_quark_gate_up_biases_from_experts(self, experts):
"""Combine Quark gate_proj and up_proj biases from individual experts"""
assert(self.has_quark_experts(experts))
assert self.has_quark_experts(experts)
combined_biases = []

for expert_id in sorted(experts.keys()):
expert = experts[expert_id]

if expert.gate_up_proj.qweight is not None:
# Fused gate_up projection
gate_up_proj = expert.gate_up_proj.bias if hasattr(expert.gate_up_proj, 'bias') and expert.gate_up_proj.bias is not None else torch.zeros(expert.gate_up_proj.qweight.shape[0])
gate_up_proj = (
expert.gate_up_proj.bias
if hasattr(expert.gate_up_proj, "bias") and expert.gate_up_proj.bias is not None
else torch.zeros(expert.gate_up_proj.qweight.shape[0])
)
combined_biases.append(gate_up_proj)
else:
# Get biases from individual projections
gate_bias = expert.gate_proj.bias if hasattr(expert.gate_proj, 'bias') and expert.gate_proj.bias is not None else torch.zeros(expert.gate_proj.qweight.shape[0])
up_bias = expert.up_proj.bias if hasattr(expert.up_proj, 'bias') and expert.up_proj.bias is not None else torch.zeros(expert.up_proj.qweight.shape[0])
gate_bias = (
expert.gate_proj.bias
if hasattr(expert.gate_proj, "bias") and expert.gate_proj.bias is not None
else torch.zeros(expert.gate_proj.qweight.shape[0])
)
up_bias = (
expert.up_proj.bias
if hasattr(expert.up_proj, "bias") and expert.up_proj.bias is not None
else torch.zeros(expert.up_proj.qweight.shape[0])
)

# Combine gate and up biases (interleaved pattern: even=gate, odd=up)
gate_out_dim = gate_bias.shape[0]
up_out_dim = up_bias.shape[0]

combined_bias = torch.zeros(gate_out_dim + up_out_dim, dtype=gate_bias.dtype, device="cpu")
combined_bias[::2] = gate_bias # Even indices = gate
combined_bias[1::2] = up_bias # Odd indices = up
combined_bias[::2] = gate_bias # Even indices = gate
combined_bias[1::2] = up_bias # Odd indices = up

combined_biases.append(combined_bias)

return torch.stack(combined_biases, dim=0)

def combine_quark_down_biases_from_experts(self, experts):
"""Combine Quark down_proj biases from individual experts"""
assert(self.has_quark_experts(experts))
assert self.has_quark_experts(experts)
combined_biases = []

for expert_id in sorted(experts.keys()):
expert = experts[expert_id]
down_bias = expert.down_proj.bias if hasattr(expert.down_proj, 'bias') and expert.down_proj.bias is not None else torch.zeros(expert.down_proj.qweight.shape[0])
down_bias = (
expert.down_proj.bias
if hasattr(expert.down_proj, "bias") and expert.down_proj.bias is not None
else torch.zeros(expert.down_proj.qweight.shape[0])
)
combined_biases.append(down_bias)

return torch.stack(combined_biases, dim=0)
Loading
Loading