Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions ggml/src/ggml-hrx/hrx-1bit-safety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.

#include "hrx-1bit-safety.h"
#include "runtime/host-memory-available.h" // MemAvailable for the live check's host term (engine #1502)

#include "ggml-impl.h"

Expand Down Expand Up @@ -247,9 +248,15 @@ pool_budget_state_t pool_budget_observe(size_t memory_total, size_t free_bytes,
// Does |size| fit the live pool with |margin| of headroom? Pure, so the test drives it: the numbers come from the driver's
// counters, and the arithmetic that matters is the underflow guard (|used| can exceed |total| transiently) and the margin
// saturation (|margin| is an operator input and may be absurd).
bool pool_live_fits(size_t size, size_t total, size_t used, size_t margin) {
const size_t live_free = used >= total ? 0 : total - used;
const size_t wanted = size + (margin > SIZE_MAX - size ? SIZE_MAX - size : margin);
bool pool_live_fits(size_t size, size_t total, size_t used, size_t margin, size_t memory_available) {
size_t live_free = used >= total ? 0 : total - used;
// engine #1502: the GTT counter alone over-promises on this APU, because the pool allocation is served from host RAM.
// Without this term the live check admits a request the open-time budget would have refused -- the reported shape was
// a 15.35 GiB request, a GTT counter showing room, and ~15 GiB of host memory available.
if (memory_available != kHostUnknown && memory_available < live_free) {
live_free = memory_available;
}
const size_t wanted = size + (margin > SIZE_MAX - size ? SIZE_MAX - size : margin);
return wanted <= live_free;
}

Expand All @@ -260,7 +267,9 @@ bool pool_reserve(size_t budget, size_t size, const char * what) {
size_t in_use = 0;
if (pool_live_counters(total, in_use)) {
const size_t margin = pool_live_margin_bytes();
if (!pool_live_fits(size, total, in_use, margin)) {
const size_t host_raw = host_memory_available_bytes();
const size_t host_available = host_raw == 0 ? kHostUnknown : host_raw;
if (!pool_live_fits(size, total, in_use, margin, host_available)) {
const size_t live_free = in_use >= total ? 0 : total - in_use;
GGML_LOG_ERROR("%s: HRX pool limit: %s of %.1f MiB does not fit the live device budget "
"(%.1f MiB free - %.1f MiB margin of %.1f MiB); this is the live check, on by default since "
Expand Down
6 changes: 5 additions & 1 deletion ggml/src/ggml-hrx/hrx-1bit-safety.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ bool gtt_counters(size_t & total, size_t & used);
// Charge `size` against `budget`; false (with one log line naming the sizes) when it does not fit.
// True when |size| fits the live pool (|total|, |used| from the driver's counters) with |margin| of headroom. Pure, so the
// test drives it; the underflow and margin-saturation guards live here rather than in the caller.
bool pool_live_fits(size_t size, size_t total, size_t used, size_t margin);
// The live check takes the host term too (engine #1502): the counters report GTT, but a hsa_amd_memory_pool_allocate on
// this APU is served by host RAM, so `min(GTT free, MemAvailable)` is the honest figure -- the same two-term model the
// open-time budget uses. kHostUnknown disables the term (an unreadable /proc must not refuse anything).
bool pool_live_fits(size_t size, size_t total, size_t used, size_t margin,
size_t memory_available = kHostUnknown);

bool pool_reserve(size_t budget, size_t size, const char * what);

Expand Down
12 changes: 12 additions & 0 deletions tests/test-hrx-pool-refusal-budget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ int main() {

expect_true("a 15.35 GiB buffer fits 45 GiB free with the default margin",
pool_live_fits(refused, gtt, gtt - 45 * GiB, margin_256));

// The host term (engine #1502): the GTT counters alone admit a request the host cannot serve. These arms are the
// reported shape -- a large request, a GTT counter showing room, and little MemAvailable left.
expect_true("live: the request the issue reports is refused once the host term is known",
!pool_live_fits(16 * GiB, gtt, gtt - 45 * GiB, margin_256, /*memory_available=*/2 * GiB));
expect_true("live: the same request passes on the GTT figure alone (the over-promise being fixed)",
pool_live_fits(16 * GiB, gtt, gtt - 45 * GiB, margin_256, kHostUnknown));
expect_true("live: an unreadable MemAvailable (kHostUnknown) never refuses on its own",
pool_live_fits(16 * GiB, gtt, gtt - 45 * GiB, margin_256, kHostUnknown) ==
pool_live_fits(16 * GiB, gtt, gtt - 45 * GiB, margin_256));
expect_true("live: a host figure LARGER than GTT free does not lift the GTT term",
!pool_live_fits(16 * GiB, gtt, gtt - 16 * GiB, margin_256, 200 * GiB));
// The case the live check exists for, built from the report's own numbers: the open-time budget was
// 104 - 74.8 = 29.2 GiB, so a 20 GiB buffer is ADMITTED by the snapshot. If a neighbour then grew the box to
// 105 GiB in use (15 GiB free), the snapshot still admits it and the failure lands in the allocator; the live
Expand Down