mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #1645] [RFC]: Hard Pin and Upsert for Mooncake Store #483
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#483
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 @00fish0 on GitHub (Mar 10, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/1645
Originally assigned to: @ykwd on GitHub.
1. Summary
This RFC proposes two independent, composable features to enhance Mooncake Store for model weight storage workloads:
ReplicateConfig.Both features are designed as orthogonal primitives. They can be used independently or together, preserving flexibility for reinforcement learning, model management, and hybrid KVCache + model weight workloads.
The rest of this document first explains the design of the two features, then summarizes implementation impact, client APIs, configuration, compatibility, and testing.
2. Design
2.1 Design Principles
replicas_,ReplicaStatus,processing_keys,DiscardedReplicas) wherever possible.REPLICA_IS_NOT_READY, consistent with a key mid-PutStart. This is acceptable for model weight workloads where updates are coordinated and inference is paused during checkpoint loading. See §2.3.8 for a detailed discussion of this trade-off.2.2 Hard Pin
2.2.1 Data Model
Add a
hard_pinnedboolean toObjectMetadata:Why a boolean instead of a timeout?
2.2.2 Setting Hard Pin
Hard pin is set at creation time via
ReplicateConfig:When
with_hard_pin=true, the object is hard-pinned from creation. This is the common path for model weight storage. Hard pin state is preserved across Upsert operations.To unpin a hard-pinned object, the user must
Removeit and re-create it withoutwith_hard_pin.2.2.3 Eviction Behavior
The eviction loop in
BatchEvict()currently has two passes:allow_evict_soft_pinned_objects_is true)With hard pin, the change is minimal — add a single check at the top of the iteration:
The eviction priority becomes:
2.2.4 HA mode Serialization
The
MetadataSerializermust persist thehard_pinnedfield so that hard pin state survives master restarts. This is a single boolean field appended to the existing msgpack serialization.2.3 Upsert (In-Place Update / Delete-and-Reallocate)
2.3.1 Core Insight
Instead of a COW approach that requires 2x memory (old + new data coexist), Upsert takes a simpler, more memory-efficient approach:
DiscardedReplicasfor delayed release, then proceed with the new Upsert.This design reuses existing mechanisms with minimal changes:
ReplicaStatusstate machineprocessing_keyssetDiscardedReplicas+ TTLDiscardExpiredProcessingReplicasRequired metadata changes:
ObjectMetadata::put_start_timemust become non-const— refreshed tonowon in-place UpsertStart, soDiscardExpiredProcessingReplicastimes out the current write cycle, not the original creation time.ObjectMetadata::client_idmust become non-const— updated to the new client on in-place UpsertStart.ObjectMetadata::sizeremainsconst— when size changes, the metadata entry is deleted and recreated.Replicaneeds new methods:mark_processing()— transitions COMPLETE → PROCESSING for in-place updates.is_processing()/fn_is_processing()— predicate for filtering PROCESSING replicas during preemption.is_busy()/fn_is_busy()— checksrefcnt > 0, used as a safety gate before allowing Case B/C.New error code:
OBJECT_REPLICA_BUSY(-714) — returned when UpsertStart finds replicas with non-zero refcnt (active RDMA reads in flight). The client should retry after readers finish.No changes are needed to eviction logic or any other existing mechanism.
2.3.2 API Design
Master Service API
Following the existing
PutStart/PutEnd/PutRevokepattern:UpsertEndandUpsertRevokedelegate toPutEndandPutRevoke— no special logic is needed. The batch variants follow the same delegation pattern (BatchUpsertEnd→BatchPutEnd, etc.).Client Service API (C++)
PyClient / RealClient / DummyClient API
The upsert API surface mirrors the existing
putfamily. Eachputvariant has anupsertcounterpart:put(key, span)upsert(key, span)put_from(key, buffer, size)upsert_from(key, buffer, size)put_parts(key, spans)upsert_parts(key, spans)put_batch(keys, spans)upsert_batch(keys, spans)batch_put_from(keys, buffers, sizes)batch_upsert_from(keys, buffers, sizes)All variants accept an optional
ReplicateConfigparameter.Python Binding API
Raw bytes — mirrors
put/put_from/put_batch/batch_put_from/put_parts:Tensor — mirrors
put_tensor/put_tensor_from/batch_put_tensor/batch_put_tensor_from:Tensor with configurable replication (upsert-only, no put counterpart):
2.3.3 Client Call Path
The upsert call path mirrors
Put, with 6 layers from Python down to the master service:The middle layers (Client Service transfer phase, Master Client, RPC Service) are structurally identical to the
Putpath. The only new logic is inMasterService::UpsertStart(§2.3.5) and theBatchUpsertpipeline (§2.3.4).2.3.4 BatchUpsert Pipeline
BatchUpsertuses the same pipeline architecture asBatchPut, reusing its transfer infrastructure. The pipeline replaces a serial loop (3N RPCs + N serial RDMA writes) with batched RPCs and parallel RDMA transfers:The implementation adds two thin wrapper methods (
StartBatchUpsert,FinalizeBatchUpsert) that call the upsert-specific RPCs. The data transfer phase (SubmitTransfers,WaitForTransfers) and operation state tracking (PutOperationclass) are reused fromBatchPutwithout modification — they are operation-agnostic.2.3.5 Detailed Flow
UpsertStart(client_id, key, slice_length, config):
Client transfers data (identical to Put — TransferWrite via RDMA/TCP)
UpsertEnd(client_id, key, replica_type):
PutEnd— mark PROCESSING → COMPLETE, grant lease, remove from processing_keys.UpsertRevoke(client_id, key, replica_type):
PutRevoke— erases all PROCESSING replicas of the given type, cleans up metadata if empty.UpsertStartreuses existing buffers by marking COMPLETE replicas as PROCESSING. If the client's transfer fails and it callsUpsertRevoke,PutRevokeerases these replicas — the old data is lost. This is a deliberate trade-off: in-place update avoids 2x memory overhead but sacrifices rollback capability. For model weight workloads, this is acceptable because the source data (GPU/CPU memory) is still available and the client can retry the upsert.2.3.6 Swimlane Diagrams
In-Place Update Path (key exists, size unchanged)
Delete-and-Reallocate Path (key exists, size changed)
Preemption Path (key has in-progress write)
2.3.7 Failure Handling
DiscardExpiredProcessingReplicaswill clean up afterput_start_discard_timeout_sec_, same as forPutStart. The newly allocated replicas are freed, and the metadata entry is removed.DiscardExpiredProcessingReplicasremoves the entire metadata entry — old data is lost. This is the same trade-off described in UpsertRevoke above: no rollback for in-place updates.PutRevokeerases the newly allocated PROCESSING replicas. For Case C, the old replicas were already moved toDiscardedReplicasand will be freed after TTL. The key effectively disappears until the client retries.PutRevokeerases the reused PROCESSING replicas — old data is lost. The client must retry from source. See "Known Limitations" below.DiscardedReplicas, but new allocation fails. The key's data is lost. The client can retry.ILLEGAL_CLIENT(if metadata was reused with a new client_id) orOBJECT_NOT_FOUND(if metadata was erased and recreated). The preempted client should treat this as a no-op — the new client has taken over the key.OBJECT_HAS_REPLICATION_TASK. The client should retry after the replication task completes.OBJECT_REPLICA_BUSY. The client should retry after readers release their references. Note: this only blocks Case B/C; if the key is mid-write (in processing_keys), preemption proceeds regardless of refcnt on the PROCESSING replicas.2.3.8 Read Visibility During Upsert
During an in-place update (Case B), all replicas are marked PROCESSING.
GetReplicaListonly returns COMPLETE replicas, so the key is temporarily unreadable — readers receiveREPLICA_IS_NOT_READYuntilUpsertEndmarks them COMPLETE again.This is a deliberate design choice to avoid read-write data races on RDMA buffers. The alternatives and their trade-offs:
For model weight workloads, the current approach is acceptable because:
For workloads that require read availability during updates, COW semantics should be considered in a future RFC.
2.3.9 Known Limitations
replica_numthan the existing object, Case B only checks size equality and reuses existing replicas. The new replica count is silently ignored.put_start_requests/put_end_requestsfor upsert operations.4. Client-Side API
The full API surface is defined in §2.3.2. Below are usage examples.
C++
Python
5. Configuration
No new configuration parameters are required. Hard pin is a per-object attribute set via
ReplicateConfig.with_hard_pinat creation time. All existing configuration parameters retain their current behavior.6. Migration and Backward Compatibility
ReplicateConfiggains one new boolean field (with_hard_pin, defaultfalse). Old clients sending the old format will default tofalse— no hard pin, existing behavior preserved.hard_pinnedfield is appended to the serializedObjectMetadata. On deserialization, if the field is absent (old snapshot), it defaults tofalse.Put,Get,Remove,PutStart/PutEnd/PutRevokeare unchanged. No existing behavior is modified.Before submitting a new issue...
@0oshowero0 commented on GitHub (Mar 13, 2026):
Excellent work! This RFC is critically important for RL scenarios.
I have a minor question regarding the API: Mooncake Store provides the standard
put/get(and their batch variants) interfaces, as well as thebatch_put_tensor/batch_get_tensorinterfaces. Since the latter set of interfaces doesn't acceptReplicateConfigas an input parameter, it seems they won't be able to benefit from this RFC directly. What is your recommended approach for RL workloads, where we primarily deal with tensor transport?@0oshowero0 commented on GitHub (Mar 13, 2026):
BTW there is a typo in documentation website :)
https://kvcache-ai.github.io/Mooncake/python-api-reference/mooncake-store.html#id1
@00fish0 commented on GitHub (Mar 13, 2026):
Could we use batch_pub_tensor?
https://kvcache-ai.github.io/Mooncake/python-api-reference/mooncake-store.html#batch-pub-tensor
Thanks for the sharp eyes! I'll fix the typo in the documentation shortly.
@0oshowero0 commented on GitHub (Mar 16, 2026):
That will be great! Another RL related issue is that we don't have an API for
batch_removeyet. Sometimesremove_by_regexis a little bit coarse-grained. Is there any plan for supporting it?@ykwd commented on GitHub (Mar 16, 2026):
@0oshowero0 We can certainly add it if needed. However, the reason we introduced the upsert API was to replace the remove + put style of weight updates in RL scenarios. If the upsert API is available, would the
batch_removeAPI still be necessary?@0oshowero0 commented on GitHub (Mar 18, 2026):
Use upsert API to replace remove+put is a good design choice for cache-like behavior control, but in RL we prefer a regular key-value based storage. In that case, maybe a
batch_removeis more natural.@he-yufeng commented on GitHub (Mar 23, 2026):
Hey, I'd like to pick up the Hard Pin portion (P0 in the RFC). PR #1662 covers UpsertStart/End/Revoke but not the hard pin mechanism, so this should be a clean split.
My plan:
with_hard_pintoReplicateConfighard_pinnedboolean toObjectMetadata(set at creation time viaPutStart)BatchEvict()hard_pinnedin snapshot serialization (backward compatible)Will put up a PR shortly.
@github-actions[bot] commented on GitHub (Jul 1, 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 (Jul 9, 2026):
Closing due to 3 months of inactivity. If this is still relevant, please comment and we can reopen.