chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,878 @@
|
||||
# Copyright (c) 2022 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 re
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
|
||||
import paddle
|
||||
from paddle.autograd.py_layer import PyLayerMeta
|
||||
from paddle.base.data_feeder import convert_dtype
|
||||
from paddle.base.dygraph.base import _convert_into_variable, in_to_static_mode
|
||||
from paddle.base.framework import Variable, core, default_main_program
|
||||
from paddle.framework import use_pir_api
|
||||
from paddle.jit.utils import OrderedSet
|
||||
from paddle.pir import Value
|
||||
from paddle.static.amp.fp16_utils import AmpOptions
|
||||
from paddle.utils import is_sequence, map_structure
|
||||
|
||||
from .py_layer import StaticPyLayer
|
||||
from .utils import (
|
||||
RETURN_NO_VALUE_VAR_NAME,
|
||||
Dygraph2StaticException,
|
||||
GetterSetterHelper,
|
||||
UndefinedVar,
|
||||
create_undefined_variable,
|
||||
)
|
||||
|
||||
__all__ = []
|
||||
|
||||
|
||||
def to_static_variable(x, dtype=None):
|
||||
'''
|
||||
Translate a Python Tensor to PaddlePaddle static graph Tensor
|
||||
'''
|
||||
if isinstance(x, bool):
|
||||
dtype = 'bool' if dtype is None else dtype
|
||||
return paddle.full(shape=[], dtype=dtype, fill_value=x)
|
||||
if isinstance(x, float):
|
||||
dtype = 'float64' if dtype is None else dtype
|
||||
return paddle.full(shape=[], dtype=dtype, fill_value=x)
|
||||
if isinstance(x, int):
|
||||
dtype = 'int64' if dtype is None else dtype
|
||||
return paddle.full(shape=[], dtype=dtype, fill_value=x)
|
||||
if not use_pir_api() and (isinstance(x, UndefinedVar) or x is None):
|
||||
"""
|
||||
for early return case, we need a variable to represent None, current we use data_layer_not_check.
|
||||
"""
|
||||
return create_undefined_variable()
|
||||
if is_sequence(x):
|
||||
return map_structure(to_static_variable, x)
|
||||
return x
|
||||
|
||||
|
||||
def convert_attr(x, attr):
|
||||
# TODO(cleanup-legacy-ir): In PIR mode, the size attr in
|
||||
# Value and Tensor are unified. So we don't need to transform
|
||||
# the size attr into a method call. The AttributeJstTransformer and
|
||||
# convert_attr can be safely removed.
|
||||
if (
|
||||
isinstance(x, Variable)
|
||||
and not isinstance(x, paddle.Tensor)
|
||||
and attr == "size"
|
||||
):
|
||||
return x.size()
|
||||
else:
|
||||
return getattr(x, attr)
|
||||
|
||||
|
||||
def convert_load(x):
|
||||
# convert dygraph `PyLayer` into StaticPyLayer
|
||||
if isinstance(x, PyLayerMeta):
|
||||
return StaticPyLayer(x)
|
||||
|
||||
if in_to_static_mode():
|
||||
if isinstance(x, paddle.Tensor):
|
||||
"""
|
||||
TODO:(@xiongkun) may run convert_load in dygraph mode, which should be fixed.
|
||||
"""
|
||||
return _convert_into_variable(x)
|
||||
|
||||
# get the new output of the var
|
||||
if isinstance(x, Value):
|
||||
from paddle.jit.dy2static.parameter_recorder import (
|
||||
_global_inplace_map,
|
||||
)
|
||||
|
||||
new_var = _global_inplace_map.get(
|
||||
paddle.static.default_main_program(), x
|
||||
)
|
||||
if new_var is not None:
|
||||
return new_var
|
||||
|
||||
if isinstance(x, Variable):
|
||||
cur_block = default_main_program().current_block()
|
||||
|
||||
from paddle.jit.dy2static.program_translator import (
|
||||
ProgramTranslator,
|
||||
)
|
||||
|
||||
new_var = ProgramTranslator.get_instance()._inplace_map.get(
|
||||
cur_block.program, x.desc.id()
|
||||
)
|
||||
if new_var is not None:
|
||||
return new_var
|
||||
|
||||
if x is paddle.amp.auto_cast and not use_pir_api():
|
||||
return convert_auto_cast
|
||||
|
||||
return x
|
||||
|
||||
|
||||
def indexable(x, code=None):
|
||||
if isinstance(x, (Variable, Value)):
|
||||
return x
|
||||
elif hasattr(x, '__iter__'):
|
||||
return list(x)
|
||||
elif hasattr(x, '__len__') and hasattr(
|
||||
x, '__getitem__'
|
||||
): # used for customed type and non-iterable type.
|
||||
return x
|
||||
else:
|
||||
raise RuntimeError("X can't be convert into indexable.")
|
||||
|
||||
|
||||
def unpack_by_structure(target, structure):
|
||||
"""unified unpack interface for paddle and python."""
|
||||
if isinstance(target, (Variable, Value)):
|
||||
return _unpack_by_structure_paddle(target, structure)
|
||||
else:
|
||||
return _unpack_by_structure_python(target, structure)
|
||||
|
||||
|
||||
def _unpack_by_structure_python(target, structure):
|
||||
"""TODO(xiongkun): analysis the differences between python and paddle unpack."""
|
||||
return _unpack_by_structure_paddle(target, structure)
|
||||
|
||||
|
||||
def _unpack_by_structure_paddle(target, structure):
|
||||
if structure == 1:
|
||||
return target
|
||||
ret = []
|
||||
for idx, ele in enumerate(structure):
|
||||
if ele == 1:
|
||||
ret.append(target[idx])
|
||||
continue
|
||||
if isinstance(ele, list):
|
||||
ret.append(unpack_by_structure(target[idx], ele))
|
||||
continue
|
||||
raise AssertionError("structure element must be 1 or list")
|
||||
return ret
|
||||
|
||||
|
||||
def convert_while_loop(
|
||||
cond, body, getter, setter, return_name_ids=None, push_pop_names=None
|
||||
):
|
||||
"""
|
||||
A function representation of a Python ``while`` statement.
|
||||
|
||||
Args:
|
||||
cond(Callable): A callable object that returns a boolean variable to control whether to execute the loop body. It takes ``loop_vars`` as arguments.
|
||||
body(Callable): A callable object that returns a tuple or list of variables with the same arguments ``loops_vars`` as ``cond`` .
|
||||
get_args(callable): Get all arguments that needed in true_fn and false_fn.
|
||||
set_args(callable): Update arguments that modified in trure_fn and false_fn.
|
||||
return_name_ids(list[string], optional): the returned names.
|
||||
push_pop_names(list[string], optional): the names on which called .append() or .pop().
|
||||
|
||||
Returns:
|
||||
A list or tuple of variables which returned by ``body``.
|
||||
"""
|
||||
|
||||
# NOTE: It may be slower if cond is very expensive, but usually cond is just O(1).
|
||||
# If loop_vars is changed during cond callable, then it causes bug, but current logical_and/logical_not/... doesn't change the loop_vars.
|
||||
pred = cond()
|
||||
if isinstance(pred, (Variable, Value)):
|
||||
_run_paddle_while(
|
||||
cond, body, getter, setter, return_name_ids, push_pop_names
|
||||
)
|
||||
else:
|
||||
_run_py_while(cond, body, getter, setter)
|
||||
|
||||
|
||||
def _convert_tensor_array_if_necessary(setterhelper, push_pop_names):
|
||||
push_pop_vars = setterhelper.get(push_pop_names)
|
||||
if push_pop_vars is None:
|
||||
return
|
||||
|
||||
def maybe_to_tensor_array(v):
|
||||
if isinstance(v, list):
|
||||
dtype = (
|
||||
paddle.base.libpaddle.DataType.UNDEFINED
|
||||
if use_pir_api()
|
||||
else "float32"
|
||||
)
|
||||
return paddle.tensor.create_array(dtype, initialized_list=v)
|
||||
else:
|
||||
return v
|
||||
|
||||
setterhelper.set(
|
||||
push_pop_names, [maybe_to_tensor_array(v) for v in push_pop_vars]
|
||||
)
|
||||
|
||||
|
||||
def _run_paddle_while(
|
||||
cond, body, getter, setter, return_name_ids, push_pop_names
|
||||
):
|
||||
# NOTE: loop_vars of Paddle op `control_flow.while_loop` must be Paddle Tensors.
|
||||
helper = GetterSetterHelper(getter, setter, return_name_ids, push_pop_names)
|
||||
_convert_tensor_array_if_necessary(helper, push_pop_names)
|
||||
|
||||
union_name = (
|
||||
OrderedSet(return_name_ids) if return_name_ids else OrderedSet()
|
||||
) | (OrderedSet(push_pop_names) if push_pop_names else OrderedSet())
|
||||
union_name = list(union_name)
|
||||
|
||||
def new_body_fn(*args):
|
||||
"""wrap the body() and add return value for `while_loop`
|
||||
the args may be differ from getter().
|
||||
"""
|
||||
mutable_loop_vars = args
|
||||
helper.set(union_name, mutable_loop_vars)
|
||||
body()
|
||||
return helper.get(union_name)
|
||||
|
||||
def new_cond_fn(*args):
|
||||
"""cond is a zero-args function, which is not
|
||||
compatible with `while_loop`.
|
||||
"""
|
||||
return cond()
|
||||
|
||||
# UndefinedVar will become data layer not check variable with value=NO_VALUE_MAGIC.
|
||||
loop_vars = [
|
||||
to_static_variable(var) if not isinstance(var, UndefinedVar) else var
|
||||
for var in helper.get(union_name)
|
||||
]
|
||||
helper.set(union_name, loop_vars) # change the non-local var to variable
|
||||
# variable maybe modified to inner var. change it into
|
||||
from paddle.static.nn import while_loop
|
||||
|
||||
loop_vars = while_loop(new_cond_fn, new_body_fn, loop_vars)
|
||||
helper.set(union_name, loop_vars)
|
||||
return loop_vars
|
||||
|
||||
|
||||
def _run_py_while(cond, body, getter, setter):
|
||||
while True:
|
||||
pred = cond()
|
||||
if isinstance(pred, (Variable, Value)):
|
||||
raise Dygraph2StaticException(
|
||||
"python while pred change from bool to variable."
|
||||
)
|
||||
if not pred:
|
||||
break
|
||||
body()
|
||||
|
||||
|
||||
def convert_logical_and(x_func, y_func):
|
||||
"""
|
||||
A function representation of a Python ``and`` statement.
|
||||
|
||||
Args:
|
||||
x_func(callable): x_func() is the left hand operand of ``and`` operator. x_func() is bool or Tensor.
|
||||
y_func(callable): y_func() is the right hand operand of ``and`` operator. y_func() is bool or Tensor.
|
||||
|
||||
Returns:
|
||||
A python bool variable or a bool Tensor.
|
||||
|
||||
NOTE(liym27):
|
||||
1) The operands are executed sequentially according to the running logic of Python. So here the arguments
|
||||
should be callable.
|
||||
2) If the left hand operand is False, the right hand operand should be executed.
|
||||
|
||||
For example:
|
||||
a = x > 1 and y < 1
|
||||
Transformed code:
|
||||
a = paddle.jit.dy2static.convert_logical_and(lambda:x>1, lambda:y<1)
|
||||
|
||||
In `convert_logical_and(lambda:x>1, lambda:y<1)`, `lambda:y<1` must be run after `lambda:x>1`. And
|
||||
if `x>1` is False, `y<1` should NOT be run.
|
||||
"""
|
||||
x_value = x_func()
|
||||
if not isinstance(x_value, (Variable, Value)):
|
||||
return _run_py_logical_and(lambda: x_value, y_func)
|
||||
|
||||
y_value = y_func()
|
||||
if not isinstance(y_value, (Variable, Value)):
|
||||
return _run_py_logical_and(lambda: y_value, lambda: x_value)
|
||||
|
||||
return _run_paddle_logical_and(x_value, y_value)
|
||||
|
||||
|
||||
def _run_paddle_logical_and(x, y):
|
||||
x = cast_bool_if_necessary(x)
|
||||
y = cast_bool_if_necessary(y)
|
||||
return paddle.logical_and(x, y)
|
||||
|
||||
|
||||
def _run_py_logical_and(x_func, y_func):
|
||||
x_value = x_func()
|
||||
assert not isinstance(x_value, (Variable, Value))
|
||||
|
||||
# NOTE(liym27):
|
||||
# 1. Returns y_func() if x_value is False;
|
||||
# 2. If x_value is False, y_func() should not be run.
|
||||
return x_value and y_func()
|
||||
|
||||
|
||||
def convert_logical_or(x_func, y_func):
|
||||
"""
|
||||
A function representation of a Python ``or`` statement.
|
||||
|
||||
Args:
|
||||
x_func(callable): x_func() is the left hand operand of ``or`` operator. x_func() is bool or Tensor.
|
||||
y_func(callable): y_func() is the right hand operand of ``or`` operator. y_func() is bool or Tensor.
|
||||
|
||||
Returns:
|
||||
A python bool variable or a bool Tensor.
|
||||
|
||||
NOTE(liym27):
|
||||
1) The operands are executed sequentially according to the running logic of Python. So here the arguments
|
||||
should be callable.
|
||||
2) If the left hand operand is True, the right hand operand should be executed.
|
||||
|
||||
For example:
|
||||
a = x > 1 or y < 1
|
||||
Transformed code:
|
||||
a = paddle.jit.dy2static.convert_logical_or(lambda:x>1, lambda:y<1)
|
||||
|
||||
In `convert_logical_or(lambda:x>1, lambda:y<1)`, `lambda:y<1` must be run after `lambda:x>1`. And
|
||||
if `x>1` is True, `y<1` should NOT be run.
|
||||
"""
|
||||
x_value = x_func()
|
||||
if not isinstance(x_value, (Variable, Value)):
|
||||
return _run_py_logical_or(lambda: x_value, y_func)
|
||||
|
||||
y_value = y_func()
|
||||
if not isinstance(y_value, (Variable, Value)):
|
||||
return _run_py_logical_or(lambda: y_value, lambda: x_value)
|
||||
|
||||
return _run_paddle_logical_or(x_value, y_value)
|
||||
|
||||
|
||||
def _run_paddle_logical_or(x, y):
|
||||
x = cast_bool_if_necessary(x)
|
||||
y = cast_bool_if_necessary(y)
|
||||
return paddle.logical_or(x, y)
|
||||
|
||||
|
||||
def _run_py_logical_or(x_func, y_func):
|
||||
x_value = x_func()
|
||||
assert not isinstance(x_value, (Variable, Value))
|
||||
|
||||
# NOTE(liym27):
|
||||
# 1. Returns y_func() if x_value is False;
|
||||
# 2. If x_value is True, y_func() should not be run.
|
||||
return x_value or y_func()
|
||||
|
||||
|
||||
def convert_logical_not(x):
|
||||
"""
|
||||
A function representation of a Python ``not`` statement.
|
||||
|
||||
Args:
|
||||
x(bool|Tensor): Operand of ``not`` operator.
|
||||
|
||||
Returns:
|
||||
A python bool variable or a bool Tensor.
|
||||
"""
|
||||
|
||||
if isinstance(x, (Variable, Value)):
|
||||
return _run_paddle_logical_not(x)
|
||||
else:
|
||||
return _run_py_logical_not(x)
|
||||
|
||||
|
||||
def _run_paddle_logical_not(x):
|
||||
x = cast_bool_if_necessary(x)
|
||||
return paddle.logical_not(x)
|
||||
|
||||
|
||||
def _run_py_logical_not(x):
|
||||
return not x
|
||||
|
||||
|
||||
def convert_ifelse(
|
||||
pred,
|
||||
true_fn,
|
||||
false_fn,
|
||||
get_args,
|
||||
set_args,
|
||||
return_name_ids,
|
||||
push_pop_names=None,
|
||||
):
|
||||
"""
|
||||
A function representation of a Python ``if/else`` statement.
|
||||
|
||||
Args:
|
||||
pred(bool|Tensor): A boolean Tensor which determines whether to return the result of ``true_fn`` or ``false_fn`` .
|
||||
true_fn(callable): A callable to be performed if ``pred`` is true.
|
||||
false_fn(callable): A callable to be performed if ``pred`` is false.
|
||||
get_args(callable): Get all arguments that needed in true_fn and false_fn.
|
||||
set_args(callable): Update arguments that modified in trure_fn and false_fn.
|
||||
return_name_ids(list[string], optional): the returned names.
|
||||
push_pop_names(list[string], optional): the names on which called .append() or .pop().
|
||||
|
||||
Returns:
|
||||
``true_fn()`` if the predicate ``pred`` is true else ``false_fn()`` .
|
||||
|
||||
"""
|
||||
if isinstance(pred, (Variable, Value)):
|
||||
out = _run_paddle_cond(
|
||||
pred,
|
||||
true_fn,
|
||||
false_fn,
|
||||
get_args,
|
||||
set_args,
|
||||
return_name_ids,
|
||||
push_pop_names,
|
||||
)
|
||||
else:
|
||||
out = _run_py_ifelse(
|
||||
pred, true_fn, false_fn, get_args, set_args, return_name_ids
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def _run_paddle_cond(
|
||||
pred, true_fn, false_fn, get_args, set_args, return_name_ids, push_pop_names
|
||||
):
|
||||
"""
|
||||
Paddle cond API will evaluate both true_fn and false_fn codes.
|
||||
"""
|
||||
helper = GetterSetterHelper(
|
||||
get_args, set_args, return_name_ids, push_pop_names
|
||||
)
|
||||
_convert_tensor_array_if_necessary(helper, push_pop_names)
|
||||
pred = cast_bool_if_necessary(pred)
|
||||
init_args = helper.get(return_name_ids)
|
||||
from paddle.jit.dy2static.parameter_recorder import _global_inplace_map
|
||||
from paddle.jit.dy2static.program_translator import ProgramTranslator
|
||||
|
||||
if use_pir_api():
|
||||
inplace_map = _global_inplace_map
|
||||
else:
|
||||
inplace_map = ProgramTranslator.get_instance()._inplace_map
|
||||
union_name = None
|
||||
# TODO(@xiongkun) lambda can have push_pop_names, which will cause error.
|
||||
if return_name_ids is None and push_pop_names is None:
|
||||
union_name = None
|
||||
else:
|
||||
union_name = (
|
||||
OrderedSet(return_name_ids) if return_name_ids else OrderedSet()
|
||||
) | (OrderedSet(push_pop_names) if push_pop_names else OrderedSet())
|
||||
union_name = list(union_name)
|
||||
|
||||
def new_true_fn():
|
||||
nonlocal union_name
|
||||
# init args may contain mutable python container like [var, 2], we copy then like in while_loop
|
||||
inplace_map_checkpoint = inplace_map.save_checkpoint()
|
||||
helper.set(
|
||||
return_name_ids,
|
||||
paddle.utils.copy_mutable_vars(init_args),
|
||||
)
|
||||
ret = true_fn()
|
||||
# IfExpr will return a non-None return value, so we just return ret.
|
||||
# We assume normal return has no return value.
|
||||
if ret is None:
|
||||
ret = helper.get(union_name)
|
||||
inplace_map.restore_checkpoint(inplace_map_checkpoint)
|
||||
return ret
|
||||
|
||||
def new_false_fn():
|
||||
nonlocal union_name
|
||||
# init args may contain mutable python container like [var, 2], we copy then like in while_loop
|
||||
inplace_map_checkpoint = inplace_map.save_checkpoint()
|
||||
helper.set(
|
||||
return_name_ids,
|
||||
paddle.utils.copy_mutable_vars(init_args),
|
||||
)
|
||||
ret = false_fn()
|
||||
if ret is None:
|
||||
ret = helper.get(union_name)
|
||||
inplace_map.restore_checkpoint(inplace_map_checkpoint)
|
||||
return ret
|
||||
|
||||
try:
|
||||
cond_outs = paddle.static.nn.cond(
|
||||
pred, new_true_fn, new_false_fn, None, union_name
|
||||
)
|
||||
except Exception as e:
|
||||
if re.search(
|
||||
"Unsupported return type of true_fn and false_fn in cond", str(e)
|
||||
):
|
||||
raise Dygraph2StaticException(
|
||||
f"Your if/else have different return type. TODO: add link to modify. {e}"
|
||||
)
|
||||
if re.search("Incompatible return values of", str(e)):
|
||||
raise Dygraph2StaticException(
|
||||
f"Your if/else have different number of return value. TODO: add link to modify. {e}"
|
||||
)
|
||||
raise e
|
||||
get_args = lambda: helper.get(union_name)
|
||||
set_args = lambda vs: helper.set(union_name, vs)
|
||||
return _recover_args_state(cond_outs, get_args, set_args, union_name)
|
||||
|
||||
|
||||
def _run_py_ifelse(
|
||||
pred, true_fn, false_fn, get_args, set_args, return_name_ids
|
||||
):
|
||||
"""
|
||||
Evaluate python original branch function if-else.
|
||||
"""
|
||||
py_outs = true_fn() if pred else false_fn()
|
||||
return py_outs
|
||||
|
||||
|
||||
def _remove_no_value_return_var(out):
|
||||
if isinstance(out, tuple) and len(out) > 0:
|
||||
processed_out = out
|
||||
align_ret = out[0]
|
||||
if isinstance(align_ret, tuple):
|
||||
for index, item in enumerate(align_ret):
|
||||
if isinstance(item, (Variable, Value)) and (
|
||||
RETURN_NO_VALUE_VAR_NAME in item.name
|
||||
):
|
||||
# return None
|
||||
if index == 0:
|
||||
processed_out = (None, *out[1:])
|
||||
elif index == 1:
|
||||
processed_out = align_ret[:1] + out[1:]
|
||||
else:
|
||||
processed_out = (align_ret[:index], *out[1:])
|
||||
break
|
||||
|
||||
for index, item in enumerate(processed_out):
|
||||
if isinstance(item, (Variable, Value)) and (
|
||||
RETURN_NO_VALUE_VAR_NAME in item.name
|
||||
):
|
||||
processed_out = processed_out[:index]
|
||||
|
||||
if not processed_out:
|
||||
return None
|
||||
elif len(processed_out) == 1:
|
||||
return processed_out[0]
|
||||
else:
|
||||
return processed_out
|
||||
|
||||
else:
|
||||
return out
|
||||
|
||||
|
||||
def _check_no_undefined_var(outs, names, branch_name):
|
||||
if names is None:
|
||||
return
|
||||
if not isinstance(outs, (list, tuple)):
|
||||
outs = [outs]
|
||||
for var, name in zip(list(outs), names):
|
||||
if isinstance(var, UndefinedVar):
|
||||
raise ValueError(
|
||||
f"Required '{name}' must be initialized both in if-else branch, but found it not initialized in '{branch_name}'."
|
||||
)
|
||||
|
||||
|
||||
def _recover_args_state(outs, get_args, set_args, return_name_ids):
|
||||
"""
|
||||
Currently we support variant length of early return statement by padding
|
||||
_no_return_value.
|
||||
|
||||
# TODO(dev): We shall consider to evaluate whether should support this for Python if-else?
|
||||
"""
|
||||
# IfExpr's return_name_ids maybe None
|
||||
if return_name_ids is None:
|
||||
return outs
|
||||
|
||||
init_args = get_args()
|
||||
# recover args state
|
||||
num_outs = len(return_name_ids)
|
||||
num_args = len(init_args)
|
||||
assert num_outs <= num_args
|
||||
|
||||
if num_args == 1:
|
||||
final_outs = (
|
||||
(outs,) if not isinstance(outs, (list, tuple)) else tuple(outs)
|
||||
)
|
||||
else:
|
||||
outs = (outs,) if num_outs == 1 else tuple(outs)
|
||||
final_outs = outs + init_args[num_outs:]
|
||||
|
||||
set_args(final_outs)
|
||||
return final_outs
|
||||
|
||||
|
||||
def convert_len(var):
|
||||
"""
|
||||
Returns variable(length) from shape ops based on var.type
|
||||
|
||||
Note: In addition to some ast transformations, some block-related
|
||||
operations are added in `len` transformation, such as appending
|
||||
`shape_op` in var.block.
|
||||
"""
|
||||
if isinstance(var, Variable):
|
||||
assert var.ndim > 0, "len() of a 0-D tensor is wrong"
|
||||
if var.type in [
|
||||
core.VarDesc.VarType.DENSE_TENSOR,
|
||||
core.VarDesc.VarType.SELECTED_ROWS,
|
||||
]:
|
||||
# Note: Length of var may be known ahead of time in dygraph,
|
||||
# but it probably represents batch size which can be variant.
|
||||
# so we return a variable dynamically inferred from var.shape.
|
||||
if (
|
||||
var.shape[0] > 0
|
||||
and var.type == core.VarDesc.VarType.DENSE_TENSOR
|
||||
):
|
||||
return var.shape[0]
|
||||
return paddle.shape(var)[0]
|
||||
elif var.type == core.VarDesc.VarType.DENSE_TENSOR_ARRAY:
|
||||
return paddle.tensor.array_length(var)
|
||||
else:
|
||||
raise TypeError(
|
||||
f'len(var) only supports DenseTensor/DenseTensorArray/SelectedRows, but received {type(var)}.'
|
||||
)
|
||||
elif isinstance(var, Value):
|
||||
if var.is_dense_tensor_type() or var.is_selected_row_type():
|
||||
assert var.ndim > 0, "len() of a 0-D tensor is wrong"
|
||||
# Note: Length of var may be known ahead of time in dygraph,
|
||||
# but it probably represents batch size which can be variant.
|
||||
# so we return a variable dynamically inferred from var.shape.
|
||||
if var.shape[0] > 0 and var.is_dense_tensor_type():
|
||||
return var.shape[0]
|
||||
return paddle.shape(var)[0]
|
||||
elif var.is_dense_tensor_array_type():
|
||||
return paddle.tensor.array_length(var)
|
||||
else:
|
||||
raise TypeError(
|
||||
'len(var) only supports DenseTensor/DenseTensorArray/SelectedRows, '
|
||||
+ f'but received {type(var)}.'
|
||||
)
|
||||
else:
|
||||
if isinstance(var, VariableTuple):
|
||||
return var.__len__()
|
||||
return len(var)
|
||||
|
||||
|
||||
def convert_zip(*args):
|
||||
for i, arg in enumerate(args):
|
||||
if isinstance(arg, (Variable, Value)) and arg.shape[0] == -1:
|
||||
raise RuntimeError(
|
||||
"Not support zip(tensor, ...) when tensor.shape[0] == -1, "
|
||||
f"but found args[{i}].shape[0] == -1 in 'zip'"
|
||||
)
|
||||
return zip(*args)
|
||||
|
||||
|
||||
def convert_super(super_fn):
|
||||
if super_fn is super:
|
||||
return super_fn
|
||||
return lambda cls, instance: super_fn()
|
||||
|
||||
|
||||
# TODO(xiongkun): delete when list<variable> is ready.
|
||||
class VariableTuple:
|
||||
"""
|
||||
this class will cause enumerate can't be wrapped by other iterator change function.
|
||||
this will be fixed when list<Variable> is produced.
|
||||
VariableTuple can only deal with variables which is fixed.
|
||||
"""
|
||||
|
||||
def __init__(self, var, start=0):
|
||||
self.var = var
|
||||
self.len = convert_len(var)
|
||||
if isinstance(self.len, (Variable, Value)):
|
||||
self.rag = paddle.arange(start, start + self.len, 1, "int64")
|
||||
else:
|
||||
self.rag = range(start, start + self.len)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return self.rag[idx], self.var[idx]
|
||||
|
||||
def __len__(self):
|
||||
return self.len
|
||||
|
||||
|
||||
def convert_enumerate(*args):
|
||||
has_variable = any(isinstance(x, (Variable, Value)) for x in args)
|
||||
if has_variable:
|
||||
return VariableTuple(*args)
|
||||
return enumerate(*args)
|
||||
|
||||
|
||||
def convert_range(*args):
|
||||
has_variable = any(isinstance(x, (Variable, Value)) for x in args)
|
||||
# NOTE(SigureMo): Add an `Assign` OP after the Tensor input to mark it as a variable, which can
|
||||
# avoid confusing it with the scalar case in `arange` API.
|
||||
# For example:
|
||||
# ```python
|
||||
# l = []
|
||||
# for i in range(n):
|
||||
# l.append(i)
|
||||
# ```
|
||||
# - If `n` is a scalar (e.g., `n=10`), we expect to create an `ArangeOp` with a fixed output shape [10].
|
||||
# - If `n` is a Tensor (e.g., `n=full([], 10, "int32")`), we expect to create an `ArangeOp` with a dynamic
|
||||
# output shape [-1]. To ensure the python level and graph level all recognize this is data-dependent control
|
||||
# flow.
|
||||
# However, we can't distinguish the scalar case and the Tensor case when creating the `ArangeOp`. Because
|
||||
# the scalar case also be convert as a `Full` OP output.
|
||||
# So we add an `Assign` OP after the Tensor input to **mark** it as a variable, which can avoid confusing
|
||||
# it with the scalar case.
|
||||
is_full_op_output = lambda x: (
|
||||
isinstance(x, Value)
|
||||
and x.get_defining_op()
|
||||
and x.get_defining_op().name() == "pd_op.full"
|
||||
)
|
||||
args = [
|
||||
paddle.assign(arg) if is_full_op_output(arg) else arg for arg in args
|
||||
]
|
||||
if has_variable:
|
||||
if len(args) == 1:
|
||||
return paddle.arange(0, args[0], 1, "int64")
|
||||
if len(args) == 2:
|
||||
return paddle.arange(args[0], args[1], 1, "int64")
|
||||
if len(args) == 3:
|
||||
return paddle.arange(args[0], args[1], args[2], "int64")
|
||||
return range(*args)
|
||||
|
||||
|
||||
def convert_shape(x):
|
||||
"""
|
||||
A function representation of the shape of variable.
|
||||
"""
|
||||
|
||||
def has_negative(list_shape):
|
||||
return any(x < 0 for x in list_shape)
|
||||
|
||||
# When `x` is Variable:
|
||||
# (1) if x.shape contains -1, such as [2, -1, 64], returns [2, var, 64],
|
||||
# where var = paddle.shape(x)[1]
|
||||
|
||||
# (2) if x.shape does not contains -1, return list(x.shape) directly
|
||||
|
||||
if isinstance(x, (Variable, Value)):
|
||||
values = list(x.shape)
|
||||
if has_negative(values):
|
||||
shape_tensor = paddle.shape(x)
|
||||
for i, v in enumerate(values):
|
||||
if v is None or v < 0:
|
||||
values[i] = shape_tensor[i]
|
||||
return values
|
||||
else:
|
||||
return x.shape
|
||||
|
||||
|
||||
def cast_bool_if_necessary(var):
|
||||
assert isinstance(var, (Variable, Value))
|
||||
if convert_dtype(var.dtype) not in ['bool']:
|
||||
var = paddle.cast(var, dtype="bool")
|
||||
return var
|
||||
|
||||
|
||||
def convert_var_dtype(var, dtype):
|
||||
if isinstance(var, (Variable, Value)):
|
||||
src_dtype = convert_dtype(var.dtype)
|
||||
assert src_dtype in [
|
||||
'bool',
|
||||
'float16',
|
||||
'float32',
|
||||
'float64',
|
||||
'int32',
|
||||
'int64',
|
||||
'uint8',
|
||||
], (
|
||||
f"The dtype of var {var.name} is {src_dtype}, which is not supported in the cast op."
|
||||
)
|
||||
assert dtype in [
|
||||
'bool',
|
||||
'int',
|
||||
'float',
|
||||
'complex',
|
||||
], (
|
||||
f"The casted target dtype is {dtype}, which is not supported in type casting."
|
||||
)
|
||||
cast_map = {
|
||||
'bool': 'bool',
|
||||
'int': 'int32',
|
||||
'float': 'float32',
|
||||
'complex': 'complex64',
|
||||
}
|
||||
return paddle.cast(var, dtype=cast_map[dtype])
|
||||
else:
|
||||
assert dtype in [
|
||||
'bool',
|
||||
'int',
|
||||
'float',
|
||||
'complex',
|
||||
], (
|
||||
f"The casted target dtype is {dtype}, which is not supported in type casting."
|
||||
)
|
||||
return eval(dtype)(var)
|
||||
|
||||
|
||||
def convert_assert(cond, message=""):
|
||||
"""
|
||||
A function representation of a Python ``assert`` statement.
|
||||
"""
|
||||
if isinstance(cond, (Variable, Value)):
|
||||
cond = paddle.cast(cond, "bool")
|
||||
# NOTE: message is not used because Paddle Assert has no corresponding parameter to use.
|
||||
from paddle.static.nn.control_flow import Assert
|
||||
|
||||
return Assert(cond)
|
||||
else:
|
||||
assert cond, message
|
||||
|
||||
|
||||
def convert_print(*objects, sep=' ', end='\n', file=None, flush=False):
|
||||
"""
|
||||
A function representing Python ``print`` function. It will print all arguments
|
||||
at compile time and only print the Tensor values at runtime.
|
||||
"""
|
||||
for obj in objects:
|
||||
if isinstance(obj, (Variable, Value)):
|
||||
paddle.static.Print(obj)
|
||||
print(*objects, sep=sep, end=end, file=file, flush=flush)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def convert_auto_cast(
|
||||
enable=True,
|
||||
custom_white_list=None,
|
||||
custom_black_list=None,
|
||||
level='O1',
|
||||
dtype='float16',
|
||||
use_promote=True,
|
||||
):
|
||||
from .program_translator import ProgramTranslator
|
||||
|
||||
warnings.warn(
|
||||
"paddle.amp.auto_cast is an experimental features in auto parallel."
|
||||
+ "This will take no effect in normal dy2static."
|
||||
)
|
||||
|
||||
amp_records = ProgramTranslator.get_instance()._amp_records
|
||||
main_program = paddle.static.default_main_program()
|
||||
current_block_idx = main_program.current_block_idx
|
||||
current_block = main_program.current_block()
|
||||
start_op_idx = len(current_block.ops)
|
||||
amp_options = AmpOptions(
|
||||
enable, custom_white_list, custom_black_list, level, dtype, use_promote
|
||||
)
|
||||
yield
|
||||
end_op_idx = len(current_block.ops)
|
||||
if current_block_idx not in amp_records:
|
||||
amp_records[current_block_idx] = []
|
||||
amp_records[current_block_idx].append(
|
||||
(amp_options, start_op_idx, end_op_idx)
|
||||
)
|
||||
|
||||
|
||||
def create_bool_as_type(x, value=True):
|
||||
'''
|
||||
Create a bool variable, which type is the same as x.
|
||||
'''
|
||||
if isinstance(x, (Variable, Value)):
|
||||
return paddle.full(shape=[], fill_value=value, dtype="bool")
|
||||
else:
|
||||
return value
|
||||
Reference in New Issue
Block a user