Skip to content

Commit f630926

Browse files
committed
Fail loudly when transfer staging is exhausted
A producer that could not seat a pull-serve marked the send done without transferring, so the consumer learned of the failure only by waiting out its transfer timeout; a consumer that could not seat a read failed it silently. The producer now waits for staging to free, bounded by the transfer deadline, and reports a transfer failure when a request still cannot be seated or can never fit. Both sides log the request, the pages asked, and the pages free, so an exhausted pool is attributed where it happens. Measured on the setup of the previous commit: at 4 fixed slots the load exhausted staging 1112 times across 139 requests, each visible only as a consumer-side failure. With staging on demand none occurred. Memory and performance results for on-demand staging itself are in the commit that introduces it, "Stage disaggregation transfers on demand instead of in fixed slots" (github.com//pull/722).
1 parent 3d51232 commit f630926

1 file changed

Lines changed: 56 additions & 16 deletions

File tree

tpu_sync/core/kv_cache_manager_with_transfer.cc

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,6 +1556,13 @@ void KVCacheManagerWithTransfer::StartRead(
15561556
if (!staged.has_value()) {
15571557
// Request larger than the staging pool can seat: surface as a recv
15581558
// failure (the connector can recompute) rather than throwing.
1559+
LOG(ERROR) << "StartRead: cannot stage " << unique_local_bids.size()
1560+
<< " blocks for req_id=" << req_id
1561+
<< " (dynamic=" << dynamic_host_staging_
1562+
<< ", free_host_blocks="
1563+
<< host_block_manager_->num_free_blocks()
1564+
<< ", free_slots=" << free_slots_.size()
1565+
<< ", max_blocks=" << max_blocks_ << ")";
15591566
failed_recving_.insert(req_id);
15601567
return;
15611568
}
@@ -1925,7 +1932,11 @@ KVCacheManagerWithTransfer::AcquireRecvStagingLocked(int64_t num_blocks,
19251932
// Lock the pages so the pool's LRU cannot evict staging that is mid-flight.
19261933
auto allocated = host_block_manager_->Allocate(static_cast<int>(num_blocks),
19271934
/*lock=*/true);
1928-
if (!allocated.ok()) return std::nullopt;
1935+
if (!allocated.ok()) {
1936+
LOG(ERROR) << "demand staging: Allocate(" << num_blocks
1937+
<< ") failed: " << allocated.status();
1938+
return std::nullopt;
1939+
}
19291940
entry->staged_host_blocks = *allocated;
19301941
std::vector<int64_t> blocks;
19311942
blocks.reserve(allocated->size());
@@ -2335,24 +2346,53 @@ void KVCacheManagerWithTransfer::StartPushInternal(
23352346
// pool. Writing D2H straight to host[src_block_id] overflows the host buffer
23362347
// once a device block id exceeds num_host_blocks.
23372348
std::vector<int64_t> host_block_ids;
2338-
{
2339-
absl::MutexLock lock(mu_);
2340-
auto it = send_entries_.find(uuid);
2341-
if (it == send_entries_.end()) {
2342-
return;
2349+
const auto stage_deadline = DeadlineFromNow();
2350+
while (true) {
2351+
{
2352+
absl::MutexLock lock(mu_);
2353+
auto it = send_entries_.find(uuid);
2354+
if (it == send_entries_.end()) {
2355+
return; // request cancelled while waiting for staging
2356+
}
2357+
if (!dynamic_host_staging_ &&
2358+
static_cast<int64_t>(src_block_ids.size()) > max_blocks_) {
2359+
// A fixed slot can never seat this request; report the send as
2360+
// failed so the consumer aborts now rather than timing out.
2361+
LOG(ERROR) << "StartPushInternal: request " << it->second->req_id
2362+
<< " needs " << src_block_ids.size()
2363+
<< " blocks but a staging slot holds " << max_blocks_;
2364+
failed_recving_.insert(it->second->req_id);
2365+
ReleaseEntrySlotLocked(it->second);
2366+
send_entries_.erase(it);
2367+
return;
2368+
}
2369+
RecvEntry staging;
2370+
auto staged = AcquireRecvStagingLocked(
2371+
static_cast<int64_t>(src_block_ids.size()), &staging);
2372+
if (staged.has_value()) {
2373+
it->second->slot_idx = staging.slot_idx;
2374+
it->second->staged_host_blocks = std::move(staging.staged_host_blocks);
2375+
host_block_ids = std::move(*staged);
2376+
break;
2377+
}
23432378
}
2344-
RecvEntry staging;
2345-
auto staged = AcquireRecvStagingLocked(
2346-
static_cast<int64_t>(src_block_ids.size()), &staging);
2347-
if (!staged.has_value()) {
2348-
done_sending_.insert(it->second->req_id);
2349-
ReleaseEntrySlotLocked(it->second);
2350-
send_entries_.erase(it);
2379+
// Staging exhausted: wait for in-flight sends to hand blocks back
2380+
// instead of reporting a send that never happened. The consumer's own
2381+
// deadline still bounds the total wait.
2382+
if (std::chrono::steady_clock::now() >= stage_deadline) {
2383+
absl::MutexLock lock(mu_);
2384+
auto it = send_entries_.find(uuid);
2385+
if (it != send_entries_.end()) {
2386+
LOG(ERROR) << "StartPushInternal: staging exhausted serving "
2387+
<< it->second->req_id << " (" << src_block_ids.size()
2388+
<< " blocks); reporting transfer failure";
2389+
failed_recving_.insert(it->second->req_id);
2390+
ReleaseEntrySlotLocked(it->second);
2391+
send_entries_.erase(it);
2392+
}
23512393
return;
23522394
}
2353-
it->second->slot_idx = staging.slot_idx;
2354-
it->second->staged_host_blocks = std::move(staging.staged_host_blocks);
2355-
host_block_ids = std::move(*staged);
2395+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
23562396
}
23572397

23582398
// Coalesce contiguous (device,host) block runs into a few large copies. With

0 commit comments

Comments
 (0)