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

yongwww 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 a6adaae5ef [Unity][DistIR] LowerDistIR (#16169)
a6adaae5ef is described below

commit a6adaae5ef30e5d4b31d6e549c6a9848e2a05e23
Author: Hongyi Jin <[email protected]>
AuthorDate: Wed Nov 29 11:19:41 2023 -0500

    [Unity][DistIR] LowerDistIR (#16169)
    
    * lower distir
    
    * format
    
    * format
    
    * add whitespace
    
    * fix lint
    
    * fix warning
---
 include/tvm/relax/distributed/transform.h          |   6 +
 python/tvm/relax/distributed/transform/__init__.py |   7 +-
 .../tvm/relax/distributed/transform/transform.py   |  11 +
 src/relax/distributed/transform/lower_distir.cc    | 271 ++++++++++++++
 .../test_distributed_transform_lower_distir.py     | 396 +++++++++++++++++++++
 5 files changed, 690 insertions(+), 1 deletion(-)

diff --git a/include/tvm/relax/distributed/transform.h 
b/include/tvm/relax/distributed/transform.h
index 2ac3492165..31727b181e 100644
--- a/include/tvm/relax/distributed/transform.h
+++ b/include/tvm/relax/distributed/transform.h
@@ -62,6 +62,12 @@ TVM_DLL Pass LowerGlobalViewToLocalView();
  */
 TVM_DLL Pass LegalizeRedistribute();
 
+/*!
+ * \brief Lower DistIR to Relax
+ *
+ * \return The Pass.
+ */
+TVM_DLL Pass LowerDistIR();
 }  // namespace transform
 }  // namespace distributed
 }  // namespace relax
diff --git a/python/tvm/relax/distributed/transform/__init__.py 
b/python/tvm/relax/distributed/transform/__init__.py
index 573b7d599d..a6158eaedb 100644
--- a/python/tvm/relax/distributed/transform/__init__.py
+++ b/python/tvm/relax/distributed/transform/__init__.py
@@ -16,4 +16,9 @@
 # under the License.
 """Relax distributed-related transformations. """
 
-from .transform import PropagateSharding, LowerGlobalViewToLocalView, 
LegalizeRedistribute
+from .transform import (
+    PropagateSharding,
+    LowerGlobalViewToLocalView,
+    LegalizeRedistribute,
+    LowerDistIR,
+)
diff --git a/python/tvm/relax/distributed/transform/transform.py 
b/python/tvm/relax/distributed/transform/transform.py
index 36aa1fe9b6..e64366c79a 100644
--- a/python/tvm/relax/distributed/transform/transform.py
+++ b/python/tvm/relax/distributed/transform/transform.py
@@ -54,3 +54,14 @@ def LegalizeRedistribute() -> tvm.ir.transform.Pass:
         The registered pass
     """
     return _ffi_api.LegalizeRedistribute()  # type: ignore
+
+
+def LowerDistIR() -> tvm.ir.transform.Pass:
+    """Lower DistIR to Relax
+
+    Returns
+    -------
+    ret : tvm.transform.Pass
+        The registered pass
+    """
+    return _ffi_api.LowerDistIR()  # type: ignore
diff --git a/src/relax/distributed/transform/lower_distir.cc 
b/src/relax/distributed/transform/lower_distir.cc
new file mode 100644
index 0000000000..2bfd38b9af
--- /dev/null
+++ b/src/relax/distributed/transform/lower_distir.cc
@@ -0,0 +1,271 @@
+/*
+ * 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.
+ */
+
+/*!
+ * \file tvm/relax/distributed/transform/lower_distir.cc
+ * \brief Pass for lowering DistIR into Relax
+ *  This pass assumes all the TensorIR functions are in local view,
+ *  so the pass only handles sharding relax tensor shape and
+ *  inserting necessary broadcast and scatter for inputs.
+ */
+
+#include <tvm/relax/attrs/ccl.h>
+#include <tvm/relax/distributed/axis_group_graph.h>
+#include <tvm/relax/distributed/transform.h>
+#include <tvm/relax/expr_functor.h>
+#include <tvm/tir/stmt_functor.h>
+
+#include "../../../tir/schedule/transform.h"
+#include "../../op/ccl/ccl.h"
+#include "../../op/tensor/manipulate.h"
+#include "utils.h"
+
+namespace tvm {
+namespace relax {
+namespace distributed {
+
+class DistIRSharder : public ExprMutator {
+ public:
+  static IRModule LowerDistIR(IRModule mod) { return 
DistIRSharder(mod).Lower(); }
+
+ private:
+  explicit DistIRSharder(IRModule mod) : ExprMutator(mod) {}
+
+  IRModule Lower() {
+    auto mod = builder_->GetContextIRModule();
+    for (const auto& [gv, base_func] : mod->functions) {
+      const auto* func_ = base_func.as<FunctionNode>();
+      if (func_ == nullptr || !IsDistIRFunc(GetRef<Function>(func_))) {
+        continue;
+      }
+      Function func = RewriteFunction(GetRef<Function>(func_));
+      builder_->UpdateFunction(gv, func);
+    }
+    return builder_->GetContextIRModule();
+  }
+
+  ShapeExpr ShardShape(ShapeExpr orig_shape, DeviceMesh device_mesh, Placement 
placement) {
+    ShapeTuple device_mesh_shape = device_mesh->shape;
+    Array<PrimExpr> new_tensor_shape_value = orig_shape->values;
+    for (int i = 0; i < static_cast<int>(device_mesh_shape.size()); i++) {
+      if (placement->dim_specs[i]->kind == PlacementSpecKind::kSharding) {
+        int shard_size = device_mesh_shape[i];
+        int axis = placement->dim_specs[i]->axis;
+        new_tensor_shape_value.Set(axis, floordiv(orig_shape->values[axis], 
shard_size));
+      }
+    }
+    return ShapeExpr(new_tensor_shape_value);
+  }
+
+  TensorStructInfo ShardDTensorSinfo(DTensorStructInfo orig_sinfo) {
+    TensorStructInfo tensor_sinfo = orig_sinfo->tensor_sinfo;
+    ICHECK(tensor_sinfo->shape);
+    const auto* orig_shape = tensor_sinfo->shape.as<ShapeExprNode>();
+    auto new_tensor_sinfo = 
make_object<TensorStructInfoNode>(*tensor_sinfo.get());
+    new_tensor_sinfo->shape =
+        ShardShape(GetRef<ShapeExpr>(orig_shape), orig_sinfo->device_mesh, 
orig_sinfo->placement);
+    return TensorStructInfo(new_tensor_sinfo);
+  }
+
+  StructInfo ConvertSinfo(StructInfo orig_sinfo, bool shard_shape) {
+    if (const auto* dtensor_sinfo = orig_sinfo.as<DTensorStructInfoNode>()) {
+      if (shard_shape) {
+        return ShardDTensorSinfo(GetRef<DTensorStructInfo>(dtensor_sinfo));
+      } else {
+        return dtensor_sinfo->tensor_sinfo;
+      }
+    } else if (const auto* tuple_sinfo = orig_sinfo.as<TupleStructInfoNode>()) 
{
+      Array<StructInfo> new_fields;
+      for (const auto& field_sinfo : tuple_sinfo->fields) {
+        if (const auto* dtensor_sinfo = 
field_sinfo.as<DTensorStructInfoNode>()) {
+          if (shard_shape) {
+            
new_fields.push_back(ShardDTensorSinfo(GetRef<DTensorStructInfo>(dtensor_sinfo)));
+          } else {
+            new_fields.push_back(dtensor_sinfo->tensor_sinfo);
+          }
+        } else {
+          new_fields.push_back(field_sinfo);
+        }
+      }
+      return TupleStructInfo(new_fields);
+    } else {
+      return orig_sinfo;
+    }
+  }
+
+  Expr ShardInputParamTensorAndConstant(Expr input) {
+    ICHECK(input->struct_info_);
+    StructInfo old_sinfo = GetStructInfo(input);
+    StructInfo new_sinfo = ConvertSinfo(old_sinfo, false);
+    if (const auto* var = input.as<VarNode>()) {
+      Var new_param(var->name_hint(), new_sinfo);
+      return new_param;
+    } else if (const auto* constant = input.as<ConstantNode>()) {
+      for (const auto& spec : 
Downcast<DTensorStructInfo>(old_sinfo)->placement->dim_specs) {
+        ICHECK(spec->kind == PlacementSpecKind::kReplica);
+      }
+      Constant new_constant(constant->data, new_sinfo);
+      return new_constant;
+    } else {
+      LOG(FATAL) << "Cannot shard tensor which is not Var or Constant: " << 
input;
+      throw;
+    }
+  }
+
+  void EmitBroadcastOrScatter(Expr old_expr, Expr new_expr, DTensorStructInfo 
dtensor_sinfo) {
+    // FIXME: this is a hack that only works for 1d device mesh
+    ICHECK(dtensor_sinfo->device_mesh->shape.size() == 1);
+    PlacementSpec sharding_spec = dtensor_sinfo->placement->dim_specs[0];
+    if (sharding_spec->kind == PlacementSpecKind::kReplica) {
+      Var new_var = builder_->Emit(broadcast_from_worker0(new_expr));
+      if (const auto* var = old_expr.as<VarNode>()) {
+        var_remap_[var->vid] = new_var;
+      } else {
+        tuple_getitem_remap_[Downcast<TupleGetItem>(old_expr)] = new_var;
+      }
+    } else if (sharding_spec->kind == PlacementSpecKind::kSharding) {
+      Var scatter_var = builder_->Emit(scatter_from_worker0(
+          new_expr, dtensor_sinfo->device_mesh->shape[0], 
sharding_spec->axis));
+      if (const auto* var = old_expr.as<VarNode>()) {
+        var_remap_[var->vid] = scatter_var;
+      } else {
+        tuple_getitem_remap_[Downcast<TupleGetItem>(old_expr)] = scatter_var;
+      }
+    } else {
+      LOG(FATAL) << "Unsupported placement spec";
+    }
+  }
+
+  void InputPreprocessing() {
+    for (int i = 0; i < static_cast<int>(func_->params.size()); i++) {
+      Var param = func_->params[i];
+      if (const auto* dtensor_sinfo = 
GetStructInfoAs<DTensorStructInfoNode>(param)) {
+        EmitBroadcastOrScatter(param, new_params_[i], 
GetRef<DTensorStructInfo>(dtensor_sinfo));
+      } else if (const auto* tuple_sinfo = 
GetStructInfoAs<TupleStructInfoNode>(param)) {
+        for (int j = 0; j < static_cast<int>(tuple_sinfo->fields.size()); j++) 
{
+          if (const auto* dtensor_sinfo = 
tuple_sinfo->fields[j].as<DTensorStructInfoNode>()) {
+            EmitBroadcastOrScatter(TupleGetItem(param, j), 
TupleGetItem(new_params_[i], j),
+                                   GetRef<DTensorStructInfo>(dtensor_sinfo));
+          }
+        }
+      }
+    }
+  }
+
+  Function RewriteFunction(Function func) {
+    Array<Var> new_params;
+    for (const Var& var : func->params) {
+      Var new_param = Downcast<Var>(ShardInputParamTensorAndConstant(var));
+      var_remap_[var->vid] = new_param;
+      new_params.push_back(new_param);
+    }
+    func_ = func;
+    new_params_ = new_params;
+    auto new_body = VisitWithNewScope(func->body, new_params);
+    Function new_func(new_params, new_body, NullOpt, func->is_pure, 
func->attrs);
+    return new_func;
+  }
+
+  void VisitBinding_(const VarBindingNode* binding, const TupleGetItemNode* 
val) {
+    if (tuple_getitem_remap_.count(GetRef<TupleGetItem>(val))) {
+      var_remap_[binding->var->vid] = 
tuple_getitem_remap_[GetRef<TupleGetItem>(val)];
+    } else {
+      ExprMutator::VisitBinding_(binding, val);
+    }
+  }
+
+  BindingBlock VisitBindingBlock_(const BindingBlockNode* block) {
+    builder_->BeginBindingBlock();
+    InputPreprocessing();
+    for (Binding binding : block->bindings) {
+      this->VisitBinding(binding);
+    }
+    return builder_->EndBlock();
+  }
+
+  BindingBlock VisitBindingBlock_(const DataflowBlockNode* block) {
+    builder_->BeginDataflowBlock();
+    InputPreprocessing();
+    for (auto binding : block->bindings) {
+      this->VisitBinding(binding);
+    }
+    return builder_->EndBlock();
+  }
+
+  Call HandleSpecialCaseinDTensorLowering(const CallNode* call, Var 
binding_var) {
+    static Op reshape_op = Op::Get("relax.reshape");
+    static Op call_tir_op = Op::Get("relax.call_tir");
+    static Op call_tir_local_view_op = 
Op::Get("relax.dist.call_tir_local_view");
+    if (call->op.same_as(reshape_op)) {
+      ICHECK(call->args[1].as<ShapeExprNode>());
+      const auto* out_sinfo = 
GetStructInfoAs<DTensorStructInfoNode>(binding_var);
+      ICHECK(out_sinfo);
+      auto new_call_node = make_object<CallNode>(*call);
+      new_call_node->args.Set(1, ShardShape(Downcast<ShapeExpr>(call->args[1]),
+                                            out_sinfo->device_mesh, 
out_sinfo->placement));
+      return Call(new_call_node);
+    } else if (call->op.same_as(call_tir_local_view_op)) {
+      auto new_call_node = make_object<CallNode>(*call);
+      new_call_node->op = call_tir_op;
+      new_call_node->sinfo_args = {ConvertSinfo(GetStructInfo(binding_var), 
true)};
+      return Call(new_call_node);
+    } else if (call->op.same_as(call_tir_op)) {
+      LOG(FATAL) << "call_tir should be lowered to call_tir_local_view before 
lowering to relax";
+    } else if (const auto* extern_func = call->op.as<ExternFuncNode>()) {
+      auto new_call_node = make_object<CallNode>(*call);
+      if (extern_func->global_symbol == 
"vm.builtin.distributed.attention_kv_cache_append") {
+        new_call_node->op = ExternFunc("vm.builtin.attention_kv_cache_append");
+      } else if (extern_func->global_symbol == 
"vm.builtin.distributed.attention_kv_cache_view") {
+        new_call_node->op = ExternFunc("vm.builtin.attention_kv_cache_view");
+        auto orig_shape = Downcast<ShapeExpr>(call->args[1]);
+        const auto* out_sinfo = 
GetStructInfoAs<DTensorStructInfoNode>(binding_var);
+        ICHECK(out_sinfo);
+        ShapeExpr new_shape = ShardShape(orig_shape, out_sinfo->device_mesh, 
out_sinfo->placement);
+        new_call_node->args.Set(1, new_shape);
+        new_call_node->sinfo_args = {TensorStructInfo(new_shape, 
out_sinfo->tensor_sinfo->dtype)};
+      }
+      return Call(new_call_node);
+    }
+    return GetRef<Call>(call);
+  }
+
+  void VisitBinding_(const VarBindingNode* binding, const CallNode* val) {
+    Call new_call =
+        Downcast<Call>(this->VisitExpr(HandleSpecialCaseinDTensorLowering(val, 
binding->var)));
+    ReEmitBinding(binding, builder_->Normalize(new_call));
+  }
+
+  Function func_;
+  Array<Var> new_params_;
+  std::unordered_map<TupleGetItem, Var, StructuralHash, StructuralEqual> 
tuple_getitem_remap_;
+};
+
+namespace transform {
+
+Pass LowerDistIR() {
+  runtime::TypedPackedFunc<IRModule(IRModule, PassContext)> pass_func =
+      [=](IRModule m, PassContext pc) { return DistIRSharder::LowerDistIR(m); 
};
+  return CreateModulePass(pass_func, 1, "LowerDistIR", {});
+}
+TVM_REGISTER_GLOBAL("relax.distributed.transform.LowerDistIR").set_body_typed(LowerDistIR);
+}  // namespace transform
+
+}  // namespace distributed
+}  // namespace relax
+}  // namespace tvm
diff --git 
a/tests/python/relax/distributed/test_distributed_transform_lower_distir.py 
b/tests/python/relax/distributed/test_distributed_transform_lower_distir.py
new file mode 100644
index 0000000000..3df65b3ea6
--- /dev/null
+++ b/tests/python/relax/distributed/test_distributed_transform_lower_distir.py
@@ -0,0 +1,396 @@
+# 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.
+
+#  type: ignore
+from tvm.script.parser import ir as I
+from tvm.script.parser import relax as R
+from tvm.script.parser import tir as T
+import tvm
+from tvm import relax
+from tvm.ir import assert_structural_equal
+import tvm.testing
+
+
+def test_mlp():
+    @I.ir_module
+    class MLP:
+        I.module_attrs({"device_num": 10})
+        I.module_global_infos(
+            {"mesh": [R.device_mesh((2,), I.Range(0, 2)), R.device_mesh((1,), 
I.Range(4, 5))]}
+        )
+
+        @T.prim_func(private=True)
+        def gelu1(
+            A: T.Buffer((T.int64(128), T.int64(64)), "float32"),
+            T_multiply: T.Buffer((T.int64(128), T.int64(64)), "float32"),
+        ):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            # with T.block("root"):
+            T_multiply_1 = T.alloc_buffer((T.int64(128), T.int64(64)))
+            compute = T.alloc_buffer((T.int64(128), T.int64(64)))
+            T_multiply_2 = T.alloc_buffer((T.int64(128), T.int64(64)))
+            T_add = T.alloc_buffer((T.int64(128), T.int64(64)))
+            for ax0, ax1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("T_multiply"):
+                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
+                    T.reads(A[v_ax0, v_ax1])
+                    T.writes(T_multiply_1[v_ax0, v_ax1])
+                    T_multiply_1[v_ax0, v_ax1] = A[v_ax0, v_ax1] * 
T.float32(0.70710678118654757)
+            for i0, i1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("compute"):
+                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
+                    T.reads(T_multiply_1[v_i0, v_i1])
+                    T.writes(compute[v_i0, v_i1])
+                    compute[v_i0, v_i1] = T.erf(T_multiply_1[v_i0, v_i1])
+            for ax0, ax1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("T_multiply_1"):
+                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
+                    T.reads(compute[v_ax0, v_ax1])
+                    T.writes(T_multiply_2[v_ax0, v_ax1])
+                    T_multiply_2[v_ax0, v_ax1] = compute[v_ax0, v_ax1] * 
T.float32(0.5)
+            for ax0, ax1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("T_add"):
+                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
+                    T.reads(T_multiply_2[v_ax0, v_ax1])
+                    T.writes(T_add[v_ax0, v_ax1])
+                    T_add[v_ax0, v_ax1] = T.float32(0.5) + T_multiply_2[v_ax0, 
v_ax1]
+            for ax0, ax1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("T_multiply_2"):
+                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
+                    T.reads(A[v_ax0, v_ax1], T_add[v_ax0, v_ax1])
+                    T.writes(T_multiply[v_ax0, v_ax1])
+                    T_multiply[v_ax0, v_ax1] = A[v_ax0, v_ax1] * T_add[v_ax0, 
v_ax1]
+
+        @T.prim_func(private=True)
+        def matmul1(
+            A: T.Buffer((T.int64(128), T.int64(128)), "float32"),
+            B: T.Buffer((T.int64(128), T.int64(64)), "float32"),
+            matmul_1: T.Buffer((T.int64(128), T.int64(64)), "float32"),
+        ):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            # with T.block("root"):
+            for i0, i1, k in T.grid(T.int64(128), T.int64(64), T.int64(128)):
+                with T.block("matmul"):
+                    v_i0, v_i1, v_k = T.axis.remap("SSR", [i0, i1, k])
+                    T.reads(A[v_i0, v_k], B[v_k, v_i1])
+                    T.writes(matmul_1[v_i0, v_i1])
+                    with T.init():
+                        matmul_1[v_i0, v_i1] = T.float32(0)
+                    matmul_1[v_i0, v_i1] = matmul_1[v_i0, v_i1] + A[v_i0, v_k] 
* B[v_k, v_i1]
+
+        @T.prim_func(private=True)
+        def matmul2(
+            A: T.Buffer((T.int64(128), T.int64(64)), "float32"),
+            B: T.Buffer((T.int64(64), T.int64(128)), "float32"),
+            matmul_1: T.Buffer((T.int64(128), T.int64(128)), "float32"),
+        ):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            # with T.block("root"):
+            for i0, i1, k in T.grid(T.int64(128), T.int64(128), T.int64(64)):
+                with T.block("matmul"):
+                    v_i0, v_i1, v_k = T.axis.remap("SSR", [i0, i1, k])
+                    T.reads(A[v_i0, v_k], B[v_k, v_i1])
+                    T.writes(matmul_1[v_i0, v_i1])
+                    with T.init():
+                        matmul_1[v_i0, v_i1] = T.float32(0)
+                    matmul_1[v_i0, v_i1] = matmul_1[v_i0, v_i1] + A[v_i0, v_k] 
* B[v_k, v_i1]
+
+        @R.function
+        def foo(
+            x: R.DTensor((128, 128), "float32", "mesh[0]", "R"),
+            weight1: R.DTensor((128, 128), "float32", "mesh[0]", "S[1]"),
+            weight2: R.DTensor((128, 128), "float32", "mesh[0]", "S[0]"),
+        ) -> R.DTensor((128, 128), "float32", "mesh[0]", "R"):
+            R.func_attr({"num_input": 1})
+            cls = MLP
+            lv0: R.DTensor((128, 128), "float32", "mesh[0]", "S[1]") = 
R.dist.call_tir_local_view(
+                cls.matmul1,
+                (x, weight1),
+                out_sinfo=R.DTensor((128, 128), "float32", "mesh[0]", "S[1]"),
+            )
+            lv1: R.DTensor((128, 128), "float32", "mesh[0]", "S[1]") = 
R.dist.call_tir_local_view(
+                cls.gelu1, (lv0,), out_sinfo=R.DTensor((128, 128), "float32", 
"mesh[0]", "S[1]")
+            )
+            lv2: R.DTensor((128, 128), "float32", "mesh[0]", "S[1]") = lv1
+            gv: R.DTensor((128, 128), "float32", "mesh[0]", "R") = 
R.dist.call_tir_local_view(
+                cls.matmul2,
+                (lv2, weight2),
+                out_sinfo=R.DTensor((128, 128), "float32", "mesh[0]", "R"),
+            )
+            lv3: R.DTensor((128, 128), "float32", "mesh[0]", "R") = 
R.ccl.allreduce(
+                gv, op_type="sum"
+            )
+            return lv3
+
+    @I.ir_module
+    class LoweredMLP:
+        I.module_attrs({"device_num": 10})
+        I.module_global_infos(
+            {"mesh": [R.device_mesh((2,), I.Range(0, 2)), R.device_mesh((1,), 
I.Range(4, 5))]}
+        )
+
+        @R.function
+        def foo(
+            x: R.Tensor((128, 128), dtype="float32"),
+            weight1: R.Tensor((128, 128), dtype="float32"),
+            weight2: R.Tensor((128, 128), dtype="float32"),
+        ) -> R.Tensor((128, 128), dtype="float32"):
+            R.func_attr({"num_input": 1})
+            cls = LoweredMLP
+            gv: R.Tensor((128, 128), dtype="float32") = 
R.ccl.broadcast_from_worker0(x)
+            gv1: R.Tensor((128, 64), dtype="float32") = 
R.ccl.scatter_from_worker0(
+                weight1, num_workers=2, axis=1
+            )
+            gv2: R.Tensor((64, 128), dtype="float32") = 
R.ccl.scatter_from_worker0(
+                weight2, num_workers=2, axis=0
+            )
+            lv0 = R.call_tir(
+                MLP.get_global_var("matmul1"),
+                (gv, gv1),
+                out_sinfo=R.Tensor((128, 64), dtype="float32"),
+            )
+            lv1 = R.call_tir(
+                MLP.get_global_var("gelu1"), (lv0,), out_sinfo=R.Tensor((128, 
64), dtype="float32")
+            )
+            lv2: R.Tensor((128, 64), dtype="float32") = lv1
+            gv_1 = R.call_tir(
+                MLP.get_global_var("matmul2"),
+                (lv2, gv2),
+                out_sinfo=R.Tensor((128, 128), dtype="float32"),
+            )
+            lv3: R.Tensor((128, 128), dtype="float32") = R.ccl.allreduce(gv_1, 
op_type="sum")
+            return lv3
+
+    for gv, func in MLP.functions_items():
+        if gv.name_hint != "foo":
+            LoweredMLP[gv] = func
+
+    mod = MLP
+    mod = relax.distributed.transform.LowerDistIR()(mod)
+    tvm.ir.assert_structural_equal(mod, LoweredMLP)
+
+
+def test_mlp_with_tuple():
+    @I.ir_module
+    class MLPWithTuple:
+        I.module_attrs({"device_num": 10})
+        I.module_global_infos(
+            {"mesh": [R.device_mesh((2,), I.Range(0, 2)), R.device_mesh((1,), 
I.Range(4, 5))]}
+        )
+
+        @T.prim_func(private=True)
+        def gelu1(
+            A: T.Buffer((T.int64(128), T.int64(64)), "float32"),
+            T_multiply: T.Buffer((T.int64(128), T.int64(64)), "float32"),
+        ):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            # with T.block("root"):
+            T_multiply_1 = T.alloc_buffer((T.int64(128), T.int64(64)))
+            compute = T.alloc_buffer((T.int64(128), T.int64(64)))
+            T_multiply_2 = T.alloc_buffer((T.int64(128), T.int64(64)))
+            T_add = T.alloc_buffer((T.int64(128), T.int64(64)))
+            for ax0, ax1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("T_multiply"):
+                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
+                    T.reads(A[v_ax0, v_ax1])
+                    T.writes(T_multiply_1[v_ax0, v_ax1])
+                    T_multiply_1[v_ax0, v_ax1] = A[v_ax0, v_ax1] * 
T.float32(0.70710678118654757)
+            for i0, i1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("compute"):
+                    v_i0, v_i1 = T.axis.remap("SS", [i0, i1])
+                    T.reads(T_multiply_1[v_i0, v_i1])
+                    T.writes(compute[v_i0, v_i1])
+                    compute[v_i0, v_i1] = T.erf(T_multiply_1[v_i0, v_i1])
+            for ax0, ax1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("T_multiply_1"):
+                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
+                    T.reads(compute[v_ax0, v_ax1])
+                    T.writes(T_multiply_2[v_ax0, v_ax1])
+                    T_multiply_2[v_ax0, v_ax1] = compute[v_ax0, v_ax1] * 
T.float32(0.5)
+            for ax0, ax1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("T_add"):
+                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
+                    T.reads(T_multiply_2[v_ax0, v_ax1])
+                    T.writes(T_add[v_ax0, v_ax1])
+                    T_add[v_ax0, v_ax1] = T.float32(0.5) + T_multiply_2[v_ax0, 
v_ax1]
+            for ax0, ax1 in T.grid(T.int64(128), T.int64(64)):
+                with T.block("T_multiply_2"):
+                    v_ax0, v_ax1 = T.axis.remap("SS", [ax0, ax1])
+                    T.reads(A[v_ax0, v_ax1], T_add[v_ax0, v_ax1])
+                    T.writes(T_multiply[v_ax0, v_ax1])
+                    T_multiply[v_ax0, v_ax1] = A[v_ax0, v_ax1] * T_add[v_ax0, 
v_ax1]
+
+        @T.prim_func(private=True)
+        def matmul11(
+            A: T.Buffer((T.int64(64), T.int64(64)), "float32"),
+            B: T.Buffer((T.int64(64), T.int64(128)), "float32"),
+            matmul: T.Buffer((T.int64(64), T.int64(128)), "float32"),
+        ):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            # with T.block("root"):
+            for i0, i1, k in T.grid(T.int64(64), T.int64(128), T.int64(64)):
+                with T.block("matmul"):
+                    v_i0, v_i1, v_k = T.axis.remap("SSR", [i0, i1, k])
+                    T.reads(A[v_i0, v_k], B[v_k, v_i1])
+                    T.writes(matmul[v_i0, v_i1])
+                    with T.init():
+                        matmul[v_i0, v_i1] = T.float32(0)
+                    matmul[v_i0, v_i1] = matmul[v_i0, v_i1] + A[v_i0, v_k] * 
B[v_k, v_i1]
+
+        @T.prim_func(private=True)
+        def matmul2(
+            A: T.Buffer((T.int64(128), T.int64(128)), "float32"),
+            B: T.Buffer((T.int64(128), T.int64(64)), "float32"),
+            matmul: T.Buffer((T.int64(128), T.int64(64)), "float32"),
+        ):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            # with T.block("root"):
+            for i0, i1, k in T.grid(T.int64(128), T.int64(64), T.int64(128)):
+                with T.block("matmul"):
+                    v_i0, v_i1, v_k = T.axis.remap("SSR", [i0, i1, k])
+                    T.reads(A[v_i0, v_k], B[v_k, v_i1])
+                    T.writes(matmul[v_i0, v_i1])
+                    with T.init():
+                        matmul[v_i0, v_i1] = T.float32(0)
+                    matmul[v_i0, v_i1] = matmul[v_i0, v_i1] + A[v_i0, v_k] * 
B[v_k, v_i1]
+
+        @T.prim_func(private=True)
+        def split11(
+            A: T.Buffer((128, 64), "float32"),
+            T_split: T.Buffer((64, 64), "float32"),
+            T_split_1: T.Buffer((64, 64), "float32"),
+        ):
+            T.func_attr({"tir.noalias": T.bool(True)})
+            # with T.block("root"):
+            for ax1, ax2 in T.grid(64, 64):
+                with T.block("T_split"):
+                    v_ax1, v_ax2 = T.axis.remap("SS", [ax1, ax2])
+                    T.reads(A[v_ax1, v_ax2])
+                    T.writes(T_split[v_ax1, v_ax2])
+                    T_split[v_ax1, v_ax2] = A[v_ax1, v_ax2]
+            for ax1, ax2 in T.grid(64, 64):
+                with T.block("T_split_1"):
+                    v_ax1, v_ax2 = T.axis.remap("SS", [ax1, ax2])
+                    T.reads(A[v_ax1 + 64, v_ax2])
+                    T.writes(T_split_1[v_ax1, v_ax2])
+                    T_split_1[v_ax1, v_ax2] = A[v_ax1 + 64, v_ax2]
+
+        @R.function
+        def foo(
+            x: R.DTensor((128, 128), "float32", "mesh[0]", "R"),
+            weight_packed: R.Tuple(
+                R.DTensor((128, 128), "float32", "mesh[0]", "S[1]"),
+                R.DTensor((128, 128), "float32", "mesh[0]", "S[0]"),
+            ),
+        ) -> R.DTensor((64, 128), "float32", "mesh[0]", "R"):
+            cls = MLPWithTuple
+            weight1: R.DTensor((128, 128), "float32", "mesh[0]", "S[1]") = 
weight_packed[0]
+            lv0: R.DTensor((128, 128), "float32", "mesh[0]", "S[1]") = 
R.dist.call_tir_local_view(
+                cls.matmul2,
+                (x, weight1),
+                out_sinfo=R.DTensor((128, 128), "float32", "mesh[0]", "S[1]"),
+            )
+            lv1: R.DTensor((128, 128), "float32", "mesh[0]", "S[1]") = 
R.dist.call_tir_local_view(
+                cls.gelu1, (lv0,), out_sinfo=R.DTensor((128, 128), "float32", 
"mesh[0]", "S[1]")
+            )
+            gv: R.Tuple(
+                R.DTensor((64, 128), "float32", "mesh[0]", "S[1]"),
+                R.DTensor((64, 128), "float32", "mesh[0]", "S[1]"),
+            ) = R.dist.call_tir_local_view(
+                cls.split11,
+                (lv1,),
+                out_sinfo=[
+                    R.DTensor((64, 128), "float32", "mesh[0]", "S[1]"),
+                    R.DTensor((64, 128), "float32", "mesh[0]", "S[1]"),
+                ],
+            )
+            lv2: R.DTensor((64, 128), "float32", "mesh[0]", "S[1]") = gv[0]
+            lv3: R.DTensor((64, 128), "float32", "mesh[0]", "S[1]") = lv2
+            weight2: R.DTensor((128, 128), "float32", "mesh[0]", "S[0]") = 
weight_packed[1]
+            gv_1: R.DTensor((64, 128), "float32", "mesh[0]", "R") = 
R.dist.call_tir_local_view(
+                cls.matmul11,
+                (lv3, weight2),
+                out_sinfo=R.DTensor((64, 128), "float32", "mesh[0]", "R"),
+            )
+            lv4: R.DTensor((64, 128), "float32", "mesh[0]", "R") = 
R.ccl.allreduce(
+                gv_1, op_type="sum"
+            )
+            return lv4
+
+    @I.ir_module
+    class LoweredMLPWithTuple:
+        I.module_attrs({"device_num": 10})
+        I.module_global_infos(
+            {"mesh": [R.device_mesh((2,), I.Range(0, 2)), R.device_mesh((1,), 
I.Range(4, 5))]}
+        )
+
+        @R.function
+        def foo(
+            x: R.Tensor((128, 128), dtype="float32"),
+            weight_packed: R.Tuple(
+                R.Tensor((128, 128), dtype="float32"), R.Tensor((128, 128), 
dtype="float32")
+            ),
+        ) -> R.Tensor((64, 128), dtype="float32"):
+            cls = LoweredMLPWithTuple
+            gv: R.Tensor((128, 128), dtype="float32") = 
R.ccl.broadcast_from_worker0(x)
+            gv1: R.Tensor((128, 128), dtype="float32") = weight_packed[0]
+            gv2: R.Tensor((128, 64), dtype="float32") = 
R.ccl.scatter_from_worker0(
+                gv1, num_workers=2, axis=1
+            )
+            gv3: R.Tensor((128, 128), dtype="float32") = weight_packed[1]
+            gv4: R.Tensor((64, 128), dtype="float32") = 
R.ccl.scatter_from_worker0(
+                gv3, num_workers=2, axis=0
+            )
+            lv0 = R.call_tir(
+                MLPWithTuple.get_global_var("matmul2"),
+                (gv, gv2),
+                out_sinfo=R.Tensor((128, 64), dtype="float32"),
+            )
+            lv1 = R.call_tir(
+                MLPWithTuple.get_global_var("gelu1"),
+                (lv0,),
+                out_sinfo=R.Tensor((128, 64), dtype="float32"),
+            )
+            gv_1 = R.call_tir(
+                MLPWithTuple.get_global_var("split11"),
+                (lv1,),
+                out_sinfo=[
+                    R.Tensor((64, 64), dtype="float32"),
+                    R.Tensor((64, 64), dtype="float32"),
+                ],
+            )
+            lv2: R.Tensor((64, 64), dtype="float32") = gv_1[0]
+            lv3: R.Tensor((64, 64), dtype="float32") = lv2
+            gv_1_1 = R.call_tir(
+                MLPWithTuple.get_global_var("matmul11"),
+                (lv3, gv4),
+                out_sinfo=R.Tensor((64, 128), dtype="float32"),
+            )
+            lv4: R.Tensor((64, 128), dtype="float32") = 
R.ccl.allreduce(gv_1_1, op_type="sum")
+            return lv4
+
+    for gv, func in MLPWithTuple.functions_items():
+        if gv.name_hint != "foo":
+            LoweredMLPWithTuple[gv] = func
+
+    mod = MLPWithTuple
+    mod = relax.distributed.transform.LowerDistIR()(mod)
+    tvm.ir.assert_structural_equal(mod, LoweredMLPWithTuple)
+
+
+if __name__ == "__main__":
+    tvm.testing.main()

Reply via email to