605 lines
26 KiB
Python
605 lines
26 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.
|
|
|
|
import paddle
|
|
from paddle.base import core
|
|
from paddle.base.backward import _append_grad_suffix_
|
|
from paddle.base.framework import Variable, in_pir_mode
|
|
from paddle.base.libpaddle.pir import build_pylayer_op, cf_yield
|
|
from paddle.common_ops_import import LayerHelper, check_type, in_dygraph_mode
|
|
from paddle.utils import flatten, map_structure
|
|
|
|
# NOTE(MarioLulab): Borrowed from `python/paddle/static/nn/control_flow.py`
|
|
from .control_flow import BlockGuard, copy_var_to_parent_block
|
|
|
|
|
|
class StaticPyLayerBlockGuard(BlockGuard):
|
|
def __init__(self, block_manager):
|
|
check_type(
|
|
block_manager,
|
|
"block",
|
|
StaticPyLayerBlock,
|
|
"StaticPyLayerBlockGuard",
|
|
)
|
|
super().__init__(block_manager.helper.main_program)
|
|
self.block_manager = block_manager
|
|
|
|
def __enter__(self):
|
|
super().__enter__()
|
|
return self.block_manager
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
self.block_manager.complete()
|
|
return super().__exit__(exc_type, exc_val, exc_tb)
|
|
|
|
|
|
class StaticPyLayerBlock:
|
|
def __init__(self, inputs, name=None, pylayer_context=None):
|
|
# used to specify the Variable type `Input` to `pylayer` op
|
|
self.fwd_inputs = [
|
|
each_input
|
|
for each_input in inputs
|
|
if isinstance(each_input, Variable)
|
|
] # filter non-Variable inputs
|
|
|
|
# used to specify the `Out` to `pylayer` op
|
|
self.fwd_outputs = []
|
|
|
|
self.context = pylayer_context
|
|
|
|
self.helper = LayerHelper("static_pylayer_block", name=name)
|
|
self.fwd_op_id = None
|
|
self._forward_block_id = None
|
|
self._backward_block_id = None
|
|
self.var_old_to_new = {}
|
|
|
|
def block(self, is_backward_block=False):
|
|
self.is_backward_block = is_backward_block
|
|
return StaticPyLayerBlockGuard(self)
|
|
|
|
@property
|
|
def forward_block_index(self):
|
|
return self._forward_block_id
|
|
|
|
@property
|
|
def backward_block_index(self):
|
|
return self._backward_block_id
|
|
|
|
@property
|
|
def fwd_op_index(self):
|
|
return self.fwd_op_id
|
|
|
|
def complete_forward_block(self):
|
|
inside_block = self.helper.main_program.current_block()
|
|
parent_block = self.helper.main_program.block(inside_block.parent_idx)
|
|
self._forward_block_id = inside_block.idx
|
|
|
|
step_scope = parent_block.create_var(
|
|
type=core.VarDesc.VarType.STEP_SCOPES
|
|
)
|
|
|
|
pylayer_op = parent_block.append_op(
|
|
type='pylayer',
|
|
inputs={
|
|
'Input': self.fwd_inputs,
|
|
},
|
|
outputs={"Out": self.fwd_outputs, "Scope": [step_scope]},
|
|
attrs={
|
|
'blocks': [inside_block],
|
|
},
|
|
)
|
|
|
|
self.fwd_op_id = pylayer_op.idx
|
|
self.helper.main_program._sync_with_cpp()
|
|
|
|
def complete_backward_block(self):
|
|
inside_block = self.helper.main_program.current_block()
|
|
parent_block = self.helper.main_program.block(inside_block.parent_idx)
|
|
|
|
self._backward_block_id = inside_block.idx
|
|
# Set OpRole to `backward`. The operators marked as `backward` are expected to be pruned in PruneBackward.
|
|
for op in inside_block.ops:
|
|
op_role_attr_name = (
|
|
core.op_proto_and_checker_maker.kOpRoleAttrName()
|
|
)
|
|
backward = core.op_proto_and_checker_maker.OpRole.Backward
|
|
op.desc._set_attr(op_role_attr_name, backward)
|
|
inside_block._set_forward_block_idx(self.forward_block_index)
|
|
|
|
# NOTE(MarioLulab): The reason of renaming the var name in the inside block is that
|
|
# we need to associating `inside_grads` and `outside_grads` at
|
|
# runtime `RunImpl` in pylayer op
|
|
_rename_var_recursively_(inside_block, self.var_old_to_new)
|
|
|
|
# update `blocks` attr by appending backward_block
|
|
forward_block_desc = parent_block.program.block(
|
|
self.forward_block_index
|
|
).desc
|
|
backward_block_desc = inside_block.desc
|
|
parent_block.ops[self.fwd_op_index].desc.set_blocks_attr(
|
|
"blocks", [forward_block_desc, backward_block_desc]
|
|
)
|
|
|
|
# remove temporary vars created by `StaticPyLayerContext.saved_tensor`
|
|
if self.context:
|
|
for var in self.context.saved_vars:
|
|
if not inside_block.has_var(var.name):
|
|
raise ValueError(
|
|
f"{var.name} was saved in forward block but could not be found in backward block. Maybe {var.name} was renamed somewhere."
|
|
)
|
|
inside_block._remove_var(var.name)
|
|
|
|
self.helper.main_program._sync_with_cpp()
|
|
|
|
def complete(self):
|
|
if not self.is_backward_block:
|
|
return self.complete_forward_block()
|
|
else:
|
|
return self.complete_backward_block()
|
|
|
|
|
|
def _get_ctx_from_func_(func):
|
|
if func is None:
|
|
return None
|
|
|
|
fn_bind_args = getattr(func, "args", None)
|
|
if fn_bind_args is None:
|
|
return None
|
|
|
|
from paddle.jit.dy2static.py_layer import StaticPyLayerContext
|
|
|
|
fn_ctx = None
|
|
if len(fn_bind_args) > 0 and isinstance(
|
|
fn_bind_args[0], StaticPyLayerContext
|
|
):
|
|
fn_ctx = fn_bind_args[0]
|
|
|
|
return fn_ctx
|
|
|
|
|
|
def _rename_var_recursively_(cur_block, var_old_to_new):
|
|
"""
|
|
Rename the var both the Variable instances and all ops' input and output arg names
|
|
in `cur_block` based on dict `var_old_to_new`.
|
|
Dict `var_old_to_new` should be the following format:
|
|
{
|
|
old_name_0 : new_name_0,
|
|
old_name_1 : new_name_1,
|
|
...
|
|
old_name_n : new_name_n,
|
|
}
|
|
"""
|
|
|
|
for old_var_name, new_var_name in var_old_to_new.items():
|
|
# NOTE(MarioLulab): The reason why not using `Block._rename_var`` is that `Block._rename_var` will raise ValueError, when `old_var_name` does not correspond to a Variable instance in Block.
|
|
|
|
if cur_block.has_var(old_var_name):
|
|
# `Block.desc._rename_var` can rename var in block and then rename var name in all ops
|
|
cur_block.desc._rename_var(
|
|
old_var_name.encode(), new_var_name.encode()
|
|
)
|
|
else:
|
|
# When cur_block does not have the var, `Block.desc._rename_var` can't rename var name in ops.
|
|
# In this case, we should traverse all ops and perform renaming manually.
|
|
for op in cur_block.ops:
|
|
op._rename_input(old_var_name, new_var_name)
|
|
op._rename_output(old_var_name, new_var_name)
|
|
|
|
# NOTE(MarioLulab): block attr type with the name of "blocks" or "sub_block" indicates
|
|
# the block might be executed. We should rename the var name in these blocks recursively
|
|
block_attr_names = ["blocks", "sub_block"]
|
|
|
|
for op in cur_block.ops:
|
|
for attr_name in op.all_attrs():
|
|
if attr_name not in block_attr_names:
|
|
continue
|
|
|
|
if op.attr_type(attr_name) == core.AttrType.BLOCK:
|
|
sub_block_id = op._block_attr_id(attr_name)
|
|
sub_block = cur_block.program.block(sub_block_id)
|
|
_rename_var_recursively_(sub_block, var_old_to_new)
|
|
elif op.attr_type(attr_name) == core.AttrType.BLOCKS:
|
|
sub_blocks_ids = op._blocks_attr_ids(attr_name)
|
|
for sub_block_id in sub_blocks_ids:
|
|
sub_block = cur_block.program.block(sub_block_id)
|
|
_rename_var_recursively_(sub_block, var_old_to_new)
|
|
|
|
|
|
def copy_var_from_parent_block(parent_block_var, layer_helper):
|
|
if not isinstance(parent_block_var, Variable):
|
|
return parent_block_var
|
|
prog = layer_helper.main_program
|
|
current_block = prog.current_block()
|
|
|
|
if (
|
|
parent_block_var.type == core.VarDesc.VarType.DENSE_TENSOR_ARRAY
|
|
and current_block._find_var_recursive(parent_block_var.name)
|
|
):
|
|
current_block_var = parent_block_var
|
|
else:
|
|
current_block_var = current_block.create_var(
|
|
dtype=parent_block_var.dtype,
|
|
shape=parent_block_var.shape,
|
|
type=parent_block_var.type,
|
|
)
|
|
paddle.assign(parent_block_var, current_block_var)
|
|
return current_block_var
|
|
|
|
|
|
class PyLayerBackwardFunction:
|
|
_register_backward_funcs = []
|
|
|
|
def __init__(self, backward_function, hook_check_func):
|
|
if backward_function is None or not callable(backward_function):
|
|
raise TypeError('func must be a Python function')
|
|
|
|
self._func = backward_function
|
|
|
|
# Note: Used to verify the number of `Value` inputs to ``forward_fn`` the same as the
|
|
# number of `Value` outputs to ``backward_fn``, and the number of `Value` outputs to ``forward_fn``
|
|
# the same as the number of `Value` inputs to ``backward_fn``.
|
|
self._hook_check_func = hook_check_func
|
|
|
|
'''
|
|
Why record self here?
|
|
For increasing reference count of self.
|
|
It seems that to release Python object
|
|
whose reference count is 1 would cause
|
|
segmentation fault error in C++ side.
|
|
May be lack of Python GC in C++ side?
|
|
'''
|
|
PyLayerBackwardFunction._register_backward_funcs.append(self)
|
|
|
|
def __call__(self, *output_grads):
|
|
assert self._hook_check_func
|
|
|
|
input_grads = self._func(*output_grads)
|
|
if not isinstance(input_grads, (list, tuple)):
|
|
input_grads = (input_grads,)
|
|
|
|
self._hook_check_func(output_grads, input_grads)
|
|
input_grads = [
|
|
input_grad
|
|
for input_grad in flatten(input_grads)
|
|
if isinstance(input_grad, (paddle.pir.Value, type(None)))
|
|
]
|
|
|
|
return input_grads
|
|
|
|
|
|
def static_pylayer(forward_fn, inputs, backward_fn=None, name=None):
|
|
"""
|
|
This API returns ``forward_fn(inputs)``, and two sub-block are created based on
|
|
the logic of ``forward_fn`` and ``backward_fn``, with the operator ``pylayer``
|
|
holding information about the two blocks.
|
|
|
|
``forward_fn`` and ``backward_fn`` should return a nest structure of Variables.
|
|
A nest structure of Variables in PaddlePaddle is Variable(s), or tuple of Variables, or
|
|
list of Variables.
|
|
|
|
Note:
|
|
1. If ``backward_fn`` is not None, user needs to keep the number of `Variable` inputs to ``forward_fn`` the same as the
|
|
number of `Variable` outputs to ``backward_fn``, and the number of `Variable` outputs to ``forward_fn``
|
|
the same as the number of `Variable` inputs to ``backward_fn``.
|
|
|
|
2. If ``backward_fn`` is None, ``stop_gradient`` attr of all Variable in ``inputs`` is expected to be True.
|
|
Otherwise it might get unexpected results in backward propagation.
|
|
|
|
3. This API can only be used under static graph mode.
|
|
|
|
Args:
|
|
forward_fn (callable): A callable to be performed in forward propagation
|
|
inputs (list[Variable]): The list of input Variable to the ``forward_fn``
|
|
backward_fn (callable, optional): A callable to be performed in backward propagation. Default: None, which means no need to do backward propagation.
|
|
name (str, optional): The default value is ``None`` . Normally users
|
|
don't have to set this parameter. For more information, please
|
|
refer to :ref:`api_guide_Name` .
|
|
|
|
Returns:
|
|
Variable|list(Variable)|tuple(Variable): returns the output of ``forward_fn(inputs)``
|
|
|
|
Examples:
|
|
.. code-block:: pycon
|
|
|
|
>>> import paddle
|
|
>>> import numpy as np
|
|
|
|
>>> paddle.enable_static()
|
|
|
|
>>> def forward_fn(x):
|
|
... return paddle.exp(x)
|
|
|
|
>>> def backward_fn(dy):
|
|
... return 2 * paddle.exp(dy)
|
|
|
|
>>> main_program = paddle.static.Program()
|
|
>>> start_program = paddle.static.Program()
|
|
|
|
>>> place = paddle.CPUPlace()
|
|
>>> exe = paddle.static.Executor(place)
|
|
>>> with paddle.static.program_guard(main_program, start_program):
|
|
... data = paddle.static.data(name="X", shape=[None, 5], dtype="float32")
|
|
... data.stop_gradient = False
|
|
... ret = paddle.static.nn.static_pylayer(forward_fn, [data], backward_fn)
|
|
... data_grad = paddle.static.gradients([ret], data)[0]
|
|
|
|
>>> exe.run(start_program)
|
|
>>> x = np.array([[1.0, 2.0, 3.0, 4.0, 5.0]], dtype=np.float32)
|
|
>>> x, x_grad, y = exe.run(
|
|
... main_program,
|
|
... feed={"X": x},
|
|
... fetch_list=[data, data_grad, ret],
|
|
... )
|
|
|
|
>>> print(x)
|
|
[[1. 2. 3. 4. 5.]]
|
|
>>> print(x_grad)
|
|
[[5.4365635 5.4365635 5.4365635 5.4365635 5.4365635]]
|
|
>>> print(y)
|
|
[[ 2.7182817 7.389056 20.085537 54.59815 148.41316 ]]
|
|
"""
|
|
assert in_dygraph_mode() is False, (
|
|
"please use PyLayer instead of static_pylayer in dygraph mode"
|
|
)
|
|
|
|
assert isinstance(inputs, list)
|
|
if backward_fn is None:
|
|
for input_var in inputs:
|
|
if input_var.stop_gradient is False:
|
|
raise ValueError(
|
|
f"``stop_gradient`` attr of all inputs to ``forward_fn`` are expected to be True, when ``backward_fn == None``, but {input_var.name}.stop_gradient got {input_var.stop_gradient}"
|
|
)
|
|
|
|
# judge if in dy2st or not, by checking binding args of `forward_fn` and `backward_fn`
|
|
fwd_fn_ctx = _get_ctx_from_func_(forward_fn)
|
|
bwd_fn_ctx = _get_ctx_from_func_(backward_fn)
|
|
static_pylayer_context = (
|
|
fwd_fn_ctx if fwd_fn_ctx and (fwd_fn_ctx == bwd_fn_ctx) else None
|
|
)
|
|
|
|
if in_pir_mode():
|
|
fwd_inputs = [
|
|
inp for inp in flatten(inputs) if isinstance(inp, paddle.pir.Value)
|
|
]
|
|
pylayer_op = build_pylayer_op(fwd_inputs)
|
|
outputs = None
|
|
if forward_fn is not None:
|
|
if not callable(forward_fn):
|
|
raise ValueError("`forward_fn` should be callable")
|
|
with pylayer_op.forward_block():
|
|
outputs = forward_fn(*inputs)
|
|
|
|
if outputs is None:
|
|
return None
|
|
|
|
fwd_outputs = [
|
|
out
|
|
for out in flatten(outputs)
|
|
if isinstance(out, paddle.pir.Value)
|
|
]
|
|
|
|
with pylayer_op.forward_block():
|
|
if fwd_outputs is not None:
|
|
cf_yield(flatten(fwd_outputs))
|
|
pylayer_op.update_output()
|
|
if backward_fn is not None:
|
|
if not callable(backward_fn):
|
|
raise ValueError("`backward_fn` should be callable")
|
|
|
|
def hook_inputs_outputs_check_function(output_grads, input_grads):
|
|
# 1. Verify the number of `Value` inputs to ``forward_fn`` the same as the
|
|
# number of `Value` outputs to ``backward_fn``
|
|
forward_inputs = [
|
|
x
|
|
for x in flatten(inputs)
|
|
if isinstance(x, paddle.pir.Value)
|
|
]
|
|
input_grads = [
|
|
x
|
|
for x in flatten(input_grads)
|
|
if isinstance(x, (paddle.pir.Value, type(None)))
|
|
]
|
|
if len(input_grads) != len(forward_inputs):
|
|
raise ValueError(
|
|
f"The number of input grads should be equal to the number of inputs, but got {len(input_grads)} and {len(forward_inputs)}."
|
|
)
|
|
for inp_grad, fwd_input in zip(input_grads, forward_inputs):
|
|
# NOTE: inp_grad will be None if fwd_input.stop_gradients=True
|
|
if inp_grad is None:
|
|
continue
|
|
assert inp_grad.dtype == fwd_input.dtype, (
|
|
f"dtype of inp_grad({inp_grad.dtype}) and fwd_input({fwd_input.dtype}) should be the same"
|
|
)
|
|
assert inp_grad.shape == fwd_input.shape, (
|
|
f"shape of inp_grad({inp_grad.shape}) and fwd_input({fwd_input.shape}) should be the same"
|
|
)
|
|
if fwd_input.is_dist():
|
|
# NOTE: placements may be not the same, so do not check it.
|
|
assert inp_grad.is_dist(), (
|
|
"fwd_input and inp_grad should both be distributed"
|
|
)
|
|
assert (
|
|
fwd_input.dist_attr().process_mesh
|
|
== inp_grad.dist_attr().process_mesh
|
|
), (
|
|
f"process_mesh of fwd_input({fwd_input.dist_attr().process_mesh}) and inp_grad({inp_grad.dist_attr().process_mesh}) should be the same"
|
|
)
|
|
else:
|
|
assert inp_grad.type() == fwd_input.type(), (
|
|
f"type of inp_grad({inp_grad.type()}) and fwd_input({fwd_input.type()}) should be the same"
|
|
)
|
|
|
|
# 2. Verify the number of `Value` outputs to ``forward_fn``
|
|
# the same as the number of `Value` inputs to ``backward_fn``
|
|
forward_outputs = [
|
|
x
|
|
for x in flatten(fwd_outputs)
|
|
if isinstance(x, paddle.pir.Value)
|
|
]
|
|
if len(output_grads) != len(forward_outputs):
|
|
raise ValueError(
|
|
f"The number of output grads should be equal to the number of outputs, but got {len(output_grads)} and {len(fwd_outputs)}."
|
|
)
|
|
for out_grad, fwd_output in zip(output_grads, forward_outputs):
|
|
if out_grad is None:
|
|
continue
|
|
assert out_grad.dtype == fwd_output.dtype, (
|
|
f"dtype of out_grad({out_grad.dtype}) and fwd_output({fwd_output.dtype}) should be the same"
|
|
)
|
|
assert out_grad.shape == fwd_output.shape, (
|
|
f"shape of out_grad({out_grad.shape}) and fwd_output({fwd_output.shape}) should be the same"
|
|
)
|
|
if fwd_output.is_dist():
|
|
# NOTE: placements may be not the same, so do not check it.
|
|
assert out_grad.is_dist(), (
|
|
"fwd_output and out_grad should both be distributed"
|
|
)
|
|
assert (
|
|
fwd_output.dist_attr().process_mesh
|
|
== out_grad.dist_attr().process_mesh
|
|
), (
|
|
f"process_mesh of fwd_output({fwd_output.dist_attr().process_mesh}) and out_grad({out_grad.dist_attr().process_mesh}) should be the same"
|
|
)
|
|
else:
|
|
assert out_grad.type() == fwd_output.type(), (
|
|
f"type of out_grad({out_grad.type}) and fwd_output({fwd_output.type}) should be the same"
|
|
)
|
|
|
|
bwd_fn = PyLayerBackwardFunction(
|
|
backward_fn, hook_check_func=hook_inputs_outputs_check_function
|
|
)
|
|
pylayer_op.register_backward_function(bwd_fn)
|
|
|
|
# NOTE: Replace pir.Value of `outputs` with pylayer_op.result, because value of `outputs` which is inside pylayer block can't be reference outside the block.
|
|
op_result_idx = 0
|
|
outputs = flatten(outputs)
|
|
for i in range(len(outputs)):
|
|
if isinstance(outputs[i], paddle.pir.Value):
|
|
outputs[i] = pylayer_op.results()[op_result_idx]
|
|
op_result_idx += 1
|
|
return outputs[0] if len(outputs) == 1 else outputs
|
|
|
|
check_type(name, "name", (str, type(None)), "base.layers.static_pylayer")
|
|
helper = LayerHelper('static_pylayer', **locals())
|
|
copy_to_parent_func = lambda var: copy_var_to_parent_block(var, helper)
|
|
|
|
assert forward_fn is not None and callable(forward_fn)
|
|
pylayer_block_manager = StaticPyLayerBlock(
|
|
inputs, pylayer_context=static_pylayer_context
|
|
)
|
|
with pylayer_block_manager.block(is_backward_block=False) as mgr:
|
|
origin_output = forward_fn(*inputs)
|
|
if origin_output is not None:
|
|
output = map_structure(copy_to_parent_func, origin_output)
|
|
mgr.fwd_outputs = [
|
|
x for x in flatten(output) if isinstance(x, Variable)
|
|
]
|
|
else:
|
|
mgr.fwd_outputs = []
|
|
|
|
current_block = helper.main_program.current_block()
|
|
current_block._sync_with_cpp()
|
|
if backward_fn is not None:
|
|
assert callable(backward_fn)
|
|
if origin_output is None:
|
|
output = []
|
|
|
|
# **Create the backward input** from the output of the op to build the
|
|
# backward block, and then delete it.
|
|
grad_var_ins = []
|
|
for fwd_var in pylayer_block_manager.fwd_outputs:
|
|
fwd_var_name = fwd_var.name
|
|
bwd_var_name = _append_grad_suffix_(fwd_var_name)
|
|
if not current_block.desc.has_var_recursive(fwd_var_name.encode()):
|
|
raise ValueError(
|
|
f"Grad var {bwd_var_name} , we can't find its related forward var {fwd_var_name}"
|
|
)
|
|
|
|
var = current_block.create_var(
|
|
dtype=fwd_var.dtype,
|
|
shape=fwd_var.shape,
|
|
type=fwd_var.type,
|
|
name=bwd_var_name,
|
|
)
|
|
|
|
grad_var_ins.append(var)
|
|
|
|
copy_from_parent_func = lambda var: copy_var_from_parent_block(
|
|
var, helper
|
|
)
|
|
assert isinstance(grad_var_ins, list)
|
|
with pylayer_block_manager.block(is_backward_block=True) as mgr:
|
|
# Step1. Copy var from parent block
|
|
inside_block_inputs = map_structure(
|
|
copy_from_parent_func, grad_var_ins
|
|
)
|
|
|
|
# Step2. Do backward propagation
|
|
grad_origin_output = backward_fn(*inside_block_inputs)
|
|
|
|
if grad_origin_output is not None:
|
|
# Step3. Check the number of inputs to ``forward_fn`` the
|
|
# same as the number of outputs to ``backward_fn``
|
|
flat_grad_origin = flatten(grad_origin_output)
|
|
|
|
# NOTE(MarioLulab): ``current_block`` was defined outside
|
|
forward_input_names = current_block.ops[
|
|
pylayer_block_manager.fwd_op_index
|
|
].desc.input_arg_names()
|
|
assert len(forward_input_names) == len(flat_grad_origin), (
|
|
f"needs to keep the number of inputs to ``forward_fn`` the same as the number of outputs to ``backward_fn``, \
|
|
but got {len(forward_input_names)} and {len(flat_grad_origin)}"
|
|
)
|
|
|
|
# Step4. Rename var name with suffix of "@GRAD"
|
|
for bwd_output, fwd_input_name in zip(
|
|
flat_grad_origin, forward_input_names
|
|
):
|
|
# NOTE(MarioLulab): Because `flat_grad_origin` are the Variables inside the backward block, which one by one corresponds
|
|
# to the gradients of the inputs to the forward function, we need to establish a link between `flat_grad_origin`,
|
|
# and the Variable outside the backward block which represent the gradient of the input ot the forward function.
|
|
# The approach we have taken is renaming `flat_grad_origin` by forward input name with suffix of "@GRAD", and aligning
|
|
# the order of `Out@GRAD` in `pylayer_grad` op with `flat_grad_origin`. And in the runtime `RunImpl` in `pylayer_grad` op,
|
|
# we will find inside_grad with the name of forward input name with suffix of "@GRAD" in the scope, and assign `inside_grads`
|
|
# to `outside_grads`.
|
|
#
|
|
# Example:
|
|
# after run the code below to create forward and backward block:
|
|
#
|
|
# out = forward_fn(x, y) # create forward block
|
|
# x_grad, y_grad = backward_fn(out_grad) # create backward block
|
|
#
|
|
# x.name is "X", y.name is "Y", and out.name is "tmp_0", but x_grad.name is "_generate_0", y_grad.name is "_generate_1".
|
|
# we rename x_grad by "X@GRAD", and y_grad by "Y@GRAD" inside backward block.
|
|
# One thing to keep in mind is that we assume there were no Variable naming "X@GRAD" inside backward block before performing rename operation.
|
|
# TODO(MarioLulab): We will validate the assumption above is whether a strong hypothesis or not.
|
|
|
|
# attach old var name into new
|
|
if isinstance(bwd_output, Variable):
|
|
bwd_out_new = _append_grad_suffix_(
|
|
fwd_input_name
|
|
) # "X" => "X@GRAD"
|
|
mgr.var_old_to_new[bwd_output.name] = (
|
|
bwd_out_new # e.g. "tmp_0.mean_0": "X@GRAD"
|
|
)
|
|
|
|
# **Delete the backward input**
|
|
for bwd_var in grad_var_ins:
|
|
current_block._remove_var(bwd_var.name)
|
|
|
|
if origin_output is None:
|
|
return None
|
|
|
|
return output
|