Skip to content

Add MOSS-VoiceGenerator community model (moss_voicegen) - #278

Merged
0xShug0 merged 4 commits into
0xShug0:mainfrom
jrohde:feat/moss-voicegen
Aug 20, 2026
Merged

Add MOSS-VoiceGenerator community model (moss_voicegen)#278
0xShug0 merged 4 commits into
0xShug0:mainfrom
jrohde:feat/moss-voicegen

Conversation

@jrohde

@jrohde jrohde commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Adds MOSS-VoiceGenerator as a community model: voice design, where the speaker comes from a
written description instead of a reference recording. This resolves #195.

moss_tts_delay — a Qwen3-1.7B backbone with 16 audio codebook embeddings and 1 + 16
heads, decoded on a delay pattern, over MOSS-Audio-Tokenizer v1 (24 kHz mono, hop 1920).
Offline vdes, English and Chinese.

audiocpp_cli --family moss_voicegen --model <model-dir> --task vdes \
  --instruct "A warm male radio voice in his fifties, calm, never shrill." \
  --text "Good evening, and welcome back to the late show." \
  --language English --out out.wav

Parity

Measured against the checkpoint's own PyTorch implementation at four layers, with the
references regenerated by scripts in tools/community_models/ and gated by tests in
tests/moss_voicegen/.

Check Result
Prompt tokens vs MossTTSDelayProcessor identical, 4 cases (trailing punctuation, none, Chinese, no instruction)
Backbone hidden state vs MossTTSDelayModel, bf16 / f32 1.9e-5 / 3.5e-7 max relative
Greedy generation vs generate(), f32 40/40 rows identical, text tokens and all 16 codes
Codec decode vs MossAudioTokenizerModel worst probe 5.0e-7

Row-for-row greedy parity holds at f32 only. At bf16 the first 16 rows match and then
diverge on a 0.003 logit gap between near-tied candidates: greedy decoding is a step
function and bf16 rounding is coarser than that gap. The test gates f32 exactly and bf16 on
a matching prefix, so the bf16 package does not look broken to anyone who runs it.

The backbone needs bf16 or f32 — as f16 it produces NaN from position zero, since it carries
attention-sink activations beyond f16 range. The codec wants the opposite: as bf16 it drifts
1.1e-2 from the reference decode, as f16 7.1e-4, at the same size. The shipped package
converts as --type bf16 --keep-type "audio_tokenizer_weights*=f16", with
--exclude-prefix "audio_tokenizer_weights/encoder" since voice design never encodes audio.
5.7 GB.

Numbers

Radeon AI PRO R9700 (ROCm/HIP), 100 requests through one long-lived audiocpp_server
session:

RTF 0.20 to 0.35, median 0.23 end to end over HTTP
VRAM 7783 -> 7811 MiB across the 100 requests
Long-form the shared case, 47 chunks, none silent, RTF 0.22
Completed 98 of 100

Also verified on Vulkan (R9700 and a 780M iGPU) and CPU. No CUDA hardware here, so those
numbers are missing.

The two failures are the model's own behaviour: nothing forces the first step to open an
audio segment, so about 2% of runs sample an ordinary text token instead and produce no
audio. The session returns an error naming the retry rather than silence.

One change outside the model directory

src/models/moss/shared/ needed five additions. MOSS-Audio-Tokenizer v1 is a different
generation from the v2 and Nano codecs already in the tree — mono instead of interleaved
stereo, a different hop, and different tensor names — so the shared codec could not load it
as it stood:

Change Why
samples_per_frame taken from the config rather than a constant v1's hop is 1920, not 3840
channels config field; the stereo de-interleave is skipped when it is 1 v1 is mono
The stage output projection is optional upstream only creates one when a stage changes width; v1 omits it on three of four decoder stages
Attention projections also accept in_projs.0 / out_projs.0 v1 keeps them in an indexed ModuleList
Feed-forward also accepts linear1 / linear2 v1 names them directly where v2 uses an nn.Sequential

Each one widens what the existing code accepts; none changes v2 or Nano behaviour. The
alternative was copying roughly 1500 lines of codec into the model directory to change five
names and two numbers. If you would rather keep shared/ pinned to v2, say so and I will
move it to a moss_voicegen-local codec instead.

Known limitations

  • The speaker is not stable across requests. An instruction fixes the class — gender,
    age, manner hold reliably — but not the individual, and a seed reproduces a take rather
    than a voice. Long-form therefore comes out as one speaker per chunk. Documented rather
    than worked around: upstream generate() takes no seed at all.
  • Chunks are not levelled against each other, about 14 dB across the long-form case.
  • HIP on gfx1103 segfaults in ggml's get_rows. Not specific to this model: the same
    card runs the same workload under Vulkan, and a hand-written HIP gather kernel runs on it.
    I will open a separate issue with a reproducer.

MOSS-TTSD is the natural follow-up, same family and same delay decoder.


Built with Claude and @jrohde as the human motivator, tester
and approver: the hardware, the listening verdicts and the final say are his.

nllive and others added 3 commits August 17, 2026 13:59
…n instruction)

Ports the moss_tts_delay architecture: a Qwen3-1.7B backbone with 16 audio
codebook embeddings and 1 + 16 heads, decoded on a delay pattern, through
MOSS-Audio-Tokenizer v1 to 24 kHz mono.

Supporting the v1 codec needed five additions in models/moss/shared: the hop
and channel count come from the config rather than constants, the stage output
projection is optional, and the attention and feed-forward weights accept v1's
naming. None of them change v2 or Nano behaviour.
…easurements

prepare() is now idempotent. The server calls it on every request against one
long-lived session, and building the runtimes there re-uploaded the whole 5.7 GB
package each time: about four seconds on top of roughly one second of work.

extract_audio_codes() keeps only frames where every codebook carries a real
code. A generation cut short by the step ceiling leaves pad in the trailing
rows, which the codec rejects as out of range.

The soak tool now reports RTF, failure kinds and VRAM growth over a long-lived
session, and the model doc carries the measured numbers: RTF 0.23 median over
100 requests, +28 MiB VRAM, 47 long-form chunks at RTF 0.22, plus the two
limitations that run exposed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@0xShug0

0xShug0 commented Aug 20, 2026

Copy link
Copy Markdown
Owner

@jrohde Thanks! I will test it tomorrow.

@0xShug0 0xShug0 added the new model Request for new model support label Aug 20, 2026
@0xShug0
0xShug0 merged commit e762542 into 0xShug0:main Aug 20, 2026
6 checks passed
@0xShug0

0xShug0 commented Aug 20, 2026

Copy link
Copy Markdown
Owner

@jrohde Merged! Quick tests on RTX 5090 CUDA RTF is 0.07 to 0.15.

NairoDorian added a commit to NairoDorian/speech.cpp that referenced this pull request Aug 22, 2026
Brings speech.cpp up to date with latest audio.cpp upstream main (4d383be):
- Community models: MOSS-VoiceGenerator (PR 0xShug0#278), MMS-300M-1130 forced aligner (PR 0xShug0#279), F5-TTS (PR 0xShug0#275).
- SenseASR encoder refactored to framework SAN-M modules (PR 0xShug0#285).
- Server: max_loaded_models limit with LRU eviction (PR 0xShug0#298) and opt-in session options listing.
- WebUI: reverse proxy hash routing (PR 0xShug0#297), Music3 precision packages, HeartMuLa options.
- CUDA & Memory: CUDA graph-cache eviction and idle pool trimming (PR 0xShug0#293), Supertonic vector arena reduction.
- GGML: tracked CUDA clear_graph and trim_pools as patch 0007.
- Build & CI: native model manager build flags, C++17 cleanups, CMake model-link guards preserved.
@jrohde

jrohde commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for merging, and for running it on the 5090 — that was the one gap we could not
fill here, so it is good to see the CUDA numbers land in the same range.

Would you like MOSS-TTSD next? It is the same architecture — MossTTSDelay, n_vq 16, the
same special tokens and the same 24 kHz v1 codec — so the delay decoder and the shared
codec work from this PR carry over unchanged. What is new is the encoder side of the v1
codec, since TTSD clones from a short reference where VoiceGenerator never encoded
anything, and the multi-speaker prompt. For the reference audio I would follow the
voice_samples convention vibevoice already uses rather than invent anything.

One thing to weigh: TTSD is 8B where VoiceGenerator was 1.7B. It quantises well, though —
I already run an in-house Q4 conversion at about 9 GB total, so it stays within reach of the
same hardware. Same story as before on my side: AMD and CPU, no CUDA.

Happy to pick it up if you want it.

@0xShug0

0xShug0 commented Aug 24, 2026

Copy link
Copy Markdown
Owner

@jrohde That sounds great! Really appreciate you helping expand audio.cpp. If you find components that can be cleanly abstracted and promoted into the framework as shared components/runtimes for the MOSS family, feel free to do so to reduce duplicated code. Please avoid extending existing framework runtimes/modules for now, as both dev and my local branch have ongoing refactors.

@0xShug0

0xShug0 commented Aug 29, 2026

Copy link
Copy Markdown
Owner

@jrohde Did some refactoring to remove cross-model dependencies #332

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new model Request for new model support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add MOSS-VoiceGenerator (voice design from a text instruction)

3 participants