mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #4255] [RFC]: [Store] Add LastHitOnly lease selection to BatchProbeKey #1068
Labels
No labels
ascend
auto-closed
bug
bug
CI/Build
Common
documentation
enhancement
feature request
good first issue
help wanted
high priority
MetaX
nightly-failure
pull-request
PyTorch Backend
question
RFC
Roadmap
stale
Store
Tests
Transfer Engine
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/Mooncake#1068
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @yokinoshitayoki on GitHub (Sep 21, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/4255
Changes proposed
K3 cache lookups can renew leases for several KDA checkpoints even though only one checkpoint is eventually loaded. This RFC proposes adding selective lease acquisition to
BatchProbeKeyso callers can protect the last complete checkpoint they can actually reuse.After discussing this with @Aionw, we agreed to propose adding a policy struct directly as an argument to the existing
BatchProbeKeyrequest. The initial implementation would support two modes:None: check existence without granting or extending leases.LastHitOnly: select the last complete candidate and grant read leases to all of its keys.The immediate use case is K3. We propose implementing only these two modes for now, with
LongestPrefixandLongestPrefixLastOnlyleft for future work when a concrete use case requires them.This follows the discussion in #3801 and builds on the probe API introduced by RFC #3769.
Motivation
BatchExistKeygrants a read lease on every readable hit. During prefix-cache lookup, however, the engine may query many candidate KDA checkpoints and load only one. Renewing all of them keeps unused state protected from eviction.For K3, the reuse requirements are different for KV and KDA:
The intended lookup flow is therefore:
batch_is_exist, determining the usable KV boundary.Some excess leasing on the KV side is acceptable for this initial change. The goal is to avoid retaining unused KDA checkpoints.
A hit must represent a complete candidate, which can contain several physical keys. For example, with KV available through position 384:
The correct result is checkpoint 128, with both of its keys leased. Selecting the last individual key, or selecting the last hit independently on each rank, would not protect the checkpoint that can actually be restored.
Proposed API
Keep the flat key list and add a policy specifying how many keys form one candidate:
Names above are proposed names.
candidate_sizeis the number of physical keys in each candidate. Withn = candidate_size, candidateicontains the keys in[i * n, (i + 1) * n):For the targeted K3 layouts, each checkpoint requires the same number of keys once the participating components, cache groups, and ranks are fixed. A single
candidate_sizetherefore describes the grouping while preserving the existing flat input. The size may differ between requests; variable candidate sizes within one request are outside this proposal's scope.The caller orders candidates by increasing resume position and places every key required for a candidate contiguously. Missing members must not be omitted, and deduplication across candidates must not change the grouping. Store does not need to understand K3, token positions, tensor layouts, or rank names. These candidate groups exist only for the request; they do not require persistent group registration.
Policy semantics
For
None,candidate_sizeis unused and the operation retains the existing probe behavior: no read lease is granted or extended.For
LastHitOnly:For a nonempty
LastHitOnlyrequest,candidate_sizemust be positive and dividekeys.size()exactly. The default value of 1 treats each key as one candidate; K3 callers must provide the full checkpoint's actual key count. Invalid sizes or unknown modes return parameter errors without leasing objects. Empty requests return empty results. Duplicate key positions remain valid; a selected physical object only needs to be leased once.Under this proposal,
BatchProbeKeybecomes an existence probe with optional, explicit lease selection.Noneremains the default mode in the policy struct. Documentation must make clear that the batch API is lease-free only withNone; single-keyProbeKeyremains lease-free.Return values
Keep the existing per-key result-vector type, length, and input order.
Noneretains its existence-result semantics. For a successfulLastHitOnlycall, return a selected-only mask: every position in the selected candidate istrueafter all of its keys have been leased; all other positions arefalse, even if those keys exist.For the example above:
[true, true, false, false]: candidate 0 was selected and leased; candidate 1 was incomplete when checked.[false, false, true, true]: candidate 1 was selected and leased, regardless of whether candidate 0 also exists.[false, false, false, false]: no complete candidate was selected.Here,
falsemeans not selected, not necessarily missing. The caller divides the mask into groups ofcandidate_sizeentries and accepts at most one all-truegroup; partially true groups or multiple true groups are invalid. An all-falsemask means no complete candidate was selected. Errors remain errors and must not be treated as ordinary misses to publish a successful lookup. No additional selected-index field is needed.Compatibility and scope
For this iteration, we assume the participating components can be updated together. We propose changing the existing RPC signature directly, without adding a versioned RPC or mixed-version fallback. This is a wire-format change; the default mode does not make it compatible with the previous signature.
ExistKey,BatchExistKey, and single-keyProbeKeyretain their existing behavior. The initial scope covers the Store client layers and Python binding, without adding new C, Go, or Rust probe APIs.Implementation
The existing lookup path can be reused for an initial scan without leases.
LastHitOnlythen examines complete candidates in reverse order, with a separate result mask initialized tofalse:false, release the locks, and try an earlier candidate.truebefore releasing the locks, then return the mask.No member should be leased until the entire candidate passes validation. This prevents a candidate that became incomplete between the scan and selection from receiving partial leases during this operation. Unselected candidates keep all-
falsemask entries, regardless of their existence.Only the candidate being validated needs its relevant shards locked together. A single RPC is not, by itself, sufficient to prevent eviction between checking different shards. The implementation must also avoid reentering helpers that acquire a shard lock already held by the caller.
The mask identifies the protected candidate, not a snapshot of all keys' existence at one instant. A candidate initially observed as incomplete may be discovered on a later request if a concurrent write completes it. Initial lookup errors must be returned without acquiring leases.
The policy must be passed through
WrappedMasterService,MasterClient,Client,RealClient,DummyClient, and the Python binding. Both the Master RPC registration and the production DummyClient-to-RealClient RPC path need coverage.Inference engine integration
The observations below are based on vLLM
601e1c7and SGLang5c69e32. The Store policy supplies selection and leasing; the engines still own KV/KDA pairing and the final resume boundary.vLLM
The native
MooncakeStoreConnectorcurrently queries KV and KDA together in onebatch_is_existcall, then uses its coordinator to determine the reusable boundary. Its Mamba path requiresalignmode. Lookup already expands the required rank namespaces and checks their completeness. See the lookup implementation.The target integration is:
BatchProbeKey(LastHitOnly). Reorder the current group → hash → namespace list into checkpoint → group → namespace order. For the fixed set of participating KDA groups,candidate_sizeis the sum of their required namespace counts; adding a size argument to the unchanged list would not be sufficient.MooncakeLookupResultand scheduler interface.These changes can remain within the connector implementation, primarily
store/worker.pyandcoordinator.py, for its currently supported K3 configurations. They do not require K3 model or core scheduler changes.Both paths typically use two Store lookup RPCs, compared with the current single mixed query. The expected benefit is less unnecessary KDA retention, not a guaranteed reduction in lookup latency.
SGLang
SGLang's K3 HiCache path already queries KV and KDA separately. However, its KDA query currently expands the full input hash chain and only applies the KV boundary when interpreting results. A local K3 checkpoint contains both temporal and convolution-state objects. See
batch_exists_v2.For a single rank and stage, the integration is straightforward:
candidate_sizefrom the key builder'skey_multiplier(currently 2 for a local K3 checkpoint). ApplyLastHitOnlyand use the same selected boundary for loading.Distributed lookup also needs a common complete boundary across all required ranks and stages. Taking the minimum of local maxima is insufficient for sparse checkpoints:
{128, 384}and{128, 256}have a common maximum of 128, not 256. The reviewed controller path currently passes on the maximum length, so this coordination must be addressed during integration.The work belongs in the Mooncake storage backend and HiCache controller, including ordinary prefetch and PP ticket coordination. The final trailing key must refer to the protected checkpoint. Shared helpers such as
_batch_existmust not be changed globally because they also serve write-side deduplication. Other required side pools must constrain the candidates before final selection.Validation
The implementation should cover:
Nonepreserving lease deadlines, including objects with shared leases.falsemask when none is selected.Performance evaluation should measure lookup RPC count and latency, lock hold time, KDA objects/bytes whose leases are renewed, and cache behavior under pressure. This RFC does not claim an end-to-end performance improvement before those measurements.
Future work
LongestPrefixandLongestPrefixLastOnly, suggested by @staryxchen in the earlier discussion #3801 , can be considered when another workload needs consecutive-prefix selection. They are not required to address the current K3 use case and are outside this implementation's scope. We do not propose adding their enum values, configuration, or a general policy framework in advance.Before submitting a new issue...
@github-actions[bot] commented on GitHub (Sep 21, 2026):
Thanks for opening this issue, @yokinoshitayoki!
113957881A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@0z5a commented on GitHub (Oct 1, 2026):
The vLLM connector follow-up is available as a draft: https://github.com/vllm-project/vllm/pull/59522.
It adds the default-off
mooncake_kda_last_hit_onlyoption for FullAttention + align-mode Mamba caches. KV lookup first bounds legal resume points using the existing coordinator; recurrent candidates are then sent in checkpoint → group → rank-namespace order, withcandidate_sizeequal to the actual physical-key count per checkpoint. Lookup metadata and loading retain the selected checkpoint, including Eagle/final-token pruning and partial-hash KV tails. The opt-in path requires independent leases.Validation on NVIDIA Thor used matching CPU/TCP client and Master builds from PR #4314 at
7943c052be6aab81c71b60fea9483952dec21141:The lookup benchmark uses 128 checkpoints, one KV namespace and four recurrent namespaces. Results are medians of three independent processes, each with 20 warmups and 1000 measured lookups per path; execution order was reversed in the second process.
In this workload, the selected path renews four recurrent physical keys instead of 512. The timing varies between processes and measures connector lookup / Store control work, rather than model inference or GPU/token throughput. Reproduction commands, p95 results and key/RPC counts are committed in the vLLM PR.
The draft currently targets the selected-only response contract implemented at the pinned #4314 revision. Aionw's review proposes per-key existence results instead; the connector response decoding and validation will need to follow the final agreed contract before this option is promoted. No Store-side changes or separate Mooncake PR are included in this follow-up.