Files
paddlepaddle--paddle/test/dygraph_to_static/test_pylayer.py
T
2026-07-13 12:40:42 +08:00

913 lines
27 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.
"""Tests for PyLayer of Dynamic-to-Static.
Only test simple cases here."""
import sys
from pathlib import Path
from dygraph_to_static_utils import enable_to_static_guard
sys.path.append(
str(Path(__file__).absolute().parent.parent.joinpath("legacy_test"))
)
import os
import tempfile
import unittest
import numpy as np
from test_jit_save_load import train
import paddle
from paddle.autograd.py_layer import PyLayer
from paddle.jit.dy2static.utils import ENV_ENABLE_CINN_IN_DY2ST
SEED = 2023
np.random.seed(SEED)
ENV_ENABLE_CINN_IN_DY2ST.set(False)
def compare_result(dygraph_res, static_res, rtol=1e-5, atol=0):
np.testing.assert_allclose(
dygraph_res.detach().numpy(),
static_res.detach().numpy(),
rtol=rtol,
atol=atol,
err_msg=f'dygraph result is {dygraph_res}\nstatic_result is {static_res}',
)
class scaled_layer_1(PyLayer):
@staticmethod
def forward(ctx, x):
y = x * 3
return y
@staticmethod
def backward(ctx, dy):
dx = paddle.sin(dy)
return dx
class scaled_layer_2(PyLayer):
@staticmethod
def forward(ctx, x1, x2):
y = 3 * x1 + x2 / 5
return y
@staticmethod
def backward(ctx, dy):
dx1 = paddle.sin(dy)
dx2 = paddle.cos(dy)
return dx1, dx2
class cus_tanh_1(PyLayer):
@staticmethod
def forward(ctx, x):
y = paddle.tanh(x)
ctx.save_for_backward(y)
return y
@staticmethod
def backward(ctx, dy):
(y,) = ctx.saved_tensor()
grad = dy * (1 - paddle.square(y))
return grad
class cus_tanh_2(PyLayer):
@staticmethod
def forward(ctx, x, func1, func2=paddle.square):
ctx.func = func2
y = func1(x)
ctx.save_for_backward(y)
return y
@staticmethod
def backward(ctx, dy):
(y,) = ctx.saved_tensor()
grad = dy * (1 - ctx.func(y))
return grad
class cus_tanh_3(PyLayer):
@staticmethod
def forward(ctx, x1, x2, func1, func2=paddle.square):
y1 = func1(x1)
y2 = func1(x2)
ctx.save_for_backward(y1, y2)
return 1, None, y1, y2, ''
@staticmethod
def backward(ctx, dy1, dy2):
y1, y2 = ctx.saved_tensor()
re1 = dy1 * (1 - paddle.square(y1))
re2 = dy2 * (1 - paddle.square(y2))
return re1, None
def user_defined_tanh(x):
y = paddle.tanh(x)
return y
def user_defined_square(x):
y = paddle.square(x)
return y
class cus_tanh_4(PyLayer):
@staticmethod
def forward(ctx, x, func, name="cus_tanh_4"):
ctx.func = func
y = user_defined_tanh(x)
ctx.save_for_backward(y)
return y
@staticmethod
def backward(ctx, dy):
(y,) = ctx.saved_tensor()
grad = dy * (1 - ctx.func(y))
return grad
class cus_tanh_5(PyLayer):
@staticmethod
def forward(ctx, x1, x2, func1, func2=paddle.square):
ctx.func = func2
y1 = func1(x1)
y2 = func1(x2)
ctx.save_for_backward(y1, y2)
return 1, None, y1, y2, ''
@staticmethod
def backward(ctx, dy1, dy2):
y1, y2 = ctx.saved_tensor()
re1 = dy1 * (1 - ctx.func(y1))
re2 = dy2 * (1 - paddle.square(y2))
return re1, re2
class cus_sigmoid(PyLayer):
@staticmethod
def forward(ctx, x, func1, func2):
ctx.func = func2
y = 1 / (1 + func1(-x))
ctx.save_for_backward(x)
return y
@staticmethod
def backward(ctx, dy):
(x,) = ctx.saved_tensor()
grad = dy * ctx.func(x) * (1 - ctx.func(x))
return grad
class nested_layer(PyLayer):
@staticmethod
def forward(ctx, x1, x2):
y = cus_tanh_1.apply(x1)
ctx.save_for_backward(y)
ret = y + x2
return ret
@staticmethod
def backward(ctx, dy):
(y,) = ctx.saved_tensor()
grad1 = scaled_layer_1.apply(dy)
grad2 = dy - paddle.square(y)
return grad1, grad2
class SimpleNet_1(paddle.nn.Layer):
def __init__(self, in_size, out_size):
super().__init__()
self.linear = paddle.nn.Linear(in_size, out_size)
@paddle.jit.to_static(full_graph=True)
def forward(self, data):
hidden = self.linear(data)
z = cus_tanh_1.apply(hidden)
return z
class SimpleNet_2(paddle.nn.Layer):
def __init__(self, in_size, out_size):
super().__init__()
self.linear = paddle.nn.Linear(in_size, out_size)
def forward(self, x):
y = self.linear(x)
out = cus_tanh_2.apply(y, func1=paddle.tanh)
return out
class SimpleNet_3(paddle.nn.Layer):
def __init__(self, in_size, out_size):
super().__init__()
self.linear = paddle.nn.Linear(in_size, out_size)
def forward(self, x):
y = self.linear(x)
out = cus_sigmoid.apply(
y, func1=paddle.exp, func2=paddle.nn.functional.sigmoid
)
return out
class SimpleNetInplace(paddle.nn.Layer):
def __init__(self):
super().__init__()
@paddle.jit.to_static(full_graph=True)
def forward(self, data):
data = data**2
z = paddle.tanh(data)
z = cus_tanh_1.apply(z)
return z
class SimplePyLayerNet(paddle.nn.Layer):
def __init__(self, in_size, out_size):
super().__init__()
self.linear = paddle.nn.Linear(in_size, out_size)
@paddle.jit.to_static(full_graph=True)
def forward(self, x):
y = self.linear(x)
out = cus_tanh_2.apply(y, func1=paddle.tanh)
out = paddle.mean(out)
return out
class SimplePyLayerNetMultiIn(paddle.nn.Layer):
def __init__(self, in_size, out_size):
super().__init__()
self.linear1 = paddle.nn.Linear(in_size, out_size)
self.linear2 = paddle.nn.Linear(in_size, out_size)
@paddle.jit.to_static(full_graph=True)
def forward(self, x1, x2):
y1 = self.linear1(x1)
y2 = self.linear1(x2)
out = cus_tanh_2.apply(y1, paddle.tanh)
out = out + y2
out = paddle.mean(out)
return out
class SimplePyLayerNetStopGrad(paddle.nn.Layer):
def __init__(self, in_size, out_size):
super().__init__()
self.linear = paddle.nn.Linear(in_size, out_size)
def forward(self, x):
y = self.linear(x)
y.stop_gradient = True
out = cus_tanh_2.apply(y, func1=paddle.tanh)
return out
class TestPyLayerBase(unittest.TestCase):
def setUp(self):
self.place = "cpu"
if paddle.is_compiled_with_cuda():
self.place = "gpu"
if paddle.is_compiled_with_xpu():
self.place = "xpu"
self.to_static: bool = False
def _run(self, *input_args, **input_kwargs):
assert getattr(self, "dygraph_func", None), (
"Please setting `self.dygraph_func` before calling `self._run`"
)
with enable_to_static_guard(self.to_static):
paddle.set_device(self.place)
result = self.dygraph_func(*input_args, **input_kwargs)
result.mean().backward()
return result
def _run_dygraph(self, *args, **kwargs):
self.to_static = False
return self._run(*args, **kwargs)
def _run_static(self, *args, **kwargs):
self.to_static = True
fn = self._run
return fn(*args, **kwargs)
# TODO(MarioLulab): In the future, this will be supported: not only `paddle.Tensor`
# but also non-Tensor objects will be included in the argument list.
def _run_and_compare(self, *args, **kwargs):
# Step1. Clone args and kwargs to avoid dygraph and static overwriting with each other
dygraph_inp_args = []
static_inp_args = []
for v in args:
assert isinstance(v, paddle.Tensor), (
f"Only Support `paddle.Tensor` now, but got {type(v)}"
)
stop_gradient = v.stop_gradient
# detach from the compute graph to turn `dygraph_inp_args` and `static_inp_args` into leaf nodes
v = v.detach()
dygraph_inp_args.append(v.clone())
static_inp_args.append(v.clone())
if not stop_gradient:
dygraph_inp_args[-1].stop_gradient = False
static_inp_args[-1].stop_gradient = False
dygraph_inp_kwargs = {}
static_inp_kwargs = {}
for k, v in kwargs.items():
stop_gradient = v.stop_gradient
assert isinstance(v, paddle.Tensor), (
"Only Support `paddle.Tensor` now"
)
# detach from the compute graph to turn `dygraph_inp_kwargs` and `static_inp_kwargs` into leaf nodes
v = v.detach()
dygraph_inp_kwargs[k] = v.clone()
static_inp_kwargs[k] = v.clone()
if not stop_gradient:
dygraph_inp_kwargs[k].stop_gradient = False
static_inp_kwargs[k].stop_gradient = False
# Step2. Run the dygraph and the static separately
dygraph_res = self._run_dygraph(*dygraph_inp_args, **dygraph_inp_kwargs)
static_res = self._run_static(*static_inp_args, **static_inp_kwargs)
# Step3. Compare forward result between dygraph and static
if not isinstance(dygraph_res, tuple):
dygraph_res = (dygraph_res,)
if not isinstance(static_res, tuple):
static_res = (static_res,)
for d, s in zip(dygraph_res, static_res):
compare_result(d, s)
# Step4. Compare grad between dygraph and static
for i in range(len(dygraph_inp_args)):
self.assertEqual(
dygraph_inp_args[i].stop_gradient,
static_inp_args[i].stop_gradient,
)
if dygraph_inp_args[i].stop_gradient:
continue
compare_result(dygraph_inp_args[i].grad, static_inp_args[i].grad)
for key in dygraph_inp_kwargs.keys():
self.assertEqual(
dygraph_inp_kwargs[key].stop_gradient,
static_inp_kwargs[key].stop_gradient,
)
if dygraph_inp_kwargs[key].stop_gradient:
continue
compare_result(
dygraph_inp_kwargs[key].grad, static_inp_kwargs[key].grad
)
class TestPyLayerWithoutContext(TestPyLayerBase):
def test_single_in_single_out(self):
@paddle.jit.to_static(full_graph=True)
def test_func(x):
y = scaled_layer_1.apply(x)
return y
self.dygraph_func = test_func
input1 = paddle.randn([2, 3]).astype("float32")
input1.stop_gradient = False
self._run_and_compare(input1)
def test_multi_in_single_out(self):
@paddle.jit.to_static(full_graph=True)
def test_func(x1, x2):
y = scaled_layer_2.apply(x1, x2)
return y
self.dygraph_func = test_func
input1 = paddle.randn([2, 3]).astype("float32")
input2 = paddle.randn([2, 3]).astype("float32")
input1.stop_gradient = False
input2.stop_gradient = False
self._run_and_compare(input1, input2)
class TestPyLayerWithContext(TestPyLayerBase):
def test_single_in_single_out(self):
@paddle.jit.to_static(full_graph=True)
def test_func(x):
y = cus_tanh_1.apply(x)
return y
self.dygraph_func = test_func
input1 = paddle.randn([2, 3]).astype("float32")
input1.stop_gradient = False
self._run_and_compare(input1)
def test_nested_pylayer(self):
@paddle.jit.to_static(full_graph=True)
def test_func(x1, x2):
y = nested_layer.apply(x1, x2)
return y
self.dygraph_func = test_func
input1 = paddle.randn([2, 3]).astype("float32")
input2 = paddle.randn([2, 3]).astype("float32")
input1.stop_gradient = False
input2.stop_gradient = False
self._run_and_compare(input1, input2)
def test_apply_kwargs_pylayer(self):
@paddle.jit.to_static(full_graph=True)
def test_func(x1, x2):
y = scaled_layer_2.apply(x1=x2, x2=x1)
return y
self.dygraph_func = test_func
input1 = paddle.randn([2, 3]).astype("float32")
input2 = paddle.randn([2, 3]).astype("float32")
input1.stop_gradient = False
input2.stop_gradient = False
self._run_and_compare(input1, input2)
def test_non_variable_inputs(self):
@paddle.jit.to_static(full_graph=True)
def test_func(x):
y = cus_tanh_2.apply(x, func1=paddle.tanh)
return y
self.dygraph_func = test_func
input1 = paddle.randn([2, 3]).astype("float32")
input1.stop_gradient = False
self._run_and_compare(input1)
def test_simple_pylayer_return_none_with_no_grad(self):
@paddle.jit.to_static(full_graph=True)
def test_func(input1, input2):
z = cus_tanh_3.apply(input1, input2, paddle.tanh, paddle.square)
z = z[2] + z[3]
return z
self.dygraph_func = test_func
input1 = paddle.randn([2, 3]).astype("float32")
input2 = paddle.randn([2, 3]).astype("float32")
input1.stop_gradient = False
input2.stop_gradient = True
self._run_and_compare(input1, input2)
def test_simple_pylayer_return_none(self):
@paddle.jit.to_static(full_graph=True)
def test_func(input1, input2):
z = cus_tanh_5.apply(input1, input2, paddle.tanh, paddle.square)
z = z[2] + z[3]
return z
self.dygraph_func = test_func
input1 = paddle.randn([2, 3]).astype("float32")
input2 = paddle.randn([2, 3]).astype("float32")
input1.stop_gradient = False
input2.stop_gradient = False
self._run_and_compare(input1, input2)
def test_non_variable_inputs_and_userdefined_call(self):
@paddle.jit.to_static(full_graph=True)
def test_func(input1):
y = cus_tanh_4.apply(
input1, func=user_defined_square, name="cus_tanh_test"
)
return y
self.dygraph_func = test_func
input1 = paddle.randn([2, 3]).astype("float32")
input1.stop_gradient = False
self._run_and_compare(input1)
class TestPyLayerInsideNet(TestPyLayerBase):
def test_single_in_single_out(self):
simple_net = SimpleNet_1(in_size=4, out_size=8)
self.dygraph_func = simple_net
input1 = paddle.randn([3, 4]).astype("float32")
input1.stop_gradient = False
self._run_and_compare(input1)
def test_inplace(self):
simple_net = SimpleNetInplace()
self.dygraph_func = simple_net
input1 = paddle.randn([3, 4]).astype("float32")
input1.stop_gradient = False
self._run_and_compare(input1)
def test_non_variable_args_pylayernet(self):
simple_net = SimplePyLayerNet(in_size=4, out_size=8)
self.dygraph_func = simple_net
input1 = paddle.randn([3, 4]).astype("float32")
input1.stop_gradient = False
self._run_and_compare(input1)
def test_pylayer_net_with_no_grad(self):
simple_net = SimplePyLayerNetMultiIn(in_size=4, out_size=8)
self.dygraph_func = simple_net
input1 = paddle.randn([3, 4]).astype("float32")
input2 = paddle.randn([3, 4]).astype("float32")
input1.stop_gradient = False
input2.stop_gradient = True
self._run_and_compare(input1, input2)
class PyLayerTrainHelper(unittest.TestCase):
def setUp(self):
self.place = "cpu"
if paddle.is_compiled_with_cuda():
self.place = "gpu"
if paddle.is_compiled_with_xpu():
self.place = "xpu"
def _run_train(
self, to_static: bool, layer_builder, build_strategy=None, in_pir=True
):
"""
Tests model decorated by `dygraph_to_static_output` in static graph mode. For users, the model is defined in dygraph mode and trained in static graph mode.
"""
paddle.set_device(self.place)
np.random.seed(SEED)
paddle.seed(SEED)
paddle.framework.random._manual_program_seed(SEED)
net = layer_builder()
if to_static:
net = paddle.jit.to_static(
net, build_strategy=build_strategy, full_graph=True
)
_, _, avg_loss = train(net)
return avg_loss.numpy()
class TestTrainingPyLayer(PyLayerTrainHelper):
def test_tanh_pylayer(self):
build_layer = lambda: SimpleNet_2(784, 20)
legacy_static_loss = self._run_train(
to_static=True, in_pir=False, layer_builder=build_layer
)
pir_static_loss = self._run_train(
to_static=True, in_pir=True, layer_builder=build_layer
)
dygraph_loss = self._run_train(
to_static=False, layer_builder=build_layer
)
np.testing.assert_allclose(
legacy_static_loss,
dygraph_loss,
rtol=1e-05,
err_msg=f'legacy_static_loss: {legacy_static_loss} \n dygraph_loss: {dygraph_loss}',
)
np.testing.assert_allclose(
pir_static_loss,
dygraph_loss,
rtol=1e-05,
err_msg=f'pir_static_loss: {pir_static_loss} \n dygraph_loss: {dygraph_loss}',
)
def test_sigmoid_pylayer(self):
build_layer = lambda: SimpleNet_3(784, 20)
legacy_static_loss = self._run_train(
to_static=True, in_pir=False, layer_builder=build_layer
)
pir_static_loss = self._run_train(
to_static=True, in_pir=True, layer_builder=build_layer
)
dygraph_loss = self._run_train(
to_static=False, layer_builder=build_layer
)
np.testing.assert_allclose(
legacy_static_loss,
dygraph_loss,
rtol=1e-05,
err_msg=f'legacy_static_loss: {legacy_static_loss} \n dygraph_loss: {dygraph_loss}',
)
np.testing.assert_allclose(
pir_static_loss,
dygraph_loss,
rtol=1e-05,
err_msg=f'pir_static_loss: {pir_static_loss} \n dygraph_loss: {dygraph_loss}',
)
def test_pylayer_net_no_grad(self):
build_layer = lambda: SimplePyLayerNetStopGrad(784, 20)
legacy_static_loss = self._run_train(
to_static=True, in_pir=False, layer_builder=build_layer
)
pir_static_loss = self._run_train(
to_static=True, in_pir=True, layer_builder=build_layer
)
dygraph_loss = self._run_train(
to_static=False, layer_builder=build_layer
)
np.testing.assert_allclose(
legacy_static_loss,
dygraph_loss,
rtol=1e-05,
err_msg=f'legacy_static_loss: {legacy_static_loss} \n dygraph_loss: {dygraph_loss}',
)
np.testing.assert_allclose(
pir_static_loss,
dygraph_loss,
rtol=1e-05,
err_msg=f'pir_static_loss: {pir_static_loss} \n dygraph_loss: {dygraph_loss}',
)
class TestPyLayerJitSaveLoad(unittest.TestCase):
def setUp(self):
self.temp_dir = tempfile.TemporaryDirectory()
self.model_path = os.path.join(
self.temp_dir.name, "test_pylayer/jit_save_model"
)
# enable dygraph mode
paddle.base.enable_dygraph()
# config seed
paddle.seed(SEED)
paddle.framework.random._manual_program_seed(SEED)
def tearDown(self):
self.temp_dir.cleanup()
def train_and_save_model(self, model_path=None):
layer = SimpleNet_1(784, 20)
example_inputs, layer, _ = train(layer)
final_model_path = model_path if model_path else self.model_path
orig_input_types = [type(x) for x in example_inputs]
paddle.jit.save(
layer=layer, path=final_model_path, input_spec=example_inputs
)
new_input_types = [type(x) for x in example_inputs]
self.assertEqual(orig_input_types, new_input_types)
return layer
def test_save_load(self):
# train and save model
train_layer = self.train_and_save_model()
# load model
loaded_layer = paddle.jit.load(self.model_path)
self.load_and_inference(train_layer, loaded_layer)
def load_and_inference(self, train_layer, infer_layer):
train_layer.eval()
infer_layer.eval()
# inference & compare
x = paddle.to_tensor(np.random.random((1, 784)).astype('float32'))
train_layer_result = train_layer(x).numpy()
infer_layer_result = infer_layer(x).numpy()
np.testing.assert_array_equal(train_layer_result, infer_layer_result)
class PyLayerWrongUsage(PyLayer):
@staticmethod
def forward(ctx, x):
ctx.x = x
x1 = paddle.tanh(x)
return x1
@staticmethod
def backward(ctx, grad):
x = ctx.x
x_grad = grad * (1 - paddle.square(x))
return x_grad
class PyLayerWrongUsageWrapper(paddle.nn.Layer):
def __init__(self):
super().__init__()
self.layer = PyLayerWrongUsage()
def forward(self, x):
return PyLayerWrongUsage.apply(x)
class TestPyLayerWrongUsage(unittest.TestCase):
def test_wrong_usage(self):
layer = PyLayerWrongUsageWrapper()
static_layer = paddle.jit.to_static(layer, full_graph=True)
x = paddle.to_tensor(np.random.random((1, 784)).astype('float32'))
with self.assertRaisesRegex(
AttributeError,
r"`ctx.x = tensor` is not allowed in static mode, please use `ctx.save_for_backward\(tensor\)` instead.",
):
static_layer(x)
class NestedStructurePyLayer(PyLayer):
@staticmethod
def forward(ctx, x, y):
ctx.save_for_backward(x, y)
x1 = paddle.tanh(x[0])
y1 = paddle.tanh(x[1])
z1 = paddle.tanh(y)
return [x1, y1, z1]
@staticmethod
def backward(ctx, *grad1):
x0, x1 = ctx.saved_tensor()
x_grad = grad1[0] * (1 - paddle.square(x0[0]))
y_grad = grad1[1] * (1 - paddle.square(x0[1]))
z_grad = grad1[2] * (1 - paddle.square(x1))
return [x_grad, y_grad], z_grad
class NestedStructurePyLayerModel(paddle.nn.Layer):
def __init__(self):
super().__init__()
self.w0 = self.create_parameter(shape=[42, 42])
self.w1 = self.create_parameter(shape=[42, 42])
self.w2 = self.create_parameter(shape=[42, 42])
def forward(self, x):
y1 = paddle.matmul(x, self.w0)
y2 = paddle.matmul(x, self.w1)
y3 = paddle.matmul(x, self.w2)
z = NestedStructurePyLayer.apply([y1, y2], y3)
return z[0] + z[1] + z[2]
class TestNestedStructurePyLayer(unittest.TestCase):
def test_nested_structure(self):
input = paddle.randn([2, 42]).astype("float32")
input.stop_gradient = False
model = NestedStructurePyLayerModel()
dygraph_res = model(input)
dygraph_res.backward()
dygraph_input_grads = [
paddle.assign(input.grad),
paddle.assign(model.w0.grad),
paddle.assign(model.w1.grad),
paddle.assign(model.w2.grad),
]
input.clear_grad()
model.w0.clear_grad()
model.w1.clear_grad()
model.w2.clear_grad()
static_model = paddle.jit.to_static(model, full_graph=True)
static_res = static_model(input)
static_res.backward()
static_input_grads = [
paddle.assign(input.grad),
paddle.assign(model.w0.grad),
paddle.assign(model.w1.grad),
paddle.assign(model.w2.grad),
]
input.clear_grad()
model.w0.clear_grad()
model.w1.clear_grad()
model.w2.clear_grad()
for i, (dygraph_grad, static_grad) in enumerate(
zip(dygraph_input_grads, static_input_grads)
):
np.testing.assert_allclose(
dygraph_grad.numpy(),
static_grad.numpy(),
rtol=1e-5,
atol=0,
err_msg=f"dygraph_grad[{i}]: {dygraph_grad} \n static_grad[{i}]: {static_grad}",
)
class NestedStructureWithNonePyLayer(PyLayer):
@staticmethod
def forward(ctx, x, y):
ctx.save_for_backward(x, y)
x1 = paddle.tanh(x[0])
y1 = paddle.tanh(x[1])
z1 = paddle.tanh(y)
return [x1, y1, z1]
@staticmethod
def backward(ctx, *grad1):
x0, x1 = ctx.saved_tensor()
x_grad = grad1[0] * (1 - paddle.square(x0[0]))
z_grad = grad1[2] * (1 - paddle.square(x1))
return [x_grad, None], z_grad
class NestedStructureWithNonePyLayerModel(paddle.nn.Layer):
def __init__(self):
super().__init__()
self.w0 = self.create_parameter(shape=[42, 42])
self.w1 = self.create_parameter(shape=[42, 42])
self.w2 = self.create_parameter(shape=[42, 42])
def forward(self, x):
y1 = paddle.matmul(x, self.w0)
y2 = paddle.matmul(x, self.w1)
y2.stop_gradient = True
y3 = paddle.matmul(x, self.w2)
z = NestedStructurePyLayer.apply([y1, y2], y3)
return z[0] + z[1] + z[2]
class TestNestedStructureWithNonePyLayer(unittest.TestCase):
def test_nested_structure(self):
input = paddle.randn([2, 42]).astype("float32")
input.stop_gradient = False
model = NestedStructurePyLayerModel()
dygraph_res = model(input)
dygraph_res.backward()
dygraph_input_grads = [
paddle.assign(input.grad),
paddle.assign(model.w0.grad),
paddle.assign(model.w1.grad),
paddle.assign(model.w2.grad),
]
input.clear_grad()
model.w0.clear_grad()
model.w1.clear_grad()
model.w2.clear_grad()
static_model = paddle.jit.to_static(model, full_graph=True)
static_res = static_model(input)
static_res.backward()
static_input_grads = [
paddle.assign(input.grad),
paddle.assign(model.w0.grad),
paddle.assign(model.w1.grad),
paddle.assign(model.w2.grad),
]
input.clear_grad()
model.w0.clear_grad()
model.w1.clear_grad()
model.w2.clear_grad()
for i, (dygraph_grad, static_grad) in enumerate(
zip(dygraph_input_grads, static_input_grads)
):
np.testing.assert_allclose(
dygraph_grad.numpy(),
static_grad.numpy(),
rtol=1e-5,
atol=0,
err_msg=f"dygraph_grad[{i}]: {dygraph_grad} \n static_grad[{i}]: {static_grad}",
)
if __name__ == "__main__":
unittest.main()