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
204 行
7.1 KiB
Python
204 行
7.1 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.
|
|
|
|
from io import BytesIO
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from lhotse import CutSet
|
|
from lhotse.serialization import load_jsonl, save_to_jsonl
|
|
from lhotse.shar.writers import JsonlShardWriter, TarWriter
|
|
from lhotse.testing.dummies import DummyManifest
|
|
from omegaconf import OmegaConf
|
|
|
|
from nemo.collections.common.data.lhotse.dataloader import get_lhotse_dataloader_from_config
|
|
|
|
|
|
class _Identity:
|
|
def __getitem__(self, cuts):
|
|
return cuts
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def cutset_path(tmp_path_factory) -> Path:
|
|
"""10 utterances of length 1s as a Lhotse CutSet."""
|
|
cuts = DummyManifest(CutSet, begin_id=0, end_id=10, with_data=True)
|
|
for c in cuts:
|
|
c.features = None
|
|
c.custom = None
|
|
c.supervisions[0].custom = None
|
|
|
|
tmp_path = tmp_path_factory.mktemp("data")
|
|
p = tmp_path / "cuts.jsonl.gz"
|
|
pa = tmp_path / "audio"
|
|
cuts.save_audios(pa).to_file(p)
|
|
return p
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def nemo_manifest_path(cutset_path: Path):
|
|
"""10 utterances of length 1s as a NeMo manifest."""
|
|
nemo = []
|
|
for idx, c in enumerate(CutSet.from_file(cutset_path)):
|
|
nemo.append(
|
|
{
|
|
"audio_filepath": c.recording.sources[0].source,
|
|
"text": f"irrelevant-{idx}",
|
|
"duration": c.duration,
|
|
}
|
|
)
|
|
p = cutset_path.parent / "nemo_manifest.json"
|
|
save_to_jsonl(nemo, p)
|
|
return p
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def nemo_tarred_manifest_path(nemo_manifest_path: Path) -> tuple[str, str]:
|
|
"""5 shards, each with 2 utterances."""
|
|
root = nemo_manifest_path.parent / "nemo_tar"
|
|
root.mkdir(exist_ok=True)
|
|
with (
|
|
TarWriter(f"{root}/audios_%01d.tar", shard_size=2) as tar_writer,
|
|
JsonlShardWriter(f"{root}/manifest_%01d.jsonl", shard_size=2) as mft_writer,
|
|
):
|
|
for idx, d in enumerate(load_jsonl(nemo_manifest_path)):
|
|
p = d["audio_filepath"]
|
|
name = Path(p).name
|
|
with open(p, "rb") as f:
|
|
tar_writer.write(name, BytesIO(f.read()))
|
|
mft_writer.write({**d, "audio_filepath": name, "shard_id": idx // 2})
|
|
return f"{root}/manifest__OP_0..4_CL_.jsonl", f"{root}/audios__OP_0..4_CL_.tar"
|
|
|
|
|
|
def test_dataloader_multiple_ranks_deterministic_rng(nemo_tarred_manifest_path: tuple[str, str]):
|
|
json_mft, tar_mft = nemo_tarred_manifest_path
|
|
config = OmegaConf.create(
|
|
{
|
|
"manifest_filepath": json_mft,
|
|
"tarred_audio_filepaths": tar_mft,
|
|
"sample_rate": 16000,
|
|
"shuffle": True,
|
|
"use_lhotse": True,
|
|
"num_workers": 1,
|
|
# lhotse specific
|
|
"use_bucketing": True,
|
|
"concurrent_bucketing": False,
|
|
"num_buckets": 2,
|
|
"drop_last": False,
|
|
"batch_duration": 4.0, # seconds
|
|
"quadratic_duration": 15.0, # seconds
|
|
"shuffle_buffer_size": 10,
|
|
"bucket_buffer_size": 100,
|
|
"seed": 0,
|
|
"shard_seed": "randomized",
|
|
}
|
|
)
|
|
|
|
# Data parallel, rank 0
|
|
dp0 = get_lhotse_dataloader_from_config(config=config, global_rank=0, world_size=2, dataset=_Identity())
|
|
|
|
# Data parallel, rank 0 copy (is the iteration deterministic? -> yes)
|
|
dp0_cpy = get_lhotse_dataloader_from_config(
|
|
config=config,
|
|
global_rank=0,
|
|
world_size=2,
|
|
dataset=_Identity(),
|
|
)
|
|
|
|
# Data parallel, rank 0, incremented seed (paranoia mode: does the iteration order change with the seed? -> yes)
|
|
config2 = config.copy()
|
|
config2["seed"] = config2["seed"] + 1
|
|
dp0_incrseed = get_lhotse_dataloader_from_config(
|
|
config=config2,
|
|
global_rank=0,
|
|
world_size=2,
|
|
dataset=_Identity(),
|
|
)
|
|
|
|
# Data parallel, rank 1 (is data different on each DP rank? -> yes)
|
|
dp1 = get_lhotse_dataloader_from_config(config=config, global_rank=1, world_size=2, dataset=_Identity())
|
|
|
|
dloaders = zip(*[iter(dl) for dl in (dp0, dp0_cpy, dp0_incrseed, dp1)])
|
|
|
|
for i in range(5):
|
|
b0, b0_cpy, b0_incrseed, b1 = next(dloaders)
|
|
assert b0 == b0_cpy
|
|
assert b0 != b1
|
|
assert b0_incrseed != b1
|
|
assert b0 != b0_incrseed
|
|
|
|
|
|
def test_dataloader_multiple_ranks_trng(nemo_tarred_manifest_path: tuple[str, str]):
|
|
"""
|
|
This test is the same as ``test_dataloader_multiple_ranks_deterministic_rng``,
|
|
except that we set ``shard_seed="trng"`` which causes the seed to be lazily
|
|
resolved in subprocesses (resolved => being drawn using OS's TRNG).
|
|
Therefore, we don't expect any reproducibility.
|
|
"""
|
|
json_mft, tar_mft = nemo_tarred_manifest_path
|
|
config = OmegaConf.create(
|
|
{
|
|
"manifest_filepath": json_mft,
|
|
"tarred_audio_filepaths": tar_mft,
|
|
"sample_rate": 16000,
|
|
"shuffle": True,
|
|
"use_lhotse": True,
|
|
"num_workers": 1,
|
|
# lhotse specific
|
|
"use_bucketing": True,
|
|
"concurrent_bucketing": False,
|
|
"num_buckets": 2,
|
|
"drop_last": False,
|
|
"batch_duration": 4.0, # seconds
|
|
"quadratic_duration": 15.0, # seconds
|
|
"shuffle_buffer_size": 10,
|
|
"bucket_buffer_size": 100,
|
|
"seed": 0,
|
|
"shard_seed": "trng",
|
|
}
|
|
)
|
|
|
|
# Data parallel, rank 0
|
|
dp0 = get_lhotse_dataloader_from_config(config=config, global_rank=0, world_size=2, dataset=_Identity())
|
|
|
|
# Data parallel, rank 0 copy (is the iteration deterministic? -> no, trng)
|
|
dp0_cpy = get_lhotse_dataloader_from_config(
|
|
config=config,
|
|
global_rank=0,
|
|
world_size=2,
|
|
dataset=_Identity(),
|
|
)
|
|
|
|
# Data parallel, rank 0, incremented seed (paranoia mode: does the iteration order change with the seed? -> yes)
|
|
config2 = config.copy()
|
|
config2["seed"] = config2["seed"] + 1
|
|
dp0_incrseed = get_lhotse_dataloader_from_config(
|
|
config=config2,
|
|
global_rank=0,
|
|
world_size=2,
|
|
dataset=_Identity(),
|
|
)
|
|
|
|
# Data parallel, rank 1 (is data different on each DP rank? -> yes)
|
|
dp1 = get_lhotse_dataloader_from_config(config=config, global_rank=1, world_size=2, dataset=_Identity())
|
|
|
|
dloaders = zip(*[iter(dl) for dl in (dp0, dp0_cpy, dp0_incrseed, dp1)])
|
|
batches = [next(dloaders) for _ in range(5)]
|
|
b0_batches, b0_cpy_batches, b0_incrseed_batches, b1_batches = map(list, zip(*batches))
|
|
|
|
assert b0_batches != b0_cpy_batches
|
|
assert b0_batches != b1_batches
|
|
assert b0_incrseed_batches != b1_batches
|
|
assert b0_batches != b0_incrseed_batches
|