Skip to content

Add file-level model weight cache control plane#2829

Open
xianzhiT wants to merge 1 commit into
kvcache-ai:mainfrom
xianzhiT:feature/file-level-weight-cache-v2
Open

Add file-level model weight cache control plane#2829
xianzhiT wants to merge 1 commit into
kvcache-ai:mainfrom
xianzhiT:feature/file-level-weight-cache-v2

Conversation

@xianzhiT

@xianzhiT xianzhiT commented Jul 10, 2026

Copy link
Copy Markdown

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

  • Python Wheel (mooncake-wheel)
  • Docs

Type of Change

  • New feature

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 -v

End-to-end (cross-machine, one storage node + one 8×H20 node):

# import a checkpoint into MooncakeStore
python -m mooncake.weight_store.cli \
    --master-server-addr <master_ip:50051> \
    --metadata-server <metadata_ip:8080> \
    --protocol rdma --rdma-devices <mlx5_x> \
    --local-hostname <host_ip:port> \
    model import \
    --checkpoint-id Qwen3-235B-A22B \
    --model-id Qwen/Qwen3-235B-A22B \
    --revision main \
    --source /path/to/local/weights

# confirm it landed
python -m mooncake.weight_store.cli ... model list
python -m mooncake.weight_store.cli ... model inspect Qwen3-235B-A22B
python -m mooncake.weight_store.cli ... model verify  Qwen3-235B-A22B

Then loaded Qwen3-235B-A22B from the store via the SGLang connector in ~30s
with correct inference output.

Test results:

  • Unit tests pass
  • Integration tests pass (if applicable)
  • Manual testing done (cross-machine import + SGLang load + inference, above)

Checklist

  • I have performed a self-review of my own code
  • I have formatted my code using ./scripts/code_format.sh
  • I have run pre-commit run --all-files and all hooks pass
  • I have updated the documentation (if applicable)
  • I have added tests to prove my changes are effective
  • For changes >500 LOC: I have filed an RFC issue ([RFC] Unified KVCache and Model Weight Management in Mooncake Store #2282)

AI Assistance Disclosure

  • AI tools were used (specify below)

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.

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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).

Suggested change
if result != 0:
if result not in (0, None):

Comment on lines +205 to +209
units = {
"KB": 1024,
"MB": 1024**2,
"GB": 1024**3,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Adding support for Terabytes (TB) and Petabytes (PB) to the size parser makes it more robust and future-proof, especially when dealing with extremely large models or storage segments.

    units = {
        "KB": 1024,
        "MB": 1024**2,
        "GB": 1024**3,
        "TB": 1024**4,
        "PB": 1024**5,
    }

Comment on lines +25 to +31
WEIGHT_SUFFIXES = {
".bin",
".gguf",
".pt",
".pth",
".safetensors",
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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).

Suggested change
WEIGHT_SUFFIXES = {
".bin",
".gguf",
".pt",
".pth",
".safetensors",
}
WEIGHT_SUFFIXES = {
".bin",
".ckpt",
".gguf",
".pt",
".pth",
".safetensors",
}

Comment on lines +264 to +276
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}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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

Comment on lines +299 to +306
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Comment on lines +317 to +329
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")))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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")))

Comment on lines +391 to +394
except BaseException:
for chunk_key in chunks:
self._remove(chunk_key, force=True)
raise

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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

@github-actions github-actions Bot added documentation Improvements or additions to documentation run-ci Installation Tests labels Jul 10, 2026
@ykwd ykwd requested a review from Aionw July 10, 2026 03:32
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

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:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a//b a valid value here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation Installation run-ci Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants