Zyphra-ZONOS2-4bit / examples /batch_generate.py
amal-david's picture
Add example MLX scripts
9f49936 verified
Raw
History Blame Contribute Delete
2.87 kB
"""ZONOS2-4bit (MLX) — batched throughput runner (the lever for running many generations).
Loads the model once and runs many prompts through batch_generate in batches of B.
On an M4 Pro, B = 32-64 is the sweet spot (~3.5x real-time aggregate on real workloads);
throughput saturates past that as top-1 MoE routing hits more distinct experts.
Run:
python batch_generate.py --texts prompts.txt --batch_size 32
python batch_generate.py # built-in demo corpus
"""
from __future__ import annotations
import argparse, time
from pathlib import Path
import mlx.core as mx
from mlx_audio.tts import load
from mlx_audio.audio_io import write as audio_write
MODEL = "amal-david/Zyphra-ZONOS2-4bit"
FPS = 44100 / 512 # audio frames per second
def peak_gb():
try:
return mx.get_peak_memory() / 1e9
except Exception:
return float("nan")
def chunks(seq, n):
for i in range(0, len(seq), n):
yield i, seq[i:i + n]
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--model", default=MODEL)
ap.add_argument("--texts", help="file with one prompt per line")
ap.add_argument("--batch_size", type=int, default=32)
ap.add_argument("--max_tokens", type=int, default=1024)
ap.add_argument("--ref_audio", default=None, help="shared reference voice for all rows")
ap.add_argument("--seed", type=int, default=42)
ap.add_argument("--out", default="outputs/batch")
args = ap.parse_args()
texts = ([ln.strip() for ln in open(args.texts) if ln.strip()] if args.texts
else [f"This is benchmark sentence number {i+1}." for i in range(64)])
out = Path(args.out); out.mkdir(parents=True, exist_ok=True)
model = load(args.model, lazy=False)
spk = model.extract_speaker_embedding(args.ref_audio) if args.ref_audio else None
t0, total_frames = time.perf_counter(), 0
for base, batch in chunks(texts, args.batch_size):
t1 = time.perf_counter()
results = sorted(
model.batch_generate(batch, speaker_embedding=spk,
max_tokens=args.max_tokens, seed=args.seed),
key=lambda r: r.sequence_idx)
dt = time.perf_counter() - t1
bf = sum(int(r.token_count) for r in results)
total_frames += bf
for r in results:
audio_write(str(out / f"sample_{base + r.sequence_idx:04d}.wav"), r.audio, r.sample_rate)
print(f" batch[{base:4d}:{base+len(batch):4d}] wall={dt:6.2f}s "
f"aggregate_realtime_x={(bf/FPS)/dt:5.2f} peak={peak_gb():.1f}GB", flush=True)
wall = time.perf_counter() - t0
audio_s = total_frames / FPS
print(f"\n{len(texts)} clips, {audio_s:.1f}s audio in {wall:.1f}s "
f"=> {audio_s/wall:.2f}x real-time aggregate, peak={peak_gb():.1f}GB -> {out}")
if __name__ == "__main__":
main()