nvidia-nemo--speech
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
210 行
9.3 KiB
Python
210 行
9.3 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 random
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import torch
|
|
from torch.nn.utils.rnn import pad_sequence
|
|
from tqdm.auto import tqdm
|
|
|
|
from nemo.collections.asr.parts.submodules.ngram_lm import KenLMBatchedWrapper, NGramGPULanguageModel
|
|
from nemo.core.utils.optional_libs import KENLM_AVAILABLE, TRITON_AVAILABLE
|
|
|
|
DEVICES = [torch.device("cpu")]
|
|
|
|
if torch.cuda.is_available():
|
|
DEVICES.append('cuda')
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def n_gpu_lm(test_data_dir):
|
|
kenlm_model_path = Path(test_data_dir) / "asr/kenlm_ngram_lm/parakeet-tdt_ctc-110m-libri-1024.kenlm.tmp.arpa"
|
|
vocab_size = 1024
|
|
return NGramGPULanguageModel.from_arpa(kenlm_model_path, vocab_size=vocab_size, normalize_unk=False)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def kenlm_wrapper(test_data_dir):
|
|
kenlm_model_path = Path(test_data_dir) / "asr/kenlm_ngram_lm/parakeet-tdt_ctc-110m-libri-1024.kenlm.tmp.arpa"
|
|
vocab_size = 1024
|
|
return KenLMBatchedWrapper.from_file(lm_path=kenlm_model_path, vocab_size=vocab_size)
|
|
|
|
|
|
class TestNGramGPULanguageModel:
|
|
@pytest.mark.with_downloads
|
|
@pytest.mark.unit
|
|
def test_load(self, test_data_dir):
|
|
kenlm_model_path = Path(test_data_dir) / "asr/kenlm_ngram_lm/parakeet-tdt_ctc-110m-libri-1024.kenlm.tmp.arpa"
|
|
_ = NGramGPULanguageModel.from_arpa(kenlm_model_path, vocab_size=1024)
|
|
|
|
@pytest.mark.with_downloads
|
|
@pytest.mark.unit
|
|
@pytest.mark.skipif(not KENLM_AVAILABLE, reason="KenLM is not available")
|
|
@pytest.mark.parametrize("device", DEVICES)
|
|
@pytest.mark.parametrize("batch_size", [1, 3])
|
|
@pytest.mark.parametrize("bos", [True, False])
|
|
def test_initial_states(
|
|
self,
|
|
n_gpu_lm: NGramGPULanguageModel,
|
|
kenlm_wrapper: KenLMBatchedWrapper,
|
|
bos: bool,
|
|
batch_size: int,
|
|
device: torch.device,
|
|
):
|
|
n_gpu_lm = n_gpu_lm.to(device)
|
|
init_states = n_gpu_lm.get_init_states(batch_size=batch_size, bos=bos)
|
|
init_states_kenlm = kenlm_wrapper.get_init_states(batch_size=batch_size, bos=bos)
|
|
scores_lm, _ = n_gpu_lm.advance(init_states)
|
|
scores_ref, _ = kenlm_wrapper.advance(init_states_kenlm)
|
|
assert torch.allclose(scores_lm, scores_ref.to(device))
|
|
|
|
@pytest.mark.with_downloads
|
|
@pytest.mark.unit
|
|
@pytest.mark.skipif(not TRITON_AVAILABLE, reason="Triton is not available")
|
|
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is not available")
|
|
def test_triton_vs_pytorch_random_states(self, n_gpu_lm: NGramGPULanguageModel, batch_size=2, num_iterations=100):
|
|
"""Randomly initializes the states and compares the scores from Triton and PyTorch implementations."""
|
|
torch.manual_seed(777)
|
|
device = torch.device("cuda")
|
|
n_gpu_lm = n_gpu_lm.to(device)
|
|
for _ in tqdm(range(num_iterations)):
|
|
start_state = random.randint(0, n_gpu_lm.num_states - 1)
|
|
with torch.no_grad():
|
|
scores1, states1 = n_gpu_lm._advance_pytorch(
|
|
states=torch.full([batch_size], fill_value=start_state, device=device, dtype=torch.int64)
|
|
)
|
|
scores2, states2 = n_gpu_lm._advance_triton(
|
|
states=torch.full([batch_size], fill_value=start_state, device=device, dtype=torch.int64)
|
|
)
|
|
assert (states1 == states2).all()
|
|
assert torch.allclose(scores1, scores2)
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.skipif(not KENLM_AVAILABLE, reason="KenLM is not available")
|
|
@pytest.mark.parametrize("device", DEVICES)
|
|
@pytest.mark.parametrize("bos", [True, False])
|
|
def test_final(
|
|
self, n_gpu_lm: NGramGPULanguageModel, kenlm_wrapper: KenLMBatchedWrapper, bos: bool, device: torch.device
|
|
):
|
|
"""Test final (eos) scores"""
|
|
n_gpu_lm = n_gpu_lm.to(device)
|
|
sentences = [
|
|
[25, 70, 12],
|
|
[58, 41, 186, 293, 306, 999, 163, 264, 689, 683, 999],
|
|
[], # empty sentence
|
|
]
|
|
last_states = []
|
|
for sentence in sentences:
|
|
state = kenlm_wrapper.get_init_state(bos=bos)
|
|
for label in sentence:
|
|
_, state = kenlm_wrapper.advance_single(state=state, label=label)
|
|
last_states.append(state)
|
|
final_ref = kenlm_wrapper.get_final(states=last_states).to(device=device)
|
|
|
|
last_states = []
|
|
for sentence in sentences:
|
|
states = n_gpu_lm.get_init_states(batch_size=1, bos=bos)
|
|
for label in sentence:
|
|
_, states = n_gpu_lm.advance(states=states)
|
|
states = states[0, label].unsqueeze(0)
|
|
last_states.append(states)
|
|
final_lm = n_gpu_lm.get_final(states=torch.cat(last_states, dim=0))
|
|
|
|
assert torch.allclose(final_lm, final_ref), "Final scores do not match"
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.skipif(not KENLM_AVAILABLE, reason="KenLM is not available")
|
|
@pytest.mark.parametrize("device", DEVICES)
|
|
@pytest.mark.parametrize("bos", [True, False])
|
|
@pytest.mark.parametrize("eos", [True, False])
|
|
def test_sentences(
|
|
self,
|
|
n_gpu_lm: NGramGPULanguageModel,
|
|
kenlm_wrapper: KenLMBatchedWrapper,
|
|
bos: bool,
|
|
eos: bool,
|
|
device: torch.device,
|
|
):
|
|
n_gpu_lm = n_gpu_lm.to(device)
|
|
sentences = [
|
|
[25, 70, 12],
|
|
[58, 41, 186, 293, 306, 999, 163, 264, 689, 683, 999],
|
|
[], # empty sentence
|
|
]
|
|
# non-batched
|
|
for sentence in sentences:
|
|
scores_ref = kenlm_wrapper.score_sentences([sentence], bos=bos, eos=eos).to(device)
|
|
scores_lm = n_gpu_lm(
|
|
labels=torch.LongTensor([sentence]).to(device),
|
|
bos=bos,
|
|
eos=eos,
|
|
)
|
|
assert torch.allclose(scores_ref, scores_lm), "Non-batched scores do not match"
|
|
|
|
# batched
|
|
scores_ref = kenlm_wrapper.score_sentences(sentences, bos=bos, eos=eos).to(device)
|
|
scores_lm = n_gpu_lm(
|
|
labels=pad_sequence([torch.LongTensor(sentence) for sentence in sentences], batch_first=True).to(device),
|
|
labels_lengths=torch.LongTensor([len(sentence) for sentence in sentences]).to(device),
|
|
bos=bos,
|
|
eos=eos,
|
|
)
|
|
assert torch.allclose(scores_lm, scores_ref), "Batched scores do not match"
|
|
|
|
@pytest.mark.unit
|
|
def test_save_load_nemo(self, tmp_path, test_data_dir):
|
|
vocab_size = 1024
|
|
kenlm_model_path = Path(test_data_dir) / "asr/kenlm_ngram_lm/parakeet-tdt_ctc-110m-libri-1024.kenlm.tmp.arpa"
|
|
n_gpu_lm = NGramGPULanguageModel.from_arpa(kenlm_model_path, vocab_size=vocab_size, normalize_unk=False)
|
|
nemo_path = tmp_path / "ngram_lm.nemo"
|
|
n_gpu_lm.save_to(f"{nemo_path}")
|
|
n_gpu_lm_loaded = NGramGPULanguageModel.from_nemo(f"{nemo_path}", vocab_size=vocab_size)
|
|
|
|
# arcs data
|
|
assert torch.allclose(n_gpu_lm_loaded.arcs_weights, n_gpu_lm.arcs_weights)
|
|
assert (n_gpu_lm_loaded.from_states == n_gpu_lm.from_states).all()
|
|
assert (n_gpu_lm_loaded.to_states == n_gpu_lm.to_states).all()
|
|
assert (n_gpu_lm_loaded.ilabels == n_gpu_lm.ilabels).all()
|
|
|
|
# states data
|
|
assert (n_gpu_lm_loaded.start_end_arcs == n_gpu_lm.start_end_arcs).all()
|
|
assert (n_gpu_lm_loaded.state_order == n_gpu_lm.state_order).all()
|
|
assert (n_gpu_lm_loaded.backoff_to_states == n_gpu_lm.backoff_to_states).all()
|
|
assert torch.allclose(n_gpu_lm_loaded.backoff_weights, n_gpu_lm.backoff_weights)
|
|
assert torch.allclose(n_gpu_lm_loaded.final_weights, n_gpu_lm.final_weights)
|
|
|
|
@pytest.mark.unit
|
|
def test_save_load_from_file(self, tmp_path, test_data_dir):
|
|
vocab_size = 1024
|
|
kenlm_model_path = Path(test_data_dir) / "asr/kenlm_ngram_lm/parakeet-tdt_ctc-110m-libri-1024.kenlm.tmp.arpa"
|
|
n_gpu_lm = NGramGPULanguageModel.from_file(kenlm_model_path, vocab_size=vocab_size, normalize_unk=False)
|
|
nemo_path = tmp_path / "ngram_lm.nemo"
|
|
n_gpu_lm.save_to(f"{nemo_path}")
|
|
n_gpu_lm_loaded = NGramGPULanguageModel.from_file(f"{nemo_path}", vocab_size=vocab_size)
|
|
|
|
# arcs data
|
|
assert torch.allclose(n_gpu_lm_loaded.arcs_weights, n_gpu_lm.arcs_weights)
|
|
assert (n_gpu_lm_loaded.from_states == n_gpu_lm.from_states).all()
|
|
assert (n_gpu_lm_loaded.to_states == n_gpu_lm.to_states).all()
|
|
assert (n_gpu_lm_loaded.ilabels == n_gpu_lm.ilabels).all()
|
|
|
|
# states data
|
|
assert (n_gpu_lm_loaded.start_end_arcs == n_gpu_lm.start_end_arcs).all()
|
|
assert (n_gpu_lm_loaded.state_order == n_gpu_lm.state_order).all()
|
|
assert (n_gpu_lm_loaded.backoff_to_states == n_gpu_lm.backoff_to_states).all()
|
|
assert torch.allclose(n_gpu_lm_loaded.backoff_weights, n_gpu_lm.backoff_weights)
|
|
assert torch.allclose(n_gpu_lm_loaded.final_weights, n_gpu_lm.final_weights)
|