289 lines
9.1 KiB
Python
Executable File
289 lines
9.1 KiB
Python
Executable File
# 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 random
|
|
import unittest
|
|
|
|
import numpy as np
|
|
|
|
import paddle
|
|
from paddle.distributed.fleet.utils import recompute
|
|
from paddle.incubate.distributed.fleet import recompute_sequential
|
|
|
|
|
|
def get_fc_block(block_idx, input_size, is_last=False):
|
|
block_name = "block_" + str(block_idx)
|
|
block = paddle.nn.Sequential(
|
|
(
|
|
block_name + "_fc_0",
|
|
paddle.nn.Linear(input_size, input_size, bias_attr=False),
|
|
),
|
|
(block_name + "_dropout", paddle.nn.Dropout(p=0.5)),
|
|
(block_name + "_relu_1", paddle.nn.ReLU()),
|
|
(
|
|
block_name + "_fc_1",
|
|
paddle.nn.Linear(input_size, input_size, bias_attr=False),
|
|
),
|
|
(block_name + "_relu_2", paddle.nn.ReLU()),
|
|
)
|
|
if is_last:
|
|
block.add_sublayer(
|
|
block_name + "_fc_2",
|
|
paddle.nn.Linear(input_size, 1, bias_attr=False),
|
|
) # add sublayer
|
|
else:
|
|
block.add_sublayer(
|
|
block_name + "_fc_2",
|
|
paddle.nn.Linear(input_size, input_size, bias_attr=False),
|
|
) # add sublayer
|
|
return block
|
|
|
|
|
|
class Naive_fc_net(paddle.nn.Layer):
|
|
def __init__(
|
|
self,
|
|
input_size=10,
|
|
recompute_blocks=[1, 3],
|
|
use_fleet_sq=False,
|
|
segments=1,
|
|
use_raw_recompute=False,
|
|
recompute_kwargs={},
|
|
):
|
|
super().__init__()
|
|
self.recompute_blocks = recompute_blocks
|
|
self.recompute_kwargs = recompute_kwargs
|
|
self.use_fleet_sq = use_fleet_sq
|
|
self.use_raw_recompute = use_raw_recompute
|
|
self.segments = segments
|
|
|
|
self.runfunc0 = get_fc_block(0, input_size, is_last=False)
|
|
self.runfunc1 = get_fc_block(1, input_size, is_last=False)
|
|
self.runfunc2 = get_fc_block(2, input_size, is_last=False)
|
|
self.runfunc3 = get_fc_block(3, input_size, is_last=False)
|
|
self.runfunc4 = get_fc_block(4, input_size, is_last=True)
|
|
|
|
if self.use_fleet_sq and not use_raw_recompute:
|
|
self.runfuncs = paddle.nn.Sequential(
|
|
self.runfunc0,
|
|
self.runfunc1,
|
|
self.runfunc2,
|
|
self.runfunc3,
|
|
self.runfunc4,
|
|
)
|
|
|
|
self.layers = [
|
|
self.runfunc0,
|
|
self.runfunc1,
|
|
self.runfunc2,
|
|
self.runfunc3,
|
|
self.runfunc4,
|
|
]
|
|
|
|
# default segments = 2
|
|
if use_raw_recompute:
|
|
self.layers = [
|
|
paddle.nn.Sequential(self.runfunc0, self.runfunc1),
|
|
paddle.nn.Sequential(
|
|
self.runfunc2, self.runfunc3, self.runfunc4
|
|
),
|
|
]
|
|
|
|
def forward(self, inputs):
|
|
if self.use_fleet_sq and not self.use_raw_recompute:
|
|
return recompute_sequential(
|
|
{"segments": self.segments}, self.runfuncs, inputs
|
|
)
|
|
|
|
if self.use_raw_recompute:
|
|
inputs = recompute(self.layers[0], inputs)
|
|
return self.layers[1](inputs)
|
|
|
|
for i in range(len(self.layers)):
|
|
if i in self.recompute_blocks:
|
|
inputs = recompute(
|
|
self.layers[i], inputs, **self.recompute_kwargs
|
|
)
|
|
else:
|
|
inputs = self.layers[i](inputs)
|
|
|
|
return inputs
|
|
|
|
|
|
def run_model(
|
|
recompute_block=[],
|
|
recompute_kwargs={},
|
|
use_fleet_sq=False,
|
|
use_raw_recompute=False,
|
|
segments=1,
|
|
enable_autocast=False,
|
|
pure_fp16=False,
|
|
):
|
|
gen = paddle.seed(10)
|
|
gen.manual_seed(10)
|
|
np.random.seed(10)
|
|
random.seed(10)
|
|
|
|
batch_size, input_size = 1, 10
|
|
model = Naive_fc_net(
|
|
input_size,
|
|
recompute_blocks=recompute_block,
|
|
use_fleet_sq=use_fleet_sq,
|
|
use_raw_recompute=use_raw_recompute,
|
|
segments=segments,
|
|
recompute_kwargs=recompute_kwargs,
|
|
)
|
|
loss_fn = paddle.nn.MSELoss(reduction='mean')
|
|
optimizer = paddle.optimizer.SGD(
|
|
learning_rate=0.01, parameters=model.parameters()
|
|
)
|
|
|
|
if enable_autocast:
|
|
scaler = paddle.amp.GradScaler(init_loss_scaling=4096)
|
|
|
|
loss_ = []
|
|
param_ = []
|
|
grad_ = []
|
|
for step in range(10):
|
|
x_data = np.random.randn(batch_size, input_size).astype(np.float32)
|
|
x = paddle.to_tensor(x_data)
|
|
# x.stop_gradient = False
|
|
level = 'O2' if pure_fp16 else 'O1'
|
|
with paddle.amp.auto_cast(True, level=level):
|
|
y_pred = model(x)
|
|
loss = y_pred.mean()
|
|
if enable_autocast:
|
|
scaler.scale(loss).backward()
|
|
scaler.minimize(optimizer, loss)
|
|
else:
|
|
loss_.append(np.asarray(loss).tolist())
|
|
loss.backward()
|
|
optimizer.step()
|
|
|
|
param_.append(np.asarray(model.parameters()[9]).tolist())
|
|
grad_.append(np.asarray(model.parameters()[3]._grad_ivar()).tolist())
|
|
|
|
optimizer.clear_grad()
|
|
return loss_, param_, grad_
|
|
|
|
|
|
class TestPyLayer(unittest.TestCase):
|
|
def test_base_case(self, enable_autocast=False, pure_fp16=False):
|
|
def check_identical(loss_ref, param_ref, grad_ref, loss, param, grad):
|
|
self.assertEqual(loss_ref, loss)
|
|
self.assertEqual(param_ref, param)
|
|
self.assertEqual(grad_ref, grad)
|
|
|
|
# without recompute
|
|
loss_ref, param_ref, grad_ref = run_model(
|
|
recompute_block=[],
|
|
enable_autocast=enable_autocast,
|
|
pure_fp16=pure_fp16,
|
|
)
|
|
|
|
# recompute second block
|
|
loss, param, grad = run_model(
|
|
recompute_block=[1],
|
|
enable_autocast=enable_autocast,
|
|
pure_fp16=pure_fp16,
|
|
)
|
|
check_identical(loss_ref, param_ref, grad_ref, loss, param, grad)
|
|
|
|
# recompute fourth block
|
|
loss, param, grad = run_model(
|
|
recompute_block=[3],
|
|
enable_autocast=enable_autocast,
|
|
pure_fp16=pure_fp16,
|
|
)
|
|
check_identical(loss_ref, param_ref, grad_ref, loss, param, grad)
|
|
|
|
# recompute second to fourth block
|
|
loss, param, grad = run_model(
|
|
recompute_block=[1, 2, 3],
|
|
enable_autocast=enable_autocast,
|
|
pure_fp16=pure_fp16,
|
|
)
|
|
check_identical(loss_ref, param_ref, grad_ref, loss, param, grad)
|
|
|
|
# recompute second & fourth block
|
|
loss, param, grad = run_model(
|
|
recompute_block=[1, 3],
|
|
enable_autocast=enable_autocast,
|
|
pure_fp16=pure_fp16,
|
|
)
|
|
check_identical(loss_ref, param_ref, grad_ref, loss, param, grad)
|
|
|
|
# recompute second & fourth block using fleet
|
|
loss, param, grad = run_model(
|
|
recompute_block=[1, 3],
|
|
enable_autocast=enable_autocast,
|
|
pure_fp16=pure_fp16,
|
|
)
|
|
check_identical(loss_ref, param_ref, grad_ref, loss, param, grad)
|
|
|
|
# recompute using recompute_sequential, segments=1
|
|
loss, param, grad = run_model(
|
|
recompute_block=[],
|
|
use_fleet_sq=True,
|
|
enable_autocast=enable_autocast,
|
|
pure_fp16=pure_fp16,
|
|
)
|
|
check_identical(loss_ref, param_ref, grad_ref, loss, param, grad)
|
|
|
|
# with base recompute, and segments=2
|
|
loss_ref, param_ref, grad_ref = run_model(
|
|
recompute_block=[],
|
|
enable_autocast=enable_autocast,
|
|
use_raw_recompute=True,
|
|
pure_fp16=pure_fp16,
|
|
)
|
|
|
|
# recompute using recompute_sequential, segments=2
|
|
loss, param, grad = run_model(
|
|
recompute_block=[],
|
|
use_fleet_sq=True,
|
|
segments=2,
|
|
enable_autocast=enable_autocast,
|
|
pure_fp16=pure_fp16,
|
|
)
|
|
check_identical(loss_ref, param_ref, grad_ref, loss, param, grad)
|
|
|
|
def test_fc_net_with_dropout(self):
|
|
self.test_base_case()
|
|
|
|
def test_fc_net_with_amp(self):
|
|
self.test_base_case(enable_autocast=True)
|
|
|
|
def test_fc_net_with_fp16(self):
|
|
self.test_base_case(enable_autocast=True, pure_fp16=True)
|
|
|
|
def test_recompute_kwargs(self):
|
|
paddle.set_device(
|
|
"xpu" if paddle.base.core.is_compiled_with_xpu() else "gpu"
|
|
)
|
|
kwargs = {"is_test": False}
|
|
with self.assertRaises(TypeError):
|
|
loss_ref, param_ref, grad_ref = run_model(
|
|
recompute_block=[2], recompute_kwargs=kwargs
|
|
)
|
|
|
|
# Paddle did not throw RuntimeError anymore
|
|
# def test_recompute_cpu_rng(self):
|
|
# paddle.set_device("cpu")
|
|
# with self.assertRaises(RuntimeError):
|
|
# loss_ref, param_ref, grad_ref = run_model(recompute_block=[2])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|