Skip to content

Add MMS (Massively Multilingual Speech) CTC ASR model - #96

Merged
justinchuby merged 9 commits into
mainfrom
justinchu/mms-models
Jun 2, 2026
Merged

Add MMS (Massively Multilingual Speech) CTC ASR model#96
justinchuby merged 9 commits into
mainfrom
justinchu/mms-models

Conversation

@justinchuby

Copy link
Copy Markdown
Member

Summary

Adds support for facebook/mms-300m and facebook/mms-1b-all — Meta's Massively Multilingual Speech models covering 1,100+ languages.

Architecture

  • Feature extractor: 7-layer causal CNN (reuses existing Wav2Vec2Model)
  • Encoder: transformer encoder with positional convolutions
  • Language adapter (add_adapter=True): N strided Conv1d + GLU layers for language-specific adaptation
  • CTC head: linear projection → per-frame logits for connectionist temporal classification

Implementation

  • src/mobius/models/wav2vec2_ctc.pyWav2Vec2ForCTCModel (extends Wav2Vec2Model), _AdapterLayer, _Adapter
  • src/mobius/tasks/_ctc_asr.pyCTCAsrTask (single-model, input_values + attention_masklogits)
  • src/mobius/_configs.pyMMSConfig (subclass of ArchitectureConfig with adapter fields)
  • Registry: mmsWav2Vec2ForCTCModel / ctc-asr

Weight name alignment

Uses bare nn.Parameter for adapter conv weights (matching the wav2vec2 feature extractor pattern) with preprocess_weights renames for HF's nn.Conv1d layout.

Testing

6 new tests in TestBuildMMSGraph — all pass. Full suite: 2676 passed.

@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 133f0e969cb590

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 60 60 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 66 66 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 107 107 +0.0%
gpt2 model_size_bytes 388 KB 388 KB +0.0%
gpt2 num_nodes 53 53 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 61 61 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 58 58 +0.0%
mamba (ssm-text-generation) model_size_bytes 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 98 98 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 59 59 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 56 56 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 61 61 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 58 58 +0.0%
qwen3_5_moe (hybrid-text-generation) model_size_bytes 506 KB 506 KB +0.0%
qwen3_5_moe (hybrid-text-generation) num_nodes 275 275 +0.0%
qwen3_5_text (hybrid-text-generation) model_size_bytes 458 KB 458 KB +0.0%
qwen3_5_text (hybrid-text-generation) num_nodes 129 129 +0.0%
qwen3_5_vl (hybrid-qwen-vl) model_size_bytes 977 KB 977 KB +0.0%
qwen3_5_vl (hybrid-qwen-vl) num_nodes 413 413 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 166 166 +0.0%
whisper (speech-to-text) model_size_bytes 1008 KB 1008 KB +0.0%
whisper (speech-to-text) num_nodes 128 128 +0.0%

No performance regressions.

@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 133f0e969cb590

Model Sub-model Changes Status
bert (feature-extraction) model 0
falcon model 0
gemma2 model 0
gemma4 (gemma4) decoder 0
gemma4 (gemma4) embedding 0
gemma4 (gemma4) vision_encoder 0
gemma4_text model 0
gpt2 model 0
llama model 0
llama (static-cache) model 0
mamba (ssm-text-generation) model 0
phi3 model 0
phi3 (static-cache) model 0
qwen model 0
qwen (static-cache) model 0
qwen2 model 0
qwen2 (static-cache) model 0
qwen2_moe model 0
qwen2_moe (static-cache) model 0
qwen3 model 0
qwen3 (static-cache) model 0
qwen3_5_moe (hybrid-text-generation) model 0
qwen3_5_text (hybrid-text-generation) model 0
qwen3_5_vl (hybrid-qwen-vl) decoder 0
qwen3_5_vl (hybrid-qwen-vl) embedding 0
qwen3_5_vl (hybrid-qwen-vl) vision_encoder 0
qwen3_moe model 0
qwen3_moe (static-cache) model 0
qwen3_next (hybrid-text-generation) model 0
t5 (seq2seq) decoder 0
t5 (seq2seq) encoder 0
whisper (speech-to-text) decoder 0
whisper (speech-to-text) encoder 0

No architecture changes detected.


Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed)

@justinchuby justinchuby self-assigned this Apr 2, 2026
@justinchuby
justinchuby marked this pull request as draft April 2, 2026 15:03
Comment thread examples/mms.py Fixed

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@justinchuby justinchuby added the ai Created by an AI agent label Apr 2, 2026
@justinchuby
justinchuby requested a review from Copilot June 2, 2026 20:18

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 98 out of 98 changed files in this pull request and generated 6 comments.

Comment thread src/mobius/models/wav2vec2_ctc.py Outdated
Comment thread src/mobius/_configs.py Outdated
Comment thread src/mobius/tasks/_audio_to_audio.py Outdated
Comment thread src/mobius/tasks/_ctc_asr.py
Comment thread examples/mms.py
Comment thread src/mobius/_registry.py Outdated
justinchuby and others added 3 commits June 2, 2026 20:30
Implements facebook/mms-300m and facebook/mms-1b-all as ONNX models
using the mobius 4-layer stack.

Architecture:
- Wav2Vec2ForCTCModel: feature extractor (7-layer CNN) + transformer
  encoder + optional language adapter + CTC projection head
- CTCAsrTask: single-model task (input_values + attention_mask → logits)
- MMSConfig: ArchitectureConfig subclass with adapter fields (add_adapter,
  output_hidden_size, adapter_kernel_size, adapter_stride, num_adapter_layers)
- _AdapterLayer: strided Conv1d → GLU using bare nn.Parameter (matches
  onnxscript path-resolution semantics) with preprocess_weights renames
  for HF's .conv.weight / .conv.bias → .conv / .conv_bias

Weight name alignment:
- HF wav2vec2.feature_extractor.conv_layers.N.conv.weight → feature_extractor.conv_layers.N.conv
- HF wav2vec2.encoder.layer_norm.* → top-level layer_norm.*
- HF wav2vec2.adapter.layers.N.conv.weight → adapter.layers.N.conv
- HF intermediate_dense/output_dense → up_proj/down_proj

Testing:
- TestBuildMMSGraph: 6 tests (package builds, IO contract, CTC head
  initializers, adapter variant, registry lookup, ORT inference)
- MMSConfig added to _COVERAGE_SKIP with reason

Example:
- examples/mms.py: CLI for multilingual CTC ASR with language adapter
  loading, audio file / mic input, greedy CTC decoding, multi-language demo

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
examples/mms.py:
- Replace build(hf_model) with build_from_module() pattern: build() takes
  a model_id string, not a PyTorch model object.  Because language adapter
  weights are loaded into the HF model via hf_model.load_adapter(lang),
  we must apply the resulting state_dict directly rather than
  re-downloading from Hub (which would reset the adapter).
- Add build_mms_package() helper that correctly wires config extraction,
  graph construction, and state-dict application.
- Import build_from_module + MMSConfig instead of build.

src/mobius/models/wav2vec2_ctc.py:
- Remove dead 'if TYPE_CHECKING: pass' block.
- Expand attention_mask docstring: note that callers can pass all-ones
  for non-padded inputs and clarify the 1=valid/0=padding convention.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
Add three new modes to the MMS ASR example:

--stream: Process audio in short windows (default 1s) and display
  partial transcript in real-time with a progress bar, overwriting
  the current terminal line as each window decodes.

--live: Capture from the default microphone continuously, buffer
  audio in configurable windows, decode each window immediately
  and print the timestamped result. Stops on Ctrl-C.
  Requires sounddevice.

--play: Play audio through the default output device in a background
  thread while transcribing. Requires sounddevice; silently skipped
  if not installed.

--window SECONDS: Shared window size for --stream and --live (default 1.0s).

New helpers:
  play_audio()                   — background playback via sounddevice
  _clear_line()                  — ANSI terminal line-erase utility
  transcribe_streaming()         — windowed decode with live progress bar
  live_microphone_transcription() — real-time mic loop with queue

Batch mode and all existing flags (--audio, --mic, --chunk, --save-to,
--list-langs) are unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
@justinchuby
justinchuby force-pushed the justinchu/mms-models branch from 2fff1f7 to d0cc0cd Compare June 2, 2026 20:35
@justinchuby

Copy link
Copy Markdown
Member Author

Rebased on current main and re-verified.

The branch was 156 commits behind and the merge state was CONFLICTING. Cherry-picked the 3 MMS commits onto fresh main and resolved:

  1. src/mobius/_registry.py — kept current main's Gemma4Config + _create_default_registry() factory pattern, added the MMS entry inside _REGISTRATIONS (so it goes through the same path as every other model) plus the mmsfacebook/mms-300m test-model-id mapping.
  2. src/mobius/tasks/__init__.py — added only ctc-asr; dropped the audio-to-audio / moshi entries that the original branch picked up from a different mixed-in feature.
  3. src/mobius/_configs/__init__.py — added MMSConfig to the imports + __all__ (was missing entirely, which would have broken from mobius._configs import MMSConfig at runtime).
  4. tests/build_graph_test.py — kept the fun_asr and mms entries in the audio specialized-tests list.

Also updated CTCAsrTask to match the current tasks/_base.py API: _make_graph(name=...) no longer takes a list of pre-built inputs; switched to the builder-based pattern (builder.input(...) + builder.add_output(...)) that the rest of the task code uses today.

Implementation review:

  • src/mobius/models/wav2vec2_ctc.py (281 lines): clean — _AdapterLayer + _Adapter + Wav2Vec2ForCTCModel extend Wav2Vec2Model and add CTC head, with focused preprocess_weights rename logic for the few HF↔mobius naming gaps (Conv1d .weight/.bias → bare params, encoder layer-norm relocation, pos-conv-embed drop).
  • src/mobius/tasks/_ctc_asr.py (57 lines): single-output task, minimal scaffolding.
  • examples/mms.py (664 lines): batch / streaming / live-mic / audio-playback modes. Imports cleanly, syntax-clean.

Lint fixes: replaced ambiguous × (MULTIPLICATION SIGN) characters with x in docstrings/comments (RUF002/RUF003) and ran ruff format.

Verification on H200:

  • TestBuildMMSGraph 8/8 pass
  • Full tests/build_graph_test.py + src/mobius/ suite: 2773 passed, 43 skipped (was: every test errored at collection before the rebase)
  • ruff check + ruff format clean on touched files

Implementation is simple, focused, and consistent with the existing model/task patterns in the repo. Ready for review.

@codecov

codecov Bot commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.32258% with 43 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/mobius/models/wav2vec2_ctc.py 59.52% 32 Missing and 2 partials ⚠️
src/mobius/_configs/_base.py 66.66% 4 Missing and 1 partial ⚠️
src/mobius/_builder.py 0.00% 3 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@justinchuby
justinchuby marked this pull request as ready for review June 2, 2026 20:41
@justinchuby
justinchuby requested a review from Copilot June 2, 2026 20:42
- wav2vec2_ctc.py: fix Wav2Vec2AdapterLayer Conv1d padding —
  was hard-coded to 1, which only matched HF for kernel_size=3.
  Now uses kernel_size // 2 (matching HF's
  Wav2Vec2AdapterLayer(padding=kernel_size // 2)). Real correctness
  bug for any non-default adapter_kernel_size.

- _ctc_asr.py: fix CTCAsrTask docstring to reflect that
  attention_mask is a required graph input, not optional. Pass an
  all-ones mask when there is no padding.

- examples/mms.py: add explanatory comment to the live-mic Ctrl-C
  handler so the "empty except" finding goes away while preserving
  the existing cleanup flow.

- examples/mms.py: fix transcribe_streaming docstring vs behaviour
  mismatch. Old docstring claimed the overlap tail was "trimmed
  from decoded output", but the implementation appended every
  segment verbatim, duplicating tokens at chunk boundaries when
  overlap_seconds > 0. Set the default to 0 and document the
  trade-off honestly so callers opting in know what to expect.

Verified locally on H200: 8/8 TestBuildMMSGraph pass, full
suite still 2773 passed + 43 skipped, ruff check + format clean.

Signed-off-by: Justin Chu <11205048+justinchuby@users.noreply.github.com>
@justinchuby

Copy link
Copy Markdown
Member Author

Addressed the Copilot review (commit 80f73da):

# Comment Status
1 examples/mms.py:345 — empty except for Ctrl-C ✅ Added an explanatory comment noting the expected exit + fall-through to the finally cleanup block.
2 wav2vec2_ctc.py:80 — Conv1d padding hard-coded to 1 Real bug. Now uses kernel_size // 2 to match HF's Wav2Vec2AdapterLayer(padding=kernel_size // 2). The hard-coded 1 only happened to be correct for the default adapter_kernel_size=3; any other value would have produced misaligned output lengths vs HuggingFace.
3 _configs.pyLfm2AudioConfig.from_transformers skips depthformer_heads n/a — this file isn't in the rebased PR; LFM2 was an unrelated scope that came in via a merge and got dropped during the rebase.
4 _audio_to_audio.py — misleading text+audio fusion docstring n/a — same as above; AudioToAudioTask isn't part of this PR after the rebase.
5 _ctc_asr.py:27attention_mask marked optional but is required ✅ Docstring updated; clarifies callers should pass all-ones when there is no padding.
6 examples/mms.py:214 — overlap docstring vs implementation ✅ The implementation appended each segment verbatim, so any overlap region was decoded into both segments → duplicated tokens. Set overlap_seconds=0.0 as the default and rewrote the docstring honestly. Callers opting into overlap_seconds > 0 now know they're accepting that trade-off.
7 _registry.py — scope creep (LFM2/Moshi etc.) ✅ Resolved by the earlier rebase: git diff origin/main --name-only is now MMS-only (11 files: wav2vec2_ctc.py, _ctc_asr.py, MMSConfig, registry/task wiring, tests, example).

Local re-verification on H200:

  • TestBuildMMSGraph 8/8 pass
  • Full tests/build_graph_test.py + src/mobius/ suite: 2773 passed, 43 skipped
  • ruff check + ruff format clean on touched files

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 11 out of 11 changed files in this pull request and generated 8 comments.

Comment thread tests/build_graph_test.py
Comment thread src/mobius/_registry.py
Comment thread src/mobius/tasks/_ctc_asr.py Outdated
Comment thread src/mobius/tasks/_ctc_asr.py
Comment thread src/mobius/models/wav2vec2_ctc.py Outdated
Comment thread src/mobius/models/wav2vec2_ctc.py
Comment thread examples/mms.py Outdated
Comment thread examples/mms.py
- New _generate_ctc_asr generator in scripts/generate_golden.py: loads
  Wav2Vec2ForCTC + AutoProcessor with target_lang, calls load_adapter,
  runs a forward pass and saves top-K of the last frame (L4) plus a
  CTC-greedy-decoded transcript sidecar (L5).
- Wire 'ctc-asr' into _HIDDEN_STATE_TASKS and the e2e dispatcher in
  tests/e2e_golden_test.py; adds an all-ones attention_mask alongside
  input_values since _prepare_audio_feeds only emits the latter.
- Add testdata/cases/audio/mms-1b-all.yaml (L4+L5) using the standard
  testdata 652-129742-0006.flac fixture. Currently skipped: the full
  1B-param graph build OOMs host RAM during ONNX construction; goldens
  are committed for future regression coverage once the build path is
  fixed.
- Auto-dispatch wav2vec2/hubert/wavlm checkpoints whose architecture
  contains 'ForCTC' to the 'mms' (Wav2Vec2ForCTCModel + ctc-asr task)
  registration so HF model_type='wav2vec2' + Wav2Vec2ForCTC reaches the
  CTC code path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <11205048+justinchuby@users.noreply.github.com>
@justinchuby

Copy link
Copy Markdown
Member Author

Added L4 + L5 golden test infrastructure for CTC-ASR:

  • New _generate_ctc_asr in scripts/generate_golden.py (loads HF Wav2Vec2ForCTC with target_lang + load_adapter, saves top-K of the last frame as L4 and the CTC-greedy-decoded transcript as L5).
  • Wired ctc-asr into _HIDDEN_STATE_TASKS and the e2e dispatcher in tests/e2e_golden_test.py (adds the required all-ones attention_mask since _prepare_audio_feeds only emits input_values).
  • New test case testdata/cases/audio/mms-1b-all.yaml against the standard 652-129742-0006.flac fixture using the eng adapter.
  • Golden references committed (testdata/golden/audio/mms-1b-all.json + mms-1b-all_generation.json); transcript: "collorflower maianes take cold boiled colorflowr break into branches adding salt pepper and vinegar to season".
  • Bonus: auto-dispatch wav2vec2/hubert/wavlm checkpoints whose architectures contains ForCTC to the mms registration in _builder.py — the existing mms registry entry was previously unreachable.

The L4 e2e is currently marked skip_reason because building the full 1B-param graph through mobius.build exhausts host RAM (1.4 TB RSS before OOM). Goldens are saved so coverage flips on automatically once the build-side issue is fixed (or once we wire a smaller CTC checkpoint into the test case).

justinchuby and others added 2 commits June 2, 2026 15:00
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
@justinchuby

Copy link
Copy Markdown
Member Author

Addressed the actionable Copilot review items in db077ed:

  • License headers: switched src/mobius/tasks/_ctc_asr.py, src/mobius/models/wav2vec2_ctc.py, examples/mms.py to the Microsoft MIT header used elsewhere in the repo.
  • CTCAsrTask.model_roles: added {"model": "encoder"} so build_from_module() doesn't apply decoder-only passes.

Remaining Copilot comments are not actionable here:

  • 'UNet tests indented inside TestBuildMMSGraph' — false positive; TestBuildMMSGraph only contains the 8 MMS tests (all pass), and TestBuildDiTGraph/TestBuildHunyuanDiTGraph are separate classes after it.
  • 'mms registry key ineffective' — fixed in 1cf7cfc by the wav2vec2/hubert/wavlm + ForCTCmms architecture override in _builder.py.
  • LFM2 / audio-to-audio / Lfm2AudioConfig.depthformer_heads / PR-scope comments target files that were dropped during the rebase and no longer in this PR.

justinchuby and others added 2 commits June 2, 2026 22:03
- Switch Apache-2.0/ONNX-Project-Contributors headers to Microsoft MIT
  on the three new files (src/mobius/tasks/_ctc_asr.py,
  src/mobius/models/wav2vec2_ctc.py, examples/mms.py) to match the
  rest of the repo.
- Add model_roles = {"model": "encoder"} to CTCAsrTask so
  build_from_module() classifies the graph as an encoder and skips
  decoder-only optimization passes.

(Copilot's other new findings are stale or false positives:
- 'UNet tests indented inside TestBuildMMSGraph' — not present, all
  8 MMS tests still pass; UNet/DiT classes are separate.
- 'mms registry key ineffective' — already fixed in 1cf7cfc by adding
  the wav2vec2/hubert/wavlm + ForCTC → mms architecture override in
  _builder.py.
- LFM2/audio-to-audio comments target files dropped during rebase.)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <11205048+justinchuby@users.noreply.github.com>
- testdata/cases/schema.json: register 'ctc-asr' in the task_type enum
  and add 'lang' under generation properties so the new mms-1b-all
  case validates. yaml_schema_test now passes (225/225).
- Drop suppressible try/except in _generate_ctc_asr (RUFF/SIM105) in
  favour of contextlib.suppress.
- Ruff-format blank line in _registry.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <11205048+justinchuby@users.noreply.github.com>
@justinchuby
justinchuby merged commit 8d492d9 into main Jun 2, 2026
20 of 21 checks passed
@justinchuby
justinchuby deleted the justinchu/mms-models branch June 2, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai Created by an AI agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants