Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
421ef8e
Add component inspection API
xiaoyu-work Jun 25, 2026
e136bca
Merge branch 'main' into xiaoyu/inspect-components-api
xiaoyu-work Jul 6, 2026
ce6eaa2
update model
xiaoyu-work Jul 13, 2026
eb47589
Align component inspection with HF runtime paths
xiaoyu-work Jul 14, 2026
ec5dbe5
Fix CUDA 12 package selection in GPU CI
xiaoyu-work Jul 20, 2026
f9a703c
Cover CTC component inspection routing
xiaoyu-work Jul 21, 2026
c27f53f
Merge branch 'main' into xiaoyu/inspect-components-api
xiaoyu-work Jul 21, 2026
b7062d0
Make ONNX parity temp files Windows-safe
xiaoyu-work Jul 21, 2026
e598afa
Merge main and resolve component inspection conflicts
xiaoyu-work Jul 23, 2026
4fbfcc8
Align Qwen3-TTS component documentation
xiaoyu-work Jul 23, 2026
91a4906
Address component inspection review feedback
xiaoyu-work Jul 23, 2026
818b974
Resolve merge conflicts with main
Copilot Jul 30, 2026
95a9f5c
Fix merge conflict: keep model_roles and __init__ in Gemma4Task
Copilot Jul 30, 2026
665e50c
Release ORT sessions eagerly in OnnxModelSession.close
Copilot Jul 30, 2026
1ebbda6
Skip Fun-ASR golden case in CI until HF model_type metadata is available
Copilot Jul 30, 2026
bb90ac0
Fix lint import order in ort inference test
Copilot Jul 30, 2026
0d6ba72
Merge branch 'main' into xiaoyu/inspect-components-api
justinchuby Aug 4, 2026
715500a
Merge remote-tracking branch 'origin/main' into xiaoyu/inspect-compon…
justinchuby Aug 4, 2026
ed9bb62
fix(testdata): restore Fun-ASR coverage model ID
justinchuby Aug 4, 2026
35d0db4
fix(testdata): use upstream Fun-ASR golden source
justinchuby Aug 4, 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
10 changes: 6 additions & 4 deletions .github/workflows/golden_regen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,17 @@ jobs:
if: inputs.device != 'cuda'
run: pip install onnxruntime

- name: Install onnxruntime (GPU)
if: inputs.device == 'cuda'
run: pip install onnxruntime-gpu

- name: Install dependencies
run: |
pip install -r requirements/ci/requirements.txt
pip install -e '.[testing,transformers]'

- name: Install onnxruntime (GPU)
if: inputs.device == 'cuda'
run: |
# Install after project dependencies so the CPU ORT package cannot overwrite it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need to uninstall the onnxruntime cpu first?

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.

onnxruntime cpi is skipped in line 77 for cuda case

pip install --pre --no-deps --index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/ onnxruntime-gpu

- name: Reject trust_remote_code cases
run: |
# Safety: never auto-run models that require trust_remote_code
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/gpu_l4_golden_parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ jobs:
pip install -r requirements/ci/requirements.txt
pip install soundfile librosa ml_dtypes flatbuffers numpy packaging protobuf sympy coloredlogs
pip install -e '.[testing,transformers]'
pip install --pre --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/ort-cuda-12-nightly/pypi/simple/ onnxruntime-gpu onnxruntime-genai-cuda
# PyPI's ORT 1.27+ wheel requires CUDA 13, while these runners use CUDA 12.
pip install --pre --no-deps --index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/ onnxruntime-gpu

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

pip install --no-deps onnxruntime-genai-cuda

- name: Check for golden test data
id: check_golden
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/gpu_l5_generation_e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ jobs:
pip install -r requirements/ci/requirements.txt
pip install soundfile librosa ml_dtypes flatbuffers numpy packaging protobuf sympy coloredlogs
pip install -e '.[testing,transformers]'
pip install --pre --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/ort-cuda-12-nightly/pypi/simple/ onnxruntime-gpu onnxruntime-genai-cuda
# PyPI's ORT 1.27+ wheel requires CUDA 13, while these runners use CUDA 12.
pip install --pre --no-deps --index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/ onnxruntime-gpu
pip install --no-deps onnxruntime-genai-cuda

- name: Run L5 generation E2E tests
env:
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,9 @@ jobs:
pip install -r requirements/ci/requirements.txt
pip install soundfile librosa ml_dtypes flatbuffers numpy packaging protobuf sympy coloredlogs
pip install -e '.[testing]'
pip install --pre --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/ort-cuda-12-nightly/pypi/simple/ "onnxruntime-gpu<1.27" onnxruntime-genai-cuda
# PyPI's ORT 1.27+ wheel requires CUDA 13, while these runners use CUDA 12.
pip install --pre --no-deps --index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/ onnxruntime-gpu
pip install --no-deps onnxruntime-genai-cuda
- name: Run fast integration tests
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/validation_examples_gpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ jobs:
pip install -r requirements/ci/requirements.txt
pip install -e '.[testing,transformers]'
pip install soundfile librosa ml_dtypes flatbuffers numpy packaging protobuf sympy coloredlogs
pip install --pre --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/ort-cuda-12-nightly/pypi/simple/ onnxruntime-gpu onnxruntime-genai-cuda
# PyPI's ORT 1.27+ wheel requires CUDA 13, while these runners use CUDA 12.
pip install --pre --no-deps --index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/ onnxruntime-gpu
pip install --no-deps onnxruntime-genai-cuda

- name: Run ${{ matrix.name }}
env:
Expand Down
3 changes: 3 additions & 0 deletions src/mobius/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"BaseModelConfig",
"CausalLMConfig",
"CausalLMTask",
"ComponentInfo",
"DepthAnythingConfig",
"EncoderConfig",
"EpCapabilities",
Expand Down Expand Up @@ -42,6 +43,7 @@
"ep_registry",
"get_build_dtype",
"get_ep",
"inspect_components",
"models",
"optimize_model",
"register_ep",
Expand Down Expand Up @@ -81,6 +83,7 @@
from mobius._constants import OPSET_VERSION
from mobius._diffusers_builder import build_diffusers_pipeline
from mobius._execution_providers import EpCapabilities, ep_registry, get_ep, register_ep
from mobius._inspect import ComponentInfo, inspect_components
from mobius._model_package import ModelPackage
from mobius._optimizations import optimize_model
from mobius._registry import (
Expand Down
173 changes: 173 additions & 0 deletions src/mobius/_inspect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

"""Inspect a model's component layout without building it.

``inspect_components`` reports the components mobius would produce for a model
(their package keys and optimization roles) **without** constructing graphs or
loading weights. External tools such as Olive use this to plan per-component
work — e.g. optimizing a VLM's ``decoder`` differently from its
``vision_encoder`` — before calling :func:`mobius.build`.

The component names returned here are the same keys :func:`mobius.build`
produces in its :class:`~mobius._model_package.ModelPackage` (and therefore the
subfolder names ``ModelPackage.save`` writes for multi-component models).
"""

from __future__ import annotations

__all__ = ["ComponentInfo", "inspect_components"]

import dataclasses
import logging

logger = logging.getLogger(__name__)
Comment on lines +21 to +24


@dataclasses.dataclass(frozen=True)
class ComponentInfo:
"""A single component of a model.

Attributes:
name: Component name. This is the ``ModelPackage`` key mobius produces
(and the subfolder name a multi-component export is saved under).
role: Optimization role of the component, e.g. ``"decoder"``,
``"encoder"``, or ``"embedding"``. Mobius uses this to gate fusion
passes (only ``"decoder"`` receives GQA / QKV-packing). It is the
value declared in the task's ``model_roles``.
source_paths: Runtime ``named_modules()`` paths that make up this
component inside the full HuggingFace model. These are not
checkpoint/state-dict key prefixes. A single component may map to
multiple disjoint sub-modules (e.g. a decoder assembled from
``model.layers``, ``model.norm`` and ``lm_head``), so this is a
tuple. Empty when the component is the whole model or the layout is
unknown. Tools such as Olive use these to optimize a submodule in
place before exporting the full model.
"""

name: str
role: str
source_paths: tuple[str, ...] = ()


def _resolve_task_model_type_and_config(
model_id: str, task, trust_remote_code: bool
) -> tuple[str, str | None, object | None]:
"""Resolve the mobius task name for a model id without building it.

Mirrors the model_type/task resolution in :func:`mobius.build`, limited to
what is needed to pick a task and inspect class-level component metadata
(no module construction, no weight loading).
"""
import transformers

from mobius._config_resolver import _default_task_for_model, _try_load_config_json
from mobius._registry import _detect_fallback_registration, registry

try:
hf_config = transformers.AutoConfig.from_pretrained(
model_id, trust_remote_code=trust_remote_code
)
except (ValueError, KeyError, OSError):
hf_config = _try_load_config_json(model_id)
if hf_config is None:
# An explicit task is enough to report component names and roles,
# but source paths require a model type and therefore stay empty.
if task is not None:
return task, None, None
raise ValueError(
f"Could not load a HuggingFace config for {model_id!r}. inspect_components supports "
"transformers/registry models; diffusers pipelines are not supported."
) from None
Comment on lines +78 to +81

model_type = hf_config.model_type

# model_type adjustments that affect task selection (subset of build()):
# Qwen3.5-MoE ships the same model_type for text-only and VL checkpoints.
if model_type == "qwen3_5_moe" and getattr(hf_config, "vision_config", None) is not None:
model_type = "qwen3_5_moe_vl"
# wav2vec2/hubert/wavlm with a CTC head map to the mms (CTC) registration.
if model_type in ("wav2vec2", "hubert", "wavlm"):
architectures = getattr(hf_config, "architectures", None) or []
if any("ForCTC" in arch for arch in architectures):
model_type = "mms"

if task is not None:
return task, model_type, hf_config
if model_type in registry:
return _default_task_for_model(model_type), model_type, hf_config

fallback = _detect_fallback_registration(hf_config)
if fallback is not None and fallback.task is not None:
return fallback.task, model_type, hf_config
return _default_task_for_model(model_type), model_type, hf_config


def _get_hf_component_sources(
module_class: type,
model_type: str,
hf_config: object,
) -> dict[str, tuple[str, ...]]:
"""Read runtime HuggingFace component paths from a registered model class."""
resolver = getattr(module_class, "get_hf_component_sources", None)
if resolver is not None:
return resolver(model_type=model_type, hf_config=hf_config)
return getattr(module_class, "HF_COMPONENT_SOURCES", {})


def inspect_components(
model_id: str,
task=None,
trust_remote_code: bool = False,
) -> list[ComponentInfo]:
"""Return the components mobius would produce for a model.

Args:
model_id: HuggingFace model id or local path.
task: Optional task name (e.g. ``"vision-language"``) or
:class:`~mobius.tasks.ModelTask` instance. When ``None``, the task
is auto-detected from the model type.
trust_remote_code: Whether to trust remote code when loading the
HuggingFace config.

Returns:
A list of :class:`ComponentInfo`. Single-component models (most LLMs)
return a single entry named ``"model"``; multi-component models (VLMs,
encoder-decoders, speech models) return one entry per component.

Raises:
ValueError: If a config cannot be resolved for ``model_id`` (e.g. a
diffusers pipeline, which is not supported).
"""
from mobius._registry import registry
from mobius.tasks import get_task

resolved_task, model_type, hf_config = _resolve_task_model_type_and_config(
model_id, task, trust_remote_code
)
task_obj = get_task(resolved_task)
roles = task_obj.model_roles or {}

Comment on lines +145 to +150
# Runtime HF paths are owned by the registered model class. Most classes
# declare a fixed ``HF_COMPONENT_SOURCES`` mapping; classes shared by
# several HF layouts can resolve paths from the already-loaded config.
component_sources: dict[str, tuple[str, ...]] = {}
if model_type is not None and hf_config is not None and model_type in registry:
module_class = registry.get(model_type)
component_sources = _get_hf_component_sources(module_class, model_type, hf_config)

components = [
ComponentInfo(
name=name,
role=role,
source_paths=tuple(component_sources.get(name, ())),
)
for name, role in roles.items()
]
logger.debug(
"inspect_components(%s): task=%s components=%s",
model_id,
resolved_task,
[c.name for c in components],
)
return components
Loading
Loading