chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:42 +08:00
commit e25996e7db
15472 changed files with 3536181 additions and 0 deletions
+226
View File
@@ -0,0 +1,226 @@
# file(GLOB TEST_OPS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "test_*.py")
# string(REPLACE ".py" "" TEST_OPS "${TEST_OPS}")
add_subdirectory(spmd_rules)
add_subdirectory(hybrid_strategy)
add_subdirectory(custom_op)
add_subdirectory(pir)
add_subdirectory(end_to_end)
if(WITH_DISTRIBUTE AND WITH_GPU)
# NOTE(zyl): unittests WITH multi cards and timeout
py_test_modules(test_converter MODULES test_converter)
set_tests_properties(test_converter PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE"
TIMEOUT 50)
py_test_modules(test_high_order_grad MODULES test_high_order_grad)
set_tests_properties(test_high_order_grad
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 50)
py_test_modules(test_iterable_dataset MODULES test_iterable_dataset)
set_tests_properties(test_iterable_dataset
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 80)
py_test_modules(test_reshard_api MODULES test_reshard_api)
set_tests_properties(test_reshard_api PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE"
TIMEOUT 150)
py_test_modules(test_reshard_s_to_p MODULES test_reshard_s_to_p)
set_tests_properties(test_reshard_s_to_p
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_reshard_p_to_s MODULES test_reshard_p_to_s)
set_tests_properties(test_reshard_p_to_s
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_reshard_s_to_s MODULES test_reshard_s_to_s)
set_tests_properties(test_reshard_s_to_s
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_reshard_r_to_s MODULES test_reshard_r_to_s)
set_tests_properties(test_reshard_r_to_s
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 320)
py_test_modules(test_reshard_p_to_r MODULES test_reshard_p_to_r)
set_tests_properties(test_reshard_p_to_r
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 160)
py_test_modules(test_reshard_s_to_r MODULES test_reshard_s_to_r)
set_tests_properties(test_reshard_s_to_r
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 150)
py_test_modules(test_reshard_r_to_p MODULES test_reshard_r_to_p)
set_tests_properties(test_reshard_r_to_p
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 200)
py_test_modules(test_reshard_x_to_r MODULES test_reshard_x_to_r)
set_tests_properties(test_reshard_x_to_r
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_reshard_r_to_x MODULES test_reshard_r_to_x)
set_tests_properties(test_reshard_r_to_x
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_reshard_nd_mesh MODULES test_reshard_nd_mesh)
set_tests_properties(test_reshard_nd_mesh
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_reshard_same_status MODULES test_reshard_same_status)
set_tests_properties(test_reshard_same_status
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_semi_auto_parallel_basic MODULES
test_semi_auto_parallel_basic)
set_tests_properties(test_semi_auto_parallel_basic
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 800)
py_test_modules(test_semi_auto_parallel_for_llama_subnet MODULES
test_semi_auto_parallel_for_llama_subnet)
set_tests_properties(test_semi_auto_parallel_for_llama_subnet
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 300)
py_test_modules(test_semi_auto_parallel_softmax_basic MODULES
test_semi_auto_parallel_softmax_basic)
set_tests_properties(test_semi_auto_parallel_softmax_basic
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 120)
py_test_modules(test_semi_auto_parallel_compare_basic MODULES
test_semi_auto_parallel_compare_basic)
set_tests_properties(test_semi_auto_parallel_compare_basic
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 120)
py_test_modules(test_semi_auto_parallel_subgraph_embedding_basic MODULES
test_semi_auto_parallel_subgraph_embedding_basic)
set_tests_properties(test_semi_auto_parallel_subgraph_embedding_basic
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 120)
py_test_modules(test_semi_auto_parallel_pylayer MODULES
test_semi_auto_parallel_pylayer)
set_tests_properties(test_semi_auto_parallel_pylayer
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_semi_auto_parallel_single_strategy MODULES
test_semi_auto_parallel_single_strategy)
set_tests_properties(test_semi_auto_parallel_single_strategy
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 400)
py_test_modules(test_semi_auto_parallel_sharding_strategy MODULES
test_semi_auto_parallel_sharding_strategy)
set_tests_properties(test_semi_auto_parallel_sharding_strategy
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 200)
py_test_modules(test_semi_auto_parallel_fsdp MODULES
test_semi_auto_parallel_fsdp)
set_tests_properties(test_semi_auto_parallel_fsdp
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 120)
py_test_modules(test_semi_auto_parallel_lazy_init MODULES
test_semi_auto_parallel_lazy_init)
set_tests_properties(test_semi_auto_parallel_lazy_init
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 120)
py_test_modules(test_semi_auto_parallel_in_framework MODULES
test_semi_auto_parallel_in_framework)
set_tests_properties(test_semi_auto_parallel_in_framework
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 300)
py_test_modules(test_semi_auto_parallel_dygraph_inplace MODULES
test_semi_auto_parallel_dygraph_inplace)
set_tests_properties(test_semi_auto_parallel_dygraph_inplace
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_semi_auto_parallel_for_flex_checkpoint MODULES
test_semi_auto_parallel_for_flex_checkpoint)
set_tests_properties(test_semi_auto_parallel_for_flex_checkpoint
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_dist_tensor_api MODULES test_dist_tensor_api)
set_tests_properties(test_dist_tensor_api
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 200)
py_test_modules(test_semi_auto_parallel_saved_tensor_hook MODULES
test_semi_auto_parallel_saved_tensor_hook)
set_tests_properties(test_semi_auto_parallel_saved_tensor_hook
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(
test_semi_auto_parallel_dist_to_static MODULES
test_semi_auto_parallel_dist_to_static ENVS FLAGS_enable_pir_api=1)
set_tests_properties(test_semi_auto_parallel_dist_to_static
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 300)
py_test_modules(test_static_reshard_api MODULES test_static_reshard_api ENVS
FLAGS_enable_pir_api=1)
set_tests_properties(test_static_reshard_api
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 300)
py_test_modules(test_dist_checkpoint_utils MODULES test_dist_checkpoint_utils)
set_tests_properties(test_dist_checkpoint_utils
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 120)
py_test_modules(
test_semi_auto_parallel_unshard_dtensor MODULES
test_semi_auto_parallel_unshard_dtensor ENVS FLAGS_enable_pir_api=1)
set_tests_properties(test_semi_auto_parallel_unshard_dtensor
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_auto_parallel_backward_test MODULES
test_auto_parallel_backward_test ENVS)
set_tests_properties(test_auto_parallel_backward_test
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 100)
py_test_modules(test_moe_utils MODULES test_moe_utils)
set_tests_properties(test_moe_utils PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE"
TIMEOUT 30)
py_test_modules(test_object_list_communication MODULES
test_object_list_communication)
set_tests_properties(test_object_list_communication
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 50)
# End of unittests WITH multi cards and timeout
# NOTE(zyl): unittests WITH multi cards and WITHOUT timeout
py_test_modules(test_semi_auto_parallel_moe_utils MODULES
test_semi_auto_parallel_moe_utils)
set_tests_properties(test_semi_auto_parallel_moe_utils
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE")
# End of unittests WITH multi cards and WITHOUT timeout
py_test_modules(test_semi_auto_parallel_functional_in_single_card MODULES
test_semi_auto_parallel_functional_in_single_card)
# End of unittests WITH single card and timeout
# NOTE(zyl): unittests WITH single card and WITHOUT timeout
py_test_modules(test_align_mode MODULES test_align_mode)
py_test_modules(test_tunable_variable MODULES test_tunable_variable)
py_test_modules(test_tunable_space MODULES test_tunable_space)
py_test_modules(test_recorder MODULES test_recorder)
py_test_modules(test_trial MODULES test_trial)
py_test_modules(test_cluster MODULES test_cluster)
py_test_modules(test_comm_cost MODULES test_comm_cost)
py_test_modules(test_comp_cost MODULES test_comp_cost)
py_test_modules(test_cluster_v2 MODULES test_cluster_v2)
py_test_modules(test_process_mesh_v2 MODULES test_process_mesh_v2)
py_test_modules(test_strategy MODULES test_strategy)
py_test_modules(test_cluster_partition MODULES test_cluster_partition)
py_test_modules(test_convert_to_process_meshes MODULES
test_convert_to_process_meshes)
py_test_modules(test_dist_tensor MODULES test_dist_tensor ENVS
FLAGS_enable_pir_api=1)
py_test_modules(test_api_dist_branch MODULES test_api_dist_branch)
py_test_modules(test_shard_tensor_api MODULES test_shard_tensor_api ENVS
FLAGS_enable_pir_api=1)
py_test_modules(test_placement_types MODULES test_placement_types)
py_test_modules(test_strategy_api MODULES test_strategy_api)
py_test_modules(test_parallel_api MODULES test_parallel_api)
py_test_modules(test_dtensor_to_local_api MODULES test_dtensor_to_local_api)
py_test_modules(test_dtensor_from_local_api MODULES
test_dtensor_from_local_api)
py_test_modules(test_dy_local_view_compute MODULES test_dy_local_view_compute)
py_test_modules(test_local_view_compute MODULES test_local_view_compute)
py_test_modules(test_microbatch MODULES test_microbatch)
py_test_modules(test_PipelineStage MODULES test_PipelineStage)
py_test_modules(
test_tp_conv
MODULES
test_tp_conv
ENVS
FLAGS_max_inplace_grad_add=4
FLAGS_cudnn_deterministic=1
FLAGS_embedding_deterministic=1
NVIDIA_TF32_OVERRIDE=0)
py_test_modules(test_PP_Schedules MODULES test_PP_Schedules)
py_test_modules(test_pipeline_sync_shared_parameters MODULES
test_pipeline_sync_shared_parameters)
py_test_modules(
test_context_parallel
MODULES
test_context_parallel
ENVS
FLAGS_max_inplace_grad_add=4
FLAGS_cudnn_deterministic=1
FLAGS_embedding_deterministic=1
NVIDIA_TF32_OVERRIDE=0)
# End of unittests WITH single card WITHOUT timeout
py_test_modules(test_clear_param_storage_api MODULES
test_clear_param_storage_api)
endif()
py_test_modules(test_job_schedule_profiler_range MODULES
test_job_schedule_profiler_range)
set_pir_tests_properties()
+678
View File
@@ -0,0 +1,678 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 random
import types
import numpy as np
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.distributed import fleet
from paddle.distributed.auto_parallel._utils import (
_patch_grads_for_step,
)
from paddle.distributed.auto_parallel.pipelining.schedules import (
Schedule1F1B,
ScheduleFThenB,
ScheduleVPP,
)
from paddle.distributed.auto_parallel.pipelining.stage import (
PipelineStage,
)
from paddle.io import DataLoader, Dataset
def fix_seeds(seed=2025):
"""Fix random seeds to ensure reproducibility"""
paddle.seed(seed)
random.seed(seed)
np.random.seed(seed)
class PPMyModel(nn.Layer):
def __init__(self):
super().__init__()
self.mesh = paddle.distributed.ProcessMesh(
[0, 1, 2, 3], dim_names=["pp"]
)
self.num_layers = 8
self.num_layers_per_card = self.num_layers // 4
self.linears = nn.LayerList()
for i in range(self.num_layers):
linear = nn.Linear(8, 8, bias_attr=False)
# Mark network parameters
linear.weight = dist.shard_tensor(
linear.weight,
self.get_pp_mesh(i),
[dist.Replicate()],
)
self.linears.append(linear)
def get_pp_mesh(self, layer_index):
# layer_index=0-3 corresponds to mesh_idx 0,0,1,1,2,2,3,3
mesh_idx = int(layer_index / (self.num_layers / 4))
return self.mesh[mesh_idx]
def forward(self, x):
x.stop_gradient = False
out = x
for i in range(self.num_layers):
# Mark intermediate variables, reshard when switching devices
if i % self.num_layers_per_card == 0 and i > 0:
out = dist.reshard(out, self.get_pp_mesh(i), [dist.Replicate()])
out = self.linears[i](out)
return paddle.cast(out, 'float32')
class PPMyModel_SingleStage(nn.Layer):
def __init__(self):
super().__init__()
self.mesh = paddle.distributed.ProcessMesh(
[0, 1, 2, 3], dim_names=["pp"]
)
self.num_layers = 8
self.num_layers_per_card = 2
self.linears = nn.LayerList()
for i in range(self.num_layers):
linear = nn.Linear(8, 8, bias_attr=False)
linear.weight = dist.shard_tensor(
linear.weight,
self.get_pp_mesh(i),
[dist.Replicate()],
)
self.linears.append(linear)
def get_pp_mesh(self, layer_index):
# layer_index=0-7 maps to mesh_idx as 0,0,1,1,2,2,3,3
mesh_idx = int(layer_index // self.num_layers_per_card)
return self.mesh[mesh_idx]
def forward(self, x):
x.stop_gradient = False
out = x
device_id = dist.get_rank()
for i in range(self.num_layers):
if int(i // self.num_layers_per_card) == device_id:
out = self.linears[i](out)
return paddle.cast(out, 'float32')
class PPMyModel_MultiStage(nn.Layer):
def __init__(self):
super().__init__()
self.mesh = paddle.distributed.ProcessMesh(
[0, 1, 2, 3], dim_names=["pp"]
)
self.num_layers = 8
self.linears = nn.LayerList()
for i in range(self.num_layers):
linear = nn.Linear(8, 8, bias_attr=False)
linear.weight = dist.shard_tensor(
linear.weight,
self.get_pp_mesh(i),
[dist.Replicate()],
)
self.linears.append(linear)
def get_pp_mesh(self, layer_index):
mesh_idx = int(layer_index % 4)
return self.mesh[mesh_idx]
def forward(self, x):
# For MultiStage, we shard model layers, so forward calls _Pipeline_model_chunk's forward
pass
class _Pipeline_model_chunk(nn.Layer):
def __init__(self, layers):
super().__init__()
self.layers = layers
def forward(self, x):
out = x
for layer in self.layers:
out = layer(out)
return out
class PP_DP_MyModel(nn.Layer):
def __init__(self):
super().__init__()
pp_mesh0 = paddle.distributed.ProcessMesh([0, 2], dim_names=["dp"])
pp_mesh1 = paddle.distributed.ProcessMesh([1, 3], dim_names=["dp"])
self.num_layers = 8
self.linears = nn.LayerList()
for i in range(self.num_layers):
linear = nn.Linear(8, 8, bias_attr=False)
if i < 4:
linear.weight = dist.shard_tensor(
linear.weight, pp_mesh0, [dist.Replicate()]
)
else:
linear.weight = dist.shard_tensor(
linear.weight, pp_mesh1, [dist.Replicate()]
)
self.linears.append(linear)
def forward(self, x):
x.stop_gradient = False
out = x
# Get current rank's position in pp group (0 or 1)
pp_rank = dist.get_rank() % 2
# Only process layers belonging to current rank
start_layer = (
4 * pp_rank
) # rank 0/2 processes layers 0-3, rank 1/3 processes layers 4-7
end_layer = start_layer + 4
for i in range(start_layer, end_layer):
out = self.linears[i](out)
return paddle.cast(out, 'float32')
class RandomDataset(Dataset):
def __init__(self, image_size, output_size, num_samples=1):
super().__init__()
self.image_size = image_size
self.num_samples = num_samples
self.output_size = output_size
def __getitem__(self, index):
input = paddle.rand([self.image_size], dtype='float32')
label = paddle.rand([self.output_size], dtype='float32')
return input, label
def __len__(self):
return self.num_samples
class Test_Schedules:
@classmethod
def setUpClass(cls):
"""Initialize test class setup"""
paddle.distributed.init_parallel_env()
cls.group = paddle.distributed.new_group([0, 1, 2, 3])
cls.rank = dist.get_rank()
cls.mesh = paddle.distributed.ProcessMesh(
[0, 1, 2, 3], dim_names=["pp"]
)
fleet.auto.set_mesh(cls.mesh)
def test_ScheduleFThenB(self):
fix_seeds()
self.model = PPMyModel_SingleStage()
self.micro_batches = 8
self.stage = PipelineStage(self.model, self.rank, 4, group=self.group)
self.stage.has_backward = True
loss_fn_ = nn.MSELoss()
schedule = ScheduleFThenB(
self.stage, self.micro_batches, loss_fn=loss_fn_
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=self.model.parameters()
)
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=8)
losses_by_step = []
num_iterations = 20
for iter_idx in range(num_iterations):
losses_by_micro_batch = []
for i, (data, label) in enumerate(loader):
schedule.step(data, target=label, losses=losses_by_micro_batch)
if self.rank == 3:
losses_by_step.append(
np.array(losses_by_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return losses_by_step
def test_Schedule1F1B(self):
fix_seeds()
self.model = PPMyModel_SingleStage()
self.micro_batches = 8
self.stage = PipelineStage(self.model, self.rank, 4, group=self.group)
self.stage.has_backward = True
loss_fn_ = nn.MSELoss()
schedule = Schedule1F1B(
self.stage, self.micro_batches, loss_fn=loss_fn_
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=self.model.parameters()
)
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=8)
losses_by_step = []
num_iterations = 20
for iter_idx in range(num_iterations):
losses_by_micro_batch = []
for i, (data, label) in enumerate(loader):
schedule.step(data, target=label, losses=losses_by_micro_batch)
if self.rank == 3:
losses_by_step.append(
np.array(losses_by_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return losses_by_step
def test_ScheduleVPP(self):
fix_seeds()
self.model = PPMyModel_MultiStage()
self.local_stages = 2
self.micro_batches = 8
self.stage_list = []
for i in range(self.local_stages):
stage_model = _Pipeline_model_chunk(
self.model.linears[self.rank + i * 4 : self.rank + i * 4 + 1]
)
self.stage_list.append(
PipelineStage(
stage_model, self.rank + i * 4, 8, group=self.group
)
)
self.stage_list[i].has_backward = True
loss_fn_ = nn.MSELoss()
schedule = ScheduleVPP(
self.stage_list, self.micro_batches, loss_fn=loss_fn_
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=self.model.parameters()
)
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=8)
losses_by_micro_batch = []
losses_by_step = []
num_iterations = 20
for iter_idx in range(num_iterations):
for i, (data, label) in enumerate(loader):
schedule.step(data, target=label, losses=losses_by_micro_batch)
if self.rank == 3:
losses_by_step.append(
np.array(losses_by_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return losses_by_step
def test_pp_model(self):
"""Test pipeline parallel model using PPMyModel as the baseline"""
fix_seeds()
pp_model = PPMyModel()
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=pp_model.parameters()
)
loss_fn = nn.MSELoss()
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=1)
pp_losses_step = []
num_iterations = 20
for iter_idx in range(num_iterations):
pp_losses_micro_batch = []
for i, (data, label) in enumerate(loader):
output = pp_model(data)
loss = loss_fn(output, label)
pp_losses_micro_batch.append(loss.item())
loss.backward()
pp_losses_step.append(
np.array(pp_losses_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return pp_losses_step
def test_dp_pp(self):
fix_seeds()
global_mesh = paddle.distributed.ProcessMesh(
[[0, 2], [1, 3]], dim_names=["pp", "dp"]
)
fleet.auto.set_mesh(global_mesh)
self.model = PP_DP_MyModel()
pp_mesh0 = paddle.distributed.ProcessMesh([0, 2], dim_names=["dp"])
pp_mesh1 = paddle.distributed.ProcessMesh([1, 3], dim_names=["dp"])
dp_pp_pleacement = [dist.Shard(0)]
pp_group_1 = paddle.distributed.new_group([0, 1])
pp_group_2 = paddle.distributed.new_group([2, 3])
dp_group = paddle.distributed.new_group([1, 3])
self.micro_batches = 4
if self.rank < 2:
self.stage = PipelineStage(
self.model, self.rank % 2, 2, group=pp_group_1
)
else:
self.stage = PipelineStage(
self.model, self.rank % 2, 2, group=pp_group_2
)
self.stage.has_backward = True
loss_fn_ = nn.MSELoss()
schedule = ScheduleFThenB(
self.stage, self.micro_batches, loss_fn=loss_fn_
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=self.model.parameters()
)
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=8)
losses_by_step = []
num_iterations = 20
all_losses_in_one_step_md5sum = []
for iter_idx in range(num_iterations):
losses_by_micro_batch = []
for i, (data, label) in enumerate(loader):
# reorder data and label
batch_size = data.shape[0]
even_indices = list(range(0, batch_size, 2))
odd_indices = list(range(1, batch_size, 2))
reordered_indices = even_indices + odd_indices
reordered_data = data[reordered_indices]
reordered_label = label[reordered_indices]
dist_data = dist.shard_tensor(
reordered_data, pp_mesh0, dp_pp_pleacement
)
dist_label = dist.shard_tensor(
reordered_label, pp_mesh1, dp_pp_pleacement
)
schedule.step(
dist_data, target=dist_label, losses=losses_by_micro_batch
)
# Losses from two dp paths are in Partial(AVG) state, need to do all_reduce
if self.rank == 1 or self.rank == 3:
reduced_losses = []
for item in losses_by_micro_batch:
local_loss = item._local_value()
dist.all_reduce(
local_loss, op=dist.ReduceOp.AVG, group=dp_group
)
reduced_losses.append(local_loss)
if iter_idx == 0:
all_losses_in_one_step_md5sum.append(
local_loss._md5sum()
)
if self.rank == 3:
# Calculate mean using reduced losses
losses_by_step.append(
np.array(reduced_losses, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return losses_by_step, all_losses_in_one_step_md5sum
def test_pp_model_with_ClipGradByGlobalNorm(self):
"""Test pipeline parallel model with ClipGradByGlobalNorm using PPMyModel as the baseline"""
fix_seeds()
pp_model = PPMyModel()
opt = paddle.optimizer.AdamW(
learning_rate=0.001,
parameters=pp_model.parameters(),
grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0),
)
loss_fn = nn.MSELoss()
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=1)
pp_losses_step = []
num_iterations = 20
for iter_idx in range(num_iterations):
pp_losses_micro_batch = []
for i, (data, label) in enumerate(loader):
output = pp_model(data)
loss = loss_fn(output, label)
pp_losses_micro_batch.append(loss.item())
loss.backward()
pp_losses_step.append(
np.array(pp_losses_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return pp_losses_step
def test_ScheduleFThenB_with_ClipGradByGlobalNorm(self):
fix_seeds()
self.model = PPMyModel_SingleStage()
self.micro_batches = 8
self.stage = PipelineStage(self.model, self.rank, 4, group=self.group)
self.stage.has_backward = True
loss_fn_ = nn.MSELoss()
schedule = ScheduleFThenB(
self.stage, self.micro_batches, loss_fn=loss_fn_
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001,
parameters=self.model.parameters(),
grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0),
)
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=8)
losses_by_step = []
num_iterations = 20
for iter_idx in range(num_iterations):
losses_by_micro_batch = []
for i, (data, label) in enumerate(loader):
schedule.step(data, target=label, losses=losses_by_micro_batch)
if self.rank == 3:
losses_by_step.append(
np.array(losses_by_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return losses_by_step
def test_FthenB_align_mode_of_GradientClipByGlobalNorm(self):
fix_seeds()
paddle.set_flags(
{'FLAGS_enable_auto_parallel_align_mode': True}
) # Represents logical alignment with GradientClipByGlobalNorm that is semi-automatically parallel to the original dynamic graph, because the processing logic here is not aligned with the dynamic graph manually parallel
self.model = PPMyModel_SingleStage()
self.micro_batches = 8
self.stage = PipelineStage(self.model, self.rank, 4, group=self.group)
self.stage.has_backward = True
loss_fn_ = nn.MSELoss()
schedule = ScheduleFThenB(
self.stage, self.micro_batches, loss_fn=loss_fn_
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001,
parameters=self.model.parameters(),
grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0),
)
if dist.in_auto_parallel_align_mode(): # When in auto parallel align mode, patching the optimizer step function
orig_step = (
opt.step.__func__ if hasattr(opt.step, "__func__") else opt.step
)
decorator = _patch_grads_for_step(amp_master_grad=True)
new_step = decorator(
orig_step
) # When the step function is wrapped by the decorator, it initializes gradients for parameters belonging to other ranks prior to step method execution, ensuring their metadata is preserved.
opt.step = types.MethodType(new_step, opt)
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=8)
losses_by_step = []
num_iterations = 20
for iter_idx in range(num_iterations):
losses_by_micro_batch = []
for i, (data, label) in enumerate(loader):
schedule.step(data, target=label, losses=losses_by_micro_batch)
if self.rank == 3:
losses_by_step.append(
np.array(losses_by_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
paddle.set_flags({'FLAGS_enable_auto_parallel_align_mode': False})
return losses_by_step
def test_dp_pp_align_mode(self):
fix_seeds()
paddle.set_flags(
{'FLAGS_enable_auto_parallel_align_mode': True}
) # Represents manual parallel alignment with dynamic graphs, mainly segmenting microbatches when aligning DP and PP mixing
global_mesh = paddle.distributed.ProcessMesh(
[[0, 2], [1, 3]], dim_names=["pp", "dp"]
)
fleet.auto.set_mesh(global_mesh)
self.model = PP_DP_MyModel()
pp_mesh0 = paddle.distributed.ProcessMesh([0, 2], dim_names=["dp"])
pp_mesh1 = paddle.distributed.ProcessMesh([1, 3], dim_names=["dp"])
dp_pp_pleacement = [dist.Shard(0)]
pp_group_1 = paddle.distributed.new_group([0, 1])
pp_group_2 = paddle.distributed.new_group([2, 3])
dp_group = paddle.distributed.new_group([1, 3])
self.micro_batches = 4
if self.rank < 2:
self.stage = PipelineStage(
self.model, self.rank % 2, 2, group=pp_group_1
)
else:
self.stage = PipelineStage(
self.model, self.rank % 2, 2, group=pp_group_2
)
self.stage.has_backward = True
loss_fn_ = nn.MSELoss()
schedule = ScheduleFThenB(
self.stage, self.micro_batches, loss_fn=loss_fn_
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=self.model.parameters()
)
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=8)
losses_by_step = []
all_losses_in_one_step_md5sum = []
num_iterations = 20
for iter_idx in range(num_iterations):
losses_by_micro_batch = []
for i, (data, label) in enumerate(loader):
dist_data = dist.shard_tensor(data, pp_mesh0, dp_pp_pleacement)
dist_label = dist.shard_tensor(
label, pp_mesh1, dp_pp_pleacement
)
schedule.step(
dist_data, target=dist_label, losses=losses_by_micro_batch
)
# Losses from two dp paths are in Partial(AVG) state, need to do all_reduce
if self.rank == 1 or self.rank == 3:
reduced_losses = []
for item in losses_by_micro_batch:
local_loss = item._local_value()
dist.all_reduce(
local_loss, op=dist.ReduceOp.AVG, group=dp_group
)
reduced_losses.append(local_loss)
if iter_idx == 0:
all_losses_in_one_step_md5sum.append(
local_loss._md5sum()
)
if self.rank == 3:
# Calculate mean using reduced losses
losses_by_step.append(
np.array(reduced_losses, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
paddle.set_flags({'FLAGS_enable_auto_parallel_align_mode': False})
return losses_by_step, all_losses_in_one_step_md5sum
def run_test(self):
"""Compare losses between three training methods"""
self.setUpClass()
pp_losses = self.test_pp_model()
scheduleFThenB_losses = self.test_ScheduleFThenB()
schedule1f1b_losses = self.test_Schedule1F1B()
schedulevpp_losses = self.test_ScheduleVPP()
pp_model_with_ClipGradByGlobalNorm_losses = (
self.test_pp_model_with_ClipGradByGlobalNorm()
)
scheduleFThenB_with_ClipGradByGlobalNorm_losses = (
self.test_ScheduleFThenB_with_ClipGradByGlobalNorm()
)
scheduleFthenB_align_mode_losses_of_GradientClipByGlobalNorm = (
self.test_FthenB_align_mode_of_GradientClipByGlobalNorm()
)
dp_pp_losses, dp_pp_losses_md5sum = self.test_dp_pp()
dp_pp_align_mode_losses, dp_pp_align_mode_losses_md5sum = (
self.test_dp_pp_align_mode()
)
if self.rank == 3:
np.testing.assert_allclose(
pp_losses,
scheduleFThenB_losses,
rtol=1e-5,
)
np.testing.assert_allclose(
schedule1f1b_losses,
scheduleFThenB_losses,
rtol=1e-5,
)
np.testing.assert_allclose(
schedulevpp_losses,
scheduleFThenB_losses,
rtol=1e-5,
)
np.testing.assert_allclose(
dp_pp_losses,
scheduleFThenB_losses,
rtol=1e-5,
)
np.testing.assert_allclose(
pp_model_with_ClipGradByGlobalNorm_losses,
scheduleFThenB_with_ClipGradByGlobalNorm_losses,
rtol=1e-5,
)
np.testing.assert_allclose(
dp_pp_align_mode_losses,
dp_pp_losses,
rtol=1e-5,
)
np.testing.assert_allclose(
scheduleFthenB_align_mode_losses_of_GradientClipByGlobalNorm,
pp_model_with_ClipGradByGlobalNorm_losses,
rtol=1e-5,
)
assert dp_pp_losses_md5sum == dp_pp_align_mode_losses_md5sum
if __name__ == '__main__':
Test_Schedules().run_test()
+532
View File
@@ -0,0 +1,532 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 __future__ import annotations
import logging
import random
from collections import defaultdict
from typing import TYPE_CHECKING
import numpy as np
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.distributed import fleet
from paddle.distributed.auto_parallel.pipelining._backward import (
stage_backward,
stage_backward_input,
stage_backward_weight,
)
from paddle.distributed.auto_parallel.pipelining.stage import (
PipelineStage,
_RecvInfo,
)
from paddle.distributed.auto_parallel.pipelining.utils import (
PipeliningShapeError,
TensorMeta,
_detach_and_keep_grad,
_friendly_debug_info,
_get_stage_mesh,
_validate_tensor_metadata,
_validate_tensors_metadata,
_zero_initialize_with_meta,
)
from paddle.io import Dataset
if TYPE_CHECKING: # 添加类型检查块
from paddle.distributed.communication.group import Group
logger = logging.getLogger(__name__)
def fix_seeds(seed=2025):
"""Fix random seeds to ensure reproducibility"""
paddle.seed(seed)
random.seed(seed)
np.random.seed(seed)
def _batch_p2p(p2p_ops, desc=None):
# TODO(zhengtianyu): 等合入Scheduler后,删除该函数
"""Execute batch point-to-point communication operations"""
if len(p2p_ops) == 0:
return None
desc_str = f"{desc}, " if desc else ""
logger.debug("batch_p2p %s%s", desc_str, p2p_ops)
return dist.batch_isend_irecv(p2p_ops).pop()
def _sorted_batch_p2p(p2p_ops, desc=None):
# TODO(zhengtianyu): 等合入Scheduler后,删除该函数
"""Sort and execute batch point-to-point communication by peer rank"""
ops_by_peer: dict[int, list[dist.P2POp]] = defaultdict(list)
work_by_peer: dict[int, dist.Work] = {}
if len(p2p_ops) == 0:
return work_by_peer
for op in p2p_ops:
ops_by_peer[op.peer].append(op)
for peer, ops in sorted(ops_by_peer.items()):
work_by_peer[peer] = _batch_p2p(ops, desc=desc)
return work_by_peer
class MyModel(nn.Layer):
def __init__(self):
super().__init__()
self.linear1 = nn.Linear(8, 8, bias_attr=False)
self.linear2 = nn.Linear(8, 8, bias_attr=False)
self.linear3 = nn.Linear(8, 8)
self.linear4 = nn.Linear(8, 8)
def forward(self, x, debug_str=None):
if hasattr(self, 'linear1'):
if debug_str:
logger.debug(f"{debug_str} linear1")
x = self.linear1(x)
x = self.linear2(x)
if hasattr(self, 'linear3'):
x = self.linear3(x)
x = self.linear4(x)
return x
class PPMyModel(nn.Layer):
def __init__(self):
super().__init__()
self.mesh = paddle.distributed.ProcessMesh([0, 1], dim_names=["pp"])
self.num_layers = 4
self.num_layers_per_card = self.num_layers // 2
# Create layers same as MyModel
self.linears = nn.LayerList()
for i in range(self.num_layers):
if i // 2 == 0:
linear = nn.Linear(8, 8, bias_attr=False)
else:
linear = nn.Linear(8, 8)
# Mark network parameters
linear.weight = dist.shard_tensor(
linear.weight,
self.get_pp_mesh(i),
[dist.Replicate()],
)
self.linears.append(linear)
def get_pp_mesh(self, layer_index):
# layer_index=0-3 corresponds to mesh_idx 0,0,1,1 respectively
mesh_idx = int(layer_index / (self.num_layers / 2))
return self.mesh[mesh_idx]
def forward(self, x):
x.stop_gradient = False
out = x
for i in range(self.num_layers):
# Mark intermediate variables, reshard when device switching is needed
if i % self.num_layers_per_card == 0 and i > 0:
out = dist.reshard(out, self.get_pp_mesh(i), [dist.Replicate()])
out = self.linears[i](out)
return paddle.cast(out, 'float32')
class RandomDataset(Dataset):
def __init__(self, image_size, num_samples=1):
super().__init__()
self.image_size = image_size
self.num_samples = num_samples
def __getitem__(self, index):
# Keep dimension as [8]
input = paddle.rand([self.image_size], dtype='float32')
label = paddle.rand([8], dtype='float32')
return input, label
def __len__(self):
return self.num_samples
def manual_model_split(
model: MyModel, stage_idx: int, group: Group
) -> PipelineStage:
"""Manually split model into pipeline stages"""
if stage_idx == 0:
del model.linear3
del model.linear4
elif stage_idx == 1:
del model.linear1
del model.linear2
else:
raise ValueError("Invalid stage index.")
return PipelineStage(model, stage_idx, 2, group=group)
class TestPipelineStage:
@classmethod
def setUpClass(cls):
"""Initialize test class setup"""
paddle.distributed.init_parallel_env()
cls.group = paddle.distributed.new_group([0, 1])
cls.rank = dist.get_rank()
cls.mesh = paddle.distributed.ProcessMesh([0, 1], dim_names=["pp"])
fleet.auto.set_mesh(cls.mesh)
def test_PipelineStage(self):
"""Test complete pipeline including forward, backward and model comparison"""
fix_seeds()
self.model = MyModel()
self.micro_batches = 1 # The PipelineStage component is currently tested separately, so it is set to 1, and the micro_batches > 1 scenario will be overridden when the schedule component is tested in the future
self.stage = manual_model_split(self.model, self.rank, self.group)
self.stage.has_backward = True
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=self.model.parameters()
)
loss_fn = nn.MSELoss()
dataset = RandomDataset(image_size=8, num_samples=100)
losses = []
num_iterations = 20
data0 = paddle.zeros([8], dtype='float32')
label0 = paddle.zeros([8], dtype='float32')
data0 = paddle.to_tensor(data0).unsqueeze(0)
label0 = paddle.to_tensor(label0).unsqueeze(0)
# Prepare infrastructure
if self.rank == 0:
output = self.stage._prepare_forward_infra(
self.micro_batches,
(data0,),
{
"debug_str": "test debug_str",
},
)
else:
output = self.stage._prepare_forward_infra(
self.micro_batches,
(),
{
"debug_str": "test debug_str",
},
)
loss = None
if self.stage.is_last:
loss = loss_fn(output[0], label0)
self.stage._prepare_backward_infra(self.micro_batches, loss)
for iter_idx in range(num_iterations):
data, label = dataset[iter_idx]
data = paddle.to_tensor(data).unsqueeze(0)
label = paddle.to_tensor(label).unsqueeze(0)
# Forward pass
fwd_sends_to_wait = []
# Receive operations
ops = self.stage.get_fwd_recv_ops(0)
works = _sorted_batch_p2p(ops, desc="fwd_recv")
for work in works.values():
work.wait()
# Forward computation
output = self.stage.forward_one_chunk(
0,
(data,),
{
"debug_str": "test debug_str",
},
)
# Send operations
ops = self.stage.get_fwd_send_ops(0)
works = _sorted_batch_p2p(ops, desc="fwd_send")
fwd_sends_to_wait.extend(works.values())
# Wait for all send operations to complete
for work in fwd_sends_to_wait:
work.wait()
# Calculate loss if last stage
loss = None
if self.stage.is_last:
loss = loss_fn(output, label)
assert loss is not None
losses.append(loss.item())
# Backward pass
bwd_sends_to_wait = []
# Receive gradients
ops = self.stage.get_bwd_recv_ops(0)
works = _sorted_batch_p2p(ops, desc="bwd_recv")
for work in works.values():
work.wait()
# Backward computation
grads = self.stage.backward_one_chunk(
0, loss=loss, last_backward=True
)
assert grads is not None
# Send gradients
ops = self.stage.get_bwd_send_ops(0)
works = _sorted_batch_p2p(ops, desc="bwd_send")
bwd_sends_to_wait.extend(works.values())
# Wait for all send operations to complete
for work in bwd_sends_to_wait:
work.wait()
self.stage.clear_runtime_states()
opt.step()
opt.clear_grad()
return losses
def test_pp_model(self):
"""Test pipeline parallel model using MyModel"""
fix_seeds()
pp_model = PPMyModel()
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=pp_model.parameters()
)
loss_fn = nn.MSELoss()
dataset = RandomDataset(image_size=8, num_samples=100)
pp_losses = []
num_iterations = 20
for iter_idx in range(num_iterations):
data, label = dataset[iter_idx]
data = paddle.to_tensor(data).unsqueeze(0)
label = paddle.to_tensor(label).unsqueeze(0)
output = pp_model(data)
loss = loss_fn(output, label)
pp_losses.append(loss.item())
loss.backward()
opt.step()
opt.clear_grad()
return pp_losses
def test_single_gpu(self):
"""Test single GPU training with the complete model"""
# Only run single GPU training on rank 1
if self.rank == 1:
fix_seeds()
single_model = MyModel()
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=single_model.parameters()
)
loss_fn = nn.MSELoss()
dataset = RandomDataset(image_size=8, num_samples=100)
losses = []
num_iterations = 20
for iter_idx in range(num_iterations):
data, label = dataset[iter_idx]
output = single_model(data)
loss = loss_fn(output, label)
losses.append(loss.item())
loss.backward()
opt.step()
opt.clear_grad()
return losses
return None
def test_simple_func_about_schedules(self):
"""Test local data transfer functions between stages on the same rank"""
if self.rank == 0:
# 1. Test set_local_fwd_input
tensor = paddle.to_tensor([1.0, 2.0, 3.0])
stage = PipelineStage(nn.Linear(3, 3), 1, 2, group=self.group)
stage.args_recv_info[0] = (_RecvInfo("test", 0, paddle.empty([3])),)
stage.set_local_fwd_input(tensor, 0)
assert stage.args_recv_info[0][0].buffer is not None
# 2. Test get_local_bwd_output
stage.has_backward = True
grad_tensor = paddle.to_tensor([4.0, 5.0, 6.0])
stage.bwd_cache[0] = (grad_tensor,)
stage.chunks = 2
bwd_output = stage.get_local_bwd_output(0)
assert bwd_output[0].equal_all(grad_tensor)
# 3. Test set_local_bwd_input
prev_stage = PipelineStage(nn.Linear(3, 3), 0, 2, group=self.group)
prev_stage.has_backward = True
prev_stage.grad_recv_info[0] = (
_RecvInfo("test", 1, paddle.empty([3])),
)
grad_input = (paddle.to_tensor([7.0, 8.0, 9.0]),)
prev_stage.set_local_bwd_input(grad_input, 0)
assert prev_stage.grad_recv_info[0][0].buffer.equal_all(
grad_input[0]
)
def test_backward_some_simple_examples(self):
"""Test simple examples in backward"""
if self.rank == 0:
# 1. Test backward propagation with dictionary and tuple outputs
input_tensor = paddle.to_tensor([1.0, 2.0], stop_gradient=False)
output_dict = {
"out": input_tensor * 2.0,
"out_tensor_is_dict_grad_is_None": {"out": input_tensor * 2.0},
"out_tensor_is_tuple_grad_is_None": (input_tensor * 2.0,),
}
grad_dict = {
"out": paddle.to_tensor([0.1, 0.2]),
"out_tensor_is_dict_grad_is_None": None,
"out_tensor_is_tuple_grad_is_None": None,
}
input_grads = stage_backward(output_dict, grad_dict, [input_tensor])
expected_grad = paddle.to_tensor([2 * 0.1, 2 * 0.2])
np.testing.assert_allclose(
input_grads[0].numpy(), expected_grad.numpy(), rtol=1e-5
)
# 2. Test not yet implemented stage_backward_input and stage_backward_weight
try:
stage_backward_input(
[input_tensor * 2.0],
[paddle.to_tensor([0.1, 0.2])],
[input_tensor],
iter([paddle.to_tensor([1.0, 1.0])]),
)
raise AssertionError("Should raise Error")
except NotImplementedError as e:
pass
try:
stage_backward_weight(
iter([paddle.to_tensor([1.0, 1.0])]),
[{"params": [paddle.to_tensor([1.0, 1.0])]}],
)
raise AssertionError("Should raise Error")
except NotImplementedError as e:
pass
def test_utils_some_simple_examples(self):
"""Test simple examples in utils"""
if self.rank == 0:
# 1. Test exceptions in _get_stage_mesh
try:
_get_stage_mesh(0, 2, style="v")
raise AssertionError("Should raise Error")
except NotImplementedError as e:
pass
try:
_get_stage_mesh(0, 2, style="unknown")
raise AssertionError("Should raise Error")
except ValueError as e:
pass
# 2. Test exceptions in _validate_tensors_metadata
try:
# Length mismatch
expected = [paddle.to_tensor([1.0, 2.0])]
actual = [paddle.to_tensor([1.0]), paddle.to_tensor([2.0])]
_validate_tensors_metadata("test", expected, actual)
raise AssertionError("Should raise Error")
except PipeliningShapeError as e:
pass
# 3. Test exceptions in _validate_tensor_metadata
try:
# Shape mismatch
expected = paddle.to_tensor([1.0, 2.0])
actual = paddle.to_tensor([1.0])
_validate_tensor_metadata("test", expected, actual)
raise AssertionError("Should raise Error")
except PipeliningShapeError as e:
pass
try:
# Dtype mismatch
expected = paddle.to_tensor([1.0, 2.0], dtype='float32')
actual = paddle.to_tensor([1, 2], dtype='int32')
_validate_tensor_metadata("test", expected, actual)
raise AssertionError("Should raise Error")
except PipeliningShapeError as e:
pass
# 4. Test _detach_and_keep_grad
a = paddle.to_tensor([2.0], stop_gradient=False)
b = a * 2
x = _detach_and_keep_grad(b)
assert x is b
assert x.stop_gradient == b.stop_gradient
assert (x.numpy() == b.numpy()).all()
x.stop_gradient = False
z = x * 3
z.backward()
assert x.grad is not None
assert a.grad is None
# 5. Test TensorMeta and _zero_initialize_with_meta
tensor = paddle.ones([4, 8])
dist_tensor = dist.shard_tensor(tensor, self.mesh, [dist.Shard(0)])
tensor_meta = TensorMeta(dist_tensor)
assert tensor_meta.shape == [4, 8]
assert tensor_meta._local_shape == [2, 8]
zero_tensor = _zero_initialize_with_meta(tensor_meta, self.mesh)
assert zero_tensor.shape == [4, 8]
assert zero_tensor.is_dist()
assert zero_tensor.process_mesh == self.mesh
assert zero_tensor.placements == [dist.Shard(0)]
# 6. Test _friendly_debug_info
a = {"test the input is not a tensor": 1}
assert _friendly_debug_info(a) == str(a)
def run_test(self):
"""Compare losses between three training methods"""
self.setUpClass()
self.test_simple_func_about_schedules()
self.test_backward_some_simple_examples()
self.test_utils_some_simple_examples()
# Run three training methods
pipeline_losses = self.test_PipelineStage()
pp_losses = self.test_pp_model()
single_losses = self.test_single_gpu()
if self.rank == 1:
np.testing.assert_allclose(
pipeline_losses,
pp_losses,
rtol=1e-5,
)
np.testing.assert_allclose(
pipeline_losses,
single_losses,
rtol=1e-5,
)
if __name__ == '__main__':
TestPipelineStage().run_test()
@@ -0,0 +1,51 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 paddle
import paddle.distributed as dist
class TestBackwardAutoParallel:
def init_data(self):
self.mesh = dist.ProcessMesh([0], dim_names=['d0'])
self.x = paddle.to_tensor([[1]])
self.y = paddle.to_tensor([[1]])
self.z = paddle.to_tensor([[1]])
self.x.stop_gradient = False
self.y.stop_gradient = False
self.z.stop_gradient = False
self.z = dist.shard_tensor(self.z, self.mesh, [dist.Replicate()])
def run_test_case1(self):
self.init_data()
o = self.x * self.y
o = o + self.z
o = o.sum()
o.backward()
def run_test_case2(self):
self.init_data()
o = self.x + self.y
o = o - self.z
o = o.sum()
o.backward()
# python -m paddle.distributed.launch --device=0 auto_parallel_backward.py
if __name__ == '__main__':
TestBackwardAutoParallel().run_test_case1()
TestBackwardAutoParallel().run_test_case2()
@@ -0,0 +1,59 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 unittest
import paddle
from paddle.distributed import fleet
from paddle.distributed.fleet.meta_optimizers.dygraph_optimizer.dygraph_sharding_optimizer import (
DygraphShardingOptimizerV2,
)
class TestClearParamStorage(unittest.TestCase):
def test_clear_param_storage(self):
class TestLayer(paddle.nn.Layer):
def __init__(self, dtype):
super().__init__()
self._w = self.create_parameter([2, 3], dtype=dtype)
self._b = self.create_parameter([2, 3], dtype=dtype)
self._w.color = {"color": "_w"}
self._b.color = {"color": "_b"}
@paddle.amp.debugging.check_layer_numerics
def forward(self, x):
return x * self._w + self._b
strategy = fleet.DistributedStrategy()
strategy.hybrid_configs = {
"dp_degree": 1,
"mp_degree": 1,
"pp_degree": 1,
"sharding_degree": 2,
}
fleet.init(is_collective=True, strategy=strategy)
hcg = fleet.get_hybrid_communicate_group()
dtype = 'float32'
model = TestLayer(dtype)
optimizer = paddle.optimizer.AdamW(parameters=model.parameters())
optimizer = DygraphShardingOptimizerV2(optimizer, hcg)
optimizer.clear_param_storage("_w")
optimizer.clear_param_storage("_b")
optimizer.clear_param_storage(None)
optimizer.reset_param_storage()
if __name__ == '__main__':
unittest.main()
+155
View File
@@ -0,0 +1,155 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 os
import random
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.distributed.auto_parallel.ring_attention import (
shard_seq_load_balance,
unshard_seq_load_balance,
)
dist.init_parallel_env()
class TestContextParallel:
def __init__(self):
self.rank = dist.get_rank()
self.world_size = dist.get_world_size()
self._sep_mesh = dist.ProcessMesh(
list(range(self.world_size)), dim_names=["sep"]
)
def set_seed(self, seed):
paddle.seed(seed)
np.random.seed(seed)
random.seed(seed)
def _test_cp_base(
self,
is_causal=True,
):
mesh = dist.ProcessMesh(list(range(self.world_size)), dim_names=['sep'])
dist.auto_parallel.set_mesh(mesh)
self.set_seed(1024)
bs = 2
seq_len = 256 # flash_attn seq_len/card > 128
dim = 16
nheads = 2
dtype = paddle.bfloat16
q = paddle.rand(
(bs, seq_len, nheads, dim),
dtype=dtype,
)
k = paddle.rand(
(bs, seq_len, nheads, dim),
dtype=dtype,
)
v = paddle.rand(
(bs, seq_len, nheads, dim),
dtype=dtype,
)
q.stop_gradient = False
k.stop_gradient = False
v.stop_gradient = False
with paddle.no_grad():
dist.broadcast(q, src=0)
dist.broadcast(k, src=0)
dist.broadcast(v, src=0)
# base compute
output_ref = paddle.nn.functional.scaled_dot_product_attention(
q, k, v, is_causal=is_causal
)
loss_ref = output_ref.mean()
loss_ref.backward()
cp_q = q.detach().clone()
cp_k = k.detach().clone()
cp_v = v.detach().clone()
placements = [dist.Replicate() for _ in range(len(mesh.dim_names))]
# shard compute
sharded_q = dist.shard_tensor(cp_q, mesh, placements)
sharded_k = dist.shard_tensor(cp_k, mesh, placements)
sharded_v = dist.shard_tensor(cp_v, mesh, placements)
sharded_q = shard_seq_load_balance(sharded_q, 1)
sharded_k = shard_seq_load_balance(sharded_k, 1)
sharded_v = shard_seq_load_balance(sharded_v, 1)
sharded_q.stop_gradient = False
sharded_k.stop_gradient = False
sharded_v.stop_gradient = False
output_sharded = paddle.nn.functional.scaled_dot_product_attention(
sharded_q, sharded_k, sharded_v, is_causal=is_causal, backend='p2p'
)
loss_sharded = paddle.mean(output_sharded)
loss_sharded.backward()
with paddle.no_grad():
reorder_t = unshard_seq_load_balance(output_sharded, 1)
np.testing.assert_allclose(
loss_ref.numpy(), loss_sharded.numpy(), rtol=5e-06, atol=5e-06
)
np.testing.assert_allclose(
output_ref.to("float32").numpy(),
reorder_t.to("float32").numpy(),
rtol=2e-01,
atol=6e-02,
)
with paddle.no_grad():
reorder_q_grad = unshard_seq_load_balance(sharded_q.grad, 1)
reorder_k_grad = unshard_seq_load_balance(sharded_k.grad, 1)
reorder_v_grad = unshard_seq_load_balance(sharded_v.grad, 1)
rtol = 3e-05
atol = 3e-05
np.testing.assert_allclose(
q.grad.to("float32").numpy(),
reorder_q_grad.to("float32").numpy(),
rtol=rtol,
atol=atol,
)
np.testing.assert_allclose(
k.grad.to("float32").numpy(),
reorder_k_grad.to("float32").numpy(),
rtol=rtol,
atol=atol,
)
np.testing.assert_allclose(
v.grad.to("float32").numpy(),
reorder_v_grad.to("float32").numpy(),
rtol=rtol,
atol=atol,
)
def run_test_cases(self):
# flash attention is not supported yet for cpu
if os.getenv("backend") == "gpu":
cuda_version_main = int(paddle.version.cuda().split(".")[0])
device_prop_main = paddle.device.cuda.get_device_capability()[0]
if cuda_version_main >= 11 and device_prop_main >= 8:
self._test_cp_base()
self._test_cp_base(is_causal=False)
if __name__ == '__main__':
tester = TestContextParallel()
tester.run_test_cases()
+104
View File
@@ -0,0 +1,104 @@
# Copyright (c) 2022 PaddlePaddle Authors. 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 numpy as np
import paddle
from paddle.distributed.auto_parallel.static.converter import Converter
def test_convert():
rank_id = paddle.distributed.get_rank()
complete_tensor = np.arange(64).reshape([8, 8])
tensor_row = np.split(complete_tensor, 2, axis=0)
tensor_col = np.split(complete_tensor, 2, axis=1)
tensor_name = "tensor_0"
complete_strategy = {
tensor_name: {
"process_shape": [2],
"process_group": [0, 1],
"dims_mapping": [-1, -1],
}
}
row_strategy = {
tensor_name: {
"process_shape": [2],
"process_group": [0, 1],
"dims_mapping": [0, -1],
}
}
col_strategy = {
tensor_name: {
"process_shape": [2],
"process_group": [0, 1],
"dims_mapping": [-1, 0],
}
}
# test merge
tensor_dict = {tensor_name: tensor_row}
converter = Converter(tensor_dict, row_strategy, complete_strategy)
convert_tensor_dict = converter.convert()
assert np.equal(convert_tensor_dict[tensor_name], complete_tensor).all()
# test slice
tensor_dict = {tensor_name: [complete_tensor]}
converter = Converter(tensor_dict, complete_strategy, col_strategy)
convert_tensor_dict = converter.convert()
assert np.equal(convert_tensor_dict[tensor_name], tensor_col[rank_id]).all()
# test merge and slice
tensor_dict = {tensor_name: tensor_col}
converter = Converter(tensor_dict, col_strategy, row_strategy)
convert_tensor_dict = converter.convert()
assert np.equal(convert_tensor_dict[tensor_name], tensor_row[rank_id]).all()
# test merge and slice with prefix match
new_name = "tensor_1"
row_strategy = {
new_name: {
"process_shape": [2],
"process_group": [0, 1],
"dims_mapping": [0, -1],
}
}
converter = Converter(tensor_dict, col_strategy, row_strategy)
convert_tensor_dict = converter.convert(strict=False)
assert np.equal(convert_tensor_dict[new_name], tensor_row[rank_id]).all()
# test sliced_shape is 1
complete_tensor = np.arange(4).reshape([2, 2])
tensor_row = np.split(complete_tensor, 2, axis=0)
complete_strategy = {
"tensor_2": {
"process_shape": [2],
"process_group": [0, 1],
"dims_mapping": [-1, -1],
}
}
row_strategy = {
"tensor_2": {
"process_shape": [2],
"process_group": [0, 1],
"dims_mapping": [0, -1],
}
}
tensor_dict = {"tensor_2": [complete_tensor]}
converter = Converter(tensor_dict, complete_strategy, row_strategy)
convert_tensor_dict = converter.convert()
assert np.equal(convert_tensor_dict["tensor_2"], tensor_row[rank_id]).all()
if __name__ == "__main__":
test_convert()
@@ -0,0 +1,16 @@
set(LOCAL_ALL_ARCH ON)
set(LOCAL_ALL_PLAT ON)
if(WITH_DISTRIBUTE
AND WITH_GPU
AND (LINUX))
py_test_modules(
test_semi_auto_parallel_custom_op
MODULES
test_semi_auto_parallel_custom_op
ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python;PADDLE_SOURCE_DIR=${PROJECT_SOURCE_DIR};WITH_ONEDNN=${WITH_ONEDNN};ONEDNN_INSTALL_DIR=${ONEDNN_INSTALL_DIR};WITH_ONEDNN=${WITH_ONEDNN};WITH_GPU=${WITH_GPU};WITH_ROCM=${WITH_ROCM};externalError_INCLUDE_DIR=${externalError_INCLUDE_DIR};PYBIND_INCLUDE_DIR=${PYBIND_INCLUDE_DIR}"
)
set_tests_properties(test_semi_auto_parallel_custom_op
PROPERTIES LABELS "RUN_TYPE=EXCLUSIVE" TIMEOUT 120)
endif()
@@ -0,0 +1,138 @@
// Copyright (c) 2023 PaddlePaddle Authors. 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.
#include <iostream>
#include <vector>
#include "paddle/extension.h"
#include "paddle/phi/api/ext/spmd_infer.h"
#include "paddle/phi/infermeta/spmd_rules/rules.h"
#define CHECK_CPU_INPUT(x) \
PADDLE_ENFORCE_EQ( \
x.is_cpu(), true, common::errors::Fatal(#x " must be a CPU Tensor."))
template <typename data_t>
void relu_cpu_forward_kernel(const data_t* x_data,
data_t* out_data,
int64_t x_numel) {
PADDLE_ENFORCE_NE(
x_data, nullptr, common::errors::Fatal("x_data is nullptr."));
PADDLE_ENFORCE_NE(
out_data, nullptr, common::errors::Fatal("out_data is nullptr."));
for (int64_t i = 0; i < x_numel; ++i) {
out_data[i] = std::max(static_cast<data_t>(0.), x_data[i]);
}
}
template <typename data_t>
void relu_cpu_backward_kernel(const data_t* grad_out_data,
const data_t* out_data,
data_t* grad_x_data,
int64_t out_numel) {
for (int64_t i = 0; i < out_numel; ++i) {
grad_x_data[i] =
grad_out_data[i] * (out_data[i] > static_cast<data_t>(0) ? 1. : 0.);
}
}
std::vector<paddle::Tensor> relu_cpu_forward(const paddle::Tensor& x) {
auto out = paddle::empty_like(x);
PD_DISPATCH_FLOATING_TYPES(
x.type(), "relu_cpu_forward", ([&] {
relu_cpu_forward_kernel<data_t>(
x.data<data_t>(), out.data<data_t>(), x.numel());
}));
return {out};
}
std::vector<paddle::Tensor> relu_cpu_backward(const paddle::Tensor& x,
const paddle::Tensor& out,
const paddle::Tensor& grad_out) {
auto grad_x = paddle::empty_like(x);
PD_DISPATCH_FLOATING_TYPES(out.type(), "relu_cpu_backward", ([&] {
relu_cpu_backward_kernel<data_t>(
grad_out.data<data_t>(),
out.data<data_t>(),
grad_x.data<data_t>(),
out.size());
}));
return {grad_x};
}
std::vector<paddle::Tensor> relu_cuda_forward(const paddle::Tensor& x);
std::vector<paddle::Tensor> relu_cuda_backward(const paddle::Tensor& x,
const paddle::Tensor& out,
const paddle::Tensor& grad_out);
std::vector<paddle::Tensor> ReluForward(const paddle::Tensor& x) {
if (x.is_cpu()) {
return relu_cpu_forward(x);
} else if (x.is_gpu()) {
return relu_cuda_forward(x);
} else {
PD_THROW("Not implemented.");
}
}
std::vector<paddle::Tensor> ReluBackward(const paddle::Tensor& x,
const paddle::Tensor& out,
const paddle::Tensor& grad_out) {
if (x.is_cpu()) {
return relu_cpu_backward(x, out, grad_out);
} else if (x.is_gpu()) {
return relu_cuda_backward(x, out, grad_out);
} else {
PD_THROW("Not implemented.");
}
}
phi::distributed::SpmdInfo ReluGradInferSpmd(
const phi::distributed::DistMetaTensor& x,
const phi::distributed::DistMetaTensor& out,
const phi::distributed::DistMetaTensor& out_grad) {
return phi::distributed::ElementwiseUnaryGradInferSpmd(x, out, out_grad);
}
PD_BUILD_OP(custom_relu)
.Inputs({"X"})
.Outputs({"Out"})
.SetKernelFn(PD_KERNEL(ReluForward))
.SetInferSpmdFn(
PD_INFER_SPMD_RULE(phi::distributed::ElementwiseUnaryInferSpmd));
PD_BUILD_GRAD_OP(custom_relu)
.Inputs({"X", "Out", paddle::Grad("Out")})
.Outputs({paddle::Grad("X")})
.SetKernelFn(PD_KERNEL(ReluBackward))
.SetInferSpmdFn(PD_INFER_SPMD_RULE(ReluGradInferSpmd));
PD_BUILD_OP(custom_relu_no_spmd)
.Inputs({"X"})
.Outputs({"Out"})
.SetKernelFn(PD_KERNEL(ReluForward));
PD_BUILD_GRAD_OP(custom_relu_no_spmd)
.Inputs({"X", "Out", paddle::Grad("Out")})
.Outputs({paddle::Grad("X")})
.SetKernelFn(PD_KERNEL(ReluBackward));
PD_REGISTER_SPMD_RULE(
custom_relu,
PD_INFER_SPMD(phi::distributed::ElementwiseUnaryInferSpmd),
PD_INFER_SPMD(phi::distributed::ElementwiseUnaryInferSpmdReverse));
@@ -0,0 +1,93 @@
// Copyright (c) 2023 PaddlePaddle Authors. 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.
#include "paddle/extension.h"
#define CHECK_GPU_INPUT(x) \
PADDLE_ENFORCE_EQ( \
x.is_gpu(), \
true, \
common::errors::InvalidArgument("Input tensor `x` must be a" \
"GPU Tensor."));
template <typename data_t>
__global__ void relu_cuda_forward_kernel(const data_t* x,
data_t* y,
int64_t num) {
int64_t gid = blockIdx.x * blockDim.x + threadIdx.x;
for (int64_t i = gid; i < num; i += blockDim.x * gridDim.x) {
y[i] = x[i] > static_cast<data_t>(0.) ? x[i] : static_cast<data_t>(0.);
}
}
template <typename data_t>
__global__ void relu_cuda_backward_kernel(const data_t* dy,
const data_t* y,
data_t* dx,
int64_t num) {
int64_t gid = blockIdx.x * blockDim.x + threadIdx.x;
for (int64_t i = gid; i < num; i += blockDim.x * gridDim.x) {
dx[i] = dy[i] * (y[i] > static_cast<data_t>(0.) ? static_cast<data_t>(1.)
: static_cast<data_t>(0.));
}
}
std::vector<paddle::Tensor> relu_cuda_forward(const paddle::Tensor& x) {
CHECK_GPU_INPUT(x);
auto out = paddle::empty_like(x);
PADDLE_ENFORCE_EQ(x.is_gpu(),
true,
common::errors::InvalidArgument(
"Input tensor `x` must be a GPU Tensor."));
int64_t numel = x.numel();
int64_t block = 512;
int64_t grid = (numel + block - 1) / block;
PD_DISPATCH_FLOATING_AND_HALF_TYPES(
x.type(), "relu_cuda_forward_kernel", ([&] {
relu_cuda_forward_kernel<data_t><<<grid, block, 0, x.stream()>>>(
x.data<data_t>(), out.data<data_t>(), numel);
}));
return {out};
}
std::vector<paddle::Tensor> relu_cuda_backward(const paddle::Tensor& x,
const paddle::Tensor& out,
const paddle::Tensor& grad_out) {
CHECK_GPU_INPUT(x);
CHECK_GPU_INPUT(out);
CHECK_GPU_INPUT(grad_out);
auto grad_x = paddle::empty_like(x);
PADDLE_ENFORCE_EQ(x.is_gpu(),
true,
common::errors::InvalidArgument(
"Input tensor `x` must be a GPU Tensor."));
int64_t numel = out.numel();
int64_t block = 512;
int64_t grid = (numel + block - 1) / block;
PD_DISPATCH_FLOATING_AND_HALF_TYPES(
out.type(), "relu_cuda_backward_kernel", ([&] {
relu_cuda_backward_kernel<data_t><<<grid, block, 0, x.stream()>>>(
grad_out.data<data_t>(),
out.data<data_t>(),
grad_x.mutable_data<data_t>(x.place()),
numel);
}));
return {grad_x};
}
@@ -0,0 +1,31 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 utils import extra_compile_args, paddle_includes
from paddle.utils.cpp_extension import CUDAExtension, setup
# Mac-CI don't support GPU
Extension = CUDAExtension
sources = ['custom_relu_op.cc', 'custom_relu_op.cu']
setup(
name='custom_relu',
ext_modules=Extension(
sources=sources,
include_dirs=paddle_includes,
extra_compile_args=extra_compile_args,
verbose=True,
),
)
@@ -0,0 +1,126 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import sys
import unittest
__dir__ = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.abspath(os.path.join(__dir__, '..')))
from semi_auto_parallel_util import SemiAutoParallelTestBase
import paddle
import paddle.distributed as dist
from paddle.distributed.auto_parallel.static.mix_to_dist_pass import (
apply_mix2dist_pass,
)
from paddle.framework import core
import custom_relu # pylint: disable=unused-import # isort:skip
assert core.contains_spmd_rule("custom_relu")
class TestCustomOpSemiAutoParallel(SemiAutoParallelTestBase):
def __init__(self):
super().__init__()
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
def check_placements(self, output, expected_placements):
assert output.placements == expected_placements, (
f"{output.placements} vs {expected_placements}"
)
def test_custom_relu(self):
shapes = [16, 4, 4]
specs = ['x', None, None]
inputs, outputs = self.runfunc_and_check(
inputs_shape=shapes,
inputs_specs=specs,
op_func=custom_relu.custom_relu,
with_backward=True,
)
self.check_placements(outputs, [dist.Shard(0)])
def test_custom_relu_no_spmd(self):
shapes = [16, 4, 4]
specs = ['x', None, None]
inputs, outputs = self.runfunc_and_check(
inputs_shape=shapes,
inputs_specs=specs,
op_func=custom_relu.custom_relu_no_spmd,
with_backward=True,
)
self.check_placements(outputs, [dist.Replicate()])
def test_custom_relu_no_shard(self):
shapes = [16, 4, 4]
specs = [None, None, None]
inputs, outputs = self.runfunc_and_check(
inputs_shape=shapes,
inputs_specs=specs,
op_func=custom_relu.custom_relu,
with_backward=True,
)
self.check_placements(outputs, [dist.Replicate()])
def run_test_case(self):
if self._backend == "cpu":
paddle.set_device("cpu")
elif self._backend == "gpu":
paddle.set_device("gpu:" + str(dist.get_rank()))
else:
raise ValueError("Only support cpu or gpu backend.")
self.test_custom_relu_no_shard()
self.test_custom_relu()
self.test_custom_relu_no_spmd()
class TestBuildFakeProgramWithCustomOp(unittest.TestCase):
def test_build_with_custom_relu(self):
shapes = [16, 4, 4]
paddle.enable_static()
with paddle.pir_utils.IrGuard():
main_program = paddle.base.Program()
with paddle.base.program_guard(main_program):
mesh = dist.ProcessMesh([0, 1], dim_names=['mp'])
input = paddle.static.data(
name='input',
shape=shapes,
)
dist_input = dist.shard_tensor(input, mesh, [dist.Shard(0)])
dist_out = custom_relu.custom_relu(dist_input)
apply_mix2dist_pass(main_program)
self.assertTrue(dist_out.is_dist_dense_tensor_type())
self.assertEqual(dist_out._local_shape, [16 // 2, 4, 4])
self.assertEqual(dist_out.dist_attr().dims_mapping, [0, -1, -1])
self.assertEqual(dist_out.dist_attr().process_mesh, mesh)
op_dist_attr = dist_out.get_defining_op().dist_attr
self.assertEqual(op_dist_attr.process_mesh, mesh)
self.assertEqual(
op_dist_attr.result(0).as_tensor_dist_attr().dims_mapping,
[0, -1, -1],
)
self.assertEqual(
op_dist_attr.operand(0).as_tensor_dist_attr().dims_mapping,
[0, -1, -1],
)
if __name__ == '__main__':
TestCustomOpSemiAutoParallel().run_test_case()
TestBuildFakeProgramWithCustomOp().test_build_with_custom_relu()
@@ -0,0 +1,52 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import site
import sys
import unittest
import collective.test_communication_api_base as test_base
from paddle.utils.cpp_extension.extension_utils import run_cmd
class TestCustomOp(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=2, timeout=200, nnode=1)
self._default_envs = {"dtype": "float32", "seed": "2023"}
self._changeable_envs = {"backend": ["cpu", "gpu"]}
cur_dir = os.path.dirname(os.path.abspath(__file__))
# compile, install the custom op egg into site-packages under background
if os.name == 'nt':
cmd = f'cd /d {cur_dir} && python custom_relu_setup.py install'
else:
site_dir = site.getsitepackages()[0]
cmd = f'cd {cur_dir} && {sys.executable} custom_relu_setup.py install --install-lib={site_dir}'
run_cmd(cmd)
# test dynamic auto parallel run
def test_dynamic_auto_parallel(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_for_custom_op.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
+50
View File
@@ -0,0 +1,50 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
from pathlib import Path
from site import getsitepackages
from paddle.utils.cpp_extension.extension_utils import (
_get_all_paddle_includes_from_include_root,
)
# Test for extra compile args
extra_cc_args = ['-w', '-g']
extra_nvcc_args = ['-O3']
extra_compile_args = {'cc': extra_cc_args, 'nvcc': extra_nvcc_args}
def get_paddle_includes():
env_dict = os.environ
paddle_includes = []
paddle_includes.append(f"{env_dict.get('PADDLE_SOURCE_DIR')}")
# onednn
if env_dict.get("WITH_ONEDNN") == 'ON':
paddle_includes.append(f"{env_dict.get('ONEDNN_INSTALL_DIR')}/include")
if env_dict.get("WITH_GPU") == 'ON' or env_dict.get("WITH_ROCM") == 'ON':
paddle_includes.append(f"{env_dict.get('externalError_INCLUDE_DIR')}")
paddle_includes.append(f"{env_dict.get('PYBIND_INCLUDE_DIR')}")
for site_packages_path in getsitepackages():
paddle_include_dir = Path(site_packages_path) / "paddle/include"
paddle_includes.extend(
_get_all_paddle_includes_from_include_root(str(paddle_include_dir))
)
return paddle_includes
paddle_includes = get_paddle_includes()
@@ -0,0 +1,77 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 os
import paddle
import paddle.distributed as dist
from paddle.distributed.auto_parallel.api import dtensor_from_local
class TestDtensorFromLocalAPI:
def __init__(self):
self._dtype = os.getenv("dtype")
self._seeds = eval(os.getenv("seeds"))
self._backend = os.getenv("backend")
self._shard = eval(os.getenv("shard"))
self._mesh = dist.ProcessMesh([[0, 1]], dim_names=["x", "y"])
def run_test_cases(self):
self.test_case_forward_backward()
def test_case_forward_backward(self):
a = paddle.ones([10, 20])
a.stop_gradient = False
tensor1 = a + 3
assert not tensor1.is_dist()
tensor1.register_hook(self.check_grad_mesh(None, None))
mesh = self._mesh
tensor2 = dtensor_from_local(
tensor1, mesh, [dist.Shard(0), dist.Replicate()]
)
assert tensor2.is_dist()
assert tensor2.process_mesh == mesh
assert tensor2.placements == [dist.Shard(0), dist.Replicate()]
tensor2.register_hook(
self.check_grad_mesh(mesh, [dist.Shard(0), dist.Replicate()])
)
tensor3 = tensor2 * 3
tensor3.register_hook(
self.check_grad_mesh(mesh, [dist.Shard(0), dist.Replicate()])
)
tensor4 = tensor3 + 4
tensor4.backward()
def check_grad_mesh(self, mesh, placements):
def _check_mesh(grad):
if mesh is None and placements is None:
assert not grad.is_dist(), "grad.is_dist() is not False"
else:
assert grad.process_mesh == mesh, (
"grad.process_mesh is not equal to mesh"
)
assert grad.placements == placements, (
"grad.placements is not equal to placements"
)
return _check_mesh
if __name__ == '__main__':
TestDtensorFromLocalAPI().run_test_cases()
@@ -0,0 +1,68 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 os
import paddle
import paddle.distributed as dist
from paddle.distributed import Partial
from paddle.distributed.auto_parallel.api import dtensor_to_local
class TestDtensorToLocalAPI:
def __init__(self):
self._shape = eval(os.getenv("shape"))
self._dtype = os.getenv("dtype")
self._seeds = eval(os.getenv("seeds"))
self._backend = os.getenv("backend")
self._shard = eval(os.getenv("shard"))
self._mesh = dist.ProcessMesh([0, 1], dim_names=["x"])
def run_test_cases(self):
self.test_case_forward_backward()
def test_case_forward_backward(self):
a = paddle.ones(self._shape)
a.stop_gradient = False
input_tensor = dist.shard_tensor(a, self._mesh, [Partial()])
input_tensor.register_hook(
self.check_grad_mesh(
input_tensor.process_mesh, input_tensor.placements
)
)
tensor1 = dtensor_to_local(
input_tensor, input_tensor.process_mesh, input_tensor.placements
)
assert not tensor1.is_dist()
tensor2 = tensor1 + 2
tensor3 = tensor2 * 3
tensor3.register_hook(self.check_grad_mesh(None, None))
tensor3.backward()
def check_grad_mesh(self, org_mesh, org_placements):
def _check_mesh(grad):
if hasattr(grad, "process_mesh") and hasattr(grad, "placements"):
assert grad.process_mesh == org_mesh
assert grad.placements == org_placements
else:
assert org_mesh is None and org_placements is None
return _check_mesh
if __name__ == '__main__':
TestDtensorToLocalAPI().run_test_cases()
@@ -0,0 +1,97 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 paddle
import paddle.distributed as dist
from paddle.distributed.auto_parallel.api import (
dtensor_from_local,
dtensor_to_local,
)
class TestLocalViewCompute:
def __init__(self):
self._mesh = dist.ProcessMesh([0, 1], dim_names=["x"])
def run_test_cases(self):
self.test_local_view_compute()
def masked_lm_loss_func(self, pred, label, ignored_idx=-100):
pred_sub = pred[:, 0:1] # shape [B,1]
label_float = paddle.cast(label, 'float32') # shape [B,1]
raw_loss = paddle.abs(pred_sub - label_float)
lossmask = label != ignored_idx
lossmask_ = lossmask.reshape([-1]).cast('float32')
raw_loss_flat = raw_loss.reshape([-1]).cast('float32')
masked_lm_loss_sum = paddle.sum(raw_loss_flat * lossmask_)
valid_count = paddle.sum(lossmask_)
loss = masked_lm_loss_sum / (valid_count + 1e-8)
return loss
def local_view_compute(self, local_pred, local_label):
# do not use dist.shard_tensor here
local_pred = local_pred + 1
local_loss = self.masked_lm_loss_func(
local_pred, local_label, ignored_idx=-100
)
return local_loss
def test_local_view_compute(self):
dist.init_parallel_env()
cur_rank = dist.get_rank()
# prepare data and label for mask_lm_loss
if cur_rank == 0:
pred = paddle.to_tensor([[1.0, 2.0], [4.0, 4.0]], dtype='float32')
label = paddle.to_tensor([[1], [3]], dtype='int64')
elif cur_rank == 1:
pred = paddle.to_tensor([[2.0, 2.0], [7.0, 8.0]], dtype='float32')
label = paddle.to_tensor([[2], [-100]], dtype='int64')
local_result = self.local_view_compute(pred.clone(), label.clone())
dist_pred = dist.shard_tensor(pred, self._mesh, [dist.Replicate()])
dist_label = dist.shard_tensor(label, self._mesh, [dist.Replicate()])
local_pred = dtensor_to_local(
dist_pred, dist_pred.process_mesh, dist_pred.placements
)
local_label = dtensor_to_local(
dist_label, dist_label.process_mesh, dist_label.placements
)
local_pred = local_pred + 1
local_loss = self.masked_lm_loss_func(
local_pred, local_label, ignored_idx=-100
)
assert local_result == local_loss, "local_result != local_loss"
tensor_list = []
dist.all_gather(tensor_list, local_loss)
loss_sum = paddle.sum(paddle.stack(tensor_list))
dist_loss = dtensor_from_local(
local_loss, self._mesh, [dist.Partial(dist.ReduceType.kRedSum)]
)
assert loss_sum == dist_loss, "loss_sum != dist_loss"
if __name__ == '__main__':
TestLocalViewCompute().run_test_cases()
@@ -0,0 +1,20 @@
# This file is generated by ${PADDLE_ROOT}/tools/gen_ut_cmakelists.py.
# Please don't modify this file manually.
# If you need to change unittests in this file, please modify testslist.csv in the current directory
# and then run the command `python3 ${PADDLE_ROOT}/tools/gen_ut_cmakelists.py -f ${CURRENT_DIRECTORY}/testslist.csv`
set(LOCAL_ALL_ARCH ON)
set(LOCAL_ALL_PLAT ON)
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_e2e_co_shard_8cards MODULES test_e2e_co_shard_8cards ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_e2e_co_shard_8cards PROPERTIES TIMEOUT "120" LABELS
"RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_e2e_co_shard MODULES test_e2e_co_shard ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_e2e_co_shard PROPERTIES TIMEOUT "120" LABELS
"RUN_TYPE=HYBRID")
endif()
@@ -0,0 +1,258 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 __future__ import annotations
from typing import TYPE_CHECKING, Any
import numpy as np
import paddle
import paddle.distributed as dist
if TYPE_CHECKING:
from collections.abc import Callable
class ArgSortTestCase:
def __init__(
self,
input_shape: list[int],
input_placements: list[dist.Placement],
axis: int,
indices_placements: list[dist.Placement],
slice_funtor: Callable[[int], Any] | None = None,
):
self.input_shape = input_shape
self.input_placements = input_placements
self.axis = axis
self.indices_placements = indices_placements
self.slice_funtor = slice_funtor
self.descending = False
self.stable = False
class ArgSortGradTestCase:
def __init__(
self,
input_shape: list[int],
x_placements: list[dist.Placement],
axis: int,
out_grad_placements: list[dist.Placement],
x_grad_placements: list[dist.Placement],
):
self.input_shape = input_shape
self.x_placements = x_placements
self.out_grad_placements = out_grad_placements
self.axis = axis
self.x_grad_placements = x_grad_placements
self.descending = False
self.stable = False
class TestArgSortCoShard:
def setUp(self):
self.mesh = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=['x', 'y', 'z']
)
self.test_cases_forward = [
# test flatten
ArgSortTestCase(
[16, 32, 48],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
-1,
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
),
ArgSortTestCase(
[16, 32, 48],
[
dist.Shard(
0,
),
dist.Shard(2, shard_order=0),
dist.Shard(2, shard_order=1),
],
2,
[
dist.Shard(
0,
),
dist.Replicate(),
dist.Replicate(),
],
),
ArgSortTestCase(
[10, 32, 48, 24],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
dist.Replicate(),
],
1,
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
dist.Replicate(),
],
),
]
self.test_cases_backward = [
# test flatten
ArgSortGradTestCase(
[16, 32, 48],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
-1,
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
),
ArgSortGradTestCase(
[16, 32, 48],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(2),
],
2,
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(2),
],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
),
ArgSortGradTestCase(
[10, 32, 48, 24],
[
dist.Shard(0),
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
dist.Replicate(),
],
1,
[
dist.Shard(0),
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
dist.Replicate(),
],
[
dist.Shard(0),
dist.Replicate(),
dist.Replicate(),
dist.Replicate(),
],
),
]
def run_test_case_forward(self, test_case: ArgSortTestCase):
a = paddle.rand(test_case.input_shape, "float32")
input_placements = test_case.input_placements
input = dist.shard_tensor(a, self.mesh, input_placements)
out = paddle.argsort(
input, test_case.axis, test_case.descending, test_case.stable
)
case_info = f"input_shape: {test_case.input_shape}, input_placements: {input_placements}, axis: {test_case.axis}"
# Verify output shape
np.testing.assert_equal(
out.shape,
test_case.input_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.input_shape}, Actual: {out.shape}",
)
# Verify placements
assert out.placements
for actual, expected in zip(
out.placements, test_case.indices_placements
):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.indices_placements}, Actual: {out.placements}",
)
# Verify local_value if given
if test_case.slice_funtor:
idx = dist.get_rank()
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[test_case.slice_funtor(idx)].numpy().flatten(),
err_msg=f"Local values mismatch when {case_info}.",
)
def run_test_case_backward(self, test_case: ArgSortGradTestCase):
a = paddle.rand(test_case.input_shape, "float32")
a.stop_gradient = False
input = dist.shard_tensor(a, self.mesh, test_case.x_placements)
out = paddle.argsort(
input, test_case.axis, test_case.descending, test_case.stable
)
out_grad = paddle.ones(out.shape, "float32")
out_grad = dist.shard_tensor(
out_grad, self.mesh, test_case.out_grad_placements
)
(x_grad,) = paddle.grad([out], input, [out_grad])
case_info = f"input_shape: {test_case.input_shape}, axis: {test_case.axis}, x_placements: {test_case.x_placements}, out_grad_placements: {test_case.out_grad_placements}"
# Verify output shape
np.testing.assert_equal(
x_grad.shape,
test_case.input_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.input_shape}, Actual: {x_grad.shape}",
)
# Verify placements
assert x_grad.placements
for actual, expected in zip(
x_grad.placements, test_case.x_grad_placements
):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.x_grad_placements}, Actual: {x_grad.placements}",
)
def run_all_tests(self):
self.setUp()
for test_case in self.test_cases_forward:
self.run_test_case_forward(test_case)
if __name__ == '__main__':
TestArgSortCoShard().run_all_tests()
+207
View File
@@ -0,0 +1,207 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 numpy as np
import paddle
import paddle.distributed as dist
class TestCoShard:
def basic_interface_case(self):
shard = dist.Shard(0, shard_order=0)
np.testing.assert_equal(shard, dist.Shard(dim=0, shard_order=0))
shard = dist.Shard(0, split_factor=2)
np.testing.assert_equal(shard, dist.Shard(dim=0, split_factor=2))
def run_test_case_0(self):
a = paddle.to_tensor([[1, 2], [3, 4], [5, 6], [7, 8]])
mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['x', 'y'])
placements = [
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
]
input = dist.shard_tensor(a, mesh, placements)
idx = dist.get_rank()
np.testing.assert_equal(
input._local_value().numpy().flatten(), a[idx].numpy().flatten()
)
reshard_placements = [dist.Replicate(), dist.Replicate()]
out = dist.reshard(input, mesh, reshard_placements)
np.testing.assert_equal(
out._local_value().numpy().flatten(), a.numpy().flatten()
)
reshard_placements = [dist.Shard(0), dist.Replicate()]
out = dist.reshard(input, mesh, reshard_placements)
new_idx = idx // 2 * 2
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[new_idx : new_idx + 2].numpy().flatten(),
)
reshard_placements = [dist.Replicate(), dist.Shard(0)]
out = dist.reshard(input, mesh, reshard_placements)
new_idx = idx % 2 * 2
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[new_idx : new_idx + 2].numpy().flatten(),
)
def run_test_case_1(self):
a = paddle.to_tensor([[1, 2], [3, 4], [5, 6], [7, 8]])
mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['x', 'y'])
placements = [
dist.Shard(0, shard_order=1),
dist.Shard(0, shard_order=0),
]
input = dist.shard_tensor(a, mesh, placements)
idx = dist.get_rank()
new_idx = idx % 2 * 2 + idx // 2
np.testing.assert_equal(
input._local_value().numpy().flatten(), a[new_idx].numpy().flatten()
)
reshard_placements = [dist.Replicate(), dist.Replicate()]
out = dist.reshard(input, mesh, reshard_placements)
np.testing.assert_equal(
out._local_value().numpy().flatten(), a.numpy().flatten()
)
reshard_placements = [dist.Shard(0), dist.Replicate()]
out = dist.reshard(input, mesh, reshard_placements)
new_idx = idx // 2 * 2
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[new_idx : new_idx + 2].numpy().flatten(),
)
reshard_placements = [dist.Replicate(), dist.Shard(0)]
out = dist.reshard(input, mesh, reshard_placements)
new_idx = idx % 2 * 2
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[new_idx : new_idx + 2].numpy().flatten(),
)
def run_test_case_2(self):
mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['x', 'y'])
# dense tensor
a = paddle.to_tensor([[1, 2], [3, 4], [5, 6], [7, 8]])
placements = [dist.Shard(0, split_factor=2), dist.Replicate()]
# distributed tensor
input = dist.shard_tensor(a, mesh, placements)
idx = dist.get_rank()
if idx == 0 or idx == 1:
golden = np.array([[1, 2], [5, 6]])
else:
golden = np.array([[3, 4], [7, 8]])
np.testing.assert_equal(
input._local_value().numpy().flatten(), golden.flatten()
)
reshard_placements = [dist.Replicate(), dist.Replicate()]
out = dist.reshard(input, mesh, reshard_placements)
np.testing.assert_equal(
out._local_value().numpy().flatten(), a.numpy().flatten()
)
reshard_placements = [dist.Shard(0), dist.Replicate()]
out = dist.reshard(input, mesh, reshard_placements)
new_idx = idx // 2 * 2
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[new_idx : new_idx + 2].numpy().flatten(),
)
reshard_placements = [dist.Replicate(), dist.Shard(0)]
out = dist.reshard(input, mesh, reshard_placements)
new_idx = idx % 2 * 2
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[new_idx : new_idx + 2].numpy().flatten(),
)
def run_test_case_3(self):
a = paddle.to_tensor([[1, 2], [3, 4], [5, 6], [7, 8]])
mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['x', 'y'])
placements = [dist.Shard(0), dist.Shard(1)]
input = dist.shard_tensor(a, mesh, placements)
reshard_placements = [
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
]
out = dist.reshard(input, mesh, reshard_placements)
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[dist.get_rank()].numpy().flatten(),
)
np.testing.assert_equal(
out.placements[0], dist.Shard(dim=0, shard_order=0)
)
np.testing.assert_equal(
out.placements[1], dist.Shard(dim=0, shard_order=1)
)
def run_test_case_4(self):
a = paddle.to_tensor([[1, 2], [3, 4], [5, 6], [7, 8]], dtype='float32')
mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['x', 'y'])
placements = [dist.Shard(0), dist.Shard(1)]
input = dist.shard_tensor(a, mesh, placements)
out = paddle.reshape(input, [-1])
np.testing.assert_equal(out.shape, [8])
np.testing.assert_equal(
out.placements[0], dist.Shard(dim=0, shard_order=0)
)
np.testing.assert_equal(
out.placements[1], dist.Shard(dim=0, shard_order=1)
)
np.testing.assert_equal(
out._local_value().numpy(), a[dist.get_rank()].numpy().flatten()
)
relu_out = paddle.nn.ReLU()(out)
np.testing.assert_equal(
relu_out.placements[0], dist.Shard(dim=0, shard_order=0)
)
np.testing.assert_equal(
relu_out.placements[1], dist.Shard(dim=0, shard_order=1)
)
# test fallback to shard by one dim.
add_out = paddle.add(relu_out, relu_out)
np.testing.assert_equal(add_out.placements[0], dist.Shard(dim=0))
np.testing.assert_equal(add_out.placements[1], dist.Replicate())
def run_test_case_main(self):
self.basic_interface_case()
self.run_test_case_0()
self.run_test_case_1()
self.run_test_case_2()
self.run_test_case_3()
self.run_test_case_4()
if __name__ == '__main__':
TestCoShard().run_test_case_main()
@@ -0,0 +1,79 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 numpy as np
import paddle
import paddle.distributed as dist
class TestElementWiseCoShard:
def run_unary_case_0(self):
mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['x', 'y'])
placements = [
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
]
x = paddle.to_tensor(
[[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0]], dtype="float32"
)
x = dist.shard_tensor(x, mesh, placements)
# paddle.round
out = paddle.round(x)
np.testing.assert_equal(out.shape, [4, 2])
assert out.placements, "The output should be a DistTensor"
np.testing.assert_equal(
out.placements[0], dist.Shard(dim=0, shard_order=0)
)
np.testing.assert_equal(
out.placements[1], dist.Shard(dim=0, shard_order=1)
)
def run_unary_case_with_partial(self):
mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['x', 'y'])
# TODO(ooooo): Test co_shard when matmul is supported.
x_placements = [
dist.Shard(0),
dist.Shard(1),
]
x = paddle.to_tensor(
[[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]], dtype="float32"
)
y = paddle.to_tensor(
[[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0]], dtype="float32"
)
x = dist.shard_tensor(x, mesh, x_placements)
y = dist.shard_tensor(
y, mesh, [dist.Replicate() for _ in range(mesh.ndim)]
)
# Generate partial placement
matmul_out = paddle.matmul(x, y)
# paddle.cast
out = paddle.cast(matmul_out, 'float64')
np.testing.assert_equal(out.shape, [2, 2])
assert out.placements, "The output should be a DistTensor"
np.testing.assert_equal(out.placements[0], dist.Shard(0))
np.testing.assert_equal(out.placements[1], dist.Partial())
def run_test_case_main(self):
self.run_unary_case_0()
self.run_unary_case_with_partial()
if __name__ == '__main__':
TestElementWiseCoShard().run_test_case_main()
@@ -0,0 +1,334 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 __future__ import annotations
import numpy as np
import paddle
import paddle.distributed as dist
class IndexSelectTestCase:
def __init__(
self,
x_shape: list[int],
x_placements: list[dist.Placement],
index_shape: list[int],
index_placements: list[dist.Placement],
axis: int,
out_shape: list[int],
out_placements: list[dist.Placement],
):
self.x_shape = x_shape
self.x_placements = x_placements
self.index_shape = index_shape
self.index_placements = index_placements
self.axis = axis
self.out_shape = out_shape
self.out_placements = out_placements
class IndexSelectGradTestCase:
def __init__(
self,
x_shape: list[int],
x_placements: list[dist.Placement],
index_shape: list[int],
index_placements: list[dist.Placement],
axis: int,
out_grad_shape: list[int],
out_grad_placements: list[dist.Placement],
x_grad_placements: list[dist.Placement],
):
self.x_shape = x_shape
self.x_placements = x_placements
self.index_shape = index_shape
self.index_placements = index_placements
self.axis = axis
self.out_grad_shape = out_grad_shape
self.out_grad_placements = out_grad_placements
self.x_grad_placements = x_grad_placements
class TestIndexSelectCoShard:
def setUp(self):
self.mesh = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=['x', 'y', 'z']
)
self.test_cases_forward = [
IndexSelectTestCase(
[8, 16, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[8],
[dist.Replicate(), dist.Replicate(), dist.Replicate()],
1,
[8, 8, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
),
IndexSelectTestCase(
[8, 16, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[8],
[dist.Replicate(), dist.Replicate(), dist.Shard(0)],
1,
[8, 8, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
),
IndexSelectTestCase(
[8, 16, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[8],
[dist.Shard(0), dist.Replicate(), dist.Replicate()],
1,
[8, 8, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
),
IndexSelectTestCase(
[8, 16, 32],
[dist.Replicate(), dist.Replicate(), dist.Shard(0)],
[8],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
1,
[8, 8, 32],
[
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
dist.Shard(0),
],
),
IndexSelectTestCase(
[8, 16, 32],
[dist.Shard(0), dist.Replicate(), dist.Replicate()],
[8],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
1,
[8, 8, 32],
[dist.Shard(0), dist.Shard(1), dist.Replicate()],
),
]
self.test_cases_backward = [
IndexSelectGradTestCase(
[8, 16, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[8],
[dist.Replicate(), dist.Replicate(), dist.Replicate()],
1,
[8, 8, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
),
IndexSelectGradTestCase(
[8, 16, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[8],
[dist.Replicate(), dist.Replicate(), dist.Shard(0)],
1,
[8, 8, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Partial(),
],
),
IndexSelectGradTestCase(
[8, 16, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[8],
[dist.Shard(0), dist.Replicate(), dist.Replicate()],
1,
[8, 8, 32],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
),
IndexSelectGradTestCase(
[8, 16, 32],
[dist.Replicate(), dist.Replicate(), dist.Shard(0)],
[8],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
1,
[8, 8, 32],
[
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
dist.Shard(0),
],
[dist.Partial(), dist.Partial(), dist.Shard(0)],
),
IndexSelectGradTestCase(
[8, 16, 32],
[dist.Shard(0), dist.Replicate(), dist.Replicate()],
[8],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
1,
[8, 8, 32],
[dist.Shard(0), dist.Shard(1), dist.Replicate()],
[dist.Shard(0), dist.Partial(), dist.Replicate()],
),
]
def run_test_case_forward(self, test_case: IndexSelectTestCase):
x = paddle.rand(test_case.x_shape, "float32")
x_placements = test_case.x_placements
x = dist.shard_tensor(x, self.mesh, x_placements)
index = paddle.randint(
0,
test_case.x_shape[test_case.axis],
test_case.index_shape,
dtype="int32",
)
index_placements = test_case.index_placements
index = dist.shard_tensor(index, self.mesh, index_placements)
out = paddle.index_select(x, index, test_case.axis)
case_info = f"x_shape: {test_case.x_shape}, x_placements: {x_placements}, index_shape: {test_case.index_shape}, index_placements: {index_placements}, axis: {test_case.axis}"
# Verify output shape
np.testing.assert_equal(
out.shape,
test_case.out_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.out_shape}, Actual: {out.shape}",
)
# Verify placements
assert out.placements
for actual, expected in zip(out.placements, test_case.out_placements):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.out_placements}, Actual: {out.placements}",
)
def run_test_case_backward(self, test_case: IndexSelectGradTestCase):
x = paddle.rand(test_case.x_shape, "float32")
x.stop_gradient = False
x_placements = test_case.x_placements
x = dist.shard_tensor(x, self.mesh, x_placements)
index = paddle.randint(
0,
test_case.x_shape[test_case.axis],
test_case.index_shape,
dtype="int32",
)
index_placements = test_case.index_placements
index = dist.shard_tensor(index, self.mesh, index_placements)
out = paddle.index_select(x, index, test_case.axis)
out_grad = paddle.ones(out.shape, "float32")
out_grad = dist.shard_tensor(
out_grad, self.mesh, test_case.out_grad_placements
)
(x_grad,) = paddle.grad([out], x, [out_grad])
case_info = f"x_shape: {test_case.x_shape}, x_placements: {test_case.x_placements}, index_shape: {test_case.index_shape}, index_placements: {test_case.index_placements}, axis: {test_case.axis}, out_grad_shape: {test_case.out_grad_shape}, out_grad_placements: {test_case.out_grad_placements}"
# Verify output shape
np.testing.assert_equal(
x_grad.shape,
test_case.x_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.x_shape}, Actual: {x_grad.shape}",
)
# Verify placements
assert x_grad.placements
for actual, expected in zip(
x_grad.placements, test_case.x_grad_placements
):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.x_grad_placements}, Actual: {x_grad.placements}",
)
def run_all_tests(self):
self.setUp()
for test_case in self.test_cases_forward:
self.run_test_case_forward(test_case)
if __name__ == '__main__':
TestIndexSelectCoShard().run_all_tests()
@@ -0,0 +1,170 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 __future__ import annotations
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.distributed import Partial, Replicate, Shard
class MatmulTestCase:
def __init__(
self,
x_shape: list[int],
x_placements: list[dist.Placement],
y_shape: list[int],
y_placements: list[dist.Placement],
trans_x: bool,
trans_y: bool,
output_shape: list[int],
output_placements: list[dist.Placement],
):
self.x_shape = x_shape
self.x_placements = x_placements
self.y_shape = y_shape
self.y_placements = y_placements
self.trans_x = trans_x
self.trans_y = trans_y
self.output_shape = output_shape
self.output_placements = output_placements
class TestMatmulCoShard:
def setUp(self):
self.mesh = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=['x', 'y', 'z']
)
self.test_cases_forward = [
# test flatten
MatmulTestCase(
[64, 32],
[Shard(0, shard_order=0), Shard(0, shard_order=1), Replicate()],
[32, 48],
[Replicate(), Replicate(), Shard(1)],
False,
False,
[64, 48],
[Shard(0, shard_order=0), Shard(0, shard_order=1), Shard(1)],
),
MatmulTestCase(
[64, 32],
[Replicate(), Replicate(), Replicate()],
[32, 48],
[Shard(0, shard_order=0), Shard(0, shard_order=1), Shard(1)],
False,
False,
[64, 48],
[Partial(), Partial(), Shard(1)],
),
MatmulTestCase(
[64, 32],
[Shard(0, shard_order=1), Shard(0, shard_order=1), Shard(1)],
[32, 48],
[Replicate(), Replicate(), Replicate()],
False,
False,
[64, 48],
[Shard(0, shard_order=0), Shard(0, shard_order=1), Partial()],
),
MatmulTestCase(
[64, 32],
[Shard(0, shard_order=1), Shard(0, shard_order=1), Shard(1)],
[32, 48],
[Shard(0), Replicate(), Replicate()],
False,
False,
[64, 48],
[Shard(0, shard_order=0), Shard(0, shard_order=1), Partial()],
),
MatmulTestCase(
[512, 48, 64, 32],
[Shard(0, shard_order=1), Shard(0, shard_order=1), Shard(1)],
[1, 32, 48],
[Replicate(), Replicate(), Replicate()],
False,
False,
[512, 48, 64, 48],
[Shard(0, shard_order=0), Shard(0, shard_order=1), Shard(1)],
),
MatmulTestCase(
[512, 48, 32, 64],
[Shard(0), Shard(2, shard_order=0), Shard(2, shard_order=1)],
[1, 32, 48],
[Replicate(), Replicate(), Shard(2)],
True,
False,
[512, 48, 64, 48],
[Shard(0), Partial(), Shard(3)],
),
MatmulTestCase(
[512, 48, 64, 32],
[Shard(0), Shard(2, shard_order=0), Shard(2, shard_order=1)],
[1, 48, 32],
[Shard(1), Replicate(), Replicate()],
False,
True,
[512, 48, 64, 48],
[Shard(0), Shard(2, shard_order=0), Shard(2, shard_order=1)],
),
MatmulTestCase(
[512, 48, 32, 64],
[Shard(2, shard_order=0), Shard(2, shard_order=1), Shard(3)],
[1, 48, 32],
[Shard(1, shard_order=0), Shard(1, shard_order=1), Shard(2)],
True,
True,
[512, 48, 64, 48],
[Shard(3, shard_order=0), Shard(3, shard_order=1), Shard(2)],
),
]
def run_test_case_forward(self, test_case: MatmulTestCase):
x = paddle.rand(test_case.x_shape, "float32")
x_placements = test_case.x_placements
x = dist.shard_tensor(x, self.mesh, x_placements)
y = paddle.rand(test_case.y_shape, "float32")
y_placements = test_case.y_placements
y = dist.shard_tensor(y, self.mesh, y_placements)
out = paddle.matmul(x, y, test_case.trans_x, test_case.trans_y)
case_info = f"x_shape: {test_case.x_shape}, x_placements: {x_placements}, y_shape: {test_case.y_shape}, y_placements: {test_case.y_placements}, trans_x: {test_case.trans_x}, trans_y: {test_case.trans_y}"
# Verify output shape
np.testing.assert_equal(
out.shape,
test_case.output_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.output_shape}, Actual: {out.shape}",
)
# Verify placements
assert out.placements
for actual, expected in zip(
out.placements, test_case.output_placements
):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.output_placements}, Actual: {out.placements}",
)
def run_all_tests(self):
self.setUp()
for test_case in self.test_cases_forward:
self.run_test_case_forward(test_case)
if __name__ == '__main__':
TestMatmulCoShard().run_all_tests()
@@ -0,0 +1,218 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 __future__ import annotations
from typing import TYPE_CHECKING, Any
import numpy as np
import paddle
import paddle.distributed as dist
if TYPE_CHECKING:
from collections.abc import Callable
class ReshapeTestCase:
def __init__(
self,
input_shape: list[int],
input_placements: list[dist.Placement],
target_shape: list[int],
output_placements: list[dist.Placement],
slice_funtor: Callable[[int], Any] | None = None,
):
self.input_shape = input_shape
self.input_placements = input_placements
self.target_shape = target_shape
self.output_placements = output_placements
self.slice_funtor = slice_funtor
class TestReshapeCoShard:
def setUp(self):
self.mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['x', 'y'])
mesh_coord = lambda idx: (idx // 2, idx % 2)
self.test_cases = [
# test flatten
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(0), dist.Shard(1)],
[192],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
lambda idx: (idx, slice(None), slice(None)),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(1), dist.Shard(2)],
[192],
[dist.Replicate(), dist.Replicate()],
lambda idx: slice(None),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
[192],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
lambda idx: (idx, slice(None), slice(None)),
),
ReshapeTestCase(
[2, 12, 8],
[dist.Shard(0), dist.Shard(1)],
[192],
[dist.Shard(0), dist.Replicate()],
lambda idx: (mesh_coord(idx)[0], slice(None), slice(None)),
),
# test split
ReshapeTestCase(
[192],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
[4, 6, 8],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
lambda idx: slice(idx * 48, (idx + 1) * 48),
),
ReshapeTestCase(
[192],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
[6, 4, 8],
[dist.Replicate(), dist.Replicate()],
lambda idx: slice(None),
),
# test combination
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(0), dist.Shard(1)],
[2, 12, 8],
[dist.Shard(0), dist.Replicate()],
lambda idx: (
slice(mesh_coord(idx)[0] * 2, (mesh_coord(idx)[0] + 1) * 2),
slice(None),
slice(None),
),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
[2, 12, 8],
[dist.Replicate(), dist.Replicate()],
lambda idx: slice(None),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(0), dist.Shard(1)],
[12, 2, 8],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
lambda idx: (idx, slice(None), slice(None)),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
[12, 2, 8],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
lambda idx: (idx, slice(None), slice(None)),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(0), dist.Shard(1)],
[8, 6, 4],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
lambda idx: (idx, slice(None), slice(None)),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(1), dist.Shard(2)],
[8, 6, 4],
[dist.Replicate(), dist.Replicate()],
lambda idx: slice(None),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(0), dist.Shard(2)],
[8, 6, 4],
[dist.Shard(0), dist.Replicate()],
lambda idx: (
slice(mesh_coord(idx)[0] * 2, (mesh_coord(idx)[0] + 1) * 2),
slice(None),
slice(None),
),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
[8, 6, 4],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
lambda idx: (idx, slice(None), slice(None)),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(2, shard_order=0), dist.Shard(2, shard_order=1)],
[24, 2, 4],
[dist.Replicate(), dist.Replicate()],
lambda idx: slice(None),
),
ReshapeTestCase(
[4, 6, 8],
[dist.Shard(2, shard_order=0), dist.Shard(2, shard_order=1)],
[24, 4, 2],
[dist.Shard(1, shard_order=0), dist.Shard(1, shard_order=1)],
lambda idx: (
slice(None),
slice(None),
slice(idx * 2, (idx + 1) * 2),
),
),
]
def run_test_case(self, test_case: ReshapeTestCase):
paddle.seed(2025)
a = paddle.rand(test_case.input_shape, "float32")
a_numpy = a.numpy()
input_placements = test_case.input_placements
input = dist.shard_tensor(a, self.mesh, input_placements)
out = paddle.reshape(input, test_case.target_shape)
case_info = f"input_shape: {test_case.input_shape}, input_placements: {input_placements}, target_shape: {test_case.target_shape}"
# Verify output shape
np.testing.assert_equal(
out.shape,
test_case.target_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.target_shape}, Actual: {out.shape}",
)
# Verify placements
assert out.placements
for actual, expected in zip(
out.placements, test_case.output_placements
):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.output_placements}, Actual: {out.placements}",
)
# Verify local_value if given
if test_case.slice_funtor:
idx = dist.get_rank()
np.testing.assert_allclose(
out._local_value().numpy().flatten(),
a_numpy[test_case.slice_funtor(idx)].flatten(),
err_msg=f"Local values mismatch when {case_info}.",
)
def run_all_tests(self):
self.setUp()
for test_case in self.test_cases:
self.run_test_case(test_case)
if __name__ == '__main__':
TestReshapeCoShard().run_all_tests()
@@ -0,0 +1,312 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 __future__ import annotations
from typing import TYPE_CHECKING, Any
import numpy as np
import paddle
import paddle.distributed as dist
if TYPE_CHECKING:
from collections.abc import Callable
class SoftmaxTestCase:
def __init__(
self,
input_shape: list[int],
input_placements: list[dist.Placement],
axis: int,
output_shape: list[int],
output_placements: list[dist.Placement],
slice_funtor: Callable[[int], Any] | None = None,
):
self.input_shape = input_shape
self.input_placements = input_placements
self.axis = axis
self.output_shape = output_shape
self.output_placements = output_placements
self.slice_funtor = slice_funtor
class SoftmaxGradTestCase:
def __init__(
self,
input_shape: list[int],
axis: int,
output_shape: list[int],
output_placements: list[dist.Placement],
out_grad_placements: list[dist.Placement],
x_grad_placements: list[dist.Placement],
):
self.input_shape = input_shape
self.axis = axis
self.output_shape = output_shape
self.output_placements = output_placements
self.out_grad_placements = out_grad_placements
self.x_grad_placements = x_grad_placements
class TestSoftmaxCoShard:
def setUp(self):
self.mesh = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=['x', 'y', 'z']
)
self.test_cases_forward = [
SoftmaxTestCase(
[32, 48, 128],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
0,
[32, 48, 128],
[dist.Replicate(), dist.Replicate(), dist.Shard(1)],
),
SoftmaxTestCase(
[32, 48, 128],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
-3,
[32, 48, 128],
[dist.Replicate(), dist.Replicate(), dist.Shard(1)],
),
SoftmaxTestCase(
[32, 48, 128],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
1,
[32, 48, 128],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
),
]
self.test_cases_backward = [
SoftmaxGradTestCase(
[32, 48, 128],
0,
[32, 48, 128],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[dist.Replicate(), dist.Replicate(), dist.Shard(1)],
),
SoftmaxGradTestCase(
[32, 48, 128],
0,
[32, 48, 128],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[
dist.Shard(0),
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
],
[
dist.Replicate(),
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
],
),
SoftmaxGradTestCase(
[32, 48, 128],
1,
[32, 48, 128],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(1),
],
[
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
dist.Shard(0),
],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(0, shard_order=2),
],
),
SoftmaxGradTestCase(
[32, 48, 128],
1,
[32, 48, 128],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
[dist.Replicate(), dist.Replicate(), dist.Shard(2)],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(2),
],
),
SoftmaxGradTestCase(
[32, 48, 128],
-1,
[32, 48, 128],
[
dist.Shard(0),
dist.Shard(1),
dist.Replicate(),
],
[
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
dist.Replicate(),
],
[
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
dist.Replicate(),
],
),
SoftmaxGradTestCase(
[32, 48, 128],
-1,
[32, 48, 128],
[dist.Shard(0), dist.Shard(1), dist.Replicate()],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
),
SoftmaxGradTestCase(
[32, 48, 128],
-1,
[32, 48, 128],
[
dist.Shard(0),
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
],
[
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
dist.Replicate(),
],
[
dist.Shard(0),
dist.Shard(1, shard_order=0),
dist.Shard(1, shard_order=1),
],
),
]
def run_test_case_forward(self, test_case: SoftmaxTestCase):
paddle.seed(2025)
a = paddle.rand(test_case.input_shape, "float32")
input_placements = test_case.input_placements
input = dist.shard_tensor(a, self.mesh, input_placements)
out = paddle.nn.functional.softmax(input, test_case.axis)
case_info = f"input_shape: {test_case.input_shape}, input_placements: {input_placements}, axis: {test_case.axis}"
# Verify output shape
np.testing.assert_equal(
out.shape,
test_case.output_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.output_shape}, Actual: {out.shape}",
)
# Verify placements
assert out.placements
for actual, expected in zip(
out.placements, test_case.output_placements
):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.output_placements}, Actual: {out.placements}",
)
# Verify local_value if given
if test_case.slice_funtor:
idx = dist.get_rank()
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[test_case.slice_funtor(idx)].numpy().flatten(),
err_msg=f"Local values mismatch when {case_info}.",
)
def run_test_case_backward(self, test_case: SoftmaxGradTestCase):
a = paddle.rand(test_case.input_shape, "float32")
a.stop_gradient = False
input_placements = [dist.Replicate() for _ in range(self.mesh.ndim)]
input = dist.shard_tensor(a, self.mesh, input_placements)
out = paddle.nn.functional.softmax(input, test_case.axis)
out = dist.reshard(out, self.mesh, test_case.output_placements)
out_grad = paddle.ones(out.shape, "float32")
out_grad = dist.shard_tensor(
out_grad, self.mesh, test_case.out_grad_placements
)
(x_grad,) = paddle.grad([out], input, [out_grad])
case_info = f"input_shape: {test_case.input_shape}, axis: {test_case.axis}, out_placements: {test_case.output_placements}, out_grad_placements: {test_case.out_grad_placements}"
# Verify output shape
np.testing.assert_equal(
x_grad.shape,
test_case.input_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.input_shape}, Actual: {x_grad.shape}",
)
# Verify placements
assert x_grad.placements
for actual, expected in zip(
x_grad.placements, test_case.x_grad_placements
):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.x_grad_placements}, Actual: {x_grad.placements}",
)
def run_all_tests(self):
self.setUp()
for test_case in self.test_cases_forward:
self.run_test_case_forward(test_case)
if __name__ == '__main__':
TestSoftmaxCoShard().run_all_tests()
@@ -0,0 +1,38 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestReshardE2E(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=4, timeout=120)
def test_co_shard(self):
self.run_test_case("co_shard.py")
def test_reshape_co_shard(self):
self.run_test_case("reshape_co_shard.py")
def test_transpose_co_shard(self):
self.run_test_case("transpose_co_shard.py")
def test_elementwise_co_shard(self):
self.run_test_case("elementwise_co_shard.py")
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,41 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestReshardE2E(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=8, timeout=120, nnode=1)
def test_tile_shard(self):
self.run_test_case("tile_co_shard.py")
def test_index_select_shard(self):
self.run_test_case("index_select_co_shard.py")
def test_softmax_shard(self):
self.run_test_case("softmax_co_shard.py")
def test_matmul_shard(self):
self.run_test_case("matmul_co_shard.py")
def test_argsort_shard(self):
self.run_test_case("argsort_co_shard.py")
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,3 @@
name,os,arch,timeout,run_type,launcher,num_port,run_serial,envs,conditions
test_e2e_co_shard_8cards,LINUX,GPU,120,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_e2e_co_shard,LINUX,GPU,120,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
1 name os arch timeout run_type launcher num_port run_serial envs conditions
2 test_e2e_co_shard_8cards LINUX GPU 120 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
3 test_e2e_co_shard LINUX GPU 120 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
@@ -0,0 +1,128 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
import paddle
import paddle.distributed as dist
if TYPE_CHECKING:
from collections.abc import Sequence
from paddle._typing import TensorOrTensors
class TileTestCase:
def __init__(
self,
x_shape: list[int],
x_placements: list[dist.Placement],
repeat_times: TensorOrTensors | Sequence[int],
out_shape: list[int],
out_placements: list[dist.Placement],
):
self.x_shape = x_shape
self.x_placements = x_placements
self.repeat_times = repeat_times
self.out_shape = out_shape
self.out_placements = out_placements
class TestTileCoShard:
def setUp(self):
self.mesh = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=['x', 'y', 'z']
)
self.test_cases_forward = [
TileTestCase(
[8, 16, 24],
[
dist.Shard(0),
dist.Shard(2, shard_order=0),
dist.Shard(2, shard_order=1),
],
[2, 2, 1, 1],
[2, 16, 16, 24],
[
dist.Replicate(),
dist.Shard(3, shard_order=0),
dist.Shard(3, shard_order=1),
],
),
TileTestCase(
[8, 16, 24],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(2),
],
[1, 2],
[8, 16, 48],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Replicate(),
],
),
TileTestCase(
[8, 16, 24],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(2),
],
[],
[8, 16, 24],
[
dist.Shard(0, shard_order=0),
dist.Shard(0, shard_order=1),
dist.Shard(2),
],
),
]
def run_test_case_forward(self, test_case: TileTestCase):
paddle.seed(2025)
x = paddle.rand(test_case.x_shape, "float32")
x_placements = test_case.x_placements
input = dist.shard_tensor(x, self.mesh, x_placements)
out = paddle.tile(input, test_case.repeat_times)
case_info = f"input_shape: {test_case.x_shape}, input_placements: {x_placements}, axis: {test_case.repeat_times}"
# Verify output shape
np.testing.assert_equal(
out.shape,
test_case.out_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.out_shape}, Actual: {out.shape}",
)
# Verify placements
assert out.placements
for actual, expected in zip(out.placements, test_case.out_placements):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.out_placements}, Actual: {out.placements}",
)
def run_all_tests(self):
self.setUp()
for test_case in self.test_cases_forward:
self.run_test_case_forward(test_case)
if __name__ == '__main__':
TestTileCoShard().run_all_tests()
@@ -0,0 +1,120 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 __future__ import annotations
from typing import TYPE_CHECKING, Any
import numpy as np
import paddle
import paddle.distributed as dist
if TYPE_CHECKING:
from collections.abc import Callable
class TransposeTestCase:
def __init__(
self,
input_shape: list[int],
input_placements: list[dist.Placement],
perm: list[int],
output_shape: list[int],
output_placements: list[dist.Placement],
slice_funtor: Callable[[int], Any] | None = None,
):
self.input_shape = input_shape
self.input_placements = input_placements
self.perm = perm
self.output_shape = output_shape
self.output_placements = output_placements
self.slice_funtor = slice_funtor
class TestTransposeCoShard:
def setUp(self):
self.mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['x', 'y'])
self.test_cases = [
# test flatten
TransposeTestCase(
[64, 48, 36, 24],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
[1, 0, 2, 3],
[48, 64, 36, 24],
[dist.Shard(1, shard_order=0), dist.Shard(1, shard_order=1)],
),
TransposeTestCase(
[64, 48, 36, 24],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
[0, 1, 2, 3],
[64, 48, 36, 24],
[dist.Shard(0, shard_order=0), dist.Shard(0, shard_order=1)],
),
TransposeTestCase(
[64, 48, 36, 24],
[dist.Shard(2, shard_order=0), dist.Shard(2, shard_order=1)],
[0, 2, 3, 1],
[64, 36, 24, 48],
[dist.Shard(1, shard_order=0), dist.Shard(1, shard_order=1)],
),
TransposeTestCase(
[64, 48, 36, 24],
[dist.Shard(2, shard_order=0), dist.Shard(2, shard_order=1)],
[-1, 0, -2, 1],
[24, 64, 36, 48],
[dist.Shard(2, shard_order=0), dist.Shard(2, shard_order=1)],
),
]
def run_test_case(self, test_case: TransposeTestCase):
paddle.seed(2025)
a = paddle.rand(test_case.input_shape, "float32")
input_placements = test_case.input_placements
input = dist.shard_tensor(a, self.mesh, input_placements)
out = paddle.transpose(input, test_case.perm)
case_info = f"input_shape: {test_case.input_shape}, input_placements: {input_placements}, perm: {test_case.perm}"
# Verify output shape
np.testing.assert_equal(
out.shape,
test_case.output_shape,
err_msg=f"Output shape mismatch when {case_info}. Expected: {test_case.output_shape}, Actual: {out.shape}",
)
# Verify placements
assert out.placements
for actual, expected in zip(
out.placements, test_case.output_placements
):
np.testing.assert_equal(
actual,
expected,
err_msg=f"Output placements mismatch when {case_info}. Expected: {test_case.output_placements}, Actual: {out.placements}",
)
# Verify local_value if given
if test_case.slice_funtor:
idx = dist.get_rank()
np.testing.assert_equal(
out._local_value().numpy().flatten(),
a[test_case.slice_funtor(idx)].numpy().flatten(),
err_msg=f"Local values mismatch when {case_info}.",
)
def run_all_tests(self):
self.setUp()
for test_case in self.test_cases:
self.run_test_case(test_case)
if __name__ == '__main__':
TestTransposeCoShard().run_all_tests()
+149
View File
@@ -0,0 +1,149 @@
# Copyright (c) 2022 PaddlePaddle Authors. 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 numpy as np
import paddle
from paddle.distributed.fleet import auto
from paddle.incubate.autograd import Hessian
np.random.seed(1234)
paddle.seed(1234)
class FCNet:
def __init__(self, num_ins, num_outs, num_layers, hidden_size):
self.num_ins = num_ins
self.num_outs = num_outs
self.num_layers = num_layers
self.hidden_size = hidden_size
self.activation = paddle.tanh
self.weights = []
self.biases = []
for i in range(self.num_layers):
if i == 0:
lsize = self.num_ins
rsize = self.hidden_size
elif i == (self.num_layers - 1):
lsize = self.hidden_size
rsize = self.num_outs
else:
lsize = self.hidden_size
rsize = self.hidden_size
w = paddle.static.create_parameter(
shape=[lsize, rsize], dtype="float32", is_bias=False
)
b = paddle.static.create_parameter(
shape=[rsize], dtype="float32", is_bias=True
)
self.weights.append(w)
self.biases.append(b)
def nn_func(self, ins):
u = ins
for i in range(self.num_layers - 1):
u = paddle.nn.functional.linear(u, self.weights[i], self.biases[i])
u = self.activation(u)
u = paddle.nn.functional.linear(u, self.weights[-1], self.biases[-1])
return u
class LaplaceModel(paddle.nn.Layer):
def __init__(self, num_ins=2, num_outs=1, num_layers=5, hidden_size=20):
super().__init__()
self.net = FCNet(
num_ins=num_ins,
num_outs=num_outs,
num_layers=num_layers,
hidden_size=hidden_size,
)
def forward(self, inputs, bc_index):
inputs.stop_gradient = False
outputs = self.net.nn_func(inputs)
# eq_loss
hes = Hessian(self.net.nn_func, inputs, is_batched=True)
eq_loss = paddle.norm(hes[:, 0, 0] + hes[:, 1, 1], p=2)
# bc_loss
bc_u = paddle.index_select(outputs, bc_index)
return eq_loss, bc_u
class LaplaceDataset(paddle.io.Dataset):
def __init__(self, num_sample):
self.num_sample = num_sample
def __getitem__(self, index):
x = np.linspace(0, 0.9, 10)
y = np.linspace(0, 0.9, 10)
np.random.seed(index) # Optional: Ensure reproducibility
bc_value = np.random.rand(36).reshape(36, 1).astype('float32')
domain_space = []
bc_index = []
for j in range(len(y)):
for i in range(len(x)):
domain_space.append([x[i], y[j]])
if i == 0 or i == 9 or j == 0 or j == 9:
bc_index.append(i + 10 * j)
domain_space = np.array(domain_space, dtype='float32')
bc_index = np.array(bc_index, dtype='int64')
# Return a single input point and its related information based on the index
idx = index % len(domain_space)
return domain_space[idx], bc_index, bc_value
def __len__(self):
return self.num_sample
def loss_func(eq_loss, bc_u, bc_value):
bc_diff = bc_u - bc_value
bc_loss = paddle.norm(bc_diff, p=2)
loss = eq_loss + bc_loss
return loss
def main():
paddle.enable_static()
# dataset
train_dataset = LaplaceDataset(10)
# optimizer
optimizer = paddle.optimizer.Adam(learning_rate=0.001)
# model
laplace = LaplaceModel()
dist_strategy = auto.Strategy()
dist_strategy.auto_mode = "semi"
engine = auto.Engine(
laplace, loss=loss_func, optimizer=optimizer, strategy=dist_strategy
)
engine.fit(train_dataset, train_sample_split=2, batch_size=None)
dist_context = engine.dist_context
block = engine.main_program.global_block()
ops = block.ops
for op in ops:
if op.type == 'p_norm':
op_dist_attr = dist_context.get_op_dist_attr_for_program(op)
assert op_dist_attr.impl_type == 'p_norm'
if 'x' in op.input_arg_names:
out_name = op.output_arg_names[0]
assert block.vars[out_name].shape[0] == 50
if __name__ == "__main__":
main()
@@ -0,0 +1,193 @@
# This file is generated by ${PADDLE_ROOT}/tools/gen_ut_cmakelists.py.
# Please don't modify this file manually.
# If you need to change unittests in this file, please modify testslist.csv in the current directory
# and then run the command `python3 ${PADDLE_ROOT}/tools/gen_ut_cmakelists.py -f ${CURRENT_DIRECTORY}/testslist.csv`
set(LOCAL_ALL_ARCH ON)
set(LOCAL_ALL_PLAT ON)
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_parallel_hybrid_strategy MODULES
test_semi_auto_parallel_hybrid_strategy ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_semi_auto_parallel_hybrid_strategy
PROPERTIES TIMEOUT "300" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_save_load_state_dict MODULES test_save_load_state_dict ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_save_load_state_dict
PROPERTIES TIMEOUT "400" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_flexcheckpoint_merge MODULES test_flexcheckpoint_merge ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_flexcheckpoint_merge
PROPERTIES TIMEOUT "120" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_parallel_c_cross_entropy MODULES
test_semi_auto_parallel_c_cross_entropy ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_semi_auto_parallel_c_cross_entropy
PROPERTIES TIMEOUT "120" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_cross_mesh_reshard MODULES test_cross_mesh_reshard ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_cross_mesh_reshard PROPERTIES TIMEOUT "120" LABELS
"RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_parallel_llama_model_amp MODULES
test_semi_auto_parallel_llama_model_amp ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_semi_auto_parallel_llama_model_amp
PROPERTIES TIMEOUT "180" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_parallel_hybrid_sharding_strategy MODULES
test_semi_auto_parallel_hybrid_sharding_strategy ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_semi_auto_parallel_hybrid_sharding_strategy
PROPERTIES TIMEOUT "120" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_global_mesh_reshard MODULES test_global_mesh_reshard ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_global_mesh_reshard PROPERTIES TIMEOUT "120" LABELS
"RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_parallel_global_input MODULES
test_semi_auto_parallel_global_input ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_semi_auto_parallel_global_input
PROPERTIES TIMEOUT "120" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_parallel_multi_inputs MODULES
test_semi_auto_parallel_multi_inputs ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_semi_auto_parallel_multi_inputs
PROPERTIES TIMEOUT "120" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_parallel_llama_model_vpp MODULES
test_semi_auto_parallel_llama_model_vpp ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_semi_auto_parallel_llama_model_vpp
PROPERTIES TIMEOUT "180" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_parallel_llama_model_pir
MODULES
test_semi_auto_parallel_llama_model_pir
ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python;FLAGS_enable_pir_api=1"
)
set_tests_properties(test_semi_auto_parallel_llama_model_pir
PROPERTIES TIMEOUT "180" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_pir_reshard_nd_mesh_func MODULES test_pir_reshard_nd_mesh_func ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_pir_reshard_nd_mesh_func
PROPERTIES TIMEOUT "60" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_llama_acc_align
MODULES
test_semi_auto_llama_acc_align
ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python;FLAGS_enable_pir_api=1"
)
set_tests_properties(test_semi_auto_llama_acc_align
PROPERTIES TIMEOUT "300" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_semi_auto_llama_save_load
MODULES
test_semi_auto_llama_save_load
ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python;FLAGS_enable_pir_api=1"
)
set_tests_properties(test_semi_auto_llama_save_load
PROPERTIES TIMEOUT "180" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_parallel_api_with_llama_1d MODULES test_parallel_api_with_llama_1d
ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_parallel_api_with_llama_1d
PROPERTIES TIMEOUT "400" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_parallel_api_with_llama_2d MODULES test_parallel_api_with_llama_2d
ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_parallel_api_with_llama_2d
PROPERTIES TIMEOUT "400" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_parallel_api_with_llama_2d_sep MODULES
test_parallel_api_with_llama_2d_sep ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_parallel_api_with_llama_2d_sep
PROPERTIES TIMEOUT "400" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_parallel_api_with_llama_3d MODULES test_parallel_api_with_llama_3d
ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_parallel_api_with_llama_3d
PROPERTIES TIMEOUT "800" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_to_distributed_api_for_llama MODULES test_to_distributed_api_for_llama
ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_to_distributed_api_for_llama
PROPERTIES TIMEOUT "180" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_parallel_api_with_llama_lora MODULES test_parallel_api_with_llama_lora
ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_parallel_api_with_llama_lora
PROPERTIES TIMEOUT "360" LABELS "RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_process_mesh MODULES test_process_mesh ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_process_mesh PROPERTIES TIMEOUT "150" LABELS
"RUN_TYPE=HYBRID")
endif()
if((WITH_GPU) AND (LINUX))
py_test_modules(
test_get_group_in_different_hybrid_configs MODULES
test_get_group_in_different_hybrid_configs ENVS
"http_proxy=;https_proxy=;PYTHONPATH=../..:${PADDLE_BINARY_DIR}/python")
set_tests_properties(test_get_group_in_different_hybrid_configs
PROPERTIES TIMEOUT "150" LABELS "RUN_TYPE=HYBRID")
endif()
@@ -0,0 +1,618 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 logging
import os
import random
from dataclasses import dataclass
from functools import reduce
import numpy as np
from single_llama_model import LlamaForCausalLM, LlamaPretrainingCriterion
from single_lora_model import LoRAModel
import paddle
import paddle.distributed as dist
from paddle import LazyGuard
from paddle.distributed.auto_parallel.intermediate.parallelize import (
parallelize_model,
parallelize_optimizer,
)
from paddle.io import BatchSampler, DataLoader, Dataset
def is_pp_enable():
global_mesh = dist.auto_parallel.get_mesh()
return "pp" in global_mesh.dim_names
def get_mesh(pp_idx=None):
global_mesh = dist.auto_parallel.get_mesh()
assert global_mesh is not None, "global_mesh is not initialized!"
if pp_idx is None:
return global_mesh
if is_pp_enable():
mesh = global_mesh.get_mesh_with_dim("pp")[pp_idx]
return mesh
else:
return global_mesh
class Config:
vocab_size = 8192
hidden_size = 512
intermediate_size = 2048
seq_length = 512
num_hidden_layers = 2
num_attention_heads = 8
rms_norm_eps = 1e-6
use_lazy_init = False
context_parallel = False
sep_parallel = False
@dataclass
class LoRaConfig:
r = 8
lora_alpha = 8
lora_dropout = 0.0
rslora = False
lora_plus_scale = 1.0
pissa = False
use_quick_lora = False
lora_use_mixer = False
use_mora = False
trainable_bias = False
trainable_modules = None
target_modules = [
".*q_proj.*",
".*v_proj.*",
".*k_proj.*",
".*o_proj.*",
".*qkv_proj.*",
".*gate_proj.*",
".*down_proj.*",
".*up_proj.*",
".*gate_up_fused_proj.*",
]
class RandomDataset(Dataset):
def __init__(self, seq_len, num_samples=100):
super().__init__()
self.seq_len = seq_len
self.num_samples = num_samples
def __getitem__(self, index):
input = np.random.uniform(size=[self.seq_len]).astype("int64")
label = (np.random.uniform(size=[self.seq_len]) * 10).astype("int64")
return input, label
def __len__(self):
return self.num_samples
def create_optimizer(model, lr_scheduler):
decay_parameters = [
p.name
for n, p in model.named_parameters()
if not any(nd in n for nd in ["bias", "norm"])
]
def apply_decay_param_fun(x):
return x in decay_parameters
# test global_clip in auto_parallel
if os.getenv("use_param_group") == "true":
param_group = {}
param_group["params"] = list(model.parameters())
param_group["weight_decay"] = 0.01
param_group["grad_clip"] = paddle.nn.ClipGradByGlobalNorm(1.0)
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=[param_group],
)
else:
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=model.parameters(),
weight_decay=0.01,
grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0),
)
return optimizer
class TestParallelAPI:
def __init__(self):
self.config = Config()
self.lora_config = LoRaConfig()
self.dp = int(os.getenv("dp"))
self.mp = int(os.getenv("mp"))
self.pp = int(os.getenv("pp"))
self.sep = int(os.getenv("sep", "1"))
if os.getenv("use_lazy_init") == "true":
self.config.use_lazy_init = True
self.gradient_accumulation_steps = int(os.getenv("acc_step"))
self.amp = False
self.amp_dtype = "float16"
self.amp_level = "O1"
self.amp_master_grad = False
if os.getenv("amp") == "true":
self.amp = True
if os.getenv("amp_dtype") in ["float16", "bfloat16"]:
self.amp_dtype = os.getenv("amp_dtype")
if os.getenv("amp_level") in ["O0", "O1", "O2"]:
self.amp_level = os.getenv("amp_level")
if os.getenv("amp_master_grad") == "true":
self.amp_master_grad = True
self.level = os.getenv("sharding_stage", "0")
self.sequence_parallel = False
if os.getenv("sequence_parallel") == "true":
self.sequence_parallel = True
self.config.context_parallel = False
if os.getenv("context_parallel", "false") == "true":
self.config.context_parallel = True
self.config.sep_parallel = False
if os.getenv("sep_parallel", "false") == "true":
self.config.sep_parallel = True
self.prepare_input_output = False
if os.getenv("prepare_input_output") == "true":
self.sequence_parallel = True
if self.sep > 1:
assert (
self.config.context_parallel is True
and self.config.sep_parallel is False
) or (
self.config.context_parallel is False
and self.config.sep_parallel is True
), (
"when sep > 1, either context_parallel or sep_parallel should be true"
)
num_hidden_layers = os.getenv("num_hidden_layers")
if num_hidden_layers:
self.config.num_hidden_layers = int(num_hidden_layers)
self.one_api = False
if os.getenv("one_api") == "true":
self.one_api = True
seed = int(os.getenv("seed", 2024))
self.share_embedding = int(os.getenv("test_share_embedding", "0"))
self.position_embedding = int(os.getenv("test_position_embedding", "0"))
self.test_lora = int(os.getenv("test_lora", "0"))
np.random.seed(seed)
random.seed(seed)
paddle.seed(seed)
self.init_dist_env()
def init_dist_env(self):
mesh_dims = [
("dp", self.dp),
("pp", self.pp),
("mp", self.mp),
("sep", self.sep),
]
if self.pp * self.mp == 1:
mesh_dims = [("dp", self.dp)]
dim_names = [mesh_dim[0] for mesh_dim in mesh_dims]
mesh_shape = [mesh_dim[1] for mesh_dim in mesh_dims]
mesh_arr = np.arange(
0, reduce(lambda x, y: x * y, mesh_shape, 1)
).reshape(mesh_shape)
global_mesh = dist.ProcessMesh(mesh_arr, dim_names)
dist.auto_parallel.set_mesh(global_mesh)
def check_mp(self, layer):
if self.mp == 1:
return
for name, sub_layer in layer.named_sublayers():
if len(sub_layer.sublayers()) == 0:
if 'q_proj' in name or 'k_proj' in name or 'v_proj' in name:
assert sub_layer.weight.placements == [
dist.Replicate(),
dist.Shard(1),
dist.Replicate(), # cp
]
assert sub_layer.bias.placements == [
dist.Replicate(),
dist.Shard(0),
dist.Replicate(), # cp
]
if self.test_lora:
assert sub_layer.lora_B.placements == [
dist.Replicate(),
dist.Shard(1),
dist.Replicate(), # cp
]
if 'gate_proj' in name or 'up_proj' in name:
assert sub_layer.weight.placements == [
dist.Replicate(),
dist.Shard(1),
dist.Replicate(), # cp
]
if self.test_lora:
assert sub_layer.lora_B.placements == [
dist.Replicate(),
dist.Shard(1),
dist.Replicate(), # cp
]
if (
'embed_tokens' in name or 'lm_head' in name
) and not self.share_embedding:
assert sub_layer.weight.placements == [
dist.Replicate(),
dist.Shard(1),
dist.Replicate(), # cp
]
if 'o_proj' in name:
assert sub_layer.weight.placements == [
dist.Replicate(),
dist.Shard(0),
dist.Replicate(), # cp
], f'{name} , {sub_layer.weight.name} , {sub_layer.weight}'
if self.test_lora:
assert sub_layer.lora_A.placements == [
dist.Replicate(),
dist.Shard(0),
dist.Replicate(), # cp
]
# assert sub_layer.bias.placements is None
if 'down_proj' in name:
assert sub_layer.weight.placements == [
dist.Replicate(),
dist.Shard(0),
dist.Replicate(), # cp
]
if self.test_lora:
assert sub_layer.lora_A.placements == [
dist.Replicate(),
dist.Shard(0),
dist.Replicate(), # cp
]
def check_lora(self, layer):
if not self.test_lora:
return
for name, sub_layer in layer.named_sublayers():
if len(sub_layer.sublayers()) == 0:
if 'q_proj' in name or 'k_proj' in name or 'v_proj' in name:
assert sub_layer.weight.stop_gradient
assert not sub_layer.lora_A.stop_gradient
assert not sub_layer.lora_B.stop_gradient
if 'gate_proj' in name or 'up_proj' in name:
assert sub_layer.weight.stop_gradient
assert not sub_layer.lora_A.stop_gradient
assert not sub_layer.lora_B.stop_gradient
if (
'embed_tokens' in name or 'lm_head' in name
) and not self.share_embedding:
assert sub_layer.weight.stop_gradient
if 'o_proj' in name:
assert sub_layer.weight.stop_gradient, (
f'{name} , {sub_layer.weight.name} , {sub_layer.weight}'
)
assert not sub_layer.lora_A.stop_gradient
assert not sub_layer.lora_B.stop_gradient
# assert sub_layer.bias.stop_gradient is None
if 'down_proj' in name:
assert sub_layer.weight.stop_gradient
assert not sub_layer.lora_A.stop_gradient
assert not sub_layer.lora_B.stop_gradient
def parallel_model(self, layer):
dp_config = None
mp_config = None
pp_config = None
cp_config = None
prefix = "model." if self.test_lora else ""
if self.pp > 1:
# decoders_per_rank = self.config.num_hidden_layers // self.pp
# split_spec = {
# ff"{prefix}llama.layers.{i * decoders_per_rank - 1}": SplitPoint.END
# for i in range(1, self.pp)
# }
pp_config = {
'split_spec': f"{prefix}llama.layers",
"global_spec": f"{prefix}llama.global_layer",
}
if self.dp > 1:
dp_config = {'sharding_level': self.level}
if self.mp > 1:
if not self.sequence_parallel:
plan = {
f"{prefix}llama.embed_tokens": dist.ColWiseParallel(
gather_output=True
),
f"{prefix}llama.position_embedding": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.q_proj": dist.ColWiseParallel(
gather_output=True
),
f"{prefix}llama.layers.*.self_attn.q_proj.lora_B": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.k_proj": dist.ColWiseParallel(
gather_output=True
),
f"{prefix}llama.layers.*.self_attn.k_proj.lora_B": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.v_proj": dist.ColWiseParallel(
gather_output=True
),
f"{prefix}llama.layers.*.self_attn.v_proj.lora_B": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.o_proj": dist.RowWiseParallel(
is_input_parallel=False
),
f"{prefix}llama.layers.*.self_attn.o_proj.lora_A": dist.RowWiseParallel(),
f"{prefix}llama.layers.*.mlp.gate_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.mlp.gate_proj.lora_B": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.mlp.up_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.mlp.up_proj.lora_B": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.mlp.down_proj": dist.RowWiseParallel(),
f"{prefix}llama.layers.*.mlp.down_proj.lora_A": dist.RowWiseParallel(),
f"{prefix}lm_head.weight": dist.ColWiseParallel(),
}
else:
if self.prepare_input_output:
plan = {
f"{prefix}llama.embed_tokens": dist.ColWiseParallel(),
f"{prefix}llama.position_embedding": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.q_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.k_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.v_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.o_proj": dist.RowWiseParallel(),
f"{prefix}llama.layers.*.mlp.gate_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.mlp.up_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.mlp.down_proj": dist.RowWiseParallel(),
f"{prefix}lm_head.weight": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.input_layernorm": dist.SequenceParallelEnable(),
f"{prefix}llama.layers.*.post_attention_layernorm": dist.SequenceParallelEnable(),
f"{prefix}llama.norm": dist.SequenceParallelEnable(),
}
else:
plan = {
f"{prefix}llama.embed_tokens": [
dist.ColWiseParallel(),
dist.SequenceParallelBegin(),
],
f"{prefix}llama.position_embedding": [
dist.ColWiseParallel(),
dist.SequenceParallelBegin(),
],
f"{prefix}llama.layers.*.self_attn.q_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.k_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.v_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.self_attn.o_proj": dist.RowWiseParallel(),
f"{prefix}llama.layers.*.self_attn": dist.SequenceParallelDisable(),
f"{prefix}llama.layers.*.mlp.gate_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.mlp.up_proj": dist.ColWiseParallel(),
f"{prefix}llama.layers.*.mlp.down_proj": dist.RowWiseParallel(),
f"{prefix}llama.layers.*.mlp": dist.SequenceParallelDisable(
need_transpose=False
),
f"{prefix}lm_head.weight": dist.ColWiseParallel(),
f"{prefix}lm_head": dist.SequenceParallelEnd(),
}
mp_config = {'parallelize_plan': plan}
if self.sep > 1:
if not (
self.config.context_parallel is True
and (
os.getenv("backend") != "gpu"
or not self.amp
or int(paddle.version.cuda().split(".")[0]) < 11
or paddle.device.cuda.get_device_capability()[0] < 8
)
):
bck = 'p2p'
if self.config.context_parallel is True:
bck = 'p2p'
elif self.config.sep_parallel is True:
bck = 'all2all'
else:
logging.error(
f"when sep > 1, should set context_parallel or sep_parallel, but got sep_parallel={self.config.sep_parallel}, context_parallel={self.context_parallel}"
)
plan = {
f"{prefix}llama": dist.PrepareContextParallel(backend=bck),
f"{prefix}llama.layers.*.self_attn.sdpa": dist.ContextParallel(
backend=bck
),
}
cp_config = {'parallelize_plan': plan}
lr_scheduler = paddle.optimizer.lr.LinearWarmup(
learning_rate=0.0001, warmup_steps=2, start_lr=0, end_lr=0.0001
)
config = {
'dp_config': dp_config,
'mp_config': mp_config,
'pp_config': pp_config,
'cp_config': cp_config,
}
if self.one_api:
optimizer = create_optimizer(layer, lr_scheduler)
model, optimizer = dist.parallelize(
layer,
optimizer,
config=config,
)
else:
layer = parallelize_model(
layer,
config=config,
)
optimizer = create_optimizer(layer, lr_scheduler)
optimizer = parallelize_optimizer(
optimizer,
config=config,
)
self.check_mp(layer)
self.check_lora(layer)
return layer, optimizer, lr_scheduler
def run_llama(self, to_static=0):
if self.config.use_lazy_init:
with LazyGuard():
model = LlamaForCausalLM(
self.config, self.share_embedding, self.position_embedding
)
else:
model = LlamaForCausalLM(
self.config, self.share_embedding, self.position_embedding
)
if self.test_lora:
if self.config.use_lazy_init:
with LazyGuard():
model = LoRAModel(model, self.lora_config)
else:
model = LoRAModel(model, self.lora_config)
model, optimizer, lr_scheduler = self.parallel_model(model)
criterion = LlamaPretrainingCriterion(self.config)
if self.config.use_lazy_init:
for param in model.parameters():
assert not param._is_initialized()
param.initialize()
if self.amp and not to_static:
model, optimizer = paddle.amp.decorate(
models=model,
optimizers=optimizer,
level=self.amp_level,
dtype=self.amp_dtype,
master_grad=self.amp_master_grad,
)
train_dataset = RandomDataset(self.config.seq_length)
train_sampler = BatchSampler(
train_dataset,
batch_size=2,
shuffle=True,
drop_last=True,
)
train_dataloader = DataLoader(
train_dataset,
batch_sampler=train_sampler,
num_workers=0,
)
if self.pp == 1:
meshes = [get_mesh(0)]
elif self.pp > 1:
meshes = [get_mesh(0), get_mesh(-1)]
else:
raise ValueError("pp should be greater or equal to 1")
dist_loader = dist.shard_dataloader(
dataloader=train_dataloader,
meshes=meshes,
shard_dims="dp",
)
global_step = 1
tr_loss = float(0)
if not to_static:
model.train()
scaler = None
if self.amp and self.amp_dtype == "float16":
scaler = paddle.amp.GradScaler(init_loss_scaling=1024)
scaler = dist.shard_scaler(scaler)
for step, inputs in enumerate(dist_loader()):
input_ids, labels = inputs
custom_black_list = [
"reduce_sum",
"c_softmax_with_cross_entropy",
]
custom_white_list = []
if self.amp_level == "O2":
custom_white_list.extend(
["lookup_table", "lookup_table_v2"]
)
with paddle.amp.auto_cast(
self.amp,
custom_black_list=set(custom_black_list),
custom_white_list=set(custom_white_list),
level=self.amp_level,
dtype=self.amp_dtype,
):
logits = model(input_ids)
tr_loss_step = criterion(logits, labels)
if self.gradient_accumulation_steps > 1:
tr_loss_step /= self.gradient_accumulation_steps
if scaler is not None:
scaler.scale(tr_loss_step).backward()
else:
tr_loss_step.backward()
tr_loss += tr_loss_step
if global_step % self.gradient_accumulation_steps == 0:
logging.info(
f"step: {global_step // self.gradient_accumulation_steps} loss: {tr_loss.numpy()}"
)
if scaler is not None:
scaler.step(optimizer)
scaler.update()
else:
optimizer.step()
optimizer.clear_grad()
lr_scheduler.step()
tr_loss = 0
global_step += 1
if global_step // self.gradient_accumulation_steps >= 3:
break
else:
strategy = dist.Strategy()
if self.gradient_accumulation_steps > 1:
strategy.pipeline.accumulate_steps = (
self.gradient_accumulation_steps
)
if self.amp:
amp = strategy.amp
amp.enable = self.amp
amp.dtype = self.amp_dtype
amp.level = self.amp_level.lower()
if self.amp_master_grad:
amp.use_master_grad = True
dist_model = dist.to_static(
model,
dist_loader,
criterion,
optimizer,
strategy=strategy,
)
dist_model.train()
for step, inputs in enumerate(dist_loader()):
input_ids, labels = inputs
loss = dist_model(input_ids, labels)
logging.info(f"step: {step} loss: {loss}")
if step >= 3:
break
def run_test_cases(self):
self.run_llama(0)
if self.sep == 1:
# sep now only support dynamic mode
self.run_llama(1)
if __name__ == '__main__':
TestParallelAPI().run_test_cases()
@@ -0,0 +1,375 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import paddle
import paddle.distributed as dist
from paddle.distributed.auto_parallel.static.pir_pass import (
ReshardPasses,
)
from paddle.distributed.auto_parallel.static.utils import set_all_ops_op_role
from paddle.distributed.fleet.meta_optimizers.common import OpRole
class TestReshardNdMesh:
def __init__(self):
self._dtype = os.getenv("dtype")
self._seed = eval(os.getenv("seed"))
self.BATCH_SIZE = 2
self.SEQ_LEN = 4
self.HIDDEN_SIZE = 8
self._backend = os.getenv("backend")
self._mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
def validate(
self, op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value
):
# tgt_* are tuples, format: (process_ids, dims_mapping, partial_status)
operand_dist_attr = op.dist_attr.operand(0).as_tensor_dist_attr()
result_dist_attr = op.dist_attr.result(0).as_tensor_dist_attr()
assert operand_dist_attr.process_mesh.process_ids == tgt_operand[0]
assert operand_dist_attr.dims_mapping == tgt_operand[1]
assert operand_dist_attr.partial_status == tgt_operand[2]
assert result_dist_attr.process_mesh.process_ids == tgt_result[0]
assert result_dist_attr.dims_mapping == tgt_result[1]
assert result_dist_attr.partial_status == tgt_result[2]
in_value = op.operand_source(0)
out_value = op.result(0)
assert in_value.is_dist_dense_tensor_type()
assert out_value.is_dist_dense_tensor_type()
assert in_value.dist_attr().process_mesh.process_ids == tgt_in_value[0]
assert in_value.dist_attr().dims_mapping == tgt_in_value[1]
assert in_value.dist_attr().partial_status == tgt_in_value[2]
assert (
out_value.dist_attr().process_mesh.process_ids == tgt_out_value[0]
)
assert out_value.dist_attr().dims_mapping == tgt_out_value[1]
assert out_value.dist_attr().partial_status == tgt_out_value[2]
def create_program(self, input_shape, input_placements, output_placements):
paddle.enable_static()
if self._backend == "cpu":
paddle.set_device("cpu")
place = paddle.CPUPlace()
elif self._backend == "gpu":
place = paddle.CUDAPlace(dist.get_rank())
with paddle.pir_utils.IrGuard():
main_program = paddle.base.Program()
with paddle.base.program_guard(main_program):
input = paddle.ones(name='input', shape=input_shape)
dist_input = dist.shard_tensor(
input, self._mesh, input_placements
)
dist_out = paddle._C_ops.reshard(
dist_input, self._mesh, output_placements
)
dist_program = main_program.clone()
set_all_ops_op_role(dist_program.global_block(), OpRole.Forward)
ReshardPasses.apply_reshard_pass(dist_program)
return main_program, dist_program
def run_pp_to_rr_case(self):
# [Partial(), Partial()] --> [Replicate(), Replicate()]
# ops: all_reduce sum + all_reduce sum
main_program, dist_program = self.create_program(
[self.BATCH_SIZE, self.SEQ_LEN, self.HIDDEN_SIZE],
[
dist.Partial(dist.ReduceType.kRedSum),
dist.Partial(dist.ReduceType.kRedSum),
],
[dist.Replicate(), dist.Replicate()],
)
new_ops = dist_program.global_block().ops
new_ops_name = [op.name() for op in dist_program.global_block().ops]
rank_id = dist.get_rank()
assert dist_program.global_block().ops[-2].name() == "pd_op.all_reduce"
assert (
dist_program.global_block().ops[-2].int_attr("reduce_type")
== dist.ReduceOp.SUM
)
assert dist_program.global_block().ops[-1].name() == "pd_op.all_reduce"
assert (
dist_program.global_block().ops[-1].int_attr("reduce_type")
== dist.ReduceOp.SUM
)
# check the first allreduce_sum
op = new_ops[-2]
if rank_id == 0 or rank_id == 2:
process_ids = [0, 2]
elif rank_id == 1 or rank_id == 3:
process_ids = [1, 3]
tgt_operand = (process_ids, [-1, -1, -1], {0: dist.ReduceType.kRedSum})
tgt_result = (process_ids, [-1, -1, -1], {})
tgt_in_value = (
self._mesh.process_ids,
[-1, -1, -1],
{0: dist.ReduceType.kRedSum, 1: dist.ReduceType.kRedSum},
)
tgt_out_value = (
self._mesh.process_ids,
[-1, -1, -1],
{1: dist.ReduceType.kRedSum},
)
self.validate(op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value)
# check the second allreduce_sum
op = new_ops[-1]
if rank_id == 0 or rank_id == 1:
process_ids = [0, 1]
elif rank_id == 2 or rank_id == 3:
process_ids = [2, 3]
tgt_operand = (process_ids, [-1, -1, -1], {0: dist.ReduceType.kRedSum})
tgt_result = (process_ids, [-1, -1, -1], {})
tgt_in_value = (
self._mesh.process_ids,
[-1, -1, -1],
{1: dist.ReduceType.kRedSum},
)
tgt_out_value = (self._mesh.process_ids, [-1, -1, -1], {})
self.validate(op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value)
def run_pr_to_rs_case(self):
# [Partial(), Replicate()] --> [Replicate(), Shard(1)]
# all_reduce sum + slice
main_program, dist_program = self.create_program(
[self.BATCH_SIZE, self.SEQ_LEN, self.HIDDEN_SIZE],
[dist.Partial(dist.ReduceType.kRedSum), dist.Replicate()],
[dist.Replicate(), dist.Shard(1)],
)
new_ops = dist_program.global_block().ops
new_ops_name = [op.name() for op in dist_program.global_block().ops]
check_all_reduce_sum = any(
op.name() == "pd_op.all_reduce"
and op.int_attr("reduce_type") == dist.ReduceOp.SUM
for op in dist_program.global_block().ops
)
assert check_all_reduce_sum
rank_id = dist.get_rank()
assert new_ops_name[-1] == "pd_op.slice"
# check the allreduce_sum
op = new_ops[new_ops_name.index("pd_op.all_reduce")]
if rank_id == 0 or rank_id == 2:
process_ids = [0, 2]
elif rank_id == 1 or rank_id == 3:
process_ids = [1, 3]
tgt_operand = (process_ids, [-1, -1, -1], {0: dist.ReduceType.kRedSum})
tgt_result = (process_ids, [-1, -1, -1], {})
tgt_in_value = (
self._mesh.process_ids,
[-1, -1, -1],
{0: dist.ReduceType.kRedSum},
)
tgt_out_value = (self._mesh.process_ids, [-1, -1, -1], {})
self.validate(op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value)
# check the second slice
op = new_ops[-1]
if rank_id == 0 or rank_id == 1:
process_ids = [0, 1]
elif rank_id == 2 or rank_id == 3:
process_ids = [2, 3]
tgt_operand = (process_ids, [-1, -1, -1], {})
tgt_result = (process_ids, [-1, 0, -1], {})
tgt_in_value = (self._mesh.process_ids, [-1, -1, -1], {})
tgt_out_value = (self._mesh.process_ids, [-1, 1, -1], {})
def run_pr_to_ss_case(self):
self.create_program(
[self.BATCH_SIZE, self.SEQ_LEN, self.HIDDEN_SIZE],
[dist.Partial(dist.ReduceType.kRedSum), dist.Replicate()],
[dist.Shard(0), dist.Shard(1)],
)
def run_ss_to_ss_case(self):
# [Shard(0), Shard(1)] --> [Shard(1), Shard(0)]
# all_gather+all_gather+slice+slice
main_program, dist_program = self.create_program(
[self.BATCH_SIZE, self.SEQ_LEN, self.HIDDEN_SIZE],
[dist.Shard(0), dist.Shard(1)],
[dist.Shard(1), dist.Shard(0)],
)
new_ops = dist_program.global_block().ops
new_ops_name = [op.name() for op in dist_program.global_block().ops]
all_gather_ops = []
slice_ops = []
for i, op in enumerate(new_ops):
if op.name() == "pd_op.all_gather":
all_gather_ops.append(op)
elif op.name() == "pd_op.slice":
slice_ops.append(op)
assert len(all_gather_ops) == 2
assert len(slice_ops) == 2
rank_id = dist.get_rank()
# check the first all_gather
op = all_gather_ops[0]
if rank_id == 0 or rank_id == 1:
process_ids = [0, 1]
elif rank_id == 2 or rank_id == 3:
process_ids = [2, 3]
tgt_operand = (
process_ids,
[-1, 0, -1],
{},
) # process_ids, dims_mapping, partial_status
tgt_result = (process_ids, [-1, -1, -1], {})
tgt_in_value = (self._mesh.process_ids, [0, 1, -1], {})
tgt_out_value = (self._mesh.process_ids, [0, -1, -1], {})
self.validate(op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value)
# check the second all_gather
op = all_gather_ops[1]
if rank_id == 0 or rank_id == 2:
process_ids = [0, 2]
elif rank_id == 1 or rank_id == 3:
process_ids = [1, 3]
tgt_operand = (
process_ids,
[0, -1, -1],
{},
) # process_ids, dims_mapping, partial_status
tgt_result = (process_ids, [-1, -1, -1], {})
tgt_in_value = (self._mesh.process_ids, [0, -1, -1], {})
tgt_out_value = (self._mesh.process_ids, [-1, -1, -1], {})
self.validate(op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value)
# check the first slice
op = slice_ops[0]
if rank_id == 0 or rank_id == 2:
process_ids = [0, 2]
elif rank_id == 1 or rank_id == 3:
process_ids = [1, 3]
tgt_operand = (process_ids, [-1, -1, -1], {})
tgt_result = (process_ids, [-1, 0, -1], {})
tgt_in_value = (self._mesh.process_ids, [-1, -1, -1], {})
tgt_out_value = (self._mesh.process_ids, [-1, 0, -1], {})
self.validate(op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value)
# check the second slice
op = slice_ops[1]
if rank_id == 0 or rank_id == 1:
process_ids = [0, 1]
elif rank_id == 2 or rank_id == 3:
process_ids = [2, 3]
tgt_operand = (process_ids, [-1, -1, -1], {})
tgt_result = (process_ids, [0, -1, -1], {})
tgt_in_value = (self._mesh.process_ids, [-1, 0, -1], {})
tgt_out_value = (self._mesh.process_ids, [1, 0, -1], {})
self.validate(op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value)
def run_ps_to_ps_case(self):
# [Partial(), Shard(0)] --> [Replicate(), Shard(1)]
# all_reduce sum + all_gather + slice
main_program, dist_program = self.create_program(
[self.BATCH_SIZE, self.SEQ_LEN, self.HIDDEN_SIZE],
[dist.Partial(dist.ReduceType.kRedSum), dist.Shard(0)],
[dist.Replicate(), dist.Shard(1)],
)
ops = dist_program.global_block().ops
op_names = [op.name() for op in ops]
check_all_reduce_sum = any(
op.name() == "pd_op.all_reduce"
and op.int_attr("reduce_type") == dist.ReduceOp.SUM
for op in ops
)
assert check_all_reduce_sum
assert "pd_op.all_gather" in op_names
assert "pd_op.slice" in op_names
allgather_op = ops[op_names.index("pd_op.all_gather")]
allreduce_sum_op = ops[op_names.index("pd_op.all_reduce")]
slice_op = ops[op_names.index("pd_op.slice")]
# check the allgather
rank_id = dist.get_rank()
if rank_id in [0, 1]:
process_ids = [0, 1]
elif rank_id in [2, 3]:
process_ids = [2, 3]
tgt_operand = (process_ids, [0, -1, -1], {})
tgt_result = (process_ids, [-1, -1, -1], {})
tgt_in_value = (
self._mesh.process_ids,
[1, -1, -1],
{0: dist.ReduceType.kRedSum},
)
tgt_out_value = (
self._mesh.process_ids,
[-1, -1, -1],
{0: dist.ReduceType.kRedSum},
)
self.validate(
allgather_op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value
)
# check the allreduce_sum
if rank_id in [0, 2]:
process_ids = [0, 2]
elif rank_id in [1, 3]:
process_ids = [1, 3]
tgt_operand = (process_ids, [-1, -1, -1], {0: dist.ReduceType.kRedSum})
tgt_result = (process_ids, [-1, -1, -1], {})
tgt_in_value = (
self._mesh.process_ids,
[-1, -1, -1],
{0: dist.ReduceType.kRedSum},
)
tgt_out_value = (self._mesh.process_ids, [-1, -1, -1], {})
self.validate(
allreduce_sum_op,
tgt_operand,
tgt_result,
tgt_in_value,
tgt_out_value,
)
# check the slice
if rank_id in [0, 1]:
process_ids = [0, 1]
elif rank_id in [2, 3]:
process_ids = [2, 3]
tgt_operand = (process_ids, [-1, -1, -1], {})
tgt_result = (process_ids, [-1, 0, -1], {})
tgt_in_value = (self._mesh.process_ids, [-1, -1, -1], {})
tgt_out_value = (self._mesh.process_ids, [-1, 1, -1], {})
self.validate(
slice_op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value
)
def run_test_cases(self):
self.run_pp_to_rr_case()
self.run_pr_to_rs_case()
self.run_pr_to_ss_case()
self.run_ss_to_ss_case()
self.run_ps_to_ps_case()
if __name__ == '__main__':
TestReshardNdMesh().run_test_cases()
@@ -0,0 +1,179 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import paddle
import paddle.distributed as dist
from paddle.distributed.auto_parallel.static.pir_pass import (
ReshardPasses,
)
from paddle.distributed.auto_parallel.static.utils import set_all_ops_op_role
from paddle.distributed.fleet.meta_optimizers.common import OpRole
class TestReshardNdMeshCrossMesh:
def __init__(self):
self._dtype = os.getenv("dtype")
self._seed = eval(os.getenv("seed"))
self.BATCH_SIZE = 2
self.SEQ_LEN = 4
self.HIDDEN_SIZE = 8
self._backend = os.getenv("backend")
self._mesh0 = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
self._mesh1 = dist.ProcessMesh([[4, 5], [6, 7]], dim_names=["x", "y"])
def validate(
self, op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value
):
# tgt_* are tuples, format: (process_ids, dims_mapping, partial_status)
operand_dist_attr = op.dist_attr.operand(0).as_tensor_dist_attr()
result_dist_attr = op.dist_attr.result(0).as_tensor_dist_attr()
assert operand_dist_attr.process_mesh.process_ids == tgt_operand[0]
assert operand_dist_attr.dims_mapping == tgt_operand[1]
assert operand_dist_attr.partial_status == tgt_operand[2]
assert result_dist_attr.process_mesh.process_ids == tgt_result[0]
assert result_dist_attr.dims_mapping == tgt_result[1]
assert result_dist_attr.partial_status == tgt_result[2]
in_value = op.operand_source(0)
out_value = op.result(0)
assert in_value.is_dist_dense_tensor_type()
assert out_value.is_dist_dense_tensor_type()
assert in_value.dist_attr().process_mesh.process_ids == tgt_in_value[0]
assert in_value.dist_attr().dims_mapping == tgt_in_value[1]
assert in_value.dist_attr().partial_status == tgt_in_value[2]
assert (
out_value.dist_attr().process_mesh.process_ids == tgt_out_value[0]
)
assert out_value.dist_attr().dims_mapping == tgt_out_value[1]
assert out_value.dist_attr().partial_status == tgt_out_value[2]
def create_program(self, input_shape, input_placements, output_placements):
paddle.enable_static()
if self._backend == "cpu":
paddle.set_device("cpu")
place = paddle.CPUPlace()
elif self._backend == "gpu":
place = paddle.CUDAPlace(dist.get_rank())
with paddle.pir_utils.IrGuard():
main_program = paddle.base.Program()
with paddle.base.program_guard(main_program):
input = paddle.ones(name='input', shape=input_shape)
dist_input = dist.shard_tensor(
input, self._mesh0, input_placements
)
dist_out = paddle._C_ops.reshard(
dist_input, self._mesh1, output_placements
)
dist_program = main_program.clone()
set_all_ops_op_role(dist_program.global_block(), OpRole.Forward)
ReshardPasses.apply_reshard_pass(dist_program)
return main_program, dist_program
def run_pp_to_rr_case(self):
# [Partial(), Partial()] --> [Replicate(), Replicate()]
# ops: all_reduce sum + all_reduce sum
main_program, dist_program = self.create_program(
[self.BATCH_SIZE, self.SEQ_LEN, self.HIDDEN_SIZE],
[
dist.Partial(dist.ReduceType.kRedSum),
dist.Partial(dist.ReduceType.kRedSum),
],
[dist.Replicate(), dist.Replicate()],
)
new_ops = dist_program.global_block().ops
old_ops_name = [op.name() for op in main_program.global_block().ops]
new_ops_name = [op.name() for op in dist_program.global_block().ops]
rank_id = dist.get_rank()
if rank_id in self._mesh0.process_ids:
assert dist_program.global_block().ops[2].name() == "pd_op.send_v2"
else:
assert dist_program.global_block().ops[2].name() == "pd_op.recv_v2"
assert (
dist_program.global_block().ops[-2].name() == "pd_op.all_reduce"
)
assert (
dist_program.global_block().ops[-2].int_attr("reduce_type")
== dist.ReduceOp.SUM
)
assert (
dist_program.global_block().ops[-1].name() == "pd_op.all_reduce"
)
assert (
dist_program.global_block().ops[-1].int_attr("reduce_type")
== dist.ReduceOp.SUM
)
# check the first allreduce_sum
op = new_ops[-2]
if rank_id == 4 or rank_id == 6:
process_ids = [4, 6]
elif rank_id == 5 or rank_id == 7:
process_ids = [5, 7]
tgt_operand = (
process_ids,
[-1, -1, -1],
{0: dist.ReduceType.kRedSum},
)
tgt_result = (process_ids, [-1, -1, -1], {})
tgt_in_value = (
self._mesh1.process_ids,
[-1, -1, -1],
{0: dist.ReduceType.kRedSum, 1: dist.ReduceType.kRedSum},
)
tgt_out_value = (
self._mesh1.process_ids,
[-1, -1, -1],
{1: dist.ReduceType.kRedSum},
)
self.validate(
op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value
)
# check the second allreduce_sum
op = new_ops[-1]
if rank_id == 4 or rank_id == 5:
process_ids = [4, 5]
elif rank_id == 6 or rank_id == 7:
process_ids = [6, 7]
tgt_operand = (
process_ids,
[-1, -1, -1],
{0: dist.ReduceType.kRedSum},
)
tgt_result = (process_ids, [-1, -1, -1], {})
tgt_in_value = (
self._mesh1.process_ids,
[-1, -1, -1],
{1: dist.ReduceType.kRedSum},
)
tgt_out_value = (self._mesh1.process_ids, [-1, -1, -1], {})
self.validate(
op, tgt_operand, tgt_result, tgt_in_value, tgt_out_value
)
def run_test_cases(self):
self.run_pp_to_rr_case()
if __name__ == '__main__':
TestReshardNdMeshCrossMesh().run_test_cases()
@@ -0,0 +1,138 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 paddle
import paddle.distributed as dist
class TestProcessMesh:
def init_dist_env(self):
dist.init_parallel_env()
paddle.seed(2025)
def test_get_submesh_with_dim(self):
curr_rank = dist.get_rank()
# Test 2D mesh
mesh_2d = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["dp", "tp"])
# Test case 1: Get submesh for dp dimension
dp_mesh = mesh_2d.get_submesh_with_dim("dp")
dp_mesh_ = mesh_2d["dp"]
assert dp_mesh == dp_mesh_
if curr_rank == 0:
assert dp_mesh.process_ids == [0, 2]
elif curr_rank == 1:
assert dp_mesh.process_ids == [1, 3]
# Test case 2: Get submesh for tp dimension
tp_mesh = mesh_2d.get_submesh_with_dim("tp")
tp_mesh_ = mesh_2d["tp"]
assert tp_mesh == tp_mesh_
if curr_rank == 0:
assert tp_mesh.process_ids == [0, 1]
elif curr_rank == 1:
assert tp_mesh.process_ids == [0, 1]
# Test case 3: 3D mesh with 8 cards (2x2x2)
mesh_3d = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=["pp", "dp", "tp"]
)
# Test each dimension
pp_mesh = mesh_3d.get_submesh_with_dim("pp")
pp_mesh_ = mesh_3d["pp"]
assert pp_mesh == pp_mesh_
dp_mesh = mesh_3d.get_submesh_with_dim("dp")
dp_mesh_ = mesh_3d["dp"]
assert dp_mesh == dp_mesh_
tp_mesh = mesh_3d.get_submesh_with_dim("tp")
tp_mesh_ = mesh_3d["tp"]
assert tp_mesh == tp_mesh_
# Verify pp dimension results
if curr_rank == 0:
assert pp_mesh.process_ids == [0, 4]
elif curr_rank == 1:
assert pp_mesh.process_ids == [1, 5]
# Verify dp dimension results
if curr_rank == 0:
assert dp_mesh.process_ids == [0, 2]
elif curr_rank == 1:
assert dp_mesh.process_ids == [1, 3]
# Verify tp dimension results
if curr_rank == 0:
assert tp_mesh.process_ids == [0, 1]
elif curr_rank == 1:
assert tp_mesh.process_ids == [0, 1]
# Test case 4: When rank is not in the mesh
mesh_small = dist.ProcessMesh([0, 1], dim_names=["x"])
if curr_rank not in [0, 1]:
assert mesh_small.get_submesh_with_dim("x") is None
def test_get_group(self):
curr_rank = dist.get_rank()
# Test case 1: Single dimension mesh without dim_name
mesh_1d = dist.ProcessMesh([0, 1], dim_names=["x"])
if curr_rank in [0, 1]:
group_1d = mesh_1d.get_group()
assert isinstance(group_1d, dist.communication.group.Group)
# Test case 2: Single dimension mesh with correct dim_name
group_1d_with_name = mesh_1d.get_group(dim_name="x")
assert isinstance(
group_1d_with_name, dist.communication.group.Group
)
assert group_1d_with_name.id == group_1d.id
# Test case 3: Single dimension mesh with wrong dim_name
try:
mesh_1d.get_group(dim_name="wrong_name")
raise AssertionError("Should raise ValueError")
except ValueError:
pass
# Test case 4: Multi-dimension mesh
mesh_2d = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["dp", "tp"])
if curr_rank in [0, 1, 2, 3]:
# Test without dim_name
try:
mesh_2d.get_group()
raise AssertionError("Should raise ValueError")
except ValueError:
pass
# Test with correct dim_name
group_2d = mesh_2d.get_group(dim_name="dp")
assert isinstance(group_2d, dist.communication.group.Group)
# Test with wrong dim_name
try:
mesh_2d.get_group(dim_name="wrong_name")
raise AssertionError("Should raise ValueError")
except ValueError:
pass
def test_process_mesh(self):
self.init_dist_env()
self.test_get_submesh_with_dim()
self.test_get_group()
if __name__ == '__main__':
TestProcessMesh().test_process_mesh()
@@ -0,0 +1,100 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 os
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.distributed.flex_checkpoint.dcp.load_state_dict import (
load_state_dict,
)
class HuggingFaceModel(nn.Layer):
def __init__(self):
super().__init__()
self.huggingface = nn.Linear(2, 2, bias_attr=False)
class FCModel(nn.Layer):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(1, 2, bias_attr=False)
self.fc2 = nn.Linear(1, 2, bias_attr=False)
def init_hf_model_weights(model):
with paddle.no_grad():
w = paddle.to_tensor([[0, 1], [2, 3]], dtype="float16")
model.huggingface.weight.set_value(w)
def save_safetensors_model(model, ckpt_path):
import safetensors.numpy
os.makedirs(ckpt_path, exist_ok=True)
weight_np = model.huggingface.weight.numpy()
file_path = os.path.join(ckpt_path, "tensor1.safetensors")
safetensors.numpy.save_file({"huggingface.weight": weight_np}, file_path)
def test_save_load_with_aoa_config_reverse():
ckpt_path = os.getenv("ckpt_path")
dist.init_parallel_env()
hf_model = HuggingFaceModel()
fc_model = FCModel()
hf_model = paddle.amp.decorate(
models=hf_model, optimizers=None, level="O2", dtype="float16"
)
init_hf_model_weights(hf_model)
save_safetensors_model(hf_model, ckpt_path)
aoa_statements = [
"huggingface.weight -> A,B ,axis = 1 \n",
"A^T -> A \n",
"B^T -> B \n",
"A -> fc1.weight ,src_dtype = 'float16', dst_dtype = 'float32' \n",
"B -> fc2.weight ,src_dtype = 'float16', dst_dtype = 'float32' \n",
]
aoa_config = {"aoa_statements": aoa_statements}
load_state_dict(
fc_model.sharded_state_dict(),
ckpt_path,
safetensors=True,
aoa_config=aoa_config,
)
full = paddle.to_tensor([[0, 1], [2, 3]], dtype="float32")
A = full[:, 0].unsqueeze(0)
B = full[:, 1].unsqueeze(0)
assert paddle.allclose(fc_model.fc1.weight, A)
assert paddle.allclose(fc_model.fc2.weight, B)
aoa_config["aoa_config_reverse"] = True
itr = fc_model.full(aoa_config=aoa_config)
full_param = dict(itr)
(full_weight_k, full_weight_v) = next(iter(full_param.items()))
assert full_weight_k == "huggingface.weight"
assert paddle.allclose(full_weight_v, hf_model.huggingface.weight)
if __name__ == "__main__":
test_save_load_with_aoa_config_reverse()
@@ -0,0 +1,223 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 json
import os
import paddle
import paddle.distributed as dist
from paddle.distributed.flex_checkpoint.dcp.sharded_weight import ShardedWeight
def get_global_tensors():
"""Create fixed test tensors for verification."""
# tensor1: [[0, 1], [2, 3]]
tensor1 = paddle.to_tensor([[0, 1], [2, 3]], dtype='float32')
# tensor2: [[4, 5], [6, 7]]
tensor2 = paddle.to_tensor([[4, 5], [6, 7]], dtype='float32')
return {"tensor1": tensor1, "tensor2": tensor2}
def save_safetensors_to_ranks(ckpt_path):
"""Save tensors to different ranks as safetensors files."""
import safetensors.numpy
global_tensors = get_global_tensors()
if dist.get_rank() == 0:
os.makedirs(ckpt_path, exist_ok=True)
file_path = os.path.join(ckpt_path, "tensor1.safetensors")
tensor1_np = global_tensors["tensor1"].numpy()
safetensors.numpy.save_file({"tensor1": tensor1_np}, file_path)
elif dist.get_rank() == 1:
os.makedirs(ckpt_path, exist_ok=True)
file_path = os.path.join(ckpt_path, "tensor2.safetensors")
tensor2_np = global_tensors["tensor2"].numpy()
safetensors.numpy.save_file({"tensor2": tensor2_np}, file_path)
dist.barrier()
def create_sharded_state_dict_for_loading():
"""Create sharded state dict for tp loading."""
sharded_state_dict = {}
if dist.get_rank() == 0:
local_tensor1 = paddle.zeros([2, 1], dtype='float32')
sharded_weight1 = ShardedWeight(
key="tensor1",
local_tensor=local_tensor1,
local_shape=(2, 1),
global_shape=(2, 2),
global_offset=(0, 0),
is_flattened=False,
)
sharded_state_dict["tensor1"] = sharded_weight1
local_tensor2 = paddle.zeros([2, 1], dtype='float32')
sharded_weight2 = ShardedWeight(
key="tensor2",
local_tensor=local_tensor2,
local_shape=(2, 1),
global_shape=(2, 2),
global_offset=(0, 0),
is_flattened=False,
)
sharded_state_dict["tensor2"] = sharded_weight2
elif dist.get_rank() == 1:
local_tensor1 = paddle.zeros([2, 1], dtype='float32')
sharded_weight1 = ShardedWeight(
key="tensor1",
local_tensor=local_tensor1,
local_shape=(2, 1),
global_shape=(2, 2),
global_offset=(0, 1),
is_flattened=False,
)
sharded_state_dict["tensor1"] = sharded_weight1
local_tensor2 = paddle.zeros([2, 1], dtype='float32')
sharded_weight2 = ShardedWeight(
key="tensor2",
local_tensor=local_tensor2,
local_shape=(2, 1),
global_shape=(2, 2),
global_offset=(0, 1),
is_flattened=False,
)
sharded_state_dict["tensor2"] = sharded_weight2
return sharded_state_dict
def test_save_safetensors_load_fc():
"""Test saving safetensors and loading with flex checkpoint."""
ckpt_path = os.getenv("ckpt_path")
dist.init_parallel_env()
save_safetensors_to_ranks(ckpt_path)
sharded_state_dict = create_sharded_state_dict_for_loading()
from paddle.distributed.flex_checkpoint.dcp.load_state_dict import (
load_state_dict,
)
load_state_dict(sharded_state_dict, ckpt_path, safetensors=True)
loaded_tensor1 = sharded_state_dict["tensor1"].local_tensor
loaded_tensor2 = sharded_state_dict["tensor2"].local_tensor
if dist.get_rank() == 0:
# Rank 0 should have first column of both tensors
# tensor1: [[0], [2]] (first column)
# tensor2: [[4], [6]] (first column)
expected_tensor1 = paddle.to_tensor([[0], [2]], dtype='float32')
expected_tensor2 = paddle.to_tensor([[4], [6]], dtype='float32')
assert paddle.allclose(loaded_tensor1, expected_tensor1), (
f"Rank 0 tensor1 mismatch: got {loaded_tensor1}, expected {expected_tensor1}"
)
assert paddle.allclose(loaded_tensor2, expected_tensor2), (
f"Rank 0 tensor2 mismatch: got {loaded_tensor2}, expected {expected_tensor2}"
)
elif dist.get_rank() == 1:
# Rank 1 should have second column of both tensors
# tensor1: [[1], [3]] (second column)
# tensor2: [[5], [7]] (second column)
expected_tensor1 = paddle.to_tensor([[1], [3]], dtype='float32')
expected_tensor2 = paddle.to_tensor([[5], [7]], dtype='float32')
assert paddle.allclose(loaded_tensor1, expected_tensor1), (
f"Rank 1 tensor1 mismatch: got {loaded_tensor1}, expected {expected_tensor1}"
)
assert paddle.allclose(loaded_tensor2, expected_tensor2), (
f"Rank 1 tensor2 mismatch: got {loaded_tensor2}, expected {expected_tensor2}"
)
dist.barrier()
def create_index_json(ckpt_path):
"""Create model.safetensors.index.json that maps keys to their safetensors files."""
index_data = {
"weight_map": {
"tensor1": "tensor1.safetensors",
"tensor2": "tensor2.safetensors",
}
}
index_file_path = os.path.join(ckpt_path, "model.safetensors.index.json")
if dist.get_rank() == 0:
with open(index_file_path, "w") as f:
json.dump(index_data, f)
dist.barrier()
def test_save_safetensors_load_fc_with_index():
"""Test saving safetensors and loading with flex checkpoint when model.safetensors.index.json exists."""
ckpt_path = os.getenv("ckpt_path")
dist.init_parallel_env()
save_safetensors_to_ranks(ckpt_path)
# Create index json to exercise the integrity check branch
create_index_json(ckpt_path)
sharded_state_dict = create_sharded_state_dict_for_loading()
from paddle.distributed.flex_checkpoint.dcp.load_state_dict import (
load_state_dict,
)
load_state_dict(sharded_state_dict, ckpt_path, safetensors=True)
loaded_tensor1 = sharded_state_dict["tensor1"].local_tensor
loaded_tensor2 = sharded_state_dict["tensor2"].local_tensor
if dist.get_rank() == 0:
expected_tensor1 = paddle.to_tensor([[0], [2]], dtype='float32')
expected_tensor2 = paddle.to_tensor([[4], [6]], dtype='float32')
assert paddle.allclose(loaded_tensor1, expected_tensor1), (
f"Rank 0 tensor1 mismatch: got {loaded_tensor1}, expected {expected_tensor1}"
)
assert paddle.allclose(loaded_tensor2, expected_tensor2), (
f"Rank 0 tensor2 mismatch: got {loaded_tensor2}, expected {expected_tensor2}"
)
elif dist.get_rank() == 1:
expected_tensor1 = paddle.to_tensor([[1], [3]], dtype='float32')
expected_tensor2 = paddle.to_tensor([[5], [7]], dtype='float32')
assert paddle.allclose(loaded_tensor1, expected_tensor1), (
f"Rank 1 tensor1 mismatch: got {loaded_tensor1}, expected {expected_tensor1}"
)
assert paddle.allclose(loaded_tensor2, expected_tensor2), (
f"Rank 1 tensor2 mismatch: got {loaded_tensor2}, expected {expected_tensor2}"
)
dist.barrier()
if __name__ == "__main__":
test_func = os.getenv("test_func", "test_save_safetensors_load_fc")
if test_func == "test_save_safetensors_load_fc_with_index":
test_save_safetensors_load_fc_with_index()
else:
test_save_safetensors_load_fc()
@@ -0,0 +1,308 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
from functools import reduce
import numpy as np
from semi_auto_parallel_llama_model import (
LlamaForCausalLMAuto,
LlamaPretrainingCriterionAuto,
get_mesh,
)
import paddle
import paddle.distributed as dist
from paddle import LazyGuard
from paddle.io import BatchSampler, DataLoader, Dataset
class Config:
vocab_size = 32000
hidden_size = 4096
intermediate_size = 11008
max_position_embeddings = 2048
seq_length = 2048
num_hidden_layers = 2
num_attention_heads = 32
num_key_value_heads = 32
initializer_range = 0.02
rms_norm_eps = 1e-6
use_cache = True
use_flash_attention = False
sequence_parallel = False
rope = True
recompute = False
recompute_granularity = None
use_lazy_init = False
class RandomDataset(Dataset):
def __init__(self, seq_len, num_samples=100):
super().__init__()
self.seq_len = seq_len
self.num_samples = num_samples
def __getitem__(self, index):
input = np.random.uniform(size=[self.seq_len]).astype("int64")
label = (np.random.uniform(size=[self.seq_len]) * 10).astype("int64")
return input, label
def __len__(self):
return self.num_samples
def create_optimizer(model, lr_scheduler):
decay_parameters = [
p.name
for n, p in model.named_parameters()
if not any(nd in n for nd in ["bias", "norm"])
]
def apply_decay_param_fun(x):
return x in decay_parameters
# test global_clip in auto_parallel
if os.getenv("use_param_group") == "true":
param_group = {}
param_group["params"] = list(model.parameters())
param_group["weight_decay"] = 0.01
param_group["grad_clip"] = paddle.nn.ClipGradByGlobalNorm(1.0)
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=[param_group],
)
else:
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=model.parameters(),
weight_decay=0.01,
grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0),
)
return optimizer
class TestLlamaAuto:
def __init__(self):
self.config = Config()
self.dp = int(os.getenv("dp"))
self.mp = int(os.getenv("mp"))
self.pp = int(os.getenv("pp"))
self.sep = int(os.getenv("sep", "1"))
if os.getenv("use_sp") == "true":
self.config.sequence_parallel = True
if os.getenv("recompute") == "true":
self.config.recompute = True
self.config.recompute_granularity = os.getenv("recompute_granularity")
if os.getenv("use_lazy_init") == "true":
self.config.use_lazy_init = True
self.gradient_accumulation_steps = int(os.getenv("acc_step"))
self.amp = False
self.amp_dtype = "float16"
self.amp_level = "O1"
self.amp_master_grad = False
if os.getenv("amp") == "true":
self.amp = True
if os.getenv("amp_dtype") in ["float16", "bfloat16"]:
self.amp_dtype = os.getenv("amp_dtype")
if os.getenv("amp_level") in ["O0", "O1", "O2"]:
self.amp_level = os.getenv("amp_level")
if os.getenv("amp_master_grad") == "true":
self.amp_master_grad = True
self.config.tensor_parallel_degree = self.mp
self.config.pipeline_parallel_degree = self.pp
self.config.context_parallel_degree = 1
self.config.sep_parallel_degree = 1
if os.getenv("context_parallel", "false") == "true":
self.config.context_parallel_degree = self.sep
self.config.use_flash_attention = True
dist.init_parallel_env()
if os.getenv("sep_parallel", "false") == "true":
self.config.sep_parallel_degree = self.sep
if self.sep > 1:
# only one of the context_parallel and sep_parallel can be True
assert (
self.config.sep_parallel_degree
!= self.config.context_parallel_degree
), (
f"only one of the context_parallel and sep_parallel can be True, but get context_parallel_degree = {self.config.context_parallel_degree} and sep_parallel_degree = {self.config.sep_parallel_degree}, please check your env"
)
self.init_dist_env()
def init_dist_env(self):
order = ["dp", "pp", "mp"]
dp_degree = self.dp
mp_degree = self.mp
pp_degree = self.pp
degree = [dp_degree, pp_degree, mp_degree]
mesh_dims = list(filter(lambda x: x[1] > 1, list(zip(order, degree))))
if not mesh_dims:
mesh_dims = [("dp", 1)]
dim_names = [mesh_dim[0] for mesh_dim in mesh_dims]
mesh_shape = [mesh_dim[1] for mesh_dim in mesh_dims]
mesh_arr = np.arange(
0, reduce(lambda x, y: x * y, mesh_shape, 1)
).reshape(mesh_shape)
global_mesh = dist.ProcessMesh(mesh_arr, dim_names)
dist.auto_parallel.set_mesh(global_mesh)
def run_llama(self, to_static=0):
if self.config.use_lazy_init:
with LazyGuard():
model = LlamaForCausalLMAuto(self.config)
for param in model.parameters():
assert not param._is_initialized()
param.initialize()
else:
model = LlamaForCausalLMAuto(self.config)
criterion = LlamaPretrainingCriterionAuto(self.config)
lr_scheduler = paddle.optimizer.lr.LinearWarmup(
learning_rate=0.0001, warmup_steps=2, start_lr=0, end_lr=0.0001
)
optimizer = create_optimizer(model, lr_scheduler)
if self.amp and not to_static:
model, optimizer = paddle.amp.decorate(
models=model,
optimizers=optimizer,
level=self.amp_level,
dtype=self.amp_dtype,
master_grad=self.amp_master_grad,
)
optimizer = dist.shard_optimizer(optimizer)
train_dataset = RandomDataset(self.config.seq_length)
train_sampler = BatchSampler(
train_dataset,
batch_size=2,
shuffle=True,
drop_last=True,
)
train_dataloader = DataLoader(
train_dataset,
batch_sampler=train_sampler,
num_workers=0,
)
if self.pp == 1:
meshes = [get_mesh(0)]
elif self.pp > 1:
meshes = [get_mesh(0), get_mesh(-1)]
else:
raise ValueError("pp should be greater or equal to 1")
dist_loader = dist.shard_dataloader(
dataloader=train_dataloader,
meshes=meshes,
shard_dims="dp",
)
global_step = 1
tr_loss = float(0)
if not to_static:
model.train()
scaler = None
if self.amp and self.amp_dtype == "float16":
scaler = paddle.amp.GradScaler(init_loss_scaling=1024)
scaler = dist.shard_scaler(scaler)
for epoch_idx in range(1):
for step, inputs in enumerate(dist_loader()):
input_ids, labels = inputs
custom_black_list = [
"reduce_sum",
"c_softmax_with_cross_entropy",
]
custom_white_list = []
if self.amp_level == "O2":
custom_white_list.extend(
["lookup_table", "lookup_table_v2"]
)
with paddle.amp.auto_cast(
self.amp,
custom_black_list=set(custom_black_list),
custom_white_list=set(custom_white_list),
level=self.amp_level,
dtype=self.amp_dtype,
):
logits = model(input_ids)
tr_loss_step = criterion(logits, labels)
if self.gradient_accumulation_steps > 1:
tr_loss_step /= self.gradient_accumulation_steps
if scaler is not None:
scaler.scale(tr_loss_step).backward()
else:
tr_loss_step.backward()
tr_loss += tr_loss_step
if global_step % self.gradient_accumulation_steps == 0:
print(
f"step: {global_step // self.gradient_accumulation_steps} loss: {tr_loss.numpy()}"
)
if scaler is not None:
scaler.step(optimizer)
scaler.update()
else:
optimizer.step()
optimizer.clear_grad()
lr_scheduler.step()
tr_loss = 0
global_step += 1
if global_step // self.gradient_accumulation_steps >= 10:
break
else:
strategy = dist.Strategy()
if self.gradient_accumulation_steps > 1:
strategy.pipeline.accumulate_steps = (
self.gradient_accumulation_steps
)
if self.amp:
amp = strategy.amp
amp.enable = self.amp
amp.dtype = self.amp_dtype
amp.level = self.amp_level.lower()
if self.amp_master_grad:
amp.use_master_grad = True
dist_model = dist.to_static(
model,
dist_loader,
criterion,
optimizer,
strategy=strategy,
)
dist_model.train()
for step, inputs in enumerate(dist_loader()):
input_ids, labels = inputs
loss = dist_model(input_ids, labels)
print(step, loss)
if step >= 10:
break
def run_test_cases(self):
self.run_llama(to_static=0)
self.run_llama(to_static=1)
if __name__ == '__main__':
TestLlamaAuto().run_test_cases()
@@ -0,0 +1,419 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
os.environ["FLAGS_enable_pir_api"] = str(1)
import random
from functools import reduce
import numpy as np
from semi_auto_parallel_llama_model import (
LlamaForCausalLMAuto,
LlamaPretrainingCriterionAuto,
get_mesh,
)
import paddle
import paddle.distributed as dist
from paddle.io import BatchSampler, DataLoader, Dataset
class Config:
vocab_size = 320
hidden_size = 8
intermediate_size = 64
max_position_embeddings = 8
seq_length = 8
num_hidden_layers = 2
num_attention_heads = 2
num_key_value_heads = 2
initializer_range = 0.02
rms_norm_eps = 1e-6
use_cache = True
use_flash_attention = False
sequence_parallel = False
rope = True
class RandomDataset(Dataset):
def __init__(self, seq_len, num_samples=100):
super().__init__()
self.seq_len = seq_len
self.num_samples = num_samples
def __getitem__(self, index):
input = np.full([self.seq_len], index, dtype="int64")
label = np.array([index] * self.seq_len)
return input, label
def __len__(self):
return self.num_samples
def create_optimizer(model, lr_scheduler):
decay_parameters = [
p.name
for n, p in model.named_parameters()
if not any(nd in n for nd in ["bias", "norm"])
]
def apply_decay_param_fun(x):
return x in decay_parameters
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=model.parameters(),
weight_decay=0.01,
grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0),
)
return optimizer
class TestLlamaAuto:
def __init__(self):
self.config = Config()
self.dp = int(os.getenv("dp"))
self.mp = int(os.getenv("mp"))
self.pp = int(os.getenv("pp"))
self.sep = int(os.getenv("sep", "1"))
if os.getenv("use_sp") == "true":
self.config.sequence_parallel = True
if os.getenv("seq_length"):
self.config.seq_length = int(os.getenv("seq_length"))
if os.getenv("hidden_size"):
self.config.hidden_size = int(os.getenv("hidden_size"))
if os.getenv("num_attention_heads"):
self.config.num_attention_heads = int(
os.getenv("num_attention_heads")
)
if os.getenv("num_key_value_heads"):
self.config.num_key_value_heads = int(
os.getenv("num_key_value_heads")
)
if os.getenv("max_position_embeddings"):
self.config.max_position_embeddings = int(
os.getenv("max_position_embeddings")
)
self.strategy = dist.Strategy()
# amp config
amp = self.strategy._amp
if os.getenv("amp"):
amp.enable = True if os.getenv("amp") == "true" else False
if os.getenv("amp_dtype"):
amp.dtype = os.getenv("amp_dtype")
if os.getenv("amp_level"):
amp.level = os.getenv("amp_level")
if os.getenv("amp_master_grad"):
amp.use_master_grad = (
True if os.getenv("amp_master_grad") == "true" else False
)
if os.getenv("scale_loss"):
amp.init_loss_scaling = os.getenv("scale_loss")
if os.getenv("amp_custom_black_list"):
amp.custom_black_list = os.getenv("amp_custom_black_list")
if os.getenv("amp_custom_white_list"):
amp.custom_white_list = os.getenv("amp_custom_white_list")
self.gradient_accumulation_steps = 1
if os.getenv("acc_step"):
self.gradient_accumulation_steps = int(os.getenv("acc_step"))
if self.gradient_accumulation_steps > 1:
self.strategy.gradient_merge.enable = True
self.strategy.gradient_merge.k_steps = (
self.gradient_accumulation_steps
)
self.strategy.gradient_merge.avg = False
self.config.recompute = False
self.config.tensor_parallel_degree = self.mp
self.config.pipeline_parallel_degree = self.pp
self.config.context_parallel_degree = 1
self.config.sep_parallel_degree = 1
if os.getenv("context_parallel", "false") == "true":
self.config.context_parallel_degree = self.sep
self.config.use_flash_attention = True
dist.init_parallel_env()
if os.getenv("sep_parallel", "false") == "true":
self.config.sep_parallel_degree = self.sep
if self.sep > 1:
# only one of the context_parallel and sep_parallel can be True
assert (
self.config.sep_parallel_degree
!= self.config.context_parallel_degree
), (
f"only one of the context_parallel and sep_parallel can be True, but get context_parallel_degree = {self.config.context_parallel_degree} and sep_parallel_degree = {self.config.sep_parallel_degree}, please check your env"
)
self.run_step = 10
self.run_step_dy2static = (
self.run_step // self.gradient_accumulation_steps
)
def run_llama(self, to_static=0):
# model
model = LlamaForCausalLMAuto(self.config)
criterion = LlamaPretrainingCriterionAuto(self.config)
if self.strategy._amp.enable and self.strategy._amp.level == "O2":
paddle.amp.decorate(
models=model,
level=self.strategy._amp.level,
dtype=self.strategy._amp.dtype,
master_grad=self.strategy._amp.use_master_grad,
)
# optimizer
lr_scheduler = paddle.optimizer.lr.LinearWarmup(
learning_rate=0.0001, warmup_steps=2, start_lr=0, end_lr=0.0001
)
optimizer = create_optimizer(model, lr_scheduler)
optimizer = dist.shard_optimizer(optimizer)
# dataloader
train_dataset = RandomDataset(self.config.seq_length)
train_sampler = BatchSampler(
train_dataset,
batch_size=4 if self.sep > 1 else 2,
shuffle=True,
drop_last=True,
)
train_dataloader = DataLoader(
train_dataset,
batch_sampler=train_sampler,
num_workers=0,
)
dist_loader = dist.shard_dataloader(
dataloader=train_dataloader,
meshes=[get_mesh(0), get_mesh(1)],
shard_dims="dp",
)
if to_static:
model = dist.to_static(
model, dist_loader, criterion, optimizer, strategy=self.strategy
)
model.train()
losses = []
for step, inputs in enumerate(dist_loader()):
if step >= self.run_step:
break
input_ids, labels = inputs
if to_static:
loss = model(input_ids, labels)
if loss is None:
numpy_array = np.array([])
else:
numpy_array = np.array(loss)
losses.append(numpy_array)
array_bytes = numpy_array.tobytes()
else:
logits = model(input_ids)
loss = criterion(logits, labels)
losses.append(np.array(loss))
loss.backward()
optimizer.step()
optimizer.clear_grad()
lr_scheduler.step()
return losses
def init_dist_env(self):
order = ["dp", "pp", "mp", "sep"]
dp_degree = self.dp
mp_degree = self.mp
pp_degree = self.pp
sep_degree = self.sep
degree = [dp_degree, pp_degree, mp_degree, sep_degree]
mesh_dims = list(filter(lambda x: x[1] > 1, list(zip(order, degree))))
if not mesh_dims:
mesh_dims = [("dp", 1)]
dim_names = [mesh_dim[0] for mesh_dim in mesh_dims]
mesh_shape = [mesh_dim[1] for mesh_dim in mesh_dims]
mesh_arr = np.arange(
0, reduce(lambda x, y: x * y, mesh_shape, 1)
).reshape(mesh_shape)
global_mesh = dist.ProcessMesh(mesh_arr, dim_names)
dist.auto_parallel.set_mesh(global_mesh)
paddle.seed(1024)
np.random.seed(1024)
random.seed(1024)
def run_dynamic(self):
model = LlamaForCausalLMAuto(self.config)
criterion = LlamaPretrainingCriterionAuto(self.config)
lr_scheduler = paddle.optimizer.lr.LinearWarmup(
learning_rate=0.0001, warmup_steps=2, start_lr=0, end_lr=0.0001
)
optimizer = create_optimizer(model, lr_scheduler)
train_dataset = RandomDataset(self.config.seq_length)
train_sampler = BatchSampler(
train_dataset,
batch_size=4 if self.sep > 1 else 2,
shuffle=False,
drop_last=True,
)
train_dataloader = DataLoader(
train_dataset,
batch_sampler=train_sampler,
num_workers=0,
)
dist_loader = dist.shard_dataloader(
dataloader=train_dataloader,
meshes=[get_mesh(0), get_mesh(1)],
shard_dims="dp",
)
tr_loss = float(0)
tr_loss_add = float(0)
model.train()
#####
for step, inputs in enumerate(dist_loader()):
if step >= self.run_step:
break
input_ids, labels = inputs
logits = model(input_ids)
tr_loss_step = criterion(logits, labels)
tr_loss_step.backward()
tr_loss_add += tr_loss_step
if int(dist.get_rank()) in [2, 3, 6, 7]:
assert tr_loss_step._is_initialized()
else:
assert not tr_loss_step._is_initialized()
if (step + 1) % self.gradient_accumulation_steps == 0:
tr_loss_add /= self.gradient_accumulation_steps
tr_loss = tr_loss_add
optimizer.step()
optimizer.clear_grad()
lr_scheduler.step()
tr_loss_add = 0
return np.array(tr_loss)
def run_dy2static(self):
model = LlamaForCausalLMAuto(self.config)
criterion = LlamaPretrainingCriterionAuto(self.config)
lr_scheduler = paddle.optimizer.lr.LinearWarmup(
learning_rate=0.0001, warmup_steps=2, start_lr=0, end_lr=0.0001
)
optimizer = create_optimizer(model, lr_scheduler)
train_dataset = RandomDataset(self.config.seq_length)
train_sampler = BatchSampler(
train_dataset,
batch_size=(
4 * self.gradient_accumulation_steps
if self.sep > 1
else 2 * self.gradient_accumulation_steps
),
shuffle=False,
drop_last=True,
)
train_dataloader = DataLoader(
train_dataset,
batch_sampler=train_sampler,
num_workers=0,
)
dist_loader = dist.shard_dataloader(
dataloader=train_dataloader,
meshes=[get_mesh(0), get_mesh(1)],
shard_dims="dp",
)
strategy = None
if self.gradient_accumulation_steps > 1:
strategy = dist.Strategy()
strategy.gradient_merge.enable = True
strategy.gradient_merge.k_steps = self.gradient_accumulation_steps
strategy.gradient_merge.avg = False
dist_model = dist.to_static(
model, dist_loader, criterion, optimizer, strategy=strategy
)
dist_model.train()
loss = None
for step, inputs in enumerate(dist_loader()):
if step >= self.run_step_dy2static:
break
input_ids, labels = inputs
loss = dist_model(input_ids, labels)
lr_scheduler.step()
if int(dist.get_rank()) in [2, 3, 6, 7]:
assert loss is not None
else:
assert loss is None
if loss is not None:
loss = np.average(loss)
return np.array(loss)
def run_test_cases(self):
self.init_dist_env()
# context parallel with flash_attn backend not support CPU, not support float32
# flash_attn only support Cuda Compute Capability >= 8 and cuda version >= 11
if self.config.context_parallel_degree > 1 and (
os.getenv("backend") != "gpu"
or not self.strategy._amp.enable
or int(paddle.version.cuda().split(".")[0]) < 11
or paddle.device.cuda.get_device_capability()[0] < 8
):
return
if self.gradient_accumulation_steps > 1:
dy_losses = self.run_dynamic()
# context parallel not support static mode
if self.sep > 1:
return
self.init_dist_env()
st_losses = self.run_dy2static()
if int(dist.get_rank()) in [2, 3, 6, 7]:
np.testing.assert_allclose(dy_losses, st_losses, atol=1e-7)
else:
dy_losses = self.run_llama(to_static=0)
# context parallel not support static mode
if self.sep > 1:
return
self.init_dist_env()
st_losses = self.run_llama(to_static=1)
assert len(dy_losses) == len(st_losses)
for idx in range(len(dy_losses)):
np.testing.assert_allclose(
dy_losses[idx], st_losses[idx], atol=1e-7
)
if __name__ == '__main__':
TestLlamaAuto().run_test_cases()
@@ -0,0 +1,281 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
from functools import reduce
import numpy as np
from semi_auto_parallel_llama_model import (
LlamaForCausalLMAuto,
LlamaPretrainingCriterionAuto,
get_mesh,
)
import paddle
import paddle.distributed as dist
from paddle import LazyGuard
from paddle.io import BatchSampler, DataLoader, Dataset
np.random.seed(2024)
class Config:
vocab_size = 32000
hidden_size = 4096
intermediate_size = 11008
max_position_embeddings = 2048
seq_length = 2048
num_hidden_layers = 2
num_attention_heads = 32
num_key_value_heads = 32
initializer_range = 0.02
rms_norm_eps = 1e-6
use_cache = True
use_flash_attention = False
sequence_parallel = False
rope = True
recompute = False
recompute_granularity = None
use_lazy_init = False
inputs = []
labels = []
for i in range(100):
inputs.append(
np.random.uniform(low=0, high=32000, size=[Config().seq_length]).astype(
"int64"
)
)
labels.append(
(np.random.uniform(size=[Config().seq_length]) * 10).astype("int64")
)
class RandomDataset(Dataset):
def __init__(self, seq_len, num_samples=100):
super().__init__()
self.seq_len = seq_len
self.num_samples = num_samples
def __getitem__(self, index):
global inputs, labels
return inputs[index], labels[index]
def __len__(self):
return self.num_samples
def create_optimizer(model, lr_scheduler):
decay_parameters = [
p.name
for n, p in model.named_parameters()
if not any(nd in n for nd in ["bias", "norm"])
]
def apply_decay_param_fun(x):
return x in decay_parameters
# test global_clip in auto_parallel
if os.getenv("use_param_group") == "true":
param_group = {}
param_group["params"] = list(model.parameters())
param_group["weight_decay"] = 0.01
param_group["grad_clip"] = paddle.nn.ClipGradByGlobalNorm(1.0)
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=[param_group],
)
else:
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=model.parameters(),
weight_decay=0.01,
grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0),
)
return optimizer
class TestLlamaAuto:
def __init__(self):
self.config = Config()
self.dp = int(os.getenv("dp"))
self.mp = int(os.getenv("mp"))
self.pp = int(os.getenv("pp"))
self.sep = int(os.getenv("sep", "1"))
if os.getenv("use_sp") == "true":
self.config.sequence_parallel = True
if os.getenv("recompute") == "true":
self.config.recompute = True
self.config.recompute_granularity = os.getenv("recompute_granularity")
if os.getenv("use_lazy_init") == "true":
self.config.use_lazy_init = True
self.gradient_accumulation_steps = int(os.getenv("acc_step"))
self.amp = False
self.amp_dtype = "float16"
self.amp_level = "O1"
self.amp_master_grad = False
if os.getenv("amp") == "true":
self.amp = True
if os.getenv("amp_dtype") in ["float16", "bfloat16"]:
self.amp_dtype = os.getenv("amp_dtype")
if os.getenv("amp_level") in ["O0", "O1", "O2"]:
self.amp_level = os.getenv("amp_level")
if os.getenv("amp_master_grad") == "true":
self.amp_master_grad = True
self.config.tensor_parallel_degree = self.mp
self.config.pipeline_parallel_degree = self.pp
self.config.context_parallel_degree = 1
self.config.sep_parallel_degree = 1
if os.getenv("context_parallel", "false") == "true":
self.config.context_parallel_degree = self.sep
self.config.use_flash_attention = True
dist.init_parallel_env()
if os.getenv("sep_parallel", "false") == "true":
self.config.sep_parallel_degree = self.sep
if self.sep > 1:
# only one of the context_parallel and sep_parallel can be True
assert (
self.config.sep_parallel_degree
!= self.config.context_parallel_degree
), (
f"only one of the context_parallel and sep_parallel can be True, but get context_parallel_degree = {self.config.context_parallel_degree} and sep_parallel_degree = {self.config.sep_parallel_degree}, please check your env"
)
self.init_dist_env()
def init_dist_env(self):
order = ["dp", "pp", "mp"]
dp_degree = self.dp
mp_degree = self.mp
pp_degree = self.pp
degree = [dp_degree, pp_degree, mp_degree]
mesh_dims = list(filter(lambda x: x[1] > 1, list(zip(order, degree))))
if not mesh_dims:
mesh_dims = [("dp", 1)]
dim_names = [mesh_dim[0] for mesh_dim in mesh_dims]
mesh_shape = [mesh_dim[1] for mesh_dim in mesh_dims]
mesh_arr = np.arange(
0, reduce(lambda x, y: x * y, mesh_shape, 1)
).reshape(mesh_shape)
global_mesh = dist.ProcessMesh(mesh_arr, dim_names)
dist.auto_parallel.set_mesh(global_mesh)
def run_llama(self, to_static=0):
if self.config.use_lazy_init:
with LazyGuard():
model = LlamaForCausalLMAuto(self.config)
for param in model.parameters():
assert not param._is_initialized()
param.initialize()
else:
model = LlamaForCausalLMAuto(self.config)
criterion = LlamaPretrainingCriterionAuto(self.config)
lr_scheduler = paddle.optimizer.lr.LinearWarmup(
learning_rate=0.0001, warmup_steps=2, start_lr=0, end_lr=0.0001
)
optimizer = create_optimizer(model, lr_scheduler)
if self.amp and not to_static:
model, optimizer = paddle.amp.decorate(
models=model,
optimizers=optimizer,
level=self.amp_level,
dtype=self.amp_dtype,
master_grad=self.amp_master_grad,
)
optimizer = dist.shard_optimizer(optimizer)
train_dataset = RandomDataset(self.config.seq_length)
train_sampler = BatchSampler(
train_dataset,
batch_size=2,
shuffle=True,
drop_last=False,
)
train_dataloader = DataLoader(
train_dataset,
batch_sampler=train_sampler,
num_workers=0,
)
if self.pp == 1:
meshes = [get_mesh(0)]
elif self.pp > 1:
meshes = [get_mesh(0), get_mesh(-1)]
else:
raise ValueError("pp should be greater or equal to 1")
dist_loader = dist.shard_dataloader(
dataloader=train_dataloader,
meshes=meshes,
shard_dims="dp",
)
global_step = 1
tr_loss = float(0)
if not to_static:
model.train()
scaler = None
if self.amp and self.amp_dtype == "float16":
scaler = paddle.amp.GradScaler(init_loss_scaling=1024)
scaler = dist.shard_scaler(scaler)
for epoch_idx in range(1):
for step, inputs in enumerate(dist_loader()):
input_ids, labels = inputs
return input_ids._local_value()._md5sum()
break
else:
strategy = dist.Strategy()
if self.gradient_accumulation_steps > 1:
strategy.pipeline.accumulate_steps = (
self.gradient_accumulation_steps
)
if self.amp:
amp = strategy.amp
amp.enable = self.amp
amp.dtype = self.amp_dtype
amp.level = self.amp_level.lower()
if self.amp_master_grad:
amp.use_master_grad = True
dist_model = dist.to_static(
model,
dist_loader,
criterion,
optimizer,
strategy=strategy,
)
dist_model.train()
for step, inputs in enumerate(dist_loader()):
input_ids, labels = inputs
return input_ids._local_value()._md5sum()
break
def run_test_cases(self):
dynamic_input_md5sum = self.run_llama(to_static=0)
static_input_md5sum = self.run_llama(to_static=1)
if dist.get_rank() == 0:
assert dynamic_input_md5sum == static_input_md5sum
if __name__ == '__main__':
TestLlamaAuto().run_test_cases()
@@ -0,0 +1,315 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
from functools import reduce
import numpy as np
from semi_auto_parallel_llama_model import (
LlamaForCausalLMAuto,
LlamaPretrainingCriterionAuto,
get_mesh,
)
import paddle
import paddle.distributed as dist
from paddle import LazyGuard
from paddle.io import BatchSampler, DataLoader, Dataset
class Config:
vocab_size = 32000
hidden_size = 4096
intermediate_size = 11008
max_position_embeddings = 2048
seq_length = 2048
num_hidden_layers = 4
num_attention_heads = 32
num_key_value_heads = 32
initializer_range = 0.02
rms_norm_eps = 1e-6
use_cache = True
use_flash_attention = False
sequence_parallel = False
rope = True
recompute = False
recompute_granularity = None
use_lazy_init = False
virtual_pp_degree = 1
class RandomDataset(Dataset):
def __init__(self, seq_len, num_samples=100):
super().__init__()
self.seq_len = seq_len
self.num_samples = num_samples
def __getitem__(self, index):
input = np.random.uniform(size=[self.seq_len]).astype("int64")
label = (np.random.uniform(size=[self.seq_len]) * 10).astype("int64")
return input, label
def __len__(self):
return self.num_samples
def create_optimizer(model, lr_scheduler):
decay_parameters = [
p.name
for n, p in model.named_parameters()
if not any(nd in n for nd in ["bias", "norm"])
]
def apply_decay_param_fun(x):
return x in decay_parameters
# test global_clip in auto_parallel
if os.getenv("use_param_group") == "true":
param_group = {}
param_group["params"] = list(model.parameters())
param_group["weight_decay"] = 0.01
param_group["grad_clip"] = paddle.nn.ClipGradByGlobalNorm(1.0)
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=[param_group],
)
else:
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=model.parameters(),
weight_decay=0.01,
grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0),
)
return optimizer
class TestLlamaAuto:
def __init__(self):
self.config = Config()
self.dp = int(os.getenv("dp"))
self.mp = int(os.getenv("mp"))
self.pp = int(os.getenv("pp"))
self.sep = int(os.getenv("sep", "1"))
if os.getenv("virtual_pp_degree"):
self.config.virtual_pp_degree = int(os.getenv("virtual_pp_degree"))
if os.getenv("use_sp") == "true":
self.config.sequence_parallel = True
if os.getenv("recompute") == "true":
self.config.recompute = True
self.config.recompute_granularity = os.getenv("recompute_granularity")
self.gradient_accumulation_steps = int(os.getenv("acc_step"))
self.only_static = os.getenv("only_static")
if self.config.virtual_pp_degree == 1:
self.schedule_mode = "1F1B"
elif self.config.virtual_pp_degree > 1:
self.schedule_mode = "VPP"
self.config.tensor_parallel_degree = self.mp
self.config.pipeline_parallel_degree = self.pp
self.config.context_parallel_degree = 1
self.config.sep_parallel_degree = 1
if os.getenv("context_parallel", "false") == "true":
self.config.context_parallel_degree = self.sep
self.config.use_flash_attention = True
dist.init_parallel_env()
if os.getenv("sep_parallel", "false") == "true":
self.config.sep_parallel_degree = self.sep
if self.sep > 1:
# only one of the context_parallel and sep_parallel can be True
assert (
self.config.sep_parallel_degree
!= self.config.context_parallel_degree
), (
f"only one of the context_parallel and sep_parallel can be True, but get context_parallel_degree = {self.config.context_parallel_degree} and sep_parallel_degree = {self.config.sep_parallel_degree}, please check your env"
)
self.init_dist_env()
def init_dist_env(self):
mesh_dims = [("pp", self.pp), ("dp", self.dp), ("mp", self.mp)]
if not mesh_dims:
mesh_dims = [("dp", 1)]
dim_names = [mesh_dim[0] for mesh_dim in mesh_dims]
mesh_shape = [mesh_dim[1] for mesh_dim in mesh_dims]
mesh_arr = np.arange(
0, reduce(lambda x, y: x * y, mesh_shape, 1)
).reshape(mesh_shape)
global_mesh = dist.ProcessMesh(mesh_arr, dim_names)
dist.auto_parallel.set_mesh(global_mesh)
def run_llama(self, to_static=0):
if self.only_static and to_static == 0:
return
if self.config.use_lazy_init:
with LazyGuard():
model = LlamaForCausalLMAuto(self.config)
for param in model.parameters():
assert not param._is_initialized()
param.initialize()
else:
model = LlamaForCausalLMAuto(self.config)
model = LlamaForCausalLMAuto(self.config)
criterion = LlamaPretrainingCriterionAuto(self.config)
lr_scheduler = paddle.optimizer.lr.LinearWarmup(
learning_rate=0.0001, warmup_steps=2, start_lr=0, end_lr=0.0001
)
optimizer = create_optimizer(model, lr_scheduler)
optimizer = dist.shard_optimizer(optimizer)
micro_bsz = 2
global_bsz = micro_bsz * self.dp * self.gradient_accumulation_steps
run_step = 5
total_sample_num = run_step * global_bsz
global_step = 1
tr_loss = float(0)
if not to_static:
train_dataset = RandomDataset(
self.config.seq_length, total_sample_num
)
train_sampler = BatchSampler(
train_dataset,
batch_size=micro_bsz,
shuffle=True,
drop_last=True,
)
train_dataloader = DataLoader(
train_dataset,
batch_sampler=train_sampler,
num_workers=0,
)
model.train()
for epoch_idx in range(1):
for step, inputs in enumerate(train_dataloader):
input_ids, labels = inputs
logits = model(input_ids)
tr_loss_step = criterion(logits, labels)
if self.gradient_accumulation_steps > 1:
tr_loss_step /= self.gradient_accumulation_steps
tr_loss_step.backward()
tr_loss += tr_loss_step
if global_step % self.gradient_accumulation_steps == 0:
print(
f"step: {global_step // self.gradient_accumulation_steps} loss: {tr_loss.numpy()}"
)
optimizer.step()
optimizer.clear_grad()
lr_scheduler.step()
tr_loss = 0
global_step += 1
if global_step // self.gradient_accumulation_steps >= 10:
break
else:
strategy = dist.Strategy()
if self.pp > 1 and self.gradient_accumulation_steps > 1:
strategy.pipeline.enable = True
strategy.pipeline.accumulate_steps = (
self.gradient_accumulation_steps
)
strategy.pipeline.pp_degree = self.pp
strategy.pipeline.micro_batch_size = micro_bsz
strategy.pipeline.schedule_mode = self.schedule_mode
strategy.pipeline.vpp_degree = self.config.virtual_pp_degree
strategy.pipeline.vpp_seg_method = "LlamaDecoderLayerAuto"
elif self.gradient_accumulation_steps > 1:
strategy.gradient_merge.enable = True
strategy.gradient_merge.k_steps = (
self.gradient_accumulation_steps
)
strategy.gradient_merge.avg = True
train_dataset = RandomDataset(
self.config.seq_length, total_sample_num
)
train_sampler = BatchSampler(
train_dataset,
batch_size=global_bsz,
shuffle=True,
drop_last=True,
)
train_dataloader = DataLoader(
train_dataset,
batch_sampler=train_sampler,
num_workers=0,
)
dist_loader = dist.shard_dataloader(
train_dataloader,
meshes=[get_mesh(0), get_mesh(-1)],
shard_dims="dp",
)
dist_model = dist.to_static(
model, dist_loader, criterion, optimizer, strategy=strategy
)
def validate_batch(batch):
if self.gradient_accumulation_steps == 1 or self.pp > 1:
batches = [batch]
else:
split_batches = [
np.split(
np.array(b), self.gradient_accumulation_steps, 0
)
for b in batch
]
batches = []
for i in range(len(split_batches[0])):
micro_batch = [
split_batch[i] for split_batch in split_batches
]
batches.append(micro_batch)
return batches
dist_model.train()
for epoch_idx in range(1):
for step, inputs in enumerate(dist_loader()):
batches = validate_batch(inputs)
for micro_batch in batches:
input_ids, labels = micro_batch
tr_loss_step = dist_model(input_ids, labels)
if (
tr_loss_step is not None
and self.gradient_accumulation_steps > 1
):
tr_loss_step = np.sum(tr_loss_step)
tr_loss_step /= self.gradient_accumulation_steps
if tr_loss_step:
tr_loss += tr_loss_step
print(f"step: {step} loss: {np.array(tr_loss)}")
lr_scheduler.step()
tr_loss = float(0)
if step >= run_step:
break
def run_test_cases(self):
self.run_llama(to_static=0)
self.run_llama(to_static=1)
if __name__ == '__main__':
TestLlamaAuto().run_test_cases()
@@ -0,0 +1,325 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 hashlib
import os
import random
import tempfile
import time
from functools import reduce
import numpy as np
from semi_auto_parallel_llama_model import (
LlamaForCausalLMAuto,
LlamaPretrainingCriterionAuto,
get_mesh,
)
import paddle
import paddle.distributed as dist
from paddle.io import BatchSampler, DataLoader, Dataset
class Config:
vocab_size = 320
hidden_size = 8
intermediate_size = 64
max_position_embeddings = 8
seq_length = 8
num_hidden_layers = 2
num_attention_heads = 2
num_key_value_heads = 2
initializer_range = 0.02
rms_norm_eps = 1e-6
use_cache = True
use_flash_attention = False
sequence_parallel = False
rope = True
class RandomDataset(Dataset):
def __init__(self, seq_len, num_samples=100):
super().__init__()
self.seq_len = seq_len
self.num_samples = num_samples
def __getitem__(self, index):
input = np.full([self.seq_len], index, dtype="int64")
label = np.array([index] * 8)
return input, label
def __len__(self):
return self.num_samples
def create_optimizer(model, lr_scheduler):
decay_parameters = [
p.name
for n, p in model.named_parameters()
if not any(nd in n for nd in ["bias", "norm"])
]
def apply_decay_param_fun(x):
return x in decay_parameters
optimizer = paddle.optimizer.adamw.AdamW(
learning_rate=lr_scheduler,
apply_decay_param_fun=apply_decay_param_fun,
parameters=model.parameters(),
weight_decay=0.01,
grad_clip=paddle.nn.ClipGradByGlobalNorm(1.0),
)
return optimizer
class TestLlamaAuto:
def __init__(self):
self.config = Config()
self.dp = int(os.getenv("dp"))
self.mp = int(os.getenv("mp"))
self.pp = int(os.getenv("pp"))
self.sep = int(os.getenv("sep", "1"))
if os.getenv("use_sp") == "true":
self.config.sequence_parallel = True
self.gradient_accumulation_steps = int(os.getenv("acc_step"))
self.config.recompute = False
self.config.context_parallel_degree = 1
self.config.sep_parallel_degree = 1
self.config.tensor_parallel_degree = self.mp
self.config.pipeline_parallel_degree = self.pp
if os.getenv("context_parallel", "false") == "true":
self.config.context_parallel_degree = self.sep
self.config.use_flash_attention = True
dist.init_parallel_env()
if os.getenv("sep_parallel", "false") == "true":
self.config.sep_parallel_degree = self.sep
if self.sep > 1:
# only one of the context_parallel and sep_parallel can be True
assert (
self.config.sep_parallel_degree
!= self.config.context_parallel_degree
), (
f"only one of the context_parallel and sep_parallel can be True, but get context_parallel_degree = {self.config.context_parallel_degree} and sep_parallel_degree = {self.config.sep_parallel_degree}, please check your env"
)
self.init_dist_env()
def init_dist_env(self):
order = ["dp", "pp", "mp"]
dp_degree = self.dp
mp_degree = self.mp
pp_degree = self.pp
degree = [dp_degree, pp_degree, mp_degree]
mesh_dims = list(filter(lambda x: x[1] > 1, list(zip(order, degree))))
if not mesh_dims:
mesh_dims = [("dp", 1)]
dim_names = [mesh_dim[0] for mesh_dim in mesh_dims]
mesh_shape = [mesh_dim[1] for mesh_dim in mesh_dims]
mesh_arr = np.arange(
0, reduce(lambda x, y: x * y, mesh_shape, 1)
).reshape(mesh_shape)
global_mesh = dist.ProcessMesh(mesh_arr, dim_names)
dist.auto_parallel.set_mesh(global_mesh)
paddle.seed(1024)
np.random.seed(1024)
random.seed(1024)
def check_program_equal(self, program_a, program_b):
assert program_a.num_ops() == program_b.num_ops(), (
f'The number of ops between two programs is different: {program_a.num_ops()} vs {program_b.num_ops()}.'
)
for i in range(program_a.num_ops()):
a_op = program_a.global_block().ops[i]
b_op = program_b.global_block().ops[i]
# check op name
assert a_op.name() == b_op.name(), (
f'The name of {i} op in program is different: {a_op.name()} vs {b_op.name()}.'
)
# check op inputs
for index in range(a_op.num_operands()):
assert str(a_op.operand(index).source()) == str(
b_op.operand(index).source()
), (
f'The type of {index} operand is different: {a_op.operand(index).source()} vs {b_op.operand(index).source()}'
)
# check op outputs
for index in range(a_op.num_results()):
assert str(a_op.result(index)) == str(b_op.result(index)), (
f'The type of {index} result is different: {a_op.result(index)} vs {b_op.result(index)}'
)
# check op attrs
for k, v in a_op.attrs().items():
if k in ["op_callstack"]:
continue
assert k in b_op.attrs(), (
f'Can not find key of {k} attribute in other program'
)
if k == 'place':
assert type(v) == type(b_op.attrs()[k]), (
f'The attribute of {k} is different: {type(v)} vs {type(b_op.attrs()[k])}'
)
else:
assert v == b_op.attrs()[k], (
f'The attribute of {k} is different: {v} vs {b_op.attrs()[k]}'
)
def run_dy2static(self, tmp_ckpt_path):
model = LlamaForCausalLMAuto(self.config)
criterion = LlamaPretrainingCriterionAuto(self.config)
lr_scheduler = paddle.optimizer.lr.LinearWarmup(
learning_rate=0.0001, warmup_steps=2, start_lr=0, end_lr=0.0001
)
optimizer = create_optimizer(model, lr_scheduler)
train_dataset = RandomDataset(self.config.seq_length)
train_sampler = BatchSampler(
train_dataset,
batch_size=2,
shuffle=False,
drop_last=True,
)
train_dataloader = DataLoader(
train_dataset,
batch_sampler=train_sampler,
num_workers=0,
)
dist_loader = dist.shard_dataloader(
dataloader=train_dataloader,
meshes=[get_mesh(0), get_mesh(1)],
shard_dims="dp",
)
strategy = None
if self.gradient_accumulation_steps > 1:
strategy = dist.Strategy()
strategy.pipeline.accumulate_steps = (
self.gradient_accumulation_steps
)
dist_model = dist.to_static(
model, dist_loader, criterion, optimizer, strategy=strategy
)
dist_model.train()
state_dict = dist_model.state_dict()
loss_before_save = []
for step, inputs in enumerate(dist_loader()):
input_ids, labels = inputs
loss = dist_model(input_ids, labels)
lr_scheduler.step()
if step == 2:
state_dict = dist_model.state_dict()
dist.save_state_dict(state_dict, tmp_ckpt_path, async_save=True)
if step > 2:
numpy_array = np.array(loss)
array_bytes = numpy_array.tobytes()
loss_md5 = hashlib.md5(array_bytes).hexdigest()
loss_before_save.append(loss_md5)
if int(dist.get_rank()) in [2, 3, 6, 7]:
assert loss is not None
else:
assert loss is None
if step >= 9:
break
# check pir dist_model save&load
paddle.enable_static()
model_file_path = os.path.join(
tmp_ckpt_path,
"rank_" + str(paddle.distributed.get_rank()) + ".pd_dist_model",
)
paddle.save(
dist_model._engine._pir_dist_main_progs["train"], model_file_path
)
loaded_model = paddle.load(model_file_path)
self.check_program_equal(
dist_model._engine._pir_dist_main_progs["train"], loaded_model
)
paddle.disable_static()
paddle.distributed.barrier()
time.sleep(10)
loss_after_load = []
for step, inputs in enumerate(dist_loader()):
if step < 2:
continue
input_ids, labels = inputs
loss = dist_model(input_ids, labels)
lr_scheduler.step()
if step == 2:
state_dict = dist_model.state_dict()
dist.load_state_dict(state_dict, tmp_ckpt_path)
if step > 2:
numpy_array = np.array(loss)
array_bytes = numpy_array.tobytes()
loss_md5 = hashlib.md5(array_bytes).hexdigest()
loss_after_load.append(loss_md5)
if int(dist.get_rank()) in [2, 3, 6, 7]:
assert loss is not None
else:
assert loss is None
if step >= 9:
break
return (loss_before_save, loss_after_load)
def broadcast_ckpt_path(self, ckpt_path):
dist.init_parallel_env()
rank = dist.get_rank()
if rank == 0:
byte_array = np.frombuffer(
ckpt_path.encode('utf-8'), dtype=np.uint8
)
length = np.array([len(byte_array)], dtype=np.int32)
else:
length = np.array([0], dtype=np.int32)
length_tensor = paddle.to_tensor(length)
dist.broadcast(length_tensor, src=0)
length = length_tensor.numpy()[0]
if rank != 0:
byte_array = np.empty(length, dtype=np.uint8)
byte_array_tensor = paddle.to_tensor(byte_array)
dist.broadcast(byte_array_tensor, src=0)
global_ckpt_path = byte_array_tensor.numpy().tobytes().decode('utf-8')
return global_ckpt_path
def run_test_cases(self):
self.init_dist_env()
ckpt_path = tempfile.TemporaryDirectory()
tmp_ckpt_path = self.broadcast_ckpt_path(ckpt_path.name)
loss = self.run_dy2static(tmp_ckpt_path)
if int(dist.get_rank()) in [2, 3, 6, 7]:
assert len(loss[0]) == len(loss[1])
for i in range(len(loss[0])):
assert loss[0][i] == loss[1][i]
ckpt_path.cleanup()
if __name__ == '__main__':
TestLlamaAuto().run_test_cases()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,100 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import numpy as np
import paddle
import paddle.distributed as dist
class TestSemiAutoParallel2DGlobalMeshReshard:
def __init__(self):
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._global_mesh = dist.ProcessMesh(
[[0, 1], [2, 3]], dim_names=["pp", "dp"]
)
self._mesh0 = dist.ProcessMesh([0, 1], dim_names=["dp"])
self._mesh1 = dist.ProcessMesh([2, 3], dim_names=["dp"])
paddle.set_device(self._backend)
def test_basic(self):
input = paddle.ones(shape=[2, 3], dtype='float32')
input = dist.shard_tensor(
input, self._global_mesh, [dist.Replicate(), dist.Shard(0)]
)
input.stop_gradient = False
global_input = input + 1.0 # global_input: 2.0
# forward on pp0
input_pp0 = dist.reshard(global_input, self._mesh0, [dist.Shard(0)])
output = input_pp0 + 1.0 # output_pp0: 3.0
# forward on pp1
output = dist.reshard(output, self._mesh1, [dist.Shard(0)])
input_pp1 = dist.reshard(global_input, self._mesh1, [dist.Shard(0)])
output = input_pp1 + output # output_pp1: 5.0
loss = paddle.sum(output) # 30.0
np.testing.assert_allclose(loss.numpy(), 30.0, rtol=1e-06, verbose=True)
loss.backward()
np.testing.assert_allclose(
input.grad.numpy(),
np.full(shape=(2, 3), fill_value=2.0, dtype=np.float32),
rtol=1e-06,
verbose=True,
)
def test_split_dim1(self):
global_mesh = dist.ProcessMesh([[0, 1], [2, 3]])
mesh0 = dist.ProcessMesh([[0], [2]])
mesh1 = dist.ProcessMesh([[1], [3]])
input = paddle.ones(shape=[2, 3], dtype='float32')
input = dist.shard_tensor(
input, global_mesh, [dist.Shard(0), dist.Replicate()]
)
input.stop_gradient = False
global_input = input + 1.0 # global_input: 2.0
# forward on pp0
input_pp0 = dist.reshard(
global_input, mesh0, [dist.Shard(0), dist.Replicate()]
)
output = input_pp0 + 1.0 # output_pp0: 3.0
# forward on pp1
output = dist.reshard(output, mesh1, [dist.Shard(0), dist.Replicate()])
input_pp1 = dist.reshard(
global_input, mesh1, [dist.Shard(0), dist.Replicate()]
)
output = input_pp1 + output # output_pp1: 5.0
loss = paddle.sum(output) # 30.0
np.testing.assert_allclose(loss.numpy(), 30.0, rtol=1e-06, verbose=True)
loss.backward()
np.testing.assert_allclose(
input.grad.numpy(),
np.full(shape=(2, 3), fill_value=2.0, dtype=np.float32),
rtol=1e-06,
verbose=True,
)
def run_test_case(self):
self.test_basic()
self.test_split_dim1()
if __name__ == '__main__':
TestSemiAutoParallel2DGlobalMeshReshard().run_test_case()
@@ -0,0 +1,82 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import numpy as np
import paddle
import paddle.distributed as dist
class TestSemiAutoParallel3DGlobalMeshReshard:
def __init__(self):
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._global_mesh = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=['pp', 'dp', 'mp']
)
self._mesh0 = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['dp', 'mp'])
self._mesh1 = dist.ProcessMesh([[4, 5], [6, 7]], dim_names=['dp', 'mp'])
paddle.set_device(self._backend)
def test_basic(self):
global_input = dist.shard_tensor(
paddle.ones(shape=[6, 8], dtype='float32'),
self._global_mesh,
[dist.Replicate(), dist.Replicate(), dist.Replicate()],
) # 1.0
global_input.stop_gradient = False
# forward on mesh0
input_mesh0 = dist.reshard(
global_input, self._mesh0, [dist.Replicate(), dist.Replicate()]
)
output = input_mesh0 + 1.0 # 2.0
# forward on mesh1
output = dist.reshard(
output, self._mesh1, [dist.Replicate(), dist.Replicate()]
)
input_mesh1 = dist.reshard(
global_input, self._mesh1, [dist.Replicate(), dist.Replicate()]
)
output = output + input_mesh1 # 3.0
loss = paddle.sum(output) # 144.0
np.testing.assert_allclose(
loss.numpy(), 144.0, rtol=1e-06, verbose=True
)
loss.backward()
np.testing.assert_allclose(
global_input.grad.numpy(),
np.full(shape=(6, 8), fill_value=2.0, dtype=np.float32),
rtol=1e-06,
verbose=True,
)
def test_3d_mesh_with_any_status(self):
dense_tensor = paddle.ones(shape=[2, 6], dtype='float32')
dist_tensor = dist.shard_tensor(
dense_tensor,
self._global_mesh,
[dist.Replicate(), dist.Shard(0), dist.Replicate()],
)
np.testing.assert_equal(dist_tensor._local_shape, [1, 6])
def run_test_case(self):
self.test_basic()
self.test_3d_mesh_with_any_status()
if __name__ == '__main__':
TestSemiAutoParallel3DGlobalMeshReshard().run_test_case()
@@ -0,0 +1,185 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 random
import numpy as np
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.distributed import Shard, fleet
from paddle.distributed.fleet import auto
def set_random_seed(seed):
random.seed(seed)
np.random.seed(seed)
paddle.seed(seed)
def dygraph_parallel_cross_entropy(data, label):
model = fleet.meta_parallel.ParallelCrossEntropy()
loss = model(data, label)
return paddle.mean(loss)
def dygraph_cross_entropy(data, label):
model = paddle.nn.CrossEntropyLoss()
loss = model(data, label)
return loss
class MyDataset(paddle.io.Dataset):
def __init__(self, data, label):
self._data = data
self._label = label
def __getitem__(self, index):
return self._data[index], self._label[index]
def __len__(self):
return self._data.shape[0]
class MyMLP(nn.Layer):
def __init__(self, process_mesh, placements):
super().__init__()
self.process_mesh = process_mesh
self.placements = placements
def forward(self, x):
dist.shard_tensor(
x, self.process_mesh, self.placements, stop_gradient=False
)
return x
def auto_parallel_cross_entropy(data, label, process_mesh, placements):
with paddle.LazyGuard():
model = MyMLP(process_mesh, placements)
loss_layer = paddle.nn.CrossEntropyLoss()
auto.fetch("input0@GRAD", "logits_grad", logging=False)
auto.fetch(
"softmax_with_cross_entropy_0.tmp_1",
"loss_before_mean",
logging=False,
)
optimizer = paddle.optimizer.Adam(
learning_rate=0.001, parameters=model.parameters()
)
engine = auto.Engine(model, loss_layer, optimizer)
train_dataset = MyDataset(data, label)
log = engine.fit(train_dataset, epochs=1, batch_size=data.shape[0])
logit_grad = np.array(log.history["fetches"][0]["logits_grad"])
loss = np.array(log.history["loss"])
paddle.disable_static()
return loss, logit_grad
class TestDpDistTraining:
def __init__(self):
self.nsample = 40
self.nclass = 20
self.seed = 100
def run_test_case(self):
strategy = fleet.DistributedStrategy()
strategy.hybrid_configs = {
"dp_degree": 2,
"mp_degree": 1,
"pp_degree": 1,
}
fleet.init(is_collective=True, strategy=strategy)
nsample = self.nsample
nclass = self.nclass
seed = self.seed
set_random_seed(seed)
rank_id = dist.get_rank()
paddle.seed(rank_id * 10)
random.seed(seed)
np.random.seed(seed)
check_group = dist.new_group(list(range(2)))
process_mesh = dist.ProcessMesh(mesh=[0, 1], dim_names=["x"])
np_label = np.random.randint(0, nclass, (nsample // 2, 1))
label = paddle.to_tensor(np_label, dtype="int64")
data = paddle.randn(
shape=[nsample // 2, nclass],
dtype='float32',
)
data.stop_gradient = False
integral_data = []
partial_data = data.clone().detach()
paddle.distributed.all_gather(
integral_data, partial_data, group=check_group
)
integral_data = paddle.concat(integral_data, axis=0)
integral_data = integral_data.detach().clone()
integral_data.stop_gradient = False
integral_label = []
partial_label = label.clone().detach()
paddle.distributed.all_gather(
integral_label, partial_label, group=check_group
)
integral_label = paddle.concat(integral_label, axis=0)
integral_label = integral_label.detach().clone()
integral_label.stop_gradient = False
loss_dygraph_parallel = dygraph_cross_entropy(data, label)
loss_auto, auto_grad = auto_parallel_cross_entropy(
integral_data.numpy(),
integral_label.numpy(),
process_mesh,
[Shard(0)],
)
np.testing.assert_allclose(
loss_dygraph_parallel.numpy(), loss_auto, rtol=1e-6
)
loss_dygraph_parallel.backward()
integral_grad = []
partial_grad = data.grad.clone().detach()
paddle.distributed.all_gather(
integral_grad, partial_grad, group=check_group
)
integral_grad = paddle.concat(integral_grad, axis=0)
integral_auto_grad = []
paddle.distributed.all_gather(
integral_auto_grad,
paddle.to_tensor(auto_grad),
group=check_group,
)
integral_auto_grad = paddle.concat(integral_auto_grad, axis=0)
parallel_grad = integral_grad.numpy()
auto_grad = integral_auto_grad.numpy()
np.testing.assert_allclose(
parallel_grad,
auto_grad,
rtol=1e-6,
)
if __name__ == '__main__':
TestDpDistTraining().run_test_case()
@@ -0,0 +1,170 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 random
import numpy as np
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.distributed import Shard, fleet
from paddle.distributed.fleet import auto
def set_random_seed(seed):
random.seed(seed)
np.random.seed(seed)
paddle.seed(seed)
def dygraph_parallel_cross_entropy(data, label):
model = fleet.meta_parallel.ParallelCrossEntropy()
loss = model(data, label)
return paddle.mean(loss)
def dygraph_cross_entropy(data, label):
model = paddle.nn.CrossEntropyLoss()
loss = model(data, label)
return loss
class MyDataset(paddle.io.Dataset):
def __init__(self, data, label):
self._data = data
self._label = label
def __getitem__(self, index):
return self._data[index], self._label[index]
def __len__(self):
return self._data.shape[0]
class MyMLP(nn.Layer):
def __init__(self, process_mesh, placements):
super().__init__()
self.process_mesh = process_mesh
self.placements = placements
def forward(self, x):
dist.shard_tensor(
x, self.process_mesh, self.placements, stop_gradient=False
)
return x
def auto_parallel_cross_entropy(data, label, process_mesh, placements):
with paddle.LazyGuard():
model = MyMLP(process_mesh, placements)
loss_layer = paddle.nn.CrossEntropyLoss()
auto.fetch("input0@GRAD", "logits_grad", logging=False)
auto.fetch("input0", "logits", logging=False)
auto.fetch(
"softmax_with_cross_entropy_0.tmp_1",
"loss_before_mean",
logging=False,
)
optimizer = paddle.optimizer.Adam(
learning_rate=0.001, parameters=model.parameters()
)
engine = auto.Engine(model, loss_layer, optimizer)
train_dataset = MyDataset(data, label)
log = engine.fit(train_dataset, epochs=1, batch_size=data.shape[0])
logit_grad = np.array(log.history["fetches"][0]["logits_grad"])
loss = np.array(log.history["loss"])
logits = np.array(log.history["fetches"][0]["logits"])
paddle.disable_static()
return loss, logit_grad
class TestHybridDistTraining:
def __init__(self):
self.nsample = 2
self.seq_len = 2
self.nclass = 4
self.seed = 100
def run_test_case(self):
strategy = fleet.DistributedStrategy()
self.model_parallel_size = 2
strategy.hybrid_configs = {
"dp_degree": 2,
"mp_degree": 2,
"pp_degree": 1,
}
fleet.init(is_collective=True, strategy=strategy)
nsample = self.nsample
nclass = self.nclass
seq_len = self.seq_len
seed = self.seed
set_random_seed(seed)
rank_id = dist.get_rank()
paddle.seed(rank_id * 10)
random.seed(seed)
np.random.seed(seed)
process_mesh = dist.ProcessMesh(
mesh=[[0, 1], [2, 3]], dim_names=["x", "y"]
)
integral_np_label = np.random.randint(0, nclass, (nsample, seq_len, 1))
integral_label = paddle.to_tensor(integral_np_label, dtype="int64")
integral_np_data = np.random.randn(nsample, seq_len, nclass).astype(
"float32"
)
integral_data = paddle.to_tensor(integral_np_data)
integral_data.stop_gradient = False
loss_dygraph = dygraph_cross_entropy(integral_data, integral_label)
dp_start_idx = rank_id // 2 * (nsample // 2)
dp_end_idx = dp_start_idx + (nsample // 2)
mp_start_idx = rank_id % 2 * (nclass // 2)
mp_end_idx = mp_start_idx + (nclass // 2)
# the dataloader cannot support shard on non-batch dim,
# so we should slice the data and label tensor manually
mp_sliced_np_data = integral_np_data[:, :, mp_start_idx:mp_end_idx]
loss_auto, auto_grad = auto_parallel_cross_entropy(
mp_sliced_np_data,
integral_np_label,
process_mesh,
[Shard(0), Shard(2)],
)
pd_loss_auto = paddle.to_tensor(loss_auto)
paddle.distributed.all_reduce(pd_loss_auto)
pd_loss_auto = pd_loss_auto / 4
np.testing.assert_allclose(
loss_dygraph.numpy(), pd_loss_auto.numpy(), rtol=1e-6
)
loss_dygraph.backward()
sliced_grad = integral_data.grad[
dp_start_idx:dp_end_idx, :, mp_start_idx:mp_end_idx
]
partial_grad = sliced_grad.clone().detach()
np.testing.assert_allclose(
partial_grad.numpy(),
auto_grad,
rtol=1e-6,
)
if __name__ == '__main__':
TestHybridDistTraining().run_test_case()
@@ -0,0 +1,174 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 random
import numpy as np
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.distributed import Shard, fleet
from paddle.distributed.fleet import auto
def set_random_seed(seed):
random.seed(seed)
np.random.seed(seed)
paddle.seed(seed)
def dygraph_parallel_cross_entropy(data, label):
model = fleet.meta_parallel.ParallelCrossEntropy()
loss = model(data, label)
return paddle.mean(loss)
def dygraph_cross_entropy(data, label):
model = paddle.nn.CrossEntropyLoss()
loss = model(data, label)
return loss
class MyDataset(paddle.io.Dataset):
def __init__(self, data, label):
self._data = data
self._label = label
def __getitem__(self, index):
return self._data[index], self._label[index]
def __len__(self):
return self._data.shape[0]
class MyMLP(nn.Layer):
def __init__(self, process_mesh, placements):
super().__init__()
self.process_mesh = process_mesh
self.placements = placements
def forward(self, x):
dist.shard_tensor(
x, self.process_mesh, self.placements, stop_gradient=False
)
return x
def auto_parallel_cross_entropy(data, label, process_mesh, placements):
with paddle.LazyGuard():
model = MyMLP(process_mesh, placements)
loss_layer = paddle.nn.CrossEntropyLoss()
auto.fetch("input0@GRAD", "logits_grad", logging=False)
auto.fetch(
"softmax_with_cross_entropy_0.tmp_1",
"loss_before_mean",
logging=False,
)
optimizer = paddle.optimizer.Adam(
learning_rate=0.001, parameters=model.parameters()
)
engine = auto.Engine(model, loss_layer, optimizer)
train_dataset = MyDataset(data, label)
log = engine.fit(train_dataset, epochs=1, batch_size=data.shape[0])
logit_grad = np.array(log.history["fetches"][0]["logits_grad"])
loss = np.array(log.history["loss"])
paddle.disable_static()
return loss, logit_grad
class TestMpDistTraining:
def __init__(self):
self.nsample = 40
self.nclass = 20
self.seed = 100
def run_test_case(self):
strategy = fleet.DistributedStrategy()
self.model_parallel_size = 2
strategy.hybrid_configs = {
"dp_degree": 1,
"mp_degree": self.model_parallel_size,
"pp_degree": 1,
}
fleet.init(is_collective=True, strategy=strategy)
nsample = self.nsample
nclass = self.nclass
seed = self.seed
set_random_seed(seed)
rank_id = dist.get_rank()
paddle.seed(rank_id * 10)
random.seed(seed)
np.random.seed(seed)
check_group = dist.new_group(list(range(self.model_parallel_size)))
process_mesh = dist.ProcessMesh(mesh=[0, 1], dim_names=["x"])
np_label = np.random.randint(0, nclass, (nsample, 1))
label = paddle.to_tensor(np_label, dtype="int64")
data = paddle.randn(
shape=[nsample, nclass // self.model_parallel_size],
dtype='float32',
)
data.stop_gradient = False
integral_data = []
partial_data = data.clone().detach()
paddle.distributed.all_gather(
integral_data, partial_data, group=check_group
)
integral_data = paddle.concat(integral_data, axis=-1)
integral_data = integral_data.detach().clone()
integral_data.stop_gradient = False
loss_dygraph_parallel = dygraph_parallel_cross_entropy(data, label)
loss_auto, auto_grad = auto_parallel_cross_entropy(
data.numpy(), np_label, process_mesh, [Shard(1)]
)
np.testing.assert_allclose(
loss_dygraph_parallel.numpy(), loss_auto, rtol=1e-6
)
loss_dygraph_parallel.backward()
integral_grad = []
partial_grad = data.grad.clone().detach()
paddle.distributed.all_gather(
integral_grad, partial_grad, group=check_group
)
integral_grad = paddle.concat(integral_grad, axis=-1)
integral_auto_grad = []
paddle.distributed.all_gather(
integral_auto_grad,
paddle.to_tensor(auto_grad),
group=check_group,
)
integral_auto_grad = paddle.concat(integral_auto_grad, axis=-1)
parallel_grad = integral_grad.numpy()
auto_grad = integral_auto_grad.numpy()
np.testing.assert_allclose(
parallel_grad,
auto_grad,
rtol=1e-6,
)
if __name__ == '__main__':
TestMpDistTraining().run_test_case()
@@ -0,0 +1,183 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import numpy as np
import paddle
import paddle.distributed as dist
class TestSemiAutoParallelCrossMeshReshard:
def __init__(self):
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._mesh0 = dist.ProcessMesh([0, 1], dim_names=["x"])
self._mesh1 = dist.ProcessMesh([2, 3], dim_names=["x"])
self._shape = (20, 20)
self._shard_axis = 0
self._out_shard_axis = 1
paddle.set_device(self._backend)
def test_p_to_r(self):
a = paddle.ones(self._shape)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Partial(dist.ReduceType.kRedSum)]
)
out = dist.reshard(input_tensor, self._mesh1, [dist.Replicate()])
if dist.get_rank() in [2, 3]:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
def test_p_to_s(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(
a, axis=self._shard_axis, num_or_sections=self._mesh1.shape[0]
)
expect_out_shape = list(self._shape)
expect_out_shape[self._shard_axis] = (
self._shape[self._shard_axis] // self._mesh1.shape[0]
)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Partial(dist.ReduceType.kRedSum)]
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Shard(self._shard_axis)]
)
if dist.get_rank() in [2, 3]:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
np.testing.assert_equal(
out._local_value().numpy(),
(
expect_out[0].numpy()
if dist.get_rank() == 2
else expect_out[1].numpy()
),
)
def test_r_to_p(self):
a = paddle.ones(self._shape)
b = paddle.zeros(self._shape)
input_tensor = dist.shard_tensor(a, self._mesh0, [dist.Replicate()])
out = dist.reshard(
input_tensor, self._mesh1, [dist.Partial(dist.ReduceType.kRedSum)]
)
if dist.get_rank() == 2:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
if dist.get_rank() == 3:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), b.numpy())
def test_r_to_s(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(
a, axis=self._shard_axis, num_or_sections=self._mesh1.shape[0]
)
expect_out_shape = list(self._shape)
expect_out_shape[self._shard_axis] = (
self._shape[self._shard_axis] // self._mesh1.shape[0]
)
input_tensor = dist.shard_tensor(a, self._mesh0, [dist.Replicate()])
out = dist.reshard(
input_tensor, self._mesh1, [dist.Shard(self._shard_axis)]
)
if dist.get_rank() in [2, 3]:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
np.testing.assert_equal(
out._local_value().numpy(),
(
expect_out[0].numpy()
if dist.get_rank() == 2
else expect_out[1].numpy()
),
)
def test_s_to_p(self):
a = paddle.ones(self._shape)
b = paddle.zeros(self._shape)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Shard(self._shard_axis)]
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Partial(dist.ReduceType.kRedSum)]
)
if dist.get_rank() == 2:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
if dist.get_rank() == 3:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), b.numpy())
def test_s_to_r(self):
a = paddle.ones(self._shape)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Shard(self._shard_axis)]
)
out = dist.reshard(input_tensor, self._mesh1, [dist.Replicate()])
if dist.get_rank() in [2, 3]:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
def test_s_to_s(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(
a, axis=self._out_shard_axis, num_or_sections=self._mesh1.shape[0]
)
expect_out_shape = list(self._shape)
expect_out_shape[self._out_shard_axis] = (
self._shape[self._out_shard_axis] // self._mesh1.shape[0]
)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Shard(self._shard_axis)]
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Shard(self._out_shard_axis)]
)
if dist.get_rank() in [2, 3]:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
np.testing.assert_equal(
out._local_value().numpy(),
(
expect_out[0].numpy()
if dist.get_rank() == 2
else expect_out[1].numpy()
),
)
def run_test_case(self):
self.test_p_to_r()
self.test_p_to_s()
self.test_r_to_p()
self.test_r_to_s()
self.test_s_to_p()
self.test_s_to_r()
self.test_s_to_s()
if __name__ == '__main__':
TestSemiAutoParallelCrossMeshReshard().run_test_case()
@@ -0,0 +1,106 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import numpy as np
import paddle
import paddle.distributed as dist
from paddle import _C_ops
class TestFusedParamGradAddForSemiAutoParallel:
def __init__(self):
self._dtype = os.getenv("dtype")
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
def check_tensor_eq(self, a, b):
np1 = a.numpy()
np2 = b.numpy()
np.testing.assert_allclose(np1, np2, rtol=1e-05, verbose=True)
def test_body(self):
x_shape = [4, 16, 32]
y_shape = [4, 16, 64]
paddle.seed(self._seed)
np.random.seed(self._seed)
x_np = np.random.random(size=x_shape).astype(self._dtype)
y_np = np.random.random(size=y_shape).astype(self._dtype)
def run_acc_step(x, y):
weight_grad = None
bias_grad = None
for _ in range(2):
weight_grad, bias_grad = _C_ops.fused_linear_param_grad_add(
x,
y,
weight_grad,
bias_grad,
False,
True,
)
return weight_grad, bias_grad
x = paddle.to_tensor(x_np)
y = paddle.to_tensor(y_np)
x.stop_gradient = True
y.stop_gradient = True
weight_grad, bias_grad = run_acc_step(x, y)
# test mp col split
x_placements = [dist.Shard(0), dist.Replicate()]
y_placements = [dist.Shard(0), dist.Shard(2)]
dist_x = dist.shard_tensor(x_np, self._mesh, x_placements)
dist_y = dist.shard_tensor(y_np, self._mesh, y_placements)
dist_x.stop_gradient = True
dist_y.stop_gradient = True
weight_grad_dist, bias_grad_dist = run_acc_step(dist_x, dist_y)
self.check_tensor_eq(weight_grad, weight_grad_dist)
self.check_tensor_eq(bias_grad, bias_grad_dist)
# test mp row split
x_placements = [dist.Shard(0), dist.Shard(2)]
y_placements = [dist.Shard(0), dist.Replicate()]
dist_x = dist.shard_tensor(x_np, self._mesh, x_placements)
dist_y = dist.shard_tensor(y_np, self._mesh, y_placements)
dist_x.stop_gradient = True
dist_y.stop_gradient = True
weight_grad_dist, bias_grad_dist = run_acc_step(dist_x, dist_y)
self.check_tensor_eq(weight_grad, weight_grad_dist)
self.check_tensor_eq(bias_grad, bias_grad_dist)
def test_fused_linear_param_grad_add(self):
self.test_body()
def run_test_case(self):
if self._backend == "cpu":
paddle.set_device("cpu")
elif self._backend == "gpu":
paddle.set_device("gpu:" + str(dist.get_rank()))
else:
raise ValueError("Only support cpu or gpu backend.")
self.test_fused_linear_param_grad_add()
if __name__ == '__main__':
TestFusedParamGradAddForSemiAutoParallel().run_test_case()
@@ -0,0 +1,312 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import random
import numpy as np
import paddle
import paddle.distributed as dist
import paddle.nn.functional as F
from paddle import nn
from paddle.distributed import Replicate, Shard
from paddle.nn.functional.flash_attention import flash_attention
BATCH_NUM = 4
BATCH_SIZE = 16
HIDDEN_SIZE = 1024
INTERMEDIATE_SIZE = 1024 // 3 * 8
SEQ_LEN = 128
N_HEAD = 8
def create_numpy_like_random(name):
return paddle.ParamAttr(
name=name, initializer=paddle.nn.initializer.Uniform(-0.1, 0.1)
)
class LlamaAttention(nn.Layer):
def __init__(self, param_prefix="", hidden_size=HIDDEN_SIZE, n_head=N_HEAD):
super().__init__()
weight_attr_0 = create_numpy_like_random(param_prefix + "_0")
weight_attr_1 = create_numpy_like_random(param_prefix + "_1")
self.hidden_size = hidden_size
self.num_heads = n_head
self.head_dim = hidden_size // n_head
self.qkv_proj = nn.Linear(hidden_size, hidden_size * 3, weight_attr_0)
self.o_proj = nn.Linear(hidden_size, hidden_size, weight_attr_1)
def forward(self, x):
mix_layer = self.qkv_proj(x)
target_shape = [0, 0, self.num_heads, 3 * self.head_dim]
mix_layer = paddle.reshape(mix_layer, target_shape)
mix_layer = paddle.cast(mix_layer, paddle.bfloat16)
query_states, key_states, value_states = paddle.split(
mix_layer, num_or_sections=3, axis=-1
)
attn_output, _ = flash_attention(
query_states, key_states, value_states, causal=True
)
attn_output = paddle.cast(attn_output, paddle.float32)
attn_output = attn_output.reshape(
[BATCH_SIZE, SEQ_LEN, self.hidden_size]
)
attn_output = self.o_proj(attn_output)
return attn_output
class LlamaMlp(nn.Layer):
def __init__(
self,
param_prefix="",
hidden_size=HIDDEN_SIZE,
intermediate_size=INTERMEDIATE_SIZE,
):
super().__init__()
self.hidden_size = hidden_size
self.intermediate_size = intermediate_size
weight_attr_0 = create_numpy_like_random(param_prefix + "_0")
bias_attr_0 = create_numpy_like_random(param_prefix + "_bias_0")
weight_attr_1 = create_numpy_like_random(param_prefix + "_1")
bias_attr_1 = create_numpy_like_random(param_prefix + "_bias_1")
weight_attr_2 = create_numpy_like_random(param_prefix + "_2")
bias_attr_2 = create_numpy_like_random(param_prefix + "_bias_2")
self.up_proj = nn.Linear(
hidden_size, intermediate_size, weight_attr_0, bias_attr_0
)
self.gate_proj = nn.Linear(
hidden_size, intermediate_size, weight_attr_1, bias_attr_1
)
self.down_proj = nn.Linear(
intermediate_size, hidden_size, weight_attr_2, bias_attr_2
)
def forward(self, x):
return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
class LlamaRMSNorm(nn.Layer):
def __init__(self, hidden_size=HIDDEN_SIZE):
super().__init__()
self.hidden_size = hidden_size
self.weight = paddle.create_parameter(
shape=[self.hidden_size],
dtype=paddle.get_default_dtype(),
default_initializer=nn.initializer.Constant(1.0),
)
self.variance_epsilon = 1.0
def forward(self, hidden_states):
with paddle.amp.auto_cast(False):
variance = (
hidden_states.astype("float32").pow(2).mean(-1, keepdim=True)
)
hidden_states = (
paddle.rsqrt(variance + self.variance_epsilon) * hidden_states
)
if self.weight.dtype in [paddle.float16, paddle.bfloat16]:
hidden_states = paddle.cast(hidden_states, self.weight.dtype)
return hidden_states * self.weight
class LlamaLayerDecoder(nn.Layer):
def __init__(
self,
param_prefix="",
hidden_size=HIDDEN_SIZE,
intermediate_size=INTERMEDIATE_SIZE,
):
super().__init__()
self.hidden_size = hidden_size
self.intermediate_size = intermediate_size
self.self_attn = LlamaAttention(param_prefix + "_att", hidden_size)
self.mlp = LlamaMlp(param_prefix + "_mlp")
self.input_layernorm = LlamaRMSNorm(hidden_size)
self.post_attn_layernorm = LlamaRMSNorm(hidden_size)
def forward(self, x):
residual = x
hidden_states = self.input_layernorm(x)
hidden_states = self.self_attn(hidden_states)
hidden_states = residual + hidden_states
residual = hidden_states
hidden_states = self.post_attn_layernorm(hidden_states)
hidden_states = self.mlp(hidden_states)
hidden_states = residual + hidden_states
return hidden_states
class TestLlamaDecoderForSemiAutoParallel:
def __init__(self):
self._dtype = os.getenv("dtype", "float32")
self._backend = os.getenv("backend", "gpu")
self._seed = eval(os.getenv("seed", "2023"))
paddle.set_device(self._backend)
self.init_single_card_net_result()
def dp_mp_shard_fn(self, layer_name, layer, process_mesh):
col_linear = ["qkv_proj", "gate_proj", "up_proj"]
row_linear = ["o_proj", "down_proj"]
def contains(a, b):
return b in a
is_col_linear = any(contains(layer_name, e) for e in col_linear)
is_row_linear = any(contains(layer_name, e) for e in row_linear)
if is_col_linear:
layer.weight = dist.shard_tensor(
layer.weight, process_mesh, [Replicate(), Shard(1)]
)
layer.bias = dist.shard_tensor(
layer.bias, process_mesh, [Replicate(), Shard(0)]
)
if is_row_linear:
layer.weight = dist.shard_tensor(
layer.weight, process_mesh, [Replicate(), Shard(0)]
)
def set_random_seed(self, seed):
random.seed(seed)
np.random.seed(seed)
paddle.seed(seed)
def init_input_data(self):
input = np.random.random([BATCH_SIZE, SEQ_LEN, HIDDEN_SIZE]).astype(
self._dtype
)
input = paddle.to_tensor(input)
return input
def init_single_card_net_result(self):
self.set_random_seed(self._seed)
self.base_out, self.base_parameters = self.train_loop(
LlamaLayerDecoder("demo_weight")
)
def train_loop(self, layer, process_mesh=None, shard_input=False):
# run forward and backward
opt = paddle.optimizer.SGD(
learning_rate=0.1, parameters=layer.parameters()
)
for _ in range(5):
input = self.init_input_data()
if shard_input:
input = dist.shard_tensor(input, process_mesh, shard_input)
out = layer(input)
loss = paddle.sum(out)
loss.backward()
opt.step()
opt.clear_grad()
return out, layer.parameters()
def check_tensor_eq(self, a, b, rtol=1e-05, atol=0, verbose=True):
if a is None:
assert b is None
return
np1 = a.astype("float32").numpy()
np2 = b.astype("float32").numpy()
np.testing.assert_allclose(
np1, np2, rtol=rtol, atol=atol, verbose=verbose
)
def check_placements(self, output, expected_placements):
assert output.placements == expected_placements, (
f"{output.placements} vs {expected_placements}"
)
def get_shard_check_hook(self, dims_mapping, check_input=False):
def check_func(layer, input, output=None):
if check_input:
if isinstance(input, tuple):
input = input[0]
self.check_placements(input, dims_mapping)
else:
if isinstance(output, tuple):
output = output[0]
self.check_placements(output, dims_mapping)
return check_func
# python -m paddle.distributed.launch --devices=0,1,2,3 semi_auto_parallel_for_llama_decoder_dp_mp.py
def test_dp_mp(self):
self.set_random_seed(self._seed)
dp_mp_mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
dp_mp_layer = dist.shard_layer(
LlamaLayerDecoder("mp_demo_weight"), dp_mp_mesh, self.dp_mp_shard_fn
)
input_layer_norm_post_hook = self.get_shard_check_hook(
[dist.Shard(0), dist.Replicate()]
)
attn_pre_hook = self.get_shard_check_hook(
[dist.Shard(0), dist.Replicate()], True
)
attn_post_hook = self.get_shard_check_hook(
[dist.Shard(0), dist.Replicate()]
)
post_attn_layer_norm_pre_hook = self.get_shard_check_hook(
[dist.Shard(0), dist.Replicate()], True
)
post_attn_layer_norm_post_hook = self.get_shard_check_hook(
[dist.Shard(0), dist.Replicate()]
)
mlp_pre_hook = self.get_shard_check_hook(
[dist.Shard(0), dist.Replicate()], True
)
mlp_post_hook = self.get_shard_check_hook(
[dist.Shard(0), dist.Replicate()]
)
dp_mp_layer.input_layernorm.register_forward_post_hook(
input_layer_norm_post_hook
)
dp_mp_layer.self_attn.register_forward_pre_hook(attn_pre_hook)
dp_mp_layer.self_attn.register_forward_post_hook(attn_post_hook)
dp_mp_layer.post_attn_layernorm.register_forward_pre_hook(
post_attn_layer_norm_pre_hook
)
dp_mp_layer.post_attn_layernorm.register_forward_post_hook(
post_attn_layer_norm_post_hook
)
dp_mp_layer.mlp.register_forward_pre_hook(mlp_pre_hook)
dp_mp_layer.mlp.register_forward_post_hook(mlp_post_hook)
dp_mp_out, dp_mp_parameters = self.train_loop(
dp_mp_layer, dp_mp_mesh, shard_input=[Shard(0), Replicate()]
)
self.check_tensor_eq(dp_mp_out, self.base_out)
for param, param_base in zip(dp_mp_parameters, self.base_parameters):
self.check_tensor_eq(param, param_base)
self.check_tensor_eq(param.grad, param_base.grad)
def run_test_case(self):
if self._backend == "gpu":
cuda_version_main = int(paddle.version.cuda().split(".")[0])
device_prop_main = paddle.device.cuda.get_device_capability()[0]
if cuda_version_main >= 11 and device_prop_main >= 8:
self.test_dp_mp()
if __name__ == '__main__':
TestLlamaDecoderForSemiAutoParallel().run_test_case()
@@ -0,0 +1,223 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.io import BatchSampler, DataLoader, Dataset
SEQ_LEN = 4
HIDDEN_SIZE = 8
global_mesh = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=['pp', 'dp', 'mp']
)
mesh0 = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['dp', 'mp'])
mesh1 = dist.ProcessMesh([[4, 5], [6, 7]], dim_names=['dp', 'mp'])
class MlpModel(paddle.nn.Layer):
def __init__(self, variable_initial_values, run_single_process=False):
super().__init__()
self.w0 = self.create_parameter(
shape=[HIDDEN_SIZE, HIDDEN_SIZE],
default_initializer=paddle.nn.initializer.Assign(
variable_initial_values[0]
),
)
self.w1 = self.create_parameter(
shape=[HIDDEN_SIZE, HIDDEN_SIZE],
default_initializer=paddle.nn.initializer.Assign(
variable_initial_values[1]
),
)
self.global_input = paddle.uniform(
shape=[SEQ_LEN, HIDDEN_SIZE],
dtype=paddle.float32,
min=-0.0001,
max=0.0001,
)
if run_single_process is False:
self.w0 = dist.shard_tensor(
self.w0,
mesh0,
[dist.Replicate(), dist.Shard(1)],
)
self.w1 = dist.shard_tensor(
self.w1,
mesh1,
[dist.Replicate(), dist.Shard(0)],
)
self.global_input = dist.shard_tensor(
self.global_input,
global_mesh,
[dist.Replicate(), dist.Replicate(), dist.Replicate()],
)
self.run_single_process = run_single_process
def process_global_input(self, input):
return input + 0.0001
def forward(self, x):
# x: [bs, seq_len, hidden]
# forward on mesh0
global_input = self.process_global_input(self.global_input)
if self.run_single_process is False:
global_input1 = dist.reshard(
global_input, mesh0, [dist.Replicate(), dist.Replicate()]
)
else:
global_input1 = global_input
x = x + global_input1
y = x @ self.w0
# forward on mesh1
if self.run_single_process is False:
y = dist.reshard(y, mesh1, [dist.Shard(0), dist.Shard(2)])
global_input2 = dist.reshard(
global_input, mesh1, [dist.Replicate(), dist.Replicate()]
)
else:
global_input2 = global_input
y = y + global_input2
z = y @ self.w1
return z
class RandomDataset(Dataset):
def __init__(self, seq_len, hidden, num_samples=8):
super().__init__()
self.seq_len = seq_len
self.hidden = hidden
self.num_samples = num_samples
self.inputs = [
np.random.uniform(size=[self.seq_len, self.hidden]).astype(
"float32"
)
for _ in range(num_samples)
]
self.labels = [
np.array(index, dtype="float32") for index in range(num_samples)
]
def __getitem__(self, index):
return self.inputs[index], self.labels[index]
def __len__(self):
return self.num_samples
def create_dataloader():
dataset = RandomDataset(SEQ_LEN, HIDDEN_SIZE)
sampler = BatchSampler(
dataset,
batch_size=2,
)
dataloader = DataLoader(
dataset,
batch_sampler=sampler,
)
return dataloader
def get_variable_initial_value(var_num=2):
res = []
for i in range(var_num):
res.append(
paddle.uniform(
shape=[HIDDEN_SIZE, HIDDEN_SIZE],
dtype=paddle.float32,
min=-0.0001,
max=0.0001,
)
)
return res
def loss_fn(logits, label):
# logits: [bs, seq_len, hidden], label: [bs]
loss = paddle.nn.MSELoss(reduction="sum")
logits = paddle.sum(logits, axis=[1, 2])
return loss(logits, label)
class TestSemiAutoParallelGlobalInput:
def __init__(self):
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._run_static = eval(os.getenv("run_static"))
paddle.seed(self._seed)
np.random.seed(self._seed)
paddle.set_device(self._backend)
self.dataloader = create_dataloader()
self.variable_initial_values = get_variable_initial_value()
self.single_process_loss = self.get_single_process_loss()
def get_single_process_loss(self):
model = MlpModel(
variable_initial_values=self.variable_initial_values,
run_single_process=True,
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=model.parameters()
)
for step, (input, label) in enumerate(self.dataloader()):
logits = model(input)
loss = loss_fn(logits, label)
loss.backward()
opt.step()
opt.clear_grad()
return loss.numpy()
def test_basic(self):
model = MlpModel(variable_initial_values=self.variable_initial_values)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=model.parameters()
)
dist_dataloader = dist.shard_dataloader(
dataloader=self.dataloader, meshes=[mesh0, mesh1], shard_dims="dp"
)
cur_rank = paddle.distributed.get_rank()
if self._run_static:
dist_model = dist.to_static(model, dist_dataloader, loss_fn, opt)
dist_model.train()
for input, label in dist_dataloader:
loss = dist_model(input, label)
if cur_rank in [5, 7]:
loss = paddle.to_tensor(loss)
group = paddle.distributed.new_group([5, 7])
dist.all_reduce(loss, group=group)
else:
dist_opt = dist.shard_optimizer(opt)
for input, label in dist_dataloader:
logits = model(input)
loss = loss_fn(logits, label)
loss.backward()
dist_opt.step()
dist_opt.clear_grad()
if cur_rank in [5, 7]:
np.testing.assert_allclose(
loss.numpy(), self.single_process_loss, rtol=1e-06, verbose=True
)
def run_test_case(self):
self.test_basic()
if __name__ == '__main__':
TestSemiAutoParallelGlobalInput().run_test_case()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,255 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.io import BatchSampler, DataLoader, Dataset
SEQ_LEN = 4
HIDDEN_SIZE = 8
global_mesh = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=['pp', 'dp', 'mp']
)
mesh0 = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['dp', 'mp'])
mesh1 = dist.ProcessMesh([[4, 5], [6, 7]], dim_names=['dp', 'mp'])
class MlpModel(paddle.nn.Layer):
def __init__(self, variable_initial_values, run_single_process=False):
super().__init__()
self.w0 = self.create_parameter(
shape=[HIDDEN_SIZE, HIDDEN_SIZE],
default_initializer=paddle.nn.initializer.Assign(
variable_initial_values[0]
),
)
self.w1 = self.create_parameter(
shape=[HIDDEN_SIZE, HIDDEN_SIZE],
default_initializer=paddle.nn.initializer.Assign(
variable_initial_values[1]
),
)
if run_single_process is False:
self.w0 = dist.shard_tensor(
self.w0,
mesh0,
[dist.Replicate(), dist.Shard(1)],
)
self.w1 = dist.shard_tensor(
self.w1,
mesh1,
[dist.Replicate(), dist.Shard(0)],
)
self.run_single_process = run_single_process
def forward(self, input1, input2, extra_input1=None, extra_input2=None):
# extra_input1 and extra_input2 only used for test non_tensor input in shard_dataloader
x = input1 + input2
# x: [bs, seq_len, hidden]
# forward on mesh0
y = x @ self.w0
# forward on mesh1
if self.run_single_process is False:
y = dist.reshard(y, mesh1, [dist.Shard(0), dist.Shard(2)])
z = y @ self.w1
return z
class RandomDataset(Dataset):
def __init__(self, seq_len, hidden, num_samples=8):
super().__init__()
self.seq_len = seq_len
self.hidden = hidden
self.num_samples = num_samples
self.inputs1 = [
np.random.uniform(size=[self.seq_len, self.hidden]).astype(
"float32"
)
for _ in range(num_samples)
]
self.inputs2 = [
np.random.uniform(size=[self.seq_len, self.hidden]).astype(
"float32"
)
for _ in range(num_samples)
]
self.labels = [
np.array(index, dtype="float32") for index in range(num_samples)
]
def __getitem__(self, index):
return {
"inputs": [self.inputs1[index], self.inputs2[index]],
"label": self.labels[index],
}
def __len__(self):
return self.num_samples
def create_dataloader(collate_fn=None):
dataset = RandomDataset(SEQ_LEN, HIDDEN_SIZE)
sampler = BatchSampler(
dataset,
batch_size=2,
)
dataloader = DataLoader(
dataset,
batch_sampler=sampler,
collate_fn=collate_fn,
)
return dataloader
def get_variable_initial_value(var_num=2):
res = []
for i in range(var_num):
res.append(
paddle.uniform(
shape=[HIDDEN_SIZE, HIDDEN_SIZE],
dtype=paddle.float32,
min=-0.0001,
max=0.0001,
)
)
return res
def loss_fn(logits, label):
# logits: [bs, seq_len, hidden], label: [bs]
loss = paddle.nn.MSELoss(reduction="sum")
logits = paddle.sum(logits, axis=[1, 2])
return loss(logits, label)
class TestSemiAutoParallelMultiInputs:
def __init__(self):
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._run_static = eval(os.getenv("run_static"))
paddle.seed(self._seed)
np.random.seed(self._seed)
paddle.set_device(self._backend)
self.dataloader = create_dataloader()
self.variable_initial_values = get_variable_initial_value()
self.single_process_loss = self.get_single_process_loss()
def get_single_process_loss(self):
model = MlpModel(
variable_initial_values=self.variable_initial_values,
run_single_process=True,
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=model.parameters()
)
for step, data in enumerate(self.dataloader()):
input1, input2 = data["inputs"]
logits = model(input1, input2)
label = data["label"]
loss = loss_fn(logits, label)
loss.backward()
opt.step()
opt.clear_grad()
return loss.numpy()
def test_basic(self):
model = MlpModel(variable_initial_values=self.variable_initial_values)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=model.parameters()
)
dist_dataloader = dist.shard_dataloader(
dataloader=self.dataloader,
meshes=[mesh0, mesh1], # or [[mesh0, mesh0], mesh1]
shard_dims="dp",
input_keys=["inputs", "label"],
)
cur_rank = paddle.distributed.get_rank()
if self._run_static:
dist_model = dist.to_static(model, dist_dataloader, loss_fn, opt)
dist_model.train()
for step, data in enumerate(dist_dataloader()):
input1, input2 = data["inputs"]
label = data["label"]
loss = dist_model(input1, input2, label)
if cur_rank in [5, 7]:
loss = paddle.to_tensor(loss)
group = paddle.distributed.new_group([5, 7])
dist.all_reduce(loss, group=group)
else:
dist_opt = dist.shard_optimizer(opt)
for step, data in enumerate(dist_dataloader()):
input1, input2 = data["inputs"]
logits = model(input1, input2)
label = data["label"]
loss = loss_fn(logits, label)
loss.backward()
dist_opt.step()
dist_opt.clear_grad()
if cur_rank in [5, 7]:
np.testing.assert_allclose(
loss.numpy(), self.single_process_loss, rtol=1e-06, verbose=True
)
def test_non_tensor_input(self):
model = MlpModel(variable_initial_values=self.variable_initial_values)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=model.parameters()
)
def custom_collate_fn(batch):
collated_batch = {
"inputs": [
paddle.to_tensor([item["inputs"][0] for item in batch]),
paddle.to_tensor([item["inputs"][1] for item in batch]),
12.0,
],
"extra_input": 12,
"label": paddle.to_tensor([item["label"] for item in batch]),
}
return collated_batch
self.dataloader = create_dataloader(custom_collate_fn)
dist_dataloader = dist.shard_dataloader(
dataloader=self.dataloader,
meshes=[mesh0, mesh0, mesh1],
shard_dims="dp",
input_keys=["inputs", "extra_input", "label"],
)
dist_opt = dist.shard_optimizer(opt)
for step, data in enumerate(dist_dataloader()):
input1, input2, extra_input1 = data["inputs"]
extra_input2 = data["extra_input"]
logits = model(input1, input2, extra_input1, extra_input2)
label = data["label"]
loss = loss_fn(logits, label)
loss.backward()
dist_opt.step()
dist_opt.clear_grad()
def run_test_case(self):
self.test_basic()
if not self._run_static:
self.test_non_tensor_input()
if __name__ == '__main__':
TestSemiAutoParallelMultiInputs().run_test_case()
@@ -0,0 +1,228 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import random
import numpy as np
from auto_parallel.semi_auto_parallel_dist_to_static_mlp import RandomDataset
from auto_parallel.semi_auto_parallel_simple_net import (
BATCH_SIZE,
CLASS_NUM,
IMAGE_SIZE,
DemoNet,
TestSimpleNetForSemiAutoParallel,
)
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.io import DataLoader
class TestSemiAutoParallelMutualLoadBetweenDynamicAndStatic(
TestSimpleNetForSemiAutoParallel
):
def __init__(self):
self._ckpt_path = os.environ.get("ckpt_path")
self._seed = os.environ.get("seed", 123)
self.mesh = dist.ProcessMesh([0, 1], dim_names=["x"])
def create_data_loader(self):
images = np.random.rand(BATCH_SIZE, IMAGE_SIZE).astype('float32')
labels = np.random.rand(BATCH_SIZE, CLASS_NUM).astype('float32')
dataset = RandomDataset(images, labels, BATCH_SIZE)
loader = DataLoader(dataset, batch_size=BATCH_SIZE)
return loader
def run_dynamic(self, layer, opt, data_loader, is_recompute=False):
# MSELoss only support pir, but test_save_load_state_dict.py set FLAGS_enable_pir_api=0
loss_fn = nn.SmoothL1Loss()
loss_list = []
for _ in range(5):
for batch_id, (image, label) in enumerate(data_loader()):
if is_recompute:
image.stop_gradient = False
out = layer(image)
loss = loss_fn(out, label)
loss_list.append(loss.numpy())
loss.backward()
opt.step()
opt.clear_grad()
return np.array(loss_list)
def run_dy2static(self, layer, opt, data_loader):
# create loss
# MSELoss only support pir, but test_save_load_state_dict.py set FLAGS_enable_pir_api=0
loss_fn = nn.SmoothL1Loss()
dist_loader = dist.shard_dataloader(
dataloader=data_loader,
meshes=[self.mesh],
shard_dims=None,
)
# static training
dist_model = dist.to_static(layer, dist_loader, loss_fn, opt)
loss_list = []
dist_model.train()
for epoch in range(5):
for batch_id, (image, label) in enumerate(dist_loader()):
loss = dist_model(image, label)
loss_list.append(loss)
return np.array(loss_list), dist_model
def set_random_seed(self, seed):
random.seed(seed)
np.random.seed(seed)
paddle.seed(seed)
def test_dygraph_save_static_load(self):
paddle.disable_static()
# set seed to promise the same input for different tp rank
self.set_random_seed(self._seed)
data_loader = self.create_data_loader()
dist_loader = dist.shard_dataloader(
dataloader=data_loader,
meshes=[self.mesh],
shard_dims=None,
)
dy_layer = dist.shard_layer(
DemoNet("dp_mp_hybrid_strategy"), self.mesh, self.shard_fn
)
dy_opt = paddle.optimizer.SGD(
learning_rate=0.1, parameters=dy_layer.parameters()
)
dy_losses = self.run_dynamic(dy_layer, dy_opt, dist_loader)
saved_dy_layer_state_dict = dy_layer.state_dict()
ckpt_path = os.path.join(
self._ckpt_path, "test_dygraph_save_static_load"
)
dist.save_state_dict(saved_dy_layer_state_dict, ckpt_path)
dist.barrier()
dy2static_opt = paddle.optimizer.SGD(
learning_rate=0.1, parameters=dy_layer.parameters()
)
# MSELoss only support pir, but test_save_load_state_dict.py set FLAGS_enable_pir_api=0
loss_fn = nn.SmoothL1Loss()
dist_model = dist.to_static(
dy_layer, dist_loader, loss_fn, dy2static_opt
)
need_load_state_dict = {}
expected_state_dict = {}
with paddle.base.dygraph.guard():
for k, v in saved_dy_layer_state_dict.items():
expected_state_dict[k] = v._local_value().clone()
need_load_state_dict[k] = paddle.zeros_like(v)
dist_model.train()
dist_model.set_state_dict(need_load_state_dict)
state_dict_to_load = dist_model.state_dict(mode="param")
assert len(state_dict_to_load) == len(expected_state_dict)
for k, v in state_dict_to_load.items():
assert k in expected_state_dict, (
f"key {k} not in expected_state_dict:{expected_state_dict}"
)
assert np.any(
np.not_equal(
v._local_value().numpy(),
expected_state_dict[k].numpy(),
)
), (
f"key:{k}, v:{v}, expected_state_dict[k]:{expected_state_dict[k]}"
)
dist.load_state_dict(state_dict_to_load, ckpt_path)
dist_model.set_state_dict(state_dict_to_load)
program_state_dict = dist_model.state_dict(mode="param")
assert len(expected_state_dict) == len(program_state_dict)
for k, v in program_state_dict.items():
assert k in expected_state_dict, (
f"key {k} not in expected_state_dict:{expected_state_dict}"
)
np.testing.assert_equal(
v._local_value().numpy(),
expected_state_dict[k].numpy(),
)
def test_static_save_dynamic_load(self):
paddle.disable_static()
# set seed to promise the same input for different tp rank
self.set_random_seed(self._seed)
data_loader = self.create_data_loader()
dy_layer = dist.shard_layer(
DemoNet("dp_mp_hybrid_strategy"), self.mesh, self.shard_fn
)
dy2static_opt = paddle.optimizer.SGD(
learning_rate=0.1, parameters=dy_layer.parameters()
)
dy2static_losses, dist_model = self.run_dy2static(
dy_layer, dy2static_opt, data_loader
)
saved_static_layer_state_dict = dist_model.state_dict("param")
ckpt_path = os.path.join(
self._ckpt_path, "test_static_save_dynamic_load"
)
dist.save_state_dict(saved_static_layer_state_dict, ckpt_path)
dist.barrier()
paddle.disable_static()
need_load_state_dict = {}
expected_state_dict = {}
with paddle.base.dygraph.guard():
for k, v in saved_static_layer_state_dict.items():
expected_state_dict[k] = v._local_value().clone()
need_load_state_dict[k] = paddle.zeros_like(v)
dy_layer.set_state_dict(need_load_state_dict)
state_dict_to_load = dy_layer.state_dict()
assert len(state_dict_to_load) == len(expected_state_dict)
for k, v in state_dict_to_load.items():
assert k in expected_state_dict, (
f"key {k} not in expected_state_dict:{expected_state_dict}"
)
assert np.any(
np.not_equal(
v._local_value().numpy(),
expected_state_dict[k].numpy(),
)
), (
f"key:{k}, v:{v}, expected_state_dict[k]:{expected_state_dict[k]}"
)
dist.load_state_dict(state_dict_to_load, ckpt_path)
dy_layer.set_state_dict(state_dict_to_load)
state_dict = dy_layer.state_dict()
assert len(expected_state_dict) == len(state_dict)
for k, v in state_dict.items():
assert k in expected_state_dict, (
f"key {k} not in expected_state_dict:{expected_state_dict}"
)
np.testing.assert_equal(
v._local_value().numpy(),
expected_state_dict[k].numpy(),
)
def run_test_case(self):
self.test_dygraph_save_static_load()
self.test_static_save_dynamic_load()
if __name__ == "__main__":
TestSemiAutoParallelMutualLoadBetweenDynamicAndStatic().run_test_case()
@@ -0,0 +1,419 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import numpy as np
import paddle
import paddle.distributed as dist
class TestSemiAutoParallelNdCrossMeshReshard:
def __init__(self):
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._mesh0 = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
self._mesh1 = dist.ProcessMesh([[4, 5], [6, 7]], dim_names=["x", "y"])
self._dst_rank = [4, 5, 6, 7]
self._shape = (20, 20)
paddle.set_device(self._backend)
def test_pp_to_rr(self):
a = paddle.ones(self._shape)
input_tensor = dist.shard_tensor(
a,
self._mesh0,
[
dist.Partial(dist.ReduceType.kRedSum),
dist.Partial(dist.ReduceType.kRedSum),
],
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Replicate(), dist.Replicate()]
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
def test_pp_to_ss(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(a, axis=0, num_or_sections=2)
if dist.get_rank() in [4, 5]:
expect_out = paddle.split(expect_out[0], axis=1, num_or_sections=2)
else:
expect_out = paddle.split(expect_out[1], axis=1, num_or_sections=2)
expect_out_shape = [10, 10]
input_tensor = dist.shard_tensor(
a,
self._mesh0,
[
dist.Partial(dist.ReduceType.kRedSum),
dist.Partial(dist.ReduceType.kRedSum),
],
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Shard(0), dist.Shard(1)]
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
np.testing.assert_equal(
out._local_value().numpy(),
expect_out[dist.get_rank() % 2].numpy(),
)
def test_rr_to_pp(self):
a = paddle.ones(self._shape)
b = paddle.zeros(self._shape)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Replicate(), dist.Replicate()]
)
out = dist.reshard(
input_tensor,
self._mesh1,
[
dist.Partial(dist.ReduceType.kRedSum),
dist.Partial(dist.ReduceType.kRedSum),
],
)
if dist.get_rank() == 4:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
if dist.get_rank() in [5, 6, 7]:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), b.numpy())
def test_rr_to_ss(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(a, axis=0, num_or_sections=2)
if dist.get_rank() in [4, 5]:
expect_out = paddle.split(expect_out[0], axis=1, num_or_sections=2)
else:
expect_out = paddle.split(expect_out[1], axis=1, num_or_sections=2)
expect_out_shape = [10, 10]
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Replicate(), dist.Replicate()]
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Shard(0), dist.Shard(1)]
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
np.testing.assert_equal(
out._local_value().numpy(),
expect_out[dist.get_rank() % 2].numpy(),
)
def test_ss_to_pp(self):
a = paddle.ones(self._shape)
b = paddle.zeros(self._shape)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Shard(0), dist.Shard(1)]
)
out = dist.reshard(
input_tensor,
self._mesh1,
[
dist.Partial(dist.ReduceType.kRedSum),
dist.Partial(dist.ReduceType.kRedSum),
],
)
if dist.get_rank() == 4:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
if dist.get_rank() in [5, 6, 7]:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), b.numpy())
def test_ss_to_rr(self):
a = paddle.ones(self._shape)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Shard(0), dist.Shard(1)]
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Replicate(), dist.Replicate()]
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
def test_ss_to_ss(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(a, axis=0, num_or_sections=2)
if dist.get_rank() in [4, 5]:
expect_out = paddle.split(expect_out[0], axis=1, num_or_sections=2)
else:
expect_out = paddle.split(expect_out[1], axis=1, num_or_sections=2)
expect_out_shape = [10, 10]
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Shard(0), dist.Shard(1)]
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Shard(1), dist.Shard(0)]
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
np.testing.assert_equal(
out._local_value().numpy(),
expect_out[dist.get_rank() % 2].numpy(),
)
def test_sp_to_ps(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(a, axis=1, num_or_sections=2)
expect_out_shape = [20, 10]
b = paddle.zeros(expect_out_shape)
input_tensor = dist.shard_tensor(
a,
self._mesh0,
[dist.Shard(0), dist.Partial(dist.ReduceType.kRedSum)],
)
out = dist.reshard(
input_tensor,
self._mesh1,
[dist.Partial(dist.ReduceType.kRedSum), dist.Shard(1)],
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
if dist.get_rank() in [4, 5]:
np.testing.assert_equal(
out._local_value().numpy(),
expect_out[dist.get_rank() % 2].numpy(),
)
else:
np.testing.assert_equal(
out._local_value().numpy(),
b.numpy(),
)
def test_sp_to_rs(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(a, axis=1, num_or_sections=2)
expect_out_shape = [20, 10]
input_tensor = dist.shard_tensor(
a,
self._mesh0,
[dist.Shard(0), dist.Partial(dist.ReduceType.kRedSum)],
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Replicate(), dist.Shard(1)]
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
np.testing.assert_equal(
out._local_value().numpy(),
expect_out[dist.get_rank() % 2].numpy(),
)
def test_sp_to_rp(self):
a = paddle.ones(self._shape)
b = paddle.zeros(self._shape)
input_tensor = dist.shard_tensor(
a,
self._mesh0,
[dist.Shard(0), dist.Partial(dist.ReduceType.kRedSum)],
)
out = dist.reshard(
input_tensor,
self._mesh1,
[dist.Replicate(), dist.Partial(dist.ReduceType.kRedSum)],
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
if dist.get_rank() % 2 == 0:
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
else:
np.testing.assert_equal(out._local_value().numpy(), b.numpy())
def test_sr_to_ps(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(a, axis=1, num_or_sections=2)
expect_out_shape = [20, 10]
b = paddle.zeros(expect_out_shape)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Shard(0), dist.Replicate()]
)
out = dist.reshard(
input_tensor,
self._mesh1,
[dist.Partial(dist.ReduceType.kRedSum), dist.Shard(1)],
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
if dist.get_rank() in [4, 5]:
np.testing.assert_equal(
out._local_value().numpy(),
expect_out[dist.get_rank() % 2].numpy(),
)
else:
np.testing.assert_equal(
out._local_value().numpy(),
b.numpy(),
)
def test_sr_to_rs(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(a, axis=1, num_or_sections=2)
expect_out_shape = [20, 10]
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Shard(0), dist.Replicate()]
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Replicate(), dist.Shard(1)]
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
local_rank_in_mesh = dist.get_rank() - 4
shard_idx = local_rank_in_mesh % 2
np.testing.assert_equal(
out._local_value().numpy(),
expect_out[shard_idx].numpy(),
)
def test_sr_to_rp(self):
a = paddle.ones(self._shape)
b = paddle.zeros(self._shape)
input_tensor = dist.shard_tensor(
a, self._mesh0, [dist.Shard(0), dist.Replicate()]
)
out = dist.reshard(
input_tensor,
self._mesh1,
[dist.Replicate(), dist.Partial(dist.ReduceType.kRedSum)],
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
if dist.get_rank() % 2 == 0:
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
else:
np.testing.assert_equal(out._local_value().numpy(), b.numpy())
def test_pr_to_ps(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(a, axis=1, num_or_sections=2)
expect_out_shape = [20, 10]
b = paddle.zeros(expect_out_shape)
input_tensor = dist.shard_tensor(
a,
self._mesh0,
[dist.Partial(dist.ReduceType.kRedSum), dist.Replicate()],
)
out = dist.reshard(
input_tensor,
self._mesh1,
[dist.Partial(dist.ReduceType.kRedSum), dist.Shard(1)],
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
if dist.get_rank() in [4, 5]:
np.testing.assert_equal(
out._local_value().numpy(),
expect_out[dist.get_rank() % 2].numpy(),
)
else:
np.testing.assert_equal(
out._local_value().numpy(),
b.numpy(),
)
def test_pr_to_rs(self):
a = paddle.ones(self._shape)
expect_out = paddle.split(a, axis=1, num_or_sections=2)
expect_out_shape = [20, 10]
input_tensor = dist.shard_tensor(
a,
self._mesh0,
[dist.Partial(dist.ReduceType.kRedSum), dist.Replicate()],
)
out = dist.reshard(
input_tensor, self._mesh1, [dist.Replicate(), dist.Shard(1)]
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
assert np.equal(out._local_shape, expect_out_shape).all()
np.testing.assert_equal(
out._local_value().numpy(),
expect_out[dist.get_rank() % 2].numpy(),
)
def test_pr_to_rp(self):
a = paddle.ones(self._shape)
b = paddle.zeros(self._shape)
input_tensor = dist.shard_tensor(
a,
self._mesh0,
[dist.Partial(dist.ReduceType.kRedSum), dist.Replicate()],
)
out = dist.reshard(
input_tensor,
self._mesh1,
[dist.Replicate(), dist.Partial(dist.ReduceType.kRedSum)],
)
if dist.get_rank() in self._dst_rank:
assert np.equal(out.shape, input_tensor.shape).all()
if dist.get_rank() % 2 == 0:
np.testing.assert_equal(out._local_value().numpy(), a.numpy())
else:
np.testing.assert_equal(out._local_value().numpy(), b.numpy())
def run_test_case(self):
self.test_pp_to_rr()
self.test_pp_to_ss()
self.test_rr_to_pp()
self.test_rr_to_ss()
self.test_ss_to_pp()
self.test_ss_to_rr()
self.test_ss_to_ss()
self.test_sp_to_ps()
self.test_sp_to_rs()
self.test_sp_to_rp()
self.test_sr_to_ps()
self.test_sr_to_rs()
self.test_sr_to_rp()
self.test_pr_to_ps()
self.test_pr_to_rs()
self.test_pr_to_rp()
if __name__ == '__main__':
TestSemiAutoParallelNdCrossMeshReshard().run_test_case()
@@ -0,0 +1,114 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 os
import numpy as np
from auto_parallel.semi_auto_parallel_dist_to_static_api import (
DemoNet,
create_data_loader,
)
import paddle
import paddle.distributed as dist
from paddle import nn
class TestSemiAutoParallelShardingStage1:
def __init__(self):
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
def check_tensor_eq(self, a, b, rtol=1e-05, atol=0, verbose=True):
np.testing.assert_allclose(a, b, rtol=rtol, atol=atol, verbose=verbose)
def shard_layer_fn(self, layer_name, layer, process_mesh):
layer.weight = dist.shard_tensor(
layer.weight, process_mesh, [dist.Replicate(), dist.Shard(1)]
)
layer.bias = dist.shard_tensor(
layer.bias, process_mesh, [dist.Replicate(), dist.Shard(0)]
)
def get_single_card_rst(self):
paddle.seed(self._seed)
linear = paddle.nn.Linear(10, 10)
batch = paddle.rand(shape=[10, 10])
opt = paddle.optimizer.AdamW(parameters=linear.parameters())
for _ in range(5):
loss = linear(batch)
loss.backward()
opt.step()
opt.clear_grad()
self.weight = linear.weight.numpy()
self.bias = linear.bias.numpy()
def test_sharding_stage_1_with_mp(self):
paddle.seed(self._seed)
linear = paddle.nn.Linear(10, 10)
linear = dist.shard_layer(linear, self._mesh, self.shard_layer_fn)
batch = paddle.rand(shape=[10, 10])
# shard the input by sharding degree
batch = dist.shard_tensor(batch, self._mesh, [dist.Shard(0)])
# shard optimizer with stage 1 fn
opt = paddle.optimizer.AdamW(parameters=linear.parameters())
opt = dist.shard_optimizer(opt, dist.ShardingStage1("x", self._mesh))
for _ in range(5):
loss = linear(batch)
loss.backward()
opt.step()
opt.clear_grad()
self.check_tensor_eq(self.weight, linear.weight.numpy())
self.check_tensor_eq(self.bias, linear.bias.numpy())
def test_sharding_stage_1_with_mp_to_static(self):
data_loader = create_data_loader()
layer = DemoNet(
self._mesh, "sharding_with_mp_demonet", shard_weight=True
)
opt = paddle.optimizer.SGD(
learning_rate=0.1, parameters=layer.parameters()
)
opt = dist.shard_optimizer(opt, dist.ShardingStage1("x", self._mesh))
loss_fn = nn.MSELoss()
dist_loader = dist.shard_dataloader(
dataloader=data_loader,
meshes=[self._mesh],
shard_dims=0,
)
dist_model = dist.to_static(layer, dist_loader, loss_fn, opt)
dist_model.train()
for epoch in range(2):
for batch_id, (image, label) in enumerate(dist_loader()):
loss = dist_model(image, label)
def run_test_case(self):
if self._backend == "cpu":
paddle.set_device("cpu")
elif self._backend == "gpu":
paddle.set_device("gpu:" + str(dist.get_rank()))
else:
raise ValueError("Only support cpu or gpu backend.")
self.get_single_card_rst()
self.test_sharding_stage_1_with_mp()
self.test_sharding_stage_1_with_mp_to_static()
if __name__ == '__main__':
TestSemiAutoParallelShardingStage1().run_test_case()
@@ -0,0 +1,114 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 os
import numpy as np
from auto_parallel.semi_auto_parallel_dist_to_static_api import (
DemoNet,
create_data_loader,
)
import paddle
import paddle.distributed as dist
from paddle import nn
class TestSemiAutoParallelShardingStage2:
def __init__(self):
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
def check_tensor_eq(self, a, b, rtol=1e-05, atol=0, verbose=True):
np.testing.assert_allclose(a, b, rtol=rtol, atol=atol, verbose=verbose)
def shard_layer_fn(self, layer_name, layer, process_mesh):
layer.weight = dist.shard_tensor(
layer.weight, process_mesh, [dist.Shard(1)]
)
layer.bias = dist.shard_tensor(
layer.bias, process_mesh, [dist.Shard(0)]
)
def get_single_card_rst(self):
paddle.seed(self._seed)
linear = paddle.nn.Linear(10, 10)
batch = paddle.rand(shape=[10, 10])
opt = paddle.optimizer.AdamW(parameters=linear.parameters())
for _ in range(5):
loss = linear(batch)
loss.backward()
opt.step()
opt.clear_grad()
self.weight = linear.weight.numpy()
self.bias = linear.bias.numpy()
def test_sharding_stage_2_with_mp(self):
paddle.seed(self._seed)
linear = paddle.nn.Linear(10, 10)
linear = dist.shard_layer(linear, self._mesh, self.shard_layer_fn)
batch = paddle.rand(shape=[10, 10])
# shard the input by sharding degree
batch = dist.shard_tensor(batch, self._mesh, [dist.Shard(0)])
# shard optimizer with stage 1 fn
opt = paddle.optimizer.AdamW(parameters=linear.parameters())
opt = dist.shard_optimizer(opt, dist.ShardingStage2("x", self._mesh))
for _ in range(5):
loss = linear(batch)
loss.backward()
opt.step()
opt.clear_grad()
self.check_tensor_eq(self.weight, linear.weight.numpy())
self.check_tensor_eq(self.bias, linear.bias.numpy())
def test_sharding_stage_2_with_mp_to_static(self):
data_loader = create_data_loader()
layer = DemoNet(
self._mesh, "sharding_with_mp_demonet", shard_weight=True
)
opt = paddle.optimizer.SGD(
learning_rate=0.1, parameters=layer.parameters()
)
opt = dist.shard_optimizer(opt, dist.ShardingStage2("x", self._mesh))
loss_fn = nn.MSELoss()
dist_loader = dist.shard_dataloader(
dataloader=data_loader,
meshes=[self._mesh],
shard_dims=0,
)
dist_model = dist.to_static(layer, dist_loader, loss_fn, opt)
dist_model.train()
for epoch in range(2):
for batch_id, (image, label) in enumerate(dist_loader()):
loss = dist_model(image, label)
def run_test_case(self):
if self._backend == "cpu":
paddle.set_device("cpu")
elif self._backend == "gpu":
paddle.set_device("gpu:" + str(dist.get_rank()))
else:
raise ValueError("Only support cpu or gpu backend.")
self.get_single_card_rst()
self.test_sharding_stage_2_with_mp()
self.test_sharding_stage_2_with_mp_to_static()
if __name__ == '__main__':
TestSemiAutoParallelShardingStage2().run_test_case()
@@ -0,0 +1,114 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 os
import numpy as np
from auto_parallel.semi_auto_parallel_dist_to_static_api import (
DemoNet,
create_data_loader,
)
import paddle
import paddle.distributed as dist
from paddle import nn
class TestSemiAutoParallelShardingStage3:
def __init__(self):
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
def check_tensor_eq(self, a, b, rtol=1e-05, atol=0, verbose=True):
np.testing.assert_allclose(a, b, rtol=rtol, atol=atol, verbose=verbose)
def shard_layer_fn(self, layer_name, layer, process_mesh):
layer.weight = dist.shard_tensor(
layer.weight, process_mesh, [dist.Shard(1)]
)
layer.bias = dist.shard_tensor(
layer.bias, process_mesh, [dist.Shard(0)]
)
def get_single_card_rst(self):
paddle.seed(self._seed)
linear = paddle.nn.Linear(10, 10)
batch = paddle.rand(shape=[10, 10])
opt = paddle.optimizer.AdamW(parameters=linear.parameters())
for _ in range(5):
loss = linear(batch)
loss.backward()
opt.step()
opt.clear_grad()
self.weight = linear.weight.numpy()
self.bias = linear.bias.numpy()
def test_sharding_stage_3_with_mp(self):
paddle.seed(self._seed)
linear = paddle.nn.Linear(10, 10)
linear = dist.shard_layer(linear, self._mesh, self.shard_layer_fn)
batch = paddle.rand(shape=[10, 10])
# shard the input by sharding degree
batch = dist.shard_tensor(batch, self._mesh, [dist.Shard(0)])
# shard optimizer with stage 1 fn
opt = paddle.optimizer.AdamW(parameters=linear.parameters())
opt = dist.shard_optimizer(opt, dist.ShardingStage3("x", self._mesh))
for _ in range(5):
loss = linear(batch)
loss.backward()
opt.step()
opt.clear_grad()
self.check_tensor_eq(self.weight, linear.weight.numpy())
self.check_tensor_eq(self.bias, linear.bias.numpy())
def test_sharding_stage_3_with_mp_to_static(self):
data_loader = create_data_loader()
layer = DemoNet(
self._mesh, "sharding_with_mp_demonet", shard_weight=True
)
opt = paddle.optimizer.SGD(
learning_rate=0.1, parameters=layer.parameters()
)
opt = dist.shard_optimizer(opt, dist.ShardingStage3("x", self._mesh))
loss_fn = nn.MSELoss()
dist_loader = dist.shard_dataloader(
dataloader=data_loader,
meshes=[self._mesh],
shard_dims=0,
)
dist_model = dist.to_static(layer, dist_loader, loss_fn, opt)
dist_model.train()
for epoch in range(2):
for batch_id, (image, label) in enumerate(dist_loader()):
loss = dist_model(image, label)
def run_test_case(self):
if self._backend == "cpu":
paddle.set_device("cpu")
elif self._backend == "gpu":
paddle.set_device("gpu:" + str(dist.get_rank()))
else:
raise ValueError("Only support cpu or gpu backend.")
self.get_single_card_rst()
self.test_sharding_stage_3_with_mp()
self.test_sharding_stage_3_with_mp_to_static()
if __name__ == '__main__':
TestSemiAutoParallelShardingStage3().run_test_case()
@@ -0,0 +1,96 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
from auto_parallel.hybrid_strategy.semi_auto_save_state_dict import (
check_structure_name_mapping,
)
from auto_parallel.semi_auto_parallel_simple_net import (
DemoNet,
TestSimpleNetForSemiAutoParallel,
)
import paddle
import paddle.distributed as dist
class TestSimpleNetHybridStrategyForSemiAutoParallel(
TestSimpleNetForSemiAutoParallel
):
def __init__(self):
self._dtype = os.getenv("dtype")
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._ckpt_path = os.getenv("ckpt_path")
self._mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
paddle.set_device(self._backend)
self.set_random_seed(self._seed)
self.init_single_card_net_result()
def test_dp_mp_demo_net(self):
self.set_random_seed(self._seed)
model = dist.shard_layer(
DemoNet("dp_mp_hybrid_strategy"), self._mesh, self.shard_fn
)
(
self.dp_mp_loss,
self.dp_mp_parameters,
) = self.run_dynamic(model, shard_input=True)
self.check_tensor_eq(self.dp_mp_loss, self.base_loss, rtol=1e-04)
for param, param_base in zip(
self.dp_mp_parameters, self.base_parameters
):
self.check_tensor_eq(param, param_base, atol=1e-06)
self.check_tensor_eq(param.grad, param_base.grad)
# save load
state_dict = model.state_dict()
paddle.distributed.save_state_dict(state_dict, self._ckpt_path)
paddle.distributed.barrier()
check_structure_name_mapping(self._ckpt_path, state_dict)
expected_local_state_dict = {}
need_load_state_dict = {}
for k, v in state_dict.items():
local_value = v._local_value()
expected_local_state_dict[k] = local_value.clone()
need_load_state_dict[k] = paddle.zeros_like(v)
model.set_state_dict(need_load_state_dict)
state_dict = model.state_dict()
for k, v in state_dict.items():
assert v.numpy().sum() == 0.0, f"state_dict {k} is not zero"
assert k in need_load_state_dict, f"state_dict {k} is not found"
assert need_load_state_dict[k].numpy().sum() == 0.0, (
f"state_dict {k} is not zero"
)
paddle.distributed.load_state_dict(
need_load_state_dict, self._ckpt_path
)
model.set_state_dict(need_load_state_dict)
state_dict = model.state_dict()
for k, v in state_dict.items():
assert k in expected_local_state_dict, k
self.check_tensor_eq(v._local_value(), expected_local_state_dict[k])
def run_test_case(self):
self.test_dp_mp_demo_net()
if __name__ == '__main__':
TestSimpleNetHybridStrategyForSemiAutoParallel().run_test_case()
@@ -0,0 +1,152 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
from auto_parallel.hybrid_strategy.semi_auto_save_state_dict import (
check_structure_name_mapping,
)
from auto_parallel.semi_auto_parallel_simple_net import (
DemoNet,
TestSimpleNetForSemiAutoParallel,
)
import paddle
import paddle.distributed as dist
from paddle.distributed import Replicate, Shard
class TestSimpleNetHybridStrategyForSemiAutoParallel(
TestSimpleNetForSemiAutoParallel
):
def __init__(self):
self._dtype = os.getenv("dtype")
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._ckpt_path = os.getenv("ckpt_path")
self._mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=["x", "y"])
self._pp_mesh0 = dist.ProcessMesh(
[[0, 1], [2, 3]], dim_names=["x", "y"]
)
self._pp_mesh1 = dist.ProcessMesh(
[[4, 5], [6, 7]], dim_names=["x", "y"]
)
self.pp_reshard_dist_attr = (self._pp_mesh1, [Shard(0), Shard(1)])
paddle.set_device(self._backend)
self.set_random_seed(self._seed)
self.init_single_card_net_result()
def dp_mp_pp_shard_fn(self, layer_name, layer, process_mesh):
if layer_name == 'linear_0':
# shard_layer doesn't support cross-mesh now.
# input process_mesh of pp_shard_fn is useless,
# it's defined just for unified format.
layer.weight = dist.shard_tensor(
layer.weight, self._pp_mesh0, [Replicate(), Shard(1)]
)
if layer.bias is not None:
layer.bias = dist.shard_tensor(
layer.bias, self._pp_mesh0, [Replicate(), Replicate()]
)
elif layer_name == 'linear_1':
layer.weight = dist.shard_tensor(
layer.weight, self._pp_mesh1, [Replicate(), Shard(0)]
)
if layer.bias is not None:
layer.bias = dist.shard_tensor(
layer.bias, self._pp_mesh1, [Replicate(), Replicate()]
)
elif layer_name == 'norm':
layer.weight = dist.shard_tensor(
layer.weight, self._pp_mesh1, [Replicate(), Replicate()]
)
if layer.bias is not None:
layer.bias = dist.shard_tensor(
layer.bias, self._pp_mesh1, [Replicate(), Replicate()]
)
def test_dp_mp_pp_demo_net(self):
self.set_random_seed(self._seed)
model = dist.shard_layer(
DemoNet(
"dp_mp_pp_hybrid_strategy",
is_pp=True,
pp_reshard_dist_attr=self.pp_reshard_dist_attr,
),
self._pp_mesh0,
self.dp_mp_pp_shard_fn,
)
(
self.dp_mp_pp_loss,
self.dp_mp_pp_parameters,
) = self.run_dynamic(model, shard_input=True, is_pp=True)
rank = dist.get_rank()
# TODO(GhostScreaming): DistTensor.numpy() doesn't support
# cross-mesh now, ReshardXToReplicated function in eager_method
# needs to be fixed later.
if rank in [0, 1, 2, 3]:
# linear_0 weight and bias
self.check_tensor_eq(
self.dp_mp_pp_parameters[0], self.base_parameters[0], rtol=2e-4
)
else:
self.check_tensor_eq(self.dp_mp_pp_loss, self.base_loss, rtol=1e-4)
self.check_tensor_eq(
self.dp_mp_pp_parameters[1], self.base_parameters[1], rtol=1e-4
)
self.check_tensor_eq(
self.dp_mp_pp_parameters[2], self.base_parameters[2], rtol=2e-5
)
self.check_tensor_eq(
self.dp_mp_pp_parameters[3], self.base_parameters[3], rtol=2e-4
)
# save load
state_dict = model.state_dict()
paddle.distributed.save_state_dict(state_dict, self._ckpt_path)
paddle.distributed.barrier()
check_structure_name_mapping(self._ckpt_path, state_dict)
expected_local_state_dict = {}
need_load_state_dict = {}
for k, v in state_dict.items():
expected_local_state_dict[k] = (
v._local_value().clone() if v._is_initialized() else None
)
need_load_state_dict[k] = (
paddle.zeros_like(v) if v._is_initialized() else v
)
model.set_state_dict(need_load_state_dict)
paddle.distributed.load_state_dict(
need_load_state_dict, self._ckpt_path
)
model.set_state_dict(need_load_state_dict)
state_dict = model.state_dict()
for k, v in state_dict.items():
assert k in expected_local_state_dict, k
if v._is_initialized():
self.check_tensor_eq(
v._local_value(), expected_local_state_dict[k]
)
def run_test_case(self):
self.test_dp_mp_pp_demo_net()
if __name__ == '__main__':
TestSimpleNetHybridStrategyForSemiAutoParallel().run_test_case()
@@ -0,0 +1,214 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import numpy as np
from auto_parallel.semi_auto_parallel_simple_net import (
TestSimpleNetForSemiAutoParallel,
create_numpy_like_random,
)
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.distributed import Replicate, Shard
BATCH_SIZE = 8
SEQUENCE_LEN = 512
HIDDEN_SIZE = 1024
NUM_HEAD = 64
HEAD_DIM = 16
CLASS_NUM = 10
np.set_printoptions(threshold=np.inf)
class DemoNet(nn.Layer):
def __init__(self, param_prefix="", is_sp=False, is_dp=False):
super().__init__()
if is_dp:
self.pp0_mesh = dist.ProcessMesh(
[[0, 1], [2, 3]], dim_names=["dp", "mp"]
)
self.pp1_mesh = dist.ProcessMesh(
[[4, 5], [6, 7]], dim_names=["dp", "mp"]
)
self.placement0 = [Replicate(), Shard(1)]
self.placement1 = [Replicate(), Shard(0)]
self.sp_reshard_placement0 = [Shard(1), Shard(0)]
self.sp_reshard_placement1 = [Shard(1), Replicate()]
else:
self.pp0_mesh = dist.ProcessMesh([0, 1], dim_names=["mp"])
self.pp1_mesh = dist.ProcessMesh([2, 3], dim_names=["mp"])
self.placement0 = [Shard(1)]
self.placement1 = [Shard(0)]
self.sp_reshard_placement0 = [Shard(0)]
self.sp_reshard_placement1 = [Replicate()]
self.is_sp = is_sp
self.is_dp = is_dp
self.norm = nn.LayerNorm(HIDDEN_SIZE, epsilon=1e-4)
self.linear_0_weight = dist.shard_tensor(
self.create_parameter(
shape=[HEAD_DIM, 4 * HIDDEN_SIZE],
attr=create_numpy_like_random(param_prefix + "w_0"),
dtype=paddle.float32,
is_bias=False,
),
self.pp0_mesh,
self.placement0,
)
self.linear_1_weight = dist.shard_tensor(
self.create_parameter(
shape=[4 * HIDDEN_SIZE, HEAD_DIM],
attr=create_numpy_like_random(param_prefix + "w_1"),
dtype=paddle.float32,
is_bias=False,
),
self.pp0_mesh,
self.placement1,
)
self.linear_2_weight = dist.shard_tensor(
self.create_parameter(
shape=[HIDDEN_SIZE, 4 * HIDDEN_SIZE],
attr=create_numpy_like_random(param_prefix + "w_2"),
dtype=paddle.float32,
is_bias=False,
),
self.pp1_mesh,
self.placement0,
)
self.linear_3_weight = dist.shard_tensor(
self.create_parameter(
shape=[4 * HIDDEN_SIZE, CLASS_NUM],
attr=create_numpy_like_random(param_prefix + "w_3"),
dtype=paddle.float32,
is_bias=False,
),
self.pp1_mesh,
self.placement1,
)
def forward(self, x):
# Layer 0
tgt = paddle.transpose(x, [1, 0, 2])
out = paddle.reshape(x, [BATCH_SIZE, SEQUENCE_LEN, NUM_HEAD, HEAD_DIM])
# [BATCH_SIZE, SEQUENCE_LEN, NUM_HEAD, HEAD_DIM] -> [BATCH_SIZE, NUM_HEAD, SEQUENCE_LEN, HEAD_DIM]
out = paddle.transpose(out, [0, 2, 1, 3])
out = paddle.matmul(out, self.linear_0_weight)
out = paddle.matmul(out, self.linear_1_weight)
out = paddle.transpose(out, [2, 0, 1, 3])
out = paddle.reshape(out, [SEQUENCE_LEN, BATCH_SIZE, HIDDEN_SIZE])
# SP Region, should be reduce_scatter
if self.is_sp:
out = dist.reshard(out, self.pp0_mesh, self.sp_reshard_placement0)
out = out + tgt
out = self.norm(out)
out = dist.reshard(out, self.pp1_mesh, self.sp_reshard_placement1)
out = paddle.matmul(out, self.linear_2_weight)
out = paddle.matmul(out, self.linear_3_weight)
out = paddle.transpose(out, [1, 0, 2])
return out
class TestSimpleNetHybridStrategyForSemiAutoParallel(
TestSimpleNetForSemiAutoParallel
):
def __init__(self):
self._dtype = os.getenv("dtype")
self._backend = os.getenv("backend")
self._seed = eval(os.getenv("seed"))
self._is_dp = os.getenv("is_dp") == "true"
if self._is_dp:
self.pp0_mesh = dist.ProcessMesh(
[[0, 1], [2, 3]], dim_names=["dp", "mp"]
)
paddle.set_device(self._backend)
self.set_random_seed(self._seed)
self.init_single_card_net_result()
def init_single_card_net_result(self):
self.set_random_seed(self._seed)
self.base_loss, self.base_parameters = self.run_dynamic(
DemoNet("demo_weight", is_sp=False, is_dp=self._is_dp), is_sp=False
)
def init_input_data(self):
image = np.random.randn(BATCH_SIZE, SEQUENCE_LEN, HIDDEN_SIZE).astype(
'float32'
)
label = np.random.randn(BATCH_SIZE, SEQUENCE_LEN, CLASS_NUM).astype(
'float32'
)
return paddle.to_tensor(image), paddle.to_tensor(label)
def check_tensor_eq(self, a, b, rtol=1e-7, atol=0, verbose=True):
np1 = a.astype("float32").numpy()
np2 = b.astype("float32").numpy()
np.testing.assert_allclose(
np1, np2, rtol=rtol, atol=atol, verbose=verbose
)
def run_dynamic(self, layer, is_sp=False):
# create loss
loss_fn = nn.MSELoss()
# run forward and backward
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=layer.parameters()
)
for _ in range(5):
image, label = self.init_input_data()
if self._is_dp:
image = dist.shard_tensor(
image, self.pp0_mesh, [Shard(0), Replicate()]
)
out = layer(image)
loss = loss_fn(out, label)
loss.backward()
opt.step()
return loss, layer.parameters()
def test_dp_mp_sp_demo_net(self):
self.set_random_seed(self._seed)
model = DemoNet("dp_mp_hybrid_strategy", is_sp=True, is_dp=self._is_dp)
(
self.dp_mp_sp_loss,
self.dp_mp_sp_parameters,
) = self.run_dynamic(model, is_sp=True)
if dist.get_rank() in model.pp1_mesh.process_ids:
self.check_tensor_eq(self.dp_mp_sp_loss, self.base_loss, rtol=1e-3)
def run_test_case(self):
self.test_dp_mp_sp_demo_net()
if __name__ == '__main__':
TestSimpleNetHybridStrategyForSemiAutoParallel().run_test_case()
@@ -0,0 +1,212 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import paddle
import paddle.distributed as dist
from paddle.distributed import save_state_dict
from paddle.distributed.flex_checkpoint.dcp.sharded_weight import (
ShardedWeight,
make_replicated_sharded_weight,
)
def get_global_state_dict():
w1 = paddle.arange(104).reshape([13, 8])
w2 = paddle.arange(32, 36).reshape([2, 2])
return {"w1": w1, "w2": w2}
def check_structure_name_mapping(ckpt_path, state_dict):
metadata_file_path = os.path.join(ckpt_path, "0.metadata")
data_file_path = os.path.join(
ckpt_path, f"{paddle.distributed.get_rank()}_0.distcp"
)
assert os.path.exists(metadata_file_path), (
f"metadata file {metadata_file_path} is not found"
)
assert os.path.exists(data_file_path), (
f"data file {data_file_path} is not found"
)
metadata = paddle.load(metadata_file_path)
cur_rank_state_dict = paddle.load(data_file_path, keep_name_table=True)
local_structure_name_mapping = cur_rank_state_dict.pop(
"StructuredToParameterName@@"
)
assert isinstance(local_structure_name_mapping, dict), (
f"local_structure_name_mapping:{local_structure_name_mapping} is not dict type"
)
for structure_name, param_name in local_structure_name_mapping.items():
assert structure_name in state_dict, (
f"tensor key:{structure_name} is not found in state dict:{state_dict}"
)
assert param_name == state_dict[structure_name].name, (
f"param name:{param_name} is not equal to param name in state_dict:{state_dict[structure_name].name}"
)
class TestSaveStateDict:
def __init__(self):
self._ckpt_path = os.getenv("ckpt_path")
def test_save_state_dict_with_one_device(self):
global_state_dict = get_global_state_dict()
keys = list(global_state_dict.keys())
w1, w2 = list(global_state_dict.values())
state_dict = dict(zip(keys, [w1, w2]))
save_state_dict(state_dict, self._ckpt_path)
check_structure_name_mapping(self._ckpt_path, state_dict)
def test_save_state_dict_with_two_devices(self):
global_state_dict = get_global_state_dict()
keys = list(global_state_dict.keys())
w1, w2 = list(global_state_dict.values())
mesh = dist.ProcessMesh([0, 1])
sharded_w1 = dist.shard_tensor(w1, mesh, [dist.Shard(0)])
sharded_w2 = dist.shard_tensor(w2, mesh, [dist.Shard(0)])
state_dict = dict(zip(keys, [sharded_w1, sharded_w2]))
save_state_dict(state_dict, self._ckpt_path)
paddle.distributed.barrier()
check_structure_name_mapping(self._ckpt_path, state_dict)
def run_test_case(self):
device_num = int(os.getenv("device_num"))
if device_num == 1:
self.test_save_state_dict_with_one_device()
elif device_num == 2:
self.test_save_state_dict_with_two_devices()
class TestSaveShardedStateDict:
def __init__(self):
self._ckpt_path = os.getenv("ckpt_path_2")
def test_save_state_dict_with_one_device(self):
# Construct a 4x4 integer tensor as expected result:
# [[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15]]
local_tensor = paddle.to_tensor(
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]],
dtype='int32',
)
sharded_state_dict = {}
sharded_state_dict["t"] = make_replicated_sharded_weight(
"t", local_tensor
)
save_state_dict(sharded_state_dict, self._ckpt_path)
def test_save_state_dict_with_two_devices(self):
if dist.get_rank() == 0:
# On rank 0:
# The global tensor (4x4) is distributed as:
# [[ 0, 1, 2, *],
# [ *, *, *, *],
# [ *, *, *, *],
# [ *, *, *, *]]
# Numbers 0,1,2 are local, '*' means not present on this rank.
local_tensor = paddle.to_tensor([0, 1, 2], dtype='int32')
sharded_weight = ShardedWeight(
key="t",
local_tensor=local_tensor,
local_shape=(4, 4),
global_shape=(4, 4),
global_offset=(0, 0),
is_flattened=True,
flattened_range=slice(0, 3),
)
elif dist.get_rank() == 1:
# On rank 1:
# The global tensor (4x4) is distributed as:
# [[ *, *, *, 3],
# [ 4, 5, 5, 6],
# [ 8, 9, 10, 11],
# [ 12, 13, 14, 15]]
# Numbers 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 are local, '*' means not present on this rank.
local_tensor = paddle.to_tensor(
[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], dtype='int32'
)
sharded_weight = ShardedWeight(
key="t",
local_tensor=local_tensor,
local_shape=(4, 4),
global_shape=(4, 4),
global_offset=(0, 0),
is_flattened=True,
flattened_range=slice(3, 16),
)
sharded_state_dict = {"t": sharded_weight}
save_state_dict(sharded_state_dict, self._ckpt_path)
paddle.distributed.barrier()
def run_test_case(self):
device_num = int(os.getenv("device_num"))
if device_num == 1:
self.test_save_state_dict_with_one_device()
elif device_num == 2:
self.test_save_state_dict_with_two_devices()
class TestSaveShardedStateDictWithReplica:
def __init__(self):
self._ckpt_path = os.getenv("ckpt_path_3")
def test_save_state_dict_with_one_device(self):
# Construct a 4x4 integer tensor as expected result:
# [[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15]]
local_tensor = paddle.to_tensor(
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]],
dtype='int32',
)
sharded_state_dict = {}
sharded_state_dict["t"] = make_replicated_sharded_weight(
"t", local_tensor
)
save_state_dict(sharded_state_dict, self._ckpt_path, save_replicas=True)
def test_save_state_dict_with_two_devices(self):
# Construct a 4x4 integer tensor as expected result:
# [[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15]]
local_tensor = paddle.to_tensor(
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]],
dtype='int32',
)
sharded_state_dict = {}
sharded_state_dict["t"] = make_replicated_sharded_weight(
"t", local_tensor
)
save_state_dict(sharded_state_dict, self._ckpt_path, save_replicas=True)
paddle.distributed.barrier()
def run_test_case(self):
device_num = int(os.getenv("device_num"))
if device_num == 1:
self.test_save_state_dict_with_one_device()
elif device_num == 2:
self.test_save_state_dict_with_two_devices()
if __name__ == "__main__":
TestSaveStateDict().run_test_case()
TestSaveShardedStateDict().run_test_case()
TestSaveShardedStateDictWithReplica().run_test_case()
@@ -0,0 +1,253 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 os
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.io import BatchSampler, DataLoader, Dataset
class RandomDataset(Dataset):
def __init__(self, seq_len, hidden, num_samples=100):
super().__init__()
self.seq_len = seq_len
self.hidden = hidden
self.num_samples = num_samples
def __getitem__(self, index):
input = np.random.uniform(size=[self.seq_len, self.hidden]).astype(
"float32"
)
return input
def __len__(self):
return self.num_samples
class DistMlpModel(paddle.nn.Layer):
def __init__(self, mesh):
super().__init__()
self.w0 = self.create_parameter(shape=[1024, 4096])
self.w1 = self.create_parameter(shape=[4096, 1024])
self.mesh = mesh
self.w0 = dist.shard_tensor(
self.w0, mesh, [dist.Replicate(), dist.Shard(1)]
)
self.w1 = dist.shard_tensor(
self.w1, mesh, [dist.Replicate(), dist.Shard(0)]
)
def forward(self, x):
x = dist.shard_tensor(x, self.mesh, [dist.Shard(0), dist.Replicate()])
y = paddle.matmul(x, self.w0)
z = paddle.matmul(y, self.w1)
return z
class MultiMlpModel(paddle.nn.Layer):
def __init__(self, mesh):
super().__init__()
self.layer1 = DistMlpModel(mesh)
self.layer2 = DistMlpModel(mesh)
def forward(self, x):
y = self.layer1(x)
z = self.layer2(x)
return z
class SingleMlpModel(paddle.nn.Layer):
def __init__(self):
super().__init__()
self.w0 = self.create_parameter(shape=[1024, 4096])
self.w1 = self.create_parameter(shape=[4096, 1024])
def forward(self, x):
y = paddle.matmul(x, self.w0)
z = paddle.matmul(y, self.w1)
return z
class TestDistCheckpoint:
def __init__(self):
np.random.seed(42)
self.mesh = dist.ProcessMesh([[0, 1], [2, 3]], dim_names=['dp', 'mp'])
self.temp_dir = os.getenv("ckpt_path")
def _get_single_loss(self, dataloader, unsharded_state_dict):
with paddle.LazyGuard():
model = SingleMlpModel()
model.w0.set_value(unsharded_state_dict['w0'])
model.w1.set_value(unsharded_state_dict['w1'])
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=model.parameters()
)
losses = []
for step, inputs in enumerate(dataloader):
data = inputs
logits = model(data)
loss = paddle.mean(logits)
losses.append(float(loss))
loss.backward()
opt.step()
opt.clear_grad()
return losses[0]
def _get_dist_loss(self, dataloader, sharded_state_dict):
with paddle.LazyGuard():
model = DistMlpModel(self.mesh)
model.w0.set_value(sharded_state_dict['w0'])
model.w1.set_value(sharded_state_dict['w1'])
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=model.parameters()
)
losses = []
for step, inputs in enumerate(dataloader):
data = inputs
logits = model(data)
loss = paddle.mean(logits)
loss.backward()
opt.step()
opt.clear_grad()
losses.append(float(loss))
return losses[0]
def dist_checkpoint(self, offload=False, safetensors=True, unique_id=0):
model_path = os.path.join(self.temp_dir, f'model.{unique_id}')
opt_path = os.path.join(self.temp_dir, f'opt.{unique_id}')
# Test checkpoint saving
with paddle.LazyGuard():
model = DistMlpModel(self.mesh)
for p in model.parameters():
p.initialize()
dataset = RandomDataset(128, 1024)
sampler = BatchSampler(
dataset,
batch_size=4,
)
dataloader = DataLoader(
dataset,
batch_sampler=sampler,
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=model.parameters()
)
opt = dist.shard_optimizer(opt)
for step, inputs in enumerate(dataloader):
data = inputs
logits = model(data)
loss = paddle.mean(logits)
loss.backward()
opt.step()
opt.clear_grad()
dist.save_state_dict(
model.state_dict(), model_path, safetensors=safetensors
)
dist.save_state_dict(
opt.state_dict(), opt_path, safetensors=safetensors
)
unsharded_state_dict = dist.load_merged_state_dict(
model_path, offload=offload, safetensors=safetensors
)
# Get single loss
single_loss = self._get_single_loss(dataloader, unsharded_state_dict)
shard_state_dict = model.state_dict()
dist.load_state_dict(
shard_state_dict, model_path, safetensors=safetensors
)
# Get distributed loss
dist_loss = self._get_dist_loss(dataloader, shard_state_dict)
np.testing.assert_array_equal(
unsharded_state_dict['w0'].numpy(), shard_state_dict['w0'].numpy()
)
np.testing.assert_array_equal(
unsharded_state_dict['w1'].numpy(), shard_state_dict['w1'].numpy()
)
def test_dist_checkpoint(self):
self.dist_checkpoint(True, True, 0)
self.dist_checkpoint(False, True, 1)
self.dist_checkpoint(True, False, 2)
self.dist_checkpoint(False, False, 3)
def count_files_in_temp_dir(self, single_path):
if not os.path.exists(single_path):
return 0
files = [
f
for f in os.listdir(single_path)
if os.path.isfile(os.path.join(single_path, f))
]
return len(files)
def test_checkpoint_load_merge_save(self):
model_path = os.path.join(self.temp_dir, 'model')
single_path = os.path.join(self.temp_dir, 'single_model')
# Test checkpoint saving
with paddle.LazyGuard():
model = MultiMlpModel(self.mesh)
for p in model.parameters():
p.initialize()
dataset = RandomDataset(128, 1024)
sampler = BatchSampler(
dataset,
batch_size=4,
)
dataloader = DataLoader(
dataset,
batch_sampler=sampler,
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=model.parameters()
)
opt = dist.shard_optimizer(opt)
for step, inputs in enumerate(dataloader):
data = inputs
logits = model(data)
loss = paddle.mean(logits)
loss.backward()
opt.step()
opt.clear_grad()
dist.save_state_dict(model.state_dict(), model_path, safetensors=False)
dist.flex_checkpoint.dcp.load_state_dict.merge_sharded_state_dict(
model_path, single_path, offload=True, safetensors=False
)
# assert self.count_files_in_temp_dir(single_path) == 5, (
# f"Expected 5 files in temp dir, but got {self.count_files_in_temp_dir(single_path)}"
# )
if __name__ == '__main__':
TestDistCheckpoint().test_dist_checkpoint()
TestDistCheckpoint().test_checkpoint_load_merge_save()
@@ -0,0 +1,330 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 paddle
import paddle.distributed as dist
import paddle.nn.functional as F
from paddle import nn
from paddle.nn.functional.flash_attention import _math_attention
class SDPALayer(paddle.nn.Layer):
def __init__(self, config):
super().__init__()
self.config = config
def forward(self, query, key, value, **kwargs):
if (
self.config.context_parallel is True
or self.config.sep_parallel is True
) and (
int(paddle.version.cuda().split(".")[0]) >= 11
and paddle.device.cuda.get_device_capability()[0] >= 8
):
out = paddle.nn.functional.scaled_dot_product_attention(
query, key, value, **kwargs
)
else:
out, _ = _math_attention(
query,
key,
value,
causal=kwargs.get("is_causal", False),
)
return out
class LlamaAttention(nn.Layer):
def __init__(self, config):
super().__init__()
self.config = config
self.hidden_size = self.config.hidden_size
self.num_heads = self.config.num_attention_heads
self.head_dim = self.hidden_size // self.config.num_attention_heads
self.sdpa = SDPALayer(config)
self.q_proj = nn.Linear(
self.hidden_size,
self.hidden_size,
bias_attr=True,
)
self.k_proj = nn.Linear(
self.hidden_size,
self.hidden_size,
bias_attr=True,
)
self.v_proj = nn.Linear(
self.hidden_size,
self.hidden_size,
bias_attr=True,
)
self.o_proj = nn.Linear(
self.hidden_size,
self.hidden_size,
bias_attr=True,
)
def forward(self, hidden_states):
query_states = self.q_proj(hidden_states).reshape(
shape=[0, 0, self.num_heads, self.head_dim]
)
key_states = self.k_proj(hidden_states).reshape(
shape=[0, 0, self.num_heads, self.head_dim]
)
value_states = self.v_proj(hidden_states).reshape(
shape=[0, 0, self.num_heads, self.head_dim]
)
bsz, q_len, _, _ = query_states.shape
outputs = self.sdpa(
query_states,
key_states,
value_states,
is_causal=True,
)
attn_output = outputs.reshape(
[-1, q_len, self.head_dim * self.num_heads]
)
attn_output = self.o_proj(attn_output)
return attn_output
class LlamaMLP(nn.Layer):
def __init__(self, config):
super().__init__()
self.config = config
self.hidden_size = self.config.hidden_size
self.intermediate_size = self.config.intermediate_size
self.gate_proj = nn.Linear(
self.hidden_size, self.intermediate_size, bias_attr=False
)
self.up_proj = nn.Linear(
self.hidden_size, self.intermediate_size, bias_attr=False
)
self.down_proj = nn.Linear(
self.intermediate_size, self.hidden_size, bias_attr=False
)
def forward(self, x, test_for_list_input_output):
out = self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
return out, test_for_list_input_output
class LlamaRMSNorm(nn.Layer):
def __init__(self, config):
super().__init__()
self.config = config
self.hidden_size = self.config.hidden_size
self.weight = paddle.create_parameter(
shape=[self.hidden_size],
dtype=paddle.get_default_dtype(),
default_initializer=nn.initializer.Constant(1.0),
)
self.variance_epsilon = self.config.rms_norm_eps
def forward(self, hidden_states):
variance = hidden_states.astype("float32").pow(2).mean(-1, keepdim=True)
hidden_states = (
paddle.rsqrt(variance + self.variance_epsilon) * hidden_states
)
if self.weight.dtype in [paddle.float16, paddle.bfloat16]:
hidden_states = paddle.cast(hidden_states, self.weight.dtype)
return hidden_states * self.weight
class LlamaDecoderLayer(nn.Layer):
def __init__(self, config):
super().__init__()
self.config = config
self.self_attn = LlamaAttention(self.config)
self.mlp = LlamaMLP(self.config)
self.input_layernorm = LlamaRMSNorm(self.config)
self.post_attention_layernorm = LlamaRMSNorm(self.config)
def forward(self, hidden_states, global_tensor):
residual = hidden_states + global_tensor
hidden_states = self.input_layernorm(hidden_states)
hidden_states = self.self_attn(hidden_states)
hidden_states = residual + hidden_states
residual = hidden_states
hidden_states = self.post_attention_layernorm(hidden_states)
hidden_states, _ = self.mlp(hidden_states, "ONLY_FOR_TEST")
hidden_states = residual + hidden_states
return (hidden_states,)
class GlobalOutputNet(nn.Layer):
def __init__(self, config) -> None:
super().__init__()
self.config = config
def forward(self, input):
return (
input
if input is not None
else paddle.rand([self.config.hidden_size], dtype="float32")
)
class LlamaModel(nn.Layer):
def __init__(self, config, position_embedding=False):
super().__init__()
self.config = config
self.vocab_size = self.config.vocab_size
self.hidden_size = self.config.hidden_size
self.embed_tokens = nn.Embedding(
self.vocab_size,
self.hidden_size,
)
self.position_embedding = (
nn.Embedding(
self.vocab_size,
self.hidden_size,
)
if position_embedding
else None
)
self.global_layer = GlobalOutputNet(self.config)
decoder_layers = []
for i in range(self.config.num_hidden_layers):
decoder_layers.append(LlamaDecoderLayer(self.config))
self.layers = nn.LayerList(decoder_layers)
self.norm = LlamaRMSNorm(self.config)
def forward(self, input_ids):
hidden_states = self.embed_tokens(input_ids)
if self.position_embedding is not None:
ones = paddle.ones(input_ids.shape, dtype="int64")
seq_length = paddle.cumsum(ones, axis=-1)
position_ids = seq_length - ones
position_embeddings = self.position_embedding(position_ids)
hidden_states = hidden_states + position_embeddings
global_tensor = self.global_layer(None)
for idx, (decoder_layer) in enumerate(self.layers):
tuple_hidden_states = decoder_layer(
hidden_states=hidden_states, global_tensor=global_tensor
)
hidden_states = tuple_hidden_states[0]
hidden_states = self.norm(hidden_states)
return hidden_states
class LlamaLMHead(nn.Layer):
def __init__(self, config, weight=None):
super().__init__()
self.config = config
self.transpose_y = False
if weight is not None:
self.weight = weight
self.transpose_y = True
else:
self.weight = self.create_parameter(
shape=[self.config.hidden_size, self.config.vocab_size],
dtype=paddle.get_default_dtype(),
)
def forward(self, hidden_states):
logits = paddle.matmul(
hidden_states, self.weight, transpose_y=self.transpose_y
)
return logits
class LlamaPretrainingCriterion(paddle.nn.Layer):
def __init__(self, config):
super().__init__()
self.ignore_index = getattr(config, "ignore_index", -100)
self.config = config
self.loss_func = paddle.nn.CrossEntropyLoss(
reduction="none", ignore_index=self.ignore_index
)
def forward(self, prediction_scores, masked_lm_labels):
if isinstance(prediction_scores, paddle.Tensor):
masked_lm_loss = self.loss_func(
prediction_scores.astype("float32")._use_gpudnn(False),
masked_lm_labels.unsqueeze(2),
)
else:
masked_lm_loss = self.loss_func(
prediction_scores.astype("float32"),
masked_lm_labels.unsqueeze(2),
)
if paddle.device.is_compiled_with_xpu():
def LocalLoss(x, mask):
masked_lm_loss = paddle.masked_select(x, mask).astype("float32")
loss = paddle.mean(masked_lm_loss).unsqueeze(0)
return loss.unsqueeze(0)
loss_func = dist.local_map(
LocalLoss,
[[dist.Shard(0), dist.Replicate()]],
[[dist.Shard(0), dist.Replicate()], None],
masked_lm_loss.process_mesh,
True,
)
loss = loss_func(masked_lm_loss, masked_lm_loss > 0)
loss = loss.mean()
return loss
masked_lm_loss = paddle.masked_select(
masked_lm_loss, masked_lm_loss > 0
).astype("float32")
loss = paddle.mean(masked_lm_loss)
return loss
class LlamaForCausalLM(nn.Layer):
enable_to_static_method = True
def __init__(self, config, share_embedding=False, position_embedding=False):
super().__init__()
self.config = config
self.llama = LlamaModel(self.config, position_embedding)
if share_embedding:
self.lm_head = LlamaLMHead(
self.config, self.llama.embed_tokens.weight
)
else:
self.lm_head = LlamaLMHead(self.config)
def forward(self, input_ids=None):
input_ids.stop_gradient = True
hidden_states = self.llama(input_ids)
logits = self.lm_head(hidden_states)
return logits
@@ -0,0 +1,448 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 logging
import math
import re
import numpy as np
import paddle
import paddle.nn.functional as F
from paddle import nn
class LoRALinear(nn.Linear):
# LoRA implemented in a dense layer
def __init__(
self,
in_features: int,
out_features: int,
r: int = 0,
lora_alpha: int = 1,
lora_dropout: float = 0.0,
use_quick_lora: bool = False,
rslora: bool = False,
lora_plus_scale: float = 1.0,
pissa: bool = False,
lora_use_mixer: bool = False,
use_mora: bool = False,
**kwargs,
):
nn.Linear.__init__(self, in_features, out_features, **kwargs)
if not isinstance(r, int) or r <= 0:
raise ValueError("Lora rank r should be a positive integer")
self.use_mora = use_mora
self.r = r
self.lora_alpha = lora_alpha
# Optional dropout
if lora_dropout > 0.0:
self.lora_dropout = nn.Dropout(p=lora_dropout)
else:
self.lora_dropout = lambda x: x
# Mark the weight as unmerged
self.merged = False
self.pissa = pissa
self.lora_use_mixer = lora_use_mixer
# Actual trainable parameters
if use_mora: # reset the rank and create high rank matrix
self.in_features = in_features
self.out_features = out_features
new_r = int(math.sqrt((in_features + out_features) * r) + 0.5)
new_r = new_r // 2 * 2
self.r = new_r
self.lora_A = self.create_parameter(
shape=[self.r, self.r],
dtype=self._dtype,
is_bias=False,
default_initializer=nn.initializer.Constant(value=0.0),
)
self.cos = None
self.sin = None
# Count the number of tiles
self.rb1 = (
self.in_features // self.r
if self.in_features % self.r == 0
else self.in_features // self.r + 1
)
self.rb2 = (
self.out_features // self.r
if self.out_features % self.r == 0
else self.out_features // self.r + 1
)
self.rope_init()
else:
self.lora_A = self.create_parameter(
shape=[in_features, r],
dtype=self._dtype,
is_bias=False,
)
if self.lora_use_mixer:
self.lora_AB = self.create_parameter(
shape=[r, r],
dtype=self._dtype,
is_bias=False,
)
self.lora_B = self.create_parameter(
shape=[r, out_features],
dtype=self._dtype,
is_bias=False,
attr=paddle.ParamAttr(
initializer=paddle.nn.initializer.Constant(value=0.0),
learning_rate=lora_plus_scale,
),
)
self.apply_pissa = False
if use_mora or pissa:
self.scaling = 1.0
elif not rslora:
self.scaling = self.lora_alpha / self.r
else:
self.scaling = self.lora_alpha / math.sqrt(self.r)
# Freezing the pre-trained weight matrix
self.weight.stop_gradient = True
self._use_quick_lora = use_quick_lora and lora_dropout == 0.0
self.disable_lora = False
def pissa_init(self, rank):
weight = self.weight
dtype = weight.dtype
if dtype != paddle.float32:
weight = weight.astype(paddle.float32)
U, S, Vh = paddle.linalg.svd(weight.data, full_matrices=False)
Ur = U[:, :rank]
Sr = S[:rank]
Vhr = Vh[:rank]
lora_A = Ur @ paddle.diag(paddle.sqrt(Sr))
lora_B = paddle.diag(paddle.sqrt(Sr)) @ Vhr
self.lora_A.set_value(lora_A.astype(dtype))
self.lora_B.set_value(lora_B.astype(dtype))
res = weight.data - lora_A @ lora_B
weight = res.astype(dtype)
self.weight.set_value(weight)
def rope_init(self):
if self.cos is None or self.sin is None:
inv_freq = 1.0 / (
10000
** (paddle.arange(0, self.r, 2, dtype=paddle.float32) / self.r)
)
t = paddle.arange(self.rb1, dtype=paddle.float32)
freqs = t.unsqueeze(1) @ inv_freq.unsqueeze(0)
emb = paddle.concat([freqs, freqs], axis=-1)
self.cos = paddle.unsqueeze(paddle.cos(emb), axis=0).astype(
self._dtype
)
self.sin = paddle.unsqueeze(paddle.sin(emb), axis=0).astype(
self._dtype
)
@property
def use_quick_lora(self):
return self._use_quick_lora and self.training and not self.merged
def _apply_mora(self, x):
r = self.r
# Calculate grouping
sum_inter = self.in_features // r
# padding
if self.in_features % r != 0:
pad_size = r - self.in_features % r
x = paddle.concat([x, x[..., :pad_size]], axis=-1)
sum_inter += 1
# reshape the input to apply RoPE
in_x = x.reshape([*x.shape[:-1], sum_inter, r])
# apply RoPE rotation
rh_in_x = paddle.concat(
[-in_x[..., r // 2 :], in_x[..., : r // 2]], axis=-1
)
in_x = in_x * self.cos + rh_in_x * self.sin
# matmul with high rank matrix
out_x = in_x @ self.lora_A
# reshape the output
out_x = out_x.reshape([*x.shape[:-1], -1])[..., : self.out_features]
if out_x.shape[-1] < self.out_features:
repeat_time = self.out_features // out_x.shape[-1]
if self.out_features % out_x.shape[-1] != 0:
repeat_time += 1
out_x = paddle.concat([out_x] * repeat_time, axis=-1)[
..., : self.out_features
]
return out_x
def get_delta_weight(self, lora_A=None, lora_B=None, lora_AB=None):
# compute the delta weightwhich is used to merge weights
if self.lora_use_mixer:
lora_A = lora_A if lora_A is not None else self.lora_A
lora_B = lora_B if lora_B is not None else self.lora_B
lora_AB = lora_AB if lora_AB is not None else self.lora_AB
delta_weight = lora_A @ lora_AB @ lora_B * self.scaling
elif self.use_mora:
lora_A = lora_A if lora_A is not None else self.lora_A
r = self.r
# compute padding
pad_size = (
r - self.in_features % r if self.in_features % r != 0 else 0
)
# initialize weights
w = paddle.zeros(
[self.in_features + pad_size, self.in_features],
dtype=lora_A.dtype,
)
# create the weights after rotation
aw2 = paddle.concat(
[lora_A[:, r // 2 :], -lora_A[:, : r // 2]], axis=-1
)
# apply RoPE
for i in range(self.rb1 - 1):
w[i * r : (i + 1) * r, i * r : (i + 1) * r] = (
aw2 * self.sin[:, i] + lora_A * self.cos[:, i]
)
# Process the last chunk that may be incomplete
i = self.rb1 - 1
w[i * r :, i * r :] = (
aw2 * self.sin[:, i] + lora_A * self.cos[:, i]
)[:, : r - pad_size]
# padding
if pad_size > 0:
w[i * r :, :pad_size] = (
aw2 * self.sin[:, i] + lora_A * self.cos[:, i]
)[:, r - pad_size :]
# reshape the weights
if self.in_features < self.out_features:
w = paddle.concat([w] * self.rb2, axis=0)[: self.out_features]
else:
w = w[: self.out_features]
final_weight = w
delta_weight = final_weight.T
else:
lora_A = lora_A if lora_A is not None else self.lora_A
lora_B = lora_B if lora_B is not None else self.lora_B
delta_weight = lora_A @ lora_B * self.scaling
return delta_weight
def merge(self):
if not self.merged:
delta_weight = self.get_delta_weight()
new_weight = self.weight + delta_weight
self.weight.set_value(new_weight)
self.merged = True
def unmerge(self):
if self.merged:
delta_weight = self.get_delta_weight()
new_weight = self.weight - delta_weight
self.weight.set_value(new_weight)
self.merged = False
def forward(self, input: paddle.Tensor, *args, **kwargs):
if not self.apply_pissa and self.pissa:
self.pissa_init(self.r)
self.apply_pissa = True
if self.disable_lora or self.merged:
result = F.linear(
x=input, weight=self.weight, bias=self.bias, name=self.name
)
elif self.use_mora:
result = F.linear(
x=input, weight=self.weight, bias=self.bias, name=self.name
)
input = self.lora_dropout(input)
mora_out = self._apply_mora(input)
result += mora_out
else:
result = F.linear(
x=input, weight=self.weight, bias=self.bias, name=self.name
)
if self.lora_use_mixer:
result += (
self.lora_dropout(input)
@ self.lora_A
@ self.lora_AB
@ self.lora_B
) * self.scaling
else:
result += (
self.lora_dropout(input) @ self.lora_A @ self.lora_B
) * self.scaling
return result
def extra_repr(self):
name = f", name={self.name}" if self.name else ""
return f"in_features={self.weight.shape[0]}, out_features={self.weight.shape[1]}, rank={self.r}{name}"
lora_layers = {
"LoRALinear": LoRALinear,
}
LoRALinear = lora_layers["LoRALinear"]
AVAILABLE_LAYERS = [
LoRALinear,
]
class LoRAModel(nn.Layer):
def __init__(self, model, lora_config) -> None:
super().__init__()
self.model = self.get_lora_model(model, lora_config)
self.lora_config = lora_config
logging.info("Mark only lora and trainable_module as trainable.")
self.mark_only_lora_as_trainable()
def forward(self, input_ids):
return self.model(input_ids)
def _find_and_replace_module(self, model, module_name, lora_config):
parent_module = model
attribute_chain = module_name.split(".")
for name in attribute_chain[:-1]:
parent_module = getattr(parent_module, name)
module = getattr(parent_module, attribute_chain[-1])
lora_module = None
if isinstance(module, nn.Linear):
lora_module = LoRALinear(
in_features=module.weight.shape[0],
out_features=module.weight.shape[1],
r=lora_config.r,
lora_alpha=lora_config.lora_alpha,
lora_dropout=lora_config.lora_dropout,
rslora=lora_config.rslora,
lora_plus_scale=lora_config.lora_plus_scale,
pissa=lora_config.pissa,
bias_attr=False if module.bias is None else None,
use_quick_lora=lora_config.use_quick_lora,
lora_use_mixer=lora_config.lora_use_mixer,
use_mora=lora_config.use_mora,
)
if lora_module is None:
raise ValueError(
f"LoRA strategy only supports paddle.nn.Linear or paddle.distributed.fleet.meta_parallel.ColumnParallelLinear or paddlenlp.transformers.sequence_utils. {module}({module_name} {type(module).__name__}) is not supported。"
)
lora_module.weight = module.weight
if module.bias is not None:
lora_module.bias = module.bias
setattr(parent_module, attribute_chain[-1], lora_module)
def print_trainable_parameters(self) -> None:
freeze_numel = 0
trainable_numel = 0
for _, weight in self.model.state_dict().items():
if weight.stop_gradient:
freeze_numel += np.prod(weight.shape)
else:
trainable_numel += np.prod(weight.shape)
logging.debug(
f"Frozen parameters: {freeze_numel:.2e} || Trainable parameters:{trainable_numel:.2e} || Total parameters:{freeze_numel + trainable_numel:.2e}|| Trainable:{trainable_numel / (freeze_numel + trainable_numel):.2%}"
)
def mark_only_lora_as_trainable(self) -> None:
for _, layer in self.model.named_sublayers():
if isinstance(layer, LoRALinear):
for name, weight in layer.state_dict().items():
if (
self.lora_config.trainable_bias in ["lora", "all"]
and "bias" in name
):
weight.stop_gradient = False
elif "lora" in name:
weight.stop_gradient = False
else:
weight.stop_gradient = True
else:
for name, weight in layer.state_dict().items():
if (
self.lora_config.trainable_bias == "all"
and "bias" in name
):
weight.stop_gradient = False
else:
weight.stop_gradient = True
if self.lora_config.trainable_modules is not None:
for name, weight in self.model.state_dict().items():
if any(
re.fullmatch(trainable_module, name)
for trainable_module in self.lora_config.trainable_modules
):
weight.stop_gradient = False
def get_lora_model(self, model, lora_config):
if lora_config.target_modules is None:
return model
elif isinstance(lora_config.target_modules, str):
target_modules = [lora_config.target_modules]
else:
target_modules = lora_config.target_modules
for target_module in target_modules:
for i in model.named_sublayers():
module_name = i[0]
if re.fullmatch(target_module, module_name):
self._find_and_replace_module(
model, module_name, lora_config
)
return model
def train(self):
self.training = True
self.model.training = True
for layer in self.model.sublayers():
layer.training = True
layer.train()
def eval(self):
self.training = False
self.model.training = False
for layer in self.model.sublayers():
layer.training = False
layer.eval()
def disable_lora(self):
for _, layer in self.model.named_sublayers():
if any(
isinstance(layer, lora_layer) for lora_layer in AVAILABLE_LAYERS
):
layer.disable_lora = True
def enable_lora(self):
for _, layer in self.model.named_sublayers():
if any(
isinstance(layer, lora_layer) for lora_layer in AVAILABLE_LAYERS
):
layer.disable_lora = False
def merge(self):
for _, layer in self.model.named_sublayers():
if any(
isinstance(layer, lora_layer) for lora_layer in AVAILABLE_LAYERS
):
layer.merge()
def unmerge(self):
for _, layer in self.model.named_sublayers():
if any(
isinstance(layer, lora_layer) for lora_layer in AVAILABLE_LAYERS
):
layer.unmerge()
@@ -0,0 +1,67 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestSemiAutoParallelCrossMeshReshard(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(
num_of_devices=4,
timeout=120,
nnode=1,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_simple_net_cross_mesh_reshard(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_cross_mesh_reshard.py",
user_defined_envs=envs,
)
class TestSemiAutoParallelNdCrossMeshReshard(
test_base.CommunicationTestDistBase
):
def setUp(self):
super().setUp(num_of_devices=8, timeout=200, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_simple_net_hybrid_strategy(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_nd_cross_mesh_reshard.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,43 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 tempfile
import unittest
import collective.test_communication_api_base as test_base
class TestDistCheckpointMerge(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=4, timeout=50, nnode=1)
self._default_envs = {}
self._changeable_envs = {"backend": ["gpu"]}
def test_merge_checkpoint(self):
ckpt_path = tempfile.TemporaryDirectory()
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"semi_flexcheckpoint_merge.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,162 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestProcessMeshDPGroupConsistency(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=2, timeout=200, nnode=1)
def test_dp_parallel(self):
"""Test data parallel group creation and consistency"""
_default_envs = {
"dp": "2",
"mp": "1",
"pp": "1",
"parallel_type": "dp",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
}
_changeable_envs = {
"backend": ["gpu"],
}
envs_list = test_base.gen_product_envs_list(
_default_envs, _changeable_envs
)
for envs in envs_list:
self.run_test_case(
"test_process_mesh_group_consistency.py",
user_defined_envs=envs,
)
class TestProcessMeshMPGroupConsistency(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=2, timeout=200, nnode=1)
def test_mp_parallel(self):
"""Test model parallel group creation and consistency"""
_default_envs = {
"dp": "1",
"mp": "2",
"pp": "1",
"parallel_type": "mp",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
}
_changeable_envs = {
"backend": ["gpu"],
}
envs_list = test_base.gen_product_envs_list(
_default_envs, _changeable_envs
)
for envs in envs_list:
self.run_test_case(
"test_process_mesh_group_consistency.py",
user_defined_envs=envs,
)
class TestProcessMeshPPGroupConsistency(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=2, timeout=200, nnode=1)
def test_pp_parallel(self):
"""Test pipeline parallel group creation and consistency"""
_default_envs = {
"dp": "1",
"mp": "1",
"pp": "2",
"parallel_type": "pp",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
}
_changeable_envs = {
"backend": ["gpu"],
}
envs_list = test_base.gen_product_envs_list(
_default_envs, _changeable_envs
)
for envs in envs_list:
self.run_test_case(
"test_process_mesh_group_consistency.py",
user_defined_envs=envs,
)
class TestProcessMeshSEPGroupConsistency(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=2, timeout=200, nnode=1)
def test_sep_parallel(self):
"""Test sequence parallel group creation and consistency"""
_default_envs = {
"dp": "1",
"mp": "1",
"pp": "1",
"sep": "2",
"sharding": "1",
"parallel_type": "sep",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
}
_changeable_envs = {
"backend": ["gpu"],
}
envs_list = test_base.gen_product_envs_list(
_default_envs, _changeable_envs
)
for envs in envs_list:
self.run_test_case(
"test_process_mesh_group_consistency.py",
user_defined_envs=envs,
)
class TestProcessMeshShardingGroupConsistency(
test_base.CommunicationTestDistBase
):
def setUp(self):
super().setUp(num_of_devices=2, timeout=200, nnode=1)
def test_sharding_parallel(self):
"""Test sharding parallel group creation and consistency"""
_default_envs = {
"dp": "1",
"mp": "1",
"pp": "1",
"sep": "1",
"sharding": "2",
"parallel_type": "sharding",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
}
_changeable_envs = {
"backend": ["gpu"],
}
envs_list = test_base.gen_product_envs_list(
_default_envs, _changeable_envs
)
for envs in envs_list:
self.run_test_case(
"test_process_mesh_group_consistency.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main() # python run
@@ -0,0 +1,73 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestSemiAutoParallel2DGlobalMeshReshard(
test_base.CommunicationTestDistBase
):
def setUp(self):
super().setUp(
num_of_devices=4,
timeout=120,
nnode=1,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_2d_global_mesh_reshard(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_2d_global_mesh_reshard.py",
user_defined_envs=envs,
)
class TestSemiAutoParallel3DGlobalMeshReshard(
test_base.CommunicationTestDistBase
):
def setUp(self):
super().setUp(
num_of_devices=8,
timeout=120,
nnode=1,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_3d_global_mesh_reshard(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_3d_global_mesh_reshard.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,154 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 tempfile
import unittest
import collective.test_communication_api_base as test_base
class TestShardingParallelAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=2, timeout=180, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "2",
"mp": "1",
"pp": "1",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["False"],
"sharding_stage": ["0", "1"],
"test_share_embedding": [
"1",
],
"test_position_embedding": [
"1",
],
}
def test_simple_net_dp2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
class TestPipelineParallelAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=2, timeout=180, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "1",
"mp": "1",
"pp": "2",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["False"],
"num_hidden_layers": ["3", "4"],
"test_share_embedding": [
"1",
],
"test_position_embedding": [
"1",
],
}
def test_simple_net_pp2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
class TestTensorParallelAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=2, timeout=180, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "1",
"mp": "2",
"pp": "1",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["true"],
"use_lazy_init": ["true", "false"],
"sequence_parallel": ["true", "false"],
"prepare_input_output": ["true", "false"],
"test_share_embedding": [
"1",
],
"test_position_embedding": [
"1",
],
}
def test_simple_net_mp2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
has_checked_lazy_init = False
has_checked_pure_mp = False
for envs in envs_list:
if envs['use_lazy_init'] == 'true':
if has_checked_lazy_init:
continue
has_checked_lazy_init = True
if envs['sequence_parallel'] != 'true':
if has_checked_pure_mp:
continue
has_checked_pure_mp = True
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,152 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 tempfile
import unittest
import collective.test_communication_api_base as test_base
class TestMPPPAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=4, timeout=180, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "1",
"mp": "2",
"pp": "2",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["true"],
"use_lazy_init": ["true"],
"sequence_parallel": ["true"],
"prepare_input_output": ["false"],
"test_share_embedding": [
"0",
],
"test_position_embedding": [
"1",
],
"one_api": ["true", "false"],
}
def test_simple_net_mp2_pp2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
class TestDPPPAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=4, timeout=180, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "2",
"mp": "1",
"pp": "2",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["true"],
"use_lazy_init": ["true"],
"num_hidden_layers": ["3", "4"],
"sharding_stage": ["0"],
"test_share_embedding": [
"1",
],
"test_position_embedding": [
"1",
],
"one_api": ["true", "false"],
}
def test_simple_net_dp2_pp2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
class TestDPMPAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=4, timeout=180, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "2",
"mp": "2",
"pp": "1",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["true"],
"use_lazy_init": ["true"],
"sequence_parallel": ["true"],
"prepare_input_output": ["false"],
"sharding_stage": ["0"],
"test_share_embedding": [
"1",
],
"test_position_embedding": [
"1",
],
"one_api": ["true", "false"],
}
def test_simple_net_dp2_tp2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,115 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 tempfile
import unittest
import collective.test_communication_api_base as test_base
class TestDPMPCPAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=4, timeout=180, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "1",
"mp": "2",
"pp": "1",
"sep": "2",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["true"],
"use_lazy_init": ["true"],
"sequence_parallel": ["false"],
"context_parallel": ["true"],
"prepare_input_output": ["false"],
"sharding_stage": ["0"],
"test_share_embedding": [
"1",
],
"test_position_embedding": [
"0",
],
"one_api": ["true", "false"],
}
def test_simple_net_mp2_cp2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
class TestDPMPSEPAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=4, timeout=180, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "1",
"mp": "1",
"pp": "2",
"sep": "2",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["true"],
"use_lazy_init": ["true"],
"sequence_parallel": ["false"],
"sep_parallel": ["true"],
"context_parallel": ["false"],
"prepare_input_output": ["false"],
"sharding_stage": ["0"],
"test_share_embedding": [
"1",
],
"test_position_embedding": [
"0",
],
"one_api": ["true", "false"],
}
def test_simple_net_mp2_sep2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,66 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 tempfile
import unittest
import collective.test_communication_api_base as test_base
class TestDPMPPPAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=8, timeout=180, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "2",
"mp": "2",
"pp": "2",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["true"],
"use_lazy_init": ["true"],
"sequence_parallel": ["true"],
"prepare_input_output": ["false"],
"sharding_stage": ["0", "1"],
"test_share_embedding": [
"0",
],
"test_position_embedding": [
"1",
],
"one_api": ["true", "false"],
}
def test_simple_net_dp2_mp2_pp2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,67 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 tempfile
import unittest
import collective.test_communication_api_base as test_base
class TestDPMPPPAPI(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=8, timeout=360, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"dp": "2",
"mp": "2",
"pp": "2",
"acc_step": "2",
}
self._changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["true"],
"use_lazy_init": ["false"],
"sequence_parallel": ["false"],
"prepare_input_output": ["false"],
"sharding_stage": ["0", "1"],
"test_share_embedding": [
"0",
],
"test_position_embedding": [
"1",
],
"one_api": ["true", "false"],
"test_lora": ["1"],
}
def test_simple_lora_net_dp2_mp2_pp2(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"parallel_api.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,61 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestPIRNdMeshReshard(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(
num_of_devices=4,
timeout=20,
nnode=1,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"backend": "gpu",
}
def test_simple_net_reshard(self):
self.run_test_case(
"pir_reshard_nd_mesh.py",
user_defined_envs=self._default_envs,
)
class TestPIRNdMeshReshardCrossMesh(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(
num_of_devices=8,
timeout=20,
nnode=1,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"backend": "gpu",
}
def test_simple_net_reshard_cross_mesh(self):
self.run_test_case(
"pir_reshard_nd_mesh_cross_mesh.py",
user_defined_envs=self._default_envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,46 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestProcessMeshPass(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(
num_of_devices=2,
timeout=50,
)
self._default_envs = {
"FLAGS_cudnn_deterministic": "1",
"FLAGS_enable_pir_api": "1",
}
self._changeable_envs = {
"backend": ["gpu"],
}
def test_process_mesh(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"process_mesh_demo_unittest.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,107 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 os
import paddle.distributed as dist
from paddle.distributed import fleet
class TestProcessMeshGroupConsistency:
def __init__(self):
# Get configuration from environment variables
self.dp = int(os.getenv("dp", "1"))
self.mp = int(os.getenv("mp", "1"))
self.pp = int(os.getenv("pp", "1"))
self.sep = int(os.getenv("sep", "1"))
self.sharding = int(os.getenv("sharding", "1"))
# Determine which parallel type to test
self.parallel_type = os.getenv("parallel_type", "dp")
def init_dist_env(self):
"""Initialize distributed environment"""
# Configure distributed strategy
dist_strategy = fleet.DistributedStrategy()
dist_strategy.hybrid_configs = {
"dp_degree": self.dp,
"mp_degree": self.mp,
"pp_degree": self.pp,
"sep_degree": self.sep,
"sharding_degree": self.sharding,
}
# Add corresponding configuration based on parallel type
if self.sep > 1:
dist_strategy.hybrid_configs["sep_degree"] = self.sep
if self.sharding > 1:
dist_strategy.hybrid_configs["sharding_degree"] = self.sharding
fleet.init(is_collective=True, strategy=dist_strategy)
def test_process_mesh_group_consistency(self):
"""Test consistency between ProcessMesh created groups and HCG created groups"""
# Create corresponding ProcessMesh and get corresponding HCG group based on parallel type
if self.parallel_type == "dp":
mesh = dist.ProcessMesh([0, 1], dim_names=["dp"])
hcg = fleet.get_hybrid_communicate_group()
group = mesh.get_group(dim_name="dp")
hcg_group = hcg.get_data_parallel_group()
elif self.parallel_type == "mp":
mesh = dist.ProcessMesh([0, 1], dim_names=["mp"])
hcg = fleet.get_hybrid_communicate_group()
group = mesh.get_group(dim_name="mp")
hcg_group = hcg.get_model_parallel_group()
elif self.parallel_type == "pp":
mesh = dist.ProcessMesh([0, 1], dim_names=["pp"])
hcg = fleet.get_hybrid_communicate_group()
group = mesh.get_group(dim_name="pp")
hcg_group = hcg.get_pipe_parallel_group()
elif self.parallel_type == "sep":
mesh = dist.ProcessMesh([0, 1], dim_names=["sep"])
hcg = fleet.get_hybrid_communicate_group()
group = mesh.get_group(dim_name="sep")
hcg_group = hcg.get_sep_parallel_group()
elif self.parallel_type == "sharding":
mesh = dist.ProcessMesh([0, 1], dim_names=["sharding"])
hcg = fleet.get_hybrid_communicate_group()
group = mesh.get_group(dim_name="sharding")
hcg_group = hcg.get_sharding_parallel_group()
else:
raise ValueError(f"Unsupported parallel type: {self.parallel_type}")
# Verify that group ranks are consistent
group_ranks = group.ranks
hcg_group_ranks = hcg_group.ranks
assert set(group_ranks) == set(hcg_group_ranks)
# Verify that group IDs are consistent
group_id = group.id
hcg_group_id = hcg_group.id
assert group_id == hcg_group_id
def run_test_cases(self):
"""Run test cases"""
self.init_dist_env()
self.test_process_mesh_group_consistency()
if __name__ == "__main__":
TestProcessMeshGroupConsistency().run_test_cases()
@@ -0,0 +1,183 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import tempfile
import unittest
import collective.test_communication_api_base as test_base
# should be set to FLAGS_enable_pir_api=1
os.environ['FLAGS_enable_pir_api'] = '0'
class TestSaveLoadStateDict(test_base.CommunicationTestDistBase):
def setUp(self):
self._default_envs = {}
self._changeable_envs = {"device_num": ["1", "2", "4", "8"]}
def test_reshard(self):
# save with 1 device
ckpt_path = tempfile.TemporaryDirectory()
ckpt_path_2 = tempfile.TemporaryDirectory()
ckpt_path_3 = tempfile.TemporaryDirectory()
super().setUp(num_of_devices=1, timeout=120, nnode=1)
self.run_test_case(
"semi_auto_save_state_dict.py",
user_defined_envs={
"device_num": "1",
"ckpt_path": ckpt_path.name,
"ckpt_path_2": ckpt_path_2.name,
"ckpt_path_3": ckpt_path_3.name,
},
)
# load with 1, 2, 4, 8 devices
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
envs["ckpt_path"] = ckpt_path.name
envs["ckpt_path_2"] = ckpt_path_2.name
envs["ckpt_path_3"] = ckpt_path_3.name
super().setUp(
num_of_devices=int(envs["device_num"]),
timeout=180,
nnode=1,
)
self.run_test_case(
"semi_auto_load_state_dict.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
ckpt_path_2.cleanup()
ckpt_path_3.cleanup()
# save with 2 devices
ckpt_path = tempfile.TemporaryDirectory()
ckpt_path_2 = tempfile.TemporaryDirectory()
ckpt_path_3 = tempfile.TemporaryDirectory()
super().setUp(num_of_devices=2, timeout=120, nnode=1)
self.run_test_case(
"semi_auto_save_state_dict.py",
user_defined_envs={
"device_num": "2",
"ckpt_path": ckpt_path.name,
"ckpt_path_2": ckpt_path_2.name,
"ckpt_path_3": ckpt_path_3.name,
},
)
# load with 1, 2, 4, 8 devices
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
envs["ckpt_path"] = ckpt_path.name
envs["ckpt_path_2"] = ckpt_path_2.name
envs["ckpt_path_3"] = ckpt_path_3.name
super().setUp(
num_of_devices=int(envs["device_num"]),
timeout=180,
nnode=1,
)
self.run_test_case(
"semi_auto_load_state_dict.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
ckpt_path_2.cleanup()
ckpt_path_3.cleanup()
def test_mutual_load_between_dynamic_and_static(self):
changeable_envs = {"device_num": ["2"]}
envs_list = test_base.gen_product_envs_list(
self._default_envs, changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
super().setUp(
num_of_devices=int(envs["device_num"]),
timeout=180,
nnode=1,
)
self.run_test_case(
"semi_auto_parallel_mutual_load_between_dynamic_and_static.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
def test_save_safetensors_load_fc(self):
"""Test saving safetensors files and loading with flex checkpoint."""
ckpt_path = tempfile.TemporaryDirectory()
super().setUp(num_of_devices=2, timeout=120, nnode=1)
self.run_test_case(
"save_safetensors_load_fc.py",
user_defined_envs={
"device_num": "2",
"ckpt_path": ckpt_path.name,
},
)
ckpt_path.cleanup()
def test_save_safetensors_load_fc_with_index(self):
"""Test saving safetensors files and loading with flex checkpoint when model.safetensors.index.json exists."""
ckpt_path = tempfile.TemporaryDirectory()
super().setUp(num_of_devices=2, timeout=120, nnode=1)
self.run_test_case(
"save_safetensors_load_fc.py",
user_defined_envs={
"device_num": "2",
"ckpt_path": ckpt_path.name,
"test_func": "test_save_safetensors_load_fc_with_index",
},
)
ckpt_path.cleanup()
def test_save_load_state_dict_with_aoa_config_reverse(self):
"""Test saving state dict and loading with flex checkpoint."""
ckpt_path = tempfile.TemporaryDirectory()
super().setUp(num_of_devices=1, timeout=60, nnode=1)
self.run_test_case(
"save_load_state_dict_with_aoa_config_reverse.py",
user_defined_envs={
"device_num": "1",
"ckpt_path": ckpt_path.name,
},
)
ckpt_path.cleanup()
def test_save_load_with_error_message(self):
"""Test logger missing key and unexpected keys."""
ckpt_path = tempfile.TemporaryDirectory()
envs_list = test_base.gen_product_envs_list(
self._default_envs, {"device_num": ["1", "2"]}
)
for envs in envs_list:
envs["ckpt_path"] = ckpt_path.name
super().setUp(
num_of_devices=int(envs["device_num"]),
timeout=60,
nnode=1,
)
self.run_test_case(
"test_save_load_with_error_message.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,133 @@
# Copyright (c) 2026 PaddlePaddle Authors. 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 os
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.distributed.flex_checkpoint.dcp.load_state_dict import (
load_state_dict,
)
class HuggingFaceModel(nn.Layer):
def __init__(self):
super().__init__()
self.huggingface = nn.Linear(2, 2, bias_attr=False)
class FCModel(nn.Layer):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(1, 2, bias_attr=False)
self.fc2 = nn.Linear(1, 2, bias_attr=False)
def init_hf_model_weights(model):
with paddle.no_grad():
w = paddle.to_tensor([[0, 1], [2, 3]], dtype="float16")
model.huggingface.weight.set_value(w)
def save_safetensors_model(model, ckpt_path):
import safetensors.numpy
os.makedirs(ckpt_path, exist_ok=True)
weight_np = model.huggingface.weight.numpy()
file_path = os.path.join(ckpt_path, "tensor1.safetensors")
safetensors.numpy.save_file({"huggingface.weight": weight_np}, file_path)
def test_save_load_with_missing_key_and_unexpected_keys():
# ckpt_path = os.getenv("ckpt_path")
ckpt_path = "/home/paddle/test/auto_parallel/hybrid_strategy/test_file"
dist.init_parallel_env()
hf_model = HuggingFaceModel()
fc_model = FCModel()
hf_model = paddle.amp.decorate(
models=hf_model, optimizers=None, level="O2", dtype="float16"
)
init_hf_model_weights(hf_model)
save_safetensors_model(hf_model, ckpt_path)
aoa_statements = []
aoa_config = {"aoa_statements": aoa_statements}
try:
load_state_dict(
fc_model.sharded_state_dict(),
ckpt_path,
safetensors=True,
aoa_config=aoa_config,
)
raise AssertionError
except Exception as e:
pass
def test_save_load_with_mapping_key_to_safetensors_file():
ckpt_path = os.getenv("ckpt_path")
dist.init_parallel_env()
hf_model = HuggingFaceModel()
fc_model = FCModel()
hf_model = paddle.amp.decorate(
models=hf_model, optimizers=None, level="O2", dtype="float16"
)
init_hf_model_weights(hf_model)
save_safetensors_model(hf_model, ckpt_path)
import json
# set the key to the wrong safetensors file tensor2.safetensors, the fact file is tensor1.safetensors
index = {
"metadata": {"total_size": 8},
"weight_map": {"huggingface.weight": "tensor2.safetensors"},
}
with open(
os.path.join(ckpt_path, "model.safetensors.index.json"), "w"
) as f:
json.dump(index, f)
aoa_statements = [
"huggingface.weight -> A,B ,axis = 1 \n",
"A^T -> A \n",
"B^T -> B \n",
"A -> fc1.weight ,src_dtype = 'float16', dst_dtype = 'float32' \n",
"B -> fc2.weight ,src_dtype = 'float16', dst_dtype = 'float32' \n",
]
aoa_config = {"aoa_statements": aoa_statements}
try:
load_state_dict(
fc_model.sharded_state_dict(),
ckpt_path,
safetensors=True,
aoa_config=aoa_config,
)
raise AssertionError
except Exception as e:
pass
def test():
test_save_load_with_missing_key_and_unexpected_keys()
test_save_load_with_mapping_key_to_safetensors_file()
if __name__ == "__main__":
test()
@@ -0,0 +1,140 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestSemiAutoParallelLlamaACCTest(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=8, timeout=200, nnode=1)
def test_simple_net_hybrid_strategy_acc(self):
_default_envs = {
"dp": "2",
"mp": "2",
"pp": "2",
"acc_step": "1",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
"FLAGS_enable_pir_api": "1",
}
_changeable_envs = {
"backend": ["gpu"],
}
envs_list = test_base.gen_product_envs_list(
_default_envs, _changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_llama_acc_align.py",
user_defined_envs=envs,
)
def test_simple_net_hybrid_strategy_acc_grad_merge(self):
_default_envs = {
"dp": "2",
"mp": "2",
"pp": "2",
"acc_step": "2",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
"FLAGS_enable_pir_api": "1",
}
_changeable_envs = {
"backend": ["gpu"],
}
envs_list = test_base.gen_product_envs_list(
_default_envs, _changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_llama_acc_align.py",
user_defined_envs=envs,
)
class TestSemiAutoParallelLlamaCPTest(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=8, timeout=200, nnode=1)
def test_dp2pp2cp2_hybrid_strategy_acc(self):
_default_envs = {
"dp": "2",
"mp": "2",
"pp": "1",
"sep": "2",
"acc_step": "1",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
"FLAGS_enable_pir_api": "1",
}
_changeable_envs = {
"backend": ["gpu"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": ["bfloat16"],
"amp_master_grad": ["false"],
"seq_length": ["1024"],
"hidden_size": ["2048"],
"num_attention_heads": ["8"],
"num_key_value_heads": ["8"],
"context_parallel": ["true"],
"max_position_embeddings": ["2048"],
}
envs_list = test_base.gen_product_envs_list(
_default_envs, _changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_llama_acc_align.py",
user_defined_envs=envs,
)
class TestSemiAutoParallelLlamaSEPTest(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=8, timeout=200, nnode=1)
def test_dp2pp2sep2_hybrid_strategy_acc(self):
# now not support mp with sep
# TODO: something wrong with this case
_default_envs = {
"dp": "4",
"mp": "1",
"pp": "1",
"sep": "2",
"acc_step": "1",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
"FLAGS_enable_pir_api": "1",
}
_changeable_envs = {
"backend": ["gpu"],
"sep_parallel": ["true"],
"context_parallel": ["false"],
}
envs_list = test_base.gen_product_envs_list(
_default_envs, _changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_llama_acc_align.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main() # python run
@@ -0,0 +1,50 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestSemiAutoParallelLlamaSaveLoadTest(
test_base.CommunicationTestDistBase
):
def setUp(self):
super().setUp(num_of_devices=8, timeout=200, nnode=1)
self._default_envs = {
"dp": "2",
"mp": "2",
"pp": "2",
"acc_step": "1",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
"FLAGS_enable_pir_api": "1",
}
self._changeable_envs = {
"backend": ["gpu"],
}
def test_simple_net_hybrid_strategy_save_load(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_llama_save_load.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main() # python run
@@ -0,0 +1,58 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
os.environ["PARALLEL_CROSS_ENTROPY"] = "true"
import os
import unittest
import collective.test_communication_api_base as test_base
os.environ['FLAGS_enable_pir_api'] = '0'
class TestParallelCrossEntropy(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=2, timeout=200, nnode=1)
def test_dp(self):
self.run_test_case(
"semi_auto_parallel_c_cross_entropy_dp.py",
)
def test_mp(self):
self.run_test_case(
"semi_auto_parallel_c_cross_entropy_mp.py",
)
def test_mp_pir(self):
os.environ["FLAGS_enable_pir_in_executor"] = "True"
self.test_mp()
os.environ["FLAGS_enable_pir_in_executor"] = "False"
class TestParallelCrossEntropyHybrid(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=4, timeout=200, nnode=1)
def test_hybrid(self):
self.run_test_case(
"semi_auto_parallel_c_cross_entropy_hybrid.py",
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,60 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 os
import unittest
import collective.test_communication_api_base as test_base
os.environ['FLAGS_enable_pir_api'] = '1'
class TestSemiAutoParallelGlobalInput(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(
num_of_devices=8,
timeout=120,
nnode=1,
)
self._default_envs = {
"dtype": "float32",
"seed": "1024",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_dynamic(self):
self._default_envs.update({"run_static": "0"})
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_global_input.py",
user_defined_envs=envs,
)
def test_static(self):
self._default_envs.update({"run_static": "1"})
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_global_input.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,69 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 os
import unittest
import collective.test_communication_api_base as test_base
os.environ['FLAGS_enable_pir_api'] = '0'
class TestSemiAutoParallelInShardingStrategy(
test_base.CommunicationTestDistBase
):
def setUp(self):
super().setUp(
num_of_devices=4,
timeout=120,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_sharding_stage_1_strategy(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_sharding_stage_1.py",
user_defined_envs=envs,
)
def test_sharding_stage_2_strategy(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_sharding_stage_2.py",
user_defined_envs=envs,
)
def test_sharding_stage_3_strategy(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_sharding_stage_3.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,133 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 os
import tempfile
import unittest
import collective.test_communication_api_base as test_base
os.environ['FLAGS_enable_pir_api'] = '0'
class TestSemiAutoParallelDPMPStrategy(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=4, timeout=120, nnode=1)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_simple_net_hybrid_strategy(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"semi_auto_parallel_simple_net_dp_mp.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
def test_fused_linear_param_grad_add(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_for_fused_linear_param_grad_add.py",
user_defined_envs=envs,
)
class TestSemiAutoParallelHybridStrategy(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(
num_of_devices=8,
timeout=120,
nnode=1,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_simple_net_hybrid_strategy(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"semi_auto_parallel_simple_net_dp_mp_pp.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
class TestSemiAutoParallelHybridStrategyWithSP(
test_base.CommunicationTestDistBase
):
def setUp(self):
super().setUp(
num_of_devices=4,
timeout=120,
nnode=1,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
}
self._changeable_envs = {"backend": ["gpu"], "is_dp": ["false"]}
def test_simple_net_mp_pp_sp(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"semi_auto_parallel_simple_net_sp.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
def test_simple_net_dp_mp_pp_sp(self):
super().setUp(
num_of_devices=8,
timeout=120,
nnode=1,
)
self._changeable_envs = {"backend": ["gpu"], "is_dp": ["true"]}
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
ckpt_path = tempfile.TemporaryDirectory()
envs["ckpt_path"] = ckpt_path.name
self.run_test_case(
"semi_auto_parallel_simple_net_sp.py",
user_defined_envs=envs,
)
ckpt_path.cleanup()
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,85 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class TestSemiAutoParallelLlama3DAMPTest(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=8, timeout=200, nnode=1)
self._default_envs = {"dp": "2", "mp": "2", "pp": "2", "acc_step": "2"}
self._changeable_envs = {
"backend": ["gpu"],
"use_sp": ["true"],
"use_param_group": ["true"],
"recompute": ["true"],
"recompute_granularity": ["full"],
"amp": ["true"],
"amp_level": ["O1"],
"amp_dtype": [
"float16",
],
"amp_master_grad": [
"False",
],
}
def test_simple_net_hybrid_strategy(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_llama.py",
user_defined_envs=envs,
)
class TestSemiAutoParallelLlama3DAMPMasterGradTest(
test_base.CommunicationTestDistBase
):
def setUp(self):
super().setUp(num_of_devices=8, timeout=200, nnode=1)
self._default_envs = {"dp": "2", "mp": "2", "pp": "2", "acc_step": "2"}
self._changeable_envs = {
"backend": ["gpu"],
"use_sp": ["true"],
"use_param_group": ["true"],
"recompute": ["true"],
"recompute_granularity": ["full"],
"amp": ["true"],
"amp_level": ["O2"],
"amp_dtype": [
"float16",
],
"amp_master_grad": [
"True",
],
}
def test_simple_net_hybrid_strategy(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_llama.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,48 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
class Test3DSemiAutoParallelStaticPirDecorate(
test_base.CommunicationTestDistBase
):
def setUp(self):
super().setUp(
num_of_devices=8,
timeout=300,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"FLAGS_enable_pir_api": "1",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_mlp(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
# self._log_dir.name = "./log"
for envs in envs_list:
self.run_test_case(
"../pir/mlp_demo_3d.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,64 @@
# Copyright (c) 2023 PaddlePaddle Authors. 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 sys
import unittest
sys.path.append("../../")
import os
import collective.test_communication_api_base as test_base
os.environ['FLAGS_enable_pir_api'] = '1'
class TestSemiAutoParallelLlama3DVPP(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(num_of_devices=8, timeout=200, nnode=1)
self._default_envs = {
"seed": "2023",
"dp": "2",
"mp": "2",
"pp": "2",
"FLAGS_embedding_deterministic": "1",
"FLAGS_cudnn_deterministic": "1",
"acc_step": "4",
"only_static": "true",
}
self._changeable_envs = {
"backend": ["gpu"],
"use_sp": ["true"],
"use_param_group": ["true"],
"recompute": ["true"],
"recompute_granularity": ["full"],
# TODO: Temporarily turn off the vpp test in PIR mode. There will be
# a hang issue, which will be fixed later.
# "virtual_pp_degree": ["2"],
"virtual_pp_degree": ["1"],
}
def test_simple_net_hybrid_strategy(self):
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
self._log_dir.name = "./vpp_log"
for envs in envs_list:
self.run_test_case(
"semi_auto_llama_pp_gradmerge.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,60 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 os
import unittest
import collective.test_communication_api_base as test_base
os.environ['FLAGS_enable_pir_api'] = '1'
class TestSemiAutoParallelMultiInputs(test_base.CommunicationTestDistBase):
def setUp(self):
super().setUp(
num_of_devices=8,
timeout=120,
nnode=1,
)
self._default_envs = {
"dtype": "float32",
"seed": "1024",
}
self._changeable_envs = {"backend": ["gpu"]}
def test_dynamic(self):
self._default_envs.update({"run_static": "0"})
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_multi_inputs.py",
user_defined_envs=envs,
)
def test_static(self):
self._default_envs.update({"run_static": "1"})
envs_list = test_base.gen_product_envs_list(
self._default_envs, self._changeable_envs
)
for envs in envs_list:
self.run_test_case(
"semi_auto_parallel_multi_inputs.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,50 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 unittest
import collective.test_communication_api_base as test_base
import paddle
class TestToDistributedApiForLlamaBasic(test_base.CommunicationTestDistBase):
def setUp(self):
self._num_of_devices = 8
super().setUp(
num_of_devices=self._num_of_devices,
timeout=120,
)
self._default_envs = {
"dtype": "float32",
"seed": "2023",
"num_of_devices": self._num_of_devices,
}
self._changeable_envs = {"backend": ["cpu", "gpu"]}
def test_llama(self):
envs_list = test_base.gen_product_envs_list(
{"dtype": "float32", "seed": "2023"}, {"backend": ["gpu"]}
)
cuda_version_main = int(paddle.version.cuda().split(".")[0])
device_prop_main = paddle.device.cuda.get_device_capability()[0]
if cuda_version_main >= 11 and device_prop_main >= 8:
for envs in envs_list:
self.run_test_case(
"to_distributed_api_for_llama.py",
user_defined_envs=envs,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,24 @@
name,os,arch,timeout,run_type,launcher,num_port,run_serial,envs,conditions
test_semi_auto_parallel_hybrid_strategy,LINUX,GPU,300,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_save_load_state_dict,LINUX,GPU,400,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_flexcheckpoint_merge,LINUX,GPU,120,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_semi_auto_parallel_c_cross_entropy,LINUX,GPU,120,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_cross_mesh_reshard,LINUX,GPU,120,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_semi_auto_parallel_llama_model_amp,LINUX,GPU,180,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_semi_auto_parallel_hybrid_sharding_strategy,LINUX,GPU,120,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_global_mesh_reshard,LINUX,GPU,120,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_semi_auto_parallel_global_input,LINUX,GPU,120,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_semi_auto_parallel_multi_inputs,LINUX,GPU,120,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_semi_auto_parallel_llama_model_vpp,LINUX,GPU,180,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_semi_auto_parallel_llama_model_pir,LINUX,GPU,180,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..;FLAGS_enable_pir_api=1,
test_pir_reshard_nd_mesh_func,LINUX,GPU,60,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_semi_auto_llama_acc_align,LINUX,GPU,300,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..;FLAGS_enable_pir_api=1,
test_semi_auto_llama_save_load,LINUX,GPU,180,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..;FLAGS_enable_pir_api=1,
test_parallel_api_with_llama_1d,LINUX,GPU,400,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_parallel_api_with_llama_2d,LINUX,GPU,400,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_parallel_api_with_llama_2d_sep,LINUX,GPU,400,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_parallel_api_with_llama_3d,LINUX,GPU,800,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_to_distributed_api_for_llama,LINUX,GPU,180,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_parallel_api_with_llama_lora,LINUX,GPU,360,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_process_mesh,LINUX,GPU,150,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
test_get_group_in_different_hybrid_configs,LINUX,GPU,150,HYBRID,test_runner.py,,,http_proxy=;https_proxy=;PYTHONPATH=../..,
1 name os arch timeout run_type launcher num_port run_serial envs conditions
2 test_semi_auto_parallel_hybrid_strategy LINUX GPU 300 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
3 test_save_load_state_dict LINUX GPU 400 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
4 test_flexcheckpoint_merge LINUX GPU 120 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
5 test_semi_auto_parallel_c_cross_entropy LINUX GPU 120 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
6 test_cross_mesh_reshard LINUX GPU 120 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
7 test_semi_auto_parallel_llama_model_amp LINUX GPU 180 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
8 test_semi_auto_parallel_hybrid_sharding_strategy LINUX GPU 120 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
9 test_global_mesh_reshard LINUX GPU 120 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
10 test_semi_auto_parallel_global_input LINUX GPU 120 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
11 test_semi_auto_parallel_multi_inputs LINUX GPU 120 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
12 test_semi_auto_parallel_llama_model_vpp LINUX GPU 180 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
13 test_semi_auto_parallel_llama_model_pir LINUX GPU 180 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..;FLAGS_enable_pir_api=1
14 test_pir_reshard_nd_mesh_func LINUX GPU 60 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
15 test_semi_auto_llama_acc_align LINUX GPU 300 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..;FLAGS_enable_pir_api=1
16 test_semi_auto_llama_save_load LINUX GPU 180 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..;FLAGS_enable_pir_api=1
17 test_parallel_api_with_llama_1d LINUX GPU 400 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
18 test_parallel_api_with_llama_2d LINUX GPU 400 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
19 test_parallel_api_with_llama_2d_sep LINUX GPU 400 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
20 test_parallel_api_with_llama_3d LINUX GPU 800 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
21 test_to_distributed_api_for_llama LINUX GPU 180 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
22 test_parallel_api_with_llama_lora LINUX GPU 360 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
23 test_process_mesh LINUX GPU 150 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
24 test_get_group_in_different_hybrid_configs LINUX GPU 150 HYBRID test_runner.py http_proxy=;https_proxy=;PYTHONPATH=../..
@@ -0,0 +1,643 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 math
import os
import numpy as np
import paddle
import paddle.distributed as dist
import paddle.nn.functional as F
from paddle import nn
from paddle.distributed import to_distributed
from paddle.distributed.auto_parallel.high_level_api import ToDistributedConfig
EPOCHS = 1
VOCAB_SIZE = 8000
BATCH_NUM = 2
BATCH_SIZE = 4
HIDDEN_SIZE = 2048
INTERMEDIATE_SIZE = 4096
SEQ_LENGTH = 1024
N_HEAD = 32
NUM_HIDDEN_LAYERS = 4
def create_numpy_like_random(name):
return paddle.ParamAttr(
name=name, initializer=paddle.nn.initializer.Uniform(-0.1, 0.1)
)
class LlamaRotaryEmbedding(nn.Layer):
def __init__(self, dim, max_position_embeddings=2048, base=10000):
super().__init__()
self.dim = dim
self.max_position_embeddings = max_position_embeddings
self.base = base
# [dim / 2]
self.inv_freq = 1.0 / (
self.base
** (
paddle.cast(paddle.arange(0, self.dim, 2), dtype="float32")
/ self.dim
)
)
self._set_cos_sin_cache(seq_len=max_position_embeddings)
def _set_cos_sin_cache(self, seq_len):
self.max_seq_len_cached = seq_len
# [seq_len]
t = paddle.arange(seq_len, dtype="float32")
# [seq_len, dim/2]
freqs = paddle.einsum("i,j->ij", t, self.inv_freq)
# Different from paper, but it uses a different permutation in order to obtain the same calculation
# [seq_len, dim]
emb = paddle.concat([freqs, freqs], axis=-1)
# [1, seqlen, 1, dim]
self.cos_cached = emb.cos()[None, :, None, :]
self.sin_cached = emb.sin()[None, :, None, :]
def forward(self, x, seq_len=None):
# x: [bs, num_attention_heads, seq_len, head_size]
cos = self.cos_cached[:, :seq_len, :, :]
sin = self.sin_cached[:, :seq_len, :, :]
return (
cos.cast(x.dtype) if cos.dtype != x.dtype else cos,
sin.cast(x.dtype) if sin.dtype != x.dtype else sin,
)
def rotate_half(x):
"""Rotates half the hidden dims of the input."""
x1 = x[..., : x.shape[-1] // 2]
x2 = x[..., x.shape[-1] // 2 :]
return paddle.concat([-x2, x1], axis=-1) # shape is the same as x
def apply_rotary_pos_emb(q, k, cos, sin, position_ids):
if position_ids is None:
# Note: Only for LlamaForCausalLMPipe model pretraining
cos = cos[:, : q.shape[1], :, :] # [bs, seq_len, 1, dim]
sin = sin[:, : q.shape[1], :, :] # [bs, seq_len, 1, dim]
else:
cos = cos.squeeze(axis=[0, 2]) # [seq_len, dim]
sin = sin.squeeze(axis=[0, 2]) # [seq_len, dim]
cos = cos[position_ids].unsqueeze(2) # [bs, seq_len, 1, dim]
sin = sin[position_ids].unsqueeze(2) # [bs, seq_len, 1, dim]
q_embed = (q * cos) + (rotate_half(q) * sin)
k_embed = (k * cos) + (rotate_half(k) * sin)
return q_embed, k_embed
def scaled_dot_product_attention(
query_states,
key_states,
value_states,
attention_mask,
):
bsz, q_len, num_heads, head_dim = query_states.shape
_, kv_seq_len, _, _ = value_states.shape
# [ bz, seqlen, nhead, head_dim] -> [bs, nhead, seq_len, head_dim]
query_states = paddle.transpose(query_states, [0, 2, 1, 3])
# merge with the next transpose
key_states = paddle.transpose(key_states, [0, 2, 1, 3])
value_states = paddle.transpose(value_states, [0, 2, 1, 3])
# matmul and divide by sqrt(head_dim)
attn_weights = paddle.matmul(
query_states / math.sqrt(head_dim), key_states.transpose([0, 1, 3, 2])
)
attention_mask = attention_mask.reshape([bsz, 1, q_len, kv_seq_len])
attn_weights = attn_weights + attention_mask
if not paddle.in_dynamic_mode():
attn_weights = F.softmax(attn_weights, axis=-1, dtype="float32").astype(
query_states.dtype
)
else:
with paddle.amp.auto_cast(False):
attn_weights = F.softmax(
attn_weights, axis=-1, dtype="float32"
).astype(query_states.dtype)
attn_output = paddle.matmul(attn_weights, value_states)
attn_output = attn_output.transpose([0, 2, 1, 3])
attn_output = attn_output.reshape([bsz, q_len, head_dim * num_heads])
return attn_output
class LlamaAttention(nn.Layer):
def __init__(self, param_prefix="", hidden_size=HIDDEN_SIZE, n_head=N_HEAD):
super().__init__()
weight_attr_0 = create_numpy_like_random(param_prefix + "_0")
weight_attr_1 = create_numpy_like_random(param_prefix + "_1")
weight_attr_2 = create_numpy_like_random(param_prefix + "_2")
weight_attr_3 = create_numpy_like_random(param_prefix + "_3")
self.hidden_size = hidden_size
self.num_heads = n_head
self.head_dim = hidden_size // n_head
self.q_proj = nn.Linear(
hidden_size, hidden_size, weight_attr_0, bias_attr=False
)
self.k_proj = nn.Linear(
hidden_size, hidden_size, weight_attr_1, bias_attr=False
)
self.v_proj = nn.Linear(
hidden_size, hidden_size, weight_attr_2, bias_attr=False
)
self.o_proj = nn.Linear(
hidden_size, hidden_size, weight_attr_3, bias_attr=False
)
self.rotary_emb = LlamaRotaryEmbedding(
self.head_dim, max_position_embeddings=SEQ_LENGTH, base=10000
)
def forward(
self,
hidden_states,
position_ids=None,
attention_mask=None,
):
# mix_layer = self.qkv_proj(x)
query_states = self.q_proj(hidden_states)
key_states = self.k_proj(hidden_states)
value_states = self.v_proj(hidden_states)
# target_shape = [0, 0, self.num_heads, 3 * self.head_dim]
target_query_shape = [0, 0, self.num_heads, self.head_dim]
target_key_value_shape = [0, 0, self.num_heads, self.head_dim]
# mix_layer = paddle.reshape(mix_layer, target_shape)
query_states = query_states.reshape(shape=target_query_shape)
key_states = key_states.reshape(shape=target_key_value_shape)
value_states = value_states.reshape(shape=target_key_value_shape)
kv_seq_len = key_states.shape[-3]
cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len)
query_states, key_states = apply_rotary_pos_emb(
query_states, key_states, cos, sin, position_ids
)
output = scaled_dot_product_attention(
query_states,
key_states,
value_states,
attention_mask,
)
attn_output = output
attn_output = self.o_proj(attn_output)
return attn_output
class LlamaMlp(nn.Layer):
def __init__(
self,
param_prefix="",
hidden_size=HIDDEN_SIZE,
intermediate_size=INTERMEDIATE_SIZE,
):
super().__init__()
self.hidden_size = hidden_size
self.intermediate_size = intermediate_size
weight_attr_0 = create_numpy_like_random(param_prefix + "_0")
weight_attr_1 = create_numpy_like_random(param_prefix + "_1")
weight_attr_2 = create_numpy_like_random(param_prefix + "_2")
self.gate_proj = nn.Linear(
hidden_size, intermediate_size, weight_attr_1, bias_attr=False
)
self.up_proj = nn.Linear(
hidden_size, intermediate_size, weight_attr_0, bias_attr=False
)
self.down_proj = nn.Linear(
intermediate_size, hidden_size, weight_attr_2, bias_attr=False
)
def forward(self, x):
x = paddle.nn.functional.swiglu(self.gate_proj(x), self.up_proj(x))
out = self.down_proj(x)
return out
class LlamaRMSNorm(nn.Layer):
def __init__(self, hidden_size=HIDDEN_SIZE):
super().__init__()
self.hidden_size = hidden_size
self.weight = paddle.create_parameter(
shape=[self.hidden_size],
dtype=paddle.get_default_dtype(),
default_initializer=nn.initializer.Constant(1.0),
)
self.variance_epsilon = 1.0
def forward(self, hidden_states):
with paddle.amp.auto_cast(False):
hidden_states = hidden_states.astype("float32")
variance = hidden_states.pow(2).mean(-1, keepdim=True)
hidden_states = (
paddle.rsqrt(variance + self.variance_epsilon) * hidden_states
)
if self.weight.dtype in [paddle.float16, paddle.bfloat16]:
hidden_states = paddle.cast(hidden_states, self.weight.dtype)
return hidden_states * self.weight
class LlamaDecoderLayer(nn.Layer):
def __init__(
self,
param_prefix="",
hidden_size=HIDDEN_SIZE,
intermediate_size=INTERMEDIATE_SIZE,
):
super().__init__()
self.hidden_size = hidden_size
self.intermediate_size = intermediate_size
self.self_attn = LlamaAttention(param_prefix + "_att", hidden_size)
self.mlp = LlamaMlp(param_prefix + "_mlp")
self.input_layernorm = LlamaRMSNorm(hidden_size)
self.post_attn_layernorm = LlamaRMSNorm(hidden_size)
def forward(
self,
hidden_states,
position_ids=None,
attention_mask=None,
):
residual = hidden_states
hidden_states = self.input_layernorm(hidden_states)
hidden_states = self.self_attn(
hidden_states, position_ids, attention_mask
)
hidden_states = residual + hidden_states
residual = hidden_states
hidden_states = self.post_attn_layernorm(hidden_states)
hidden_states = self.mlp(hidden_states)
hidden_states = residual + hidden_states
return hidden_states
def _expand_2d_mask(mask, dtype, tgt_length):
"""
Expands attention_mask from `[batch_size, src_length]` to `[batch_size, 1, tgt_length, src_length]`.
"""
batch_size, src_length = mask.shape[0], mask.shape[-1]
tgt_length = tgt_length if tgt_length is not None else src_length
mask = mask[:, None, None, :].astype("bool")
mask.stop_gradient = True
expanded_mask = mask.expand([batch_size, 1, tgt_length, src_length])
return expanded_mask
def _make_causal_mask(input_ids_shape, past_key_values_length):
"""
Make casual mask used for self-attention
"""
batch_size, target_length = input_ids_shape # target_length: seq_len
mask = paddle.tril(
paddle.ones((target_length, target_length), dtype="bool")
)
if past_key_values_length > 0:
# [tgt_len, tgt_len + past_len]
mask = paddle.concat(
[
paddle.ones(
[target_length, past_key_values_length], dtype="bool"
),
mask,
],
axis=-1,
)
# [bs, 1, tgt_len, tgt_len + past_len]
return mask[None, None, :, :].expand(
[batch_size, 1, target_length, target_length + past_key_values_length]
)
def _prepare_decoder_attention_mask(
attention_mask, input_shape, past_key_values_length, dtype
):
if attention_mask is not None:
if len(attention_mask.shape) == 2:
expanded_attn_mask = _expand_2d_mask(
attention_mask, dtype, tgt_length=input_shape[-1]
)
if input_shape[-1] > 1:
combined_attention_mask = _make_causal_mask(
input_shape,
past_key_values_length=past_key_values_length,
)
expanded_attn_mask = (
expanded_attn_mask & combined_attention_mask
)
# [bsz, seq_len, seq_len] -> [bsz, 1, seq_len, seq_len]
elif len(attention_mask.shape) == 3:
expanded_attn_mask = attention_mask.unsqueeze(1).astype("bool")
else:
expanded_attn_mask = attention_mask
else:
expanded_attn_mask = _make_causal_mask(
input_shape, past_key_values_length=past_key_values_length
)
expanded_attn_mask = paddle.where(
expanded_attn_mask, 0.0, paddle.finfo(dtype).min
).astype(dtype)
return expanded_attn_mask
class LlamaModel(nn.Layer):
def __init__(
self,
param_prefix="",
vocab_size=VOCAB_SIZE,
hidden_size=HIDDEN_SIZE,
intermediate_size=INTERMEDIATE_SIZE,
):
super().__init__()
self.vocab_size = vocab_size
self.hidden_size = hidden_size
self.intermediate_size = intermediate_size
self.embed_tokens = nn.Embedding(
vocab_size,
hidden_size,
)
self.layers = nn.LayerList(
[
LlamaDecoderLayer(param_prefix + "_decoder_" + str(i))
for i in range(NUM_HIDDEN_LAYERS)
]
)
self.norm = LlamaRMSNorm(hidden_size)
def forward(
self,
input_ids=None,
position_ids=None,
attention_mask=None,
):
batch_size, seq_length = input_ids.shape
inputs_embeds = self.embed_tokens(input_ids)
# embed positions
attention_mask = paddle.ones(
(batch_size, seq_length), dtype=paddle.bool
)
if position_ids is None:
position_ids = paddle.arange(seq_length, dtype="int64").expand(
(batch_size, seq_length)
)
attention_mask = _prepare_decoder_attention_mask(
attention_mask,
(batch_size, seq_length),
0,
inputs_embeds.dtype,
) # [bs, 1, seq_len, seq_len]
hidden_states = inputs_embeds
for idx, (decoder_layer) in enumerate(self.layers):
layer_outputs = decoder_layer(
hidden_states,
position_ids,
attention_mask,
)
hidden_states = layer_outputs
hidden_states = self.norm(hidden_states)
return hidden_states
class LlamaPretrainingCriterion(paddle.nn.Layer):
"""
Criterion for Llama.
It calculates the final loss.
"""
def __init__(
self,
param_prefix="",
vocab_size=VOCAB_SIZE,
hidden_size=HIDDEN_SIZE,
intermediate_size=INTERMEDIATE_SIZE,
):
super().__init__()
self.ignore_index = -100
self.loss_func = paddle.nn.CrossEntropyLoss(
reduction="none", ignore_index=self.ignore_index
)
def forward(self, prediction_scores, masked_lm_labels):
with paddle.amp.auto_cast(False):
masked_lm_loss = self.loss_func(
prediction_scores.astype("float32"),
masked_lm_labels.unsqueeze(2),
)
binary_sequence = paddle.where(
masked_lm_loss > 0,
paddle.ones_like(masked_lm_loss),
paddle.zeros_like(masked_lm_loss),
)
count = paddle.sum(binary_sequence)
if count == 0:
loss = paddle.sum(masked_lm_loss * binary_sequence)
else:
loss = paddle.sum(masked_lm_loss * binary_sequence) / count
return loss
class LlamaLMHead(paddle.nn.Layer):
def __init__(
self,
param_prefix="",
vocab_size=VOCAB_SIZE,
hidden_size=HIDDEN_SIZE,
intermediate_size=INTERMEDIATE_SIZE,
):
super().__init__()
self.vocab_size = vocab_size
self.hidden_size = hidden_size
self.weight = self.create_parameter(
shape=[hidden_size, vocab_size],
dtype=paddle.get_default_dtype(),
)
def forward(self, hidden_states, tensor_parallel_output=None):
logits = paddle.matmul(hidden_states, self.weight, transpose_y=False)
return logits
class LlamaForCausalLM(paddle.nn.Layer):
def __init__(
self,
param_prefix="",
vocab_size=VOCAB_SIZE,
hidden_size=HIDDEN_SIZE,
intermediate_size=INTERMEDIATE_SIZE,
):
super().__init__()
self.llama = LlamaModel(
param_prefix + "_llama", vocab_size, hidden_size, intermediate_size
)
self.lm_head = LlamaLMHead(
param_prefix + "_lm_head",
vocab_size,
hidden_size,
intermediate_size,
)
self.criterion = LlamaPretrainingCriterion(
param_prefix + "_criterion",
vocab_size,
hidden_size,
intermediate_size,
)
def forward(
self,
input_ids=None,
position_ids=None,
attention_mask=None,
labels=None,
):
outputs = self.llama(
input_ids,
position_ids=position_ids,
attention_mask=attention_mask,
)
logits = self.lm_head(outputs)
loss = None
if labels is not None:
loss = self.criterion(logits, labels)
return (loss, logits)
class RandomDataset(paddle.io.Dataset):
def __init__(self, inputs, labels, num_samples):
self.inputs = inputs
self.labels = labels
self.num_samples = num_samples
def __getitem__(self, idx):
return self.inputs[idx], self.labels[idx]
def __len__(self):
return self.num_samples
class TestLlamaDecoderForSemiAutoParallel:
def __init__(self):
self._dtype = os.getenv("dtype", "float32")
self._backend = os.getenv("backend", "gpu")
self._seed = eval(os.getenv("seed", "2023"))
self._device_num = os.getenv("num_of_devices", 8)
self._node_num = 1
np.random.seed(self._seed)
paddle.seed(self._seed)
self._model = LlamaForCausalLM("demo_llama")
# ensure that input data between dp is different and data within dp is the same
self._mesh = dist.ProcessMesh(
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], dim_names=["pp", "dp", "mp"]
)
if "dp" in self._mesh.dim_names:
dp_seed = self._mesh.get_rank_by_dim_and_process_id(
"dp", dist.get_rank()
)
else:
dp_seed = 0
np.random.seed(self._seed + dp_seed)
paddle.seed(self._seed + dp_seed)
self._input_seqs = np.random.randint(
low=0, high=1024, size=(BATCH_SIZE * BATCH_NUM, SEQ_LENGTH)
).astype("int64")
self._labels = np.random.randint(
low=0, high=1024, size=(BATCH_SIZE * BATCH_NUM, SEQ_LENGTH)
).astype("int64")
self._dataset = RandomDataset(
self._input_seqs, self._labels, BATCH_SIZE * BATCH_NUM
)
self._sampler = paddle.io.BatchSampler(
self._dataset, batch_size=BATCH_SIZE, shuffle=False, drop_last=True
)
self._loader = paddle.io.DataLoader(
self._dataset, batch_sampler=self._sampler
)
self._opt = paddle.optimizer.SGD(
learning_rate=0.1, parameters=self._model.parameters()
)
paddle.set_device(self._backend)
def test_to_distributed_api(self):
# # config: sequence_parallel
dist_config = ToDistributedConfig()
dist_config.sequence_parallel = True
# # wrap model by using **to_distributed**
dist_model, dist_opt, dist_loader = to_distributed(
self._model,
self._opt,
self._loader,
self._device_num,
self._node_num,
dist_config,
)
for epoch in range(EPOCHS):
dist_model.train()
for i, data in enumerate(dist_loader()):
inputs, labels = data
loss, _ = dist_model(inputs, labels=labels)
loss.backward()
dist_opt.step()
dist_opt.clear_grad()
def run_test_case(self):
if self._backend == "gpu":
cuda_version_main = int(paddle.version.cuda().split(".")[0])
device_prop_main = paddle.device.cuda.get_device_capability()[0]
if cuda_version_main >= 11 and device_prop_main >= 8:
self.test_to_distributed_api()
if __name__ == '__main__':
TestLlamaDecoderForSemiAutoParallel().run_test_case()
+159
View File
@@ -0,0 +1,159 @@
# Copyright (c) 2022 PaddlePaddle Authors. 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 os
import tempfile
import numpy as np
import paddle
import paddle.nn.functional as F
from paddle import nn
from paddle.distributed.fleet import auto
paddle.enable_static()
global_process_mesh = auto.ProcessMesh(mesh=[0, 1])
PP_MESH_0 = auto.ProcessMesh([0])
PP_MESH_1 = auto.ProcessMesh([1])
batch_size = 2
batch_num = 10
hidden_size = 1024
sequence_len = 512
image_size = hidden_size
class_num = 10
paddle.seed(44)
class MyDataset(paddle.io.IterableDataset):
def __init__(self, num_samples):
self.num_samples = num_samples
def __iter__(self):
for i in range(self.num_samples):
input = np.random.uniform(size=image_size).astype("float32")
label = np.random.randint(0, class_num - 1, dtype="int64")
yield input, label
class MyDataset1(paddle.io.Dataset):
def __init__(self, num_samples):
self.num_samples = num_samples
self.data = []
for i in range(self.num_samples):
input1 = np.random.uniform(size=image_size).astype("float32")
label1 = np.array(
np.random.randint(0, class_num - 1, dtype="int64")
)
input2 = np.random.uniform(size=image_size).astype("float32")
label2 = np.array(
np.random.randint(0, class_num - 1, dtype="int64")
)
input = np.stack((input1, input2))
label = np.stack((label1, label2))
self.data.append((input, label))
def __getitem__(self, idx):
return self.data[idx]
def __len__(self):
return len(self.data)
class MLPLayer(nn.Layer):
def __init__(
self,
hidden_size=1024,
intermediate_size=4 * 1024,
dropout_ratio=0.1,
initializer_range=0.02,
):
super().__init__()
d_model = hidden_size
dim_feedforward = intermediate_size
weight_attr = paddle.ParamAttr(
initializer=nn.initializer.Normal(mean=0.0, std=initializer_range)
)
bias_attr = None
self.linear0 = nn.Linear(
d_model, dim_feedforward, weight_attr, bias_attr=bias_attr
)
self.linear1 = nn.Linear(
dim_feedforward, d_model, weight_attr, bias_attr=bias_attr
)
self.linear2 = nn.Linear(d_model, 1, weight_attr, bias_attr=bias_attr)
self.norm = nn.LayerNorm(d_model, epsilon=1e-5)
self.dropout = nn.Dropout(dropout_ratio, mode="upscale_in_train")
def forward(self, input):
out = auto.shard_op(self.norm, PP_MESH_0)(input)
out = self.linear0(out)
out = F.gelu(out, approximate=True)
out = auto.shard_op(self.linear1, PP_MESH_1)(out)
out = self.dropout(out)
out = self.linear2(out)
self.out = out
return out
def train(fetch):
mlp = MLPLayer(
hidden_size=hidden_size,
intermediate_size=4 * hidden_size,
dropout_ratio=0.1,
initializer_range=0.02,
)
loss = paddle.nn.CrossEntropyLoss()
optimizer = paddle.optimizer.Adam(
learning_rate=0.00001,
beta1=0.9,
beta2=0.999,
epsilon=1e-08,
grad_clip=None,
)
dist_strategy = auto.Strategy()
dist_strategy.auto_mode = "semi"
dist_strategy.split_data = True
# init engine
engine = auto.Engine(
mlp, loss, optimizer, paddle.metric.Accuracy(), strategy=dist_strategy
)
# train
train_dataset = MyDataset(batch_num * batch_size)
engine.fit(train_dataset, epochs=2, batch_size=batch_size)
train_dataset1 = MyDataset1(batch_size * batch_num)
engine.fit(train_dataset1, epochs=2, batch_size=None)
# eval
eval_dataset = MyDataset(batch_size)
engine.evaluate(eval_dataset, batch_size=batch_size)
# predict
test_dataset = MyDataset(batch_size)
engine.predict(test_dataset, batch_size=batch_size)
# save
temp_dir = tempfile.TemporaryDirectory()
model_filename = os.path.join(temp_dir.name, 'mlp_inf')
engine.save(model_filename, training=False)
temp_dir.cleanup()
if __name__ == "__main__":
train(fetch=True)
+23
View File
@@ -0,0 +1,23 @@
# Copyright (c) 2021 PaddlePaddle Authors. 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 os
from paddle.distributed.fleet import launch
from paddle.distributed.fleet.launch_utils import run_with_coverage
if __name__ == "__main__":
if os.environ.get("WITH_COVERAGE", "OFF") == "ON":
run_with_coverage(True)
launch.launch()
+220
View File
@@ -0,0 +1,220 @@
# Copyright (c) 2024 PaddlePaddle Authors. 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 random
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.distributed import ProcessMesh, fleet, get_rank, shard_dataloader
from paddle.io import BatchSampler, DataLoader, DistributedBatchSampler
base_lr = 0.001 # Learning rate
l2_decay = 1e-5 # Weight decay
epoch = 5 # Number of training epochs
batch_num = 100 # Number of batches per epoch
batch_size = 32 # Batch size for training
class_dim = 10
global_local_loss_list = []
class RandomDataset(paddle.io.Dataset):
def __init__(self, images, labels):
self.num_samples = len(images)
self.images = images
self.labels = labels
def __getitem__(self, idx):
# image = np.random.random([256]).astype('float32')
# label = np.random.randint(0, class_dim - 1, (1, )).astype('int64')
image = self.images[idx]
label = self.labels[idx]
return image, label
def __len__(self):
return self.num_samples
class SimpleNet(paddle.nn.Layer):
def __init__(self, input_size, inner_size, output_size):
super().__init__()
self.linear1 = paddle.nn.Linear(input_size, inner_size)
self.linear2 = paddle.nn.Linear(inner_size, input_size)
self.linear3 = paddle.nn.Linear(input_size, output_size)
self.relu = paddle.nn.ReLU()
def forward(self, x):
x = self.linear1(x)
x = self.linear2(x)
x = self.linear3(x)
x = self.relu(x)
return x
def masked_lm_loss_func(pred, label, global_local_loss_list_item=None):
"""自定义损失函数,基于rank进行掩码"""
lossmask = paddle.zeros_like(label).astype('float32')
if dist.get_rank() == 0:
lossmask[:8] = 1
else:
lossmask[8:16] = 1
pred_sub = pred[:, 0:1] # shape [B,1]
# NOTE(Pan Zhaowu): Using float64 as golden to provide more
# persuasive result.
label_float = paddle.cast(label, 'float64') # shape [B,1]
raw_loss = paddle.abs(pred_sub - label_float)
lossmask_ = lossmask.reshape([-1]).cast('float64')
raw_loss_flat = raw_loss.reshape([-1]).cast('float64')
masked_lm_loss_sum = paddle.sum(raw_loss_flat * lossmask_)
valid_count = paddle.sum(lossmask_)
loss = masked_lm_loss_sum / (valid_count + 1e-8)
if global_local_loss_list_item is not None:
np.testing.assert_allclose(
global_local_loss_list_item,
loss.numpy(),
rtol=1e-8,
)
return loss
class TestLocalViewCompute:
def __init__(self):
self._mesh = dist.ProcessMesh([0, 1], dim_names=["x"])
def set_random_seed(self):
np.random.seed(2025)
paddle.seed(2025)
random.seed(2025)
def create_dataset(self):
images = np.random.rand(batch_num * batch_size * 2, 256).astype(
'float32'
)
labels = np.random.randint(
0, class_dim - 1, (batch_num * batch_size * 2, 1)
).astype('int64')
datasets = RandomDataset(images, labels)
return datasets
def run_test_cases(self):
# run_dy_hand_get_local_loss
self.set_random_seed()
dataset = self.create_dataset()
dist_strategy = fleet.DistributedStrategy()
dist_strategy.hybrid_configs = {
"dp_degree": 2,
"mp_degree": 1,
"pp_degree": 1,
}
fleet.init(is_collective=True, strategy=dist_strategy)
model = SimpleNet(
input_size=256, inner_size=102400, output_size=class_dim
)
clip = paddle.nn.ClipGradByGlobalNorm(clip_norm=1.0)
optimizer = paddle.optimizer.AdamW(
learning_rate=base_lr,
weight_decay=l2_decay,
parameters=model.parameters(),
grad_clip=clip,
)
model = fleet.distributed_model(model)
optimizer = fleet.distributed_optimizer(optimizer)
sampler = DistributedBatchSampler(
dataset,
rank=get_rank(),
batch_size=batch_size // 2,
shuffle=False,
drop_last=True,
)
train_loader = DataLoader(
dataset, batch_sampler=sampler, num_workers=1, shuffle=False
)
model.train()
for batch_id, data in enumerate(train_loader()):
if batch_id > 10:
break
img, label = data
out = model(img)
avg_loss = masked_lm_loss_func(out, label)
avg_loss.backward()
optimizer.step()
model.clear_gradients()
global_local_loss_list.append(avg_loss.numpy())
# run_dy_semi_auto
self.set_random_seed()
dataset = self.create_dataset()
world_process_mesh = ProcessMesh([0, 1], dim_names=["dp"])
model = SimpleNet(
input_size=256, inner_size=102400, output_size=class_dim
)
optimizer = paddle.optimizer.AdamW(
learning_rate=base_lr,
weight_decay=l2_decay,
parameters=model.parameters(),
grad_clip=clip,
)
sampler = BatchSampler(
dataset, batch_size=batch_size, shuffle=False, drop_last=True
)
train_loader = DataLoader(
dataset, batch_sampler=sampler, num_workers=1, shuffle=False
)
dist_dataloader = shard_dataloader(
dataloader=train_loader, meshes=world_process_mesh, shard_dims="dp"
)
model.train()
process_mesh = ProcessMesh([0, 1], dim_names=["dp"])
out_placements = [dist.Partial(dist.ReduceType.kRedAvg)]
for batch_id, data in enumerate(dist_dataloader()):
if batch_id > 10:
break
img, label = data
out = model(img)
loss_func = dist.local_map(
masked_lm_loss_func,
out_placements=out_placements,
in_placements=[None, None],
process_mesh=process_mesh,
)
avg_loss = loss_func(
out,
label,
global_local_loss_list_item=global_local_loss_list[batch_id],
)
avg_loss.backward()
optimizer.step()
model.clear_gradients()
if __name__ == '__main__':
TestLocalViewCompute().run_test_cases()
+239
View File
@@ -0,0 +1,239 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 paddle
from paddle.distributed.auto_parallel.pipelining.microbatch import (
TensorChunkSpec,
merge_chunks,
split_args_kwargs_into_chunks,
)
class TestMicrobatch:
def __init__(self):
paddle.seed(2024)
paddle.distributed.init_parallel_env()
self.batch_size = 8
self.feature_size = 4
self.tensor = paddle.randn([self.batch_size, self.feature_size])
self.rank = paddle.distributed.get_rank()
def test_tensor_chunk_spec(self):
# Test creation and string representation of TensorChunkSpec
spec = TensorChunkSpec(0)
assert spec.split_axis == 0
assert str(spec) == "TensorChunkSpec(0)"
assert "TensorChunkSpec(0)" in repr(spec)
def test_split_args_kwargs(self):
# Test basic parameter splitting
args = (self.tensor,)
kwargs = {"input": self.tensor}
num_chunks = 2
args_split, kwargs_split = split_args_kwargs_into_chunks(
args, kwargs, num_chunks
)
assert len(args_split) == num_chunks
assert len(kwargs_split) == num_chunks
assert args_split[0][0].shape[0] == self.batch_size // num_chunks
# Test splitting with non-tensor parameters
args = (self.tensor, 42, "string")
kwargs = {"tensor": self.tensor, "number": 42}
num_chunks = 2
args_split, kwargs_split = split_args_kwargs_into_chunks(
args, kwargs, num_chunks
)
# Verify non-tensor parameters remain unchanged in each chunk
assert args_split[0][1] == 42
assert args_split[0][2] == "string"
assert kwargs_split[0]["number"] == 42
# Test splitting with custom specification
tensor_2d = paddle.randn([4, 6])
args = (tensor_2d,)
args_chunk_spec = (TensorChunkSpec(1),) # Split on second dimension
args_split, _ = split_args_kwargs_into_chunks(
args, None, 2, args_chunk_spec
)
assert args_split[0][0].shape[1] == 3
def test_merge_chunks(self):
# Test merging chunks
chunk1 = paddle.randn([4, 4])
chunk2 = paddle.randn([4, 4])
chunks = [chunk1, chunk2]
chunk_spec = [TensorChunkSpec(0)]
merged = merge_chunks(chunks, chunk_spec)
assert merged.shape[0] == 8
# Test merging chunks containing non-tensor values
chunks = [(paddle.randn([4, 4]), 42)] * 2
chunk_spec = [TensorChunkSpec(0), None]
merged = merge_chunks(chunks, chunk_spec)
assert merged[1] == 42
# Test error cases
try:
# Test error when tensor size is smaller than number of chunks
small_tensor = paddle.randn([1, 4])
split_args_kwargs_into_chunks((small_tensor,), None, 2)
raise AssertionError("Expected ValueError")
except ValueError:
pass
try:
# Test error when parameter count doesn't match chunk_spec length
split_args_kwargs_into_chunks(
(self.tensor,),
None,
2,
(TensorChunkSpec(0), TensorChunkSpec(1)),
)
raise AssertionError("Expected ValueError")
except AssertionError:
pass
# test merge empty chunks
empty_chunks = []
result = merge_chunks(empty_chunks, None)
assert result == []
# test tensor size smaller than chunks number
small_tensor = paddle.randn([1, 4])
try:
split_args_kwargs_into_chunks((small_tensor,), None, 2)
raise AssertionError("Expected ValueError")
except ValueError:
pass
# test merge non-tensor with tensor spec
chunks = [(42,), (42,)]
chunk_spec = (TensorChunkSpec(0),)
result = merge_chunks(chunks, chunk_spec)
assert result[0] == 42
def test_nested_structure(self):
# test nested tensor
nested_tensor = [
[paddle.randn([4, 2]), paddle.randn([4, 2])],
[paddle.randn([4, 2]), paddle.randn([4, 2])],
]
args = (nested_tensor,)
kwargs = {"nested": nested_tensor}
args_split, kwargs_split = split_args_kwargs_into_chunks(
args, kwargs, 2
)
assert len(args_split) == 2
assert len(args_split[0][0]) == 2
assert len(args_split[0][0][0]) == 2
assert args_split[0][0][0][0].shape == [2, 2]
assert len(kwargs_split) == 2
assert len(kwargs_split[0]["nested"]) == 2
assert len(kwargs_split[0]["nested"][0]) == 2
assert kwargs_split[0]["nested"][0][0].shape == [2, 2]
merged_args = merge_chunks(
args_split,
[
[TensorChunkSpec(0), TensorChunkSpec(0)],
[TensorChunkSpec(0), TensorChunkSpec(0)],
],
)
assert merged_args[0][0][0].shape == [4, 2]
assert merged_args[0][1][1].shape == [4, 2]
assert len(merged_args[0]) == 2
assert len(merged_args[0][0]) == 2
def test_dist_tensor_split_and_merge(self):
# test dist tensor split and merge
base_tensor = self.tensor
dense_tensor, _ = split_args_kwargs_into_chunks(
(base_tensor,),
None,
2,
)
mesh = paddle.distributed.ProcessMesh([0, 1], dim_names=["dp"])
dist_tensor = paddle.distributed.shard_tensor(
self.tensor,
mesh,
[paddle.distributed.Shard(0)],
)
dist_tensor_split, _ = split_args_kwargs_into_chunks(
(dist_tensor,),
None,
2,
)
if self.rank == 0:
is_equal = (
dist_tensor_split[0][0]
._local_value()
.equal_all(dense_tensor[0][0][:2])
)
assert is_equal.item()
is_equal = (
dist_tensor_split[1][0]
._local_value()
.equal_all(dense_tensor[0][0][2:])
)
assert is_equal.item()
else:
is_equal = (
dist_tensor_split[0][0]
._local_value()
.equal_all(dense_tensor[1][0][:2])
)
assert is_equal.item()
is_equal = (
dist_tensor_split[1][0]
._local_value()
.equal_all(dense_tensor[1][0][2:])
)
assert is_equal.item()
chunk1 = dist_tensor_split[0][0]
chunk2 = dist_tensor_split[1][0]
chunk_spec = [TensorChunkSpec(0)]
merged_chunk = merge_chunks([chunk1, chunk2], chunk_spec)
if self.rank == 0:
is_equal = merged_chunk._local_value().equal_all(base_tensor[:4])
assert is_equal.item()
else:
is_equal = merged_chunk._local_value().equal_all(base_tensor[4:])
assert is_equal.item()
def run_all_tests(self):
"""Run all test methods"""
self.test_tensor_chunk_spec()
self.test_split_args_kwargs()
self.test_merge_chunks()
self.test_nested_structure()
self.test_dist_tensor_split_and_merge()
if __name__ == "__main__":
TestMicrobatch().run_all_tests()
@@ -0,0 +1,146 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 paddle
import paddle.distributed as dist
class _recv_info:
def __init__(self, tensor, placements):
self.obj_size = 10
self.obj_type1 = paddle.distributed.Shard(0)
self.obj_type2 = paddle.distributed.Replicate()
self.obj_type3 = paddle.distributed.Partial()
self.obj_list = [
paddle.distributed.Shard(0),
paddle.distributed.Replicate(),
paddle.distributed.Partial(),
]
self.dtype = paddle.int64
class TestObjectListCommunication:
def init_dist_env(self):
dist.init_parallel_env()
paddle.seed(2025)
def test_object_list_communication(self):
"""Test object list communication functionalities including parameter validation,
group operations and normal communication process"""
self.init_dist_env()
curr_rank = dist.get_rank()
# Test case 1: Parameter validation - empty list
if curr_rank == 0:
try:
dist.send_object_list([], dst=1)
raise AssertionError("Should raise ValueError")
except ValueError:
pass
else:
try:
dist.recv_object_list([], src=0)
raise AssertionError("Should raise ValueError")
except ValueError:
pass
# Test case 2: Group operations - rank not in group
excluded_group = dist.new_group([2, 3])
send_list = ["test"]
recv_list = [None]
if curr_rank == 0:
# test the dst is not in the group
dist.send_object_list(["test"], dst=1, group=excluded_group)
elif curr_rank == 1:
# test the src is not in the group
dist.recv_object_list([None], src=0, group=excluded_group)
assert recv_list[0] is None
excluded_group_1 = dist.new_group([0, 1])
if curr_rank == 0:
dist.send_object_list(send_list, dst=1, group=excluded_group_1)
elif curr_rank == 1:
dist.recv_object_list(recv_list, src=0, group=excluded_group_1)
assert recv_list[0] == "test"
# Test case 3: Group operations - parameter conflicts
if curr_rank == 0:
try:
dist.send_object_list(["test"], dst=1, dst_in_group=1)
raise AssertionError("Should raise ValueError")
except ValueError:
pass
elif curr_rank == 1:
try:
dist.recv_object_list([None], src=0, src_in_group=0)
raise AssertionError("Should raise ValueError")
except ValueError:
pass
# Test case 4: Group operations - using src_in_group/dst_in_group
test_group = dist.new_group([0, 1])
if curr_rank == 0:
data = ["test_group_dst"]
dist.send_object_list(data, group=test_group, dst_in_group=1)
elif curr_rank == 1:
data = [None]
dist.recv_object_list(data, group=test_group, src_in_group=0)
assert data[0] == "test_group_dst"
# Test case 5: Normal communication process
if curr_rank == 0:
data = [
42, # integer
"hello", # string
{"key": "value"}, # dictionary
]
dist.send_object_list(data, dst=1)
elif curr_rank == 1:
data = [None] * 3
dist.recv_object_list(data, src=0)
assert data[0] == 42
assert data[1] == "hello"
assert data[2] == {"key": "value"}
# Test case 6: Test objects with distributed attributes
curr_rank = dist.get_rank()
if curr_rank == 0:
data1 = _recv_info(None, None)
data = [data1]
dist.send_object_list(data, dst=1)
elif curr_rank == 1:
data = [None]
dist.recv_object_list(data, src=0)
assert isinstance(data[0], _recv_info)
assert type(data[0].obj_size) == int
assert data[0].obj_size == 10
assert isinstance(data[0].obj_type1, paddle.distributed.Shard)
assert isinstance(data[0].obj_type2, paddle.distributed.Replicate)
assert isinstance(data[0].obj_type3, paddle.distributed.Partial)
assert data[0].dtype == paddle.int64
assert len(data[0].obj_list) == 3
assert isinstance(data[0].obj_list[0], paddle.distributed.Shard)
assert isinstance(data[0].obj_list[1], paddle.distributed.Replicate)
assert isinstance(data[0].obj_list[2], paddle.distributed.Partial)
if __name__ == '__main__':
TestObjectListCommunication().test_object_list_communication()
@@ -0,0 +1,374 @@
# Copyright (c) 2025 PaddlePaddle Authors. 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 random
import numpy as np
import paddle
import paddle.distributed as dist
from paddle import nn
from paddle.distributed import fleet
from paddle.distributed.auto_parallel.pipelining.schedules import (
Schedule1F1B,
ScheduleFThenB,
ScheduleVPP,
)
from paddle.distributed.auto_parallel.pipelining.stage import PipelineStage
from paddle.io import DataLoader, Dataset
def fix_seeds(seed=2025):
"""Fix random seeds to ensure reproducibility"""
paddle.seed(seed)
random.seed(seed)
np.random.seed(seed)
class PPModel(nn.Layer):
def __init__(self, name_prefix="", schedule="FThenB", shared_parameters={}):
super().__init__(name_scope=name_prefix)
self.name_prefix = name_prefix
self.mesh = paddle.distributed.ProcessMesh(
[0, 1, 2, 3], dim_names=["pp"]
)
self.num_layers = 8
self.num_layers_per_card = self.num_layers // 4
# Store the names of each pair of shared parameters.
self.shared_parameters = shared_parameters
self.linears = nn.LayerList()
for i in range(self.num_layers):
linear = nn.Linear(8, 8, bias_attr=False)
# Different models have distinct parameter name spaces to avoid naming conflicts.
linear.weight.name = f"{self.name_prefix}_linear_{i}_weight"
# Mark network parameters
linear.weight = dist.shard_tensor(
linear.weight,
(
self.get_pp_mesh(i)
if schedule != "VPP"
else self.get_vpp_mesh(i)
),
[dist.Replicate()],
)
self.linears.append(linear)
# Store the parameters to be shared under different model names.
self.model_shared_param_mp = {}
# Build `model_shared_param_mp`.
self.set_shared_param()
def set_shared_param(self):
for pair in self.shared_parameters:
assert len(pair) == 2
ori_name = pair[0]
sync_name = pair[1]
ori_param = None
for _, linear in enumerate(self.linears):
if ori_name == linear.weight.name:
ori_param = linear.weight
assert ori_param is not None
self.model_shared_param_mp[sync_name] = ori_param
def get_pp_mesh(self, layer_index):
mesh_idx = int(layer_index / (self.num_layers / 4))
return self.mesh[mesh_idx]
def get_vpp_mesh(self, layer_index):
mesh_idx = int(layer_index % 4)
return self.mesh[mesh_idx]
def forward(self, x):
x.stop_gradient = False
out = x
for i in range(self.num_layers):
# Mark intermediate variables, reshard when switching devices
cur_mesh = self.get_pp_mesh(i)
if i % self.num_layers_per_card == 0 and i > 0:
out = dist.reshard(out, cur_mesh, [dist.Replicate()])
weight = self.linears[i].weight
if weight.name in self.model_shared_param_mp:
weight = dist.reshard(
self.model_shared_param_mp[weight.name],
cur_mesh,
[dist.Replicate()],
)
out = paddle.matmul(out, weight)
else:
out = self.linears[i](out)
return paddle.cast(out, 'float32')
class SingleStage(nn.Layer):
def __init__(self, layers):
super().__init__()
self.layers = layers
def forward(self, x):
x.stop_gradient = False
out = x
for i in range(len(self.layers)):
out = self.layers[i](out)
return paddle.cast(out, 'float32')
class RandomDataset(Dataset):
def __init__(self, image_size, output_size, num_samples=1):
super().__init__()
self.image_size = image_size
self.num_samples = num_samples
self.output_size = output_size
def __getitem__(self, index):
input = paddle.rand([self.image_size], dtype='float32')
label = paddle.rand([self.output_size], dtype='float32')
return input, label
def __len__(self):
return self.num_samples
def _get_param_from_name(param_name, model):
for param in model.parameters():
if param.name == param_name:
return param
return None
def build_shared_parameters(shared_params_names, model):
# Find the two shared parameters and build shared parameter information.
shared_mp = []
for pair in shared_params_names:
assert len(pair) == 2
ori_name = pair[0]
sync_name = pair[1]
ori_param = _get_param_from_name(ori_name, model)
sync_param = _get_param_from_name(sync_name, model)
# Note: Users must strictly maintain the format of the data structure here.
shared_mp.append({"params": [ori_param, sync_param]})
return shared_mp
rtol = 1e-5
class TestSharedParameters:
@classmethod
def setUpClass(cls):
"""Initialize test class setup"""
paddle.distributed.init_parallel_env()
cls.group = paddle.distributed.new_group([0, 1, 2, 3])
cls.rank = dist.get_rank()
cls.mesh = paddle.distributed.ProcessMesh(
[0, 1, 2, 3], dim_names=["pp"]
)
fleet.auto.set_mesh(cls.mesh)
def test_single_schedule(self, sing_schedule="FThenB"):
"""Test pipeline parallel model with shared parameters using FThenB/1F1B strategy"""
fix_seeds()
name_prefix = "pp_" + sing_schedule
self.model = PPModel(name_prefix=name_prefix)
self.micro_batches = 8
shared_params_names = [
[
f"{name_prefix}_linear_0_weight.dist",
f"{name_prefix}_linear_7_weight.dist",
]
]
# Pre-build shared parameter information.
shared_mp = build_shared_parameters(shared_params_names, self.model)
num_layers_per_card = 2
cur_rank = dist.get_rank()
stage_layers = SingleStage(
self.model.linears[
cur_rank * num_layers_per_card : (cur_rank + 1)
* num_layers_per_card
]
)
self.stage = PipelineStage(
stage_layers,
self.rank,
4,
group=self.group,
shared_parameters=shared_mp,
)
self.stage.has_backward = True
loss_fn_ = nn.MSELoss()
if sing_schedule == "FThenB":
schedule = ScheduleFThenB(
self.stage, self.micro_batches, loss_fn=loss_fn_
)
elif sing_schedule == "1F1B":
schedule = Schedule1F1B(
self.stage, self.micro_batches, loss_fn=loss_fn_
)
else:
raise ValueError(
f"Unknown schedule type: {sing_schedule}. "
f"Currently `test_single_schedule` supported types are 'FThenB' and '1F1B'."
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=self.model.parameters()
)
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=8)
losses_by_step = []
num_iterations = 20
for _ in range(num_iterations):
losses_by_micro_batch = []
for _, (data, label) in enumerate(loader):
schedule.step(data, target=label, losses=losses_by_micro_batch)
if self.rank == 3:
losses_by_step.append(
np.array(losses_by_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return losses_by_step
def test_multi_schedule(self, multi_schedule="VPP"):
"""Test pipeline parallel with shared parameters model using VPP strategy"""
fix_seeds()
name_prefix = "pp_" + multi_schedule
self.model = PPModel(name_prefix=name_prefix, schedule="VPP")
self.local_stages = 2
self.micro_batches = 8
self.stage_list = []
shared_params_names = [
[
f"{name_prefix}_linear_0_weight.dist",
f"{name_prefix}_linear_7_weight.dist",
]
]
# Pre-build shared parameter information.
shared_mp = build_shared_parameters(shared_params_names, self.model)
cur_rank = dist.get_rank()
for i in range(self.local_stages):
stage_layers = SingleStage(
self.model.linears[cur_rank + i * 4 : cur_rank + i * 4 + 1]
)
# Note: In VPP mode, the same `shared_mp` is used for building multiple
# stages to avoid redundant group creation.
self.stage_list.append(
PipelineStage(
stage_layers,
cur_rank + i * 4,
8,
group=self.group,
shared_parameters=shared_mp,
)
)
self.stage_list[i].has_backward = True
loss_fn_ = nn.MSELoss()
schedule = ScheduleVPP(
self.stage_list, self.micro_batches, loss_fn=loss_fn_
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=self.model.parameters()
)
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=8)
losses_by_micro_batch = []
losses_by_step = []
num_iterations = 20
for _ in range(num_iterations):
for _, (data, label) in enumerate(loader):
schedule.step(data, target=label, losses=losses_by_micro_batch)
if self.rank == 3:
losses_by_step.append(
np.array(losses_by_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return losses_by_step
def test_pp_model(self):
"""Test pipeline parallel model using PPModel as the baseline"""
fix_seeds()
name_prefix = "pp_model"
shared_params_names = [
[
f"{name_prefix}_linear_0_weight.dist",
f"{name_prefix}_linear_7_weight.dist",
]
]
pp_model = PPModel(
name_prefix=name_prefix, shared_parameters=shared_params_names
)
opt = paddle.optimizer.AdamW(
learning_rate=0.001, parameters=pp_model.parameters()
)
loss_fn = nn.MSELoss()
dataset = RandomDataset(image_size=8, output_size=8, num_samples=8)
loader = DataLoader(dataset, batch_size=1)
pp_losses_step = []
num_iterations = 20
for _ in range(num_iterations):
pp_losses_micro_batch = []
for _, (data, label) in enumerate(loader):
output = pp_model(data)
loss = loss_fn(output, label)
pp_losses_micro_batch.append(loss.item())
loss.backward()
pp_losses_step.append(
np.array(pp_losses_micro_batch, dtype=np.float32).mean()
)
opt.step()
opt.clear_grad()
return pp_losses_step
def run_test(self):
"""Compare shared params losses between three training methods"""
self.setUpClass()
pp_losses = self.test_pp_model()
pp_FThenB_losses = self.test_single_schedule(sing_schedule="FThenB")
pp_1F1B_losses = self.test_single_schedule(sing_schedule="1F1B")
pp_vpp_losses = self.test_multi_schedule(multi_schedule="VPP")
if self.rank == 3:
np.testing.assert_allclose(
pp_losses,
pp_FThenB_losses,
rtol=rtol,
)
np.testing.assert_allclose(
pp_losses,
pp_1F1B_losses,
rtol=rtol,
)
np.testing.assert_allclose(
pp_losses,
pp_vpp_losses,
rtol=rtol,
)
if __name__ == '__main__':
TestSharedParameters().run_test()

Some files were not shown because too many files have changed in this diff Show More