| --- |
| license: other |
| license_name: mixed-provenance |
| license_link: https://huggingface.co/Scicom-intl/CallEnhancer-Lite#licence |
| language: [en, ms, zh] |
| tags: [speech-enhancement, speech-restoration, call-centre, telephony, distilhubert, realtime] |
| base_model: ntu-spml/distilhubert |
| pipeline_tag: audio-to-audio |
| --- |
| |
| # CallEnhancer-Lite β realtime call-centre speech restoration |
|
|
| A realtime-oriented sibling of [`Scicom-intl/CallEnhancer`](https://huggingface.co/Scicom-intl/CallEnhancer) |
| (enhancer-small) and [`Scicom-intl/CallEnhancer-Base`](https://huggingface.co/Scicom-intl/CallEnhancer-Base) |
| (enhancer-base). Same two-stage recipe β a feature predictor followed by a DAC vocoder β |
| but the 580M w2v-BERT 2.0 encoder is replaced by **DistilHuBERT (2 transformer layers)** |
| plus a small predictor head, and the target degradation is narrowed to three jobs: |
| **call-centre channel**, **background-noise removal**, and **cross-talk**. |
|
|
| | | encoder | decoder | total | |
| |---|---|---|---| |
| | enhancer-small | w2v-BERT 24L + LoRA β 580M | DAC 3072ch @48 kHz β 188M | 768M | |
| | enhancer-base | w2v-BERT 24L + LoRA β 580M | DAC 3072ch +extra_res β 381M | 961M | |
| | **enhancer-lite** | **DistilHuBERT 2L + head β 37M** | DAC 1536ch @24 kHz β 52M | **89M** | |
| |
| ## β οΈ Status: stage 1 only β this is not yet a usable enhancer |
| |
| **This repo currently contains the ENCODER only.** It maps degraded 16 kHz audio to |
| 1024-d SSL features; it does **not** produce audio. The matching 24 kHz decoder is still |
| training and will be added as `decoder_lite/`. Until then these weights are useful for |
| resuming training or for feature-level work, not for restoring calls. |
|
|
| ## What the encoder does |
|
|
| ``` |
| degraded 16 kHz --> [DistilHuBERT 2L + PredictorHead] --> features [T, 1024] @ 50 Hz |
| ``` |
|
|
| It is distilled against a **frozen w2v-BERT 2.0 (24L) teacher running on the clean |
| audio**, so it predicts the same 1024-d feature space the existing CallEnhancer decoders |
| consume. Both stacks emit 50 Hz features (DistilHuBERT's conv frontend strides to 320 |
| samples @16 kHz), which is exactly the DAC decoder's frame rate. |
|
|
| ## Results |
|
|
| Feature-space error against the clean teacher, as `nmse = MSE / var(target) = 1 β RΒ²` |
| (scale-free; 1.0 = no better than predicting the mean, 0.0 = exact): |
|
|
| | model | encoder params | nmse | |
| |---|---|---| |
| | enhancer-small FE (w2v-BERT 24L + LoRA) | 580M | ~0.147 | |
| | **enhancer-lite FE @1M (this checkpoint)** | **37M** | **0.1401** | |
|
|
| The 15Γ smaller encoder matches the large one on this metric. The LR schedule was the |
| whole story: constant 1e-4 to 200k (plateau at 0.1652), then cosine decay with SGDR-style |
| warm restarts β 0.1652 β 0.1505 @300k β 0.1458 @500k β 0.1401 @1M. |
|
|
| Caveats for honest comparison: the two runs use different degradation menus, and ~40% of |
| the loss is measured on zero-padding in both (median training file is 6.7 s against a |
| 12 s window). The quoted figure is a rolling mean over 10k steps; per-step values swing |
| by roughly Β±0.007 around it. |
|
|
| ## Speed |
|
|
| Measured on 16 CPU threads, 2 s chunk, batch 1, fp32: |
|
|
| | stack | encoder | decoder | total | xRT | |
| |---|---|---|---|---| |
| | enhancer-small | 137.1 ms | 468.2 ms | 605.2 ms | 3.3 | |
| | **enhancer-lite** (37M + 52M @24 kHz) | **18.7 ms** | **114.4 ms** | **133.1 ms** | **15.0** | |
|
|
| The encoder is 6.7Γ faster; the decoder shrink is what delivers the rest. On GPU both |
| stacks are far past realtime, so the encoder swap's value there is sequential depth |
| (24 attention blocks β 2), weight traffic (1.2 GB β 74 MB bf16) and no O(TΒ²) attention β |
| i.e. small-chunk streaming latency and per-stream memory. |
|
|
| Algorithmic lookahead: 80 ms (2 head blocks Γ kernel 5 at 50 Hz). |
|
|
| ## Files |
|
|
| | file | what | |
| |---|---| |
| | `fe_lite/fe_only.pt` | encoder weights (~148 MB) β for inference | |
| | `fe_lite/last.pt` | full train state incl. optimizer (~443 MB) β resumable | |
|
|
| ## Usage |
|
|
| ```python |
| import torch, fe_distilhubert as fedh # runpod/fe_distilhubert.py in the Sidon repo |
| |
| fe = fedh.load_fe("fe_lite/fe_only.pt", torch.device("cuda")) |
| proc = fedh.make_processor() |
| batch = fedh.collate_wavs([wav_16k_float32], proc) # raw waveform in |
| feats = fe(**{k: v.cuda() for k, v in batch.items()}).last_hidden_state # [1, T, 1024] |
| ``` |
|
|
| No `peft` needed β unlike the LoRA-based enhancer-small FE, this is a full finetune. |
|
|
| ## Training |
|
|
| * **Degradation** (all applied online, never precomputed): telephony channel 85% |
| (200β350 Hz high-pass β random 2.8β4.2 kHz ceiling via an 8/11.025/12/16 kHz bottleneck |
| β GSM / G.711 Β΅-law / none β 16β40 kbps MP3 β line noise β VoIP dropouts); caller-side |
| background noise 85% at 0β22 dB SNR from a real corpus; **cross-talk 50%** (1β2 |
| interferer speakers, each active in a random 30β80% sub-segment with raised-cosine |
| fades, optionally passed through their own phone channel, SIR 2β15 dB, plus half-duplex |
| ducking); babble 20%. Reverb, echo, AGC, hum, codec tandeming, packet-loss concealment |
| and handset EQ are deliberately **off** β the small model spends its capacity on three |
| problems rather than ten. |
| * Cross-talk and babble are mixed into the **input only**; the target remains the clean |
| dominant speaker, so "restore the loudest talker" is well defined. |
| * 1M steps, effective batch 32, 12 s windows, bf16, 2Γ H20. |
| * Teacher pool: ~2,700 h of β₯44.1 kHz speech, DNSMOS-filtered, spanning read, audiobook, |
| podcast and conversational registers across many languages. |
|
|
| ## Licence |
|
|
| `other` β provenance is mixed and not uniformly permissive. The base model |
| (`ntu-spml/distilhubert`) is Apache-2.0, but the noise corpus used during training |
| includes CC BY-NC and share-alike material, and parts of the speech pool carry |
| non-commercial terms. Review the upstream sources before any commercial use. |
|
|