/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace tvm; using namespace tvm::relax; namespace { TensorType ScalarTensorType(PrimType dtype) { auto n = tvm::ffi::make_object(); n->dtype = std::move(dtype); n->ndim = 0; return TensorType(n); } } // namespace TEST(NestedMsg, Basic) { // start with no annotation relax::Var x("x", std::nullopt), y("y", std::nullopt); // constructor from array, T and nullopt. NestedMsg msg({x, std::nullopt, x}); EXPECT_TRUE(msg.IsNested()); EXPECT_FALSE(msg.IsLeaf()); EXPECT_TRUE(msg != nullptr); EXPECT_ANY_THROW(msg.LeafValue()); auto arr = msg.NestedArray(); EXPECT_TRUE(arr[0].LeafValue().same_as(x)); EXPECT_TRUE(arr[1] == nullptr); EXPECT_TRUE(arr[1].IsNull()); EXPECT_TRUE(arr[2].LeafValue().same_as(x)); auto a0 = arr[0]; EXPECT_TRUE(a0.IsLeaf()); // assignment // assign null a0 = std::nullopt; EXPECT_TRUE(a0 == nullptr); // assign array a0 = {x, {x, std::nullopt, y}}; EXPECT_TRUE(a0.IsNested()); auto t0 = a0.NestedArray()[1]; EXPECT_TRUE(t0.IsNested()); EXPECT_TRUE(t0.NestedArray()[2].LeafValue().same_as(y)); // assign leaf a0 = x; EXPECT_TRUE(a0.IsLeaf()); EXPECT_TRUE(a0.LeafValue().same_as(x)); } TEST(NestedMsg, IntAndAny) { NestedMsg msg({1, std::nullopt, 2}); Any any_msg = msg; NestedMsg msg2 = any_msg.cast>(); EXPECT_TRUE(msg2.IsNested()); EXPECT_EQ(msg2.NestedArray()[0].LeafValue(), 1); EXPECT_TRUE(msg2.NestedArray()[1].IsNull()); EXPECT_EQ(msg2.NestedArray()[2].LeafValue(), 2); } TEST(NestedMsg, ForEachLeaf) { relax::Var x("x", std::nullopt), y("y", std::nullopt); NestedMsg msg = {x, {x, y}, std::nullopt, {x, {x, y}}}; int x_count = 0, y_count = 0; ForEachLeaf(msg, [&](const Expr& v) { if (v.same_as(x)) ++x_count; if (v.same_as(y)) ++y_count; }); EXPECT_EQ(x_count, 4); EXPECT_EQ(y_count, 2); } TEST(NestedMsg, Equal) { relax::Var x("x", std::nullopt), y("y", std::nullopt); relax::Var z("z", std::nullopt); auto fequal = [](Expr lhs, Expr rhs) { return lhs.same_as(rhs); }; using M = NestedMsg; EXPECT_TRUE(Equal(M(std::nullopt), M(std::nullopt), fequal)); EXPECT_TRUE(Equal(M(x), M(x), fequal)); EXPECT_TRUE(Equal(M({x, y}), M({x, y}), fequal)); EXPECT_TRUE(Equal(M({x, std::nullopt}), M({x, std::nullopt}), fequal)); EXPECT_TRUE(Equal(M({x, {std::nullopt, y}}), M({x, {std::nullopt, y}}), fequal)); EXPECT_TRUE(Equal(M({x, {std::nullopt, y}, {x, z}}), M({x, {std::nullopt, y}, {x, z}}), fequal)); // type mismatch EXPECT_FALSE(Equal(M({x, {std::nullopt, y}, x}), M({x, {std::nullopt, y}, {x, z}}), fequal)); EXPECT_FALSE(Equal(M({x, {std::nullopt, y}, {x, std::nullopt}}), M({x, {std::nullopt, y}, {x, z}}), fequal)); EXPECT_FALSE(Equal(M({x, {std::nullopt, y}}), M({x, {std::nullopt, y}, {x, z}}), fequal)); EXPECT_FALSE(Equal(M(x), M(std::nullopt), fequal)); EXPECT_FALSE(Equal(M(std::nullopt), M(x), fequal)); EXPECT_FALSE(Equal(M(x), M(ffi::Array({x})), fequal)); EXPECT_FALSE(Equal(M(ffi::Array({x})), M(x), fequal)); } TEST(NestedMsg, MapAndDecompose) { relax::Var x("x", PrimType::Int(16)); relax::Var y("y", PrimType::Int(32)); relax::Var z("z", PrimType::Int(64)); BlockBuilder bb = BlockBuilder::Create(std::nullopt); relax::Expr t0 = bb->Normalize(Tuple({x, y})); relax::Expr t1 = bb->Normalize(Tuple({t0, x, z, t0})); auto c0 = IntImm::Int32(0); auto c1 = IntImm::Int32(1); auto c2 = IntImm::Int32(2); auto output = MapToNestedMsg(t1, [&](Expr value) { if (value.same_as(x)) return c0; if (value.same_as(y)) return c1; return c2; }); NestedMsg expected = {{c0, c1}, c0, c2, {c0, c1}}; EXPECT_TRUE(Equal(output, expected, [](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; })); auto output2 = MapToNestedMsg(GetType(t1), [&](Type ty) -> NestedMsg { const auto* prim_ty = ty.as(); if (prim_ty == nullptr) return std::nullopt; int bits = prim_ty->dtype.bits; if (bits == 16) return c0; if (bits == 32) return c1; if (bits == 64) return c2; return std::nullopt; }); EXPECT_TRUE(Equal(output2, expected, [](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; })); int x_count = 0, y_count = 0, z_count = 0; DecomposeNestedMsg(t1, expected, [&](Expr value, NestedMsg msg) { if (value.same_as(x)) { EXPECT_TRUE(msg.LeafValue().same_as(c0)); ++x_count; } else if (value.same_as(y)) { EXPECT_TRUE(msg.LeafValue().same_as(c1)); ++y_count; } else { EXPECT_TRUE(msg.LeafValue().same_as(c2)); ++z_count; } }); EXPECT_EQ(x_count, 3); EXPECT_EQ(y_count, 2); EXPECT_EQ(z_count, 1); } TEST(NestedMsg, MapToNestedMsgByType) { auto sf0 = ScalarTensorType(PrimType::Float(32)); auto sf1 = TupleType({sf0, sf0}); auto sf2 = TupleType({sf0, sf0}); auto x = relax::Var("x", TupleType({sf1, sf2, sf0})); auto msg = MapToNestedMsgByType(x, [](Expr value) { return value; }); EXPECT_TRUE(msg.IsNested()); auto arr = msg.NestedArray(); EXPECT_TRUE(arr[1].IsNested()); auto arr1 = arr[1].NestedArray(); EXPECT_TRUE(arr1[0].IsLeaf()); EXPECT_TRUE( tvm::ffi::StructuralEqual()(arr1[0].LeafValue(), TupleGetItem(TupleGetItem(x, 1), 0))); EXPECT_TRUE(arr[2].IsLeaf()); EXPECT_TRUE(tvm::ffi::StructuralEqual()(arr[2].LeafValue(), TupleGetItem(x, 2))); } TEST(NestedMsg, NestedMsgToExpr) { auto sf0 = ScalarTensorType(PrimType::Float(32)); auto sf1 = TupleType({sf0, sf0}); auto c0 = IntImm::Int32(0); auto c1 = IntImm::Int32(1); auto c2 = IntImm::Int32(2); relax::Var x("x", sf0), y("y", sf0), z("z", sf0); NestedMsg msg = {c0, {c0, c1}, {c0, {c1, c2}}}; auto expr = NestedMsgToExpr(msg, [&](ffi::Optional leaf) { TVM_FFI_ICHECK(leaf.has_value()); int value = leaf.value()->value; switch (value) { case 0: return x; case 1: return y; default: return z; } }); Expr expected = Tuple({x, Tuple({x, y}), Tuple({x, Tuple({y, z})})}); EXPECT_TRUE(tvm::ffi::StructuralEqual()(expr, expected)); // test simplified relax::Var t("t", sf1); NestedMsg msg1 = {TupleGetItem(t, 0), TupleGetItem(t, 1)}; auto expr1 = NestedMsgToExpr(msg1, [](ffi::Optional leaf) { return leaf.value(); }); EXPECT_TRUE(tvm::ffi::StructuralEqual()(expr1, t)); } TEST(NestedMsg, CombineNestedMsg) { auto c0 = IntImm::Int32(0); auto c1 = IntImm::Int32(1); auto c2 = IntImm::Int32(2); NestedMsg lhs = {c0, {c0, c1}, std::nullopt, {c0, {c1, c2}}}; NestedMsg rhs = {c1, {c2, std::nullopt}, std::nullopt, {c1, {c2, c2}}}; NestedMsg expected = {c1, {c2, c1}, std::nullopt, {c1, {c2, c2}}}; auto output = CombineNestedMsg(lhs, rhs, [](IntImm x, IntImm y) { if (x->value > y->value) return x; return y; }); EXPECT_TRUE(Equal(output, expected, [](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; })); } TEST(NestedMsg, MapNestedMsg) { auto c0 = IntImm::Int32(0); auto c1 = IntImm::Int32(1); auto c2 = IntImm::Int32(2); auto c3 = IntImm::Int32(3); NestedMsg msg = {c0, {c0, c1}, std::nullopt, {c0, {c2, c1}}}; NestedMsg expected = {c3, {c3, std::nullopt}, std::nullopt, {c3, {c2, std::nullopt}}}; auto output = MapNestedMsg(msg, [](IntImm x) { if (x->value == 0) { return NestedMsg(IntImm::Int32(3)); } else if (x->value == 1) { return NestedMsg(); } else { return NestedMsg(x); } }); EXPECT_TRUE(Equal(output, expected, [](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; })); } TEST(NestedMsg, TransformTupleLeaf) { auto c0 = IntImm::Int32(0); auto c1 = IntImm::Int32(1); auto c2 = IntImm::Int32(2); using NInt = NestedMsg; NInt msg1 = {c0, {c0, c1}, c2, {c0, {c1, c2}}}; NInt msg2 = {c1, {c2, c0}, c2, {c1, {c2, c0}}}; PrimType s = PrimType::Int(32); relax::Var x("x", s), y("y", s), z("z", s); BlockBuilder bb = BlockBuilder::Create(std::nullopt); Expr expr = bb->Normalize(Tuple({x, Tuple({x, x}), x, Tuple({x, Tuple({x, x})})})); auto ftransleaf = [&](Expr value, std::array msgs) -> Expr { int lhs = msgs[0].LeafValue().as_or_throw()->value; int rhs = msgs[1].LeafValue().as_or_throw()->value; if (lhs > rhs) return z; else if (lhs == rhs) return value; else return y; }; Expr expected = Tuple({y, Tuple({y, z}), x, Tuple({y, Tuple({y, z})})}); EXPECT_TRUE(tvm::ffi::StructuralEqual()( TransformTupleLeaf(expr, std::array({msg1, msg2}), ftransleaf), expected)); EXPECT_TRUE( expr.same_as(TransformTupleLeaf(expr, std::array({msg1, msg1}), ftransleaf))); }