resident RAM mode: a budget keeps the prompt path's lend region when it has room (#1683)

pin_cache_complement set lend_from_slot = -1 whenever --resident-budget-gib was given, so the experts of the cache
slots the prompt path borrows were in neither VRAM nor RAM during a prompt and were read from the model files on every
chunk, and again when the slots were refilled - even with room left in the budget. The budget branch now appends the
lend region's experts after the ranked ones, from the last slot down (the order the borrowing takes them), while they
fit; the budget stays a hard cap, and the ones past it keep the file fallback. The start-up line reports the count, and
the lend warning names a larger budget as the remedy when one is set.

Measured on an RX 6900 XT 16 GB (HIP gfx1030), Ryzen 9 5950X, 125 GB RAM, UD-Q4_K_XL, --resident-budget-gib 71 (the
copy held all 63.47 GiB outside the cache, 7.5 GiB unused; 1,231 lent slots = 3.60 GiB), engine 0.1.42, one run per
case, greedy: file reads of the lent experts per request 0.5-2.6 GB -> 0; SSD reads per fresh 8K / 16K prompt 2.8 /
1.9 GB -> 0.3 GB; prompt-path host staging 0.8-2.1 s -> 0.06-0.5 s per request; fresh prompts 887 / 7.8K / 15.8K
tokens 180 -> 194, 633 -> 676, 677 -> 698 tok/s. Decode unchanged.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HfV9GaWcc3RmPgCvhX2eUf
This commit is contained in:
Evan LightcapandClaude Fable 5.1 committed 2026-10-10 18:31:43 -04:00
1 parent 61b3fb5dd3
commit 29063eb2bc
3 files changed
+57 -13

No files matched your search

+7 -1
View File
@@ -250,7 +250,13 @@ start (locked; page-locked when the driver allows the whole budget), and the res
It implies `--mmap-experts` and leaves 4 GiB of headroom. On Windows, available commit capacity also
limits the budget; a larger N is clamped to the smaller limit less 4 GiB and a 256 MiB margin, with a message.
A clamped budget no longer fails the safety check that follows (#403). A budget that cannot be kept at all is a
warning, with every expert read from the files. Setup sets N with `--resident-budget-gib N`. With
warning, with every expert read from the files. Setup sets N with `--resident-budget-gib N`. When the budget has room
left after the ranked experts, the slots the prompt path borrows keep their experts in RAM too, as they do without a
budget (the engine prints `the prompt path's lend region: N of its M slots keep their experts in RAM too`); before, a
budget always left them out, and every prompt read the lent slots' experts from the model files again (RX 6900 XT,
UD-Q4_K_XL, budget 71 GiB with 7.5 GiB unused: 0.5-2.6 GB of file reads per agent turn and 1.9-2.8 GB from the SSD
per fresh 8K-16K prompt went to zero; fresh prompts read 3-8% faster, the prompt path's host staging fell from
0.8-2.1 s to 0.06-0.5 s per request). With
the GGUF read in place it also warms the next layer's likely experts: while the CPU works on a layer, a thread applies
the next layer's router to this layer's input and asks the OS for the pages of the predicted experts that neither the
GPU nor the RAM budget holds (only pages - the experts computed are the same; `STRATA_LOOKAHEAD=0` turns it off). This
+43 -9
View File
@@ -2579,6 +2579,7 @@ bool FileExpertSource::pin_cache_complement(
}
#endif
const bool what_fits = budget_bytes == kResidentWhatFits; // #467: the soft mode's second try
int64_t budget_lent = 0, budget_lendable = 0; // the lend region's slots kept by a budget (below), and its size
uint64_t budget_physical = 0; // #403: reuse the memory snapshot a budget was sized from
uint64_t budget_commit = std::numeric_limits<uint64_t>::max();
if (budget_bytes > 0) {
@@ -2630,6 +2631,36 @@ bool FileExpertSource::pin_cache_complement(
std::fprintf(stderr, "FileExpertSource: RAM budget %.2f GiB: %lld of the %.2f GiB of experts the GPU cache does "
"not hold, by profile rank; the rest are read from the files\n",
(double) budget_bytes / 1073741824.0, (long long) held, (double) bytes / 1073741824.0);
// The prompt path's lend region under a budget. Until now a budget turned the loan off (`lend_from_slot = -1`
// below), so every lent slot's expert was read from the pack during every prompt chunk and again when the slot
// was refilled after the prompt - even with room left in the budget. Measured on an RX 6900 XT 16 GB with
// --resident-budget-gib 71 and UD-Q4_K_XL: the copy kept all 63.47 GiB of the experts outside the cache, 7.5
// GiB of the budget stayed unused, and every agent turn read 0.5-2.6 GB of the 1,231 lent slots' experts (3.6
// GiB) from the GGUF (a fresh 17K-token prompt 24 GB). As the unbudgeted path does, take the lend region's
// experts from the last slot down (the order the borrowing takes them) while they fit in what the budget has
// left; the ones past it keep the file fallback, and `rank` keeps its precedence (the hottest experts first).
if (lend_from_slot >= 0 && lend_from_slot < n_slots && additional_gpu_pairs.empty()) {
budget_lendable = n_slots - lend_from_slot;
std::vector<size_t> order; // the pairs in the lend region, highest slot first
for (size_t i = 0; i < primary_gpu_pairs.size(); ++i)
if (pair_slot[i] >= lend_from_slot && pair_slot[i] < n_slots) order.push_back(i);
std::sort(order.begin(), order.end(), [&](size_t a, size_t b) { return pair_slot[a] > pair_slot[b]; });
const uint64_t at0 = at;
for (size_t i : order) {
const auto& pr = primary_gpu_pairs[i];
const size_t idx = (size_t) pr.first * (size_t) n_expert_ + (size_t) pr.second;
const uint64_t b = layer_blob_bytes_[(size_t) pr.first];
if (b > budget_bytes - at) break; // a contiguous tail of the region: a short prompt lends only the last few
if (ranked[idx] != kNoComplement) continue;
ranked[idx] = at;
at += b;
++budget_lent;
}
std::fprintf(stderr, "FileExpertSource: the prompt path's lend region: %lld of its %lld slots keep their experts "
"in RAM too (%.2f GiB of the budget's remaining %.2f GiB)\n",
(long long) budget_lent, (long long) budget_lendable, (double) (at - at0) / 1073741824.0,
(double) (budget_bytes - at0) / 1073741824.0);
}
offsets.swap(ranked);
bytes = at;
lend_from_slot = -1;
@@ -3094,7 +3125,7 @@ bool FileExpertSource::pin_cache_complement(
complement_partial_ = complement_registered_ && !(paced && partial_pin >= bytes);
complement_locked_ = locked;
complement_lock_off_ = lock_off;
complement_lent_slots_ = lend ? n_slots - keep_from : 0;
complement_lent_slots_ = lend ? n_slots - keep_from : budget_lent; // a budget keeps its own tail of the region
complement_lent_slots_ += stage_lent; // and the split stages' regions: pairs the copy kept of them
complement_ready_ = true;
std::fprintf(stderr, "FileExpertSource: %s cache complement ready: resident %.2f GiB, pinned %.2f GiB in %.0f s%s%s\n",
@@ -3102,21 +3133,24 @@ bool FileExpertSource::pin_cache_complement(
(double) resident_bytes() / 1073741824.0, (double) pinned_bytes() / 1073741824.0,
std::chrono::duration<double>(std::chrono::steady_clock::now() - pin_t0).count(),
note.empty() ? "" : "; ", note.c_str());
if (lend)
const int64_t lendable_all = lend ? n_slots - lend_from_slot : budget_lendable; // 0: no lend region at all
if (lendable_all > 0)
std::fprintf(stderr, "FileExpertSource: %lld of the prompt path's %lld lendable slots keep their experts in RAM "
"too%s\n", (long long) complement_lent_slots_, (long long) (n_slots - lend_from_slot),
complement_lent_slots_ < n_slots - lend_from_slot
"too%s\n", (long long) (lend ? n_slots - keep_from : budget_lent), (long long) lendable_all,
(lend ? n_slots - keep_from : budget_lent) < lendable_all
? " (the others are read from the file when lent: not enough RAM for them)" : "");
if (lend && complement_lent_slots_ < n_slots - lend_from_slot) {
if (lendable_all > 0 && (lend ? n_slots - keep_from : budget_lent) < lendable_all) {
// #1389: below full coverage the uncovered experts are read from the pack during a long prompt (a 74K prompt
// routes through nearly all experts, so the misses repeat). The reporter measured 18% uncovered = -26% prompt
// speed on a gfx1100; short prompts barely touch them. A warning, not a refusal.
const long long lendable = (long long) (n_slots - lend_from_slot);
const long long missing = lendable - (long long) complement_lent_slots_;
const long long lendable = (long long) lendable_all;
const long long missing = lendable - (long long) (lend ? n_slots - keep_from : budget_lent);
std::fprintf(stderr, "FileExpertSource: WARNING: %lld of %lld lendable slots (%.0f%%) will read their experts from the pack "
"during a long prompt; expect a slower prompt read on 50K+ token prompts (one measurement: 18%% uncovered "
"cost 26%%). Free RAM or lower STRATA_RESIDENT_HEADROOM_GIB (now the RAM left free at start) and restart; "
"the line above should read N of N.\n", missing, lendable, 100.0 * (double) missing / (double) std::max<long long>(1, lendable));
"cost 26%%). %s and restart; the line above should read N of N.\n", missing, lendable,
100.0 * (double) missing / (double) std::max<long long>(1, lendable),
lend ? "Free RAM or lower STRATA_RESIDENT_HEADROOM_GIB (now the RAM left free at start)"
: "Raise --resident-budget-gib (the lend region comes after the ranked experts)");
}
if (!stage_lend_regions_.empty()) {
int64_t offered = 0;
+7 -3
View File
@@ -6641,14 +6641,18 @@ int main(int argc, char** argv) {
// #669 / #765: a big chunk lends many cache slots to the prompt path, and each lent slot's expert is read back
// from the SSD whenever the RAM could not keep its copy (31 GB: auto:32768 read prompts ~3x slower than auto).
// Said, never capped: --prefill is the user's, and with the RAM for them it is the faster one (+21-35%).
if (resident_ok && lend_from >= 0 && o.prefill_chunk > 8192 && xcache.slots() > lend_from &&
// #1683: with a RAM budget the lend region is kept after the ranked experts, as far as the budget reaches, so
// the advice there is a larger budget, not a smaller chunk (the budget used to keep none of it whatever the chunk)
if (resident_ok && lend_from >= 0 && (o.prefill_chunk > 8192 || o.resident_budget > 0) && xcache.slots() > lend_from &&
src.resident_lent_slots() < xcache.slots() - lend_from)
std::fprintf(stderr, "strata generate: WARNING: a prompt chunk of %lld tokens lends %lld cache slots to the "
"prompt path, but only %lld of their experts fit in RAM: the others are read from the "
"SSD on every chunk, and prompts can read ~3x slower than with --prefill auto (#669). "
"A smaller --prefill, or auto, keeps them all in RAM\n",
"%s keeps them all in RAM\n",
(long long) o.prefill_chunk, (long long) (xcache.slots() - lend_from),
(long long) src.resident_lent_slots());
(long long) src.resident_lent_slots(),
o.resident_budget > 0 ? "A larger --resident-budget-gib (the lend region comes after the ranked experts), or a smaller --prefill,"
: "A smaller --prefill, or auto,");
if (resident_ok) {
// --adapt-async: twice the exchange buffers - the second half bounces the copies in whose source is not
// page-locked (an asynchronous copy instead of the driver's staged, synchronous one)