Repository navigation
fix(rocr): do not fail queue creation when the scratch reservation fails - #14949
shwetagkhatri wants to merge 1 commit into
Conversation
✅ All Policy Checks Passed
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
cfreeamd
left a comment
There was a problem hiding this comment.
🤖 AI-assisted pre-review (for @cfreeamd)
The GpuAgent::ReserveScratch() change looks functionally correct. Verified against develop source that AcquireQueueMainScratch performs full on-demand scratch allocation (cache → fresh scratch_pool_.alloc/MakeMemoryResident → trim), using the reserved block only as a late retry tier — so dispatch is unaffected when nothing is reserved. Cleanup is sound: on map failure scratch_cache_.reserve() is never called (no accounting corruption) and scratch_pool_.free() correctly undoes the alloc. The sole caller in amd_aql_queue.cpp is wrapped by a QueueGuard scope guard so removing the throw does not break queue teardown.
[Required] Title format: rocr: do not fail queue creation when the scratch reservation fails does not follow the required type(component): convention. ROCm squash-merges, so this becomes the permanent commit title. Please retitle to: fix(rocr): do not fail queue creation when the scratch reservation fails
[Question] Missing PR description: This flips queue-creation semantics from fail-hard to succeed-silently. A brief description explaining the scenario where the reservation legitimately fails (e.g. memory pressure / fragmentation) and any perf measurement would help reviewers verify the trade-off.
a558b3f to
dfb7461
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Failed scratch VA allocation can leave a queue with an empty SmallHeap, and a later unguarded high_split() call dereferences the empty set, causing undefined behavior instead of reporting out-of-resources.
1 open issue
What changed in this PR
Makes ROCr’s optional scratch reservation nonfatal so reservation failures do not abort queue creation or escape through unrelated APIs, extending the empty-pool handling introduced in #10509.
Changes:
- Replaces allocation and mapping failure exceptions with optional diagnostics and early returns.
- Preserves cleanup when mapping a reserved block fails.
- Documents that scratch reservation is an optimization, not a queue-creation requirement.
| File | Description |
|---|---|
projects/rocr-runtime/runtime/hsa-runtime/core/runtime/amd_gpu_agent.cpp |
Allows ReserveScratch() to fail without throwing, with flag-controlled diagnostics. |
Review effort: Balanced
6dcc1ac to
043ebab
Compare
The scratch reservation is an optional pre-bind that a later scratch request can fall back on, so queue creation does not depend on it. A reservation that cannot be satisfied now emits a diagnostic and returns instead of throwing out of queue creation. The diagnostic is gated on the existing queue-fault message flag and stays present in release builds. Also define the empty case of the scratch pool so that querying its high water mark is safe before any memory is bound, and skip that comparison when the allocation returned null. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
043ebab to
28300aa
Compare

JIRA ID : ROCM-31873
GpuAgent::ReserveScratch() pre-binds an optional scratch block that
AcquireQueueMainScratch can later fall back on. When that reservation could not be
satisfied it threw HSA_STATUS_ERROR_OUT_OF_RESOURCES. That exception unwinds the
AqlQueue constructor and surfaces out of whichever API call happens to be the first
to materialize an agent's utility queue - in the reported case hsa_executable_freeze,
which has nothing to do with scratch.
The reservation is an optimization, not a prerequisite. AcquireQueueMainScratch runs
a three stage retry - check the cache, trim unused blocks, trim all blocks - and only
reaches scratch_cache_.use_reserved() as its third attempt. With nothing reserved
that attempt does not help and the function continues to its occupancy-reduction
path. It returns a null base rather than throwing. So a missing reservation does not
break queue creation.
To be clear about what still fails and where: a dispatch that genuinely needs scratch
and cannot obtain it is not silently accepted. AqlQueue::DynamicQueueEventsHandler
turns a null main and alt scratch base into HSA_STATUS_ERROR_OUT_OF_RESOURCES and
delivers it through the queue's error callback. That is the right place and the right
error. What this change removes is the failure of queue CREATION, and the bare 4104
escaping from an unrelated API.
This change replaces both throws with a diagnostic and continues without a reserved
block. The diagnostic is written with fprintf(stderr, ...) behind the existing
enable_queue_fault_message() flag, not debug_print, which is a no-op under NDEBUG and
so would not be visible in a release build.
Precedent. PR #10509, "fix(rocr): validate scratch allocation under constrained
address spaces", merged 2026-09-08, landed commit 626752b whose own subject is
"fix(rocr): survive scratch allocation failure under constrained address spaces".
That change made InitScratchPool tolerate exactly this condition and install an empty
pool instead of asserting. ReserveScratch was not brought along and kept throwing,
which is what this change completes.
How it was hit
rocrtstNeg.Queue_Validation_InvalidDimension and
rocrtstNeg.Queue_Validation_InvalidKernelObject fail under Valgrind on an 8 GPU
gfx950 host. Each agent reserves a 32 GiB scratch VA aperture, 256 GiB in total.
Valgrind's usable address space is much smaller, so InitScratchPool succeeds for the
first two agents and returns HSA_STATUS_ERROR_OUT_OF_RESOURCES for the remaining six,
leaving those agents with an empty scratch pool. Measured on an instrumented build:
the 140 MiB reservation then fails against a pool whose base is null and whose size
is zero, while 287 GiB of device memory is free. Without Valgrind, on the same binary
and host, all eight apertures are obtained and the test passes.
The Valgrind "definitely lost" report named in the ticket is a consequence of this.
The test aborts on the 4104, hsa_shut_down never runs, and the test fixture's own
teardown never runs either. Of the four loss records, two are signals created by
hsa_init that only hsa_shut_down frees, one is a signal allocated by the test itself
in its SetUp, and one is the pair of signals in the queue constructor. With this
change the same command reports 0 bytes in 0 blocks and 0 Memcheck errors, which is
what rules out an independent leak.
A single GPU gfx950 host does not reproduce, because one 32 GiB aperture fits.
Validation
Host: 8 x gfx950, SPX/NPS1, ROCm 10.3.0, valgrind 3.27.1. Both libraries are Release
builds of the same tree, differing only by this patch, confirmed by a binary string
differential before each run.
Regression, same-tree A/B, both hosts:
No test changes state between the arms on either host. Max_Reference_Count is
excluded only because it runs for well over 30 minutes.
Not covered: the Valgrind A/B above is gfx950 only. A separate functional A/B at the
current merge base was run on 8 x gfx942; it does not re-measure the Valgrind figures.
Rebase note: the validation above was run on this patch applied to develop at
f8b5dd4. The branch has been rebased twice since, onto 1e8c6de and then onto
f030609. Neither the Valgrind A/B nor the gfx950 regression table above has been
re-run against either base. A functional
A/B of rocrtstFunc.* minus the Interprocess and IPC groups was run at f030609 on
8 x gfx942 in the normal configuration and was 97 of 97 on both arms. The patch body was
byte-identical across the first rebase. It is NOT identical after the second: in
response to review the diagnostic moved from debug_print to fprintf behind the
queue-fault flag, the map-failure branch gained an explicit return, and the added
comment was corrected. A follow-up commit then guarded the empty-pool high-split read
that the no-reservation case exposes.
Across the first range, f8b5dd4..1e8c6de, exactly one commit touches the
patched file: a9c3b00, "fix(rocr): Select I$ and GL2 invalidation for gfx125x code
caches (#13924)". Its new branch in InvalidateCodeCaches is gated on GFX major 12 /
minor 5, and its else branch replaced a literal 1 with
PM4_ACQUIRE_MEM_GCR_CNTL_GLI_INV_ALL, which is defined as 1U. Neither is reachable as a
behaviour change on gfx950.
Across the second range, 1e8c6de..f030609, no commit touches the patched file.
Other rocr-runtime commits did land in that range and are linked into the same library,
so this is a narrower statement than a re-run would be.
🤖 Generated with Claude Code