decode: the adaptive tier's evictions reach the device's residency table before the next window when the tier does not wait (STRATA_ADAPT_NOWAIT=1, STRATA_ADAPT_LAG > 1)

adapt() marks an evicted expert non-resident in host_res at once; d_res follows in apply_pending, once the round's
copies have landed.  When the tier does not wait for them (STRATA_ADAPT_NOWAIT=1, or STRATA_ADAPT_LAG > 1) the next
window runs before that.  doorbell_publish_res reads d_res, sees the evicted expert as resident, and when every routed
expert of a layer looks resident it does not publish the layer's activation.  The pool plans from host_res and
computes the evicted expert on the CPU from the activation published before.  Now every verifier launch (run,
run_slot_rows, batch_launch, pl_launch; a layer split's stages too) runs a pre-launch hook.  It patches the device
tables from a list of changed entries: res_patch, a small kernel on the verifier's stream reading mapped memory; the
whole table when the list overflows or a device does not map it at the host's address.  A failed whole-table upload in
apply_pending leaves its entries to the hook.  Only where the tier does not wait.  --batch-groups with a layer split
publishes every activation instead: set_always_publish, which now also turns E-6's device plan off.
--pipeline-windows keeps its fence.  STRATA_ADAPT_EVICT_SYNC=0 restores the old behaviour.  STRATA_ADAPT_CHECKRES=1
reads the tables back before each window, compares them with host_res, and counts the layer-windows that computed a
CPU expert with no activation published.

RTX 5090, IQ2_XS, --expert-cache 12000, 32K explain, 600 tokens: NOWAIT=1 34 of 222 windows with mismatched tables
(2470 entries), 1 such layer-window -> 0 / 0 / 0; LAG=2 55 of 223 (3222 entries: every swap), 5 -> 0 / 0 / 0.  LAG=2
is reproducible (two runs of each arm, the same tokens), and the fix changes its answer from token 228.  The hook's
cost under NOWAIT (6297 slots, 3 pairs): ms a round -1.1% chat, -1.2% 32K, noise.  The default (the tier waits)
installs no hook: tokens and first-token logits identical to d5ea7133 (p2k 200 tokens, 32K explain 600).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
This commit is contained in:
sergiywithandClaude Opus 5.5 committed 2026-10-08 08:47:17 +03:00
1 parent d5ea713374
commit eb47960451
7 files changed
+292 -9

No files matched your search

+12
View File
@@ -398,6 +398,18 @@ struct ExpertDispatch {
/// Plan v0.3 P6: decayed routing counts per (layer, expert) during decode (sized by the caller; empty = off),
/// which the driver uses to swap the most-routed missing experts into the VRAM tier between rounds.
std::vector<float> usage;
/// The adaptive tier's evictions reach the device's table before the next window, not when the round's copies
/// land: until then doorbell_publish_res can skip a layer's activation while the pool computes an evicted expert
/// from the one published before. The driver sets it where the tier does not wait for its copies
/// (STRATA_ADAPT_NOWAIT=1, STRATA_ADAPT_LAG > 1; STRATA_ADAPT_EVICT_SYNC=0: off, for an A/B). `res_dirty` asks the
/// driver to patch the device's copy before the next window, `res_dirty_idx` lists the entries changed since.
bool evict_sync = false;
bool res_dirty = false;
std::vector<int32_t> res_dirty_idx;
/// STRATA_ADAPT_CHECKRES: the device's residency table as read back before the window; a layer with CPU experts
/// whose routed ids all look resident there publishes no activation (doorbell_publish_res) - counted
const int32_t* dres_check = nullptr;
int64_t stale_x = 0;
int64_t multi_misses = 0; ///< distinct (layer, expert) pairs the CPU computed in verify windows
int64_t multi_entries = 0; ///< routed (token, expert) entries the CPU served in verify windows
/// Multi-GPU: the second GPU's tier. Its experts are computed there instead of on the CPU (kind 2).
+12 -2
View File
@@ -32,6 +32,7 @@
#include <atomic>
#include <chrono>
#include <functional>
#include <map>
#include <cstdint>
#include <string>
@@ -211,8 +212,12 @@ public:
/// Every layer copies the token rows to the host (doorbell_publish), not only the layers with a routed expert
/// outside the VRAM tier by the device's residency table: with windows in flight the adaptive tier marks an
/// expert evicted on the host (the pool then computes it on the CPU) before the device table follows. Also turns
/// the device-planned layers (E-6) off. Before `init`.
void set_always_publish(bool on) { always_publish_ = on; }
/// the device-planned layers (E-6) off, which read the device table this mode lets lag. Before the first window
/// (its graphs are captured there).
void set_always_publish(bool on) {
always_publish_ = on;
if (on) device_plan_ = false;
}
cudaStream_t stream() const { return cs_; }
int device() const { return device_; }
/// Capture every window size and the commit graph now (a capture syncs the stream: never with a window in flight).
@@ -261,6 +266,10 @@ public:
/// arena directly, 2 = a copy kernel stages it inside the graph (no API calls on the pool's thread; best when
/// the CPU is RAM-bound, Q2_0). Set before the first `run`.
void set_pcie_mode(int mode) { sink_.pcie_mode = mode; }
/// Called before every window this verifier launches (run, the batch and pipelined launches): the device's
/// residency table catches up with the host's (the adaptive tier's evictions while its copies are in flight).
/// It gets this verifier's stream; false (with the message in `err`) fails the launch.
void set_pre_launch(std::function<bool(cudaStream_t cs, std::string& err)> f) { pre_launch_ = std::move(f); }
/// the pool never plans a PCIe share (--pcie-frac 0): the window skips that path. Before the first run.
double ms_wait = 0, ms_pool = 0, ms_host = 0, ms_commit = 0;
@@ -435,6 +444,7 @@ private:
static void publish_plan(void* ctx);
void set_plan_slot(int grp);
bool split_ = false; // opt-in (--spec-split): exact but slower, see the overlap study
std::function<bool(cudaStream_t, std::string&)> pre_launch_; // set_pre_launch
int groups_[9] = {};
float* h_ymiss_ = nullptr; float* m_ymiss_ = nullptr; // T * k * n_embd
@@ -80,6 +80,9 @@ void copy_indexed(float* dst, const float* src, int64_t stride, const int32_t* i
/// into dst + k * blob_bytes with coalesced 16-byte loads - the PCIe share of a layer's missed experts, staged
/// into VRAM before the grouped expert kernel reads them. Launched for a capacity of `cap` blobs.
void fetch_blobs(const unsigned long long* src, const int32_t* n, uint8_t* dst, int64_t blob_bytes, int cap, void* stream);
/// table[pairs[2k]] = pairs[2k + 1] for k < n, `pairs` in mapped host memory: a residency table's changed entries,
/// stream-ordered before the window that reads it, without the copy engine (where the tier's swaps may queue).
void res_patch(int32_t* table, const int32_t* pairs, int n, void* stream);
/// ptr[k] = base + k * blob_bytes for k < *n (the staged copies `fetch_blobs` made).
void rebase_ptrs(unsigned long long* ptr, const int32_t* n, uint8_t* base, int64_t blob_bytes, void* stream);
+7
View File
@@ -3114,6 +3114,13 @@ void expert_pool_dispatch_multi(ExpertDispatch& d, const float* x_f, const int32
bool any_cpu = false;
for (int64_t i = 0; i < n; ++i)
if (kind[i] < 0) { any_cpu = true; break; }
if (d.dres_check != nullptr && any_cpu) { // STRATA_ADAPT_CHECKRES
bool published = false;
for (int64_t i = 0; i < n && !published; ++i)
published = ids[i] < 0 || ids[i] >= d.n_expert ||
d.dres_check[(size_t) d.layers * (size_t) d.n_expert + (size_t) ids[i]] < 0;
if (!published) ++d.stale_x;
}
const auto c1 = std::chrono::steady_clock::now();
if (any_cpu) {
// #578 --remote-expert-opt: a token whose experts all run on a GPU (CUDA0 or a helper) needs no CPU activation
+4
View File
@@ -1871,6 +1871,7 @@ bool Verifier::run(int T, const int32_t* tokens, int64_t pos0, PoolMultiFn pool,
const ModelGeometry& g = *g_;
SessionState& ss = *ss_;
if (pos0 + T > ss.qsa_states[ss.qsa_primary()].max_cells) { err = "verify: the window runs past the context"; return false; }
if (pre_launch_ && !pre_launch_(cs_, err)) return false;
refresh_ar();
if (!capture(T, err) || !capture_commit(err)) return false;
VDBG("captured; staging\n");
@@ -2583,6 +2584,7 @@ bool Verifier::run_slot_rows(const int* rows, int S, const int32_t* tokens, cons
using namespace strata::kernels;
const OnDevice on_device(device_);
const ModelGeometry& g = *g_;
if (pre_launch_ && !pre_launch_(cs_, err)) return false;
if (!stage_batch(rows, S, 0, tokens, pos, err)) return false;
const cudaError_t le = cudaGraphLaunch(exec_bm_[bkey(rows, S, 0)], cs_);
if (le != cudaSuccess) { err = std::string("verify: batch launch: ") + cudaGetErrorString(le); return false; }
@@ -2742,6 +2744,7 @@ bool Verifier::batch_launch(int base, int S, const int32_t* tokens, const int64_
if (b_running_) { err = "verify: batch_launch while this stage is busy"; return false; }
int rows[8] = {};
for (int t = 0; t < S && t < 8; ++t) rows[t] = base + t;
if (pre_launch_ && !pre_launch_(cs_, err)) return false;
if (!stage_batch(rows, S, base, tokens, pos, err)) return false;
cudaError_t le = cudaGraphLaunch(exec_bm_[bkey(rows, S, base)], cs_);
if (le == cudaSuccess) le = cudaGraphLaunch(commit_bm_[bkey(rows, S, base)], cs_); // right behind it: every row is kept
@@ -2928,6 +2931,7 @@ bool Verifier::pl_launch(int T, const int32_t* tokens, int64_t pos0, std::string
err = "verify: pipelined window not prepared (capture_all)";
return false;
}
if (pre_launch_ && !pre_launch_(cs_, err)) return false;
const Clock::time_point t0 = Clock::now();
last_batch_ = false;
bool staged = pl_prestaged_ && last_t_ == T && last_pos0_ == pos0;
+10
View File
@@ -507,6 +507,10 @@ __global__ void fetch_blobs_kernel(const unsigned long long* __restrict__ src, c
}
}
__global__ void res_patch_kernel(int32_t* __restrict__ table, const volatile int32_t* pairs, int n) {
for (int k = blockIdx.x * blockDim.x + threadIdx.x; k < n; k += gridDim.x * blockDim.x) table[pairs[2 * k]] = pairs[2 * k + 1];
}
__global__ void rebase_ptrs_kernel(unsigned long long* ptr, const int32_t* n, unsigned long long base, long long bytes) {
const int k = threadIdx.x;
if (k < *n) ptr[k] = base + (unsigned long long) k * (unsigned long long) bytes;
@@ -602,6 +606,12 @@ void fetch_blobs(const unsigned long long* src, const int32_t* n, uint8_t* dst,
check("fetch_blobs");
}
void res_patch(int32_t* table, const int32_t* pairs, int n, void* stream) {
if (n <= 0) return;
res_patch_kernel<<<(n + 255) / 256 < 64 ? (n + 255) / 256 : 64, 256, 0, (cudaStream_t) stream>>>(table, pairs, n);
check("res_patch");
}
void rebase_ptrs(unsigned long long* ptr, const int32_t* n, uint8_t* base, int64_t blob_bytes, void* stream) {
rebase_ptrs_kernel<<<1, 128, 0, (cudaStream_t) stream>>>(ptr, n, (unsigned long long) base, (long long) blob_bytes);
check("rebase_ptrs");
+244 -7
View File
@@ -1222,6 +1222,67 @@ int adapt_lag() {
return v;
}
/// ExpertDispatch::evict_sync where the tier does not wait for its copies; STRATA_ADAPT_EVICT_SYNC=0 turns it off.
bool adapt_evict_sync() {
static const bool v = [] { const char* e = std::getenv("STRATA_ADAPT_EVICT_SYNC"); return e == nullptr || std::atoi(e) != 0; }();
return v;
}
/// STRATA_ADAPT_CHECKRES=1: before a verify window, the device's residency table read back into `back` and
/// compared with the host's (the pool's plan); the mismatched entries, the first ones reported.
int64_t res_check(const int32_t* d_res, const std::vector<int32_t>& host_res, std::vector<int32_t>& back,
int64_t n_expert, int64_t window) {
if (cudaMemcpy(back.data(), d_res, host_res.size() * sizeof(int32_t), cudaMemcpyDeviceToHost) != cudaSuccess) {
std::fprintf(stderr, "strata: res check: the read-back failed: %s\n", cudaGetErrorString(cudaGetLastError()));
return 0;
}
static int printed = 0;
int64_t bad = 0;
for (size_t i = 0; i < host_res.size(); ++i)
if (back[i] != host_res[i]) {
++bad;
if (printed < 20) {
++printed;
std::fprintf(stderr, "strata: res check: window %lld layer %lld expert %lld: host %d, device %d\n",
(long long) window, (long long) (i / (size_t) n_expert), (long long) (i % (size_t) n_expert),
(int) host_res[i], (int) back[i]);
}
}
return bad;
}
/// The pre-launch hook's patch: the changed entries of the host's residency table (`idx`) as (index, entry) pairs into
/// the mapped `pairs` (room for `cap`); their count, or -1 when they do not fit (then the whole table goes up).
int64_t res_patch_pairs(const std::vector<int32_t>& idx, const std::vector<int32_t>& host_res, int32_t* pairs, size_t cap) {
if (pairs == nullptr || idx.size() > cap) return -1;
for (size_t k = 0; k < idx.size(); ++k) {
pairs[2 * k] = idx[k];
pairs[2 * k + 1] = host_res[(size_t) idx[k]];
}
std::atomic_thread_fence(std::memory_order_seq_cst);
return (int64_t) idx.size();
}
/// Mapped host memory for res_patch_pairs (portable: every card of a layer split reads it), or null. The kernels read
/// it at the host's address: each of `devs` (-1: the current device) must map it there, else the whole table goes up.
int32_t* res_pairs_alloc(size_t cap, const std::vector<int>& devs) {
void* p = nullptr;
if (cudaHostAlloc(&p, cap * 2 * sizeof(int32_t), cudaHostAllocMapped | cudaHostAllocPortable) != cudaSuccess) {
(void) cudaGetLastError();
return nullptr;
}
for (int dev : devs) {
const strata::core::OnDevice on(dev);
void* d = nullptr;
if (cudaHostGetDevicePointer(&d, p, 0) != cudaSuccess || d != p) {
(void) cudaGetLastError();
cudaFreeHost(p);
return nullptr;
}
}
return (int32_t*) p;
}
/// --adapt-async: one background job at a time (the asynchronous adaptive tier's copies and memcpys). `post` hands
/// it over, `idle` says whether it has finished; the owner never posts while a job runs.
class JobThread {
@@ -7240,13 +7301,14 @@ int main(int argc, char** argv) {
cudaEvent_t adapt_ev = nullptr;
cudaEventCreateWithFlags(&adapt_ev, cudaEventDisableTiming);
// a layer split's later stages keep a copy of the residency table on their devices, and swap on their own
auto res_upload = [&]() {
if (d_res != nullptr)
res_put(d_res);
auto res_upload = [&]() -> cudaError_t { // the first error, every table tried
cudaError_t r = d_res != nullptr ? res_put(d_res) : cudaSuccess;
for (auto& st : stages) {
const strata::core::OnDevice on(st->dev);
res_put(st->d_res);
const cudaError_t e = res_put(st->d_res);
if (r == cudaSuccess) r = e;
}
return r;
};
auto apply_pending = [&](bool wait) {
if (peer.valid()) peer.apply_pending(wait);
@@ -7265,10 +7327,116 @@ int main(int argc, char** argv) {
host_res[(size_t) i] = slot;
srcp->release((int64_t) i / g.n_expert, (int64_t) i % g.n_expert); // in VRAM now: RAM not needed
}
if (drive.d.evict_sync) { // a failed upload leaves them to the pre-launch hook
for (const auto& pr : pending) drive.d.res_dirty_idx.push_back(pr.first);
drive.d.res_dirty = true;
}
pending.clear();
pending_age = 0;
res_upload();
if (res_upload() == cudaSuccess) { // the whole table: the hook has nothing left
drive.d.res_dirty = false;
drive.d.res_dirty_idx.clear();
}
};
// Where the tier does not wait for its copies (STRATA_ADAPT_NOWAIT=1, STRATA_ADAPT_LAG > 1), its evictions reach
// the device's tables before the next window: adapt() marks them non-resident at once, apply_pending uploads
// only when the round's copies land - a window or more later, and until then doorbell_publish_res skips the
// activation of a layer whose routed experts all look resident, while the pool computes an evicted one from the
// activation published before. The hook runs at each verifier's launch; the serial windows (one at a time,
// the batch windows included, a layer split's stages one after another) take it.
// --batch-groups with a layer split launches a group while another is in flight on the other stage, which
// waits on this thread: a synchronous upload there could wait on that window, so those verifiers publish
// every activation instead (set_always_publish; the device table then decides nothing a stale entry breaks).
// --pipeline-windows is exempt: its verifiers always publish, and the tier fences the windows in flight
// before it overwrites a slot (adapt_fence). --adapt-async uploads at each of its steps.
// STRATA_ADAPT_CHECKRES=1 reads the tables back at each launch and compares them with the host's.
const bool piped_groups = o.batch > 0 && o.batch_groups > 1 && !stages.empty();
const bool serial_windows = !pipe && !piped_groups;
const bool tier_lags = !drive.d.usage.empty() && (adapt_nowait() || adapt_lag() > 1);
drive.d.evict_sync = serial_windows && tier_lags && adapt_evict_sync();
if (piped_groups && tier_lags && adapt_evict_sync()) {
ver.set_always_publish(true);
for (auto& st : stages) st->ver.set_always_publish(true);
}
const bool res_checking = std::getenv("STRATA_ADAPT_CHECKRES") != nullptr && serial_windows;
std::vector<int32_t> res_back;
int64_t res_windows = 0, res_bad = 0, res_bad_windows = 0;
if (res_checking) {
res_back.assign(host_res.size(), 0);
drive.d.dres_check = res_back.data();
}
// The changed entries go up by a kernel on each card's verifier stream, reading them from mapped memory: a
// synchronous upload waits for the copy engine, which the tier's swaps of the round before still hold (~4 ms
// on 33 swaps; measured +0.9 ms a chat round). Too many changes, or no mapped memory: the whole table.
constexpr size_t kResPatchCap = 16384;
std::vector<int> res_devs{-1};
for (auto& st : stages) res_devs.push_back(st->dev);
int32_t* res_pairs = drive.d.evict_sync ? res_pairs_alloc(kResPatchCap, res_devs) : nullptr;
auto res_sync = [&](bool check, std::string& e) -> bool {
if (drive.d.res_dirty) {
const int64_t n = res_patch_pairs(drive.d.res_dirty_idx, host_res, res_pairs, kResPatchCap);
cudaError_t r = cudaSuccess;
if (n >= 0) {
if (d_res != nullptr) {
strata::kernels::res_patch(d_res, res_pairs, (int) n, ver.stream());
r = cudaGetLastError();
}
for (size_t k = 0; k < stages.size() && r == cudaSuccess; ++k) {
const strata::core::OnDevice on(stages[k]->dev);
strata::kernels::res_patch(stages[k]->d_res, res_pairs, (int) n, stages[k]->ver.stream());
r = cudaGetLastError();
}
} else {
r = res_upload();
}
if (r != cudaSuccess) {
e = std::string("the residency table update failed: ") + cudaGetErrorString(r);
return false;
}
drive.d.res_dirty = false;
drive.d.res_dirty_idx.clear();
}
if (!check) return true;
cudaStreamSynchronize(ver.stream()); // the patches landed (the verifier streams do not block the read)
for (auto& st : stages) {
const strata::core::OnDevice on(st->dev);
cudaStreamSynchronize(st->ver.stream());
}
int64_t bad = 0;
for (auto& st : stages) { // the later stages' copies first: res_back ends with CUDA0's (the pool's check)
const strata::core::OnDevice on(st->dev);
bad += res_check(st->d_res, host_res, res_back, g.n_expert, res_windows);
}
bad += res_check(d_res, host_res, res_back, g.n_expert, res_windows);
++res_windows;
res_bad += bad;
res_bad_windows += bad > 0;
if ((res_windows & 255) == 0 || (bad > 0 && res_bad_windows <= 4))
std::fprintf(stderr, "strata serve: res check: %lld windows, %lld with mismatches (%lld entries), "
"%lld layer-windows with CPU experts and no activation published\n",
(long long) res_windows, (long long) res_bad_windows, (long long) res_bad,
(long long) drive.d.stale_x);
return true;
};
struct PreLaunchOff { // the hooks' captures die before the verifiers
strata::core::Verifier& v;
std::vector<std::unique_ptr<GpuStage>>& stages;
strata::core::ExpertDispatch& d;
int32_t* pairs;
~PreLaunchOff() {
v.set_pre_launch(nullptr);
for (auto& st : stages) st->ver.set_pre_launch(nullptr);
d.dres_check = nullptr;
if (pairs != nullptr) cudaFreeHost(pairs);
}
} pre_launch_off{ver, stages, drive.d, res_pairs};
if (drive.d.evict_sync || res_checking) {
ver.set_pre_launch([&](cudaStream_t, std::string& e) { return res_sync(res_checking, e); });
for (auto& st : stages) st->ver.set_pre_launch([&](cudaStream_t, std::string& e) { return res_sync(false, e); });
if (drive.d.evict_sync)
std::fprintf(stderr, "strata serve: the adaptive tier's evictions reach the device's table before the next "
"window (STRATA_ADAPT_EVICT_SYNC=0: when its copies land)\n");
}
// --pipeline-windows: set while the tier adapts beside windows in flight (see its use in adapt)
std::function<void()> adapt_fence;
// the VRAM tier follows the conversation (the same rule as the speculative loop below)
@@ -7369,6 +7537,10 @@ int main(int argc, char** argv) {
srcp->prefetch(s.layer, s.out); // a file-backed arena released its pages: read them back ahead
pending.emplace_back((int32_t) in, slot); // resident once the copy has landed
}
if (drive.d.evict_sync && !swaps.empty()) { // the evicted ones are misses now (see the pre-launch hook)
for (const Swap& s : swaps) drive.d.res_dirty_idx.push_back((int32_t) (s.layer * g.n_expert + s.out));
drive.d.res_dirty = true;
}
if (!cp_dst.empty() && strata::core::copy_blobs(cp_dst.data(), cp_src.data(), cp_bytes.data(), cp_dst.size(),
adapt_stream, dma_mode) != cudaSuccess) {
std::fprintf(stderr, "strata serve: adaptive swap copy failed (batched): %s\n",
@@ -11330,6 +11502,57 @@ int main(int argc, char** argv) {
const uint64_t fbytes0 = src.file_blob_bytes(), fall0 = src.file_read_bytes();
const double fms0 = src.file_ms();
if (o.adapt_every > 0 && o.adapt_swaps > 0) drive.d.usage.assign((size_t) (g.n_layers * g.n_expert), 0.0f);
// before every window: the device's table follows the host's (the tier's evictions while its copies are in
// flight - see the serve path); the hook's captures live in this block, so it is taken off the verifier when the
// block ends
const bool res_checking = std::getenv("STRATA_ADAPT_CHECKRES") != nullptr && d_res != nullptr;
std::vector<int32_t> res_back;
int64_t res_windows = 0, res_bad = 0, res_bad_windows = 0;
if (res_checking) {
res_back.assign(host_res.size(), 0);
drive.d.dres_check = res_back.data();
}
drive.d.evict_sync = d_res != nullptr && !drive.d.usage.empty() && (adapt_nowait() || adapt_lag() > 1) &&
adapt_evict_sync();
// the changed entries by a kernel on the verifier's stream (see the serve path)
constexpr size_t kResPatchCap = 16384;
int32_t* res_pairs = drive.d.evict_sync ? res_pairs_alloc(kResPatchCap, {-1}) : nullptr;
struct PreLaunchOff {
strata::core::Verifier& v;
strata::core::ExpertDispatch& d;
int32_t* pairs;
~PreLaunchOff() {
v.set_pre_launch(nullptr);
d.dres_check = nullptr;
if (pairs != nullptr) cudaFreeHost(pairs);
}
} pre_launch_off{ver, drive.d, res_pairs};
if (drive.d.evict_sync || res_checking)
ver.set_pre_launch([&](cudaStream_t cs, std::string& e) -> bool {
if (drive.d.res_dirty) {
const int64_t n = res_patch_pairs(drive.d.res_dirty_idx, host_res, res_pairs, kResPatchCap);
cudaError_t r;
if (n >= 0) {
strata::kernels::res_patch(d_res, res_pairs, (int) n, cs);
r = cudaGetLastError();
} else {
r = res_put(d_res);
}
if (r != cudaSuccess) {
e = std::string("the residency table update failed: ") + cudaGetErrorString(r);
return false;
}
drive.d.res_dirty = false;
drive.d.res_dirty_idx.clear();
}
if (!res_checking) return true;
cudaStreamSynchronize(cs); // the patch landed
const int64_t bad = res_check(d_res, host_res, res_back, g.n_expert, res_windows);
++res_windows;
res_bad += bad;
res_bad_windows += bad > 0;
return true;
});
int64_t swaps_total = 0;
double ms_adapt = 0;
cudaStream_t adapt_stream = nullptr;
@@ -11363,10 +11586,16 @@ int main(int argc, char** argv) {
host_res[(size_t) i] = slot;
srcp->release((int64_t) i / g.n_expert, (int64_t) i % g.n_expert); // in VRAM now: RAM not needed
}
if (drive.d.evict_sync) { // a failed upload leaves them to the pre-launch hook
for (const auto& pr : pending) drive.d.res_dirty_idx.push_back(pr.first);
drive.d.res_dirty = true;
}
pending.clear();
pending_age = 0;
if (d_res != nullptr)
res_put(d_res);
if (d_res != nullptr && res_put(d_res) == cudaSuccess) { // the whole table: the hook has nothing left
drive.d.res_dirty = false;
drive.d.res_dirty_idx.clear();
}
};
// Plan v0.3 P6: the VRAM tier follows the conversation. Candidates are missing experts routed at least
// twice (decayed); each is paired with its layer's least-routed resident expert and swapped when it was
@@ -11450,6 +11679,10 @@ int main(int argc, char** argv) {
srcp->prefetch(s.layer, s.out); // a file-backed arena released its pages: read them back ahead
pending.emplace_back((int32_t) in, slot); // resident once the copy has landed
}
if (drive.d.evict_sync && !swaps.empty()) { // the evicted ones are misses now (see the pre-launch hook)
for (const Swap& s : swaps) drive.d.res_dirty_idx.push_back((int32_t) (s.layer * g.n_expert + s.out));
drive.d.res_dirty = true;
}
if (!cp_dst.empty() && strata::core::copy_blobs(cp_dst.data(), cp_src.data(), cp_bytes.data(), cp_dst.size(),
adapt_stream, dma_mode) != cudaSuccess) {
std::fprintf(stderr, "strata generate: an adaptive refill failed\n");
@@ -11705,6 +11938,10 @@ int main(int argc, char** argv) {
if (rounds > 0 && !drive.d.usage.empty())
std::printf("%-24s %lld experts swapped into the VRAM tier (every %d rounds, %.3f ms/round)\n", "adaptive tier",
(long long) swaps_total, o.adapt_every, ms_adapt / rounds);
if (res_checking)
std::printf("%-24s %lld windows, %lld with mismatches (%lld entries), %lld layer-windows with CPU experts and "
"no activation published\n", "residency check", (long long) res_windows,
(long long) res_bad_windows, (long long) res_bad, (long long) drive.d.stale_x);
if (src.complement_ready())
std::printf("%-24s %.2f GiB of experts in RAM, %lld exchanged with the VRAM tier, %lld blob reads from "
"the file\n", "resident RAM", (double) src.resident_bytes() / 1073741824.0,