Repository navigation
perf(rocprofiler-sdk): keep kernel replay snapshot staging across dispatches - #12610
Draft
amd-vkale wants to merge 3 commits into
Conversation
…patches Every replayed dispatch built its snapshot in fresh std::vector<char> buffers and freed them when the replay window closed. resize() zero-fills, and glibc serves blocks above its mmap threshold (32 MiB at most) with a fresh mapping, so each dispatch also first-touch faulted the whole tracked footprint and returned it to the OS afterwards. On the host alone that costs about 0.35 ms per MiB of footprint per dispatch: roughly 20 ms at 64 MiB and 375 ms at 1 GiB, before any copy happens. ROCr's hsa_memory_copy then locks (pins and GPU-maps) that pageable memory for every copy, which on fresh pages also means faulting them in. Staging storage now comes from a per-agent pool: a snapshot takes the best fitting retained buffer (at most twice the request) and allocates, without zero-filling, only when nothing fits; destroying the snapshot returns the storage. snap() frees retained storage it did not take, so what the pool holds between dispatches is bounded by the last snapshot's footprint. The block keeps data() and size(), so restore() and the snapshot tests are unchanged. This is the host-side counterpart of the device backing cache in #10916, without moving the snapshot into device memory. A GPU-free unit test covers reuse, the best-fit cap, per-agent pools, move semantics, and the trim in snap(). Co-authored-by: Vivek Kale <vivek.kale@amd.com> (cherry picked from commit 93fce3f)
…ings The performance assessment listed per-dispatch staging churn as an open problem; describe what the pool does now, with the host-only cost it removes. Correct how hsa_memory_copy treats pageable memory -- ROCr locks and unlocks the range for every copy rather than staging through driver buffers -- since that is what makes pinning the retained buffers the next experiment. Document the INFO phase timings, and update the host RAM limitation now that host copies outlive a single replay window. Co-authored-by: Vivek Kale <vivek.kale@amd.com> (cherry picked from commit e5fa013)
This was referenced Oct 1, 2026
Draft
1 task done
…into users/vkale/kr-perf-5-retained-staging--with-cursor-help Co-authored-by: Vivek Kale <vivek.kale@amd.com>
This was referenced Oct 6, 2026
cursor Bot
pushed a commit
that referenced
this pull request
Oct 16, 2026
…eplay integration Co-authored-by: Vivek Kale <vivek.kale@amd.com>
Contributor
|
This pull request has been inactive for 25 days and will be marked as stale. If you would like to keep this PR open, please:
This PR will be automatically closed in 5 days if no further activity occurs. |
Contributor
|
This pull request has been automatically closed due to inactivity (30 days with no updates). If you'd like to continue working on this, feel free to reopen the PR or create a new one. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Underlying Problem
The snapshot is a local of the replay window. Before this change, every replayed dispatch built it in fresh
std::vector<char>buffers, one per tracked region, and freed them all when the window closed. For a gigabyte-scale footprint that is expensive before a single byte is copied:resize()zero-fills the whole footprint, even thoughsnap()overwrites every byte straight away.Measured on the host alone, that cycle costs about 0.35 ms per MiB of footprint per dispatch: roughly 20 ms at 64 MiB, 94 ms at 256 MiB and 375 ms at 1 GiB, all before any copy. ROCr's
hsa_memory_copythen locks (pins and GPU-maps) that pageable memory for every copy, which on fresh pages also means faulting them in.The performance assessment on develop lists "The staging buffer is allocated and freed per dispatch" as an open problem, and proposes exactly this fix: a per-agent staging buffer retained across dispatches.
Notes on Bigger Picture
This is the host-side counterpart of the device backing cache in #10916, without moving the snapshot into device memory. The device-side work, with snapshots in GPU memory and blit-based restores, stays in #10916 with Mythreya Kuricheti. The two compose: on that design, host staging remains the fallback tier when GPU memory is short. With #12608, the saving shows in the "snapshot" phase of the INFO line.
Technical Details
Snapshot storage comes from a per-agent pool and goes back to it, and the pool never holds more than the last snapshot used.
flowchart LR S["snap(agent)"] --> A{"retained buffer with capacity in [size, 2*size]?"} A -- yes --> T["take it (best fit)"] A -- no --> N["allocate size bytes, no zero-fill"] T --> C["copy device to host"] N --> C C --> P["passes and restores use the snapshot"] P --> D["window closes, snapshot destroyed: storage returns to the agent's pool"] S -. "when capture ends" .-> TR["free retained storage this snap did not take"]Code
staging_buffer_t(memory_snapshot.hppL44-L83) is a move-only RAII owner of one region's host storage. It exposesacquire(agent, size),data()andsize().mem_block_t::host_copychanges fromstd::vector<char>to it. It keepsdata()andsize(), sorestore(), feat(rocprofiler-sdk): report kernel replay phase timings at INFO #12608's byte count and the existing snapshot tests are unchanged.Synchronizedmap from agent handle to a capacity-orderedstd::multimapof free buffers. It is deliberately leaked, so a buffer destroyed during static destruction still has a pool to return to.acquire()takes the best-fitting retained buffer, capped at twice the request. A small region cannot take a large buffer that a later, larger region would then have to allocate again. When nothing fits it allocates withnew (std::nothrow) char[size], which does not zero-fill. A failed allocation still reports memory pressure, sosnap()declines replay exactly as before.release()returns the storage to the owning agent's free list on destruction or move-assignment. If the free list cannot grow (bad_alloc), the storage is simply freed.snap()is a scope guard. When capture ends, successful or not, it frees whatever retained storage this snapshot did not take: buffers for regions that were freed or shrank. Between dispatches the pool therefore holds at most the last snapshot's footprint.retained_staging_bytes(agent)reports what the pool holds for an agent, for the tests.Behaviour changes
Docs and CHANGELOG (second commit)
hsa_memory_copytreats pageable memory. ROCr locks and unlocks the range for every copy, rather than staging through driver buffers. That is what makes pinning the now-retained buffers (hsa_amd_memory_lock) the next experiment, and a policy decision rather than a code change.using-kernel-replay.rstandkernel_replay_memory_snapshot.mdupdate the host-RAM limitation.__hip_cuid_skip (perf(rocprofiler-sdk): skip HIP's compilation-unit markers in the kernel replay snapshot #12609) and the INFO phase timings (feat(rocprofiler-sdk): report kernel replay phase timings at INFO #12608).Reviewer Guide
acquire()never hands out a buffer more than twice the request, and allocates without zero-filling.snap()copies over every byte before anything reads it.snap()trim runs on every exit (scope guard), including declined snapshots, so retained memory cannot grow past the last footprint.Out of scope: pinning retained buffers (documented as the next experiment), device-memory backing (#10916), and copying only what a kernel writes (the assessment's largest open lever).
Issue Tracking
JIRA ID: AIPROFSDK-68
Test Plan
kernel-replay-staging-test, registered intests/CMakeLists.txtL119-L135. Each test uses its own agent handle, so pool state does not leak between tests.acquire_sizes_storage_to_the_request: a fresh buffer is sized to the request and writable, and nothing is retained.destroyed_storage_is_reused_by_the_next_acquire: the next snapshot of the same footprint gets the very same storage back.reuse_is_best_fit_within_twice_the_request: a 40 KiB request does not take a 100 KiB buffer, but a 60 KiB request does.pools_are_per_agent: storage retained for one agent is not handed to another.moves_return_storage_exactly_once: move construction and assignment transfer ownership, and only the final owner returns the storage.snap_frees_retained_storage_it_does_not_reuse:snap()trims retained storage even when it declines the snapshot, here because no HSA runtime is live.unit.kernel_replay_snapshot.*, including the 8, 32 and 128 MiB snapshot bandwidth tests and the restore tests, which now run on retained storage. Also the kernel replay integration tests and the rocprofv3 kernel replay tests, in CI.ROCPROFILER_LOG_LEVEL=infoon a multi-dispatch replay run shows the "snapshot" phase drop after the first dispatch, because later dispatches reuse retained storage.Test Result
Submission Checklist
Related PRs