LMCache 的 K3s 构建基 CI Harness 架构:单节点 Kubernetes 之上的 GPU 测试集群设计与实现
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
本文系统梳理 LMCache 项目中基于 K3s 的 Buildkite CI Harness(CI 基架)的整体架构:它如何在任意一台裸机 Linux GPU 机器上,通过一条脚本拉起一套完整的 CI 测试集群,并以"临时 Pod"方式执行每个测试任务。读完本文,你将掌握这套 CI 体系的核心设计决策(GPU Operator、agent-stack-k8s、本地基础镜像、共享卷)、节点初始化与 CI 执行的完整流程,以及 setup 脚本、环境准备脚本、GPU 监控与 teardown 的实操细节,可以直接复用到自己的 GPU CI 场景。
一、为什么 LMCache 需要一套 K3s CI Harness
LMCache 是一个面向 LLM 的 KV Cache 层,其 CI 需要频繁在真实 GPU 上跑 vLLM 集成测试、多进程测试与各类正确性验证。在引入这套 K3s 方案之前,传统的裸机 Buildkite agent 方式存在明显的痛点,ARCHITECTURE.md 总结了四点核心动机:
- 易于搭建(Ease of Setup):希望在任何一台裸机 Linux GPU 机器上,仅凭一个脚本(
setup-cluster.sh)就能拉起一个功能完整的 CI 节点,避免手动配置依赖或维护常驻的 Buildkite agent。 - 与机器无关(Machine Agnostic):不再依赖机器专属脚本(例如自定义的
pick-free-gpu.sh)。任何带 NVIDIA GPU 和 Docker 的机器上,CI 都以相同的方式运行。 - 干净环境(Clean Environments):消除测试运行之间的状态泄漏——没有共享的 pip/uv 缓存,没有残留文件,每个任务都拿到一个全新的环境。
- 自动化资源管理(Automated Resource Management):借助标准 Kubernetes 原语完成 GPU 分配、卷挂载与清理,而不是靠手写脚本去锁 GPU。
二、五项核心设计决策
1. K3s + NVIDIA GPU Operator
CI 集群采用K3s——一个轻量级、单节点的 Kubernetes 发行版。NVIDIA GPU Operator自动配置容器运行时并把 GPU 暴露给 K8s,抽象掉底层硬件差异、自动完成 GPU 发现。从 setup-cluster.sh 可以看到,GPU Operator 通过 Helm 安装,并显式设置driver.enabled=false(宿主机已装驱动)与toolkit.enabled=true,随后等待 device-plugin daemonset 就绪,再用kubectl get node -o jsonpath='{.items[0].status.allocatable.nvidia\.com/gpu}'确认可分配的 GPU 数量。
2. 基于临时 Pod 的执行(agent-stack-k8s)
不使用常驻的buildkite-agent二进制,而是采用 Buildkite 官方的agent-stack-k8s:一个 controller Pod 在 K3s 内轮询 Buildkite 队列,动态地为每个任务拉起一个临时的 K8s Pod 执行任务;任务结束后 Pod 被销毁。因此机器上不存在任何常驻 agent,也就没有 agent 生命周期管理、没有跨任务的环境污染。
3. 声明式 GPU 分配与自动并行
每个 pipeline step 在 Kubernetes Pod 规格中显式声明它需要的 GPU 数量:
resources: limits: nvidia.com/gpu: "2" # 请求 2 张 GPU由于 Kubernetes 负责原子化的资源调度,多 GPU 节点上的任务会自动并行。文档给出了一个直观的例子:若节点有 4 张 GPU,三个任务分别请求 1、2、1 张 GPU,K8s 会把它们同时调度运行;若再来一个请求 2 张 GPU 的任务,它会排队等待资源释放。这彻底消除了手动 GPU 锁机制(例如仓库中旧有的pick-free-gpu.sh、pick-free-gpu-amd.sh这类脚本)的需求。真实的 pipeline 用法可参考 unit/pipeline.yml:step 通过kubernetes插件指定podSpec,在limits.nvidia.com/gpu声明 GPU 数量,并用requests/limits声明 CPU 与内存。
4. 本地基础镜像与临时环境准备
为避免对 Docker registry 的依赖,setup-cluster.sh会自动检测宿主 GPU 的计算能力(compute capability),本地构建lmcache/ci-base:latest镜像,并直接导入 K3s 的 containerd。每个任务 Pod 使用该基础镜像,在运行时通过setup-env.sh动态安装 vLLM 与 LMCache。镜像本身不包含 vLLM/LMCache(见 ci-base.Dockerfile 的注释说明),只提供 CUDA 13.0.2 + Ubuntu 24.04 + Python 3.12 + uv + 构建依赖,并把requirements/*.txt中不常变的依赖预先装好。
5. 共享宿主机卷
像 HuggingFace 模型权重、数据集这类体积大、读多写少的目录,通过hostPath挂载进 Pod,避免重复下载、加速测试,同时任务环境本身保持无状态。在 unit/pipeline.yml 中可以看到三种典型卷:/data/huggingface挂到/root/.cache/huggingface(模型缓存)、/data/gds-scratch挂到/scratch(GDS 临时目录,注释特别说明不能用 subPath 否则会破坏 cuFile 的 fs-type 检测)、以及一个 16 GiB 的 tmpfsemptyDir挂到/dev/shm(规避 K8s 默认 64 MiB shm 导致 shm_allocator 测试 SIGBUS 的问题)。
三、整体架构流程
节点初始化(一次性 setup)
[ Raw Linux Host + NVIDIA GPU ] | | (run setup-cluster.sh) v +-----------------------------------------------------+ | K3s Cluster | | | | 1. Install K3s | | 2. Install Helm -> Deploy NVIDIA GPU Operator | | 3. Build CI Base Image (lmcache/ci-base:latest) | | 4. Import Base Image to K3s containerd | +-----------------------------------------------------+ | | (run install-agent-stack.sh) v [ agent-stack-k8s Controller Pod (Watches queue) ]对应的落地脚本是 setup-cluster.sh,它被设计为幂等(脚本头部注释明确 "Safe to re-run"):K3s 已运行则跳过安装;GPU Operator 已安装则跳过;基础镜像已导入 containerd 则跳过构建。关键细节包括:
- 安装 K3s 时使用
--disable=traefik(CI 场景不需要 ingress)与--write-kubeconfig-mode=644,并把KUBECONFIG=/etc/rancher/k3s/k3s.yaml持久化到~/.bashrc; - 通过
nvidia-smi --query-gpu=compute_cap自动探测计算能力,生成TORCH_CUDA_ARCH_LIST="${COMPUTE_CAP}+PTX"作为docker build的 build-arg,确保镜像中的 torch/CUDA 扩展与宿主机 GPU 匹配; - 镜像构建后用
docker save | k3s ctr images import -直接灌入 K3s containerd,全程不依赖外部 registry; - 末尾创建共享宿主机卷目录
/data/huggingface与/data/datasets,并打印集群就绪摘要(K3s 版本、GPU Operator 版本、GPU 数量与型号、arch list、镜像名)。
CI 执行流程
Buildkite Web UI | | 1. Job pushed to 'k8s' queue v agent-stack-k8s Controller (in K3s) | | 2. Reads job, creates ephemeral Pod requesting GPUs v +-------------------------------------------------------------+ | Job Pod (e.g., limits: nvidia.com/gpu: "1") | | | | - Mounts /data/huggingface from host | | - Runs setup-env.sh (Installs vLLM, LMCache) | | - Executes test script (e.g., pytest) | +-------------------------------------------------------------+ | | 3. Test finishes (Pass/Fail) v agent-stack-k8s Controller | | 4. Reports result to Buildkite | 5. Destroys the Pod (wipes environment) v Buildkite Web UI四、与 Buildkite 的集成方式
README.md 详细说明了这套方案与传统裸机 agent 的区别:机器上没有常驻 agent。agent-stack-k8s 的 controller Pod 轮询 Buildkite 队列,有任务出现就创建 Pod,任务结束就删除 Pod。
在 Buildkite Web UI 侧需要准备三件事:
- 创建一个队列:进入 Organization Settings → Default cluster → Queues → New Queue,创建名为
k8s的队列(名字可自选)。队列不需要任何配置,也不要注册任何 agent; - 获取 agent token:从 cluster 设置页复制 agent token;
- 获取 GitHub token:创建对仓库有读写权限的 PAT(或 fine-grained token),用于 HTTPS checkout 以及向
benchmarks-main分支推送 baseline。
然后执行 install-agent-stack.sh:
.buildkite/k3_harness/install-agent-stack.sh <BUILDKITE_AGENT_TOKEN> <GITHUB_TOKEN>若想使用k8s以外的队列名,通过环境变量指定:
BUILDKITE_QUEUE=my-queue .buildkite/k3_harness/install-agent-stack.sh <AGENT_TOKEN> <GITHUB_TOKEN>该脚本内部做了两件事:一是创建名为buildkite-git-creds的 K8s secret,包含两个键——.git-credentials(供 agent-stack-k8s 的 checkout 容器做 HTTPS clone,前缀点号对应 git-credential-store 的约定)与GITHUB_TOKEN(注入任务容器供 git push 使用);二是通过 Helm 从oci://ghcr.io/buildkite/helm/agent-stack-k8s安装/升级agent-stack-k8s(版本 0.38.0),设置agentToken、config.queue,并通过config.default-checkout-params.gitCredentialsSecret让 checkout 走 HTTPS。
Pipeline step 通过如下方式指向该队列:
agents: queue: "k8s" # must match the queue name五、每个任务的环境准备:setup-env.sh 深度拆解
每个 CI 任务都会先 source setup-env.sh 来安装 vLLM 与 LMCache:
command: | source .buildkite/k3_harness/setup-env.sh bash .buildkite/scripts/my-test.sh每个 Pod 拥有独立的临时文件系统,任务结束后被完全清空——没有共享 pip/uv 缓存,没有跨 Pod 争用。这个脚本远比"装两个包"复杂,它沉淀了大量真实 CI 踩坑经验,值得逐段理解:
- GPU 健康预检:脚本开头调用
helpers.sh中的check_gpu_health 80(见 helpers.sh)。若 Pod 分到的 GPU 空闲内存低于 80%(通常由宿主机上的残留进程导致),任务立即失败并给出明确信息,而不是在 setup 之后才撞上晦涩的 CUDA OOM。 - 运行时架构重探测:基础镜像可能在不同 GPU 主机上构建(例如 H200 的 sm_90 镜像无法在 A100 的 sm_80 上加载内核),因此脚本用
nvidia-smi重新获取运行时 GPU 的计算能力并导出TORCH_CUDA_ARCH_LIST,覆盖镜像构建时的值。 - 合并 PR 目标分支:调用
merge_pr_base_branch(helpers.sh 中定义),在任务 Pod 内把 PR 分支合并进目标分支再测试。 - vLLM 版本解析:source resolve-pinned-vllm.sh,按优先级解析要装的 vLLM nightly:显式
PINNED_VLLM_VERSION环境变量 > 从buildkite_latest_tested_vllm分支拉取的latest_tested_vllm.txt(canary 构建最近验证过的版本)> 空值回退到"最新 nightly"。pin 文件除了裸版本号,还携带short_sha、full_sha、archive_index_url等元数据,使安装侧无需额外 API 调用;USE_PINNED_VLLM=false可跳过 pin(canary 构建自身使用,避免"自我确认")。 - 字节码/缓存驱逐:CI 中曾出现
ImportError: cannot import name 'GenerationConfig' from 'transformers'——即使已安装文件明确包含该符号,同一安装配方在全新 venv 中总能成功,说明故障与基础镜像文件系统状态(残留__pycache__、overlayfs 部分升级)有关。因此脚本在安装前后各执行一次find ... -name __pycache__ -exec rm -rf和uv cache clean。 - vLLM nightly 安装(钉死 cu130 索引):基础镜像是
nvidia/cuda:13.0.2-devel-ubuntu24.04(系统 nvcc 13)。vLLM 通用 nightly 索引可能随机解析到 cu128 或 cu130 的 torch wheel,当解析到 cu128 时torch.utils.cpp_extension._check_cuda_version会因 CUDA 版本不匹配(13.0 vs 12.8)中止 LMCache 的可编辑安装。因此脚本强制使用--extra-index-url https://wheels.vllm.ai/nightly/cu130与https://download.pytorch.org/whl/cu130,并配合--reinstall-package transformers/tokenizers/huggingface-hub/safetensors/vllm强制重装,绕开基础镜像中的文件系统级不一致。若启用 pin,则优先使用archive_index_url指向的 commit 归档索引(vLLM nightly 索引只保留最新 wheel,一两天就滚动下线,但wheels.vllm.ai/<full-commit-sha>/<cuda>/是永久保留的 PEP 503 索引);旧格式 pin 文件则通过 GitHub commits API 展开短 SHA。 - vLLM CLI 探测与自愈:通过子进程执行
vllm --help来完整探测vllm serve的导入链(vllm.entrypoints.cli.main.main()会在函数体内触发from transformers import GenerationConfig, PretrainedConfig;仅import vllm.entrypoints.cli.main不会执行函数体,无法发现问题)。探测失败且为ModuleNotFoundError时,自动安装缺失模块(最多 5 次,如 vLLM nightly 中未声明的pandas);其他错误则输出一份完整的 transformers 诊断 dump(包版本、目录、_import_structure、_class_to_module等)并退出。 - torch/nvcc CUDA 版本一致性检查:
python片段对比torch.version.cuda与系统nvcc的主版本号,不匹配立即失败——否则这个错误会在 ninja 编译深处以晦涩的cusparse.h: No such file or directory出现。 - LMCache 从源码可编辑安装:设置
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_LMCACHE=0.0.0+ci(仓库带有nightly、nightly-cu13等非 PEP-440 tag,会击穿较新的 vcs_versioning 后端);uv pip install -e . --no-build-isolation后安装requirements/proto.txt并运行lmcache/v1/multiprocess/transport/grpc_impl/_proto_gen/_generate.py生成 gRPC 绑定;安装前后各uv pip freeze | sort一次并 diff,展示 LMCache 安装对依赖的改动。 - 安装后二次 CLI 探测:LMCache 可编辑安装可能为了满足
requirements/common.txt的版本上限而降级传递依赖,破坏 vLLM CLI 导入链。脚本再次执行probe_vllm_cli,失败则打印 traceback 与uv pip freeze直接退出,而不是让每个测试 harness 在wait_for_server超时 180 秒后才暴露问题。
最终脚本以python -c "import vllm; import lmcache; ..."验证环境就绪。此外,仓库还提供 setup-lmcache-only-env.sh(不需要 vLLM 的轻量任务,如单元测试)与 setup-sglang-env.sh、setup-blend-env.sh(特定任务场景)等变体。
六、共享卷与 GPU 分配细节
README 中给出了挂载进每个 Pod 的共享卷清单:
| Host | Container | What |
|---|---|---|
/data/huggingface | /root/.cache/huggingface | 模型权重(读密集、写一次) |
/data/datasets | /root/correctness | 测试数据集(下载后只读) |
GPU 分配方式为每个 pipeline step 显式声明:
plugins: - kubernetes: podSpec: containers: - resources: limits: nvidia.com/gpu: "2" # 1 或 2GPU 的原子化分配由 K8s device plugin 完成,不再需要pick-free-gpu.sh之类的脚本。
七、CI 基础镜像的构建与重建
ci-base.Dockerfile 基于nvidia/cuda:13.0.2-devel-ubuntu24.04,安装 ccache、git、curl、jq、lsof、ffmpeg、libnuma1、libcudart12 等系统依赖,安装 uv,创建/opt/venv,并预装requirements/cuda.txt与requirements/build.txt。TORCH_CUDA_ARCH_LIST作为 build-arg 在构建时写入环境变量,与 CI 机器的 GPU 匹配。
基础镜像默认由setup-cluster.sh自动构建并导入 K3s containerd,无需 registry。修改requirements/*.txt或ci-base.Dockerfile后需要强制重建:
REBUILD_IMAGE=1 .buildkite/k3_harness/setup-cluster.sh八、集群验证、GPU 监控与 teardown
冒烟测试
smoke-test.sh 用于验证集群就绪:检查节点、检查可分配 GPU 数(少于 1 则失败),随后提交一个请求nvidia.com/gpu: "1"的nvidia/cuda:12.8.0-base-ubuntu24.04Pod 执行nvidia-smi,等待其 Succeeded 后输出日志并清理。
双层 GPU 监控
这套体系对 GPU 健康做了双层防护:
- 任务级:每个 CI 任务启动时通过
check_gpu_health(默认要求 80% 空闲内存)快速失败; - 宿主机级:gpu-monitor.sh 每 10 分钟扫描一次,通过遍历
/sys/fs/cgroup/*/kubepods*的 cgroup.procs、以及crictl ps/crictl inspect收集 K8s 容器 PID,与nvidia-smi --query-compute-apps=pid的 GPU 进程集合做差集,找出不属于任何 K8s Pod 的残留 GPU 进程,并记录 PID、命令、GPU bus、显存占用与进程年龄(从/proc/$pid/stat的 start time 推算),输出到/var/log/gpu-monitor.log。安装方式:
# 安装每 10 分钟检查一次残留进程的 cron 任务 sudo bash .buildkite/k3_harness/setup-gpu-monitor.shsetup-gpu-monitor.sh会同时配置 logrotate(每日轮转、保留 7 份、压缩)。查看日志与移除 cron:
# 查看监控日志 tail -f /var/log/gpu-monitor.log # 移除 cron 任务 crontab -l 2>/dev/null | grep -v gpu-monitor.sh | crontab -完整 teardown
teardown.sh 按序卸载 agent-stack-k8s、GPU Operator(含命名空间),最后调用k3s-uninstall.sh移除 K3s。/data/*宿主数据卷被保留,便于下次重建集群后继续复用模型与数据集缓存。
九、k3_harness 目录速览
.buildkite/k3_harness/ ├── ci-base.Dockerfile # CI 基础镜像定义(CUDA 13 + Python 3.12 + uv,不含 vLLM/LMCache) ├── setup-cluster.sh # 一次性:K3s + GPU Operator + 基础镜像构建与导入(幂等) ├── install-agent-stack.sh # 一次性:安装 agent-stack-k8s(需要 agent token + GitHub token) ├── values.yaml # 参考 Helm values(仅文档用途) ├── setup-env.sh # 每任务:安装 vLLM + LMCache(含 GPU 健康检查与多级自检) ├── resolve-pinned-vllm.sh # 解析 vLLM nightly pin 版本 ├── setup-lmcache-only-env.sh / setup-sglang-env.sh / setup-blend-env.sh # 场景化环境准备变体 ├── smoke-test.sh # 验证 GPU Pod 能在 K3s 中运行 ├── gpu-monitor.sh # 宿主机级:检测非 K8s 的残留 GPU 进程 ├── setup-gpu-monitor.sh # 将 gpu-monitor.sh 安装为 cron 任务 └── teardown.sh # 卸载全部组件(保留 /data/*)配合 k3_tests 目录下的各测试套件(unit、comprehensive、integration、multiprocess、correctness、blend、sglang、amd、musa、xpu 等)以及 pipelines 下的clean.yml、comprehensive-tests.yml、end-to-end-tests.yml、multiprocessing-test.yml,这套 K3s harness 构成了 LMCache 在真实 GPU 上持续验证的核心基座:任何机器、一条脚本、干净的临时环境、由 Kubernetes 原生保证的 GPU 并行与隔离。
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考