Skip to content

fix(hipblaslt): keep large pinned host buffers only while tests reuse them - #14608

Merged
tony-davis merged 7 commits into
developfrom
users/tony-davis/AIHPBLAS-5352-host-pool-release
Nov 6, 2026
Merged

tony-davis merged 7 commits into
developfrom
users/tony-davis/AIHPBLAS-5352-host-pool-release

Conversation

@tony-davis

@tony-davis tony-davis commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

JIRA ID : AIHPBLAS-5352

Motivation

hipblaslt-test, the hipBLASLt test client, keeps the host buffers that each test frees in a pool, so the next test can reuse them instead of allocating again. These buffers are pinned (allocated with hipHostMalloc, so the operating system can neither page them out nor reclaim them), and the pool kept every one until the process exited. On the gfx1250 hardware test host, which has about 499 GiB of memory, the 93 cases with 2^31- and 2^32-element sizes enabled in #13504 left about 123 GiB pinned in one process. With 4 CI runners each splitting the suite into 4 parallel test processes, the out-of-memory killer took the runners down on 10-23 and 10-27. Separately, a failed host allocation returns a null buffer, and the test then writes through it.

I don't think this stack guarantees the gfx1250 host never runs short. What it does is make the client hold much less pinned memory, give it back sooner, and skip a case with the amounts instead of getting killed, which is the part of the problem the client can fix. If pressure comes back, the 110 GiB container caps from #14569 keep a kill inside one job, and putting the lock back (#14982 removes it) is a one-file revert. So I'm not planning a long soak test before merging.

The pool comes from ROCm/hipBLASLt#1202, which reused allocations to speed up hipblaslt-bench across about 500,000 small matmuls. Reuse still matters for large buffers: on the test host below, hipHostMalloc takes about 0.18 s per GiB and hipHostFree about 0.06 s per GiB.

Technical Details

  • A pooled host buffer of 1 GiB or more (kHostPoolRetainLimit in d_vector.hpp) now stays cached only while consecutive tests reuse it. The pool records the test count when a buffer comes back, and HostPoolReleaseListener, a new hook that Google Test calls after every test, frees the large buffers that the test just ended did not use. A run of same-shape cases, such as the four transpose combinations (NN, NT, TN, TT) of one size, still shares one allocation.
  • When a test asks for a large buffer that no cached buffer fits, the pool first frees the large buffers that earlier tests left, then pins new memory. This keeps the peak near one test's footprint when the shape changes.
  • A host buffer no longer gets the pool's 20% extra capacity when the extra would take it to 1 GiB or more (host_pool_capacity()), so a buffer under the limit stays under it. The existing cutoff for skipping the extra is a quarter of device memory, so on gfx1250 nearly every host buffer got it.
  • HipHostBuffer::memcheck() reports a failed pinned allocation, and testing_matmul checks every host buffer after allocating, including the four temporary buffers a swizzled A or B is built in. A failure now skips the test with the requested size ("could not allocate N GiB of pinned host memory").
  • A pooled host buffer under 1 GiB is released once 100 consecutive tests have not reused it (kHostPoolSmallIdleTests). The pool reuses a buffer only for requests between half its size and its size, so without this a run of shrinking requests kept every earlier buffer pinned.
  • In hipblaslt-bench, a failed host allocation now exits with a failure, as a failed device allocation already did, instead of reporting success.
  • Device memory behaves as before. hipblaslt-bench does not install the listener, so it never frees large buffers early; it does see the 20% change for host buffers of 1 GiB or more. The hipBLASLt library itself is unchanged.

This is the first of four stacked PRs for AIHPBLAS-5352. #14609 makes the client's host-memory query respect container memory limits, #14610 runs the memory preflight for every matmul case, and #14982 drops the host-wide lock in the gfx1250 workflow.

Test Plan

  • HostMemory_pre_checkin (new, clients/tests/src/host_memory_gtest.cpp, 6 tests; the _pre_checkin suffix puts it in the standard tier the PR checks run). The tests check that a large buffer survives one test boundary and is freed after the next, that a same-shape test reuses it (what the first test wrote is still there), that a large miss frees two buffers earlier tests left, that a 1 GiB request gets exactly 1 GiB, that a small buffer is released after 100 tests that do not reuse it (and kept when one does), and that a failed allocation leaves a null buffer whose size is reported. A 0.9 GiB request gets exactly 0.9 GiB (the old rule gave it 1.08 GiB, over the limit). The tests skip, naming the buffer, on a host that cannot pin that much, and the reuse test skips on a Windows host where the pool frees its idle buffers before every request. A temporary variant without the small-buffer aging failed the new test.
  • To confirm the tests can fail, I built two temporary variants of the pool. One undid four behaviors at once (no release at the end of a test, no release on a miss, the 20% extra restored, memcheck() always succeeding), and it failed 4 of the 5 tests. The other freed large buffers whether or not a test used them, and it failed 3. Every test failed under at least one variant.
  • The 82 large-memory cases that apply to gfx90a (the five name patterns from ci(gfx1250): run the large-memory hipBLASLt tests one job at a time #14545), and the smoke and quick tiers, on develop and on this branch, for wall time and peak resident memory.
  • Handle leaks: fix(hipblaslt): release matmul descriptors on early exits #14714 and fix(hipblaslt): release matmul timing events on early exits #14700 on develop give the matrix layouts, matmul descriptors and HIP events owners, so an early return no longer leaks them. To check that with this stack's new early returns, a preloaded library counted creates and destroys of all three, on the full stack, over 1,892 matmul cases, once normally and once under a 1 MiB host limit so that most cases skip at the preflight.
  • Adjacent: the FastCheck_pre_checkin and FastCheckDevice_pre_checkin suites, including memory_shortfall_reclaims_idle_buffers, which uses the pool's existing ReleaseCached(). hipblaslt-bench was built, because it compiles the non-gtest form of the new check, but not run.

Test Result

One MI210 (gfx90a) on a shared host with 2.2 TB of memory. This branch's clients were built against the 10-25 TheRock nightly hipBLASLt gfx90a device library. Each pair of runs went back to back, but the host was busy (load average near 300), so the times carry some noise.

Measurement develop this PR
82 large-memory cases, wall time 316.9 s 313.8 s
82 large-memory cases, peak resident memory 81.9 GiB 49.2 GiB
82 large-memory cases, mean resident memory 66.9 GiB 16.6 GiB
Resident memory after the last large case 74 GiB 2 GiB
smoke + quick (9,171 tests), two runs 66.6 s, 62.0 s 63.6 s, 64.1 s
  • Large-memory cases: 82 of 82 passed on both. smoke + quick: 9,169 passed and 2 skipped (GroupedMatmul.smoke_Fp16NN and smoke_Bf16NN, which skip on this GPU) on both.
  • HostMemory_pre_checkin, FastCheck_pre_checkin and FastCheckDevice_pre_checkin: 64 of 64 passed.
  • A first version that freed every large buffer at the end of each test took 449.6 s for the large cases against 318.2 s on develop, which is why the pool keeps a buffer while the next test reuses it.
  • Handle leaks: 0 layouts, descriptors or events left, both normally (13,580 created) and with 409 cases skipped at the preflight (11,235 created).
  • With the small-buffer aging, smoke + quick passed 9,225 tests in 59.3 s with peak resident memory 2.18 GiB.
  • gfx1250: this PR alone passed the hardware workflow's hipBLASLt tests in 40934683157, and Archana dispatched the full stack in 41582794047, which passed hipBLASLt twice with no kills and no memory skips. Her run on the rebased stack, 41717825380, was clean too. All three kept the host-wide lock; ci(gfx1250): drop the host-wide lock on the large hipBLASLt tests #14982 covers the run without it.
  • Automated GitHub checks are pending.

Submission Checklist

Risk level

Medium (3/5). Every hipblaslt-test case passes through the new listener and the host pool, so a mistake here would affect the whole suite. The hipBLASLt library and device behavior are unchanged; only the test and bench clients change. The bench sees the 20% change near 1 GiB, and now exits with a failure when a host allocation fails.

Related

Device / Architecture Coverage

Host-side test-client code only, with no kernel, library or kernel-selection change, so it does not depend on the GPU architecture and the standard PR checks are enough. Measured on gfx90a.

Flags / Guardrails

None. The 1 GiB threshold is the constant kHostPoolRetainLimit.

@therock-pr-bot

therock-pr-bot Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —
🤖 therock-pr-bot ✅ Pass —

🎉 All checks passed! This PR is ready for review.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

@therock-pr-bot

Copy link
Copy Markdown

🎉 All checks passed! This PR is ready for review.

@codecov

codecov Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #14608      +/-   ##
===========================================
+ Coverage    77.60%   77.65%   +0.04%     
===========================================
  Files         2903     2905       +2     
  Lines       490304   490744     +440     
  Branches     74249    74315      +66     
===========================================
+ Hits        380492   381039     +547     
+ Misses       84781    84684      -97     
+ Partials     25031    25021      -10     
Flag Coverage Δ *Carryforward flag
TensileLite-CPP 53.94% <ø> (ø) Carriedforward from 7be597e
TensileLite-Unit 84.37% <ø> (ø) Carriedforward from 7be597e
hipBLAS 90.62% <ø> (ø) Carriedforward from 7be597e
hipBLASLt 70.05% <ø> (+1.08%) ⬆️
hipCUB 82.68% <ø> (ø) Carriedforward from 7be597e
hipDNN 87.25% <ø> (ø) Carriedforward from 7be597e
hipFFT 42.57% <ø> (ø) Carriedforward from 7be597e
hipRAND 76.12% <ø> (ø) Carriedforward from 7be597e
hipSOLVER 69.67% <ø> (ø) Carriedforward from 7be597e
hipSPARSE 87.03% <ø> (ø) Carriedforward from 7be597e
rocBLAS 69.20% <ø> (ø) Carriedforward from 7be597e
rocFFT 46.49% <ø> (ø) Carriedforward from 7be597e
rocRAND 57.32% <ø> (ø) Carriedforward from 7be597e
rocSOLVER 76.74% <ø> (ø) Carriedforward from 7be597e
rocSPARSE 83.54% <ø> (ø) Carriedforward from 7be597e
rocThrust 93.15% <ø> (ø) Carriedforward from 7be597e

*This pull request uses carry forward flags. Click here to find out more.
see 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@tony-davis
tony-davis force-pushed the users/tony-davis/AIHPBLAS-5352-host-pool-release branch from 1e0e740 to 808000f Compare October 29, 2026 15:23
@tony-davis
tony-davis marked this pull request as ready for review October 29, 2026 18:51
@tony-davis
tony-davis requested review from a team as code owners October 29, 2026 18:51

@Alex-Vasile Alex-Vasile 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.

Approving CmakeLists changes

@newling newling 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.

Thanks for these PRs @tony-davis!

Overall, I’m happy with the direction of this stack. It addresses an immediate issue, and the agent review requests are small, local fixes and improvements.

Agent review: Review of the host-memory PR stack. The accompanying branches contain proposed fixes for the zero-budget handling and environment restoration.

I’m still getting familiar with the gfx1250 CI setup. I assume the workflow is configured to run automatically for eligible PRs. Just wondering if we need to do some sort of manual validation before landing.

Separately, I’d like us to revisit the complexity of host-memory allocation and caching in this part of the client, including where pinning and buffer reuse are worthwhile. That is follow-up work, not something I’m asking you to address in this stack.

@archana-ramalingam archana-ramalingam 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.

Thanks @tony-davis for this fix!
Approved with minor non-blocking follow-up toward AIHPBLAS-5352 (4 concurrent jobs, no OOM; a shortage skips a case instead of killing a shard), from the gfx1250 cap kills:

  • Measured on a gfx1250 host before this stack (10-28, 4 shards without the 2^31/2^32 tests): shard peaks 25/25/31/53 GiB, 134 GiB if they align (cap 110). The large tests in one process held 122.6 GiB until exit.
  • Their test positions align: on 11-02, without this stack, 9 of 12 shards were killed at the cap (41512870903, 41517092105, 41519929996), all at test 15,619–15,975 of ~17,470, where the 4 shards, in step, run their biggest in-shard cases (fast_check_large 16385×16383×4097 f32, complex 4096×2187×2653).
  • In that probe the 4 shards together reached only ~69 GiB (host sampled every 10 s), so short spikes or CI-only usage make up the gap to 110. Shared memory was 334 of 393 GiB at the full-suite peak, likely the pinned host buffers, and the cap has no swap (relevant to @newling's pinning follow-up).

Buffers ≥ 2x a request stay in the pool (fix 1, below 1 GiB).
A miss skips them (capacity() < 2 * bytes) and evicts only the largest too-small one (#L351-L353), so they stay until a later request fits them or a shortfall clears the pool. EndTest (#L259) could age them out with the ≥ 1 GiB rule (restored_test < m_test).

gfx1250 validation (on @newling's manual-validation question):

  • This PR alone passed gfx1250 once: 40934683157 (10-30, same pool code as the head): 4 shards (17,404–17,407 passed each) and the large groups, no kill. One run proves little, since pre-stack kills came in bursts.
  • #14609/#14610 never triggered the workflow, so I dispatched it on users/tony-davis/AIHPBLAS-5352-memory-preflight (the full stack): 41582794047 passed hipBLASLt twice (attempts 1 and 2, 12–13 min each): 4 shards (17,421–17,427 passed each) and all 6 large groups, no kill and no memory skips (no [ MEMORY ] list). A few more runs would make it convincing.
  • A gfx1250 counterpart to your large-case table: the 122.6 GiB above vs your 82 gfx90a cases at 81.9 → 49.2 GiB with this PR. Rerunning the 93 in one process with the stack would show whether the large groups can merge.
  • For per-shard peaks, /usr/bin/time -v needs apt-get install time in the CI image.

Peaks with the stack, and the shared-headroom preflight, are on #14610.

@tony-davis
tony-davis force-pushed the users/tony-davis/AIHPBLAS-5352-host-pool-release branch from e738f61 to 64e1afa Compare November 3, 2026 16:28
Copilot AI balanced review requested due to automatic review settings November 3, 2026 16:28
@tony-davis

tony-davis commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks @newling and @archana-ramalingam ! I took all of it, and the stack is pushed:

On gfx1250: Archana, thanks for dispatching 41582794047! I think that's our best evidence so far (two clean passes of the full stack, with the large phase still split out). I agree with you both that removing the lock should be its own PR, run with HIPBLASLT_TEST_FAIL_ON_MEMORY_SKIP=1 and per-shard peaks over a few days, since kills come in bursts.

James, agreed on revisiting allocation and caching as follow-up. I filed AIHPBLAS-5451 for it.

Copilot AI 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.

🟡 Changes recommended

Unresolved allocation-handling issues can cause Windows test failures, falsely successful benchmark exits, and leaked HIP resources.

3 open issues
What changed in this PR

This PR reduces pinned host-memory retention in hipBLASLt’s test client and detects failed host allocations before use. It is the first step in the memory-safety stack; container-aware limits and broader memory preflight checks follow separately. The hipBLASLt library is unchanged.

Changes:

  • Release unused large host buffers between tests, age out smaller buffers, and remove extra capacity from large host requests.
  • Check host allocations before matmul initialization and swizzling.
  • Add six regression tests and register them in the standard test tier.
File Description
projects/​hipblaslt/​TESTING.md Documents the host-memory regression suite.
projects/​hipblaslt/​clients/​tests/​src/​host_memory_gtest.cpp Tests retention, reuse, allocation sizing, and failure reporting.
projects/​hipblaslt/​clients/​tests/​src/​hipblaslt_gtest_main.cpp Registers the end-of-test pool cleanup listener.
projects/​hipblaslt/​clients/​tests/​src/​CMakeLists.txt Includes the new tests in the test executable.
projects/​hipblaslt/​clients/​common/​include/​testing_matmul.hpp Checks main and temporary host-buffer allocations.
projects/​hipblaslt/​clients/​common/​include/​hipBuffer.hpp Exposes allocation status and requested-size diagnostics.
projects/​hipblaslt/​clients/​common/​include/​hipblaslt_test.hpp Adds allocation-failure handling for tests and benchmarks.
projects/​hipblaslt/​clients/​common/​include/​d_vector.hpp Implements host-buffer aging, reclamation, and large-request sizing.

Review effort: Balanced

@tony-davis
tony-davis force-pushed the users/tony-davis/AIHPBLAS-5352-host-pool-release branch 2 times, most recently from 97a92ca to 5a9c5c6 Compare November 3, 2026 21:00
@tony-davis
tony-davis enabled stacked auto-merge (squash) November 3, 2026 22:06
@peymanr

peymanr commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

PR Pundit review

(Testing PR Pundit Code Review agent. Not all comments may be relevant. This is a one-time automated pass: please address what is useful, no need to request a re-review. Please reply to each point, whether it applies or not: we harvest these replies, and both kinds are enormously helpful for tuning the agent. Reach out to @peymanr for suggestions.)

# Finding Where
1 🟠 Large-host-buffer classification mismatch pins ~0.2 GiB extra: this PR split slack logic between get() and release projects/hipblaslt/clients/common/include/d_vector.hpp:365
2 🟠 Large-miss release frees buffers the current test still reuses, causing extra pin cycles: this PR added it projects/hipblaslt/clients/common/include/d_vector.hpp:338
3 🟠 Host OOM shows as gtest SKIPPED, not in hipBLASLt's '[ SKIPPED ] N tests' line: this PR used GTEST_SKIP projects/hipblaslt/clients/common/include/hipblaslt_test.hpp:97
4 🟠 HostMemory_pre_checkin fails instead of skipping on a memory-starved host: this PR asserts large pinned allocations succeed projects/hipblaslt/clients/tests/src/host_memory_gtest.cpp:27

1. 🟠 Should fix: Large-host-buffer classification mismatch pins ~0.2 GiB extra: this PR split slack logic between get() and release

projects/hipblaslt/clients/common/include/d_vector.hpp:365

get() skips the 20% slack only when the request is >= kHostPoolRetainLimit (or a quarter of device memory), but release_from_earlier_tests classifies by capacity(). So a pinned host request of about 853 MiB to 1 GiB gets about 1.2x capacity, above 1 GiB. It is then treated as a large buffer, released after one test that does not reuse it, as every buffer of 1 GiB or more is by design. The effect is up to about 0.2 GiB of extra pinned memory and a mismatch with TESTING.md and the comments ('large buffers get no 20% reuse slack'). A missed request in this band also skips the large-miss early release of buffers left by earlier tests. Deciding the slack with bytes * 1.2 >= kHostPoolRetainLimit (or capping capacity below the limit) would make the two classifications agree.

Who is affected: hipblaslt-test runs whose host operand buffers fall between about 853 MiB and 1 GiB, on GPUs with more than about 4 GiB of device memory.

Checked against the code by an independent pass.

2. 🟠 Should fix: Large-miss release frees buffers the current test still reuses, causing extra pin cycles: this PR added it

projects/hipblaslt/clients/common/include/d_vector.hpp:338

On a host-pool miss of at least kHostPoolRetainLimit (1 GiB), get() calls release_from_earlier_tests and frees every large idle buffer earlier tests restored. That includes one a later Get in the same test would have reused (e.g. B/C/D keep the previous shape while A grows). That buffer is then pinned again with hipHostMalloc, an extra free/pin cycle of multi-GiB pinned memory. Peak pinned memory is not raised, because the buffers are freed before the new allocation. Large leftovers are also bounded: HostPoolReleaseListener::OnTestEnd drops them after any test that did not return them, so a following sub-1 GiB test holds them for at most that one test.

Who is affected: hipblaslt-test runs where consecutive tests share some multi-GiB host operands but one operand grows past the cached size.

Checked against the code by an independent pass.

3. 🟠 Should fix: Host OOM shows as gtest SKIPPED, not in hipBLASLt's '[ SKIPPED ] N tests' line: this PR used GTEST_SKIP

projects/hipblaslt/clients/common/include/hipblaslt_test.hpp:97

CHECK_HOST_ALLOCATION reports a failed pinned host allocation with GTEST_SKIP, while CHECK_DEVICE_ALLOCATION reports device OOM as SUCCEED() << LIMITED_MEMORY_STRING. The two OOM cases therefore use different channels. Host skips are not added to the custom skipped_tests counter of ConfigurableEventListener, but gtest's own printer still counts and lists them as [ SKIPPED ] with the 'could not allocate ... GiB of pinned host memory' reason. They are visible and distinguishable, just reported differently from device OOM. Unifying the two channels is optional consistency cleanup.

Who is affected: hipblaslt-test runs on hosts where a large pinned host allocation fails; the result is reported as a gtest skip rather than the custom limited-memory pass.

Checked against the code by an independent pass.

Related past reviews: #14025 on projects/hipblaslt/clients/common/include/testing_matmul.hpp, #14127 on projects/hipsparse/clients/include/hipsparse_local_dnvec.hpp, #5740 on projects/hipblaslt/clients/tests/src/hipblaslt_gtest_main.cpp, #9122 on projects/hipblaslt/clients/common/include/testing_matmul.hpp

4. 🟠 Should fix: HostMemory_pre_checkin fails instead of skipping on a memory-starved host: this PR asserts large pinned allocations succeed

projects/hipblaslt/clients/tests/src/host_memory_gtest.cpp:27

The new HostMemory_pre_checkin tests ASSERT_NE/ASSERT_EQ that pinned allocations of kHostPoolRetainLimit (1 GiB) and up succeed; the miss test holds about 2.1 GiB at once. On Linux, h_memory refuses any request larger than sysinfo freeram, which excludes page cache, so a busy shared host fails these tests instead of skipping them. The matmul tests in this PR skip such failures with CHECK_HOST_ALLOCATION. Consider GTEST_SKIP when the large Get returns null, for consistency.

Who is affected: Linux CI hosts running *pre_checkin* while other processes leave less than about 1-2 GiB of freeram.

Checked against the code by an independent pass.

Shared from a pilot of PR Pundit, an agentic code reviewer that builds and tests the change and reads the project's past reviews. Reviewed at 5a9c5c66; findings checked and posted by @peymanr; points already raised on this PR are left out.

@tony-davis

Copy link
Copy Markdown
Contributor Author

Thanks! Replies by number:

  1. Agreed, fixed in d914613. A new host_pool_capacity() gives no slack when the 20% would reach the 1 GiB limit, and both get() and the preflight estimate in fix(hipblaslt): run the memory preflight for every matmul case and list memory skips #14610 use it. A test pins a 0.9 GiB buffer and checks its capacity; the old rule gave it 1.08 GiB.
  2. Accurate, but deliberate. Freeing before pinning is what keeps peak pinned memory down, which is the point of this PR, and you're right that it doesn't raise the peak. We measured the large cases at the same wall time as develop with this policy, so I think the extra pin cycle is worth it. No change.
  3. fix(hipblaslt): run the memory preflight for every matmul case and list memory skips #14610 handles this: device OOM and host OOM both go through SKIP_FOR_MEMORY, and the run lists them under [ MEMORY ]. No change here.
  4. Agreed, fixed in d914613. The pool tests now skip, naming the buffer they could not pin. In a 1.5 GiB cgroup the miss test skips instead of failing.

@tony-davis
tony-davis force-pushed the users/tony-davis/AIHPBLAS-5352-host-pool-release branch 2 times, most recently from d914613 to 91a4f79 Compare November 5, 2026 16:17
tony-davis and others added 7 commits November 5, 2026 13:19
… them

hipblaslt-test kept every pinned host buffer a test returned to the
client memory pool until the process exited. On the gfx1250 hardware
test host the 2^31/2^32-size cases left about 123 GiB pinned in one
process, and with 16 test processes per host the out-of-memory killer
took the runners down.

A pooled host buffer of 1 GiB or more now stays cached only while
consecutive tests reuse it: HostPoolReleaseListener frees it after a
test that did not use it, and a large request the pool cannot supply
first frees what earlier tests left. Host requests of 1 GiB or more no
longer get the pool's 20% extra capacity. testing_matmul checks every
host buffer after allocating and skips the test with the requested
size instead of writing through a null pointer.

New HostMemory_pre_checkin tests cover each behavior.

JIRA ID : AIHPBLAS-5352

Co-authored-by: Cursor <cursoragent@cursor.com>
The Windows clients compile as C++17 (C++20 reaches the Linux build only
through hipblaslt-mxdatagen), so std::erase_if broke hipblaslt-bench and
hipblaslt-test-hostunit there. Use erase and remove_if, and include
<algorithm> directly.

JIRA ID : AIHPBLAS-5352

Co-authored-by: Cursor <cursoragent@cursor.com>
The four temporary HipHostBuffers that hold a swizzled A or B were used
without checking the allocation, so a failed pinned allocation still
reached swizzle_tensor with a null destination. Check each one with
CHECK_HOST_ALLOCATION, through a new single-buffer form of
host_allocation_failure().

JIRA ID : AIHPBLAS-5352

Co-authored-by: Cursor <cursoragent@cursor.com>
…not reuse

The pool reuses a buffer only for requests between half its size and its
size, and on a miss drops only the largest buffer that is too small, so a
run of shrinking requests kept every earlier buffer under 1 GiB pinned.
HostPoolReleaseListener now also releases such a buffer once 100
consecutive tests have not reused it (kHostPoolSmallIdleTests).

JIRA ID : AIHPBLAS-5352

Co-authored-by: Cursor <cursoragent@cursor.com>
… the reuse test where the pool cannot reuse

hipblaslt-bench reported success after a failed host allocation, since
CHECK_HOST_ALLOCATION returned from the void case. It now exits with a
failure, as CHECK_DEVICE_ALLOCATION does.

On a Windows host with at most 128 GiB, the pool frees its idle buffers
before every request, so the reuse test could not pass there.
memory_pool::FreesBeforeEachGet() holds that condition for get() and the
test, which skips on such a host.

Reported by Copilot.

JIRA ID : AIHPBLAS-5352

Co-authored-by: Cursor <cursoragent@cursor.com>
…skip pool tests that cannot pin

get() decided the 20% slack by the request, but the release rules go by
the buffer's capacity, so a request between about 853 MiB and 1 GiB got
a buffer over 1 GiB and was treated as large. host_pool_capacity() now
gives no slack when the extra would reach kHostPoolRetainLimit, and
get() uses it.

The pool tests asserted that pins of 1 GiB and more succeed, so a host
without that much to spare failed them. They now skip, naming the buffer
they could not pin.

Reported by PR Pundit.

JIRA ID : AIHPBLAS-5352

Co-authored-by: Cursor <cursoragent@cursor.com>
windows.h defines near as an empty macro, so the Windows build of
host_memory_gtest.cpp saw "const size_t = ..." and failed. Call it
request.

JIRA ID : AIHPBLAS-5352

Co-authored-by: Cursor <cursoragent@cursor.com>
@tony-davis
tony-davis force-pushed the users/tony-davis/AIHPBLAS-5352-host-pool-release branch from 91a4f79 to 2594eb1 Compare November 5, 2026 18:20
@tony-davis
tony-davis merged commit 0ecc182 into develop Nov 6, 2026
136 of 138 checks passed
@tony-davis
tony-davis deleted the users/tony-davis/AIHPBLAS-5352-host-pool-release branch November 6, 2026 06:10
tony-davis added a commit that referenced this pull request Nov 6, 2026
…ainer limits (#14609)

JIRA ID : AIHPBLAS-5352

## Motivation

Before `hipblaslt-test`, the hipBLASLt test client, pins a host buffer,
it checks that enough host memory is free, so that a shortage ends in a
skipped test rather than a killed process. It asked two different
questions to do that: `h_memory` read `freeram` from `sysinfo()`, and
the `fast_check` memory preflight read `MemAvailable` from
`/proc/meminfo`. Neither looks at the memory limit of a Linux control
group (cgroup), the kernel feature that containers and CI runners use to
cap a job's memory, and inside a container both report the whole host. A
test inside a container with a memory limit therefore sees plenty of
memory, pins past the limit, and the kernel kills the process.

On this PR's test host, a large `stress` case inside an 8 GiB limit is
killed within 4 seconds on develop, and also with only the previous PR
in this stack.

## Technical Details

- New `available_host_memory_bytes()` in
`clients/common/include/host_memory.hpp` and `src/host_memory.cpp`. On
Linux it returns the smaller of `MemAvailable` and the room left under
each cgroup memory limit, from the process's own cgroup (read from
`/proc/self/cgroup`) up to the root of the hierarchy. It reads cgroup v2
(`memory.max` minus `memory.current`) and v1 (`memory.limit_in_bytes`
minus `memory.usage_in_bytes`). Page cache the kernel can drop does not
count as used: `active_file` and `inactive_file` in `memory.stat`, less
dirty and writeback pages. A removed cgroup, which `/proc/self/cgroup`
lists with a ` (deleted)` suffix, still has its limit applied.
- The cgroup's path is translated through its mount in
`/proc/self/mountinfo` (the cgroup2 mount, or the v1 mount that carries
the memory controller, such as `cpu,memory`), and the walk never goes
above that mount. Without mountinfo it falls back to `/sys/fs/cgroup`. A
container may list its cgroup as `/` or by the host's path, which does
not exist inside the container; the walk always checks the top it sees,
which is where the container's own limit is.
- `/proc/meminfo` and the cgroups each cap the answer on their own, so a
full container reports 0 even when `/proc/meminfo` cannot be read.
- `h_memory` (in `d_vector.hpp`) and `fast_check_memory_shortfall()`
both call it on Linux. For `h_memory` this also changes the base value
from `freeram` to `MemAvailable`, which counts reclaimable page cache,
as the preflight already did. On this host `freeram` was 181 GiB and
`MemAvailable` about 2,070 GiB, because most memory held page cache.
When the query has no answer at all, `h_memory` falls back to `freeram`,
as before. Inside an 8 GiB cgroup holding 6 GiB of page cache, active or
inactive, a 6 GiB `hipHostMalloc` succeeded and the kernel reclaimed the
cache, so counting the cache as available matches what pinning can
actually get.
- The managed-memory (`use_HMM`) branch of `d_memory` uses the same
check, as it did with `freeram`. In these tests a managed buffer is
first written on the host, so its pages are charged to the cgroup until
they migrate, and the host headroom is a conservative bound. No HMM case
(they are in the `full` tier) has been run under a limit yet.
- Windows keeps its existing behavior at each call site: `h_memory` uses
half of physical memory, and the preflight uses available physical
memory.
- `HIPBLASLT_CLIENT_HOST_MEMORY_LIMIT`, a new optional environment
variable, caps the budget for a machine with no container limit (a
development box, Windows, or bare metal), and lets a test force a memory
skip. It can only lower the budget: a full container stays full, and
when the system cannot be read the limit is the budget. The value is a
byte count with an optional K, M, G or T suffix in powers of 1024, such
as `64G`; an unreadable value is ignored with one warning. CI does not
need to set it. Geo Min suggested it on #14569.
- `available_host_memory_bytes()` returns `std::optional`: nothing when
the system cannot be read, and 0 when no memory is left, so a full
container or a limit of 0 makes the preflight skip instead of letting
the case run (James Newling's commit).
- The limit caps the free memory each query reports. It does not limit
what a process allocates in total, and it reserves nothing against other
processes; `host_memory.hpp` and `TESTING.md` say so.
- The file paths the query reads are parameters (`HostMemorySources`),
so tests can point it at temporary files.

This is the second of four stacked PRs for AIHPBLAS-5352 and depends on
#14608. With #14608, a pinned allocation that does not fit skips the
test with the requested size; this PR makes the container's limit part
of what "fits" means.

## Test Plan

- `HostMemory_pre_checkin.query_*` (13 new tests in
`clients/tests/src/host_memory_gtest.cpp`). Twelve build a temporary
`/proc/meminfo`, `/proc/self/cgroup`, `/proc/self/mountinfo` and cgroup
tree and check the result for: no cgroup; a v2 limit below
`MemAvailable`, with page cache subtracted; a v2 cgroup with no limit
(`max`); a parent cgroup with a tighter limit than the process's own; a
container listing a host path, with its limit at the top, also when the
mount's root is another cgroup; a mount of a subtree (`/runner`) with a
tighter limit below it; a v1 memory controller mounted with cpu; cgroup
v1, limited and unlimited; a full container whose usage is mostly active
page cache, with and without dirty pages; a removed cgroup; a full
container with no `/proc/meminfo`; and missing, unreadable or over-limit
files. The thirteenth only checks that the real files on the test host
are readable and that its cgroups do not raise `MemAvailable`.
- `HostMemory_pre_checkin.no_host_memory_left_makes_the_preflight_skip`
checks a budget of 0. The test helpers restore any
`HIPBLASLT_CLIENT_HOST_MEMORY_LIMIT` the run started with, and the
fake-host tests clear it while they run (James Newling's commit).
- `HostMemory_pre_checkin.limit_*` (4 new tests): the size format,
including suffixes, overflow and malformed values; that the variable
lowers the budget, never raises it, and leaves a full container at 0;
that it is the budget when `/proc/meminfo` cannot be read; and that
`fast_check_memory_shortfall()` reports "needs 2 GiB of host memory and
1 GiB is available" under a 1 GiB limit (skipped on a runner with less
than 2 GiB free). The pool tests clear the limit while they run, since
pinning past it is what they test.
- To confirm the tests can fail, I built temporary variants. One stopped
the cgroup walk at the process's own cgroup and counted page cache as
used, and it failed 3 of the first 8 `query_*` tests. Another ignored
cgroups altogether, and it failed 5; the three tests that passed under
both check behavior with no limit set. A variant that ignored the
environment variable failed 3 of the 4 `limit_*` tests (all but the
format test), and one that let the variable replace a full container's 0
failed the cap test. The later fixes each have a test that a variant
without the fix failed: no mount translation (2 tests), no v1 mount
lookup, reading the cgroups only with `/proc/meminfo`, and walking above
the mount.
- On the real host: one `stress` case inside a transient 8 GiB cgroup
(`systemd-run --user --scope -p MemoryMax=8G -p MemorySwapMax=0`), on
develop, on the previous PR alone, and on this PR.
- Adjacent: the rest of `HostMemory_pre_checkin`,
`FastCheck_pre_checkin` and `FastCheckDevice_pre_checkin` (the
preflight's suites), and the `smoke` and `quick` tiers.
`hipblaslt-bench` was built but not run.

## Test Result

One MI210 (gfx90a), with this branch's clients built against the 10-25
TheRock nightly hipBLASLt gfx90a device library.

-
`stress_matmul_size_t_index_overflow_f32_..._NN_2048_2048_2048_2_2048_2048_4_1049100_1049100_1`
inside an 8 GiB limit: develop and the previous PR alone were killed
(exit code 137) within 4 seconds. With this PR the test was skipped with
"could not allocate 8 GiB of pinned host memory" and the process exited
0.
- `HostMemory_pre_checkin`, `FastCheck_pre_checkin` and
`FastCheckDevice_pre_checkin`: all passed with no limit set and with
`HIPBLASLT_CLIENT_HOST_MEMORY_LIMIT` at 4G, 1G and 64M for the whole
run. Inside a 1.5 GiB cgroup, the preflight-limit test skips instead of
failing.
- On the same host, 4 GiB pinned with `hipHostMalloc` inside an 8 GiB
cgroup raised its `memory.current` from 0.01 GiB to 4.03 GiB, so the
container's headroom already counts the clients' pinned buffers.
- `smoke` + `quick`: 9,169 passed and 2 skipped
(`GroupedMatmul.smoke_Fp16NN` and `smoke_Bf16NN`, which skip on this
GPU), the same as develop, in 62.3 s.
- Not covered: Windows (unchanged by design), and a real cgroup v1 host.
The v1 path is checked only against temporary files.
- Automated GitHub checks are pending.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

## Risk level

Low (2/5). Every pinned host allocation in the test and bench clients
now consults the new query, so a wrong answer would skip tests that
should run, or let one run that should skip. Outside a memory-limited
cgroup the answer is `MemAvailable`, which the preflight already used.
The hipBLASLt library and device behavior are unchanged.

## Related

- Work tracking:
[AIHPBLAS-5352](https://amd-hub.atlassian.net/browse/AIHPBLAS-5352), fix
3 (container-aware memory budget, with the override) of its list.
- Depends on #14608. Next in the stack: #14610.
- #14569 caps each gfx1250 test container at 110 GiB, which is the
budget this query reads there.

## Device / Architecture Coverage

Host-side test-client code only, with no kernel, library or
kernel-selection change, so it does not depend on the GPU architecture
and the standard PR checks are enough.

## Flags / Guardrails

`HIPBLASLT_CLIENT_HOST_MEMORY_LIMIT` (optional, unset by default) caps
the host-memory budget. It is documented in `TESTING.md`.


[AIHPBLAS-5352]:
https://amd-hub.atlassian.net/browse/AIHPBLAS-5352?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: James Newling <james.newling@gmail.com>
tony-davis added a commit that referenced this pull request Nov 6, 2026
…st memory skips (#14610)

JIRA ID : AIHPBLAS-5352

## Motivation

Before allocating, `hipblaslt-test`, the hipBLASLt test client,
estimates how much device and host memory a matmul case needs, and skips
the case with both amounts when either does not fit. That memory
preflight ran only for `fast_check` cases. The `stress` cases checked
with `unit_check`, such as `matmul_size_t_index_overflow` and its `_hpa`
variant, pin up to about 48 GiB of host memory each on gfx90a and
allocated without any check, so on a host or container without room they
were killed rather than skipped. Memory skips were also easy to miss: a
device out-of-memory error was reported as a passing test, and nothing
summarized which cases did not run.

## Technical Details

- Every `hipblaslt-test` matmul case in `testing_matmul` now runs the
preflight; `hipblaslt-bench` still runs it only for `fast_check` cases.
One estimate in `testing_matmul.hpp` serves both kinds of case. Besides
the buffers it already counted, it now adds the pool's 20% extra
capacity on host buffers below 1 GiB, the CPU reference's float or
double copies of one batch of A and B (and of D when the output type is
not the compute type), the temporary buffers a swizzled A or B is built
in, and one set of buffers per batch in pointer-array mode.
- `matmul_batch_offset` cases go through their own function,
`testing_matmul_batch_offset`, which now runs the same preflight. Its
estimate counts the four host and device buffers, the reference D and
the 128 MiB workspace bound.
- Every stop for lack of memory goes through one macro,
`SKIP_FOR_MEMORY`: the preflight, a failed pinned host allocation, and a
device out-of-memory error. Each one skips the test with a message that
begins "Not enough memory:", and the end of the run lists those tests
under `[ MEMORY ]`, one line each with the amounts. The unused
`LIMITED_MEMORY_STRING` messages are removed.
- `HIPBLASLT_TEST_FAIL_ON_MEMORY_SKIP=1` turns those skips into
failures, so a lane on a large-memory host can require that every case
ran.
- `HIPBLASLT_TEST_CHECK_MEMORY_ESTIMATE=1` (Linux) checks the estimate
on real cases. For a case estimated at 256 MiB or more, it resets the
process's peak resident memory (`VmHWM`, which counts pinned and
pageable memory alike), and fails the case when the peak rose more than
the estimate plus 10% and 256 MiB. It leaves out the first case in a
process, which also loads the kernel library (about 0.6 GiB here).
- Behavior change for reviewers to weigh: a device out-of-memory error
at allocation (`CHECK_DEVICE_ALLOCATION`) used to call `SUCCEED()`,
which counts as a pass. It is now a skip. On a GPU with enough memory
nothing changes; on a smaller one those cases move from "passed" to
"skipped", which is what they were.

This is the last of three stacked PRs for AIHPBLAS-5352 and depends on
#14609, which depends on #14608.

## Test Plan

- `MatmulMemory_pre_checkin` (2 new tests in
`clients/tests/src/matmul_gtest.cpp`). One runs a 4096 x 4096 x 16 f32
`unit_check` case under a 64 MiB `HIPBLASLT_CLIENT_HOST_MEMORY_LIMIT`
with `HIPBLASLT_TEST_FAIL_ON_MEMORY_SKIP=1`, and expects the preflight's
message. The other runs two cases under
`HIPBLASLT_TEST_CHECK_MEMORY_ESTIMATE=1` and bounds the estimate from
both sides: f32 8192 x 8192 x 16, where six 256 MiB host buffers with
their 20% extra make up the estimate, and f16 16384 x 16 x 16384, where
the CPU reference's 1 GiB float copy of A is most of it. Together they
take about 3 s and pin up to about 1.9 GiB.
-
`MatmulMemory_pre_checkin.preflight_stops_a_batch_offset_case_that_does_not_fit`
does the same for a `matmul_batch_offset` case. The test helpers restore
any environment variable the run started with (James Newling's commit).
-
`HostMemory_pre_checkin.memory_shortages_are_listed_with_their_reasons`
checks the `[ MEMORY ]` summary.
- To confirm the tests can fail, I built three temporary variants.
Dropping the 20% extra from the estimate failed the f32 case (estimate
1.5 GiB, used 1.93 GiB), dropping the reference copies failed the f16
case (0.606 GiB against 1.67 GiB), and running the preflight only for
`fast_check` again failed both tests.
- The standard tier (`*smoke*:*quick*:*pre_checkin*`) on develop, on
this PR, and on this PR with `HIPBLASLT_TEST_CHECK_MEMORY_ESTIMATE=1`.
- The 82 large-memory cases that apply to gfx90a (the five name patterns
from #14545): normally, with the estimate check, and inside a transient
8 GiB cgroup (`systemd-run --user --scope -p MemoryMax=8G -p
MemorySwapMax=0`).

## Test Result

One MI210 (gfx90a), with this branch's clients built against the 10-25
TheRock nightly hipBLASLt gfx90a device library.

- Standard tier: develop passed 19,695 and skipped 26; this PR passed
19,715 and skipped the same 26. The 20 extra passes are the new tests
across the three PRs, so the preflight added no skips there. With the
estimate check on, nothing failed. Peak resident memory for the tier
fell from 30.1 GiB to 21.5 GiB, mostly from #14608.
- In the estimate tests the f32 case was estimated at 1.94 GB and used
1.94 GB, and the f16 case 1.73 GB and 1.71 GB.
- Large-memory cases: 82 of 82 passed (327.5 s, peak resident memory
49.2 GiB). With the estimate check all 82 passed again; for the 35
`stress` cases it measured (the 36th was the first case in the process),
the rise in peak resident memory was between 0.996 and 1.003 times the
estimate, which reached 48.1 GiB. The 46 `fast_check` cases estimate
less than 256 MiB of host memory each, so the check does not apply to
them.
- Inside the 8 GiB cgroup: develop was killed (exit code 137) within 4
seconds, at the first `stress` case. This PR exited 0 after 84 s: the 46
`fast_check` cases passed, and the 36 `stress` cases skipped and were
listed, for example "this case needs 16.2 GiB of host memory and 7.41
GiB is available".
- `matmul_batch_offset_large`'s shape (2^32-element offsets, run by hand
on gfx90a because the YAML case is limited to gfx942, gfx950 and
gfx1250): the estimate was 51.55 GB and the measured rise 51.51 GB.
- `smoke`, the memory and `fast_check` suites and the `batch_offset`
cases: 2,802 passed and 9 `GroupedMatmul` cases skipped, both with
nothing set and with `HIPBLASLT_CLIENT_HOST_MEMORY_LIMIT=4G` and
`HIPBLASLT_TEST_FAIL_ON_MEMORY_SKIP=1` set for the whole run. No memory
skips.
- gfx1250: Archana dispatched the full stack in
[41582794047](https://github.com/ROCm/rocm-libraries/actions/runs/41582794047).
It passed hipBLASLt twice (4 shards and all 6 large groups), with no
kills and an empty `[ MEMORY ]` list, with the large-case phase still
separate from the shards. Shards still check memory one moment at a time
and reserve nothing; whether that matters with the lock removed needs
its own runs.
- Automated GitHub checks are pending.

## Submission Checklist

- [x] Look over the contributing guidelines at
https://github.com/ROCm/TheRock/blob/main/GOVERNANCE.md#pull-requests.

## Risk level

Medium (3/5). Every `hipblaslt-test` matmul and batch-offset case now
runs the preflight, so an estimate that is too high would skip cases
that would have run; the standard tier shows the same skips as develop,
and the estimate tests bound overestimates. Device out-of-memory errors
now report as skips instead of passes. The hipBLASLt library and device
behavior are unchanged.

## Related

- Work tracking:
[AIHPBLAS-5352](https://amd-hub.atlassian.net/browse/AIHPBLAS-5352),
fixes 2 (preflight for every case, one estimator) and 5 (visible memory
skips) of its list.
- Depends on #14609 and #14608.
- #14545 runs the large cases after the shards behind a host-wide lock,
and #14569 caps each gfx1250 container at 110 GiB. With this stack and
the cap in place, a follow-up change to the gfx1250 workflow can remove
the lock; the large cases keep their own phase until the question of
several shards in one container (fix 6 in the ticket) is settled.

## Device / Architecture Coverage

Host-side test-client code only, with no kernel, library or
kernel-selection change, so the standard PR checks are enough for
correctness. The gfx1250 hardware run in the ticket's acceptance checks
the result where the failure happened.

## Flags / Guardrails

`HIPBLASLT_TEST_FAIL_ON_MEMORY_SKIP` and
`HIPBLASLT_TEST_CHECK_MEMORY_ESTIMATE`, both off by default and
documented in `TESTING.md`.


[AIHPBLAS-5352]:
https://amd-hub.atlassian.net/browse/AIHPBLAS-5352?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: James Newling <james.newling@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants