mirror of
https://github.com/modelscope/FunASR.git
synced 2026-10-12 02:19:18 +00:00
fix(vad): keep Silero inputs on the model device
This commit is contained in:
@@ -189,6 +189,12 @@ from funasr import AutoModel
|
||||
model = AutoModel(model="paraformer-zh", vad_model="fsmn-vad", punc_model="ct-punc", spk_model="cam++", device="cuda")
|
||||
result = model.generate(input="https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav", hotword="关键词 20")
|
||||
|
||||
# Optional Silero VAD (install first: python -m pip install "funasr[silero]")
|
||||
model = AutoModel(
|
||||
model="paraformer-zh", vad_model="silero-vad", device="cuda",
|
||||
vad_kwargs={"silero_threshold": 0.5, "silero_min_silence_duration_ms": 100},
|
||||
)
|
||||
result = model.generate(input="audio.wav")
|
||||
|
||||
# Streaming real-time (feed audio chunk by chunk)
|
||||
import soundfile as sf
|
||||
|
||||
@@ -34,6 +34,8 @@ class SileroVad(torch.nn.Module):
|
||||
if not max_single_segment_time:
|
||||
return segments
|
||||
limit_ms = int(max_single_segment_time)
|
||||
if limit_ms < 0:
|
||||
raise ValueError("max_single_segment_time must be non-negative")
|
||||
split = []
|
||||
for start, end in segments:
|
||||
while end - start > limit_ms:
|
||||
@@ -58,24 +60,38 @@ class SileroVad(torch.nn.Module):
|
||||
started = time.perf_counter()
|
||||
results = []
|
||||
for index, audio in enumerate(audio_list):
|
||||
waveform = torch.as_tensor(audio, dtype=torch.float32).flatten().cpu()
|
||||
waveform = (
|
||||
torch.as_tensor(audio, dtype=torch.float32)
|
||||
.flatten()
|
||||
.to(self.anchor.device)
|
||||
)
|
||||
timestamps = self.get_speech_timestamps(
|
||||
waveform,
|
||||
self.model,
|
||||
sampling_rate=sample_rate,
|
||||
threshold=kwargs.get("silero_threshold", 0.5),
|
||||
min_speech_duration_ms=kwargs.get("silero_min_speech_duration_ms", 250),
|
||||
min_silence_duration_ms=kwargs.get("silero_min_silence_duration_ms", 100),
|
||||
min_silence_duration_ms=kwargs.get(
|
||||
"silero_min_silence_duration_ms", 100
|
||||
),
|
||||
speech_pad_ms=kwargs.get("silero_speech_pad_ms", 30),
|
||||
)
|
||||
segments = [
|
||||
[int(item["start"] * 1000 / sample_rate), int(item["end"] * 1000 / sample_rate)]
|
||||
[
|
||||
int(item["start"] * 1000 / sample_rate),
|
||||
int(item["end"] * 1000 / sample_rate),
|
||||
]
|
||||
for item in timestamps
|
||||
]
|
||||
segments = self._split_long_segments(
|
||||
segments, kwargs.get("max_single_segment_time")
|
||||
)
|
||||
results.append({"key": key[index] if key else str(index), "value": segments})
|
||||
results.append(
|
||||
{"key": key[index] if key else str(index), "value": segments}
|
||||
)
|
||||
elapsed = time.perf_counter() - started
|
||||
total_samples = sum(len(torch.as_tensor(audio)) for audio in audio_list)
|
||||
return results, {"batch_data_time": total_samples / sample_rate, "forward": elapsed}
|
||||
return results, {
|
||||
"batch_data_time": total_samples / sample_rate,
|
||||
"forward": elapsed,
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ class TestSileroVadAdapter(unittest.TestCase):
|
||||
|
||||
@patch("silero_vad.get_speech_timestamps")
|
||||
@patch("silero_vad.load_silero_vad")
|
||||
def test_returns_funasr_millisecond_segments_and_honors_max_length(self, load_model, timestamps):
|
||||
def test_returns_funasr_millisecond_segments_and_honors_max_length(
|
||||
self, load_model, timestamps
|
||||
):
|
||||
load_model.side_effect = self._load_stub
|
||||
timestamps.side_effect = self._timestamps_stub
|
||||
model = SileroVad()
|
||||
@@ -32,7 +34,9 @@ class TestSileroVadAdapter(unittest.TestCase):
|
||||
max_single_segment_time=500,
|
||||
)
|
||||
|
||||
self.assertEqual(results, [{"key": "sample", "value": [[100, 600], [600, 1100]]}])
|
||||
self.assertEqual(
|
||||
results, [{"key": "sample", "value": [[100, 600], [600, 1100]]}]
|
||||
)
|
||||
self.assertEqual(metadata["batch_data_time"], 2.0)
|
||||
load_model.assert_called_once_with(onnx=False)
|
||||
|
||||
@@ -46,12 +50,32 @@ class TestSileroVadAdapter(unittest.TestCase):
|
||||
|
||||
@patch("silero_vad.get_speech_timestamps")
|
||||
@patch("silero_vad.load_silero_vad")
|
||||
def test_auto_model_alias_uses_the_existing_vad_build_path(self, load_model, timestamps):
|
||||
def test_auto_model_alias_uses_the_existing_vad_build_path(
|
||||
self, load_model, timestamps
|
||||
):
|
||||
load_model.side_effect = self._load_stub
|
||||
model, resolved = AutoModel.build_model(model="silero-vad", device="cpu")
|
||||
self.assertIsInstance(model, SileroVad)
|
||||
self.assertEqual(resolved["model"], "SileroVad")
|
||||
|
||||
@patch("silero_vad.get_speech_timestamps")
|
||||
@patch("silero_vad.load_silero_vad")
|
||||
def test_waveform_follows_the_adapter_device(self, load_model, timestamps):
|
||||
load_model.side_effect = self._load_stub
|
||||
|
||||
def timestamps_stub(waveform, model, sampling_rate, **options):
|
||||
self.assertEqual(waveform.device.type, "meta")
|
||||
return []
|
||||
|
||||
timestamps.side_effect = timestamps_stub
|
||||
model = SileroVad().to("meta")
|
||||
results, _ = model.inference(data_in=[torch.zeros(16000)], key=["sample"])
|
||||
self.assertEqual(results, [{"key": "sample", "value": []}])
|
||||
|
||||
def test_rejects_negative_max_segment_length(self):
|
||||
with self.assertRaisesRegex(ValueError, "non-negative"):
|
||||
SileroVad._split_long_segments([[100, 1100]], -500)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user