180 lines
5.7 KiB
Python
180 lines
5.7 KiB
Python
# 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 logging
|
|
|
|
import numpy as np
|
|
from dist_amp_base import (
|
|
MLP,
|
|
RandomDataset,
|
|
compare_state_dict,
|
|
create_optimizer,
|
|
save_model_parameters,
|
|
)
|
|
|
|
import paddle
|
|
from paddle.distributed.fleet.utils.hybrid_parallel_util import (
|
|
fused_allreduce_gradients,
|
|
)
|
|
|
|
logging.basicConfig(level="INFO", format="%(message)s")
|
|
|
|
|
|
def train_mlp(
|
|
model, train_loader, use_pure_bf16=False, use_main_grad=False, acc_steps=1
|
|
):
|
|
logging.info(
|
|
f"-- Train Info: use_pure_bf16={use_pure_bf16}, use_main_grad={use_main_grad}, acc_steps={acc_steps}"
|
|
)
|
|
|
|
optimizer = create_optimizer(
|
|
model=model, use_pure_bf16=use_pure_bf16, use_main_grad=use_main_grad
|
|
)
|
|
if use_pure_bf16:
|
|
level = 'O2'
|
|
custom_white_list = None
|
|
model = paddle.amp.decorate(
|
|
models=model,
|
|
dtype="bfloat16",
|
|
level=level,
|
|
)
|
|
else:
|
|
level = 'O1'
|
|
custom_white_list = [
|
|
"matmul_v2",
|
|
"elementwise_add",
|
|
"relu",
|
|
]
|
|
model = paddle.DataParallel(model)
|
|
|
|
if not use_pure_bf16:
|
|
for param in model.parameters():
|
|
t = paddle.cast(
|
|
paddle.cast(param, dtype='bfloat16'), dtype='float32'
|
|
)
|
|
param.set_value(t)
|
|
|
|
local_rank = paddle.distributed.get_rank()
|
|
|
|
losses = []
|
|
epoch = 2
|
|
for eop in range(epoch):
|
|
model.train()
|
|
|
|
for batch_id, data in enumerate(train_loader()):
|
|
data.stop_gradient = True
|
|
|
|
enable_stats = False # eop == 0
|
|
if enable_stats:
|
|
logging.info("<<<<<<<<<<<< forward-backward >>>>>>>>>>>")
|
|
paddle.amp.debugging.enable_operator_stats_collection()
|
|
with model.no_sync():
|
|
with paddle.amp.auto_cast(
|
|
True,
|
|
level=level,
|
|
dtype="bfloat16",
|
|
custom_white_list=custom_white_list,
|
|
):
|
|
out = model(data)
|
|
|
|
# compute loss in float32
|
|
loss = paddle.mean(out.astype("float32"))
|
|
|
|
# normal implementation for gradient accumulation.
|
|
if acc_steps != 1:
|
|
loss = loss / acc_steps
|
|
|
|
losses.append(loss.item())
|
|
loss.backward()
|
|
logging.info(
|
|
f"-- [rank={local_rank}] epoch {eop}, batch {batch_id}, loss: {loss.astype(paddle.float32).numpy()}"
|
|
)
|
|
if enable_stats:
|
|
paddle.amp.debugging.disable_operator_stats_collection()
|
|
|
|
if (batch_id + 1) % acc_steps == 0:
|
|
if enable_stats:
|
|
logging.info(
|
|
"<<<<<<<<<<<< fused_allreduce_gradients >>>>>>>>>>>"
|
|
)
|
|
paddle.amp.debugging.enable_operator_stats_collection()
|
|
fused_allreduce_gradients(list(model.parameters()), None)
|
|
if enable_stats:
|
|
paddle.amp.debugging.disable_operator_stats_collection()
|
|
|
|
if enable_stats:
|
|
logging.info("<<<<<<<<<<<< optimizer >>>>>>>>>>>")
|
|
paddle.amp.debugging.enable_operator_stats_collection()
|
|
optimizer.step()
|
|
optimizer.clear_grad()
|
|
if enable_stats:
|
|
paddle.amp.debugging.disable_operator_stats_collection()
|
|
|
|
model_param_dict = save_model_parameters(model)
|
|
optimizer_state_dict = optimizer.state_dict()
|
|
return losses, model_param_dict, optimizer_state_dict
|
|
|
|
|
|
def test_dp_bf16():
|
|
if not paddle.amp.is_bfloat16_supported():
|
|
logging.info("BFloat16 is not supported!")
|
|
return
|
|
|
|
paddle.distributed.init_parallel_env()
|
|
local_rank = paddle.distributed.get_rank()
|
|
paddle.seed(2023 + local_rank)
|
|
np.random.seed(2023 + local_rank)
|
|
|
|
# For DataParallel, DataLoader should feed different data for different GPUs.
|
|
train_loader = paddle.io.DataLoader(
|
|
RandomDataset(),
|
|
batch_size=100,
|
|
shuffle=False,
|
|
drop_last=True,
|
|
num_workers=0,
|
|
)
|
|
|
|
single_mlp = MLP()
|
|
state_dict = single_mlp.state_dict()
|
|
|
|
def _compare_bf16_o1_vs_o2(acc_steps=1):
|
|
# dp bf16 O1 vs dp bf16 O2 main_grad
|
|
mlp1 = MLP()
|
|
mlp2 = MLP()
|
|
mlp1.set_state_dict(state_dict)
|
|
mlp2.set_state_dict(state_dict)
|
|
losses_o1, model_param_dict_o1, optimizer_state_dict_o1 = train_mlp(
|
|
mlp1, train_loader, use_pure_bf16=False, acc_steps=acc_steps
|
|
)
|
|
losses_o2, model_param_dict_o2, optimizer_state_dict_o2 = train_mlp(
|
|
mlp2,
|
|
train_loader,
|
|
use_pure_bf16=True,
|
|
use_main_grad=True,
|
|
acc_steps=acc_steps,
|
|
)
|
|
np.testing.assert_array_equal(losses_o2, losses_o1)
|
|
compare_state_dict(
|
|
model_param_dict_o1, model_param_dict_o2, optimizer_state_dict_o2
|
|
)
|
|
|
|
# no gradient accumulation
|
|
_compare_bf16_o1_vs_o2(acc_steps=1)
|
|
# gradient accumulation
|
|
_compare_bf16_o1_vs_o2(acc_steps=2)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test_dp_bf16()
|