Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion docs/model-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,13 @@ Also registered with `T5ForConditionalGeneration` (task: `seq2seq`):
|---|---|---|---|
| `qwen3_tts` | `Qwen3TTSForConditionalGeneration` | `tts` | — |
| `qwen3_tts_tokenizer_12hz` | `Qwen3TTSTokenizerV2Model` | `codec` | — |
| `vibevoice` | `VibeVoiceForConditionalGeneration` | `vibevoice-tts` | `vibevoice/VibeVoice-1.5B-hf` |
| `vibevoice` | `VibeVoiceForConditionalGeneration` | `vibevoice-tts` | `microsoft/VibeVoice-1.5B` |

`microsoft/VibeVoice-1.5B@c00898d` is tested with its official weights. Its legacy
release has no Transformers-native tokenizer or processor assets, so Mobius pins only
those executable sidecars to `vibevoice/VibeVoice-1.5B-hf@edc39f8`; it never substitutes
mirror weights. The Realtime and ASR entries in the Microsoft VibeVoice collection are
explicitly rejected until their distinct streaming or ASR architectures are implemented.

### Audio Feature Extraction

Expand Down
49 changes: 42 additions & 7 deletions src/mobius/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,14 @@ def _resolve_static_cache_task(model_type: str) -> ModelTask:
from mobius.models.reuse import REUSE_REVISION

revision = REUSE_REVISION
if args.model:
from mobius.models.vibevoice import resolve_vibevoice_sources

vibevoice_sources = resolve_vibevoice_sources(args.model, revision)
if vibevoice_sources is not None:
# Pin the early Diffusers probe to the official checkpoint. The
# builder separately resolves its modern executable sidecars.
revision = vibevoice_sources.weight_revision
output_dir = args.output_dir
os.makedirs(output_dir, exist_ok=True)
dtype_override = resolve_dtype(args.dtype)
Expand Down Expand Up @@ -616,6 +624,30 @@ def _resolve_static_cache_task(model_type: str) -> ModelTask:
_save_package(pkg, output_dir, args, optimize, component_filter)


def _runtime_asset_source(
pkg,
source: str | None,
explicit_revision: str | None,
) -> tuple[str | None, str | None]:
"""Select a pinned processor source without changing checkpoint provenance."""
processor_sources = {
processor_source
for model in pkg.values()
if (
processor_source := getattr(model, "metadata_props", {}).get(
"mobius.processor_source"
)
)
is not None
}
if len(processor_sources) == 1:
processor_source = processor_sources.pop()
model_id, separator, revision = processor_source.rpartition("@")
if separator and model_id and revision:
return model_id, revision
return source, _runtime_source_revision(pkg, explicit_revision)


def _runtime_source_revision(pkg, explicit_revision: str | None) -> str | None:
"""Recover the effective build revision for runtime asset downloads."""
if explicit_revision is not None:
Expand Down Expand Up @@ -675,15 +707,15 @@ def _save_package(
if runtime == "ort-genai":
from mobius.integrations.ort_genai import write_ort_genai_config

hf_model_id = getattr(args, "model", None)
hf_model_id, runtime_revision = _runtime_asset_source(
pkg,
getattr(args, "model", None),
getattr(args, "revision", None),
)
ep = getattr(args, "execution_provider", "cpu")
# When --config (local dir) is used instead of --model, copy tokenizer
# files from the local directory rather than downloading from HF.
local_config_dir = getattr(args, "config", None)
runtime_revision = _runtime_source_revision(
pkg,
getattr(args, "revision", None),
)
artifacts = write_ort_genai_config(
pkg,
output_dir,
Expand All @@ -705,8 +737,11 @@ def _save_package(
)

config = getattr(pkg, "config", None)
source = getattr(args, "config", None) or getattr(args, "model", None)
revision = _runtime_source_revision(pkg, getattr(args, "revision", None))
source, revision = _runtime_asset_source(
pkg,
getattr(args, "config", None) or getattr(args, "model", None),
getattr(args, "revision", None),
)
if is_native_vlm_package(pkg):
try:
artifacts = write_native_vlm_package_metadata(
Expand Down
34 changes: 25 additions & 9 deletions src/mobius/integrations/transformers/_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,18 @@ def build_transformers_model(
_config_from_hf,
_default_task_for_model,
)
from mobius.models.vibevoice import resolve_vibevoice_sources

detection_revision = revision
if model_id == "vibevoice/VibeVoice-1.5B-hf" and detection_revision is None:
from mobius.models.vibevoice import VIBEVOICE_REVISION

# The native conversion is the executable source of truth. Pin the
# first config probe and every later processor/weight call together.
revision = VIBEVOICE_REVISION
detection_revision = VIBEVOICE_REVISION
vibevoice_sources = resolve_vibevoice_sources(model_id, revision)
config_model_id = model_id
if vibevoice_sources is not None:
# The original official checkpoint has legacy metadata and no tokenizer
# assets. Resolve only executable inputs from the pinned conversion;
# model_id remains the graph identity and official weights stay official.
revision = vibevoice_sources.weight_revision
config_model_id = vibevoice_sources.config_model_id
detection_revision = vibevoice_sources.config_revision
if model_id == "nvidia/RE-USE" and detection_revision is None:
# Pin the very first AutoConfig/raw-JSON probe, not only the later
# bespoke loader. Otherwise mutable Hub main could change dispatch
Expand All @@ -318,7 +321,7 @@ def build_transformers_model(
detection_revision = REUSE_REVISION

hf_config, loaded_from_raw_json = _load_transformers_config(
model_id,
config_model_id,
revision=detection_revision,
trust_remote_code=trust_remote_code,
)
Expand Down Expand Up @@ -527,6 +530,13 @@ def build_transformers_model(
model.graph.name = f"{model_id}/{name}"
if model_type in _QWEN4_MODEL_TYPES | {"vibevoice"}:
model.metadata_props["mobius.source_revision"] = revision or "unpinned"
if vibevoice_sources is not None:
model.metadata_props["mobius.executable_source"] = (
f"{vibevoice_sources.config_model_id}@{vibevoice_sources.config_revision}"
)
model.metadata_props["mobius.processor_source"] = (
f"{vibevoice_sources.processor_model_id}@{vibevoice_sources.processor_revision}"
)

if load_weights:
_reject_unsupported_affine_qwen4(model_type, config)
Expand Down Expand Up @@ -586,7 +596,13 @@ def build_transformers_model(
else:
state_dict = _download_weights(model_id, revision=revision)
if hasattr(model_module, "preprocess_weights"):
state_dict = model_module.preprocess_weights(state_dict)
if vibevoice_sources is not None:
state_dict = model_module.preprocess_weights(
state_dict,
checkpoint_layout=vibevoice_sources.weight_layout,
)
else:
state_dict = model_module.preprocess_weights(state_dict)
state_dict = preprocess_component_quantized_state_dict(
state_dict,
model_module,
Expand Down
102 changes: 99 additions & 3 deletions src/mobius/integrations/transformers/_builder_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import onnx_ir as ir
import pytest
import torch
from onnxscript import nn

from mobius._configs import QuantizationConfig, QuantizationOverride
Expand Down Expand Up @@ -241,7 +242,11 @@ def stop_after_config(model_id, **kwargs):


def test_vibevoice_none_revision_pins_first_config_probe(monkeypatch) -> None:
from mobius.models.vibevoice import VIBEVOICE_MODEL_ID, VIBEVOICE_REVISION
from mobius.models.vibevoice import (
VIBEVOICE_EXECUTABLE_MODEL_ID,
VIBEVOICE_EXECUTABLE_REVISION,
VIBEVOICE_MODEL_ID,
)

calls = []

Expand All @@ -263,9 +268,9 @@ def stop_after_config(model_id, **kwargs):

assert calls == [
(
VIBEVOICE_MODEL_ID,
VIBEVOICE_EXECUTABLE_MODEL_ID,
{
"revision": VIBEVOICE_REVISION,
"revision": VIBEVOICE_EXECUTABLE_REVISION,
"trust_remote_code": False,
},
)
Expand Down Expand Up @@ -1009,6 +1014,97 @@ def fake_build_diffusers(*args, **kwargs):
]


def test_official_vibevoice_uses_pinned_sidecars_and_official_weights(monkeypatch) -> None:
"""The official legacy config must never replace the requested checkpoint."""
from mobius.models.vibevoice import (
VIBEVOICE_EXECUTABLE_MODEL_ID,
VIBEVOICE_EXECUTABLE_REVISION,
VIBEVOICE_MODEL_ID,
VIBEVOICE_REVISION,
)

parent = SimpleNamespace(model_type="vibevoice", architectures=[])
package = ModelPackage(
{
"audio_encoder": ir.Model(
ir.Graph([], [], nodes=[], name="audio_encoder"), ir_version=11
)
}
)
calls = []

class OfficialVibeVoiceModule(_DummyModule):
def preprocess_weights(self, state_dict, *, checkpoint_layout):
calls.append(("preprocess", state_dict, checkpoint_layout))
return state_dict

monkeypatch.setattr(
transformers_builder,
"_load_transformers_config",
lambda *args, **kwargs: calls.append(("config", args, kwargs)) or (parent, False),
)
monkeypatch.setattr(
transformers_builder,
"_select_primary_config",
lambda value: (value, value, "vibevoice"),
)
monkeypatch.setattr(
transformers_builder,
"_resolve_module_class",
lambda *args, **kwargs: (OfficialVibeVoiceModule, "vibevoice-tts", "vibevoice"),
)
monkeypatch.setattr(
_config_resolver,
"_config_from_hf",
lambda *args, **kwargs: make_config(model_type="vibevoice"),
)
monkeypatch.setattr(
transformers_builder, "build_from_module", lambda *args, **kwargs: package
)
state_dict = {"weight": torch.ones(())}
monkeypatch.setattr(
transformers_builder,
"_download_weights",
lambda *args, **kwargs: calls.append(("weights", args, kwargs)) or state_dict,
)

result = transformers_builder.build_transformers_model(VIBEVOICE_MODEL_ID)

assert result is package
assert calls == [
(
"config",
(VIBEVOICE_EXECUTABLE_MODEL_ID,),
{"revision": VIBEVOICE_EXECUTABLE_REVISION, "trust_remote_code": False},
),
("weights", (VIBEVOICE_MODEL_ID,), {"revision": VIBEVOICE_REVISION}),
("preprocess", state_dict, "official"),
]
assert package["audio_encoder"].metadata_props == {
"mobius.source_revision": VIBEVOICE_REVISION,
"mobius.executable_source": f"{VIBEVOICE_EXECUTABLE_MODEL_ID}@{VIBEVOICE_EXECUTABLE_REVISION}",
"mobius.processor_source": f"{VIBEVOICE_EXECUTABLE_MODEL_ID}@{VIBEVOICE_EXECUTABLE_REVISION}",
}


@pytest.mark.parametrize(
"model_id",
[
"microsoft/VibeVoice-Realtime-0.5B",
"microsoft/VibeVoice-ASR",
"microsoft/VibeVoice-ASR-Streaming-7B",
"microsoft/VibeVoice-ASR-Streaming-1.5B",
"microsoft/VibeVoice-ASR-BitNet",
"microsoft/VibeVoice-ASR-HF",
"microsoft/VibeVoice-AcousticTokenizer",
"Microsoft/VibeVoice-ASR",
],
)
def test_unimplemented_official_vibevoice_collection_ids_fail_closed(model_id):
with pytest.raises(NotImplementedError, match="unsupported"):
transformers_builder.build_transformers_model(model_id, load_weights=False)


def test_glm_full_attention_rejects_diffusers_dispatch(monkeypatch) -> None:
"""``--glm-full-attention`` must raise on the diffusers-dispatch branch.

Expand Down
Loading
Loading