# 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: F841 import pytest import tvm import tvm.testing from tvm.relax.analysis import name_to_binding from tvm.relax.binding_rewrite import DataflowBlockRewrite from tvm.relax.expr import DataflowVar, Var from tvm.script import relax as R @tvm.script.ir_module class Identity: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x R.output(lv0) return lv0 def assert_immutability(rwt, original_dfb, original_root_fn): assert rwt.mutated_dfb() != original_dfb assert rwt.mutated_root_fn() != original_root_fn assert rwt.mutated_root_fn().body.blocks[0] != original_dfb assert rwt.mutated_root_fn().body.blocks[0] == rwt.mutated_dfb() def test_null_construct(): root_fn = Identity["main"] dfb = root_fn.body.blocks[0] DataflowBlockRewrite(dfb, root_fn) def test_simple_add(): root_fn = Identity["main"] dfb = root_fn.body.blocks[0] rwt = DataflowBlockRewrite(dfb, root_fn) rwt.add(name="tmp", expr=Identity["main"].params[0], is_dfvar=True) assert_immutability(rwt, dfb, root_fn) # check "tmp" added assert "tmp" in name_to_binding(rwt.mutated_root_fn()) @tvm.script.ir_module class GroundTruth: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x tmp: R.Tensor((32, 32), "float32") = x R.output(lv0) return lv0 tvm.ir.assert_structural_equal(rwt.mutated_root_fn(), GroundTruth["main"]) def test_simple_auto_add_var(): root_fn = Identity["main"] dfb = root_fn.body.blocks[0] rwt = DataflowBlockRewrite(dfb, root_fn) rwt.add(root_fn.params[0], is_dfvar=False) assert isinstance(rwt.mutated_dfb().bindings[-1].var, Var) assert_immutability(rwt, dfb, root_fn) def test_simple_auto_add_dfvar(): root_fn = Identity["main"] dfb = root_fn.body.blocks[0] rwt = DataflowBlockRewrite(dfb, root_fn) rwt.add(root_fn.params[0], is_dfvar=True) assert isinstance(rwt.mutated_dfb().bindings[-1].var, DataflowVar) # immutatbility assert_immutability(rwt, dfb, root_fn) def test_simple_remove_unused(): @tvm.script.ir_module class IdentityUnused: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x unused = lv0 R.output(lv0) return lv0 root_fn = IdentityUnused["main"] dfb = root_fn.body.blocks[0] n2binding = name_to_binding(IdentityUnused["main"]) rwt = DataflowBlockRewrite(dfb, root_fn) rwt.remove_unused(n2binding["unused"][0].var) assert_immutability(rwt, dfb, root_fn) # check "unused" removed assert "unused" not in name_to_binding(rwt.mutated_root_fn()) @tvm.script.ir_module class GroundTruth: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x R.output(lv0) return lv0 tvm.ir.assert_structural_equal(rwt.mutated_root_fn(), GroundTruth["main"]) def test_remove_unused_undef(): root_fn = Identity["main"] dfb = root_fn.body.blocks[0] with pytest.raises(RuntimeError): rwt = DataflowBlockRewrite(dfb, root_fn) rwt.remove_unused(Var("whatever")) rwt = DataflowBlockRewrite(dfb, root_fn) rwt.remove_unused(Var("whatever"), allow_undef=True) assert root_fn == rwt.mutated_root_fn() def test_simple_rm_all_unused(): @tvm.script.ir_module class IdentityUnused: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x unused0 = lv0 unused1 = lv0 R.output(lv0) return lv0 root_fn = IdentityUnused["main"] dfb = root_fn.body.blocks[0] rwt = DataflowBlockRewrite(dfb, root_fn) rwt.remove_all_unused() @tvm.script.ir_module class GroundTruth: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x R.output(lv0) return lv0 tvm.ir.assert_structural_equal(rwt.mutated_root_fn(), GroundTruth["main"]) @tvm.script.ir_module class DeadDFBlock: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor((32, 32), "float32"): with R.dataflow(): lv0 = x R.output(lv0) return x def test_empty_dfb_after_removal(): root_fn = DeadDFBlock["main"] dfb = root_fn.body.blocks[0] rwt = DataflowBlockRewrite(dfb, root_fn) rwt.remove_unused(DeadDFBlock["main"].body.blocks[0].bindings[0].var) @tvm.script.ir_module class GroundTruth: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor((32, 32), "float32"): return x tvm.ir.assert_structural_equal(rwt.mutated_root_fn(), GroundTruth["main"]) def test_empty_dfb_after_all_removal(): dfb = DeadDFBlock["main"].body.blocks[0] root_fn = DeadDFBlock["main"] rwt = DataflowBlockRewrite(dfb, root_fn) rwt.remove_all_unused() @tvm.script.ir_module class GroundTruth: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor((32, 32), "float32"): return x tvm.ir.assert_structural_equal(rwt.mutated_root_fn(), GroundTruth["main"]) def test_chained_rm_all_unused(): @tvm.script.ir_module class IdentityChainedUnused: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x unused0 = R.call_dps_packed("my_sigmoid", (x,), R.Tensor((32, 32), dtype="float32")) unused1 = R.call_dps_packed( "my_sigmoid", (unused0,), R.Tensor((32, 32), dtype="float32") ) R.output(lv0) return lv0 root_fn = IdentityChainedUnused["main"] dfb = root_fn.body.blocks[0] rwt = DataflowBlockRewrite(dfb, root_fn) rwt.remove_all_unused() @tvm.script.ir_module class GroundTruth: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x R.output(lv0) return lv0 tvm.ir.assert_structural_equal(rwt.mutated_root_fn(), GroundTruth["main"]) def test_simple_replace_all_uses(): @tvm.script.ir_module class Lv0To1: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor((32, 32), "float32"): # lv0 => lv1 # / \ # lv2 lv3 # \ / # lv4 with R.dataflow(): lv0: R.Tensor((32, 32), "float32") = R.call_dps_packed( "my_relu", (x,), R.Tensor((32, 32), dtype="float32") ) lv1: R.Tensor((32, 32), "float32") = R.call_dps_packed( "my_sigmoid", (x,), R.Tensor((32, 32), dtype="float32") ) lv2: R.Tensor((32, 32), "float32") = R.call_dps_packed( "my_add", (x, lv0), R.Tensor((32, 32), dtype="float32") ) lv3: R.Tensor((32, 32), "float32") = R.call_dps_packed( "my_mul", (x, lv0), R.Tensor((32, 32), dtype="float32") ) lv4: R.Tensor((32, 32), "float32") = R.call_dps_packed( "my_whatever", (lv2, lv3), R.Tensor((32, 32), dtype="float32") ) R.output(lv4) return lv4 root_fn = Lv0To1["main"] dfb = root_fn.body.blocks[0] n2binding = name_to_binding(root_fn) rwt = DataflowBlockRewrite(dfb, root_fn) missing = Var("missing") with pytest.raises(RuntimeError, match="Cannot find"): rwt.replace_all_uses(missing, missing) rwt.replace_all_uses(n2binding["lv0"][0].var, n2binding["lv1"][0].var) rwt.replace_all_uses(n2binding["lv1"][0].var, n2binding["lv1"][0].var) with pytest.raises(RuntimeError, match=r"is used by 2 vars"): rwt.remove_unused(n2binding["lv1"][0].var) rwt.remove_unused(n2binding["lv0"][0].var) assert_immutability(rwt, dfb, root_fn) n2binding_after = name_to_binding(rwt.mutated_root_fn()) assert "lv0" not in n2binding_after def test_simple_module_update(): @tvm.script.ir_module class Identity: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x R.output(lv0) return lv0 root_fn = Identity["main"] dfb = root_fn.body.blocks[0] rwt = DataflowBlockRewrite(dfb, root_fn) rwt.add(name="tmp", expr=root_fn.params[0], is_dfvar=True) new_ir = rwt.mutate_irmodule(Identity) # immutatbility assert new_ir != Identity assert 2 == len(new_ir["main"].body.blocks[0].bindings) @tvm.script.ir_module class GroundTruth: @R.function def main(x: R.Tensor((32, 32), "float32")) -> R.Tensor: with R.dataflow(): lv0 = x tmp: R.Tensor((32, 32), "float32") = x R.output(lv0) return lv0 tvm.ir.assert_structural_equal(new_ir, GroundTruth) if __name__ == "__main__": tvm.testing.main()