mirror of
https://github.com/modelscope/FunASR.git
synced 2026-10-12 02:19:18 +00:00
fix: respect explicit VAD silence threshold
This commit is contained in:
@@ -45,6 +45,14 @@ cd /path/to/FunASR && pip install -e .
|
||||
|
||||
**Hardware**: GPU ≥ 8 GB VRAM, CUDA ≥ 11.8. 16 GB+ recommended.
|
||||
|
||||
Install a PyTorch/torchaudio/vLLM combination that matches your NVIDIA driver and
|
||||
CUDA runtime. Do not blindly keep the newest wheel if it was built for a newer
|
||||
CUDA runtime than your driver supports; PyTorch can fail during CUDA
|
||||
initialization with `The NVIDIA driver on your system is too old` before FunASR
|
||||
starts. If that happens, reinstall compatible PyTorch, torchaudio, and vLLM
|
||||
wheels for the CUDA version reported by `nvidia-smi`, or update the NVIDIA
|
||||
driver first.
|
||||
|
||||
---
|
||||
|
||||
## 2. vLLM Engine Architecture
|
||||
|
||||
@@ -44,6 +44,13 @@ cd /path/to/FunASR && pip install -e .
|
||||
|
||||
**硬件**:GPU ≥ 8GB VRAM,CUDA ≥ 11.8。推荐 16GB+。
|
||||
|
||||
请根据 NVIDIA 驱动和 `nvidia-smi` 显示的 CUDA 版本选择匹配的
|
||||
PyTorch/torchaudio/vLLM 组合,不要无条件保留 pip 拉到的最新 wheel。若
|
||||
vLLM 或 PyTorch wheel 依赖的 CUDA runtime 高于当前驱动支持范围,可能在
|
||||
FunASR 启动前就报 `The NVIDIA driver on your system is too old`。遇到该错误时,
|
||||
优先重装与当前驱动/CUDA 匹配的 PyTorch、torchaudio、vLLM wheel,或先升级
|
||||
NVIDIA 驱动。
|
||||
|
||||
---
|
||||
|
||||
## 2. vLLM 推理引擎架构
|
||||
|
||||
@@ -45,6 +45,13 @@ cd /path/to/FunASR && pip install -e .
|
||||
|
||||
**硬件**:GPU ≥ 8GB VRAM,CUDA ≥ 11.8。推荐 16GB+。
|
||||
|
||||
请根据 NVIDIA 驱动和 `nvidia-smi` 显示的 CUDA 版本选择匹配的
|
||||
PyTorch/torchaudio/vLLM 组合,不要无条件保留 pip 拉到的最新 wheel。若
|
||||
vLLM 或 PyTorch wheel 依赖的 CUDA runtime 高于当前驱动支持范围,可能在
|
||||
FunASR 启动前就报 `The NVIDIA driver on your system is too old`。遇到该错误时,
|
||||
优先重装与当前驱动/CUDA 匹配的 PyTorch、torchaudio、vLLM wheel,或先升级
|
||||
NVIDIA 驱动。
|
||||
|
||||
---
|
||||
|
||||
## 2. vLLM 推理引擎架构
|
||||
|
||||
@@ -938,8 +938,10 @@ class FsmnVADStreaming(nn.Module):
|
||||
n = int(len(audio_sample) // chunk_stride_samples + int(_is_final))
|
||||
m = int(len(audio_sample) % chunk_stride_samples * (1 - int(_is_final)))
|
||||
segments = []
|
||||
# Dynamic silence threshold
|
||||
dynamic_silence = kwargs.get("dynamic_silence", True)
|
||||
# Keep explicit fixed-threshold requests from being overwritten by the dynamic schedule.
|
||||
dynamic_silence = kwargs.get(
|
||||
"dynamic_silence", kwargs.get("max_end_silence_time") is None
|
||||
)
|
||||
silence_schedule = kwargs.get("silence_schedule", DEFAULT_SILENCE_SCHEDULE)
|
||||
speech_to_sil_ms = self.vad_opts.speech_to_sil_time_thres
|
||||
accumulated_ms = cache.get("_dynamic_accumulated_ms", 0)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
import torch
|
||||
|
||||
from funasr.models.fsmn_vad_streaming import model as vad_model
|
||||
|
||||
|
||||
class TestFsmnVadDynamicSilence(unittest.TestCase):
|
||||
def _run_inference(self, **kwargs):
|
||||
vad = vad_model.FsmnVADStreaming.__new__(vad_model.FsmnVADStreaming)
|
||||
vad.vad_opts = SimpleNamespace(speech_to_sil_time_thres=100)
|
||||
vad.forward = lambda **batch: []
|
||||
|
||||
cache = {
|
||||
"frontend": {},
|
||||
"prev_samples": torch.empty(0),
|
||||
"encoder": {},
|
||||
"stats": SimpleNamespace(
|
||||
vad_state_machine=vad_model.VadStateMachine.kVadInStateInSpeechSegment,
|
||||
max_end_sil_frame_cnt_thresh=200,
|
||||
speech_noise_thres=0.6,
|
||||
),
|
||||
}
|
||||
frontend = SimpleNamespace(fs=16000, frame_shift=10, lfr_n=1)
|
||||
|
||||
def fake_extract_fbank(*args, **kwargs):
|
||||
cache["frontend"]["waveforms"] = torch.zeros(1, 16000)
|
||||
return torch.zeros(1, 1, 80), torch.tensor([100])
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
vad_model,
|
||||
"load_audio_text_image_video",
|
||||
return_value=[torch.zeros(16000)],
|
||||
),
|
||||
patch.object(vad_model, "extract_fbank", side_effect=fake_extract_fbank),
|
||||
):
|
||||
vad_model.FsmnVADStreaming.inference(
|
||||
vad,
|
||||
torch.zeros(16000),
|
||||
frontend=frontend,
|
||||
cache=cache,
|
||||
key=["utt"],
|
||||
chunk_size=1000,
|
||||
is_final=False,
|
||||
device="cpu",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return cache
|
||||
|
||||
def test_explicit_max_end_silence_time_keeps_fixed_threshold_by_default(self):
|
||||
cache = self._run_inference(max_end_silence_time=300)
|
||||
|
||||
self.assertEqual(cache["stats"].max_end_sil_frame_cnt_thresh, 200)
|
||||
self.assertNotIn("_dynamic_accumulated_ms", cache)
|
||||
|
||||
def test_explicit_dynamic_silence_still_enables_schedule(self):
|
||||
cache = self._run_inference(max_end_silence_time=300, dynamic_silence=True)
|
||||
|
||||
self.assertEqual(cache["stats"].max_end_sil_frame_cnt_thresh, 1900)
|
||||
self.assertEqual(cache["_dynamic_accumulated_ms"], 1000)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user