420 lines
12 KiB
Python
420 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 functools
|
|
import weakref
|
|
from typing import TYPE_CHECKING, Any, TypeVar
|
|
from weakref import WeakValueDictionary
|
|
|
|
import paddle
|
|
from paddle.jit.dy2static.utils import parameters_persistent_mode_is_enabled
|
|
from paddle.jit.utils import OrderedSet
|
|
from paddle.utils import flatten, map_structure
|
|
|
|
from ..utils import (
|
|
InnerError,
|
|
NameGenerator,
|
|
Singleton,
|
|
flatten_extend,
|
|
get_api_fullname,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
from contextlib import AbstractContextManager
|
|
|
|
|
|
_StatementContextT = TypeVar("_StatementContextT", bound="StatementContext")
|
|
|
|
|
|
class ParametersHolder:
|
|
def __init__(self):
|
|
self._params = WeakValueDictionary[
|
|
str, paddle.base.framework.EagerParamBase
|
|
]()
|
|
|
|
def set(self, name, param):
|
|
self._params[name] = param
|
|
|
|
def get(self, name):
|
|
if (param := self._params.get(name)) is None:
|
|
raise InnerError(
|
|
f"Parameter '{name}' not found in ParametersHolder."
|
|
)
|
|
return param
|
|
|
|
def copy(self):
|
|
new_holder = ParametersHolder()
|
|
new_holder._params = self._params.copy()
|
|
return new_holder
|
|
|
|
|
|
class Reference: # to unify weak_ref and strong_ref
|
|
def __init__(self, value, is_weak):
|
|
self.is_weak = is_weak
|
|
if is_weak is True:
|
|
self.ref = weakref.ref(value)
|
|
else:
|
|
self.ref = value
|
|
|
|
def __call__(self):
|
|
if self.is_weak is True:
|
|
return self.ref()
|
|
else:
|
|
return self.ref
|
|
|
|
|
|
class Symbol:
|
|
"""
|
|
Symbol is used to distinguish a string and a `math variable`.
|
|
"""
|
|
|
|
def __init__(self, name: str):
|
|
self.name = name
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def __repr__(self):
|
|
return str(self)
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, str):
|
|
return self.name == other
|
|
return self.name == other.name
|
|
|
|
def __hash__(self):
|
|
return hash(self.name)
|
|
|
|
def __deepcopy__(self, memo=None):
|
|
return Symbol(self.name)
|
|
|
|
|
|
class StatementContext: ...
|
|
|
|
|
|
class StatementContextRegistry:
|
|
_ctx_map: dict[
|
|
type[Any],
|
|
Callable[[Any], AbstractContextManager[None]],
|
|
] = {}
|
|
|
|
@classmethod
|
|
def register_context_guard(
|
|
cls,
|
|
ctx_cls: type[_StatementContextT],
|
|
handler: Callable[[_StatementContextT], AbstractContextManager[None]],
|
|
):
|
|
"""
|
|
Register a context handler for the given context.
|
|
"""
|
|
if ctx_cls in cls._ctx_map:
|
|
raise ValueError(f"Context {ctx_cls} is already registered.")
|
|
cls._ctx_map[ctx_cls] = handler
|
|
|
|
@classmethod
|
|
def register_context(
|
|
cls,
|
|
handler: Callable[[_StatementContextT], AbstractContextManager[None]],
|
|
):
|
|
def decorator(ctx_cls: type[_StatementContextT]):
|
|
cls.register_context_guard(ctx_cls, handler)
|
|
return ctx_cls
|
|
|
|
return decorator
|
|
|
|
@classmethod
|
|
def get_context_guard(
|
|
cls,
|
|
ctx_cls: type[_StatementContextT],
|
|
) -> Callable[[_StatementContextT], AbstractContextManager[None]]:
|
|
"""
|
|
Get the context handler for the given context.
|
|
"""
|
|
if ctx_cls not in cls._ctx_map:
|
|
raise ValueError(f"Context {ctx_cls} is not registered.")
|
|
return cls._ctx_map[ctx_cls]
|
|
|
|
|
|
class Statement:
|
|
"""
|
|
Statement is used to represent a sentence of code for building the neural network model,
|
|
which has four types: "call", "api", "method", and "layer".
|
|
|
|
Note:
|
|
Statement temporarily does not support control flow.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
type: str,
|
|
name: str,
|
|
inputs: list[Symbol],
|
|
outputs: list[Symbol],
|
|
contexts: list[StatementContext],
|
|
stacks: list[str],
|
|
):
|
|
assert type in ["call", "api", "method", "layer", "AST"]
|
|
self.name = name
|
|
self.inputs = inputs # (list of Symbols, dict of Symbols)
|
|
self.outputs = outputs # list of Symbol | PythonObj
|
|
self.contexts = contexts # list of StatementContext
|
|
self.stmt_stack = (
|
|
stacks # a list of string to record the source code callstack.
|
|
)
|
|
self.type = type
|
|
|
|
def __str__(self):
|
|
return "{} || {} = {} ({}) ".format(
|
|
self.type + " " * (10 - len(self.type)),
|
|
self.to_string(self.outputs),
|
|
self.name,
|
|
self.to_string(self.inputs),
|
|
)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
@staticmethod
|
|
def to_string(inps):
|
|
return ", ".join(repr(x) for x in flatten(inps))
|
|
|
|
|
|
class CallStatement(Statement):
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
inputs: list[Symbol],
|
|
outputs: list[Symbol],
|
|
contexts: list[StatementContext],
|
|
stacks: list[str],
|
|
):
|
|
super().__init__("call", name, inputs, outputs, contexts, stacks)
|
|
self.sir_name = name
|
|
|
|
|
|
class ApiStatement(Statement):
|
|
def __init__(
|
|
self,
|
|
api: Callable,
|
|
inputs: list[Symbol],
|
|
outputs: list[Symbol],
|
|
contexts: list[StatementContext],
|
|
stacks: list[str],
|
|
):
|
|
fullname = get_api_fullname(api)
|
|
if fullname is None:
|
|
fullname = "paddle." + api.__name__
|
|
super().__init__("api", fullname, inputs, outputs, contexts, stacks)
|
|
self.api = api
|
|
|
|
|
|
class MethodStatement(Statement):
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
inputs: list[Symbol],
|
|
outputs: list[Symbol],
|
|
contexts: list[StatementContext],
|
|
stacks: list[str],
|
|
):
|
|
super().__init__("method", name, inputs, outputs, contexts, stacks)
|
|
self.method = name
|
|
|
|
|
|
class LayerStatement(Statement):
|
|
def __init__(
|
|
self,
|
|
layer: Reference, # Reference of paddle.nn.Layer
|
|
inputs: list[Symbol],
|
|
outputs: list[Symbol],
|
|
contexts: list[StatementContext],
|
|
stacks: list[str],
|
|
):
|
|
if isinstance(layer, Reference):
|
|
name = layer().__class__.__name__
|
|
else:
|
|
name = layer.__class__.__name__
|
|
super().__init__(
|
|
"layer",
|
|
name,
|
|
inputs,
|
|
outputs,
|
|
contexts,
|
|
stacks,
|
|
)
|
|
self.layer = layer
|
|
|
|
|
|
class ASTStatement(Statement):
|
|
def __init__(
|
|
self,
|
|
static_function,
|
|
inputs: list[Symbol],
|
|
outputs: list[Symbol],
|
|
contexts: list[StatementContext],
|
|
stacks: list[str],
|
|
):
|
|
# this dygraph_function always has attr __code__, which is checked before
|
|
dygraph_func = static_function.dygraph_function
|
|
super().__init__(
|
|
"AST",
|
|
dygraph_func.__code__.co_name,
|
|
inputs,
|
|
outputs,
|
|
contexts,
|
|
stacks,
|
|
)
|
|
converted_func = paddle.jit.dy2static.convert_to_static(dygraph_func)
|
|
func_self = getattr(dygraph_func, '__self__', None)
|
|
if func_self is not None:
|
|
converted_func = functools.partial(converted_func, func_self)
|
|
self.converted_func = converted_func
|
|
|
|
|
|
class StatementIR:
|
|
"""
|
|
StatementIR is the carrier that records the code for building the neural network model.It is
|
|
a representation of a purely computational structure, and does not care about specific values.
|
|
The function converted from StatementIR can ensure that it can be turned into a static state.
|
|
In this way, we can reuse the original `to_static` function to realize the execution of the static graph.
|
|
|
|
Note:
|
|
Don't create by yourself, just use the StatementIRFactory.create()
|
|
"""
|
|
|
|
def __init__(self, name: str):
|
|
self.name = name
|
|
self.inputs: list[Symbol] = []
|
|
self.params: list[Symbol] = []
|
|
self.outputs: list[Symbol] = []
|
|
self.statements: list[Statement] = []
|
|
|
|
self.symbol_meta_map = {}
|
|
self.param_symbol = set()
|
|
self.non_param_symbol = set()
|
|
|
|
@property
|
|
def input_with_params(self):
|
|
return self.inputs + self.params
|
|
|
|
def __len__(self):
|
|
return len(self.statements)
|
|
|
|
def __deepcopy__(self, memo=None):
|
|
new_sir = StatementIR(self.name)
|
|
new_sir.inputs = list(self.inputs)
|
|
new_sir.params = list(self.params)
|
|
new_sir.outputs = list(self.outputs)
|
|
new_sir.statements = list(self.statements)
|
|
new_sir.symbol_meta_map = dict(self.symbol_meta_map.items())
|
|
new_sir.param_symbol = set(self.param_symbol)
|
|
new_sir.non_param_symbol = set(self.non_param_symbol)
|
|
return new_sir
|
|
|
|
def set_parameter_info(self, params, non_params):
|
|
self.param_symbol.update(params)
|
|
self.non_param_symbol.update(non_params)
|
|
|
|
def set_symbol_meta_map(self, meta_map):
|
|
# if the meta of a input symbol inplace changed, we should get the origin meta as input of SIR
|
|
meta_map.update(self.symbol_meta_map)
|
|
self.symbol_meta_map = meta_map
|
|
|
|
def add_input(self, input):
|
|
self.inputs.append(input)
|
|
|
|
def add_output(self, output):
|
|
self.outputs.append(output)
|
|
|
|
def add_statement(self, statement):
|
|
assert isinstance(statement, Statement)
|
|
self.statements.append(statement)
|
|
|
|
def analyse_inputs(self):
|
|
used_symbols = OrderedSet()
|
|
generated_symbols = OrderedSet()
|
|
for stmt in self.statements:
|
|
for inp in flatten_extend(stmt.inputs):
|
|
if isinstance(inp, Symbol) and inp not in generated_symbols:
|
|
used_symbols.add(inp)
|
|
for out in flatten_extend(stmt.outputs):
|
|
if isinstance(out, Symbol):
|
|
generated_symbols.add(out)
|
|
|
|
used_symbols = sorted(used_symbols, key=lambda x: x.name)
|
|
if not parameters_persistent_mode_is_enabled():
|
|
return used_symbols, []
|
|
input_symbols = [
|
|
symbol for symbol in used_symbols if symbol not in self.param_symbol
|
|
]
|
|
param_symbols = [
|
|
symbol for symbol in used_symbols if symbol in self.param_symbol
|
|
]
|
|
return input_symbols, param_symbols
|
|
|
|
def __str__(self):
|
|
strs = []
|
|
strs.append(f"StatementIR: {self.name}")
|
|
strs.append(f" inputs: {map_structure(lambda x: x.name, self.inputs)}")
|
|
strs.append(f" params: {map_structure(lambda x: x.name, self.params)}")
|
|
strs.append(
|
|
f" outputs: {map_structure(lambda x: x.name, self.outputs)}"
|
|
)
|
|
strs.append(" statements: ")
|
|
for stmt in self.statements:
|
|
strs.append(f" {stmt}")
|
|
return "\n".join(strs)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
|
|
class StatementIRFactory(metaclass=Singleton):
|
|
"""
|
|
It is used to create a StatementIR.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.cache = {}
|
|
self.name_generator = NameGenerator("SIR_")
|
|
|
|
def __getitem__(self, key):
|
|
return self.cache[key]
|
|
|
|
def create(self, input_name=None):
|
|
if input_name:
|
|
name = input_name
|
|
else:
|
|
name = self.name_generator.next()
|
|
|
|
sir = StatementIR(name)
|
|
self.cache[name] = sir
|
|
return sir
|
|
|
|
def update(self, stmt_ir):
|
|
name = stmt_ir.name
|
|
self.cache[name] = stmt_ir
|
|
|
|
def clear(self):
|
|
want_clear = [
|
|
key
|
|
for key in self.cache.keys()
|
|
if self.name_generator.match_name(key)
|
|
]
|
|
for key in want_clear:
|
|
del self.cache[key]
|