fish-audio-training-scripts / train_s2_pro_hebrew.py
sm3222's picture
Upload train_s2_pro_hebrew.py with huggingface_hub
92e1e5f verified
Raw
History Blame Contribute Delete
5.3 kB
# /// script
# dependencies = ["huggingface_hub", "datasets==2.18.0", "soundfile", "tqdm"]
# ///
"""
Fish Audio S2 Pro Hebrew LoRA fine-tuning on HF Jobs.
Usage: hf jobs uv run --flavor a100-large --timeout 6h --secrets HF_TOKEN https://hf.co/sm3222/fish-audio-training-scripts/resolve/main/train_s2_pro_hebrew.py
"""
import os
import shutil
import subprocess
import sys
import json
from datetime import datetime
from pathlib import Path
HF_TOKEN = os.environ["HF_TOKEN"]
OUTPUT_REPO = os.environ.get("OUTPUT_REPO", "sm3222/fish-audio-s2-pro-hebrew-lora")
BASE_MODEL = "fishaudio/s2-pro"
DATASET_ID = "shunyalabs/hebrew-speech-dataset"
WORKDIR = Path("/workspace")
DATA_DIR = WORKDIR / "data"
CKPT_DIR = WORKDIR / "checkpoints" / "s2-pro"
RESULTS_DIR = WORKDIR / "results"
FISH_DIR = WORKDIR / "fish-speech"
LOGS_DIR = WORKDIR / "logs"
for d in [DATA_DIR, CKPT_DIR, RESULTS_DIR, LOGS_DIR]:
d.mkdir(parents=True, exist_ok=True)
def log(msg):
print(f"[{datetime.now():%H:%M:%S}] {msg}", flush=True)
def run(cmd, **kwargs):
log(f"$ {' '.join(str(p) for p in cmd)}")
subprocess.run(cmd, check=True, text=True,
cwd=kwargs.pop("cwd", WORKDIR), **kwargs)
# ── 0. Clone fish-speech ────────────────────────────────────────────
log("=== 0/6 Clone fish-speech ===")
if not FISH_DIR.exists():
run(["git", "clone", "https://github.com/fishaudio/fish-speech.git", str(FISH_DIR)])
run(["uv", "sync", "--python", "3.12", "--extra", "cu129"], cwd=FISH_DIR)
# ── 1. Download base model ──────────────────────────────────────────
log("=== 1/6 Download S2 Pro base model ===")
run(["hf", "download", "--token", HF_TOKEN, "--local-dir", str(CKPT_DIR), BASE_MODEL],
cwd=FISH_DIR)
# ── 2. Download & prepare dataset ───────────────────────────────────
log("=== 2/6 Prepare Hebrew dataset ===")
run([sys.executable, "-c", f"""
from datasets import load_dataset
import soundfile as sf
import numpy as np
from pathlib import Path
ds = load_dataset("{DATASET_ID}", split="train", token="{HF_TOKEN}")
out = Path("{DATA_DIR}") / "speaker_0"
out.mkdir(parents=True, exist_ok=True)
for i, row in enumerate(ds):
audio = row["audio"]["array"]
sr = row["audio"]["sampling_rate"]
text = row["transcript"]
path = out / f"{{i:05d}}"
sf.write(str(path) + ".wav", audio, sr)
path.with_suffix(".lab").write_text(text, encoding="utf-8")
if (i + 1) % 1000 == 0:
print(f" {{i+1}}/{{len(ds)}}")
print(f"Done: {{len(ds)}} pairs")
"""], cwd=FISH_DIR)
# ── 3. Extract VQGAN tokens ─────────────────────────────────────────
log("=== 3/6 Extract VQGAN semantic tokens ===")
run([
"uv", "run", "python", "tools/vqgan/extract_vq.py",
str(DATA_DIR), "--num-workers", "4", "--batch-size", "16",
"--checkpoint-path", str(CKPT_DIR / "codec.safetensors"),
], cwd=FISH_DIR)
# ── 4. Pack protobuf ────────────────────────────────────────────────
log("=== 4/6 Pack protobuf ===")
run([
"uv", "run", "python", "tools/llama/build_dataset.py",
"--input", str(DATA_DIR),
"--output", str(DATA_DIR / "protos"),
"--text-extension", ".lab", "--num-workers", "4",
], cwd=FISH_DIR)
# ── 5. LoRA fine-tune ──────────────────────────────────────────────
log("=== 5/6 LoRA fine-tuning ===")
os.environ["HYDRA_FULL_ERROR"] = "1"
run([
"uv", "run", "python", "fish_speech/train.py",
f"--config-name=text2semantic_finetune",
f"project=hebrew_s2_pro_lora",
f"pretrained_ckpt_path={CKPT_DIR}",
f"train_dataset.proto_files=[{DATA_DIR / 'protos'}]",
f"val_dataset.proto_files=[{DATA_DIR / 'protos'}]",
"+lora@model.model.lora_config=r_8_alpha_16",
"trainer.max_steps=5000",
"trainer.accumulate_grad_batches=4",
"data.batch_size=2",
"data.num_workers=4",
], cwd=FISH_DIR)
# ── 6. Merge LoRA + push to Hub ─────────────────────────────────────
log("=== 6/6 Merge LoRA + push ===")
ckpt_dir = WORKDIR / "hebrew_s2_pro_lora" / "checkpoints"
checkpoints = sorted(ckpt_dir.glob("step_*.ckpt"))
if not checkpoints:
raise FileNotFoundError(f"No checkpoints in {ckpt_dir}")
best = checkpoints[-2] if len(checkpoints) > 1 else checkpoints[0]
log(f"Merging: {best.name}")
merged = RESULTS_DIR / "merged"
run([
"uv", "run", "python", "tools/llama/merge_lora.py",
"--lora-config", "r_8_alpha_16",
"--base-weight", str(CKPT_DIR),
"--lora-weight", str(best),
"--output", str(merged),
], cwd=FISH_DIR)
from huggingface_hub import HfApi
api = HfApi(token=HF_TOKEN)
api.create_repo(repo_id=OUTPUT_REPO, repo_type="model", exist_ok=True)
api.upload_folder(folder_path=str(merged), repo_id=OUTPUT_REPO,
repo_type="model", ignore_patterns=[".*"])
log(f"βœ“ {OUTPUT_REPO}")