Files
wehub-resource-sync ba4be087d5
Create PR to main with cherry-pick from release / cherry-pick (push) Failing after 0s
CICD NeMo / pre-flight (push) Failing after 0s
CICD NeMo / configure (push) Has been skipped
Build, validate, and release Neural Modules / pre-flight (push) Failing after 1s
CICD NeMo / code-linting (push) Has been skipped
Build, validate, and release Neural Modules / release (push) Has been skipped
Build, validate, and release Neural Modules / release-summary (push) Has been cancelled
CICD NeMo / cicd-test-container-build (push) Has been cancelled
CICD NeMo / cicd-import-tests (push) Has been cancelled
CICD NeMo / L0_Setup_Test_Data_And_Models (push) Has been cancelled
CICD NeMo / cicd-main-unit-tests (push) Has been cancelled
CICD NeMo / cicd-main-speech (push) Has been cancelled
CICD NeMo / Nemo_CICD_Test (push) Has been cancelled
CICD NeMo / Coverage (e2e) (push) Has been cancelled
CICD NeMo / Coverage (unit-test) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CICD NeMo / cicd-wait-in-queue (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:28:58 +08:00

162 lines
5.9 KiB
Python

# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import tempfile
import numpy as np
import pytest
import torch
from lightning.pytorch import Trainer
from nemo.collections.asr.models import EncDecCTCModelBPE
from nemo.collections.asr.parts import context_biasing
from nemo.collections.asr.parts.context_biasing.ctc_based_word_spotter import WSHyp
from nemo.collections.asr.parts.utils import rnnt_utils
@pytest.fixture(scope="module")
def conformer_ctc_bpe_model():
model = EncDecCTCModelBPE.from_pretrained(model_name="stt_en_conformer_ctc_small")
model.set_trainer(Trainer(devices=1, accelerator="cpu"))
model = model.eval()
return model
class TestContextGraphCTC:
@pytest.mark.unit
def test_graph_building(self):
context_biasing_list = [["gpu", [['▁g', 'p', 'u'], ['▁g', '▁p', '▁u']]]]
context_graph = context_biasing.ContextGraphCTC(blank_id=1024)
context_graph.add_to_graph(context_biasing_list)
assert context_graph.num_nodes == 8
assert context_graph.blank_token == 1024
assert not context_graph.root.next['▁g'].is_end
assert context_graph.root.next['▁g'].next['p'].next['u'].is_end
assert context_graph.root.next['▁g'].next['p'].next['u'].word == 'gpu'
assert context_graph.root.next['▁g'].next['▁p'].next['▁u'].is_end
assert context_graph.root.next['▁g'].next['▁p'].next['▁u'].word == 'gpu'
class TestCTCWordSpotter:
@pytest.mark.unit
@pytest.mark.with_downloads
def test_run_word_spotter(self, test_data_dir, conformer_ctc_bpe_model):
asr_model = conformer_ctc_bpe_model
audio_file_path = os.path.join(test_data_dir, "asr/test/an4/wav/cen3-mjwl-b.wav")
target_text = "nineteen"
target_tokenization = asr_model.tokenizer.text_to_ids(target_text)
ctc_logprobs = (
asr_model.transcribe([audio_file_path], batch_size=1, return_hypotheses=True)[0].alignments.cpu().numpy()
)
context_biasing_list = [[target_text, [target_tokenization]]]
context_graph = context_biasing.ContextGraphCTC(blank_id=asr_model.decoding.blank_id)
context_graph.add_to_graph(context_biasing_list)
# without context biasing
ws_results = context_biasing.run_word_spotter(
ctc_logprobs,
context_graph,
asr_model,
blank_idx=asr_model.decoding.blank_id,
beam_threshold=5.0,
cb_weight=0.0,
ctc_ali_token_weight=0.6,
)
assert len(ws_results) == 0
# with context biasing
ws_results = context_biasing.run_word_spotter(
ctc_logprobs,
context_graph,
asr_model,
blank_idx=asr_model.decoding.blank_id,
beam_threshold=5.0,
cb_weight=3.0,
ctc_ali_token_weight=0.6,
)
assert len(ws_results) == 1
assert ws_results[0].word == target_text
assert ws_results[0].start_frame == 9
assert ws_results[0].end_frame == 19
torch.testing.assert_close(ws_results[0].score, 8.9967, atol=1e-3, rtol=1e-4)
class TestContextBiasingUtils:
@pytest.mark.unit
@pytest.mark.with_downloads
def test_merge_alignment_with_ws_hyps(self, conformer_ctc_bpe_model):
asr_model = conformer_ctc_bpe_model
blank_idx = asr_model.decoding.blank_id
ws_results = [WSHyp(word="gpu", score=6.0, start_frame=0, end_frame=2)]
# ctc argmax predictions
preds = np.array([120, 29, blank_idx, blank_idx])
pred_text, raw_text = context_biasing.merge_alignment_with_ws_hyps(
preds,
asr_model,
ws_results,
decoder_type="ctc",
blank_idx=blank_idx,
)
assert raw_text == "gp"
assert pred_text == "gpu"
# rnnt token predictions
preds = rnnt_utils.Hypothesis(
y_sequence=torch.tensor([120, 29]),
score=0.0,
timestamp=torch.tensor([0, 1, 2, 3]),
)
pred_text, raw_text = context_biasing.merge_alignment_with_ws_hyps(
preds,
asr_model,
ws_results,
decoder_type="rnnt",
blank_idx=blank_idx,
)
assert raw_text == "gp"
assert pred_text == "gpu"
# rnnt empty token predictions
preds = rnnt_utils.Hypothesis(
y_sequence=[],
score=0.0,
timestamp=[],
)
pred_text, raw_text = context_biasing.merge_alignment_with_ws_hyps(
preds,
asr_model,
ws_results,
decoder_type="rnnt",
blank_idx=blank_idx,
)
assert raw_text == ""
assert pred_text == "gpu"
@pytest.mark.unit
def test_compute_fscore(self):
recog_manifest = """{"audio_filepath": "test.wav", "duration": 1.0, "text": "a new gpu for nvidia", "pred_text": "a new gpu for invidia"}\n"""
context_words = ["gpu", "cpu", "nvidia"]
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8') as f:
f.write(recog_manifest)
f.seek(0)
fscore_stats = context_biasing.compute_fscore(f.name, context_words)
assert (round(fscore_stats[0], 4), round(fscore_stats[1], 4), round(fscore_stats[2], 4)) == (
1.0,
0.5,
0.6667,
)