File size: 6,398 Bytes
241501a | 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 | """IndiCodec inference graph. No weight download.
Architecture classes still come from installed `neucodec` (Apache 2.0, commit ed3e6cd).
The release model.safetensors holds every tensor, including the 16 w2v-BERT 2.0 (MIT) layers we use, built from the
shipped w2v_bert/config.json. The internal .pt has no w2v-BERT, so that path still fetches it (pinned revision).
"""
from __future__ import annotations
import math
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
SEM_LAYER = 16
HOP16, HOP24 = 320, 480
LEVELS_S = [3] * 8
W2V = "facebook/w2v-bert-2.0"
W2V_REV = "da985ba0987f70aaeb84a80f2851cfac8c697a7b"
W2V_DIR = Path(__file__).resolve().parent / "w2v_bert" # config (16 layers) + preprocessor config, from W2V_REV
NEED_GEN = (
"fsq_s.", "fsq_a.", "backbone.", "head.",
"CodecEnc.", "SemanticEncoder_module.",
)
# training-only head (a semantic reconstruction loss); never runs at inference, dropped from the release file
TRAIN_ONLY = ("SemanticDecoder_module.",)
class IndiNet(nn.Module):
"""IndiCodec encode / decode only. Refuses phase2d, rope_fix, continuous, joint FSQ."""
def __init__(self, levels_a: list[int], w2v_bundled: bool = True):
super().__init__()
from transformers import Wav2Vec2BertConfig, Wav2Vec2BertModel
from vector_quantize_pytorch import FSQ
from neucodec.codec_encoder import CodecEncoder
from neucodec.codec_decoder_vocos import ISTFTHead, VocosBackbone
from neucodec.module import SemanticEncoder
self.w2v_bundled = bool(w2v_bundled)
if self.w2v_bundled: # weights come from the release file in load_gen
sm = Wav2Vec2BertModel(Wav2Vec2BertConfig.from_json_file(str(W2V_DIR / "config.json")))
else:
sm = Wav2Vec2BertModel.from_pretrained(W2V, revision=W2V_REV, output_hidden_states=True)
sm.encoder.layers = sm.encoder.layers[:SEM_LAYER]
if len(sm.encoder.layers) != SEM_LAYER:
raise SystemExit(f"w2v-BERT has {len(sm.encoder.layers)} layers, IndiCodec needs {SEM_LAYER}")
self.semantic_model = sm.eval().requires_grad_(False)
self.SemanticEncoder_module = SemanticEncoder(1024, 1024, 1024)
self.CodecEnc = CodecEncoder()
self.backbone = VocosBackbone(hidden_dim=1024, depth=12, heads=16, pos_meb_dim=64)
self.head = ISTFTHead(dim=1024, n_fft=HOP24 * 4, hop_length=HOP24, padding="same")
self.fsq_s = FSQ(levels=LEVELS_S, dim=1024)
self.levels_a = list(levels_a)
self.fsq_a = FSQ(levels=self.levels_a, dim=1024)
self.tail_frames = 0
def load_gen(self, gen: dict) -> None:
gen = {k: v for k, v in gen.items() if not k.startswith(TRAIN_ONLY)}
missing_pref = [p for p in NEED_GEN if not any(k.startswith(p) for k in gen)]
if missing_pref:
raise SystemExit(f"ckpt gen missing {missing_pref}; this infer needs the IndiCodec trained towers")
r = self.load_state_dict(gen, strict=False)
bad_u = [k for k in r.unexpected_keys if k.startswith("head.refine.")]
if bad_u:
raise RuntimeError(f"checkpoint has PhaseRefiner2D ({bad_u[0]}); IndiCodec infer refuses it")
bad_m = [m for m in r.missing_keys if self.w2v_bundled or not m.startswith("semantic_model.")]
if bad_m:
raise SystemExit(f"ckpt missing {len(bad_m)} keys, e.g. {bad_m[:5]}")
@torch.no_grad()
def semantic_target(self, feats: torch.Tensor) -> torch.Tensor:
return self.semantic_model(feats).hidden_states[SEM_LAYER].transpose(1, 2)
def towers(self, wav16: torch.Tensor, feats: torch.Tensor):
return self.semantic_target(feats), self.CodecEnc(wav16).transpose(1, 2)
def encode(self, wav16: torch.Tensor, feats: torch.Tensor):
sem, ac = self.towers(wav16, feats)
s_enc = self.SemanticEncoder_module(sem)
t = min(sem.shape[-1], ac.shape[-1], s_enc.shape[-1])
t -= t % 2
sem, ac, s_enc = sem[..., :t], ac[..., :t], s_enc[..., :t]
s25 = F.avg_pool1d(s_enc, 2, 2)
zq_s, idx_s = self.fsq_s(s25.transpose(1, 2))
c50 = zq_s.repeat_interleave(2, dim=1)
res = ac.transpose(1, 2) - c50.detach()
zq_a, idx_a = self.fsq_a(res)
return {"idx_s": idx_s, "idx_a": idx_a, "t": t}
def codes_to_latent(self, idx_s: torch.Tensor, idx_a: torch.Tensor | None) -> torch.Tensor:
c50 = self.fsq_s.indices_to_codes(idx_s).repeat_interleave(2, dim=1)
if idx_a is None:
return c50
za = self.fsq_a.indices_to_codes(idx_a)
t = min(c50.shape[1], za.shape[1])
return c50[:, :t] + za[:, :t]
def decode_latent(self, h: torch.Tensor) -> torch.Tensor:
return self.head(self.backbone(h))[0]
@torch.no_grad()
def decode_codes(self, idx_s, idx_a, mode: str = "full") -> torch.Tensor:
if mode == "zero_c":
return self.decode_latent(self.fsq_a.indices_to_codes(idx_a))
if mode == "zero_ac":
return self.decode_latent(self.codes_to_latent(idx_s, None))
return self.decode_latent(self.codes_to_latent(idx_s, idx_a))
_FE = None
def features(wav16_np_list):
global _FE
if _FE is None:
from transformers import AutoFeatureExtractor
_FE = AutoFeatureExtractor.from_pretrained(str(W2V_DIR)) # same file as W2V at W2V_REV
return _FE(wav16_np_list, sampling_rate=16_000, return_tensors="pt").input_features
def to_rate(w, src_sr, tgt):
from scipy.signal import resample_poly
if int(src_sr) == int(tgt):
return np.ascontiguousarray(w, dtype=np.float32)
g = math.gcd(int(src_sr), int(tgt))
return np.ascontiguousarray(resample_poly(np.asarray(w, np.float64), tgt // g, src_sr // g), dtype=np.float32)
def pad_w16(w16: np.ndarray, tail_frames: int) -> np.ndarray:
return np.pad(w16, (0, (-len(w16)) % HOP16 + int(tail_frames) * HOP16))
@torch.no_grad()
def encode_wav(model: IndiNet, wav, sr, device, amp: bool = True):
w16 = pad_w16(to_rate(wav, sr, 16000), getattr(model, "tail_frames", 0))
feats = features([w16]).to(device)
x = torch.from_numpy(w16)[None, None].to(device)
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=amp and device != "cpu"):
e = model.encode(x, feats)
return e["idx_s"], e["idx_a"], e["t"]
|