# 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 ["", "", "", ""] 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]