Skip to content

[Target][LLVM] Fall back to unoptimized module on invalid post-opt IR#20025

Closed
hamzaqureshi5 wants to merge 1 commit into
apache:mainfrom
hamzaqureshi5:fix-avg-pool2d-llvm-codegen-crash
Closed

[Target][LLVM] Fall back to unoptimized module on invalid post-opt IR#20025
hamzaqureshi5 wants to merge 1 commit into
apache:mainfrom
hamzaqureshi5:fix-avg-pool2d-llvm-codegen-crash

Conversation

@hamzaqureshi5

Copy link
Copy Markdown
Contributor

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.

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +396 to +410
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);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 cbalint13 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM !
Thanks @hamzaqureshi5 !

@cbalint13

Copy link
Copy Markdown
Contributor

@hamzaqureshi5
Just please fix lint and lets make sure all CI is in green.

@cbalint13 cbalint13 self-assigned this Jul 17, 2026
@tqchen

tqchen commented Jul 18, 2026

Copy link
Copy Markdown
Member

i don't think this is the right fix unfortunately and adding retry would makes things more complicated

@tqchen tqchen closed this Jul 18, 2026
@cbalint13

Copy link
Copy Markdown
Contributor

i don't think this is the right fix unfortunately and adding retry would makes things more complicated

@tqchen , @hamzaqureshi5

  • At last, the warn message alone could be kept, informing the user about llvm opt-level (there is a dedicated argument for it) or upgrade/downgrade the llvm version.

  • Such bugs are very annoying, any minimal info can be very useful.

Thanks @hamzaqureshi5 for the time spent on this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants