chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
@@ -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 what’s expected.
|
||||
|
||||
**Additional Context**
|
||||
Include any other context, references, or screenshots that would be useful.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
@@ -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}"
|
||||
@@ -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."
|
||||
@@ -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}"
|
||||
@@ -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}/"
|
||||
@@ -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__)"
|
||||
@@ -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())"
|
||||
Executable
+648
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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/
|
||||
@@ -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/
|
||||
@@ -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/
|
||||
@@ -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/
|
||||
@@ -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
|
||||
|
||||
@@ -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}}"
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "mypy",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.+):(\\d+):\\s(error|warning):\\s(.+)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"severity": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user