# 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. import tvm import tvm.testing from tvm.script import ir as I from tvm.script import relax as R from tvm.script import tirx as T def test_remove_unused_relax_parameter(): """A relax parameter may be removed This is only allowed for internal function calls, where all callsites can be updated. For externally-exposed functions, the signature may not be modified. """ @I.ir_module class Before: @R.function def main(A: R.Tensor, B: R.Tensor): return Before.func(A, B) @R.function(private=True) def func(A: R.Tensor, B: R.Tensor) -> R.Tensor: return A @I.ir_module class Expected: @R.function def main(A: R.Tensor, B: R.Tensor): return Expected.func(A) @R.function(private=True) def func(A: R.Tensor) -> R.Tensor: return A After = tvm.relax.transform.RemoveUnusedParameters()(Before) tvm.ir.assert_structural_equal(After, Expected) def test_replace_symbolic_variables(): """If a parameter is only required for its symbolic variables, provide them directly The relax parameter `A` isn't used by the subroutine. However, its shape defines the symbolic variables `m` and `n`. When removing the `R.Tensor` argument, we may need to provide additional parameters to define the symbolic variables. A `PrimType` carries only a dtype and defines no TIR var. Each free symbolic variable is therefore promoted through a 1-D `R.Shape` parameter, which actually *defines* the variable. """ @I.ir_module class Before: @R.function def main(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): return Before.func(A) @R.function(private=True) def func(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): m = T.int64() n = T.int64() return R.zeros(R.shape([m, n]), dtype="float32") @I.ir_module class Expected: @R.function def main(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): m = T.int64() n = T.int64() out: R.Tensor([m, n], "float32") = Expected.func(R.shape([n]), R.shape([m])) return out @R.function(private=True) def func(param_n: R.Shape(["n"]), param_m: R.Shape(["m"])) -> R.Tensor( ["m", "n"], "float32" ): m = T.int64() n = T.int64() return R.zeros(R.shape([m, n]), dtype="float32") After = tvm.relax.transform.RemoveUnusedParameters()(Before) tvm.ir.assert_structural_equal(After, Expected) def test_no_extra_symbolic_variables(): """Don't add symbolic variables if they can be inferred. Even though some cases require adding new parameters to provide symbolic variables, not every symbolic variable requires a distinct parameter. """ @I.ir_module class Before: @R.function def main(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): return Before.func(A) @R.function(private=True) def func(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): m = T.int64() n = T.int64() zeros = R.zeros(R.shape([m, n]), dtype="float32") out = R.add(A, zeros) return out Expected = Before After = tvm.relax.transform.RemoveUnusedParameters()(Before) tvm.ir.assert_structural_equal(After, Expected) def test_remove_extra_prim_parameters(): """Remove unused scalar parameters. The tensor parameter already defines the symbolic dimensions, while the dtype-only scalar parameters are unused by the private function. """ @I.ir_module class Before: @R.function def main(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): m = T.int64() n = T.int64() return Before.func(A, R.prim_value(m), R.prim_value(n)) @R.function(private=True) def func( A: R.Tensor(["m", "n"], "float32"), _m: R.Prim("int64"), _n: R.Prim("int64"), ) -> R.Tensor(["m", "n"], "float32"): m = T.int64() n = T.int64() zeros = R.zeros(R.shape([m, n]), dtype="float32") out = R.add(A, zeros) return out @I.ir_module class Expected: @R.function def main(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): return Expected.func(A) @R.function(private=True) def func(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): m = T.int64() n = T.int64() zeros = R.zeros(R.shape([m, n]), dtype="float32") out = R.add(A, zeros) return out After = tvm.relax.transform.RemoveUnusedParameters()(Before) tvm.ir.assert_structural_equal(After, Expected) def test_remove_extra_shape_variables(): """Remove parameters that only serve to define existing symbolic variables If a `R.Shape` parameter provides a definition of a symbolic variable, but that symbolic variable can be determined from a different parameter, then the `R.Shape` parameter can be removed. """ @I.ir_module class Before: @R.function def main(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): m = T.int64() n = T.int64() return Before.func(A, R.shape([m, n])) @R.function(private=True) def func( A: R.Tensor(["m", "n"], "float32"), _: R.Shape(["m", "n"]), ) -> R.Tensor(["m", "n"], "float32"): m = T.int64() n = T.int64() zeros = R.zeros(R.shape([m, n]), dtype="float32") out = R.add(A, zeros) return out @I.ir_module class Expected: @R.function def main(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): return Expected.func(A) @R.function(private=True) def func(A: R.Tensor(["m", "n"], "float32")) -> R.Tensor(["m", "n"], "float32"): m = T.int64() n = T.int64() zeros = R.zeros(R.shape([m, n]), dtype="float32") out = R.add(A, zeros) return out After = tvm.relax.transform.RemoveUnusedParameters()(Before) tvm.ir.assert_structural_equal(After, Expected) if __name__ == "__main__": tvm.testing.main()