File size: 3,543 Bytes
8c4a637
 
 
fd13602
8c4a637
fd13602
8c4a637
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"""How weights/*.fp16.pt were made from the training checkpoints (kept for provenance; not needed at runtime).
v0.8-en (run on the training machine in the tts-sv container, paths relative to /w):
    python tools/export_weights.py --out weights \
        --hugin hugin-v0.8-b1=elev/runs/hugin-v0.8-en/ckpt_176000.pt:w:1:vocos-b1-40k:en_v2
(vocos-b1-40k.fp16.pt and g2p.fp16.pt are unchanged since v0.5: --vocos elev/runs/vob1/vocos_40000.pt --g2p novel/g2p/g2p.pt;
hift-s3gopen.fp16.pt (the vocoder for own voices from hugin-finetune) and g2p_en.fp16.pt since v0.7: --hift out/runs/s3gopen/s3gen_8000.pt --g2p-en novel/g2p/g2p_en.pt)
The optional 5th field FRONT is stored as info["front"]; "en_v2" switches on the v0.8-en English text path in hugin/synth.py.
Only inference weights are kept (no optimiser state, no GAN discriminators). Floating tensors are stored as fp16 and loaded
back to fp32 at runtime; tiny vectors (< 4096 values: norms, biases, mel statistics, the iSTFT window) stay fp32."""
import argparse, os, torch


def half(sd, small=4096):
    return {k: (v.half() if v.is_floating_point() and v.numel() >= small else v.clone()) for k, v in sd.items()}


def g2p(src, dst):
    g = torch.load(src, map_location="cpu", weights_only=False)       # keys model, vocab
    torch.save({"model": half(g["model"]), "vocab": {"in": list(g["vocab"]["in"]), "out": list(g["vocab"]["out"])}}, dst)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--hugin", action="append", default=[], help="NAME=CKPT:VOICE:TAG:VOCODER[:FRONT] (repeatable)")
    ap.add_argument("--vocos"); ap.add_argument("--hift"); ap.add_argument("--g2p"); ap.add_argument("--g2p-en")
    ap.add_argument("--out", default="weights")
    a = ap.parse_args(); os.makedirs(a.out, exist_ok=True)
    for spec in a.hugin:
        name, rest = spec.split("=", 1); f = rest.split(":"); ck, voice, tag, voc = f[:4]; front = f[4] if len(f) > 4 else None
        s = torch.load(ck, map_location="cpu", weights_only=False)   # keys model, vocab, arch, (opt, D, optD dropped)
        torch.save({"model": half(s["model"]), "vocab": list(s["vocab"]), "arch": dict(s.get("arch", {})),
                    "info": {"name": "futhark-hugin", "version": name.replace("hugin-", ""), "step": int(s.get("step", 0)),
                             "prominence": True, "voice": voice, "tag": int(tag), "vocoder": voc, **({"front": front} if front else {})}},
                   os.path.join(a.out, f"{name}.fp16.pt"))
    if a.vocos:
        v = torch.load(a.vocos, map_location="cpu", weights_only=False)   # keys V (generator), D, oG, oD (dropped)
        torch.save({"V": half(v["V"]), "info": {"name": "vocos-b1", "step": int(v.get("step", 0))}}, os.path.join(a.out, "vocos-b1-40k.fp16.pt"))
    if a.hift:
        g = torch.load(a.hift, map_location="cpu", weights_only=True)    # full S3Gen state dict; keep only the HiFT vocoder
        h = {k[len("mel2wav."):]: v for k, v in g.items() if k.startswith("mel2wav.")}
        torch.save({"hift": half(h), "info": {"name": "hift-s3gopen", "source": "Chatterbox S3Gen HiFT (MIT), fine-tuned in s3gopen (licence-clean Swedish data), step 8000"}},
                   os.path.join(a.out, "hift-s3gopen.fp16.pt"))
    if a.g2p: g2p(a.g2p, os.path.join(a.out, "g2p.fp16.pt"))
    if a.g2p_en: g2p(a.g2p_en, os.path.join(a.out, "g2p_en.fp16.pt"))
    for f in sorted(os.listdir(a.out)):
        if f.endswith(".pt"): print(f, f"{os.path.getsize(os.path.join(a.out, f)) / 1e6:.1f} MB")


if __name__ == "__main__":
    main()