chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:24:33 +08:00
commit f213ec8976
2101 changed files with 494002 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
# LMCache CODEOWNERS
# Core engine
/lmcache/v1/cache_engine.py @ApostaC @YaoJiayi @sammshen
/lmcache/v1/cache_controller/ @maobaolong @YaoJiayi @chunxiaozheng
/lmcache/v1/offload_server/ @maobaolong @YaoJiayi
# Memory management
/lmcache/v1/memory_management.py @sammshen @deng451e @chunxiaozheng @DongDongJu @ApostaC
# Multiprocess
/lmcache/v1/multiprocess/ @ApostaC @OasisGit @hlin99
/lmcache/v1/multiprocess/protocols/observability.py @royyhuang @ApostaC
/lmcache/v1/multiprocess/http_server.py @royyhuang @ApostaC @OasisGit
/lmcache/v1/mp_observability/ @yoo-kumaneko @royyhuang @ApostaC @OasisGit @sammshen
# Distributed / L2
/lmcache/v1/distributed/ @ApostaC @maobaolong @chunxiaozheng
/lmcache/v1/distributed/eviction.py @YaoJiayi @hlin99
/lmcache/v1/distributed/eviction_policy/ @YaoJiayi @hlin99
/lmcache/v1/distributed/storage_controllers/eviction_controller.py @YaoJiayi @hlin99
/lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py @maobaolong
/lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py @sammshen
# GPU connector
/lmcache/v1/gpu_connector/ @sammshen @ApostaC @hlin99
# Platform
/lmcache/v1/platform/ @maobaolong @hlin99
# Lookup client
/lmcache/v1/lookup_client/ @maobaolong @YaoJiayi @sammshen
/lmcache/v1/lookup_client/mooncake_lookup_client.py @maobaolong
# Storage backends
/lmcache/v1/storage_backend/ @maobaolong @sammshen @chunxiaozheng @DongDongJu
/lmcache/v1/storage_backend/connector/base_connector.py @sammshen @maobaolong @chunxiaozheng
/lmcache/v1/storage_backend/connector/instrumented_connector.py @maobaolong @chunxiaozheng
/lmcache/v1/storage_backend/connector/s3_connector.py @sammshen @YaoJiayi
/lmcache/v1/storage_backend/connector/s3_adapter.py @sammshen @YaoJiayi
/lmcache/v1/storage_backend/connector/mooncakestore_connector.py @maobaolong @chunxiaozheng
/lmcache/v1/storage_backend/connector/mooncakestore_adapter.py @maobaolong @chunxiaozheng
/lmcache/v1/storage_backend/connector/fs_connector.py @maobaolong @chunxiaozheng
/lmcache/v1/storage_backend/connector/fs_adapter.py @maobaolong @chunxiaozheng
/lmcache/v1/storage_backend/connector/audit_connector.py @maobaolong @chunxiaozheng
/lmcache/v1/storage_backend/connector/audit_adapter.py @maobaolong @chunxiaozheng
/lmcache/v1/storage_backend/connector/redis_connector.py @sammshen @hlin99
/lmcache/v1/storage_backend/connector/redis_adapter.py @sammshen @hlin99
/lmcache/v1/storage_backend/connector/lm_connector.py @sammshen @hlin99
/lmcache/v1/storage_backend/connector/lm_adapter.py @sammshen @hlin99
# Integration
/lmcache/integration/vllm/ @sammshen @maobaolong @YaoJiayi @deng451e @hlin99
/lmcache/integration/sglang/ @OasisGit @DongDongJu @sammshen @Shaoting-Feng
/lmcache/integration/tensorrt_llm/ @sammshen
# Non-CUDA equivalents
/lmcache/non_cuda_equivalents.py @hlin99 @hickeyma
# C extensions
/csrc/ @ApostaC @YaoJiayi @sammshen
/csrc/storage_backends/ @sammshen @hlin99
# Native connector L2 adapters
/lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py @sammshen @hlin99
/lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py @sammshen @hlin99
/lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py @sammshen @hlin99
# Offline analysis tools
/lmcache/tools/ @yoo-kumaneko
# Tests
/tests/ @hickeyma @sammshen @ApostaC @deng451e
# CLI
/lmcache/cli/ @KuntaiDu @deng451e @royyhuang @ApostaC @sammshen
# Documentation
/docs/source/mp/ @ApostaC @YaoJiayi @KuntaiDu @royyhuang
/docs/source/kv_cache/ @sammshen @ApostaC
/docs/source/kv_cache_optimizations/ @ApostaC @YaoJiayi
/docs/source/getting_started/ @sammshen @deng451e
/docs/source/developer_guide/ @sammshen @deng451e
/docs/source/production/ @maobaolong @chunxiaozheng
/docs/source/production/kv_cache_events.rst @hickeyma
/docs/ @sammshen @ApostaC @deng451e
# Examples
/examples/ @sammshen @deng451e
# CI / Infrastructure
/.github/ @hickeyma @sammshen @ApostaC @deng451e
/.buildkite/ @sammshen @ApostaC @deng451e @hickeyma
# Operator
/operator/ @royyhuang
# Rust
/rust/ @DongDongJu
# CXL / Maru / DAX
/lmcache/v1/storage_backend/maru_backend.py @DongDongJu
/lmcache/v1/storage_backend/plugins/dax_backend.py @DongDongJu
/docs/source/kv_cache/storage_backends/maru.rst @DongDongJu
/docs/source/kv_cache/storage_backends/dax.rst @DongDongJu
# Packaging
pyproject.toml @sammshen @ApostaC @deng451e @hickeyma
setup.py @sammshen @ApostaC @deng451e @hickeyma
+30
View File
@@ -0,0 +1,30 @@
---
name: Blank issue
about: Create a new issue from scratch
title: ''
labels: ''
assignees: ''
---
**Label**
Please label your issue so that it can easily be easily categorized under [LMCache Onboarding](https://github.com/LMCache/LMCache/issues/1882)
**Summary**
A concise overview of the issue you want to raise.
**Details**
Provide all relevant details here — context, reasoning, and any background information.
**Steps / Reproduction (if applicable)**
If this relates to a reproducible issue or process, list the steps here:
1. Step one
2. Step two
3. Step three
**Expected Outcome / Goal**
Describe the desired resolution or end state.
**Actual Outcome (if applicable)**
Describe the current behavior or state, if different from whats expected.
**Additional Context**
Include any other context, references, or screenshots that would be useful.
+40
View File
@@ -0,0 +1,40 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Label**
Please label your issue with "bug" and any other relevant labels so that it can easily be easily categorized under [LMCache Onboarding](https://github.com/LMCache/LMCache/issues/1882)
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
+22
View File
@@ -0,0 +1,22 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Label**
Please label your issue with "new feature" and any other relevant labels so that it can easily be easily categorized under [LMCache Onboarding](https://github.com/LMCache/LMCache/issues/1882)
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+12
View File
@@ -0,0 +1,12 @@
<!-- Thanks for your contribution to LMCache! Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/LMCache/LMCache/blob/dev/CONTRIBUTING.md
2. If this PR closes another issue, add 'Fixes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'Refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->
**What this PR does / why we need it**:
**Special notes for your reviewers**:
**If applicable**:
- [ ] this PR contains user facing changes - docs added
- [ ] this PR contains unit tests
@@ -0,0 +1,29 @@
name: 'Free Disk Space'
description: 'Frees disk space on the runner safely'
runs:
using: "composite"
steps:
- name: Print disk space before cleanup
run: df -h
shell: bash
- name: Free Disk Space Linux
if: runner.os == 'Linux'
run: |
# 1. Remove massive toolsets first (fastest gain)
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /usr/local/share/powershell /usr/share/swift /usr/local/.ghcup /usr/lib/jvm /usr/local/lib/node_modules || true
# 2. Clean Docker images
sudo docker image prune -af >/dev/null 2>&1 || true
# 3. Clean APT (Skip aptitude, use standard apt-get)
# We wrap this in a block to ensure the script continues even if apt is locked
sudo apt-get update >/dev/null 2>&1 || true
sudo apt-get purge -y mysql-client mysql-server google-cloud-sdk azure-cli >/dev/null 2>&1 || true
sudo apt-get autoremove -y >/dev/null 2>&1 || true
sudo apt-get clean >/dev/null 2>&1 || true
shell: bash
- name: Print disk space after cleanup
run: df -h
shell: bash
+25
View File
@@ -0,0 +1,25 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
labels: ["dependencies"]
open-pull-requests-limit: 5
reviewers: ["ApostaC", "YaoJiayi", "hickeyma"]
allow:
- dependency-type: "all"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-patch"]
- dependency-name: "torch"
- dependency-name: "torchvision"
- dependency-name: "xformers"
groups:
minor-update:
applies-to: version-updates
update-types: ["minor"]
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
#
# CPU device test: runs both server bench and vLLM e2e tests
# in the same environment to avoid repeated vLLM installation.
#
# Usage: cpu_device_test.sh [mode]
# mode: server_bench, vllm_e2e, or all (default)
#
# Environment variables:
# LMCACHE_BENCH_TRANSFER_MODE engine_driven|lmcache_driven
# (default: engine_driven)
# LMCACHE_E2E_TRANSPORT_MODE engine_driven|lmcache_driven|shm|pickle
# (default: engine_driven)
# LMCACHE_E2E_DATA_MODE shm|pickle (default: shm)
# LMCACHE_HTTP_PORT_BENCH HTTP port for bench (default: 18080)
# LMCACHE_ZMQ_PORT_BENCH ZMQ port for bench (default: 15555)
# LMCACHE_HTTP_PORT_E2E HTTP port for e2e (default: 18081)
# LMCACHE_ZMQ_PORT_E2E ZMQ port for e2e (default: 15557)
# VLLM_PORT_E2E HTTP port for vLLM (default: 18000)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OS="$(uname -s)"
TEST_MODE="${1:-all}"
echo "==> CPU device test (OS: ${OS}, Mode: ${TEST_MODE})"
echo " Python: $(python3 --version 2>&1 || true)"
# Configuration
BENCH_TRANSFER_MODE="${LMCACHE_BENCH_TRANSFER_MODE:-engine_driven}"
E2E_TRANSPORT_MODE="${LMCACHE_E2E_TRANSPORT_MODE:-engine_driven}"
E2E_DATA_MODE="${LMCACHE_E2E_DATA_MODE:-shm}"
HTTP_PORT_BENCH="${LMCACHE_HTTP_PORT_BENCH:-18080}"
ZMQ_PORT_BENCH="${LMCACHE_ZMQ_PORT_BENCH:-15555}"
HTTP_PORT_E2E="${LMCACHE_HTTP_PORT_E2E:-18081}"
ZMQ_PORT_E2E="${LMCACHE_ZMQ_PORT_E2E:-15557}"
VLLM_PORT_E2E="${VLLM_PORT_E2E:-18000}"
# Validate modes
case "${BENCH_TRANSFER_MODE}" in
lmcache_driven|engine_driven) ;;
*)
echo "!! Unknown LMCACHE_BENCH_TRANSFER_MODE='${BENCH_TRANSFER_MODE}'"
exit 1
;;
esac
# Map user-facing LMCACHE_E2E_TRANSPORT_MODE to internal representation.
# shm/pickle are aliases for lmcache_driven mode with sub-mode selection.
case "${E2E_TRANSPORT_MODE}" in
lmcache_driven|engine_driven)
MAPPED_TRANSPORT_MODE="${E2E_TRANSPORT_MODE}"
MAPPED_DATA_MODE="${E2E_DATA_MODE}"
;;
shm)
MAPPED_TRANSPORT_MODE="engine_driven"
MAPPED_DATA_MODE="shm"
;;
pickle)
MAPPED_TRANSPORT_MODE="engine_driven"
MAPPED_DATA_MODE="pickle"
;;
*)
echo "!! Unknown LMCACHE_E2E_TRANSPORT_MODE='${E2E_TRANSPORT_MODE}'"
echo " Valid values: engine_driven, lmcache_driven, shm, pickle"
exit 1
;;
esac
echo " Bench transfer mode: ${BENCH_TRANSFER_MODE}"
echo " E2E transport mode: ${E2E_TRANSPORT_MODE}"
echo " Ports: bench=${HTTP_PORT_BENCH}/${ZMQ_PORT_BENCH}, e2e=${HTTP_PORT_E2E}/${ZMQ_PORT_E2E}/${VLLM_PORT_E2E}"
# Reap any LMCache/vLLM children started by this run on exit so the
# next workflow step does not collide on default ZMQ/HTTP ports.
cleanup_processes_safe() {
local rc=$?
set +e
# Kill child processes started by this shell first (e.g. lmcache server
# backgrounded by the shared validation script).
pkill -P $$ 2>/dev/null || true
sleep 1
pkill -9 -P $$ 2>/dev/null || true
return $rc
}
trap cleanup_processes_safe EXIT
# Function to run server bench test
run_server_bench() {
echo ""
echo "==> Running CPU server bench test"
# Set environment for bench test
export LMCACHE_BENCH_TRANSFER_MODE="${BENCH_TRANSFER_MODE}"
export LMCACHE_HTTP_PORT="${HTTP_PORT_BENCH}"
export LMCACHE_ZMQ_PORT="${ZMQ_PORT_BENCH}"
export LMCACHE_LOG_FILE="/tmp/cpu_device_bench_${BENCH_TRANSFER_MODE}_lmcache.log"
export BENCH_OUTPUT_LOG="/tmp/cpu_device_bench_${BENCH_TRANSFER_MODE}_output.log"
export LMCACHE_HEALTHCHECK_TIMEOUT="30"
export BENCH_NUM_REQUESTS="3"
export BENCH_NUM_TOKENS="512"
# Run bench test
bash "${SCRIPT_DIR}/cpu_server_bench_test.sh"
echo "==> CPU server bench test completed successfully"
}
# Function to run vLLM e2e test
run_vllm_e2e() {
echo ""
echo "==> Running CPU vLLM e2e test"
# Set environment for e2e test
export LMCACHE_TRANSPORT_MODE="${MAPPED_TRANSPORT_MODE}"
export LMCACHE_DATA_MODE="${MAPPED_DATA_MODE}"
export LMCACHE_HTTP_PORT="${HTTP_PORT_E2E}"
export LMCACHE_ZMQ_PORT="${ZMQ_PORT_E2E}"
export VLLM_PORT="${VLLM_PORT_E2E}"
export LMCACHE_LOG_FILE="/tmp/cpu_device_e2e_${E2E_TRANSPORT_MODE}_lmcache.log"
export VLLM_LOG_FILE="/tmp/cpu_device_e2e_${E2E_TRANSPORT_MODE}_vllm.log"
export LMCACHE_HEALTHCHECK_TIMEOUT="30"
export VLLM_READY_TIMEOUT="300"
# Run e2e test
bash "${SCRIPT_DIR}/cpu_vllm_e2e_test.sh"
echo "==> CPU vLLM e2e test completed successfully"
}
# Determine which tests to run
case "${TEST_MODE}" in
"server_bench")
run_server_bench
;;
"vllm_e2e")
run_vllm_e2e
;;
"all")
run_server_bench
run_vllm_e2e
;;
*)
echo "!! Unknown test mode: ${TEST_MODE}"
echo " Supported modes: server_bench, vllm_e2e, all"
exit 1
;;
esac
echo ""
echo "==> CPU device test passed for modes:"
echo " Test mode: ${TEST_MODE}"
echo " Server bench: ${BENCH_TRANSFER_MODE}"
echo " vLLM e2e: ${E2E_TRANSPORT_MODE}"
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
#
# CPU server bench test: starts lmcache server, runs
# ``lmcache bench server --mode cpu`` with the requested transfer mode,
# then tears down.
#
# Transfer modes (LMCACHE_BENCH_TRANSFER_MODE):
# lmcache_driven - worker-side gather/scatter via POSIX SHM pool
# engine_driven - server-side copy via POSIX SHM IPC (shm_open/mmap)
#
# Environment variables (all optional, defaults shown):
# LMCACHE_BENCH_TRANSFER_MODE lmcache_driven|engine_driven
# (default: engine_driven)
# LMCACHE_HTTP_PORT HTTP port (default: 18080)
# LMCACHE_ZMQ_PORT ZMQ RPC port (default: 15555)
# LMCACHE_LOG_FILE server log path (default: /tmp/...)
# LMCACHE_HEALTHCHECK_TIMEOUT seconds (default: 60)
# BENCH_NUM_REQUESTS requests to run (default: 3)
# BENCH_NUM_TOKENS tokens per request (default: 512)
set -euo pipefail
OS="$(uname -s)"
echo "==> CPU server bench test (OS: ${OS})"
echo " Python: $(python3 --version 2>&1 || true)"
TRANSFER_MODE="${LMCACHE_BENCH_TRANSFER_MODE:-engine_driven}"
HTTP_PORT="${LMCACHE_HTTP_PORT:-18080}"
ZMQ_PORT="${LMCACHE_ZMQ_PORT:-15555}"
LOG_FILE="${LMCACHE_LOG_FILE:-/tmp/cpu_server_bench_lmcache.log}"
HEALTHCHECK_TIMEOUT="${LMCACHE_HEALTHCHECK_TIMEOUT:-60}"
BENCH_NUM_REQUESTS="${BENCH_NUM_REQUESTS:-3}"
BENCH_NUM_TOKENS="${BENCH_NUM_TOKENS:-512}"
case "${TRANSFER_MODE}" in
lmcache_driven|engine_driven) ;;
*)
echo "!! Unknown LMCACHE_BENCH_TRANSFER_MODE='${TRANSFER_MODE}'"
echo " Valid values: lmcache_driven, engine_driven"
exit 1
;;
esac
echo " TRANSFER_MODE=${TRANSFER_MODE}"
echo " HTTP_PORT=${HTTP_PORT} ZMQ_PORT=${ZMQ_PORT}"
echo " BENCH_NUM_REQUESTS=${BENCH_NUM_REQUESTS}"
echo " BENCH_NUM_TOKENS=${BENCH_NUM_TOKENS}"
# ------------------------------------------------------------------ #
# Start lmcache server
# ------------------------------------------------------------------ #
echo ""
echo "==> Starting lmcache server (log: ${LOG_FILE})"
rm -f "${LOG_FILE}"
lmcache server \
--port "${ZMQ_PORT}" \
--http-port "${HTTP_PORT}" \
--l1-size-gb 1 \
--eviction-policy LRU \
>"${LOG_FILE}" 2>&1 &
SERVER_PID=$!
cleanup() {
echo "==> Cleanup: stopping lmcache server (pid=${SERVER_PID})"
if kill -0 "${SERVER_PID}" 2>/dev/null; then
kill "${SERVER_PID}" 2>/dev/null || true
for _ in $(seq 1 10); do
kill -0 "${SERVER_PID}" 2>/dev/null || break
sleep 1
done
kill -9 "${SERVER_PID}" 2>/dev/null || true
fi
echo "==> Last 50 lines of server log:"
tail -n 50 "${LOG_FILE}" 2>/dev/null || true
}
trap cleanup EXIT
# ------------------------------------------------------------------ #
# Wait for healthcheck
# ------------------------------------------------------------------ #
echo "==> Waiting for healthcheck (timeout: ${HEALTHCHECK_TIMEOUT}s)"
READY=0
for i in $(seq 1 "${HEALTHCHECK_TIMEOUT}"); do
if ! kill -0 "${SERVER_PID}" 2>/dev/null; then
echo "!! lmcache server exited prematurely after ${i}s"
break
fi
if curl -fsS --max-time 2 \
"http://127.0.0.1:${HTTP_PORT}/healthcheck" >/dev/null 2>&1; then
READY=1
echo " Server healthy after ${i}s"
break
fi
sleep 1
done
if [ "${READY}" != "1" ]; then
echo "!! lmcache server did not become healthy within ${HEALTHCHECK_TIMEOUT}s"
exit 1
fi
# ------------------------------------------------------------------ #
# Run bench and validate results
# ------------------------------------------------------------------ #
echo ""
echo "==> Running: lmcache bench server" \
"--mode cpu --transfer-mode ${TRANSFER_MODE}" \
"--num-tokens ${BENCH_NUM_TOKENS}" \
"--end ${BENCH_NUM_REQUESTS}"
BENCH_LOG="${BENCH_OUTPUT_LOG:-/tmp/cpu_server_bench_output.log}"
lmcache bench server \
--rpc-url "tcp://127.0.0.1:${ZMQ_PORT}" \
--url "http://127.0.0.1:${HTTP_PORT}" \
--mode cpu \
--transfer-mode "${TRANSFER_MODE}" \
--num-tokens "${BENCH_NUM_TOKENS}" \
--end "${BENCH_NUM_REQUESTS}" \
2>&1 | tee "${BENCH_LOG}"
echo ""
echo "==> Validating bench results"
if grep -q "CHECKSUM MISMATCH" "${BENCH_LOG}"; then
echo "!! CHECKSUM MISMATCH detected — store/retrieve data corruption"
exit 1
fi
MATCH_COUNT="$(grep -c "CHECKSUM MATCH OK" "${BENCH_LOG}" || true)"
if [ "${MATCH_COUNT}" -lt "${BENCH_NUM_REQUESTS}" ]; then
echo "!! CHECKSUM MATCH count (${MATCH_COUNT}) < expected (${BENCH_NUM_REQUESTS})"
exit 1
fi
echo " CHECKSUM MATCH: ${MATCH_COUNT}/${BENCH_NUM_REQUESTS} request(s) verified OK"
echo ""
echo "==> CPU server bench (${TRANSFER_MODE}) passed."
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
#
# Generic CPU vLLM e2e wrapper for both macOS and Ubuntu.
# Assumes vLLM CPU build and facebook/opt-125m are already installed/
# downloaded by the CI workflow steps before this script is invoked.
#
# Delegates all logic to the shared run-cpu-e2e-validation.sh with:
# SKIP_INSTALL=1 (install done by CI workflow steps)
#
# Transport mode is selected via LMCACHE_TRANSPORT_MODE:
# engine_driven -> LMCACHE_MP_TRANSFER_MODE=engine_driven (server-side copy)
# lmcache_driven -> LMCACHE_DATA_MODE selects shm (default) or pickle
#
# Environment variables (all optional, defaults shown):
# LMCACHE_TRANSPORT_MODE Transport: engine_driven|lmcache_driven
# (default: engine_driven)
# LMCACHE_DATA_MODE Data transfer mode: shm|pickle (default: shm)
# LMCACHE_HTTP_PORT HTTP port for LMCache server (default: 8080)
# VLLM_PORT HTTP port for vLLM server (default: 8000)
# LMCACHE_L1_SIZE_GB LMCache L1 cache size in GB (default: 2)
# VLLM_READY_TIMEOUT Seconds to wait for vLLM (default: 300)
# LMCACHE_HEALTHCHECK_TIMEOUT Seconds to wait for LMCache (default: 60)
set -euo pipefail
OS="$(uname -s)"
echo "==> CPU vLLM e2e test (OS: ${OS})"
echo " Python: $(python3 --version 2>&1 || true)"
echo " uname: $(uname -a)"
if [ "${OS}" = "Darwin" ]; then
sw_vers 2>/dev/null || true
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
SHARED_SCRIPT="${REPO_ROOT}/.github/scripts/run-cpu-e2e-validation.sh"
if [ ! -f "${SHARED_SCRIPT}" ]; then
echo "!! Shared script not found: ${SHARED_SCRIPT}"
exit 1
fi
LMCACHE_TRANSPORT_MODE="${LMCACHE_TRANSPORT_MODE:-engine_driven}"
# When LMCACHE_TRANSPORT_MODE=engine_driven, LMCACHE_DATA_MODE selects the
# specific data transfer mechanism: shm (default) or pickle.
LMCACHE_DATA_MODE="${LMCACHE_DATA_MODE:-shm}"
# Map LMCACHE_TRANSPORT_MODE to the vars expected by the shared script.
case "${LMCACHE_TRANSPORT_MODE}" in
engine_driven)
case "${LMCACHE_DATA_MODE}" in
shm)
export LMCACHE_SHM_NAME="__default__"
;;
pickle)
export LMCACHE_SHM_NAME=""
;;
esac
export LMCACHE_MP_TRANSFER_MODE="engine_driven"
;;
lmcache_driven)
export LMCACHE_MP_TRANSFER_MODE="lmcache_driven"
;;
*)
echo "!! Unknown LMCACHE_TRANSPORT_MODE='${LMCACHE_TRANSPORT_MODE}'"
echo " Valid values: engine_driven, lmcache_driven"
exit 1
;;
esac
export SKIP_INSTALL="${SKIP_INSTALL:-1}"
export LMCACHE_HEALTHCHECK_TIMEOUT="${LMCACHE_HEALTHCHECK_TIMEOUT:-60}"
export VLLM_READY_TIMEOUT="${VLLM_READY_TIMEOUT:-300}"
export LMCACHE_LOG_FILE="${LMCACHE_LOG_FILE:-/tmp/cpu_e2e_lmcache.log}"
export VLLM_LOG_FILE="${VLLM_LOG_FILE:-/tmp/cpu_e2e_vllm.log}"
echo " LMCACHE_TRANSPORT_MODE=${LMCACHE_TRANSPORT_MODE}"
echo " LMCACHE_DATA_MODE=${LMCACHE_DATA_MODE}"
echo " SKIP_INSTALL=${SKIP_INSTALL}"
echo " Delegating to: ${SHARED_SCRIPT}"
exec bash "${SHARED_SCRIPT}"
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
#
# Download (or just verify the local cache of) a HuggingFace model
# repo, with bounded retry + exponential backoff so flaky HF mirrors
# don't fail the whole CI run.
#
# Usage:
# download_model.sh <repo_id> [<repo_id> ...]
# MODEL_ID=facebook/opt-125m download_model.sh
#
# Environment:
# MODEL_ID fallback when no positional args given
# HF_TOKEN optional HuggingFace token for authenticated
# requests (avoids anonymous rate limits)
# HF_DOWNLOAD_MAX_RETRIES default 5
# HF_DOWNLOAD_RETRY_DELAY default 30 (seconds, doubled per retry)
# HF_DOWNLOAD_FAIL_ON_ERROR default 0 (1 -> exit non-zero on failure)
set -euo pipefail
if [ "$#" -eq 0 ]; then
if [ -z "${MODEL_ID:-}" ]; then
echo "!! download_model.sh: no model id provided"
echo " pass repo ids as args or set MODEL_ID=..."
exit 2
fi
set -- "${MODEL_ID}"
fi
MAX_RETRIES="${HF_DOWNLOAD_MAX_RETRIES:-5}"
RETRY_DELAY="${HF_DOWNLOAD_RETRY_DELAY:-30}"
FAIL_ON_ERROR="${HF_DOWNLOAD_FAIL_ON_ERROR:-0}"
MAX_RETRIES="${MAX_RETRIES}" RETRY_DELAY="${RETRY_DELAY}" \
FAIL_ON_ERROR="${FAIL_ON_ERROR}" HF_TOKEN="${HF_TOKEN:-}" \
python3 - "$@" <<'PY'
import os
import random
import sys
import time
from huggingface_hub import snapshot_download
max_retries = int(os.environ["MAX_RETRIES"])
base_delay = int(os.environ["RETRY_DELAY"])
fail_on_error = os.environ["FAIL_ON_ERROR"] == "1"
hf_token = os.environ.get("HF_TOKEN") or None
repos = sys.argv[1:]
failures = []
for repo in repos:
# Try local cache first to avoid unnecessary HF API calls
# (which can 429 on busy CI runners even when the model is cached).
try:
snapshot_download(repo, local_files_only=True, token=hf_token)
print(f"CACHED: {repo} (local, no network)")
continue
except Exception:
pass
delay = base_delay
ok = False
for attempt in range(max_retries):
try:
print(f"Attempt {attempt + 1}/{max_retries}: {repo}")
snapshot_download(repo, token=hf_token)
print(f"OK: {repo}")
ok = True
break
except Exception as exc:
print(f"Attempt {attempt + 1} failed for {repo}: {exc}")
if attempt < max_retries - 1:
# Add ±25% jitter to avoid thundering herd
jitter = delay * (0.75 + random.random() * 0.5)
print(
"Waiting %.1fs before retry..." % jitter)
time.sleep(jitter)
delay *= 2
if not ok:
failures.append(repo)
if failures:
print("All retry attempts failed for: %s" % ", ".join(failures))
sys.exit(1 if fail_on_error else 0)
PY
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
#
# Download facebook/opt-125m from the LMCache/opt-125m GitHub release
# and place it into the HuggingFace hub cache structure so vLLM can
# find it via ``facebook/opt-125m`` without any network call to HF.
#
# Environment:
# OPT125M_GH_RELEASE release tag (default: v1.0)
# OPT125M_SNAPSHOT HF snapshot hash (default: 27dcfa...)
set -euo pipefail
GH_RELEASE="${OPT125M_GH_RELEASE:-v1.0}"
SNAPSHOT="${OPT125M_SNAPSHOT:-27dcfa74d334bc871f3234de431e71c6eeba5dd6}"
HF_CACHE="${HF_HOME:-$HOME/.cache/huggingface}/hub"
MODEL_DIR="${HF_CACHE}/models--facebook--opt-125m"
SNAPSHOT_DIR="${MODEL_DIR}/snapshots/${SNAPSHOT}"
if [ -f "${SNAPSHOT_DIR}/pytorch_model.bin" ]; then
echo "opt-125m already cached in ${SNAPSHOT_DIR}, skipping download"
exit 0
fi
echo "Downloading opt-125m from GitHub release ${GH_RELEASE}..."
mkdir -p "${SNAPSHOT_DIR}"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TMP_DIR}"' EXIT
TARBALL="${TMP_DIR}/opt-125m.tar.gz"
DOWNLOAD_URL="https://github.com/LMCache/opt-125m/releases/download/${GH_RELEASE}/opt-125m.tar.gz"
for i in $(seq 1 5); do
if curl -fsSL --retry 3 -o "${TARBALL}" "${DOWNLOAD_URL}"; then
break
fi
if [ "${i}" -eq 5 ]; then
echo "!! Failed to download after 5 attempts: ${DOWNLOAD_URL}"
exit 1
fi
sleep $((10 * i))
done
# Extract directly the tarball contains opt-125m/ at top level.
EXTRACT_DIR="${TMP_DIR}/extract"
mkdir -p "${EXTRACT_DIR}"
tar -xzf "${TARBALL}" -C "${EXTRACT_DIR}"
mv "${EXTRACT_DIR}"/opt-125m/* "${SNAPSHOT_DIR}/"
# Create the refs/main pointer so snapshot_download(local_files_only=True)
# can resolve the snapshot hash.
mkdir -p "${MODEL_DIR}/refs"
echo "${SNAPSHOT}" > "${MODEL_DIR}/refs/main"
echo "opt-125m cached at ${SNAPSHOT_DIR}"
ls -lah "${SNAPSHOT_DIR}/"
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
#
# Install lmcache in editable, CPU-only mode (NO_GPU_EXT=1).
# Assumes vLLM (and therefore torch) was already installed by
# install_vllm_cpu.sh, so we use --no-deps to keep that pinned torch
# in place.
#
# Usage:
# install_lmcache_cpu.sh # plain pip
# PIP_BIN="uv pip" install_lmcache_cpu.sh
set -euo pipefail
PIP_BIN="${PIP_BIN:-pip}"
export NO_GPU_EXT=1
export SETUPTOOLS_SCM_PRETEND_VERSION="${SETUPTOOLS_SCM_PRETEND_VERSION:-0.0.0.dev0}"
${PIP_BIN} install --upgrade pip
${PIP_BIN} install -r requirements/build.txt
${PIP_BIN} install -r requirements/common.txt
${PIP_BIN} install -r requirements/cli.txt
${PIP_BIN} install -e . --no-deps --no-build-isolation
python -c "import lmcache, vllm; \
print('lmcache:', lmcache.__version__, 'vllm:', vllm.__version__)"
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
#
# Install the prebuilt CPU-only vLLM wheel (`vllm-cpu-nightly`) plus a
# `vllm-<ver>+cpu.dist-info` alias.
#
# Why the alias: the wheel installs the `vllm/` package but registers
# its dist metadata under `vllm-cpu-nightly`. vLLM's CLI / internal
# callers do `importlib.metadata.version("vllm")` (distribution name,
# not import name); without the alias that raises PackageNotFoundError
# and `vllm serve` won't start. The `+cpu` local label is also needed
# so `cpu_platform_plugin()` activates the CPU platform (it greps the
# dist metadata for the substring "cpu"); our build strips `+cpu`
# before PyPI upload because PyPI rejects local versions.
#
# Usage:
# install_vllm_cpu.sh # use plain `pip`
# PIP_BIN="uv pip" install_vllm_cpu.sh
# # use `uv pip` and pass extra flags
# # via PIP_INSTALL_EXTRA_ARGS
#
# Idempotent: re-running just rewrites the alias.
set -euo pipefail
PIP_BIN="${PIP_BIN:-pip}"
PIP_INSTALL_EXTRA_ARGS="${PIP_INSTALL_EXTRA_ARGS:-}"
# `--extra-index-url` is required because the wheel pins torch==2.11.0
# which only lives on the pytorch CPU index. Harmless on macOS.
${PIP_BIN} install "numpy<2"
# shellcheck disable=SC2086
${PIP_BIN} install vllm-cpu-nightly \
--extra-index-url https://download.pytorch.org/whl/cpu \
${PIP_INSTALL_EXTRA_ARGS}
python - <<'PY'
import importlib.metadata as md
import pathlib
import shutil
dist = md.distribution("vllm-cpu-nightly")
ver = dist.version
fake_ver = f"{ver}+cpu"
site_root = pathlib.Path(dist.locate_file(""))
info_name = next(
p.parts[0] for p in (dist.files or [])
if p.parts and p.parts[0].endswith(".dist-info")
)
src = site_root / info_name
dst = src.with_name(f"vllm-{fake_ver}.dist-info")
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(src, dst)
meta = dst / "METADATA"
txt = meta.read_text()
txt = txt.replace("Name: vllm-cpu-nightly", "Name: vllm", 1)
txt = txt.replace(f"Version: {ver}", f"Version: {fake_ver}", 1)
meta.write_text(txt)
print(f"Aliased {src.name} -> {dst.name}")
print("vllm version (via importlib.metadata):", md.version("vllm"))
PY
python -c "import vllm, torch; \
print('vllm:', vllm.__version__, 'torch:', torch.__version__, \
'cuda:', torch.cuda.is_available())"
+648
View File
@@ -0,0 +1,648 @@
#!/usr/bin/env bash
set -euo pipefail
echo "Build ID: ${BUILDKITE_BUILD_ID:-local}"
echo "Python: $(python3 --version 2>&1 || true)"
if command -v uv >/dev/null 2>&1; then
echo "uv: $(uv --version 2>&1 || true)"
else
echo "uv: not installed"
fi
BUILD_ID="${BUILDKITE_BUILD_ID:-local_$$}"
VENV_DIR=".venv-${BUILD_ID}"
SHARED_SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LMCACHE_LOG="${LMCACHE_LOG_FILE:-/tmp/build_${BUILD_ID}_lmcache_cpu_validation.log}"
VLLM_LOG="${VLLM_LOG_FILE:-/tmp/build_${BUILD_ID}_vllm_cpu_validation.log}"
LMCACHE_PID=""
VLLM_PID=""
LMCACHE_HTTP_PORT="${LMCACHE_HTTP_PORT:-8080}"
LMCACHE_ZMQ_PORT="${LMCACHE_ZMQ_PORT:-5555}"
VLLM_PORT="${VLLM_PORT:-8000}"
LMCACHE_L1_SIZE_GB="${LMCACHE_L1_SIZE_GB:-1}"
LMCACHE_EVICTION_POLICY="${LMCACHE_EVICTION_POLICY:-LRU}"
LMCACHE_CHUNK_SIZE="${LMCACHE_CHUNK_SIZE:-128}"
VLLM_GPU_MEMORY_UTILIZATION="${VLLM_GPU_MEMORY_UTILIZATION:-0.1}"
# CPU-only KV cache pool size in GiB. vLLM defaults to 4 GiB when unset;
# 1 GiB is plenty for opt-125m e2e validation.
VLLM_CPU_KVCACHE_SPACE="${VLLM_CPU_KVCACHE_SPACE:-1}"
# Cap context length and concurrent sequences to shrink scheduler buffers.
VLLM_MAX_MODEL_LEN="${VLLM_MAX_MODEL_LEN:-2048}"
VLLM_MAX_NUM_SEQS="${VLLM_MAX_NUM_SEQS:-4}"
LMCACHE_HEALTHCHECK_TIMEOUT="${LMCACHE_HEALTHCHECK_TIMEOUT:-30}"
VLLM_READY_TIMEOUT="${VLLM_READY_TIMEOUT:-120}"
# Transport mode selection:
# LMCACHE_MP_TRANSFER_MODE=engine_driven -> engine-driven data path,
# sub-selected by LMCACHE_SHM_NAME:
# LMCACHE_SHM_NAME="" -> pickle transport
# LMCACHE_SHM_NAME=__default__ -> shm transport (default)
# LMCACHE_MP_TRANSFER_MODE=lmcache_driven -> lmcache-driven IPC handle path
LMCACHE_SHM_NAME="${LMCACHE_SHM_NAME-__default__}"
LMCACHE_MP_TRANSFER_MODE="${LMCACHE_MP_TRANSFER_MODE:-engine_driven}"
# Set SKIP_INSTALL=1 to skip Phase 1 (install) — useful when the caller
# has already installed everything (e.g. macOS CI workflow steps).
SKIP_INSTALL="${SKIP_INSTALL:-0}"
# Directory to collect artifacts before workspace is deleted
ARTIFACT_DIR="/tmp/build_${BUILD_ID}_artifacts"
mkdir -p "${ARTIFACT_DIR}"
upload_artifacts() {
# Copy logs to artifact dir (which survives workspace deletion)
cp -f "${LMCACHE_LOG}" "${ARTIFACT_DIR}/lmcache_cpu_validation.log" 2>/dev/null || true
cp -f "${VLLM_LOG}" "${ARTIFACT_DIR}/vllm_cpu_validation.log" 2>/dev/null || true
if [ -n "${BUILDKITE_BUILD_ID:-}" ] && command -v buildkite-agent >/dev/null 2>&1; then
buildkite-agent artifact upload "${ARTIFACT_DIR}/*.log" || true
fi
}
cleanup_workspace() {
if [ -n "${BUILDKITE_BUILD_ID:-}" ]; then
export TARGET="$PWD"
case "$TARGET" in
""|"/"|"/usr"|"/var"|"/etc"|"/bin"|"/sbin"|"/opt"|"/home"|"/tmp")
echo "❌ Refusing to delete unsafe workspace path: ${TARGET:-<empty>}"
return 1
;;
esac
if [ "$TARGET" = "$HOME" ]; then
echo "❌ Refusing to delete unsafe workspace path: ${TARGET:-<empty>}"
return 1
fi
if [ ! -d "$TARGET/.git" ] || [ ! -f "$TARGET/pyproject.toml" ]; then
echo "❌ Refusing to delete unexpected workspace path: $TARGET"
return 1
fi
echo "Deleting current workspace $TARGET"
cd /
if command -v sudo >/dev/null 2>&1; then
sudo rm -rf "$TARGET"
else
rm -rf "$TARGET"
fi
fi
}
print_failure_logs() {
echo "=== LMCache Server Log (${LMCACHE_LOG}) ==="
if [ -f "${LMCACHE_LOG}" ]; then
tail -n 200 "${LMCACHE_LOG}" || true
else
echo "Log not found"
fi
echo "=== vLLM Log (${VLLM_LOG}) ==="
if [ -f "${VLLM_LOG}" ]; then
tail -n 200 "${VLLM_LOG}" || true
else
echo "Log not found"
fi
}
cleanup_processes() {
set +e
if [ -n "${VLLM_PID}" ] && kill -0 "${VLLM_PID}" 2>/dev/null; then
echo "Stopping vLLM (PID=${VLLM_PID})"
kill "${VLLM_PID}" 2>/dev/null || true
wait "${VLLM_PID}" 2>/dev/null || true
fi
if [ -n "${LMCACHE_PID}" ] && kill -0 "${LMCACHE_PID}" 2>/dev/null; then
echo "Stopping LMCache server (PID=${LMCACHE_PID})"
kill "${LMCACHE_PID}" 2>/dev/null || true
wait "${LMCACHE_PID}" 2>/dev/null || true
fi
set -e
}
wait_for_endpoint_contains() {
local url="$1"
local timeout="$2"
local expected="$3"
local label="$4"
local response
for _ in $(seq 1 "${timeout}"); do
if response="$(curl -fsS "${url}" 2>/dev/null)"; then
if [ -z "${expected}" ] || echo "${response}" | grep -q "${expected}"; then
return 0
fi
fi
sleep 1
done
echo "${label} did not become ready within ${timeout}s"
return 1
}
# Scrape a Prometheus counter value from LMCache /metrics endpoint
scrape_metric() {
local metric_name="$1"
python3 - <<EOF
import sys, urllib.request
url = "http://localhost:${LMCACHE_HTTP_PORT}/metrics"
try:
body = urllib.request.urlopen(url, timeout=10).read().decode()
except Exception as e:
print(f"ERROR fetching {url}: {e}", file=sys.stderr)
print("0")
sys.exit(0)
total = 0.0
for line in body.splitlines():
if line.startswith("#"):
continue
if not line.startswith("${metric_name}"):
continue
parts = line.rsplit(" ", 1)
if len(parts) != 2:
continue
try:
total += float(parts[1])
except ValueError:
continue
print(int(total))
EOF
}
# Poll a Prometheus counter until its value differs from `baseline`,
# or until `timeout` seconds elapse. Returns 0 on change, 1 on timeout
# (callers typically `|| true` it because not every probe expects a
# change to actually happen).
wait_for_metric_change() {
local metric_name="$1"
local baseline="$2"
local timeout="${3:-10}"
local current
for _ in $(seq 1 "${timeout}"); do
current="$(scrape_metric "${metric_name}")"
if [ "${current}" != "${baseline}" ]; then
return 0
fi
sleep 1
done
return 1
}
# Send a completion request and print the text output
send_completion() {
local prompt_file="$1"
local max_tokens="${2:-50}"
local body_file
body_file="$(mktemp)"
# Build the JSON body in a separate process to avoid nested-quote
# quoting nightmares with -d "$(python3 -c "...")". Pass the prompt
# file path and max_tokens via argv so the python snippet itself
# does not need any shell interpolation inside its string body.
PROMPT_FILE="${prompt_file}" MAX_TOKENS="${max_tokens}" \
python3 - >"${body_file}" <<'PYEOF'
import json
import os
prompt = open(os.environ['PROMPT_FILE']).read()
print(json.dumps({
'model': 'facebook/opt-125m',
'prompt': prompt,
'max_tokens': int(os.environ['MAX_TOKENS']),
'temperature': 0,
}))
PYEOF
local response
response="$(curl -fsS "http://localhost:${VLLM_PORT}/v1/completions" \
-H "Content-Type: application/json" \
--data-binary "@${body_file}")"
rm -f "${body_file}"
echo "${response}" | python3 -c "import json,sys; print(json.load(sys.stdin)['choices'][0]['text'])"
}
start_vllm() {
echo "Starting vLLM server..."
# Export so multiproc_executor worker children inherit these. Without
# VLLM_CPU_KVCACHE_SPACE, CPU backend falls back to
# `total_memory * gpu_memory_utilization`, which can request 100s of GiB
# on big hosts and OOM (see vllm/v1/worker/cpu_worker.py:determine_available_memory).
# VLLM_DEVICE is the modern env var (vLLM 0.8+); VLLM_TARGET_DEVICE is
# kept for backwards-compatibility with older vLLM CPU wheels.
export VLLM_DEVICE=cpu
export VLLM_TARGET_DEVICE=cpu
export VLLM_CPU_KVCACHE_SPACE="${VLLM_CPU_KVCACHE_SPACE}"
export LMCACHE_MP_TRANSFER_MODE="${LMCACHE_MP_TRANSFER_MODE}"
# Pin gloo / vLLM rendezvous to loopback. Otherwise vLLM's
# network_utils.get_ip() picks a LAN address (e.g. 192.168.x.x on the
# macOS GHA runner) and gloo's init_process_group sits there for ~16
# minutes doing slow socket bind/connect retries before the engine
# ever loads weights.
export VLLM_HOST_IP="${VLLM_HOST_IP:-127.0.0.1}"
if [ -z "${GLOO_SOCKET_IFNAME:-}" ]; then
case "$(uname -s)" in
Darwin) export GLOO_SOCKET_IFNAME=lo0 ;;
Linux) export GLOO_SOCKET_IFNAME=lo ;;
esac
fi
local kv_cache_bytes
kv_cache_bytes="$(python3 -c "print(int(${VLLM_CPU_KVCACHE_SPACE} * 1024 * 1024 * 1024))")"
# Tell LMCacheMPConnector where the lmcache server actually listens.
# Without this it falls back to tcp://localhost:5555 and dies with
# "Cannot reach the LMCache MP server" whenever we run multiple e2e
# steps in parallel/sequence on different ZMQ ports.
local kv_transfer_config
kv_transfer_config="$(python3 -c "
import json
print(json.dumps({
'kv_connector': 'LMCacheMPConnector',
'kv_role': 'kv_both',
'kv_connector_module_path': 'lmcache.integration.vllm.lmcache_mp_connector',
'kv_connector_extra_config': {
'lmcache.mp.host': 'tcp://localhost',
'lmcache.mp.port': int('${LMCACHE_ZMQ_PORT}'),
},
}))")"
vllm serve facebook/opt-125m \
--port "${VLLM_PORT}" \
--dtype bfloat16 \
--disable-hybrid-kv-cache-manager \
--no-enable-prefix-caching \
--gpu-memory-utilization "${VLLM_GPU_MEMORY_UTILIZATION}" \
--kv-cache-memory-bytes "${kv_cache_bytes}" \
--max-model-len "${VLLM_MAX_MODEL_LEN}" \
--max-num-seqs "${VLLM_MAX_NUM_SEQS}" \
--kv-transfer-config "${kv_transfer_config}" \
--enforce-eager \
>"${VLLM_LOG}" 2>&1 &
VLLM_PID=$!
echo "vLLM server started (PID=${VLLM_PID})"
sleep 1
if ! kill -0 "${VLLM_PID}" 2>/dev/null; then
echo "❌ vLLM server exited immediately after startup. See ${VLLM_LOG} for details"
return 1
fi
echo "Waiting for vLLM readiness at http://localhost:${VLLM_PORT}/v1/models (timeout: ${VLLM_READY_TIMEOUT}s)"
if ! wait_for_endpoint_contains "http://localhost:${VLLM_PORT}/v1/models" "${VLLM_READY_TIMEOUT}" "facebook/opt-125m" "vLLM server"; then
return 1
fi
echo "✅ vLLM server is ready"
}
stop_vllm() {
if [ -n "${VLLM_PID}" ] && kill -0 "${VLLM_PID}" 2>/dev/null; then
echo "Stopping vLLM (PID=${VLLM_PID})"
kill "${VLLM_PID}" 2>/dev/null || true
wait "${VLLM_PID}" 2>/dev/null || true
VLLM_PID=""
fi
}
on_error() {
local exit_code=$?
trap - ERR
echo "❌ CPU install validation failed (exit code: ${exit_code})"
set +e
print_failure_logs
cleanup_processes
upload_artifacts
cleanup_workspace || echo "❌ Workspace cleanup failed"
set -e
exit "$exit_code"
}
trap on_error ERR
if [ "${SKIP_INSTALL}" = "1" ]; then
echo "=== CPU Install Validation (Phase 1) — SKIPPED (SKIP_INSTALL=1) ==="
else
echo "=== CPU Install Validation (Phase 1) ==="
echo "Creating virtual environment with uv at ${VENV_DIR}"
uv venv --python 3.12 "${VENV_DIR}"
# shellcheck disable=SC1091
source "${VENV_DIR}/bin/activate"
uv pip install --upgrade pip setuptools wheel
# `--index-strategy unsafe-best-match` is required because uv's
# default `first-index` strategy locks each package to the first
# index that lists it; the pytorch CPU index ships an older mirror
# of `setuptools` that would block the version vllm-cpu-nightly
# pins.
uv pip uninstall -y vllm vllm-cpu-nightly 2>/dev/null || true
PIP_BIN="uv pip" \
PIP_INSTALL_EXTRA_ARGS="--index-strategy unsafe-best-match" \
bash "${SHARED_SCRIPTS_DIR}/install_vllm_cpu.sh"
PIP_BIN="uv pip" \
bash "${SHARED_SCRIPTS_DIR}/install_lmcache_cpu.sh"
echo "Freezing installed package versions"
uv pip freeze
echo "✅ CPU install validation passed"
fi
echo "=== CPU E2E Validation (Phase 2) ==="
if [ "${SKIP_INSTALL}" = "1" ]; then
echo "[Phase 2 / Step 1] numpy<2 install — SKIPPED (SKIP_INSTALL=1)"
else
echo "[Phase 2 / Step 1] Installing numpy<2 for scipy/vLLM compatibility"
uv pip install "numpy<2"
echo "✅ numpy<2 installed"
fi
echo "[Phase 2 / Step 2] Downloading facebook/opt-125m model (GitHub release)"
bash "${SHARED_SCRIPTS_DIR}/download_opt125m_github.sh"
echo "✅ Model download/check complete"
echo "[Phase 2 / Step 3] Starting LMCache server"
echo "LMCache log: ${LMCACHE_LOG}"
# Build lmcache server args
LMCACHE_ARGS=(
--port "${LMCACHE_ZMQ_PORT}"
--http-port "${LMCACHE_HTTP_PORT}"
--l1-size-gb "${LMCACHE_L1_SIZE_GB}"
--eviction-policy "${LMCACHE_EVICTION_POLICY}"
--chunk-size "${LMCACHE_CHUNK_SIZE}"
)
# `engine_driven` / `lmcache_driven` resolve to different worker-side
# TransferContexts:
# engine_driven -> EngineDrivenTransferContext (worker gathers/scatters data)
# sub-mode: SHM (--shm-name __default__) or pickle (--shm-name "")
# lmcache_driven -> LMCacheDrivenTransferContext (server-side IPC handle)
# Step 5.5 verifies which one the worker actually entered.
if [ "${LMCACHE_MP_TRANSFER_MODE}" = "engine_driven" ]; then
if [ "${LMCACHE_SHM_NAME}" = "__default__" ]; then
echo "Transport mode: engine-driven/shm (shared memory)"
EXPECTED_TRANSPORT="shm"
else
echo "Transport mode: engine-driven/pickle (--shm-name '${LMCACHE_SHM_NAME}')"
LMCACHE_ARGS+=(--shm-name "${LMCACHE_SHM_NAME}")
EXPECTED_TRANSPORT="pickle"
fi
elif [ "${LMCACHE_MP_TRANSFER_MODE}" = "lmcache_driven" ]; then
echo "Transport mode: lmcache-driven (IPC handle path)"
EXPECTED_TRANSPORT="lmcache_driven"
else
echo "Transport mode: unknown '${LMCACHE_MP_TRANSFER_MODE}',"
echo " falling back to LMCACHE_SHM_NAME-based detection"
if [ "${LMCACHE_SHM_NAME}" = "__default__" ]; then
echo "Transport mode: data/shm (shared memory, fallback)"
EXPECTED_TRANSPORT="shm"
else
echo "Transport mode: data/pickle (--shm-name '${LMCACHE_SHM_NAME}')"
LMCACHE_ARGS+=(--shm-name "${LMCACHE_SHM_NAME}")
EXPECTED_TRANSPORT="pickle"
fi
fi
lmcache server "${LMCACHE_ARGS[@]}" \
>"${LMCACHE_LOG}" 2>&1 &
LMCACHE_PID=$!
echo "LMCache server started (PID=${LMCACHE_PID})"
sleep 1
if ! kill -0 "${LMCACHE_PID}" 2>/dev/null; then
echo "❌ LMCache server exited immediately after startup. See ${LMCACHE_LOG} for details"
false
fi
echo "Waiting for LMCache healthcheck at http://localhost:${LMCACHE_HTTP_PORT}/healthcheck (timeout: ${LMCACHE_HEALTHCHECK_TIMEOUT}s)"
if ! wait_for_endpoint_contains "http://localhost:${LMCACHE_HTTP_PORT}/healthcheck" "${LMCACHE_HEALTHCHECK_TIMEOUT}" "" "LMCache server"; then
false
fi
echo "✅ LMCache server is healthy"
echo "[Phase 2 / Step 4] Installing system libs (Linux only) and starting vLLM server"
if [ "$(uname -s)" = "Linux" ]; then
# System libs vLLM's CPU serve path dlopens at import time:
# - libnuma1: needed by some vLLM CPU code paths.
# - ffmpeg: provides libavutil.so.{56..60} etc. that torchcodec
# dlopens. Recent vLLM nightlies import torchcodec
# unconditionally, so `vllm serve` aborts with
# "libavutil.so.NN: cannot open shared object file"
# without FFmpeg — even for text-only models.
# Prefer sudo if present (GitHub ubuntu runners need it). Install only
# what's missing and never let an apt hiccup fail the step.
MISSING_PKGS=()
if [ ! -e /usr/lib/x86_64-linux-gnu/libnuma.so.1 ] \
&& [ ! -e /lib/x86_64-linux-gnu/libnuma.so.1 ]; then
MISSING_PKGS+=(libnuma1)
fi
# torchcodec supports FFmpeg 4-8; the distro's ffmpeg package provides a
# compatible libavutil. Detect via ldconfig so we match whichever
# soname (56..60) the runner already ships.
if ! ldconfig -p 2>/dev/null | grep -q 'libavutil\.so'; then
MISSING_PKGS+=(ffmpeg)
fi
if [ "${#MISSING_PKGS[@]}" -gt 0 ]; then
echo "Installing missing system packages: ${MISSING_PKGS[*]}"
if command -v sudo >/dev/null 2>&1; then
sudo apt-get update \
&& sudo apt-get install -y --no-install-recommends "${MISSING_PKGS[@]}" \
|| echo "⚠️ apt-get install (${MISSING_PKGS[*]}) via sudo failed; continuing"
else
apt-get update \
&& apt-get install -y --no-install-recommends "${MISSING_PKGS[@]}" \
|| echo "⚠️ apt-get install (${MISSING_PKGS[*]}) failed; continuing"
fi
else
echo "libnuma1 and FFmpeg runtime libs already present, skipping apt install"
fi
fi
# VLLM_DEVICE is the modern env var (vLLM 0.8+)
export VLLM_DEVICE=cpu
export VLLM_TARGET_DEVICE=cpu
start_vllm
echo "[Phase 2 / Step 5] Sending E2E test request"
completion_response="$(curl -fsS "http://localhost:${VLLM_PORT}/v1/completions" \
-H "Content-Type: application/json" \
-d '{"model":"facebook/opt-125m","prompt":"Hello","max_tokens":5}')"
echo "Completion response: ${completion_response}"
if ! echo "${completion_response}" | grep -q '"choices"'; then
echo "❌ E2E request response failed structural validation"
false
fi
if ! echo "${completion_response}" | grep -q "facebook/opt-125m"; then
echo "❌ E2E request response missing expected model"
false
fi
echo "✅ E2E request validation passed"
# Verify transport mode (logged after vLLM connects to LMCache server)
echo "[Phase 2 / Step 5.5] Verifying transport mode: expecting '${EXPECTED_TRANSPORT}'"
# Worker logs `Creating transfer context (device_type=<dev>, mode=<m>)`
# from worker_transfer.py:create_transfer_context, where <m> is the
# resolved MPTransferMode after env-var lookup. This is the single source
# of truth for which TransferContext the worker actually entered. Note:
# the worker is a child of `vllm serve`, so its LMCache log lines land in
# VLLM_LOG (vllm's stdout), not in LMCACHE_LOG (lmcache server's stdout).
# The shm/pickle branches still grep LMCACHE_LOG because the strategy
# line is emitted by the lmcache server itself.
if [ "${EXPECTED_TRANSPORT}" = "lmcache_driven" ]; then
if ! grep -q "Creating transfer context.*mode=lmcache_driven" "${VLLM_LOG}" 2>/dev/null; then
echo "❌ Expected lmcache-driven worker context but 'mode=lmcache_driven' not found in vLLM log"
tail -50 "${VLLM_LOG}"
false
fi
echo "✅ Transport mode confirmed: lmcache-driven (IPC handle path)"
elif [ "${EXPECTED_TRANSPORT}" = "engine_driven" ]; then
if ! grep -q "Creating transfer context.*mode=engine_driven" "${VLLM_LOG}" 2>/dev/null; then
echo "❌ Expected engine-driven worker context but 'mode=engine_driven' not found in vLLM log"
tail -50 "${VLLM_LOG}"
false
fi
echo "✅ Transport mode confirmed: engine-driven"
elif [ "${EXPECTED_TRANSPORT}" = "shm" ]; then
if ! grep -q "Using shm non-GPU transfer strategy" "${LMCACHE_LOG}" 2>/dev/null; then
echo "❌ Expected shm transport but server strategy line not found in log"
tail -50 "${LMCACHE_LOG}"
false
fi
echo "✅ Transport mode confirmed: shm"
elif [ "${EXPECTED_TRANSPORT}" = "pickle" ]; then
if ! grep -q "Using pickle non-GPU transfer strategy" "${LMCACHE_LOG}" 2>/dev/null; then
echo "❌ Expected pickle transport but server strategy line not found in log"
tail -50 "${LMCACHE_LOG}"
false
fi
echo "✅ Transport mode confirmed: pickle"
fi
echo "[Phase 2 / Step 6] Cleaning up Phase 2 vLLM"
stop_vllm
echo "✅ Phase 2 cleanup completed"
echo "✅ CPU E2E validation passed"
# ═══════════════════════════════════════════════════════════════════
# Phase 3: Cache Hit Validation
# ═══════════════════════════════════════════════════════════════════
# Scenario:
# - LMCache server stays running the entire time
# - vLLM instance 1: request A → LMCache store; request A again → LMCache hit
# - vLLM restart (instance 2): request A → LMCache hit (cross-instance)
# - All three outputs must be identical (bit-exact with temperature=0)
# ═══════════════════════════════════════════════════════════════════
echo "=== Cache Hit Validation (Phase 3) ==="
# Generate a fixed ~1000 token prompt
PROMPT_FILE="/tmp/build_${BUILD_ID}_phase3_prompt.txt"
python3 -c "
# A diverse, non-repetitive story prompt (~1000 tokens for opt-125m).
# The model will continue the narrative; correctness doesn't matter,
# only that all three runs produce identical output (cache consistency).
story = '''Once upon a time in a small coastal village, there lived an old lighthouse keeper named Thomas. Every evening, he climbed the one hundred and thirty-seven steps to the top of the lighthouse to light the great lamp. The sea was unpredictable in those parts. Ships from distant lands carried spices, silk, and stories of places Thomas had never seen. One stormy night in November, a merchant vessel called the Silver Heron appeared on the horizon, listing dangerously to starboard. Thomas watched through his brass telescope as the waves crashed against its hull. He knew that if the ship did not change course within the next ten minutes, it would strike the jagged rocks known locally as the Devil's Teeth. He grabbed the emergency flare gun from the wooden cabinet and fired three red flares into the sky. The captain of the Silver Heron saw the warning and ordered hard to port. The ship groaned as it turned, barely clearing the outermost rock by twenty meters. The next morning, the captain rowed ashore to thank Thomas personally. He brought a gift: a small wooden box containing a compass that always pointed not north, but toward home. Thomas kept it on his desk for the rest of his days. Years later, when Thomas retired, he passed the compass to his granddaughter Elena, who had inherited his love of the sea. Elena became a marine biologist studying the migration patterns of humpback whales along the Pacific coast. She traveled from Alaska to Mexico following the whale pods, documenting their songs and social behaviors. Her research revealed that whale families maintained bonds across thousands of miles, communicating through low-frequency calls that could travel entire ocean basins. One afternoon while diving near a coral reef off the coast of Baja California, Elena discovered something extraordinary beneath a rocky overhang:'''
print(story, end='')
" > "${PROMPT_FILE}"
echo "Generated prompt file: ${PROMPT_FILE} ($(wc -c < "${PROMPT_FILE}") bytes)"
# Reset metrics to have a clean baseline
echo "[Phase 3 / Step 1] Resetting LMCache metrics"
curl -fsS -X POST "http://localhost:${LMCACHE_HTTP_PORT}/metrics/reset" >/dev/null
echo "✅ Metrics reset"
# Start vLLM instance 1
echo "[Phase 3 / Step 2] Starting vLLM (instance 1)"
start_vllm
# Request A (first time) → should trigger store
echo "[Phase 3 / Step 3] Request A (first) — expecting LMCache store"
L1_WRITE_BEFORE=$(scrape_metric "lmcache_mp_l1_write_chunks_total")
OUTPUT_1=$(send_completion "${PROMPT_FILE}" 50)
echo "Output 1: ${OUTPUT_1}"
sleep 2 # allow async store to complete
L1_WRITE_AFTER=$(scrape_metric "lmcache_mp_l1_write_chunks_total")
STORE_DELTA=$((L1_WRITE_AFTER - L1_WRITE_BEFORE))
echo "L1 write chunks delta: ${STORE_DELTA}"
if [ "${STORE_DELTA}" -lt 1 ]; then
echo "❌ No L1 write activity after first request (expected store)"
false
fi
echo "✅ LMCache store verified (${STORE_DELTA} chunks written)"
# Request A (second time, same vLLM instance) → should trigger read/hit
echo "[Phase 3 / Step 4] Request A (second) — expecting LMCache hit"
L1_READ_BEFORE=$(scrape_metric "lmcache_mp_l1_read_chunks_total")
OUTPUT_2=$(send_completion "${PROMPT_FILE}" 50)
echo "Output 2: ${OUTPUT_2}"
sleep 2
L1_READ_AFTER=$(scrape_metric "lmcache_mp_l1_read_chunks_total")
READ_DELTA=$((L1_READ_AFTER - L1_READ_BEFORE))
echo "L1 read chunks delta: ${READ_DELTA}"
if [ "${READ_DELTA}" -lt 1 ]; then
echo "❌ No L1 read activity on second request (expected cache hit)"
false
fi
echo "✅ LMCache hit verified on same instance (${READ_DELTA} chunks read)"
# Restart vLLM
echo "[Phase 3 / Step 5] Restarting vLLM (instance 2)"
stop_vllm
sleep 2
start_vllm
# Request A (third time, new vLLM instance) → should trigger read/hit from LMCache
echo "[Phase 3 / Step 6] Request A (third) — expecting LMCache hit after vLLM restart"
L1_READ_BEFORE=$(scrape_metric "lmcache_mp_l1_read_chunks_total")
OUTPUT_3=$(send_completion "${PROMPT_FILE}" 50)
echo "Output 3: ${OUTPUT_3}"
sleep 2
L1_READ_AFTER=$(scrape_metric "lmcache_mp_l1_read_chunks_total")
READ_DELTA=$((L1_READ_AFTER - L1_READ_BEFORE))
echo "L1 read chunks delta: ${READ_DELTA}"
if [ "${READ_DELTA}" -lt 1 ]; then
echo "❌ No L1 read activity after vLLM restart (expected cross-instance cache hit)"
false
fi
echo "✅ LMCache cross-instance hit verified (${READ_DELTA} chunks read)"
# Verify all three outputs are identical
echo "[Phase 3 / Step 7] Verifying output consistency"
if [ "${OUTPUT_1}" != "${OUTPUT_2}" ]; then
echo "❌ Output mismatch between request 1 and request 2"
echo " Output 1: ${OUTPUT_1}"
echo " Output 2: ${OUTPUT_2}"
false
fi
if [ "${OUTPUT_1}" != "${OUTPUT_3}" ]; then
echo "❌ Output mismatch between request 1 and request 3 (after vLLM restart)"
echo " Output 1: ${OUTPUT_1}"
echo " Output 3: ${OUTPUT_3}"
false
fi
echo "✅ All three outputs are identical — cache does not alter inference results"
# Negative test: a completely different prompt should NOT hit the cache
echo "[Phase 3 / Step 8] Request B (different prompt) — expecting cache MISS"
PROMPT_FILE_B="/tmp/build_${BUILD_ID}_phase3_prompt_b.txt"
python3 -c "
# A completely different prompt that shares no prefix with prompt A
story_b = '''In the year 2147, humanity established its first permanent colony on Mars. The settlement, named Arcadia, housed three thousand researchers and engineers working to terraform the red planet. Chief botanist Dr. Yuki Tanaka spent her days in the greenhouse domes, cultivating genetically modified crops that could thrive in Martian soil. The atmospheric processors hummed day and night, slowly converting carbon dioxide into breathable oxygen. It was tedious work measured in decades, but the colonists were patient. Every morning, Dr. Tanaka checked her instruments and recorded the oxygen levels in her logbook. Today the readings showed'''
print(story_b, end='')
" > "${PROMPT_FILE_B}"
L1_READ_BEFORE=$(scrape_metric "lmcache_mp_l1_read_chunks_total")
OUTPUT_B=$(send_completion "${PROMPT_FILE_B}" 50)
echo "Output B: ${OUTPUT_B}"
wait_for_metric_change "lmcache_mp_l1_read_chunks_total" "${L1_READ_BEFORE}" 5 || true
L1_READ_AFTER=$(scrape_metric "lmcache_mp_l1_read_chunks_total")
READ_DELTA=$((L1_READ_AFTER - L1_READ_BEFORE))
echo "L1 read chunks delta for new prompt: ${READ_DELTA}"
if [ "${READ_DELTA}" -gt 0 ]; then
echo "❌ Unexpected cache hit on a completely different prompt — metrics may be unreliable"
false
fi
echo "✅ Cache miss confirmed for different prompt — metrics are trustworthy"
echo "[Phase 3 / Step 9] Cleaning up"
stop_vllm
cleanup_processes
echo "✅ Phase 3 cleanup completed"
echo ""
echo "=========================================="
echo "✅ All phases passed (Phase 1 + 2 + 3)"
echo "=========================================="
# Make sure lmcache/vllm processes started in this run are reaped so
# the next CI step does not collide on their default ZMQ/HTTP ports.
cleanup_processes
# Upload artifacts BEFORE deleting the workspace
upload_artifacts
cleanup_workspace
+3
View File
@@ -0,0 +1,3 @@
# Since dependabot cannot update workflows using docker,
# we use this indirection since dependabot can update this file.
FROM rhysd/actionlint:1.7.7@sha256:887a259a5a534f3c4f36cb02dca341673c6089431057242cdc931e9f133147e9
+52
View File
@@ -0,0 +1,52 @@
name: Lint GitHub Actions workflows
on:
push:
branches:
- "dev"
- "release-**"
paths:
- '.github/actions/*.ya?ml'
- '.github/workflows/*.ya?ml'
- '.github/workflows/actionlint.*' # This workflow
pull_request:
branches:
- "dev"
- "release-**"
paths:
- '.github/actions/*.ya?ml'
- '.github/workflows/*.ya?ml'
- '.github/workflows/actionlint.*' # This workflow
env:
LC_ALL: en_US.UTF-8
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
actionlint:
runs-on: ubuntu-latest
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: "Checkout"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: "Download actionlint"
run: |
docker build --tag actionlint - < .github/workflows/actionlint.dockerfile
- name: "Check workflow files"
run: |
echo "::add-matcher::.github/workflows/matchers/actionlint.json"
docker run --volume="${PWD}:/repo" --workdir=/repo actionlint -color
+142
View File
@@ -0,0 +1,142 @@
# Optional Aerospike native L2 integration (BUILD_AEROSPIKE=1 + CE Docker).
name: Aerospike integration
on:
push:
branches:
- "dev"
- "release-**"
paths:
- "csrc/storage_backends/aerospike/**"
- "lmcache/v1/distributed/l2_adapters/aerospike_l2_adapter.py"
- "lmcache/v1/storage_backend/native_clients/aerospike_client.py"
- "setup.py"
- "tests/aerospike_ce.conf.template"
- "tests/v1/distributed/test_aerospike_*"
- ".github/workflows/aerospike_integration.yml"
pull_request:
branches:
- "dev"
- "release-**"
paths:
- "csrc/storage_backends/aerospike/**"
- "lmcache/v1/distributed/l2_adapters/aerospike_l2_adapter.py"
- "lmcache/v1/storage_backend/native_clients/aerospike_client.py"
- "setup.py"
- "tests/aerospike_ce.conf.template"
- "tests/v1/distributed/test_aerospike_*"
- ".github/workflows/aerospike_integration.yml"
workflow_dispatch:
permissions:
contents: read
env:
AEROSPIKE_CLIENT_C_VERSION: "7.3.0"
AEROSPIKE_CLIENT_C_UBUNTU: "ubuntu22.04"
AEROSPIKE_CONTAINER_NAME: lmcache-aerospike-ci
jobs:
aerospike-native-l2:
name: Aerospike native L2
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
cache: pip
- name: Install Aerospike C client (prebuilt)
run: |
set -euo pipefail
DEPS="${GITHUB_WORKSPACE}/.deps"
INSTALL="${DEPS}/aerospike-install"
VERSION="${AEROSPIKE_CLIENT_C_VERSION}"
UBUNTU="${AEROSPIKE_CLIENT_C_UBUNTU}"
BASE="aerospike-client-c-libuv_${VERSION}_${UBUNTU}_x86_64"
URL="https://download.aerospike.com/artifacts/aerospike-client-c/${VERSION}/${BASE}.tgz"
mkdir -p "${DEPS}"
curl -fsSL -o "${DEPS}/${BASE}.tgz" "${URL}"
tar xzf "${DEPS}/${BASE}.tgz" -C "${DEPS}"
rm -rf "${INSTALL}"
mkdir -p "${INSTALL}"
dpkg-deb -x "${DEPS}/${BASE}/aerospike-client-c-libuv-devel_${VERSION}-${UBUNTU}_amd64.deb" "${INSTALL}"
dpkg-deb -x "${DEPS}/${BASE}/aerospike-client-c-libuv_${VERSION}-${UBUNTU}_amd64.deb" "${INSTALL}"
sudo apt-get update -qq
sudo apt-get install -y -qq libyaml-0-2 libuv1-dev
(cd "${DEPS}" && apt download libyaml-dev && \
rm -rf libyaml-install && mkdir -p libyaml-install && \
dpkg-deb -x libyaml-dev_*.deb libyaml-install)
{
echo "AEROSPIKE_INCLUDE_DIR=${INSTALL}/usr/include"
echo "AEROSPIKE_LIBRARY_DIR=${INSTALL}/usr/lib"
echo "LD_LIBRARY_PATH=${INSTALL}/usr/lib:${DEPS}/libyaml-install/usr/lib/x86_64-linux-gnu"
} >> "${GITHUB_ENV}"
- name: Build LMCache with Aerospike extension
run: |
set -euo pipefail
python -m pip install --upgrade pip wheel
python -m pip install 'setuptools>=77.0.3,<81.0.0' 'pybind11>=2.12'
python -m pip install numpy
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
export BUILD_AEROSPIKE=1
export NO_GPU_EXT=1
python -m pip install -e . --no-build-isolation
python -m pip install 'aerospike>=14.0.0,<19.0.0' pytest
python -c "from lmcache.lmcache_aerospike import LMCacheAerospikeClient; print('OK:', LMCacheAerospikeClient)"
- name: Start Aerospike CE
id: aerospike
run: |
set -euo pipefail
HOST_PORT=$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()')
CONF_DIR=$(mktemp -d)
sed "s/__ACCESS_PORT__/${HOST_PORT}/g" tests/aerospike_ce.conf.template > "${CONF_DIR}/aerospike.conf"
docker rm -f "${AEROSPIKE_CONTAINER_NAME}" 2>/dev/null || true
docker run -d \
--name "${AEROSPIKE_CONTAINER_NAME}" \
-p "${HOST_PORT}:3000" \
-v "${CONF_DIR}/aerospike.conf:/etc/aerospike/aerospike.template.conf:ro" \
aerospike/aerospike-server:latest
deadline=$((SECONDS + 90))
until docker exec "${AEROSPIKE_CONTAINER_NAME}" asinfo -v status 2>/dev/null | grep -qE 'ok|normal'; do
if (( SECONDS >= deadline )); then
docker logs "${AEROSPIKE_CONTAINER_NAME}" 2>&1 | tail -80
exit 1
fi
sleep 1
done
host_deadline=$((SECONDS + 90))
until python -c "
import aerospike
c = aerospike.client({'hosts': [('127.0.0.1', ${HOST_PORT})]})
c.connect()
info = c.info_random_node('namespace/lmcache')
c.close()
assert 'nsup-period=120' in info
"; do
if (( SECONDS >= host_deadline )); then
docker logs "${AEROSPIKE_CONTAINER_NAME}" 2>&1 | tail -80
exit 1
fi
sleep 1
done
{
echo "AEROSPIKE_TEST_HOST=127.0.0.1"
echo "AEROSPIKE_TEST_PORT=${HOST_PORT}"
echo "AEROSPIKE_TEST_NAMESPACE=lmcache"
echo "RUN_AEROSPIKE_INTEGRATION=1"
} >> "${GITHUB_ENV}"
- name: Integration tests
run: |
pytest tests/v1/distributed/test_aerospike_l2_integration.py -v --tb=short
- name: Stop Aerospike CE
if: always()
run: docker rm -f "${AEROSPIKE_CONTAINER_NAME}" 2>/dev/null || true
+17
View File
@@ -0,0 +1,17 @@
name: Label auto-merge PRs
on:
pull_request_target:
types: [ auto_merge_enabled, auto_merge_disabled ]
permissions:
pull-requests: write
jobs:
add_remove_labels:
runs-on: ubuntu-latest
steps:
- uses: ubuntudroid/automerge-labeler@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
label: 'full'
+106
View File
@@ -0,0 +1,106 @@
name: Build CLI Artifacts
# Reusable workflow that builds the pure-Python lmcache-cli wheel (no CUDA).
# Uploaded as the `release-cli-artifacts` GHA artifact.
on:
workflow_call:
pull_request:
branches:
- dev
- "release-**"
paths:
- 'lmcache/cli/**.py'
- 'pyproject_cli.toml'
- 'requirements/cli.txt'
- '.github/workflows/build_cli_artifacts.yml'
env:
LC_ALL: en_US.UTF-8
defaults:
run:
shell: bash
# Cancel older in-flight runs on the same PR / branch.
concurrency:
group: build-cli-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build-cli-artifacts:
name: Build CLI artifacts
runs-on: ubuntu-latest
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
# Keep stable vX.Y.Z and rc/a/b pre-release tags; strip variant tags
# (v*-cu129, v*-alpha) that yield invalid PEP 440 versions (see #3123).
- name: Remove non-release tags (keep stable + rc/a/b pre-releases)
run: git tag -l | grep -vE '^v[0-9]+\.[0-9]+\.[0-9]+((rc|a|b)[0-9]+)?$' | xargs -r git tag -d
- name: Setup Python 3.13
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- name: Install build tooling
run: |
python -m pip install --upgrade pip
pip install build
- name: Clean up release artifacts
run: |
rm -rf dist/
- name: Build lmcache-cli wheel (pure Python, no native extensions)
run: |
cp pyproject_cli.toml pyproject.toml
NO_NATIVE_EXT=1 python -m build --wheel
- name: Smoke-test CLI wheel (no torch)
run: |
pip install dist/lmcache_cli-*.whl
# CLI wheel must be pure-Python: no compiled extension bundled.
if unzip -l dist/lmcache_cli-*.whl | grep -E '\.(so|pyd)$'; then
echo "::error::CLI wheel contains compiled extensions; should be pure-Python"
exit 1
fi
# Pure-Python surface must import without torch / native ops.
python -c "import lmcache"
# Native CUDA ops must be unreachable from the slim wheel.
if python -c "import lmcache.c_ops" 2>/dev/null; then
echo "::error::CLI wheel can import lmcache.c_ops; native ops must be absent from the pure-Python wheel"
exit 1
fi
echo "OK: lmcache.c_ops is not importable from the CLI wheel"
lmcache --help
# Slim-supported subcommands only (server is full-only).
for cmd in mock kvcache describe ping query bench tool trace; do
echo "::group::lmcache $cmd --help"
lmcache "$cmd" --help
echo "::endgroup::"
done
- name: Upload CLI release artifacts to GHA
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: release-cli-artifacts
path: dist/
+122
View File
@@ -0,0 +1,122 @@
name: Build CPU Artifacts
# Build LMCache with NO_GPU_EXT=1 (common C++ extensions only).
# Gates the CPU-only build path against regression.
on:
workflow_call:
pull_request:
branches:
- dev
- "release-**"
paths:
- 'setup.py'
- 'pyproject.toml'
- 'csrc/**'
- 'requirements/common.txt'
- 'requirements/build.txt'
- 'lmcache/__init__.py'
- '.github/workflows/build_cpu_artifacts.yml'
env:
LC_ALL: en_US.UTF-8
defaults:
run:
shell: bash
# Cancel stale runs per ref.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build-cpu-artifacts:
name: Build CPU artifacts
runs-on: ubuntu-latest
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: Remove non-release tags
run: git tag -l | grep -vE '^v[0-9]+\.[0-9]+\.[0-9]+$' | xargs -r git tag -d
- name: Setup Python 3.13
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- name: Install build tooling and CPU torch
run: |
python -m pip install --upgrade pip
pip install --index-url https://download.pytorch.org/whl/cpu torch
# Pre-install build deps for --no-isolation below.
pip install -r requirements/build.txt
pip install build
- name: Clean up release artifacts
run: |
rm -rf dist/
- name: Build CPU-only wheel (common C++ extensions, no GPU backend)
# --skip-dependency-check: the pre-installed CPU torch may not
# match pyproject.toml's exact build-system pin, but is ABI-
# compatible for compiling CppExtensions.
run: |
NO_GPU_EXT=1 python -m build --wheel --no-isolation \
--skip-dependency-check
- name: Verify CPU wheel contents
run: |
set -e
# Required: common C++ extensions.
for mod in native_storage_ops lmcache_redis lmcache_fs; do
if ! unzip -l dist/lmcache-*.whl | grep -qE "lmcache/${mod}\..*\.so"; then
echo "::error::missing common C++ extension: ${mod}"
exit 1
fi
done
# Forbidden: GPU extensions.
for mod in c_ops xpu_ops; do
if unzip -l dist/lmcache-*.whl | grep -qE "lmcache/${mod}\..*\.so"; then
echo "::error::unexpected GPU extension in CPU wheel: ${mod}"
exit 1
fi
done
- name: Smoke-test CPU wheel
# Run from $RUNNER_TEMP so Python resolves `lmcache` from the
# installed wheel, not the checkout on CWD.
working-directory: ${{ runner.temp }}
run: |
set -e
pip install ${{ github.workspace }}/dist/lmcache-*.whl
python -c "
import lmcache
assert lmcache.torch_device_type == 'cpu', lmcache.torch_device_type
"
python -c "import lmcache.native_storage_ops"
python -c "import lmcache.lmcache_redis"
python -c "import lmcache.lmcache_fs"
# c_ops resolves to the python_ops_fallback shim on CPU.
python -c "
import sys, lmcache, lmcache.c_ops
assert sys.modules['lmcache.c_ops'].__name__ == 'lmcache.python_ops_fallback'
"
- name: Upload CPU release artifacts to GHA
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: release-cpu-artifacts
path: dist/
+105
View File
@@ -0,0 +1,105 @@
name: Build cu129 Artifacts
# Reusable workflow that builds the cu129 CUDA 12.9 wheel via cibuildwheel
# against the pytorch manylinux cu12.9 base image.
# Uploaded as the `release-cu129-artifacts` GHA artifact.
# The cu12.9 wheel carries no local version suffix and is published to a
# dedicated v{tag}-cu12 GitHub Release (not PyPI).
on:
workflow_call:
env:
LC_ALL: en_US.UTF-8
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
build-cu129-artifacts:
name: Build cu129 artifacts
runs-on: ubuntu-latest
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
# Keep stable vX.Y.Z and rc/a/b pre-release tags; strip variant tags
# (v*-cu129, v*-alpha) that yield invalid PEP 440 versions (see #3123).
- name: Remove non-release tags (keep stable + rc/a/b pre-releases)
run: git tag -l | grep -vE '^v[0-9]+\.[0-9]+\.[0-9]+((rc|a|b)[0-9]+)?$' | xargs -r git tag -d
- name: Login to DockerHub
# Skip when either the username var or the token secret is
# absent. Secrets are not inherited into reusable workflows
# without `secrets: inherit`, and fork PRs never get secrets,
# so the token can legitimately be empty on some runs.
if: vars.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != ''
env:
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Free disk space
uses: ./.github/actions/free-disk-space
- name: Setup Python 3.13
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- name: Install build tooling
run: |
python -m pip install --upgrade pip
pip install build cibuildwheel
- name: Clean up release artifacts
run: |
rm -rf dist/
- name: Get base version for cu12.9 wheel
run: |
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
BASE="${GITHUB_REF_NAME#v}"
else
BASE=$(git tag --list 'v[0-9]*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1 | sed 's/^v//' || echo "0.0.0.dev")
fi
echo "CU129_VERSION=${BASE}" >> "$GITHUB_ENV"
- name: Build cu12.9 CUDA wheels with cibuildwheel
env:
CIBW_MANYLINUX_X86_64_IMAGE: docker.io/pytorch/manylinux2_28-builder:cuda12.9
CIBW_ENVIRONMENT: 'TORCH_CUDA_ARCH_LIST="7.0;7.5;8.0;8.6;8.9;9.0;10.0;12.0" ENABLE_CXX11_ABI=1 LMCACHE_CUDA_MAJOR=12 SETUPTOOLS_SCM_PRETEND_VERSION=${{ env.CU129_VERSION }} PIP_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cu129'
CIBW_REPAIR_WHEEL_COMMAND_LINUX: >-
auditwheel repair
--plat manylinux_2_28_x86_64
--exclude libtorch.so
--exclude libtorch_cuda.so
--exclude libtorch_python.so
--exclude libtorch_cpu.so
--exclude libc10.so
--exclude libc10_cuda.so
--exclude libcudart.so.12
-w {dest_dir} {wheel}
CIBW_TEST_COMMAND: "python -c 'import lmcache.c_ops'"
run: |
python -m cibuildwheel --output-dir dist
- name: Upload cu12.9 release artifacts to GHA
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: release-cu129-artifacts
path: dist/
+106
View File
@@ -0,0 +1,106 @@
name: Build and Deploy Online Documentation
on:
# Trigger the workflow on push request,
# for the dev branch
push:
branches:
- 'dev'
paths:
- 'docs/**'
- 'lmcache/**'
- 'examples/**'
- '.github/workflows/build_doc.yml'
jobs:
build-docs:
name: Build docs
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
# Full git history needed for sphinx-multiversion to detect all tags/branches
fetch-depth: 0
# Fetch all branches and tags
fetch-tags: true
- name: Setup Python 3.13
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements/docs.txt
- name: Build Versioned Sphinx Documentation
run: |
sphinx-multiversion docs/source output
SPHINX_LANGUAGE=zh_CN sphinx-multiversion docs/source output-zh
continue-on-error: false
- name: Setup root docs, .nojekyll, and Chinese docs
run: |
touch output/.nojekyll
# Copy dev docs to root (latest docs at root URL)
cp -r output/dev/* output/
# Remove dev directory to avoid duplication
rm -rf output/dev
# Copy Chinese dev docs to /zh_CN/ and versioned docs under /zh_CN/<version>/
mkdir -p output/zh_CN
cp -r output-zh/dev/* output/zh_CN/
rm -rf output-zh/dev
cp -r output-zh/. output/zh_CN/
rm -rf output-zh
- name: Upload doc artifacts to GHA
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: doc-artifacts
path: output/
deploy-docs:
name: Deploy docs online
runs-on: ubuntu-latest
needs: build-docs
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit
- name: Fetch doc artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: doc-artifacts
path: output
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
repository: LM-Cache-Website/lm-cache-website.github.io
token: ${{ secrets.LMCACHE_DOC }}
path: target-repo
- name: Copy Files to Target Repository
run: |
cp -r output/* target-repo/
- name: Commit and Push to Target Repository
run: |
cd target-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions@github.com"
git add .
if ! git diff --cached --quiet; then
git commit -m "Deploy updated online docs"
git push https://x-access-token:${{ secrets.LMCACHE_DOC }}@github.com/LM-Cache-Website/lm-cache-website.github.io.git main --force
else
echo "No changes to commit."
fi
@@ -0,0 +1,82 @@
name: Build Main Artifacts
# Reusable workflow that builds the main release artifacts:
# - source distribution (tar ball)
# - CUDA wheel (built via cibuildwheel, latest torch)
# Uploaded as the `release-artifacts` GHA artifact.
on:
workflow_call:
env:
LC_ALL: en_US.UTF-8
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
build-artifacts:
name: Build artifacts
runs-on: ubuntu-latest
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
# for setuptools-scm
fetch-depth: 0
# Keep stable vX.Y.Z and rc/a/b pre-release tags; strip variant tags
# (v*-cu129, v*-alpha) that yield invalid PEP 440 versions (see #3123).
- name: Remove non-release tags (keep stable + rc/a/b pre-releases)
run: git tag -l | grep -vE '^v[0-9]+\.[0-9]+\.[0-9]+((rc|a|b)[0-9]+)?$' | xargs -r git tag -d
- name: Pin release version from tag (setuptools-scm)
if: startsWith(github.ref, 'refs/tags/v')
run: echo "SETUPTOOLS_SCM_PRETEND_VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"
- name: Free disk space
uses: ./.github/actions/free-disk-space
- name: Setup Python 3.13
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- name: Install build tooling
run: |
python -m pip install --upgrade pip
pip install build cibuildwheel
- name: Clean up release artifacts
run: |
rm -rf dist/
- name: Build source distribution (no native extensions)
run: |
NO_NATIVE_EXT=1 python -m build --sdist
- name: Build CUDA wheels with cibuildwheel
# Configuration is set in pyproject.toml
# this will build lmcache with the latest version of torch
# building from source will be required if a serving engine uses
# an earlier non-ABI compatible version of torch
env:
CIBW_TEST_COMMAND: "python -c 'import lmcache.c_ops'"
run: |
python -m cibuildwheel --output-dir dist
- name: Upload release artifacts to GHA
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: release-artifacts
path: dist/
+68
View File
@@ -0,0 +1,68 @@
name: Code Quality
on:
workflow_call:
pull_request:
# Cancel older in-flight runs on the same PR / branch.
concurrency:
group: code-quality-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
lmcache: ${{ github.event_name == 'release' || steps.filter.outputs.lmcache == 'true' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
if: github.event_name == 'push'
with:
fetch-depth: 2
- uses: dorny/paths-filter@v3
if: github.event_name == 'push' || github.event_name == 'pull_request'
id: filter
with:
filters: |
lmcache:
- '**'
- '!operator/**'
- '!.github/workflows/operator_*.yml'
pre-commit:
needs: changes
if: needs.changes.outputs.lmcache == 'true'
name: Check code quality
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: true
egress-policy: audit
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
# for setuptools-scm
fetch-depth: 0
- name: Setup Python 3.13
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- run: echo "::add-matcher::.github/workflows/matchers/mypy.json"
- name: Run code quality checks - lint, format, spell, & static checks
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
with:
extra_args: --all-files
+112
View File
@@ -0,0 +1,112 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: [ "dev" ]
pull_request:
branches: [ "dev" ]
schedule:
- cron: '29 4 * * 0'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ubuntu-latest
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
# Disable C/CPP scan for now until GPU runner available for the repo
# - language: c-cpp
# build-mode: autobuild
- language: python
build-mode: none
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: true
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
# for setuptools-scm
fetch-depth: 0
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
with:
category: "/language:${{matrix.language}}"
+174
View File
@@ -0,0 +1,174 @@
name: CPU Device Tests
# CPU device tests: server bench + vLLM e2e. Both job types share a
# single environment (built once per OS) to avoid re-installing vLLM.
on:
workflow_call:
workflow_dispatch:
pull_request:
branches:
- "dev"
- "release-**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
lmcache: >-
${{
github.event_name == 'workflow_dispatch' ||
github.event_name == 'workflow_call' ||
steps.filter.outputs.lmcache == 'true'
}}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
if: github.event_name == 'push'
with:
fetch-depth: 2
- uses: dorny/paths-filter@v3
if: >-
github.event_name == 'push' ||
github.event_name == 'pull_request'
id: filter
with:
filters: |
lmcache:
- 'lmcache/**'
- 'pyproject.toml'
- 'setup.py'
- 'requirements/**.txt'
- '.github/workflows/cpu_device.yml'
- '.github/scripts/cpu_device_test.sh'
- '.github/scripts/cpu_server_bench_test.sh'
- '.github/scripts/cpu_vllm_e2e_test.sh'
- '.github/scripts/install_vllm_cpu.sh'
- '.github/scripts/install_lmcache_cpu.sh'
- '.github/scripts/download_model.sh'
- '.github/scripts/run-cpu-e2e-validation.sh'
- '!operator/**'
cpu-device-test:
needs: changes
if: needs.changes.outputs.lmcache == 'true'
name: CPU device (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-latest]
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 1
- name: Setup Python 3.12
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.12"
cache: pip
cache-dependency-path: |
pyproject.toml
requirements/*.txt
- name: Install FFmpeg (macOS)
if: runner.os == 'macOS'
run: |
if ! command -v ffmpeg >/dev/null 2>&1; then
brew install ffmpeg
else
echo "ffmpeg already installed: $(ffmpeg -version | head -n1)"
fi
- name: Cache opt-125m model (GitHub release)
uses: actions/cache@v4
with:
path: ~/.cache/huggingface
key: opt-125m-gh-v2
- name: Install vLLM CPU (prebuilt nightly from PyPI)
run: bash .github/scripts/install_vllm_cpu.sh
- name: Install lmcache (CPU-only, no vLLM)
run: bash .github/scripts/install_lmcache_cpu.sh
- name: Download facebook/opt-125m (GitHub release)
run: bash .github/scripts/download_opt125m_github.sh
- name: Server bench — LMCache-driven
run: |
LMCACHE_BENCH_TRANSFER_MODE=lmcache_driven \
LMCACHE_HTTP_PORT_BENCH=18080 \
LMCACHE_ZMQ_PORT_BENCH=15555 \
bash .github/scripts/cpu_device_test.sh server_bench
- name: Server bench — Engine-driven
run: |
LMCACHE_BENCH_TRANSFER_MODE=engine_driven \
LMCACHE_HTTP_PORT_BENCH=18081 \
LMCACHE_ZMQ_PORT_BENCH=15556 \
bash .github/scripts/cpu_device_test.sh server_bench
- name: vLLM e2e — Engine-driven (pickle)
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
LMCACHE_E2E_TRANSPORT_MODE=pickle \
LMCACHE_HTTP_PORT_E2E=18082 \
LMCACHE_ZMQ_PORT_E2E=15557 \
VLLM_PORT_E2E=18000 \
bash .github/scripts/cpu_device_test.sh vllm_e2e
# POSIX SHM transport is Linux-only.
- name: vLLM e2e — Engine-driven (shm)
if: runner.os == 'Linux'
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
LMCACHE_E2E_TRANSPORT_MODE=shm \
LMCACHE_HTTP_PORT_E2E=18083 \
LMCACHE_ZMQ_PORT_E2E=15558 \
VLLM_PORT_E2E=18001 \
bash .github/scripts/cpu_device_test.sh vllm_e2e
- name: vLLM e2e — Engine-driven
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
LMCACHE_E2E_TRANSPORT_MODE=engine_driven \
LMCACHE_HTTP_PORT_E2E=18084 \
LMCACHE_ZMQ_PORT_E2E=15559 \
VLLM_PORT_E2E=18002 \
bash .github/scripts/cpu_device_test.sh vllm_e2e
- name: Upload logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: cpu-device-logs-${{ matrix.os }}
path: |
/tmp/cpu_device_*.log
if-no-files-found: ignore
@@ -0,0 +1,17 @@
{
"problemMatcher": [
{
"owner": "actionlint",
"pattern": [
{
"regexp": "^(?:\\x1b\\[\\d+m)?(.+?)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*: (?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)* \\[(.+?)\\]$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"code": 5
}
]
}
]
}
+16
View File
@@ -0,0 +1,16 @@
{
"problemMatcher": [
{
"owner": "mypy",
"pattern": [
{
"regexp": "^(.+):(\\d+):\\s(error|warning):\\s(.+)$",
"file": 1,
"line": 2,
"severity": 3,
"message": 4
}
]
}
]
}
+221
View File
@@ -0,0 +1,221 @@
name: Build nightly container image of latest code
on:
schedule:
- cron: '30 7 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
nightly-wheels:
name: Build and publish nightly wheels
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: Remove non-release tags
run: git tag -l | grep -vE '^v[0-9]+\.[0-9]+\.[0-9]+$' | xargs -r git tag -d
- name: Free disk space
uses: ./.github/actions/free-disk-space
- name: Setup Python 3.13
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- name: Login to DockerHub
if: vars.DOCKERHUB_USERNAME != ''
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Install build tooling
run: |
python -m pip install --upgrade pip
pip install cibuildwheel
- name: Build cu13.0 nightly wheel
env:
CIBW_TEST_COMMAND: "python -c 'import lmcache.c_ops'"
run: |
python -m cibuildwheel --output-dir dist/cu130
- name: Publish cu13.0 wheels to rolling nightly release
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
gh release delete nightly --yes --cleanup-tag --repo ${{ github.repository }} 2>/dev/null || true
gh release create nightly \
--repo ${{ github.repository }} \
--title "Nightly $(date +'%Y-%m-%d') · CUDA 13.0" \
--notes "Nightly CUDA 13.0 wheels built from \`dev\` on $(date +'%Y-%m-%d').
\`\`\`
uv pip install lmcache --pre \\
--extra-index-url https://download.pytorch.org/whl/cu130 \\
--find-links https://github.com/${{ github.repository }}/releases/expanded_assets/nightly \\
--index-strategy unsafe-best-match
\`\`\`" \
--prerelease \
dist/cu130/*.whl
- name: Build cu12.9 nightly wheel
env:
CIBW_MANYLINUX_X86_64_IMAGE: docker.io/pytorch/manylinux2_28-builder:cuda12.9
CIBW_ENVIRONMENT: 'TORCH_CUDA_ARCH_LIST="7.0;7.5;8.0;8.6;8.9;9.0;10.0" ENABLE_CXX11_ABI=1 LMCACHE_CUDA_MAJOR=12 PIP_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cu129'
CIBW_REPAIR_WHEEL_COMMAND_LINUX: >-
auditwheel repair
--plat manylinux_2_28_x86_64
--exclude libtorch.so
--exclude libtorch_cuda.so
--exclude libtorch_python.so
--exclude libtorch_cpu.so
--exclude libc10.so
--exclude libc10_cuda.so
--exclude libcudart.so.12
-w {dest_dir} {wheel}
CIBW_TEST_COMMAND: "python -c 'import lmcache.c_ops'"
run: |
python -m cibuildwheel --output-dir dist/cu129
- name: Publish cu12.9 wheels to rolling nightly-cu129 release
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
gh release delete nightly-cu129 --yes --cleanup-tag --repo ${{ github.repository }} 2>/dev/null || true
gh release create nightly-cu129 \
--repo ${{ github.repository }} \
--title "Nightly $(date +'%Y-%m-%d') · CUDA 12.9" \
--notes "Nightly CUDA 12.9 wheels built from \`dev\` on $(date +'%Y-%m-%d').
\`\`\`
uv pip install lmcache --pre \\
--extra-index-url https://download.pytorch.org/whl/cu129 \\
--find-links https://github.com/${{ github.repository }}/releases/expanded_assets/nightly-cu129 \\
--index-strategy unsafe-best-match
\`\`\`" \
--prerelease \
dist/cu129/*.whl
nightly-build:
name: Build image
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Login to DockerHub
if: vars.DOCKERHUB_USERNAME != ''
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
# for setuptools-scm
fetch-depth: 0
- name: Remove non-release tags
run: git tag -l | grep -vE '^v[0-9]+\.[0-9]+\.[0-9]+$' | xargs -r git tag -d
- name: Free disk space
uses: ./.github/actions/free-disk-space
- name: Setup Python 3.13
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- name: Get the current date
run: |
echo "NOW=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
- name: Build lmcache/vllm-openai container image (cu13 nightly)
run: |
docker system prune -f --all
docker builder prune -af
docker build \
--build-arg BASE_IMAGE=nvcr.io/nvidia/cuda-dl-base:25.09-cuda13.0-devel-ubuntu24.04 \
--build-arg CUDA_VERSION=13.0 \
--build-arg torch_cuda_arch_list='7.5 8.0 8.6 8.9 9.0 10.0 12.0+PTX' \
--target image-build \
--tag lmcache/vllm-openai:latest-nightly --tag lmcache/vllm-openai:nightly-${{ env.NOW }} \
--file docker/Dockerfile .
- name: Push lmcache/vllm-openai container image to DockerHub
run: |
docker push lmcache/vllm-openai:latest-nightly
docker push lmcache/vllm-openai:nightly-${{ env.NOW }}
- name: Build lmcache/standalone container image (cu13 nightly)
run: |
docker system prune -f --all
docker builder prune -af
docker build \
--build-arg BASE_IMAGE=nvcr.io/nvidia/cuda-dl-base:25.09-cuda13.0-devel-ubuntu24.04 \
--build-arg CUDA_VERSION=13.0 \
--build-arg torch_cuda_arch_list='7.5 8.0 8.6 8.9 9.0 10.0 12.0+PTX' \
--target lmcache-final \
--tag lmcache/standalone:nightly --tag lmcache/standalone:nightly-${{ env.NOW }} \
--file docker/Dockerfile.standalone .
- name: Push lmcache/standalone container image to DockerHub
run: |
docker push lmcache/standalone:nightly
docker push lmcache/standalone:nightly-${{ env.NOW }}
- name: Build lmcache/vllm-openai container image (cu12.9 nightly)
run: |
docker system prune -f --all
docker builder prune -af
docker build \
--build-arg BASE_IMAGE=nvcr.io/nvidia/cuda-dl-base:25.05-cuda12.9-devel-ubuntu24.04 \
--build-arg CUDA_VERSION=12.9 --build-arg UBUNTU_VERSION=24.04 \
--target image-build \
--tag lmcache/vllm-openai:latest-nightly-cu129 --tag lmcache/vllm-openai:nightly-${{ env.NOW }}-cu129 \
--file docker/Dockerfile .
- name: Push lmcache/vllm-openai cu12.9 nightly image to DockerHub
run: |
docker push lmcache/vllm-openai:latest-nightly-cu129
docker push lmcache/vllm-openai:nightly-${{ env.NOW }}-cu129
- name: Build lmcache/standalone container image (cu12.9 nightly)
run: |
docker system prune -f --all
docker builder prune -af
docker build \
--build-arg BASE_IMAGE=nvcr.io/nvidia/cuda-dl-base:25.05-cuda12.9-devel-ubuntu24.04 \
--build-arg CUDA_VERSION=12.9 --build-arg UBUNTU_VERSION=24.04 \
--target lmcache-final \
--tag lmcache/standalone:nightly-cu129 --tag lmcache/standalone:nightly-${{ env.NOW }}-cu129 \
--file docker/Dockerfile.standalone .
- name: Push lmcache/standalone cu12.9 nightly image to DockerHub
run: |
docker push lmcache/standalone:nightly-cu129
docker push lmcache/standalone:nightly-${{ env.NOW }}-cu129
+76
View File
@@ -0,0 +1,76 @@
name: "Operator: CI"
on:
push:
branches:
- dev
- "release-**"
pull_request:
branches:
- dev
- "release-**"
permissions:
contents: read
defaults:
run:
shell: bash
working-directory: operator
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
operator: ${{ steps.filter.outputs.operator == 'true' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
if: github.event_name == 'push'
with:
fetch-depth: 2
- uses: dorny/paths-filter@v3
if: github.event_name == 'push' || github.event_name == 'pull_request'
id: filter
with:
filters: |
operator:
- 'operator/**'
- '.github/workflows/operator_ci.yml'
ci:
needs: changes
if: needs.changes.outputs.operator == 'true'
name: Format, lint, and test
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: operator/go.mod
- name: Check formatting
run: |
test -z "$(gofmt -l .)" || { gofmt -d .; exit 1; }
- name: Check linter configuration
run: make lint-config
- name: Lint
run: make lint
- name: Test
run: |
go mod tidy
make test
+80
View File
@@ -0,0 +1,80 @@
name: "Operator: Nightly"
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
env:
IMAGE_NAME: lmcache/lmcache-operator
permissions:
contents: write
defaults:
run:
shell: bash
working-directory: operator
jobs:
nightly:
name: Build, push, and release nightly
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Compute nightly tag
id: tag
run: |
DATE=$(date -u +%Y-%m-%d)
echo "TAG=nightly-${DATE}" >> "$GITHUB_OUTPUT"
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: operator
platforms: linux/amd64
push: true
tags: |
${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.TAG }}
${{ env.IMAGE_NAME }}:nightly
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: operator/go.mod
- name: Build installer manifest
run: make build-installer IMG=${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.TAG }}
- name: Update nightly release
uses: softprops/action-gh-release@v2
with:
tag_name: operator-nightly-latest
name: "Operator Nightly Latest (${{ steps.tag.outputs.TAG }})"
prerelease: true
make_latest: false
body: |
Automated nightly operator build from `dev` branch.
**Image:** `${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.TAG }}`
```bash
kubectl apply -f https://github.com/${{ github.repository }}/releases/download/operator-nightly-latest/install.yaml
```
files: operator/dist/install.yaml
+100
View File
@@ -0,0 +1,100 @@
name: "Operator: Release"
on:
release:
types: [published]
env:
IMAGE_NAME: lmcache/lmcache-operator
permissions:
contents: write
defaults:
run:
shell: bash
working-directory: operator
jobs:
release:
name: Build, push, and release
# Filter out main lmcache `v*` releases — only fire for operator-prefixed tags.
if: startsWith(github.ref_name, 'operator-v')
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract version from tag
id: version
run: |
# Strip "operator-" prefix from tag to get version (e.g. operator-v0.4.8rc1 -> v0.4.8rc1).
VERSION="${GITHUB_REF_NAME#operator-}"
echo "VERSION=${VERSION}" >> "$GITHUB_OUTPUT"
# Pre-releases (rc/alpha/beta) get the floating ":latest-rc" tag and must
# never move the stable ":latest" image tag or the "operator-latest" alias.
# Detect from the version string rather than the GitHub "pre-release" flag
# so behavior is robust whether or not that box is ticked in the UI.
if [[ "${VERSION}" =~ (rc|alpha|beta) ]]; then
echo "FLOATING_TAG=latest-rc" >> "$GITHUB_OUTPUT"
echo "IS_PRERELEASE=true" >> "$GITHUB_OUTPUT"
else
echo "FLOATING_TAG=latest" >> "$GITHUB_OUTPUT"
echo "IS_PRERELEASE=false" >> "$GITHUB_OUTPUT"
fi
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: operator
platforms: linux/amd64
push: true
tags: |
${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
${{ env.IMAGE_NAME }}:${{ steps.version.outputs.FLOATING_TAG }}
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: operator/go.mod
- name: Build installer manifest
run: make build-installer IMG=${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
- name: Attach installer to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: operator/dist/install.yaml
- name: Update latest release alias
# Skip for pre-releases: an rc/alpha/beta must not repoint the stable
# "operator-latest" alias (or its install.yaml) at a pre-release build.
if: steps.version.outputs.IS_PRERELEASE == 'false'
uses: softprops/action-gh-release@v2
with:
tag_name: operator-latest
name: "Operator Latest (${{ steps.version.outputs.VERSION }})"
make_latest: false
body: |
Points to the latest stable operator release: `${{ steps.version.outputs.VERSION }}`
**Image:** `${{ env.IMAGE_NAME }}:latest`
```bash
kubectl apply -f https://github.com/${{ github.repository }}/releases/download/operator-latest/install.yaml
```
files: operator/dist/install.yaml
+52
View File
@@ -0,0 +1,52 @@
name: PR Full Build
# Build all release artifacts (sdist + CUDA wheel, CLI wheel, cu129 wheel)
# on PRs carrying the `full` label. The `full` label is added automatically
# by automerge-labeler.yml when auto-merge is enabled on a code-change PR,
# so this acts as a pre-merge sanity check that wheels still build.
#
# No tests, no code-quality checks, no publishing — those are covered by
# test.yml / code_quality_checks.yml (per-PR) and publish.yml (on merge).
on:
pull_request:
branches:
- dev
- "release-**"
types: [opened, synchronize, reopened, labeled]
# Cancel older in-flight runs on the same PR. github.ref is
# refs/pull/<N>/merge on pull_request events, so each PR gets its own group.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Each build runs independently — a failure in one does not skip the
# others, matching publish.yml's isolation model.
build-main:
name: Build main
if: contains(github.event.pull_request.labels.*.name, 'full')
uses: ./.github/workflows/build_main_artifacts.yml
build-cli:
name: Build CLI
if: contains(github.event.pull_request.labels.*.name, 'full')
uses: ./.github/workflows/build_cli_artifacts.yml
build-cpu:
name: Build CPU
if: contains(github.event.pull_request.labels.*.name, 'full')
uses: ./.github/workflows/build_cpu_artifacts.yml
build-cu129:
name: Build cu129
if: contains(github.event.pull_request.labels.*.name, 'full')
uses: ./.github/workflows/build_cu129_artifacts.yml
# Fork PRs won't expose DOCKERHUB_TOKEN even with `inherit`; the login
# step in build_cu129_artifacts.yml guards on the token being non-empty.
secrets: inherit
+531
View File
@@ -0,0 +1,531 @@
name: Build and Publish to PyPI
on:
# Trigger the workflow on push to the dev and any release branches,
# and on release tag pushes / GitHub release publication.
# Per-PR verification lives in test.yml, code_quality_checks.yml, and
# pr_full_build.yml — this workflow is the release pipeline.
push:
branches:
- dev
- "release-**"
tags:
- "v*"
release:
types:
- published
env:
LC_ALL: en_US.UTF-8
# Cancel superseded runs on the same ref, but never abort a tag/release publish.
concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') && github.event_name != 'release' }}
defaults:
run:
shell: bash
permissions:
contents: read
pull-requests: read
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
# Skip the whole pipeline for operator releases (tags like `operator-v*`).
# operator_release.yml handles those; this workflow is lmcache-only.
lmcache: ${{ !startsWith(github.ref_name, 'operator-') && (github.event_name == 'release' || startsWith(github.ref, 'refs/tags/') || steps.filter.outputs.lmcache == 'true') }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')
with:
fetch-depth: 2
- uses: dorny/paths-filter@v3
if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')
id: filter
with:
filters: |
lmcache:
- '**.py'
- 'pyproject.toml'
- 'pyproject_cli.toml'
- 'requirements/**.txt'
- 'docker/**'
- '.github/workflows/test.yml'
- '.github/workflows/code_quality_checks.yml'
- '.github/workflows/publish.yml'
- '.github/workflows/build_main_artifacts.yml'
- '.github/workflows/build_cli_artifacts.yml'
- '.github/workflows/build_cu129_artifacts.yml'
- '!operator/**'
# Build each artifact group in its own reusable workflow so that a
# failure in one (e.g. cu129) does not block publishing of the others.
build-main:
needs: changes
if: needs.changes.outputs.lmcache == 'true'
name: Build main
uses: ./.github/workflows/build_main_artifacts.yml
build-cli:
needs: changes
if: needs.changes.outputs.lmcache == 'true'
name: Build CLI
uses: ./.github/workflows/build_cli_artifacts.yml
build-cu129:
needs: changes
if: needs.changes.outputs.lmcache == 'true'
name: Build cu129
uses: ./.github/workflows/build_cu129_artifacts.yml
# DockerHub creds needed for the cu12.9 manylinux image pull;
# reusable workflows do not auto-inherit secrets.
secrets: inherit
# Run tests and code quality checks before publishing
test:
needs: changes
if: needs.changes.outputs.lmcache == 'true'
name: Run tests
uses: ./.github/workflows/test.yml
code-quality:
needs: changes
if: needs.changes.outputs.lmcache == 'true'
name: Run code quality checks
uses: ./.github/workflows/code_quality_checks.yml
# Push to Test PyPI when:
# - a new GitHub release is published
# - a commit lands on dev (PR merged)
publish-test-pypi:
name: Publish packages to test.pypi.org
# Require upstream jobs to have succeeded so a cancelled/skipped build
# doesn't trigger a publish that fails on the missing artifact.
if: |
needs.build-main.result == 'success' &&
needs.test.result == 'success' &&
needs.code-quality.result == 'success' &&
needs.changes.outputs.lmcache == 'true' &&
github.repository_owner == 'LMCache' &&
github.event_name == 'push' && github.ref == 'refs/heads/dev'
permissions:
contents: read
# see https://docs.pypi.org/trusted-publishers/
id-token: write
runs-on: ubuntu-latest
needs: [changes, build-main, test, code-quality]
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Fetch release artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: release-artifacts
path: dist
- name: Upload to Test PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
repository-url: https://test.pypi.org/legacy/
verbose: true
publish-cli-test-pypi:
name: Publish lmcache-cli packages to test.pypi.org
if: |
needs.build-cli.result == 'success' &&
needs.test.result == 'success' &&
needs.code-quality.result == 'success' &&
needs.changes.outputs.lmcache == 'true' &&
github.repository_owner == 'LMCache' &&
github.event_name == 'push' && github.ref == 'refs/heads/dev'
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
needs: [changes, build-cli, test, code-quality]
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Fetch CLI release artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: release-cli-artifacts
path: dist
- name: Upload lmcache-cli to Test PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
repository-url: https://test.pypi.org/legacy/
verbose: true
# Push to PyPI (production) when:
# - a new GitHub release is created
publish-pypi:
name: Publish release to pypi.org
if: |
needs.build-main.result == 'success' &&
needs.test.result == 'success' &&
needs.code-quality.result == 'success' &&
needs.changes.outputs.lmcache == 'true' &&
github.repository_owner == 'LMCache' && github.event.action == 'published'
permissions:
# see https://docs.pypi.org/trusted-publishers/
id-token: write
# allow gh release upload
contents: write
runs-on: ubuntu-latest
needs: [changes, build-main, test, code-quality]
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Fetch release artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: release-artifacts
path: dist
- name: Upload release artifacts to GitHub release
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
gh release upload '${{ github.ref_name }}' dist/* --repo '${{ github.repository }}'
- name: Upload to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
verbose: true
publish-cli-pypi:
name: Publish lmcache-cli release to pypi.org
if: |
needs.build-cli.result == 'success' &&
needs.test.result == 'success' &&
needs.code-quality.result == 'success' &&
needs.changes.outputs.lmcache == 'true' &&
github.repository_owner == 'LMCache' && github.event.action == 'published'
permissions:
id-token: write
contents: write
runs-on: ubuntu-latest
needs: [changes, build-cli, test, code-quality]
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Fetch CLI release artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: release-cli-artifacts
path: dist
- name: Upload lmcache-cli release artifacts to GitHub release
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
gh release upload '${{ github.ref_name }}' dist/* --repo '${{ github.repository }}'
- name: Upload lmcache-cli to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
verbose: true
# Publish cu12.9 wheel to GitHub Release (not PyPI) when a release is published
publish-cu129-github-release:
name: Publish cu129 wheel to GitHub Release
if: |
needs.build-cu129.result == 'success' &&
needs.test.result == 'success' &&
needs.code-quality.result == 'success' &&
needs.changes.outputs.lmcache == 'true' &&
github.repository_owner == 'LMCache' && github.event.action == 'published'
permissions:
contents: write
runs-on: ubuntu-latest
needs: [changes, build-cu129, test, code-quality]
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Fetch cu12.9 release artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: release-cu129-artifacts
path: dist
- name: Publish cu12.9 wheel to dedicated GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
gh release create "${{ github.ref_name }}-cu129" \
--repo ${{ github.repository }} \
--latest=false \
--title "Release ${{ github.ref_name }} · CUDA 12.9" \
--notes "CUDA 12.9 wheel for LMCache ${{ github.ref_name }}.
\`\`\`
VERSION=${{ github.ref_name }}
uv pip install lmcache==${VERSION#v} \\
--extra-index-url https://download.pytorch.org/whl/cu129 \\
--find-links https://github.com/${{ github.repository }}/releases/expanded_assets/${VERSION}-cu129 \\
--index-strategy unsafe-best-match
\`\`\`" \
dist/*.whl
# Build container image and push to DockerHub when:
# - a new stable GitHub release is created (non-RC)
publish-image:
name: Publish image to DockerHub
if: |
needs.publish-pypi.result == 'success' &&
!cancelled() &&
github.repository_owner == 'LMCache' && github.event.action == 'published' &&
!contains(github.ref_name, 'rc')
runs-on: ubuntu-latest
needs: [publish-pypi, publish-cu129-github-release]
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Login to DockerHub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
# for setuptools-scm
fetch-depth: 0
- name: Free disk space
uses: ./.github/actions/free-disk-space
- name: Resolve the release tag that triggered this run
run: |
echo "LATEST_TAG=${{ github.ref_name }}" >> "$GITHUB_ENV"
- name: Build lmcache/vllm-openai container image (cu13)
run: |
docker build \
--build-arg BASE_IMAGE=nvcr.io/nvidia/cuda-dl-base:25.09-cuda13.0-devel-ubuntu24.04 \
--build-arg CUDA_VERSION=13.0 \
--build-arg LMCACHE_VERSION=${{ env.LATEST_TAG }} \
--build-arg torch_cuda_arch_list='7.5 8.0 8.6 8.9 9.0 10.0 12.0+PTX' \
--target image-release \
--tag lmcache/vllm-openai:latest --tag lmcache/vllm-openai:${{ env.LATEST_TAG }} \
--file docker/Dockerfile .
# `lmcache --help` exercises eager CLI subcommand discovery,
# so a missing runtime dep fails the build before the image ships.
- name: Smoke test lmcache/vllm-openai (cu13)
run: |
docker run --rm --entrypoint lmcache lmcache/vllm-openai:${{ env.LATEST_TAG }} --help
- name: Push lmcache/vllm-openai container image to DockerHub
run: |
docker push lmcache/vllm-openai:latest
docker push lmcache/vllm-openai:${{ env.LATEST_TAG }}
- name: Clean up Docker layers and cache
run: |
docker system prune -f --all
docker builder prune -af
- name: Build lmcache/vllm-openai:lightweight
run: |
docker build \
--tag lmcache/vllm-openai:lightweight --tag lmcache/vllm-openai:${{ env.LATEST_TAG }}-lightweight \
--file docker/Dockerfile.lightweight .
- name: Smoke test lmcache/vllm-openai:lightweight
run: |
docker run --rm --entrypoint lmcache lmcache/vllm-openai:${{ env.LATEST_TAG }}-lightweight --help
- name: Push lmcache/vllm-openai:lightweight image to DockerHub
run: |
docker push lmcache/vllm-openai:lightweight
docker push lmcache/vllm-openai:${{ env.LATEST_TAG }}-lightweight
- name: Clean up Docker layers and cache
run: |
docker system prune -f --all
docker builder prune -af
- name: Build lmcache/standalone container image (cu13)
run: |
docker build \
--build-arg BASE_IMAGE=nvcr.io/nvidia/cuda-dl-base:25.09-cuda13.0-devel-ubuntu24.04 \
--build-arg CUDA_VERSION=13.0 \
--build-arg torch_cuda_arch_list='7.5 8.0 8.6 8.9 9.0 10.0 12.0+PTX' \
--target lmcache-final \
--tag lmcache/standalone:latest --tag lmcache/standalone:${{ env.LATEST_TAG }} \
--tag lmcache/standalone:latest-cu130 --tag lmcache/standalone:${{ env.LATEST_TAG }}-cu130 \
--file docker/Dockerfile.standalone .
- name: Smoke test lmcache/standalone (cu13)
run: |
docker run --rm --entrypoint lmcache lmcache/standalone:${{ env.LATEST_TAG }} --help
- name: Push lmcache/standalone container image to DockerHub
run: |
docker push lmcache/standalone:latest
docker push lmcache/standalone:${{ env.LATEST_TAG }}
docker push lmcache/standalone:latest-cu130
docker push lmcache/standalone:${{ env.LATEST_TAG }}-cu130
- name: Clean up Docker layers and cache
run: |
docker system prune -f --all
docker builder prune -af
- name: Build lmcache/vllm-openai container image (cu12.9)
if: needs.publish-cu129-github-release.result == 'success'
run: |
docker build \
--build-arg BASE_IMAGE=nvcr.io/nvidia/cuda-dl-base:25.05-cuda12.9-devel-ubuntu24.04 \
--build-arg CUDA_VERSION=12.9 \
--build-arg LMCACHE_VERSION=${{ env.LATEST_TAG }} \
--target image-release-cu129 \
--tag lmcache/vllm-openai:latest-cu129 --tag lmcache/vllm-openai:${{ env.LATEST_TAG }}-cu129 \
--file docker/Dockerfile .
- name: Smoke test lmcache/vllm-openai (cu12.9)
if: needs.publish-cu129-github-release.result == 'success'
run: |
docker run --rm --entrypoint lmcache lmcache/vllm-openai:${{ env.LATEST_TAG }}-cu129 --help
- name: Push lmcache/vllm-openai cu129 container image to DockerHub
if: needs.publish-cu129-github-release.result == 'success'
run: |
docker push lmcache/vllm-openai:latest-cu129
docker push lmcache/vllm-openai:${{ env.LATEST_TAG }}-cu129
- name: Clean up Docker layers and cache
run: |
docker system prune -f --all
docker builder prune -af
- name: Build lmcache/standalone container image (cu12.9)
if: needs.publish-cu129-github-release.result == 'success'
run: |
docker build \
--build-arg BASE_IMAGE=nvcr.io/nvidia/cuda-dl-base:25.05-cuda12.9-devel-ubuntu24.04 \
--build-arg CUDA_VERSION=12.9 \
--target lmcache-final \
--tag lmcache/standalone:latest-cu129 --tag lmcache/standalone:${{ env.LATEST_TAG }}-cu129 \
--file docker/Dockerfile.standalone .
- name: Smoke test lmcache/standalone (cu12.9)
if: needs.publish-cu129-github-release.result == 'success'
run: |
docker run --rm --entrypoint lmcache lmcache/standalone:${{ env.LATEST_TAG }}-cu129 --help
- name: Push lmcache/standalone cu129 container image to DockerHub
if: needs.publish-cu129-github-release.result == 'success'
run: |
docker push lmcache/standalone:latest-cu129
docker push lmcache/standalone:${{ env.LATEST_TAG }}-cu129
# Build RC container image and push to DockerHub when:
# - a new RC GitHub release is created (e.g. v0.4.8rc1)
# Only pushes lmcache/vllm-openai:<tag> — does not touch latest or other image variants.
publish-image-rc:
name: Publish RC image to DockerHub
if: |
needs.publish-pypi.result == 'success' &&
!cancelled() &&
github.repository_owner == 'LMCache' && github.event.action == 'published' &&
contains(github.ref_name, 'rc')
runs-on: ubuntu-latest
needs: [publish-pypi]
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: Login to DockerHub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: Free disk space
uses: ./.github/actions/free-disk-space
- name: Get the release tag
run: |
REF="${{ github.ref_name }}"
[[ "$REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+rc[0-9]+$ ]] && echo "LATEST_RC_TAG=$REF" >> "$GITHUB_ENV"
- name: Build lmcache/vllm-openai container image (cu13)
run: |
docker build \
--build-arg BASE_IMAGE=nvcr.io/nvidia/cuda-dl-base:25.09-cuda13.0-devel-ubuntu24.04 \
--build-arg CUDA_VERSION=13.0 \
--build-arg LMCACHE_VERSION=${{ env.LATEST_RC_TAG }} \
--build-arg torch_cuda_arch_list='7.5 8.0 8.6 8.9 9.0 10.0 12.0+PTX' \
--target image-release \
--tag lmcache/vllm-openai:${{ env.LATEST_RC_TAG }} \
--file docker/Dockerfile .
- name: Smoke test lmcache/vllm-openai (cu13)
run: |
docker run --rm --entrypoint lmcache lmcache/vllm-openai:${{ env.LATEST_RC_TAG }} --help
- name: Push lmcache/vllm-openai RC image to DockerHub
run: |
docker push lmcache/vllm-openai:${{ env.LATEST_RC_TAG }}
+84
View File
@@ -0,0 +1,84 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '23 8 * * 6'
push:
branches: [ "dev" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
# `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
# Uncomment the permissions below if installing in a private repository.
# contents: read
# actions: read
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: false
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore
# file_mode: git
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif
+51
View File
@@ -0,0 +1,51 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
env:
LC_ALL: en_US.UTF-8
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
stale:
permissions:
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
disable-sudo-and-containers: true
egress-policy: audit
- name: "Stale Action"
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with:
stale-issue-label: 'stale'
stale-issue-message: >
This issue has been automatically marked as stale because it has not had activity within 60 days.
It will be automatically closed if no further activity occurs within 30 days.
close-issue-message: >
This issue has been automatically closed due to inactivity.
Please feel free to reopen if you feel it is still relevant!
days-before-issue-stale: 60
days-before-issue-close: 30
stale-pr-label: 'stale'
stale-pr-message: >
This pull request has been automatically marked as stale because it has not had activity within 60 days.
It will be automatically closed if no further activity occurs within 30 days.
close-pr-message: >
This pull request has been automatically closed due to inactivity.
Please feel free to reopen if you intend to continue working on it!
days-before-pr-stale: 60
days-before-pr-close: 30
operations-per-run: 300
+147
View File
@@ -0,0 +1,147 @@
name: Sync Torch Version
on:
workflow_dispatch:
schedule:
- cron: "0 2 * * 1"
permissions:
contents: write
pull-requests: write
concurrency:
group: sync-torch-version
cancel-in-progress: false
jobs:
sync:
name: Sync torch pin with vLLM
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out dev branch
uses: actions/checkout@v4
with:
ref: dev
token: ${{ github.token }}
fetch-depth: 0
- name: Sync torch version with vLLM
id: sync
shell: bash
run: |
set -Eeuo pipefail
readonly UPSTREAM_URL="https://raw.githubusercontent.com/vllm-project/vllm/main/pyproject.toml"
readonly TARGET_FILE="pyproject.toml"
tmp_upstream="$(mktemp)"
tmp_output="$(mktemp)"
cleanup() {
rm -f "$tmp_upstream" "$tmp_output"
}
trap cleanup EXIT
echo "Fetching upstream pyproject.toml from vLLM..."
curl \
--fail \
--show-error \
--silent \
--location \
--retry 3 \
--retry-delay 2 \
--connect-timeout 10 \
"$UPSTREAM_URL" \
-o "$tmp_upstream"
extract_torch_version() {
local file="$1"
sed -nE 's/.*torch[[:space:]]*==[[:space:]]*([0-9]+(\.[0-9]+){1,2}).*/\1/p' "$file"
}
mapfile -t upstream_matches < <(extract_torch_version "$tmp_upstream")
mapfile -t current_matches < <(extract_torch_version "$TARGET_FILE")
if [[ "${#upstream_matches[@]}" -ne 1 ]]; then
echo "Expected exactly one torch pin in upstream file, found ${#upstream_matches[@]}"
printf 'Matches: %s\n' "${upstream_matches[@]:-<none>}"
exit 1
fi
if [[ "${#current_matches[@]}" -ne 1 ]]; then
echo "Expected exactly one torch pin in ${TARGET_FILE}, found ${#current_matches[@]}"
printf 'Matches: %s\n' "${current_matches[@]:-<none>}"
exit 1
fi
upstream="${upstream_matches[0]}"
current="${current_matches[0]}"
echo "Current torch version : $current"
echo "Upstream torch version: $upstream"
{
echo "current=$current"
echo "upstream=$upstream"
} >> "$GITHUB_OUTPUT"
if [[ "$upstream" == "$current" ]]; then
echo "Torch version already up to date."
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
awk -v new_ver="$upstream" '
BEGIN { updated = 0 }
{
if ($0 ~ /torch[[:space:]]*==[[:space:]]*[0-9]+(\.[0-9]+){1,2}/) {
sub(/torch[[:space:]]*==[[:space:]]*[0-9]+(\.[0-9]+){1,2}/, "torch==" new_ver)
updated++
}
print
}
END {
if (updated != 1) {
printf("Expected to update exactly one torch pin, updated %d\n", updated) > "/dev/stderr"
exit 1
}
}
' "$TARGET_FILE" > "$tmp_output"
mv "$tmp_output" "$TARGET_FILE"
if git diff --quiet -- "$TARGET_FILE"; then
echo "No file change detected after update."
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "changed=true" >> "$GITHUB_OUTPUT"
- name: Create pull request
if: steps.sync.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v8
with:
token: ${{ github.token }}
branch: automation/sync-torch-version
base: dev
delete-branch: true
signoff: true
add-paths: |
pyproject.toml
commit-message: "[Build] sync torch version with vLLM (${{
steps.sync.outputs.upstream }})"
title: "[Build] sync torch version with vLLM (${{
steps.sync.outputs.upstream }})"
body: |
Auto-sync the pinned torch version in `pyproject.toml` with vLLM.
- Current: `${{ steps.sync.outputs.current }}`
- Upstream: `${{ steps.sync.outputs.upstream }}`
This PR was generated automatically by GitHub Actions.
labels: |
dependencies
automation
+170
View File
@@ -0,0 +1,170 @@
name: Test
on:
workflow_call:
workflow_dispatch:
pull_request:
branches:
- "dev"
- "release-**"
env:
LC_ALL: en_US.UTF-8
PYTHONHASHSEED: 0
defaults:
run:
shell: bash
# Cancel older in-flight runs on the same PR / branch.
concurrency:
group: test-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
lmcache: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'release' || steps.filter.outputs.lmcache == 'true' }}
raw_block: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'release' || steps.filter.outputs.raw_block == 'true' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
if: github.event_name == 'push'
with:
fetch-depth: 2
- uses: dorny/paths-filter@v3
if: github.event_name == 'push' || github.event_name == 'pull_request'
id: filter
with:
filters: |
lmcache:
- '**.py'
- 'pyproject.toml'
- 'requirements/**.txt'
- '.github/workflows/test.yml'
- '!operator/**'
raw_block:
- 'rust/raw_block/**'
- 'lmcache/v1/storage_backend/raw_block/**'
- 'lmcache/v1/distributed/l2_adapters/raw_block_l2_adapter.py'
- 'lmcache/v1/multiprocess/**'
- 'tests/v1/storage_backend/test_raw_block*.py'
- 'tests/v1/storage_backend/raw_block_test_utils.py'
- 'tests/v1/multiprocess/test_raw_block_l2_adapter.py'
- '.github/workflows/test.yml'
test:
needs: changes
if: needs.changes.outputs.lmcache == 'true'
name: "test: ${{ matrix.python }} on ${{ matrix.platform }}"
runs-on: "${{ matrix.platform }}"
strategy:
matrix:
python:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
platform:
- "ubuntu-latest"
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
# https://github.com/actions/checkout/issues/249
fetch-depth: 0
- name: Free disk space
uses: ./.github/actions/free-disk-space
- name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: ${{ matrix.python }}
cache: pip
cache-dependency-path: |
**/pyproject.toml
**/requirements/*.txt
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install vllm
python -m pip install -r requirements/test.txt
python -m pip install -r requirements/common.txt
- name: "Run non-CUDA unit tests"
run: |
pytest --ignore=tests/disagg \
--ignore=tests/v1/multiprocess/ \
--ignore=tests/v1/distributed/ \
--ignore=tests/v1/mp_observability/ \
--ignore=tests/skipped \
--ignore=tests/cli/commands/trace \
--ignore=tests/v1/storage_backend/test_eic.py \
--ignore=tests/v1/storage_backend/test_p2p_backend_with_controller.py
raw-block-tests:
needs: changes
if: needs.changes.outputs.raw_block == 'true'
name: raw-block temp-file tests
runs-on: ubuntu-latest
env:
LMCACHE_LOG_LEVEL: CRITICAL
steps:
- name: "Harden Runner"
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: Setup Python 3.11
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.11"
cache: pip
cache-dependency-path: |
**/pyproject.toml
**/requirements/*.txt
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install maturin
python -m pip install -r requirements/test.txt
python -m pip install -r requirements/common.txt
- name: Build raw-block Rust extension
run: |
python -m maturin build --release \
--manifest-path rust/raw_block/Cargo.toml \
--out .pytest-tmp/raw-block-dist
python -m pip install .pytest-tmp/raw-block-dist/*.whl
- name: Run raw-block tests
run: |
pytest -q --basetemp=.pytest-tmp/raw-block \
tests/v1/storage_backend/test_raw_block_device.py \
tests/v1/storage_backend/test_raw_block_core.py \
tests/v1/multiprocess/test_raw_block_l2_adapter.py
+109
View File
@@ -0,0 +1,109 @@
name: Update Chinese Documentation Translations
on:
schedule:
- cron: "0 9 * * 1"
workflow_dispatch:
inputs:
max_entries:
description: "Maximum entries to translate in this run"
required: false
default: "50"
concurrency:
group: update-chinese-docs
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
jobs:
translate-docs:
name: Update Chinese docs translations
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
ref: dev
- name: Setup Python 3.13
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.13"
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements/docs.txt
- name: Reuse translations from open PR
env:
GH_TOKEN: ${{ github.token }}
run: |
# Reuse the automation branch only while its PR is open; a closed PR's
# branch is stale and would overwrite human edits merged to dev.
open_prs=$(gh pr list --repo "$GITHUB_REPOSITORY" \
--head automation/update-chinese-docs --state open \
--json number --jq 'length' || echo 0)
if [ "${open_prs:-0}" -gt 0 ] && \
git fetch origin automation/update-chinese-docs 2>/dev/null; then
echo "Open translation PR found; reusing its translations."
git checkout FETCH_HEAD -- docs/source/locale/zh_CN/LC_MESSAGES/ || true
else
echo "No open translation PR; using dev as the translation base."
fi
- name: Update gettext catalogs
run: |
sphinx-build -b gettext docs/source docs/build/gettext
sphinx-intl update -p docs/build/gettext -l zh_CN -d docs/source/locale -j 1
- name: Delete translations for renamed or removed docs
run: |
find docs/source/locale/zh_CN/LC_MESSAGES -name '*.po' | while read -r po; do
rel="${po#docs/source/locale/zh_CN/LC_MESSAGES/}"
if [ ! -f "docs/build/gettext/${rel%.po}.pot" ]; then
echo "deleting $po (no matching source doc)"
git rm -fq --ignore-unmatch "$po"
fi
done
- name: Translate missing Chinese entries
env:
TRANSLATION_API_BASE_URL: ${{ secrets.TRANSLATION_API_BASE_URL }}
TRANSLATION_API_KEY: ${{ secrets.TRANSLATION_API_KEY }}
TRANSLATION_MODEL: ${{ secrets.TRANSLATION_MODEL }}
TRANSLATION_MAX_ENTRIES: ${{ inputs.max_entries || '500' }}
run: |
python tools/translate_docs_zh.py
- name: Validate Chinese docs build
run: |
SPHINX_LANGUAGE=zh_CN sphinx-build -b html docs/source docs/build/html/zh_CN
- name: Open or update translation PR
uses: peter-evans/create-pull-request@v8
with:
token: ${{ github.token }}
branch: automation/update-chinese-docs
base: dev
delete-branch: true
signoff: true
add-paths: |
docs/source/locale/zh_CN/LC_MESSAGES
commit-message: "Update Chinese documentation translations"
title: "Update Chinese documentation translations"
body: |
Automated weekly update for Chinese documentation translations.
Generated by `.github/workflows/translate_doc_zh.yml`.
labels: |
documentation
automation