214 lines
6.4 KiB
Python
214 lines
6.4 KiB
Python
import os
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
import ray
|
|
from ray.train import ScalingConfig
|
|
from ray.train.lightning import (
|
|
RayDDPStrategy,
|
|
RayDeepSpeedStrategy,
|
|
RayFSDPStrategy,
|
|
RayLightningEnvironment,
|
|
RayTrainReportCallback,
|
|
)
|
|
from ray.train.lightning._lightning_utils import import_lightning
|
|
from ray.train.tests.lightning_test_utils import DummyDataModule, LinearModule
|
|
from ray.train.torch import TorchTrainer
|
|
|
|
pl = import_lightning()
|
|
|
|
|
|
@pytest.fixture
|
|
def ray_start_6_cpus_2_gpus():
|
|
address_info = ray.init(num_cpus=6, num_gpus=2)
|
|
yield address_info
|
|
# The code after the yield will run as teardown code.
|
|
ray.shutdown()
|
|
|
|
|
|
@pytest.fixture
|
|
def ray_start_6_cpus_4_gpus():
|
|
address_info = ray.init(num_cpus=6, num_gpus=4)
|
|
yield address_info
|
|
# The code after the yield will run as teardown code.
|
|
ray.shutdown()
|
|
|
|
|
|
@pytest.mark.parametrize("strategy_name", ["ddp", "fsdp"])
|
|
@pytest.mark.parametrize("accelerator", ["cpu", "gpu"])
|
|
@pytest.mark.parametrize("datasource", ["dataloader", "datamodule"])
|
|
def test_trainer_with_native_dataloader(
|
|
ray_start_6_cpus_2_gpus, strategy_name, accelerator, datasource
|
|
):
|
|
"""Test basic ddp and fsdp training with dataloader and datamodule."""
|
|
|
|
if accelerator == "cpu" and strategy_name == "fsdp":
|
|
return
|
|
|
|
num_workers = 2
|
|
num_epochs = 4
|
|
batch_size = 8
|
|
dataset_size = 256
|
|
|
|
strategy_map = {"ddp": RayDDPStrategy(), "fsdp": RayFSDPStrategy()}
|
|
|
|
def train_loop():
|
|
model = LinearModule(input_dim=32, output_dim=4, strategy=strategy_name)
|
|
|
|
strategy = strategy_map[strategy_name]
|
|
|
|
trainer = pl.Trainer(
|
|
max_epochs=num_epochs,
|
|
devices="auto",
|
|
accelerator=accelerator,
|
|
strategy=strategy,
|
|
plugins=[RayLightningEnvironment()],
|
|
callbacks=[RayTrainReportCallback()],
|
|
)
|
|
|
|
datamodule = DummyDataModule(batch_size, dataset_size)
|
|
|
|
if datasource == "dataloader":
|
|
trainer.fit(
|
|
model,
|
|
train_dataloaders=datamodule.train_dataloader(),
|
|
val_dataloaders=datamodule.val_dataloader(),
|
|
)
|
|
if datasource == "datamodule":
|
|
trainer.fit(model, datamodule=datamodule)
|
|
|
|
trainer = TorchTrainer(
|
|
train_loop_per_worker=train_loop,
|
|
scaling_config=ScalingConfig(num_workers=2, use_gpu=(accelerator == "gpu")),
|
|
)
|
|
|
|
results = trainer.fit()
|
|
assert results.metrics["epoch"] == num_epochs - 1
|
|
assert (
|
|
results.metrics["step"] == num_epochs * dataset_size / num_workers / batch_size
|
|
)
|
|
assert "loss" in results.metrics
|
|
assert "val_loss" in results.metrics
|
|
|
|
|
|
@pytest.mark.parametrize("strategy_name", ["ddp", "fsdp"])
|
|
@pytest.mark.parametrize("accelerator", ["cpu", "gpu"])
|
|
def test_trainer_with_ray_data(ray_start_6_cpus_2_gpus, strategy_name, accelerator):
|
|
"""Test Data integration with ddp and fsdp."""
|
|
|
|
if accelerator == "cpu" and strategy_name == "fsdp":
|
|
return
|
|
|
|
num_epochs = 4
|
|
batch_size = 8
|
|
num_workers = 2
|
|
dataset_size = 256
|
|
|
|
strategy_map = {"ddp": RayDDPStrategy(), "fsdp": RayFSDPStrategy()}
|
|
|
|
dataset = np.random.rand(dataset_size, 32).astype(np.float32)
|
|
train_dataset = ray.data.from_numpy(dataset)
|
|
val_dataset = ray.data.from_numpy(dataset)
|
|
|
|
def train_loop():
|
|
model = LinearModule(input_dim=32, output_dim=4, strategy=strategy_name)
|
|
|
|
strategy = strategy_map[strategy_name]
|
|
|
|
trainer = pl.Trainer(
|
|
max_epochs=num_epochs,
|
|
devices="auto",
|
|
accelerator=accelerator,
|
|
strategy=strategy,
|
|
plugins=[RayLightningEnvironment()],
|
|
callbacks=[RayTrainReportCallback()],
|
|
)
|
|
|
|
train_data_iterable = ray.train.get_dataset_shard("train").iter_torch_batches(
|
|
batch_size=batch_size
|
|
)
|
|
val_data_iterable = ray.train.get_dataset_shard("val").iter_torch_batches(
|
|
batch_size=batch_size
|
|
)
|
|
|
|
trainer.fit(
|
|
model,
|
|
train_dataloaders=train_data_iterable,
|
|
val_dataloaders=val_data_iterable,
|
|
)
|
|
|
|
trainer = TorchTrainer(
|
|
train_loop_per_worker=train_loop,
|
|
scaling_config=ScalingConfig(num_workers=2, use_gpu=(accelerator == "gpu")),
|
|
datasets={"train": train_dataset, "val": val_dataset},
|
|
)
|
|
|
|
results = trainer.fit()
|
|
assert results.metrics["epoch"] == num_epochs - 1
|
|
assert (
|
|
results.metrics["step"] == num_epochs * dataset_size / num_workers / batch_size
|
|
)
|
|
assert "loss" in results.metrics
|
|
assert "val_loss" in results.metrics
|
|
|
|
|
|
@pytest.mark.parametrize("stage", [1, 2, 3])
|
|
def test_deepspeed_zero_stages(ray_start_6_cpus_4_gpus, tmpdir, stage):
|
|
num_epochs = 5
|
|
batch_size = 8
|
|
num_workers = 4
|
|
dataset_size = 256
|
|
|
|
def train_loop():
|
|
model = LinearModule(input_dim=32, output_dim=4, strategy="deepspeed")
|
|
|
|
strategy = RayDeepSpeedStrategy(stage=stage)
|
|
|
|
trainer = pl.Trainer(
|
|
max_epochs=num_epochs,
|
|
devices="auto",
|
|
accelerator="gpu",
|
|
strategy=strategy,
|
|
plugins=[RayLightningEnvironment()],
|
|
callbacks=[RayTrainReportCallback()],
|
|
)
|
|
|
|
datamodule = DummyDataModule(batch_size, dataset_size)
|
|
trainer.fit(model, datamodule=datamodule)
|
|
|
|
trainer = TorchTrainer(
|
|
train_loop_per_worker=train_loop,
|
|
scaling_config=ScalingConfig(num_workers=num_workers, use_gpu=True),
|
|
)
|
|
|
|
result = trainer.fit()
|
|
|
|
# Check all deepspeed model/optimizer shards are saved
|
|
all_files = os.listdir(f"{result.checkpoint.path}/checkpoint.ckpt/checkpoint")
|
|
for rank in range(num_workers):
|
|
full_model = "mp_rank_00_model_states.pt"
|
|
model_shard = f"zero_pp_rank_{rank}_mp_rank_00_model_states.pt"
|
|
optim_shard = f"zero_pp_rank_{rank}_mp_rank_00_optim_states.pt"
|
|
|
|
assert (
|
|
optim_shard in all_files
|
|
), f"[stage-{stage}] Optimizer states `{optim_shard}` doesn't exist!"
|
|
|
|
if stage == 3:
|
|
assert (
|
|
model_shard in all_files
|
|
), f"[stage-{stage}] Model states {model_shard} doesn't exist!"
|
|
else:
|
|
assert (
|
|
full_model in all_files
|
|
), f"[stage-{stage}] Model states {full_model} doesn't exist!"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.exit(pytest.main(["-v", "-x", __file__]))
|