1318 lines
32 KiB
Python
1318 lines
32 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you 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.
|
|
# pylint: disable=unused-argument, redefined-builtin, invalid-name
|
|
"""Gradient definitions for Relax operators."""
|
|
|
|
import functools
|
|
import operator
|
|
|
|
from tvm import relax
|
|
from tvm.arith import Analyzer
|
|
from tvm.ir import Call, PrimType
|
|
from tvm.relax.type import ShapeType
|
|
|
|
from ..block_builder import BlockBuilder
|
|
from ..expr import Expr, ShapeExpr, Var
|
|
from .base import register_gradient
|
|
from .binary import greater_equal, less
|
|
from .create import triu
|
|
from .datatype import astype
|
|
from .grad import (
|
|
avg_pool2d_backward,
|
|
max_pool2d_backward,
|
|
nll_loss_backward,
|
|
no_grad,
|
|
take_backward,
|
|
)
|
|
from .index import strided_slice
|
|
from .linear_algebra import matmul
|
|
from .manipulate import (
|
|
broadcast_to,
|
|
collapse_sum_to,
|
|
concat,
|
|
expand_dims,
|
|
flatten,
|
|
permute_dims,
|
|
reshape,
|
|
split,
|
|
squeeze,
|
|
)
|
|
from .nn import conv2d, conv2d_transpose
|
|
from .search import where
|
|
from .statistical import cumsum, sum
|
|
from .unary import cos, exp, log, sigmoid, sin
|
|
|
|
# TODO(yixin, chaofan): handle symbolic shape for most of the gradients
|
|
|
|
|
|
##################### Utilities #####################
|
|
|
|
|
|
def _get_shape(expr: Expr) -> ShapeExpr:
|
|
"""Get the shape from a Tensor expr."""
|
|
try:
|
|
shape = expr.ty.shape
|
|
except Exception as error:
|
|
raise RuntimeError(
|
|
f"Get the shape of {expr} failed. Please normalize it first and ensure it is a Tensor."
|
|
) from error
|
|
return shape
|
|
|
|
|
|
def _get_dtype(expr: Expr) -> str:
|
|
"""Get the dtype from a Tensor expr."""
|
|
try:
|
|
dtype = expr.ty.dtype
|
|
except Exception as error:
|
|
raise RuntimeError(
|
|
f"Get the dtype of {expr} failed. Please normalize it first and ensure it is a Tensor."
|
|
) from error
|
|
if isinstance(dtype, PrimType):
|
|
dtype = dtype.dtype
|
|
return dtype
|
|
|
|
|
|
def _fit_shape(bb: BlockBuilder, input_grad: Expr, input: Expr) -> Expr:
|
|
"""When expr and target has the same shape, return expr;
|
|
otherwise return `collapse_sum_to(expr, target.ty.shape)`.
|
|
|
|
Will use BlockBuilder to normalize expr first.
|
|
"""
|
|
target_shape = _get_shape(input)
|
|
expr_ty = _get_shape(bb.normalize(input_grad)).ty
|
|
target_ty = target_shape.ty
|
|
assert isinstance(expr_ty, ShapeType)
|
|
assert isinstance(target_ty, ShapeType)
|
|
|
|
def _check_shape_equal(lhs: ShapeType, rhs: ShapeType):
|
|
if len(lhs.values) != len(rhs.values):
|
|
return False
|
|
analyzer = Analyzer()
|
|
for i, field in enumerate(lhs.values):
|
|
if not analyzer.can_prove_equal(field, rhs.values[i]):
|
|
return False
|
|
return True
|
|
|
|
return (
|
|
input_grad
|
|
if _check_shape_equal(expr_ty, target_ty)
|
|
else collapse_sum_to(input_grad, target_shape)
|
|
)
|
|
|
|
|
|
def _get_shape_prod(expr, axis):
|
|
# Requires static shape
|
|
shape = _get_shape(expr)
|
|
if axis is None:
|
|
return functools.reduce(operator.mul, (int(i) for i in shape), 1)
|
|
return functools.reduce(operator.mul, (int(shape[int(i)]) for i in axis), 1)
|
|
|
|
|
|
##################### Binary #####################
|
|
|
|
|
|
@register_gradient("relax.add")
|
|
def add_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of add.
|
|
|
|
Forward Form:
|
|
`z = relax.add(x, y)`
|
|
|
|
Backward:
|
|
Returns `[z_output_grad, z_grad]`.
|
|
"""
|
|
return [
|
|
_fit_shape(ctx, output_grad, orig_call.args[0]),
|
|
_fit_shape(ctx, output_grad, orig_call.args[1]),
|
|
]
|
|
|
|
|
|
@register_gradient("relax.subtract")
|
|
def subtract_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of subtract.
|
|
|
|
Forward Form:
|
|
`z = relax.subtract(x, y)`
|
|
|
|
Backward:
|
|
Returns `[z_output_grad, -z_grad]`.
|
|
"""
|
|
return [
|
|
_fit_shape(ctx, output_grad, orig_call.args[0]),
|
|
_fit_shape(ctx, -output_grad, orig_call.args[1]),
|
|
]
|
|
|
|
|
|
@register_gradient("relax.multiply")
|
|
def multiply_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of multiply.
|
|
|
|
Forward Form:
|
|
`z = relax.multiply(x, y)`
|
|
|
|
Backward:
|
|
Returns `[z_grad * y, z_grad * x]`.
|
|
"""
|
|
x, y = orig_call.args
|
|
return [
|
|
_fit_shape(ctx, output_grad * y, x),
|
|
_fit_shape(ctx, output_grad * x, y),
|
|
]
|
|
|
|
|
|
@register_gradient("relax.divide")
|
|
def divide_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of divide.
|
|
|
|
Forward Form:
|
|
`z = relax.divide(x, y)`
|
|
|
|
Backward:
|
|
Returns `[z_grad / y, -z_grad * z / y]`.
|
|
"""
|
|
x, y = orig_call.args
|
|
return [
|
|
_fit_shape(ctx, output_grad / y, x),
|
|
_fit_shape(ctx, -output_grad * orig_var / y, y),
|
|
]
|
|
|
|
|
|
@register_gradient("relax.power")
|
|
def power_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of power.
|
|
|
|
Forward Form:
|
|
`z = relax.power(x, y)`
|
|
|
|
Backward:
|
|
Returns `[y * x ** (y-1) * z_grad, z * ln(x) * z_grad]`.
|
|
|
|
The gradient w.r.t. the second parameter, y, makes sense only when x > 0.
|
|
"""
|
|
x, y = orig_call.args
|
|
one = relax.const(1, _get_dtype(y))
|
|
return [
|
|
_fit_shape(ctx, output_grad * y * (x ** (y - one)), x),
|
|
_fit_shape(ctx, output_grad * orig_var * log(x), y),
|
|
]
|
|
|
|
|
|
@register_gradient("relax.maximum")
|
|
def maximum_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of maximum.
|
|
|
|
Forward Form:
|
|
`z = relax.maximum(x, y)`
|
|
|
|
Backward:
|
|
Returns `[where(x < y, 0, z_grad), where(x >= y, 0, z_grad)]`.
|
|
"""
|
|
x = orig_call.args[0]
|
|
y = orig_call.args[1]
|
|
zero = relax.const(0, _get_dtype(x))
|
|
return [
|
|
_fit_shape(ctx, where(less(x, y), zero, output_grad), x),
|
|
_fit_shape(ctx, where(greater_equal(x, y), zero, output_grad), y),
|
|
]
|
|
|
|
|
|
@register_gradient("relax.minimum")
|
|
def minimum_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of minimum.
|
|
|
|
Forward Form:
|
|
`z = relax.minimum(x, y)`
|
|
|
|
Backward:
|
|
Returns `[where(x >= y, 0, z_grad), where(x < y, 0, z_grad)]`.
|
|
"""
|
|
x = orig_call.args[0]
|
|
y = orig_call.args[1]
|
|
zero = relax.const(0, _get_dtype(x))
|
|
return [
|
|
_fit_shape(ctx, where(greater_equal(x, y), zero, output_grad), x),
|
|
_fit_shape(ctx, where(less(x, y), zero, output_grad), y),
|
|
]
|
|
|
|
|
|
##################### Binary Comparison #####################
|
|
|
|
# For comparison operators, the gradients are no_grad
|
|
|
|
|
|
@register_gradient("relax.equal")
|
|
def equal_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0]), no_grad(orig_call.args[1])]
|
|
|
|
|
|
@register_gradient("relax.greater")
|
|
def greater_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0]), no_grad(orig_call.args[1])]
|
|
|
|
|
|
@register_gradient("relax.greater_equal")
|
|
def greater_equal_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0]), no_grad(orig_call.args[1])]
|
|
|
|
|
|
@register_gradient("relax.less")
|
|
def less_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0]), no_grad(orig_call.args[1])]
|
|
|
|
|
|
@register_gradient("relax.less_equal")
|
|
def less_equal_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0]), no_grad(orig_call.args[1])]
|
|
|
|
|
|
@register_gradient("relax.not_equal")
|
|
def not_equal_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0]), no_grad(orig_call.args[1])]
|
|
|
|
|
|
##################### Create #####################
|
|
|
|
# For zeros/ones/full operators, the gradients are no_grad.
|
|
|
|
|
|
@register_gradient("relax.zeros_like")
|
|
def zeros_like_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0])]
|
|
|
|
|
|
@register_gradient("relax.ones_like")
|
|
def ones_like_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0])]
|
|
|
|
|
|
@register_gradient("relax.full_like")
|
|
def full_like_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0]), no_grad(orig_call.args[1])]
|
|
|
|
|
|
@register_gradient("relax.zeros")
|
|
def zeros_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0])]
|
|
|
|
|
|
@register_gradient("relax.ones")
|
|
def ones_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0])]
|
|
|
|
|
|
@register_gradient("relax.full")
|
|
def full_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
return [no_grad(orig_call.args[0]), no_grad(orig_call.args[1])]
|
|
|
|
|
|
# Other create gradients operators
|
|
|
|
|
|
@register_gradient("relax.triu")
|
|
def triu_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of triu.
|
|
|
|
Forward Form:
|
|
`y = relax.triu(x, k)`
|
|
|
|
Backward:
|
|
Returns `[triu(y_grad, k)]`.
|
|
"""
|
|
k = orig_call.args[1]
|
|
return [triu(output_grad, k)]
|
|
|
|
|
|
##################### Unary #####################
|
|
|
|
|
|
@register_gradient("relax.abs")
|
|
def abs_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of abs.
|
|
|
|
Forward Form:
|
|
`y = relax.abs(x)`
|
|
|
|
Backward:
|
|
Returns `[y_grad * where(x < 0, -1, 1)]`.
|
|
"""
|
|
x = orig_call.args[0]
|
|
zero = relax.const(0, _get_dtype(x))
|
|
one = relax.const(1, _get_dtype(x))
|
|
return [output_grad * where(less(x, zero), -one, one)]
|
|
|
|
|
|
@register_gradient("relax.cos")
|
|
def cos_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of cos.
|
|
|
|
Forward Form:
|
|
`y = relax.cos(x)`
|
|
|
|
Backward:
|
|
Returns `[-y_grad * sin(x)]`.
|
|
"""
|
|
return [-output_grad * sin(orig_call.args[0])]
|
|
|
|
|
|
@register_gradient("relax.exp")
|
|
def exp_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of exp.
|
|
|
|
Forward Form:
|
|
`y = relax.exp(x)`
|
|
|
|
Backward:
|
|
Returns `[y_grad * y]`.
|
|
"""
|
|
return [output_grad * orig_var]
|
|
|
|
|
|
@register_gradient("relax.log")
|
|
def log_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of log.
|
|
|
|
Forward Form:
|
|
`y = relax.log(x)`
|
|
|
|
Backward:
|
|
Returns `[y_grad / x]`.
|
|
"""
|
|
return [output_grad / orig_call.args[0]]
|
|
|
|
|
|
@register_gradient("relax.negative")
|
|
def negative_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of negative.
|
|
|
|
Forward Form:
|
|
`y = relax.negative(x)`
|
|
|
|
Backward:
|
|
Returns `[-y_grad]`.
|
|
"""
|
|
return [-output_grad]
|
|
|
|
|
|
@register_gradient("relax.sigmoid")
|
|
def sigmoid_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of sigmoid.
|
|
|
|
Forward Form:
|
|
`y = relax.sigmoid(x)`
|
|
|
|
Backward:
|
|
Returns `[y_grad * y * (1 - y)]`.
|
|
"""
|
|
one = relax.const(1, _get_dtype(orig_call.args[0]))
|
|
return [output_grad * orig_var * (one - orig_var)]
|
|
|
|
|
|
@register_gradient("relax.sin")
|
|
def sin_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of sin.
|
|
|
|
Forward Form:
|
|
`y = relax.sin(x)`
|
|
|
|
Backward:
|
|
Returns `[y_grad * cos(x)]`.
|
|
"""
|
|
return [output_grad * cos(orig_call.args[0])]
|
|
|
|
|
|
@register_gradient("relax.sqrt")
|
|
def sqrt_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of sqrt.
|
|
|
|
Forward Form:
|
|
`y = relax.sqrt(x)`
|
|
|
|
Backward:
|
|
Returns `[0.5 * y_grad / y]`.
|
|
"""
|
|
x = orig_call.args[0]
|
|
cst = relax.const(0.5, _get_dtype(x))
|
|
return [cst * output_grad / orig_var]
|
|
|
|
|
|
@register_gradient("relax.tanh")
|
|
def tanh_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of tanh.
|
|
|
|
Forward Form:
|
|
`y = relax.tanh(x)`
|
|
|
|
Backward:
|
|
Returns `[y_grad * (1 - y * y)]`.
|
|
"""
|
|
one = relax.const(1, _get_dtype(orig_call.args[0]))
|
|
return [output_grad * (one - orig_var * orig_var)]
|
|
|
|
|
|
##################### Statistical #####################
|
|
|
|
|
|
@register_gradient("relax.sum")
|
|
def sum_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of sum.
|
|
|
|
Forward Form:
|
|
`y = relax.sum(x, axis, keepdims)`
|
|
|
|
Backward:
|
|
Returns `[broadcast_to(y_output_grad, x.shape)]`.
|
|
|
|
If `keepdims=False`, the summed axis will be added back.
|
|
"""
|
|
axis = orig_call.attrs.axis
|
|
keepdims = orig_call.attrs.keepdims
|
|
if not keepdims and axis:
|
|
output_grad = expand_dims(output_grad, axis)
|
|
return [broadcast_to(output_grad, _get_shape(orig_call.args[0]))]
|
|
|
|
|
|
@register_gradient("relax.mean")
|
|
def mean_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of mean.
|
|
|
|
Forward Form:
|
|
`y = relax.mean(x, axis, keepdims)`
|
|
|
|
Backward:
|
|
Returns `[broadcast_to(y_output_grad, x.shape) / prod(x.shape[i] for i in axis)]`.
|
|
|
|
If `keepdims=False`, the mean axis will be added back.
|
|
"""
|
|
axis = orig_call.attrs.axis
|
|
keepdims = orig_call.attrs.keepdims
|
|
output_grad = output_grad / relax.const(
|
|
_get_shape_prod(orig_call.args[0], axis), _get_dtype(output_grad)
|
|
)
|
|
if not keepdims and axis:
|
|
output_grad = expand_dims(output_grad, axis)
|
|
return [broadcast_to(output_grad, _get_shape(orig_call.args[0]))]
|
|
|
|
|
|
@register_gradient("relax.variance")
|
|
def variance_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of variance.
|
|
|
|
Forward Form:
|
|
`y = relax.variance(x, axis, keepdims)`
|
|
|
|
Backward:
|
|
Returns `[broadcast_to(y_output_grad, x.shape)]`.
|
|
|
|
If `keepdims=False`, the summed axis will be added back.
|
|
"""
|
|
x = orig_call.args[0]
|
|
axis = orig_call.attrs.axis
|
|
keepdims = orig_call.attrs.keepdims
|
|
shape_prod = _get_shape_prod(x, axis)
|
|
dtype = _get_dtype(x)
|
|
grad1 = relax.const(2.0 / shape_prod, dtype) * x
|
|
grad2 = relax.const(2.0 / shape_prod / shape_prod, dtype) * sum(x, axis, keepdims=True)
|
|
if not keepdims and axis:
|
|
output_grad = expand_dims(output_grad, axis)
|
|
return [output_grad * (grad1 - grad2)]
|
|
|
|
|
|
##################### Manipulate #####################
|
|
|
|
|
|
@register_gradient("relax.permute_dims")
|
|
def permute_dims_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of permute_dims.
|
|
|
|
Forward Form:
|
|
`y = relax.permute_dims(x, axes)`
|
|
|
|
Backward:
|
|
Returns grad transposed over the **inverse permutation** of the original permute_dims axes.
|
|
"""
|
|
axes = orig_call.attrs.axes
|
|
if axes:
|
|
dims = len(axes)
|
|
new_axes = [0] * dims
|
|
for i in range(dims):
|
|
new_axes[int(axes[i])] = i
|
|
return [permute_dims(output_grad, axes=new_axes)]
|
|
return [permute_dims(output_grad)]
|
|
|
|
|
|
@register_gradient("relax.concat")
|
|
def concat_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of concat.
|
|
|
|
Forward Form:
|
|
`y = relax.concat((x1, x2, x3), axis)`
|
|
|
|
Backward:
|
|
Returns `[split(y_output_grad, [x1.shape[axis], x1.shape[axis] + x2.shape[axis]], axis)]`.
|
|
"""
|
|
axis = orig_call.attrs.axis
|
|
assert axis is not None
|
|
axis = int(axis)
|
|
split_indices: list[Expr] = []
|
|
ty = orig_call.args[0].ty
|
|
assert isinstance(ty, relax.TupleType)
|
|
for i in range(len(ty.fields) - 1):
|
|
tensor_ty = ty.fields[i]
|
|
assert isinstance(tensor_ty, relax.TensorType)
|
|
assert tensor_ty.shape is not None
|
|
index = tensor_ty.shape[axis]
|
|
if i > 0:
|
|
index += split_indices[i - 1]
|
|
split_indices.append(index)
|
|
return [split(output_grad, split_indices, axis)]
|
|
|
|
|
|
@register_gradient("relax.split")
|
|
def split_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of split.
|
|
|
|
Forward Form:
|
|
`y = relax.split(x, indices, axis)`
|
|
|
|
Backward:
|
|
Returns `[concat(y_output_grad, axis)]`.
|
|
"""
|
|
axis = orig_call.attrs.axis
|
|
axis = int(axis)
|
|
return [concat(output_grad, axis)]
|
|
|
|
|
|
@register_gradient("relax.expand_dims")
|
|
def expand_dims_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of expand_dims.
|
|
|
|
Forward Form:
|
|
`y = relax.expand_dims(x, axis)`
|
|
|
|
Backward:
|
|
Returns `[squeeze_dims(y_grad, axis)]`.
|
|
"""
|
|
return [squeeze(output_grad, orig_call.attrs.axis)]
|
|
|
|
|
|
@register_gradient("relax.reshape")
|
|
def reshape_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of reshape.
|
|
|
|
Forward Form:
|
|
`y = relax.reshape(x, new_shape)`
|
|
|
|
Backward:
|
|
Returns `[reshape(y_grad, x.shape), no_grad]`.
|
|
|
|
The second parameter, the target ShapeExpr, is not differentiable.
|
|
"""
|
|
return [
|
|
reshape(output_grad, _get_shape(orig_call.args[0])),
|
|
no_grad(orig_call.args[1]),
|
|
]
|
|
|
|
|
|
@register_gradient("relax.cumsum")
|
|
def cumsum_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of cumsum.
|
|
|
|
Forward Form:
|
|
`y = relax.cumsum(x, axis)`
|
|
|
|
Backward:
|
|
The "reversed" cumsum along the same axis. Implemented by some tricks now.
|
|
"""
|
|
|
|
axis = orig_call.attrs.axis
|
|
dtype = orig_call.attrs.dtype
|
|
x_shape = _get_shape(orig_call.args[0])
|
|
|
|
if axis is not None:
|
|
axis = int(axis)
|
|
grad = sum(output_grad, axis, keepdims=True) - cumsum(output_grad, axis) + output_grad
|
|
else:
|
|
grad = reshape(
|
|
sum(output_grad, keepdims=True) - cumsum(output_grad) + flatten(output_grad), x_shape
|
|
)
|
|
|
|
if dtype is not None:
|
|
grad = astype(grad, _get_dtype(orig_call.args[0]))
|
|
|
|
return [grad]
|
|
|
|
|
|
@register_gradient("relax.broadcast_to")
|
|
def broadcast_to_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of broadcast_to.
|
|
|
|
Forward Form:
|
|
`y = relax.broadcast_to(x, new_shape)`
|
|
|
|
Backward:
|
|
Returns `[collapse_sum_to(y_grad, x.shape), no_grad]`.
|
|
|
|
The second parameter, the target ShapeExpr, is not differentiable.
|
|
"""
|
|
return [
|
|
collapse_sum_to(output_grad, _get_shape(orig_call.args[0])),
|
|
no_grad(orig_call.args[1]),
|
|
]
|
|
|
|
|
|
##################### Index #####################
|
|
|
|
|
|
@register_gradient("relax.take")
|
|
def take_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of take.
|
|
|
|
Forward Form:
|
|
`y = relax.take(x, indices, axis)`
|
|
|
|
Backward:
|
|
Returns [x_grad, no_grad].
|
|
|
|
The second parameter, the indices, is not differentiable.
|
|
"""
|
|
|
|
axis = orig_call.attrs["axis"]
|
|
|
|
return [
|
|
take_backward(output_grad, orig_call.args[0], orig_call.args[1], axis),
|
|
no_grad(orig_call.args[1]),
|
|
]
|
|
|
|
|
|
##################### Search #####################
|
|
|
|
|
|
@register_gradient("relax.where")
|
|
def where_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of where.
|
|
|
|
Forward Form:
|
|
`y = relax.where(cond, x1, x2)`
|
|
|
|
Backward:
|
|
Returns `[where(cond, y_grad, 0), where(cond, 0, y_grad)]`.
|
|
|
|
The first parameter, the condition, is not differentiable.
|
|
"""
|
|
|
|
cond = orig_call.args[0]
|
|
x1_zero = relax.const(0, _get_dtype(orig_call.args[1]))
|
|
x2_zero = relax.const(0, _get_dtype(orig_call.args[2]))
|
|
|
|
return [
|
|
no_grad(orig_call.args[0]),
|
|
where(cond, output_grad, x1_zero),
|
|
where(cond, x2_zero, output_grad),
|
|
]
|
|
|
|
|
|
##################### Linear Algebra #####################
|
|
|
|
|
|
@register_gradient("relax.matmul")
|
|
def matmul_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of matmul.
|
|
|
|
Forward Form:
|
|
`c = relax.matmul(a, b)`
|
|
|
|
Backward:
|
|
Generally, returns `[c_grad @ b^T, a^T @ c_grad]`.
|
|
|
|
Here we only transpose the last two dimensions because of the definition
|
|
of batch matmul. Note that ndim=1 should be treaded specially.
|
|
"""
|
|
|
|
tensor_a, tensor_b = orig_call.args
|
|
|
|
a_dim = len(_get_shape(tensor_a))
|
|
b_dim = len(_get_shape(tensor_b))
|
|
|
|
def _transpose_last_two_dim(tensor, ndim):
|
|
"""Helper function for reversing the last two dimensions."""
|
|
assert ndim > 1
|
|
return permute_dims(
|
|
tensor, axes=[i if i < ndim - 2 else 2 * ndim - 3 - i for i in range(ndim)]
|
|
)
|
|
|
|
if a_dim > 1 and b_dim > 1:
|
|
a_grad = matmul(output_grad, _transpose_last_two_dim(tensor_b, b_dim))
|
|
b_grad = matmul(_transpose_last_two_dim(tensor_a, a_dim), output_grad)
|
|
elif a_dim == 1 and b_dim > 1:
|
|
a_expand = expand_dims(tensor_a, 1)
|
|
grad_expand = expand_dims(output_grad, -2)
|
|
a_grad = matmul(grad_expand, _transpose_last_two_dim(tensor_b, b_dim))
|
|
b_grad = matmul(a_expand, grad_expand)
|
|
elif b_dim == 1 and a_dim > 1:
|
|
b_expand = expand_dims(tensor_b, 0)
|
|
grad_expand = expand_dims(output_grad, -1)
|
|
a_grad = matmul(grad_expand, b_expand)
|
|
b_grad = squeeze(
|
|
matmul(_transpose_last_two_dim(tensor_a, a_dim), grad_expand), axis=-1
|
|
) # squeeze last dim
|
|
else:
|
|
assert a_dim == 1 and b_dim == 1
|
|
a_grad = output_grad * tensor_b
|
|
b_grad = output_grad * tensor_a
|
|
|
|
return [
|
|
_fit_shape(ctx, a_grad, tensor_a),
|
|
_fit_shape(ctx, b_grad, tensor_b),
|
|
]
|
|
|
|
|
|
##################### Datatype #####################
|
|
|
|
|
|
@register_gradient("relax.astype")
|
|
def astype_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of astype.
|
|
|
|
Forward Form:
|
|
`y = relax.astype(x, dtype_of_y)`
|
|
|
|
Backward:
|
|
Returns `[astype(y_grad, dtype_of_x)]`.
|
|
"""
|
|
return [astype(output_grad, _get_dtype(orig_call.args[0]))]
|
|
|
|
|
|
##################### Neural network #####################
|
|
|
|
|
|
@register_gradient("relax.nn.relu")
|
|
def relu_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of relu.
|
|
|
|
Forward Form:
|
|
`y = relax.relu(x)`
|
|
|
|
Backward:
|
|
Returns `[where(x < 0, 0, y_grad)]`.
|
|
"""
|
|
x = orig_call.args[0]
|
|
zero = relax.const(0, _get_dtype(x))
|
|
return [where(less(x, zero), zero, output_grad)]
|
|
|
|
|
|
@register_gradient("relax.nn.silu")
|
|
def silu_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of silu.
|
|
|
|
Forward Form:
|
|
`y = relax.silu(x)`
|
|
|
|
Backward:
|
|
Returns `[y_grad * (sigmoid(x) + y * (1 - sigmoid(x)))]`.
|
|
"""
|
|
x = orig_call.args[0]
|
|
sig = sigmoid(x)
|
|
one = relax.const(1, _get_dtype(x))
|
|
return [output_grad * (sig + orig_var * (one - sig))]
|
|
|
|
|
|
@register_gradient("relax.nn.softmax")
|
|
def softmax_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of softmax.
|
|
|
|
Forward Form:
|
|
`y = relax.softmax(x, axis)`
|
|
|
|
Backward:
|
|
Returns `[(y_grad - sum(y_grad * y, axis, keepdims=True)) * y]`
|
|
"""
|
|
return [(output_grad - sum(output_grad * orig_var, orig_call.attrs.axis, True)) * orig_var]
|
|
|
|
|
|
@register_gradient("relax.nn.log_softmax")
|
|
def log_softmax_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of log_softmax.
|
|
|
|
Forward Form:
|
|
`y = relax.log_softmax(x, axis)`
|
|
|
|
Backward:
|
|
Returns `[y_grad - sum(y_grad, axis, keepdims=True) * exp(y)]`
|
|
"""
|
|
y_exp = exp(orig_var)
|
|
return [output_grad - sum(output_grad, orig_call.attrs.axis, True) * y_exp]
|
|
|
|
|
|
@register_gradient("relax.nn.cross_entropy_with_logits")
|
|
def cross_entropy_with_logits_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of cross_entropy_with_logits.
|
|
|
|
Forward Form:
|
|
`z = relax.nn.cross_entropy_with_logits(x, y)`
|
|
|
|
Backward:
|
|
Returns `[-z_grad * y, -z_grad * x]`.
|
|
If it has batch_size N, the results should divide by N.
|
|
"""
|
|
x, y = orig_call.args
|
|
|
|
if x.ty.ndim > 1:
|
|
batch_size = int(_get_shape(x)[0])
|
|
output_grad = output_grad / relax.const(batch_size, _get_dtype(output_grad))
|
|
|
|
return [-output_grad * y, -output_grad * x]
|
|
|
|
|
|
# TODO(chaofan, yixin): remove nll_loss_backward and register the gradient using existing operators
|
|
# This may require one_hot, strided_set, etc.
|
|
@register_gradient("relax.nn.nll_loss")
|
|
def nll_loss_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
):
|
|
"""Gradient of nll_loss.
|
|
|
|
Forward Form:
|
|
`z = relax.nn.nll_loss(predictions, targets, weights, reduction, ignore_index)`
|
|
|
|
Suppose that `out = nll_loss(predictions, targets, weights, "none", ignore_index)`, and
|
|
`z = reduction(out)` where reduction is in `["none", "mean", "sum"]`.
|
|
|
|
Backward:
|
|
First find the gradient w.r.t. `out`. Assume it is `out_grad`.
|
|
|
|
Gererally, the gradient w.r.t. predictions is
|
|
|
|
`predictions_grad[n, c, i_1, ..., i_k] = -o * w if c == t else 0`, where
|
|
- `o = out_grad[n, i_1, ..., i_k]`,
|
|
- `w = weights[n, i_1, ..., i_k]`,
|
|
- `t = targets[n, i_1, ..., i_k]`.
|
|
|
|
Additional checks are added if `ignore_index >= 0`, `weights=None`, or the predictions
|
|
provided do not have batch.
|
|
|
|
The gradient w.r.t. targets and weights are not available.
|
|
"""
|
|
pred_grad = nll_loss_backward(
|
|
output_grad,
|
|
orig_call.args[0],
|
|
orig_call.args[1],
|
|
weights=orig_call.args[2] if len(orig_call.args) == 3 else None,
|
|
reduction=orig_call.attrs.reduction,
|
|
ignore_index=orig_call.attrs.ignore_index,
|
|
)
|
|
if len(orig_call.args) == 2:
|
|
return [pred_grad, no_grad(orig_call.args[1])]
|
|
|
|
return [pred_grad, no_grad(orig_call.args[1]), no_grad(orig_call.args[2])]
|
|
|
|
|
|
@register_gradient("relax.nn.conv2d")
|
|
def conv2d_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
) -> list[Expr]:
|
|
"""Gradient of conv2d. Now only supports `NCHW` data layout and `OIHW` kernel layout.
|
|
|
|
Forward Form:
|
|
`y = relax.nn.conv2d(x, weight, strides, padding, dilation, groups, data_layout, \
|
|
kernel_layout, out_layout, out_dtype)`
|
|
|
|
Backward:
|
|
Returns `[x_grad, weight_grad]`
|
|
"""
|
|
attrs = orig_call.attrs
|
|
assert attrs.data_layout == "NCHW", "only support NCHW data layout"
|
|
assert attrs.kernel_layout == "OIHW", "only support OIHW kernel layout"
|
|
assert attrs.out_layout == "NCHW", "only support NCHW output layout"
|
|
|
|
assert len(attrs.padding) == 4
|
|
assert len(attrs.strides) == 2
|
|
assert len(attrs.dilation) == 2
|
|
|
|
# calculate output_padding
|
|
data, weight = orig_call.args
|
|
batch, out_channel, grad_h, grad_w = _get_shape(orig_var)
|
|
_, in_channel, in_h, in_w = _get_shape(data)
|
|
_, _, filter_h, filter_w = _get_shape(weight)
|
|
|
|
pad_top, pad_left, pad_bottom, pad_right = attrs.padding
|
|
stride_h, stride_w = attrs.strides
|
|
dilation_h, dilation_w = attrs.dilation
|
|
|
|
out_h = (grad_h - 1) * stride_h - pad_top - pad_bottom + filter_h
|
|
out_w = (grad_w - 1) * stride_w - pad_left - pad_right + filter_w
|
|
|
|
output_padding = (int(in_h - out_h), int(in_w - out_w))
|
|
|
|
data_grad = conv2d_transpose( # type: ignore
|
|
output_grad,
|
|
orig_call.args[1],
|
|
attrs.strides,
|
|
attrs.padding,
|
|
output_padding,
|
|
attrs.dilation,
|
|
attrs.groups,
|
|
attrs.out_layout,
|
|
attrs.kernel_layout[1] + attrs.kernel_layout[0] + attrs.kernel_layout[2:],
|
|
attrs.data_layout,
|
|
attrs.out_dtype,
|
|
)
|
|
|
|
if attrs.groups != 1:
|
|
data = reshape(data, (batch, attrs.groups, in_channel // attrs.groups, in_h, in_w))
|
|
data = permute_dims(data, [1, 0, 2, 3, 4])
|
|
data = reshape(data, (batch * attrs.groups, in_channel // attrs.groups, in_h, in_w))
|
|
|
|
weight_grad = conv2d(
|
|
data,
|
|
output_grad,
|
|
strides=attrs.dilation,
|
|
padding=attrs.padding,
|
|
dilation=attrs.strides,
|
|
groups=attrs.groups,
|
|
out_dtype=attrs.out_dtype,
|
|
data_layout="CNHW",
|
|
kernel_layout="IOHW",
|
|
out_layout="CNHW",
|
|
)
|
|
|
|
# infer shape of weight_grad
|
|
weight_grad_h = (in_h - (grad_h - 1) * stride_h - 1 + pad_top + pad_bottom) // dilation_h + 1
|
|
weight_grad_w = (in_w - (grad_w - 1) * stride_w - 1 + pad_left + pad_right) // dilation_w + 1
|
|
|
|
assert weight_grad_h >= filter_h
|
|
assert weight_grad_w >= filter_w
|
|
|
|
if weight_grad_h > filter_h or weight_grad_w > filter_w:
|
|
weight_grad = strided_slice(
|
|
weight_grad,
|
|
axes=[0, 1, 2, 3],
|
|
begin=[0, 0, 0, 0],
|
|
end=[out_channel, in_channel // attrs.groups, filter_h, filter_w],
|
|
)
|
|
|
|
return [data_grad, weight_grad]
|
|
|
|
|
|
@register_gradient("relax.nn.max_pool2d")
|
|
def max_pool2d_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
):
|
|
"""Gradient of max_pool2d.
|
|
|
|
Forward Form:
|
|
`y = relax.nn.max_pool2d(x, pool_size, strides, padding, dilation, ceil_mode, layout, \
|
|
out_layout)`
|
|
|
|
Backward:
|
|
Returns `[x_grad]`
|
|
"""
|
|
return [
|
|
max_pool2d_backward( # type: ignore
|
|
output_grad,
|
|
orig_call.args[0],
|
|
orig_call.attrs.pool_size,
|
|
orig_call.attrs.strides,
|
|
orig_call.attrs.padding,
|
|
orig_call.attrs.dilation,
|
|
orig_call.attrs.ceil_mode,
|
|
orig_call.attrs.count_include_pad,
|
|
orig_call.attrs.layout,
|
|
orig_call.attrs.out_layout,
|
|
)
|
|
]
|
|
|
|
|
|
@register_gradient("relax.nn.avg_pool2d")
|
|
def avg_pool2d_grad(
|
|
orig_var: Var,
|
|
orig_call: Call,
|
|
output_grad: Var,
|
|
ctx: BlockBuilder,
|
|
):
|
|
"""Gradient of avg_pool2d.
|
|
|
|
Forward Form:
|
|
`y = relax.nn.avg_pool2d(x, pool_size, strides, padding, dilation, ceil_mode, layout, \
|
|
out_layout)`
|
|
|
|
Backward:
|
|
Returns `[x_grad]`
|
|
"""
|
|
return [
|
|
avg_pool2d_backward( # type: ignore
|
|
output_grad,
|
|
orig_call.args[0],
|
|
orig_call.attrs.pool_size,
|
|
orig_call.attrs.strides,
|
|
orig_call.attrs.padding,
|
|
orig_call.attrs.dilation,
|
|
orig_call.attrs.ceil_mode,
|
|
orig_call.attrs.count_include_pad,
|
|
orig_call.attrs.layout,
|
|
orig_call.attrs.out_layout,
|
|
)
|
|
]
|