[Bug][Relax] FuseTIR crashes when FuseOps creates a private function named fused for nested tuple getitem[Bug]
- Dominant language
- Python
- Stars
- 13.7k
- Forks
- 4k
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 112
Description
### Expected behavior
The CPU target-specific Relax pipeline should compile a well-formed program that selects a tensor through a nested tuple boundary.
If FuseOps creates a private Relax function for tuple/getitem selection, FuseTIR should either lower it correctly or skip it if it is not a valid TIR fusion candidate. It should not abort because the
generated private function name is exactly fused.
### Actual behavior
The input module is well-formed. The generic default Relax pipeline builds and runs correctly.
However, the official LLVM target-default pipeline:
relax.get_default_pipeline(tvm.target.Target("llvm"))
fails in FuseTIR with:
Check failed: (func_info_.global_name != "fused") is false:
A pass-by-pass trace shows that FuseOps succeeds and the IR is still well-formed, but it creates a private Relax function named exactly fused:
@R.function(private=True)
def fused(...):
R.func_attr({"Primitive": True})
...
The next pass, FuseTIR, aborts when processing this generated function.
### Environment
OS: Linux x86_64
Python: 3.10.12
TVM version: 0.26.dev1
TVM commit: 5a8dae4d95c55c8fec9246a607a28c3ff54ffe05
Target: llvm
Relax VM exec_mode: bytecode
### Steps to reproduce
import numpy as np
import tvm
from tvm import relax
def build_module(tuple_depth):
shape = (2, 3)
dtype = "float32"
bb = relax.BlockBuilder()
x = relax.Var("x", relax.TensorType(shape, dtype))
with bb.function("main", params=[x]):
with bb.dataflow():
zero_scalar = bb.emit(relax.op.zeros((1, 1), dtype))
zero = bb.emit(relax.op.broadcast_to(zero_scalar, shape))
one_scalar = bb.emit(relax.op.ones((1, 1), dtype))
one = bb.emit(relax.op.broadcast_to(one_scalar, shape))
add_zero = bb.emit(relax.op.add(x, zero))
producer = bb.emit(relax.op.multiply(add_zero, one))
dead = bb.emit(zero)
if tuple_depth == 1:
tup = bb.emit(relax.Tuple([producer, dead]))
selected = bb.emit(relax.TupleGetItem(tup, 0))
else:
inner = bb.emit(relax.Tuple([producer, dead]))
outer = bb.emit(relax.Tuple([inner, dead]))
inner_selected = bb.emit(relax.TupleGetItem(outer, 0))
selected = bb.emit(relax.TupleGetItem(inner_selected, 0))
out = bb.emit(selected)
gv = bb.emit_output(out)
bb.emit_func_output(gv)
return bb.get()
def make_input():
data = np.arange(6, dtype="float32").reshape(2, 3)
return (np.sin(data * 0.11) + 0.25).astype("float32")
def run_build(mod, pipeline):
target = tvm.target.Target("llvm")
exe = relax.build(
mod,
target=target,
relax_pipeline=pipeline,
exec_mode="bytecode",
)
vm = relax.VirtualMachine(exe, tvm.cpu())
x_np = make_input()
actual = vm["main"](tvm.runtime.tensor(x_np, tvm.cpu())).numpy()
print("match:", np.allclose(actual, x_np, rtol=1e-5, atol=1e-5))
target = tvm.target.Target("llvm")
depth1 = build_module(tuple_depth=1)
depth2 = build_module(tuple_depth=2)
print("depth1 generic/default")
run_build(depth1, "default")
print("depth1 llvm target-default")
run_build(depth1, relax.get_default_pipeline(target))
print("depth2 generic/default")
run_build(depth2, "default")
print("depth2 llvm target-default")
# This fails locally in FuseTIR:
# Check failed: (func_info_.global_name != "fused") is false:
run_build(depth2, relax.get_default_pipeline(target))
Observed result:
depth1 generic/default
match: True
depth1 llvm target-default
match: True
depth2 generic/default
match: True
depth2 llvm target-default
tvm.error.InternalError:
Check failed: (func_info_.global_name != "fused") is false:
The depth-1 case is a control and passes. The depth-2 nested tuple/getitem case passes under generic/default but fails under the target-default pipeline.
This suggests a naming/protocol mismatch between FuseOps and FuseTIR: FuseOps may generate a private function whose global name is exactly fused, while FuseTIR rejects that name internally.
### Triage
- needs-triage
- type: bug
- relax
Contributor guide
No contributing guide indexed for this repository
Research direction
Run the supplied reproducer with the LLVM target-default pipeline and compare the depth-1 and depth-2 cases. Start by tracing the Relax FuseOps and FuseTIR passes around the generated private function named fused. Done means the nested tuple/getitem case no longer aborts and produces the expected output, while the depth-1 control case continues to pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 64/100