Skip to content

Commit 44c74f9

Browse files
voidstackloopclaude
andcommitted
moe-cache : fail the decode instead of aborting on a mailbox timeout
The mailbox's CPU op aborted the process when the GPU did not hand a layer over within 20 s, taking llama-server down with it. Now it computes no expert for the step (all -1 ids), sets an error flag, and llama_context::graph_compute turns the flag into GGML_STATUS_FAILED, so llama_decode returns -3 and rolls the step back. Later layers of the same step skip the wait once one has timed out. The GPU-side receive is launched after the CPU split, so giving up cannot deadlock it, and a late send carries an epoch the next step rejects. Checked by forcing the timeout: one error line, decode fails, exit code 1 instead of 134. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 7f5581d commit 44c74f9

3 files changed

Lines changed: 28 additions & 3 deletions

File tree

‎src/llama-context.cpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2768,6 +2768,11 @@ ggml_status llama_context::graph_compute(
27682768
LLAMA_LOG_ERROR("%s: ggml_backend_sched_graph_compute_async failed with error %d\n", __func__, status);
27692769
}
27702770

2771+
// the CPU splits have run by now, the MoE cache's mailbox ops among them
2772+
if (moe_cache && moe_cache->take_mailbox_error() && status == GGML_STATUS_SUCCESS) {
2773+
status = GGML_STATUS_FAILED;
2774+
}
2775+
27712776
// fprintf(stderr, "splits: %d\n", ggml_backend_sched_get_n_splits(sched));
27722777

27732778
return status;

‎src/llama-moe-cache.cpp‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ struct llama_moe_cache::impl {
308308
float heat_inc = 1.0f; // the weight of a selection now: it grows, which is how older ones decay
309309
int32_t steps_since_rebalance = 0;
310310
bool decode_seen = false; // a CPU op of the hybrid ran in the last graph
311+
std::atomic<bool> mailbox_error{false}; // a hand-over timed out in the last graph, see take_mailbox_error
311312
size_t n_swap = 0;
312313
// mailbox: how long the CPU waited for the GPU's hand-overs (the time the GPU spends before the experts), and how many
313314
uint64_t wait_ns = 0;
@@ -859,11 +860,25 @@ struct llama_moe_cache::impl {
859860
impl & self = *h.self;
860861
const mailbox_layer & m = self.mailbox[h.il];
861862
const uint32_t epoch = self.epoch;
863+
const int64_t k = m.ids->ne[0];
864+
int32_t * cold = (int32_t *) dst->data;
865+
866+
// an earlier layer of this step timed out already: the step fails anyway, do not wait 20 s for every layer
867+
if (self.mailbox_error) {
868+
std::fill(cold, cold + k, -1);
869+
return;
870+
}
862871

863872
const auto t0 = std::chrono::steady_clock::now();
864873
for (uint64_t spins = 0; *m.host_flag != epoch; ++spins) {
865874
if ((spins & 0xFFFFF) == 0xFFFFF && std::chrono::steady_clock::now() - t0 > std::chrono::seconds(20)) {
866-
GGML_ABORT("MoE cache: the GPU did not hand layer %d over within 20 s", h.il);
875+
// a stalled GPU fails the step, not the process: the CPU computes no expert (its GPU-side receive is
876+
// launched after this split anyway) and graph_compute turns the error into a failed decode. A late send
877+
// is stamped with this epoch, which the next step's no longer matches
878+
LLAMA_LOG_ERROR("%s: the GPU did not hand layer %d over within 20 s, failing the step\n", __func__, h.il);
879+
std::fill(cold, cold + k, -1);
880+
self.mailbox_error = true;
881+
return;
867882
}
868883
}
869884
std::atomic_thread_fence(std::memory_order_acquire);
@@ -872,8 +887,6 @@ struct llama_moe_cache::impl {
872887

873888
const int32_t * ids = (const int32_t *) m.ids->data;
874889
const int32_t * slots = (const int32_t *) dst->src[2]->data;
875-
const int64_t k = m.ids->ne[0];
876-
int32_t * cold = (int32_t *) dst->data;
877890

878891
// consumed: a graph computed without begin_step() would wait for a new send instead of reading these ids again
879892
*m.host_flag = 0;
@@ -1461,6 +1474,10 @@ void llama_moe_cache::begin_step() {
14611474
pimpl->begin_step();
14621475
}
14631476

1477+
bool llama_moe_cache::take_mailbox_error() {
1478+
return pimpl->mailbox_error.exchange(false);
1479+
}
1480+
14641481
ggml_tensor * llama_moe_cache::build_hybrid_ids(ggml_context * ctx, ggml_tensor * selected_experts, int32_t il) const {
14651482
return pimpl->build_hybrid_ids(ctx, selected_experts, il);
14661483
}

‎src/llama-moe-cache.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ class llama_moe_cache {
7272
void build_mailbox_recv(ggml_context * ctx, int32_t il, ggml_tensor * e_cold, ggml_tensor *& copy, ggml_tensor *& recv) const;
7373
// before each graph compute: a new epoch, which the GPU's sends stamp on their flags
7474
void begin_step();
75+
// after each graph compute: true if a hand-over from the GPU timed out in it (the CPU then computed none of its
76+
// experts, so the step's results are wrong and it must fail); clears the error
77+
bool take_mailbox_error();
7578

7679
// ggml_backend_sched copy callback, returns false if src is not a slot map
7780
bool copy(ggml_backend_t backend, const ggml_tensor * src, ggml_tensor * dst, ggml_cgraph * graph);

0 commit comments

Comments
 (0)