diff --git a/.ci/run_test.sh b/.ci/run_test.sh deleted file mode 100644 index afeac3a3..00000000 --- a/.ci/run_test.sh +++ /dev/null @@ -1,51 +0,0 @@ -set -ex -TEST_DIR=$1 -OUTPUT_DIR=$2 - - -run_test(){ - echo "$OUTPUT_DIR" - ./"$1" - LOCAL_SUCCESS=$? - if [[ $LOCAL_SUCCESS == 0 ]]; then - echo "PASSED : $1 " >> "$OUTPUT_DIR"/results.txt - else - echo "FAILED : $1 " >> "$OUTPUT_DIR"/results.txt - fi -} - - -pushd $TEST_DIR -etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://10.0.0.1:2379 & -export MC_GID_INDEX=1 -run_test transport_uint_test -run_test tcp_transport_test -export MC_GID_INDEX=1 - -# sudo pkill etcd -# sleep 1 -# etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://10.0.0.1:2379 & -# sudo fuser -k 14345/tcp -export MC_GID_INDEX=1 && ./rdma_transport_test --mode=target --metadata_server=127.0.0.1:2379 --local_server_name=127.0.0.2:14345 --device_name=erdma_0 & -export MC_GID_INDEX=1 && ./rdma_transport_test --metadata_server=127.0.0.1:2379 --segment_id=127.0.0.2:14345 --local_server_name=127.0.0.3:14346 --device_name=erdma_1 -LOCAL_SUCCESS=$? -if [[ $LOCAL_SUCCESS == 0 ]]; then - echo "PASSED : rdma_transport_test " >> "$OUTPUT_DIR"/results.txt -else - echo "FAILED : rdma_transport_test " >> "$OUTPUT_DIR"/results.txt -fi - -# sudo pkill etcd -# sleep 1 -# etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://10.0.0.1:2379 & -# export MC_GID_INDEX=1 -# sudo fuser -k 15345/tcp -# export MC_GID_INDEX=1 && ./rdma_transport_test --mode=target --metadata_server=127.0.0.1:2379 --local_server_name=127.0.0.2:14345 --device_name=erdma_0 & -export MC_GID_INDEX=1 && ./rdma_transport_test2 --metadata_server=127.0.0.1:2379 --segment_id=127.0.0.2:14345 --local_server_name=127.0.0.3:14346 --device_name=erdma_1 -LOCAL_SUCCESS=$? -if [[ $LOCAL_SUCCESS == 0 ]]; then - echo "PASSED : rdma_transport_test2 " >> "$OUTPUT_DIR"/results.txt -else - echo "FAILED : rdma_transport_test2 " >> "$OUTPUT_DIR"/results.txt -fi -popd \ No newline at end of file diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 00000000..e15c8550 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-marketplace.json", + "name": "mooncake", + "owner": { + "name": "Mooncake Team" + }, + "metadata": { + "description": "Built-in Claude Code skills for working with Mooncake: deployment troubleshooting, local CI validation, and the Python API." + }, + "plugins": [ + { + "name": "mooncake-troubleshoot", + "source": { + "source": "git-subdir", + "url": "https://github.com/kvcache-ai/Mooncake.git", + "path": ".claude/skills/mooncake-troubleshoot" + }, + "description": "Systematically diagnose Mooncake deployment and runtime issues (services, RDMA, env vars, connectivity, logs) and propose fixes.", + "homepage": "https://kvcache-ai.github.io/Mooncake/skills/mooncake-troubleshoot.html" + }, + { + "name": "mooncake-ci-local", + "source": { + "source": "git-subdir", + "url": "https://github.com/kvcache-ai/Mooncake.git", + "path": ".claude/skills/mooncake-ci-local" + }, + "description": "Run Mooncake pre-PR local validation via scripts/run_ci_test.sh, reproducing the reproducible parts of GitHub Actions.", + "homepage": "https://kvcache-ai.github.io/Mooncake/skills/mooncake-ci-local.html" + }, + { + "name": "mooncake-api", + "source": { + "source": "git-subdir", + "url": "https://github.com/kvcache-ai/Mooncake.git", + "path": ".claude/skills/mooncake-api" + }, + "description": "Work with the Mooncake Store, Transfer Engine, and EP/Backend Python APIs.", + "homepage": "https://kvcache-ai.github.io/Mooncake/skills/mooncake-api.html" + } + ] +} diff --git a/.claude/skills/mooncake-api/.claude-plugin/plugin.json b/.claude/skills/mooncake-api/.claude-plugin/plugin.json new file mode 100644 index 00000000..81bad701 --- /dev/null +++ b/.claude/skills/mooncake-api/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "mooncake-api", + "description": "Work with the Mooncake Store, Transfer Engine, and EP/Backend Python APIs.", + "author": { + "name": "Mooncake Team" + }, + "homepage": "https://kvcache-ai.github.io/Mooncake/skills/mooncake-api.html", + "repository": "https://github.com/kvcache-ai/Mooncake", + "license": "Apache-2.0", + "keywords": ["mooncake", "python", "api", "transfer-engine", "store"] +} diff --git a/.claude/skills/mooncake-api/SKILL.md b/.claude/skills/mooncake-api/SKILL.md index daa1b6c5..d0edb73a 100644 --- a/.claude/skills/mooncake-api/SKILL.md +++ b/.claude/skills/mooncake-api/SKILL.md @@ -1,4 +1,7 @@ - +--- +name: mooncake-api +description: Help users work with the Mooncake Python APIs for distributed storage and high-performance data transfer. Use when working with Mooncake Store (distributed KV cache), Transfer Engine (RDMA/TCP transfers), service setup (master, metadata server), PyTorch tensors in the Store, zero-copy/buffer management, batch operations and replication, or Mooncake EP / Backend (Expert Parallelism). Trigger on questions about MooncakeDistributedStore, TransferEngine, put/get, put_tensor, register_buffer, ReplicateConfig, or the mooncake.store / mooncake.engine / mooncake.pg Python modules. +--- # Mooncake Python API Skill diff --git a/.claude/skills/mooncake-ci-local/.claude-plugin/plugin.json b/.claude/skills/mooncake-ci-local/.claude-plugin/plugin.json new file mode 100644 index 00000000..afb3699b --- /dev/null +++ b/.claude/skills/mooncake-ci-local/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "mooncake-ci-local", + "description": "Run Mooncake pre-PR local validation via scripts/run_ci_test.sh, reproducing the reproducible parts of GitHub Actions.", + "author": { + "name": "Mooncake Team" + }, + "homepage": "https://kvcache-ai.github.io/Mooncake/skills/mooncake-ci-local.html", + "repository": "https://github.com/kvcache-ai/Mooncake", + "license": "Apache-2.0", + "keywords": ["mooncake", "ci", "validation", "pre-pr"] +} diff --git a/.claude/skills/mooncake-ci-local/SKILL.md b/.claude/skills/mooncake-ci-local/SKILL.md index 0729957a..546bcc24 100644 --- a/.claude/skills/mooncake-ci-local/SKILL.md +++ b/.claude/skills/mooncake-ci-local/SKILL.md @@ -1,266 +1,159 @@ --- name: mooncake-ci-local -description: Run Mooncake CI test suite locally — maps GitHub Actions CI steps to local commands. Use this skill whenever the user wants to run tests locally, reproduce a CI failure, check if their changes break tests, or run any subset of the CI test suite (C++ unit tests via ctest, Python integration tests, code format checks, or the full test pipeline). Trigger on phrases like "run tests", "run CI locally", "reproduce CI failure", "check my changes", "test before PR", "run ctest", "run python tests", "run all tests". +description: Run Mooncake pre-PR local validation through scripts/run_ci_test.sh. Use this skill whenever the user wants to validate a branch before opening or submitting a PR, run local CI, run ci test, check changes before PR, reproduce GitHub Actions locally, or force a full pre-submit verification. Trigger on phrases like "提交 PR 前验证", "run ci test", "run local CI", "check my branch", "test before PR", "pre-submit validation", and "reproduce CI locally". --- -# Mooncake CI Local Test Runner +# Mooncake Pre-PR Local Validation -You help users run the Mooncake CI test suite locally. The CI has three test layers. Map what the user wants to the right layer, check prerequisites, and run the tests. +Use `bash scripts/run_ci_test.sh` as the default entry point. This is the single local lane for PR-before-submit validation, and it already coordinates the reproducible parts of GitHub Actions. -## CI Test Layers +## Default Entry Point -### Layer 1 — C++ Unit Tests (ctest) -**CI equivalent:** `build` job in `ci.yml` — "Test (in build env) with coverage" +When the user asks for any of the following, run the repo script first instead of reconstructing the workflow by hand: -**Prerequisite services:** -```bash -# 1. etcd (port 2379) -etcd --advertise-client-urls http://127.0.0.1:2379 --listen-client-urls http://127.0.0.1:2379 & -sleep 2 -etcdctl --endpoints=http://127.0.0.1:2379 endpoint health # verify +- 提交 PR 前本地验证 +- run ci test +- run local CI +- check my branch before PR +- reproduce CI locally -# 2. HTTP metadata server (port 8080) -cd mooncake-transfer-engine/example/http-metadata-server-python -pip install aiohttp -python ./bootstrap_server.py & -cd - -``` - -**Run:** -```bash -cd build -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib -MC_METADATA_SERVER=http://127.0.0.1:8080/metadata DEFAULT_KV_LEASE_TTL=500 ctest -j --output-on-failure -``` - -**Run specific test:** -```bash -cd build -MC_METADATA_SERVER=http://127.0.0.1:8080/metadata DEFAULT_KV_LEASE_TTL=500 ctest -R --output-on-failure -# List all available tests: ctest -N -``` - -### Layer 2 — Python Integration Tests -**CI equivalent:** `test-wheel-ubuntu` job — `run_tests.sh` - -**Prerequisite:** Mooncake wheel must be installed (either via `pip install` or via `make install` after build). - -**Check install:** -```bash -python -c "import mooncake; print('OK')" -which mooncake_master # must NOT be /usr/local/bin (must be from Python package) -``` - -**Run full suite:** -```bash -# Start metadata server first -mooncake_http_metadata_server --port 8080 & -sleep 1 - -cd mooncake-wheel/tests -MC_METADATA_SERVER=http://127.0.0.1:8080/metadata DEFAULT_KV_LEASE_TTL=500 MC_FORCE_TCP=true \ - bash ../../scripts/run_tests.sh -``` - -**Individual Python tests** (all require metadata server + mooncake_master on port 50051): -```bash -# Setup shared services -mooncake_http_metadata_server --port 8080 & -mooncake_master --default_kv_lease_ttl=500 & -sleep 2 - -cd mooncake-wheel/tests -export MC_METADATA_SERVER=http://127.0.0.1:8080/metadata -export DEFAULT_KV_LEASE_TTL=500 -export MC_FORCE_TCP=true - -# Pick any test: -python test_distributed_object_store.py -python test_replicated_distributed_object_store.py -python test_put_get_tensor.py # requires torch + numpy -python test_safetensor_functions.py # requires safetensors -python test_dummy_client.py -python test_cli.py -python test_distributed_object_store_cxl.py # requires CXL build -``` - -**Transfer engine tests specifically:** -```bash -cd mooncake-wheel/tests -MC_METADATA_SERVER=http://127.0.0.1:8080/metadata MC_FORCE_TCP=true python transfer_engine_target.py & -TARGET_PID=$! -MC_METADATA_SERVER=http://127.0.0.1:8080/metadata MC_FORCE_TCP=true python transfer_engine_initiator_test.py -kill $TARGET_PID -``` - -**Scripts-based tests** (from `test-wheel-ubuntu` job): -```bash -# Tensor API perf test -export MOONCAKE_MASTER="127.0.0.1:50051" -export MOONCAKE_TE_META_DATA_SERVER="http://127.0.0.1:8080/metadata" -export MOONCAKE_PROTOCOL="tcp" -export LOCAL_HOSTNAME="127.0.0.1" -python scripts/test_tensor_api.py -n 1 -python scripts/test_async_store.py -python scripts/test_copy_move_api.py -``` - -### Layer 3 — Static Checks (no services needed) -**CI equivalent:** `clang-format` and `spell-check` jobs - -**Code format (changed files vs main):** -```bash -./scripts/code_format.sh --check --base origin/main -# Auto-fix: -./scripts/code_format.sh --base origin/main -``` - -**Spell check:** -```bash -# Requires typos tool: cargo install typos-cli -typos -``` - -**Pre-commit (runs all hooks):** -```bash -pip install pre-commit -pre-commit run --all-files -# Or just on staged files: -pre-commit run -``` - -## Build Configurations (from CI) - -If the user needs to build first, here are the CI-equivalent cmake flags: - -**Standard build with coverage (mirrors `build` job):** -```bash -mkdir build && cd build -cmake -G Ninja .. -DUSE_HTTP=ON -DUSE_CXL=ON -DUSE_ETCD=ON -DSTORE_USE_ETCD=ON -DENABLE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug -cmake --build . -sudo cmake --install . -``` - -**All features ON (mirrors `build-flags` job):** -```bash -mkdir build && cd build -cmake -G Ninja .. -DUSE_ETCD=ON -DUSE_CXL=ON -DUSE_REDIS=ON -DUSE_HTTP=ON -DWITH_STORE=ON -DWITH_P2P_STORE=ON -DWITH_METRICS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_EXAMPLES=ON -cmake --build . -sudo cmake --install . -``` - -**Transfer engine only:** -```bash -cd mooncake-transfer-engine -mkdir build && cd build -cmake -G Ninja .. -DUSE_ETCD=OFF -DUSE_CXL=ON -DUSE_REDIS=ON -DUSE_HTTP=ON -DBUILD_UNIT_TESTS=ON -DBUILD_EXAMPLES=ON -cmake --build . -``` - -## Workflow: Diagnosing and Running Tests - -### Step 1 — Understand what the user wants - -Ask (or infer from context): -- All tests, or a specific subset? -- Did a specific CI job fail? Which one? -- Is the build already done, or do they need to build first? - -### Step 2 — Check and Fix Prerequisites - -**One-command setup** — this script checks all prerequisites and auto-fixes issues: +Default command: ```bash -bash .claude/skills/mooncake-ci-local/scripts/check-prerequisites.sh +bash scripts/run_ci_test.sh ``` -**What it checks:** -1. ✓ Build directory exists -2. ✓ mooncake package installed (auto-installs via cmake --install if missing) -3. ✓ ctest available -4. ✓ Restarts all services (etcd, metadata server) in clean state -5. ✓ Verifies all services are healthy +What this script already covers: -**If you need to build first:** -```bash -mkdir build && cd build -cmake -G Ninja .. -DUSE_HTTP=ON -DUSE_ETCD=ON -DUSE_CXL=ON -DSTORE_USE_ETCD=ON -DCMAKE_BUILD_TYPE=Debug -cmake --build . -sudo cmake --install . -``` +- GitHub-like `paths-filter` against `origin/main` +- `typos` +- `scripts/code_format.sh --check` +- default CMake configure/build/install in `build-ci-local` +- `ctest` +- wheel build in `build-wheel-local` +- wheel installation validation +- `scripts/run_tests.sh` +- selected Python API and integration tests +- per-stage summary and logs under `local_test/run-ci-logs//` -**If script fails:** -- Build issues: See "Build Configurations" section below -- mooncake install fails: Try `pip install mooncake-wheel/dist/*.whl` manually -- etcd install fails: Download from https://github.com/etcd-io/etcd/releases +## Standard Agent Workflow -### Step 3 — Run and report +1. Run `bash scripts/run_ci_test.sh` from the repo root unless the user explicitly asks for a narrower subset. +2. Read the stage summary instead of dumping raw terminal output. +3. Report these items back to the user: + - passed stages + - failed stages + - blocked stages + - unsupported stages + - whether `paths-filter` skipped downstream stages + - the log directory under `local_test/run-ci-logs/...` +4. If there is a failure, inspect the corresponding stage log and summarize the root cause. -Run the relevant test layer. On failure: -1. Show the exact error message -2. Check if it's a service/env issue (most common) vs a real test failure -3. Suggest the fix (see common issues below) +## Common Options -## Common Local Test Issues - -**"mooncake_master found in /usr/local/bin" error in run_tests.sh:** -The test expects mooncake_master to come from the Python package, not a system install. -```bash -# Remove the system-installed binary: -sudo rm /usr/local/bin/mooncake_master -# Or use the wheel-installed one: -pip install mooncake-wheel/dist/*.whl -``` - -**etcd port conflict:** -```bash -pkill etcd && sleep 1 -etcd --advertise-client-urls http://127.0.0.1:2379 --listen-client-urls http://127.0.0.1:2379 & -``` - -**Metadata server port conflict:** -```bash -pkill -f bootstrap_server.py -pkill -f mooncake_http_metadata_server -``` - -**Tests hang (master not responding):** -```bash -pkill mooncake_master -sleep 2 -mooncake_master --default_kv_lease_ttl=500 & -sleep 1 -``` - -**torch/numpy not installed for tensor tests:** -```bash -pip install torch numpy safetensors packaging -``` - -**ctest shows no tests found:** -```bash -# Rebuild with unit tests enabled: -cd build -cmake .. -DBUILD_UNIT_TESTS=ON -cmake --build . -``` - -## Quick One-Liners +Force a full lane even if `paths-filter` would skip downstream stages: ```bash -# Run ALL C++ tests (after building with etcd + metadata server running): -# Note: full suite takes 5-15 minutes depending on hardware -cd build && MC_METADATA_SERVER=http://127.0.0.1:8080/metadata DEFAULT_KV_LEASE_TTL=500 ctest -j --output-on-failure - -# Run only fast tests (skip slow integration tests): -cd build && MC_METADATA_SERVER=http://127.0.0.1:8080/metadata DEFAULT_KV_LEASE_TTL=500 ctest -j --output-on-failure --exclude-regex "etcd|ha_test|redis" - -# Run ALL Python tests: -mooncake_http_metadata_server --port 8080 & sleep 1 && cd mooncake-wheel/tests && MC_METADATA_SERVER=http://127.0.0.1:8080/metadata MC_FORCE_TCP=true bash ../../scripts/run_tests.sh - -# Check code format (changed files only): -./scripts/code_format.sh --check --base origin/main - -# Full pre-commit check: -pre-commit run --all-files +bash scripts/run_ci_test.sh --skip-path-filter ``` + +Use another base ref: + +```bash +bash scripts/run_ci_test.sh --base origin/main +``` + +Auto-install missing dependencies: + +```bash +bash scripts/run_ci_test.sh --install-deps +``` + +Keep services running for follow-up debugging: + +```bash +bash scripts/run_ci_test.sh --keep-services +``` + +## Minimal Example + +User prompt: + +- 提交 PR 前,帮我跑一遍本地 CI 验证当前分支。 + +Expected action: + +```bash +bash scripts/run_ci_test.sh +``` + +If the user wants to ignore changed-path optimization and force the full lane: + +```bash +bash scripts/run_ci_test.sh --skip-path-filter +``` + +See also `.claude/skills/mooncake-ci-local/examples/minimal.md`. + +## How To Interpret Results + +- `passed`: the stage succeeded locally. +- `failed`: the stage reproduced a real local failure and needs investigation. +- `blocked`: local environment or dependency issue prevented execution. +- `unsupported`: intentionally not run in the local lane because it needs external platforms, special hardware, or a non-default build. + +If `paths-filter` skips downstream stages, explain that the current branch changed only non-source paths relative to the selected base. + +## Current Local Coverage + +Included by default: + +- spell check +- code format check +- default ASan CMake lane in `build-ci-local` +- `ctest` +- wheel build and installation test +- `scripts/run_tests.sh` +- selected Python API tests + +Unsupported by design in the default local lane: + +- Ascend jobs +- T-one integration jobs +- MUSA jobs +- Docker image build jobs +- CUDA 13 wheel jobs +- PG-backend tests absent from the default wheel build +- Python drain-http API stage in the local ASan lane + +## Targeted Reruns For Debugging + +Use targeted reruns only after the full script identifies a failing area, or when the user explicitly asks for a smaller scope. + +Rerun a specific C++ test pattern: + +```bash +cd build-ci-local +MC_METADATA_SERVER=http://127.0.0.1:8080/metadata DEFAULT_KV_LEASE_TTL=500 ctest -R --output-on-failure +``` + +Rerun the Python wheel integration lane: + +```bash +source test_env/bin/activate +MC_STORE_MEMCPY=false TEST_SSD_OFFLOAD_IN_EVICT=true ./scripts/run_tests.sh +``` + +Rerun the safetensor unittest: + +```bash +source test_env/bin/activate +python -m unittest mooncake-wheel.tests.test_safetensor_functions +``` + +## Notes For The Agent + +- Prefer the repo script over rebuilding the CI workflow step by step. +- Preserve the separation between `build-ci-local` and `build-wheel-local`. +- Summarize failing stages from their logs instead of pasting raw output. +- If the user only asks whether the branch is safe before opening a PR, the default answer path is `bash scripts/run_ci_test.sh`. diff --git a/.claude/skills/mooncake-ci-local/examples/minimal.md b/.claude/skills/mooncake-ci-local/examples/minimal.md new file mode 100644 index 00000000..3d081c73 --- /dev/null +++ b/.claude/skills/mooncake-ci-local/examples/minimal.md @@ -0,0 +1,30 @@ +# Minimal Example + +Goal: validate the current branch before opening or submitting a PR. + +User prompt: + +- 提交 PR 前,帮我跑一遍本地 CI 验证当前分支。 + +Expected action: + +```bash +bash scripts/run_ci_test.sh +``` + +Force a full rerun even if `paths-filter` would skip downstream stages: + +```bash +bash scripts/run_ci_test.sh --skip-path-filter +``` + +Result location: + +- `local_test/run-ci-logs//` + +Typical report format: + +- passed stages +- failed or blocked stages +- unsupported stages +- first actionable failure and its log path diff --git a/.claude/skills/mooncake-troubleshoot/.claude-plugin/plugin.json b/.claude/skills/mooncake-troubleshoot/.claude-plugin/plugin.json new file mode 100644 index 00000000..239659ad --- /dev/null +++ b/.claude/skills/mooncake-troubleshoot/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "mooncake-troubleshoot", + "description": "Systematically diagnose Mooncake deployment and runtime issues (services, RDMA, env vars, connectivity, logs) and propose fixes.", + "author": { + "name": "Mooncake Team" + }, + "homepage": "https://kvcache-ai.github.io/Mooncake/skills/mooncake-troubleshoot.html", + "repository": "https://github.com/kvcache-ai/Mooncake", + "license": "Apache-2.0", + "keywords": ["mooncake", "troubleshooting", "rdma", "deployment"] +} diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7835f4c0..a5729ef9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -12,7 +12,7 @@ RUN apt-get install -y libibverbs-dev \ libjsoncpp-dev \ libnuma-dev \ libpython3-dev \ - libboost-all-dev \ + libboost-dev \ libssl-dev \ libgrpc-dev \ libgrpc++-dev \ @@ -26,6 +26,7 @@ RUN apt-get install -y libibverbs-dev \ libzstd-dev \ libmsgpack-dev \ libgflags-dev \ + libxxhash-dev \ pkg-config \ patchelf diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7df63069..dea0cbb2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,8 @@ { "name": "Mooncake Dev", - "dockerFile": "Dockerfile", + "build": { + "dockerfile": "Dockerfile" + }, "runArgs": [ "--cap-add=SYS_PTRACE", "--cap-add=NET_RAW", @@ -18,7 +20,7 @@ "vscode": { "settings": { "terminal.integrated.shell.linux": "/bin/bash", - "bazel.buildifierFixOnFormat": true, + "bazel.buildifierFixOnFormat": true } } } diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6641f3f1..eabf4b29 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ /mooncake-store @ykwd @stmatengss @XucSh @YiXR /mooncake-store/*/ha/ @Libotry @YiXR @00fish0 /mooncake-transfer-engine @alogfans @doujiang24 @chestnut-Q +/mooncake-transfer-engine/tent @alogfans @doujiang24 @chestnut-Q @staryxchen @00fish0 @dtcccc /mooncake-transfer-engine/*/transport/hip_transport/ @alogfans @amd-arozanov /mooncake-transfer-engine/*/transport/ascend_transport/ @alogfans @ascend-direct-dev /mooncake-transfer-engine/*/transport/efa_transport/ @alogfans @whn09 diff --git a/.github/labeler.yml b/.github/labeler.yml index dad607da..aa76d0be 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -22,3 +22,43 @@ Mooncake EP: Installation: - changed-files: - any-glob-to-any-file: 'mooncake-wheel/**/*' + +P2P Store: + - changed-files: + - any-glob-to-any-file: 'mooncake-p2p-store/**/*' + +Integration: + - changed-files: + - any-glob-to-any-file: 'mooncake-integration/**/*' + +Common: + - changed-files: + - any-glob-to-any-file: 'mooncake-common/**/*' + +CI/Build: + - changed-files: + - any-glob-to-any-file: + - '.github/**/*' + - 'CMakeLists.txt' + - '.pre-commit-config.yaml' + - 'dependencies.sh' + +Documentation: + - changed-files: + - any-glob-to-any-file: + - 'docs/**/*' + - '*.md' + +Tests: + - changed-files: + - any-glob-to-any-file: + - 'scripts/test_*' + - 'mooncake-wheel/tests/**/*' + - 'scripts/tone_tests/**/*' + +Ascend/NPU: + - changed-files: + - any-glob-to-any-file: + - '**/ascend*/**' + - 'scripts/ascend/**' + - '.github/workflows/*ascend*' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 541ebfd2..a926d407 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -7,10 +7,11 @@ - [ ] Transfer Engine (`mooncake-transfer-engine`) - [ ] Mooncake Store (`mooncake-store`) - [ ] Mooncake EP (`mooncake-ep`) +- [ ] Mooncake PG (`mooncake-pg`) - [ ] Integration (`mooncake-integration`) - [ ] P2P Store (`mooncake-p2p-store`) - [ ] Python Wheel (`mooncake-wheel`) -- [ ] PyTorch Backend (`mooncake-pg`) +- [ ] Common (`mooncake-common`) - [ ] Mooncake RL (`mooncake-rl`) - [ ] CI/CD - [ ] Docs @@ -23,15 +24,38 @@ - [ ] Refactor - [ ] Breaking change - [ ] Documentation update +- [ ] Performance improvement - [ ] Other ## How Has This Been Tested? - + + +**Test commands:** +```bash +# Example: bash scripts/run_ci_test.sh +``` + +**Test results:** +- [ ] Unit tests pass +- [ ] Integration tests pass (if applicable) +- [ ] Manual testing done (describe below) ## Checklist -- [ ] I have performed a self-review of my own code. -- [ ] I have formatted my own code using `./scripts/code_format.sh` before submitting. -- [ ] I have updated the documentation. -- [ ] I have added tests to prove my changes are effective. +- [ ] 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 + +## AI Assistance Disclosure + + + +- [ ] No AI tools were used +- [ ] AI tools were used (specify below) + + diff --git a/.github/workflows/assistant.yml b/.github/workflows/assistant.yml new file mode 100644 index 00000000..59e76321 --- /dev/null +++ b/.github/workflows/assistant.yml @@ -0,0 +1,60 @@ +name: Qoder Assistant + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + qoder-assistant: + if: | + contains(github.event.comment.body, '@qoder') && + !endsWith(github.event.comment.user.login, '[bot]') + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build Arguments + id: build_args + run: | + ARGS="REPO: ${{ github.repository }} + REQUEST_SOURCE: ${{ github.event_name }} + THREAD_ID: ${{ github.event.comment.node_id }} + COMMENT_ID: ${{ github.event.comment.id }} + AUTHOR: ${{ github.event.comment.user.login }} + BODY: ${{ github.event.comment.body }} + URL: ${{ github.event.comment.html_url }} + IS_PR: ${{ github.event.issue.pull_request != null || github.event_name == 'pull_request_review_comment' }} + ISSUE_OR_PR_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}" + + if [ -n "${{ github.event.comment.pull_request_review_id }}" ]; then + ARGS="$ARGS + REVIEW_ID: ${{ github.event.comment.pull_request_review_id }}" + fi + + if [ -n "${{ github.event.comment.in_reply_to_id }}" ]; then + ARGS="$ARGS + REPLY_TO_COMMENT_ID: ${{ github.event.comment.in_reply_to_id }}" + fi + + echo "args<> $GITHUB_OUTPUT + echo "$ARGS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Run Qoder Assistant + uses: QoderAI/qoder-action@v0 + with: + qoder_personal_access_token: ${{ secrets.QODER_PERSONAL_ACCESS_TOKEN }} + prompt: | + /assistant + ${{ steps.build_args.outputs.args }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e72c7e46..5b6eb46e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,9 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Install and start etcd run: | wget https://github.com/etcd-io/etcd/releases/download/v3.6.1/etcd-v3.6.1-linux-amd64.tar.gz @@ -72,6 +75,11 @@ jobs: sudo apt-get update sudo apt-get install -y lcov gcovr ninja-build + - name: Test HugeTLB sizing helper + run: | + python3 scripts/test_hicache_hugepage_requirements.py + shell: bash + - name: Set up coverage compilation flags run: | echo "Setting up coverage compilation flags..." @@ -90,17 +98,13 @@ jobs: core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - name: Run sccache stat for check - shell: bash - run: ${SCCACHE_PATH} --show-stats - - name: Configure project with coverage support run: | sudo apt update -y sudo bash -x dependencies.sh -y mkdir build cd build - cmake -G Ninja .. -DUSE_HTTP=ON -DUSE_CXL=ON -DUSE_UB=ON -DUSE_ETCD=ON -DSTORE_USE_ETCD=ON -DENABLE_ASAN=ON -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Debug + cmake -G Ninja .. -DUSE_HTTP=ON -DUSE_CXL=ON -DUSE_UB=ON -DUSE_ETCD=ON -DSTORE_USE_ETCD=ON -DENABLE_ASAN=ON -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Debug -DENABLE_DEBUG_SYMBOLS=OFF shell: bash - name: Build project @@ -118,6 +122,11 @@ jobs: bash build.sh ../../build/mooncake-transfer-engine/nvlink-allocator/ shell: bash + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + - name: Start Metadata Server run: | cd mooncake-transfer-engine/example/http-metadata-server-python @@ -125,6 +134,39 @@ jobs: python ./bootstrap_server.py & shell: bash + - name: Run Mooncake Store Rust smoke test and benchmark + run: | + $GITHUB_WORKSPACE/build/mooncake-store/src/mooncake_master \ + --eviction_high_watermark_ratio=0.95 \ + --cluster_id=ci_rust_test_cluster \ + --port 50051 & + MASTER_PID=$! + sleep 3 + cd mooncake-store/rust + export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/build/mooncake-asio:$GITHUB_WORKSPACE/build/mooncake-store/src:$GITHUB_WORKSPACE/build/mooncake-store/src/cachelib_memory_allocator:$GITHUB_WORKSPACE/build/mooncake-transfer-engine/src:$GITHUB_WORKSPACE/build/mooncake-transfer-engine/src/common/base:$GITHUB_WORKSPACE/build/mooncake-common/etcd:$LD_LIBRARY_PATH + export MOONCAKE_BUILD_DIR=$GITHUB_WORKSPACE/build + export MOONCAKE_STORE_LIB_DIR=$GITHUB_WORKSPACE/build/mooncake-store/src + export MOONCAKE_STORE_INCLUDE_DIR=$GITHUB_WORKSPACE/mooncake-store/include + # This job builds Mooncake with -DENABLE_ASAN=ON, so the C++ libraries + # the Rust package links against carry undefined __asan_* references. Opt + # in to linking the ASan runtime; build.rs emits -lasan first, which + # keeps libasan first in the initial library list as ASan requires. + # Non-sanitized builds leave this unset and link without ASan. + export MOONCAKE_LINK_ASAN=1 + export MC_METADATA_SERVER=http://127.0.0.1:8080/metadata + export MC_RUST_STORE_RUN_INTEGRATION=true + export MC_RUST_STORE_MASTER_ADDR=127.0.0.1:50051 + export MC_RUST_STORE_LOCAL_HOSTNAME=127.0.0.1 + export MC_RUST_STORE_PROTOCOL=tcp + export MC_RUST_STORE_DEVICE_NAME= + cargo test --test minimal_smoke -- --nocapture + MC_RUST_BENCH_ITERATIONS=4 \ + MC_RUST_BENCH_VALUE_SIZE=4096 \ + MC_RUST_BENCH_WARMUP=1 \ + cargo run --release --example store_benchmark + kill $MASTER_PID 2>/dev/null || true + shell: bash + - name: Run Go store binding integration tests run: | $GITHUB_WORKSPACE/build/mooncake-store/src/mooncake_master \ @@ -137,7 +179,7 @@ jobs: export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/build/mooncake-common:$GITHUB_WORKSPACE/build/mooncake-store/src:$GITHUB_WORKSPACE/build/mooncake-transfer-engine/src:$GITHUB_WORKSPACE/build/mooncake-transfer-engine/src/common/base:$GITHUB_WORKSPACE/build/mooncake-common/etcd export CGO_ENABLED=1 export CGO_CFLAGS="-I$GITHUB_WORKSPACE/mooncake-store/include -I$GITHUB_WORKSPACE/mooncake-transfer-engine/include" - export CGO_LDFLAGS="-L$GITHUB_WORKSPACE/build/mooncake-store/src -L$GITHUB_WORKSPACE/build/mooncake-store/src/cachelib_memory_allocator -L$GITHUB_WORKSPACE/build/mooncake-transfer-engine/src -L$GITHUB_WORKSPACE/build/mooncake-transfer-engine/src/common/base -L$GITHUB_WORKSPACE/build/mooncake-common -L$GITHUB_WORKSPACE/build/mooncake-common/etcd -lmooncake_store -lcachelib_memory_allocator -ltransfer_engine -lbase -lasio -letcd_wrapper -lstdc++ -lnuma -lglog -lgflags -libverbs -ljsoncpp -lzstd -lcurl -luring -lasan -lm -lgcov" + export CGO_LDFLAGS="-L$GITHUB_WORKSPACE/build/mooncake-store/src -L$GITHUB_WORKSPACE/build/mooncake-store/src/cachelib_memory_allocator -L$GITHUB_WORKSPACE/build/mooncake-transfer-engine/src -L$GITHUB_WORKSPACE/build/mooncake-transfer-engine/src/common/base -L$GITHUB_WORKSPACE/build/mooncake-common -L$GITHUB_WORKSPACE/build/mooncake-common/etcd -lmooncake_store -lcachelib_memory_allocator -ltransfer_engine -lbase -lasio -letcd_wrapper -lstdc++ -lnuma -lglog -lgflags -libverbs -lmlx5 -ljsoncpp -lzstd -lcurl -luring -lasan -lm -lgcov -lxxhash -lyaml-cpp" # Link cudart if CUDA is available (needed for D2H staging in mooncake_store) if [ -d /usr/local/cuda/lib64 ]; then export CGO_LDFLAGS="$CGO_LDFLAGS -L/usr/local/cuda/lib64 -lcudart"; fi ASAN_OPTIONS=detect_leaks=0:verify_asan_link_order=0 MC_METADATA_SERVER=http://127.0.0.1:8080/metadata go test -v ./tests/... @@ -262,7 +304,7 @@ jobs: bash -x dependencies.sh -y mkdir build cd build - cmake -G Ninja .. -DUSE_MUSA=ON -DUSE_MNNVL=ON -DUSE_ETCD=ON -DSTORE_USE_ETCD=ON -DUSE_CXL=ON -DUSE_TCP=ON -DBUILD_UNIT_TESTS=OFF -DBUILD_EXAMPLES=OFF + cmake -G Ninja .. -DUSE_MUSA=ON -DUSE_MNNVL=ON -DUSE_ETCD=ON -DSTORE_USE_ETCD=ON -DUSE_CXL=ON -DUSE_TCP=ON -DBUILD_UNIT_TESTS=OFF -DBUILD_EXAMPLES=OFF -DENABLE_DEBUG_SYMBOLS=OFF shell: bash - name: Build project @@ -273,6 +315,106 @@ jobs: cmake --install . shell: bash + build-arm64: + needs: [spell-check, clang-format, check-paths] + if: >- + (needs.check-paths.outputs.should-run-downstream == 'true' || + github.event_name == 'workflow_dispatch') && + (github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + github.event.action == 'opened' || + contains(github.event.pull_request.labels.*.name, 'run-ci')) + runs-on: ubuntu-22.04-arm + strategy: + matrix: + python-version: ['3.10', '3.12'] + env: + CI: "true" + CU13_BUILD: "1" + TORCH_CUDA_ARCH_LIST: "9.0" + SCCACHE_GHA_ENABLED: "true" + CUDA_HOME: "/usr/local/cuda" + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + + - name: Install CUDA Toolkit 13.0 (arm64 SBSA) + run: | + wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/sbsa/cuda-keyring_1.1-1_all.deb + sudo dpkg -i cuda-keyring_1.1-1_all.deb + sudo apt-get update + sudo apt-get install -y cuda-toolkit-13-0 + echo "/usr/local/cuda/bin" >> $GITHUB_PATH + /usr/local/cuda/bin/nvcc --version + shell: bash + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake -G Ninja .. -DUSE_HTTP=ON -DUSE_CUDA=ON -DUSE_MNNVL=ON -DWITH_EP=OFF -DWITH_STORE_RUST=OFF -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + shell: bash + + - name: Build project + run: | + export CUDA_HOME=/usr/local/cuda + for dir in /usr/local/cuda/lib64/stubs /usr/local/cuda/targets/*/lib/stubs; do + if [ -d "$dir" ]; then + export LIBRARY_PATH="$dir:${LIBRARY_PATH:-}" + fi + done + cd build + cmake --build . + sudo cmake --install . + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_arm64 + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_arm64.outputs.python_version_tag }} ./scripts/build_wheel.sh + shell: bash + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: wheel-arm64-cuda13-py${{ steps.generate_tag_arm64.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_arm64.outputs.python_version_tag }}/*.whl + test-wheel-ubuntu: needs: [spell-check, clang-format, build-flags] if: >- @@ -430,7 +572,7 @@ jobs: MC_FORCE_TCP: "true" run: | source test_env/bin/activate - python -m unittest mooncake-wheel.tests.test_mooncake_backend_cpu + python mooncake-pg/tests/test_pg_collectives.py shell: bash - name: Test Safetensor Functions @@ -458,6 +600,7 @@ jobs: BUILD_WITH_EP: "1" TORCH_CUDA_ARCH_LIST: "8.0;9.0" SCCACHE_GHA_ENABLED: "true" + PIP_NO_CACHE_DIR: "1" steps: - uses: actions/checkout@v4 @@ -496,10 +639,6 @@ jobs: core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - name: Run sccache stat for check - shell: bash - run: ${SCCACHE_PATH} --show-stats - - name: Install dependencies run: | sudo apt update -y @@ -518,7 +657,7 @@ jobs: cd build export PATH=/usr/local/nvidia/bin:/usr/local/nvidia/lib64:$PATH export LD_LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LD_LIBRARY_PATH - cmake -G Ninja .. -DUSE_ETCD=OFF -DUSE_CXL=ON -DUSE_REDIS=ON -DUSE_HTTP=ON -DWITH_METRICS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_EXAMPLES=ON -DENABLE_SCCACHE=ON -DUSE_CUDA=OFF -DUSE_MNNVL=OFF -DUSE_UB=OFF -DCMAKE_EXE_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs" + cmake -G Ninja .. -DUSE_ETCD=OFF -DUSE_CXL=ON -DUSE_REDIS=ON -DUSE_HTTP=ON -DWITH_METRICS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_EXAMPLES=ON -DENABLE_SCCACHE=ON -DUSE_CUDA=OFF -DUSE_MNNVL=OFF -DUSE_UB=OFF -DCMAKE_EXE_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs" -DENABLE_DEBUG_SYMBOLS=OFF cmake --build . sudo cmake --install . df -h @@ -528,7 +667,7 @@ jobs: run: | mkdir build cd build - cmake -G Ninja .. -DUSE_ETCD=ON -DUSE_CXL=ON -DUSE_REDIS=ON -DUSE_HTTP=ON -DWITH_STORE=ON -DWITH_P2P_STORE=ON -DWITH_METRICS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_EXAMPLES=ON -DENABLE_SCCACHE=ON -DUSE_CUDA=ON -DUSE_MNNVL=OFF -DUSE_UB=OFF -DCMAKE_EXE_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs" + cmake -G Ninja .. -DUSE_ETCD=ON -DUSE_CXL=ON -DUSE_REDIS=ON -DUSE_HTTP=ON -DWITH_STORE=ON -DWITH_P2P_STORE=ON -DWITH_METRICS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_EXAMPLES=ON -DENABLE_SCCACHE=ON -DUSE_CUDA=ON -DUSE_MNNVL=OFF -DUSE_UB=OFF -DCMAKE_EXE_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs" -DENABLE_DEBUG_SYMBOLS=OFF shell: bash # TODO: lack USE_NVMEOF,USE_MNNVL @@ -545,7 +684,7 @@ jobs: - name: Configure project with unit tests and examples run: | cd build - cmake -G Ninja .. -DBUILD_UNIT_TESTS=ON -DBUILD_EXAMPLES=ON -DWITH_STORE_RUST=ON -DENABLE_SCCACHE=ON + cmake -G Ninja .. -DBUILD_UNIT_TESTS=ON -DBUILD_EXAMPLES=ON -DWITH_STORE_RUST=ON -DENABLE_SCCACHE=ON -DENABLE_DEBUG_SYMBOLS=OFF shell: bash - name: Build project with unit tests and examples @@ -557,19 +696,28 @@ jobs: sudo cmake --install . shell: bash - - name: Check Mooncake Store Rust bindings and example + - name: Check Mooncake Store Rust bindings, examples, and tests run: | + # libcuda.so.1 (SONAME of the CUDA stub) must be findable at runtime. + # The toolkit stubs dir only ships libcuda.so; create the versioned symlink. + if [ -f /usr/local/cuda/lib64/stubs/libcuda.so ] && \ + [ ! -e /usr/local/cuda/lib64/stubs/libcuda.so.1 ]; then + sudo ln -s libcuda.so /usr/local/cuda/lib64/stubs/libcuda.so.1 + fi + export LD_LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LD_LIBRARY_PATH cd mooncake-store/rust + export MOONCAKE_BUILD_DIR=$GITHUB_WORKSPACE/build + cargo test --lib MOONCAKE_STORE_LIB_DIR=$GITHUB_WORKSPACE/build/mooncake-store/src \ MOONCAKE_STORE_INCLUDE_DIR=$GITHUB_WORKSPACE/mooncake-store/include \ - cargo check --example basic_usage --tests + cargo test --examples --tests --no-run shell: bash - name: Configure project run: | cd build rm -r */tests - cmake -G Ninja .. -DBUILD_UNIT_TESTS=OFF -DBUILD_EXAMPLES=OFF -DUSE_HTTP=ON -DENABLE_SCCACHE=ON -DUSE_CXL=ON -DWITH_EP=ON -DEP_TORCH_VERSIONS="2.9.0;2.9.1;2.10.0;2.11.0" + cmake -G Ninja .. -DBUILD_UNIT_TESTS=OFF -DBUILD_EXAMPLES=OFF -DUSE_HTTP=ON -DENABLE_SCCACHE=ON -DUSE_CXL=ON -DWITH_EP=ON -DEP_TORCH_VERSIONS="2.9.1;2.10.0;2.11.0;2.12.0;2.12.1" -DENABLE_DEBUG_SYMBOLS=OFF shell: bash - name: Build project @@ -581,6 +729,26 @@ jobs: sudo cmake --install . shell: bash + - name: Configure project with TENT + run: | + mkdir build-tent + cd build-tent + cmake -G Ninja .. -DUSE_TENT=ON -DUSE_HTTP=ON -DENABLE_SCCACHE=ON -DBUILD_UNIT_TESTS=ON -DBUILD_EXAMPLES=ON -DENABLE_DEBUG_SYMBOLS=OFF + shell: bash + + - name: Build project with TENT + run: | + cd build-tent + cmake --build . + sudo cmake --install . + shell: bash + + - name: Test (TENT) + run: | + cd build-tent + ctest --test-dir mooncake-transfer-engine/tent/tests -j --output-on-failure + shell: bash + - name: Build nvlink_allocator.so run: | mkdir -p build/mooncake-transfer-engine/nvlink-allocator @@ -591,6 +759,11 @@ jobs: bash build.sh ../../build/mooncake-transfer-engine/nvlink-allocator/ shell: bash + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + - name: Generate Python version tag id: generate_tag_flags run: | @@ -638,10 +811,10 @@ jobs: spell-check: name: Spell Check with Typos if: >- - github.event_name == 'push' || - github.event_name == 'workflow_dispatch' || - github.event.action == 'opened' || - contains(github.event.pull_request.labels.*.name, 'run-ci') + (github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + github.event.action == 'opened' || + contains(github.event.pull_request.labels.*.name, 'run-ci')) runs-on: ubuntu-22.04 steps: - name: Checkout Actions Repository @@ -654,10 +827,10 @@ jobs: clang-format: name: Check code format if: >- - github.event_name == 'push' || - github.event_name == 'workflow_dispatch' || - github.event.action == 'opened' || - contains(github.event.pull_request.labels.*.name, 'run-ci') + (github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + github.event.action == 'opened' || + contains(github.event.pull_request.labels.*.name, 'run-ci')) runs-on: ubuntu-22.04 steps: - name: Checkout Actions Repository @@ -680,7 +853,7 @@ jobs: echo "Error: code_format.sh not found or not executable" exit 1 fi - + # Determine base ref for comparison if [ "${{ github.event_name }}" == "pull_request" ]; then # For PRs: compare against the target branch @@ -701,6 +874,48 @@ jobs: shell: bash + docs-check: + name: Check Sphinx docs build + if: >- + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + github.event.action == 'opened' || + contains(github.event.pull_request.labels.*.name, 'run-ci') + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + persist-credentials: false + + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + docs: + - 'docs/**' + - 'requirements_docs.txt' + + - name: Set up Python + if: steps.filter.outputs.docs == 'true' + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install documentation dependencies + if: steps.filter.outputs.docs == 'true' + run: | + python -m pip install --upgrade pip + pip install -r requirements_docs.txt + + - name: Build docs with strict mode + if: steps.filter.outputs.docs == 'true' + run: | + cd docs + make html SPHINXOPTS=-W + shell: bash + + check-paths: if: >- github.event_name == 'push' || @@ -746,6 +961,18 @@ jobs: uses: ./.github/workflows/ci_cu13.yml secrets: inherit + build-wheel-efa: + needs: [spell-check, clang-format, check-paths] + if: >- + (needs.check-paths.outputs.should-run-downstream == 'true' || + github.event_name == 'workflow_dispatch') && + (github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + github.event.action == 'opened' || + contains(github.event.pull_request.labels.*.name, 'run-ci')) + uses: ./.github/workflows/ci_efa.yml + secrets: inherit + ascend-test: needs: [build, check-paths] if: needs.check-paths.outputs.should-run-downstream == 'true' @@ -764,12 +991,15 @@ jobs: needs: - spell-check - clang-format + - docs-check - build - build-musa + - build-arm64 - build-flags - build-docker - test-wheel-ubuntu - build-wheel-cu13 + - build-wheel-efa - ascend-test - integration-test runs-on: ubuntu-latest diff --git a/.github/workflows/ci_ascend.yml b/.github/workflows/ci_ascend.yml index c3f39c98..13f79a24 100644 --- a/.github/workflows/ci_ascend.yml +++ b/.github/workflows/ci_ascend.yml @@ -4,7 +4,7 @@ on: workflow_call: inputs: checkout_ref: - description: 'Git ref to checkout (PR head SHA for pull_request_target)' + description: 'Git ref to checkout (PR head SHA)' required: false type: string @@ -49,7 +49,7 @@ jobs: if: steps.checkout_code.outcome == 'failure' shell: bash env: - ASCEND_GITHUB_MIRROR_URLS: ${{ vars.ASCEND_GITHUB_MIRROR_URLS }} + ASCEND_GITHUB_MIRROR_URLS: 'https://ghfast.top/' CHECKOUT_REF: ${{ inputs.checkout_ref || github.sha }} run: | set -euo pipefail @@ -108,7 +108,7 @@ jobs: - name: Configure CMake shell: bash env: - ASCEND_GITHUB_MIRROR_URLS: ${{ vars.ASCEND_GITHUB_MIRROR_URLS }} + ASCEND_GITHUB_MIRROR_URLS: 'https://ghfast.top/' run: | source /usr/local/Ascend/cann-9.0.0/set_env.sh pwd @@ -163,7 +163,9 @@ jobs: cmake .. \ -DUSE_ASCEND_DIRECT=ON \ -DBUILD_EXAMPLES=OFF \ - -DBUILD_UNIT_TESTS=OFF + -DBUILD_UNIT_TESTS=OFF \ + -DENABLE_DEBUG_SYMBOLS=OFF \ + -DBUILD_BENCHMARK=OFF - name: Build shell: bash @@ -171,7 +173,7 @@ jobs: source /usr/local/Ascend/cann-9.0.0/set_env.sh echo "Building..." cd build - cmake --build . -j$(nproc) + cmake --build . -j48 cmake --install . echo "Mooncake installed successfully." diff --git a/.github/workflows/ci_cu13.yml b/.github/workflows/ci_cu13.yml index 0fae01f7..828491f3 100644 --- a/.github/workflows/ci_cu13.yml +++ b/.github/workflows/ci_cu13.yml @@ -52,10 +52,6 @@ jobs: core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - name: Run sccache stat for check - shell: bash - run: ${SCCACHE_PATH} --show-stats - - name: Install dependencies run: | sudo apt update -y @@ -75,7 +71,7 @@ jobs: -DWITH_STORE=ON \ -DWITH_P2P_STORE=ON \ -DWITH_EP=ON \ - -DEP_TORCH_VERSIONS="2.9.0;2.9.1;2.10.0;2.11.0" \ + -DEP_TORCH_VERSIONS="2.9.1;2.10.0;2.11.0;2.12.0;2.12.1" \ -DWITH_METRICS=ON \ -DBUILD_UNIT_TESTS=OFF \ -DBUILD_EXAMPLES=ON \ @@ -83,7 +79,8 @@ jobs: -DBUILD_BENCHMARK=ON \ -DUSE_CUDA=ON \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_EXE_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs" + -DCMAKE_EXE_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs" \ + -DENABLE_DEBUG_SYMBOLS=OFF shell: bash - name: Build project @@ -106,6 +103,11 @@ jobs: bash build.sh ../../build/mooncake-transfer-engine/nvlink-allocator/ shell: bash + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + - name: Generate Python version tag id: generate_tag run: | diff --git a/.github/workflows/ci_efa.yml b/.github/workflows/ci_efa.yml new file mode 100644 index 00000000..64daa8c8 --- /dev/null +++ b/.github/workflows/ci_efa.yml @@ -0,0 +1,154 @@ +name: 'Build Wheel (AWS EFA)' + +on: + workflow_call: {} + +# Builds the AWS EFA (libfabric) wheel variants on a stock ubuntu runner. +# No EFA hardware is required to *build*: USE_EFA only needs the libfabric +# headers/lib to compile and link. auditwheel later excludes libfabric/libefa +# from the wheel so they resolve to the user's system EFA install +# (/opt/amazon/efa/lib) at runtime. The distro libfabric (1.x) is ABI-forward- +# compatible with the AWS EFA libfabric (2.x) that loads at runtime; the EFA +# transport only uses long-stable fi_* core APIs. +# +# Two variants, since the EFA transport's memory path is CUDA-aware +# (FI_HMEM_CUDA / GPUDirect under USE_CUDA=ON, FI_HMEM=system otherwise): +# efa USE_CUDA=ON (GPU) +# efa-non-cuda USE_CUDA=OFF (CPU/DRAM) +# PR validation builds one python version per variant to keep CI cheap; +# the release workflow builds the full python matrix. +jobs: + build-wheel-efa: + runs-on: ubuntu-22.04 + strategy: + matrix: + include: + - variant: cuda + use_cuda: "ON" + build_env: "EFA_BUILD" + python-version: "3.12" + - variant: non-cuda + use_cuda: "OFF" + build_env: "EFA_NON_CUDA_BUILD" + python-version: "3.10" + env: + TORCH_CUDA_ARCH_LIST: "8.0;9.0" + SCCACHE_GHA_ENABLED: "true" + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/lib/android + df -h + + - name: Install CUDA Toolkit + if: matrix.use_cuda == 'ON' + uses: Jimver/cuda-toolkit@v0.2.24 + with: + cuda: '12.8.1' + method: 'network' + sub-packages: '["nvcc", "nvrtc-dev"]' + non-cuda-sub-packages: '["libcusparse-dev", "libcublas-dev", "libcusolver-dev"]' + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Install dependencies + run: | + sudo apt update -y + sudo apt install -y ninja-build libfabric-dev libfabric1 + sudo bash -x dependencies.sh -y + df -h + shell: bash + + - name: Configure project + run: | + mkdir build + cd build + EXTRA_FLAGS="" + if [ "${{ matrix.use_cuda }}" = "ON" ]; then + EXTRA_FLAGS="-DCMAKE_EXE_LINKER_FLAGS=-L/usr/local/cuda/lib64/stubs" + fi + cmake -G Ninja .. \ + -DUSE_ETCD=ON \ + -DUSE_HTTP=ON \ + -DWITH_STORE=ON \ + -DWITH_METRICS=ON \ + -DBUILD_UNIT_TESTS=OFF \ + -DBUILD_EXAMPLES=ON \ + -DENABLE_SCCACHE=ON \ + -DBUILD_BENCHMARK=ON \ + -DUSE_EFA=ON \ + -DUSE_CUDA=${{ matrix.use_cuda }} \ + -DLIBFABRIC_INCLUDE_DIR=/usr/include \ + -DLIBFABRIC_LIBRARY=/usr/lib/x86_64-linux-gnu/libfabric.so \ + -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_DEBUG_SYMBOLS=OFF \ + $EXTRA_FLAGS + shell: bash + + - name: Build project + run: | + if [ "${{ matrix.use_cuda }}" = "ON" ]; then + export LD_LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LD_LIBRARY_PATH + export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LIBRARY_PATH + fi + cd build + cmake --build . + sudo cmake --install . + df -h + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib + export ${{ matrix.build_env }}=1 + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag.outputs.python_version_tag }} ./scripts/build_wheel.sh + shell: bash + + - name: Verify libfabric is excluded from the wheel + run: | + WHL=$(ls mooncake-wheel/dist-py${{ steps.generate_tag.outputs.python_version_tag }}/*.whl | head -1) + echo "Inspecting $WHL" + if unzip -l "$WHL" | grep -iE 'libfabric|libefa'; then + echo "::error::libfabric/libefa must NOT be bundled in the EFA wheel" + exit 1 + fi + echo "OK: libfabric/libefa correctly excluded (resolve to system EFA at runtime)" + shell: bash + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: mooncake-wheel-efa-${{ matrix.variant }}-ubuntu-py${{ steps.generate_tag.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag.outputs.python_version_tag }}/*.whl diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml new file mode 100644 index 00000000..94622c08 --- /dev/null +++ b/.github/workflows/code-review.yml @@ -0,0 +1,29 @@ +name: Qoder Auto Code Review + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + qoder-review: + # Skip fork and cross-repo PRs (head repo must match this repository) + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run Qoder Code Review + uses: QoderAI/qoder-action@v0 + with: + qoder_personal_access_token: ${{ secrets.QODER_PERSONAL_ACCESS_TOKEN }} + prompt: | + /review-pr + REPO:${{ github.repository }} PR_NUMBER:${{ github.event.pull_request.number }} diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml new file mode 100644 index 00000000..c199c049 --- /dev/null +++ b/.github/workflows/issue-bot.yml @@ -0,0 +1,71 @@ +name: Issue Bot + +on: + issues: + types: [opened] + schedule: + # Daily at 03:00 UTC + - cron: "0 3 * * *" + workflow_dispatch: + +permissions: + issues: write + +jobs: + welcome-and-assign: + if: github.event_name == 'issues' + runs-on: ubuntu-latest + steps: + - name: Post welcome comment + uses: actions/github-script@v7 + with: + script: | + const issue = context.payload.issue; + const body = [ + `Thanks for opening this issue, @${issue.user.login}!`, + '', + '| Field | Value |', + '|-------|-------|', + `| **Issue** | #${issue.number} |`, + `| **GitHub user ID** | \`${issue.user.id}\` |`, + `| **Reporter** | @${issue.user.login} |`, + '', + 'A maintainer will triage this when possible. To help us respond faster, please include:', + '', + '- Mooncake version or commit SHA', + '- Environment (OS, CUDA/driver, RDMA stack if relevant)', + '- Steps to reproduce and expected vs. actual behavior', + '', + `Useful links: [Documentation](https://kvcache-ai.github.io/Mooncake/) · [Contributing guide](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/CONTRIBUTING.md)`, + '', + '> This message was posted automatically by the issue bot.', + ].join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body, + }); + + close-stale-issues: + if: github.event_name != 'issues' + runs-on: ubuntu-latest + steps: + - name: Close inactive issues (3+ months) + uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 90 + days-before-close: 7 + stale-issue-label: stale + close-issue-label: auto-closed + stale-issue-message: > + 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. + close-issue-message: > + Closing due to 3 months of inactivity. If this is still relevant, + please comment and we can reopen. + exempt-issue-labels: pinned,keep-open,security + operations-per-run: 100 diff --git a/.github/workflows/pr-tidy.yml b/.github/workflows/pr-tidy.yml new file mode 100644 index 00000000..82342963 --- /dev/null +++ b/.github/workflows/pr-tidy.yml @@ -0,0 +1,31 @@ +name: PR Tidy + +on: + pull_request_target: + types: [opened] + +permissions: + pull-requests: write + +jobs: + tidy: + runs-on: ubuntu-latest + steps: + - name: Clean PR description + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + if (!pr.body) return; + + // Remove HTML comment blocks (template instructions) + let body = pr.body.replace(//g, '').trim(); + + if (body !== pr.body) { + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + body: body + }); + } diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml new file mode 100644 index 00000000..4e74b164 --- /dev/null +++ b/.github/workflows/pre-release.yaml @@ -0,0 +1,549 @@ +name: Pre-Release + +# Dry-run of the release pipelines: build wheels like Release / Release Non-CUDA / +# Release CUDA 13, validate artifacts, but do not create a GitHub Release or publish to PyPI. +# +# Trigger by pushing a pre-release tag, for example: +# git tag v1.0.0-rc1 && git push origin v1.0.0-rc1 +on: + push: + tags: + - 'v*-rc*' + - 'v*-alpha*' + - 'v*-beta*' + - 'v*-pre*' + +env: + SCCACHE_GHA_ENABLED: "true" + +jobs: + build-cuda: + name: Build (CUDA 12) + runs-on: ubuntu-22.04 + permissions: + contents: read + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + env: + BUILD_WITH_EP: "1" + TORCH_CUDA_ARCH_LIST: "8.0;9.0" + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set version from tag + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/lib/android + df -h + + - name: Install CUDA Toolkit + uses: Jimver/cuda-toolkit@v0.2.24 + with: + cuda: '12.8.1' + linux-local-args: '["--toolkit"]' + method: 'network' + sub-packages: '["nvcc", "nvrtc-dev"]' + non-cuda-sub-packages: '["libcusparse-dev", "libcublas-dev", "libcusolver-dev"]' + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake .. -DBUILD_UNIT_TESTS=OFF -DUSE_HTTP=ON -DUSE_ETCD=ON -DUSE_CUDA=ON -DWITH_EP=ON -DEP_TORCH_VERSIONS="2.9.1;2.10.0;2.11.0;2.12.0;2.12.1" -DSTORE_USE_ETCD=ON -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + shell: bash + + - name: Build project + run: | + export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LIBRARY_PATH + cd build + make -j + sudo -E make install + shell: bash + + - name: Build nvlink_allocator.so + run: | + export PATH=/usr/local/nvidia/bin:/usr/local/nvidia/lib64:$PATH + export LD_LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LD_LIBRARY_PATH + export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LIBRARY_PATH + mkdir -p build/mooncake-transfer-engine/nvlink-allocator + cd mooncake-transfer-engine/nvlink-allocator + bash build.sh ../../build/mooncake-transfer-engine/nvlink-allocator/ + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_release + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_release.outputs.python_version_tag }} ./scripts/build_wheel.sh + env: + VERSION: ${{ env.VERSION }} + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: pre-release-cuda-py${{ steps.generate_tag_release.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + + build-non-cuda: + name: Build (Non-CUDA) + runs-on: ubuntu-22.04 + permissions: + contents: read + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + env: + BUILD_WITH_EP: "0" + NON_CUDA_BUILD: "1" + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set version from tag + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake .. -DUSE_HTTP=ON -DUSE_ETCD=ON -DUSE_CUDA=OFF -DWITH_EP=OFF -DSTORE_USE_ETCD=ON -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + shell: bash + + - name: Build project + run: | + cd build + make -j + sudo make install + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_release + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_release.outputs.python_version_tag }} ./scripts/build_wheel.sh + env: + VERSION: ${{ env.VERSION }} + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: pre-release-non-cuda-py${{ steps.generate_tag_release.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + + build-cuda13: + name: Build (CUDA 13) + runs-on: ubuntu-22.04 + permissions: + contents: read + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + env: + BUILD_WITH_EP: "1" + CU13_BUILD: "1" + TORCH_CUDA_ARCH_LIST: "8.0;9.0" + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set version from tag + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/lib/android + df -h + + - name: Install CUDA Toolkit 13 + uses: Jimver/cuda-toolkit@v0.2.29 + with: + cuda: '13.0.2' + linux-local-args: '["--toolkit"]' + method: 'network' + sub-packages: '["nvcc", "nvrtc-dev"]' + non-cuda-sub-packages: '["libcusparse-dev", "libcublas-dev", "libcusolver-dev"]' + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake .. -DBUILD_UNIT_TESTS=OFF -DUSE_HTTP=ON -DUSE_ETCD=ON -DUSE_CUDA=ON -DWITH_EP=ON -DEP_TORCH_VERSIONS="2.9.1;2.10.0;2.11.0;2.12.0;2.12.1" -DSTORE_USE_ETCD=ON -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + shell: bash + + - name: Build project + run: | + export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LIBRARY_PATH + cd build + make -j + sudo make install + shell: bash + + - name: Build nvlink_allocator.so + run: | + export PATH=/usr/local/nvidia/bin:/usr/local/nvidia/lib64:$PATH + export LD_LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LD_LIBRARY_PATH + export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LIBRARY_PATH + mkdir -p build/mooncake-transfer-engine/nvlink-allocator + cd mooncake-transfer-engine/nvlink-allocator + bash build.sh ../../build/mooncake-transfer-engine/nvlink-allocator/ + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_release + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_release.outputs.python_version_tag }} ./scripts/build_wheel.sh + env: + VERSION: ${{ env.VERSION }} + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: pre-release-cuda13-py${{ steps.generate_tag_release.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + + build-cuda-arm64: + name: Build (CUDA 12, arm64) + runs-on: ubuntu-22.04-arm + permissions: + contents: read + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + env: + TORCH_CUDA_ARCH_LIST: "9.0" + CUDA_HOME: "/usr/local/cuda" + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set version from tag + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + + - name: Install CUDA Toolkit 12.8 (arm64 SBSA) + run: | + wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/sbsa/cuda-keyring_1.1-1_all.deb + sudo dpkg -i cuda-keyring_1.1-1_all.deb + sudo apt-get update + sudo apt-get install -y cuda-toolkit-12-8 + echo "/usr/local/cuda/bin" >> $GITHUB_PATH + /usr/local/cuda/bin/nvcc --version + shell: bash + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake -G Ninja .. -DUSE_HTTP=ON -DUSE_CUDA=ON -DUSE_MNNVL=ON -DWITH_EP=OFF -DWITH_STORE_RUST=OFF -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + shell: bash + + - name: Build project + run: | + export CUDA_HOME=/usr/local/cuda + for dir in /usr/local/cuda/lib64/stubs /usr/local/cuda/targets/*/lib/stubs; do + if [ -d "$dir" ]; then + export LIBRARY_PATH="$dir:${LIBRARY_PATH:-}" + fi + done + cd build + cmake --build . + sudo cmake --install . + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_arm64 + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export CUDA_HOME=/usr/local/cuda + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_arm64.outputs.python_version_tag }} ./scripts/build_wheel.sh + env: + VERSION: ${{ env.VERSION }} + shell: bash + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: pre-release-cuda-arm64-py${{ steps.generate_tag_arm64.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_arm64.outputs.python_version_tag }}/*.whl + + build-cuda13-arm64: + name: Build (CUDA 13, arm64) + runs-on: ubuntu-22.04-arm + permissions: + contents: read + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + env: + CU13_BUILD: "1" + TORCH_CUDA_ARCH_LIST: "9.0" + CUDA_HOME: "/usr/local/cuda" + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set version from tag + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + + - name: Install CUDA Toolkit 13.0 (arm64 SBSA) + run: | + wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/sbsa/cuda-keyring_1.1-1_all.deb + sudo dpkg -i cuda-keyring_1.1-1_all.deb + sudo apt-get update + sudo apt-get install -y cuda-toolkit-13-0 + echo "/usr/local/cuda/bin" >> $GITHUB_PATH + /usr/local/cuda/bin/nvcc --version + shell: bash + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake -G Ninja .. -DUSE_HTTP=ON -DUSE_CUDA=ON -DUSE_MNNVL=ON -DWITH_EP=OFF -DWITH_STORE_RUST=OFF -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + shell: bash + + - name: Build project + run: | + export CUDA_HOME=/usr/local/cuda + for dir in /usr/local/cuda/lib64/stubs /usr/local/cuda/targets/*/lib/stubs; do + if [ -d "$dir" ]; then + export LIBRARY_PATH="$dir:${LIBRARY_PATH:-}" + fi + done + cd build + cmake --build . + sudo cmake --install . + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_arm64 + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export CUDA_HOME=/usr/local/cuda + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_arm64.outputs.python_version_tag }} ./scripts/build_wheel.sh + env: + VERSION: ${{ env.VERSION }} + shell: bash + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: pre-release-cuda13-arm64-py${{ steps.generate_tag_arm64.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_arm64.outputs.python_version_tag }}/*.whl + + validate-release: + name: Validate release artifacts + needs: [build-cuda, build-non-cuda, build-cuda13, build-cuda-arm64, build-cuda13-arm64] + runs-on: ubuntu-22.04 + permissions: + contents: read + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Download all pre-release wheel artifacts + uses: actions/download-artifact@v4 + with: + path: mooncake-wheel/dist-all + pattern: pre-release-* + + - name: Prepare wheels for validation + run: | + mkdir -p mooncake-wheel/dist-release + find mooncake-wheel/dist-all -name "*.whl" -exec cp {} mooncake-wheel/dist-release/ \; + echo "Pre-release tag: ${GITHUB_REF_NAME}" + echo "Collected wheels:" + ls -la mooncake-wheel/dist-release/ + wheel_count=$(find mooncake-wheel/dist-release -name "*.whl" | wc -l) + echo "wheel_count=${wheel_count}" >> "$GITHUB_ENV" + if [ "${wheel_count}" -lt 20 ]; then + echo "Expected at least 20 wheels (x86: 4 Python versions x 3 variants; arm64: 4 Python versions x 2 CUDA variants), found ${wheel_count}" + exit 1 + fi + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Validate wheels with twine + run: | + pip install twine + twine check mooncake-wheel/dist-release/*.whl + + - name: Upload validated wheels as workflow artifacts + uses: actions/upload-artifact@v4 + with: + name: pre-release-wheels-${{ github.ref_name }} + path: mooncake-wheel/dist-release/*.whl + retention-days: 14 + + - name: Pre-release validation summary + run: | + echo "## Pre-Release validation passed" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "- Tag: \`${GITHUB_REF_NAME}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Wheels built: ${wheel_count}" >> "$GITHUB_STEP_SUMMARY" + echo "- PyPI publish: skipped (pre-release dry run)" >> "$GITHUB_STEP_SUMMARY" + echo "- GitHub Release upload: skipped (pre-release dry run)" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release-cuda13.yaml b/.github/workflows/release-cuda13.yaml index 42d8fc0b..e12bb16b 100644 --- a/.github/workflows/release-cuda13.yaml +++ b/.github/workflows/release-cuda13.yaml @@ -10,6 +10,7 @@ env: jobs: build: runs-on: ubuntu-22.04 + container: pytorch/manylinux2_28-builder:cuda13.0 permissions: contents: write strategy: @@ -23,27 +24,15 @@ jobs: - name: Checkout source uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} + - name: Mark workspace safe for git (container runs as root) + run: git config --global --add safe.directory '*' - - name: Free up disk space + - name: Select Python ${{ matrix.python-version }} from manylinux image run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /opt/hostedtoolcache/CodeQL - sudo rm -rf /usr/local/lib/android - df -h - - - name: Install CUDA Toolkit 13 - uses: Jimver/cuda-toolkit@v0.2.29 - with: - cuda: '13.0.2' - linux-local-args: '["--toolkit"]' - method: 'network' - sub-packages: '["nvcc", "nvrtc-dev"]' - non-cuda-sub-packages: '["libcusparse-dev", "libcublas-dev", "libcusolver-dev"]' + PYV_NODOT=$(echo "${{ matrix.python-version }}" | tr -d '.') + PYBIN="/opt/python/cp${PYV_NODOT}-cp${PYV_NODOT}/bin" + echo "$PYBIN" >> "$GITHUB_PATH" + "$PYBIN/pip" install --quiet "cmake<4" setuptools wheel - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.9 @@ -55,17 +44,13 @@ jobs: core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - name: Run sccache stat for check - shell: bash - run: ${SCCACHE_PATH} --show-stats - - name: Configure project run: | - sudo apt update -y - sudo bash -x dependencies.sh -y + bash -x dependencies.sh -y + echo "/usr/local/go/bin" >> "$GITHUB_PATH" mkdir build cd build - cmake .. -DBUILD_UNIT_TESTS=OFF -DUSE_HTTP=ON -DUSE_ETCD=ON -DUSE_CUDA=ON -DWITH_EP=ON -DEP_TORCH_VERSIONS="2.9.0;2.9.1;2.10.0;2.11.0" -DSTORE_USE_ETCD=ON -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + cmake .. -DBUILD_UNIT_TESTS=OFF -DUSE_HTTP=ON -DUSE_ETCD=ON -DUSE_CUDA=ON -DWITH_EP=ON -DEP_TORCH_VERSIONS="2.9.1;2.10.0;2.11.0;2.12.0;2.12.1" -DSTORE_USE_ETCD=ON -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release -DPython3_EXECUTABLE="$(which python3)" shell: bash - name: Build project @@ -73,7 +58,7 @@ jobs: export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LIBRARY_PATH cd build make -j - sudo make install + make install shell: bash - name: Build nvlink_allocator.so @@ -86,6 +71,11 @@ jobs: bash build.sh ../../build/mooncake-transfer-engine/nvlink-allocator/ shell: bash + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + - name: Generate Python version tag id: generate_tag_release run: | @@ -106,8 +96,102 @@ jobs: name: mooncake-wheel-cuda13-py${{ steps.generate_tag_release.outputs.python_version_tag }} path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + build-arm64: + if: ${{ !contains(github.ref_name, '-') }} + runs-on: ubuntu-22.04-arm + permissions: + contents: write + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + env: + CU13_BUILD: "1" + TORCH_CUDA_ARCH_LIST: "9.0" + CUDA_HOME: "/usr/local/cuda" + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + + - name: Install CUDA Toolkit 13.0 (arm64 SBSA) + run: | + wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/sbsa/cuda-keyring_1.1-1_all.deb + sudo dpkg -i cuda-keyring_1.1-1_all.deb + sudo apt-get update + sudo apt-get install -y cuda-toolkit-13-0 + echo "/usr/local/cuda/bin" >> $GITHUB_PATH + /usr/local/cuda/bin/nvcc --version + shell: bash + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake -G Ninja .. -DUSE_HTTP=ON -DUSE_CUDA=ON -DUSE_MNNVL=ON -DWITH_EP=OFF -DWITH_STORE_RUST=OFF -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + shell: bash + + - name: Build project + run: | + export CUDA_HOME=/usr/local/cuda + for dir in /usr/local/cuda/lib64/stubs /usr/local/cuda/targets/*/lib/stubs; do + if [ -d "$dir" ]; then + export LIBRARY_PATH="$dir:${LIBRARY_PATH:-}" + fi + done + cd build + cmake --build . + sudo cmake --install . + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_release + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export CUDA_HOME=/usr/local/cuda + export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:/usr/local/lib" + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_release.outputs.python_version_tag }} ./scripts/build_wheel.sh + shell: bash + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: mooncake-wheel-cuda13-arm64-py${{ steps.generate_tag_release.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + publish-release: - needs: build + if: ${{ !contains(github.ref_name, '-') }} + needs: [build, build-arm64] runs-on: ubuntu-22.04 permissions: contents: write @@ -120,7 +204,7 @@ jobs: uses: actions/download-artifact@v4 with: path: mooncake-wheel/dist-all - pattern: mooncake-wheel-cuda13-py* + pattern: mooncake-wheel-cuda13* - name: Prepare wheels for release run: | diff --git a/.github/workflows/release-efa-non-cuda.yaml b/.github/workflows/release-efa-non-cuda.yaml new file mode 100644 index 00000000..9916ba4b --- /dev/null +++ b/.github/workflows/release-efa-non-cuda.yaml @@ -0,0 +1,155 @@ +name: Release EFA Non-CUDA + +on: + push: + tags: + - 'v*' + +# Publishes the AWS EFA (libfabric) non-CUDA wheel variant: +# mooncake-transfer-engine-efa-non-cuda USE_EFA=ON USE_CUDA=OFF (CPU/DRAM only) +# The CUDA variant (mooncake-transfer-engine-efa) is built by release-efa.yaml +# — split into its own workflow because each PyPI package publishes from a +# dedicated release workflow (trusted publisher / artifact pattern is +# per-package), mirroring release.yaml vs release-non-cuda.yaml. +# +# No EFA hardware is needed to build: USE_EFA only needs libfabric headers/lib +# to compile/link. auditwheel excludes libfabric/libefa from the wheel so they +# resolve to the user's system AWS EFA install (/opt/amazon/efa/lib) at +# runtime. The distro libfabric (1.x) used to build is ABI-forward-compatible +# with the AWS EFA libfabric (2.x) loaded at runtime; the EFA transport uses +# only long-stable fi_* core APIs. +env: + SCCACHE_GHA_ENABLED: "true" +jobs: + build: + runs-on: ubuntu-22.04 + permissions: + contents: write + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + env: + EFA_NON_CUDA_BUILD: "1" + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/lib/android + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo apt install -y libfabric-dev libfabric1 + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake .. \ + -DUSE_HTTP=ON \ + -DUSE_ETCD=ON \ + -DUSE_CUDA=OFF \ + -DWITH_EP=OFF \ + -DSTORE_USE_ETCD=ON \ + -DUSE_EFA=ON \ + -DLIBFABRIC_INCLUDE_DIR=/usr/include \ + -DLIBFABRIC_LIBRARY=/usr/lib/x86_64-linux-gnu/libfabric.so \ + -DENABLE_SCCACHE=ON \ + -DCMAKE_BUILD_TYPE=Release + shell: bash + + - name: Build project + run: | + cd build + make -j + sudo make install + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_release + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_release.outputs.python_version_tag }} ./scripts/build_wheel.sh + env: + VERSION: ${{ env.VERSION }} + + - name: Verify libfabric is excluded from the wheel + run: | + WHL=$(ls mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl | head -1) + echo "Inspecting $WHL" + if unzip -l "$WHL" | grep -iE 'libfabric|libefa'; then + echo "::error::libfabric/libefa must NOT be bundled in the EFA wheel" + exit 1 + fi + echo "OK: libfabric/libefa correctly excluded" + shell: bash + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: mooncake-wheel-efa-non-cuda-py${{ steps.generate_tag_release.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + + publish-release: + if: ${{ !contains(github.ref_name, '-') }} + needs: build + runs-on: ubuntu-22.04 + permissions: + contents: write + id-token: write + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Download all wheel artifacts + uses: actions/download-artifact@v4 + with: + path: mooncake-wheel/dist-all + pattern: mooncake-wheel-efa-non-cuda-py* + + - name: Prepare wheels for release + run: | + mkdir -p mooncake-wheel/dist-release + find mooncake-wheel/dist-all -name "*.whl" -exec cp {} mooncake-wheel/dist-release/ \; + echo "Collected wheels for release:" + ls -la mooncake-wheel/dist-release/ + + - name: Upload wheels to GitHub Release + uses: softprops/action-gh-release@v1 + with: + files: mooncake-wheel/dist-release/*.whl + + - name: Publish package to PyPI + if: github.repository == 'kvcache-ai/Mooncake' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: mooncake-wheel/dist-release/ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/release-efa.yaml b/.github/workflows/release-efa.yaml new file mode 100644 index 00000000..cc327358 --- /dev/null +++ b/.github/workflows/release-efa.yaml @@ -0,0 +1,166 @@ +name: Release EFA + +on: + push: + tags: + - 'v*' + +# Publishes the AWS EFA (libfabric) CUDA wheel variant: +# mooncake-transfer-engine-efa USE_EFA=ON USE_CUDA=ON (GPU, GPUDirect/FI_HMEM_CUDA) +# The non-CUDA variant (mooncake-transfer-engine-efa-non-cuda) is built by +# release-efa-non-cuda.yaml — split into its own workflow because each PyPI +# package publishes from a dedicated release workflow (trusted publisher / +# artifact pattern is per-package), mirroring release.yaml vs release-non-cuda.yaml. +# +# No EFA hardware is needed to build: USE_EFA only needs libfabric headers/lib +# to compile/link. auditwheel excludes libfabric/libefa from the wheel so they +# resolve to the user's system AWS EFA install (/opt/amazon/efa/lib) at +# runtime. The distro libfabric (1.x) used to build is ABI-forward-compatible +# with the AWS EFA libfabric (2.x) loaded at runtime; the EFA transport uses +# only long-stable fi_* core APIs. +env: + SCCACHE_GHA_ENABLED: "true" +jobs: + build: + runs-on: ubuntu-22.04 + permissions: + contents: write + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + env: + EFA_BUILD: "1" + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/lib/android + + - name: Install CUDA Toolkit + uses: Jimver/cuda-toolkit@v0.2.24 + with: + cuda: '12.8.1' + method: 'network' + sub-packages: '["nvcc", "nvrtc-dev"]' + non-cuda-sub-packages: '["libcusparse-dev", "libcublas-dev", "libcusolver-dev"]' + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo apt install -y libfabric-dev libfabric1 + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake .. \ + -DUSE_HTTP=ON \ + -DUSE_ETCD=ON \ + -DUSE_CUDA=ON \ + -DWITH_EP=OFF \ + -DSTORE_USE_ETCD=ON \ + -DUSE_EFA=ON \ + -DLIBFABRIC_INCLUDE_DIR=/usr/include \ + -DLIBFABRIC_LIBRARY=/usr/lib/x86_64-linux-gnu/libfabric.so \ + -DENABLE_SCCACHE=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_EXE_LINKER_FLAGS=-L/usr/local/cuda/lib64/stubs + shell: bash + + - name: Build project + run: | + export LD_LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LD_LIBRARY_PATH + export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LIBRARY_PATH + cd build + make -j + sudo make install + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_release + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_release.outputs.python_version_tag }} ./scripts/build_wheel.sh + env: + VERSION: ${{ env.VERSION }} + + - name: Verify libfabric is excluded from the wheel + run: | + WHL=$(ls mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl | head -1) + echo "Inspecting $WHL" + if unzip -l "$WHL" | grep -iE 'libfabric|libefa'; then + echo "::error::libfabric/libefa must NOT be bundled in the EFA wheel" + exit 1 + fi + echo "OK: libfabric/libefa correctly excluded" + shell: bash + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: mooncake-wheel-efa-py${{ steps.generate_tag_release.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + + publish-release: + if: ${{ !contains(github.ref_name, '-') }} + needs: build + runs-on: ubuntu-22.04 + permissions: + contents: write + id-token: write + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Download all wheel artifacts + uses: actions/download-artifact@v4 + with: + path: mooncake-wheel/dist-all + pattern: mooncake-wheel-efa-py* + + - name: Prepare wheels for release + run: | + mkdir -p mooncake-wheel/dist-release + find mooncake-wheel/dist-all -name "*.whl" -exec cp {} mooncake-wheel/dist-release/ \; + echo "Collected wheels for release:" + ls -la mooncake-wheel/dist-release/ + + - name: Upload wheels to GitHub Release + uses: softprops/action-gh-release@v1 + with: + files: mooncake-wheel/dist-release/*.whl + + - name: Publish package to PyPI + if: github.repository == 'kvcache-ai/Mooncake' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: mooncake-wheel/dist-release/ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/release-musa.yaml b/.github/workflows/release-musa.yaml new file mode 100644 index 00000000..577c2344 --- /dev/null +++ b/.github/workflows/release-musa.yaml @@ -0,0 +1,161 @@ +name: Release MUSA + +on: + push: + tags: + - 'v*' + +jobs: + build: + if: ${{ !contains(github.ref_name, '-') }} + runs-on: ubuntu-22.04 + container: mthreads/musa:rc4.3.0-devel-ubuntu22.04-amd64 + + permissions: + contents: write + + strategy: + max-parallel: 2 + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + + env: + BUILD_WITH_EP: "0" + MUSA_BUILD: "1" + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Mark repository as safe + shell: bash + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Set Python bin + shell: bash + run: | + set -euo pipefail + PYTHON_BIN="$(command -v python${{ matrix.python-version }})" + "$PYTHON_BIN" --version + "$PYTHON_BIN" -m pip --version + echo "PYTHON_BIN=${PYTHON_BIN}" >> "$GITHUB_ENV" + + - name: Install dependencies + shell: bash + run: | + set -eo pipefail + export PYTHONPATH="${PYTHONPATH:-}" + export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH:-}" + bash -x dependencies.sh -y + echo "PATH=/usr/local/go/bin:/usr/local/musa/bin:${PATH}" >> "$GITHUB_ENV" + echo "LD_LIBRARY_PATH=/usr/local/musa/lib:${LD_LIBRARY_PATH:-}" >> "$GITHUB_ENV" + echo "LIBRARY_PATH=/usr/local/musa/lib:${LIBRARY_PATH:-}" >> "$GITHUB_ENV" + + - name: Configure project + shell: bash + run: | + set -eo pipefail + export PYTHONPATH="${PYTHONPATH:-}" + export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH:-}" + rm -rf build + mkdir build + cd build + cmake_args=( + -DUSE_MUSA=ON + -DUSE_HTTP=ON + -DUSE_ETCD=ON + -DSTORE_USE_ETCD=ON + -DBUILD_UNIT_TESTS=OFF + -DENABLE_DEBUG_SYMBOLS=OFF + -DCMAKE_BUILD_TYPE=Release + -DPython3_EXECUTABLE="${PYTHON_BIN}" + ) + cmake -G Ninja .. "${cmake_args[@]}" + + - name: Build project + shell: bash + run: | + set -eo pipefail + export PYTHONPATH="${PYTHONPATH:-}" + export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH:-}" + cd build + cmake --build . -j"$(nproc)" + + - name: Build MUSA allocator + shell: bash + run: | + set -eo pipefail + mkdir -p build/mooncake-transfer-engine/nvlink-allocator + cd mooncake-transfer-engine/nvlink-allocator + bash build.sh --use-mcc ../../build/mooncake-transfer-engine/nvlink-allocator/ + + - name: Install project + shell: bash + run: | + set -eo pipefail + cd build + cmake --install . + + - name: Generate Python version tag + id: generate_tag_release + shell: bash + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + + - name: Build Python wheel + shell: bash + run: | + set -eo pipefail + export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:/usr/local/lib:/usr/local/musa/lib" + MUSA_BUILD=1 PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-musa-py${{ steps.generate_tag_release.outputs.python_version_tag }} ./scripts/build_wheel.sh + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: mooncake-wheel-musa-x86_64-py${{ steps.generate_tag_release.outputs.python_version_tag }} + path: mooncake-wheel/dist-musa-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + + publish-release: + if: ${{ !contains(github.ref_name, '-') }} + needs: build + runs-on: ubuntu-22.04 + environment: pypi + + permissions: + contents: write + id-token: write + + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Download all wheel artifacts + uses: actions/download-artifact@v4 + with: + path: mooncake-wheel/dist-all + pattern: mooncake-wheel-musa-* + + - name: Prepare wheels for release + run: | + mkdir -p mooncake-wheel/dist-release + find mooncake-wheel/dist-all -name "*.whl" -exec cp {} mooncake-wheel/dist-release/ \; + echo "Collected wheels for release:" + ls -la mooncake-wheel/dist-release/ + + - name: Upload wheels to GitHub Release + uses: softprops/action-gh-release@v1 + with: + files: mooncake-wheel/dist-release/*.whl + + - name: Publish package to PyPI + if: github.repository == 'kvcache-ai/Mooncake' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: mooncake-wheel/dist-release/ diff --git a/.github/workflows/release-non-cuda.yaml b/.github/workflows/release-non-cuda.yaml index ab9b8424..18527173 100644 --- a/.github/workflows/release-non-cuda.yaml +++ b/.github/workflows/release-non-cuda.yaml @@ -43,10 +43,6 @@ jobs: core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - name: Run sccache stat for check - shell: bash - run: ${SCCACHE_PATH} --show-stats - - name: Configure project run: | sudo apt update -y @@ -63,6 +59,11 @@ jobs: sudo make install shell: bash + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + - name: Generate Python version tag id: generate_tag_release run: | @@ -84,6 +85,7 @@ jobs: path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl publish-release: + if: ${{ !contains(github.ref_name, '-') }} needs: build runs-on: ubuntu-22.04 permissions: @@ -120,4 +122,3 @@ jobs: with: packages-dir: mooncake-wheel/dist-release/ password: ${{ secrets.PYPI_API_TOKEN }} - diff --git a/.github/workflows/release-npu.yaml b/.github/workflows/release-npu.yaml new file mode 100644 index 00000000..dae2fd9b --- /dev/null +++ b/.github/workflows/release-npu.yaml @@ -0,0 +1,196 @@ +name: Release Ascend NPU + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Release tag (e.g. v0.1.0)' + required: true + type: string + +jobs: + build: + if: ${{ (github.event_name == 'push' && !contains(github.ref_name, '-')) || (github.event_name == 'workflow_dispatch' && startsWith(inputs.tag, 'v') && !contains(inputs.tag, '-')) }} + + strategy: + max-parallel: 2 + matrix: + arch: [aarch64, x86_64] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + include: + - arch: aarch64 + runner: ubuntu-22.04-arm + cann_arch: aarch64 + - arch: x86_64 + runner: ubuntu-22.04 + cann_arch: x86_64 + + runs-on: ${{ matrix.runner }} + + permissions: + contents: write + + env: + BUILD_WITH_EP: "0" + NPU_BUILD: "1" + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }} + fetch-depth: 0 + + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Set Python bin + shell: bash + run: | + set -euo pipefail + PYTHON_BIN="$(command -v python${{ matrix.python-version }})" + "$PYTHON_BIN" --version + "$PYTHON_BIN" -m pip --version + echo "PYTHON_BIN=${PYTHON_BIN}" >> "$GITHUB_ENV" + + - name: Install CANN Toolkit + shell: bash + run: | + set -euo pipefail + CANN_BASE_URL="https://ascend.devcloud.huaweicloud.com/cann/run/software" + CANN_VERSION=$(curl -s "${CANN_BASE_URL}/" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+(?=/)' | sort -V | tail -1) + echo "Latest CANN version: ${CANN_VERSION}" + CANN_URL="${CANN_BASE_URL}/${CANN_VERSION}/${{ matrix.cann_arch }}/Ascend-cann-toolkit_${CANN_VERSION}_linux-${{ matrix.cann_arch }}.run" + echo "Downloading CANN ${CANN_VERSION} from ${CANN_URL}" + wget -q --show-progress -O /tmp/cann_toolkit.run "${CANN_URL}" + chmod +x /tmp/cann_toolkit.run + sudo /tmp/cann_toolkit.run --install --install-for-all --install-path=/usr/local/Ascend --quiet + rm -f /tmp/cann_toolkit.run + + - name: Build and install HIXL + shell: bash + run: | + set -eo pipefail + export PYTHONPATH="${PYTHONPATH:-}" + export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH:-}" + source /usr/local/Ascend/cann/set_env.sh + sudo ${PYTHON_BIN} -m pip install setuptools + cd /tmp + git clone https://gitcode.com/cann/hixl.git + cd hixl + bash build.sh -j$(nproc) + sudo ./build_out/cann-hixl_*.run --full --quiet --pylocal --install-path=/usr/local/Ascend + + - name: Install dependencies + shell: bash + run: | + set -eo pipefail + export PYTHONPATH="${PYTHONPATH:-}" + export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH:-}" + source /usr/local/Ascend/cann/set_env.sh + sudo bash -x dependencies.sh -y + sudo bash scripts/ascend/dependencies_ascend_installation.sh + echo "PATH=/usr/local/go/bin:${PATH}" >> "$GITHUB_ENV" + + - name: Configure project + shell: bash + run: | + set -eo pipefail + export PYTHONPATH="${PYTHONPATH:-}" + export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH:-}" + source /usr/local/Ascend/cann/set_env.sh + rm -rf build + mkdir build + cd build + cmake_args=( + -DUSE_ASCEND_DIRECT=ON + -DUSE_ETCD=ON + -DSTORE_USE_ETCD=ON + -DBUILD_UNIT_TESTS=OFF + -DCMAKE_BUILD_TYPE=Release + -DPython3_EXECUTABLE="${PYTHON_BIN}" + -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=OFF + -DCMAKE_BUILD_WITH_INSTALL_RPATH=OFF + -DCMAKE_SKIP_INSTALL_RPATH=ON + ) + cmake .. "${cmake_args[@]}" + + - name: Build project + shell: bash + run: | + set -eo pipefail + export PYTHONPATH="${PYTHONPATH:-}" + export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH:-}" + source /usr/local/Ascend/cann/set_env.sh + export PATH="/usr/local/go/bin:${PATH}" + cd build + cmake --build . -j"$(nproc)" + sudo cmake --install . + + - name: Generate Python version tag + id: generate_tag_release + shell: bash + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + + - name: Build Python wheel + shell: bash + run: | + set -eo pipefail + export PYTHONPATH="${PYTHONPATH:-}" + export CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH:-}" + source /usr/local/Ascend/cann/set_env.sh + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib + NPU_BUILD=1 PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-npu-py${{ steps.generate_tag_release.outputs.python_version_tag }} ./scripts/build_wheel.sh + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: mooncake-wheel-npu-${{ matrix.arch }}-py${{ steps.generate_tag_release.outputs.python_version_tag }} + path: mooncake-wheel/dist-npu-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + + publish-release: + if: ${{ (github.event_name == 'push' && !contains(github.ref_name, '-')) || (github.event_name == 'workflow_dispatch' && startsWith(inputs.tag, 'v') && !contains(inputs.tag, '-')) }} + needs: build + runs-on: ubuntu-22.04 + environment: pypi + + permissions: + contents: write + id-token: write + + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }} + + - name: Download all wheel artifacts + uses: actions/download-artifact@v4 + with: + path: mooncake-wheel/dist-all + pattern: mooncake-wheel-npu-* + + - name: Prepare wheels for release + run: | + mkdir -p mooncake-wheel/dist-release + find mooncake-wheel/dist-all -name "*.whl" -exec cp {} mooncake-wheel/dist-release/ \; + echo "Collected wheels for release:" + ls -la mooncake-wheel/dist-release/ + + - name: Upload wheels to GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} + files: mooncake-wheel/dist-release/*.whl + + - name: Publish package to PyPI + if: github.repository == 'kvcache-ai/Mooncake' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: mooncake-wheel/dist-release/ diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 26d5398d..51ea8e60 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,7 +9,10 @@ env: SCCACHE_GHA_ENABLED: "true" jobs: build: + # Skip semver pre-release tags (e.g. v1.0.0-rc1); those are handled by pre-release.yaml. + if: ${{ !contains(github.ref_name, '-') }} runs-on: ubuntu-22.04 + container: pytorch/manylinux2_28-builder:cuda12.8 permissions: contents: write strategy: @@ -22,27 +25,15 @@ jobs: - name: Checkout source uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} + - name: Mark workspace safe for git (container runs as root) + run: git config --global --add safe.directory '*' - - name: Free up disk space + - name: Select Python ${{ matrix.python-version }} from manylinux image run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /opt/hostedtoolcache/CodeQL - sudo rm -rf /usr/local/lib/android - df -h - - - name: Install CUDA Toolkit - uses: Jimver/cuda-toolkit@v0.2.24 - with: - cuda: '12.8.1' - linux-local-args: '["--toolkit"]' - method: 'network' - sub-packages: '["nvcc", "nvrtc-dev"]' - non-cuda-sub-packages: '["libcusparse-dev", "libcublas-dev", "libcusolver-dev"]' + PYV_NODOT=$(echo "${{ matrix.python-version }}" | tr -d '.') + PYBIN="/opt/python/cp${PYV_NODOT}-cp${PYV_NODOT}/bin" + echo "$PYBIN" >> "$GITHUB_PATH" + "$PYBIN/pip" install --quiet "cmake<4" setuptools wheel - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.9 @@ -54,25 +45,21 @@ jobs: core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - name: Run sccache stat for check - shell: bash - run: ${SCCACHE_PATH} --show-stats - - name: Configure project run: | - sudo apt update -y - sudo bash -x dependencies.sh -y + bash -x dependencies.sh -y + echo "/usr/local/go/bin" >> "$GITHUB_PATH" mkdir build cd build - cmake .. -DBUILD_UNIT_TESTS=OFF -DUSE_HTTP=ON -DUSE_ETCD=ON -DUSE_CUDA=ON -DWITH_EP=ON -DEP_TORCH_VERSIONS="2.9.0;2.9.1;2.10.0;2.11.0" -DSTORE_USE_ETCD=ON -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + cmake .. -DBUILD_UNIT_TESTS=OFF -DUSE_HTTP=ON -DUSE_ETCD=ON -DUSE_CUDA=ON -DWITH_EP=ON -DEP_TORCH_VERSIONS="2.9.1;2.10.0;2.11.0;2.12.0;2.12.1" -DSTORE_USE_ETCD=ON -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release -DPython3_EXECUTABLE="$(which python3)" shell: bash - name: Build project run: | - export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:LIBRARY_PATH + export LIBRARY_PATH=/usr/local/cuda/lib64/stubs:$LIBRARY_PATH cd build make -j - sudo make install + make install shell: bash - name: Build nvlink_allocator.so @@ -85,6 +72,11 @@ jobs: bash build.sh ../../build/mooncake-transfer-engine/nvlink-allocator/ shell: bash + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + - name: Generate Python version tag id: generate_tag_release run: | @@ -105,8 +97,100 @@ jobs: name: mooncake-wheel-py${{ steps.generate_tag_release.outputs.python_version_tag }} path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + build-arm64: + if: ${{ !contains(github.ref_name, '-') }} + runs-on: ubuntu-22.04-arm + permissions: + contents: write + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + env: + TORCH_CUDA_ARCH_LIST: "9.0" + CUDA_HOME: "/usr/local/cuda" + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + + - name: Install CUDA Toolkit 12.8 (arm64 SBSA) + run: | + wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/sbsa/cuda-keyring_1.1-1_all.deb + sudo dpkg -i cuda-keyring_1.1-1_all.deb + sudo apt-get update + sudo apt-get install -y cuda-toolkit-12-8 + echo "/usr/local/cuda/bin" >> $GITHUB_PATH + /usr/local/cuda/bin/nvcc --version + shell: bash + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Configure sccache + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Configure project + run: | + sudo apt update -y + sudo bash -x dependencies.sh -y + mkdir build + cd build + cmake -G Ninja .. -DUSE_HTTP=ON -DUSE_CUDA=ON -DUSE_MNNVL=ON -DWITH_EP=OFF -DWITH_STORE_RUST=OFF -DENABLE_SCCACHE=ON -DCMAKE_BUILD_TYPE=Release + shell: bash + + - name: Build project + run: | + export CUDA_HOME=/usr/local/cuda + for dir in /usr/local/cuda/lib64/stubs /usr/local/cuda/targets/*/lib/stubs; do + if [ -d "$dir" ]; then + export LIBRARY_PATH="$dir:${LIBRARY_PATH:-}" + fi + done + cd build + cmake --build . + sudo cmake --install . + shell: bash + + - name: Run sccache stat for check + if: ${{ env.SCCACHE_PATH != '' }} + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Generate Python version tag + id: generate_tag_release + run: | + echo "python_version_tag=$(echo ${{ matrix.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT + shell: bash + + - name: Build Python wheel + run: | + export CUDA_HOME=/usr/local/cuda + export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:/usr/local/lib" + PYTHON_VERSION=${{ matrix.python-version }} OUTPUT_DIR=dist-py${{ steps.generate_tag_release.outputs.python_version_tag }} ./scripts/build_wheel.sh + shell: bash + + - name: Upload Python wheel artifact + uses: actions/upload-artifact@v4 + with: + name: mooncake-wheel-arm64-py${{ steps.generate_tag_release.outputs.python_version_tag }} + path: mooncake-wheel/dist-py${{ steps.generate_tag_release.outputs.python_version_tag }}/*.whl + publish-release: - needs: build + needs: [build, build-arm64] runs-on: ubuntu-22.04 permissions: contents: write diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..d4b92c8f --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,41 @@ +name: Close Stale Issues and PRs + +on: + schedule: + - cron: '37 2 * * *' + workflow_dispatch: {} + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed in 30 days if no further activity occurs. + If this issue is still relevant, please comment to keep it open. + stale-pr-message: > + This PR has been automatically marked as stale because it has not had + recent activity. It will be closed in 14 days if no further activity occurs. + Please update the PR or comment to keep it open. + close-issue-message: > + This issue was closed because it has been inactive for 120 days. + Feel free to reopen if this is still relevant. + close-pr-message: > + This PR was closed because it has been inactive for 104 days. + Feel free to reopen if you'd like to continue the work. + days-before-stale: 120 + days-before-close: 30 + days-before-pr-stale: 120 + days-before-pr-close: 30 + stale-issue-label: 'stale' + stale-pr-label: 'stale' + exempt-issue-labels: 'pinned,security,RFC' + exempt-pr-labels: 'pinned,work-in-progress' + operations-per-run: 100 diff --git a/.gitignore b/.gitignore index dafcb87c..4a3ec467 100644 --- a/.gitignore +++ b/.gitignore @@ -206,3 +206,9 @@ _codeql_detected_source_root # CodeBuddy Memory .codebuddy/ + +# core dumps +core_* +# MacOS +.DS_Store +.envrc diff --git a/.typos.toml b/.typos.toml index b3fe85df..320972c8 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,13 +1,17 @@ [default] -extend-ignore-words = ["CANN", "ASO", "fre", "wqs"] +extend-ignore-words = ["CANN", "ASO", "fre", "wqs", "hsa"] [default.extend-words] CANN = "CANN" ASO = "ASO" fre = "fre" wqs = "wqs" +# AMD HSA runtime symbol prefix (hsa_*, hsaRes, hsaErr, etc.) — used by the +# ROCm dmabuf MR registration path. +hsa = "hsa" +HPE = "HPE" [files] extend-exclude = [ "mooncake-transfer-engine/tent/include/tent/thirdparty/nlohmann/json.h", -] \ No newline at end of file +] diff --git a/CMakeLists.txt b/CMakeLists.txt index c6dd0699..79c79383 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ option(WITH_P2P_STORE "build p2p store library and sample code" OFF) option(WITH_RUST_EXAMPLE "build the Rust interface and sample code for the transfer engine" OFF) option(WITH_STORE_RUST "build the Rust bindings for the Mooncake Store" ON) option(WITH_EP "build mooncake with expert parallelism support" OFF) +option(USE_NOF "build mooncake store with NoF SSD pool support" OFF) include(${CMAKE_CURRENT_SOURCE_DIR}/mooncake-common/SetupPython.cmake) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extern/pybind11) @@ -56,6 +57,12 @@ if (STORE_USE_K8S_LEASE) add_compile_definitions(STORE_USE_K8S_LEASE) endif() +if (USE_NOF) + add_compile_definitions(USE_NOF) +else() + message(STATUS "USE_NOF=OFF, NoF SSD pool support is disabled") +endif() + option(STORE_USE_JEMALLOC "Use jemalloc in mooncake store master" OFF) # Define ASIO macros before building targets that include ASIO headers. @@ -63,6 +70,7 @@ add_compile_definitions(ASIO_SEPARATE_COMPILATION ASIO_DYN_LINK) add_subdirectory(mooncake-common) include_directories(mooncake-common/etcd) +include_directories(mooncake-common/k8s-lease) include_directories(mooncake-common/include) if (WITH_TE) @@ -94,8 +102,10 @@ if (WITH_EP) include_directories(mooncake-pg/include) else () message(STATUS "WITH_EP enabled: building Mooncake EP and PG Python extensions") - find_package(CUDAToolkit REQUIRED) - message(STATUS "Detected CUDA version: ${CUDAToolkit_VERSION}") + if(USE_CUDA) + find_package(CUDAToolkit REQUIRED) + message(STATUS "Detected CUDA version: ${CUDAToolkit_VERSION}") + endif() # EP_TORCH_VERSIONS: semicolon-separated list of PyTorch versions to build for. # Can be set via -DEP_TORCH_VERSIONS="2.9.1;2.8.0" or the EP_TORCH_VERSIONS env var. @@ -136,6 +146,7 @@ if (WITH_EP) "-DTORCH_CUDA_ARCH_LIST=${_torch_cuda_arch_list_pipe}" "-DSTAGING_DIR=${EP_PG_STAGING_DIR}" "-DENGINE_SO_PATH=$" + "-DEP_USE_MUSA=$,1,0>" -P "${CMAKE_CURRENT_SOURCE_DIR}/mooncake-ep/BuildEpExt.cmake" COMMENT "Building Mooncake EP Python extension(s)" DEPENDS engine @@ -152,6 +163,7 @@ if (WITH_EP) "-DTORCH_CUDA_ARCH_LIST=${_torch_cuda_arch_list_pipe}" "-DSTAGING_DIR=${EP_PG_STAGING_DIR}" "-DENGINE_SO_PATH=$" + "-DEP_USE_MUSA=$,1,0>" -P "${CMAKE_CURRENT_SOURCE_DIR}/mooncake-pg/BuildPgExt.cmake" COMMENT "Building Mooncake PG Python extension(s)" DEPENDS engine mooncake_ep_ext diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fdab919a..b18fa536 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ git commit -m "chore: pre-commit autoupdate" If clang-format is missing, install it (Ubuntu example): ```bash -sudo apt-get update && sudo apt-get install -y clang-format +sudo apt-get update && sudo apt-get install -y clang-format-20 ``` You can temporarily skip hooks: diff --git a/FAST25-release/README.md b/FAST25-release/README.md new file mode 100644 index 00000000..395186de --- /dev/null +++ b/FAST25-release/README.md @@ -0,0 +1,17 @@ +```json +{ + "timestamp": 27482, + "input_length": 6955, + "output_length": 52, + "hash_ids": [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 2353, 2354] +} +{ + "timestamp": 30535, + "input_length": 6472, + "output_length": 26, + "hash_ids": [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 2366] +} +``` +The above presents two samples from our trace dataset. The trace includes the timing of request arrivals, the number of input tokens, the number of output tokens, and the remapped block hash. To protect our customers' privacy, we applied several mechanisms to remove user-related information while preserving the dataset's utility for simulated evaluation. More descriptions of the trace (e.g., up to 50% cache hit ratio) can be found in Section 4 of the technical report. + +**_Update [Feb 21, 2025]: The updated [traces](./traces) used in our FAST'25 paper have been released! Please refer to the paper's appendix (found [here](Mooncake-FAST25.pdf)) for more details._** \ No newline at end of file diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 3e978d9c..297963d9 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -12,6 +12,6 @@ Current list of codeowners on this project: | ---------------------------- | ---------------------------- | ---------------------------- | ---------------------------- | | | | | | | | | | | -| | | | | +| | | | | Want to include your company logo? Just open a Pull Request! \ No newline at end of file diff --git a/README.md b/README.md index 4a1e2ca9..b08dbfee 100644 --- a/README.md +++ b/README.md @@ -6,34 +6,41 @@ Paper | Slides | Traces - | Technical Report - | Blog + | Documentation + | Blog | Slack

- [![Docs](https://img.shields.io/badge/docs-live-brightgreen)](https://kvcache-ai.github.io/Mooncake/) - [![PyPI](https://img.shields.io/pypi/v/mooncake-transfer-engine)](https://pypi.org/project/mooncake-transfer-engine) - [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mooncake-transfer-engine)](https://pypi.org/project/mooncake-transfer-engine) - [![CUDA <=12.9](https://img.shields.io/static/v1?label=CUDA&message=%3C%3D12.9&color=76B900)](https://pypi.org/project/mooncake-transfer-engine) - [![CUDA 13.0/13.1](https://img.shields.io/static/v1?label=CUDA&message=13.0%2F13.1&color=76B900)](https://pypi.org/project/mooncake-transfer-engine-cuda13) - [![PyPI - Downloads](https://img.shields.io/pypi/dm/mooncake-transfer-engine)](https://pypi.org/project/mooncake-transfer-engine) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/kvcache-ai/Mooncake) + [![PyPI - Downloads](https://static.pepy.tech/badge/mooncake-transfer-engine?period=month)](https://pypi.org/project/mooncake-transfer-engine) [![GitHub commit activity](https://img.shields.io/github/commit-activity/w/kvcache-ai/Mooncake)](https://github.com/kvcache-ai/Mooncake/graphs/commit-activity) [![license](https://img.shields.io/github/license/kvcache-ai/mooncake.svg)](https://github.com/kvcache-ai/Mooncake/blob/main/LICENSE-APACHE) + [![Docker](https://img.shields.io/docker/v/kvcacheai/mooncake?label=docker&logo=docker&logoColor=white&color=2496ED)](https://hub.docker.com/r/kvcacheai/mooncake) +
+ [![PyPI](https://img.shields.io/pypi/v/mooncake-transfer-engine)](https://pypi.org/project/mooncake-transfer-engine) + [![PyPI CUDA <=12.9](https://img.shields.io/static/v1?label=pypi&message=CUDA%20%3C%3D12.9&color=76B900)](https://pypi.org/project/mooncake-transfer-engine) + [![PyPI CUDA 13.0/13.1](https://img.shields.io/static/v1?label=pypi&message=CUDA%2013.0%2F13.1&color=76B900)](https://pypi.org/project/mooncake-transfer-engine-cuda13) + [![PyPI Non-CUDA](https://img.shields.io/static/v1?label=pypi&message=non-CUDA&color=00BFFF)](https://pypi.org/project/mooncake-transfer-engine-non-cuda/) + [![PyPI NPU](https://img.shields.io/static/v1?label=pypi&message=NPU&color=F87171)](https://pypi.org/project/mooncake-transfer-engine-npu/)
Mooncake is the serving platform for icon Kimi, a leading LLM service provided by icon Moonshot AI. -Now both the Transfer Engine and Mooncake Store are open-sourced! -This repository also hosts its technical report and the open-sourced traces. +Under real workloads, Mooncake’s innovative architecture enables Kimi to handle 75% more requests while adhering to SLOs.

🔄 Updates

+- **May 7, 2026**: 🚀 [vLLM officially features Mooncake Store](https://vllm.ai/blog/mooncake-store) — a deep dive into how Mooncake's distributed KVCache engine supercharges vLLM inference with high-throughput, memory-efficient, cross-instance KV cache sharing! +- **Apr 29, 2026**: SGLang introduces [RDMA-based P2P weight transfer for large-scale distributed RL](https://lmsys.org/blog/2026-04-29-p2p-update/) using Mooncake TransferEngine, achieving 7x faster weight updates for the 1T-parameter Kimi-K2 model (53s → 7.2s) with zero-copy RDMA transfer across thousands of GPUs. - **Mar 19, 2026**: [TorchSpec: Speculative Decoding Training at Scale](https://pytorch.org/blog/torchspec-speculative-decoding-training-at-scale) is [open sourced](https://github.com/torchspec-project/TorchSpec), using Mooncake to decouple inference and training via efficient hidden states management. - **Mar 5, 2026**: [LightX2V](https://github.com/ModelTC/LightX2V/pull/893) now supports disaggregated deployment based on Mooncake, enabling encoder/transformer service decoupling with Mooncake Transfer Engine for high-performance cross-device and cross-machine data transfer. - **Feb 25, 2026**: [SGLang](https://github.com/sgl-project/sglang) merged [Encoder Global Cache Manager](https://github.com/sgl-project/sglang/pull/16137), introducing a Mooncake-powered global multimodal embedding cache that enables cross-instance sharing of ViT embeddings to avoid redundant GPU computation. + +
+More + - **Feb 24, 2026**: [vLLM-Omni](https://docs.vllm.ai/projects/vllm-omni/en/latest/design/feature/disaggregated_inference/) introduces disaggregated inference connectors with support for both `MooncakeStoreConnector` and `MooncakeTransferEngineConnector` for multi-node omni-modality pipelines. - **Feb 12, 2026**: [Mooncake Joins PyTorch Ecosystem](https://pytorch.org/blog/mooncake-joins-pytorch-ecosystem/) We are thrilled to announce that Mooncake has officially joined the PyTorch Ecosystem! - **Jan 28, 2026**: [FlexKV](https://github.com/taco-project/FlexKV), a distributed KV store and cache system from Tencent and NVIDIA in collaboration with the community, now supports [distributed KVCache reuse](https://github.com/taco-project/FlexKV/blob/main/docs/dist_reuse/README_en.md) with the Mooncake Transfer Engine. @@ -63,127 +70,136 @@ This repository also hosts its technical report and the open-sourced traces. - **June 27, 2024**: We present a series of Chinese blogs with more discussions on zhihu 1, 2, 3, 4, 5, 6, 7. - **June 26, 2024**: Initial technical report release. +

🎉 Overview

-Mooncake features a KVCache-centric disaggregated architecture that separates the prefill and decoding clusters. It also leverages the underutilized CPU, DRAM, and SSD resources of the GPU cluster to implement a disaggregated KVCache pool. - -![architecture](image/architecture.png) - -The core of Mooncake is its KVCache-centric scheduler, which balances maximizing overall effective throughput while meeting latency-related Service Level Objectives (SLOs). Unlike traditional studies that assume all requests will be processed, Mooncake faces challenges in highly overloaded scenarios. To mitigate these, we developed a prediction-based early rejection policy. Experiments show that Mooncake excels in long-context scenarios. Compared to the baseline method, Mooncake can achieve up to a 525% increase in throughput in certain simulated scenarios while adhering to SLOs. Under real workloads, Mooncake’s innovative architecture enables Kimi to handle 75% more requests. - -

🧩 Components

- - +
+ +
-**Mooncake Core Component: Transfer Engine (TE)** -The core of Mooncake is the Transfer Engine (TE), which provides a unified interface for batched data transfer across various storage devices and network links. Supporting multiple protocols including TCP, RDMA, CXL/shared-memory, and NVMe over Fabric (NVMe-of), TE is designed to enable fast and reliable data transfer for AI workloads. Compared to Gloo (used by Distributed PyTorch) and traditional TCP, TE achieves significantly lower I/O latency, making it a superior solution for efficient data transmission. +Mooncake is an infrastructure project for large-scale LLM inference and training. It features a KV cache-centric disaggregated architecture that separates prefill and decode clusters, while leveraging otherwise underutilized CPU, DRAM, and SSD resources in GPU clusters to build a disaggregated KV cache pool. -**P2P Store and Mooncake Store** -Both P2P Store and Mooncake Store are built on the Transfer Engine and provide key/value caching for different scenarios. P2P Store focuses on sharing temporary objects (e.g., checkpoint files) across nodes in a cluster, preventing bandwidth saturation on a single machine. Mooncake Store, on the other hand, supports distributed pooled KVCache, specifically designed for XpYd disaggregation to enhance resource utilization and system performance. - -**Mooncake Integration with Leading LLM Inference Systems** -Mooncake has been seamlessly integrated with several popular large language model (LLM) inference systems. Through collaboration with the vLLM and SGLang teams, Mooncake now officially supports prefill-decode disaggregation. By leveraging the high-efficiency communication capabilities of RDMA devices, Mooncake significantly improves inference efficiency in prefill-decode disaggregation scenarios, providing robust technical support for large-scale distributed inference tasks. -In addition, Mooncake has been successfully integrated with SGLang's Hierarchical KV Caching, vLLM's prefill serving, and LMCache, augmenting KV cache management capabilities across large-scale inference scenarios. - -**Elastic Expert Parallelism Support** -Mooncake adds elasticity and fault tolerance support for MoE model inference, enabling inference systems to remain responsive and recoverable in the event of GPU failures or changes in resource configuration. This functionality includes automatic faulty rank detection and can work with the EPLB module to dynamically route tokens to healthy ranks during inference. - -**Tensor-Centric Ecosystem** -Mooncake establishes a full-stack, Tensor-oriented AI infrastructure where Tensors serve as the fundamental data carrier. The ecosystem spans from the Transfer Engine, which accelerates Tensor data movement across heterogeneous storage (DRAM/VRAM/NVMe), to the P2P Store and Mooncake Store for distributed management of Tensor objects (e.g., Checkpoints and KVCache), up to the Mooncake Backend enabling Tensor-based elastic distributed computing. This architecture is designed to maximize Tensor processing efficiency for large-scale model inference and training. +Mooncake includes a high-performance Transfer Engine for low-latency data movement across heterogeneous networks and accelerators; Mooncake Store for distributed KV cache and model-weight management; and Mooncake EP & PG for elastic MoE serving. Deeply integrated with ecosystems such as SGLang and vLLM, Mooncake helps LLM systems improve cache reuse, reduce serving latency, and scale efficiently across multi-node clusters.

🔥 Show Cases

-### Use Transfer Engine Standalone ([Guide](https://kvcache-ai.github.io/Mooncake/design/transfer-engine/index.html)) +### Transfer Engine (TE) -Transfer Engine is a high-performance data transfer framework. Transfer Engine provides a unified interface to transfer data from DRAM, VRAM or NVMe, while the technical details related to hardware are hidden. Transfer Engine supports multiple communication protocols including TCP, RDMA (InfiniBand/RoCEv2/eRDMA/NVIDIA GPUDirect), NVMe over Fabric (NVMe-of), NVLink, HIP, CXL, and Ascend. When built with the corresponding runtime, Transfer Engine can also detect and route accelerator memory on CUDA, MUSA, HIP, and Cambricon MLU devices. For a complete list of supported protocols and configuration guide, see the [Supported Protocols Documentation](https://kvcache-ai.github.io/Mooncake/getting_started/supported-protocols.html). +The core of Mooncake is the Transfer Engine (TE), a high-performance data transfer framework. TE offers a unified interface for batched data movement across diverse storage, network, and accelerator environments. By supporting multiple transport protocols, topology-aware routing, multi-NIC bandwidth aggregation, and automatic failover, TE delivers low-latency, scalable, and robust data transmission for distributed AI workloads. See the [Transfer Engine guide](https://kvcache-ai.github.io/Mooncake/design/transfer-engine/index.html) for details. + +
+Highlights -#### Highlights - **Efficient use of multiple RDMA NIC devices.** Transfer Engine supports the use of multiple RDMA NIC devices to achieve the *aggregation of transfer bandwidth*. -- **Topology aware path selection.** Transfer Engine can *select optimal devices* based on the location (NUMA affinity, etc.) of both source and destination. +- **Topology-aware path selection.** Transfer Engine can *select optimal devices* based on the location (NUMA affinity, etc.) of both source and destination. -- **More robust against temporary network errors.** Once transmission fails, Transfer Engine will try to use alternative paths for data delivery automatically. +- **Robust against temporary network errors.** Once transmission fails, Transfer Engine will try to use alternative paths for data delivery automatically. -#### Performance -With 40 GB of data (equivalent to the size of the KVCache generated by 128k tokens in the LLaMA3-70B model), Mooncake Transfer Engine delivers up to **87 GB/s** and **190 GB/s** of bandwidth in 4×200 Gbps and 8×400 Gbps RoCE networks respectively, which are about **2.4x and 4.6x faster** than the TCP protocol. +- **Superior performance at scale.** With 40 GB of data (equivalent to the size of the KVCache generated by 128k tokens in the LLaMA3-70B model), Mooncake Transfer Engine delivers up to **87 GB/s** and **190 GB/s** of bandwidth in 4×200 Gbps and 8×400 Gbps RoCE networks respectively, which are about **2.4x and 4.6x faster** than the TCP protocol. -### P2P Store ([Guide](https://kvcache-ai.github.io/Mooncake/design/p2p-store.html)) -P2P Store is built on the Transfer Engine and supports sharing temporary objects between peer nodes in a cluster. P2P Store is ideal for scenarios like checkpoint transfer, where data needs to be rapidly and efficiently shared across a cluster. -**P2P Store has been used in the checkpoint transfer service of Moonshot AI.** +- **Broad support for heterogeneous transports and accelerators.** Transfer Engine provides unified data transfer across diverse protocols, including TCP, RDMA, AWS EFA, NVMe-oF, NVLink, HIP, Barex, CXL, and Ascend-family transports. When built with the corresponding runtime, Transfer Engine can detect accelerator memory and select suitable transport paths for efficient data movement across CUDA, MUSA, HIP, MACA, Cambricon MLU, and Ascend-enabled environments. For a complete list of supported protocols and configuration guide, see the [Supported Protocols Documentation](https://kvcache-ai.github.io/Mooncake/getting_started/supported-protocols.html). -#### Highlights -- **Decentralized architecture.** P2P Store leverages a pure client-side architecture with global metadata managed by the etcd service. +- **Widely adopted across the LLM ecosystem.** TE is used in production inference stacks such as [SGLang](https://github.com/sgl-project/sglang), [vLLM](https://github.com/vllm-project/vllm), [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM), [vLLM-Ascend](https://github.com/vllm-project/vllm-ascend), [checkpoint-engine](https://github.com/MoonshotAI/checkpoint-engine), and [NIXL](https://github.com/ai-dynamo/nixl), among others, to efficiently transfer KV cache, embeddings, model weights, and other data. -- **Efficient data distribution.** Designed to enhance the efficiency of large-scale data distribution, P2P Store *avoids bandwidth saturation* issues by allowing replicated nodes to share data directly. This reduces the CPU/RDMA NIC pressures of data providers (e.g., trainers). +
- +### Mooncake Store - +Mooncake Store is a high-performance distributed key-value cache storage engine designed for LLM inference. Built on the Transfer Engine, it stores and manages reusable KV caches and model weights across inference clusters, with support for efficient object storage, replication, eviction, and high-bandwidth data transfer. See the [Mooncake Store guide](https://kvcache-ai.github.io/Mooncake/design/mooncake-store.html) for details. -### Mooncake Store ([Guide](https://kvcache-ai.github.io/Mooncake/design/mooncake-store.html)) -Mooncake Store is a distributed KVCache storage engine specialized for LLM inference based on Transfer Engine. It is the central component of the KVCache-centric disaggregated architecture. The goal of Mooncake Store is to store the reusable KV caches across various locations in an inference cluster. Mooncake Store has been supported in [SGLang's Hierarchical KV Caching](https://lmsys.org/blog/2025-09-10-sglang-hicache/), [vLLM's prefill serving](https://docs.vllm.ai/en/latest/features/disagg_prefill.html) and is now integrated with [LMCache](https://kvcache-ai.github.io/Mooncake/getting_started/examples/lmcache-integration.html) to provide enhanced KVCache management capabilities. +
+Highlights -#### Highlights -- **Multi-replica support**: Mooncake Store supports storing multiple data replicas for the same object, effectively alleviating hotspots in access pressure. +- **High bandwidth utilization.** Mooncake Store supports large-object striping, parallel I/O, and end-to-end zero-copy data transfer, fully utilizing aggregated bandwidth across multiple NICs. -- **High bandwidth utilization**: Mooncake Store supports striping and parallel I/O transfer of large objects, fully utilizing multi-NIC aggregated bandwidth for high-speed data reads and writes. +- **Multi-tier cache hierarchy**. Mooncake Store supports a multi-level cache design across DRAM and SSD/NVMe, enabling larger cache capacity. -### SGLang Integration ([Guide](https://kvcache-ai.github.io/Mooncake/getting_started/examples/sglang-integration/hicache-integration-v1.html)) +- **Elastic and disaggregated storage.** Mooncake Store decouples KVCache storage from inference engines, allowing storage nodes to be dynamically added or removed while keeping cached data independent from engine restarts, upgrades, and scheduling decisions. -SGLang officially supports Mooncake Store as a [HiCache storage backend](https://lmsys.org/blog/2025-09-10-sglang-hicache/). This integration enables scalable KV cache retention and high-performance access for large-scale LLM serving scenarios. +- **Programmatic object management.** Mooncake Store allows applications to control object placement and lifecycle through per-object policies, including replica counts, preferred segments, soft pin, and hard pin. These controls help inference systems protect important KV caches and model weights while guiding replication, placement, and eviction behavior. + +- **Broad ecosystem adoption.** Mooncake Store is used across the LLM systems ecosystem as a high-performance distributed storage backend for KV caches, hidden states, and model weights. It supports integrations with [SGLang's Hierarchical KV Caching](https://lmsys.org/blog/2025-09-10-sglang-hicache/), [vLLM's prefill serving](https://docs.vllm.ai/en/latest/features/disagg_prefill.html), and [LMCache](https://kvcache-ai.github.io/Mooncake/getting_started/examples/lmcache-integration.html), and has been adopted by systems such as [TorchSpec](https://pytorch.org/blog/torchspec-speculative-decoding-training-at-scale/) and [TransferQueue](https://github.com/Ascend/TransferQueue) to decouple inference, training, and reinforcement-learning workloads through efficient state management and asynchronous data movement. + +
+ +### Mooncake EP and Process Group (PG) + +Mooncake EP and Mooncake PG extend Mooncake from high-performance data movement to fault-tolerant distributed execution for large-scale MoE inference. Mooncake EP adapts DeepEP-style expert-parallel dispatch and combine operations with rank activeness awareness, while Mooncake PG provides a PyTorch distributed process-group backend with collective communication primitives that can detect failed ranks, report failures to upper layers, and recover ranks without restarting the entire inference service. See the [Mooncake EP & Backend guide](https://kvcache-ai.github.io/Mooncake/python-api-reference/ep-backend.html) for details. + +
+Highlights + +- **Fault-tolerant expert parallelism.** Mooncake EP adds `active_ranks` awareness to expert-parallel dispatch and combine APIs, allowing MoE inference systems to route around failed ranks and continue serving with healthy experts. + +- **DeepEP-compatible programming model.** Mooncake EP keeps the API largely consistent with DeepEP's low-latency mode, making it easier for inference engines to adopt fault-tolerant expert parallelism without rewriting their MoE communication stack. + +- **PyTorch ProcessGroup integration.** Mooncake PG can be registered as a `torch.distributed` backend, enabling standard collective APIs such as `all_gather` while using Mooncake's communication and failure-reporting mechanisms underneath. + +- **Elastic rank recovery.** Mooncake PG exposes recovery-oriented primitives such as peer-state polling and rank recovery, allowing replacement processes to rejoin existing process groups and helping inference services recover from partial failures. + +- **SGLang integration for production MoE serving.** Mooncake's collective backend and expert-parallel kernels are integrated into SGLang to support fault-tolerant expert-parallel inference for large MoE models, including Elastic Expert Parallel serving scenarios. + +
+ +### Tensor-Centric Ecosystem + +Mooncake establishes a full-stack, Tensor-oriented AI infrastructure where Tensors serve as the fundamental data carrier. The ecosystem spans from the Transfer Engine, which accelerates Tensor data movement across heterogeneous storage (DRAM/VRAM/NVMe), to Mooncake Store for distributed management of Tensor objects (e.g., KVCache and model weight), up to the Mooncake Backend enabling Tensor-based elastic distributed computing. This architecture is designed to maximize Tensor processing efficiency for large-scale model inference and training. + +### SGLang Integration ([Guide](https://kvcache-ai.github.io/Mooncake/getting_started/examples/sglang-integration/index.html)) + +Mooncake is deeply integrated into [SGLang](https://github.com/sgl-project/sglang/) as a high-performance communication and storage backend. These integrations enable efficient KV cache transfer in PD-disaggregated serving, scalable multi-level KV caching through HiCache, fault-tolerant expert-parallel inference, high-performance multimodal pipeline data movement, and fast RDMA-based weight synchronization for large-scale RL training. Together, Mooncake and SGLang provide a production-oriented foundation for building elastic, high-throughput, and resource-efficient LLM and multimodal serving systems. + +
+Details + +- **PD Disaggregated Serving:** SGLang officially supports Mooncake Transfer Engine as a backend for disaggregated serving and KV cache transfer, enabling prefill and decode workers to exchange KV cache data efficiently across devices and machines. -#### Highlights - **Hierarchical KV Caching**: Mooncake Store serves as an external storage backend in SGLang's HiCache system, extending RadixAttention with multi-level KV cache storage across device, host, and remote storage layers. -- **Flexible Cache Management**: Supports multiple cache policies including write-through, write-through-selective, and write-back modes, with intelligent prefetching strategies for optimal performance. -- **Comprehensive Optimizations**: Features advanced data plane optimizations including page-first memory layout for improved I/O efficiency, zero-copy mechanisms for reduced memory overhead, GPU-assisted I/O kernels delivering fast CPU-GPU transfers, and layer-wise overlapping for concurrent KV cache loading while computation executes. -- **Elastic Expert Parallel**: Mooncake's collective communication backend and expert parallel kernels are integrated into SGLang to enable fault-tolerant expert parallel inference ([sglang#11657](https://github.com/sgl-project/sglang/pull/11657)). -- **Significant Performance Gains**: The multi-turn benchmark demonstrates substantial performance improvements over the non-HiCache setting. See our [benchmark report](https://kvcache-ai.github.io/Mooncake/performance/sglang-hicache-benchmark-results-v1.html) for more details. -- **Community Feedback**: Effective KV caching significantly reduces TTFT by eliminating redundant and costly re-computation. Integrating SGLang HiCache with the Mooncake service enables scalable KV cache retention and high-performance access. In our evaluation, we tested the DeepSeek-R1-671B model under PD-disaggregated deployment using in-house online requests sampled from a general QA scenario. On average, cache hits achieved an 84% reduction in TTFT compared to full re-computation. – Ant Group -### vLLM Integration ([Guide v0.2](https://kvcache-ai.github.io/Mooncake/getting_started/examples/vllm-integration/vllm-integration-v0.2.html)) -To optimize LLM inference, the vLLM community is working on supporting [disaggregated prefilling (PR 10502)](https://github.com/vllm-project/vllm/pull/10502). This feature allows separating the **prefill** phase from the **decode** phase in different processes. The vLLM uses `nccl` and `gloo` as the transport layer by default, but currently it cannot efficiently decouple both phases in different machines. +- **Elastic Expert Parallel**: Mooncake's collective communication backend and expert parallel kernels are integrated into SGLang to enable fault-tolerant expert parallel inference ([Elastic EP](https://www.lmsys.org/blog/2026-03-25-eep-partial-failure-tolerance/)). -We have implemented vLLM integration, which uses Transfer Engine as the network layer instead of `nccl` and `gloo`, to support **inter-node KVCache transfer** [(PR 10884)](https://github.com/vllm-project/vllm/pull/10884). Transfer Engine provides simpler interfaces and more efficient use of RDMA devices. +- **Cloud-Native SGLang HiCache Deployment with RBG**: The [RBG](https://github.com/sgl-project/rbg) + SGLang HiCache + Mooncake integration provides a role-based, out-of-the-box cloud-native deployment solution that is elastic, scalable, and optimized for high-performance inference workloads. -We will soon release the new vLLM integration based on Mooncake Store, which supports xPyD prefill/decode disaggregation. +- **Encode-Prefill-Decode Disaggregation for Multimodal Serving**: SGLang introduces Encode-Prefill-Decode disaggregation with Mooncake as a transfer backend. This enables compute-intensive multimodal encoders, such as Vision Transformers, to be decoupled from language model workers while transferring large embeddings efficiently through Mooncake’s RDMA-based engine. -**_Update[Dec 16, 2024]: Here is the latest vLLM Integration ([Guide v0.2](https://kvcache-ai.github.io/Mooncake/getting_started/examples/vllm-integration/vllm-integration-v0.2.html)) that is based on vLLM's main branch._** +- **SGLang-Omni Multi-Stage Pipeline Data Transfer**: [SGLang-Omni](https://github.com/sgl-project/sglang-omni) integrates Mooncake as a relay backend for efficient cross-stage tensor and blob transfer in multimodal serving pipelines. This enables high-performance data movement between heterogeneous components such as thinker, talker, codec, and vocoder stages. -#### Performance -By supporting Topology Aware Path Selection and multi-card bandwidth aggregation, Mean TTFT of vLLM with Transfer Engine is up to 25% lower than traditional TCP-based transports. -In the future, we will further improve TTFT through GPUDirect RDMA and zero-copy. +- **RDMA-Based P2P Weight Transfer for Distributed RL**: SGLang adopts Mooncake TransferEngine for RDMA-based peer-to-peer weight transfer in large-scale distributed reinforcement learning. This enables zero-copy weight updates across thousands of GPUs and significantly accelerates synchronization for trillion-parameter models. -| Backend/Setting | Output Token Throughput (tok/s) | Total Token Throughput (tok/s) | Mean TTFT (ms) | Median TTFT (ms) | P99 TTFT (ms)| -|---------------------------------------------------------|---------------------------------|--------------------------------|----------------|------------------|---------------| -| Transfer Engine (RDMA) | 12.06 | 2042.74 | 1056.76 | 635.00 | 4006.59 | -| TCP | 12.05 | 2041.13 | 1414.05 | 766.23 | 6035.36 | +
-- Click [here](https://kvcache-ai.github.io/Mooncake/performance/vllm-benchmark-results-v0.2.html) to access detailed benchmark results. +### vLLM Integration ([Guide](https://kvcache-ai.github.io/Mooncake/getting_started/examples/vllm-integration/index.html)) -**More advanced features are coming soon, so stay tuned!** +Mooncake integrates with [vLLM](https://github.com/vllm-project/vllm) to accelerate large language model serving through high-performance KV cache transfer and distributed KV cache storage. The integration supports both disaggregated prefill-decode serving and cross-instance KV cache sharing, helping vLLM deployments reduce TTFT, improve cache reuse, and scale more efficiently across multi-node inference clusters. -

🚀 Quick Start

+
+Details -### Before using Mooncake +- **Disaggregated prefill-decode serving**: Mooncake enables vLLM to split prefill and decode workloads across different nodes. Through MooncakeConnector, vLLM transfers KV cache blocks from prefill workers to decode workers using Mooncake’s high-performance transfer engine, allowing prefill and decode resources to scale independently while keeping cross-node KV transfer overhead low. -Mooncake is designed and optimized for high-speed RDMA networks. Though Mooncake supports TCP-only data transfer, we **strongly** recommend users to evaluate the functionality and performance of Mooncake with RDMA network support. +- **Distributed KV cache pooling and sharing**: [Mooncake Store extends vLLM](https://vllm.ai/blog/2026-05-06-mooncake-store) from isolated per-instance KV caches to a shared, cluster-level KV cache pool. Through MooncakeStoreConnector, multiple vLLM instances can store, retrieve, and reuse KV cache blocks based on hash-based prefix caching, reducing redundant prefill computation and improving cache efficiency for workloads with repeated prefixes, especially agentic and multi-turn serving scenarios. -The following need to be installed before running any component of Mooncake: -- RDMA Driver & SDK, such as Mellanox OFED. -- Python 3.10, virtual environment is recommended. -- CUDA 12.1 and above, including NVIDIA GPUDirect Storage Support, if the package is built with `-DUSE_CUDA` (disabled by default). *You may install them from [here](https://developer.nvidia.com/cuda-downloads)*. -- Cambricon Neuware, if the package is built with `-DUSE_MLU`. By default Mooncake looks for Neuware under `NEUWARE_HOME` or `/usr/local/neuware`. +- **vLLM-Omni stage communication**: Mooncake also integrates with [vLLM-Omni](https://github.com/vllm-project/vllm-omni) through `MooncakeTransferEngineConnector` and `MooncakeStoreConnector`, enabling efficient cross-node data exchange between vLLM-Omni stages. -### Use Python package -The simplest way to use Mooncake Transfer Engine is using `pip`: +
-**For CUDA-enabled systems:** +

🖥️ Supported Hardware

+ +Mooncake supports hardware backends across accelerator vendors, cloud fabrics, and standard datacenter interconnects, as listed below. See the [supported protocols](https://kvcache-ai.github.io/Mooncake/getting_started/supported-protocols.html) and [Transfer Engine design docs](https://kvcache-ai.github.io/Mooncake/design/transfer-engine/index.html) for details. + +| NVIDIA | Huawei | AMD | Cambricon | Moore Threads | AWS | +| --- | --- | --- | --- | --- | --- | +| MetaX | T-Head | Alibaba Cloud | Sunrise | Hygon | | + +

🚀 Getting Started

+ +Install Mooncake using `pip`. The `mooncake-transfer-engine` package includes Mooncake Transfer Engine, Mooncake Store, Mooncake EP and PG: - CUDA < 13.0 ```bash @@ -194,108 +210,84 @@ pip install mooncake-transfer-engine pip install mooncake-transfer-engine-cuda13 ``` -**For non-CUDA systems:** -```bash -pip install mooncake-transfer-engine-non-cuda +In addition to CUDA, Mooncake also supports other accelerator backends, along with flexible installation and deployment options. See the guides below for details: + +- [Quick Start](https://kvcache-ai.github.io/Mooncake/getting_started/quick-start.html) +- [Build from Source](https://kvcache-ai.github.io/Mooncake/getting_started/build.html) +- [Deployment Guide](https://kvcache-ai.github.io/Mooncake/deployment/mooncake-store-deployment-guide.html) + + +### Skills for AI Assistants + +Mooncake ships a set of **built-in skills** under [`.claude/skills`](.claude/skills) — reusable, task-focused playbooks that an AI coding assistant (such as Claude Code) invokes automatically when your request matches, or that you can run as a slash command. + +
+Details + +| Skill | Description | +|-------|-------------| +| `/mooncake-troubleshoot` | Diagnose Mooncake deployment and runtime issues (services, RDMA, env vars, logs). | +| `/mooncake-ci-local` | Run pre-PR local validation via `scripts/run_ci_test.sh`. | +| `/mooncake-api` | Work with the Mooncake Store, Transfer Engine, and EP/Backend Python APIs. | + +Install them without cloning the repository via the [Claude Code plugin marketplace](https://code.claude.com/docs/en/plugin-marketplaces): + +```text +/plugin marketplace add kvcache-ai/Mooncake --sparse .claude-plugin +/plugin install mooncake-troubleshoot@mooncake +/plugin install mooncake-ci-local@mooncake +/plugin install mooncake-api@mooncake ``` -> [!IMPORTANT] -> - The CUDA version (`mooncake-transfer-engine`) includes Mooncake-EP and GPU topology detection, requiring CUDA 12.1+. -> - The non-CUDA version (`mooncake-transfer-engine-non-cuda`) is for environments without CUDA dependencies. -> - MLU support is currently available through source builds with `-DUSE_MLU=ON`; there is no dedicated prebuilt MLU wheel yet. -> - If users encounter problems such as missing `lib*.so`, they should uninstall the package they installed and build the binaries manually. +The `--sparse .claude-plugin` flag fetches only the marketplace catalog, and each plugin is published as a `git-subdir` source, so installing one fetches only that single skill directory — never the whole repo. If you are already working inside a Mooncake checkout, the skills under `.claude/skills/` load automatically with no setup. -### Use Docker image -Mooncake supports Docker-based deployment, see [Build Guide](https://kvcache-ai.github.io/Mooncake/getting_started/build.html) in detail. +
-To produce an image that compiles Mooncake from source, builds the wheel via `scripts/build_wheel.sh`, and installs that wheel inside the container, use `build-wheel.dockerfile`: +

📦 Open Source Traces and Tools

-```bash -docker build -f docker/mooncake.Dockerfile \ - --build-arg PYTHON_VERSION=3.10 \ - --build-arg EP_TORCH_VERSIONS="2.9.1" \ - -t mooncake:from-source . -``` +We open-source anonymized request traces containing request arrival times, input and output token counts, and remapped block hashes. These traces are designed to support reproducible simulation and evaluation of caching behavior while preserving user privacy. The released traces and related details are available in [FAST25-release](FAST25-release). -The resulting image already has a virtual environment at `/opt/venv` with the freshly built wheel installed. Launch it with GPU/RDMA access as needed, for example: - -```bash -docker run --gpus all --network host -it mooncake:from-source /bin/bash -``` - -> [!NOTE] -> Make sure you build the image from the repository root so that Git metadata and submodules are available inside the build context. - -### Build and use binaries -The following are additional dependencies for building Mooncake: -- Build essentials, including gcc, g++ (9.4+) and cmake (3.16+). -- Go 1.20+, if you want to build with `-DWITH_P2P_STORE`, `-DUSE_ETCD` (enabled by default to use etcd as metadata servers), or `-DSTORE_USE_ETCD` (use etcd for the failover of the store master). -- CUDA 12.1 and above, including NVIDIA GPUDirect Storage Support, if the package is built with `-DUSE_CUDA`. *This is NOT included in the `dependencies.sh` script. You may install them from [here](https://developer.nvidia.com/cuda-downloads)*. -- Cambricon Neuware, if you want to build with `-DUSE_MLU`. *This is NOT included in the `dependencies.sh` script.* Mooncake resolves it from `NEUWARE_HOME` or `/usr/local/neuware` by default, and also supports overriding `MLU_INCLUDE_DIR` / `MLU_LIB_DIR` during CMake configure. -- [Optional] Rust Toolchain, if you want to build with `-DWITH_RUST_EXAMPLE`. *This is NOT included in the `dependencies.sh` script.* -- [Optional] `hiredis`, if you want to build with `-DUSE_REDIS` to use Redis instead of etcd as metadata servers. -- [Optional] `curl`, if you want to build with `-DUSE_HTTP` to use HTTP instead of etcd as metadata servers. - -The build and installation steps are as follows: -1. Retrieve source code from GitHub repo - ```bash - git clone https://github.com/kvcache-ai/Mooncake.git - cd Mooncake - ``` - -2. Install dependencies - ```bash - bash dependencies.sh - ``` - -3. Compile Mooncake and examples - ```bash - mkdir build - cd build - cmake .. - make -j - sudo make install # optional, make it ready to be used by vLLM/SGLang - ``` - -For Cambricon MLU builds, configure CMake with `-DUSE_MLU=ON`. For example: -```bash -mkdir build -cd build -cmake .. -DUSE_MLU=ON -DNEUWARE_ROOT=/usr/local/neuware -make -j -``` - - -

🛣️ Incoming Milestones

- -- [x] First release of Mooncake and integrate with latest vLLM -- [ ] Share KV caches across multiple serving engines -- [ ] User and developer documentation - -

📦 Open Source Trace

- -```json -{ - "timestamp": 27482, - "input_length": 6955, - "output_length": 52, - "hash_ids": [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 2353, 2354] -} -{ - "timestamp": 30535, - "input_length": 6472, - "output_length": 26, - "hash_ids": [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 2366] -} -``` -The above presents two samples from our trace dataset. The trace includes the timing of request arrivals, the number of input tokens, the number of output tokens, and the remapped block hash. To protect our customers' privacy, we applied several mechanisms to remove user-related information while preserving the dataset's utility for simulated evaluation. More descriptions of the trace (e.g., up to 50% cache hit ratio) can be found in Section 4 of the technical report. - -**_Update[Feb 21, 2025]: The updated [traces](FAST25-release/traces) used in our FAST'25 paper have been released! Please refer to the paper's appendix (found [here](FAST25-release/Mooncake-FAST25.pdf)) for more details._** +Together with the released traces, we also provide two KV cache analysis tools: a [KV Cache Size Calculator](https://kvcache.ai/tools/kv-cache-size-calculator/) for calculating cache capacity across popular LLM model families, and a [KV Cache Hit Rate Simulator](https://kvcache.ai/tools/kv-cache-hit-rate-simulator/) for analyzing KV cache hit rates and planning cache capacity under different workloads and models. These tools help users better understand KV cache storage costs and caching effectiveness when analyzing or reproducing serving workloads. The tools are open-sourced [here](https://github.com/kvcache-ai/kvcache-blog).

📑 Citation

-Please kindly cite our paper if you find the paper or the traces are useful: +Please kindly cite our papers if you find the papers or the traces are useful: ```bibtex +@inproceedings{qin2025mooncake, + author = {Ruoyu Qin and Zheming Li and Weiran He and Jialei Cui and Feng Ren and Mingxing Zhang and Yongwei Wu and Weimin Zheng and Xinran Xu}, + title = {Mooncake: Trading More Storage for Less Computation {\textemdash} A {KVCache-centric} Architecture for Serving {LLM} Chatbot}, + booktitle = {23rd USENIX Conference on File and Storage Technologies (FAST 25)}, + year = {2025}, + isbn = {978-1-939133-45-8}, + address = {Santa Clara, CA}, + pages = {155--170}, + url = {https://www.usenix.org/conference/fast25/presentation/qin}, + publisher = {USENIX Association}, + month = {feb}, +} +``` + +
+More + +```bibtex +@misc{ren2026tentdeclarativeslicespraying, + title = {TENT: A Declarative Slice Spraying Engine for Performant and Resilient Data Movement in Disaggregated LLM Serving}, + author = {Feng Ren and Ruoyu Qin and Teng Ma and Shangming Cai and Zheng Liu and Chao Lei and Dejiang Zhu and Ke Yang and Zheming Li and Jialei Cui and Weixiao Huang and Yikai Zhao and Yineng Zhang and Hao Wu and Xiang Gao and Yuhao Fu and Jinlei Jiang and Yongwei Wu and Mingxing Zhang}, + year = {2026}, + eprint = {2604.00368}, + archivePrefix = {arXiv}, + primaryClass = {cs.DC}, + url = {https://arxiv.org/abs/2604.00368}, +} + +@article{sun2026survivingpartialrankfailures, + title = {Surviving Partial Rank Failures in Wide Expert-Parallel MoE Inference}, + author = {Xun Sun and Shaoyuan Chen and Pingchuan Ma and Yue Chen and Ziwei Yuan and Zhanhao Cao and Han Han and Shangming Cai and Teng Ma and Xuchun Shang and Xinpeng Zhao and Ke Yang and Junlin Wei and Lianzhi Lin and Yuji Liu and Feng Ren and Haoran Hu and Cheng Wan and Yingdi Shan and Yongwei Wu and Mingxing Zhang}, + year = {2026}, + url = {https://arxiv.org/abs/2605.10670}, +} + @article{qin2025mooncake_tos, author = {Qin Ruoyu and Li Zheming and He Weiran and Cui Jialei and Tang Heyi and Ren Feng and Ma Teng and Cai Shangming and Zhang Yineng and Zhang Mingxing and Wu Yongwei and Zheng Weimin and Xu Xinran}, title = {Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving}, @@ -310,19 +302,6 @@ Please kindly cite our paper if you find the paper or the traces are useful: keywords = {Machine learning system, LLM serving, KVCache}, } -@inproceedings{qin2025mooncake, - author = {Ruoyu Qin and Zheming Li and Weiran He and Jialei Cui and Feng Ren and Mingxing Zhang and Yongwei Wu and Weimin Zheng and Xinran Xu}, - title = {Mooncake: Trading More Storage for Less Computation {\textemdash} A {KVCache-centric} Architecture for Serving {LLM} Chatbot}, - booktitle = {23rd USENIX Conference on File and Storage Technologies (FAST 25)}, - year = {2025}, - isbn = {978-1-939133-45-8}, - address = {Santa Clara, CA}, - pages = {155--170}, - url = {https://www.usenix.org/conference/fast25/presentation/qin}, - publisher = {USENIX Association}, - month = {feb}, -} - @article{qin2024mooncake_arxiv, title = {Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving}, author = {Ruoyu Qin and Zheming Li and Weiran He and Mingxing Zhang and Yongwei Wu and Weimin Zheng and Xinran Xu}, @@ -330,3 +309,5 @@ Please kindly cite our paper if you find the paper or the traces are useful: url = {https://arxiv.org/abs/2407.00079}, } ``` + +
diff --git a/benchmarks/storage_benchmark_v1/__init__.py b/benchmarks/storage_benchmark_v1/__init__.py new file mode 100644 index 00000000..2973545a --- /dev/null +++ b/benchmarks/storage_benchmark_v1/__init__.py @@ -0,0 +1,41 @@ +""" +Mooncake KVCache Storage Benchmark Library +""" + +__version__ = "2.0.0" + +from benchmark import ( + main, + run_benchmark, + print_results, + StorageBenchmark, + TraceReplay, + KVCacheRequest, +) +from storage import Storage, KVKey, KVValue, DiskHashTable, SSDStorage +from layout import ( + KVLayout, MLALayout, KVEntry, MLA_MODEL_CONFIG, get_model_config, create_layout, +) + +__all__ = [ + # Main + 'main', + 'run_benchmark', + 'print_results', + 'StorageBenchmark', + 'TraceReplay', + 'KVCacheRequest', + # Storage + 'Storage', + 'KVKey', + 'KVValue', + 'DiskHashTable', + 'SSDStorage', + # Layout + 'KVLayout', + 'MLALayout', + 'KVEntry', + 'MLA_MODEL_CONFIG', + 'get_model_config', + 'create_layout', +] diff --git a/benchmarks/storage_benchmark_v1/__main__.py b/benchmarks/storage_benchmark_v1/__main__.py new file mode 100644 index 00000000..2ae8bc89 --- /dev/null +++ b/benchmarks/storage_benchmark_v1/__main__.py @@ -0,0 +1,11 @@ +""" +Entry point for running the benchmark as a module + +Usage: + python -m storage_benchmark --model=glm5 --scenario=toolagent --max-requests=100 +""" + +from benchmark import main + +if __name__ == '__main__': + main() diff --git a/benchmarks/storage_benchmark_v1/benchmark.py b/benchmarks/storage_benchmark_v1/benchmark.py new file mode 100644 index 00000000..3f5ba5ed --- /dev/null +++ b/benchmarks/storage_benchmark_v1/benchmark.py @@ -0,0 +1,486 @@ +#!/usr/bin/env python3 +""" +Mooncake KVCache Storage Benchmark + +Complete benchmark tool with CLI interface. +""" + +import argparse +import json +import sys +import time +import statistics +import signal +from dataclasses import dataclass +from pathlib import Path +from typing import Iterator, List, Dict, Any + +from storage import DiskHashTable +from layout import get_model_config, create_layout + + +# ============================================================================ +# Data Structures +# ============================================================================ + +@dataclass +class KVCacheRequest: + """KVCache request from trace""" + timestamp: float + hash_ids: List[int] + input_length: int + output_length: int + + +# ============================================================================ +# Trace Replay +# ============================================================================ + +class TraceReplay: + """Trace replay handler""" + + def __init__(self, trace_path: str): + self.trace_path = trace_path + + def load_all(self) -> List[KVCacheRequest]: + """Load all requests from trace file""" + requests = [] + with open(self.trace_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line: + req = json.loads(line) + requests.append(KVCacheRequest( + timestamp=req.get('timestamp', 0), + hash_ids=req.get('hash_ids', []), + input_length=req.get('input_length', 0), + output_length=req.get('output_length', 0), + )) + return requests + + +# ============================================================================ +# Storage Benchmark +# ============================================================================ + +class StorageBenchmark: + """KVCache storage benchmark + + Processes KVCache requests using layout-generated access patterns. + """ + + def __init__(self, storage_dir: str, model_config: dict, + page_size_tokens: int = 512, + max_pages: int = 100000, + fsync_mode: str = 'none', fsync_batch_size: int = 100): + """Initialize benchmark + + Args: + storage_dir: Directory for storage files + model_config: Model configuration dict + page_size_tokens: Tokens per page (default: 512) + max_pages: Maximum number of pages + fsync_mode: When to fsync ('none', 'batch', 'always', 'end') + fsync_batch_size: Number of writes between fsync in batch mode + """ + self.model_config = model_config + self.layout = create_layout(model_config, page_size_tokens) + self.page_size_bytes = self.layout.value_size_bytes + + # Initialize storage + self.storage = DiskHashTable( + storage_dir=storage_dir, + page_size=self.page_size_bytes, + max_pages=max_pages, + fsync_mode=fsync_mode, + fsync_batch_size=fsync_batch_size + ) + + # Statistics + self.stats = { + 'total_requests': 0, + 'total_tokens': 0, + 'read_pages': 0, + 'write_pages': 0, + 'page_hits': 0, + 'request_latencies_ms': [], + } + + def process_request(self, req: KVCacheRequest) -> float: + """Process a KVCache request + + Args: + req: KVCache request + + Returns: + Total latency in milliseconds + """ + self.stats['total_requests'] += 1 + self.stats['total_tokens'] += req.input_length + req.output_length + + total_latency = 0.0 + + # Process each access requirement from layout + for access in self.layout.get_operations(req): + if self.storage.exists(access.page_id): + # Page exists, perform READ + total_latency += self.storage.read( + access.page_id, + offset_in_page=access.offset_in_page, + length=access.length + ) + self.stats['read_pages'] += 1 + self.stats['page_hits'] += 1 + else: + # Page doesn't exist, perform WRITE + total_latency += self.storage.write( + access.page_id, + offset_in_page=access.offset_in_page, + length=access.length + ) + self.stats['write_pages'] += 1 + + latency_ms = total_latency if total_latency > 0 else 0.0 + if latency_ms > 0: + self.stats['request_latencies_ms'].append(latency_ms) + return latency_ms + + def get_stats(self) -> Dict: + """Get statistics""" + storage_stats = self.storage.get_stats() + request_latencies = self.stats['request_latencies_ms'] + + if request_latencies: + sorted_latencies = sorted(request_latencies) + n = len(sorted_latencies) + + def get_percentile(p: float) -> float: + idx = int(n * p) + return sorted_latencies[idx] if idx < n else sorted_latencies[-1] + + latency_stats = { + 'avg_ms': statistics.mean(request_latencies), + 'p50_ms': sorted_latencies[n // 2], + 'p95_ms': get_percentile(0.95), + 'p99_ms': get_percentile(0.99), + } + else: + latency_stats = {'avg_ms': 0, 'p50_ms': 0, 'p95_ms': 0, 'p99_ms': 0} + + total_pages = self.stats['read_pages'] + self.stats['write_pages'] + + return { + 'total_requests': self.stats['total_requests'], + 'total_tokens': self.stats['total_tokens'], + 'total_pages': total_pages, + 'read_pages': self.stats['read_pages'], + 'write_pages': self.stats['write_pages'], + 'page_hits': self.stats['page_hits'], + 'page_hit_rate': self.stats['read_pages'] / total_pages if total_pages > 0 else 0, + 'write_ratio': self.stats['write_pages'] / total_pages if total_pages > 0 else 0, + 'latency': latency_stats, + 'storage': storage_stats, + } + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + return False + + def close(self, force_sync: bool = True): + self.storage.close(force_sync=force_sync) + + +# ============================================================================ +# Benchmark Runner +# ============================================================================ + +def get_max_page_id(requests: List[KVCacheRequest]) -> int: + max_id = 0 + for req in requests: + if req.hash_ids: + max_id = max(max_id, max(req.hash_ids)) + return max_id + + +def run_benchmark(trace_path: str, storage_dir: str, model_config: dict, + max_requests: int = None, max_pages: int = None, + page_size_tokens: int = 512, + fsync_mode: str = 'none', fsync_batch_size: int = 100) -> Dict: + """Run benchmark + + Args: + trace_path: Trace file path + storage_dir: Storage directory + model_config: Model configuration + max_requests: Maximum number of requests (None = all) + max_pages: Maximum number of pages (None = auto-calculate) + page_size_tokens: Tokens per page + fsync_mode: When to fsync + fsync_batch_size: Number of writes between fsync + + Returns: + Benchmark results dictionary + """ + print(f"\n{'='*80}") + print(f"Running: {Path(trace_path).name}") + print(f"Model: {model_config['name']}") + print(f"Layers: {model_config['num_layers']}") + print(f"Page size: {page_size_tokens} tokens") + print(f"{'='*80}") + + # Load trace + replay = TraceReplay(trace_path) + requests = replay.load_all() + + if max_requests: + requests = requests[:max_requests] + + print(f"Loaded {len(requests)} requests") + + # Find max page_id from trace + max_page_id = get_max_page_id(requests) + max_pages_needed = max_page_id + 1 # page_id is 0-based + + # Create layout to get page size + layout = create_layout(model_config, page_size_tokens) + page_size_bytes = layout.value_size_bytes + + # Determine max_pages + if max_pages is None: + # Use max page_id from trace + max_pages = max_pages_needed + # Round up to next thousand for cleaner numbers + max_pages = ((max_pages + 999) // 1000) * 1000 + else: + # User specified max_pages + pass + + max_size_gb = max_pages * page_size_bytes / (1024**3) + trace_size_gb = max_pages_needed * page_size_bytes / (1024**3) + + print(f"\n[Storage Configuration]") + print(f" Max page_id in trace: {max_page_id:,}") + print(f" Pages needed (trace): {max_pages_needed:,}") + print(f" Trace storage size: {trace_size_gb:.2f} GB") + print(f" Max pages configured: {max_pages:,}") + print(f" Max storage available: {max_size_gb:.2f} GB") + + if max_pages_needed > max_pages: + shortfall = max_pages_needed - max_pages + shortfall_gb = shortfall * page_size_bytes / (1024**3) + compression_ratio = max_pages / max_pages_needed + print(f"\n ⚠️ Storage insufficient: {shortfall:,} pages shortfall ({shortfall_gb:.2f} GB)") + print(f" ⚠️ Consider increasing --max-pages to at least {max_pages_needed:,} for full simulation") + else: + surplus = max_pages - max_pages_needed + surplus_pct = (surplus / max_pages) * 100 if max_pages > 0 else 0 + print(f" ✓ Direct mapping: all {max_pages_needed:,} logical pages uniquely mapped") + + # Run benchmark + with StorageBenchmark( + storage_dir=storage_dir, + model_config=model_config, + page_size_tokens=page_size_tokens, + max_pages=max_pages, + fsync_mode=fsync_mode, + fsync_batch_size=fsync_batch_size + ) as benchmark: + start_time = time.perf_counter() + try: + for i, req in enumerate(requests): + benchmark.process_request(req) + # Print progress for each request + elapsed = time.perf_counter() - start_time + qps = (i + 1) / elapsed if elapsed > 0 else 0 + stats = benchmark.get_stats() + storage = stats.get('storage', {}) + read_latency = storage.get('read', {}).get('avg_ms', 0) + write_latency = storage.get('write', {}).get('avg_ms', 0) + read_mb = storage.get('read', {}).get('mb', 0) + write_mb = storage.get('write', {}).get('mb', 0) + read_time = storage.get('read', {}).get('time_s', 0) + write_time = storage.get('write', {}).get('time_s', 0) + read_mbps = read_mb / read_time if read_time > 0 else 0 + write_mbps = write_mb / write_time if write_time > 0 else 0 + print(f" [{i+1:5d}/{len(requests)}] ids={len(req.hash_ids):3d} " + f"tokens={req.input_length+req.output_length:6d} | " + f"QPS={qps:7.2f} | " + f"R={stats['read_pages']:6d} ({read_latency:6.2f}ms, {read_mbps:6.1f}MB/s) | " + f"W={stats['write_pages']:6d} ({write_latency:6.2f}ms, {write_mbps:6.1f}MB/s)") + except KeyboardInterrupt: + print(f"\n\n{'='*80}") + print(f"Interrupted! Showing partial results:") + print(f"{'='*80}") + elapsed = time.perf_counter() - start_time + stats = benchmark.get_stats() + print_results([{ + 'trace_file': Path(trace_path).name, + 'total_requests': i + 1, + 'io_time_s': elapsed, + 'requests_per_second': (i + 1) / elapsed if elapsed > 0 else 0, + 'model': model_config['name'], + 'fsync_mode': fsync_mode, + **stats, + }]) + sys.exit(0) + + elapsed = time.perf_counter() - start_time + stats = benchmark.get_stats() + + return { + 'trace_file': Path(trace_path).name, + 'total_requests': len(requests), + 'io_time_s': elapsed, + 'requests_per_second': len(requests) / elapsed if elapsed > 0 else 0, + 'model': model_config['name'], + 'fsync_mode': fsync_mode, + **stats, + } + + +# ============================================================================ +# Output Formatting +# ============================================================================ + +def format_storage_stats(stats: Dict, title: str = "Storage"): + """Format storage statistics with clear read/write separation""" + storage = stats.get('storage', {}) + read_stats = storage.get('read', {}) + write_stats = storage.get('write', {}) + + output = [] + output.append(f"\n[{title}]") + + # General info + output.append(f"\n[General]") + output.append(f" Model: {stats.get('model', 'N/A')}") + output.append(f" Requests: {stats.get('total_requests', 0):,}") + output.append(f" Tokens: {stats.get('total_tokens', 0):,}") + output.append(f" Total I/O Time: {stats.get('io_time_s', 0):.3f} s") + output.append(f" QPS: {stats.get('requests_per_second', 0):.2f}") + output.append(f" Hit Rate: {stats.get('page_hit_rate', 0):.2%}") + + # Read Stats + output.append(f"\n[Read Operations]") + output.append(f" Count: {read_stats.get('count', 0):,}") + output.append(f" Data Volume: {read_stats.get('mb', 0):.2f} MB") + read_time = read_stats.get('time_s', 0) + read_mbps = read_stats.get('mb', 0) / read_time if read_time > 0 else 0 + output.append(f" Total Time: {read_time:.3f} s") + output.append(f" Bandwidth: {read_mbps:.2f} MB/s") + output.append(f" Latency:") + output.append(f" Avg: {read_stats.get('avg_ms', 0):.3f} ms") + output.append(f" P50: {read_stats.get('p50_ms', 0):.3f} ms") + output.append(f" P95: {read_stats.get('p95_ms', 0):.3f} ms") + output.append(f" P99: {read_stats.get('p99_ms', 0):.3f} ms") + + # Write Stats + output.append(f"\n[Write Operations]") + output.append(f" Count: {write_stats.get('count', 0):,}") + output.append(f" Data Volume: {write_stats.get('mb', 0):.2f} MB") + write_time = write_stats.get('time_s', 0) + write_mbps = write_stats.get('mb', 0) / write_time if write_time > 0 else 0 + output.append(f" Total Time: {write_time:.3f} s") + output.append(f" Bandwidth: {write_mbps:.2f} MB/s") + output.append(f" Latency:") + output.append(f" Avg: {write_stats.get('avg_ms', 0):.3f} ms") + output.append(f" P50: {write_stats.get('p50_ms', 0):.3f} ms") + output.append(f" P95: {write_stats.get('p95_ms', 0):.3f} ms") + output.append(f" P99: {write_stats.get('p99_ms', 0):.3f} ms") + + # Storage Info + output.append(f"\n[Storage Info]") + output.append(f" Max Pages: {storage.get('max_pages', 0):,}") + output.append(f" Written Pages: {storage.get('written_pages', 0):,}") + output.append(f" Sync Count: {storage.get('sync_count', 0):,}") + + return "\n".join(output) + + +def print_results(results: List[Dict]): + """Print benchmark results""" + for i, r in enumerate(results, 1): + print(f"\n{'='*80}") + print(f" [{i}/{len(results)}] {r['trace_file']}") + print(f"{'='*80}") + print(format_storage_stats(r)) + + +# ============================================================================ +# CLI Entry Point +# ============================================================================ + +def main(): + """Main entry point""" + parser = argparse.ArgumentParser( + description='Mooncake KVCache Storage Benchmark', + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument('--trace-dir', type=str, default='../../FAST25-release/traces', + help='Trace files directory') + parser.add_argument('--scenario', type=str, choices=['conversation', 'synthetic', 'toolagent', 'all'], + default='toolagent', help='Test scenario') + parser.add_argument('--storage-dir', type=str, default='/tmp/mooncake_bench', + help='Storage directory') + parser.add_argument('--model', type=str, default='glm5', choices=['glm5', 'kimi-k2.6'], + help='Model preset') + parser.add_argument('--page-size-tokens', type=int, default=512, + help='Page size in tokens (default: 512)') + parser.add_argument('--max-requests', type=int, default=None, + help='Maximum number of requests') + parser.add_argument('--max-pages', type=int, default=2000, + help='Maximum number of pages') + parser.add_argument('--fsync-mode', type=str, choices=['batch', 'always', 'end', 'none'], + default='none', help='When to fsync') + parser.add_argument('--fsync-batch-size', type=int, default=100, + help='Number of writes between fsync') + + args = parser.parse_args() + + print(f"\n{'='*80}") + print(f"{'Mooncake KVCache Storage Benchmark':^80}") + print(f"{'='*80}") + + model_config = get_model_config(args.model) + print(f"Model: {args.model} ({model_config['num_layers']} layers)") + + # Determine scenarios + scenarios = ['conversation', 'synthetic', 'toolagent'] if args.scenario == 'all' else [args.scenario] + trace_files = { + 'conversation': 'conversation_trace.jsonl', + 'synthetic': 'synthetic_trace.jsonl', + 'toolagent': 'toolagent_trace.jsonl' + } + + # Run benchmarks + results = [] + for scenario in scenarios: + trace_path = Path(args.trace_dir) / trace_files[scenario] + if trace_path.exists(): + result = run_benchmark( + str(trace_path), + str(Path(args.storage_dir) / scenario), + model_config, + args.max_requests, + args.max_pages, + args.page_size_tokens, + args.fsync_mode, + args.fsync_batch_size + ) + results.append(result) + else: + print(f"Warning: Trace file not found: {trace_path}") + + # Print results + if results: + print_results(results) + else: + print("Error: No trace files were successfully processed.", file=sys.stderr) + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/benchmarks/storage_benchmark_v1/doc/README.md b/benchmarks/storage_benchmark_v1/doc/README.md new file mode 100644 index 00000000..fa490aad --- /dev/null +++ b/benchmarks/storage_benchmark_v1/doc/README.md @@ -0,0 +1,121 @@ +# KVCache Storage Benchmark v1 + +## Overview + +The KVCache Storage Benchmark is a tool for evaluating storage performance of KVCache workloads. It simulates real-world cache access patterns using trace replay and measures storage I/O performance with detailed statistics. + +## Usage + +### Basic Usage + +```bash +cd benchmarks/storage_benchmark_v1 +python benchmark.py --scenario conversation \ + --trace-dir /path/to/Mooncake/FAST25-release/traces \ + --storage-dir /path/to/test/drive +``` + +### Command Line Options + +| Option | Default | Description | +|--------|---------|-------------| +| `--trace-dir` | `../../FAST25-release/traces` | Directory containing trace files | +| `--scenario` | `toolagent` | Test scenario: `conversation`, `synthetic`, `toolagent`, or `all` | +| `--storage-dir` | `/tmp/mooncake_bench` | Directory for storage files | +| `--model` | `glm5` | Model preset: `glm5` or `kimi-k2.6` | +| `--page-size-tokens` | `512` | Page size in tokens | +| `--max-requests` | `None` | Maximum number of requests to process | +| `--max-pages` | `2000` | Maximum number of pages (creates modulo mapping if trace is larger) | +| `--fsync-mode` | `none` | When to fsync: `none`, `batch`, `always`, or `end` | +| `--fsync-batch-size` | `100` | Number of writes between fsync in batch mode | + +## Output Format + +### Progress Output + +During execution, each request displays real-time statistics: + +``` +[ 10/12031] ids= 35 tokens= 18060 | QPS= 2.45 | R= 36 ( 22.01ms, 2435.2MB/s) | W= 963 ( 19.35ms, 2770.1MB/s) +``` + +Fields: +- `[N/Total]`: Current request progress +- `ids=N`: Number of hash_ids in this request +- `tokens=N`: Total tokens (input + output) +- `QPS=X`: Queries per second (overall) +- `R=N (latency, bandwidth)`: Read count, average latency, bandwidth +- `W=N (latency, bandwidth)`: Write count, average latency, bandwidth + +### Final Results + +``` +================================================================================ + [1/1] toolagent_trace.jsonl +================================================================================ + +[General] + Model: glm5 + Requests: 12031 + Tokens: 123456789 + Total I/O Time: 245.123 s + QPS: 49.07 + Hit Rate: 3.25% + +[Read Operations] + Count: 390 + Data Volume: 20919.62 MB + Total Time: 8.590 s + Bandwidth: 2435.67 MB/s + Latency: + Avg: 22.032 ms + P50: 21.456 ms + P95: 28.912 ms + P99: 35.234 ms + +[Write Operations] + Count: 11641 + Data Volume: 654321.45 MB + Total Time: 236.533 s + Bandwidth: 2765.89 MB/s + Latency: + Avg: 20.312 ms + P50: 19.876 ms + P95: 25.123 ms + P99: 31.456 ms + +[Storage Info] + Max Pages: 2000 + Written Pages: 2000 + Sync Count: 0 +``` + +## Modulo Mapping + +When the trace requires more pages than `--max-pages`, modulo mapping is enabled: + +``` +physical_page_id = logical_page_id % max_pages +``` + +This allows simulating large traces (millions of pages) with limited storage (thousands of pages). The first-hit/read-then-write logic is preserved by tracking written logical pages in memory. + +**Example**: With `--max-pages 2000`, logical page IDs 0-1999 map directly to physical pages 0-1999. Logical page 2000 maps to physical page 0, logical page 2001 maps to physical page 1, etc. + +## Graceful Interruption + +Press `Ctrl-C` at any time to stop the benchmark and view partial results. The output will display all statistics collected up to the interruption point, using the same format as final results. + +## Troubleshooting + +### Insufficient Storage Warning +``` +⚠️ Modulo mapping ENABLED (limited storage) +⚠️ Storage insufficient: 14,257,620 pages shortfall (762.34 GB) +``` +Increase `--max-pages` to reduce modulo mapping effects. + +### Low Bandwidth or High Latency +- Check fsync mode (`--fsync-mode none` for best performance) +- Verify disk performance with `fio` or `dd` +- Verify storage device health diff --git a/benchmarks/storage_benchmark_v1/layout/__init__.py b/benchmarks/storage_benchmark_v1/layout/__init__.py new file mode 100644 index 00000000..5d00a789 --- /dev/null +++ b/benchmarks/storage_benchmark_v1/layout/__init__.py @@ -0,0 +1,17 @@ +""" +KVCache Layout Module + +Provides layout interface and implementations for different model architectures. +""" + +from .interface import KVLayout, StorageAccess +from .mla import MLALayout, MLA_MODEL_CONFIG, get_model_config, create_layout + +__all__ = [ + 'KVLayout', + 'StorageAccess', + 'MLALayout', + 'MLA_MODEL_CONFIG', + 'get_model_config', + 'create_layout', +] diff --git a/benchmarks/storage_benchmark_v1/layout/interface.py b/benchmarks/storage_benchmark_v1/layout/interface.py new file mode 100644 index 00000000..569bc104 --- /dev/null +++ b/benchmarks/storage_benchmark_v1/layout/interface.py @@ -0,0 +1,55 @@ +""" +KVCache Layout Interface + +Defines the abstract interface for KVCache storage layouts. +Layout layer converts normalized requests into storage access requirements. +""" + +from abc import ABC, abstractmethod +from typing import Iterator, Any +from dataclasses import dataclass + + +@dataclass +class StorageAccess: + """Storage access requirement + + Represents a need to access a page. Whether to READ or WRITE is determined + by the upper layer based on whether the page already exists. + """ + page_id: int # Page ID (hash_id from trace) + offset_in_page: int = 0 # Offset within the page (default: 0) + length: int = None # Number of bytes (default: entire page) + + def __repr__(self): + if self.offset_in_page == 0 and self.length is None: + return f"Access(page_id={self.page_id})" + else: + return f"Access(page_id={self.page_id}, offset={self.offset_in_page}, length={self.length})" + + +class KVLayout(ABC): + """Abstract interface for KVCache storage layout + + Converts normalized KVCache requests into storage access requirements. + + Request format: + - hash_ids: List[int] - chunk/page identifiers + - input_length: int - input token count + - output_length: int - output token count + + Output: + - Iterator of StorageAccess (page access requirements) + """ + + @abstractmethod + def get_operations(self, request: Any) -> Iterator[StorageAccess]: + """Generate storage access requirements for a request + + Args: + request: KVCache request with hash_ids, input_length, output_length + + Yields: + StorageAccess: Page access requirements (READ vs WRITE decided by upper layer) + """ + pass diff --git a/benchmarks/storage_benchmark_v1/layout/mla.py b/benchmarks/storage_benchmark_v1/layout/mla.py new file mode 100644 index 00000000..6d7a3864 --- /dev/null +++ b/benchmarks/storage_benchmark_v1/layout/mla.py @@ -0,0 +1,174 @@ +""" +MLA (Multi-head Latent Attention) KVCache Layout + +Implements the KVLayout interface for MLA architecture. +""" + +from typing import Iterator, Any + +from .interface import KVLayout, StorageAccess + + +# ============================================================================ +# MLA Model Configurations +# ============================================================================ + +# MLA Model configurations +# Source: https://kvcache.ai/tools/kv-cache-calculator/ +# MLA architecture: hash_id -> {layer_0: [pages], layer_1: [pages], ...} +MLA_MODEL_CONFIG = { + # GLM-5: 78 layers, 64 tokens/page + # KV: 78 layers × 64 tokens × (512+64+128) × 2 = 90,112 bytes/page + # Per token: 1,408 bytes + "glm5": { + "name": "glm5", + "num_layers": 78, + "kv_lora_rank": 512, + "qk_rope_head_dim": 64, + "index_head_dim": 128, + "kv_precision_bytes": 2, # BF16 + "indexer_precision_bytes": 2, # BF16 + }, + + # Kimi-K2.6: 61 layers, 64 tokens/page + # KV: 61 layers × 64 tokens × (512+64) × 2 = 73,728 bytes/page + # Per token: 1,152 bytes + "kimi-k2.6": { + "name": "kimi-k2.6", + "num_layers": 61, + "kv_lora_rank": 512, + "qk_rope_head_dim": 64, + "index_head_dim": 0, # Kimi doesn't use separate indexer + "kv_precision_bytes": 2, # BF16 + "indexer_precision_bytes": 0, + }, +} + + +class MLALayout(KVLayout): + """MLA (Multi-head Latent Attention) KVCache layout + + MLA Architecture: + - Each hash_id corresponds to a 512-token chunk + - Each hash_id maps to ONE complete entry containing all layers + - Entry contains KV + Indexer data for all layers for 512 tokens + - Value size is fixed per hash_id (includes all layers) + + Value Size Calculation (per hash_id entry): + per_layer_size = 512 × (kv_lora_rank + qk_rope_head_dim + index_head_dim) × precision_bytes + value_size = per_layer_size × num_layers + + For GLM-5 with 512 tokens per entry: + per_layer_size = 512 × (512 + 64 + 128) × 2 = 720,896 bytes + value_size = 720,896 × 78 = 56,229,888 bytes = 53.6 MiB + + Key Pattern: hash_id → single entry (all layers included) + Total Keys = len(hash_ids) + + Used in: GLM-5, Kimi-K2.6 + """ + + def __init__(self, num_layers: int, kv_lora_rank: int, qk_rope_head_dim: int, + index_head_dim: int, precision_bytes: int, page_size_tokens: int = 512): + """Initialize MLA layout + + Args: + num_layers: Number of transformer layers + kv_lora_rank: KV LoRA rank dimension + qk_rope_head_dim: QK rope head dimension + index_head_dim: Indexer head dimension + precision_bytes: Precision in bytes (BF16=2, INT8=1, INT4=0.5) + page_size_tokens: Tokens per page (default: 512) + """ + self.num_layers = num_layers + self.kv_lora_rank = kv_lora_rank + self.qk_rope_head_dim = qk_rope_head_dim + self.index_head_dim = index_head_dim + self.precision_bytes = precision_bytes + self.page_size_tokens = page_size_tokens + + # Calculate fixed value size per entry (per hash_id) + # Each entry contains KV + Indexer for all layers for page_size_tokens + # Per layer: page_size_tokens × (kv_lora_rank + qk_rope_head_dim + index_head_dim) × precision_bytes + # Total: per_layer_size × num_layers + per_layer_size = page_size_tokens * (kv_lora_rank + qk_rope_head_dim + index_head_dim) * precision_bytes + self.value_size_bytes = per_layer_size * num_layers + + # Store page_size for backward compatibility + self.page_size = self.value_size_bytes + + def get_operations(self, request: Any) -> Iterator[StorageAccess]: + """Generate storage access requirements for a request + + For MLA architecture: + - Each hash_id corresponds to one complete page (512 tokens, all layers) + - Generate one access requirement per hash_id + + Args: + request: KVCache request with hash_ids, input_length, output_length + + Yields: + StorageAccess: Page access requirements + """ + for hash_id in request.hash_ids: + yield StorageAccess( + page_id=hash_id, + offset_in_page=0, + length=self.value_size_bytes + ) + + +# ============================================================================ +# Utility Functions +# ============================================================================ + +def get_model_config(model_name: str) -> dict: + """Get MLA model configuration by name + + Args: + model_name: Model identifier (e.g., 'glm5', 'kimi-k2.6') + + Returns: + dict: Model configuration + + Raises: + KeyError: If model name is not found + """ + if model_name not in MLA_MODEL_CONFIG: + available = ", ".join(MLA_MODEL_CONFIG.keys()) + raise KeyError(f"Unknown model: {model_name}. Available: {available}") + return MLA_MODEL_CONFIG[model_name].copy() + + +def create_layout(model_config: dict, page_size_tokens: int = 64) -> MLALayout: + """Create an MLALayout instance from model configuration + + Args: + model_config: Model configuration dictionary with fields: + - num_layers: Number of transformer layers + - kv_lora_rank: KV LoRA rank dimension + - qk_rope_head_dim: QK rope head dimension + - index_head_dim: Indexer head dimension + - kv_precision_bytes: Precision in bytes (BF16=2, INT8=1) + page_size_tokens: Tokens per page (default 64) + + Returns: + MLALayout: Layout instance for MLA architecture + + Raises: + ValueError: If model configuration is invalid + """ + required_fields = ['num_layers', 'kv_lora_rank', 'qk_rope_head_dim', + 'index_head_dim', 'kv_precision_bytes'] + for field in required_fields: + if field not in model_config: + raise ValueError(f"Missing required field: {field}") + + return MLALayout( + num_layers=model_config['num_layers'], + kv_lora_rank=model_config['kv_lora_rank'], + qk_rope_head_dim=model_config['qk_rope_head_dim'], + index_head_dim=model_config['index_head_dim'], + precision_bytes=model_config['kv_precision_bytes'], + page_size_tokens=page_size_tokens, + ) diff --git a/benchmarks/storage_benchmark_v1/storage/__init__.py b/benchmarks/storage_benchmark_v1/storage/__init__.py new file mode 100644 index 00000000..40fe3856 --- /dev/null +++ b/benchmarks/storage_benchmark_v1/storage/__init__.py @@ -0,0 +1,13 @@ +""" +KVCache Storage Module + +Provides storage backend implementations for KVCache systems. +""" + +from .interface import Storage +from .disk import DiskHashTable + +__all__ = [ + 'Storage', + 'DiskHashTable', +] diff --git a/benchmarks/storage_benchmark_v1/storage/disk.py b/benchmarks/storage_benchmark_v1/storage/disk.py new file mode 100644 index 00000000..286ff751 --- /dev/null +++ b/benchmarks/storage_benchmark_v1/storage/disk.py @@ -0,0 +1,328 @@ +""" +Simple SSD Hash Table Storage + +Each key maps to a complete page entry. +""" + +import os +import time +from pathlib import Path +from typing import Dict, List, Optional, Any + +from .interface import Storage + + +def calc_percentiles(data): + """Calculate latency percentiles""" + if not data: + return {'avg_ms': 0, 'p50_ms': 0, 'p95_ms': 0, 'p99_ms': 0} + import statistics + sorted_data = sorted(data) + n = len(sorted_data) + def get_percentile(p): + idx = int(n * p / 100) + if idx >= n: idx = n - 1 + return sorted_data[idx] + return { + 'avg_ms': statistics.mean(data), + 'p50_ms': get_percentile(50), + 'p95_ms': get_percentile(95), + 'p99_ms': get_percentile(99), + } + + +class DiskHashTable(Storage): + """Simple disk-based hash table + + File Layout: + Page 0: offset = 0 * page_size + Page 1: offset = 1 * page_size + Page 2: offset = 2 * page_size + ... + Page N: offset = N * page_size + + When max_pages < actual page_id range, uses modulo mapping: + actual_page_id = page_id % max_pages + This allows simulating large traces with limited storage. + """ + + def __init__(self, storage_dir: str, page_size: int, + max_pages: int = 100000, + fsync_mode: str = 'batch', fsync_batch_size: int = 100): + """Initialize disk hash table + + Args: + storage_dir: Storage directory + page_size: Size of each entry (page) in bytes + max_pages: Maximum number of entries (creates circular mapping if trace is larger) + fsync_mode: When to fsync ('batch', 'always', 'end', 'none') + fsync_batch_size: Writes between fsync + """ + self.storage_dir = Path(storage_dir) + self.page_size = page_size + self.max_pages = max_pages + self.max_page_id = max_pages - 1 + self.fsync_mode = fsync_mode + self.fsync_batch_size = fsync_batch_size + self.storage_dir.mkdir(parents=True, exist_ok=True) + self.storage_file = self.storage_dir / "data.bin" + self._allocate_file() + self.fd = None + self._buffer = os.urandom(page_size) + self.stats = { + 'read_count': 0, + 'write_count': 0, + 'read_bytes': 0, + 'write_bytes': 0, + 'read_latencies_ms': [], + 'write_latencies_ms': [], + 'read_time_s': 0.0, + 'write_time_s': 0.0, + 'sync_count': 0, + 'hit': 0, + 'miss': 0, + } + self._pending_syncs = 0 + self._written_pages: set = set() + + def _allocate_file(self): + """Pre-allocate disk space efficiently""" + file_size = self.max_pages * self.page_size + if not self.storage_file.exists(): + print(f" [Storage] Creating file: {self.storage_file}") + print(f" [Storage] Requested size: {file_size / (1024**3):.2f} GB ({self.max_pages:,} pages × {self.page_size} bytes = {file_size:,} bytes)") + # Use fallocate for efficient preallocation (Linux) + fd = os.open(self.storage_file, os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0o644) + try: + # Try fallocate first (Linux specific, much faster) + try: + import fcntl + fcntl.fallocate(fd, 0, file_size) + method = "fallocate" + except (ImportError, AttributeError, OSError): + # Fallback to seek+write method + os.lseek(fd, file_size - 1, os.SEEK_SET) + os.write(fd, b'\0') + os.fsync(fd) + method = "seek+write" + finally: + os.close(fd) + actual_size = self.storage_file.stat().st_size if self.storage_file.exists() else 0 + print(f" [Storage] Pre-allocated {actual_size / (1024**3):.2f} GB using {method}") + else: + actual_size = self.storage_file.stat().st_size + actual_pages = actual_size // self.page_size + print(f" [Storage] Reusing existing file: {self.storage_file}") + print(f" [Storage] Current file size: {actual_size / (1024**3):.2f} GB ({actual_pages:,} pages × {self.page_size} bytes = {actual_size:,} bytes)") + + def _get_fd(self): + if self.fd is None: + print(f" [Storage] Opening file: {self.storage_file}") + self.fd = os.open(self.storage_file, os.O_RDWR | os.O_CREAT, 0o644) + if self.storage_file.exists(): + actual_size = self.storage_file.stat().st_size + actual_pages = actual_size // self.page_size + print(f" [Storage] File size: {actual_size / (1024**3):.2f} GB ({actual_pages:,} pages × {self.page_size} bytes = {actual_size:,} bytes)") + return self.fd + + # ======================================================================== + # Core operations + # ======================================================================== + + def _map_page_id(self, page_id: int) -> int: + """Map logical page_id to physical page_id using modulo + + This allows simulating large traces with limited storage space. + + Args: + page_id: Logical page_id (from trace) + + Returns: + Physical page_id in storage (0 to max_pages-1) + """ + return page_id % self.max_pages + + def read(self, page_id: int, offset_in_page: int = 0, length: int = None) -> float: + """Read entry from disk + + Args: + page_id: Logical page ID (hash_id from trace) + offset_in_page: Offset within the page (default: 0) + length: Number of bytes to read (default: entire page) + + Returns: + Read latency in ms + """ + if length is None: + length = self.page_size - offset_in_page + + # Validate parameters + if offset_in_page < 0 or offset_in_page >= self.page_size: + raise ValueError(f"offset_in_page {offset_in_page} out of range [0, {self.page_size})") + if length <= 0 or offset_in_page + length > self.page_size: + raise ValueError(f"length {length} invalid with offset_in_page {offset_in_page} (page_size={self.page_size})") + + # Map to physical page_id and calculate offset + physical_page_id = self._map_page_id(page_id) + offset = physical_page_id * self.page_size + offset_in_page + start = time.perf_counter() + + try: + fd = self._get_fd() + os.pread(fd, length, offset) + + latency = (time.perf_counter() - start) * 1000.0 + self.stats['read_count'] += 1 + self.stats['read_bytes'] += length + self.stats['read_latencies_ms'].append(latency) + self.stats['read_time_s'] += latency / 1000.0 + self.stats['hit'] += 1 + return latency + except OSError as e: + print(f"Read error (page_id={page_id}, physical_page_id={physical_page_id}): {e}") + return 0.0 + + def write(self, page_id: int, offset_in_page: int = 0, length: int = None) -> float: + """Write entry to disk + + Args: + page_id: Logical page ID (hash_id from trace) + offset_in_page: Offset within the page (default: 0) + length: Number of bytes to write (default: entire page) + + Returns: + Write latency in ms + """ + if length is None: + length = self.page_size - offset_in_page + + # Validate parameters + if offset_in_page < 0 or offset_in_page >= self.page_size: + raise ValueError(f"offset_in_page {offset_in_page} out of range [0, {self.page_size})") + if length <= 0 or offset_in_page + length > self.page_size: + raise ValueError(f"length {length} invalid with offset_in_page {offset_in_page} (page_size={self.page_size})") + + # Map to physical page_id and calculate offset + physical_page_id = self._map_page_id(page_id) + offset = physical_page_id * self.page_size + offset_in_page + start = time.perf_counter() + + try: + fd = self._get_fd() + # Use corresponding portion of buffer + os.pwrite(fd, self._buffer[:length], offset) + write_done = time.perf_counter() + + # Fsync + if self.fsync_mode == 'always': + os.fsync(fd) + self.stats['sync_count'] += 1 + self._pending_syncs = 0 + latency = (time.perf_counter() - start) * 1000.0 + elif self.fsync_mode == 'batch': + self._pending_syncs += 1 + if self._pending_syncs >= self.fsync_batch_size: + os.fsync(fd) + self.stats['sync_count'] += 1 + self._pending_syncs = 0 + latency = (write_done - start) * 1000.0 + else: + latency = (write_done - start) * 1000.0 + + self._written_pages.add(page_id) + self.stats['write_count'] += 1 + self.stats['write_bytes'] += length + self.stats['write_latencies_ms'].append(latency) + self.stats['write_time_s'] += latency / 1000.0 + self.stats['miss'] += 1 + return latency + except OSError as e: + print(f"Write error (page_id={page_id}, offset_in_page={offset_in_page}, length={length}): {e}") + return 0.0 + + # ======================================================================== + # Storage interface methods + # ======================================================================== + + def exists(self, page_id: int) -> bool: + """Check if logical page has been written + + Args: + page_id: Logical page ID (hash_id from trace) + + Returns: + True if this logical page has been written before + """ + return page_id in self._written_pages + + def delete(self, page_id: int) -> bool: + """Delete page (no-op in direct mapping) + + Args: + page_id: Page ID to delete + + Returns: + True (always succeeds in direct mapping) + """ + # No-op since we don't track which pages have been written + return True + + def get_stats(self) -> Dict[str, Any]: + """Get statistics""" + import statistics + + def calc_stats(latencies): + if not latencies: + return {'avg_ms': 0, 'p50_ms': 0, 'p95_ms': 0, 'p99_ms': 0} + return { + 'avg_ms': statistics.mean(latencies), + **calc_percentiles(latencies), + } + + return { + 'read': { + 'count': self.stats['read_count'], + 'mb': self.stats['read_bytes'] / 1024 / 1024, + 'time_s': self.stats['read_time_s'], + **calc_stats(self.stats['read_latencies_ms']) + }, + 'write': { + 'count': self.stats['write_count'], + 'mb': self.stats['write_bytes'] / 1024 / 1024, + 'time_s': self.stats['write_time_s'], + **calc_stats(self.stats['write_latencies_ms']) + }, + 'sync_count': self.stats['sync_count'], + 'max_pages': self.max_pages, + 'written_pages': len(self._written_pages), + 'page_hits': self.stats['hit'], + 'page_misses': self.stats['miss'], + } + + # ======================================================================== + # Resource management + # ======================================================================== + + def close(self, force_sync: bool = True): + """Close file""" + if force_sync and self.fsync_mode in ['end', 'batch']: + if self.fd is not None: + try: + os.fsync(self.fd) + self.stats['sync_count'] += 1 + except OSError: + pass + + if self.fd is not None: + try: + os.close(self.fd) + except OSError: + pass + self.fd = None + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + return False diff --git a/benchmarks/storage_benchmark_v1/storage/interface.py b/benchmarks/storage_benchmark_v1/storage/interface.py new file mode 100644 index 00000000..e7c2aa02 --- /dev/null +++ b/benchmarks/storage_benchmark_v1/storage/interface.py @@ -0,0 +1,93 @@ +""" +KVCache Storage Interface + +Simplified storage interface for KVCache benchmark. +Key = page_id (int), Value = fixed-size bytes. +""" + +from abc import ABC, abstractmethod +from typing import Dict, Any + + +class Storage(ABC): + """Abstract base class for KVCache storage + + Simplified design: + - Key: page_id (int, hash_id from trace) + - Value: fixed-size bytes (page_size) + - Direct mapping: page_id -> offset -> payload + + Core operations: read, write, exists, delete. + """ + + @abstractmethod + def read(self, page_id: int, offset_in_page: int = 0, length: int = None) -> float: + """Read page from disk + + Args: + page_id: Page ID (hash_id from trace) + offset_in_page: Offset within the page (default: 0) + length: Number of bytes to read (default: entire page) + + Returns: + Read latency in milliseconds + """ + pass + + @abstractmethod + def write(self, page_id: int, offset_in_page: int = 0, length: int = None) -> float: + """Write page to disk + + Args: + page_id: Page ID (hash_id from trace) + offset_in_page: Offset within the page (default: 0) + length: Number of bytes to write (default: entire page) + + Returns: + Write latency in milliseconds + """ + pass + + @abstractmethod + def exists(self, page_id: int) -> bool: + """Check if page exists + + Args: + page_id: Page ID (hash_id from trace) + + Returns: + True if page_id is within valid range + """ + pass + + @abstractmethod + def delete(self, page_id: int) -> bool: + """Delete page (no-op in direct mapping) + + Args: + page_id: Page ID to delete + + Returns: + True (always succeeds in direct mapping) + """ + pass + + @abstractmethod + def get_stats(self) -> Dict[str, Any]: + """Get storage statistics + + Returns: + Dictionary containing storage statistics + """ + pass + + def close(self): + """Close the storage and release resources""" + pass + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + return False diff --git a/dependencies.sh b/dependencies.sh index 607e58af..bbbd82aa 100755 --- a/dependencies.sh +++ b/dependencies.sh @@ -24,6 +24,7 @@ NC="\033[0m" # No Color REPO_ROOT=`pwd` GITHUB_PROXY=${GITHUB_PROXY:-"https://github.com"} GOVER=1.25.9 +OS_RELEASE_FILE=${OS_RELEASE_FILE:-/etc/os-release} # Function to print section headers print_section() { @@ -48,23 +49,57 @@ check_success() { fi } +read_os_release_value() { + local key="$1" + awk -F= -v key="$key" ' + $1 == key { + value = $0 + sub(/^[^=]*=/, "", value) + gsub(/^"|"$/, "", value) + print value + exit + } + ' "$OS_RELEASE_FILE" +} + +# Function to detect OS +detect_os() { + if [ -f "$OS_RELEASE_FILE" ]; then + ID=$(read_os_release_value ID) + VERSION_ID=$(read_os_release_value VERSION_ID) + OS=$(echo "$ID" | tr '[:upper:]' '[:lower:]') + OS_VERSION=$VERSION_ID + elif [ -f /etc/redhat-release ]; then + OS="centos" + else + print_error "Cannot detect OS. Supported OS: Ubuntu, Debian, CentOS, RHEL, Rocky, AlmaLinux, EulerOS, and openEuler." + fi + + echo -e "${GREEN}Detected OS: $OS ${OS_VERSION:-unknown}${NC}" +} + if [ $(id -u) -ne 0 ]; then print_error "Require root permission, try sudo ./dependencies.sh" fi # Parse command line arguments SKIP_CONFIRM=false +INSTALL_SPDK=false for arg in "$@"; do case $arg in -y|--yes) SKIP_CONFIRM=true ;; + --with-spdk) + INSTALL_SPDK=true + ;; -h|--help) echo -e "${YELLOW}Mooncake Dependencies Installer${NC}" echo -e "Usage: ./dependencies.sh [OPTIONS]" echo -e "\nOptions:" - echo -e " -y, --yes Skip confirmation and install all dependencies" - echo -e " -h, --help Show this help message and exit" + echo -e " -y, --yes Skip confirmation and install all dependencies" + echo -e " --with-spdk Install SPDK for NVMe-oF support" + echo -e " -h, --help Show this help message and exit" exit 0 ;; esac @@ -77,6 +112,9 @@ echo -e "The following components will be installed:" echo -e " - System packages (build tools, libraries)" echo -e " - Git submodules (including pybind11 and yalantinglibs)" echo -e " - Go $GOVER" +if [ "$INSTALL_SPDK" = true ]; then + echo -e " - SPDK (for NVMe-oF support)" +fi echo # Ask for confirmation unless -y flag is used @@ -89,51 +127,100 @@ if [ "$SKIP_CONFIRM" = false ]; then fi fi +# Detect OS +detect_os # Update package lists print_section "Updating package lists" -apt-get update -check_success "Failed to update package lists" +if [ "$OS" = "ubuntu" ] || [ "$OS" = "debian" ]; then + apt-get update + check_success "Failed to update package lists" +elif [ "$OS" = "centos" ] || [ "$OS" = "rhel" ] || [ "$OS" = "rocky" ] || [ "$OS" = "almalinux" ] || [ "$OS" = "euleros" ] || [ "$OS" = "openeuler" ]; then + yum install -y dnf-plugins-core epel-release || true + yum config-manager --set-enabled powertools || yum config-manager --set-enabled crb || true + yum clean all + yum makecache + check_success "Failed to update package lists" +else + print_error "Unsupported OS: $OS" +fi # Install system packages print_section "Installing system packages" echo -e "${YELLOW}This may take a few minutes...${NC}" -SYSTEM_PACKAGES="build-essential \ - cmake \ - ninja-build \ - git \ - wget \ - unzip \ - libibverbs-dev \ - libgoogle-glog-dev \ - libgtest-dev \ - libjsoncpp-dev \ - libunwind-dev \ - libnuma-dev \ - libpython3-dev \ - libboost-all-dev \ - libssl-dev \ - libgrpc-dev \ - libgrpc++-dev \ - libprotobuf-dev \ - libyaml-cpp-dev \ - protobuf-compiler-grpc \ - libcurl4-openssl-dev \ - libhiredis-dev \ - liburing-dev \ - libjemalloc-dev \ - libmsgpack-dev \ - libzstd-dev \ - libasio-dev \ - libxxhash-dev \ - pkg-config \ - patchelf \ - libc6-dev \ - libc-bin" +if [ "$OS" = "ubuntu" ] || [ "$OS" = "debian" ]; then + SYSTEM_PACKAGES="build-essential \ + cmake \ + ninja-build \ + git \ + wget \ + unzip \ + libibverbs-dev \ + libgoogle-glog-dev \ + libgtest-dev \ + libjsoncpp-dev \ + libunwind-dev \ + libnuma-dev \ + libpython3-dev \ + libboost-all-dev \ + libssl-dev \ + libgrpc-dev \ + libgrpc++-dev \ + libprotobuf-dev \ + libyaml-cpp-dev \ + protobuf-compiler-grpc \ + libcurl4-openssl-dev \ + libhiredis-dev \ + liburing-dev \ + libjemalloc-dev \ + libmsgpack-dev \ + libzstd-dev \ + libasio-dev \ + libxxhash-dev \ + pkg-config \ + patchelf \ + libc6-dev \ + libc-bin" + + apt-get install -y $SYSTEM_PACKAGES + check_success "Failed to install system packages" + +elif [ "$OS" = "centos" ] || [ "$OS" = "rhel" ] || [ "$OS" = "rocky" ] || [ "$OS" = "almalinux" ] || [ "$OS" = "euleros" ] || [ "$OS" = "openeuler" ]; then + SYSTEM_PACKAGES="@development \ + cmake \ + git \ + wget \ + rdma-core-devel \ + glog-devel \ + gflags-devel \ + gtest-devel \ + jsoncpp-devel \ + libunwind-devel \ + numactl-devel \ + python3-devel \ + boost1.78-devel \ + openssl-devel \ + protobuf-devel \ + yaml-cpp-devel \ + libcurl-devel \ + hiredis-devel \ + liburing-devel \ + jemalloc-devel \ + msgpack-devel \ + libzstd-devel \ + pkgconf-pkg-config \ + elfutils-libelf-devel \ + patchelf \ + xxhash-devel \ + libbsd-devel" + + yum install -y $SYSTEM_PACKAGES + check_success "Failed to install system packages" +else + print_error "Unsupported OS: $OS" +fi -apt-get install -y $SYSTEM_PACKAGES -check_success "Failed to install system packages" print_success "System packages installed successfully" # Initialize and update git submodules @@ -185,15 +272,16 @@ cd "${REPO_ROOT}" print_section "Verifying essential build tools" # Verify getconf and ldd (required for glibc version detection in build_wheel.sh) -# Both are provided by libc-bin, which is included in SYSTEM_PACKAGES -if ! command -v getconf >/dev/null 2>&1; then - print_error "getconf not found after installing system packages. This should not happen." +if [ "$OS" = "ubuntu" ] || [ "$OS" = "debian" ]; then + if ! command -v getconf >/dev/null 2>&1; then + print_error "getconf not found after installing system packages. This should not happen." + fi + if ! command -v ldd >/dev/null 2>&1; then + print_error "ldd not found after installing system packages. This should not happen." + fi + print_success "getconf found: $(getconf --version 2>&1 | head -1)" + print_success "ldd found: $(ldd --version 2>&1 | head -1)" fi -if ! command -v ldd >/dev/null 2>&1; then - print_error "ldd not found after installing system packages. This should not happen." -fi -print_success "getconf found: $(getconf --version 2>&1 | head -1)" -print_success "ldd found: $(ldd --version 2>&1 | head -1)" print_section "Installing Go $GOVER" @@ -224,8 +312,6 @@ install_go() { echo "Downloading Go $GOVER from ${url}..." if wget -q --show-progress --timeout=30 --tries=2 -O "${GO_TARBALL}" "${url}"; then DOWNLOAD_SUCCESS=true - # If the official source (go.dev) failed and we fell back to a CN mirror, - # it likely means the network has restricted access to international sites. if [[ "$url" != "https://go.dev/dl/${GO_TARBALL}" ]]; then USED_CN_MIRROR=true fi @@ -241,19 +327,16 @@ install_go() { print_error "Failed to download Go $GOVER from all mirrors" fi - # Install Go echo "Installing Go $GOVER..." tar -C /usr/local -xzf "${GO_TARBALL}" check_success "Failed to install Go $GOVER" - # Clean up downloaded file rm -f "${GO_TARBALL}" check_success "Failed to clean up Go installation file" print_success "Go $GOVER installed successfully" } -# Check if Go is already installed if command -v go &> /dev/null; then GO_VERSION=$(go version | awk '{print $3}') if [[ "$GO_VERSION" == "go$GOVER" ]]; then @@ -273,8 +356,7 @@ if ! grep -q "export PATH=\$PATH:/usr/local/go/bin" ~/.bashrc; then echo -e "${YELLOW}Please run 'source ~/.bashrc' or start a new terminal to use Go${NC}" fi -# Set GOPROXY only if Go download fell back to a CN mirror, indicating restricted -# network access to international sites. Skip if user already configured GOPROXY. +# Set GOPROXY only if Go download fell back to a CN mirror if [ "$USED_CN_MIRROR" = true ] && [ -z "$GOPROXY" ]; then export GOPROXY=https://goproxy.cn,https://goproxy.io,direct echo -e "${YELLOW}Detected restricted network (Go was downloaded from a CN mirror).${NC}" @@ -287,6 +369,69 @@ elif [ -n "$GOPROXY" ]; then echo -e "${GREEN}GOPROXY already set to: ${GOPROXY}${NC}" fi +# Install SPDK if requested +if [ "$INSTALL_SPDK" = true ]; then + print_section "Installing SPDK" + + cd "${REPO_ROOT}/extern" + check_success "Failed to change to extern directory" + + # Remove existing SPDK if present + if [ -d "spdk" ]; then + echo -e "${YELLOW}SPDK directory already exists. Removing for fresh install...${NC}" + rm -rf spdk + check_success "Failed to remove existing SPDK directory" + fi + + # Clone SPDK + echo "Cloning SPDK from ${GITHUB_PROXY}/spdk/spdk.git..." + git clone ${GITHUB_PROXY}/spdk/spdk.git + check_success "Failed to clone SPDK" + + cd spdk + check_success "Failed to change to SPDK directory" + + # Checkout specific version + echo "Checking out SPDK version v23.01.1..." + git checkout v23.01.1 + check_success "Failed to checkout SPDK version v23.01.1" + + # Initialize submodules + echo "Initializing SPDK submodules..." + git submodule update --init + check_success "Failed to initialize SPDK submodules" + + # Install SPDK dependencies + echo "Installing SPDK dependencies..." + ./scripts/pkgdep.sh + check_success "Failed to install SPDK dependencies" + + # Configure SPDK with RDMA support + echo "Configuring SPDK with RDMA support..." + ./configure --with-rdma + check_success "Failed to configure SPDK" + + # Build SPDK + echo "Building SPDK (using $(nproc) cores)..." + make -j$(nproc) + check_success "Failed to build SPDK" + + # Install SPDK + echo "Installing SPDK..." + make install + check_success "Failed to install SPDK" + + # Copy DPDK libraries to system library path + if ls dpdk/build/lib/*.a >/dev/null 2>&1; then + echo "Copying DPDK libraries to /usr/local/lib..." + cp dpdk/build/lib/*.a /usr/local/lib/ + check_success "Failed to copy DPDK libraries" + fi + + print_success "SPDK installed successfully" + cd "${REPO_ROOT}" +fi + # Return to the repository root cd "${REPO_ROOT}" @@ -298,6 +443,13 @@ echo -e " ${GREEN}✓${NC} System packages" echo -e " ${GREEN}✓${NC} yalantinglibs" echo -e " ${GREEN}✓${NC} Git submodules" echo -e " ${GREEN}✓${NC} Go $GOVER" +if [ "$INSTALL_SPDK" = true ]; then + echo -e " ${GREEN}✓${NC} SPDK (v23.01.1)" +fi echo echo -e "You can now build and run Mooncake." echo -e "${YELLOW}Note: You may need to restart your terminal or run 'source ~/.bashrc' to use Go.${NC}" + +if [ "$INSTALL_SPDK" = true ]; then + echo -e "${YELLOW}Note: SPDK requires hugepages and RDMA configuration. Please refer to SPDK documentation for setup.${NC}" +fi diff --git a/docker/mooncake.Dockerfile b/docker/mooncake.Dockerfile index 60d7314d..3dda2e6c 100644 --- a/docker/mooncake.Dockerfile +++ b/docker/mooncake.Dockerfile @@ -116,6 +116,7 @@ RUN apt-get update && \ # Copy wheels produced in builder stage and install them via pip COPY --from=builder /workspace/mooncake-wheel/dist /tmp/mooncake-wheel +COPY --chmod=755 scripts/check_hicache_hugepage_requirements.py /usr/local/bin/mooncake-hicache-sizing RUN python${PYTHON_VERSION} -m pip install --no-cache-dir /tmp/mooncake-wheel/*.whl && rm -rf /tmp/mooncake-wheel /root/.cache/pip CMD ["/bin/bash"] diff --git a/docs/source/api-reference/cpp/index.md b/docs/source/api-reference/cpp/index.md new file mode 100644 index 00000000..35e77191 --- /dev/null +++ b/docs/source/api-reference/cpp/index.md @@ -0,0 +1,16 @@ +# C/C++ API Reference + +| Module | Description | +|--------|-------------| +| [Transfer Engine C++ API](../../design/transfer-engine/cpp-api) | `TransferEngine` class — memory registration, batch transfer, segment management, RDMA transport | +| [TENT C++ API](../../design/tent/cpp-api) | `mooncake::tent::TransferEngine` — next-gen transfer engine with automatic transport selection and fault tolerance | +| [Mooncake Store Client C++ API](mooncake-store) | `Client` class — `Put`/`Get`/`Remove`/`Replicate` operations, `BufferAllocatorBase` interface | + +:::{toctree} +:maxdepth: 1 +:hidden: + +../../design/transfer-engine/cpp-api +../../design/tent/cpp-api +mooncake-store +::: diff --git a/docs/source/api-reference/cpp/mooncake-store.md b/docs/source/api-reference/cpp/mooncake-store.md new file mode 100644 index 00000000..41c3028a --- /dev/null +++ b/docs/source/api-reference/cpp/mooncake-store.md @@ -0,0 +1,178 @@ +# Mooncake Store C++ API Reference + +## Client C++ API + +### Constructor and Initialization `Init` + +```C++ +ErrorCode Init(const std::string& local_hostname, + const std::string& metadata_connstring, + const std::string& protocol, + void** protocol_args, + const std::string& master_server_entry); +``` + +Initializes the Mooncake Store client. The parameters are as follows: +- `local_hostname`: The `IP:Port` of the local machine or an accessible domain name (default value used if port is not included) +- `metadata_connstring`: The address of the metadata service (e.g., etcd/Redis) required for Transfer Engine initialization +- `protocol`: The protocol supported by the Transfer Engine, including RDMA and TCP +- `protocol_args`: Protocol parameters required by the Transfer Engine +- `master_server_entry`: The address information of the Master (`IP:Port` for default mode and `etcd://IP:Port;IP:Port;...;IP:Port` for high availability mode) + +### Get + +```C++ +tl::expected Get(const std::string& object_key, + std::vector& slices); +``` + +`Get` retrieves the value of `object_key` into the provided `slices`. The returned data is guaranteed to be complete and correct. Each slice must reference local DRAM/VRAM memory that has been pre-registered with `registerLocalMemory(addr, len)` (not the global segments that contribute to the distributed memory pool). When persistence is enabled and the requested data is not found in the distributed memory pool, `Get` will fall back to loading the data from SSD. + +### Put + +```C++ +tl::expected Put(const ObjectKey& key, + std::vector& slices, + const ReplicateConfig& config); +``` + +`Put` stores the value associated with `key` in the distributed memory pool. The `config` parameter allows specifying the required number of replicas as well as the preferred segment for storing the value. When persistence is enabled, `Put` also asynchronously triggers a persistence operation to SSD. + +**Replication Guarantees and Best Effort Behavior:** +- Each slice of an object is guaranteed to be replicated to different segments, ensuring distribution across separate storage nodes +- Different slices from different objects may be placed in the same segment +- Replication operates on a best-effort basis: if insufficient space is available for all requested replicas, the object will still be written with as many replicas as possible + +The data structure details of `ReplicateConfig` are as follows: + +```C++ +struct ReplicateConfig { + size_t replica_num{1}; // Total number of replicas for the object + bool with_soft_pin{false}; // Whether to enable soft pin mechanism for this object + bool with_hard_pin{false}; // Whether to enable hard pin (never evicted) + std::string preferred_segment{}; // Preferred segment for allocation +}; +``` + +### Upsert + +```C++ +tl::expected Upsert(const ObjectKey& key, + std::vector& slices, + const ReplicateConfig& config); + +std::vector> BatchUpsert( + const std::vector& keys, + std::vector>& batched_slices, + const ReplicateConfig& config); +``` + +`Upsert` inserts `key` if it does not exist and updates the existing object if +it does. It uses the same replication configuration model as `Put`, while +allowing the store to reuse existing placement for in-place updates when the +current layout permits it. `BatchUpsert` performs the same operation for +multiple keys using a shared replication configuration. + +### Remove + +```C++ +tl::expected Remove(const ObjectKey& key); +``` + +Used to delete the object corresponding to the specified key. This interface marks all data replicas associated with the key in the storage engine as deleted, without needing to communicate with the corresponding storage node (Client). + +### CreateCopyTask + +```C++ +tl::expected CreateCopyTask( + const std::string& key, + const std::vector& targets); +``` + +`CreateCopyTask` creates an asynchronous copy task that will be executed by the client's task execution system. This is useful when you want to submit multiple copy operations without waiting for each one to complete. The task is submitted to the master service, assigned a unique task ID, and executed asynchronously by an available client. The task status can be queried using `QueryTask`. + +**Task Execution and Result Reporting:** +1. **Task Assignment**: The master service assigns the task to an available client during the client's periodic ping operation +2. **Task Execution**: The assigned client executes the copy operation asynchronously in a background thread pool +3. **Result Reporting**: Upon completion (success or failure), the client automatically reports the result to the master service via `MarkTaskToComplete`: + - On success: `status = SUCCESS`, `message = "Task completed successfully"` + - On failure: `status = FAILED`, `message = ` +4. **Status Query**: You can query the task status at any time using `QueryTask` to monitor progress + +### CreateMoveTask + +```C++ +tl::expected CreateMoveTask( + const std::string& key, + const std::string& source, + const std::string& target); +``` + +`CreateMoveTask` creates an asynchronous move task that will be executed by the client's task execution system. This is useful when you want to submit multiple move operations without waiting for each one to complete. The task is submitted to the master service, assigned a unique task ID, and executed asynchronously by an available client. The task status can be queried using `QueryTask`. + +**Task Execution and Result Reporting:** +1. **Task Assignment**: The master service assigns the task to an available client during the client's periodic ping operation +2. **Task Execution**: The assigned client executes the move operation asynchronously in a background thread pool +3. **Result Reporting**: Upon completion (success or failure), the client automatically reports the result to the master service via `MarkTaskToComplete`: + - On success: `status = SUCCESS`, `message = "Task completed successfully"` + - On failure: `status = FAILED`, `message = ` +4. **Status Query**: You can query the task status at any time using `QueryTask` to monitor progress + +### QueryTask + +```C++ +tl::expected QueryTask(const UUID& task_id); +``` + +`QueryTask` queries the status of an asynchronous task (copy or move). This allows you to monitor the progress of task-based operations. The response includes task status, type, creation time, last update time, assigned client, and status message. + +The data structure details of `QueryTaskResponse` are as follows: + +```C++ +struct QueryTaskResponse { + UUID id; // Task UUID + TaskType type; // Task type (REPLICA_COPY or REPLICA_MOVE) + TaskStatus status; // Task status (PENDING, PROCESSING, SUCCESS, or FAILED) + int64_t created_at_ms_epoch; // Task creation timestamp in milliseconds + int64_t last_updated_at_ms_epoch; // Last update timestamp in milliseconds + UUID assigned_client; // UUID of the client assigned to execute the task + std::string message; // Status message or error description +}; +``` + +### BatchQueryIp + +```C++ +tl::expected, boost::hash>, ErrorCode> +BatchQueryIp(const std::vector& client_ids); +``` + +Used to batch query the IP addresses for multiple client IDs. For each client ID in the input list, this interface retrieves the unique IP addresses from all segments mounted by that client. The operation is performed on the Master Service and returns a map from client ID to their IP address lists. Only client IDs that have successfully mounted segments are included in the result map. This is useful for discovering the network locations of storage nodes in the cluster. + +### BatchReplicaClear + +```C++ +tl::expected, ErrorCode> +BatchReplicaClear(const std::vector& object_keys, + const UUID& client_id, + const std::string& segment_name); +``` + +Used to batch clear replicas for multiple object keys belonging to a specific client ID. This interface allows clearing replicas either on a specific segment or across all segments. If segment_name is empty, all replicas of the specified objects are cleared (the objects are deleted entirely). If segment_name is provided, only replicas located on that specific segment are cleared. The operation is performed on the Master Service and returns a list of object keys that were successfully cleared. Only objects that belong to the specified `client_id`, have expired leases, and meet the clearing criteria are processed. This is useful for managing storage resources and cleaning up data on specific storage nodes. + +### QueryByRegex + +```C++ +tl::expected>, ErrorCode> +QueryByRegex(const std::string& str); +``` + +Used to query the replica information for all objects whose keys match the given regular expression. This is useful for batch operations or for retrieving a group of related objects. The operation is performed on the Master and returns a map of keys to their replica lists. + +### RemoveByRegex + +```C++ +tl::expected RemoveByRegex(const ObjectKey& str); +``` + +Used to delete all objects from the store whose keys match the specified regular expression. This provides a powerful way to perform bulk deletions. The command returns the number of objects that were successfully removed. diff --git a/docs/source/api-reference/http/index.md b/docs/source/api-reference/http/index.md new file mode 100644 index 00000000..e946409f --- /dev/null +++ b/docs/source/api-reference/http/index.md @@ -0,0 +1,14 @@ +# HTTP + +| Module | Description | +|--------|-------------| +| [HTTP Service](../../http-api-reference/http-service) | RESTful endpoints for cluster management, metrics, and data inspection | +| [Conductor Indexer API](../../design/conductor/indexer-api-design) | Cache-aware routing: service registration, KV event subscription, prefix cache-hit query APIs | + +:::{toctree} +:maxdepth: 1 +:hidden: + +../../http-api-reference/http-service +../../design/conductor/indexer-api-design +::: diff --git a/docs/source/api-reference/python/index.md b/docs/source/api-reference/python/index.md new file mode 100644 index 00000000..3bb15f03 --- /dev/null +++ b/docs/source/api-reference/python/index.md @@ -0,0 +1,18 @@ +# Python + +| Module | Description | +|--------|-------------| +| [Mooncake Store](../../python-api-reference/mooncake-store) | Distributed KV cache storage client — `put`/`get`/`remove`/`replicate` operations | +| [DataProto Structured Object Transfer](../../python-api-reference/dataproto-structured-object-transfer) | Structured-object helpers for storing and retrieving DataProto-like payloads | +| [Transfer Engine](../../python-api-reference/transfer-engine) | High-performance RDMA/TCP data transfer between nodes | +| [EP Backend](../../python-api-reference/ep-backend) | Expert-parallel backend for large MoE model deployment | + +:::{toctree} +:maxdepth: 1 +:hidden: + +../../python-api-reference/mooncake-store +../../python-api-reference/dataproto-structured-object-transfer +../../python-api-reference/transfer-engine +../../python-api-reference/ep-backend +::: diff --git a/docs/source/api-reference/rust/index.md b/docs/source/api-reference/rust/index.md new file mode 100644 index 00000000..30ab8e83 --- /dev/null +++ b/docs/source/api-reference/rust/index.md @@ -0,0 +1,15 @@ +# Rust + +| Module | Description | +|--------|-------------| +| [Mooncake Store](./mooncake-store) | Safe Rust bindings for the distributed KV-cache store (`mooncake_store`) | +| [Transfer Engine](./transfer-engine) | Rust bindings for Transfer Engine (FFI wrapper used by the Rust example crate) | + +```{toctree} +:maxdepth: 1 +:hidden: + +mooncake-store +transfer-engine +``` + diff --git a/docs/source/api-reference/rust/mooncake-store.md b/docs/source/api-reference/rust/mooncake-store.md new file mode 100644 index 00000000..f589246d --- /dev/null +++ b/docs/source/api-reference/rust/mooncake-store.md @@ -0,0 +1,223 @@ +# Mooncake Store Rust API + +This page documents the Rust crate `mooncake_store` (located at `mooncake-store/rust`). +It is a **safe wrapper** around the Mooncake Store C API (`store_c.h`). + +For deployment and service prerequisites, also see: + +- Mooncake Store deployment guide: `deployment/mooncake-store-deployment-guide` +- Error code reference: `troubleshooting/error-code` + +## Build & runtime prerequisites + +The Rust crate links against the C++ Mooncake build outputs. + +- **Build**: + - Build Mooncake with Store + Rust enabled via CMake: `-DWITH_STORE=ON -DWITH_STORE_RUST=ON` + - Or build with Cargo after exporting the CMake build directory / include paths (see `mooncake-store/rust/README.md`). +- **Runtime**: + - Dynamic linker must find Mooncake shared libraries (typically via `LD_LIBRARY_PATH` pointing at the CMake build outputs). + - The store client requires: + - a **metadata server** (HTTP metadata or etcd, depending on your deployment) + - `mooncake_master` + +## Quick start (copy-paste) + +```rust +use mooncake_store::MooncakeStore; + +fn main() -> Result<(), mooncake_store::StoreError> { + let store = MooncakeStore::new()?; + store.setup( + "127.0.0.1", + "http://127.0.0.1:8080/metadata", + 512 << 20, // global_segment_size + 128 << 20, // local_buffer_size + "tcp", + "", + "127.0.0.1:50051", + )?; + + store.put("hello", b"world", None)?; + let value = store.get("hello")?; + assert_eq!(value, b"world"); + + store.remove("hello", false)?; + Ok(()) +} +``` + +## API reference + +### `MooncakeStore` + +#### `new() -> Result` + +Allocate a new store handle (uninitialised). You must call `setup()` before any data operations. + +#### `setup(...) -> Result<(), StoreError>` + +Initialise the store client and establish connections. + +Parameters: + +- `local_hostname`: IP/hostname for this node. +- `metadata_server`: metadata URI, for example: + - HTTP: `"http://127.0.0.1:8080/metadata"` + - etcd: `"etcd://127.0.0.1:2379"` +- `global_segment_size`: per-segment size in bytes. +- `local_buffer_size`: local staging buffer size in bytes. +- `protocol`: transport protocol string (for example `"tcp"` / `"rdma"`). +- `device_name`: device selector; empty string means auto-select (when supported by the backend). +- `master_server_addr`: `mooncake_master` address, e.g. `"127.0.0.1:50051"`. + +Returns `Ok(())` on success, otherwise `StoreError::OperationFailed(code)`. + +#### `health_check() -> Result<(), StoreError>` + +Connectivity health check. Returns `Ok(())` when the backend is reachable. + +#### `put(key, value, config) -> Result<(), StoreError>` + +Store `value` under `key`. This is a **copying** API: `value` is copied into store-managed buffers. + +- `config`: optional replication settings (`ReplicateConfig`). + +#### `get(key) -> Result, StoreError>` + +Retrieve the full value for `key` into a newly allocated `Vec`. + +Notes: + +- Internally calls `get_size()` to allocate an exact-sized buffer, then `get_into()` to fill it. +- A missing key or backend failure can surface as `OperationFailed(...)` because the C API does not provide a distinct NotFound code in all paths. + +#### `unsafe get_into(key, buffer, size) -> Result` + +Retrieve the value for `key` into a caller-provided buffer. + +- **Returns**: number of bytes written on success. +- **Safety**: `buffer` must point to at least `size` bytes of writable valid memory. + +#### `is_exist(key) -> Result` + +Existence check. + +- `Ok(true)` if exists, `Ok(false)` if missing. +- Any other return code becomes `StoreError::OperationFailed(code)`. + +#### `get_size(key) -> Result` + +Get the stored value size in bytes. + +Important limitation: + +- The underlying C API uses a single negative return code for multiple error conditions, so Rust surfaces errors as `OperationFailed(raw_code)` without distinguishing NotFound. + +#### `get_hostname() -> Result` + +Returns the hostname (and potentially port) that the store client is registered under. + +#### `remove(key, force) -> Result<(), StoreError>` + +Remove a key. + +- If `force = true`, the key is removed even if another client is reading it. + +#### `remove_by_regex(pattern, force) -> Result` + +Remove keys matching a regex pattern. Returns number of removed keys. + +#### `remove_all(force) -> Result` + +Remove **all** keys. Returns number of removed keys. + +### Zero-copy APIs (advanced) + +The Rust wrapper exposes zero-copy APIs that map directly to the underlying RDMA-capable C++ store. + +#### `unsafe register_buffer(buffer, size) -> Result<(), StoreError>` + +Register a memory region for zero-copy operations. + +- **Safety**: `buffer` must remain valid and pinned until `unregister_buffer()` is called. +- This is required before calling `put_from()` or other registered-memory operations. + +#### `unsafe unregister_buffer(buffer) -> Result<(), StoreError>` + +Deregister a previously registered buffer. + +#### `unsafe put_from(key, buffer, size, config) -> Result<(), StoreError>` + +Store from a registered buffer. + +- **Safety**: `buffer` must have been registered via `register_buffer()` and be at least `size` bytes. + +### Batch APIs + +Batch forms are useful when you want to amortize RPC overhead. + +#### `unsafe batch_put_from(keys, buffers, sizes, config) -> Result, StoreError>` + +Batch version of `put_from()`. + +- **Returns**: per-key result codes (0 = success, non-zero = error code for that key). +- **Safety**: each `buffers[i]` must be registered and valid for `sizes[i]` bytes. + +#### `unsafe batch_get_into(keys, buffers, sizes) -> Result, StoreError>` + +Batch version of `get_into()`. + +- **Returns**: per-key bytes written (≥ 0) or error code (< 0). +- **Safety**: each destination buffer must be writable and at least `sizes[i]` bytes. + +#### `batch_is_exist(keys) -> Result, StoreError>` + +Batch existence check. Errors are returned as `OperationFailed(code)`. + +### `ReplicateConfig` + +Replication settings for write operations (`put`, `put_from`, `batch_put_from`). + +Fields: + +- `replica_num`: number of replicas (0 means “use server default”). +- `with_soft_pin`: prefer retaining the object in memory (soft pin). +- `with_hard_pin`: never evict (hard pin). +- `preferred_segments`: whitelist of segment names that should host a replica. + +Example: + +```rust +use mooncake_store::{MooncakeStore, ReplicateConfig}; + +fn write_with_replication(store: &MooncakeStore) -> Result<(), mooncake_store::StoreError> { + let cfg = ReplicateConfig { + replica_num: 2, + with_soft_pin: true, + with_hard_pin: false, + preferred_segments: vec!["seg-a".to_string(), "seg-b".to_string()], + }; + + store.put("k", b"v", Some(&cfg))?; + Ok(()) +} +``` + +### `StoreError` + +Errors returned by the Rust wrapper. + +- `NullHandle`: store handle allocation failed. +- `InvalidString`: input string contained an interior `\0` (cannot form C string). +- `OperationFailed(i32)`: underlying C layer returned a non-zero / negative code. +- `NotFound`: convenience for consumers that implement a NotFound check externally. +- `InvalidArgument(String)`: wrapper-level argument validation failure (e.g. mismatched array lengths). + +## Safety & thread-safety + +- `MooncakeStore` is `Send + Sync` (the underlying C object is internally synchronised). +- Methods that accept raw pointers are marked `unsafe`: + - You must uphold Rust aliasing and lifetime rules for buffers passed to FFI. + - For zero-copy operations, buffers must be registered and remain valid until unregistered. + diff --git a/docs/source/api-reference/rust/transfer-engine.md b/docs/source/api-reference/rust/transfer-engine.md new file mode 100644 index 00000000..a7279115 --- /dev/null +++ b/docs/source/api-reference/rust/transfer-engine.md @@ -0,0 +1,207 @@ +# Transfer Engine Rust API + +This page documents the Rust bindings living under `mooncake-transfer-engine/rust`. + +At the time of writing, the crate (`transfer_engine_rust`) is primarily used as a **Rust-side binding + example binary**. The public Rust types are implemented in `src/transfer_engine.rs` and wrap the Transfer Engine C API (`transfer_engine_c.h`). + +For Transfer Engine design docs and non-Rust APIs, see: + +- Transfer Engine design docs: `design/transfer-engine/index` +- Transfer Engine C++ API: `design/transfer-engine/cpp-api` + +## Build & runtime prerequisites + +The Rust crate uses bindgen + CMake to link against the Transfer Engine C/C++ build outputs. + +- **Build**: + - Requires Rust toolchain and libclang (bindgen). + - The crate has `build.rs` that expects to find / build the native library via CMake. +- **Runtime**: + - Dynamic linker must find Transfer Engine shared libraries. + - You need a metadata server backend (commonly etcd) and a reachable peer segment registry. + +## Mental model + +The Transfer Engine operates on **segments** and **transfer batches**: + +- You create a `TransferEngine` bound to: + - `metadata_uri` (for example, etcd endpoint) + - `local_server_name` (this node's address/name) + - `rpc_port` (RPC listener port) +- You register local memory regions as RDMA-capable buffers. +- You open a remote segment to obtain a `segment_id` (an integer handle). +- You allocate a batch id for a fixed number of transfer requests. +- You submit a batch of `TransferRequest`. +- You poll status per task id inside the batch, then free the batch id. + +## API reference + +### Enums + +#### `OpcodeEnum` + +- `OpcodeEnum::Read` +- `OpcodeEnum::Write` + +Used by `TransferRequest.opcode`. + +#### `TransferStatusEnum` + +Status values returned by the C layer. Common values you will check for: + +- `Completed` +- `Failed` +- `Timeout` + +### Structs + +#### `TransferRequest` + +One transfer operation inside a batch. + +Fields: + +- `opcode: OpcodeEnum` +- `source: *mut c_void`: local source/destination pointer (depends on opcode). +- `target_id: i32`: segment id returned by `open_segment()`. +- `target_offset: u64`: byte offset inside the target segment. +- `length: u64`: transfer length in bytes. + +#### `BufferEntry` + +Used for batch memory registration: + +- `addr: *mut c_void` +- `length: u64` + +### `TransferEngine` + +#### `new(metadata_uri, local_server_name, rpc_port) -> anyhow::Result` + +Create a new engine handle. + +Notes: + +- `metadata_uri` and `local_server_name` are passed through `CString`; interior `\0` bytes will error. +- The wrapper currently disables `auto_discover` in the underlying C call. + +#### `discover_topology() -> anyhow::Result<()>` + +Trigger topology discovery. + +#### `install_transport(proto) -> anyhow::Result<()>` + +Install a transport by name (e.g. `"tcp"`, `"rdma"`, `"efa"` depending on build/runtime support). + +#### `register_local_memory(addr, length, location) -> anyhow::Result<()>` + +Register a local memory region for zero-copy transfers. + +- `addr`: pointer to the memory region. +- `length`: size in bytes. +- `location`: location string such as `"cpu:0"`. + +#### `unregister_local_memory(addr) -> anyhow::Result<()>` + +Unregister a previously registered memory region. + +#### `register_local_memory_batch(buffer_list, location) -> anyhow::Result<()>` + +Batch register multiple local buffers. + +- No-op when `buffer_list` is empty. + +#### `unregister_local_memory_batch(buffer_list) -> anyhow::Result<()>` + +Batch unregister multiple local buffers. + +#### `open_segment(name: String) -> anyhow::Result` + +Open a remote segment by name and get a segment id. + +#### `close_segment(segment_id: i32) -> anyhow::Result<()>` + +Close a previously opened segment. + +#### `warmup_efa_segment(name: &str) -> anyhow::Result<()>` + +Eagerly establish EFA endpoints so the first `submit_transfer()` does not pay the serial connection setup cost. + +- No-op on non-EFA transports. +- Call after `open_segment()` and after the metadata server has published the peer's NIC list. + +#### `sync_segment_cache() -> anyhow::Result<()>` + +Synchronize segment cache from metadata. + +#### `allocate_batch_id(batch_size) -> anyhow::Result` + +Allocate a batch id for `batch_size` transfer requests. + +You must call `free_batch_id(batch_id)` after all tasks are done. + +#### `submit_transfer(batch_id, requests) -> anyhow::Result<()>` + +Submit a batch transfer request list. + +- No-op when `requests` is empty. +- The wrapper converts each `TransferRequest` into the C representation (`transfer_request_t`). + +#### `get_transfer_status(batch_id, task_id) -> anyhow::Result<(i32, u64)>` + +Get status for one task in a batch. + +- `task_id` is an index inside the batch, typically `0..batch_size`. +- Returns `(status_code, transferred_bytes)`. + +The `status_code` maps to values in `TransferStatusEnum` (represented as `i32`). + +#### `free_batch_id(batch_id) -> anyhow::Result<()>` + +Free a previously allocated batch id. + +## Minimal usage example (pseudo-code) + +The crate's `src/main.rs` contains a full benchmark-style example. The following sketch shows the typical control flow: + +```rust +use std::ffi::c_void; +use transfer_engine_rust::transfer_engine::{OpcodeEnum, TransferEngine, TransferRequest}; + +fn main() -> anyhow::Result<()> { + let engine = TransferEngine::new("127.0.0.1:2379", "127.0.0.1", 12345)?; + engine.discover_topology()?; + engine.install_transport("tcp")?; + + // Register local memory (example only; you must allocate and pin memory appropriately). + let mut buffer = vec![0u8; 4096]; + engine.register_local_memory(buffer.as_mut_ptr() as *mut c_void, buffer.len(), "cpu:0")?; + + let seg_id = engine.open_segment("target-seg".to_string())?; + let batch_id = engine.allocate_batch_id(1)?; + + let mut reqs = [TransferRequest { + opcode: OpcodeEnum::Write, + source: buffer.as_mut_ptr() as *mut c_void, + target_id: seg_id, + target_offset: 0, + length: buffer.len() as u64, + }]; + + engine.submit_transfer(batch_id, &mut reqs)?; + let (status, bytes) = engine.get_transfer_status(batch_id, 0)?; + println!("status={status}, bytes={bytes}"); + + engine.free_batch_id(batch_id)?; + engine.close_segment(seg_id)?; + engine.unregister_local_memory(buffer.as_mut_ptr() as *mut c_void)?; + Ok(()) +} +``` + +## Safety & thread-safety + +- The wrapper marks `TransferEngine` as `Send + Sync`, but it owns an FFI handle (`transfer_engine_t`). +- All pointer-based arguments must satisfy Rust’s aliasing and lifetime rules. +- You must ensure registered memory remains valid until it is unregistered. + diff --git a/docs/source/conf.py b/docs/source/conf.py index 817b116d..a505aa4f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -41,35 +41,28 @@ extensions = [ "sphinx.ext.linkcode", "sphinx.ext.intersphinx", "sphinx_copybutton", - "autodoc2", "myst_parser", "sphinxarg.ext", "sphinx_design", "sphinx_togglebutton", + "sphinxcontrib.mermaid", ] myst_enable_extensions = [ "colon_fence", "fieldlist", ] -autodoc2_packages = [ - { - "path": "../../Mooncake", - "exclude_dirs": ["__pycache__", "third_party"], - }, -] -autodoc2_output_dir = "api" -autodoc2_render_plugin = "myst" -autodoc2_hidden_objects = ["dunder", "private", "inherited"] -autodoc2_sort_names = True -autodoc2_index_template = None - +myst_fence_as_directive = ["mermaid"] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns: list[str] = ["**/*.template.md", "**/*.inc.md"] +exclude_patterns: list[str] = [ + "**/*.template.md", + "**/*.inc.md", + "zh_archive/**", +] # Exclude the prompt "$" when copying code copybutton_prompt_text = r"\$ " diff --git a/docs/source/deployment/mooncake-store-deployment-guide.md b/docs/source/deployment/mooncake-store-deployment-guide.md index 46f44f32..93fe6492 100644 --- a/docs/source/deployment/mooncake-store-deployment-guide.md +++ b/docs/source/deployment/mooncake-store-deployment-guide.md @@ -1,81 +1,228 @@ -# Mooncake Store Deployment & Operations Guide +# Mooncake Store Deployment & Tuning Guide -This page summarizes useful flags, environment variables, and HTTP endpoints to help advanced users tune Mooncake Master and observe metrics. +This guide covers minimal deployment, and operational tuning of Mooncake Store. -## Master Startup Flags (with defaults) +## Architecture Overview -- RPC Related - - `--rpc_port` (int, default 50051): RPC listen port. - - `--rpc_thread_num` (int, default min(4, CPU cores)): RPC worker threads. If not set, uses `--max_threads` (default 4) capped by CPU cores. - - `--rpc_address` (str, default `0.0.0.0`): RPC bind address. - - `--rpc_interface` (str, default empty): Network interface used to resolve the final RPC address. When set, Mooncake Master resolves the interface's current IPv4 address at startup and uses it as the final `rpc_address`. This overrides `--rpc_address`. - - `--rpc_conn_timeout_seconds` (int, default `0`): RPC idle connection timeout; `0` disables. - - `--rpc_enable_tcp_no_delay` (bool, default `true`): Enable TCP_NODELAY. -- Metrics - - `--enable_metric_reporting` (bool, default `true`): Periodically log master metrics to INFO. - - `--metrics_port` (int, default `9003`): HTTP port for `/metrics` endpoints. +![architecture](../image/mooncake-store-preview.png) -- HTTP Metadata Server For Mooncake Transfer Engine - - `--enable_http_metadata_server` (bool, default `false`): Enable embedded HTTP metadata server. - - `--http_metadata_server_host` (str, default `0.0.0.0`): Metadata bind host. - - `--http_metadata_server_port` (int, default `8080`): Metadata TCP port. +**Master Service** (`mooncake_master`): The central coordinator. It manages cluster membership, allocates object storage across client nodes, and enforces eviction/placement policies. Runs as a standalone process. -- Allocation Strategy - - `--allocation_strategy` (str, default `random`): Memory allocation strategy for replica placement. Available options: - - `random`: Pure random selection across segments (baseline, fastest). - - `free_ratio_first`: Free-ratio-first strategy. Samples multiple candidates and selects those with highest free space ratio for better load balancing. +**Client Node**: Each node contributes DRAM (and optionally VRAM/SSD) to form the distributed cache pool. Clients communicate with the master over RPC for control operations (`Put`/`Get`/`Remove`), but transfer actual data directly between each other via the Transfer Engine — the master is never in the data path. -- Eviction and TTLs - - `--default_kv_lease_ttl` (duration, default `5000` ms): Default lease TTL for KV objects. The default unit is milliseconds, so `5000` means `5000ms`. Duration strings such as `5000ms`, `5s`, `30m`, or `1h` are also supported. - - `--default_kv_soft_pin_ttl` (duration, default `1800000` ms): Soft pin TTL (30 minutes). The default unit is milliseconds, so `1800000` means `1800000ms`. Duration strings such as `1800000ms`, `30m`, or `1h` are also supported. - - `--allow_evict_soft_pinned_objects` (bool, default `true`): Allow evicting soft-pinned objects. - - `--eviction_ratio` (double, default `0.05`): Fraction evicted when hitting high watermark. - - `--eviction_high_watermark_ratio` (double, default `0.95`): Usage ratio to trigger eviction. +**Metadata Service**: A separate service (etcd, Redis, or HTTP) used by the Transfer Engine for peer discovery and configuration. The master's embedded HTTP metadata server can replace an external etcd/Redis for simple deployments. We also provide a P2P handshake mechanism (`P2PHANDSHAKE`) that enables decentralized metadata management by storing metadata locally on each node, eliminating the need for a centralized service — this is the simplest metadata handshake method and the recommended starting point (see [Quick Start](#quick-start)). -- High Availability (optional) - - `--enable_ha` (bool, default `false`): Enable HA (requires etcd). - - `--etcd_endpoints` (str, default empty unless HA config): etcd endpoints, semicolon separated. - - `--client_ttl` (int64, default `10` s): Client alive TTL after last ping (HA mode). - - `--cluster_id` (str, default `mooncake_cluster`): Cluster ID for persistence in HA mode. +For a detailed design discussion, see the [Mooncake Store Design](../design/mooncake-store.md). -- Task Manager (optional) - - `--max_total_finished_tasks` (uint32, default `10000`): Maximum number of finished tasks to keep in memory. When this limit is reached, the oldest finished tasks will be pruned from memory. - - `--max_total_pending_tasks` (uint32, default `10000`): Maximum number of pending tasks that can be queued in memory. When this limit is reached, new task submissions will fail with `TASK_PENDING_LIMIT_EXCEEDED` error. - - `--max_total_processing_tasks` (uint32, default `10000`): Maximum number of tasks that can be processing simultaneously. When this limit is reached, no new tasks will be popped from the pending queue until some processing tasks complete. - - `--max_retry_attempts` (uint32, default `10`): Maximum number of retry attempts for failed tasks. Tasks that fail with `NO_AVAILABLE_HANDLE` error will be retried up to this many times before being marked as failed. +--- -- DFS Storage (optional) - - `--root_fs_dir` (str, default empty): DFS mount directory for storage backend, used in Multi-layer Storage Support. - - `--global_file_segment_size` (int64, default `int64_max`): Maximum available space for DFS segments. +## Quick Start -- Snapshot / Restore (optional) - - `--enable_snapshot` (bool, default `false`): Enable periodic snapshot of master metadata data (effective when using the `offset` memory allocator). - - `--snapshot_interval_seconds` (uint64, default `600`): Interval in seconds between periodic snapshots of master data. - - `--snapshot_child_timeout_seconds` (uint64, default `300`): Timeout in seconds for each snapshot child process. - - `--snapshot_retention_count` (uint32, default `2`): Number of recent snapshots to keep. Older snapshots beyond this limit will be automatically deleted. - - `--snapshot_backend_type` (str, required when snapshot enabled): Snapshot storage backend type: `local` for local filesystem, `s3` for S3 storage. - - `--snapshot_backup_dir` (str, default empty): Optional local directory for snapshot backup. If empty (default), local backup is disabled. When set, it serves two purposes: (1) during snapshot persistence, data will be saved locally as a fallback if uploading to the backend fails; (2) during restore, downloaded metadata will also be saved to this directory as a local backup. - - `--enable_snapshot_restore` (bool, default `false`): Enable restore from the latest snapshot at master startup. - - **Environment variable** `MOONCAKE_SNAPSHOT_LOCAL_PATH` (**required** when `--snapshot_backend_type=local`): Persistent directory path for local snapshot storage. This variable **must** be set before starting the master; there is no default value. Example: `export MOONCAKE_SNAPSHOT_LOCAL_PATH=/data/mooncake_snapshots`. +Deploy a minimal single-node Mooncake Store in three steps. - > **Warning: Managed Directory** - > - > The snapshot storage path (`MOONCAKE_SNAPSHOT_LOCAL_PATH` for local backend, or S3 bucket for S3 backend) is a **managed directory** exclusively controlled by the Mooncake snapshot system. **DO NOT store other files or data in this directory.** Old snapshots exceeding `--snapshot_retention_count` will be automatically and permanently deleted during cleanup. Use a dedicated, isolated directory for snapshot storage to avoid accidental data loss. +### 1. Start the Metadata Service -Example (enable embedded HTTP metadata and metrics): +This quick start uses **P2P handshake** — the simplest option, with **nothing to start**: each node exchanges and stores Transfer Engine metadata locally during connection setup. You just pass the literal string `P2PHANDSHAKE` as the client's `metadata_server` (step 3). + +For large or long-lived clusters, use the master's embedded HTTP metadata server or an external etcd/Redis instead — see [Deployment Scenarios](#deployment-scenarios). + +### 2. Start the Master Service + +With P2P handshake the master needs no metadata-server flags: + +```bash +mooncake_master +``` + +On success the master logs a single line like: + +``` +Master service started on port 50051, max_threads=4, ... +``` + +The master's default RPC port is `50051`. (To embed an HTTP metadata server instead of using P2P, add `--enable_http_metadata_server=true --http_metadata_server_port=8080`.) + +(start-a-store-client)= +### 3. Start a Store Client + +A client contributes DRAM (and optionally SSD) to the cluster. The simplest way is to embed Mooncake in a Python process and call `store.setup(...)` with `metadata_server="P2PHANDSHAKE"`: + +```python +from mooncake.store import MooncakeDistributedStore + +store = MooncakeDistributedStore() +store.setup( + local_hostname="localhost", + metadata_server="P2PHANDSHAKE", # decentralized; no metadata service + global_segment_size=3200 * 1024 * 1024, # DRAM contributed to the cluster + local_buffer_size=512 * 1024 * 1024, # Transfer Engine buffer + protocol="tcp", + rdma_devices="", # keyword is rdma_devices (not device_name) + master_server_addr="127.0.0.1:50051", # keyword is master_server_addr +) +``` + +There are **three ways** to run a client — programmatic (above), a standalone `mooncake_store_service` process (configured via `MOONCAKE_*`), and the `mooncake_client` real-client RPC process. See [Reference: Client Configuration & Tuning](#reference-client-configuration-tuning) for all three, with full parameter/env tables. + +**What just happened:** + +1. The client registered itself with the master via RPC. +2. The master allocated a 3.2 GB segment on this node and added it to the cluster's memory pool. +3. The client is now ready to serve `Put`/`Get`/`Remove` requests. + +### Run the Stress Benchmark + +Mooncake Store includes sample programs for validating C++ and Python integrations. The [stress benchmark script](gh-file:mooncake-store/tests/stress_cluster_benchmark.py) can be used to verify a two-role prefill/decode setup. + +Configure the script with command-line flags (run with `--help` for the full list): + +- `--local-hostname`: the local machine's reachable IP address or hostname. +- `--metadata-server`: the Transfer Engine metadata service, e.g. `P2PHANDSHAKE`, `http://127.0.0.1:8080/metadata`, or an etcd address. +- `--master-server`: the Mooncake Store master address. Use `IP:Port` in default mode, or `etcd://IP:Port;IP:Port;...;IP:Port` in etcd-backed HA mode. +- `--protocol`: transport, `tcp` / `rdma` / `cxl` / `ascend` (defaults to `rdma`). + +Then start the roles: + +```bash +python3 mooncake-store/tests/stress_cluster_benchmark.py --role prefill +python3 mooncake-store/tests/stress_cluster_benchmark.py --role decode +``` + +For RDMA, topology auto-discovery and NIC filters can be passed through environment variables: + +```bash +MC_MS_AUTO_DISC=1 MC_MS_FILTERS="mlx5_1,mlx5_2" python3 mooncake-store/tests/stress_cluster_benchmark.py --role prefill +MC_MS_AUTO_DISC=1 MC_MS_FILTERS="mlx5_1,mlx5_2" python3 mooncake-store/tests/stress_cluster_benchmark.py --role decode +``` + +The absence of errors indicates successful data transfer. + +### Verify Installed Examples + +For a Python integration check, run `mooncake-store/tests/distributed_object_store_provider.py` after starting the metadata service and `mooncake_master`. + +For a C++ integration check, run `build/mooncake-store/tests/client_integration_test` after building tests and starting the required services. + +### Verify + +```bash +# Health check — master metrics endpoint +curl -s http://localhost:9003/metrics/summary + +# List registered clients +# (exposed through the store's Python API or RPC) +``` + +--- + +## Deployment Scenarios + +### Single-Node (TCP) — Development / Quick Evaluation + +The simplest deployment, as shown in [Quick Start](#quick-start). A single `mooncake_master` orchestrates clients over TCP. Suitable for development, testing, and single-host evaluation. ```bash mooncake_master \ --enable_http_metadata_server=true \ --http_metadata_server_host=0.0.0.0 \ - --http_metadata_server_port=8080 \ - --rpc_thread_num=64 \ - --metrics_port=9003 \ - --enable_metric_reporting=true + --http_metadata_server_port=8080 ``` -Example (resolve the master RPC address from a stable interface name in a container): +Limitation: the master is a single point of failure. If it crashes, cluster operations pause until it is restored. + +--- + +### High-Availability (etcd) — Production HA + +Runs a cluster of master instances coordinated through etcd. If the leader fails, the remaining instances elect a new leader automatically. + +```bash +# Start each master instance with: +mooncake_master \ + --enable_ha=true \ + --etcd_endpoints="10.0.0.1:2379;10.0.0.2:2379;10.0.0.3:2379" \ + --rpc_address=10.0.0.1 +``` + +Each instance must specify its own reachable `--rpc_address`. The etcd cluster used for HA can be shared with or separate from the Transfer Engine's metadata etcd. + +**Client addressing:** to reach an HA cluster, clients must use the `etcd://` master-address form (so they can discover the current leader) instead of a single `IP:Port` — set `master_server_addr` (Method A) / `MOONCAKE_MASTER` (Method B) / `--master_server_address` (Method C) to `etcd://10.0.0.1:2379;10.0.0.2:2379;...`. + +--- + +### High-Availability (Redis) — Alternative HA Backend + +Same HA semantics but using Redis instead of etcd for leader election: + +```bash +mooncake_master \ + --enable_ha=true \ + --ha_backend_type=redis \ + --ha_backend_connstring="redis://127.0.0.1:6379" \ + --rpc_address=10.0.0.1 +``` + +**Client addressing:** clients reach a Redis-backed HA cluster with the `redis://connstring` master-address form (e.g. `redis://127.0.0.1:6379`) for `master_server_addr` / `MOONCAKE_MASTER` / `--master_server_address`, instead of a single `IP:Port`. + + +--- + +### Snapshot & Restore — Backup / Disaster Recovery + +```{caution} +Metadata Snapshot And Restore is experimental feature. +``` + +Periodically persist master metadata to local disk or S3, enabling recovery from a recent snapshot after a crash. + +```bash +export MOONCAKE_SNAPSHOT_LOCAL_PATH=/data/mooncake_snapshots + +mooncake_master \ + --enable_snapshot=true \ + --snapshot_interval_seconds=300 \ + --snapshot_retention_count=5 \ + --snapshot_object_store_type=local \ + --enable_snapshot_restore=true +``` + +--- + +### Tiered Storage with SSD Offload — Cost-Effective Capacity + +Extends the cache pool from DRAM to SSD while keeping normal reads and writes on the distributed memory path. With `--enable_offload=true`, completed memory writes are queued for asynchronous SSD persistence through the master control plane. Set `--offload_on_evict=true` to defer that SSD write until the memory eviction path selects an object for reclamation. When `--promotion_on_hit=true`, SSD-only objects can be promoted back to DRAM after repeated reads; admission is gated by `--promotion_admission_threshold`. + +```bash +mooncake_master \ + --enable_offload=true \ + --offload_on_evict=true \ + --promotion_on_hit=true \ + --promotion_admission_threshold=2 \ + --root_fs_dir=/mnt/ssd_cache \ + --enable_http_metadata_server=true \ + --http_metadata_server_port=8080 +``` + +--- + +### CXL-Aware Allocation — Memory Tiering + +When the host has CXL-attached memory, the master can preferentially allocate new objects on the CXL tier, reserving local DRAM for latency-sensitive operations. + +```bash +mooncake_master \ + --enable_cxl=true \ + --cxl_path=/dev/dax0.0 \ + --cxl_size=17179869184 \ + --allocation_strategy=cxl +``` + +--- + +### Container / Dynamic Network Interface + +When the master runs in a container with a dynamic IP, use `--rpc_interface` to resolve the RPC address from a stable interface name: ```bash mooncake_master \ @@ -85,86 +232,558 @@ mooncake_master \ --http_metadata_server_port=8080 ``` -This resolves the current IPv4 address of `eth0` at startup and uses it as the final `rpc_address`. +The master resolves the current IPv4 address of `eth0` at startup and uses it as the advertised RPC address. -Example (use free-ratio-first allocation strategy for better load balancing): -```bash -mooncake_master \ - --allocation_strategy=free_ratio_first \ - --enable_http_metadata_server=true \ - --http_metadata_server_port=8080 -``` - -**Tips:** - -In addition to command-line flags, the Master also supports configuration via JSON and YAML files. For example: - -```bash -mooncake_master \ - --config_path=mooncake-store/conf/master.yaml -``` - -For config files, the equivalent setting is: - -```yaml -rpc_interface: "eth0" -rpc_port: 50051 -``` +--- ## Metrics Endpoints -The master exposes Prometheus-style metrics over HTTP on `--metrics_port`: - -- `GET /metrics` — Prometheus format (`text/plain; version=0.0.4`). -- `GET /metrics/summary` — Human-readable summary. - -Examples: +The master exposes Prometheus-style metrics on `--metrics_port`: ```bash +# Prometheus format curl -s http://:9003/metrics + +# Human-readable summary curl -s http://:9003/metrics/summary ``` -## Client/Engine Tuning (Env Vars, with defaults) +When tenant quota is enabled, `/metrics` also includes per-tenant quota gauges and quota counters: -- Topology discovery (Store Client → Transfer Engine) - - `MC_MS_AUTO_DISC` (default `1`): Auto-discover NIC/GPU topology. Set `0` to disable and provide `rdma_devices` manually. - - `MC_MS_FILTERS` (default empty): Optional comma-separated NIC whitelist when auto-discovery is enabled (e.g., `mlx5_0,mlx5_2`). - - If `MC_MS_AUTO_DISC=0`, pass `rdma_devices` (comma-separated) to the Python `setup(...)` call. +- `mooncake_tenant_quota_requested_bytes{tenant_id}` +- `mooncake_tenant_quota_effective_bytes{tenant_id}` +- `mooncake_tenant_quota_used_bytes{tenant_id}` +- `mooncake_tenant_quota_reserved_bytes{tenant_id}` +- `mooncake_tenant_quota_committed_count{tenant_id}` +- `mooncake_tenant_quota_metadata_object_count{tenant_id}` +- `mooncake_tenant_quota_over_quota{tenant_id}` +- `mooncake_tenant_quota_explicit_policy{tenant_id}` +- `mooncake_tenant_quota_reject_total{tenant_id,reason}` +- `mooncake_tenant_evict_bytes_total{tenant_id}` +- `mooncake_tenant_quota_allocatable_capacity_bytes` +- `mooncake_tenant_quota_requested_bytes_sum` +- `mooncake_tenant_quota_effective_bytes_sum` -- Transfer Engine metrics (disabled by default) - - `MC_TE_METRIC` (default `0`/unset): Set to `1` to enable periodic engine metrics logging. **Note:** Not supported when using Transfer Engine TENT. - - `MC_TE_METRIC_INTERVAL_SECONDS` (default `5`): Positive integer seconds between reports (effective only if metrics enabled). +--- -- Client metrics (enabled by default) - - `MC_STORE_CLIENT_METRIC` (default `1`): Client-side metrics on by default; set `0` to disable entirely. - - `MC_STORE_CLIENT_METRIC_INTERVAL` (default `0`): Reporting interval in seconds; `0` collects but does not periodically report. +## Tenant Quota Management -- Local memcpy optimization (Store transfer path) - - `MC_STORE_MEMCPY` (default `0`/false): Set to `1` to prefer local memcpy when source/destination are on the same client. +Tenant quota admission is disabled by default. Enable strict multi-tenant mode on the master when you want memory writes admitted against connector-managed per-tenant quota: -## Set the Log Level for yalantinglibs coro_rpc and coro_http -By default, the log level is set to warning. You can customize it using the following environment variable: +```bash +mooncake_master \ + --enable_multi_tenants=true \ + --tenant_quota_connector_type=file \ + --tenant_quota_connector_uri=/etc/mooncake/tenant_quotas.yaml +``` -`export MC_YLT_LOG_LEVEL=info` +The v1 connector is a writable YAML file. The file must use schema version `1`; tenant names must be non-empty, unique, must not start with `_`, and must not contain NUL or control characters; quotas must be positive integers with optional `B`, `KB`, `MB`, `GB`, or `TB` units: -This sets the log level for yalantinglibs (including coro_rpc and coro_http) to info. +```yaml +version: 1 -Available log levels: trace, debug, info, warn (or warning), error, and critical. +tenants: + - name: tenant-a + quota: 200GB + + - name: tenant-b + quota: 500GB +``` + +When strict multi-tenant mode is enabled, write requests must include a registered tenant. The `default` tenant is not special unless it is explicitly registered in the connector policy. + +The same HTTP port used for metrics exposes the tenant quota admin API: + +```bash +# List tenant quota snapshots +curl -s http://:9003/api/v1/tenant_quotas + +# Query one tenant +curl -s "http://:9003/api/v1/tenant_quotas?tenant_id=tenant-a" + +# Upsert an explicit policy. Explicit tenant policies must be positive. +curl -s -X PUT "http://:9003/api/v1/tenant_quotas?tenant_id=tenant-a" \ + -H 'Content-Type: application/json' \ + -d '{"requested_quota_bytes":2147483648}' + +# Delete an explicit policy. The tenant must not own objects or quota usage. +curl -s -X DELETE "http://:9003/api/v1/tenant_quotas?tenant_id=tenant-a" +``` + +Each tenant quota snapshot returns: + +```json +{ + "success": true, + "data": { + "tenant_id": "tenant-a", + "requested_quota_bytes": 2147483648, + "effective_quota_bytes": 2147483648, + "used_bytes": 0, + "reserved_bytes": 0, + "committed_count": 0, + "metadata_object_count": 0, + "over_quota": false, + "has_explicit_policy": true + } +} +``` + +In HA mode, quota admin requests are served only by the active master service. Standby, candidate, or inactive services return HTTP 503. If strict multi-tenant mode is disabled, the quota admin API returns HTTP 409 with `UNAVAILABLE_IN_CURRENT_MODE`. Deleting a non-empty tenant returns HTTP 409 with `TENANT_NOT_EMPTY`. + +--- ## Quick Tips - Scale `--rpc_thread_num` with available CPU cores and workload. - Start with default eviction settings; adjust `--eviction_high_watermark_ratio` and `--eviction_ratio` based on memory pressure and object churn. - Use `/metrics/summary` during bring-up; integrate `/metrics` with Prometheus/Grafana for production. +- For detailed SSD offload configuration (storage backends, eviction policies, io_uring), see the [SSD Offload guide](ssd-offload). +- For NVMe-oF SSD pool configuration see the [NVMe-oF SSD Pool Deployment Guide](nvmf-ssd-deployment-guide) +- For experimental 3FS (USRBIO) integration as a persistent storage backend, see the [3FS USRBIO Plugin guide](../getting_started/plugin-usage/3FS-USRBIO-Plugin). +- For detailed monitoring and observation see [Observability](../getting_started/observability) +:::{toctree} +:maxdepth: 1 +:hidden: + +ssd-offload +NvMe-Of SSD Pool +HF3FS Plugin (Experimental)<../getting_started/plugin-usage/3FS-USRBIO-Plugin> +../getting_started/observability +::: --- -:::{toctree} -:caption: Advanced Topics -:maxdepth: 1 +## Reference: Master Startup Flags -ssd-offload -::: +### RPC + +| Flag | Default | Description | +|------|---------|-------------| +| `--rpc_port` | `0` → effective `50051` | RPC listen port. The literal default is `0`, which falls back to the deprecated `--port` (default `50051`) | +| `--rpc_thread_num` | `0` → effective `min(4, CPU cores)` | RPC worker threads. The literal default is `0`, which falls back to the deprecated `--max_threads` → `min(4, CPU cores)` | +| `--rpc_address` | `0.0.0.0` | RPC bind address | +| `--rpc_interface` | empty | Network interface to resolve RPC address at startup (overrides `--rpc_address`) | +| `--rpc_conn_timeout_seconds` | `0` | Idle connection timeout; `0` disables | +| `--rpc_enable_tcp_no_delay` | `true` | Enable TCP_NODELAY | + +### Logging + +The master uses glog. When `--log_dir` is set, all severities are merged into a single journal file in that directory (`mooncake_master.INFO.-