alt.freeweights.speech / moss-va-sft3-rate-lora
From: laion <huggingface.co/laion>Newsgroups: alt.freeweights.speechSubject:

moss-va-sft3-rate-lora

Date: 2026-11-15Commit: cb6c668Signed: 2DC28CC7B26A283B ✓

Text to speech model. Safetensors weights, 275 MB in all. Published by laion under Apache-2.0.

speech275MBmoss-va-sft3-rate-lorasafetensorspermissive
FactsParamsnot statedSize275 MBFormatssafetensorsTasktext to speechLicenseApache-2.0From sourcepermissive
$ fw get laion/moss-va-sft3-rate-lora
# manifest sha256 cb9c00e723b5… · 275 MB · 6 files
$ fw verify ./moss-va-sft3-rate-lora laion/moss-va-sft3-rate-lora
From sourceHosted (signed)laion/moss-va-sft3-rate-loraLast check: 2026-11-15 05:30 UTC

Get help: custom fine-tune or private deploy. We review your setup before we quote.

Adapter of moss-tts-local-transformer-4.55b-voice-acting-v2-sft3.

Works with

Overview

Size
275 MB
Weights
275 MB (safetensors)
Parameters
not stated
Formats
safetensors
Task
text to speech
License
Apache-2.0 (permissive)
Files
6, each with a sha256
Publisher
laion
Added
2026-11-15
Section
alt.freeweights.speech
Lineage

Original release from the publisher.

Base model: laion/moss-tts-local-transformer-4.55b-voice-acting-v2-sft3

Where these bytes come from
Publisher at the pinned commit→You

Sources are tried in order. A file counts only when its size and sha256 match the manifest. Sources are checked when you ask for them, not on a timer.

Sources
BadgeHostLast checkResult
From sourcehuggingface.co2026-11-15 05:30 UTCpassed

Model card

From the publisher at commit cb6c668. Shown as plain text with links; images open at the publisher.

MOSS VA SFT3 — speaking-rate LoRA (rank 16)#

⚠️ Do not merge this adapter into the base weights#

merge_and_unload(), merge_adapter(), and any offline "bake the LoRA into the checkpoint" script will destroy the model irrecoverably. This is not a performance caveat. Read this before you write a deployment script.

Why#

This adapter targets audio_lm_heads.0 … audio_lm_heads.11 and text_lm_head — 12 of its 23 target modules. In this architecture those output heads are weight-tied to the input embeddings: tie_weights() sets

audio_lm_heads[i].weight  IS  audio_embeddings[i].weight     # the same tensor, not a copy
text_lm_head.weight       IS  transformer.embed_tokens.weight

They are one allocation with two names. So when a merge adds B @ A * (alpha/r) into the head weight, it writes that delta straight into the embedding table at the same time. The model then reads its own inputs through a matrix that has been shifted by an output-side correction. Generation does not fail loudly — it degrades into noise or into a fixed babble, and the damage is inside the checkpoint you just saved. There is nothing to unmerge afterwards, because the original values are gone.

Verify it yourself in three lines#

Do not take our word for it:

m = base.model if hasattr(base, "model") else base
print(m.audio_lm_heads[0].weight.data_ptr() == m.audio_embeddings[0].weight.data_ptr())
# True  -> same storage, merging corrupts the embeddings
What to do instead#

Load with PEFT and leave the adapter unmerged. Set its strength through the scaling factor:

from peft import PeftModel

model = PeftModel.from_pretrained(base, "<this repo>", adapter_name="a").to(dev).eval()
# do NOT call model.merge_and_unload()

def set_weight(model, name, w):
    """Scale one named adapter's contribution.  alpha/r is its own base scaling."""
    for module in model.modules():
        scaling = getattr(module, "scaling", None)
        if isinstance(scaling, dict) and name in scaling:
            if not hasattr(module, "_base_scaling"):
                module._base_scaling = {}
            module._base_scaling.setdefault(name, scaling[name])
            scaling[name] = module._base_scaling[name] * float(w)

set_weight(model, "a", 1.0)
model.base_model.set_adapter(["a"])      # several adapters can be active at once

This sounds identical to a merge. An unmerged LoRA computes Wx + (B @ A)x * (alpha/r), which is exactly what the merged weight W + B @ A * (alpha/r) would compute — the same arithmetic, in a different order. You give up a small amount of inference speed and you keep the ability to change the weight, stack several adapters, or turn one off. Nothing about the sound changes.

If you are stacking adapters#

Set each one's scaling separately and activate them together with model.base_model.set_adapter([...]). Note that stacking is not free: in our own measurements a deep stack held audio quality but destroyed intelligibility (word error 0.063 → 0.554). Add adapters deliberately and measure.

If you maintain code that merges#

A regex over module names is not enough — the reliable test is identity of storage. Group the modules by weight.data_ptr() and refuse to merge into any group with more than one member. lora_bank.py in LAION-AI/Humaneness-Voice-Demo-Server does this and asserts on the merge path.


Read this first: this adapter does not do what it was built to do. It was trained to make the model obey an explicit speaking-rate instruction in the prompt. Measured against the base model in 400 paired trials across two designs, it changes the speaking rate by nothing measurable. It is published because the measurement is the useful part, and because it is the initialisation of the two quality adapters that build on it.

It is harmless — word error rate and duration accuracy are unchanged — but it is not a rate control and should not be described as one.

What was supposed to happen, in plain language#

A text-to-speech model normally decides for itself how fast to talk. We wanted to be able to say "say this line at 26 characters per second" and have it comply, so an agent could direct pacing the way a director does.

To teach that, we took 121,940 real recordings and, for each one, wrote the speaking rate of the actual recording into the prompt. A third of the rows say the duration ("3.3 seconds"), a third say the rate ("20.0 characters per second"), a third say both. Then we trained a small adapter — 34.4 million extra weights on top of a frozen 4.55-billion-parameter model — to predict the audio given that prompt.

Why it could not have worked#

The rate in every training row was computed from the duration: rate = characters / duration, where both the characters and the duration are already printed in the prompt. So the rate tag never carried information the prompt did not already contain. The model had no reason to learn to read it — the answer was always available somewhere else.

Worse, the prompt envelope also carries a Tokens: budget that fixes the total length directly. That budget, not the tag, is what actually controls timing — and the base model already obeys it.

The measurements#

Base = laion/moss-tts-local-transformer-4.55b-voice-acting-v2-sft3. 8 texts × 5 requested rates × 3 samples, paired within cell on a shared seed. "Tempo" is characters per voiced second (silence excluded); "slope" is achieved tempo regressed on requested tempo, where 1.0 would be perfect obedience and 0.0 would be total indifference.

Design A — the budget moves with the request (the natural way to ask)#
tag modemodelslope±setempo @14tempo @26WER
duration onlybase0.6590.07217.625.80.074
duration only+ this adapter0.5890.08318.125.40.071
rate onlybase0.6640.06617.626.40.049
rate only+ this adapter0.6560.07518.026.40.049
bothbase0.7740.07216.325.80.067
both+ this adapter0.7780.06618.127.70.067

The base model tracks the request just as well as the adapter — and it has never seen a rate tag. It is responding to the length budget, which moves with the request in all three modes. Interaction term (adapter's gain in rate mode beyond its gain in duration mode): −0.09.

Design B — the budget is pinned, only the tag varies (the decisive test)#
tag modemodelslope±setempo @14tempo @26
rate onlybase0.0080.06918.518.3
rate only+ this adapter−0.0340.06918.418.1
bothbase0.1040.06617.918.4
both+ this adapter−0.0060.05517.317.7

All four slopes are zero. Told 14 or told 26, the model speaks at 18.

What is controllable#
  1. Total duration: fully. Median error 0.000 s in 13 of 14 conditions of a separate 1,596-clip study, regression slope 0.979. This works through the Tokens: budget and needs no adapter.
  2. Tempo: only indirectly. A tighter budget forces faster speech, but there is a floor around 18 characters per voiced second — below it the model does not slow down, it pads with silence.
  3. The rate tag: inert.
Audio quality (16 prompts, paired vs base)#
DNSMOSSIGBAKWERgenuineness
−0.054 (t −1.55)−0.047−0.036+0.028−0.047

No significant change on anything.

How it was trained#

baselaion/moss-tts-local-transformer-4.55b-voice-acting-v2-sft3
data121,940 voice-profile recordings, rate modes in equal thirds (40,835 / 40,609 / 40,496)
corpus ratemedian 20.7 characters per second, p10 16.2, p90 27.4; 1.30 rate segments per clip
adapterLoRA rank 16, α 32, dropout 0.05, 34.4 M of 4.164 B trainable (0.825 %)
schedule1 epoch, 3,788 updates, lr 1e-4 cosine to 0.1×, 50 % warmup, weight decay 0.1, global batch 32
hardware4 GPUs, 1 h 47 min
val loss3.8225 → 3.8227 → 3.8216 → 3.8200 over the four evaluation points

The validation loss is not a quality signal here and should not be read as one. It runs over every audio token of a clip, and a rate tag moves almost none of that distribution; the total movement across the whole run is 0.0025 at n = 116. This is exactly why the behavioural test above exists.

Prompt format#

This adapter was trained on prompt format hash 090c4ca315519a57, not the project's usual 073aeb09dc923376. A duration-only row is byte-identical between the two; the rate forms are new.

[3.3 seconds duration]                              # identical to the standard format
[20.0 characters per second]                        # replaces the duration tag
[3.3 seconds duration; 20.0 characters per second]  # both

Inference#

⚠️ Never merge this adapter into the base weights. Its target modules include audio_lm_heads.0 … audio_lm_heads.11, and those tensors share storage with audio_embeddings.N.weight (they are weight-tied). Merging writes the head delta into the embedding table and destroys generation irrecoverably. Load with PEFT and leave it unmerged.

import torch
from transformers import AutoConfig, AutoModel, AutoProcessor
from peft import PeftModel

BASE = "laion/moss-tts-local-transformer-4.55b-voice-acting-v2-sft3"
dev = "cuda:0"

proc = AutoProcessor.from_pretrained(BASE, trust_remote_code=True)
proc.audio_tokenizer = proc.audio_tokenizer.to(dev).eval()
cfg = AutoConfig.from_pretrained(BASE, trust_remote_code=True)
base = AutoModel.from_pretrained(BASE, trust_remote_code=True,
                                 dtype=torch.bfloat16,
                                 attn_implementation="sdpa").to(dev).eval()

model = PeftModel.from_pretrained(base, "laion/moss-va-sft3-rate-lora",
                                  adapter_name="rate").to(dev).eval()
# do NOT call model.merge_and_unload()

batch = proc([[{"role": "user", "content": prompt, "audio_codes_list": []}]],
             mode="generation")
ids = batch["input_ids"].to(dev)
msk = batch["attention_mask"].to(dev).to(torch.bool)
out = model.generate(input_ids=ids, attention_mask=msk,
                     max_new_tokens=340, do_sample=True,
                     temperature=1.0, top_p=0.95, top_k=50)

Decode the returned audio codes with the processor's audio tokenizer as for the base model.

Setting the adapter strength#
for module in model.modules():
    scaling = getattr(module, "scaling", None)
    if isinstance(scaling, dict) and "rate" in scaling:
        module.scaling["rate"] = 2.0 * w   # base scaling is alpha/r = 32/16 = 2.0

All measurements above used w = 1.0.

Checkpoints#

checkpoints/step3788 is the final state and the copy at the repository root. Earlier checkpoints (947, 1894, 2841) exist but were not evaluated, and nothing is published here without numbers.

What a second attempt should do#

Train on audio where the rate varies independently of the natural duration: the same text, a generous fixed length budget, differently time-stretched audio, and the tag naming the stretched rate. Only then does the tag predict something the rest of the prompt does not.

Files

6 files · 275 MBWeights 275 MB (safetensors). Sizes and sha256 from the signed manifest.
Hide all 6 files with their sha256
FileSizesha256
.gitattributes1.52 KB11ad7efa24975ee4b0c3c3a38ed18737f0658a5f75a0a96787b576a78a023361
README.md12.4 KBf35c5d45748b772ec1ae8ff5a9c09d7341641d79b59f51190223259f4113417b
adapter_config.json1.66 KB0287ff865ebb4a80789122f530fa4b480e70928e33a91df3fe07c1c24af90def
adapter_model.safetensors138 MB4966c70522fb9ca2686f6e7e63faa4f22b957aef482488af54a4e1dd897b8ddb
checkpoints/step3788/adapter_config.json1.66 KB0287ff865ebb4a80789122f530fa4b480e70928e33a91df3fe07c1c24af90def
checkpoints/step3788/adapter_model.safetensors138 MB4966c70522fb9ca2686f6e7e63faa4f22b957aef482488af54a4e1dd897b8ddb
VersionsEach time the pinned upstream commit or file list changed in this repo. Newest first.
Upstream commitDateContents
cb6c668f2e pinned now2026-11-15 05:30 UTC6 files · 275 MB

Use it

$ fw get laion/moss-va-sft3-rate-lora
# manifest sha256 cb9c00e723b5… · 275 MB · 6 files
$ fw verify ./moss-va-sft3-rate-lora laion/moss-va-sft3-rate-lora
fw.pyChecks the signature and every sha256, tries each source in order, resumes.
$ python3 fw.py get https://freeweights.allternit.com/manifests/laion/moss-va-sft3-rate-lora.json --dir ./moss-va-sft3-rate-lora $ python3 fw.py verify ./moss-va-sft3-rate-lora ./laion__moss-va-sft3-rate-lora.json
hfThe Hugging Face CLI with one setting. Same repo id.
$ HF_ENDPOINT=https://freeweights.allternit.com hf download laion/moss-va-sft3-rate-lora --revision cb6c668f2e37ea8d113df09970f9bde048a964bb --local-dir ./moss-va-sft3-rate-lora

License

Name
Apache License 2.0
Id
Apache-2.0
Class
Permissive
Redistribution
yes
Listing
Hosted (signed)
Decided
License as declared by the publisher. No person read it. 2026-11-15
Text in
README.md
Text sha256
f35c5d45748b772ec1ae8ff5a9c09d7341641d79b59f51190223259f4113417b
Terms gate
No extra terms beyond the license.

Read the license text

Licenses are checked against the license table. A custom license passes only after a person reads it.

Provenance

From: laion <huggingface.co/laion>Newsgroups: alt.freeweights.speechSubject: moss-va-sft3-rate-loraDate: 2026-11-15Commit: cb6c668Signed: 2DC28CC7B26A283B ✓
Upstream
laion/moss-va-sft3-rate-lora
Pinned commit
cb6c668f2e37ea8d113df09970f9bde048a964bb
Manifest
moss-va-sft3-rate-lora.json · .minisig
Manifest sha256
cb9c00e723b51ae5a6c35c32c5a18787e5681b62200faa08ce75a11855343cc9
Signature
minisign, Ed25519, key 2DC28CC7B26A283B
Created
2026-11-15 05:30 UTC
Generator
freeweights-manifest/0.1.0
Takedowns
None.

How to verify

$ curl -O https://freeweights.allternit.com/manifests/laion/moss-va-sft3-rate-lora.json $ curl -O https://freeweights.allternit.com/manifests/laion/moss-va-sft3-rate-lora.json.minisig $ minisign -Vm moss-va-sft3-rate-lora.json -P RWQ7KGqyx4zCLXtUFOqvfv8L3g+8fpKJ1FsuxPyOYMF+4KufbUxKq1Oq $ shasum -a 256 <file> # compare with the Files tab

A manifest whose signature fails must be rejected. A file whose size or sha256 does not match must be discarded. Verify a download

Model tree

No model in the catalog names this one as its parent.

Get it
Download
$ HF_ENDPOINT=https://freeweights.allternit.com hf download laion/moss-va-sft3-rate-lora
Open in Freeweights Studio

Pull, verify and run it in the Allternit app on Desktop, web or phone.

Signed manifest
minisign, key 2DC28CC7B26A283B. moss-va-sft3-rate-lora.json. Provenance. Takedown history: none.
From: MIT <huggingface.co/MIT>Subject: ast-finetuned-audioset-10-10-0.4593
From: onnx-community <huggingface.co/onnx-community>Subject: ast-finetuned-audioset-10-10-0.4593-ONNX
From: Edge0 <huggingface.co/Edge0>Subject: Audio8-TTS-Preview-0.6b
From: Edge0 <huggingface.co/Edge0>Subject: Audio8-TTS-Realistic-Air
From: facebook <huggingface.co/facebook>Subject: audiobox-aesthetics
From: mlx-community <huggingface.co/mlx-community>Subject: chatterbox-4bit