[Target][LLVM] Fall back to unoptimized module on invalid post-opt IR#20025
[Target][LLVM] Fall back to unoptimized module on invalid post-opt IR#20025hamzaqureshi5 wants to merge 1 commit into
Conversation
relax.build(target="llvm") hard-crashed during LLVM codegen verification
for avg_pool2d with input shape [N, C=4, H(even>=6), W=3], e.g. [1,4,6,3]:
InternalError: LLVM module verification failed:
Instruction does not dominate all uses!
%314 = shufflevector <4 x float> %287, <4 x float> %311, <2 x i32> <i32 2, i32 6>
%90 = shufflevector <2 x float> %314, <2 x float> %89, <4 x i32> <i32 0, 1, 2, 3>
Root cause is an LLVM optimizer bug, not TVM codegen. The TIR TVM lowers is
scalar and the IR TVM emits verifies clean (both llvm::verifyModule and
llvm-as accept it); the crash only appears at opt-level >= 2. Running stock
`opt -passes='default<O2>'` on TVM's valid IR reproduces the identical
"does not dominate all uses" abort, so LLVM's own vectorizer turns valid IR
into a structurally-invalid module (C=4 fills a 4-wide vector and W=3
misaligns the strided gather, steering the SLP/loop vectorizer into the bug).
Rather than hard-crash on a backend-optimizer bug, CodeGenLLVM::Finish() now
keeps a verified clone of the pre-optimization module and, if the optimized
module fails verification, warns and falls back to that known-good module.
The build then produces correct (unoptimized) code and never emits the broken
module. The pre-opt Verify() still throws, so genuine TVM codegen bugs remain
surfaced.
Adds a numerical regression test in test_vm_build.py covering the crashing
and neighbouring shapes.
There was a problem hiding this comment.
Code Review
This pull request introduces a fallback mechanism in the LLVM codegen to handle an upstream LLVM optimizer bug (issue #20015) where valid IR is miscompiled into an invalid module. It clones the module before optimization and falls back to this unoptimized version if post-optimization verification fails. A regression test is also added. The reviewer suggested optimizing this process by only cloning the module when the optimization level is greater than zero to avoid unnecessary overhead at O0.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| std::unique_ptr<llvm::Module> pre_opt_module = llvm::CloneModule(*module_); | ||
|
|
||
| this->Optimize(); | ||
| this->Verify(); | ||
|
|
||
| std::string verify_errors_storage; | ||
| llvm::raw_string_ostream verify_errors(verify_errors_storage); | ||
| if (llvm::verifyModule(*module_, &verify_errors)) { | ||
| LOG(WARNING) << "LLVM " << (TVM_LLVM_VERSION / 10) << "." << (TVM_LLVM_VERSION % 10) | ||
| << " optimization produced an invalid module; this is a known LLVM " | ||
| "optimizer bug on valid IR (see TVM issue #20015). Falling back to the " | ||
| "unoptimized module so the build can proceed with correct results.\n" | ||
| << "Verifier errors:\n" | ||
| << verify_errors.str(); | ||
| module_ = std::move(pre_opt_module); | ||
| } |
There was a problem hiding this comment.
Unconditionally cloning the LLVM module on every compilation can introduce unnecessary overhead, especially when optimization is disabled (at O0). Since the optimizer bug only occurs at higher optimization levels, we can avoid this cloning overhead when the optimization level is None (0). If verification fails at O0 (where we didn't clone), we can throw a proper error instead of falling back.
std::unique_ptr<llvm::Module> pre_opt_module;
if (static_cast<int>(llvm_target_->GetOptLevel()) > 0) {
pre_opt_module = llvm::CloneModule(*module_);
}
this->Optimize();
std::string verify_errors_storage;
llvm::raw_string_ostream verify_errors(verify_errors_storage);
if (llvm::verifyModule(*module_, &verify_errors)) {
if (pre_opt_module) {
LOG(WARNING) << "LLVM " << (TVM_LLVM_VERSION / 10) << "." << (TVM_LLVM_VERSION % 10)
<< " optimization produced an invalid module; this is a known LLVM "
"optimizer bug on valid IR (see TVM issue #20015). Falling back to the "
"unoptimized module so the build can proceed with correct results.\n"
<< "Verifier errors:\n"
<< verify_errors.str();
module_ = std::move(pre_opt_module);
} else {
TVM_FFI_THROW(InternalError) << "LLVM module verification failed after optimization: \n"
<< verify_errors.str();
}
}
cbalint13
left a comment
There was a problem hiding this comment.
LGTM !
Thanks @hamzaqureshi5 !
|
@hamzaqureshi5 |
|
i don't think this is the right fix unfortunately and adding retry would makes things more complicated |
Thanks @hamzaqureshi5 for the time spent on this issue. |
relax.build(target="llvm") hard-crashed during LLVM codegen verification for avg_pool2d with input shape [N, C=4, H(even>=6), W=3], e.g. [1,4,6,3]:
Root cause is an LLVM optimizer bug, not TVM codegen. The TIR TVM lowers is scalar and the IR TVM emits verifies clean (both llvm::verifyModule and llvm-as accept it); the crash only appears at opt-level >= 2. Running stock
opt -passes='default<O2>'on TVM's valid IR reproduces the identical "does not dominate all uses" abort, so LLVM's own vectorizer turns valid IR into a structurally-invalid module (C=4 fills a 4-wide vector and W=3 misaligns the strided gather, steering the SLP/loop vectorizer into the bug).Rather than hard-crash on a backend-optimizer bug, CodeGenLLVM::Finish() now keeps a verified clone of the pre-optimization module and, if the optimized module fails verification, warns and falls back to that known-good module. The build then produces correct (unoptimized) code and never emits the broken module. The pre-opt Verify() still throws, so genuine TVM codegen bugs remain surfaced.
Adds a numerical regression test in test_vm_build.py covering the crashing and neighbouring shapes.