feat(umbp): cache remote fetches into local DRAM (dual-scheme)#442
Merged
Conversation
Make the cache_remote_fetches flag live on dual-scheme main. After a successful remote DRAM fetch, PoolClient re-caches the block into this node's local exportable DRAM tier and publishes it (KvEvent::ADD via ExecuteLocalPut -> Commit) so the master can route future reads local. - Admission gate (NEVER/SIZE/ALWAYS + admission_max_block_bytes, 16MB default) ported from Team C's reference, additive to config + pybind. - Requester-local GET routing: TierPriorityRouteGetStrategy::Select now prefers a best-tier replica owned by the requesting node instead of choosing at random, so a re-cached block actually serves the re-caching node's own subsequent reads locally. - Re-cache install runs on an async worker thread (bounded, drop-on-full) off the Get critical path, so it does not add tail latency to concurrent Gets. 2P1D sglang E2E (12r/64c, fresh master per arm, round_0 hit=0.0): throughput +8.9% / +4.4%, p99 TTFT -76% / -23% ON vs OFF across two independent A/B pairs; p90 TTFT -92.5% when the OFF arm hits a tail spike.
Add hermetic gtest (test_cache_remote_admission) covering the ShouldAdmitReCache predicate: disabled-flag / NEVER / zero-size / ALWAYS-ignores-cap / SIZE-cap boundary / zero-cap-unlimited. Dedup MaybeReCacheAfterRemote to call the shared predicate instead of an inline copy. No GPU/RDMA/master needed.
Wrap the test_cache_remote_admission target_link_libraries call to satisfy the cmake-format pre-commit hook (matches the file's existing style).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cache_remote_fetchesis already declared inUMBPDistributedConfig, exposed in pybind, printed in the client config dump, and documented (design-master-control-plane.md + theUMBP_CACHE_REMOTE_FETCHESenv var) — but it was never implemented: a remote fetch read the peer's block into the caller and dropped it, keeping no local copy. This PR implements the documented behavior.When a node fetches a KV block from a remote peer's L3, the block is now written back into that node's local L3 DRAM tier and re-published to the master, so subsequent reads of the same key are served locally instead of paying another cross-node RDMA round-trip.
Motivation
In multi-node UMBP serving (e.g. sglang HiCache L3 with 2 prefill nodes sharing prefixes), two nodes repeatedly read the same shared-prefix KV blocks. Without re-caching, every repeat read of a peer-owned block is a full cross-node RDMA fetch and keeps load on the owning node. Re-caching turns "remote every time" into "remote once, then local" — lowering TTFT tails and raising throughput. (sglang's own L2 only holds the block while hot in the small host cache and does not create a durable local L3 replica, so the large L3 tier stays empty for peer-owned keys without this feature.)
What changed (additive; no proto/API break)
PoolClient(distributed/pool_client.cpp): on a successful remote Get, ifcache_remote_fetchesand the admission gate passes, enqueue the block for a local install —MaybeReCacheAfterRemote→ async worker →ExecuteLocalPut(DRAM)→Commit→KvEvent::ADDpublish. Wired into the Get/BatchGet finalize path.CacheRemoteAdmission {SIZE (default), NEVER, ALWAYS}+admission_max_block_bytes(16 MB) onUMBPDistributedConfig/PoolClientConfig.routing/route_get_strategy.cpp): prefer a best-tier replica whosenode_id == requester(self-local) before random pick, so a re-cached block is actually read locally on the requester's next request.Design notes
Init(when enabled), drained/joined inShutdownbefore the allocator is torn down. Re-cache is idempotent.Performance — 2P1D sglang HiCache E2E, on vs off
2 prefill nodes share the UMBP L3 prefix cache; ON vs OFF differ ONLY in
cache_remote_fetches. Model DeepSeek-V3-5layer, 8xMI300X/node, mori RoCE transfer, real forward. Freshumbp_masterper arm (round_0 cache-hit = 0.0, contamination-free).This PR (dual-scheme main), two clean pairs:
(*) pair A's OFF arm hit a tail spike; pair B's OFF baseline was already tail-clean, so its avg/p90 are flat — but ON never regresses. Robust signals (throughput up, p99 down) agree across both pairs.
Independent N=3 confirmation (same mechanism, earlier UMBP lineage, alternated order):
ON p90 TTFT was lower in all 3 reps.
Note: cache-hit rate is ~flat by design — the feature accelerates the fetch path (remote→local), it does not change which prefixes hit; the value shows up in latency/throughput.
Reproduction
Consumed via sglang's HiCache UMBP backend, so E2E repro needs the companion sglang UMBP stack (see sgl-project/sglang#25377).
3 nodes — P1 = prefill + umbp_master, P2 = prefill, D = decode + router. RoCE env on every node (RoCEv2 GID index; pin one NIC == the node's UMBP address):
export MORI_IB_GID_INDEX=3 MORI_RDMA_DEVICES=mlx5_0 MORI_SOCKET_IFNAME=eth0P1 (prefill + auto-start umbp_master):
P2 (prefill, joins P1's master): same as P1 but
UMBP_NODE_ADDRESS=$P2,UMBP_MASTER_AUTO_START=false, and dropUMBP_MASTER_BIN.D (decode + router, drives the benchmark):
OFF arm: rerun all three with
UMBP_CACHE_REMOTE_FETCHES=falseand a freshumbp_master(kill it between arms so L3 starts cold; verify round_0 hit = 0.0). Per-request metrics land in the decode node'sperformance_metrics.jsonl.Backward compatibility
Additive only.
cache_remote_fetchesdefault preserved; when off, behavior is identical to before. No proto changes;PublishLocalBlockis an existing RPC.Testing