IndiCodec / indicodec /infer.py
prakharkhatri's picture
prakhar khatri
IndiCodec v1.0
241501a
Raw History Blame Contribute Delete
5.18 kB
"""IndiCodec inference. Self-contained; the release file needs no weight download.
from indicodec import IndiCodec
c = IndiCodec.load("model.safetensors") # release file; the internal .pt still loads
idx_s, idx_a = c.encode(wav, sr)
wav24 = c.decode(idx_s, idx_a, n_samples=n24)
wav24 = c.reconstruct(wav, sr)
"""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import numpy as np
import torch
_HERE = Path(__file__).resolve().parent
_SPEC = json.loads((_HERE / "codec_spec.json").read_text())
def md5_file(p: Path) -> str:
h = hashlib.md5()
with open(p, "rb") as f:
for b in iter(lambda: f.read(1 << 24), b""):
h.update(b)
return h.hexdigest()
def n24_of(wav, sr: int) -> int:
return int(round(len(wav) * _SPEC["sample_rate_out"] / sr))
def _refuse_train_flags(st: dict) -> None:
bad = []
if st.get("acoustic_continuous"):
bad.append("acoustic_continuous")
if int(st.get("acoustic_frames") or 1) != 1:
bad.append(f"acoustic_frames={st.get('acoustic_frames')}")
if st.get("rope_fix"):
bad.append("rope_fix")
if int(st.get("phase2d") or 0):
bad.append(f"phase2d={st.get('phase2d')}")
if bad:
raise SystemExit(f"IndiCodec infer refuses {bad}: not an IndiCodec release checkpoint")
def read_ckpt(p: Path) -> tuple[dict, str]:
"""md5-check a weight file against the spec and return ({"gen", "tail_frames", "levels_a", ...}, md5)."""
got = md5_file(p)
if p.suffix == ".safetensors": # release format (2026-10-25): tensors only, no pickle
want = _SPEC["md5_safetensors"]
if got != want:
raise SystemExit(f"{p} md5 {got} != spec {want}")
from safetensors import safe_open
with safe_open(str(p), framework="pt", device="cpu") as f:
meta = f.metadata() or {}
gen = {k: f.get_tensor(k) for k in f.keys()}
st = {"gen": gen, "meta": meta, "tail_frames": int(meta.get("tail_frames", 0)),
"levels_a": [int(x) for x in meta.get("levels_a", "").split(",") if x]}
else: # internal training checkpoint (pickle)
want = _SPEC["md5"]
if got != want:
raise SystemExit(f"{p} md5 {got} != spec {want}")
st = torch.load(p, map_location="cpu", weights_only=False)
return st, got
class IndiCodec:
def __init__(self, model, device: str, amp: bool, ckpt_md5: str):
self.model = model
self.device = device
self.amp = amp
self.ckpt_md5 = ckpt_md5
self.spec = _SPEC
@classmethod
def load(cls, ckpt: str | Path, device: str | None = None, amp: bool | None = None) -> "IndiCodec":
try:
from .model import IndiNet
except ImportError:
from model import IndiNet # folder-alone: infer.py + model.py side by side
p = Path(ckpt)
st, got = read_ckpt(p)
if int(st.get("tail_frames", 0)) != int(_SPEC["tail_frames"]):
raise SystemExit(f"ckpt tail_frames {st.get('tail_frames')} != spec {_SPEC['tail_frames']}")
if list(st.get("levels_a") or []) != list(_SPEC["acoustic"]["levels"]):
raise SystemExit(f"ckpt levels_a {st.get('levels_a')} != spec {_SPEC['acoustic']['levels']}")
_refuse_train_flags(st)
bundled = any(k.startswith("semantic_model.") for k in st["gen"]) # release file; the .pt has no w2v-BERT
m = IndiNet(levels_a=st.get("levels_a"), w2v_bundled=bundled)
m.load_gen(st["gen"])
m.tail_frames = int(st.get("tail_frames", 0))
if device is None:
device = "cuda" if torch.cuda.is_available() else "cpu"
m.to(device).eval()
if amp is None:
amp = device != "cpu"
return cls(m, device, amp, got)
def encode(self, wav, sr: int) -> tuple[np.ndarray, np.ndarray]:
try:
from .model import encode_wav
except ImportError:
from model import encode_wav
idx_s, idx_a, _ = encode_wav(self.model, wav, sr, self.device, amp=self.amp)
return idx_s.detach().cpu().numpy(), idx_a.detach().cpu().numpy()
@torch.no_grad()
def decode(self, idx_s, idx_a, n_samples: int | None = None) -> np.ndarray:
s = torch.as_tensor(idx_s, device=self.device)
a = torch.as_tensor(idx_a, device=self.device)
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=self.amp and self.device != "cpu"):
y = self.model.decode_codes(s, a, mode="full").float().cpu().numpy().reshape(-1)
if n_samples is None:
return y
return y[:n_samples] if len(y) >= n_samples else np.pad(y, (0, n_samples - len(y)))
def reconstruct(self, wav, sr: int) -> np.ndarray:
return self.decode(*self.encode(wav, sr), n_samples=n24_of(wav, sr))
def deterministic(self, on: bool = True) -> None:
if on:
os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8")
torch.backends.cudnn.deterministic = on
torch.backends.cudnn.benchmark = not on
torch.use_deterministic_algorithms(on, warn_only=True)