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

306 lines
13 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
from lightning.pytorch.strategies.model_parallel import ModelParallelStrategy
from nemo_automodel.components.distributed.config import FSDP2Config
from nemo_automodel.components.moe.config import MoEParallelizerConfig
from omegaconf import DictConfig
from nemo.collections.speechlm2.parts.parallel import AutomodelParallelStrategy
from nemo.utils.trainer_utils import _resolve_automodel_configs, resolve_trainer_cfg
# ---------------------------------------------------------------------------
# AutomodelParallelStrategy
# ---------------------------------------------------------------------------
class TestAutomodelParallelStrategy:
def test_is_subclass_of_model_parallel_strategy(self):
assert issubclass(AutomodelParallelStrategy, ModelParallelStrategy)
def test_default_init(self):
strategy = AutomodelParallelStrategy()
assert strategy._dp_size is None
assert strategy._dp_replicate_size is None
assert strategy._tp_size == 1
assert strategy._pp_size == 1
assert strategy._cp_size == 1
assert strategy._ep_size == 1
assert strategy._distributed_config is None
assert strategy._moe_config is None
assert strategy._moe_mesh is None
assert strategy.activation_checkpointing_llm is False
assert strategy.activation_checkpointing_perception is False
def test_accepts_activation_checkpointing_flags(self):
strategy = AutomodelParallelStrategy(
activation_checkpointing_llm=True,
activation_checkpointing_perception=True,
)
assert strategy.activation_checkpointing_llm is True
assert strategy.activation_checkpointing_perception is True
def test_activation_checkpointing_flags_are_independent(self):
"""Each AC flag can be set without the other."""
llm_only = AutomodelParallelStrategy(activation_checkpointing_llm=True)
assert llm_only.activation_checkpointing_llm is True
assert llm_only.activation_checkpointing_perception is False
perception_only = AutomodelParallelStrategy(activation_checkpointing_perception=True)
assert perception_only.activation_checkpointing_llm is False
assert perception_only.activation_checkpointing_perception is True
def test_custom_parallelism_sizes(self):
strategy = AutomodelParallelStrategy(
dp_size=4,
dp_replicate_size=2,
tp_size=2,
pp_size=2,
cp_size=2,
ep_size=4,
)
assert strategy._dp_size == 4
assert strategy._dp_replicate_size == 2
assert strategy._tp_size == 2
assert strategy._pp_size == 2
assert strategy._cp_size == 2
assert strategy._ep_size == 4
def test_accepts_distributed_config(self):
cfg = FSDP2Config(sequence_parallel=True, defer_fsdp_grad_sync=False)
strategy = AutomodelParallelStrategy(distributed_config=cfg)
assert strategy.distributed_config is cfg
assert strategy.distributed_config.sequence_parallel is True
assert strategy.distributed_config.defer_fsdp_grad_sync is False
def test_accepts_moe_config(self):
cfg = MoEParallelizerConfig()
strategy = AutomodelParallelStrategy(moe_config=cfg)
assert strategy.moe_config is cfg
def test_save_distributed_checkpoint_forwarded(self):
strategy = AutomodelParallelStrategy(save_distributed_checkpoint=False)
assert strategy._save_distributed_checkpoint is False
def test_moe_mesh_initially_none(self):
strategy = AutomodelParallelStrategy()
assert strategy.moe_mesh is None
def test_device_mesh_raises_before_setup(self):
strategy = AutomodelParallelStrategy()
with pytest.raises(RuntimeError):
_ = strategy.device_mesh
def test_distributed_sampler_kwargs_raises_before_setup(self):
strategy = AutomodelParallelStrategy()
with pytest.raises(RuntimeError):
_ = strategy.distributed_sampler_kwargs
# ---------------------------------------------------------------------------
# _resolve_automodel_configs
# ---------------------------------------------------------------------------
class TestResolveAutomodelConfigs:
"""Tests for _resolve_automodel_configs which operates on an instantiated strategy object."""
def test_plain_dict_to_fsdp2_config(self):
strategy = AutomodelParallelStrategy(
distributed_config={"defer_fsdp_grad_sync": False, "sequence_parallel": True},
)
_resolve_automodel_configs(strategy)
assert isinstance(strategy.distributed_config, FSDP2Config)
assert strategy.distributed_config.defer_fsdp_grad_sync is False
assert strategy.distributed_config.sequence_parallel is True
# Check that __post_init__ created the default mp_policy
assert strategy.distributed_config.mp_policy is not None
def test_plain_dict_to_moe_config(self):
strategy = AutomodelParallelStrategy(
moe_config={"reshard_after_forward": True},
)
_resolve_automodel_configs(strategy)
assert isinstance(strategy.moe_config, MoEParallelizerConfig)
assert strategy.moe_config.reshard_after_forward is True
def test_noop_when_no_configs(self):
"""Strategy without configs (e.g. DDPStrategy) is untouched."""
from lightning.pytorch.strategies import DDPStrategy
strategy = DDPStrategy()
_resolve_automodel_configs(strategy) # should not raise
def test_noop_when_already_objects(self):
original_cfg = FSDP2Config()
original_moe = MoEParallelizerConfig()
strategy = AutomodelParallelStrategy(
distributed_config=original_cfg,
moe_config=original_moe,
)
_resolve_automodel_configs(strategy)
assert strategy.distributed_config is original_cfg
assert strategy.moe_config is original_moe
def test_noop_when_configs_are_none(self):
strategy = AutomodelParallelStrategy()
_resolve_automodel_configs(strategy)
assert strategy.distributed_config is None
assert strategy.moe_config is None
def test_empty_dict_creates_default_configs(self):
strategy = AutomodelParallelStrategy(distributed_config={}, moe_config={})
_resolve_automodel_configs(strategy)
assert isinstance(strategy.distributed_config, FSDP2Config)
assert isinstance(strategy.moe_config, MoEParallelizerConfig)
# All defaults should apply
assert strategy.distributed_config.defer_fsdp_grad_sync is True
assert strategy.distributed_config.sequence_parallel is False
def test_nested_target_in_distributed_config(self):
"""A sub-field like mp_policy can use _target_ for Hydra instantiation."""
strategy = AutomodelParallelStrategy(
distributed_config={
"sequence_parallel": True,
"mp_policy": {
"_target_": "torch.distributed.fsdp.MixedPrecisionPolicy",
"param_dtype": "torch.bfloat16",
},
},
)
_resolve_automodel_configs(strategy)
assert isinstance(strategy.distributed_config, FSDP2Config)
assert strategy.distributed_config.sequence_parallel is True
from torch.distributed.fsdp import MixedPrecisionPolicy
assert isinstance(strategy.distributed_config.mp_policy, MixedPrecisionPolicy)
def test_both_configs_resolved_together(self):
strategy = AutomodelParallelStrategy(
distributed_config={"sequence_parallel": False},
moe_config={},
)
_resolve_automodel_configs(strategy)
assert isinstance(strategy.distributed_config, FSDP2Config)
assert isinstance(strategy.moe_config, MoEParallelizerConfig)
# ---------------------------------------------------------------------------
# resolve_trainer_cfg (end-to-end with AutomodelParallelStrategy)
# ---------------------------------------------------------------------------
class TestResolveTrainerCfg:
def test_automodel_strategy_from_yaml(self):
"""End-to-end: YAML dict config -> instantiated AutomodelParallelStrategy."""
trainer_cfg = DictConfig(
{
"devices": 1,
"accelerator": "cpu",
"strategy": {
"_target_": "nemo.collections.speechlm2.parts.parallel.AutomodelParallelStrategy",
"tp_size": 2,
"pp_size": 1,
"distributed_config": {
"sequence_parallel": True,
"defer_fsdp_grad_sync": False,
},
"moe_config": {},
},
}
)
resolved = resolve_trainer_cfg(trainer_cfg)
strategy = resolved["strategy"]
assert isinstance(strategy, AutomodelParallelStrategy)
assert strategy._tp_size == 2
assert strategy._pp_size == 1
assert isinstance(strategy.distributed_config, FSDP2Config)
assert strategy.distributed_config.sequence_parallel is True
assert strategy.distributed_config.defer_fsdp_grad_sync is False
assert isinstance(strategy.moe_config, MoEParallelizerConfig)
def test_non_automodel_strategy_unaffected(self):
"""Other strategies (e.g. DDPStrategy) should pass through unchanged."""
trainer_cfg = DictConfig(
{
"devices": 1,
"accelerator": "cpu",
"strategy": {
"_target_": "lightning.pytorch.strategies.DDPStrategy",
"gradient_as_bucket_view": True,
"find_unused_parameters": True,
},
}
)
resolved = resolve_trainer_cfg(trainer_cfg)
from lightning.pytorch.strategies import DDPStrategy
assert isinstance(resolved["strategy"], DDPStrategy)
def test_string_strategy_unaffected(self):
"""A plain string strategy (e.g. 'ddp') should pass through."""
trainer_cfg = DictConfig(
{
"devices": 1,
"accelerator": "cpu",
"strategy": "ddp",
}
)
resolved = resolve_trainer_cfg(trainer_cfg)
assert resolved["strategy"] == "ddp"
def test_automodel_strategy_with_ac_flags_from_yaml(self):
"""Both AC flags can be set via YAML and survive Hydra instantiation."""
trainer_cfg = DictConfig(
{
"devices": 1,
"accelerator": "cpu",
"strategy": {
"_target_": "nemo.collections.speechlm2.parts.parallel.AutomodelParallelStrategy",
"ep_size": 4,
"activation_checkpointing_llm": True,
"activation_checkpointing_perception": True,
},
}
)
resolved = resolve_trainer_cfg(trainer_cfg)
strategy = resolved["strategy"]
assert isinstance(strategy, AutomodelParallelStrategy)
assert strategy.activation_checkpointing_llm is True
assert strategy.activation_checkpointing_perception is True
def test_automodel_strategy_without_configs(self):
"""AutomodelParallelStrategy can be specified without distributed_config/moe_config."""
trainer_cfg = DictConfig(
{
"devices": 1,
"accelerator": "cpu",
"strategy": {
"_target_": "nemo.collections.speechlm2.parts.parallel.AutomodelParallelStrategy",
"tp_size": 4,
"ep_size": 8,
},
}
)
resolved = resolve_trainer_cfg(trainer_cfg)
strategy = resolved["strategy"]
assert isinstance(strategy, AutomodelParallelStrategy)
assert strategy._tp_size == 4
assert strategy._ep_size == 8
# No configs passed → still None (will be defaulted in setup_environment)
assert strategy._distributed_config is None
assert strategy._moe_config is None