This is an automated email from the ASF dual-hosted git repository.
tlopex 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 f6726b0245 [Relax][VM] Improve diagnostics for unlowered Relax
operators (#19899)
f6726b0245 is described below
commit f6726b024545e916ece1b6063bfbafc1e5f49065
Author: Yin Li <[email protected]>
AuthorDate: Fri Sep 11 01:19:44 2026 +0400
[Relax][VM] Improve diagnostics for unlowered Relax operators (#19899)
## What changed
This improves the diagnostics emitted when VM codegen sees a normal
Relax operator that was not lowered before codegen.
- Include actionable guidance in both bytecode (`CodeGenVM`) and
compiled (`CodeGenVMTIR`) paths.
- Print the full offending Relax call, including attributes, instead of
only the operator name.
- Add VM build coverage using an unsupported `relax.nn.conv2d_transpose`
layout so the diagnostic exposes the layout attributes.
## Why
Issue #19694 reports that unsupported `conv{1,2,3}d_transpose`
attributes can pass through legalization and later fail with an opaque
VM codegen error that only names the operator. Recent upstream work
added dilation support, while layout/out-layout passthrough remains
useful for external codegen flows. This keeps legalization behavior
unchanged but makes an eventual VM failure actionable by showing the
exact unlowered call and how to address it.
Helps #19694.
## Validation
- Merged current `main` (87 upstream commits at validation time) without
changing the three-file PR scope.
- Configured and built TVM from source on Apple Silicon with CMake 4.3.3
(`USE_LLVM=OFF`): `tvm_runtime` and `tvm_compiler` completed
successfully.
- Built and installed the pinned `tvm-ffi` submodule as a non-editable
wheel, per repository guidance.
- `pytest tests/python/relax/test_vm_build.py -k
test_vm_compile_unlowered_operator_error -vv`: 2 passed (`bytecode` and
`compiled`). The diagnostic is raised before low-level LLVM code
generation, so the test is valid in this build configuration.
- Full pre-commit suite on all three changed files: ASF header, file
checks, Ruff, Ruff format, and clang-format passed.
- Python byte compilation and `git diff upstream/main --check` passed.
The previous branch CI was also green on Windows, macOS, and lint before
the latest-main merge.
## AI assistance
AI tooling assisted with validation and branch maintenance. I reviewed
the final diagnostic and test behavior and ran the source build,
targeted tests, and pre-commit checks listed above.
---------
Co-authored-by: Kevin-Li-2025 <[email protected]>
---
src/relax/backend/vm/codegen_vm.cc | 7 +++++--
src/relax/backend/vm/codegen_vm_tir.cc | 7 +++++--
tests/python/relax/test_vm_build.py | 24 ++++++++++++++++++++++++
3 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/src/relax/backend/vm/codegen_vm.cc
b/src/relax/backend/vm/codegen_vm.cc
index a7dd9a21a8..066469eac6 100644
--- a/src/relax/backend/vm/codegen_vm.cc
+++ b/src/relax/backend/vm/codegen_vm.cc
@@ -157,8 +157,11 @@ class CodeGenVM : public
ExprFunctor<Instruction::Arg(const Expr&)> {
} else {
// every "normal" operator is lowered to a global var in the IRModule.
The Attrs for those
// ops are handled in a pass when lowering them to TIR.
- TVM_FFI_THROW(InternalError) << "CodeGenVM cannot handle this
intrinsic now:\n"
- << call_node->op;
+ TVM_FFI_THROW(InternalError)
+ << "CodeGenVM cannot emit this Relax operator directly. "
+ << "Run the appropriate lowering pass, or route the operator to an
external "
+ << "codegen before VM codegen.\nOffending call:\n"
+ << call;
}
} else {
EmitNormalCall(call, dst_reg);
diff --git a/src/relax/backend/vm/codegen_vm_tir.cc
b/src/relax/backend/vm/codegen_vm_tir.cc
index 77057d31ed..b060f935df 100644
--- a/src/relax/backend/vm/codegen_vm_tir.cc
+++ b/src/relax/backend/vm/codegen_vm_tir.cc
@@ -252,8 +252,11 @@ class CodeGenVMTIR : public
ExprFunctor<ffi::Optional<Expr>(const Expr&)> {
} else {
// every "normal" operator is lowered to a global var in the IRModule.
The Attrs for those
// ops are handled in a pass when lowering them to TIR.
- TVM_FFI_THROW(InternalError) << "CodeGenVMTIR cannot handle this
intrinsic now:\n"
- << call_node->op;
+ TVM_FFI_THROW(InternalError)
+ << "CodeGenVMTIR cannot emit this Relax operator directly. "
+ << "Run the appropriate lowering pass, or route the operator to an
external "
+ << "codegen before VM codegen.\nOffending call:\n"
+ << call;
}
} else {
EmitNormalCall(call, dst_reg);
diff --git a/tests/python/relax/test_vm_build.py
b/tests/python/relax/test_vm_build.py
index ad8f94e8a5..d3537615ff 100644
--- a/tests/python/relax/test_vm_build.py
+++ b/tests/python/relax/test_vm_build.py
@@ -84,6 +84,30 @@ def test_vm_compile_without_target_arg(exec_mode):
tvm.testing.assert_allclose(inp2.numpy(), inp1.numpy(), rtol=1e-7,
atol=1e-7)
+def test_vm_compile_unlowered_operator_error(exec_mode):
+ @tvm.script.ir_module
+ class Conv2dTranspose:
+ @R.function
+ def main(
+ x: R.Tensor((1, 4, 4, 1), "float32"),
+ w: R.Tensor((1, 2, 3, 3), "float32"),
+ ):
+ gv = R.nn.conv2d_transpose(
+ x,
+ w,
+ data_layout="NHWC",
+ kernel_layout="IOHW",
+ )
+ return gv
+
+ mod = relax.transform.LegalizeOps()(Conv2dTranspose)
+ with pytest.raises(
+ tvm.error.InternalError,
+ match=r"(?s)Offending
call:.*(?:R|relax)\.nn\.conv2d_transpose.*data_layout.*NHWC",
+ ):
+ relax.build(mod, target="llvm", exec_mode=exec_mode)
+
+
def test_match_check(exec_mode):
@tvm.script.ir_module
class TestMatchCheck: