[AMDGPU] Surface QuadrantsAssertionError after in-kernel assert (fix barrier hang) - #871
Conversation
Replace S_ENDPGM with __builtin_trap so peer wavefronts waiting on s_barrier do not hang the host, and publish assert state into pinned coherent host memory so the host can format QuadrantsAssertionError after hipErrorLaunchFailure (HIP context is dead afterward). Co-authored-by: Cursor <cursoragent@cursor.com>
Timeout is enforced by the subprocess.run(..., timeout=) path instead. Co-authored-by: Cursor <cursoragent@cursor.com>
Some ROCm/HSA configurations (notably inside Docker) turn the in-kernel __builtin_trap() into an uncatchable SIGABRT rather than returning a catchable hipErrorLaunchFailure, so the host never raises QuadrantsAssertionError. Treat a SIGABRT-killed child as a skip (an environment limitation) while still failing on the wall-clock timeout (barrier-hang regression) and on a wrong/absent exception. Upstream AMDGPU CI runs bare-metal, where the trap is catchable. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb26004957
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…inding Previously, once an in-kernel assert surfaced, AMDGPUFunction::operator() swallowed every subsequent hipErrorLaunchFailure (719) as success until the next materialize_runtime(). That masks dead-context errors if user code catches the QuadrantsAssertionError and keeps issuing GPU work (Codex Genesis-Embodied-AI#871 P1). Now 719 is suppressed only where throwing would std::terminate(): during teardown (g_amdgpu_device_in_teardown, opened in LlvmProgramImpl::pre_finalize() before the finalize() syncs, cleared on the next materialize) or while unwinding (std::uncaught_exceptions() > 0). Any other post-assert GPU call now raises a clear hard error instead of returning stale/uninitialized results. Adds test_amdgpu_assert_dead_context_reuse_raises to lock in the behavior. All three amdgpu assert tests pass on the MI308X. Co-authored-by: Cursor <cursoragent@cursor.com>
Addressed the P1 (post-assert launch-failure swallowing) in 376f1ffThanks @codex — good catch. The prior code set a "surfaced" flag on the first in-kernel assert and then swallowed every subsequent Fix: 719 is now suppressed only in the two situations where throwing would
Any other post-assert GPU call now raises a clear hard error instead of returning stale/uninitialized results: if (amdgpu_device_assert_already_surfaced()) {
if (amdgpu_device_in_teardown() || std::uncaught_exceptions() > 0) {
return; // swallow: throwing here would std::terminate()
}
QD_ERROR(
"AMDGPU device context is unusable after an in-kernel assertion failure; "
"re-initialize Quadrants in a fresh process before issuing further GPU work "
"(while calling {} ({}))",
name_, symbol_name_);
}Regression test: Validated on an MI308X (gfx942, ROCm 7.2.4): all three assert tests pass. |
|
To use Codex here, create a Codex account and connect to github. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 376f1ff612
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
… ABI guards Codex + Opus review fixes for the in-kernel assert trap path: - Fix multi-wave publish/trap race (Codex P1): set the device-side runtime->error_code gate LAST, after the pinned assert state is fully published and fenced. Previously the gate was set first, so a peer wave could observe error_code==1, skip the locked block, and trap the whole dispatch while this wave was still copying -- leaving the host to read an unpublished pinned buffer (error_code==0) and surface a generic launch failure instead of QuadrantsAssertionError. - Keep the offline cache safe (Codex P1): move assert_error_state_dev_ptr to the end of LLVMRuntime. Inserting it mid-struct shifted every later field; the default-on offline cache keys on the numeric version only, so an old cached kernel would misread the shifted fields. Appending at the tail preserves existing offsets. - Guard ABI drift (Opus): add static_asserts pinning both AmdgpuAssertErrorState and its hand-mirrored host view (AmdgpuAssertErrorStateHostView) to the same canonical layout via the shared constants. - Harden the fence patch (Opus): warn if amdgpu_system_mem_fence is not found during runtime-module patching instead of silently leaving the no-op host stub, which would break publish-before-trap ordering. - Replace non-ASCII em dashes in added lines (Opus) so the Check non-ASCII characters CI job passes. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks for the thorough review. Pushed c766253 addressing the concrete before-merge items, validated on MI308X (gfx942, ROCm 7.2.4). Addressed in code
Also folded in the two Codex P1s (replies on those threads):
Local validation (MI308X, gfx942, ROCm 7.2.4): clean build (the new Acknowledged, not code-changed
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c766253441
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // context is dead (`hipErrorLaunchFailure` on subsequent calls) - an accepted debug-mode | ||
| // limitation; the host surfaces QuadrantsAssertionError from the pinned state above. | ||
| amdgpu_system_mem_fence(); | ||
| __builtin_trap(); |
There was a problem hiding this comment.
Install assert state for bounds-check-only mode
When AMDGPU is initialized with check_out_of_bound=True and debug=False, the bounds-check pass still emits calls to this assertion path, but materialize_runtime() installs the pinned state and launch-failure hook only when config_.debug is true (llvm_runtime_executor.cpp:967). An out-of-bounds access therefore executes this trap with no state to translate the resulting 719: the next synchronization raises a generic HIP launch failure and leaves the context permanently unusable instead of reporting the bounds error. Allocate the state whenever assertions can be emitted, or restrict the destructive trap to the debug configuration.
Useful? React with 👍 / 👎.
| // Trap the whole dispatch so peer wavefronts waiting on s_barrier do not hang the host | ||
| // (the previous `S_ENDPGM` only killed the faulting wavefront). After the trap the HIP | ||
| // context is dead (`hipErrorLaunchFailure` on subsequent calls) - an accepted debug-mode | ||
| // limitation; the host surfaces QuadrantsAssertionError from the pinned state above. |
There was a problem hiding this comment.
Document the dead-context recovery requirement
On AMDGPU this changes the user-facing assertion flow so that catching QuadrantsAssertionError does not permit continued use or even reinitialization in the same process—the HIP context is permanently dead and recovery requires a fresh process. The assertions section in docs/source/user_guide/debug.md does not mention this backend-specific constraint, and this commit contains no docs/ update, so users following the documented catch-and-continue model can unexpectedly lose the whole GPU session. Add the recovery/lifetime caveat to the user-facing debug documentation.
AGENTS.md reference: AGENTS.md:L15-L22
Useful? React with 👍 / 👎.
|
Hi, could you create the pr from |
|
The reason it's created from AMD-Ecosystem is actually because that's the new company policy for creating PRs. I could redo it, what is the problem with it being from AMD-Ecosystem? |
|
Note: working, and commenting, at #895
|
|
Agent says: AMD GPU CI finding: the Heads-up from validating this PR's changes on the parallel [DONOTMERGE] PR #895 (it carries this branch's full diff plus a few review fixes). #895 is the first time the full Result: 35 failed, 3870 passed. Every one of the 35 failures is an AMDGPU debug-mode assert / bounds-check / error-raising test. Run: https://github.com/Genesis-Embodied-AI/quadrants/actions/runs/33201317112/job/98958973203 Mechanism. On this runner That produces two failure shapes:
Net: on this AMD CI environment the trap-based approach does not turn a barrier-hang into a catchable Transparency note on blast radius. Addressing an earlier Codex P1, I added an AMDGPU offline-cache-key revision so pre-change cached kernels (old Pausing further code changes on this until you have had a chance to weigh in on the trap mechanism. Happy to help however is useful - e.g. trying an alternative halt mechanism or reworking the debug-assert tests to run in isolated subprocesses - just say which direction you prefer. |
|
Thanks for running this end-to-end on #895 -- that's the data point we were missing, and it's decisive. I pulled the job log (run 33201317112) and confirmed the mechanism:
Conclusion: this isn't a test-harness gap -- it falsifies the PR's core assumption. Proposed direction -- move the halt off the hardware trap onto a cooperative, memory-signalled exit, keeping all the pinned-buffer/host-translation machinery we already built:
The crux to prove out is the workgroup-uniform exit around Either way it stays |
|
Correction to my comment above: I mis-stated the runner architecture. The AMD CI runner is not the same arch as my dev box. Per That's a different GPU architecture, so the If anything this strengthens the case for the cooperative, non-trap exit: it removes reliance on |


Summary
On AMDGPU, a failed in-kernel
assertpreviously emittedasm("S_ENDPGM"), which only terminates the faulting wavefront. Peer wavefronts still waiting ons_barrierthen deadlock, and the host hangs forever inhipStreamSynchronize. This PR replaces that with a dispatch-wide__builtin_trap()and translates the resulting fault back into a properQuadrantsAssertionErroron the host, preserving the debug-mode assertion contract without hanging.CUDA / CPU / Metal paths are unchanged. All new behavior is gated on
debug+Arch::amdgpu.Approach
__builtin_trap()faults the whole dispatch, so the host getshipErrorLaunchFailure(719) rather than a hang — but the context is then dead, so the usual device-side error-retrieval kernels can no longer run. To preserve the error message:materialize_runtime(debug + amdgpu only) wehipHostMalloc(...Coherent)anAmdgpuAssertErrorStateand publish its device-mapped address into the runtime. This mirrors the existingadstack_overflow_flag_dev_ptrprecedent and survives a device fault.quadrants_assert_format, the faulting wavefront (serialized under the existingerror_message_lock) copies the message template + arguments into the pinned buffer, issues a system-scope fence (amdgpu_system_mem_fence, patched to an LLVM seq_cst fence inllvm_context.cpp), storeserror_codelast, then__builtin_trap()s.AMDGPUFunction::operator()interceptshipErrorLaunchFailure, and a debug-only hook reads the pinned state and raisesQuadrantsAssertionError(a subclass ofAssertionError). Subsequent 719s on the now-dead context are ignored soProgramteardown does notterminate()from a destructor.Testing
Validated on an AMD Instinct MI308X (gfx942), ROCm 7.2.4, base
main:tests/python/test_assert.py::test_amdgpu_assert_raises— a failed assert raisesQuadrantsAssertionErrorwith the formatted message;isinstance(e, AssertionError)holds.tests/python/test_assert.py::test_amdgpu_assert_barrier_no_hang— one thread asserts while siblings hitblock.sync(); raises instead of hanging (the original bug).test_assert_*) unchanged.Both new tests run each case in an isolated child subprocess (the HIP context is dead after a trap; HIP is unsafe after
fork) with a wall-clock timeout that fails on the barrier-hang regression.CI notes
Upstream AMDGPU CI (
test_gpu.yml→test_linux_amdgpu,runs-on: amdgpu) runs bare-metal on the self-hosted runner — no container — which matches the environment where the trap returns a catchablehipErrorLaunchFailure. Some ROCm/HSA configs (notably inside Docker) instead escalate the trap to an uncatchableSIGABRT; the tests treat aSIGABRT-killed child aspytest.skip(environment limitation) while still failing on timeout or on a wrong/absent exception, so no runner goes spuriously red.Known limitations / possible follow-ups
AmdgpuAssertErrorStateHostView) inllvm_runtime_executor.cpp; astatic_asserton size/offsets would harden this.Program).Made with Cursor