ZipVoice.AXERA / cpp /scripts /generate_vocoder_calib.py
HY-2012's picture
Upload the cpp version
92264aa verified
Raw
History Blame Contribute Delete
5.62 kB
#!/usr/bin/env python3
"""
Generate vocoder calibration data on dev machine (no axengine needed).
Collects mel features from:
1. Real mel output files (output_mel.bin from previous C++ runs)
2. Random mel features in the expected value range
3. Zero/silence mel features
Then runs them through ONNX to get intermediate features for head_linear calibration.
Usage:
python3 generate_vocoder_calib.py --output-dir ./calib_data_vocoder
"""
import sys, os, argparse, json, glob
from pathlib import Path
import numpy as np
import onnxruntime as ort
SCRIPT_DIR = Path(__file__).resolve().parent
REPO_DIR = SCRIPT_DIR.parent.parent # scripts/ → cpp/ → repo root
ONNX_DIR = SCRIPT_DIR.parent / "vocoder_onnx" # cpp/vocoder_onnx
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--output-dir", default=str(ONNX_DIR / "calib_data"))
parser.add_argument("--num-samples", type=int, default=16)
parser.add_argument("--seed", type=int, default=42)
args = parser.parse_args()
out_dir = Path(args.output_dir)
out_dir.mkdir(parents=True, exist_ok=True)
rng = np.random.RandomState(args.seed)
T = 620
mel_samples = []
# --- Source 1: Real mel features if available (undo feat_scale to match vocoder input) ---
feat_scale = 0.1
for pattern in ["output_mel.bin", "output_mel_debug.bin"]:
for p in [REPO_DIR / pattern, SCRIPT_DIR / pattern]:
if p.exists():
mel = np.fromfile(str(p), dtype=np.float32).reshape(-1, 100).T[np.newaxis, :, :] / feat_scale # [1, 100, frames]
gen_frames = min(mel.shape[2], T)
padded = np.zeros((1, 100, T), dtype=np.float32)
padded[0, :, :gen_frames] = mel[0, :, :gen_frames]
mel_samples.append(padded)
print(f"Real mel from {p}: shape={mel.shape}")
# --- Source 2: Random mel in vocoder input range (after /feat_scale) ---
# Python: features / 0.1 before vocoder → range ~[-8.6, 4.0]
# So calibration mel should be in [feat_scaled] range, divided by feat_scale
# to match the actual vocoder input distribution
feat_scale = 0.1
for i in range(max(0, args.num_samples - len(mel_samples))):
length = rng.randint(100, T + 1)
# Generate in feat_scaled range, then undo feat_scale
mel = (rng.randn(1, 100, T).astype(np.float32) * 0.3 - 0.04) / feat_scale
mel[0, :, length:] = 0.0
mel_samples.append(mel)
# --- Source 3: Edge cases (with feat_scale undo) ---
mel_samples.append(np.zeros((1, 100, T), dtype=np.float32))
mel_samples.append(np.ones((1, 100, T), dtype=np.float32) * 0.5 / feat_scale)
mel_samples.append(rng.randn(1, 100, T).astype(np.float32) * 0.1 / feat_scale)
mel_samples.append(rng.randn(1, 100, T).astype(np.float32) / feat_scale)
print(f"\nTotal mel samples: {len(mel_samples)}")
# --- Run ONNX to get backbone outputs (for head_linear calibration) ---
bb_path = ONNX_DIR / "vocos_backbone_B1_T620.onnx"
hl_path = ONNX_DIR / "vocos_head_linear_B1_T620.onnx"
if not bb_path.exists():
print(f"ERROR: {bb_path} not found. Run export_vocos_onnx.py first.")
return
sess_bb = ort.InferenceSession(str(bb_path))
print(f"Loaded backbone: {bb_path}")
# --- Save backbone calibration data ---
bb_dir = out_dir / "vocos_backbone" / "mel"
bb_dir.mkdir(parents=True, exist_ok=True)
bb_entries = []
backbone_outputs = []
for i, mel in enumerate(mel_samples):
np.save(bb_dir / f"{i:04d}.npy", mel)
bb_entries.append({"file": f"mel/{i:04d}.npy", "shape": list(mel.shape)})
# Run ONNX to get head_linear input
feat = sess_bb.run(None, {'mel': mel})[0]
backbone_outputs.append(feat)
print(f"Backbone calibration: {len(bb_entries)} samples")
# --- Save head_linear calibration data ---
hl_dir = out_dir / "vocos_head_linear" / "features"
hl_dir.mkdir(parents=True, exist_ok=True)
hl_entries = []
for i, feat in enumerate(backbone_outputs):
np.save(hl_dir / f"{i:04d}.npy", feat)
hl_entries.append({"file": f"features/{i:04d}.npy", "shape": list(feat.shape)})
print(f"Head_linear calibration: {len(hl_entries)} samples")
# --- Verify head_linear ONNX with calibration data ---
if hl_path.exists():
sess_hl = ort.InferenceSession(str(hl_path))
for i in range(min(3, len(backbone_outputs))):
r, im = sess_hl.run(None, {'features': backbone_outputs[i]})
print(f" Verify head[{i}]: real range=[{r.min():.3f},{r.max():.3f}], "
f"imag range=[{im.min():.3f},{im.max():.3f}]")
# --- Manifest ---
manifest = {
"description": "Vocoder ONNX calibration data",
"backbone": {
"model": "vocos_backbone_B1_T620.onnx",
"input": "mel", "shape": [1, 100, 620], "dtype": "float32",
"num_samples": len(bb_entries),
"files": bb_entries,
},
"head_linear": {
"model": "vocos_head_linear_B1_T620.onnx",
"input": "features", "shape": [1, 620, 512], "dtype": "float32",
"num_samples": len(hl_entries),
"files": hl_entries,
},
}
with open(out_dir / "calib_manifest.json", "w") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
print(f"\nDone! Output: {out_dir}")
print(f" vocos_backbone/mel/ : {len(bb_entries)} .npy files")
print(f" vocos_head_linear/features/: {len(hl_entries)} .npy files")
if __name__ == "__main__":
main()