Files
paddlepaddle--paddle/python/paddle/jit/dy2static/program_translator.py
T
2026-07-13 12:40:42 +08:00

1900 lines
66 KiB
Python

# Copyright (c) 2020 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 collections
import inspect
import os
import threading
import warnings
import weakref
from typing import TYPE_CHECKING, Any, Generic, TypeVar
from typing_extensions import ParamSpec, Self
import paddle
import paddle.pir.core as ir_static
from paddle import decomposition, get_flags
from paddle.base import core, framework
from paddle.base.data_feeder import check_type
from paddle.base.dygraph.base import (
param_guard,
switch_to_static_graph,
to_static_mode_guard,
)
from paddle.framework import in_dynamic_mode, use_pir_api
from paddle.nn.layer import layers
from paddle.pir import Value
from paddle.utils import flatten, gast
from . import error, logging_utils
from .function_spec import (
FunctionSpec,
_hash_spec_names,
get_buffers,
get_parameters,
)
from .origin_info import (
attach_origin_info,
create_and_update_origin_info_map,
update_op_callstack_with_origin_info,
)
from .partial_program import PartialProgramLayer, PartialProgramLayerHook
from .pir_partial_program import (
PartialProgramLayer as PirPartialProgramLayer,
PartialProgramLayerHook as PirPartialProgramLayerHook,
)
from .transformers import DygraphToStaticAst
from .utils import (
ALREADY_D2S,
NO_SHAPE_VAR_TYPE,
TransformOptions,
ast_to_func,
backend_guard,
cuda_pinned_tensors_move_to_excepted_place,
func_to_source_code,
graph_tracing_guard,
input_specs_compatible,
is_paddle_func,
make_hashable,
prim_or_cinn_is_enabled,
type_name,
use_specialized_device,
)
if TYPE_CHECKING:
from collections.abc import Callable
from paddle._typing import NestedSequence
from paddle.static import InputSpec, Program
from paddle.static.amp.fp16_utils import AmpOptions
_RetT = TypeVar("_RetT")
_InputT = ParamSpec("_InputT")
__all__ = []
# For each traced function, we set `max_traced_program_count` = 10 to consider caching performance.
# Once exceeding the threshold, we will raise warning to users to make sure the conversion is as expected.
MAX_TRACED_PROGRAM_COUNT = 10
def synchronized(func):
func.__lock__ = threading.Lock()
def lock_func(*args, **kwargs):
with func.__lock__:
return func(*args, **kwargs)
return lock_func
def show_op_callstack(op):
op_callstack = op.callstack
target_lines = {
"outputs = static_func(*inputs)",
"outputs = static_func(*inputs, **_kwargs)",
}
op_callstack_message = ""
for index, line in enumerate(op_callstack):
if line.strip() in target_lines:
op_callstack_result = '\n'.join(op_callstack[index + 1 :])
op_callstack_message = (
f"In transformed code:\n\n{op_callstack_result}\n\n"
)
raise ValueError(
f"{op_callstack_message}Sorry about what's happened. In to_static mode, {op.name()}'s output variable is a viewed Tensor in dygraph. "
f"This will result in inconsistent calculation behavior between dynamic and static graphs. "
f"You must find the location of the strided ops be called, and call paddle.assign() before inplace input. "
f"If you certainly make sure it's safe, you can set env stride_in_no_check_dy2st_diff to 1."
)
def check_view_api_used_by_inplace(program: paddle.pir.Program) -> None:
"""
check viewed value used by inplace op in pir mode.
Two scenarios will raise ValueError:
# one
a = transpose(b)
a.add_(c)
# two
a = transpose(b)
b.add_(c)
"""
# TODO(ooooo): Deal with these inplace ops
skipped_inplace_ops = [
"pd_op.set_value_",
"pd_op.set_value_with_tensor_",
# It willn't change tensor imdeiately,but it's output is dangerous.
"pd_op.share_data_",
]
def val_is_used_by_stride_op(op, val):
return op.name() in framework.stride_ops and op.operand_source(
0
).is_same(val)
def is_used_by_inplace_op(op, val, info):
return op.name().endswith("_") and any(
op.operand_source(index).is_same(val) for index in info.values()
)
all_vars_list = program.list_vars()
for value in all_vars_list:
used_by_stride_ops = []
for op in reversed(value.all_used_ops()):
inplace_info = paddle.core.pir.get_op_inplace_info(op)
if val_is_used_by_stride_op(op, value):
used_by_stride_ops.append(op)
if is_used_by_inplace_op(op, value, inplace_info):
if op.name() in skipped_inplace_ops:
continue
if value.get_defining_op().name() in framework.stride_ops:
show_op_callstack(op)
if len(used_by_stride_ops) == 0:
continue
show_op_callstack(op)
class FunctionCache:
"""
Caches the transformed functions to avoid redundant conversions of the same function.
"""
def __init__(self):
# Caches the converted static functions. {dygraph_func: static_func}
self._converted_static_func_caches = weakref.WeakKeyDictionary()
# Caches the converted ast node for same source code. {source_code: ast_root}
self._code_to_ast_caches = {}
self._dygraph_to_static = DygraphToStaticAst()
def convert_with_cache(self, func):
"""
Returns the cached static function or converts it when first encounters the function.
"""
# If hit cache, return it directly.
static_func = self._converted_static_func_caches.get(func, None)
if static_func is None:
static_func = self._convert(func)
self._converted_static_func_caches[func] = static_func
return static_func
def _convert(self, func):
"""
Converts dygraph function into static function. For two functions with same dedent code,
the second function will reuse the transformed ast node of previous one.
For example:
# A.py
def foo(x, y):
z = x + y
return z
# B.py
def foo(x, y):
z = x + y
return z
If the conversion of A.foo happens after B.foo, it will reuse the transformed ast node of B.foo
to speed up the conversion.
"""
func = inspect.unwrap(func)
source_code = func_to_source_code(func)
# TODO(liym27):
# Consider this case: source_code in self._code_to_ast_caches,
# but actually they are methods in different classes.
# Maybe use (__class__, source_code) as key
if source_code in self._code_to_ast_caches:
root = self._code_to_ast_caches[source_code]
else:
root = gast.parse(source_code)
root = attach_origin_info(root, func)
root = self._dygraph_to_static.get_static_ast(root)
self._code_to_ast_caches[source_code] = root
# Get static function from AST
static_func, file_name = ast_to_func(root, func)
create_and_update_origin_info_map(root, static_func)
return static_func
def exist(self, func):
return func in self._converted_static_func_caches
_CACHE_LOCK = threading.Lock()
_FUNCTION_CACHE = FunctionCache()
def convert_to_static(function):
"""
Transforms function of dygraph into static function using the cache mechanism.
Note(dev): It will return function.__func__ if encountering class method.
Args:
function(callable): The function with dygraph layers that will be converted into static layers.
"""
if getattr(function, ALREADY_D2S, None):
return function
# Return directly if decorated with @jit.marker.unified and DO NOT Cache it
# or ignore paddle api
need_skip = (
not TransformOptions.check_fn_need_transform(
function, TransformOptions.ToStaticMode.AST
)
) or is_paddle_func(function)
if need_skip:
return function.__func__ if inspect.ismethod(function) else function
with _CACHE_LOCK:
static_func = _FUNCTION_CACHE.convert_with_cache(function)
setattr(static_func, ALREADY_D2S, True)
return static_func
class CacheKey:
"""
Cached key for ProgramCache.
"""
__slots__ = [
'function_spec',
'input_args_with_spec',
'input_kwargs_with_spec',
'class_instance',
'is_grad_enabled',
'kwargs',
'_spec_names_id',
'_pir_flags',
]
def __init__(
self,
function_spec,
input_args_with_spec,
input_kwargs_with_spec,
class_instance,
**kwargs,
):
"""
Initializes a cache key.
Args:
functions_spec(FunctionSpec): a FunctionSpec instance of decorated function.
input_args_with_spec(list[InputSpec]): actual input args with some arguments replaced by InputSpec.
input_kwargs_with_spec(list[{string:InputSpec}]): actual input kwargs with some arguments replaced by InputSpec.
class_instance(object): a instance of class `Layer`.
**kwargs(dict): manage other arguments used for better scalability
"""
self.function_spec = function_spec
self.input_args_with_spec = input_args_with_spec
self.input_kwargs_with_spec = input_kwargs_with_spec
self.class_instance = class_instance
self.is_grad_enabled = paddle.is_grad_enabled()
# NOTE: `kwargs` is usually not considered as basic member for `__hash__`
self.kwargs = kwargs
self._spec_names_id = _hash_spec_names(
input_args_with_spec, input_kwargs_with_spec
)
self._pir_flags = (
get_flags('FLAGS_enable_pir_in_executor')[
'FLAGS_enable_pir_in_executor'
]
or get_flags('FLAGS_enable_pir_with_pt_in_dy2st')[
'FLAGS_enable_pir_with_pt_in_dy2st'
]
)
@classmethod
def from_func_and_args(cls, function_spec, args, kwargs, class_instance):
"""
Generated a CacheKey instance by given inputs.
Args:
functions_spec(FunctionSpec): a FunctionSpec instance of decorated function.
args(tuple): tuple of actual inputs arguments.
kwargs(dict): dict of actual inputs keyword arguments.
class_instance(object): a instance of class `Layer`.
"""
# 1. filter `self` in args
if args and isinstance(args[0], layers.Layer):
args = args[1:]
# 2. convert tensor and numpy array into InputSpec
_args, _kwargs = function_spec.unified_args_and_kwargs(args, kwargs)
(
input_args_with_spec,
input_kwargs_with_spec,
) = function_spec.args_to_input_spec(_args, _kwargs)
# 3. check whether hit the cache or build a new program for the input arguments
return CacheKey(
function_spec,
input_args_with_spec,
input_kwargs_with_spec,
class_instance,
)
def __hash__(self):
error_msg = "Arguments to a `@paddle.jit.to_static` must be a hashable Python objects (or nested structures of these types)."
with_hook = self.kwargs.get("with_hook", False)
is_train = self.kwargs.get("is_train", False)
return hash(
(
id(self.function_spec),
make_hashable(self.input_args_with_spec, error_msg),
make_hashable(self.input_kwargs_with_spec, error_msg),
self._spec_names_id,
self.class_instance,
with_hook,
is_train,
self._pir_flags,
use_pir_api(),
self.is_grad_enabled,
)
)
def __eq__(self, other):
return (type(self) is type(other)) and hash(self) == hash(other)
def __neq__(self, other):
return not self == other
def __repr__(self):
return f"id(function_spec): {id(self.function_spec)}, input_args_with_spec: {self.input_args_with_spec}, input_kwargs_with_spec: {self.input_kwargs_with_spec}, class_instance: {self.class_instance}"
def unwrap_decorators(func):
"""
Unwraps a decorated function and returns the decorator list and inner target.
"""
decorators = []
cur = func
while True:
if isinstance(cur, StaticFunction):
decorators.append(cur)
# Note: if `cur` is a method, keep it as bound method of class.
instance = cur.class_instance
if instance is not None:
cur = cur.dygraph_function.__get__(instance)
else:
cur = cur.dygraph_function
else:
break
return decorators, cur
class StaticFunction(Generic[_InputT, _RetT]):
def __init__(self, function, input_spec=None, **kwargs):
"""
Initializes a `StaticFunction`.
Args:
function(callable): A function or method that will be converted into static program.
input_spec(list[InputSpec]): list of InputSpec to specify the `shape/dtype/name` information for each input argument, default None.
**kwargs(dict): other arguments like `build_strategy` et.al.
"""
# save the instance `self` while decorating a method of class.
if inspect.ismethod(function):
self._dygraph_function = function.__func__
self._class_instance = weakref.ref(function.__self__)
if not hasattr(self.class_instance, '_original_funcs'):
raise TypeError(
"When using 'to_static' to convert method of a class, "
"please ensure the class inherits from nn.Layer"
)
self.class_instance._original_funcs[function.__name__] = (
self._dygraph_function
)
else:
self._dygraph_function = function
self._class_instance = None
self._input_spec = input_spec
self._function_spec = FunctionSpec(function, input_spec)
self._program_cache = ProgramCache()
self._descriptor_cache = weakref.WeakKeyDictionary()
# Note: Hold a reference to ProgramTranslator for switching `enable_to_static`.
self._program_trans = ProgramTranslator()
self._kwargs = kwargs
self._training = True
self._property = kwargs.get("property", False)
# Note: Record the patched method name for rollback.
self._patched_name = None
@property
def is_property(self) -> bool:
# whether is class proproty to be exported.
return self._property
def train(self) -> None:
if (
isinstance(self.class_instance, layers.Layer)
and self.class_instance.training is False
):
raise RuntimeError(
f"Failed to switch train mode. {self.dygraph_function} is a Layer's method, "
"please use Layer.train() to switch train mode."
)
self._training = True
def eval(self) -> None:
if (
isinstance(self.class_instance, layers.Layer)
and self.class_instance.training is True
):
raise RuntimeError(
f"Failed to switch eval mode. {self.dygraph_function} is a Layer's method, "
"please use Layer.eval() to switch eval mode."
)
self._training = False
def __get__(self, instance, owner):
"""
Overrides this method to parse the class instance and call bound method correctly.
For example:
'''
class Net(Layer):
def __init__(self):
pass
@paddle.jit.to_static
def forward(self, x, y):
return x + y
net = Net()
out = net(x, y)
'''
In above case, `net(x, y)` will call `net.forward(x, y)` firstly that is a bound method
of `Net` instance. After decorated by `@paddle.jit.to_static`, it will firstly to call `__get__`
to parse the class instance correctly instead of the `StaticFunction` instance.
"""
if instance not in self._descriptor_cache:
if instance is None:
return self
# Note(Aurelius84): To construct new instance of StaticFunction when we
# first encounter the bound function of layer and cache it.
new_static_layer = self._clone()
if (
isinstance(instance, layers.Layer)
and hasattr(instance, "_original_funcs")
and self._dygraph_function.__name__
not in instance._original_funcs.keys()
):
instance._original_funcs[self._dygraph_function.__name__] = (
self._dygraph_function
)
new_static_layer._class_instance = weakref.ref(instance)
self._descriptor_cache[instance] = new_static_layer
return self._descriptor_cache[instance]
def _clone(self) -> Self:
return self.__class__(
self.dygraph_function, self._input_spec, **self._kwargs
)
def __call__(self, *args: _InputT.args, **kwargs: _InputT.kwargs) -> _RetT:
"""
Supports to call the returned instance with input `args` and `kwargs` directly.
Args:
*args(tuple): tuple of all input arguments from original decorated function.
**kwargs(dict): dict of all input keyword arguments from original decorated function.
Return:
Outputs of decorated function.
"""
if self._property:
return self._call_dygraph_function(*args, **kwargs)
# 1. call dygraph function directly if not enable `declarative`
if not self._program_trans.enable_to_static:
# NOTE(liym27):
# Here calls `warnings.warn` but not `logging_utils.warn` because by default warnings.warn(message)
# will show up **only once**. StaticFunction.__call__ will run many times, it is appropriate to
# display this warning message only once.
logging_utils.warn(
"The decorator '@paddle.jit.to_static' does NOT work when setting 'paddle.jit.enable_to_static' to False. "
"We will just return dygraph output. If you would like to get static graph output, please call API "
"paddle.jit.enable_to_static(True)"
)
return self._call_dygraph_function(*args, **kwargs)
if not in_dynamic_mode():
raise RuntimeError(
f"Failed to run the callable object {self.dygraph_function} decorated by '@paddle.jit.to_static', "
"because it is NOT in dynamic mode. Please disable the static graph mode to enter dynamic mode with the "
"following API: paddle.disable_static()."
)
return self._perform_call(*args, **kwargs)
def _is_train_mode(self) -> bool:
if self.class_instance is not None:
if not hasattr(self.class_instance, 'training'):
raise TypeError(
"When using 'to_static' to convert method of a class, "
"please ensure the class inherits from nn.Layer"
)
return self.class_instance.training
else:
return self._training
def _call_dygraph_function(
self, *args: _InputT.args, **kwargs: _InputT.kwargs
) -> _RetT:
"""
Calls dygraph function directly and returns the outputs.
Args:
*args(tuple): tuple of all input arguments from original decorated function.
**kwargs(dict): dict of all input keyword arguments from original decorated function.
Return:
Outputs of dygraph function.
"""
return self.dygraph_function(*args, **kwargs)
def _raise_when_property(self):
"""raise RuntimeError when property=True
Raises:
RuntimeError: can not call this func when property=True
"""
if self.is_property:
raise RuntimeError("Can not call the func when property=True.")
def get_concrete_program(
self, *args: _InputT.args, **kwargs: _InputT.kwargs
) -> tuple[ConcreteProgram, PirPartialProgramLayer]:
raise NotImplementedError("Not implemented yet.")
def get_concrete_program_with_cache_key(self, cached_key):
raise NotImplementedError("Not implemented yet.")
def get_traced_count(self):
raise NotImplementedError("Not implemented yet.")
@property
def code(self) -> str:
raise NotImplementedError("Not implemented yet.")
@property
def class_instance(self):
if self._class_instance is None:
return None
if self._class_instance() is None:
raise RuntimeError(
"The instance of class has been deleted, please re-create the instance."
)
return self._class_instance()
@property
def dygraph_function(self) -> Callable[_InputT, _RetT]:
"""
Returns the original decorated function.
"""
if self.class_instance is not None:
return self._dygraph_function.__get__(self.class_instance)
else:
return self._dygraph_function
@property
def concrete_program(self) -> ConcreteProgram:
raise NotImplementedError("Not implemented yet.")
def concrete_program_specify_input_spec(
self,
input_spec: NestedSequence[InputSpec] | None = None,
with_hook: bool = False,
is_prim_infer: bool = False,
):
raise NotImplementedError("Not implemented yet.")
def rollback(self) -> Callable[_InputT, _RetT]:
"""
Rollback into original dygraph functions for current class instance.
Returns:
Function or Method
Examples:
.. code-block:: pycon
>>> # doctest: +SKIP('`paddle.jit.to_static` can not run in xdoctest')
>>> import paddle
>>> class Net(paddle.nn.Layer):
... def __init__(self):
... super().__init__()
...
... def forward(self, x, flag=True):
... if flag:
... out = x + 1
... else:
... out = x - 1
... return out
>>> x = paddle.randn([10, 1], 'float32')
>>> net = paddle.jit.to_static(Net()) # convert into static graph mode
>>> out = net(x)
>>> net.forward.rollback() # rollback into dygraph mode
>>> out = net(x)
"""
if self.class_instance is None:
return self._dygraph_function
# only rollback sub-functions on path of top _dygraph_function
fn_name = (
self._patched_name
if self._patched_name is not None
else self._dygraph_function.__name__
)
assert fn_name in self.class_instance._original_funcs, (
f"Not Found function '{fn_name}' in class '{self.class_instance.__class__}'."
)
func = self.class_instance._original_funcs[fn_name]
setattr(self.class_instance, fn_name, func.__get__(self.class_instance))
return getattr(self.class_instance, fn_name)
def __deepcopy__(self, memo):
"""
Customized behavior for copy.deepcopy, return a new StaticFunction instance.
Examples:
.. code-block:: pycon
>>> import copy
>>> import paddle
>>> class Net(paddle.nn.Layer):
... def __init__(self):
... super().__init__()
...
... def forward(self, x, flag=True):
... if flag:
... out = x + 1
... else:
... out = x - 1
... return out
>>> x = paddle.randn([10, 1], 'float32')
>>> net = paddle.jit.to_static(Net()) # convert into static graph mode
>>> copy_net = copy.deepcopy(net) # still in static graph mode
"""
if self.class_instance is not None:
copied_static_fn = type(self)(
self._dygraph_function, self._input_spec, **self._kwargs
)
copied_static_fn._training = self._training
copied_static_fn._program_cache = self._program_cache
copied_static_fn._descriptor_cache = self._descriptor_cache
copied_static_fn._patched_name = self._patched_name
return copied_static_fn.__get__(
memo[id(self.class_instance)], type(self.class_instance)
)
else:
return self._dygraph_function
@property
def inputs(self) -> list[Any]:
raise NotImplementedError("Not implemented yet.")
@property
def outputs(self) -> list[Any]:
raise NotImplementedError("Not implemented yet.")
@property
def main_program(self) -> Program:
raise NotImplementedError("Not implemented yet.")
@property
def program_cache(self) -> ProgramCache:
raise NotImplementedError("Not implemented yet.")
@property
def function_spec(self) -> FunctionSpec:
raise NotImplementedError("Not implemented yet.")
def raise_error_template(func_str):
def _raise_error(*args, **kwargs):
error_template = (
"Can't call {func} when full_graph=False. "
"Use paddle.jit.to_static(full_graph=True) instead."
)
raise RuntimeError(error_template.format(func=func_str))
return _raise_error
class SymbolicStaticFunction(StaticFunction):
def __init__(self, function, input_spec=None, **kwargs):
if input_spec is not None:
warnings.warn(
"full_graph=False don't support input_spec arguments. It will not produce any effect.\n"
"You can set full_graph=True, then you can assign input spec.\n"
)
super().__init__(function, input_spec, **kwargs)
self.last_call_input_spec = None
def _perform_call(self, *args, **kwargs):
from ..sot import symbolic_translate
args, kwargs = self._function_spec.unified_args_and_kwargs(args, kwargs)
if not use_specialized_device():
cuda_pinned_tensors_move_to_excepted_place(args)
(
input_args_with_spec,
input_kwargs_with_spec,
) = self._function_spec.args_to_input_spec(args, kwargs)
self.last_call_input_spec = input_args_with_spec
build_strategy = self._kwargs.get("build_strategy", None)
backend = self._kwargs.get("backend", None)
traced_fun = symbolic_translate(
self._dygraph_function,
build_strategy=build_strategy,
training=self._is_train_mode(),
backend=backend,
)
if self.class_instance is not None:
args = (self.class_instance, *args)
return traced_fun(*args, **kwargs)
@property
def code(self):
raise_error_template("code")()
@property
def concrete_program(self):
raise_error_template("concrete_program")()
concrete_program_specify_input_spec = raise_error_template(
"concrete_program_specify_input_spec"
)
get_concrete_program = raise_error_template("get_concrete_program")
get_concrete_program_with_cache_key = raise_error_template(
"get_concrete_program_with_cache_key"
)
get_traced_count = raise_error_template("get_traced_count")
@property
def inputs(self):
raise_error_template("inputs")()
@property
def outputs(self):
raise_error_template("outputs")()
@property
def main_program(self):
raise_error_template("main_program")()
@property
def program_cache(self):
raise_error_template("program_cache")()
@property
def function_spec(self):
raise_error_template("function_spec")()
class ASTStaticFunction(StaticFunction[_InputT, _RetT]):
"""
Wrapper class to Manage program conversion of decorated function.
"""
def __init__(self, function, input_spec=None, **kwargs):
super().__init__(function, input_spec, **kwargs)
def _perform_call(self, *args, **kwargs):
# 1. trace ops from dygraph layers and cache the generated program.
args, kwargs = self._function_spec.unified_args_and_kwargs(args, kwargs)
try:
_, partial_program_layer = self.get_concrete_program(
*args, **kwargs, is_train=self._is_train_mode()
)
# 2. synchronize self.training attribute.
if isinstance(self.class_instance, layers.Layer):
partial_program_layer.training = self.class_instance.training
else:
partial_program_layer.training = self._training
# 3. return outputs.
try:
return partial_program_layer(args)
except Exception as e:
if not hasattr(e, error.ERROR_DATA):
# runtime error
error.attach_error_data(e, in_runtime=True)
raise
except Exception as e:
error_data = getattr(e, error.ERROR_DATA, None)
if error_data:
error_data.raise_new_exception()
else:
logging_utils.warn(
"Please file an issue at 'https://github.com/PaddlePaddle/Paddle/issues'"
f" if you can't handle this {type(e)} yourself."
)
raise e
def get_concrete_program(
self, *args: _InputT.args, **kwargs: _InputT.kwargs
) -> tuple[ConcreteProgram, PirPartialProgramLayer]:
"""
Returns traced concrete program and inner executable partial layer.
Args:
*args(tuple): input arguments values or InputSpec
**kwargs(dict) : input kwargs values.
Returns:
Traced ConcreteProgram and executable translated Layer.
"""
self._raise_when_property()
with_hook = kwargs.get("with_hook", False)
is_train = kwargs.get("is_train", True)
is_prim_infer = kwargs.get("is_prim_infer", False)
if "is_train" in kwargs:
kwargs.pop("is_train")
if "with_hook" in kwargs:
kwargs.pop("with_hook")
if "is_prim_infer" in kwargs:
kwargs.pop("is_prim_infer")
# 1. unify args/kwargs and replace Tensor with InputSpec
if len(args) != len(self._function_spec.args_name):
args, kwargs = self._function_spec.unified_args_and_kwargs(
args, kwargs
)
(
input_args_with_spec,
input_kwargs_with_spec,
) = self._function_spec.args_to_input_spec(args, kwargs)
# 2. generate cache key
cache_key = CacheKey(
self._function_spec,
input_args_with_spec,
input_kwargs_with_spec,
self.class_instance,
**self._kwargs,
with_hook=with_hook,
is_train=is_train,
)
if is_prim_infer:
(
concrete_program,
partial_program_layer,
) = self._program_cache.get_program_without_cache(cache_key)
else:
# 3. check whether hit the cache or build a new program for the input arguments
concrete_program, partial_program_layer = self._program_cache[
cache_key
]
return concrete_program, partial_program_layer
def get_concrete_program_with_cache_key(
self, cached_key: CacheKey
) -> tuple[ConcreteProgram, PartialProgramLayer | PirPartialProgramLayer]:
"""
Returns traced concrete program and inner executable partial layer by cached key.
Args:
cached_key(CacheKey): The cached key use to get concrete program.
Returns:
Traced ConcreteProgram and executable translated Layer.
"""
self._raise_when_property()
(
concrete_program,
partial_program_layer,
) = self._program_cache.get_program_without_cache(cached_key)
return concrete_program, partial_program_layer
def get_traced_count(self) -> int:
"""
Returns the number of traced programs for the decorated function.
"""
return len(self._program_cache)
@property
def code(self) -> str:
"""
Returns the source code of transformed static function for debugging.
"""
static_func = convert_to_static(self.dygraph_function)
source_code = func_to_source_code(static_func)
return source_code
@property
def concrete_program(self) -> ConcreteProgram:
"""
Returns recent ConcreteProgram instance of decorated function.
Examples:
.. code-block:: pycon
>>> # doctest: +SKIP('`paddle.jit.to_static` can not run in xdoctest')
>>> import paddle
>>> from paddle.jit import to_static
>>> from paddle.static import InputSpec
>>> paddle.disable_static()
>>> def foo(x, y):
... z = x + y
... return z
>>> # usage 1:
>>> decorated_foo = to_static(foo, input_spec=[InputSpec([10], name='x'), InputSpec([10], name='y')])
>>> print(decorated_foo.concrete_program)
>>> # usage 2:
>>> decorated_foo = to_static(foo)
>>> out_foo = decorated_foo(paddle.rand([10]), paddle.rand([10]))
>>> print(decorated_foo.concrete_program)
"""
return self.concrete_program_specify_input_spec(input_spec=None)
def concrete_program_specify_input_spec(
self,
input_spec: NestedSequence[InputSpec] | None = None,
with_hook: bool = False,
is_prim_infer: bool = False,
) -> ConcreteProgram:
"""
Returns recent ConcreteProgram instance of decorated function while
specifying input_spec. If the self._function_spec already has
input_spec, it will check the compatibility of input input_spec and
the self._function_spec.input_spec. If input input_spec=None, then
this method uses self._function_spec.input_spec
args:
input_spec (list[InputSpec], optional): Describes the input of
the translate function.
"""
self._raise_when_property()
# if specific the `input_spec`, the length of program_cache will always 1,
# else, return the last one.
cached_program_len = len(self._program_cache)
# If specific `input_spec`, apply conversion from dygraph layers into static Program.
# NOTE(jiabin): is_prim_infer indicates this method called by paddle.jit.save and it is worked in prim mode
desired_input_spec = input_spec
if self._function_spec.input_spec is not None:
if input_spec is not None and not input_specs_compatible(
flatten(input_spec), flatten(self._function_spec.input_spec)
):
raise ValueError(
f"The `input_spec`: {input_spec} used to construct concrete_program is conflict with the `input_spec`: {self._function_spec.input_spec} in `@paddle.jit.to_static`"
)
# NOTE(chenweihang): we should always translated program based on the `input_spec`
# decorated on forward if it is valid
desired_input_spec = self._function_spec.input_spec
if input_spec is not None:
logging_utils.warn(
f"\n\nYou have specified `input_spec` both in function definition (higher priority) and `paddle.jit.save` (will be ignored.)\n\n\t Using: {desired_input_spec}\n\n\t Ignore: {input_spec}\n"
)
has_input_spec = desired_input_spec is not None
if has_input_spec:
concrete_program, _ = self.get_concrete_program(
*desired_input_spec,
with_hook=with_hook,
is_train=self._is_train_mode(),
is_prim_infer=is_prim_infer,
)
return concrete_program
else:
if cached_program_len != 0:
logging_utils.warn(
"No input_spec is found, save cached program instead"
)
if cached_program_len > 1:
logging_utils.warn(
f"Current {self._function_spec} has more than one cached programs: {cached_program_len}, the last traced program will be return by default."
)
cache_key = self._program_cache._recent_cache_key
if with_hook:
cache_key.kwargs["with_hook"] = True
if is_prim_infer:
(
concrete_program,
_,
) = self.get_concrete_program_with_cache_key(cache_key)
return concrete_program
else:
concrete_program, _ = self._program_cache[cache_key]
return concrete_program
else:
raise ValueError(
f"No valid transformed program for {self._function_spec}.\n\t Please specific `input_spec` in `@paddle.jit.to_static` or feed input tensor to call the decorated function at once.\n"
)
@property
def inputs(self) -> list[Any]:
"""
Returns input tensors of recent converted static program.
"""
self._raise_when_property()
concrete_program = self.concrete_program
inputs = [
var
for var in flatten(concrete_program.inputs)
if isinstance(var, (framework.Variable, Value))
]
return inputs
@property
def outputs(self) -> list[Any]:
"""
Returns output tensors of recent converted static program.
"""
self._raise_when_property()
concrete_program = self.concrete_program
outputs = [
var
for var in flatten(concrete_program.outputs)
if isinstance(var, (framework.Variable, Value))
]
return outputs
@property
def main_program(self) -> Program:
"""
Returns recent converted static main program.
"""
self._raise_when_property()
concrete_program = self.concrete_program
main_program = concrete_program.main_program
return main_program
@property
def program_cache(self) -> ProgramCache:
return self._program_cache
@property
def function_spec(self) -> FunctionSpec:
return self._function_spec
def _verify_init_in_dynamic_mode(class_instance):
"""
Verifies the instance is initialized in dynamic mode.
"""
if isinstance(class_instance, layers.Layer):
if not class_instance._init_in_dynamic_mode:
raise RuntimeError(
" `paddle.jit.to_static` is only available in dynamic mode. Please call `paddle.disable_static()` before "
f"initializing your Layer class `{class_instance}` . Because parameters of Layer class should be initialized firstly "
"in dynamic mode while applying transformation."
)
class HookHelper:
"""
Only For converting pre/post hooks operation in outermost layer while jit.save.
Because hooks in sublayer have been processed automatically.
"""
def __init__(self, func, class_instance, with_hook=False):
self.func = func
self.class_instance = class_instance
self.with_hook = with_hook
self.need_apply_hook = (
with_hook
and isinstance(self.class_instance, layers.Layer)
and func.__name__ == "forward"
)
def apply_pre_hooks(self, inputs):
"""
Apply _forward_pre_hooks from outermost layer
"""
if not self.need_apply_hook:
return inputs
inputs = inputs[1:]
for forward_pre_hook in self.class_instance._forward_pre_hooks.values():
hook_result = forward_pre_hook(self.class_instance, inputs)
if hook_result is not None:
if not isinstance(hook_result, tuple):
hook_result = (hook_result,)
inputs = hook_result
return [self.class_instance, *list(inputs)]
def apply_post_hooks(self, inputs, outputs):
"""
Apply _forward_post_hooks from outermost layer
"""
if not self.need_apply_hook:
return outputs
inputs = inputs[1:]
for (
forward_post_hook
) in self.class_instance._forward_post_hooks.values():
hook_result = forward_post_hook(
self.class_instance, inputs, outputs
)
if hook_result is not None:
outputs = hook_result
inputs.insert(0, self.class_instance)
return outputs
class ConcreteProgram:
__slots__ = [
'inputs',
'outputs',
'main_program',
"startup_program",
"parameters",
"function",
'kwargs',
'constraints',
]
def __init__(
self,
inputs,
outputs,
parameters,
function,
main_program,
startup_program=None,
*,
constraints=None,
**kwargs,
):
self.inputs = inputs
self.outputs = outputs
# Avoid mutable default argument pitfall (new list per instance)
self.constraints = constraints if constraints is not None else []
self.main_program = main_program
self.startup_program = startup_program
self.parameters = parameters
self.function = function
self.kwargs = kwargs
@staticmethod
def extract_constraints(input_specs):
"""
Extract constraints from input_specs
"""
input_specs = flatten(input_specs)
constraints = []
for input_spec in input_specs:
if not hasattr(input_spec, "ranges"):
return []
if len(input_spec.ranges):
for range in input_spec.ranges:
constraints.append((input_spec.name, range))
return constraints
@staticmethod
@switch_to_static_graph
def pir_from_func_spec(
func_spec, input_spec, input_kwargs_spec, class_instance, **kwargs
):
"""
Builds the main_program with specialized inputs and returns outputs
of program as fetch_list.
Args:
func_spec(FunctionSpec): A FunctionSpec instance for decorated function.
input_spec(list[InputSpec]):
"""
backend = kwargs["backend"]
# verify the instance is initialized in imperative mode.
_verify_init_in_dynamic_mode(class_instance)
# Transforms dygraph function into static function and caches it.
dygraph_function = func_spec.dygraph_function
static_func = convert_to_static(dygraph_function)
# apply pre\post hook for outermost layer
hook_helper = HookHelper(
dygraph_function, class_instance, kwargs.get("with_hook", False)
)
main_program, startup_program = ir_static.Program(), ir_static.Program()
# Note: The random seed should be synchronized into cached program
# if set in `fluid.dygraph_guard` because some ops rely on it, such as
# `fluid.layers.dropout`.
main_program.random_seed = (
paddle.static.default_main_program().random_seed
)
startup_program.random_seed = (
paddle.static.default_startup_program().random_seed
)
with (
ir_static.program_guard(main_program, startup_program),
graph_tracing_guard(main_program) as ctx,
):
# 1. Adds `paddle.static.data` layers for input if needed
static_inputs, program_inputs = (
func_spec.pir_to_static_inputs_with_spec(
input_spec, main_program
)
)
_kwargs, _ = func_spec.pir_to_static_inputs_with_spec(
input_kwargs_spec, main_program
)
if class_instance:
static_inputs = (
class_instance,
*list(static_inputs),
)
program_inputs = (
class_instance,
*list(program_inputs),
)
# 2. Builds program only once and returns the output Variables.
with (
param_guard(get_parameters(class_instance, True)),
param_guard(get_buffers(class_instance, True)),
backend_guard(backend),
):
try:
# only for jit.save, do nothing while train and eval process
inputs = hook_helper.apply_pre_hooks(static_inputs)
if _kwargs:
outputs = static_func(*inputs, **_kwargs)
else:
outputs = static_func(*inputs)
outputs = hook_helper.apply_post_hooks(inputs, outputs)
except BaseException as e:
# NOTE: If e is raised in compile time, e should be attached to ERROR_DATA here.
error.attach_error_data(e)
error_data = getattr(e, error.ERROR_DATA, None)
if error_data:
error_data.raise_new_exception()
raise
if outputs is not None:
need_wrap_into_list = (
not isinstance(outputs, (tuple, list)) or len(outputs) == 1
)
if need_wrap_into_list:
outputs = [outputs]
main_program = update_op_callstack_with_origin_info(main_program)
if not os.environ.get("stride_in_no_check_dy2st_diff", "0") == "1":
check_view_api_used_by_inplace(main_program)
constraints = ConcreteProgram.extract_constraints(input_spec)
return ConcreteProgram(
inputs=program_inputs,
outputs=outputs,
parameters=ctx.get_params_with_values(),
function=dygraph_function,
main_program=main_program,
startup_program=startup_program,
constraints=constraints,
**kwargs,
)
# TODO(@xiongkun): remove after new ir is switch
@staticmethod
@switch_to_static_graph
def from_func_spec(
func_spec, input_spec, input_kwargs_spec, class_instance, **kwargs
):
"""
Builds the main_program with specialized inputs and returns outputs
of program as fetch_list.
Args:
func_spec(FunctionSpec): A FunctionSpec instance for decorated function.
input_spec(list[InputSpec]):
"""
# verify the instance is initialized in imperative mode.
_verify_init_in_dynamic_mode(class_instance)
# Transforms dygraph function into static function and caches it.
dygraph_function = func_spec.dygraph_function
static_func = convert_to_static(dygraph_function)
# apply pre\post hook for outermost layer
hook_helper = HookHelper(
dygraph_function, class_instance, kwargs.get("with_hook", False)
)
main_program, startup_program = framework.Program(), framework.Program()
# Note: The random seed should be synchronized into cached program
# if set in `base.dygraph_guard` because some ops rely on it, such as
# `base.layers.dropout`.
main_program.random_seed = (
paddle.static.default_main_program().random_seed
)
startup_program.random_seed = (
paddle.static.default_startup_program().random_seed
)
ProgramTranslator.get_instance()._amp_records.clear()
with (
framework.program_guard(main_program, startup_program),
to_static_mode_guard(is_to_static=True),
):
# 1. Adds `paddle.static.data` layers for input if needed
static_inputs = func_spec.to_static_inputs_with_spec(
input_spec, main_program
)
_kwargs = func_spec.to_static_inputs_with_spec(
input_kwargs_spec, main_program
)
if class_instance:
static_inputs = (
class_instance,
*list(static_inputs),
)
# 2. Builds program only once and returns the output Variables.
with (
param_guard(get_parameters(class_instance, True)),
param_guard(get_buffers(class_instance, True)),
):
try:
# only for jit.save, do nothing while train and eval process
inputs = hook_helper.apply_pre_hooks(static_inputs)
if _kwargs:
outputs = static_func(*inputs, **_kwargs)
else:
outputs = static_func(*inputs)
outputs = hook_helper.apply_post_hooks(inputs, outputs)
except BaseException as e:
# NOTE: If e is raised in compile time, e should be attached to ERROR_DATA here.
error.attach_error_data(e)
error_data = getattr(e, error.ERROR_DATA, None)
if error_data:
error_data.raise_new_exception()
raise
# 3. Gets all ParamBases and buffered VarBases in the function
all_parameters_and_buffers = (
ProgramTranslator.get_instance()._params_recorder.pop(
main_program
)
)
if outputs is not None:
need_wrap_into_list = (
not isinstance(outputs, (tuple, list)) or len(outputs) == 1
)
if need_wrap_into_list:
outputs = [outputs]
main_program = update_op_callstack_with_origin_info(main_program)
return ConcreteProgram(
inputs=static_inputs,
outputs=outputs,
parameters=all_parameters_and_buffers,
function=dygraph_function,
main_program=main_program,
startup_program=startup_program,
**kwargs,
)
def _program_hash(program):
"""
because program is not deleted while calling from_func_spec.
so it's ok to use id(program)
"""
return id(program)
class ParametersRecorder:
def __init__(self):
self.params_dict = {}
@synchronized
def add(self, program, param):
"""use the default_program as key, append param the parameter list."""
key = _program_hash(program)
if key not in self.params_dict:
self.params_dict[key] = set()
params = self.params_dict[key]
params.add(param)
def pop(self, program):
params = self.params_dict.get(_program_hash(program))
if params is None:
return []
del self.params_dict[_program_hash(program)]
params = list(params)
params.sort(key=lambda x: x.name)
return params
class InplaceMap:
def __init__(self):
self.params_dict = {}
@synchronized
def add(self, program, id, param):
"""use the default_program as key, append param the parameter list."""
key = _program_hash(program)
if key not in self.params_dict:
self.params_dict[key] = {}
params = self.params_dict[key]
params[id] = param
def get(self, program, id):
params = self.params_dict.get(_program_hash(program))
if params is None:
return None
if id not in params:
return None
root_var = params[id]
saved = []
while root_var.desc.id() in params.keys():
saved.append(root_var)
root_var = params[root_var.desc.id()]
for var in saved:
params[var.desc.id()] = root_var
return root_var
def restore_checkpoint(self, checkpoint):
# InplaceMap is a nested effect.
# when enter a block, we should save a checkpoint
# when exit a block, we should restore a checkpoint
# for example:
# if cond > 0:
# x [:] = 0
# return x
# x[:] only effect current cond block, we should restore in false block.
self.params_dict = checkpoint
def save_checkpoint(self):
ckp = {}
for program_id, params in self.params_dict.items():
new_params = dict(params.items())
ckp[program_id] = new_params
return ckp
class PirPrimHooker(PirPartialProgramLayerHook):
def __init__(self, original_program, backend):
self.backend = backend
self.custom_vjps = set()
with backend_guard(self.backend):
if core._is_all_prim_enabled():
self.custom_vjps = {
op.name()
for op in original_program.global_block().ops
if core.has_custom_vjp(op)
}
def before_append_backward(self, forward_program, src_vars):
with backend_guard(self.backend):
if core._is_fwd_prim_enabled():
dst_vars = decomposition.decompose(
forward_program, src_vars, blacklist=self.custom_vjps
)
return forward_program, dst_vars
return forward_program, src_vars
def after_append_backward(
self,
whole_program,
inputs,
src_vars,
grad_outputs,
forward_end_idx,
backward_start_idx,
):
with backend_guard(self.backend):
if core._is_fwd_prim_enabled() and len(self.custom_vjps) != 0:
backward_length = (
len(whole_program.global_block().ops) - forward_end_idx
)
# decompose forward program
dst_vars = decomposition.decompose(
whole_program,
src_vars,
whitelist=self.custom_vjps,
end_index=forward_end_idx,
)
new_start_index = (
len(whole_program.global_block().ops) - backward_length
)
# decompose backward program
dst_vars = decomposition.decompose(
whole_program,
dst_vars,
whitelist=self.custom_vjps,
start_index=new_start_index,
)
return whole_program, new_start_index, dst_vars
return whole_program, forward_end_idx, src_vars
def after_infer(self, infer_program):
with backend_guard(self.backend):
if core._is_fwd_prim_enabled():
targets = decomposition.decompose(
infer_program.program, infer_program.out_values
)
infer_program.out_values = targets
infer_program.forward_range = (
0,
len(infer_program.program.global_block().ops),
)
return
class PirAutoRecomputeHooker(PirPartialProgramLayerHook):
def __init__(self, recompute_ops=None):
self.recompute_ops = recompute_ops
def before_append_backward(self, forward_program, src_vars):
return forward_program, src_vars
def after_append_backward(
self,
whole_program,
inputs,
src_vars,
grad_outputs,
forward_end_idx,
backward_start_idx,
):
if core._enable_auto_recompute():
whole_program, forward_end_idx = decomposition.auto_recompute(
whole_program,
inputs,
src_vars,
grad_outputs,
forward_end_idx,
backward_start_idx,
)
return whole_program, forward_end_idx, src_vars
class ProgramCache:
"""
Wrapper class for the program functions defined by dygraph function.
"""
def __init__(self):
# {hash_id : (concrete_program, partial_layer)}
self._caches = collections.OrderedDict()
# trace mostly recent used program
self._recent_key = None
self._recent_cache_key = None
def _build_once(self, cache_key):
if use_pir_api():
concrete_program = ConcreteProgram.pir_from_func_spec(
func_spec=cache_key.function_spec,
input_spec=cache_key.input_args_with_spec,
input_kwargs_spec=cache_key.input_kwargs_with_spec,
class_instance=cache_key.class_instance,
**cache_key.kwargs,
)
else:
concrete_program = ConcreteProgram.from_func_spec(
func_spec=cache_key.function_spec,
input_spec=cache_key.input_args_with_spec,
input_kwargs_spec=cache_key.input_kwargs_with_spec,
class_instance=cache_key.class_instance,
**cache_key.kwargs,
)
backend = cache_key.kwargs['backend']
if not use_pir_api():
# decrease prim_is_enable() call to decrease print log
if prim_or_cinn_is_enabled(
cache_key.kwargs['build_strategy'], backend
):
for var in concrete_program.main_program.list_vars():
if var.type not in NO_SHAPE_VAR_TYPE and -1 in var.shape:
warnings.warn(
f"Now prim and cinn do not support -1 shape, but the shape of var {var.name} is {var.shape}"
)
if use_pir_api():
from .pir_partial_program import partial_program_from
partial_program = partial_program_from(
concrete_program, cache_key.class_instance is not None
)
else: # TODO(pir): remove later.
from .partial_program import partial_program_from
partial_program = partial_program_from(
concrete_program, cache_key.class_instance is not None
)
with backend_guard(backend):
if core._is_fwd_prim_enabled():
if use_pir_api():
partial_program.add_hooker(
PirPrimHooker(concrete_program.main_program, backend)
)
else:
partial_program.set_hooker(
PrimHooker(concrete_program.main_program, backend)
)
if use_pir_api() and core._enable_auto_recompute():
partial_program.add_hooker(PirAutoRecomputeHooker())
return concrete_program, partial_program
def __getitem__(self, item):
if not isinstance(item, CacheKey):
raise ValueError(
f'type(item) should be CacheKey, but received {type_name(item)}'
)
item_id = hash(item)
self._recent_cache_key = item
self._recent_key = item_id
if item_id not in self._caches:
self._caches[item_id] = self._build_once(item)
# Note: raise warnings if number of traced program is more than `max_tracing_count`
current_tracing_count = len(self._caches)
if current_tracing_count > MAX_TRACED_PROGRAM_COUNT:
logging_utils.warn(
f"Current traced program number: {current_tracing_count} > `max_tracing_count`:{MAX_TRACED_PROGRAM_COUNT}. Too much cached programs will bring expensive overhead. "
"The reason may be: (1) passing tensors with different shapes, (2) passing python objects instead of tensors."
)
return self._caches[item_id]
def get_program_without_cache(self, cache_key):
return self._build_once(cache_key=cache_key)
def get_program(self, item):
if not isinstance(item, CacheKey):
raise ValueError(
f"Input item's type should be FunctionSpec, but received {type_name(item)}"
)
item_id = hash(item)
if item_id not in self._caches:
raise RuntimeError(
"Failed to find program for input item, please decorate input function by `@paddle.jit.to_static`."
)
return self._caches[item_id]
def last(self):
assert len(self._caches) >= 1, (
"No valid cached program in ProgramCache."
)
assert self._recent_key is not None
return self._recent_key, self._caches[self._recent_key]
def __len__(self):
return len(self._caches)
def concrete_programs(self):
return [cp for key, (cp, _) in self._caches.items()]
def clear(self):
self._caches = collections.OrderedDict()
class PrimHooker(PartialProgramLayerHook):
def __init__(self, original_program, backend):
self.backend = backend
self.custom_vjps = set()
with backend_guard(self.backend):
if core._is_all_prim_enabled():
self.custom_vjps = {
op.type
for op in original_program.block(0).ops
if core.has_comp_grad_op_maker(op.type)
}
def before_append_backward(self, forward_program):
with backend_guard(self.backend):
if core._is_fwd_prim_enabled():
_to_prim(forward_program.blocks, blacklist=self.custom_vjps)
return forward_program
def after_append_backward(self, whole_program, backward_start_idx):
with backend_guard(self.backend):
backward_length = (
len(whole_program.block(0).ops) - backward_start_idx
)
if core._is_fwd_prim_enabled() and len(self.custom_vjps) != 0:
# only process backward part of block
_to_prim(whole_program.blocks, backward_length=backward_length)
new_start_index = len(whole_program.block(0).ops) - backward_length
if backward_length > 0:
# only process forward part of block
_to_prim(whole_program.blocks, start_idx=new_start_index)
return whole_program, new_start_index
def after_infer(self, infer_program):
with backend_guard(self.backend):
if core._is_fwd_prim_enabled():
_to_prim(infer_program.block(0))
return infer_program
class ProgramTranslator:
"""
Class to translate dygraph function into static graph function. The object
of this class is a singleton.
Args:
None.
Returns:
ProgramTranslator: the singleton object.
Examples:
.. code-block:: pycon
>>> import paddle
>>> # Two methods get same object because ProgramTranslator is a singleton
>>> paddle.jit.dy2static.program_translator.ProgramTranslator()
>>> paddle.jit.dy2static.program_translator.ProgramTranslator.get_instance()
"""
_singleton_lock = threading.Lock()
_instance = None
@synchronized
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kwargs)
cls._instance._initialized = False
return cls._instance
@classmethod
def get_instance(cls):
if cls._instance is None:
with cls._singleton_lock:
cls._instance = cls()
return cls._instance
@classmethod
def reset(cls):
if cls._instance is not None:
cls._instance._initialized = False
cls._instance.__init__()
def __init__(self):
# To make sure that calls __init__ only once.
if self._initialized:
return
self._initialized = True
self._program_cache = ProgramCache()
self._params_recorder = ParametersRecorder()
self._inplace_map = InplaceMap()
self._amp_records: dict[int, list[tuple[AmpOptions, int, int]]] = {}
self.enable_to_static = True
def enable(self, enable_to_static):
check_type(
enable_to_static,
"enable_to_static",
bool,
"ProgramTranslator.enable",
)
self.enable_to_static = enable_to_static
def enable_to_static(enable_to_static_bool: bool) -> None:
"""
Enable or disable the converting from imperative to static graph by
ProgramTranslator globally.
Args:
enable_to_static_bool (bool): True or False to enable or disable converting to static.
Returns:
None.
Examples:
.. code-block:: pycon
>>> import paddle
>>> @paddle.jit.to_static
>>> def func(x):
... if paddle.mean(x) > 0:
... x_v = x - 1
... else:
... x_v = x + 1
... return x_v
>>> paddle.jit.enable_to_static(False)
>>> x = paddle.ones([1, 2])
>>> # ProgramTranslator is disabled so the func is run in dygraph
>>> print(func(x))
Tensor(shape=[1, 2], dtype=float32, place=Place(cpu), stop_gradient=True,
[[0., 0.]])
"""
check_type(
enable_to_static_bool,
"enable_to_static_bool",
bool,
"paddle.jit.enable_to_static",
)
_program_trans = ProgramTranslator()
_program_trans.enable(enable_to_static_bool)
@switch_to_static_graph
def _to_prim(
blocks,
blacklist=frozenset(),
whitelist=frozenset(),
start_idx=-1,
backward_length=-1,
):
"""Switch to static graph and call to_prim."""
# TODO(Aurelius84): Fix this cycle import problem
from paddle.incubate.autograd import primapi
primapi.to_prim(
blocks,
blacklist=blacklist,
whitelist=whitelist,
start_idx=start_idx,
backward_length=backward_length,
)