542 lines
14 KiB
Python
542 lines
14 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 builtins
|
|
import copy
|
|
import inspect
|
|
import sys
|
|
import time
|
|
import types
|
|
import weakref
|
|
from collections import OrderedDict
|
|
from collections.abc import Callable
|
|
from contextlib import contextmanager
|
|
from dataclasses import is_dataclass
|
|
from functools import lru_cache
|
|
from typing import TYPE_CHECKING, Any, TypeVar
|
|
from weakref import WeakValueDictionary
|
|
|
|
import numpy as np
|
|
|
|
import paddle
|
|
from paddle.jit.dy2static.utils import (
|
|
TransformOptions,
|
|
dataclass_as_dict,
|
|
dataclass_from_dict,
|
|
)
|
|
from paddle.utils import flatten, map_structure
|
|
|
|
from .envs import (
|
|
ENV_SOT_LOG_LEVEL,
|
|
ENV_SOT_SPECIALIZED_DIM_NUMBERS,
|
|
ENV_STRICT_MODE,
|
|
)
|
|
from .paddle_api_config import (
|
|
break_graph_functions,
|
|
paddle_api_list,
|
|
paddle_api_module_prefix,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
from paddle._typing import NestedStructure
|
|
|
|
T = TypeVar("T")
|
|
T1 = TypeVar("T1")
|
|
T2 = TypeVar("T2")
|
|
T3 = TypeVar("T3")
|
|
ConstTypes = (int, float, str, bool, type(None), bytes)
|
|
|
|
|
|
class Singleton(type):
|
|
_instances: dict[Any, Any] = {}
|
|
|
|
def __call__(cls, *args: Any, **kwargs: Any):
|
|
if cls not in cls._instances:
|
|
cls._instances[cls] = super().__call__(*args, **kwargs)
|
|
return cls._instances[cls]
|
|
|
|
|
|
class NameGenerator:
|
|
def __init__(self, prefix):
|
|
self.counter = 0
|
|
self.prefix = prefix
|
|
|
|
def next(self):
|
|
name = self.prefix + str(self.counter)
|
|
self.counter += 1
|
|
return name
|
|
|
|
def match_name(self, name: str) -> bool:
|
|
return name.startswith(self.prefix)
|
|
|
|
|
|
class SymbolRegistry:
|
|
def __init__(self):
|
|
self.symbol_generator = NameGenerator(prefix="___t_")
|
|
self.tmp_names_record = OrderedDict()
|
|
self.declared_symbols: set[str] = set()
|
|
self.symbol_table = {}
|
|
|
|
def next_symbol(self) -> str:
|
|
return self.symbol_generator.next()
|
|
|
|
def request_symbol(self, expr: str) -> str:
|
|
if expr in self.symbol_table:
|
|
return self.symbol_table[expr]
|
|
symbol = self.next_symbol()
|
|
self.symbol_table[expr] = symbol
|
|
return symbol
|
|
|
|
def gen_expr(self, expr: str, gen_expr_fn):
|
|
symbol = self.symbol_table[expr]
|
|
if symbol in self.declared_symbols:
|
|
return symbol
|
|
self.declared_symbols.add(symbol)
|
|
return f"({symbol} := ({gen_expr_fn()}))"
|
|
|
|
|
|
_symbol_registry = SymbolRegistry()
|
|
|
|
|
|
@contextmanager
|
|
def switch_symbol_registry():
|
|
global _symbol_registry
|
|
original_registry = _symbol_registry
|
|
_symbol_registry = SymbolRegistry()
|
|
yield
|
|
_symbol_registry = original_registry
|
|
|
|
|
|
def current_symbol_registry():
|
|
global _symbol_registry
|
|
return _symbol_registry
|
|
|
|
|
|
class ResumeFnNameFactory(metaclass=Singleton):
|
|
def __init__(self) -> None:
|
|
self.gen = NameGenerator('resume_')
|
|
|
|
def next(self):
|
|
name = self.gen.next()
|
|
return name
|
|
|
|
|
|
class SIRToCodeMap(metaclass=Singleton):
|
|
def __init__(self):
|
|
self._map = {}
|
|
|
|
def register(self, sir, code):
|
|
self._map[sir.name] = code
|
|
|
|
def get(self, sir):
|
|
return self._map.get(sir.name)
|
|
|
|
|
|
def log(level, *args):
|
|
cur_level = ENV_SOT_LOG_LEVEL.get()
|
|
if level <= cur_level:
|
|
print(*args, end="", flush=True)
|
|
|
|
|
|
def log_do(level, fn):
|
|
cur_level = ENV_SOT_LOG_LEVEL.get()
|
|
if level <= cur_level:
|
|
fn()
|
|
|
|
|
|
def log_format(level, str, *args):
|
|
cur_level = ENV_SOT_LOG_LEVEL.get()
|
|
if level <= cur_level:
|
|
print(str.format(*args), end="", flush=True)
|
|
|
|
|
|
def log_enabled(level):
|
|
return level <= ENV_SOT_LOG_LEVEL.get()
|
|
|
|
|
|
@lru_cache
|
|
def log_once(msg):
|
|
print(msg, flush=True)
|
|
|
|
|
|
def no_eval_frame(func):
|
|
def no_eval_frame_func(*args, **kwargs):
|
|
old_cb = paddle.framework.core.set_eval_frame(None)
|
|
try:
|
|
retval = func(*args, **kwargs)
|
|
except:
|
|
raise
|
|
finally:
|
|
paddle.framework.core.set_eval_frame(old_cb)
|
|
return retval
|
|
|
|
return no_eval_frame_func
|
|
|
|
|
|
def is_comprehensive_name(name):
|
|
return name in ["<listcomp>", "<dictcomp>", "<setcomp>", "<genexpr>"]
|
|
|
|
|
|
def is_paddle_api(func):
|
|
if isinstance(func, paddle.nn.Layer): # ignore all the classes
|
|
return False
|
|
if hasattr(func, "__self__"): # ignore all the methods
|
|
return False
|
|
if inspect.isclass(
|
|
func
|
|
): # paddle.Tensor should not be wrapped, but how about other situations?
|
|
return False
|
|
return in_paddle_module(func) or func in paddle_api_list
|
|
|
|
|
|
def already_unified_in_dynamic_and_static_graph(fn):
|
|
if is_paddle_api(fn):
|
|
return True
|
|
return not TransformOptions.check_fn_need_transform(
|
|
fn, TransformOptions.ToStaticMode.SOT
|
|
)
|
|
|
|
|
|
def need_capture_control_flow(fn):
|
|
return TransformOptions.check_fn_need_capture_control_flow(fn)
|
|
|
|
|
|
def is_builtin_fn(fn):
|
|
special_builtin_fns = [weakref.ref]
|
|
if fn in special_builtin_fns:
|
|
return True
|
|
if isinstance(fn, types.BuiltinFunctionType):
|
|
return True
|
|
for member_name, member in inspect.getmembers(builtins):
|
|
if member is fn and isinstance(member, type):
|
|
return True
|
|
return False
|
|
|
|
|
|
def in_paddle_module(func):
|
|
if hasattr(func, "__module__"):
|
|
module_str = func.__module__
|
|
if module_str is None:
|
|
return False
|
|
log(5, "find paddle function with __module__: ", module_str, "\n")
|
|
if hasattr(func, "__name__"):
|
|
log(
|
|
5, " with __name__ : ", func.__name__, "\n"
|
|
)
|
|
log(5, " with results : ")
|
|
for prefix in paddle_api_module_prefix:
|
|
if module_str.startswith(prefix):
|
|
log(5, " True\n")
|
|
return True
|
|
log(5, " False\n")
|
|
return False
|
|
|
|
|
|
def is_break_graph_api(func):
|
|
return func in break_graph_functions
|
|
|
|
|
|
def is_namedtuple_class(cls):
|
|
if not inspect.isclass(cls):
|
|
return False
|
|
if not issubclass(cls, tuple):
|
|
return False
|
|
# The signature created by nametuple function
|
|
namedtuple_attrs = {"_make", "_asdict", "_fields", "_replace"}
|
|
cls_attrs = set(dir(cls))
|
|
return namedtuple_attrs.issubset(cls_attrs)
|
|
|
|
|
|
def map_if(
|
|
*structures: NestedStructure[T1],
|
|
pred: Callable[[T1], bool],
|
|
true_fn: Callable[[T1], T2],
|
|
false_fn: Callable[[T1], T3],
|
|
) -> NestedStructure[T2 | T3]:
|
|
def replace(*args):
|
|
if pred(*args):
|
|
return true_fn(*args)
|
|
return false_fn(*args)
|
|
|
|
return map_structure(replace, *structures)
|
|
|
|
|
|
def flatten_extend(structure):
|
|
for item in flatten(structure):
|
|
if isinstance(item, slice):
|
|
yield item.start
|
|
yield item.stop
|
|
yield item.step
|
|
else:
|
|
yield item
|
|
|
|
|
|
def map_if_extend(structure, pred, true_fn, false_fn):
|
|
"""support extended structures like slice and SliceVariable"""
|
|
|
|
def wrapped_pred(x):
|
|
if isinstance(x, slice):
|
|
return True
|
|
if is_dataclass(x) and not isinstance(x, type):
|
|
return True
|
|
return pred(x)
|
|
|
|
def wrapped_true_fn(x):
|
|
if isinstance(x, (slice)):
|
|
l = [x.start, x.stop, x.step]
|
|
l = map_if_extend(l, pred, true_fn, false_fn)
|
|
return slice(*l)
|
|
|
|
if is_dataclass(x) and not isinstance(x, type):
|
|
dt_dict = dataclass_as_dict(x)
|
|
dt_dict = map_if_extend(dt_dict, pred, true_fn, false_fn)
|
|
return dataclass_from_dict(type(x), dt_dict)
|
|
|
|
return true_fn(x)
|
|
|
|
return map_if(
|
|
structure, pred=wrapped_pred, true_fn=wrapped_true_fn, false_fn=false_fn
|
|
)
|
|
|
|
|
|
def count_if(*structures, pred):
|
|
def is_true(*args):
|
|
if pred(*args):
|
|
return 1
|
|
return 0
|
|
|
|
return sum(flatten(map_structure(is_true, *structures)))
|
|
|
|
|
|
class Cache:
|
|
def __init__(self, weak=False, copy=False):
|
|
if not weak:
|
|
self.cache = {}
|
|
else:
|
|
self.cache = WeakValueDictionary()
|
|
self.hit_num = 0
|
|
self.copy = copy
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
cache_key = self.key_fn(*args, **kwargs)
|
|
if not hashable(cache_key):
|
|
return self.value_fn(*args, **kwargs)
|
|
if cache_key in self.cache:
|
|
log(5, "cache hit: ", cache_key, "\n")
|
|
self.hit_num += 1
|
|
cache_item = self.cache[cache_key]
|
|
if self.copy:
|
|
cache_item = copy.deepcopy(cache_item)
|
|
return cache_item
|
|
value = self.value_fn(*args, **kwargs)
|
|
self.cache[cache_key] = value
|
|
return value
|
|
|
|
def clear(self):
|
|
self.cache.clear()
|
|
self.hit_num = 0
|
|
|
|
def key_fn(self, *args, **kwargs):
|
|
raise NotImplementedError
|
|
|
|
def value_fn(self, *args, **kwargs):
|
|
raise NotImplementedError
|
|
|
|
|
|
def execute_time(func):
|
|
def wrapper(*args, **kwargs):
|
|
start_time = time.time()
|
|
result = func(*args, **kwargs)
|
|
end_time = time.time()
|
|
execution_time = end_time - start_time
|
|
print("Execute time:", execution_time)
|
|
return result
|
|
|
|
return wrapper
|
|
|
|
|
|
def meta_str(shape, dtype, stop_gradient):
|
|
return f"(shape: {shape}, dtype: {dtype}, stop_gradient: {stop_gradient})"
|
|
|
|
|
|
def is_strict_mode():
|
|
return ENV_STRICT_MODE.get()
|
|
|
|
|
|
def list_find_index_by_id(li: list[Any], item: Any) -> int:
|
|
return [id(it) for it in li].index(id(item))
|
|
|
|
|
|
def list_contain_by_id(li: list[Any], item: Any) -> int:
|
|
return id(item) in [id(it) for it in li]
|
|
|
|
|
|
def get_unbound_method(obj, name):
|
|
# TODO(dev): Consider the case of patching methods to instances
|
|
return getattr(obj.__class__, name)
|
|
|
|
|
|
class SotUndefinedVar(metaclass=Singleton):
|
|
pass
|
|
|
|
|
|
def hashable(obj):
|
|
try:
|
|
hash(obj)
|
|
return True
|
|
except TypeError as e:
|
|
return False
|
|
|
|
|
|
def printable(obj):
|
|
try:
|
|
str(obj)
|
|
return True
|
|
except Exception as e:
|
|
return False
|
|
|
|
|
|
class StepInfo:
|
|
BACK_TRACE_STEPS = 20
|
|
|
|
def __init__(self):
|
|
self.step_count = -1
|
|
|
|
def need_back_trace(self):
|
|
return self.step_count < self.BACK_TRACE_STEPS
|
|
|
|
|
|
class StepInfoManager(metaclass=Singleton):
|
|
def __init__(self):
|
|
self.step_record = {}
|
|
self.current_code = None
|
|
self.current_step_info = None
|
|
|
|
@contextmanager
|
|
def step_guard(self, code):
|
|
try:
|
|
old_code = self.current_code
|
|
old_info = self.current_step_info
|
|
|
|
self.current_code = code
|
|
if code not in self.step_record:
|
|
self.step_record[code] = StepInfo()
|
|
self.current_step_info = self.step_record[code]
|
|
|
|
self.current_step_info.step_count += 1
|
|
yield
|
|
finally:
|
|
self.current_code = old_code
|
|
self.current_step_info = old_info
|
|
|
|
@property
|
|
def need_back_trace(self):
|
|
return (
|
|
self.current_step_info is not None
|
|
and self.current_step_info.need_back_trace()
|
|
)
|
|
|
|
@property
|
|
def current_step(self):
|
|
return self.current_step_info.step_count
|
|
|
|
def clear(self):
|
|
self.step_record.clear()
|
|
self.current_code = None
|
|
self.current_step = -1
|
|
|
|
|
|
def get_api_fullname(api):
|
|
api_name = api.__name__
|
|
module_str = api.__module__
|
|
while len(module_str) > 0:
|
|
if module_str not in sys.modules:
|
|
return api_name
|
|
module = sys.modules[module_str]
|
|
if hasattr(module, api_name):
|
|
return module_str + "." + api_name
|
|
module_str = module_str.rpartition(".")[0]
|
|
return None
|
|
|
|
|
|
def get_numpy_ufuncs():
|
|
ufuncs = [
|
|
ufunc
|
|
for _, ufunc in inspect.getmembers(
|
|
np, lambda member: isinstance(member, np.ufunc)
|
|
)
|
|
]
|
|
unary_ufuncs = filter(lambda ufunc: ufunc.nin == 1, ufuncs)
|
|
binary_ufuncs = filter(lambda ufunc: ufunc.nin == 2, ufuncs)
|
|
return list(unary_ufuncs), list(binary_ufuncs)
|
|
|
|
|
|
def do_until_stop_iteration(fn: Callable[[], T]) -> list[T]:
|
|
from paddle.jit.sot.utils.exceptions import SotCapturedStopIteration
|
|
|
|
res = []
|
|
while True:
|
|
try:
|
|
res.append(fn())
|
|
except SotCapturedStopIteration:
|
|
break
|
|
return res
|
|
|
|
|
|
def update_list_inplace(
|
|
original_list: list[T], new_contents: list[T]
|
|
) -> list[T]:
|
|
original_list.clear()
|
|
original_list.extend(new_contents)
|
|
return original_list
|
|
|
|
|
|
def get_obj_stable_repr(obj) -> str:
|
|
if hasattr(obj, '__qualname__'):
|
|
return obj.__qualname__
|
|
if hasattr(obj, '__name__'):
|
|
return obj.__name__
|
|
|
|
class_name = obj.__class__.__name__
|
|
|
|
# If module is available and not __main__, include it
|
|
if hasattr(obj, "__class__") and hasattr(obj.__class__, "__module__"):
|
|
module = obj.__class__.__module__
|
|
if module not in ("__main__", "builtins"):
|
|
return f"{module}.{class_name}()"
|
|
|
|
return f"{class_name}()"
|
|
|
|
|
|
def get_min_non_specialized_number() -> int:
|
|
specialized_dim_numbers_raw_str = (
|
|
ENV_SOT_SPECIALIZED_DIM_NUMBERS.get().lower()
|
|
)
|
|
assert specialized_dim_numbers_raw_str in [
|
|
"no",
|
|
"0",
|
|
"01",
|
|
], f"Unsupported specialized_dim_numbers: {specialized_dim_numbers_raw_str}"
|
|
to_min_non_specialized_number = {
|
|
# specialized numbers, minimum non-specialized number
|
|
"no": 0,
|
|
"0": 1,
|
|
"01": 2,
|
|
}
|
|
return to_min_non_specialized_number[specialized_dim_numbers_raw_str]
|