Skip to content

feat(native): add MOSS-Transcribe-Diarize — speaker diarization for Local Native ASR #321

Description

@jiangzhuo

Add handy-computer/moss-transcribe-diarize-gguf to the Local Native ASR catalog. It is the only model in the transcribe.cpp family that adds a capability dimension our ASR roster lacks entirely: speaker diarization.

Surfaced while enumerating the full handy-computer GGUF family (68 models; we currently ship 15).

The honest catch: one catalog row ≠ one feature

Adding the row is trivial. Realising the value is not. MOSS emits the canonical diarized format [start][Sxx]text[end], and per the model card the speaker tags and segment timestamps are generated text, not special tokens.

So there are two distinct pieces of work:

  1. The catalog row — makes the model run. On its own this only buys us another offline en/zh ASR card at WER 1.93, which is worse than Cohere Transcribe (1.25) and narrower than most of the roster. Not worth doing by itself.
  2. The diarization plumbing — parse [Sxx] out of the transcript, carry speaker identity through the translate stage, and surface it in the conversation items / subtitle overlay. This is where all the value is, and none of it exists today.

This issue should not be closed by landing step 1 alone.

Why it is interesting anyway

  • Speaker-attributed translation is a real product capability for our core use case (multi-participant meetings: Meet / Teams / Zoom). Today every speaker collapses into one undifferentiated transcript stream.
  • It is Apache-2.0 and an official handy-computer GGUF, i.e. it lands on transcribe_cpp — the backend all 15 of our ASR cards already use. No new runtime, no new dependency, no torch.
  • Accuracy is competitive: WER 1.93 on the author's uniform LibriSpeech test-clean benchmark — it would slot between Parakeet TDT 0.6B v3 (1.92) and Whisper large-v3-turbo (2.01).

Model facts (verified 2026-07-16 via HF API + model card)

Repo handy-computer/moss-transcribe-diarize-gguf
Base OpenMOSS-Team/MOSS-Transcribe-Diarize
License Apache-2.0
Architecture 0.9B audio-LLM — Whisper-Medium encoder (24L, d_model 1024) → 4x temporal merge + VQAdaptor bridge → Qwen3-0.6B decoder (28L), via audio-token injection
Languages en, zh only
Streaming No (offline, whole-clip)
Timestamps segment
Output [start][Sxx]text[end] — tags/timestamps are generated text

Quant ladder + WER (LibriSpeech test-clean, author's runner)

Quant Size WER
F16 1,833,665,696 2.07
Q8_0 986,899,616 1.93 ← best
Q6_K 768,151,712 1.96
Q5_K_M 700,313,760 1.99
Q4_K_M 617,345,184 2.59 ⚠️

Note Q8_0 (1.93) beats BF16 (2.08) — same quirk as Fun-ASR MLT Nano, where q6_k beats bf16. So default = Q8_0.

⚠️ Q4_K_M must not be recommended. Its excess WER is not broad degradation but 4-bit tail failures: 6 empty outputs, 5 English→Chinese language-drift utterances, and 1 timestamp-token repetition loop. The card says "Prefer Q5_K_M or higher". Language drift in a translation app is a user-visible correctness bug.

This collides with _tc_row's hardcoded curated = {"q8_0", "q6_k", "q4_k_m"}, which would give q4_k_m rank 1.0 (a recommendation candidate). Cleanest fix: omit Q4_K_M from the quants dict entirely so it is never offered. Alternative: add a per-row curated= override.

Speed (author-reported)

Machine Tier RTF
M4 Max metal 28.1
M4 Max cpu 5.8
Ryzen 4750U vulkan 3.0
Ryzen 4750U cpu 1.6

Real-time on a modest CPU, comfortable on GPU.

Proposed catalog row

In sidecar/sokuji_sidecar/catalog.py, ASR_MODELS (sort_order 85 = between parakeet 80 and turbo 90):

    # WER 1.93 (q8_0 beats bf16 2.08) — en/zh + speaker diarization.
    # Q4_K_M deliberately omitted: 4-bit tail failures incl. en->zh language drift.
    _tc_row("moss-transcribe-diarize", "MOSS Transcribe-Diarize", ("en", "zh"),
            "handy-computer/moss-transcribe-diarize-gguf", "MOSS-Transcribe-Diarize",
            85, {"F16": 1833665696, "Q8_0": 986899616, "Q6_K": 768151712,
                 "Q5_K_M": 700313760}, default="Q8_0"),

Open questions

  • Pipeline shape: our ASR stage returns plain text. Where do the [Sxx] tags get parsed — sidecar-side (return structured segments) or renderer-side? Sidecar-side is probably right, but it changes the ASR result contract, which every other card shares.
  • Downstream: how does speaker identity survive the translate stage and reach conversation items + the subtitle overlay? Does the subtitle wire format need a speaker field? (Careful: see the heavy-field lesson from the subtitle port.)
  • Is diarization even useful without streaming? This is an offline whole-clip model. It fits our existing non-streaming ASR flow, but a diarized live transcript may be what users actually want. multitalker-parakeet-streaming-0.6b-v1-gguf is the streaming alternative in the same family — worth comparing before committing.
  • UI: how are speakers labelled to the user? S01/S02 are meaningless; are they mappable to real participants?
  • en/zh only — is that enough coverage to justify the feature work?

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions