This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 2eca9f0270 [TIR] Return error code from kernels in SplitHostDevice
(#15241)
2eca9f0270 is described below
commit 2eca9f02705651359de7075e7c0d1a37a33c2e0a
Author: Eric Lunderberg <[email protected]>
AuthorDate: Tue Jul 18 13:10:36 2023 -0500
[TIR] Return error code from kernels in SplitHostDevice (#15241)
* [TVMScript] Handle parsing of PrimFunc calls with non-void return
Prior to this commit, the return type of all internal function calls
was hard-coded as `"void"`. After this commit, the `GlobalVar`
representing the internal function has type annotation based on the
callee's signature, which is then used as the return type of the
internal call.
* Update CallNode return type in MakeUnpackedAPI
* [TIR] Return error code from kernels in SplitHostDevice
Some codegen types delegate to `CodeGenCPU` for their compute kernels,
as they may delegate work to packed functions. Because `CodeGenCPU`
assumes that it can return an error code at any point (e.g. when
launching a parallel for loop), the compute kernel should return an
error code.
* [TIR] Remove builtin::ret(0) from device-side kernel
* Restrict the int32 return type to targets that need to propagate errors
* Updated unit tests for CPU-specific checks
---
src/tir/transforms/lower_device_kernel_launch.cc | 41 +++++++++++++++++++++-
src/tir/transforms/split_host_device.cc | 33 +++++++++++++++--
.../test_tir_transform_split_host_device.py | 38 ++++++++++++++++++++
3 files changed, 108 insertions(+), 4 deletions(-)
diff --git a/src/tir/transforms/lower_device_kernel_launch.cc
b/src/tir/transforms/lower_device_kernel_launch.cc
index 52f06ea45c..932116485f 100644
--- a/src/tir/transforms/lower_device_kernel_launch.cc
+++ b/src/tir/transforms/lower_device_kernel_launch.cc
@@ -145,6 +145,36 @@ class DeviceInfoCollector : public StmtVisitor {
// The amount of dynamic shared memory used
Optional<PrimExpr> dyn_shmem_size{NullOpt};
};
+
+class ReturnRemover : public StmtExprMutator {
+ public:
+ static Stmt Apply(const Stmt& stmt) {
+ ReturnRemover mutator;
+ return mutator(stmt);
+ }
+
+ private:
+ using Parent = StmtExprMutator;
+ Stmt VisitStmt_(const EvaluateNode* op) override {
+ if (auto* call = op->value.as<CallNode>()) {
+ if (call->op.same_as(builtin::ret())) {
+ ICHECK_EQ(call->args.size(), 1);
+ auto as_int = call->args[0].as<IntImmNode>();
+ ICHECK(as_int && as_int->value == 0)
+ << "Device kernel may only contain successful return, T.ret(0)";
+ return Evaluate(0);
+ }
+ }
+ return Parent::VisitStmt_(op);
+ }
+
+ PrimExpr VisitExpr_(const CallNode* op) override {
+ if (op->op.same_as(builtin::ret())) {
+ LOG(FATAL) << "Call to builtin::ret() should only appear within an
Evaluate node";
+ }
+ return Parent::VisitExpr_(op);
+ }
+};
} // namespace
class DeviceKernelMutator : public StmtExprMutator {
@@ -185,10 +215,19 @@ class DeviceKernelMutator : public StmtExprMutator {
if (is_kernel_launch) {
const auto& info = device_info_map_.at(gvar.get());
+ // Kernel launches provide an int32 error code to the caller,
+ // but do not accept any return type from the callee.
+ {
+ auto write_ptr = func.CopyOnWrite();
+ write_ptr->ret_type = VoidType();
+ write_ptr->body = ReturnRemover::Apply(write_ptr->body);
+ }
+
func = WithAttrs(std::move(func),
{{tvm::attr::kCallingConv,
Integer(tvm::CallingConv::kDeviceKernelLaunch)},
{tvm::tir::attr::kKernelLaunchParams,
info.launch_params},
{tvm::attr::kGlobalSymbol, info.global_symbol}});
+
} else if (is_call_extern &&
!func->GetAttr<String>(tvm::attr::kGlobalSymbol)) {
func = WithAttr(func, tvm::attr::kGlobalSymbol, gvar->name_hint);
}
@@ -197,7 +236,7 @@ class DeviceKernelMutator : public StmtExprMutator {
}
private:
- PrimExpr VisitExpr_(const CallNode* op) {
+ PrimExpr VisitExpr_(const CallNode* op) override {
auto node = Downcast<Call>(Parent::VisitExpr_(op));
auto* gvar = op->op.as<GlobalVarNode>();
diff --git a/src/tir/transforms/split_host_device.cc
b/src/tir/transforms/split_host_device.cc
index ac5dc7131d..9b1dbf1a66 100644
--- a/src/tir/transforms/split_host_device.cc
+++ b/src/tir/transforms/split_host_device.cc
@@ -60,7 +60,7 @@ class HostDeviceSplitter : public StmtMutator {
VarUseDefAnalyzer use_def(/*defined_vars=*/{},
/*visit_thread_extent=*/false);
use_def(body);
- // Sort first by variable typ, then by variable name
+ // Sort first by variable type, then by variable name
std::vector<Var> params{use_def.undefined_.begin(),
use_def.undefined_.end()};
std::sort(params.begin(), params.end(), [](const Var& a, const Var& b) {
auto sort_key = [](const Var& var) {
@@ -74,8 +74,25 @@ class HostDeviceSplitter : public StmtMutator {
return params;
}();
+ // CodeGenCPU is used for some device-side targets, such as
+ // "ext_dev", and expects to be able to return a int32_t status
+ // code.
+
+ bool can_propagate_errors = [&]() {
+ auto kind = device_target->GetTargetDeviceType();
+ return kind == kDLCPU || kind == kDLExtDev || kind == kDLHexagon;
+ }();
+ IntImm success(DataType::Int(32), 0);
+ Type kernel_ret_type;
+ if (can_propagate_errors) {
+ kernel_ret_type = PrimType(DataType::Int(32));
+ body = SeqStmt::Flatten(body, Evaluate(ret(success)));
+ } else {
+ kernel_ret_type = VoidType();
+ }
+
GlobalVar kernel_symbol_global = var_supply_();
- PrimFunc device_func(params, body);
+ PrimFunc device_func(params, body, kernel_ret_type);
device_func = WithAttrs(std::move(device_func), {{tvm::attr::kTarget,
device_target},
{tir::attr::kNoAlias,
Bool(true)},
{tir::attr::kIsGlobalFunc, Bool(true)}});
@@ -83,7 +100,17 @@ class HostDeviceSplitter : public StmtMutator {
(*device_mod_)->Add(kernel_symbol_global, device_func);
Array<PrimExpr> args = params.Map([](const Var& var) -> PrimExpr { return
var; });
- return Evaluate(Call(DataType::Void(), kernel_symbol_global, args));
+ if (can_propagate_errors) {
+ Var kernel_error_code("kernel_error_code", success->dtype);
+ Call kernel_call(success->dtype, kernel_symbol_global, args);
+ AssertStmt assert_success(kernel_error_code == success,
+ StringImm("Error executing compute kernel"),
Evaluate(0));
+ LetStmt let_check(kernel_error_code, kernel_call, assert_success);
+
+ return std::move(let_check);
+ } else {
+ return Evaluate(Call(DataType::Void(), kernel_symbol_global, args));
+ }
}
// target ir module
diff --git a/tests/python/unittest/test_tir_transform_split_host_device.py
b/tests/python/unittest/test_tir_transform_split_host_device.py
index ca16fe908f..a4dbb6b6b9 100644
--- a/tests/python/unittest/test_tir_transform_split_host_device.py
+++ b/tests/python/unittest/test_tir_transform_split_host_device.py
@@ -129,6 +129,44 @@ class TestSplitHostDevice(BaseCompare):
return mod
+class TestSplitHostDeviceOnCPU(BaseCompare):
+ """A kernel running on the CPU may return an error code"""
+
+ def before(self):
+ @I.ir_module
+ class mod:
+ @T.prim_func
+ def main(n: T.int32):
+ T.func_attr({"target": T.target("cuda", host="llvm
-opt-level=0")})
+ T.attr(T.target("llvm"), "target", 0)
+ T.evaluate(n)
+
+ return mod
+
+ def expected(self):
+ @I.ir_module
+ class mod:
+ @T.prim_func
+ def main(n: T.int32):
+ T.func_attr({"target": T.target("cuda", host="llvm
-opt-level=0")})
+ err = mod.main_kernel(n)
+ assert err == 0, "Error executing compute kernel"
+
+ @T.prim_func
+ def main_kernel(n: T.int32) -> T.int32:
+ T.func_attr(
+ {
+ "target": T.target("llvm"),
+ "tir.noalias": T.bool(True),
+ "tir.is_global_func": True,
+ }
+ )
+ T.evaluate(n)
+ T.ret(0)
+
+ return mod
+
+
class TestSplitHostDeviceWithoutFuncHostAttribute(BaseCompare):
"""Like TestSplitHostDevice, but no host specified in the host's target