ALWOAL commited on
Commit
57c4236
·
verified ·
1 Parent(s): 4a9b62b

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. README.md +9 -7
  2. app.py +60 -47
  3. core.py +7 -1
README.md CHANGED
@@ -26,20 +26,22 @@ The token stream is untouched, so the codec still runs at **0.1625 kbps**
26
 
27
  | Tab | What it shows |
28
  | --- | --- |
29
- | **Compare** | One reconstructed clip + input/reconstruction spectrograms, side by side for all 8 levels |
30
- | **Playground** | Encode → decode a single clip at one chosen level, with its metrics row |
31
- | **Benchmark** | SI-SDR, STOI, LSD, stored model size, bitrate and real-time factor for all 8 levels, plus an `lscodec-25hz` baseline row and a summary chart |
32
 
33
  ## Method
34
 
35
  Post-training **fake quantization** of every floating-point tensor: per-output-channel
36
  absmax scale, round-to-nearest in the target format, dequantize back to fp32, then run
37
- the untouched FocalCodec forward pass. `fp4` is IEEE-style E2M1, `fp8` is E4M3, `fp16`
38
  and `fp32` are exact casts. Nothing is fine-tuned or calibrated on data.
39
 
40
- The Benchmark tab also runs [LSCodec-25 Hz](https://github.com/X-LANCE/LSCodec-Inference)
41
- (self-prompted with WavLM-Large) as a second codec for reference: it is a different model
42
- at a different bitrate (0.25 kbps vs 0.1625 kbps), so read it as a floor, not a control.
 
 
43
 
44
  ⚠️ **On SI-SDR:** it is deeply negative at every level, including `fp32`. That is
45
  expected — at 0.16 kbps FocalCodec does not preserve waveform phase, and the
 
26
 
27
  | Tab | What it shows |
28
  | --- | --- |
29
+ | **Compare** | One reconstructed clip + input/reconstruction spectrograms, side by side for all 8 FocalCodec levels |
30
+ | **Playground** | Encode → decode a single clip at one chosen codec + weight format, with its metrics row |
31
+ | **Benchmark** | SI-SDR, STOI, LSD, stored model size, bitrate and real-time factor for all 16 levels — 8 FocalCodec + 8 LSCodec — plus a summary chart |
32
 
33
  ## Method
34
 
35
  Post-training **fake quantization** of every floating-point tensor: per-output-channel
36
  absmax scale, round-to-nearest in the target format, dequantize back to fp32, then run
37
+ the untouched codec forward pass. `fp4` is IEEE-style E2M1, `fp8` is E4M3, `fp16`
38
  and `fp32` are exact casts. Nothing is fine-tuned or calibrated on data.
39
 
40
+ The same sweep runs on a second codec, [LSCodec-25 Hz](https://github.com/X-LANCE/LSCodec-Inference),
41
+ whose rows are prefixed `lscodec-`: its encoder, vocoder and codebook are quantized
42
+ identically, while the WavLM-Large features it self-prompts with are not. It is a
43
+ different model at a different bitrate (0.25 kbps vs 0.1625 kbps), so read it as a
44
+ floor, not a control.
45
 
46
  ⚠️ **On SI-SDR:** it is deeply negative at every level, including `fp32`. That is
47
  expected — at 0.16 kbps FocalCodec does not preserve waveform phase, and the
app.py CHANGED
@@ -16,9 +16,7 @@ from core import QUANT_LEVELS
16
  MODEL_REPO = "lucadellalib/focalcodec_12_5hz"
17
  HUB_REPO = "lucadellalib/focalcodec"
18
  LS_REPO = "cantabile-kwok/lscodec_25hz"
19
- LS_LEVEL = "lscodec-25hz"
20
- LS_SIZE_MB = 196.3 # encoder 70.4 + vocoder 125.6 + codebook 0.3 MB
21
- LS_BITRATE_KBPS = 25 * 10 / 1000 # 25 tokens/s x 10-bit codebook
22
  MAX_SECONDS = 20
23
  HEADERS = ["Level", "SI-SDR (dB)", "STOI", "LSD (dB)", "Model size (MB)", "Bitrate (kbps)", "RTF"]
24
  DATATYPES = ["str", "number", "number", "number", "number", "number", "number"]
@@ -27,6 +25,7 @@ EXAMPLES = [
27
  ["assets/p226_006.wav"],
28
  ["assets/5_1812_20170628135856.wav"],
29
  ]
 
30
 
31
  codec = torch.hub.load(HUB_REPO, "focalcodec", config=MODEL_REPO, trust_repo=True)
32
  codec.eval().requires_grad_(False)
@@ -84,6 +83,9 @@ def _boot_lscodec() -> dict | None:
84
  "dec": dec,
85
  "books": books,
86
  "wavlm": wavlm,
 
 
 
87
  "repeat": bool(vocoder_config.get("repeat_input_tokens")),
88
  "out_sr": int(vocoder_config["sampling_rate"]),
89
  }
@@ -95,15 +97,20 @@ except Exception as exc: # noqa: BLE001 a third-party dep breaking must not ta
95
  LS = None
96
  print(f"[lscodec] baseline disabled: {exc!r}")
97
 
98
- BENCH_LEVELS = QUANT_LEVELS + ((LS_LEVEL,) if LS is not None else ())
 
99
 
100
 
101
- def _lscodec_decode(ref: np.ndarray, sr: int) -> np.ndarray:
102
- """Self-prompted LSCodec-25 Hz reconstruction, resampled back to `sr`."""
103
- audio = torch.from_numpy(ref).float().view(1, -1).cuda()
104
  with torch.inference_mode():
105
  prompt_input = torch.nn.functional.layer_norm(audio, audio.shape) # fairseq WavLM cfg.normalize
106
- prompt = LS["wavlm"](prompt_input, output_hidden_states=True).hidden_states[6]
 
 
 
 
 
107
  _, _, index = LS["enc"].encode(audio.unsqueeze(0))
108
  if LS["repeat"]:
109
  index = index.repeat_interleave(2, dim=0)
@@ -116,13 +123,25 @@ def _lscodec_decode(ref: np.ndarray, sr: int) -> np.ndarray:
116
  return rec
117
 
118
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  def _estimate_duration(audio_path: str, levels: tuple) -> float:
120
  try:
121
  seconds = min(sf.info(audio_path).duration, MAX_SECONDS)
122
  except Exception: # noqa: BLE001 unreadable input falls back to the full cap
123
  seconds = float(MAX_SECONDS)
124
  # ZeroGPU rejects a request above the visitor's tier cap, so never ask for more than 60 s.
125
- return min(60.0, 4.0 + 2.0 * len(levels) + 2.0 * seconds + (8.0 if LS_LEVEL in levels else 0.0))
126
 
127
 
128
  @spaces.GPU(duration=_estimate_duration)
@@ -132,6 +151,7 @@ def _encode_decode(audio_path: str, levels: tuple) -> dict:
132
  seconds = len(ref) / sr
133
  rows, reconstructed = [], {}
134
  focal_levels = tuple(level for level in levels if level in QUANT_LEVELS)
 
135
  if focal_levels:
136
  sig = torch.from_numpy(ref).unsqueeze(0).cuda()
137
  with torch.inference_mode(): # untimed warmup so the first row's RTF isn't inflated
@@ -147,15 +167,22 @@ def _encode_decode(audio_path: str, levels: tuple) -> dict:
147
  rows.append(core.evaluate(ref, rec, sr, rtf, SIZES[level], level))
148
  reconstructed[level] = (sr, rec)
149
  codec.load_state_dict(BASE_STATE)
150
- if LS_LEVEL in levels:
151
- with torch.inference_mode(): # untimed warmup, as above
152
- _lscodec_decode(ref, sr)
153
- torch.cuda.synchronize()
154
- start = time.perf_counter()
155
- rec = _lscodec_decode(ref, sr)
156
- torch.cuda.synchronize()
157
- rtf = (time.perf_counter() - start) / seconds
158
- rows.append(core.evaluate(ref, rec, sr, rtf, LS_SIZE_MB, LS_LEVEL, LS_BITRATE_KBPS))
 
 
 
 
 
 
 
159
  return {"input": (sr, ref), "rows": rows, "audio": reconstructed}
160
 
161
 
@@ -164,15 +191,10 @@ def _table(rows: list[dict]) -> list[list]:
164
 
165
 
166
  _CACHE: dict[str, dict] = {}
167
- _LS_CACHE: dict[str, dict] = {}
168
 
169
 
170
  def run(audio_path: str, levels: list | tuple) -> dict:
171
- """Encode and decode the audio at every requested weight-quantization level.
172
-
173
- Results are cached by audio content and level set, so the Compare and
174
- Benchmark tabs share a single GPU pass.
175
- """
176
  if not audio_path:
177
  raise gr.Error("Provide audio first: upload, record, or pick an example.")
178
  levels = tuple(levels)
@@ -207,22 +229,9 @@ def playground(audio_path: str, level: str):
207
  )
208
 
209
 
210
- def _lscodec_row(audio_path: str) -> dict:
211
- """LSCodec-25 Hz score for one input, cached separately from the quantization sweep."""
212
- with open(audio_path, "rb") as fh:
213
- key = hashlib.md5(fh.read()).hexdigest()
214
- if key not in _LS_CACHE:
215
- if len(_LS_CACHE) >= 6:
216
- _LS_CACHE.clear()
217
- _LS_CACHE[key] = _encode_decode(audio_path, (LS_LEVEL,))["rows"][0]
218
- return _LS_CACHE[key]
219
-
220
-
221
  def benchmark(audio_path: str):
222
- """Score every quantization level on one input: metrics table plus summary chart."""
223
- rows = run(audio_path, QUANT_LEVELS)["rows"]
224
- if LS_LEVEL in BENCH_LEVELS:
225
- rows = rows + [_lscodec_row(audio_path)]
226
  return _table(rows), core.metrics_figure(rows)
227
 
228
 
@@ -239,8 +248,9 @@ The bitrate never changes — **{core.BITRATE_KBPS:g} kbps** (12.5 tokens/s × 1
239
 
240
  FOOTER = """Audio examples from the [FocalCodec repository](https://github.com/lucadellalib/focalcodec)
241
  (LibriSpeech, VoiceBank, MagicData). Model and method by Luca Della Libera et al., Apache-2.0.
242
- The **lscodec-25hz** baseline is [LSCodec](https://github.com/X-LANCE/LSCodec-Inference) by
243
- Yiwei Guo et al. (MIT), self-prompted with WavLM-Large from [`microsoft/wavlm-large`](https://huggingface.co/microsoft/wavlm-large)."""
 
244
 
245
  with gr.Blocks(title="FocalCodec quantization lab") as demo:
246
  gr.Markdown(HEADER)
@@ -278,7 +288,9 @@ with gr.Blocks(title="FocalCodec quantization lab") as demo:
278
  )
279
  gr.Examples(examples=EXAMPLES, inputs=pg_input, label="Examples")
280
  pg_level = gr.Dropdown(
281
- choices=list(QUANT_LEVELS), value="int8", label="Weight format"
 
 
282
  )
283
  pg_button = gr.Button("Encode + decode", variant="primary")
284
  with gr.Column(scale=3):
@@ -303,12 +315,13 @@ with gr.Blocks(title="FocalCodec quantization lab") as demo:
303
  "**RTF** = encode+decode time ÷ audio duration · size assumes "
304
  "the stored weight format."
305
  )
306
- if LS_LEVEL in BENCH_LEVELS:
307
  gr.Markdown(
308
- f"**{LS_LEVEL}** is a second codec, not a weight format: it runs at "
309
- f"**{LS_BITRATE_KBPS:g} kbps** (25 tokens/s × 10 bits) versus FocalCodec's "
310
- f"**{core.BITRATE_KBPS:g} kbps**, and its size counts only the codec "
311
- f"({LS_SIZE_MB:g} MB) — the WavLM-Large prompt encoder it shares is 1.2 GB."
 
312
  )
313
  bm_table = gr.Dataframe(headers=HEADERS, datatype=DATATYPES, interactive=False)
314
  bm_plot = gr.Plot(label="Quality and size per level")
 
16
  MODEL_REPO = "lucadellalib/focalcodec_12_5hz"
17
  HUB_REPO = "lucadellalib/focalcodec"
18
  LS_REPO = "cantabile-kwok/lscodec_25hz"
19
+ LS_PREFIX = "lscodec-"
 
 
20
  MAX_SECONDS = 20
21
  HEADERS = ["Level", "SI-SDR (dB)", "STOI", "LSD (dB)", "Model size (MB)", "Bitrate (kbps)", "RTF"]
22
  DATATYPES = ["str", "number", "number", "number", "number", "number", "number"]
 
25
  ["assets/p226_006.wav"],
26
  ["assets/5_1812_20170628135856.wav"],
27
  ]
28
+ LS_BITRATE_KBPS = 25 * 10 / 1000 # 25 tokens/s x 10-bit codebook
29
 
30
  codec = torch.hub.load(HUB_REPO, "focalcodec", config=MODEL_REPO, trust_repo=True)
31
  codec.eval().requires_grad_(False)
 
83
  "dec": dec,
84
  "books": books,
85
  "wavlm": wavlm,
86
+ "enc_state": {k: v.detach().cpu().clone() for k, v in enc.state_dict().items()},
87
+ "dec_state": {k: v.detach().cpu().clone() for k, v in dec.state_dict().items()},
88
+ "books_state": {k: v.detach().cpu().clone() for k, v in books.state_dict().items()},
89
  "repeat": bool(vocoder_config.get("repeat_input_tokens")),
90
  "out_sr": int(vocoder_config["sampling_rate"]),
91
  }
 
97
  LS = None
98
  print(f"[lscodec] baseline disabled: {exc!r}")
99
 
100
+ LS_LEVELS = tuple(LS_PREFIX + level for level in QUANT_LEVELS) if LS is not None else ()
101
+ BENCH_LEVELS = QUANT_LEVELS + LS_LEVELS
102
 
103
 
104
+ def _lscodec_prompt(audio: torch.Tensor) -> torch.Tensor:
105
+ """WavLM-Large layers 0..5 features: a function of the input only, so compute it once per clip."""
 
106
  with torch.inference_mode():
107
  prompt_input = torch.nn.functional.layer_norm(audio, audio.shape) # fairseq WavLM cfg.normalize
108
+ return LS["wavlm"](prompt_input, output_hidden_states=True).hidden_states[6]
109
+
110
+
111
+ def _lscodec_decode(prompt: torch.Tensor, audio: torch.Tensor, sr: int) -> np.ndarray:
112
+ """Reconstruct `audio` with LSCodec at the currently loaded weight level, resampled back to `sr`."""
113
+ with torch.inference_mode():
114
  _, _, index = LS["enc"].encode(audio.unsqueeze(0))
115
  if LS["repeat"]:
116
  index = index.repeat_interleave(2, dim=0)
 
123
  return rec
124
 
125
 
126
+ def _load_lscodec(level: str) -> None:
127
+ """Swap the LSCodec encoder, vocoder and codebook onto `level` (any QUANT_LEVELS entry)."""
128
+ for key in ("enc", "dec", "books"):
129
+ state = LS[key + "_state"]
130
+ LS[key].load_state_dict(state if level == "fp32" else core.quantize_state(state, level))
131
+
132
+
133
+ def _lscodec_size_mb(level: str) -> float:
134
+ """Stored size of the codec at `level`; the WavLM prompt encoder is excluded (see the tab note)."""
135
+ return sum(core.model_size_mb(LS[key + "_state"], level) for key in ("enc", "dec", "books"))
136
+
137
+
138
  def _estimate_duration(audio_path: str, levels: tuple) -> float:
139
  try:
140
  seconds = min(sf.info(audio_path).duration, MAX_SECONDS)
141
  except Exception: # noqa: BLE001 unreadable input falls back to the full cap
142
  seconds = float(MAX_SECONDS)
143
  # ZeroGPU rejects a request above the visitor's tier cap, so never ask for more than 60 s.
144
+ return min(60.0, 3.0 + len(levels) + 1.5 * seconds)
145
 
146
 
147
  @spaces.GPU(duration=_estimate_duration)
 
151
  seconds = len(ref) / sr
152
  rows, reconstructed = [], {}
153
  focal_levels = tuple(level for level in levels if level in QUANT_LEVELS)
154
+ ls_levels = tuple(level for level in levels if level.startswith(LS_PREFIX))
155
  if focal_levels:
156
  sig = torch.from_numpy(ref).unsqueeze(0).cuda()
157
  with torch.inference_mode(): # untimed warmup so the first row's RTF isn't inflated
 
167
  rows.append(core.evaluate(ref, rec, sr, rtf, SIZES[level], level))
168
  reconstructed[level] = (sr, rec)
169
  codec.load_state_dict(BASE_STATE)
170
+ if ls_levels:
171
+ audio = torch.from_numpy(ref).float().view(1, -1).cuda()
172
+ with torch.inference_mode(): # one prompt for every level, plus an untimed warmup pass
173
+ prompt = _lscodec_prompt(audio)
174
+ _lscodec_decode(prompt, audio, sr)
175
+ for level in ls_levels:
176
+ _load_lscodec(level[len(LS_PREFIX) :])
177
+ torch.cuda.synchronize()
178
+ start = time.perf_counter()
179
+ rec = _lscodec_decode(prompt, audio, sr)
180
+ torch.cuda.synchronize()
181
+ rtf = (time.perf_counter() - start) / seconds
182
+ base = level[len(LS_PREFIX) :]
183
+ rows.append(core.evaluate(ref, rec, sr, rtf, _lscodec_size_mb(base), level, LS_BITRATE_KBPS))
184
+ reconstructed[level] = (sr, rec)
185
+ _load_lscodec("fp32") # leave the baseline weights in place for the next request
186
  return {"input": (sr, ref), "rows": rows, "audio": reconstructed}
187
 
188
 
 
191
 
192
 
193
  _CACHE: dict[str, dict] = {}
 
194
 
195
 
196
  def run(audio_path: str, levels: list | tuple) -> dict:
197
+ """Encode and decode the audio at every requested level (either codec), cached per level set."""
 
 
 
 
198
  if not audio_path:
199
  raise gr.Error("Provide audio first: upload, record, or pick an example.")
200
  levels = tuple(levels)
 
229
  )
230
 
231
 
 
 
 
 
 
 
 
 
 
 
 
232
  def benchmark(audio_path: str):
233
+ """Score every level of both codecs on one input: metrics table plus summary chart."""
234
+ rows = run(audio_path, BENCH_LEVELS)["rows"]
 
 
235
  return _table(rows), core.metrics_figure(rows)
236
 
237
 
 
248
 
249
  FOOTER = """Audio examples from the [FocalCodec repository](https://github.com/lucadellalib/focalcodec)
250
  (LibriSpeech, VoiceBank, MagicData). Model and method by Luca Della Libera et al., Apache-2.0.
251
+ The **`lscodec-…`** rows are [LSCodec](https://github.com/X-LANCE/LSCodec-Inference) by
252
+ Yiwei Guo et al. (MIT), quantized identically and self-prompted with WavLM-Large from
253
+ [`microsoft/wavlm-large`](https://huggingface.co/microsoft/wavlm-large)."""
254
 
255
  with gr.Blocks(title="FocalCodec quantization lab") as demo:
256
  gr.Markdown(HEADER)
 
288
  )
289
  gr.Examples(examples=EXAMPLES, inputs=pg_input, label="Examples")
290
  pg_level = gr.Dropdown(
291
+ choices=list(QUANT_LEVELS) + list(LS_LEVELS),
292
+ value="int8",
293
+ label="Codec / weight format",
294
  )
295
  pg_button = gr.Button("Encode + decode", variant="primary")
296
  with gr.Column(scale=3):
 
315
  "**RTF** = encode+decode time ÷ audio duration · size assumes "
316
  "the stored weight format."
317
  )
318
+ if LS_LEVELS:
319
  gr.Markdown(
320
+ f"Rows prefixed `lscodec-` are the second codec, quantized the same way: "
321
+ f"it runs at **{LS_BITRATE_KBPS:g} kbps** (25 tokens/s × 10 bits) versus "
322
+ f"FocalCodec's **{core.BITRATE_KBPS:g} kbps**, and its size covers the encoder, "
323
+ "vocoder and codebook only — the WavLM-Large prompt encoder it needs is 1.2 GB "
324
+ "and stays excluded."
325
  )
326
  bm_table = gr.Dataframe(headers=HEADERS, datatype=DATATYPES, interactive=False)
327
  bm_plot = gr.Plot(label="Quality and size per level")
core.py CHANGED
@@ -88,7 +88,11 @@ def quantize_state(state: dict, level: str) -> dict:
88
 
89
 
90
  def model_size_mb(state: dict, level: str) -> float:
91
- return sum(v.numel() for v in state.values()) * _BITS[level] / 8 / 1e6
 
 
 
 
92
 
93
 
94
  def load_audio(path: str, target_sr: int, max_seconds: float | None = None) -> tuple[np.ndarray, int]:
@@ -180,6 +184,8 @@ if __name__ == "__main__":
180
  assert torch.equal(grid8.to(torch.float8_e4m3fn).float(), grid8), "fp8 grid escapes E4M3-FN"
181
  grid4 = fp_levels(w, "fp4")
182
  assert grid4.abs().max() <= _FP_MAX_NORM["fp4"] * 1.0000001, "fp4 grid escapes E2M1"
 
 
183
  x = np.random.randn(16000).astype(np.float32)
184
  assert si_sdr(x, x) > 60, "SI-SDR of identical signals should be large"
185
  assert si_sdr(x, 3 * x) > 60, "SI-SDR must be scale invariant"
 
88
 
89
 
90
  def model_size_mb(state: dict, level: str) -> float:
91
+ """Stored size of `state` at `level`; non-float buffers keep their own width."""
92
+ return sum(
93
+ v.numel() * (v.element_size() if not v.is_floating_point() else _BITS[level] / 8)
94
+ for v in state.values()
95
+ ) / 1e6
96
 
97
 
98
  def load_audio(path: str, target_sr: int, max_seconds: float | None = None) -> tuple[np.ndarray, int]:
 
184
  assert torch.equal(grid8.to(torch.float8_e4m3fn).float(), grid8), "fp8 grid escapes E4M3-FN"
185
  grid4 = fp_levels(w, "fp4")
186
  assert grid4.abs().max() <= _FP_MAX_NORM["fp4"] * 1.0000001, "fp4 grid escapes E2M1"
187
+ assert model_size_mb({"w": w}, "fp32") == w.numel() * 4 / 1e6
188
+ assert model_size_mb({"i": torch.zeros(8, dtype=torch.int64)}, "int4") == 8 * 8 / 1e6 # buffers keep 8 bytes
189
  x = np.random.randn(16000).astype(np.float32)
190
  assert si_sdr(x, x) > 60, "SI-SDR of identical signals should be large"
191
  assert si_sdr(x, 3 * x) > 60, "SI-SDR must be scale invariant"