Files
2026-07-13 12:40:42 +08:00

541 lines
15 KiB
Python

# Copyright (c) 2019 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 unittest
import numpy as np
from dygraph_to_static_utils import (
Dy2StTestBase,
enable_to_static_guard,
test_ast_only,
)
import paddle
import paddle.jit.dy2static as _jst
from paddle.jit.dy2static.utils import TransformOptions, func_to_source_code
SEED = 2020
np.random.seed(SEED)
# Situation 1 : test recursive call
# Use a decorator to test exception
@paddle.jit.to_static(full_graph=True)
def dyfunc_with_if(x_v):
if paddle.mean(x_v).numpy() > 5:
x_v = x_v - 1
else:
x_v = x_v + 1
return x_v
@paddle.jit.to_static(full_graph=True)
def nested_func(x_v):
x_v = paddle.assign(x_v)
def fn1():
return x_v
res = fn1()
return res
@paddle.jit.to_static(full_graph=True)
def dyfunc_with_third_library_logging(x_v):
logging.info('test dyfunc_with_third_library_logging')
if paddle.mean(x_v).numpy() > 5:
x_v = x_v - 1
else:
x_v = x_v + 1
return x_v
class A:
@staticmethod
def add(a, b):
"""
dygraph mode, return a numpy object.
static graph mode, return a variable object.
"""
return paddle.to_tensor(a.numpy() + b.numpy())
@paddle.jit.to_static(full_graph=True)
def dyfunc_with_staticmethod(x_v):
a = A()
return a.add(x_v, x_v)
class TestRecursiveCall1(Dy2StTestBase):
def setUp(self):
self.input = np.random.random([10, 16]).astype('float32')
def init_test_func(self):
self.dyfunc = nested_func
def get_dygraph_output(self):
with enable_to_static_guard(False):
res = self.dyfunc(self.input).numpy()
return res
def get_static_output(self):
with enable_to_static_guard(True):
res = self.dyfunc(self.input).numpy()
return res
def test_transformed_static_result(self):
self.init_test_func()
static_res = self.get_static_output()
dygraph_res = self.get_dygraph_output()
np.testing.assert_allclose(
dygraph_res,
static_res,
rtol=1e-05,
err_msg=f'dygraph res is {dygraph_res}\nstatic_res is {static_res}',
)
lambda_fun = lambda x: x
class MyConvLayer(paddle.nn.Layer):
def __init__(self):
super().__init__()
self._conv = paddle.nn.Conv2D(
in_channels=3,
out_channels=2,
kernel_size=3,
weight_attr=paddle.ParamAttr(
initializer=paddle.nn.initializer.Constant(value=0.99)
),
bias_attr=paddle.ParamAttr(
initializer=paddle.nn.initializer.Constant(value=0.5)
),
)
@paddle.jit.to_static(full_graph=True)
def forward(self, inputs):
y = dyfunc_with_if(inputs)
y = lambda_fun(y)
y = self.dymethod(y)
return y
@paddle.jit.to_static(full_graph=True)
def dymethod(self, x_v):
x_v = paddle.assign(x_v)
return x_v
class MyLayer(paddle.nn.Layer):
def __init__(self):
super().__init__()
self.conv = MyConvLayer()
self.fc = paddle.nn.Linear(
in_features=5,
out_features=1,
weight_attr=paddle.ParamAttr(
initializer=paddle.nn.initializer.Constant(value=0.99)
),
bias_attr=paddle.ParamAttr(
initializer=paddle.nn.initializer.Constant(value=0.5)
),
)
self.act = paddle.nn.ReLU()
@paddle.jit.to_static(full_graph=True)
def forward(self, inputs):
h = self.conv(inputs)
out = self.fc(h)
return self.act(out)
class TestRecursiveCall2(Dy2StTestBase):
def setUp(self):
self.input = np.random.random((1, 3, 3, 5)).astype('float32')
def set_func(self):
self.dygraph_func = MyLayer()
def _run(self):
data = paddle.to_tensor(self.input)
res = self.dygraph_func(data)
return res.numpy()
def get_dygraph_output(self):
with enable_to_static_guard(False):
return self._run()
def get_static_output(self):
with enable_to_static_guard(True):
return self._run()
def test_transformed_static_result(self):
self.set_func()
dygraph_res = self.get_dygraph_output()
static_res = self.get_static_output()
np.testing.assert_allclose(dygraph_res, static_res, rtol=1e-05)
class TestThirdPartyLibrary(TestRecursiveCall2):
def set_func(self):
self.dygraph_func = dyfunc_with_third_library_logging
class TestStaticMethod(TestRecursiveCall2):
def set_func(self):
self.dygraph_func = dyfunc_with_staticmethod
# Situation 2 : test not_to_static
class NotToStaticHelper(paddle.nn.Layer):
def __init__(self):
super().__init__()
def sum(self, x):
if x.shape[0] > 1:
res = x + 1
res = paddle.sum(x)
return res
def outer(self, x):
res = self.sum(x)
return res
def inner(self, x):
return self.outer(x)
class TestNotToConvert(TestRecursiveCall2):
def set_func(self):
self.net = NotToStaticHelper()
# Apply the `not_to_static` decorator to `self.net.sum`.
paddle.jit.not_to_static()(self.net.sum)
self.dygraph_func = paddle.jit.to_static(self.net.outer)
def test_transform_options(self):
self.set_func()
self.assertTrue(
not TransformOptions.check_fn_need_transform(
self.net.sum, TransformOptions.ToStaticMode.AST
)
)
self.assertTrue(
TransformOptions.check_fn_need_transform(
self.net.sum, TransformOptions.ToStaticMode.SOT
)
)
def test_code(self):
self.set_func()
# check 'if statement' is not converted
self.assertIn(
"if x.shape[0] > 1", func_to_source_code(_jst.Call(self.net.sum))
)
class TestNotToConvert2(TestRecursiveCall2):
def set_func(self):
self.net = NotToStaticHelper()
# for to_static(not_to_static(function)) == enable_static
paddle.jit.not_to_static(self.net.sum)
self.dygraph_func = paddle.jit.to_static(self.net.sum)
def test_transform_options(self):
self.set_func()
self.assertTrue(
not TransformOptions.check_fn_need_transform(
self.net.sum, TransformOptions.ToStaticMode.AST
)
)
self.assertTrue(
TransformOptions.check_fn_need_transform(
self.net.sum, TransformOptions.ToStaticMode.SOT
)
)
@test_ast_only
def test_code(self):
self.set_func()
self.dygraph_func = paddle.jit.to_static(self.net.sum)
# check 'if statement' is not converted
self.assertIn("if x.shape[0] > 1", self.dygraph_func.code)
# Situation 3 : test to_static for paddle api
@paddle.jit.not_to_static
def forward(self, x):
if x.shape[0] > 1:
x = x + 1
return x
class TestConvertPaddleAPI(Dy2StTestBase):
@test_ast_only
def test_functional_api(self):
func = paddle.nn.functional.relu
func = paddle.jit.to_static(func)
self.assertNotIn("_jst.IfElse", func.code)
self.assertIn("if in_dynamic_or_pir_mode()", func.code)
@test_ast_only
def test_class_api(self):
bn = paddle.nn.SyncBatchNorm(2)
paddle.jit.to_static(bn)
self.assertNotIn("_jst.IfElse", bn.forward.code)
self.assertIn("if in_dynamic_or_pir_mode()", bn.forward.code)
@test_ast_only
def test_class_patch_api(self):
paddle.nn.SyncBatchNorm.forward = forward
bn = paddle.nn.SyncBatchNorm(2)
paddle.jit.to_static(bn)
self.assertNotIn("_jst.IfElse", bn.forward.code)
self.assertIn("if x.shape[0] > 1", bn.forward.code)
class TestMarkerUnified(Dy2StTestBase):
def test_plain_function(self):
def fn(x):
return x
self.assertTrue(
TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.SOT
)
)
self.assertTrue(
TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.AST
)
)
def test_decorator_skip_sot_only(self):
@paddle.jit.marker.unified(for_sot=True, for_ast=False)
def fn(x):
return x
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.SOT
)
)
self.assertTrue(
TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.AST
)
)
def test_decorator_skip_ast_only(self):
@paddle.jit.marker.unified(for_sot=False, for_ast=True)
def fn(x):
return x
self.assertTrue(
TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.SOT
)
)
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.AST
)
)
def test_decorator_skip_ast_and_sot(self):
@paddle.jit.marker.unified(for_sot=True, for_ast=True)
def fn(x):
return x
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.SOT
)
)
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.AST
)
)
def test_decorator_no_arg(self):
@paddle.jit.marker.unified
def fn(x):
return x
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.SOT
)
)
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.AST
)
)
def test_function_call_skip_sot_only(self):
def fn(x):
return x
paddle.jit.marker.unified(fn, for_sot=True, for_ast=False)
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.SOT
)
)
self.assertTrue(
TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.AST
)
)
def test_function_call_skip_ast_only(self):
def fn(x):
return x
paddle.jit.marker.unified(fn, for_sot=False, for_ast=True)
self.assertTrue(
TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.SOT
)
)
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.AST
)
)
def test_function_call_skip_ast_and_sot(self):
def fn(x):
return x
paddle.jit.marker.unified(fn, for_sot=True, for_ast=True)
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.SOT
)
)
self.assertTrue(
not TransformOptions.check_fn_need_transform(
fn, TransformOptions.ToStaticMode.AST
)
)
def test_nn_layer_subclass_skip_sot_only(self):
@paddle.jit.marker.unified(for_sot=True, for_ast=False)
class MyLayer(paddle.nn.Layer):
def __init__(self):
super().__init__()
self.w = paddle.create_parameter(shape=[1], dtype='float32')
def forward(self, x):
return x * self.w
self.assertFalse(
TransformOptions.check_fn_need_transform(
MyLayer(), TransformOptions.ToStaticMode.SOT
)
)
self.assertTrue(
TransformOptions.check_fn_need_transform(
MyLayer(), TransformOptions.ToStaticMode.AST
)
)
def test_nn_layer_subclass_skip_ast_only(self):
@paddle.jit.marker.unified(for_sot=False, for_ast=True)
class MyLayer(paddle.nn.Layer):
def __init__(self):
super().__init__()
self.w = paddle.create_parameter(shape=[1], dtype='float32')
def forward(self, x):
return x * self.w
self.assertTrue(
TransformOptions.check_fn_need_transform(
MyLayer(), TransformOptions.ToStaticMode.SOT
)
)
self.assertFalse(
TransformOptions.check_fn_need_transform(
MyLayer(), TransformOptions.ToStaticMode.AST
)
)
def test_nn_layer_subclass_skip_ast_and_sot(self):
@paddle.jit.marker.unified()
class MyLayer(paddle.nn.Layer):
def __init__(self):
super().__init__()
self.w = paddle.create_parameter(shape=[1], dtype='float32')
def forward(self, x):
return x * self.w
self.assertFalse(
TransformOptions.check_fn_need_transform(
MyLayer(), TransformOptions.ToStaticMode.SOT
)
)
self.assertFalse(
TransformOptions.check_fn_need_transform(
MyLayer(), TransformOptions.ToStaticMode.AST
)
)
class TestCaptureControlFlow(Dy2StTestBase):
def test_decorator(self):
def fn1(x):
return x
self.assertTrue(
not TransformOptions.check_fn_need_capture_control_flow(fn1)
)
@paddle.jit.marker.capture_control_flow()
def fn2(x):
return x
self.assertTrue(
TransformOptions.check_fn_need_capture_control_flow(fn2)
)
def test_decorator_no_arg(self):
def fn(x):
return x
self.assertTrue(
not TransformOptions.check_fn_need_capture_control_flow(fn)
)
fn = paddle.jit.marker.capture_control_flow(fn)
self.assertTrue(TransformOptions.check_fn_need_capture_control_flow(fn))
if __name__ == '__main__':
unittest.main()