mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #1403] [RFC]: Mooncake KV-Store Indexer API Standardization #449
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#449
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 @yejj710 on GitHub (Jan 19, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/1403
Motivation
Currently, we are planning to implement an indexer on Mooncake to track the global status of KVCache (including VRAM, DRAM, SSD, etc.), which can assist the gateway in achieving precise cache-aware request scheduling.
Goals
1.Use KVEvents API as a Public Contract
The KVEvents schema is clearly defined in both vLLM and SGLang. Therefore, we will use KVEvents as the data structure for indexer updates.
2.Support different storage media
Enable KV-cache management across multiple tiers, including:
3.DP rank Awareness
Track KV-cache status across different DP ranks to assist the gateway in achieving global cache-aware DPLB
4.Support querying by Token ID or hash key
Long sequences context impose high network overhead if token IDs are directly transmitted, so we should provide an API supporting hash keys as query input.
5.Supports dynamic service discovery via
registerandunregisterapiProposed Change
/register
POST api
/unregister
POST api
/query
POST api
/query_by_hash
POST api
CC List: @stmatengss @doujiang24 @Asher-XunZhang
Before submitting a new issue...
@doujiang24 commented on GitHub (Jan 21, 2026):
mediumin input seems unnecessary? please show more details about the usage of the field.keep simple as start could be better.
@yejj710 commented on GitHub (Jan 21, 2026):
Thanks for comment~ medium is an optional arg. I've submitted a pr that includes more detailed explanations.
@PeaBrane commented on GitHub (Jan 30, 2026):
@yejj710 several comments / questions
Should we also standardize here the hashing algo? I think each engine framework hashes their KV events differently, so we may also want to discuss a deterministic hashing algo here (sha256) as the universal contract.
If passing by raw tokens, where would the hashing happen, is it inside the KV indexer service? This may make the indexer service itself not super scalable. I wonder if we should just not support raw tokens as input altogether.
What is the mechanism here for each engine registering their
engine_nameto the KvIndexer service? Would there be any dynamic discovery and heartbeat mechanism here?I think we should just return
matched_blocksinstead ofmatched_tokensand returnblock_sizeas well explicitly. Also,matched_tokenshere is a bit ambiguous; is it the max matched tokens across all dp instances and memory tiers?Should we have multi-tenant isolation, e.g. another field such as
namespaceortenant_id. It also prevents a super large output, if the user is only interested in a subset of instances.@doujiang24 commented on GitHub (Jan 30, 2026):
Hi @PeaBrane
yeah, matched_tokens is confused,
longest_matchedcould be better, as discussed in:https://github.com/kvcache-ai/Mooncake/pull/1416/changes#diff-43ffc2328e105132991240dc43eb6e4186b327788430b48bf55e1b0b3d804802R61
Is there some benifits that router aware of the
block_size? I haven't figured it out.If there's no benefit, maybe
tokenswould be better?@PeaBrane commented on GitHub (Jan 30, 2026):
@doujiang24 I think it's just for better observability, not strictly needed. The other points may be more important.
On an unrelated note, that may also be a need for a extra_id defaulting to None (or generally an additional optional u64) that translates to a salt hash in the indexer. That would be potentially beneficial for, say, a promise to keep hash lookups of different "customers" separate.
@yejj710 commented on GitHub (Jan 30, 2026):
@PeaBrane Thank you for your comments. Here are some of my thoughts:
q1: Standardizing hashing algo brings many benefits, such as reducing the network overhead caused by passing token ids. The challenge lies in promoting different communities to adopt this practice. This involves: 1. Initializing root_hash in the inference framework, 2. Hashing algorithms, and 3. block_hash calculation in kv-events (vllm's kv-events use the
maybe_convert_block_hashfunction by default to convert the hashed result as the block_hash value).q2: The KV indexer will calculates the hash based on tokens (during the
/queryAPI). I think for a service, supporting more APIs might not be a bad thing?q3: Under the current design, service discovery and heartbeat support are required, and I think that the service registration of the indexer is handled by the router. Therefore,
engine_nameis merely an alias for a inference service instance, which facilitates retrieval and look-up operations. I plan to first implement this through a simple/registerAPI, with preliminary ideas like:Actually, I’m not familiar with this part. Do you have a better design in mind?
q4: This has been changed to
longest_matched, meaning exactly as you described: the maximum number of hits across all data parallel instances and memory levels. I’ll make the updates later.q5: The multi-tenant scenario is reasonable, and I prefer using the term
tenant_id@PeaBrane commented on GitHub (Jan 30, 2026):
@yejj710 Thank you.
Yea that is the unfortunate part, so I think this is a good opportunity to set a community contract for them to follow. At the minimum, to even use the /query-by-hash, there would be an assumption of the hashing algo used for the blocks, iiuc, and I think it would be good to define that here.
In addition, internally, I'm guessing the KvIndexer would need to rely on a unique hash for each sequence block, which I'm assuming would need to be a rolling hash of the block hashes (calling it sequence hash from here on). This is something the engines could also compute themselves and emit as KvEvents (as currently done), and we don't technically need to control that. But if we can enforce that rolling hash algo for the seq hashes as well, it would enable a lot of optimization opportunities for the indexer itself. So it would be nice to define/standardize that here as well.
For reference, dynamo uses xxh3-64 for both block hash and sequence hash computations, with a seed of 1337. We can standardize something like this.
I think I'm fine with that
I'm not an expert, but can refer to the dynamo discovery and mechanism. But API wise, I think
/registershould be fine.@doujiang24 commented on GitHub (Jan 31, 2026):
Agreed, a standard hashing algo could be better. xxh3-64 used in dynamo sounds like a good choice.
My thoughts:
@PeaBrane @yejj710 Thoughts?
@doujiang24 commented on GitHub (Jan 31, 2026):
yeah,
engine_nameis confusing.publisher-unique-namemight be better, it is a unique name for a kv-events publisher endpoint in the engine side.Also, router and kv-indexer shared the same dynamic discovery mechanism, so the router also know the
publisher-unique-name, then it can map thepublisher-unique-nameto the endpoint(ip:port) for inferencing, and other metadata etc.If I remember correctly, each dp-rank have a unique kvevents zmq publisher endpoint.
And for the dynamic discovery mechanism, It should be extensible, as there may be multiple different implementations.
@doujiang24 commented on GitHub (Jan 31, 2026):
Doe the multi-tenant means
modelin the inference API and/queryAPI, but they have two group of engine instances, so we need thetenant_idin the/queryAPI?For 1: it might not be reasonable.
For 2: it might make sense, it could be an option field, not a requirement. As a use case, we do use different
modelwhen users do not want to share engine instances.@doujiang24 commented on GitHub (Jan 31, 2026):
Does it mean: two users shared the same engine instances, but they do not want to share the kvcache?
Or is this some kind of security consideration?
@yejj710 commented on GitHub (Jan 31, 2026):
@PeaBrane thanks
The idea of salt hash sounds like a very reasonable use case. I will add the
cache_saltattribute for this scenario.yeah, that's how the code is currently implemented. Maybe starting with standardization between
KvIndexerand therouterwould be a good first step.@yejj710 commented on GitHub (Jan 31, 2026):
Totally agree
This name is clearer than
engine_name, but there is another issue to consider: under Hybrid DPLB and Internal DPLB deployment modes, the router cannot accurately dispatch requests to specific DP ranks, and thus cannot maintain this map. In other words, does the router need to consider both the Hybrid DPLB and Internal DPLB deployment modes?@doujiang24
Yes, the specific implementation details in vLLM can be viewed at: https://github.com/vllm-project/vllm/blob/main/vllm/distributed/kv_events.py#L294
@PeaBrane commented on GitHub (Jan 31, 2026):
@doujiang24 @yejj710 Thanks all for the discussions. I think we are basically aligned now.
Yes, exactly. We can internally compute the correct seq hash with
token_idsinfo at current. But once the hashing algo is enforced,token_idscan be removed (which would save a lot of BW on the publisher / relay end). Additionally, could also have the engine emitblock_idxas well (position of the block in the seq), but that is not strictly needed, but would make the indexer's life easier.Yes this is right, this would prevent the user from getting a huge output from the indexer response if they are only interested in a group of deployments.
Yes it's something like that. Technically, if the indexer is implemented correctly, we would always have logical separation of blocks, e.g. a customer cannot easily query the blocks of another customer (or get hints about them). This additional salt_cache is more as an additional guarantee that we are really "keeping the blocks of separate customers separate", so there is less surface of bugs that may introduce security vunerabilites etc. Additionally, if customer A is bloating up its radix tree really badly, it would not affect the radix traversal of customer B at all (but this gets into indexer impl details now).
@doujiang24 commented on GitHub (Jan 31, 2026):
@yejj710 Could you please describe the details?
If the router could not determine the dp-rank, them the dp-rank info in kvevents is useless for router.
@doujiang24 commented on GitHub (Jan 31, 2026):
Sounds great.
In my opinion, each
tenant_id+model+lora_namewill have a seperated radix tree, customers are logical separation as expected.But, I do agree that the
saltcould be a additional security improvement, and it should be optional, same astenant_id, to make people easier to use.@doujiang24 commented on GitHub (Jan 31, 2026):
Also, a question about the implementaion.
For a new kvevent, we are inserting it to the existing radix tree.
What should we do, when there are multiple blocks with the same hash matched the
parent_block_hashin the kvevent?Append the new block in kvevent to both of the multiple blocks?
@PeaBrane @yejj710 Thoughts?
So, the
block_idxcould be useful to filter the block.@PeaBrane commented on GitHub (Jan 31, 2026):
@doujiang24 I may have misunderstood the question, so please let me know.
Currently, I think for all 3 engines (vllm, sglang, trtllm), the behavior is that if there is a chain of blocks stored, there would be a kv event with a parent, then the list of blocks in sequential order. So the likely scenario here is a prefill event. And that sequential ordering is a strong assumption we make in the dynamo kv indexer. If we have the
block_idx, then that sequential condition can be relaxed, and maybe theparent_block_hashinfo can also be unnecessary.@doujiang24 commented on GitHub (Feb 1, 2026):
Sorry @PeaBrane , I forgot an important detail, the question is how we handle hash collisions.
There must be a case where two different blocks have the same hash in the radix tree, as you mentioned,
lookup[worker][parent_hash]inhttps://github.com/ai-dynamo/dynamo/pull/5785/changes#diff-a35c64d704a831a90b2313bcc9005b21ef3f581e2b532487f2ce550959984194R121
@PeaBrane commented on GitHub (Feb 1, 2026):
@doujiang24 Ah, I see. I wasn't originally intending for
block_idxto solve the hash collision problem (more to get rid of the radix structure). But you are right, this would indeed be an important additional benefit that I glossed over 😄(Checking with Gemini, for a 64-bit hash, around ~5 billion blocks, hash collision prob becomes ~50%. Sounds within data-server inference scale, so in fact a legit concern. Glad you brought it up. More motivation for kv events to emit
block_idxthen)@doujiang24 commented on GitHub (Feb 2, 2026):
Yep, two blocks with hash collision, will leads to two duplicated sub trees eventually (when we choose to append the new blocks to both of them).
@PeaBrane Two more questions:
@PeaBrane commented on GitHub (Feb 2, 2026):
@doujiang24
@doujiang24 commented on GitHub (Feb 2, 2026):
@PeaBrane Not entirely.
We are discussing the kvevents produced by Mooncake here: https://github.com/kvcache-ai/Mooncake/pull/1306/changes#diff-54037b18eb12d9bdb23a1116313dcef631ba2da82e33e9c02c69c47aef137ddcR107-R122
Would you like to create an RFC page, both in Mooncake, ai-dynamo or sglang/vllm is okay, we can refer it as always.
@PeaBrane commented on GitHub (Feb 2, 2026):
@doujiang24 I'm ok with any as well. In mooncake is fine if it's easier. Would you like to take the lead on it?
@yejj710 commented on GitHub (Feb 2, 2026):
@doujiang24 Sorry for the late reply
Thanks for the reminder. I hadn’t considered the hash collision issue before. Using
block_idxto avoid collisions sounds reasonable.we know that the llm
API serveris the service entry point of the engine and also the destination for router-dispatched requests. Suppose we have two nodes and want to run DP=4Only Node 0 will launch the API server, so the router can only route requests to Node 0.
Each node will launch an API server, and DPLB is performed within the node. In other words, the router can schedule tasks to the node but cannot specify which DP (except for nodes that deploy a single DP rank) within the node to schedule to.
Based on the above, we need to provide a unique identifier for each API server (I've tentatively named it
engine_name).Therefore, I’d like to align with everyone on whether we need to consider the two special scenarios of hybrid DPLB and internal DPLB. The foundational setup is very important. What is your opinion? @PeaBrane @doujiang24
@doujiang24 commented on GitHub (Feb 2, 2026):
@PeaBrane Nope, you and Ishan could be better, you guys are the experts, I don't have too much time recently, I'm happy to contribute comments.
@doujiang24 commented on GitHub (Feb 2, 2026):
@yejj710 For
Internal DPLBandHybrid DPLB, the external router can not determine DP rank, right? I don't think it's a good solution.@yejj710 commented on GitHub (Feb 2, 2026):
yeah, it cannot control which specific DP rank the request is sent to.
I think so. Then using
publisher-unique-nameshould be good enough. I believe we’re aligned now.@yejj710 commented on GitHub (Feb 2, 2026):
Sorry, I almost forgot that mooncake also needs a corresponding publisher mechanism. Perhaps using
api_server_unique_namewould be more appropriate?@doujiang24 commented on GitHub (Feb 4, 2026):
I'm not sure I understand it, do you have a whole picture to describe the
api_server_unique_name, where it come from? who/how to use it?@yejj710 commented on GitHub (Feb 4, 2026):
As previously discussed, each dp-rank have a unique kvevents zmq publisher endpoint and a unique API server endpoint.
BTW, mooncake will have a unique kvevents zmq publisher endpoint.
I just thought the name
api_server_unique_namemight be more precise. The meaning it represents is same with the description here.@doujiang24 commented on GitHub (Feb 5, 2026):
@yejj710 Could you please describe a bit about the workflow, where it come from? And who/how to use it?
@yejj710 commented on GitHub (Feb 6, 2026):
@doujiang24 Sorry for the late reply, I've been a bit busy recently.
Regarding the shared dynamic service discovery for kv-indexer and router, I believe we have already reached a consensus, but some details do not seem to be fully aligned yet.
In current practice, the service discovery for the kv-indexer should be driven by the router (the service registration action is performed by the router). As for the registration and discovery of inference services, this should be handled by the router and the inference engine. Against this background, here is how a new inference service
Ais discovered by the kv-indexer and router:Aand the router completed service registration and discovery, and the router thereby learned aboutA'sAPI server endpointand the correspondingpublisher endpointAPI server endpointand other metadata (including thepublisher endpoint) for inferenceAs for the construction method of the unique ID, I prefer
Api server endpointrelated. Its meaning is more accurate and has stronger extensibility compared topublisher endpointrelated:publisher-unique-namewould not meet the requirements@doujiang24 commented on GitHub (Feb 7, 2026):
@yejj710 Okay, I see what you mean, I don't think it works for sglang, since a single sglang instance have mutiple kvevent publishers, but a single api server endpoints, thoughts?
@yejj710 commented on GitHub (Feb 9, 2026):
@doujiang24
Does
multiple publishershere refer to multiple dp ranks within a single instance, or to something likemulti-publisherin hierarchical storage?We can make some small adjustments to make it compatible, for example, the information maintained on the router side is roughly based on the following structure:
The router registers this information with the kv-indexer. And then, the kv-indexer can bind events from
[tcp://1.1.1.1:5557, tcp:1.1.1.1:6667, ..]to the engine_unique_id. What's your opinion?@PeaBrane commented on GitHub (Feb 25, 2026):
@yejj710 @doujiang24 We have an MVP of a standalone KV indexer HTTP service up in dynamo: https://github.com/ai-dynamo/dynamo/pull/6569. The API follows the conventions discussed here (/register, /unregister, /query, /query_by_hash) and works with bare vLLM and SGLang ZMQ event streams. Feedback welcome!
For the instance_id discussion — in our implementation we went with an opaque instance_id that the caller provides, with per-dp_rank ZMQ endpoints registered separately under the same instance_id. The indexer returns per-dp_rank scores nested under instance_id, and it's the router's responsibility to decide how to aggregate based on the DPLB mode. This keeps the indexer agnostic to how engines are deployed.
@doujiang24 commented on GitHub (Mar 3, 2026):
@yejj710
these fields in query APIs, seems make not sense?
tenant_idin response does not make sense?tenant_idmeans a filter, there won't be othertenant_idin the response.@doujiang24 commented on GitHub (Mar 3, 2026):
@yejj710 @PeaBrane
For
/registerapi, we'd better register a model with multiple endpoints? i.e.@yejj710 commented on GitHub (Mar 4, 2026):
@doujiang24
for q1:
block_sizeis important, same model with differentblock_sizewill have different prefix hit;yes,
instance_idis a redundant fields,tenant_idis already sufficientfor q2: yes, I will delete this layer of nesting
does it mean that a single /register call registers kv-publishers for all dp_ranks? sounds reasonable
@doujiang24 commented on GitHub (Mar 5, 2026):
@yejj710
How does the kvindexer hanlde different
block_size? could you please explain the workflow?Yes.
@PeaBrane commented on GitHub (Mar 6, 2026):
@doujiang24 @yejj710
I'm guessing two radix trees with different block sizes can live in the same indexer microservice. We assume this heterogeneous design in the current dynamo impl
@yejj710 commented on GitHub (Mar 6, 2026):
@doujiang24
As @PeaBrane mentioned, currently there are eight dimensions that affect the identifier: (
model_name, block_size, additional_salt, lora_name, tenant_id, instance_id/backend_id, medium, dp_rank).Regarding how to identify a model service instance in the indexer, I think six dimensions are sufficient (
tenant_id, instance_id, model_name, lora_name, block_size, additional_salt).Therefore, in the current implementation, a model service instance is uniquely identified using a 6-tuple. This constraint is relatively loose, allowing multiple identical model services to be managed through a single indexer; it also allows different tenants to deploy model using different block_size, and the indexer will internally maintain an independent radix-tree or nested map based on any change in one of the six dimensions.
@github-actions[bot] commented on GitHub (Jun 9, 2026):
This issue has had no activity for 90 days and will be closed in 7 days if there is no further activity. Please comment or react if it should stay open.
@github-actions[bot] commented on GitHub (Jun 16, 2026):
Closing due to 3 months of inactivity. If this is still relevant, please comment and we can reopen.