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
155 lines
4.8 KiB
Python
155 lines
4.8 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.
|
|
"""Unit tests for ``validate_parallelism_compatibility``.
|
|
|
|
Pure-function tests — no Lightning, no model, no device mesh required.
|
|
"""
|
|
import warnings
|
|
|
|
import pytest
|
|
|
|
from nemo.collections.speechlm2.parts.parallel import validate_parallelism_compatibility
|
|
|
|
|
|
# Combinations that must pass without raising or warning.
|
|
|
|
|
|
def test_bshd_cp1_te_passes():
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=False,
|
|
cp_size=1,
|
|
attn_backend="te",
|
|
nvte_fused_attn=None,
|
|
device_capability=(9, 0), # H100
|
|
)
|
|
|
|
|
|
def test_bshd_cp1_sdpa_passes():
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=False,
|
|
cp_size=1,
|
|
attn_backend="sdpa",
|
|
nvte_fused_attn=None,
|
|
device_capability=(9, 0),
|
|
)
|
|
|
|
|
|
def test_thd_cp1_te_with_fused_attn_off_passes():
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=True,
|
|
cp_size=1,
|
|
attn_backend="te",
|
|
nvte_fused_attn="0",
|
|
device_capability=(12, 0), # sm_120 — still fine because env is set
|
|
)
|
|
|
|
|
|
def test_thd_cp2_te_with_fused_attn_off_passes():
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=True,
|
|
cp_size=2,
|
|
attn_backend="te",
|
|
nvte_fused_attn="0",
|
|
device_capability=(12, 0),
|
|
)
|
|
|
|
|
|
# BSHD + CP > 1 — hard error regardless of other knobs.
|
|
|
|
|
|
@pytest.mark.parametrize("cp_size", [2, 4, 8])
|
|
def test_bshd_with_cp_raises(cp_size):
|
|
with pytest.raises(ValueError, match="BSHD .* incompatible with cp_size > 1"):
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=False,
|
|
cp_size=cp_size,
|
|
attn_backend="te",
|
|
nvte_fused_attn="0",
|
|
device_capability=(9, 0),
|
|
)
|
|
|
|
|
|
# THD + non-TE attention — hard error.
|
|
|
|
|
|
@pytest.mark.parametrize("attn_backend", ["sdpa", "flex"])
|
|
def test_thd_with_non_te_attn_raises(attn_backend):
|
|
with pytest.raises(ValueError, match=r"THD.*requires.*attn=te"):
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=True,
|
|
cp_size=1,
|
|
attn_backend=attn_backend,
|
|
nvte_fused_attn="0",
|
|
device_capability=(9, 0),
|
|
)
|
|
|
|
|
|
# THD + TE + NVTE_FUSED_ATTN unset — warns on non-sm_120, raises on sm_120.
|
|
|
|
|
|
@pytest.mark.parametrize("nvte_fused_attn", [None, "", "1", "true"])
|
|
def test_thd_te_without_fused_attn_off_warns_on_other_archs(nvte_fused_attn):
|
|
with warnings.catch_warnings(record=True) as caught:
|
|
warnings.simplefilter("always")
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=True,
|
|
cp_size=1,
|
|
attn_backend="te",
|
|
nvte_fused_attn=nvte_fused_attn,
|
|
device_capability=(9, 0), # H100
|
|
)
|
|
assert len(caught) == 1
|
|
assert "NVTE_FUSED_ATTN" in str(caught[0].message)
|
|
|
|
|
|
@pytest.mark.parametrize("nvte_fused_attn", [None, "", "1", "true"])
|
|
def test_thd_te_without_fused_attn_off_raises_on_sm120(nvte_fused_attn):
|
|
with pytest.raises(ValueError, match="NVTE_FUSED_ATTN"):
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=True,
|
|
cp_size=1,
|
|
attn_backend="te",
|
|
nvte_fused_attn=nvte_fused_attn,
|
|
device_capability=(12, 0), # sm_120
|
|
)
|
|
|
|
|
|
def test_thd_te_with_fused_attn_off_does_not_warn_on_sm120():
|
|
"""The escape-hatch case: user has the env set, no warning fires."""
|
|
with warnings.catch_warnings(record=True) as caught:
|
|
warnings.simplefilter("always")
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=True,
|
|
cp_size=1,
|
|
attn_backend="te",
|
|
nvte_fused_attn="0",
|
|
device_capability=(12, 0),
|
|
)
|
|
assert len(caught) == 0
|
|
|
|
|
|
def test_unknown_device_capability_warns_not_raises():
|
|
"""``device_capability=None`` (CPU-only env) treats the THD/TE check as a warning."""
|
|
with warnings.catch_warnings(record=True) as caught:
|
|
warnings.simplefilter("always")
|
|
validate_parallelism_compatibility(
|
|
packed_sequences=True,
|
|
cp_size=1,
|
|
attn_backend="te",
|
|
nvte_fused_attn=None,
|
|
device_capability=None,
|
|
)
|
|
assert len(caught) == 1
|
|
assert "NVTE_FUSED_ATTN" in str(caught[0].message)
|