File size: 7,163 Bytes
d484ff4 | 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | """
eval_codebook_depth.py
Diagnostic: generate with different codebook depths to isolate where musical
coherence breaks down.
n_cb=2 → melody only (cb0+cb1)
n_cb=4 → melody + harmony
n_cb=8 → full (current default)
Also tests shorter durations to stay within the trained context window.
Outputs saved to outputs_cb_depth/.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import torch
_REPO_ROOT = Path(__file__).resolve().parent
if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT))
from sangeet.audio.postprocess import postprocess_wav
from sangeet.config import resolve_path
from sangeet.data.dataset import TokenSpec, token_ids_to_codes
from sangeet.data.vocab import load_vocab
from sangeet.model.transformer_lm import CarnaticLMConfig, CarnaticTransformerLM
from sangeet.tokenizer.encodec_codec import (
EncodecConfig,
decode_codes_to_wav,
load_encodec_model,
)
from sangeet.utils.runtime import find_repo_root
# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------
CKPT_PATH = Path("runs/hindustani_cfg/checkpoints/latest.pt") # step_155000
OUTPUT_DIR = Path("outputs_cb_depth")
CONDITIONING = {
"raga": "Kalyāṇ", # "Yaman" is not in vocab; Kalyāṇ is the closest match
"tala": "Tīntāl",
"artist": "unknown",
"text": "",
}
# (label, duration_sec, n_codebooks_to_use)
# n_codebooks_to_use: generate this many, zero-fill the rest in the decoder
EXPERIMENTS = [
("6s_cb2", 6.0, 2), # melody only, short — well within context
("6s_cb4", 6.0, 4), # melody+harmony, short
("6s_cb8", 6.0, 8), # full, short
("10s_cb4", 10.0, 4), # melody+harmony, medium
("10s_cb8", 10.0, 8), # full, medium
("20s_cb4", 20.0, 4), # melody+harmony, long (current default)
("20s_cb8", 20.0, 8), # full long — baseline (should match cfg_150k)
]
# Sampling — best preset from eval_cfg sweep
TEMPERATURE = 0.75
TOP_P = 0.9
CFG_SCALE = 5.0
# Inverted CB scales: cb0 tight, cb4-cb7 hot
CB_TEMPERATURE_SCALES = [0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3]
POST_HF_CUTOFF_HZ = 10_000.0
POST_TARGET_LUFS = -14.0
POST_PEAK_DB = -1.0
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _safe_encode(vocab, key: str) -> int:
if key in vocab.stoi:
return vocab.encode(key)
return vocab.encode("unknown")
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> None:
repo_root = find_repo_root()
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
ckpt_path = repo_root / CKPT_PATH
ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False)
run_cfg = ckpt["cfg"]
token_meta = ckpt["token_meta"]
token_spec = TokenSpec(
n_codebooks=int(token_meta["n_codebooks"]),
codebook_size=int(token_meta["codebook_size"]),
)
vocabs_dir = Path(run_cfg.get("data", {}).get("vocabs_dir", ""))
if not vocabs_dir.is_absolute():
vocabs_dir = repo_root / vocabs_dir
if not vocabs_dir.exists():
vocabs_dir = ckpt_path.parent.parent / "vocabs"
raga_vocab = load_vocab(vocabs_dir / "raga.json")
tala_vocab = load_vocab(vocabs_dir / "tala.json")
artist_vocab = load_vocab(vocabs_dir / "artist.json")
mcfg = CarnaticLMConfig(
d_model=int(run_cfg["model"]["d_model"]),
n_layers=int(run_cfg["model"]["n_layers"]),
n_heads=int(run_cfg["model"]["n_heads"]),
dropout=float(run_cfg["model"].get("dropout", 0.1)),
ff_mult=int(run_cfg["model"].get("ff_mult", 4)),
cross_attention=bool(run_cfg["model"].get("cross_attention", True)),
max_seq_len=int(run_cfg["model"].get("max_seq_len", 4096)),
)
model = CarnaticTransformerLM(
mcfg,
token_spec=token_spec,
raga_vocab_size=raga_vocab.size,
tala_vocab_size=tala_vocab.size,
artist_vocab_size=artist_vocab.size,
)
model.load_state_dict(ckpt["model"], strict=False)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
model.eval()
raga_id = _safe_encode(raga_vocab, CONDITIONING["raga"])
tala_id = _safe_encode(tala_vocab, CONDITIONING["tala"])
artist_id = _safe_encode(artist_vocab, CONDITIONING["artist"])
frame_rate = float(token_meta.get("frame_rate", 75.0))
n_cb_full = int(token_meta["n_codebooks"])
cb_size = int(token_meta["codebook_size"])
sample_rate = int(token_meta["encodec_sample_rate"])
bandwidth = float(token_meta["encodec_bandwidth"])
enc_cfg = EncodecConfig(model="24khz", bandwidth=bandwidth, device=str(device), use_normalize=False)
enc_model = load_encodec_model(enc_cfg)
total = len(EXPERIMENTS)
for i, (label, duration_sec, n_cb_use) in enumerate(EXPERIMENTS, 1):
out_wav = OUTPUT_DIR / f"{label}.wav"
n_frames = max(1, int(duration_sec * frame_rate))
tokens_in_context = n_frames * n_cb_full
print(
f"\n[{i}/{total}] {label} "
f"({duration_sec}s, {n_frames} frames, {n_cb_use}/{n_cb_full} codebooks, "
f"{tokens_in_context} tokens in context)"
)
cb_scales = CB_TEMPERATURE_SCALES[:n_cb_full]
with torch.inference_mode():
token_ids = model.generate(
raga_id=raga_id,
tala_id=tala_id,
artist_id=artist_id,
n_frames=n_frames,
temperature=TEMPERATURE,
top_p=TOP_P,
cfg_scale=CFG_SCALE,
cb_temperature_scales=cb_scales,
device=device,
)
# Convert to codes array [n_cb_full, n_frames]
codes_full = token_ids_to_codes(
token_ids.detach().cpu().numpy().astype(np.int64),
token_spec,
)
# Zero-fill codebooks beyond n_cb_use
if n_cb_use < n_cb_full:
codes_full[n_cb_use:, :] = 0
print(f" Zero-filled cb{n_cb_use}–cb{n_cb_full - 1}")
decode_codes_to_wav(enc_model, codes=codes_full, out_wav_path=out_wav, sample_rate=sample_rate)
postprocess_wav(out_wav, out_wav, hf_cutoff_hz=POST_HF_CUTOFF_HZ, target_lufs=POST_TARGET_LUFS, peak_db=POST_PEAK_DB)
print(f"[OK] {out_wav}")
print(f"\n[DONE] {total} files in {OUTPUT_DIR}/")
print("""
Listen in this order:
6s_cb2 → if this has melody: model works, problem is high codebooks
6s_cb4 → does adding cb2+cb3 help or hurt?
6s_cb8 → full model, short — compare to 20s_cb8
10s_cb4 → does coherence hold over 10s with 4 codebooks?
20s_cb4 → best candidate for usable output right now
20s_cb8 → your current 'static' baseline
""")
if __name__ == "__main__":
main()
|