Views
No views yet
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.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() setsaudio_lm_heads[i].weight IS audio_embeddings[i].weight # the same tensor, not a copy
text_lm_head.weight IS transformer.embed_tokens.weightB @ 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.1m = base.model if hasattr(base, "model") else base
2print(m.audio_lm_heads[0].weight.data_ptr() == m.audio_embeddings[0].weight.data_ptr())
3# True -> same storage, merging corrupts the embeddings1from peft import PeftModel
2
3model = PeftModel.from_pretrained(base, "<this repo>", adapter_name="a").to(dev).eval()
4# do NOT call model.merge_and_unload()
5
6def set_weight(model, name, w):
7 """Scale one named adapter's contribution. alpha/r is its own base scaling."""
8 for module in model.modules():
9 scaling = getattr(module, "scaling", None)
10 if isinstance(scaling, dict) and name in scaling:
11 if not hasattr(module, "_base_scaling"):
12 module._base_scaling = {}
13 module._base_scaling.setdefault(name, scaling[name])
14 scaling[name] = module._base_scaling[name] * float(w)
15
16set_weight(model, "a", 1.0)
17model.base_model.set_adapter(["a"]) # several adapters can be active at onceWx + (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.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.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.laion/moss-tts-local-transformer-4.55b-voice-acting-v2,
trained on an emotion-selected subset of German public-broadcast (Mediathek) speech — real
recorded human speech, not synthetic.| arm | composite | genuineness | blend | target emotion |
|---|---|---|---|---|
| base (prompt only) | 0.5015 | 3.179 | 4.732 | 1.468 |
| r16_e1 | 0.5076 | 2.597 | 5.823 | 2.279 |
| r16_e2 | 0.5086 | 2.736 | 5.612 | 2.399 |
| r16_e3 | 0.4879 | 2.643 | 5.354 | 2.465 |
| r32_e1 | 0.5114 | 2.648 | 5.815 | 2.382 |
| r32_e2 | 0.4978 | 2.703 | 5.451 | 2.293 |
| r32_e3 | 0.4979 | 2.604 | 5.618 | 2.251 |
| r64_e1 | 0.4951 | 2.609 | 5.553 | 2.165 |
| r64_e2 ← default | 0.5275 | 2.909 | 5.701 | 2.509 |
| r64_e3 | 0.5050 | 2.618 | 5.737 | 2.486 |
diff_vs_base ranges −0.014 to +0.026, |z| ≤ 0.83.r64_e2 is the default: the best composite of the nine, the highest genuineness of any LoRA
arm (so the smallest sincerity penalty), and essentially the top target-emotion score. If you want
maximum emotional push and can accept lower genuineness, r16_e3 or r64_e3 score marginally
higher on target emotion.1import torch
2from transformers import AutoModel, AutoProcessor
3from peft import PeftModel
4
5REPO = "laion/moss-tts-local-transformer-4.55b-voice-acting-v2"
6proc = AutoProcessor.from_pretrained(
7 REPO, trust_remote_code=True,
8 codec_path="OpenMOSS-Team/MOSS-Audio-Tokenizer-v2")
9model = AutoModel.from_pretrained( # AutoModel, NOT AutoModelForCausalLM
10 REPO, trust_remote_code=True,
11 dtype=torch.bfloat16, attn_implementation="sdpa").cuda().eval()
12
13model = PeftModel.from_pretrained(model, "laion/moss-mediathek-emotion-lora/r64_e2").eval()
14
15conv = [[proc.build_user_message(
16 text="Ich habe wirklich alles versucht.", # spoken words ONLY
17 instruction='GENERAL: A tired middle-aged man, close mic.\n'
18 'SCRIPT:\n(quietly, on the edge of giving up) "Ich habe wirklich alles versucht."',
19 language="German",
20 tokens=6)]]
21b = proc(conv, mode="generation")
22out = model.generate(input_ids=b["input_ids"].cuda(),
23 attention_mask=b["attention_mask"].cuda(),
24 max_new_frames=400, do_sample=True,
25 audio_temperature=1.0, audio_top_p=0.95, audio_top_k=30)
26wav = proc.decode(out)[0].audio_codes_list[0].cpu().float().numpy() # 48 kHzAutoModel, not AutoModelForCausalLM.audio_lm_heads.* / text_lm_head.weight warnings on load are benign.instruction holds the whole GENERAL: …\nSCRIPT:… caption; text holds only the spoken
words. Empty fields render as the literal string "None".| base | laion/moss-tts-local-transformer-4.55b-voice-acting-v2 |
| data | emotion-selected German Mediathek speech (real recordings) |
| held-out set | 400 clips |
| LoRA alpha | 128 |
| dropout | 0.05 |
| ranks | 16 / 32 / 64 |
| epochs | 1 / 2 / 3 |