# 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. # ruff: noqa: E501, F841 import pytest import tvm.testing from tvm.ir.base import assert_structural_equal from tvm.relax.training import AppendLoss from tvm.script import ir as I from tvm.script import relax as R def test_simple(): # fmt: off @I.ir_module class Before: @R.function def main(x: R.Tensor((3, 3), "float32"), y: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = x + y R.output(gv0) return gv0 @R.function def loss(arg1: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = R.sum(arg1) R.output(gv0) return gv0 @I.ir_module class Expected: @R.function def main(x: R.Tensor((3, 3), "float32"), y: R.Tensor((3, 3), "float32")) -> R.Tensor((3, 3), "float32"): with R.dataflow(): gv0: R.Tensor((3, 3), "float32") = R.add(x, y) R.output(gv0) return gv0 @R.function def main_loss(x: R.Tensor((3, 3), "float32"), y: R.Tensor((3, 3), "float32")) -> R.Tensor((), "float32"): with R.dataflow(): gv0: R.Tensor((3, 3), "float32") = R.add(x, y) gv0_1: R.Tensor((), "float32") = R.sum(gv0, axis=None, keepdims=False) R.output(gv0_1) return gv0_1 # fmt: on After = AppendLoss("main", loss)(Before) assert_structural_equal(After, Expected) def test_num_backbone_outputs(): # fmt: off @I.ir_module class Before: @R.function def main(x: R.Tensor((3, 3), "float32"), y: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = R.sum(x) gv1 = R.sum(y) R.output(gv0, gv1) return gv0, gv1 @R.function def loss(arg1: R.Tensor((), "float32"), arg2: R.Tensor((), "float32")): with R.dataflow(): gv0 = R.add(arg1, arg2) R.output(gv0) return gv0 @I.ir_module class Expected: @R.function def main(x: R.Tensor((3, 3), "float32"), y: R.Tensor((3, 3), "float32")) -> R.Tuple(R.Tensor((), "float32"), R.Tensor((), "float32")): with R.dataflow(): gv0: R.Tensor((), "float32") = R.sum(x, axis=None, keepdims=False) gv1: R.Tensor((), "float32") = R.sum(y, axis=None, keepdims=False) R.output(gv0, gv1) return (gv0, gv1) @R.function def main_loss(x: R.Tensor((3, 3), "float32"), y: R.Tensor((3, 3), "float32")) -> R.Tensor((), "float32"): with R.dataflow(): gv0: R.Tensor((), "float32") = R.sum(x, axis=None, keepdims=False) gv1: R.Tensor((), "float32") = R.sum(y, axis=None, keepdims=False) gv0_1: R.Tensor((), "float32") = R.add(gv0, gv1) R.output(gv0_1) return gv0_1 # fmt: on After = AppendLoss("main", loss, 2)(Before) assert_structural_equal(After, Expected) def test_extra_params(): # fmt: off @I.ir_module class Before: @R.function def main(x: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = R.sum(x) gv1 = R.add(x, x) gv2 = x R.output(gv0, gv1, gv2) return gv0, gv1, gv2 @R.function def loss( arg1: R.Tensor((), "float32"), arg2: R.Tensor((3, 3), "float32"), arg3: R.Tensor((3, 3), "float32"), ): with R.dataflow(): gv0 = R.add(arg2, arg3) gv1 = R.sum(gv0) R.output(gv1) return gv1 @I.ir_module class Expected: @R.function def main(x: R.Tensor((3, 3), "float32")) -> R.Tuple(R.Tensor((), "float32"), R.Tensor((3, 3), "float32"), R.Tensor((3, 3), "float32")): with R.dataflow(): gv0: R.Tensor((), "float32") = R.sum(x, axis=None, keepdims=False) gv1: R.Tensor((3, 3), "float32") = R.add(x, x) gv2: R.Tensor((3, 3), "float32") = x R.output(gv0, gv1, gv2) return (gv0, gv1, gv2) @R.function def main_loss(x: R.Tensor((3, 3), "float32"), arg3: R.Tensor((3, 3), "float32")) -> R.Tuple(R.Tensor((), "float32"), R.Tensor((3, 3), "float32")): with R.dataflow(): gv0: R.Tensor((), "float32") = R.sum(x, axis=None, keepdims=False) gv1: R.Tensor((3, 3), "float32") = R.add(x, x) gv2: R.Tensor((3, 3), "float32") = x gv0_1: R.Tensor((3, 3), "float32") = R.add(gv1, arg3) gv1_1: R.Tensor((), "float32") = R.sum(gv0_1, axis=None, keepdims=False) R.output(gv2, gv1_1) return (gv1_1, gv2) # fmt: on After = AppendLoss("main", loss, 2)(Before) assert_structural_equal(After, Expected) def test_error_return_value_vs_parameter(): # Type not match # fmt: off @I.ir_module class Module1: @R.function def main(x: R.Tensor((3, 3), "float32"), y: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = R.sum(x) gv1 = R.sum(y) R.output(gv0, gv1) return gv0, gv1 @R.function def loss1(arg1: R.Tensor((), "float64"), arg2: R.Tensor((), "float64")): with R.dataflow(): gv0 = R.add(arg1, arg2) R.output(gv0) return gv0 # fmt: on with pytest.raises(RuntimeError): AppendLoss("main", loss1, 2)(Module1) # The numbers of backbone return value and loss parameter are not enough # fmt: off @I.ir_module class Module2: @R.function def main(x: R.Tensor((3, 3), "float32"), y: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = x + y R.output(gv0) return gv0 @R.function def loss2(arg1: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = R.sum(arg1) R.output(gv0) return gv0 # fmt: on with pytest.raises(RuntimeError): AppendLoss("main", loss2, 2)(Module2) # Backbone returns nested tuple # fmt: off @I.ir_module class Module3: @R.function def main(x: R.Tensor((3, 3), "float32"), y: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = x gv1 = y gv2 = x + y R.output(gv0, gv1, gv2) return gv0, (gv1, gv2) @R.function def loss3(arg1: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = R.sum(arg1) R.output(gv0) return gv0 # fmt: on with pytest.raises(RuntimeError): AppendLoss("main", loss3, 1)(Module3) def test_error_more_blocks(): # backbone more than one blocks # fmt: off @I.ir_module class Module1: @R.function def main(x: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = x R.output(gv0) gv1 = gv0 return gv1 @R.function def loss1(arg: R.Tensor((3, 3), "float32")): with R.dataflow(): gv = R.sum(arg) R.output(gv) return gv # fmt: on with pytest.raises(RuntimeError): AppendLoss("main", loss1)(Module1) # loss more than one blocks # fmt: off @I.ir_module class Module2: @R.function def main(x: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = x R.output(gv0) return gv0 @R.function def loss2(arg: R.Tensor((3, 3), "float32")): with R.dataflow(): gv = R.sum(arg) R.output(gv) gv1 = gv return gv1 # fmt: on with pytest.raises(RuntimeError): AppendLoss("main", loss2)(Module2) def test_loss_return_value(): # loss returns non-scalar var # fmt: off @I.ir_module class Module: @R.function def main(x: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = x R.output(gv0) return gv0 @R.function def loss(arg1: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = arg1 R.output(gv0) return gv0 # fmt: on with pytest.raises(RuntimeError): AppendLoss("main", loss)(Module) # loss returns tuple # fmt: off @I.ir_module class Module: @R.function def main(x: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = x R.output(gv0) return gv0 @R.function def loss(arg1: R.Tensor((3, 3), "float32")): with R.dataflow(): gv0 = R.sum(arg1) gv1 = gv0 + gv0 R.output(gv0, gv1) return gv0, gv1 # fmt: on with pytest.raises(RuntimeError): AppendLoss("main", loss)(Module) if __name__ == "__main__": tvm.testing.main()