Files
paddlepaddle--paddle/test/collective/fleet/dygraph_dist_save_load.py
T
2026-07-13 12:40:42 +08:00

419 lines
12 KiB
Python

# 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 argparse
import os
import shutil
import subprocess
import sys
import tempfile
import numpy as np
import paddle
from paddle import distributed as dist
from paddle.distributed import fleet
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_optimizer_stage2 import (
GroupShardedOptimizerStage2,
)
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_stage2 import (
GroupShardedStage2,
)
from paddle.incubate.distributed.utils.io import load, save
from paddle.nn import Linear
print(load)
epoch = 2
linear_size = 1000
class MLP(paddle.nn.Layer):
def __init__(self, linear_size=1000, param_attr=None, bias_attr=None):
super().__init__()
self._linear1 = Linear(linear_size, linear_size)
self._linear2 = Linear(linear_size, linear_size)
self._linear3 = Linear(linear_size, 10)
def forward(self, inputs):
y = self._linear1(inputs)
y = self._linear2(y)
y = self._linear3(y)
return y
class RandomDataset(paddle.io.Dataset):
def __init__(self, num_samples=2000, linear_size=1000):
self.num_samples = num_samples
self.linear_size = linear_size
def __getitem__(self, idx):
img = np.random.rand(self.linear_size).astype('float32')
label = np.ones(1).astype('int64')
return img, label
def __len__(self):
return self.num_samples
def optimizer_setting(model, use_pure_fp16, opt_group=False):
clip = paddle.nn.ClipGradByGlobalNorm(clip_norm=1.0)
optimizer = paddle.optimizer.AdamW(
parameters=(
[
{
"params": model.parameters(),
}
]
if opt_group
else model.parameters()
),
learning_rate=0.001,
weight_decay=0.00001,
grad_clip=clip,
multi_precision=use_pure_fp16,
)
return optimizer
def train_mlp(
model,
sharding_stage,
batch_size=100,
use_pure_fp16=False,
accumulate_grad=False,
opt_group=False,
save_model=False,
test_minimize=False,
opt_state=None,
):
if sharding_stage != "dp":
group = paddle.distributed.new_group([0, 1], backend="nccl")
if opt_group:
optimizer = optimizer_setting(
model=model, use_pure_fp16=use_pure_fp16, opt_group=opt_group
)
else:
optimizer = optimizer_setting(model=model, use_pure_fp16=use_pure_fp16)
if sharding_stage == 2:
optimizer = GroupShardedOptimizerStage2(
params=optimizer._parameter_list, optim=optimizer, group=group
)
model = GroupShardedStage2(
model, optimizer, group=group, buffer_max_size=2**21
)
model._set_reduce_overlap(True)
optimizer._set_broadcast_overlap(True, model)
else:
model = paddle.DataParallel(model)
# check optimizer.minimize() error
if test_minimize:
try:
optimizer.minimize()
except:
print(
"====== Find sharding_stage2_optimizer.minimize() error ======"
)
return
paddle.seed(2023)
np.random.seed(2023)
train_loader = paddle.io.DataLoader(
RandomDataset(),
batch_size=batch_size,
shuffle=False,
drop_last=True,
num_workers=0,
)
if sharding_stage == 2:
model.to(device="gpu")
if opt_state is not None:
optimizer.set_state_dict(opt_state)
for eop in range(epoch):
model.train()
for batch_id, data in enumerate(train_loader()):
img, label = data
label.stop_gradient = True
img.stop_gradient = True
out = model(img)
loss = paddle.nn.functional.cross_entropy(input=out, label=label)
avg_loss = paddle.mean(x=loss.cast(dtype=paddle.float32))
if batch_size == 20:
avg_loss = avg_loss / 5
avg_loss.backward()
if not accumulate_grad:
optimizer.step()
optimizer.clear_grad()
if accumulate_grad:
optimizer.step()
optimizer.clear_grad()
paddle.device.cuda.synchronize()
if save_model:
return model, optimizer
return model.parameters()
def save_model(model, output_dir, **configs):
configs["save_model"] = True
model, opt = train_mlp(model, **configs)
model_file = os.path.join(
output_dir, f"rank{dist.get_rank()}model.pdparams"
)
opt_file = os.path.join(output_dir, f"rank{dist.get_rank()}model.pdopt")
g_model_file = os.path.join(
output_dir, f"rank{dist.get_rank()}g_model.pdparams"
)
g_opt_file = os.path.join(output_dir, f"rank{dist.get_rank()}g_model.pdopt")
paddle.save(model.state_dict(), model_file)
paddle.save(opt.state_dict(), opt_file)
save(
model.state_dict(), g_model_file, gather_to=[0, 1], state_type="params"
)
save(opt.state_dict(), g_opt_file, gather_to=[0, 1], state_type="opt")
def load_mode(model, model_state_dict, output_param_path, **configs):
configs["save_model"] = False
model.set_state_dict(model_state_dict)
params = train_mlp(model, **configs)
paddle.save(params, output_param_path)
def step_check(path1, path2):
m1 = paddle.load(path1)
m2 = paddle.load(path2)
for v1, v2 in zip(m1, m2):
np.testing.assert_allclose(v1.numpy(), v2.numpy())
print(f"value same: {v1.name}")
def step_save(strategy, output_dir, seed):
python_exe = sys.executable
# save data
os.makedirs(output_dir + "/logs", exist_ok=True)
filename = os.path.basename(__file__)
cmd = (
f"{python_exe} -m paddle.distributed.launch --log_dir {output_dir}/logs"
f" --gpus 0,1 {filename} --cmd save --strategy {strategy} --output_dir {output_dir} --seed {seed}"
)
p = subprocess.Popen(cmd.split())
p.communicate()
assert p.poll() == 0
def step_load(
saved_strategy, current_strategy, saved_dir, load_way, output_path, seed
):
python_exe = sys.executable
os.makedirs(f"{saved_dir}/load/logs", exist_ok=True)
filename = os.path.basename(__file__)
# load dp
cmd = (
f"{python_exe} -m paddle.distributed.launch --log_dir {saved_dir}/load/logs"
f" --gpus 0,1 {filename} --cmd load --strategy {current_strategy} --output_dir {saved_dir} --load_dir {saved_dir}/{saved_strategy}/save --load_way {load_way}"
f" --output_param_path {output_path} --seed {seed}"
)
p = subprocess.Popen(cmd.split())
p.communicate()
assert p.poll() == 0
def test_save_load(args):
np.random.seed(args.seed)
paddle.seed(args.seed)
if args.cmd == "main":
run_case(args)
return
paddle.distributed.init_parallel_env()
strategy = fleet.DistributedStrategy()
if args.strategy == "dp":
strategy.hybrid_configs = {
"dp_degree": 2,
"mp_degree": 1,
"pp_degree": 1,
"sharding_degree": 1,
}
elif args.strategy == "sharding_stage2":
strategy.hybrid_configs = {
"dp_degree": 1,
"mp_degree": 1,
"pp_degree": 1,
"sharding_degree": 2,
}
else:
raise ValueError(f"Not supported strategy: {args.strategy}")
fleet.init(is_collective=True, strategy=strategy)
fleet.set_log_level("DEBUG")
mlp1 = MLP()
output_dir = os.path.join(args.output_dir, args.strategy, args.cmd)
os.makedirs(output_dir, exist_ok=True)
if args.cmd.lower() == "save":
if args.strategy == "dp":
# DP VS stage2
save_model(
mlp1,
output_dir,
sharding_stage="dp",
use_pure_fp16=False,
opt_group=False,
save_model=True,
)
elif args.strategy == "sharding_stage2":
save_model(
mlp1,
output_dir,
sharding_stage=2,
use_pure_fp16=False,
opt_group=False,
save_model=True,
)
else:
raise ValueError(f"Not supported {args.strategy}")
elif args.cmd.lower() == "load":
output_dir = args.load_dir
model_file = os.path.join(
output_dir, f"rank{dist.get_rank()}model.pdparams"
)
opt_file = os.path.join(output_dir, f"rank{dist.get_rank()}model.pdopt")
g_model_file = os.path.join(
output_dir, f"rank{args.gather_to}g_model.pdparams"
)
g_opt_file = os.path.join(
output_dir, f"rank{args.gather_to}g_model.pdopt"
)
if args.load_way == "load":
model_file = g_model_file
opt_file = g_opt_file
load_ = lambda x: eval(args.load_way)(x, place='cpu')
else:
load_ = eval(args.load_way)
model = load_(model_file)
opt = load_(opt_file)
for k in opt.keys():
print("opt k:", k)
if args.strategy == "dp":
load_mode(
mlp1,
model,
args.output_param_path,
sharding_stage="dp",
use_pure_fp16=False,
opt_group=False,
save_model=False,
opt_state=opt,
)
elif args.strategy == "sharding_stage2":
load_mode(
mlp1,
model,
args.output_param_path,
sharding_stage=2,
use_pure_fp16=False,
opt_group=False,
save_model=False,
opt_state=opt,
)
else:
raise ValueError(f"Not supported strategy {args.strategy}")
else:
raise ValueError(f"Not supported cmd: {args.cmd}")
def run_case(args):
saving_strategy = args.test_case.split(":")[0]
loading_strategy = args.test_case.split(":")[1]
output_dir = tempfile.mkdtemp()
print("output dir:", output_dir)
os.makedirs(output_dir + "/load_save", exist_ok=True)
# save dp
step_save(saving_strategy, output_dir, args.seed)
# return
# load dp
p1 = os.path.join(output_dir, "m1.pdparams")
p2 = os.path.join(output_dir, "m2.pdparams")
step_load(
saving_strategy,
saving_strategy,
output_dir,
"paddle.load",
p1,
args.seed + 1,
)
step_load(
saving_strategy, loading_strategy, output_dir, "load", p2, args.seed + 2
)
# check
step_check(p1, p2)
shutil.rmtree(output_dir)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
"--cmd", default="main", choices=["main", "save", "load"]
)
parser.add_argument(
"--strategy", required=False, choices=["dp", "sharding_stage2"]
)
parser.add_argument(
"--load_way", choices=["paddle.load", "load"], required=False
)
parser.add_argument("--load_dir", required=False)
parser.add_argument("--output_dir", required=False)
parser.add_argument("--output_param_path", required=False)
parser.add_argument(
"--test_case",
required=False,
choices=[
"dp:dp",
"dp:sharding_stage2",
"sharding_stage2:dp",
"sharding_stage2:sharding_stage2",
],
)
parser.add_argument("--gather_to", required=False, default=0)
parser.add_argument("--seed", type=int, default=2022)
args = parser.parse_args()
test_save_load(args)