LocalAI-io/LocalVQE-demo
23
1"""Gradio demo for LocalVQE — real-time AEC + NS + dereverb.2 3Loads released model versions side-by-side and exposes a runtime4selector so you can A/B them on the same clip:5 6 v1.3-NR — r009 ep5. v1.3 ep18 + 5 epochs of fine-tune with the7 explicit noise-residual term (W_noise · |pred^c-target^c| on8 NE-active frames, weight=2.0). Targets the active-frame9 mask-softening artifact ("noise audible during speech")10 diagnosed on v1.3 via tmp/diagnostic_mask_during_speech.py.11 Architecture identical to v1.3. Path from LOCALVQE_V13NR_CKPT.12 v1.3 — candidate. 5.1 M params. Same SiLU + dmax 64 + STFT-25613 substrate as v1.2, but mic_enc1/far_enc1 widened (32→107,14 32→64) and bottleneck enlarged (162→512) on the back of15 per-channel Fisher analysis. Blind ICASSP 2022: +5-10 dB16 FE-ST ERLE, +0.07-0.14 DNSMOS, +0.10-0.16 DT preservation17 vs v1.2; small FE-ST echo_mos regression (-0.17). Path18 from LOCALVQE_V13_CKPT, no HF publish yet.19 v1.2 — previous release. 1.3 M params. SiLU activation + dmax 6420 (1024 ms echo-search window) + wider clean-pool DNSMOS21 filter + phone-bandwidth + codec round-trip aug. Adds22 ~+0.3 echo_mos / ~+1 dB ERLE on AEC blind FE-ST vs v1.1.23 Path resolves from LOCALVQE_V12_CKPT, else HF.24 v1.1 — previous release. 1.3 M params. ReLU6, pre-norm25 CausalGroupNorm, STFT-256 codec. Fixes intermittent26 crackling that v1 produced under heavy background noise.27 Path resolves from LOCALVQE_V11_CKPT, else HF.28 v1 — original release. Path resolves from LOCALVQE_V1_CKPT29 (or LOCALVQE_LOCAL_CKPT for backward compat), else HF.30 31 v1.4-AEC — echo cancellation ONLY (203 K params). Two-stage32 cascade: adaptive Kalman front-end (GCC-PHAT prealign +33 PBFDKF + learned controller) + compact neural mask on the34 residual. Keeps near-end speech, noise, and room acoustics35 intact by design. Path from LOCALVQE_V14AEC_CKPT, else HF.36 37If a checkpoint isn't reachable that entry is hidden from the38selector. Each architecture lives in an independent Python39package so they can be loaded simultaneously without import40collisions:41 v1 → space/localvqe_model/42 v1.1 → space/localvqe_v11/43 v1.2 → space/localvqe_v12/44 v1.4-AEC → space/localvqe_v14aec/45"""46import hashlib47import os48from pathlib import Path49 50import gradio as gr51import numpy as np52import soundfile as sf53from scipy.signal import resample_poly54 55from gguf_engine import GGUFModel56 57# Where the demo runs decides the lineup. On HF Spaces (SPACE_ID set)58# only the GGML C++ engine + released .gguf files are used — the same59# artifact production users deploy — and torch is never imported. The60# local instance additionally loads the PyTorch reference checkpoints61# so both engines can be A/B'd on the same clip.62ON_SPACE = "SPACE_ID" in os.environ63 64 65# v1 (original release) — namespace 'localvqe_model'. PyTorch entries66# are local-only, so all their imports are lazy.67def _import_v1():68 from localvqe_model import (69 Config as ConfigV1,70 LocalVQE as LocalVQEv1,71 apply_ckpt_model_config as apply_ckpt_v1,72 load_checkpoint as load_ckpt_v1,73 )74 return ConfigV1, LocalVQEv1, apply_ckpt_v1, load_ckpt_v175 76# v1.1 / v1.2 — bundled in this directory. Imported on demand to keep77# startup time low when those versions aren't configured.78def _import_v11():79 from localvqe_v11 import (80 Config as ConfigV11,81 LocalVQE as LocalVQEv11,82 apply_ckpt_model_config as apply_ckpt_v11,83 load_checkpoint as load_ckpt_v11,84 )85 return ConfigV11, LocalVQEv11, apply_ckpt_v11, load_ckpt_v1186 87def _import_v12():88 from localvqe_v12 import (89 Config as ConfigV12,90 LocalVQE as LocalVQEv12,91 apply_ckpt_model_config as apply_ckpt_v12,92 load_checkpoint as load_ckpt_v12,93 )94 return ConfigV12, LocalVQEv12, apply_ckpt_v12, load_ckpt_v1295 96SR = 1600097HF_REPO_ID = "LocalAI-io/LocalVQE"98HF_V1_FILE = "localvqe-v1-1.3M.pt"99HF_V11_FILE = "localvqe-v1.1-1.3M.pt"100HF_V12_FILE = "localvqe-v1.2-1.3M.pt"101HF_V13_FILE = "localvqe-v1.3-4.8M.pt"102HF_V14AEC_FILE = "localvqe-v1.4-aec-200K.pt"103EXAMPLES_DIR = Path(__file__).resolve().parent / "examples"104 105 106def _sha256(path: str) -> str:107 h = hashlib.sha256()108 with open(path, "rb") as f:109 for chunk in iter(lambda: f.read(1 << 20), b""):110 h.update(chunk)111 return h.hexdigest()112 113 114def _resolve_v1_ckpt() -> str | None:115 # Backward-compat: LOCALVQE_LOCAL_CKPT used to be the way to override.116 for env in ("LOCALVQE_V1_CKPT", "LOCALVQE_LOCAL_CKPT"):117 v = os.environ.get(env)118 if v:119 return v120 try:121 from huggingface_hub import hf_hub_download122 return hf_hub_download(repo_id=HF_REPO_ID, filename=HF_V1_FILE)123 except Exception as e:124 print(f"v1 unavailable from HF ({e})")125 return None126 127 128def _resolve_v11_ckpt() -> str | None:129 v = os.environ.get("LOCALVQE_V11_CKPT")130 if v:131 return v132 try:133 from huggingface_hub import hf_hub_download134 return hf_hub_download(repo_id=HF_REPO_ID, filename=HF_V11_FILE)135 except Exception:136 return None137 138 139def _resolve_v12_ckpt() -> str | None:140 v = os.environ.get("LOCALVQE_V12_CKPT")141 if v:142 return v143 try:144 from huggingface_hub import hf_hub_download145 return hf_hub_download(repo_id=HF_REPO_ID, filename=HF_V12_FILE)146 except Exception:147 return None148 149 150def _resolve_v121_ckpt() -> str | None:151 # No HF fallback yet — v1.2.1 isn't published. Set LOCALVQE_V121_CKPT152 # in docker-compose.yml (defaults to checkpoints/release/...) to load153 # the local finetuned copy.154 return os.environ.get("LOCALVQE_V121_CKPT") or None155 156 157def _resolve_v12a_ckpt() -> str | None:158 # v1.2a — v9 (widened DRR + longer RIRs + global gain) from-scratch159 # epoch 14. Architecture identical to v1.2/v1.2.1 (uses localvqe_v12160 # package). No HF publish yet.161 return os.environ.get("LOCALVQE_V12A_CKPT") or None162 163 164def _resolve_v12b_ckpt() -> str | None:165 # v1.2b — v10 (v1.2 + audible reverb + 80/20 conference mix +166 # pipeline pop fixes, no experimental augs) from-scratch e19.167 # Architecture identical to v1.2 (uses localvqe_v12 package).168 return os.environ.get("LOCALVQE_V12B_CKPT") or None169 170 171def _resolve_v12c_ckpt() -> str | None:172 # v1.2c — v11 (v10 + level-invariance mic-gain aug,173 # clean_attenuation_factor=1.0) from-scratch e17. Addresses174 # low-SNR wobble near noise floor. Architecture identical to175 # v1.2 (uses localvqe_v12 package).176 return os.environ.get("LOCALVQE_V12C_CKPT") or None177 178 179def _resolve_v12d_ckpt() -> str | None:180 # v1.2d — v11_refine e22 (10-epoch low-LR cosine continuation181 # of v1.2c from v11 e20, peak LR 1e-4). Blind eval beats182 # v1.2c on FE-ST echo_mos (+0.31) and NE-ST deg_mos (+0.04)183 # while recovering 2.4 dB of FE-ST ERLE. Architecture184 # identical to v1.2 (uses localvqe_v12 package).185 return os.environ.get("LOCALVQE_V12D_CKPT") or None186 187 188def _resolve_v13_ckpt() -> str | None:189 # v1.3 — current 4.8 M release. Architecture190 # mic=[2,112,32,104,96,152], far=[2,64,32], bn=256 (vs v1.2's191 # [2,32,40,40,40,40] / [2,32,40] / 162). Same SiLU + dmax 64 +192 # STFT-256 substrate, so loads via localvqe_v12 package. Trained193 # under the v1.3-MEP2 loss recipe (mask-energy + symmetric preserve194 # + dt-balanced + echo-aware). Peak val/score 0.7688 — beats v1.2195 # on doubletalk deg_mos, trades off some farend echo_mos. Promoted196 # from train run v1.3.r003 epoch_0020.197 v = os.environ.get("LOCALVQE_V13_CKPT")198 if v:199 return v200 try:201 from huggingface_hub import hf_hub_download202 return hf_hub_download(repo_id=HF_REPO_ID, filename=HF_V13_FILE)203 except Exception:204 return None205 206 207def _resolve_v13nr_ckpt() -> str | None:208 # v1.3-NR — r009 ep5: v1.3 ep18 fine-tuned 5 epochs with the209 # explicit noise-residual term (weight=2.0) targeting the210 # active-frame mask-softening artifact diagnosed on v1.3.211 # Same architecture as v1.3.212 return os.environ.get("LOCALVQE_V13NR_CKPT") or None213 214 215def _resolve_v13me_ckpt() -> str | None:216 # v1.3-ME — r011 ep5: v1.3 ep18 fine-tuned 5 epochs with the217 # mask-energy term (weight=1.0). Penalises CCM kernel energy218 # directly in noise-dominated bins on active frames. Gradient219 # is data-independent (Lean-verified). Cuts R4 noise mask 83 %220 # vs v1.3 but also reduces R3 speech-only preservation.221 return os.environ.get("LOCALVQE_V13ME_CKPT") or None222 223 224def _resolve_v13mep_ckpt() -> str | None:225 # v1.3-MEP — r013 ep5: ME + symmetric preserve term226 # (mask_preserve_weight=1.0, threshold=0.5). One-sided ReLU227 # hinge pushes mask coefficients outward in clean-dominated bins228 # below threshold — recovers R3 speech preservation that r011's229 # bare ME term sacrificed, while keeping most of the R4 gate-230 # closing benefit. Only run that's strictly better than v1.3 on231 # both R4 (noise during speech) and R3 (clean preservation).232 return os.environ.get("LOCALVQE_V13MEP_CKPT") or None233 234 235def _resolve_v13mep2_ckpt() -> str | None:236 # v1.3-MEP2 — r014 ep5: continuation of r013 from ep5 with a237 # softer cosine (peak 2e-5 → 1e-7, 5 epochs, fresh optimizer).238 # Convergence test: r013 loss was still decreasing at ep5; r014239 # checked whether more LR time would help. Result: train_loss240 # plateaued at 0.3142 (identical to r013 ep5), R4/R3 mask_eng241 # within ±2 % of r013. Only meaningful delta is R2 (silent-gate)242 # tightened ~20 %. Same loss config as r013.243 return os.environ.get("LOCALVQE_V13MEP2_CKPT") or None244 245 246def _resolve_v13r2_ckpt() -> str | None:247 # v1.3.r2 — v1.3.r002 epoch_0018: 1.55 M, from-scratch under the248 # MEP2 loss on the r017-discovered uniform-width [2,32,48,48,48,48]249 # / bn 168 shape. Smaller A/B partner to r003.250 return os.environ.get("LOCALVQE_V13R2_CKPT") or None251 252 253# v1.3.r3 is now the canonical v1.3 release (see _resolve_v13_ckpt above).254# The .r3 alias was used pre-release while the size/quality tradeoff was255# being established; the resolver was removed when the file was promoted256# to checkpoints/release/localvqe-v1.3-4.8M.pt.257 258 259def _resolve_v14aec_ckpt() -> str | None:260 v = os.environ.get("LOCALVQE_V14AEC_CKPT")261 if v:262 return v263 try:264 from huggingface_hub import hf_hub_download265 return hf_hub_download(repo_id=HF_REPO_ID, filename=HF_V14AEC_FILE)266 except Exception:267 return None268 269 270# v1.4 NS+dereverb backends (full enhancement). Same DAF front-end + slim CCM271# cascade as v1.4-AEC, but trained toward the anechoic `clean` target so they272# remove noise + reverb (not echo-only). Local-only; default to the packaged273# release copies, no HF publish yet.274def _resolve_v14nsdr200_ckpt() -> str | None:275 v = os.environ.get("LOCALVQE_V14NSDR200_CKPT")276 if v:277 return v278 d = "/workspace/localvqe/checkpoints/release/localvqe-v1.4-nsdr-200K.pt"279 return d if os.path.exists(d) else None280 281 282def _resolve_v14nsdr800_ckpt() -> str | None:283 v = os.environ.get("LOCALVQE_V14NSDR800_CKPT")284 if v:285 return v286 d = "/workspace/localvqe/checkpoints/release/localvqe-v1.4-nsdr-800K.pt"287 return d if os.path.exists(d) else None288 289 290def _resolve_gate_ckpt() -> str | None:291 # The 1,657-param learned front-end NS/dereverb gate (fegate_proto.pt).292 # Local-only research entry; default to the trained prototype.293 v = os.environ.get("LOCALVQE_GATE_CKPT")294 if v:295 return v296 # fegate_preserve.pt = trained with the clean energy-preservation term (the297 # original fegate_proto.pt collapses to silence — EXPERIMENTS 2026-06-18).298 for d in ("/workspace/localvqe/checkpoints/fegate_preserve.pt",299 "/workspace/localvqe/checkpoints/fegate_proto.pt"):300 if os.path.exists(d):301 return d302 return None303 304 305def _resolve_gtcrn_ckpt() -> str | None:306 # The 48.9K AEC-aware GTCRN_AEC (gtcrn_ns_aec.pt) — all-scene, yhat far-end307 # branch. Local-only research entry; default to the trained checkpoint.308 v = os.environ.get("LOCALVQE_GTCRN_CKPT")309 if v:310 return v311 d = "/workspace/localvqe/checkpoints/gtcrn_ns_aec.pt"312 return d if os.path.exists(d) else None313 314 315def _resolve_gtcrn_os400_ckpt() -> str | None:316 # os-weight 400 + movement bank — preserves quiet near-end (DT level 0.71→0.84)317 # AND a strong echo guard recovered by the movement clips (FE-ST eval 17.2 dB,318 # post-movement transient +5.5 dB vs os400). Pareto-dominates os140/os400.319 v = os.environ.get("LOCALVQE_GTCRN_OS400_CKPT")320 if v:321 return v322 for d in ("/workspace/localvqe/checkpoints/gtcrn_ns_aec_os400_move.pt",323 "/workspace/localvqe/checkpoints/gtcrn_ns_aec_os400.pt"):324 if os.path.exists(d):325 return d326 return None327 328 329def _resolve_gtcrn_keepnoise_ckpt() -> str | None:330 # LocalVQE-Pi-AEC-v1-49k — keep-noise sibling of the full Pi model. Same 49K331 # GTCRN_AEC arch, trained toward `room` (remove echo; keep noise + reverb)332 # instead of `clean`. The echo-only counterpart to LocalVQE-Pi-v1-49k.333 v = os.environ.get("LOCALVQE_GTCRN_KEEPNOISE_CKPT")334 if v:335 return v336 d = "/workspace/localvqe/checkpoints/gtcrn_aec_keepnoise.pt"337 return d if os.path.exists(d) else None338 339 340def _build_v14aec():341 ckpt_path = _resolve_v14aec_ckpt()342 if ckpt_path is None:343 return None, None344 from localvqe_v14aec import load_cascade345 model = load_cascade(ckpt_path)346 info = {347 "source": ckpt_path,348 "sha256": _sha256(ckpt_path),349 "n_params": sum(p.numel() for p in model.parameters()),350 "label": "v1.4-AEC (echo cancellation only)",351 }352 print(f"v1.4-AEC loaded: {info['n_params']:,} params "353 f"sha={info['sha256'][:16]}… src={ckpt_path}")354 return model, info355 356 357def _build_v14aec_fe():358 """Stage-1 (adaptive filter) alone — the 2.7K front-end candidate."""359 ckpt_path = _resolve_v14aec_ckpt()360 if ckpt_path is None:361 return None, None362 from localvqe_v14aec import load_cascade363 model = load_cascade(ckpt_path, frontend_only=True)364 info = {365 "source": ckpt_path,366 "sha256": _sha256(ckpt_path),367 "n_params": 2742,368 "label": "v1.4-AEC front-end only (adaptive filter, no neural mask)",369 }370 print(f"v1.4-AEC-FE loaded: 2,742 params sha={info['sha256'][:16]}…")371 return model, info372 373 374def _build_v14aec_path(ckpt_path, label, frontend_only=False):375 """Load a v1.4-AEC cascade from an explicit path (e.g. a controller-grafted376 blob) so a retrained front-end controller can be A/B'd vs the deployed one.377 frontend_only=True drops the neural mask -> the adaptive filter alone, so the378 controller's echo behaviour is audible without the backend masking it."""379 if not ckpt_path or not os.path.exists(ckpt_path):380 return None, None381 from localvqe_v14aec import load_cascade382 model = load_cascade(ckpt_path, frontend_only=frontend_only)383 info = {384 "source": ckpt_path,385 "sha256": _sha256(ckpt_path),386 "n_params": 2742 if frontend_only else sum(p.numel() for p in model.parameters()),387 "label": label,388 }389 print(f"{label}: loaded sha={info['sha256'][:16]}… src={ckpt_path}")390 return model, info391 392 393def _build_v14nsdr(resolve, label):394 """v1.4 NS+dereverb cascade. Same loader as v1.4-AEC (load_cascade); wrapped395 in try/except so an unreachable/incompatible checkpoint hides the entry396 rather than crashing app startup."""397 ckpt_path = resolve()398 if ckpt_path is None:399 return None, None400 from localvqe_v14aec import load_cascade401 try:402 model = load_cascade(ckpt_path)403 except Exception as e:404 print(f"{label}: load failed ({e})")405 return None, None406 info = {407 "source": ckpt_path,408 "sha256": _sha256(ckpt_path),409 "n_params": sum(p.numel() for p in model.parameters()),410 "label": label,411 }412 print(f"{label} loaded: {info['n_params']:,} params "413 f"sha={info['sha256'][:16]}… src={ckpt_path}")414 return model, info415 416 417def _build_gate():418 """The 1.7K learned front-end gate. Reuses a v1.4 cascade's DAF front-end (so419 it sees the same `e` as the CCM backends) and applies the per-bin magnitude420 gate to STFT(e). Local-only; needs both the gate weights and a front-end ckpt."""421 gate_ckpt = _resolve_gate_ckpt()422 fe_ckpt = _resolve_v14nsdr200_ckpt() or _resolve_v14aec_ckpt()423 if gate_ckpt is None or fe_ckpt is None:424 return None, None425 from localvqe_gate import load_gate426 try:427 model = load_gate(fe_ckpt, gate_ckpt)428 except Exception as e:429 print(f"gate: load failed ({e})")430 return None, None431 info = {432 "source": gate_ckpt,433 "sha256": _sha256(gate_ckpt),434 "n_params": sum(p.numel() for p in model.parameters()),435 "label": "gate (1.7K front-end magnitude gain — ≈passthrough, see EXPERIMENTS 2026-06-18)",436 }437 print(f"gate loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "438 f"front-end={fe_ckpt}")439 return model, info440 441 442def _build_gtcrn(resolve, label, fe_ckpt_override=None):443 """The 48.9K AEC-aware GTCRN_AEC. Reuses a v1.4 cascade's DAF front-end (same `e`444 as the CCM backends) plus the echo estimate yhat=mic-e, and applies a complex-ratio445 mask on STFT(e). Local-only; needs both the GTCRN weights and a front-end ckpt.446 fe_ckpt_override lets a grafted (retrained-controller) cascade supply the front-end."""447 gtcrn_ckpt = resolve()448 fe_ckpt = fe_ckpt_override or _resolve_v14nsdr200_ckpt() or _resolve_v14aec_ckpt()449 if gtcrn_ckpt is None or fe_ckpt is None:450 return None, None451 from localvqe_gtcrn import load_gtcrn_aec452 try:453 model = load_gtcrn_aec(fe_ckpt, gtcrn_ckpt)454 except Exception as e:455 print(f"gtcrn-aec ({label}): load failed ({e})")456 return None, None457 info = {458 "source": gtcrn_ckpt,459 "sha256": _sha256(gtcrn_ckpt),460 "n_params": sum(p.numel() for p in model.parameters()),461 "label": label,462 }463 print(f"gtcrn-aec loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "464 f"front-end={fe_ckpt} [{label}]")465 return model, info466 467 468def _build_v1():469 ckpt_path = _resolve_v1_ckpt()470 if ckpt_path is None:471 return None, None472 import torch473 ConfigV1, LocalVQEv1, apply_ckpt_v1, load_ckpt_v1 = _import_v1()474 cfg = ConfigV1()475 peek = torch.load(ckpt_path, map_location="cpu", weights_only=False)476 apply_ckpt_v1(peek, cfg)477 del peek478 model = LocalVQEv1.from_config(cfg).to("cpu")479 load_ckpt_v1(ckpt_path, model)480 # Fold the trained AlignBlock softmax temperature (a buffer in the481 # checkpoint) into the smoothing conv — without this, eval runs at482 # the default 1.0 instead of the trained value, losing ~5 dB ERLE.483 model.align.fold_temperature()484 model.eval()485 info = {486 "source": ckpt_path,487 "sha256": _sha256(ckpt_path),488 "n_params": sum(p.numel() for p in model.parameters()),489 "label": "v1 (previous release)",490 }491 print(f"v1 loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "492 f"src={ckpt_path}")493 return model, info494 495 496def _build_v11():497 ckpt_path = _resolve_v11_ckpt()498 if ckpt_path is None:499 return None, None500 import torch501 ConfigV11, LocalVQEv11, apply_ckpt_v11, load_ckpt_v11 = _import_v11()502 cfg = ConfigV11()503 peek = torch.load(ckpt_path, map_location="cpu", weights_only=False)504 apply_ckpt_v11(peek, cfg)505 del peek506 model = LocalVQEv11.from_config(cfg).to("cpu")507 load_ckpt_v11(ckpt_path, model)508 model.align.fold_temperature()509 model.eval()510 info = {511 "source": ckpt_path,512 "sha256": _sha256(ckpt_path),513 "n_params": sum(p.numel() for p in model.parameters()),514 "label": "v1.1 (previous release)",515 }516 print(f"v1.1 loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "517 f"src={ckpt_path}")518 return model, info519 520 521def _build_v12_like(ckpt_path, label):522 """Shared builder for v1.2 and v1.2.1 — same architecture, same package."""523 import torch524 ConfigV12, LocalVQEv12, apply_ckpt_v12, load_ckpt_v12 = _import_v12()525 cfg = ConfigV12()526 peek = torch.load(ckpt_path, map_location="cpu", weights_only=False)527 apply_ckpt_v12(peek, cfg)528 del peek529 model = LocalVQEv12.from_config(cfg).to("cpu")530 load_ckpt_v12(ckpt_path, model)531 model.align.fold_temperature()532 model.eval()533 info = {534 "source": ckpt_path,535 "sha256": _sha256(ckpt_path),536 "n_params": sum(p.numel() for p in model.parameters()),537 "label": label,538 }539 return model, info540 541 542def _build_v12():543 ckpt_path = _resolve_v12_ckpt()544 if ckpt_path is None:545 return None, None546 model, info = _build_v12_like(ckpt_path, "v1.2 (current release)")547 print(f"v1.2 loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "548 f"src={ckpt_path}")549 return model, info550 551 552def _build_v121():553 ckpt_path = _resolve_v121_ckpt()554 if ckpt_path is None:555 return None, None556 model, info = _build_v12_like(ckpt_path, "v1.2.1 (movement-aug finetune)")557 print(f"v1.2.1 loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "558 f"src={ckpt_path}")559 return model, info560 561 562def _build_v12a():563 ckpt_path = _resolve_v12a_ckpt()564 if ckpt_path is None:565 return None, None566 model, info = _build_v12_like(567 ckpt_path, "v1.2a (widened DRR + longer RIRs, from-scratch)")568 print(f"v1.2a loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "569 f"src={ckpt_path}")570 return model, info571 572 573def _build_v12b():574 ckpt_path = _resolve_v12b_ckpt()575 if ckpt_path is None:576 return None, None577 model, info = _build_v12_like(578 ckpt_path, "v1.2b (v10: audible reverb + conference mix + pop fixes)")579 print(f"v1.2b loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "580 f"src={ckpt_path}")581 return model, info582 583 584def _build_v12c():585 ckpt_path = _resolve_v12c_ckpt()586 if ckpt_path is None:587 return None, None588 model, info = _build_v12_like(589 ckpt_path, "v1.2c (v11: level-invariance mic-gain on v1.2b base)")590 print(f"v1.2c loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "591 f"src={ckpt_path}")592 return model, info593 594 595def _build_v12d():596 ckpt_path = _resolve_v12d_ckpt()597 if ckpt_path is None:598 return None, None599 model, info = _build_v12_like(600 ckpt_path, "v1.2d (v11_refine e22: low-LR cosine polish of v1.2c)")601 print(f"v1.2d loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "602 f"src={ckpt_path}")603 return model, info604 605 606def _build_v13():607 ckpt_path = _resolve_v13_ckpt()608 if ckpt_path is None:609 return None, None610 model, info = _build_v12_like(ckpt_path, "v1.3 (current release)")611 print(f"v1.3 loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "612 f"src={ckpt_path}")613 return model, info614 615 616def _build_v13nr():617 ckpt_path = _resolve_v13nr_ckpt()618 if ckpt_path is None:619 return None, None620 model, info = _build_v12_like(621 ckpt_path, "v1.3-NR (r009 ep5: v1.3 + noise-residual fine-tune)")622 print(f"v1.3-NR loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "623 f"src={ckpt_path}")624 return model, info625 626 627def _build_v13me():628 ckpt_path = _resolve_v13me_ckpt()629 if ckpt_path is None:630 return None, None631 model, info = _build_v12_like(632 ckpt_path, "v1.3-ME (r011 ep5: v1.3 + mask-energy fine-tune)")633 print(f"v1.3-ME loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "634 f"src={ckpt_path}")635 return model, info636 637 638def _build_v13mep():639 ckpt_path = _resolve_v13mep_ckpt()640 if ckpt_path is None:641 return None, None642 model, info = _build_v12_like(643 ckpt_path, "v1.3-MEP (r013 ep5: ME + symmetric preserve)")644 print(f"v1.3-MEP loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "645 f"src={ckpt_path}")646 return model, info647 648 649def _build_v13mep2():650 ckpt_path = _resolve_v13mep2_ckpt()651 if ckpt_path is None:652 return None, None653 model, info = _build_v12_like(654 ckpt_path, "v1.3-MEP2 (r014 ep5: r013 cont, softer cosine)")655 print(f"v1.3-MEP2 loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "656 f"src={ckpt_path}")657 return model, info658 659 660def _build_v13r2():661 ckpt_path = _resolve_v13r2_ckpt()662 if ckpt_path is None:663 return None, None664 model, info = _build_v12_like(665 ckpt_path, "v1.3.r2 (1.55M from-scratch MEP2 — archslim)")666 print(f"v1.3.r2 loaded: {info['n_params']:,} params sha={info['sha256'][:16]}… "667 f"src={ckpt_path}")668 return model, info669 670 671# ── GGML C++ engine entries (released .gguf artifacts) ──────────────────────672# label, HF filename, params (display only), local_only673GGUF_SPECS = [674 ("v1 (1.3M, GGUF)", "localvqe-v1-1.3M-f32.gguf", 1_290_453, False),675 ("v1.1 (1.3M, GGUF)", "localvqe-v1.1-1.3M-f32.gguf", 1_290_845, False),676 ("v1.2 (1.3M, GGUF)", "localvqe-v1.2-1.3M-f32.gguf", 1_290_845, False),677 ("v1.3 (4.8M, GGUF)", "localvqe-v1.3-4.8M-f32.gguf", 4_814_655, False),678 ("v1.4-AEC (203K, echo-only, GGUF)",679 "localvqe-v1.4-aec-200K-f32.gguf", 202_941, False),680 ("v1.4-AEC (203K, echo-only, GGUF bf16)",681 "localvqe-v1.4-aec-200K-bf16.gguf", 202_941, True),682 # Front-end-only (2.7K): daf.standalone GGUF — the engine emits the683 # adaptive filter's `e` directly (no neural mask). Same process_f32 path.684 ("v1.4-AEC front-end only (2.7K, GGUF)",685 "localvqe-v1.4-aec-2.7K-f32.gguf", 2_742, False),686 # Compact / low-power line (~49K GTCRN-AEC). Self-contained GGUFs (the DAF687 # front-end is embedded), so they load through the same GGUFModel path with688 # no extra files. v1 = full enhance; AEC = echo-only keep-noise.689 ("LocalVQE-Pi-v1-49k (GGUF)", "localvqe-pi-v1-49k-f32.gguf", 48_965, False),690 ("LocalVQE-Pi-AEC-v1-49k (GGUF)",691 "localvqe-pi-aec-v1-49k-f32.gguf", 48_965, False),692]693 694 695def _resolve_gguf(fname: str) -> str | None:696 d = os.environ.get("LOCALVQE_GGUF_DIR")697 if d and (Path(d) / fname).exists():698 return str(Path(d) / fname)699 try:700 from huggingface_hub import hf_hub_download701 return hf_hub_download(repo_id=HF_REPO_ID, filename=fname)702 except Exception:703 return None704 705 706def _build_gguf(label: str, fname: str, n_params: int):707 p = _resolve_gguf(fname)708 if p is None:709 return None, None710 try:711 model = GGUFModel(p)712 except Exception as e:713 print(f"{label}: GGML engine unavailable: {e}")714 return None, None715 info = {716 "source": p,717 "sha256": _sha256(p),718 "n_params": n_params,719 "label": f"{label} — GGML C++ engine",720 }721 print(f"{label} loaded (GGML): sha={info['sha256'][:16]}… src={p}")722 return model, info723 724 725# PyTorch reference entries are local-only — never built on HF Spaces,726# which keeps torch entirely unimported there.727if ON_SPACE:728 MODEL_V1 = MODEL_V11 = MODEL_V12 = MODEL_V13 = None729 MODEL_V14AEC = MODEL_V14AECFE = None730 MODEL_GTCRN2 = MODEL_GTCRN_KN = None731 INFO_V1 = INFO_V11 = INFO_V12 = INFO_V13 = None732 INFO_V14AEC = INFO_V14AECFE = None733 INFO_GTCRN2 = INFO_GTCRN_KN = None734else:735 MODEL_V1, INFO_V1 = _build_v1()736 MODEL_V11, INFO_V11 = _build_v11()737 MODEL_V12, INFO_V12 = _build_v12()738 MODEL_V13, INFO_V13 = _build_v13()739 MODEL_V14AEC, INFO_V14AEC = _build_v14aec()740 MODEL_V14AECFE, INFO_V14AECFE = _build_v14aec_fe()741 # LocalVQE-Pi 49K GTCRN_AEC family (Raspberry-Pi deployment target). Same742 # architecture, different training target:743 # LocalVQE-Pi-v1-49k full enhancer (echo + NS + dereverb; `clean`)744 # LocalVQE-Pi-AEC-v1-49k echo-only (keep noise + reverb; `room`)745 MODEL_GTCRN2, INFO_GTCRN2 = _build_gtcrn(746 _resolve_gtcrn_os400_ckpt, "LocalVQE-Pi-v1-49k")747 MODEL_GTCRN_KN, INFO_GTCRN_KN = _build_gtcrn(748 _resolve_gtcrn_keepnoise_ckpt, "LocalVQE-Pi-AEC-v1-49k")749# Lineup trimmed to the released artifacts + the two LocalVQE-Pi 49K models.750# Unreleased research entries pruned (builders kept above for revival):751# v1.2.1/v1.2a-d, v1.3-NR/ME/MEP/MEP2, v1.3.r2 (1.55M), v1.4 NS+dereverb752# 200K/800K, the retrained-controller v1.4-AEC variants, the 1.7K gate, and753# the earlier GTCRN echo-priority / +retrained-FE experiments.754MODEL_V121 = MODEL_V12A = MODEL_V12B = MODEL_V12C = MODEL_V12D = None755MODEL_V13NR = MODEL_V13ME = MODEL_V13MEP = MODEL_V13MEP2 = None756INFO_V121 = INFO_V12A = INFO_V12B = INFO_V12C = INFO_V12D = None757INFO_V13NR = INFO_V13ME = INFO_V13MEP = INFO_V13MEP2 = None758 759MODELS: dict[str, object] = {}760INFOS: dict[str, dict] = {}761# Lineup: GGML engine entries first (the shipped artifacts — the only762# entries on HF Spaces), then the PyTorch references (local-only, for763# A/B against the deployed engine). Dropdown labels carry the param764# size so the capacity each variant represents is visible at a glance.765for _label, _fname, _np_, _local_only in GGUF_SPECS:766 if _local_only and ON_SPACE:767 continue768 _m, _i = _build_gguf(_label, _fname, _np_)769 if _m is not None:770 MODELS[_label] = _m771 INFOS[_label] = _i772if MODEL_V1 is not None:773 MODELS["v1 (1.3M)"] = MODEL_V1774 INFOS["v1 (1.3M)"] = INFO_V1775if MODEL_V11 is not None:776 MODELS["v1.1 (1.3M)"] = MODEL_V11777 INFOS["v1.1 (1.3M)"] = INFO_V11778if MODEL_V12 is not None:779 MODELS["v1.2 (1.3M)"] = MODEL_V12780 INFOS["v1.2 (1.3M)"] = INFO_V12781if MODEL_V13 is not None:782 MODELS["v1.3 (4.8M)"] = MODEL_V13783 INFOS["v1.3 (4.8M)"] = INFO_V13784if MODEL_V14AEC is not None:785 MODELS["v1.4-AEC (203K, echo-only)"] = MODEL_V14AEC786 INFOS["v1.4-AEC (203K, echo-only)"] = INFO_V14AEC787if MODEL_V14AECFE is not None:788 MODELS["v1.4-AEC front-end only (2.7K)"] = MODEL_V14AECFE789 INFOS["v1.4-AEC front-end only (2.7K)"] = INFO_V14AECFE790if MODEL_GTCRN2 is not None:791 MODELS["LocalVQE-Pi-v1-49k"] = MODEL_GTCRN2792 INFOS["LocalVQE-Pi-v1-49k"] = INFO_GTCRN2793if MODEL_GTCRN_KN is not None:794 MODELS["LocalVQE-Pi-AEC-v1-49k"] = MODEL_GTCRN_KN795 INFOS["LocalVQE-Pi-AEC-v1-49k"] = INFO_GTCRN_KN796if not MODELS:797 raise RuntimeError(798 "No model could be loaded. Check that space/lib contains the "799 "GGML bundle (build_lib.sh) and the released .gguf files are "800 "reachable (LOCALVQE_GGUF_DIR or HF access); for the local "801 "PyTorch entries set LOCALVQE_V1_CKPT .. LOCALVQE_V14AEC_CKPT."802 )803 804DEFAULT_MODEL_KEY = next(805 (k for k in ("v1.3 (4.8M, GGUF)", "v1.3 (4.8M)", "v1.2 (1.3M, GGUF)",806 "v1.2 (1.3M)") if k in MODELS),807 next(iter(MODELS)),808)809 810# Dev mode: shows the diagnostic-source dropdown and mask-smoother811# accordion in the UI. Auto-on locally, auto-off on HF Spaces (which812# always sets `SPACE_ID`). Either can be overridden by setting813# LOCALVQE_DEV_MODE=1 (force on) or =0 (force off).814def _dev_mode() -> bool:815 explicit = os.environ.get("LOCALVQE_DEV_MODE")816 if explicit in ("0", "1"):817 return explicit == "1"818 return "SPACE_ID" not in os.environ819DEV_MODE = _dev_mode()820if DEV_MODE:821 print("DEV_MODE=on (debug accordions visible). Set LOCALVQE_DEV_MODE=0 to hide.")822 823 824def _load_mono_16k(path: str) -> np.ndarray:825 wav, sr = sf.read(path, dtype="float32", always_2d=False)826 if wav.ndim == 2:827 wav = wav.mean(axis=1)828 if sr != SR:829 from math import gcd830 g = gcd(sr, SR)831 wav = resample_poly(wav, SR // g, sr // g).astype(np.float32)832 return wav833 834 835# Debug / diagnostic helpers live in `_debug.py`, which is excluded836# from the HuggingFace Spaces deploy. When this file is missing the837# app silently degrades: no debug accordions, no diagnostic-source838# branches, just the standard model forward.839try:840 import _debug as _dbg841 DEBUG_AVAILABLE = True842except ImportError:843 _dbg = None844 DEBUG_AVAILABLE = False845 846 847def _noise_gate(x: np.ndarray, threshold_dbfs: float) -> np.ndarray:848 """Hard-gate frames whose RMS is below `threshold_dbfs` to zero.849 850 Operates on 10 ms frames (160 samples at 16 kHz) — short enough851 that speech bursts aren't truncated, long enough that a single852 out-of-band sample inside an active region doesn't get muted.853 The ungated tail (samples that don't fill a full final frame) is854 passed through unchanged.855 """856 frame = 160857 n = len(x) // frame858 if n == 0:859 return x860 f = x[: n * frame].reshape(n, frame).astype(np.float32)861 rms = np.sqrt((f * f).mean(axis=-1) + 1e-12)862 rms_db = 20.0 * np.log10(rms + 1e-12)863 keep = (rms_db > threshold_dbfs).astype(np.float32)864 gated = (f * keep[:, None]).reshape(-1)865 return np.concatenate([gated, x[n * frame:]]).astype(x.dtype)866 867 868def enhance(mic_path: str, ref_path: str,869 model_choice: str = DEFAULT_MODEL_KEY,870 gate_enabled: bool = False,871 gate_threshold_db: float = -45.0,872 smoother_mode: str = "off",873 smoother_attack_db: float = 12.0,874 smoother_release_db: float = 1.0,875 smoother_ema_alpha: float = 0.7,876 smoother_floor_db: float = 20.0,877 smoother_median_k: int = 3,878 debug_source: str = "enhanced",879 f_smooth_kernel: int = 31,880 f_smooth_mode: str = "median") -> tuple[int, np.ndarray]:881 if mic_path is None:882 raise gr.Error("Upload or pick a mic recording first.")883 if model_choice not in MODELS:884 raise gr.Error(f"Model {model_choice!r} not loaded. Available: {list(MODELS)}")885 model = MODELS[model_choice]886 887 mic = _load_mono_16k(mic_path)888 if ref_path is None:889 ref = np.zeros_like(mic)890 else:891 ref = _load_mono_16k(ref_path)892 893 n = max(len(mic), len(ref))894 if len(mic) < n:895 mic = np.pad(mic, (0, n - len(mic)))896 if len(ref) < n:897 ref = np.pad(ref, (0, n - len(ref)))898 899 if isinstance(model, GGUFModel):900 # Released C++ engine: waveform in, waveform out. The debug901 # accordions poke PyTorch internals and don't apply here.902 out = model.process(mic, ref)903 elif getattr(model, "IS_GATE", False):904 # Front-end gate: DAF → e → per-bin magnitude gate → iSTFT. Time-domain905 # in/out (no enc/decoder split); the debug accordions don't apply.906 out = model.process(mic, ref)907 elif getattr(model, "IS_GTCRN", False):908 # AEC-aware GTCRN: DAF → (e, yhat=mic−e) → complex mask on e → iSTFT.909 # Time-domain in/out; the debug accordions don't apply.910 out = model.process(mic, ref)911 else:912 import torch913 mic_t = torch.from_numpy(mic).unsqueeze(0)914 ref_t = torch.from_numpy(ref).unsqueeze(0)915 916 with torch.no_grad():917 if DEBUG_AVAILABLE and debug_source != "enhanced":918 enc = _dbg.apply_debug_source(919 model, mic_t, ref_t, debug_source,920 smoother_ema_alpha=smoother_ema_alpha,921 f_smooth_kernel=f_smooth_kernel,922 f_smooth_mode=f_smooth_mode,923 )924 else:925 enc = model(mic_t, ref_t)926 927 if (DEBUG_AVAILABLE and smoother_mode != "off"928 and debug_source not in ("passthrough", "bypass_ccm")):929 enc = _dbg.apply_smoother(930 enc, model.encoder(mic_t), smoother_mode,931 attack_db=smoother_attack_db,932 release_db=smoother_release_db,933 ema_alpha=smoother_ema_alpha,934 floor_db=smoother_floor_db,935 median_k=smoother_median_k,936 )937 enh = model.decoder(enc.float(), length=n)938 939 out = enh[0].cpu().numpy()940 peak = float(np.abs(out).max())941 if peak > 0.95:942 out = out / peak * 0.95943 # Optional residual-echo gate: silence frames whose RMS sits below944 # `gate_threshold_db` dBFS. Off by default so listeners can A/B945 # against the raw model output via the slider.946 if gate_enabled:947 out = _noise_gate(out, gate_threshold_db)948 # Convert to int16 ourselves: Gradio's gr.Audio output otherwise949 # peak-normalises float arrays via convert_to_16_bit_wav (data /=950 # np.abs(data).max(); * 32767), which amplifies the cancelled-echo951 # residual on AEC-heavy clips by 1000×+ and makes it sound like952 # the model isn't suppressing anything. Returning int16 preserves953 # the true (quiet) loudness so listeners hear the actual output.954 out_i16 = np.clip(out * 32767, -32768, 32767).astype(np.int16)955 return SR, out_i16956 957 958EXAMPLES = [959 [960 str(EXAMPLES_DIR / "ne_st_noisy_mic.wav"),961 str(EXAMPLES_DIR / "ne_st_noisy_ref.wav"),962 ],963 [964 str(EXAMPLES_DIR / "ne_st_clean_mic.wav"),965 str(EXAMPLES_DIR / "ne_st_clean_ref.wav"),966 ],967 [968 str(EXAMPLES_DIR / "fe_st_mic.wav"),969 str(EXAMPLES_DIR / "fe_st_ref.wav"),970 ],971 [972 str(EXAMPLES_DIR / "fe_st2_mic.wav"),973 str(EXAMPLES_DIR / "fe_st2_ref.wav"),974 ],975 [976 str(EXAMPLES_DIR / "dt_mic.wav"),977 str(EXAMPLES_DIR / "dt_ref.wav"),978 ],979]980 981DESCRIPTION = """982**LocalVQE** is a ~1 M-parameter open-source model that cleans up a983microphone signal on a voice call: it cancels the remote participant's984voice being picked up again (echo), suppresses background noise, and985removes reverberation — all in a single causal pass on CPU.986 987Provide two inputs:988 989- **Mic**: the raw microphone recording (what the far end would hear990 without any processing).991- **Far-end reference**: the audio being played out of your speakers.992 For a pure noise-suppression test (no speaker playback), upload993 silence or leave empty.994 995Try the bundled examples first — they cover heavy and light996near-end noise (NE-ST mixed with DNS5 background at 5 dB and 20 dB997SNR), a clean far-end single-talk clip, a far-end clip with some998near-end overlap (mislabelled in the source corpus, but a useful999test of AEC + near-end preservation together), and a double-talk1000clip — all from the ICASSP 2022 AEC Challenge blind set.1001 1002The **v1.4-AEC** entry in the model selector removes *only* the echo:1003background noise and room sound are kept on purpose (use it when1004something downstream owns noise suppression, or when you want the1005natural ambience). On the noise-only examples it should sound close1006to the input — that's correct behaviour, not a failure to enhance.1007 1008Weights: [LocalAI-io/LocalVQE](https://huggingface.co/LocalAI-io/LocalVQE) ·1009Code: [github.com/localai-org/LocalVQE](https://github.com/localai-org/LocalVQE)1010"""1011 1012with gr.Blocks(title="LocalVQE Demo") as demo:1013 gr.Markdown("# LocalVQE: real-time AEC + noise suppression + dereverb")1014 gr.Markdown(DESCRIPTION)1015 with gr.Row():1016 mic_in = gr.Audio(label="Mic (microphone recording)", type="filepath")1017 ref_in = gr.Audio(label="Far-end reference (speaker playback)", type="filepath")1018 model_choice = gr.Radio(1019 choices=list(MODELS.keys()),1020 value=DEFAULT_MODEL_KEY,1021 label="Model",1022 info=(1023 "GGUF entries run the released GGML C++ engine — the same "1024 "artifact you'd deploy. v1.3 (joint AEC + noise suppression "1025 "+ dereverb) is the current full-enhancement release; v1.2 "1026 "is its smaller/faster sibling, v1.1 / v1 are kept for A/B. "1027 "v1.4-AEC is different by design: it ONLY removes echo — "1028 "your voice, the room, and any background noise stay in the "1029 "output. Switch and re-run on the same clip to compare."1030 ),1031 ) if len(MODELS) > 1 else gr.State(DEFAULT_MODEL_KEY)1032 with gr.Row():1033 gate_enabled = gr.Checkbox(1034 label="Residual-echo gate",1035 value=False,1036 info=(1037 "Post-process the enhanced output: silence any 10 ms frame "1038 "whose RMS falls below the threshold. Cleans up the quiet "1039 "residual you'd hear during far-end-only stretches; will "1040 "also mute genuinely quiet speech below the threshold."1041 ),1042 )1043 gate_threshold_db = gr.Slider(1044 label="Gate threshold (dBFS)",1045 minimum=-70.0, maximum=-20.0, value=-45.0, step=1.0,1046 )1047 if DEBUG_AVAILABLE and DEV_MODE:1048 _dbg_components = _dbg.build_debug_ui(gr)1049 debug_source = _dbg_components["debug_source"]1050 f_smooth_kernel = _dbg_components["f_smooth_kernel"]1051 f_smooth_mode = _dbg_components["f_smooth_mode"]1052 smoother_mode = _dbg_components["smoother_mode"]1053 smoother_attack_db = _dbg_components["smoother_attack_db"]1054 smoother_release_db = _dbg_components["smoother_release_db"]1055 smoother_ema_alpha = _dbg_components["smoother_ema_alpha"]1056 smoother_floor_db = _dbg_components["smoother_floor_db"]1057 smoother_median_k = _dbg_components["smoother_median_k"]1058 else:1059 # Production / no _debug.py — hidden gr.State holders carrying1060 # neutral defaults, so `enhance()` keeps a stable input list.1061 debug_source = gr.State("enhanced")1062 f_smooth_kernel = gr.State(31)1063 f_smooth_mode = gr.State("median")1064 smoother_mode = gr.State("off")1065 smoother_attack_db = gr.State(12.0)1066 smoother_release_db = gr.State(1.0)1067 smoother_ema_alpha = gr.State(0.7)1068 smoother_floor_db = gr.State(20.0)1069 smoother_median_k = gr.State(3)1070 btn = gr.Button("Enhance", variant="primary")1071 out = gr.Audio(label="Enhanced output", type="numpy")1072 1073 gr.Examples(1074 examples=EXAMPLES,1075 inputs=[mic_in, ref_in],1076 label=(1077 "Examples — top to bottom: near-end + heavy noise (5 dB SNR, "1078 "pure NS), near-end + light noise (20 dB SNR, NS preserving "1079 "clean speech), far-end single-talk (pure AEC), far-end with "1080 "brief near-end overlap (AEC while preserving NE), and "1081 "double-talk (AEC while near-end is also talking)."1082 ),1083 )1084 1085 btn.click(1086 enhance,1087 inputs=[mic_in, ref_in, model_choice,1088 gate_enabled, gate_threshold_db,1089 smoother_mode, smoother_attack_db, smoother_release_db,1090 smoother_ema_alpha, smoother_floor_db, smoother_median_k,1091 debug_source, f_smooth_kernel, f_smooth_mode],1092 outputs=out,1093 )1094 1095 _info_lines = []1096 for key in MODELS:1097 i = INFOS[key]1098 _info_lines.append(1099 f"<b>{i['label']}</b> — <code>{i['source']}</code> · "1100 f"sha256 <code>{i['sha256'][:16]}…</code> · "1101 f"{i['n_params']:,} params"1102 )1103 gr.Markdown("<sub>Loaded models:<br>" + "<br>".join(_info_lines) + "</sub>")1104 1105if __name__ == "__main__":1106 demo.launch(server_name=os.environ.get("GRADIO_SERVER_NAME", "127.0.0.1"))1107 