Files
apache--tvm/python/tvm/relax/training/utils.py
T
wehub-resource-sync 26446540fa
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

212 lines
7.0 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
"""Utility functions for relax training."""
from collections.abc import Callable
from tvm_ffi import register_global_func
import tvm
from tvm import relax
from tvm.ir import Call
from tvm.relax.block_builder import BlockBuilder
from ..expr import Function, Var
from . import _ffi_api
def AppendLoss(
func_name: str,
loss_function: Function,
num_backbone_outputs: int = 1,
new_func_name: str | None = None,
) -> tvm.ir.transform.Pass:
"""Append the loss function to the backbone function specified by `func_name`. Generally, the
loss function is generated by instances of `relax.training.Loss`.
The backbone function and the loss function should satisfy a few restrictions:
- Both backbone and loss should contain exactly one DataflowBlock.
- Backbone should return either one Var, or a tuple of Vars
- Loss should return a scalar(0-dim Tensor) Var
They should be like:
.. code-block:: python
@R.function
def backbone(input_instances, parameters, states):
with R.dataflow():
# Predicts the result
...
return backbone_result, updated_states
@R.function
def loss(backbone_result, targets):
with R.dataflow():
# calculate the loss between backbone_result and targets
...
# loss should be a scalar Var
return loss
Here each of input_instances, parameters, states, backbone_result and updated_states can
denote a number of parameters.
`states` denote the states that we need to maintain as the training process proceeds, such as
the running mean and the running var of the batch norm operator. The updated states is returned
in `updated_states`. States can be empty if there is no state that needs to be updated.
The appended result contains only one DataflowBlock containing all bindings in backbone and
loss. It will be like:
.. code-block:: python
@R.function
def backbone_loss(input_instances, parameters, states, targets):
with R.dataflow():
# all bindings in backbone and loss
...
return loss, updated_states
Parameters
----------
func_name : str
The name of the backbone function in the IRModule.
loss_func : Function
The loss function.
num_backbone_outputs : int
Specify the number of `prediction_outputs` of the backbone function. Default: 1.
new_func_name : Optional[str]
Specify the name of the appended result. If it is not specified, the name will be
`func_name + "_loss"`.
Returns
-------
ret : Function
The result function.
Examples
--------
.. code-block:: python
@I.ir_module
class Module
@R.function
def predict(x: R.Tensor((2, 4), "float32"), y: R.Tensor((2, 4), "float32")):
with R.dataflow():
out = R.add(x, y)
R.output(out)
return out
@R.function
def loss(predictions: R.Tensor((2, 4), "float32"), labels: R.Tensor((2, 4), "float32")):
with R.dataflow():
lv = R.subtract(predictions, labels)
lv1 = R.multiply(lv, lv)
gv = R.sum(lv1)
R.output(gv)
return gv
expected = AppendLoss("predict", loss)(Module)
expected.show()
Will get
.. code-block:: python
@I.ir_module
class Module
@R.function
def predict(x: R.Tensor((2, 4), "float32"), y: R.Tensor((2, 4), "float32")):
with R.dataflow():
out = R.add(x, y)
R.output(out)
return out
@R.function
def predict_loss(x: R.Tensor((2, 4), "float32"), y: R.Tensor((2, 4), "float32"),
labels: R.Tensor((2, 4), "float32")) -> R.Tensor((), "float32"):
with R.dataflow():
out: R.Tensor((2, 4), "float32") = R.add(x, y)
lv: R.Tensor((2, 4), "float32") = R.subtract(out, labels)
lv1: R.Tensor((2, 4), "float32") = R.multiply(lv, lv)
gv: R.Tensor((), "float32") = R.sum(lv1)
R.output(gv)
return gv
Notes
-----
This util can be replaced if we have inline pass. It is equivalent to inline a tail call in
some sense.
"""
return _ffi_api.AppendLoss( # type: ignore
func_name,
loss_function,
num_backbone_outputs,
new_func_name,
)
def register_te_gradient(te_grad_name: str, te_grad_func: Callable | None = None):
"""Register a te gradient function bind with name te_grad_name. te_grad_name can be referenced
later in call_tir_with_grad nodes.
Parameters
----------
te_grad_name : str
The registered name of the te gradient function. Should be align with the te_grad_name in
call_tir_with_grad nodes.
grad_func : Callable
The te grad function.
It must be a function taking (output_grad: Tensor, arg1: Tensor, arg2: Tensor, ...)
as inputs and returning a list of Tensor created by te.compute.
Returns
-------
mod : IRModule
The mod with corresponding attributes attached.
"""
def register(func: Callable):
func_prefix = "tvm.relax.te_grad._register."
# The handler function is used to let the backend (cpp side) to emit_te.
# It's a wrapper of the te_grad_func.
# It takes the blockbuilder, the gradient var of the output and the forward call expr.
# It will return the emitted var.
def handler(
orig_var: Var, call_tir_with_grad: Call, output_grad: Var, ctx: BlockBuilder
) -> relax.Expr:
return ctx.emit_te(
func,
output_grad,
*call_tir_with_grad.args[1],
**call_tir_with_grad.attrs.te_grad_kwargs,
primfunc_name_hint=te_grad_name,
)
register_global_func(func_prefix + te_grad_name, handler)
return func
return register(te_grad_func) if te_grad_func else register