Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

242 lines
8.8 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=invalid-name,unused-argument
"""Default legalization function for perators to implement operaor gradients."""
import logging
from tvm import te, tirx, topi
from tvm.ir import Call
from tvm.script.ir_builder import IRBuilder
from tvm.script.ir_builder import tirx as T
from tvm.tirx.script.builder.utils import buffer_proxy
from ...block_builder import BlockBuilder
from ...expr import Expr
from .common import register_legalize
@register_legalize("relax.grad.no_grad")
def _no_grad(bb: BlockBuilder, call: Call) -> Expr:
return call.args[0]
@register_legalize("relax.grad.start_checkpoint")
def _start_checkpoint(bb: BlockBuilder, call: Call) -> Expr:
return call.args[0]
@register_legalize("relax.grad.end_checkpoint")
def _end_checkpoint(bb: BlockBuilder, call: Call) -> Expr:
return call.args[0]
@register_legalize("relax.grad.nll_loss_backward")
def _grad_nll_loss_backward(bb: BlockBuilder, call: Call) -> Expr:
# topi.sum don't support zero-dim x
# we add support for that
def topi_sum_extend(x):
return x if x.ndim == 0 else topi.sum(x)
def te_nll_loss_backward(output_grad, predictions, targets, weights, reduction, ignore_index):
# handle ignore_index
if ignore_index >= 0:
weights = te.compute(
weights.shape,
lambda i: tirx.Select(i == ignore_index, tirx.const(0, weights.dtype), weights(i)),
"weights_new",
)
all_weights = te.compute(targets.shape, lambda *i: weights(targets(*i)), "all_weights")
# handle reduction
if reduction == "sum":
output_grad = topi.broadcast_to(output_grad, targets.shape)
elif reduction == "mean":
weight_sum = topi_sum_extend(all_weights)
output_grad = topi.divide(topi.broadcast_to(output_grad, targets.shape), weight_sum)
# handle no batch
if predictions.ndim == 1:
return te.compute(
predictions.shape,
lambda i: tirx.Select(
i == targets(), -all_weights() * output_grad(), tirx.const(0, predictions.dtype)
),
"pred_grad",
)
return te.compute(
predictions.shape,
lambda *i: tirx.Select(
i[1] == targets(*i[:1], *i[2:]),
-all_weights(*i[:1], *i[2:]) * output_grad(*i[:1], *i[2:]),
tirx.const(0, predictions.dtype),
),
"pred_grad",
)
def te_nll_loss_backward_no_weight(output_grad, predictions, targets, reduction, ignore_index):
weight = topi.full(
(predictions.shape[1] if len(predictions.shape) > 1 else predictions.shape[0],),
predictions.dtype,
1.0,
)
return te_nll_loss_backward(
output_grad, predictions, targets, weight, reduction, ignore_index
)
if len(call.args) == 3:
return bb.call_te(
te_nll_loss_backward_no_weight,
*call.args,
reduction=call.attrs.reduction,
ignore_index=call.attrs.ignore_index,
)
return bb.call_te(
te_nll_loss_backward,
*call.args,
reduction=call.attrs.reduction,
ignore_index=call.attrs.ignore_index,
primfunc_name_hint="nll_loss_backward",
)
@register_legalize("relax.grad.max_pool2d_backward")
def _grad_max_pool2d_backward(bb: BlockBuilder, call: Call) -> Expr:
if not (len(call.attrs.dilation) == 2 and all(i == 1 for i in call.attrs.dilation)):
logging.info("Dilation is not supported in TOPI pool_grad and is not legalized.")
return call
return bb.call_te(
topi.nn.pool_grad,
call.args[0],
call.args[1],
kernel=call.attrs.pool_size,
stride=call.attrs.strides,
padding=call.attrs.padding,
pool_type="max",
ceil_mode=call.attrs.ceil_mode,
count_include_pad=call.attrs.count_include_pad,
layout=call.attrs.layout,
primfunc_name_hint="max_pool2d_backward",
)
@register_legalize("relax.grad.avg_pool2d_backward")
def _grad_avg_pool2d_backward(bb: BlockBuilder, call: Call) -> Expr:
if not (len(call.attrs.dilation) == 2 and all(i == 1 for i in call.attrs.dilation)):
logging.info("Dilation is not supported in TOPI pool_grad and is not legalized.")
return call
return bb.call_te(
topi.nn.pool_grad,
call.args[0],
call.args[1],
kernel=call.attrs.pool_size,
stride=call.attrs.strides,
padding=call.attrs.padding,
pool_type="avg",
ceil_mode=call.attrs.ceil_mode,
count_include_pad=call.attrs.count_include_pad,
layout=call.attrs.layout,
primfunc_name_hint="avg_pool2d_backward",
)
@register_legalize("relax.grad.take_backward")
def _grad_take_backward(bb: BlockBuilder, call: Call) -> Expr:
axis = call.attrs.axis
if axis is not None:
axis = int(axis)
def te_take_backward(output_grad, x, indices):
def gen_ir(output_grad_ptr, x_ptr, indices_ptr, out_ptr):
# pylint: disable=invalid-name
# Use buffer_proxy for flat indexing on multi-dimensional buffers
out = buffer_proxy(out_ptr)
grad = buffer_proxy(output_grad_ptr)
idx = buffer_proxy(indices_ptr)
fused_shape = 1
for i in x_ptr.shape:
fused_shape *= i
assert len(indices_ptr.shape) == 1 # indices in take must be 1-dim Tensor
indices_len = indices_ptr.shape[0]
with IRBuilder() as ib:
with T.seq_scope():
# Init loop (zero-fill output buffer)
with T.serial(fused_shape) as i:
out[i] = tirx.const(0, dtype=x_ptr.dtype)
# Accumulation loop
if axis is not None:
fused_output_grad_shape_pre = 1
fused_output_grad_shape_nxt = 1
for i in range(len(output_grad_ptr.shape)):
if i < axis:
fused_output_grad_shape_pre *= output_grad_ptr.shape[i]
elif i > axis:
fused_output_grad_shape_nxt *= output_grad_ptr.shape[i]
x_axis_len = x_ptr.shape[axis]
with T.serial(
fused_output_grad_shape_pre * fused_output_grad_shape_nxt
) as fused:
i = fused // fused_output_grad_shape_nxt
j = fused % fused_output_grad_shape_nxt
with T.serial(indices_len) as loop_l:
out_idx = (
i * fused_output_grad_shape_nxt * x_axis_len
+ idx[loop_l] * fused_output_grad_shape_nxt
+ j
)
grad_idx = (
i * fused_output_grad_shape_nxt * indices_len
+ loop_l * fused_output_grad_shape_nxt
+ j
)
out[out_idx] = out[out_idx] + grad[grad_idx]
else:
with T.serial(indices_len) as loop_l:
out[idx[loop_l]] = out[idx[loop_l]] + grad[loop_l]
return ib.get()
shape = x.shape
out_buf = tirx.decl_buffer(shape, x.dtype, "out_buf", layout=None)
return te.extern(
[shape],
[output_grad, x, indices],
lambda ins, outs: gen_ir(ins[0], ins[1], ins[2], outs[0]),
dtype=x.dtype,
out_buffers=[out_buf],
name="take_backward",
tag="take_backward",
)
return bb.call_te(
te_take_backward,
call.args[0],
call.args[1],
call.args[2],
primfunc_name_hint="take_backward",
)