CoolFace
Apppublic

LocalAI-io/LocalVQE-demo

sourceHugging Faceapache-2.0updated 3mo agoView on Hugging Face
23likes
app.py1107 linesDownload Raw Back to root
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