446 lines
12 KiB
Python
446 lines
12 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.
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import inspect
|
|
import logging
|
|
import sys
|
|
import unittest
|
|
from contextlib import contextmanager
|
|
from enum import Flag, auto
|
|
from functools import wraps
|
|
from pathlib import Path
|
|
from typing import TypeAlias
|
|
|
|
import paddle
|
|
from paddle import set_flags
|
|
from paddle.jit.api import sot_mode_guard
|
|
from paddle.jit.dy2static.utils import (
|
|
ENV_ENABLE_CINN_IN_DY2ST,
|
|
cinn_is_available,
|
|
)
|
|
from paddle.jit.sot.opcode_translator.executor.executor_cache import (
|
|
OpcodeExecutorCache,
|
|
)
|
|
from paddle.jit.sot.utils.envs import min_graph_size_guard
|
|
from paddle.utils.environments import (
|
|
BooleanEnvironmentVariable,
|
|
EnvironmentVariableGuard,
|
|
)
|
|
|
|
"""
|
|
# Usage:
|
|
class MyTest(Dy2StTestBase):
|
|
@set_to_static_mode(
|
|
ToStaticMode.AST | ToStaticMode.SOT
|
|
)
|
|
@set_backend_mode(BackendMode.PHI | BackendMode.CINN)
|
|
def test_case1(self):
|
|
raise ValueError("MyTest 1")
|
|
|
|
def test_case2(self):
|
|
raise ValueError("MyTest 2")
|
|
|
|
|
|
class MyTest2(MyTest):
|
|
def test_case1(self):
|
|
raise ValueError("MyTest2 1")
|
|
"""
|
|
|
|
ENV_EXE_SEQUENTIAL_RUN = BooleanEnvironmentVariable(
|
|
"FLAGS_new_executor_sequential_run", False
|
|
)
|
|
ENV_ENABLE_DY2ST_UT_DEBUG_MODE = BooleanEnvironmentVariable(
|
|
"ENABLE_DY2ST_UT_DEBUG_MODE", False
|
|
)
|
|
|
|
logger = logging.getLogger("Dygraph to static utils")
|
|
logger.setLevel(
|
|
logging.WARNING
|
|
if not ENV_ENABLE_DY2ST_UT_DEBUG_MODE.get()
|
|
else logging.DEBUG
|
|
)
|
|
|
|
|
|
class ToStaticMode(Flag):
|
|
AST = auto()
|
|
SOT = auto()
|
|
# SOT with MIN_GRAPH_SIZE=10, we only test SOT_MGS10 + LEGACY_IR to avoid regression
|
|
SOT_MGS10 = auto()
|
|
|
|
def lower_case_name(self):
|
|
return self.name.lower()
|
|
|
|
|
|
class BackendMode(Flag):
|
|
PHI = auto()
|
|
CINN = auto()
|
|
|
|
def lower_case_name(self):
|
|
return self.name.lower()
|
|
|
|
|
|
ModeTuple: TypeAlias = tuple[ToStaticMode, BackendMode]
|
|
DEFAULT_TO_STATIC_MODE = (
|
|
ToStaticMode.AST | ToStaticMode.SOT | ToStaticMode.SOT_MGS10
|
|
)
|
|
DEFAULT_BACKEND_MODE = BackendMode.PHI | BackendMode.CINN
|
|
VALID_MODES = [
|
|
(ToStaticMode.AST, BackendMode.PHI),
|
|
(ToStaticMode.SOT, BackendMode.PHI),
|
|
(ToStaticMode.SOT_MGS10, BackendMode.PHI),
|
|
]
|
|
if cinn_is_available():
|
|
VALID_MODES.append(
|
|
(ToStaticMode.SOT, BackendMode.CINN),
|
|
) # For default mode, we test SOT+CINN
|
|
|
|
|
|
DISABLED_TO_STATIC_TEST_FILES = {
|
|
ToStaticMode.AST: [],
|
|
ToStaticMode.SOT: [],
|
|
ToStaticMode.SOT_MGS10: [],
|
|
}
|
|
DISABLED_BACKEND_TEST_FILES = {
|
|
BackendMode.PHI: [],
|
|
BackendMode.CINN: [],
|
|
}
|
|
|
|
|
|
def to_ast_test(fn):
|
|
"""
|
|
convert run AST
|
|
"""
|
|
|
|
@wraps(fn)
|
|
def ast_impl(*args, **kwargs):
|
|
logger.info("[AST] running AST")
|
|
with sot_mode_guard(False):
|
|
fn(*args, **kwargs)
|
|
|
|
return ast_impl
|
|
|
|
|
|
def to_sot_test(fn):
|
|
"""
|
|
convert run SOT
|
|
"""
|
|
|
|
@wraps(fn)
|
|
def sot_impl(*args, **kwargs):
|
|
logger.info("[SOT] running SOT (MIN_GRAPH_SIZE=0)")
|
|
|
|
OpcodeExecutorCache().clear()
|
|
with (
|
|
sot_mode_guard(True),
|
|
min_graph_size_guard(0),
|
|
):
|
|
fn(*args, **kwargs)
|
|
|
|
return sot_impl
|
|
|
|
|
|
def to_sot_mgs10_test(fn):
|
|
"""
|
|
convert run SOT and MIN_GRAPH_SIZE=10
|
|
"""
|
|
|
|
@wraps(fn)
|
|
def sot_mgs10_impl(*args, **kwargs):
|
|
logger.info("[SOT_MGS10] running SOT (MIN_GRAPH_SIZE=10)")
|
|
|
|
OpcodeExecutorCache().clear()
|
|
with (
|
|
sot_mode_guard(True),
|
|
min_graph_size_guard(10),
|
|
):
|
|
fn(*args, **kwargs)
|
|
|
|
return sot_mgs10_impl
|
|
|
|
|
|
def to_phi_test(fn):
|
|
@wraps(fn)
|
|
def phi_impl(*args, **kwargs):
|
|
logger.info("[PHI] running phi")
|
|
with EnvironmentVariableGuard(ENV_ENABLE_CINN_IN_DY2ST, False):
|
|
fn(*args, **kwargs)
|
|
|
|
return phi_impl
|
|
|
|
|
|
def to_cinn_test(fn):
|
|
@wraps(fn)
|
|
def cinn_impl(*args, **kwargs):
|
|
logger.info("[CINN] running cinn")
|
|
with EnvironmentVariableGuard(ENV_ENABLE_CINN_IN_DY2ST, True):
|
|
fn(*args, **kwargs)
|
|
|
|
return cinn_impl
|
|
|
|
|
|
# Metaclass and BaseClass
|
|
class Dy2StTestMeta(type):
|
|
TO_STATIC_HANDLER_MAP = {
|
|
ToStaticMode.AST: to_ast_test,
|
|
ToStaticMode.SOT: to_sot_test,
|
|
ToStaticMode.SOT_MGS10: to_sot_mgs10_test,
|
|
}
|
|
|
|
BACKEND_HANDLER_MAP = {
|
|
BackendMode.PHI: to_phi_test,
|
|
BackendMode.CINN: to_cinn_test,
|
|
}
|
|
|
|
def __new__(cls, name, bases, attrs):
|
|
module_name = attrs["__module__"]
|
|
filepath = sys.modules[module_name].__file__
|
|
assert filepath
|
|
filename = Path(filepath).stem
|
|
new_attrs = {}
|
|
original_test_cases = {
|
|
key: value
|
|
for key, value in attrs.items()
|
|
if key.startswith("test") and inspect.isfunction(value)
|
|
}
|
|
logger.info(f"[creating {name}]")
|
|
new_attrs.update(
|
|
{
|
|
key: value
|
|
for key, value in attrs.items()
|
|
if key not in original_test_cases
|
|
}
|
|
)
|
|
for fn_name, fn in original_test_cases.items():
|
|
logger.info(f"Generating {fn_name}")
|
|
fn_disabled_test_cases = getattr(fn, "disabled_test_cases", [])
|
|
logger.info(f"fn_disabled_test_cases: {fn_disabled_test_cases}")
|
|
# Disable inherited test cases
|
|
for base in bases:
|
|
for attr in dir(base):
|
|
if attr.startswith(f"{fn_name}__"):
|
|
new_attrs[attr] = None
|
|
# Get all valid test cases with to_static_mode and ir_mode
|
|
mode_tuples = Dy2StTestMeta.get_all_test_mode_tuples(fn)
|
|
|
|
# Filter out invalid test cases
|
|
mode_tuples = list(
|
|
filter(
|
|
# Filter out disabled test cases by decorator
|
|
lambda mode_tuple: (
|
|
not Dy2StTestMeta.is_disabled_by_attr(
|
|
fn_disabled_test_cases,
|
|
mode_tuple,
|
|
)
|
|
# Filter out disabled test cases by file
|
|
and not Dy2StTestMeta.is_disabled_by_file(
|
|
filename,
|
|
mode_tuple,
|
|
)
|
|
# Skip invalid test cases
|
|
and mode_tuple in VALID_MODES
|
|
),
|
|
mode_tuples,
|
|
)
|
|
)
|
|
# Generate all test cases
|
|
for mode_tuple in mode_tuples:
|
|
new_attrs[Dy2StTestMeta.test_case_name(fn_name, mode_tuple)] = (
|
|
Dy2StTestMeta.convert_test_case(fn, mode_tuple)
|
|
)
|
|
return type.__new__(cls, name, bases, new_attrs)
|
|
|
|
@staticmethod
|
|
def get_all_test_mode_tuples(fn):
|
|
fn_to_static_modes = getattr(
|
|
fn, "to_static_mode", DEFAULT_TO_STATIC_MODE
|
|
)
|
|
fn_backend_modes = getattr(fn, "backend_mode", DEFAULT_BACKEND_MODE)
|
|
logger.info(f"fn_to_static_modes: {fn_to_static_modes}")
|
|
logger.info(f"fn_backend_modes: {fn_backend_modes}")
|
|
return [
|
|
(to_static_mode, backend_mode)
|
|
for to_static_mode in ToStaticMode
|
|
for backend_mode in BackendMode
|
|
if to_static_mode & fn_to_static_modes
|
|
and backend_mode & fn_backend_modes
|
|
]
|
|
|
|
@staticmethod
|
|
def is_disabled_by_attr(
|
|
fn_disabled_test_cases: list[ModeTuple], mode_tuple: ModeTuple
|
|
):
|
|
to_static_mode, backend_mode = mode_tuple
|
|
for (
|
|
disabled_to_static_mode,
|
|
disabled_backend_mode,
|
|
) in fn_disabled_test_cases:
|
|
if (
|
|
to_static_mode & disabled_to_static_mode
|
|
and backend_mode & disabled_backend_mode
|
|
):
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def is_disabled_by_file(
|
|
filename: str,
|
|
mode_tuple: ModeTuple,
|
|
):
|
|
to_static_mode, backend_mode = mode_tuple
|
|
if (
|
|
filename in DISABLED_TO_STATIC_TEST_FILES[to_static_mode]
|
|
or filename in DISABLED_BACKEND_TEST_FILES[backend_mode]
|
|
):
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def test_case_name(original_name: str, mode_tuple: ModeTuple):
|
|
to_static_mode, backend_mode = mode_tuple
|
|
return f"{original_name}__{to_static_mode.lower_case_name()}_{backend_mode.lower_case_name()}"
|
|
|
|
@staticmethod
|
|
def convert_test_case(fn, mode_tuple: ModeTuple):
|
|
to_static_mode, backend_mode = mode_tuple
|
|
fn = Dy2StTestMeta.BACKEND_HANDLER_MAP[backend_mode](fn)
|
|
fn = Dy2StTestMeta.TO_STATIC_HANDLER_MAP[to_static_mode](fn)
|
|
return fn
|
|
|
|
|
|
class Dy2StTestBase(unittest.TestCase, metaclass=Dy2StTestMeta):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
# Base decorators
|
|
def set_to_static_mode(mode: ToStaticMode):
|
|
def decorator(fn):
|
|
fn.to_static_mode = mode
|
|
return fn
|
|
|
|
return decorator
|
|
|
|
|
|
def set_backend_mode(mode: BackendMode):
|
|
def decorator(fn):
|
|
fn.backend_mode = mode
|
|
return fn
|
|
|
|
return decorator
|
|
|
|
|
|
def disable_test_case(flags: tuple[ToStaticMode, BackendMode]):
|
|
def decorator(fn):
|
|
disabled_test_cases = getattr(fn, "disabled_test_cases", [])
|
|
disabled_test_cases.append(flags)
|
|
fn.disabled_test_cases = disabled_test_cases
|
|
return fn
|
|
|
|
return decorator
|
|
|
|
|
|
# Sugar decorators
|
|
# These decorators can be simply composed by base decorators
|
|
def test_ast_only(fn):
|
|
fn = set_to_static_mode(ToStaticMode.AST)(fn)
|
|
return fn
|
|
|
|
|
|
def test_sot_only(fn):
|
|
fn = set_to_static_mode(ToStaticMode.SOT | ToStaticMode.SOT_MGS10)(fn)
|
|
return fn
|
|
|
|
|
|
def test_phi_only(fn):
|
|
fn = set_backend_mode(BackendMode.PHI)(fn)
|
|
return fn
|
|
|
|
|
|
def test_cinn_only(fn):
|
|
fn = set_backend_mode(BackendMode.CINN)(fn)
|
|
return fn
|
|
|
|
|
|
# Some decorators for save CI time
|
|
def test_default_mode_only(fn):
|
|
# Some unittests has high time complexity, we only test them with default mode
|
|
fn = set_to_static_mode(ToStaticMode.SOT)(fn)
|
|
fn = set_backend_mode(BackendMode.PHI)(fn)
|
|
return fn
|
|
|
|
|
|
def test_sot_mgs0_only(fn):
|
|
fn = set_to_static_mode(ToStaticMode.SOT)(fn)
|
|
return fn
|
|
|
|
|
|
# For debug
|
|
def show_all_test_cases(test_class):
|
|
logger.info(f"[showing {test_class.__name__}]")
|
|
for attr in dir(test_class):
|
|
if attr.startswith("test"):
|
|
fn = getattr(test_class, attr)
|
|
logger.info(f"{attr}: {fn}")
|
|
|
|
|
|
# Other utilities
|
|
def import_module_from_path(module_name, module_path):
|
|
"""A better way to import module from other directory than using sys.path.append"""
|
|
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def import_legacy_test_utils():
|
|
test_root = Path(__file__).parent.parent
|
|
legacy_test_utils_path = test_root / "legacy_test/utils.py"
|
|
legacy_test_utils = import_module_from_path(
|
|
"legacy_test_utils", legacy_test_utils_path
|
|
)
|
|
return legacy_test_utils
|
|
|
|
|
|
legacy_test_utils = import_legacy_test_utils()
|
|
dygraph_guard = legacy_test_utils.dygraph_guard
|
|
static_guard = legacy_test_utils.static_guard
|
|
|
|
|
|
@contextmanager
|
|
def enable_to_static_guard(flag: bool):
|
|
program_translator = paddle.jit.api.ProgramTranslator()
|
|
original_flag_value = program_translator.enable_to_static
|
|
program_translator.enable(flag)
|
|
try:
|
|
yield
|
|
finally:
|
|
program_translator.enable(original_flag_value)
|
|
|
|
|
|
@contextmanager
|
|
def exe_sequential_run_guard(value: bool):
|
|
exe_sequential_run_flag = ENV_EXE_SEQUENTIAL_RUN.name
|
|
original_flag_value = paddle.get_flags(exe_sequential_run_flag)[
|
|
exe_sequential_run_flag
|
|
]
|
|
with EnvironmentVariableGuard(ENV_EXE_SEQUENTIAL_RUN, value):
|
|
try:
|
|
set_flags({exe_sequential_run_flag: value})
|
|
yield
|
|
finally:
|
|
set_flags({exe_sequential_run_flag: original_flag_value})
|