This is an automated email from the ASF dual-hosted git repository.

lunderberg pushed a commit to branch unity
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/unity by this push:
     new d509661f89 [Unity][Analysis] Handle PrimStructInfo in 
EraseToWellDefined (#16304)
d509661f89 is described below

commit d509661f8929fc1c21a72c1c184270970f3667ee
Author: Eric Lunderberg <[email protected]>
AuthorDate: Thu Jan 4 11:02:20 2024 -0600

    [Unity][Analysis] Handle PrimStructInfo in EraseToWellDefined (#16304)
    
    * [Unity][Analysis] Handle PrimStructInfo in EraseToWellDefined
    
    Prior to this commit, the `EraseToWellDefined` pass would update
    symbolic variable definitions in `ShapeStructInfo` and
    `TensorStructInfo`, but did not in `PrimStructInfo`.  This commit
    updates the `WellDefinedEraser` to include symbolic variables defined
    in `PrimStructInfo`.
    
    * Update collecting of symbolic variables in InferSymbolicVarMap
    
    * CI bump due to flaky unit test
    
    `tests/python/relax/test_frontend_onnx.py::test_attention` fails for
    some inputs.  Failures on `unity` head occurred 3/100 test cases
---
 src/relax/analysis/struct_info_analysis.cc  | 32 +++++++++-
 src/relax/ir/block_builder.cc               | 11 +++-
 src/relax/utils.cc                          | 18 ++++++
 tests/python/relax/test_bind_params.py      |  5 +-
 tests/python/relax/test_tvmscript_parser.py | 94 ++++++++++++++++++++++++++++-
 5 files changed, 156 insertions(+), 4 deletions(-)

diff --git a/src/relax/analysis/struct_info_analysis.cc 
b/src/relax/analysis/struct_info_analysis.cc
index 18d21cb4d4..b939ea712c 100644
--- a/src/relax/analysis/struct_info_analysis.cc
+++ b/src/relax/analysis/struct_info_analysis.cc
@@ -118,6 +118,28 @@ class WellDefinedEraser : public StructInfoMutator,
                     std::function<Optional<Expr>(const Var& var)> f_var_map, 
arith::Analyzer* ana)
       : f_shape_var_map_(f_shape_var_map), f_var_map_(f_var_map), ana_(ana) {}
 
+  StructInfo VisitStructInfo_(const PrimStructInfoNode* op) final {
+    bool has_undefined = false;
+    Optional<PrimExpr> value;
+
+    if (op->value.defined()) {
+      std::swap(has_undefined_, has_undefined);
+      value = VisitPrimExpr(op->value.value());
+      std::swap(has_undefined_, has_undefined);
+    }
+
+    // erase symbolic shape if we have undefined.
+    if (!has_undefined) {
+      if (value.same_as(op->value)) {
+        return GetRef<StructInfo>(op);
+      } else {
+        return PrimStructInfo(value.value(), op->span);
+      }
+    } else {
+      return PrimStructInfo(op->dtype, op->span);
+    }
+  }
+
   StructInfo VisitStructInfo_(const ShapeStructInfoNode* op) final {
     bool has_undefined = false;
     Optional<Array<PrimExpr>> values;
@@ -295,7 +317,15 @@ class StructInfoBaseChecker
       if (other.as<ObjectStructInfoNode>()) return BaseCheckResult::kFailL1;
       return BaseCheckResult::kFailL0;
     }
-    return lhs->dtype == rhs->dtype ? BaseCheckResult::kPass : 
BaseCheckResult::kFailL0;
+
+    if (lhs->dtype != rhs->dtype) {
+      return BaseCheckResult::kFailL0;
+    }
+
+    if (!lhs->value.defined()) return BaseCheckResult::kPass;
+    if (!rhs->value.defined()) return BaseCheckResult::kFailL2;
+
+    return PrimValueMatchCheck(lhs->value.value(), rhs->value.value());
   }
 
   BaseCheckResult VisitStructInfo_(const ShapeStructInfoNode* lhs, const 
StructInfo& other) final {
diff --git a/src/relax/ir/block_builder.cc b/src/relax/ir/block_builder.cc
index b445bde6f5..f74434bd74 100644
--- a/src/relax/ir/block_builder.cc
+++ b/src/relax/ir/block_builder.cc
@@ -437,13 +437,22 @@ class BlockBuilderImpl : public BlockBuilderNode {
 
     void VisitStructInfo_(const ShapeStructInfoNode* op) final {
       for (const PrimExpr& s : op->values.value_or(Array<PrimExpr>())) {
-        // Only collect single var defined shape. Ignore something like 
`R.Tensor((m + 1, n + 1))
+        // Only collect single var defined shape. Ignore something like 
`R.Shape((m + 1, n + 1))
         if (const auto* var = s.as<tir::VarNode>()) {
           shape_var_map_.Set(GetRef<tir::Var>(var), s);
         }
       }
     }
 
+    void VisitStructInfo_(const PrimStructInfoNode* op) final {
+      // Only collect single var defined shape. Ignore something like 
`R.Prim(value=m + 1)`
+      if (op->value.defined()) {
+        if (auto var = op->value.as<tir::Var>()) {
+          shape_var_map_.Set(var.value(), op->value.value());
+        }
+      }
+    }
+
    private:
     Map<tir::Var, PrimExpr> shape_var_map_;
   };
diff --git a/src/relax/utils.cc b/src/relax/utils.cc
index 9e91e07592..efb2d02204 100644
--- a/src/relax/utils.cc
+++ b/src/relax/utils.cc
@@ -155,6 +155,23 @@ tvm::Map<tir::Var, PrimExpr> InferSymbolicVarMap(
     }
   };
 
+  auto bind_from_prim_value = [&bind_from_prim_expr](const StructInfo& var,
+                                                     const StructInfo& expr) {
+    auto var_sinfo = var.as<PrimStructInfoNode>();
+    if (!var_sinfo) return;
+
+    auto expr_sinfo = expr.as<PrimStructInfoNode>();
+    CHECK(expr_sinfo) << "Cannot bind expression with struct type " << expr
+                      << " to variable with struct type " << var;
+    CHECK_EQ(var_sinfo->dtype, expr_sinfo->dtype)
+        << "Cannot bind expression with struct type " << expr << " to variable 
with struct type "
+        << var << ", due to conflicting PrimExpr DataType";
+
+    if (!var_sinfo->value.defined() || !expr_sinfo->value.defined()) return;
+
+    bind_from_prim_expr(var_sinfo->value.value(), expr_sinfo->value.value());
+  };
+
   auto bind_from_shape = [&bind_from_prim_expr](const StructInfo& var, const 
StructInfo& expr) {
     auto var_shape = var.as<ShapeStructInfoNode>();
     if (!var_shape) return;
@@ -195,6 +212,7 @@ tvm::Map<tir::Var, PrimExpr> InferSymbolicVarMap(
 
     bind_from_tensor(var_sinfo, expr_sinfo);
     bind_from_shape(var_sinfo, expr_sinfo);
+    bind_from_prim_value(var_sinfo, expr_sinfo);
   }
 
   return tir_var_remap;
diff --git a/tests/python/relax/test_bind_params.py 
b/tests/python/relax/test_bind_params.py
index 189a44303d..bed44c4a6a 100644
--- a/tests/python/relax/test_bind_params.py
+++ b/tests/python/relax/test_bind_params.py
@@ -112,11 +112,14 @@ prim_value_dtype = tvm.testing.parameter("int64", 
"int32", "float32")
 
 
 def test_bind_prim_value(prim_value_dtype):
+    if prim_value_dtype != "int64":
+        pytest.xfail(reason="Currently, only support int64 as known symbolic 
value")
+
     N = tir.Var("N", prim_value_dtype)
     value = tir.const(16, prim_value_dtype)
 
     @R.function
-    def before(A: R.Prim(value=N)):
+    def before(A: R.Prim(value=N)) -> R.Prim(value=N):
         R.func_attr({"global_symbol": "main"})
         B: R.Prim(value=N) = A
         return B
diff --git a/tests/python/relax/test_tvmscript_parser.py 
b/tests/python/relax/test_tvmscript_parser.py
index b45c3c6e4a..ce6fd8e042 100644
--- a/tests/python/relax/test_tvmscript_parser.py
+++ b/tests/python/relax/test_tvmscript_parser.py
@@ -1162,7 +1162,7 @@ def test_if_branch_var_scope():
             return w
 
 
-def test_erase_to_well_defined():
+def test_erase_to_well_defined_removes_internal_vars():
     @R.function
     def foo(x: R.Tensor):
         q = x
@@ -1172,9 +1172,101 @@ def test_erase_to_well_defined():
         return w
 
     tvm.ir.assert_structural_equal(foo.ret_struct_info, R.Tensor(ndim=2))
+    assert foo.ret_struct_info.shape is None
     _check(foo)
 
 
+def test_erase_to_well_defined_keeps_variables_exposed_by_tensor_shape():
+    @R.function
+    def foo(x: R.Tensor(["m", "n"])):
+        q = x
+        m, n = T.int64(), T.int64()
+        z = R.match_cast(q, R.Tensor((m, n)))
+        w = z
+        return w
+
+    assert foo.ret_struct_info.shape is not None
+    _check(foo)
+
+
+def test_erase_to_well_defined_keeps_variants_exposed_by_shape_expr():
+    @R.function
+    def foo(x: R.Tensor, _: R.Shape(["m", "n"])):
+        q = x
+        m, n = T.int64(), T.int64()
+        z = R.match_cast(q, R.Tensor((m, n)))
+        w = z
+        return w
+
+    assert foo.ret_struct_info.shape is not None
+    _check(foo)
+
+
+def test_erase_to_well_defined_keeps_variants_exposed_by_prim_value():
+    @R.function
+    def foo(x: R.Tensor, _m: R.Prim(value="m"), _n: R.Prim(value="n")):
+        q = x
+        m, n = T.int64(), T.int64()
+        z = R.match_cast(q, R.Tensor((m, n)))
+        w = z
+        return w
+
+    assert foo.ret_struct_info.shape is not None
+    _check(foo)
+
+
+def test_erase_to_well_defined_infers_from_shape_expr():
+    @I.ir_module
+    class Module:
+        # The subroutine's symbolic variables are only in-scope for the 
subroutine.
+        @R.function
+        def subroutine(x: R.Tensor, _: R.Shape(["m", "n"])) -> R.Tensor(["m", 
"n"]):
+            q = x
+            m, n = T.int64(), T.int64()
+            z = R.match_cast(q, R.Tensor((m, n)))
+            w = z
+            return w
+
+        # However, struct inference can make the symbolic variables in
+        # the main function to the symbolic variables in the
+        # subroutine.  Therefore, the shape of the tensor returned
+        # from main can have a well-defined shape.
+        @R.function
+        def main(x: R.Tensor, shape: R.Shape(["m", "n"])):
+            output = Module.subroutine(x, shape)
+            return output
+
+    assert Module["main"].ret_struct_info.shape is not None
+    _check(Module)
+
+
+def test_erase_to_well_defined_infers_from_prim_value():
+    @I.ir_module
+    class Module:
+        # The subroutine's symbolic variables are only in-scope for the 
subroutine.
+        @R.function
+        def subroutine(
+            x: R.Tensor, _m: R.Prim(value="m"), _n: R.Prim(value="n")
+        ) -> R.Tensor(["m", "n"]):
+            q = x
+            m, n = T.int64(), T.int64()
+            z = R.match_cast(q, R.Tensor((m, n)))
+            w = z
+            return w
+
+        # However, struct inference can make the symbolic variables in
+        # the main function to the symbolic variables in the
+        # subroutine.  Therefore, the shape of the tensor returned
+        # from main can have a well-defined shape.
+        @R.function
+        def main(x: R.Tensor, relax_m: R.Prim(value="m"), relax_n: 
R.Prim(value="n")):
+            output = Module.subroutine(x, relax_m, relax_n)
+            return output
+
+    assert Module["main"].ret_struct_info.shape is not None
+    _check(Module)
+
+
 def test_empty_tuple():
     @R.function
     def foo(x: R.Tuple()):

Reply via email to