Skip to content

Commit af93b97

Browse files
justinchubyCopilot
andcommitted
Replace build-nemo subcommand with .nemo auto-detection in build
Drop the dedicated `mobius build-nemo` subcommand (~130 lines: handler + 8-arg subparser) in favor of auto-detecting `.nemo` inputs in the existing `build` command, mirroring how diffusers pipelines are auto-detected. When `--model` ends with `.nemo` (local file or `owner/repo:model.nemo` HF ref), `build` routes to `build_from_nemo` and reuses the standard `--dtype`/`--ep`/ `--external-data` args and `_save_package` save logic. The GenAI `nemotron_speech` bundle path (formerly `--genai`/`--chunk-seconds`/ `--no-vad`) is not part of the core CLI; it remains available via the Python API (`write_genai_bundle`) and the example script. Add test_build_dot_nemo_model_routes_to_nemo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Justin Chu <11205048+justinchuby@users.noreply.github.com>
1 parent b35bbe9 commit af93b97

2 files changed

Lines changed: 31 additions & 127 deletions

File tree

src/mobius/__main__.py

Lines changed: 15 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,21 @@ def _cmd_build(args: argparse.Namespace) -> None:
161161
_save_package(pkg, output_dir, args, optimize, component_filter)
162162
return
163163

164+
# Auto-detect NeMo .nemo archives (local file or HF ref like
165+
# 'owner/repo:model.nemo'). Routes to the NeMo import path; reuses the
166+
# standard build args (--dtype, --ep, --external-data) and save logic.
167+
if args.model and args.model.endswith(".nemo"):
168+
from mobius.integrations.nemo import build_from_nemo
169+
170+
print(f"Detected NeMo archive: {args.model}")
171+
pkg = build_from_nemo(
172+
args.model,
173+
dtype=dtype_override,
174+
execution_provider=execution_provider,
175+
)
176+
_save_package(pkg, output_dir, args, optimize, component_filter)
177+
return
178+
164179
# Build from HuggingFace model ID or local config
165180
if args.config:
166181
import transformers
@@ -340,54 +355,6 @@ def _cmd_build_gguf(args: argparse.Namespace) -> None:
340355
print(f"Saved {name} to {path}")
341356

342357

343-
def _cmd_build_nemo(args: argparse.Namespace) -> None:
344-
"""Execute the 'build-nemo' subcommand."""
345-
from mobius.integrations.nemo import build_from_nemo
346-
347-
nemo_path = args.nemo_path
348-
# Local file: default output beside it; HF repo ref: use the repo basename.
349-
if os.path.splitext(nemo_path)[1] == ".nemo" and os.path.exists(nemo_path):
350-
default_output = os.path.splitext(nemo_path)[0] + "_onnx"
351-
else:
352-
default_output = nemo_path.split(":", 1)[0].split("/")[-1] + "_onnx"
353-
output_dir = args.output or default_output
354-
os.makedirs(output_dir, exist_ok=True)
355-
356-
pkg = build_from_nemo(
357-
nemo_path,
358-
dtype=args.dtype,
359-
execution_provider=args.execution_provider,
360-
revision=getattr(args, "revision", None),
361-
)
362-
363-
if getattr(args, "genai", False):
364-
from mobius.integrations.nemo import write_genai_bundle
365-
from mobius.integrations.nemo._reader import NeMoArchive
366-
367-
archive = NeMoArchive(nemo_path, revision=getattr(args, "revision", None))
368-
out = write_genai_bundle(
369-
pkg,
370-
archive,
371-
output_dir,
372-
chunk_seconds=args.chunk_seconds,
373-
include_vad=not args.no_vad,
374-
)
375-
print(f"Saved ONNX Runtime GenAI nemotron_speech bundle to {out}")
376-
return
377-
378-
pkg.save(
379-
output_dir,
380-
external_data=args.external_data,
381-
)
382-
for name in pkg:
383-
use_subfolders = len(pkg) > 1
384-
if use_subfolders:
385-
path = os.path.join(output_dir, name, "model.onnx")
386-
else:
387-
path = os.path.join(output_dir, "model.onnx")
388-
print(f"Saved {name} to {path}")
389-
390-
391358
def _cmd_info(args: argparse.Namespace) -> None:
392359
"""Execute the 'info' subcommand."""
393360
from mobius._diffusers_builder import (
@@ -621,85 +588,6 @@ def main(argv: list[str] | None = None) -> None:
621588
)
622589
gguf_parser.set_defaults(func=_cmd_build_gguf)
623590

624-
# --- build-nemo ---
625-
nemo_parser = subparsers.add_parser(
626-
"build-nemo", help="Build ONNX model(s) from a NeMo .nemo archive."
627-
)
628-
nemo_parser.add_argument(
629-
"nemo_path",
630-
help=(
631-
"Path to a local .nemo file, or a HuggingFace Hub reference "
632-
"('owner/repo' or 'owner/repo:filename.nemo')."
633-
),
634-
)
635-
nemo_parser.add_argument(
636-
"--output",
637-
"-o",
638-
default=None,
639-
metavar="DIR",
640-
help="Output directory (default: <nemo_stem>_onnx/).",
641-
)
642-
nemo_parser.add_argument(
643-
"--dtype",
644-
choices=sorted(DTYPE_MAP),
645-
default=None,
646-
help=(
647-
"Target dtype for model weights (f32/f16/bf16). Note: the GenAI "
648-
"bundle (--genai) is float32 only, per the nemotron_speech runtime."
649-
),
650-
)
651-
nemo_parser.add_argument(
652-
"--revision",
653-
default=None,
654-
metavar="REV",
655-
help=(
656-
"HuggingFace Hub revision (branch, tag, or commit SHA) to pin "
657-
"downloads for reproducible builds. Ignored for local .nemo paths."
658-
),
659-
)
660-
nemo_parser.add_argument(
661-
"--external-data",
662-
choices=["onnx", "safetensors"],
663-
default="onnx",
664-
help="External data format (default: onnx).",
665-
)
666-
nemo_parser.add_argument(
667-
"--ep",
668-
"--execution-provider",
669-
dest="execution_provider",
670-
default="default",
671-
metavar="EP",
672-
help=(
673-
"Target execution provider for EP-aware graph optimisations. "
674-
"Defaults to 'default' (portable ONNX, no vendor fusions)."
675-
),
676-
)
677-
nemo_parser.add_argument(
678-
"--genai",
679-
action="store_true",
680-
help=(
681-
"Write an ONNX Runtime GenAI 'nemotron_speech' bundle (flat "
682-
"encoder/decoder/joint ONNX + genai_config.json, "
683-
"audio_processor_config.json and tokenizer) instead of the default "
684-
"subfolder layout. FastConformer-RNNT only."
685-
),
686-
)
687-
nemo_parser.add_argument(
688-
"--chunk-seconds",
689-
type=float,
690-
default=1.12,
691-
help=(
692-
"Streaming chunk length in seconds for the GenAI bundle "
693-
"(default: 1.12, the model's native att_context [70, 13] chunk)."
694-
),
695-
)
696-
nemo_parser.add_argument(
697-
"--no-vad",
698-
action="store_true",
699-
help="Skip the Silero VAD download/config block in the GenAI bundle.",
700-
)
701-
nemo_parser.set_defaults(func=_cmd_build_nemo)
702-
703591
# --- list ---
704592
list_parser = subparsers.add_parser(
705593
"list", help="List supported models, tasks, dtypes, or EPs."

tests/cli_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,22 @@ def test_no_runtime_does_not_call_write_ort_genai_config(self):
213213

214214
mock_export.assert_not_called()
215215

216+
def test_build_dot_nemo_model_routes_to_nemo(self):
217+
"""A ``.nemo`` --model argument is auto-detected and routed to NeMo."""
218+
with (
219+
tempfile.TemporaryDirectory() as tmpdir,
220+
mock.patch(
221+
"mobius.integrations.nemo.build_from_nemo",
222+
return_value=mock.MagicMock(),
223+
) as mock_build_nemo,
224+
mock.patch("mobius.__main__._save_package") as mock_save,
225+
):
226+
main(["build", "--model", "/some/model.nemo", tmpdir])
227+
228+
mock_build_nemo.assert_called_once()
229+
assert mock_build_nemo.call_args.args[0] == "/some/model.nemo"
230+
mock_save.assert_called_once()
231+
216232
def test_invalid_runtime_value_errors(self):
217233
"""An unrecognised --runtime value causes argparse to exit with an error."""
218234
with tempfile.TemporaryDirectory() as tmpdir, pytest.raises(SystemExit):

0 commit comments

Comments
 (0)