25acf9db6e
横向对比云端 gummy 与本地开源模型(faster-whisper/SenseVoice/Paraformer), 重点覆盖中英混说,产出准确率(CER/WER/MER)/速度(延迟/RTF)/资源(cpu/mem/模型大小) 对比报告。公共集(ASCEND/AISHELL/LibriSpeech)统一走 HF 适配器 + 自定义 JSONL manifest。 gummy 引擎对照 server/internal/asr/gummy.go 协议移植。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
2.2 KiB
Python
71 lines
2.2 KiB
Python
"""本地 faster-whisper 引擎(CTranslate2,CPU-first)。多语,对中英混说有基础能力。
|
||
|
||
依赖:pip install '.[whisper]'
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import time
|
||
|
||
from ..metrics.resource import dir_size_mb
|
||
from .base import Engine, Transcript
|
||
|
||
|
||
class WhisperEngine(Engine):
|
||
kind = "offline"
|
||
is_local = True
|
||
|
||
def __init__(
|
||
self,
|
||
name: str = "whisper-small",
|
||
model_size: str = "small",
|
||
device: str = "cpu",
|
||
compute_type: str = "int8",
|
||
model_dir: str | None = None,
|
||
):
|
||
self.name = name
|
||
self.model_size = model_size
|
||
self.device = device
|
||
self.compute_type = compute_type
|
||
self.model_dir = model_dir
|
||
self.model = None
|
||
self._weights_path: str | None = None
|
||
|
||
def load(self) -> None:
|
||
from faster_whisper import WhisperModel
|
||
|
||
self.model = WhisperModel(
|
||
self.model_size,
|
||
device=self.device,
|
||
compute_type=self.compute_type,
|
||
download_root=self.model_dir,
|
||
)
|
||
self._resolve_weights_path()
|
||
|
||
def _resolve_weights_path(self) -> None:
|
||
# 解析磁盘权重路径用于报模型大小(best-effort)
|
||
try:
|
||
from huggingface_hub import snapshot_download
|
||
|
||
repo = f"Systran/faster-whisper-{self.model_size}"
|
||
self._weights_path = snapshot_download(repo, local_files_only=True, cache_dir=self.model_dir)
|
||
except Exception:
|
||
self._weights_path = self.model_dir
|
||
|
||
def transcribe(self, audio_path: str) -> Transcript:
|
||
if self.model is None:
|
||
self.load()
|
||
t0 = time.monotonic()
|
||
# language=None → 自动检测;中英混说交给模型自身
|
||
segments, info = self.model.transcribe(audio_path, language=None, beam_size=5)
|
||
text = "".join(seg.text for seg in segments) # 生成器在此 join 时才真正解码
|
||
proc = time.monotonic() - t0
|
||
return Transcript(text=text.strip(), audio_sec=float(info.duration), proc_sec=proc)
|
||
|
||
def model_size_mb(self) -> float | None:
|
||
size = dir_size_mb(self._weights_path) if self._weights_path else 0.0
|
||
return size or None
|
||
|
||
def unload(self) -> None:
|
||
self.model = None
|