fela-streaming-asr / modeling.py
itstheraj's picture
initial commit
c513220
Raw
History Blame Contribute Delete
3.86 kB
from __future__ import annotations
import json
import os
from typing import Optional
import torch
TARGET_SR = 16000
def validate_audio(wav: torch.Tensor, sr: int):
if sr != TARGET_SR:
raise ValueError(
f"sample rate must be {TARGET_SR} Hz after resampling; got {sr}"
)
if wav.dim() != 2 or wav.size(0) != 1:
raise ValueError(
f"waveform must be mono with shape (1, num_samples); got {tuple(wav.shape)}"
)
if wav.numel() == 0:
raise ValueError("empty waveform")
if wav.abs().max() > 1.5:
raise ValueError(
"waveform looks unnormalized (values outside [-1, 1]); "
"scale int16 by 1/32768 before passing it in"
)
def preprocess_audio(source, sr: Optional[int] = None) -> torch.Tensor:
import torchaudio
if isinstance(source, str):
wav, in_sr = torchaudio.load(source)
else:
wav = torch.as_tensor(source, dtype=torch.float32)
if wav.dim() == 1:
wav = wav.unsqueeze(0)
in_sr = sr if sr is not None else TARGET_SR
if wav.abs().max() > 1.5:
wav = wav / 32768.0
if wav.size(0) > 1:
wav = wav.mean(0, keepdim=True)
if in_sr != TARGET_SR:
wav = torchaudio.functional.resample(wav, in_sr, TARGET_SR)
validate_audio(wav, TARGET_SR)
return wav
def _read_config(config_path: Optional[str]) -> dict:
if config_path and os.path.exists(config_path):
with open(config_path) as f:
return json.load(f)
return {}
def load_model(
weights: str, bpe: str = "bpe256.model", config_path: Optional[str] = None
):
here = os.path.dirname(os.path.abspath(__file__))
if config_path is None:
cand = os.path.join(here, "config.json")
config_path = cand if os.path.exists(cand) else None
cfg_json = _read_config(config_path)
from fela_ctc2 import FELACTC2, BPE, greedy_decode_bpe
from model_cpu_gpt2 import CPUGPTConfig
bpe_obj = BPE(model_file=bpe)
cfg = CPUGPTConfig(
vocab_size=cfg_json.get("vocab", 257),
seq_len=cfg_json.get("seq_len", 2048),
n_layer=cfg_json.get("n_layer", 16),
n_embd=cfg_json.get("n_embd", 512),
n_head=cfg_json.get("n_head", 8),
fno_modes=cfg_json.get("fno_modes", 256),
gla_chunk=cfg_json.get("gla_chunk", 64),
ffn_hidden=cfg_json.get("ffn_hidden", 2048),
layer_pattern=cfg_json.get("layer_pattern", "FNO"),
dropout=0.0,
)
if hasattr(cfg, "gla_delta"):
cfg.gla_delta = bool(cfg_json.get("gla_delta", False))
model = FELACTC2(cfg, vocab=bpe_obj.vocab).eval()
if weights.endswith(".safetensors"):
from safetensors.torch import load_file
state = load_file(weights)
else:
ck = torch.load(weights, map_location="cpu", weights_only=False)
state = ck.get("state", ck.get("model", ck)) if isinstance(ck, dict) else ck
model.load_state_dict(state, strict=False)
return model, bpe_obj, greedy_decode_bpe
def from_pretrained(repo_id: str = "lowdown-labs/asr-streaming"):
from huggingface_hub import hf_hub_download
cfg_path = hf_hub_download(repo_id, "config.json")
bpe = hf_hub_download(repo_id, "bpe256.model")
try:
w = hf_hub_download(repo_id, "model.safetensors")
except Exception:
w = hf_hub_download(repo_id, "model.pt")
return load_model(w, bpe=bpe, config_path=cfg_path)
@torch.no_grad()
def stream_transcribe(
model,
bpe,
decode_fn,
wav: torch.Tensor,
frame_chunk: int = 100,
reset_frames: int = 1200,
) -> str:
out = []
for logp in model.stream_logits(
wav, frame_chunk=frame_chunk, reset_frames=reset_frames
):
t = decode_fn(logp[0], bpe)
if t:
out.append(t)
return " ".join(out)