Add file-level model weight cache control plane#2829
Conversation
Add a file-level weight caching control plane on top of MooncakeStore, so model checkpoints can be imported into the store and served to inference engines (e.g. SGLang) without a tensor-level, GPU-coupled save path. - mooncake/weight_store/: import/list/verify/delete/inspect a HuggingFace safetensors checkpoint as file-level chunks with a per-model manifest (cli.py, model.py, model_keyspace.py, __init__.py). - Store weights as fixed-size chunks with a manifest describing files, sizes, sha256 digests and chunk keys; supports a config file so server addresses need not be passed on every call. - pyproject.toml: expose the `mooncake_weight_store` console entry point. - tests: weight store model cache and CLI tests. - docs: file-level weight cache control-plane design and the SGLang file connector contract.
There was a problem hiding this comment.
Code Review
This pull request introduces an experimental file-level model weight cache control plane for Mooncake Store, adding a command-line interface, a model cache client, keyspace helpers, and comprehensive unit tests. The implementation is solid, but several improvements should be made to enhance robustness, correctness, and performance. Specifically, the store setup result check should safely handle None returns, and the size parser should support TB and PB units. Additionally, file materialization should write to a temporary file and atomically rename it to prevent corruption, index updates should be made atomic to avoid race conditions during concurrent imports, redundant store existence checks should be removed to optimize round-trips, and chunk cleanup during import failures should be wrapped in try-except blocks to ensure all orphaned chunks are removed.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| except TypeError: | ||
| result = store.setup(config) | ||
|
|
||
| if result != 0: |
There was a problem hiding this comment.
The setup method of MooncakeDistributedStore might return None on success in some environments or wrappers. Checking result != 0 could lead to a false-positive RuntimeError if None is returned. It is safer and more consistent with the rest of the codebase to check result not in (0, None).
| if result != 0: | |
| if result not in (0, None): |
| units = { | ||
| "KB": 1024, | ||
| "MB": 1024**2, | ||
| "GB": 1024**3, | ||
| } |
There was a problem hiding this comment.
| WEIGHT_SUFFIXES = { | ||
| ".bin", | ||
| ".gguf", | ||
| ".pt", | ||
| ".pth", | ||
| ".safetensors", | ||
| } |
There was a problem hiding this comment.
Consider adding .ckpt to the list of recognized weight suffixes. It is a very common format used by many models (e.g., Stable Diffusion checkpoints).
| WEIGHT_SUFFIXES = { | |
| ".bin", | |
| ".gguf", | |
| ".pt", | |
| ".pth", | |
| ".safetensors", | |
| } | |
| WEIGHT_SUFFIXES = { | |
| ".bin", | |
| ".ckpt", | |
| ".gguf", | |
| ".pt", | |
| ".pth", | |
| ".safetensors", | |
| } |
| with output.open("wb") as output_file: | ||
| for chunk_key in self._record_chunks(record): | ||
| chunk = self._get(chunk_key) | ||
| output_file.write(chunk) | ||
| digest.update(chunk) | ||
| size += len(chunk) | ||
| if size != record.size: | ||
| raise ValueError( | ||
| f"file size mismatch for {record.path}: " | ||
| f"expected {record.size}, got {size}" | ||
| ) | ||
| if digest.hexdigest() != record.sha256: | ||
| raise ValueError(f"sha256 mismatch for {record.path}") |
There was a problem hiding this comment.
Writing directly to the target output_path can leave a partially written or corrupted file if the operation is interrupted or fails halfway through. A more robust pattern is to write to a temporary file in the same directory and atomically rename it to the target path once the size and checksum verification succeeds.
| with output.open("wb") as output_file: | |
| for chunk_key in self._record_chunks(record): | |
| chunk = self._get(chunk_key) | |
| output_file.write(chunk) | |
| digest.update(chunk) | |
| size += len(chunk) | |
| if size != record.size: | |
| raise ValueError( | |
| f"file size mismatch for {record.path}: " | |
| f"expected {record.size}, got {size}" | |
| ) | |
| if digest.hexdigest() != record.sha256: | |
| raise ValueError(f"sha256 mismatch for {record.path}") | |
| temp_output = output.with_suffix(f".{time.time_ns()}.tmp") | |
| try: | |
| with temp_output.open("wb") as output_file: | |
| for chunk_key in self._record_chunks(record): | |
| chunk = self._get(chunk_key) | |
| output_file.write(chunk) | |
| digest.update(chunk) | |
| size += len(chunk) | |
| if size != record.size: | |
| raise ValueError( | |
| f"file size mismatch for {record.path}: " | |
| f"expected {record.size}, got {size}" | |
| ) | |
| if digest.hexdigest() != record.sha256: | |
| raise ValueError(f"sha256 mismatch for {record.path}") | |
| temp_output.rename(output) | |
| except BaseException: | |
| if temp_output.exists(): | |
| temp_output.unlink() | |
| raise |
| def _add_to_indexes(self, manifest: ModelFileManifest) -> None: | ||
| all_models = self._read_index(model_index_key()) | ||
| all_models.add(manifest.checkpoint_id) | ||
| self._write_index(model_index_key(), all_models) | ||
|
|
||
| per_model = self._read_index(model_id_index_key(manifest.model_id)) | ||
| per_model.add(manifest.checkpoint_id) | ||
| self._write_index(model_id_index_key(manifest.model_id), per_model) |
There was a problem hiding this comment.
The read-modify-write cycle for updating index files (weight/index/models and weight/index/model/{sha256}) is not atomic. If multiple clients import checkpoints concurrently, they can overwrite each other's updates, leading to lost index entries. Consider using a proper metadata database/catalog backend or prefix-based key listing if supported by the store to avoid a single global index object.
| def _read_index(self, key: str) -> set[str]: | ||
| if not self._exists(key): | ||
| return set() | ||
| value = self.store.get(key) | ||
| if value is None: | ||
| return set() | ||
| if isinstance(value, str): | ||
| payload = value.encode("utf-8") | ||
| else: | ||
| payload = bytes(value) | ||
| if not payload: | ||
| return set() | ||
| return set(json.loads(payload.decode("utf-8"))) |
There was a problem hiding this comment.
Calling _exists before get causes an unnecessary extra round-trip to the store. Since self.store.get(key) already returns None if the key does not exist, we can directly call get and handle the None case, which is much more efficient.
def _read_index(self, key: str) -> set[str]:
value = self.store.get(key)
if value is None:
return set()
if isinstance(value, str):
payload = value.encode("utf-8")
else:
payload = bytes(value)
if not payload:
return set()
return set(json.loads(payload.decode("utf-8")))| except BaseException: | ||
| for chunk_key in chunks: | ||
| self._remove(chunk_key, force=True) | ||
| raise |
There was a problem hiding this comment.
If self._remove raises an exception during cleanup (e.g., due to a temporary network issue or store error), the loop will be interrupted, leaving the remaining chunks orphaned in the store. Wrapping the individual chunk deletion in a try...except block ensures that we attempt to clean up as many chunks as possible.
except BaseException:
for chunk_key in chunks:
try:
self._remove(chunk_key, force=True)
except Exception:
pass
raise|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
| return hashlib.sha256(value.encode("utf-8")).hexdigest() | ||
|
|
||
|
|
||
| def _validate_id(name: str, value: str) -> None: |
There was a problem hiding this comment.
Is a//b a valid value here?
Description
Add a file-level weight caching control plane on top of MooncakeStore, so model checkpoints can be imported into the store and served to inference engines (e.g. SGLang) as plain files — without a tensor-level, GPU-coupled save path.
The idea is to treat MooncakeStore as a generic model cache that is decoupled from the inference engine: weights are stored file-level (HuggingFace safetensors chunked with a per-model manifest), and engines load files exactly as they would from disk, only the source changes (disk → MooncakeStore). This control plane provides the management side — import / list / inspect / verify / delete — independently of any serving process.
The matching SGLang connector (data plane) is in sgl-project/sglang#30728. Design discussion / RFC: #2282 (Unified KVCache and Model Weight Management); this PR implements Phase 1 (store-backed weight loading) plus the import/manage control plane.
Module
mooncake-wheel)Type of Change
How Has This Been Tested?
Test commands:
cd mooncake-wheel python -m pytest tests/test_weight_model_cache.py tests/test_weight_model_cli.py -vEnd-to-end (cross-machine, one storage node + one 8×H20 node):
Then loaded Qwen3-235B-A22B from the store via the SGLang connector in ~30s
with correct inference output.
Test results:
Checklist
./scripts/code_format.shpre-commit run --all-filesand all hooks passAI Assistance Disclosure
AI assistance (Claude Code) was used for parts of the implementation, tests, and documentation. All changes have been reviewed and are understood by the human submitter.