Files
Shushi Hong eafcba1c44 [Relax][TensorRT] Fix YOLO BYOC offload and partitioning gaps (#19998)
Fixes #19887.

This PR fixes several Relax TensorRT BYOC issues exposed by YOLO-style
models:

- adds TensorRT support for SiLU and resize2d
- preserves operand and TupleGetItem ordering during codegen
- fixes cyclic and unsafe Tuple/TGI region merging
- handles static Shape bindings and nested packed-function outputs
- normalizes PrimType dtype arguments passed to relax.arange

With these changes, yolo11n-seg can be merged into a single TensorRT
region, while yolo11n can be imported and partitioned successfully.
2026-07-16 15:57:40 -04:00

763 lines
27 KiB
Python

# 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.script
import tvm.testing
from tvm import relax
from tvm.script import ir as I
from tvm.script import relax as R
from tvm.script import tirx as T
def _make_dataflow_block_with_dual_role_var():
prim_ty = tvm.ir.PrimType("int64")
source = tvm.ir.Var("source", prim_ty)
data = tvm.ir.Var("data", relax.TensorType(dtype="float32", ndim=1))
extent = tvm.ir.Var("extent", prim_ty)
matched_ty = relax.TensorType([extent], "float32")
matched = relax.DataflowVar("matched", matched_ty)
block = relax.DataflowBlock(
[
relax.VarBinding(extent, source),
relax.MatchCast(matched, data, matched_ty),
]
)
func = relax.Function([source, data], relax.SeqExpr([block], extent), prim_ty)
return relax.transform.Normalize()(tvm.IRModule.from_expr(func))
def test_dataflowblock_pass_rejects_deleting_binding_role_of_symbolic_var():
mod = _make_dataflow_block_with_dual_role_var()
@relax.transform.dataflowblock_pass(opt_level=0)
def delete_ordinary_binding(block, _mod, _ctx):
return relax.DataflowBlock([block.bindings[1]])
with pytest.raises(tvm.error.InternalError, match="global-scope Var"):
delete_ordinary_binding(mod)
def test_dataflowblock_pass_rejects_rewriting_match_cast_role_of_binding_var():
mod = _make_dataflow_block_with_dual_role_var()
@relax.transform.dataflowblock_pass(opt_level=0)
def rewrite_match_cast_symbol(block, _mod, _ctx):
old_match_cast = block.bindings[1]
replacement = tvm.ir.Var("replacement", "int64")
replacement_ty = relax.TensorType([replacement], "float32")
new_match_cast = relax.MatchCast(
relax.DataflowVar(old_match_cast.var.name, replacement_ty),
old_match_cast.value,
replacement_ty,
)
return relax.DataflowBlock([block.bindings[0], new_match_cast])
with pytest.raises(tvm.error.InternalError, match="symbolic Var declared by a MatchCast"):
rewrite_match_cast_symbol(mod)
def test_to_non_dataflow():
@tvm.script.ir_module
class TestToNonDataflow:
@R.function
def foo(x: R.Tensor(("m", "n"), "float32")):
m, n = T.int64(), T.int64()
with R.dataflow():
lv0 = R.call_dps_packed(
"test.op.identity",
(x,),
R.Tensor(
(m, n),
dtype="float32",
),
)
gv0 = R.call_dps_packed(
"test.op.identity",
(lv0,),
R.Tensor(
(m, n),
dtype="float32",
),
)
R.output(gv0)
return gv0
mod = TestToNonDataflow
old_vars = []
def fvisit(e):
if isinstance(e, relax.Var) and not tvm.ir.is_prim_expr(e):
nonlocal old_vars
old_vars.append(e)
relax.analysis.post_order_visit(mod["foo"], fvisit)
x, lv0, gv0 = old_vars
new_mod = relax.transform.ToNonDataflow()(mod)
new_vars = []
def fvisit(e):
if isinstance(e, relax.Var) and not tvm.ir.is_prim_expr(e):
nonlocal new_vars
new_vars.append(e)
relax.analysis.post_order_visit(new_mod["foo"], fvisit)
assert x == new_vars[0]
assert lv0 != new_vars[1]
assert isinstance(lv0, relax.DataflowVar)
assert not isinstance(new_vars[1], relax.DataflowVar)
assert isinstance(gv0, relax.Var)
assert isinstance(new_vars[2], relax.Var)
assert gv0 == new_vars[2]
def test_call_tir_rewrite():
@tvm.script.ir_module
class TestCallTIRRewrite:
@T.prim_func(s_tir=True)
def exp(A_handle: T.handle, B_handle: T.handle):
m = T.int64()
n = T.int64()
A = T.match_buffer(A_handle, (m, n), "float32")
B = T.match_buffer(B_handle, (m, n), "float32")
T.evaluate(0)
@R.function
def foo(x: R.Tensor(("m", "n"), "float32")):
# we expect RemovePurityChecking to have been used before this point
R.func_attr({"relax.force_pure": True})
m, n = T.int64(), T.int64()
gv0 = R.call_tir(TestCallTIRRewrite.exp, (x,), R.Tensor((m, n), dtype="float32"))
return gv0
mod = TestCallTIRRewrite
# before rewrite
v0 = mod["foo"].body.blocks[0].bindings[0].var
s0 = mod["foo"].body.blocks[0].bindings[0].value
assert isinstance(s0, relax.Call)
assert s0.op.name == "relax.call_tir"
# after rewrite
new_mod = relax.transform.CallTIRRewrite()(mod)
func = new_mod["foo"]
block = func.body.blocks[0]
assert not isinstance(block, relax.DataflowBlock)
s1 = block.bindings[0].value
assert isinstance(s1, relax.Call)
assert s1.op.name == "relax.builtin.alloc_tensor"
assert isinstance(s1.args[0], relax.ShapeExpr)
tvm.ir.assert_structural_equal(s1.args[0], s0.ty_args[0].shape)
s2 = block.bindings[1].value
tvm.ir.expr.GlobalVar
assert s2.op.name_hint == "exp"
def test_call_tir_rewrite_with_interspersed_primitive_argument():
@I.ir_module(s_tir=True)
class Module:
@T.prim_func(s_tir=True)
def scale_add(
A: T.Buffer((16,), "float32"),
scale: T.float32,
C: T.Buffer((16,), "float32"),
B: T.Buffer((16,), "float32"),
):
for i in range(16):
B[i] = A[i] + scale * C[i]
@R.function
def main(
A: R.Tensor((16,), "float32"),
scale: R.Prim("float32"),
C: R.Tensor((16,), "float32"),
) -> R.Tensor((16,), "float32"):
R.func_attr({"relax.force_pure": True})
B = R.call_tir(Module.scale_add, (A, scale, C), R.Tensor((16,), "float32"))
return B
after = relax.transform.CallTIRRewrite()(Module)
func = after["main"]
bindings = func.body.blocks[0].bindings
output_buffer = bindings[0].var
call = bindings[1].value
assert call.op.name_hint == "scale_add"
tvm.ir.assert_structural_equal(
call.args,
[func.params[0], func.params[1], func.params[2], output_buffer],
)
def test_transform_remove_purity_checking():
@tvm.script.ir_module
class Before:
@R.function
def base(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
y = R.add(x, x)
z = R.add(x, y)
return z
@R.function
def use_call_pure_packed(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
y = R.add(x, x)
z = R.call_pure_packed("vm.builtin.copy", y, ty_args=(R.Tensor((), dtype="int32")))
return z
@R.function
def use_invoke_pure_closure(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
closure = R.make_closure(Before.base, ())
res = R.invoke_pure_closure(closure, (x,), ty_args=R.Tensor((), "int32"))
return res
@R.function(pure=False)
def impure_func() -> R.Any:
y = R.print(format="I am impure!")
return y
@R.function
def nested_pure_func() -> R.Tensor((), "int32"):
@R.function
def nested(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
y = R.add(x, x)
q = R.call_pure_packed("vm.builtin.copy", y, ty_args=(R.Tensor((), dtype="int32")))
return q
z = R.const(1, dtype="int32")
w = nested(z)
return w
@R.function(pure=False)
def nested_impure_func() -> R.Tensor((), "int32"):
@R.function(pure=False)
def nested() -> R.Any:
x = R.print(format="Oops!")
return x
y = R.const(1, dtype="int32")
z = nested()
return y
@tvm.script.ir_module
class Expected:
@R.function
def base(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
R.func_attr({"relax.force_pure": True})
y = R.add(x, x)
z = R.add(x, y)
return z
@R.function
def use_call_pure_packed(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
R.func_attr({"relax.force_pure": True})
y = R.add(x, x)
z = R.call_packed("vm.builtin.copy", y, ty_args=(R.Tensor((), dtype="int32")))
return z
@R.function
def use_invoke_pure_closure(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
R.func_attr({"relax.force_pure": True})
closure = R.make_closure(Expected.base, ())
res = R.invoke_closure(closure, (x,), ty_args=R.Tensor((), "int32"))
return res
@R.function(pure=False)
def impure_func() -> R.Any:
y = R.print(format="I am impure!")
return y
@R.function
def nested_pure_func() -> R.Tensor((), "int32"):
R.func_attr({"relax.force_pure": True})
@R.function
def nested(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
R.func_attr({"relax.force_pure": True})
y = R.add(x, x)
q = R.call_packed("vm.builtin.copy", y, ty_args=(R.Tensor((), dtype="int32")))
return q
z = R.const(1, dtype="int32")
w = nested(z)
return w
@R.function(pure=False)
def nested_impure_func() -> R.Tensor((), "int32"):
@R.function(pure=False)
def nested() -> R.Any:
x = R.print(format="Oops!")
return x
y = R.const(1, dtype="int32")
z = nested()
return y
new_mod = relax.transform.RemovePurityChecking()(Before)
tvm.ir.assert_structural_equal(new_mod, Expected)
def test_call_dps_packed_rewrite():
@tvm.script.ir_module
class TestCallDPSPackedRewrite:
@R.function
def foo(x: R.Tensor(("m", "n"), "float32")):
# we expect RemovePurityChecking to have been used before this point
R.func_attr({"relax.force_pure": True})
m, n = T.int64(), T.int64()
gv0 = R.call_dps_packed("test.op.identity", (x,), R.Tensor((m, n), dtype="float32"))
return gv0
mod = TestCallDPSPackedRewrite
# before rewrite
v0 = mod["foo"].body.blocks[0].bindings[0].var
s0 = mod["foo"].body.blocks[0].bindings[0].value
assert isinstance(s0, relax.Call)
assert s0.op.name == "relax.call_dps_packed"
# CallTIRRewrite also works for call_dps_packed
new_mod = relax.transform.CallTIRRewrite()(mod)
func = new_mod["foo"]
block = func.body.blocks[0]
assert not isinstance(block, relax.DataflowBlock)
s1 = block.bindings[0].value
assert isinstance(s1, relax.Call)
assert s1.op.name == "relax.builtin.alloc_tensor"
assert isinstance(s1.args[0], relax.ShapeExpr)
tvm.ir.assert_structural_equal(s1.args[0], s0.ty_args[0].shape)
s2 = block.bindings[1].value
assert s2.op.global_symbol == "test.op.identity"
def test_call_dps_packed_rewrite_nested_tuple_output():
"""Flatten nested outputs for the packed ABI, then rebuild their Relax structure."""
input_ty = relax.TensorType((2, 3), "float32")
flat_output_types = [
relax.TensorType((2, 3), "float32"),
relax.TensorType((4,), "int32"),
relax.TensorType((5, 6), "float16"),
]
output_ty = tvm.ir.TupleType([flat_output_types[0], tvm.ir.TupleType(flat_output_types[1:])])
x = relax.Var("x", input_ty)
call = relax.Call(
tvm.ir.Op.get("relax.call_dps_packed"),
[relax.ExternFunc("test.op.nested_outputs"), relax.Tuple([x])],
ty_args=[output_ty],
)
builder = relax.BlockBuilder()
with builder.function("main", [x], attrs={"relax.force_pure": True}):
out = builder.emit(call)
builder.emit_func_output(out)
after = relax.transform.CallTIRRewrite()(builder.get())
relax.analysis.well_formed(after)
func = after["main"]
block = func.body.blocks[0]
alloc_bindings = block.bindings[:3]
for binding, expected_ty in zip(alloc_bindings, flat_output_types):
assert binding.value.op.name == "relax.builtin.alloc_tensor"
tvm.ir.assert_structural_equal(binding.var.ty, expected_ty)
packed_call = block.bindings[3].value
assert packed_call.op.global_symbol == "test.op.nested_outputs"
assert packed_call.args[0].same_as(func.params[0])
assert all(
arg.same_as(binding.var) for arg, binding in zip(packed_call.args[1:], alloc_bindings)
)
rebuilt = block.bindings[4].value
assert rebuilt.fields[0].same_as(alloc_bindings[0].var)
assert rebuilt.fields[1].fields[0].same_as(alloc_bindings[1].var)
assert rebuilt.fields[1].fields[1].same_as(alloc_bindings[2].var)
def test_call_tir_inplace_simple():
# simple case: one inplace argument
@tvm.script.ir_module
class Input:
@T.prim_func(s_tir=True)
def zeros(A: T.Buffer((2, 3), "int32")):
# just overwrites A with 0s
T.func_attr({"tirx.noalias": True})
for i0, i1 in T.grid(T.int64(2), T.int64(3)):
with T.sblock("T_zeros"):
ax0, ax1 = T.axis.remap("SS", [i0, i1])
T.writes(A[ax0, ax1])
A[ax0, ax1] = T.int32(0)
@R.function
def foo(x: R.Tensor((2, 3), "int32")) -> R.Tensor((2, 3), "int32"):
# we expect RemovePurityChecking to have been used before this point
R.func_attr({"relax.force_pure": True})
gv0 = R.call_tir_inplace(Input.zeros, x, 0, R.Tensor((2, 3), dtype="int32"))
return gv0
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def zeros(A: T.Buffer((2, 3), "int32")):
T.func_attr({"tirx.noalias": True})
for i0, i1 in T.grid(T.int64(2), T.int64(3)):
with T.sblock("T_zeros"):
ax0, ax1 = T.axis.remap("SS", [i0, i1])
T.writes(A[ax0, ax1])
A[ax0, ax1] = T.int32(0)
@R.function
def foo(x: R.Tensor((2, 3), "int32")) -> R.Tensor((2, 3), "int32"):
R.func_attr({"relax.force_pure": True})
_ = Expected.zeros(x)
gv0 = x
return gv0
new_mod = relax.transform.CallTIRRewrite()(Input)
tvm.ir.assert_structural_equal(Expected["foo"], new_mod["foo"], map_free_vars=True)
def test_call_tir_inplace_multiple_args():
@tvm.script.ir_module
class Input:
@T.prim_func(s_tir=True)
def copy(
A: T.Buffer((2, 3), "int32"), B: T.Buffer((2, 3), "int32"), C: T.Buffer((2, 3), "int32")
):
# copies the contents of C into A and B
T.func_attr({"tirx.noalias": True})
for i0, i1 in T.grid(T.int64(2), T.int64(3)):
with T.sblock("T_zeros"):
ax0, ax1 = T.axis.remap("SS", [i0, i1])
T.reads(C[ax0, ax1])
T.writes(A[ax0, ax1], B[ax0, ax1])
A[ax0, ax1] = C[ax0, ax1]
B[ax0, ax1] = C[ax0, ax1]
@R.function
def foo(
x: R.Tensor((2, 3), "int32"), y: R.Tensor((2, 3), "int32"), z: R.Tensor((2, 3), "int32")
) -> R.Tuple(R.Tensor((2, 3), "int32"), R.Tensor((2, 3), "int32")):
R.func_attr({"relax.force_pure": True})
gv0 = R.call_tir_inplace(
Input.copy,
(x, y, z),
[0, 1],
[R.Tensor((2, 3), dtype="int32"), R.Tensor((2, 3), dtype="int32")],
)
return gv0
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def copy(
A: T.Buffer((2, 3), "int32"), B: T.Buffer((2, 3), "int32"), C: T.Buffer((2, 3), "int32")
):
# copies the contents of C into A and B
T.func_attr({"tirx.noalias": True})
for i0, i1 in T.grid(T.int64(2), T.int64(3)):
with T.sblock("T_zeros"):
ax0, ax1 = T.axis.remap("SS", [i0, i1])
T.reads(C[ax0, ax1])
T.writes(A[ax0, ax1], B[ax0, ax1])
A[ax0, ax1] = C[ax0, ax1]
B[ax0, ax1] = C[ax0, ax1]
@R.function
def foo(
x: R.Tensor((2, 3), "int32"), y: R.Tensor((2, 3), "int32"), z: R.Tensor((2, 3), "int32")
) -> R.Tuple(R.Tensor((2, 3), "int32"), R.Tensor((2, 3), "int32")):
R.func_attr({"relax.force_pure": True})
_ = Expected.copy(x, y, z)
gv0 = (x, y)
return gv0
new_mod = relax.transform.CallTIRRewrite()(Input)
tvm.ir.assert_structural_equal(Expected["foo"], new_mod["foo"], map_free_vars=True)
def test_call_tir_inplace_some_new():
@tvm.script.ir_module
class Input:
@T.prim_func(s_tir=True)
def copy(
A: T.Buffer((2, 3), "int32"),
B: T.Buffer((2, 3), "int32"),
C: T.Buffer((2, 3), "int32"),
out1: T.Buffer((2, 3), "int32"),
out2: T.Buffer((2, 3), "int32"),
):
# copies the contents of C into A, out1, and out2
T.func_attr({"tirx.noalias": True})
for i0, i1 in T.grid(T.int64(2), T.int64(3)):
with T.sblock("T_zeros"):
ax0, ax1 = T.axis.remap("SS", [i0, i1])
T.reads(C[ax0, ax1])
T.writes(A[ax0, ax1], out1[ax0, ax1], out2[ax0, ax1])
A[ax0, ax1] = C[ax0, ax1]
out1[ax0, ax1] = C[ax0, ax1]
out2[ax0, ax1] = C[ax0, ax1]
@R.function
def foo(
x: R.Tensor((2, 3), "int32"), y: R.Tensor((2, 3), "int32"), z: R.Tensor((2, 3), "int32")
) -> R.Tuple(
R.Tensor((2, 3), "int32"), R.Tensor((2, 3), "int32"), R.Tensor((2, 3), dtype="int32")
):
R.func_attr({"relax.force_pure": True})
gv0 = R.call_tir_inplace(
Input.copy,
(x, y, z),
[0, -1, -1],
[
R.Tensor((2, 3), dtype="int32"),
R.Tensor((2, 3), dtype="int32"),
R.Tensor((2, 3), dtype="int32"),
],
)
return gv0
@tvm.script.ir_module
class Expected:
@T.prim_func(s_tir=True)
def copy(
A: T.Buffer((2, 3), "int32"),
B: T.Buffer((2, 3), "int32"),
C: T.Buffer((2, 3), "int32"),
out1: T.Buffer((2, 3), "int32"),
out2: T.Buffer((2, 3), "int32"),
):
T.func_attr({"tirx.noalias": True})
for i0, i1 in T.grid(T.int64(2), T.int64(3)):
with T.sblock("T_zeros"):
ax0, ax1 = T.axis.remap("SS", [i0, i1])
T.reads(C[ax0, ax1])
T.writes(A[ax0, ax1], out1[ax0, ax1], out2[ax0, ax1])
A[ax0, ax1] = C[ax0, ax1]
out1[ax0, ax1] = C[ax0, ax1]
out2[ax0, ax1] = C[ax0, ax1]
@R.function
def foo(
x: R.Tensor((2, 3), "int32"), y: R.Tensor((2, 3), "int32"), z: R.Tensor((2, 3), "int32")
) -> R.Tuple(
R.Tensor((2, 3), "int32"), R.Tensor((2, 3), "int32"), R.Tensor((2, 3), dtype="int32")
):
R.func_attr({"relax.force_pure": True})
gv0: R.Tensor((2, 3), dtype="int32") = R.emit_with_ty(
"relax.builtin.alloc_tensor",
(R.shape([2, 3]), R.dtype("int32"), R.prim_value(0), R.str("global")),
(R.Tensor((2, 3), dtype="int32"),),
)
gv1: R.Tensor((2, 3), dtype="int32") = R.emit_with_ty(
"relax.builtin.alloc_tensor",
(R.shape([2, 3]), R.dtype("int32"), R.prim_value(0), R.str("global")),
(R.Tensor((2, 3), dtype="int32"),),
)
_ = Expected.copy(x, y, z, gv0, gv1)
gv2 = (x, gv0, gv1)
return gv2
new_mod = relax.transform.CallTIRRewrite()(Input)
tvm.ir.assert_structural_equal(Expected["foo"], new_mod["foo"], map_free_vars=True)
def test_call_tir_inplace_repeated_input():
with pytest.raises(tvm.error.DiagnosticError):
@tvm.script.ir_module
class Input:
@T.prim_func(s_tir=True)
def func(
A: T.Buffer((2, 3), "int32"),
B: T.Buffer((2, 3), "int32"),
C: T.Buffer((2, 3), "int32"),
):
T.evaluate(0)
@R.function
def foo(
x: R.Tensor((2, 3), "int32"),
y: R.Tensor((2, 3), "int32"),
z: R.Tensor((2, 3), "int32"),
) -> R.Tuple(R.Tensor((2, 3), "int32"), R.Tensor((2, 3), "int32")):
R.func_attr({"relax.force_pure": True})
gv0 = R.call_tir_inplace(
Input.func,
(x, y, z),
# repeated 0 -> that's an error
[0, 0],
[R.Tensor((2, 3), dtype="int32"), R.Tensor((2, 3), dtype="int32")],
)
return gv0
def test_call_tir_inplace_all_new():
with pytest.raises(tvm.error.DiagnosticError):
@tvm.script.ir_module
class Input:
@T.prim_func(s_tir=True)
def func(A: T.Buffer((2, 3), "int32")):
T.evaluate(0)
@R.function
def foo(x: R.Tensor((2, 3), "int32")) -> R.Tensor((2, 3), "int32"):
R.func_attr({"relax.force_pure": True})
# cannot make the only output a fresh one
gv0 = R.call_tir_inplace(Input.func, x, -1, R.Tensor((2, 3), dtype="int32"))
return gv0
def test_inplace_mutation_with_tuple_argument_raises_error():
"""TIR PrimFuncs do not support Tuple arguments
The `R.call_tir_inplace` operator must receive an in-line tuple of
arguments, where each argument in the tuple may be expressed in
TIR. Here, `[[A]]` specifies a tuple of arguments, where the
first argument is itself a tuple. Since PrimFuncs do not support
Tuple arguments, this is invalid.
This is a regression test. In previous implementations, this
triggered a segfault rather than raising an exception.
"""
with pytest.raises(tvm.error.DiagnosticError):
@I.ir_module(s_tir=True)
class Module:
@R.function
def main(A: R.Tensor((16,), dtype="float32")) -> R.Tensor((16,), dtype="float32"):
cls = Module
gv1 = R.call_tir_inplace(
cls.multiply_by_two,
[[A]],
out_ty=R.Tensor((16,), dtype="float32"),
inplace_indices=[0],
)
return gv1
@T.prim_func(private=True, s_tir=True)
def multiply_by_two(A: T.Buffer((16,), "float32")):
for i in range(16):
A[i] = A[i] * T.float32(2)
def test_inplace_mutation_with_non_tensor_argument_raises_error():
"""In-place argument must be a tensor
The `R.call_tir_inplace` operator must receive an in-line tuple of
arguments, where each argument in the tuple may be expressed in
TIR. Here, the argument `A` is not a tensor.
This is a regression test. In previous implementations, this
triggered a segfault rather than raising an exception.
"""
with pytest.raises(tvm.error.DiagnosticError):
@I.ir_module(s_tir=True)
class Module:
@R.function
def main(A: R.Any):
gv1 = R.call_tir_inplace(
Module.multiply_by_two,
[A],
out_ty=R.Tensor((16,), dtype="float32"),
inplace_indices=[0],
)
return gv1
@T.prim_func(private=True, s_tir=True)
def multiply_by_two(A: T.Buffer((16,), "float32")):
for i in range(16):
A[i] = A[i] * T.float32(2)
def test_inplace_mutation_with_incompatible_tensor_shape_raises_error():
"""In-place argument must have compatible shape
The `R.call_tir_inplace` operator must receive an in-line tuple of
arguments, where the shape of each in-place argument is compatible
with the corresponding output. Here, the shape of argument `A` is
different than the output's shape (`[32]` as opposed to `[16]`).
"""
with pytest.raises(tvm.error.DiagnosticError):
@I.ir_module(s_tir=True)
class Module:
@R.function
def main(A: R.Tensor([32], dtype="float32")):
gv1 = R.call_tir_inplace(
Module.multiply_by_two,
[A],
out_ty=R.Tensor((16,), dtype="float32"),
inplace_indices=[0],
)
return gv1
@T.prim_func(private=True, s_tir=True)
def multiply_by_two(A: T.Buffer((16,), "float32")):
for i in range(16):
A[i] = A[i] * T.float32(2)
def test_inplace_mutation_with_incompatible_tensor_dtype_raises_error():
"""In-place argument must have compatible dtype
The `R.call_tir_inplace` operator must receive an in-line tuple of
arguments, where the shape of each in-place argument is compatible
with the corresponding output. Here, the dtype of argument `A` is
different than the output's dtype (`int32` as opposed to `float32`).
"""
with pytest.raises(tvm.error.DiagnosticError):
@I.ir_module(s_tir=True)
class Module:
@R.function
def main(A: R.Tensor([16], dtype="int32")):
gv1 = R.call_tir_inplace(
Module.multiply_by_two,
[A],
out_ty=R.Tensor((16,), dtype="float32"),
inplace_indices=[0],
)
return gv1
@T.prim_func(private=True, s_tir=True)
def multiply_by_two(A: T.Buffer((16,), "float32")):
for i in range(16):
A[i] = A[i] * T.float32(2)
if __name__ == "__main__":
tvm.testing.main()