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
11 changes: 4 additions & 7 deletions ggml/src/ggml-hrx/ggml-hrx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1654,13 +1654,10 @@ static std::unique_ptr<ggml_backend_hrx_reg_context> create_registry_context() {
if (device_ctx->memory_budget == 0 && device_ctx->memory_total != 0) {
// Nothing fits under the ceiling: refuse the device here, loudly, instead of planning a launch that can
// reach the fault line (#680). Report only, do not try to be clever about it.
const size_t reserve = ggml::hrx::onebit::pool_reserve_bytes();
GGML_LOG_ERROR("ggml_hrx: %s: refusing to use the device: GTT total %.1f GiB with %.1f GiB free leaves "
"nothing under the %.1f GiB ceiling (reserve GGML_HRX_GTT_RESERVE_MB, override "
"GGML_HRX_POOL_LIMIT_MB); free GTT or lower the reserve (engine #680)\n",
device_ctx->name.c_str(), device_total / (1024.0 * 1024.0 * 1024.0),
device_free / (1024.0 * 1024.0 * 1024.0),
(device_total > reserve ? device_total - reserve : 0) / (1024.0 * 1024.0 * 1024.0));
// engine #2069: the line names the term that made the budget zero -- a host-bound refusal that says "free GTT"
// sends the operator to the wrong resource. The wording is a pure function so both branches are tested.
GGML_LOG_ERROR("%s\n", ggml::hrx::onebit::pool_refusal_note(terms, device_total, device_free,
device_ctx->name.c_str()).c_str());
hrx_device_release(hrx_device);
continue;
}
Expand Down
30 changes: 30 additions & 0 deletions ggml/src/ggml-hrx/hrx-1bit-safety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,36 @@ pool_budget_terms_t pool_budget_terms(size_t memory_total, size_t memory_free, s
return t;
}

// engine #2069 (llama.cpp #259's review): the refusal has to name the term that bound it. `budget == 0` is reached two ways
// that look identical and have opposite remedies -- the GTT ceiling leaving nothing under the reserve, or the host figure
// being the smaller term (a measured zero refuses by design since #1942/#259, and a tiny MemAvailable does the same). The
// numbers printed are the ones the decision used, not the ones that happen to be nearby.
std::string pool_refusal_note(const pool_budget_terms_t & terms, size_t memory_total, size_t memory_free,
const char * device_name) {
char line[512];
const double gib = 1024.0 * 1024.0 * 1024.0;
if (terms.host_bound) {
// The host term was the smaller one and it is what made the budget zero.
std::snprintf(line, sizeof line,
"ggml_hrx: %s: refusing to use the device: the HOST term bound the pool - host MemAvailable %.1f GiB "
"against a GTT term of %.1f GiB (GTT total %.1f GiB with %.1f GiB free). This is not the GTT ceiling: "
"freeing GTT will not help while host memory is the smaller term%s (engine #1502, #2069)",
device_name != nullptr ? device_name : "HRX", terms.host_available / gib, terms.gtt_term / gib,
memory_total / gib, memory_free / gib,
terms.host_available == 0 ? " -- a measured zero refuses by design, so check that /proc/meminfo is readable (#1942)"
: "");
return std::string(line);
}
const size_t reserve = pool_reserve_bytes();
std::snprintf(line, sizeof line,
"ggml_hrx: %s: refusing to use the device: GTT total %.1f GiB with %.1f GiB free leaves nothing under the "
"%.1f GiB ceiling (reserve GGML_HRX_GTT_RESERVE_MB, override GGML_HRX_POOL_LIMIT_MB); free GTT or lower the "
"reserve (engine #680)",
device_name != nullptr ? device_name : "HRX", memory_total / gib, memory_free / gib,
(memory_total > reserve ? memory_total - reserve : 0) / gib);
return std::string(line);
}

size_t pool_budget(size_t memory_total, size_t memory_free, size_t memory_available) {
return pool_budget_terms(memory_total, memory_free, memory_available).budget;
}
Expand Down
8 changes: 8 additions & 0 deletions ggml/src/ggml-hrx/hrx-1bit-safety.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#pragma once

#include <string>

#include <cstddef>
#include <cstdint>

Expand Down Expand Up @@ -46,6 +48,12 @@ inline constexpr size_t kHostUnknown = static_cast<size_t>(-1);

pool_budget_terms_t pool_budget_terms(size_t memory_total, size_t memory_free, size_t memory_available = kHostUnknown);
size_t pool_budget(size_t memory_total, size_t memory_free, size_t memory_available = kHostUnknown);
// The refusal line, as text (engine #2069, llama.cpp #259's review). A budget of 0 means "nothing fits", and WHICH term made
// it zero decides what the operator should free: the GTT ceiling leaves GTT and the reserve as the knobs, while a host-bound
// budget leaves host memory, and "free GTT" would send them to the wrong one. Pure, so the two wordings are pinned by
// test-hrx-pool-refusal-budget.cpp without a device -- the whole product of a guardrail is this line.
std::string pool_refusal_note(const pool_budget_terms_t & terms, size_t memory_total, size_t memory_free, const char * device_name);

// true when GGML_HRX_POOL_LIMIT_MB is in force: pool_budget() then returns that fixed limit rather than a
// remainder, so a caller that prints the figure has to word it differently.
bool pool_budget_is_fixed_limit();
Expand Down
28 changes: 28 additions & 0 deletions tests/test-hrx-pool-refusal-budget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,34 @@ int main() {
expect_eq("and a stale caller value is not reported as a reading", raw, 0);
expect_eq("no reader at all is unknown too", pool_host_term_read(nullptr, raw), kHostUnknown);

// ---- the refusal WORDING names the term that bound it (engine #2069) -------------------------------------------
//
// The budget is 0 in two situations with opposite remedies, and the old line printed GTT numbers for both: "free GTT or
// lower the reserve" when the HOST term was the smaller one sends the operator to the wrong resource. Both wordings are
// pure, so this is device-free -- and the line is the whole product of a guardrail.
{
const size_t total = 120 * GiB;
const size_t free_ = 78 * GiB; // 62 GiB of GTT headroom cannot explain a refusal
const auto host0 = pool_budget_terms(total, free_, 0); // a MEASURED zero refuses by design
const std::string note0 = pool_refusal_note(host0, total, free_, "HRX0");
expect_true("a measured-zero host refusal says HOST, not the GTT ceiling", note0.find("HOST term") != std::string::npos);
expect_true("and it does not send the operator to GTT", note0.find("free GTT or lower the reserve") == std::string::npos);
expect_true("and it names the measured zero and its likely cause", note0.find("measured zero refuses by design") != std::string::npos);

const auto host_tight = pool_budget_terms(total, free_, 1 * GiB);
const std::string note1 = pool_refusal_note(host_tight, total, free_, "HRX0");
expect_true("a tight-host refusal also says HOST", note1.find("HOST term") != std::string::npos);
expect_true("and does NOT claim a measured zero", note1.find("measured zero") == std::string::npos);

// A GTT refusal keeps its familiar advice: there the ceiling and the reserve really are the knobs.
const size_t in_use = 100 * GiB;
const auto gtt = pool_budget_terms(total, total - in_use, kHostUnknown);
const std::string note2 = pool_refusal_note(gtt, total, total - in_use, "HRX0");
expect_true("a GTT-bound refusal still names the ceiling and the reserve",
note2.find("free GTT or lower the reserve") != std::string::npos);
expect_true("and does not claim the host bound it", note2.find("HOST term") == std::string::npos);
}

printf(failures == 0 ? "test-hrx-pool-refusal-budget: all requirements passed\n"
: "test-hrx-pool-refusal-budget: %d FAILED\n", failures);
return failures == 0 ? 0 : 1;
Expand Down