Spaces:
Running on Zero
Running on Zero
File size: 7,775 Bytes
c1b448c 4a9b62b c1b448c 4a9b62b c1b448c 4a9b62b c1b448c 4a9b62b c1b448c 4a9b62b c1b448c 4a9b62b c1b448c 4a9b62b c1b448c 57c4236 c1b448c 4a9b62b c1b448c 4a9b62b c1b448c 4a9b62b c1b448c 4a9b62b 57c4236 c1b448c 4a9b62b c1b448c | 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 | """Quantization, audio IO, objective metrics and plots for the FocalCodec lab.
Kept free of Gradio / torch.hub so it can be exercised standalone.
"""
from __future__ import annotations
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import soundfile as sf
import torch
from pystoi import stoi
from scipy.signal import resample_poly, stft
QUANT_LEVELS = ("int4", "int8", "int16", "int32", "fp4", "fp8", "fp16", "fp32")
TOKEN_RATE_HZ = 12.5
BITS_PER_TOKEN = 13
BITRATE_KBPS = TOKEN_RATE_HZ * BITS_PER_TOKEN / 1000.0
_BITS = {"int4": 4, "int8": 8, "int16": 16, "int32": 32, "fp4": 4, "fp8": 8, "fp16": 16, "fp32": 32}
_FP_FORMATS = {"fp4": (2, 1), "fp8": (4, 3)} # (exponent bits, mantissa bits) -> E2M1 / E4M3
_FP_MAX_NORM = {"fp4": 6.0, "fp8": 448.0} # E2M1 max; E4M3-FN finite max (top encoding is NaN)
def _ref_scale(w: torch.Tensor) -> torch.Tensor:
"""Per-output-channel absmax, per-tensor for 1-D params."""
if w.dim() > 1:
return w.abs().amax(dim=tuple(range(1, w.dim())), keepdim=True)
return w.abs().amax()
def int_scale(w: torch.Tensor, bits: int) -> torch.Tensor:
return (_ref_scale(w) / (1 << (bits - 1))).clamp_min(1e-12)
def int_levels(w: torch.Tensor, bits: int) -> torch.Tensor:
"""Signed integer levels before rescaling (what an int-format export stores)."""
qmax = 1 << (bits - 1)
return (w / int_scale(w, bits)).round().clamp(-qmax, qmax - 1)
def fp_scale(w: torch.Tensor, level: str) -> torch.Tensor:
return (_ref_scale(w) / _FP_MAX_NORM[level]).clamp_min(1e-12)
def fp_levels(w: torch.Tensor, level: str) -> torch.Tensor:
"""Values on the format's own grid (what a float-format export stores, unscaled)."""
e_bits, m_bits = _FP_FORMATS[level]
bias = (1 << (e_bits - 1)) - 1
max_exp = (1 << e_bits) - 1 - bias
x = w / fp_scale(w, level)
ax = x.abs()
exp = torch.floor(torch.log2(ax.clamp_min(1e-30))).clamp(1 - bias, max_exp)
step = torch.pow(2.0, exp - m_bits)
return torch.sign(x) * ((ax / step).round() * step)
def _quant_int(w: torch.Tensor, bits: int) -> torch.Tensor:
if bits >= 32:
return w
return int_levels(w, bits) * int_scale(w, bits)
def _quant_fp(w: torch.Tensor, level: str) -> torch.Tensor:
return fp_levels(w, level) * fp_scale(w, level)
def quantize_state(state: dict, level: str) -> dict:
"""Fake-quantize every float tensor in `state` to `level` (per-channel absmax scaling)."""
if level == "fp32":
return dict(state)
out = {}
for key, value in state.items():
if not value.is_floating_point():
out[key] = value
elif level == "fp16":
out[key] = value.half().float()
elif level.startswith("int"):
out[key] = _quant_int(value, int(level[3:]))
else:
out[key] = _quant_fp(value, level)
return out
def model_size_mb(state: dict, level: str) -> float:
"""Stored size of `state` at `level`; non-float buffers keep their own width."""
return sum(
v.numel() * (v.element_size() if not v.is_floating_point() else _BITS[level] / 8)
for v in state.values()
) / 1e6
def load_audio(path: str, target_sr: int, max_seconds: float | None = None) -> tuple[np.ndarray, int]:
"""Read `path` as mono float32, resample to `target_sr`, optionally truncate."""
x, sr = sf.read(path, dtype="float32", always_2d=True)
x = x.mean(axis=1)
if sr != target_sr:
g = np.gcd(sr, target_sr)
x = resample_poly(x, target_sr // g, sr // g).astype(np.float32)
sr = target_sr
if max_seconds is not None and len(x) > int(max_seconds * sr):
x = x[: int(max_seconds * sr)]
return x, sr
def si_sdr(ref: np.ndarray, rec: np.ndarray) -> float:
ref = ref - ref.mean()
rec = rec - rec.mean()
denom = float(ref @ ref) + 1e-12
target = (float(rec @ ref) / denom) * ref
noise = rec - target
return float(10 * np.log10((float(target @ target) + 1e-12) / (float(noise @ noise) + 1e-12)))
def log_spectral_distance(ref: np.ndarray, rec: np.ndarray, sr: int) -> float:
"""Frame-averaged log-spectral distance (dB) between reference and reconstruction."""
_, _, R = stft(ref, fs=sr, nperseg=1024, noverlap=768)
_, _, S = stft(rec, fs=sr, nperseg=1024, noverlap=768)
diff = 20 * np.log10(np.abs(R) + 1e-8) - 20 * np.log10(np.abs(S) + 1e-8)
return float(np.mean(np.sqrt(np.mean(diff**2, axis=0))))
def evaluate(
ref: np.ndarray, rec: np.ndarray, sr: int, rtf: float, size_mb: float, level: str,
bitrate_kbps: float | None = None,
) -> dict:
n = min(len(ref), len(rec))
ref, rec = ref[:n], rec[:n]
if bitrate_kbps is None:
bitrate_kbps = BITRATE_KBPS
return {
"Level": level,
"SI-SDR (dB)": round(si_sdr(ref, rec), 2),
"STOI": round(float(stoi(ref, rec, sr, extended=False)), 3),
"LSD (dB)": round(log_spectral_distance(ref, rec, sr), 2),
"Model size (MB)": round(size_mb, 1),
"Bitrate (kbps)": round(bitrate_kbps, 4),
"RTF": round(rtf, 3),
}
def spectrogram_figure(ref: np.ndarray, rec: np.ndarray, sr: int, title: str):
fig, axes = plt.subplots(1, 2, figsize=(9.0, 2.6), sharey=True)
for ax, sig, label in zip(axes, (ref, rec), ("Input", "Reconstruction")):
ax.specgram(sig, NFFT=1024, noverlap=768, Fs=sr, cmap="magma")
ax.set_title(label, fontsize=9)
ax.set_xlabel("Time (s)")
axes[0].set_ylabel("Frequency (Hz)")
fig.suptitle(title, fontsize=10)
fig.tight_layout()
return fig
def metrics_figure(rows: list[dict]):
levels = [r["Level"] for r in rows]
panels = (("SI-SDR (dB)", "#4c72b0"), ("STOI", "#55a868"), ("Model size (MB)", "#c44e52"))
fig, axes = plt.subplots(1, len(panels), figsize=(11.5, 2.9))
for ax, (key, color) in zip(axes, panels):
ax.bar(levels, [r[key] for r in rows], color=color)
ax.set_title(key, fontsize=9)
ax.tick_params(axis="x", rotation=45)
ax.grid(axis="y", alpha=0.3)
fig.tight_layout()
return fig
if __name__ == "__main__":
torch.manual_seed(0)
w = (torch.randn(64, 32) * 0.05).requires_grad_(False)
for level in QUANT_LEVELS:
q = quantize_state({"w": w}, level)["w"]
assert torch.isfinite(q).all(), f"{level} not finite"
assert q.abs().max() <= w.abs().max() * 1.01 + 1e-9, f"{level} broke the magnitude bound"
# the packed levels a real export stores must reproduce the fake-quant demo exactly
assert torch.equal(int_levels(w, 8) * int_scale(w, 8), quantize_state({"w": w}, "int8")["w"])
assert torch.equal(fp_levels(w, "fp8") * fp_scale(w, "fp8"), quantize_state({"w": w}, "fp8")["w"])
# fp8 grid values are exactly float8_e4m3fn-representable, so no precision is lost on store
grid8 = fp_levels(w, "fp8")
assert torch.equal(grid8.to(torch.float8_e4m3fn).float(), grid8), "fp8 grid escapes E4M3-FN"
grid4 = fp_levels(w, "fp4")
assert grid4.abs().max() <= _FP_MAX_NORM["fp4"] * 1.0000001, "fp4 grid escapes E2M1"
assert model_size_mb({"w": w}, "fp32") == w.numel() * 4 / 1e6
assert model_size_mb({"i": torch.zeros(8, dtype=torch.int64)}, "int4") == 8 * 8 / 1e6 # buffers keep 8 bytes
x = np.random.randn(16000).astype(np.float32)
assert si_sdr(x, x) > 60, "SI-SDR of identical signals should be large"
assert si_sdr(x, 3 * x) > 60, "SI-SDR must be scale invariant"
assert abs(evaluate(x, x, 16000, 1.0, 1.0, "fp32")["STOI"] - 1.0) < 1e-3
assert evaluate(x, x, 16000, 1.0, 1.0, "fp32")["Bitrate (kbps)"] == round(BITRATE_KBPS, 4)
assert evaluate(x, x, 16000, 1.0, 1.0, "lscodec-25hz", 0.25)["Bitrate (kbps)"] == 0.25
print("core self-check ok")
|