ba4be087d5
CICD NeMo / cicd-main-unit-tests (push) Blocked by required conditions
CICD NeMo / cicd-main-speech (push) Blocked by required conditions
CICD NeMo / cicd-test-container-build (push) Blocked by required conditions
CICD NeMo / cicd-import-tests (push) Blocked by required conditions
CICD NeMo / L0_Setup_Test_Data_And_Models (push) Blocked by required conditions
CICD NeMo / Nemo_CICD_Test (push) Blocked by required conditions
CICD NeMo / Coverage (e2e) (push) Blocked by required conditions
CICD NeMo / Coverage (unit-test) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
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
CICD NeMo / cicd-wait-in-queue (push) Waiting to run
Build, validate, and release Neural Modules / release (push) Has been skipped
Build, validate, and release Neural Modules / release-summary (push) Has been cancelled
230 lines
7.6 KiB
Python
230 lines
7.6 KiB
Python
# Copyright (c) 2026, 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 pytest
|
|
import torch
|
|
|
|
from nemo.collections.speechlm2.data.salm_dataset import MultiSpeakerConfig
|
|
from nemo.collections.speechlm2.parts.encoder_chunking import (
|
|
_recombine_chunked_audio_embeddings,
|
|
_split_audio_into_chunks,
|
|
_split_spk_targets_into_chunks,
|
|
encode_audio_with_optional_chunking,
|
|
)
|
|
from tests.collections.speechlm2._chunking_helpers import ChunkingTestPerception
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"input_signal_lengths",
|
|
"chunk_size_samples",
|
|
"min_chunk_size_samples",
|
|
"expected_chunk_lens",
|
|
"expected_chunks_per_audio",
|
|
"expected_chunk_spans",
|
|
),
|
|
[
|
|
(
|
|
[5, 0, 7],
|
|
3,
|
|
2,
|
|
[3, 2, 0, 3, 4],
|
|
[2, 1, 2],
|
|
[(0, 0, 3), (0, 3, 5), (1, 0, 0), (2, 0, 3), (2, 3, 7)],
|
|
),
|
|
(
|
|
[6],
|
|
2,
|
|
1,
|
|
[2, 2, 2],
|
|
[3],
|
|
[(0, 0, 2), (0, 2, 4), (0, 4, 6)],
|
|
),
|
|
],
|
|
)
|
|
def test_split_audio_into_chunks_returns_spans_independent_of_spk_targets(
|
|
input_signal_lengths,
|
|
chunk_size_samples,
|
|
min_chunk_size_samples,
|
|
expected_chunk_lens,
|
|
expected_chunks_per_audio,
|
|
expected_chunk_spans,
|
|
):
|
|
max_signal_len = max(input_signal_lengths, default=0)
|
|
input_signal = torch.arange(len(input_signal_lengths) * max_signal_len, dtype=torch.float32).reshape(
|
|
len(input_signal_lengths), max_signal_len
|
|
)
|
|
chunks, chunk_lens, chunks_per_audio, chunk_spans = _split_audio_into_chunks(
|
|
input_signal=input_signal,
|
|
input_signal_lengths=input_signal_lengths,
|
|
chunk_size_samples=chunk_size_samples,
|
|
min_chunk_size_samples=min_chunk_size_samples,
|
|
)
|
|
|
|
assert chunk_lens == expected_chunk_lens
|
|
assert chunks_per_audio == expected_chunks_per_audio
|
|
assert chunk_spans == expected_chunk_spans
|
|
for chunk, (audio_idx, begin, end) in zip(chunks, chunk_spans):
|
|
assert torch.equal(chunk, input_signal[audio_idx, begin:end])
|
|
|
|
assert _split_spk_targets_into_chunks(None, input_signal_lengths, chunk_spans) is None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("chunk_values", "chunk_lens", "chunks_per_audio", "expected_audio_values"),
|
|
[
|
|
(
|
|
[[1.0, 2.0, 0.0], [3.0, 4.0, 5.0]],
|
|
[2, 3],
|
|
[2],
|
|
[[1.0, 2.0, 3.0, 4.0, 5.0]],
|
|
),
|
|
(
|
|
[[1.0, 2.0, 0.0], [3.0, 4.0, 5.0], [10.0, 11.0, 0.0], [12.0, 13.0, 14.0]],
|
|
[2, 3, 2, 3],
|
|
[2, 2],
|
|
[[1.0, 2.0, 3.0, 4.0, 5.0], [10.0, 11.0, 12.0, 13.0, 14.0]],
|
|
),
|
|
],
|
|
)
|
|
def test_recombine_chunked_audio_embeddings_reconstructs_original_rows(
|
|
chunk_values,
|
|
chunk_lens,
|
|
chunks_per_audio,
|
|
expected_audio_values,
|
|
):
|
|
chunked_embs = torch.tensor(chunk_values, dtype=torch.float32).unsqueeze(-1)
|
|
chunked_emb_lens = torch.tensor(chunk_lens, dtype=torch.long)
|
|
|
|
audio_embs = _recombine_chunked_audio_embeddings(chunked_embs, chunked_emb_lens, chunks_per_audio)
|
|
|
|
assert len(audio_embs) == len(expected_audio_values)
|
|
for audio_emb, expected_values in zip(audio_embs, expected_audio_values):
|
|
assert torch.equal(audio_emb.squeeze(-1), torch.tensor(expected_values))
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"input_signal_lengths",
|
|
"chunk_spans",
|
|
"expected_chunks",
|
|
),
|
|
[
|
|
(
|
|
[5],
|
|
[(0, 0, 2), (0, 2, 5)],
|
|
[
|
|
[[0.0, 1.0, 2.0, 3.0], [4.0, 5.0, 6.0, 7.0], [4.0, 5.0, 6.0, 7.0]],
|
|
[[8.0, 9.0, 10.0, 11.0], [12.0, 13.0, 14.0, 15.0], [16.0, 17.0, 18.0, 19.0]],
|
|
],
|
|
),
|
|
(
|
|
[4],
|
|
[(0, 0, 2), (0, 2, 4)],
|
|
[
|
|
[[0.0, 1.0, 2.0, 3.0], [4.0, 5.0, 6.0, 7.0], [4.0, 5.0, 6.0, 7.0]],
|
|
[[8.0, 9.0, 10.0, 11.0], [12.0, 13.0, 14.0, 15.0], [16.0, 17.0, 18.0, 19.0]],
|
|
],
|
|
),
|
|
],
|
|
)
|
|
def test_split_spk_targets_into_chunks_uses_chunk_spans(
|
|
input_signal_lengths,
|
|
chunk_spans,
|
|
expected_chunks,
|
|
):
|
|
cfg = MultiSpeakerConfig()
|
|
spk_targets = torch.arange(5 * cfg.num_speakers, dtype=torch.float32).reshape(1, 5, cfg.num_speakers)
|
|
|
|
chunked_spk_targets = _split_spk_targets_into_chunks(spk_targets, input_signal_lengths, chunk_spans)
|
|
|
|
assert torch.equal(chunked_spk_targets, torch.tensor(expected_chunks))
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("audio_values", "audio_len", "expected_chunk_lens"),
|
|
[
|
|
([1.0, 2.0, 3.0, 4.0, 5.0], 5, [2, 3]),
|
|
([1.0, 2.0, 3.0, 4.0], 4, [2, 2]),
|
|
],
|
|
)
|
|
def test_encode_audio_with_optional_chunking_does_not_forward_absent_spk_targets(
|
|
audio_values, audio_len, expected_chunk_lens
|
|
):
|
|
perception = ChunkingTestPerception(sampling_rate=2, hop_length=1)
|
|
audios = torch.tensor([audio_values])
|
|
audio_lens = torch.tensor([audio_len], dtype=torch.long)
|
|
|
|
embs = encode_audio_with_optional_chunking(
|
|
perception,
|
|
audios,
|
|
audio_lens,
|
|
chunk_size_seconds=1.0,
|
|
sampling_rate=2,
|
|
spk_targets=None,
|
|
)
|
|
|
|
chunked_signal, chunked_lens = perception.calls[0]
|
|
assert chunked_signal.shape == (len(expected_chunk_lens), max(expected_chunk_lens))
|
|
assert torch.equal(chunked_lens, torch.tensor(expected_chunk_lens, dtype=torch.long))
|
|
assert perception.spk_targets_calls[0] is None
|
|
assert torch.equal(embs[0].squeeze(-1), audios[0])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("audio_values", "audio_len", "expected_chunk_lens", "expected_spk_targets"),
|
|
[
|
|
(
|
|
[1.0, 2.0, 3.0, 4.0, 5.0],
|
|
5,
|
|
[2, 3],
|
|
[
|
|
[[0.0, 1.0, 2.0, 3.0], [4.0, 5.0, 6.0, 7.0], [4.0, 5.0, 6.0, 7.0]],
|
|
[[8.0, 9.0, 10.0, 11.0], [12.0, 13.0, 14.0, 15.0], [16.0, 17.0, 18.0, 19.0]],
|
|
],
|
|
),
|
|
(
|
|
[1.0, 2.0, 3.0, 4.0],
|
|
4,
|
|
[2, 2],
|
|
[
|
|
[[0.0, 1.0, 2.0, 3.0], [4.0, 5.0, 6.0, 7.0], [4.0, 5.0, 6.0, 7.0]],
|
|
[[8.0, 9.0, 10.0, 11.0], [12.0, 13.0, 14.0, 15.0], [16.0, 17.0, 18.0, 19.0]],
|
|
],
|
|
),
|
|
],
|
|
)
|
|
def test_encode_audio_with_optional_chunking_forwards_chunked_spk_targets(
|
|
audio_values, audio_len, expected_chunk_lens, expected_spk_targets
|
|
):
|
|
cfg = MultiSpeakerConfig()
|
|
perception = ChunkingTestPerception(sampling_rate=2, hop_length=1)
|
|
audios = torch.tensor([audio_values])
|
|
audio_lens = torch.tensor([audio_len], dtype=torch.long)
|
|
spk_targets = torch.arange(5 * cfg.num_speakers, dtype=torch.float32).reshape(1, 5, cfg.num_speakers)
|
|
|
|
embs = encode_audio_with_optional_chunking(
|
|
perception,
|
|
audios,
|
|
audio_lens,
|
|
chunk_size_seconds=1.0,
|
|
sampling_rate=2,
|
|
spk_targets=spk_targets,
|
|
)
|
|
|
|
chunked_signal, chunked_lens = perception.calls[0]
|
|
assert chunked_signal.shape == (len(expected_chunk_lens), max(expected_chunk_lens))
|
|
assert torch.equal(chunked_lens, torch.tensor(expected_chunk_lens, dtype=torch.long))
|
|
assert torch.equal(perception.spk_targets_calls[0], torch.tensor(expected_spk_targets))
|
|
assert torch.equal(embs[0].squeeze(-1), audios[0])
|