Add VocalRender VoxCPM2 finetune and conversion script
Browse files
.gitattributes
CHANGED
|
@@ -249,3 +249,4 @@ Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q4_k.gguf filte
|
|
| 249 |
Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 250 |
Kokoro-82M-GGUF/kokoro-82m-multilingual-q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 251 |
LatentSync-1.6-GGUF/latentsync-1.6-f32-f16.gguf filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 249 |
Voxtral-Mini-4B-Realtime-2602-GGUF/voxtral-mini-4b-realtime-2602-q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 250 |
Kokoro-82M-GGUF/kokoro-82m-multilingual-q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 251 |
LatentSync-1.6-GGUF/latentsync-1.6-f32-f16.gguf filter=lfs diff=lfs merge=lfs -text
|
| 252 |
+
VoxCPM2-GGUF/finetune/vocalrender-pro-orig.gguf filter=lfs diff=lfs merge=lfs -text
|
VoxCPM2-GGUF/finetune/convert_vocalrender.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Convert a VocalRender checkpoint into a standalone VoxCPM2 GGUF.
|
| 3 |
+
|
| 4 |
+
Requires torch, safetensors, and an audiocpp_gguf executable built with VoxCPM2.
|
| 5 |
+
Example:
|
| 6 |
+
python convert_vocalrender.py --input ./VocalRender-Pro \
|
| 7 |
+
--output ./vocalrender-pro-orig.gguf --converter ./audiocpp_gguf
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
import json
|
| 12 |
+
import os
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
import shutil
|
| 15 |
+
import subprocess
|
| 16 |
+
import tempfile
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
from safetensors import safe_open
|
| 20 |
+
from safetensors.torch import save_file
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def main():
|
| 24 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 25 |
+
parser.add_argument("--input", type=Path, required=True, help="Source checkpoint directory.")
|
| 26 |
+
parser.add_argument("--output", type=Path, required=True, help="Output orig-precision GGUF.")
|
| 27 |
+
parser.add_argument("--converter", default="audiocpp_gguf", help="Converter executable path or PATH command.")
|
| 28 |
+
parser.add_argument("--model-spec", type=Path, help="Optional voxcpm2 spec; otherwise use the converter catalog.")
|
| 29 |
+
parser.add_argument("--overwrite", action="store_true")
|
| 30 |
+
args = parser.parse_args()
|
| 31 |
+
source = args.input.expanduser().resolve(strict=True)
|
| 32 |
+
output = args.output.expanduser().resolve()
|
| 33 |
+
converter = shutil.which(os.path.expanduser(args.converter))
|
| 34 |
+
if converter is None:
|
| 35 |
+
parser.error("Converter not found; provide --converter or add audiocpp_gguf to PATH.")
|
| 36 |
+
if output.exists() and not args.overwrite:
|
| 37 |
+
parser.error("Output already exists; use --overwrite to replace it.")
|
| 38 |
+
weights = source / "model.safetensors"
|
| 39 |
+
with safe_open(weights, framework="pt", device="cpu") as tensors:
|
| 40 |
+
shape = tensors.get_slice("base_lm.embed_tokens.weight").get_shape()
|
| 41 |
+
config = json.loads((source / "config.json").read_text(encoding="utf-8"))
|
| 42 |
+
tokenizer = json.loads((source / "tokenizer.json").read_text(encoding="utf-8"))
|
| 43 |
+
token_config = json.loads((source / "tokenizer_config.json").read_text(encoding="utf-8"))
|
| 44 |
+
ids = [*tokenizer["model"]["vocab"].values(), *[t["id"] for t in tokenizer["added_tokens"]]]
|
| 45 |
+
if len(shape) != 2 or shape[1] != config["lm_config"]["hidden_size"]:
|
| 46 |
+
raise ValueError("Embedding shape does not match the model configuration.")
|
| 47 |
+
if min(ids) < 0 or max(ids) >= shape[0]:
|
| 48 |
+
raise ValueError("Tokenizer IDs exceed the checkpoint embedding capacity.")
|
| 49 |
+
config["lm_config"]["vocab_size"] = shape[0]
|
| 50 |
+
tokenizer["model"]["merges"] = [
|
| 51 |
+
" ".join(merge) if isinstance(merge, list) else merge
|
| 52 |
+
for merge in tokenizer["model"]["merges"]
|
| 53 |
+
]
|
| 54 |
+
token_config["added_tokens_decoder"] = {
|
| 55 |
+
str(t["id"]): {key: value for key, value in t.items() if key != "id"}
|
| 56 |
+
for t in tokenizer["added_tokens"]
|
| 57 |
+
}
|
| 58 |
+
special = {key: token_config[key] for key in ("bos_token", "eos_token", "unk_token")}
|
| 59 |
+
output.parent.mkdir(parents=True, exist_ok=True)
|
| 60 |
+
with tempfile.TemporaryDirectory(prefix="vocalrender-convert-", dir=output.parent) as temporary:
|
| 61 |
+
root = Path(temporary) / source.name
|
| 62 |
+
root.mkdir()
|
| 63 |
+
for name, data in (("config.json", config), ("tokenizer.json", tokenizer),
|
| 64 |
+
("tokenizer_config.json", token_config), ("special_tokens_map.json", special)):
|
| 65 |
+
(root / name).write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
| 66 |
+
checkpoint = torch.load(source / "audiovae.pth", map_location="cpu", weights_only=True)
|
| 67 |
+
state = checkpoint.get("state_dict", checkpoint)
|
| 68 |
+
tensors = {}
|
| 69 |
+
for name, tensor in state.items():
|
| 70 |
+
if not isinstance(name, str) or not isinstance(tensor, torch.Tensor):
|
| 71 |
+
raise ValueError("AudioVAE state_dict must contain named tensors only.")
|
| 72 |
+
tensors[name] = tensor.detach().cpu().contiguous()
|
| 73 |
+
if not tensors:
|
| 74 |
+
raise ValueError("AudioVAE checkpoint contains no tensors.")
|
| 75 |
+
vae = root / "audiovae.safetensors"
|
| 76 |
+
save_file(tensors, vae)
|
| 77 |
+
with safe_open(vae, framework="pt", device="cpu") as saved:
|
| 78 |
+
if set(saved.keys()) != set(tensors):
|
| 79 |
+
raise ValueError("AudioVAE tensor names changed during conversion.")
|
| 80 |
+
for name, expected in tensors.items():
|
| 81 |
+
actual = saved.get_tensor(name)
|
| 82 |
+
if actual.dtype != expected.dtype or actual.shape != expected.shape or not torch.equal(actual, expected):
|
| 83 |
+
raise ValueError(f"AudioVAE tensor changed during conversion: {name}")
|
| 84 |
+
command = [str(Path(converter).resolve()), "--family", "voxcpm2", "--type", "orig",
|
| 85 |
+
"--input", f"weights={weights}", "--input", f"audiovae_weights={vae}",
|
| 86 |
+
"--root", str(root), "--output", str(output), "--log"]
|
| 87 |
+
if args.model_spec:
|
| 88 |
+
command.extend(["--model-spec", str(args.model_spec.expanduser().resolve(strict=True))])
|
| 89 |
+
if args.overwrite:
|
| 90 |
+
command.append("--overwrite")
|
| 91 |
+
subprocess.run(command, check=True)
|
| 92 |
+
print(f"Converted: {output}")
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
if __name__ == "__main__":
|
| 96 |
+
main()
|
VoxCPM2-GGUF/finetune/vocalrender-pro-orig.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1c9148d67d3df67a0b04a8dbf9201d61bb7cac346bbfc9650b42f9d387a3df05
|
| 3 |
+
size 9544158368
|