Prhub

#51911 [CI] Add registry layer cache to x86 CPU image build

原始 PR 作者 bigPYJ1151 合并时间 2026-08-12 17:58 文件变更 2 提交数 12 评论 17 代码增减 +172 / -24

执行摘要

CPU CI 镜像构建接入 buildx 注册表缓存与 sccache

PR body 明确描述问题:CPU CI 镜像构建 “used classic docker build with no layer cache, so every build recompiled csrc/rust from scratch”,而 CUDA 镜像构建(image_build.sh)已经具备 registry 层缓存,CPU 路径是明显缺口。实现过程中又发现仅 registry 层缓存不够——rust 与 C++ 编译使用 BuildKit cache mount(ccache、cargo registry)作为对象存储,--cache-to type=registry 不会导出 cache mount,换一台构建机即失效,因此同步引入 S3 后端 sccache(USE_SCCACHE=1)保证跨 builder 命中。相关 PR #46711 只覆盖了 x86 CUDA release 镜像构建,本 PR 恰好补齐 CPU CI(非 release)镜像构建的空缺。

推荐负责 CI 与镜像构建的工程师精读本 PR。值得学习的点:① buildx type=registry 缓存与 BuildKit cache mount 的边界——cache mount 不被 registry 缓存导出,跨 builder 命中必须依赖 sccache 这类远端缓存;② docker-container builder 的网络隔离对 IMDS/S3 的影响及 network=host 的权衡;③ 缓存 key 的分支/PR 回退设计(CACHE_FROM_BASE_BRANCHCACHE_FROM_MAIN 去重与兜底)。后续可考虑将 prepare_cache_tags 抽象为多平台共享脚本,并补充 arm64 构建验证。

讨论亮点

该 PR 没有人类 review 评论线程:reviewer jikunshang 直接 approve(无评论),claude[bot] 因 PR 来自 fork 而跳过自动 review。值得注意的“讨论”发生在开发过程中并已通过 commit 自述固化:① 空 SCCACHE_ENDPOINT 被无条件导出,导致 sccache 以非法自定义 S3 endpoint URI 启动崩溃,修复为仅非空时导出;② docker-container builder 处于独立网络命名空间,无法访问 EC2 IMDS 获取 S3 凭证,每次编译都写缓存失败,最终用 network=host 解决;③ 早期使用本地 --mount=type=cache(ccache/cargo registry),发现其不会被 --cache-to type=registry 导出,因此必须引入 S3 后端 sccache 才能跨 builder 命中。这些属于值得复用的排障经验。

实现拆解

  1. 缓存 key 解析(脚本层):在 image_build_cpu.sh 移植 clean_docker_tag()prepare_cache_tags(),按 BUILDKITE_PULL_REQUESTBUILDKITE_BRANCH 区分 main 分支、feature 分支 push、PR 合入 main、PR 合入非 main 分支四种场景,得到 CACHE_TO / CACHE_FROM / CACHE_FROM_BASE_BRANCH / CACHE_FROM_MAIN,并对 cache-from ref 去重。
  2. ECR 复用与登录:缓存写入 vllm-ci-test-cache / vllm-ci-postmerge-cache 私有 ECR,所有 tag 统一带 -x86_cpu 后缀;脚本新增 aws ecr get-login-password 私有 ECR 登录。
  3. 构建命令迁移docker build 换为 docker buildx build --push--cache-to type=registry,mode=max--cache-from 依次回退到 PR 专属缓存、base 分支缓存、main 缓存;创建 docker-container 驱动的 builder vllm-cpu-builder,并用 --driver-opt network=host 保证 sccache 可访问 EC2 实例元数据服务(IMDS)。
  4. Dockerfile 阶段重构:新增 base 阶段安装公共包与可选 sccache;base-commonrust-buildFROM base 并行;为三个编译阶段注入 USE_SCCACHESCCACHE_ENDPOINTSCCACHE_BUCKET 等 ARG/ENV,rust 用 RUSTC_WRAPPER=sccache,triton-cpu 用 CMAKE_C/CXX_COMPILER_LAUNCHER=sccache,并让 rust-build 的 SCCACHE_SERVER_PORT=4227 避免与主构建阶段端口冲突。
  5. 验证与配套bash -n 语法检查、prepare_cache_tags() 四场景推演、本地 registry:2 干跑验证缓存 round-trip;真实 ECR 路径通过多轮 /ci run 在 Buildkite 上验证,期间的临时验证 commit 全部 revert。无运行时、测试或模型配套改动。
文件 模块 状态 重要度
.buildkite/image_build/image_build_cpu.sh 构建脚本 modified 5.21
docker/Dockerfile.cpu 镜像构建 modified 5.2

关键符号

clean_docker_tag prepare_cache_tags

关键源码片段

.buildkite/image_build/image_build_cpu.sh core-logic

CPU CI 镜像构建入口脚本,从经典 docker build 切换为 buildx registry 缓存构建,新增 clean_docker_tag / prepare_cache_tags 缓存 key 解析、私有 ECR 登录与 sccache 参数传递,是本 PR 的核心控制逻辑。

# clean_docker_tag: 将任意字符串规整为合法 Docker tag,
# 非 [a-zA-Z0-9._-] 字符替换为下划线,并截断到 128 字符上限
clean_docker_tag() {
  local input="$1"
  echo "$input" | sed 's/[^a-zA-Z0-9._-]/_/g' | cut -c1-128
}# prepare_cache_tags: 解析并设置 CACHE_TO / CACHE_FROM 等全局缓存引用,
# 复用 CUDA 镜像构建的 ECR 缓存仓库,所有 tag 加 -x86_cpu 后缀与 CUDA 隔离
prepare_cache_tags() {
  TEST_CACHE_ECR="936637512419.dkr.ecr.us-east-1.amazonaws.com/vllm-ci-test-cache"
  MAIN_CACHE_ECR="936637512419.dkr.ecr.us-east-1.amazonaws.com/vllm-ci-postmerge-cache"  if [[ "${BUILDKITE_PULL_REQUEST:-false}" == "false" ]]; then
    if [[ "${BUILDKITE_BRANCH:-}" == "main" ]]; then
      # main 分支直接命中 postmerge 长期缓存
      cache="${MAIN_CACHE_ECR}:latest-x86_cpu"
    else
      # 非 main 分支 push 时用清理后的分支名作为缓存 key
      clean_branch=$(clean_docker_tag "${BUILDKITE_BRANCH:-unknown}")
      cache="${TEST_CACHE_ECR}:${clean_branch}-x86_cpu"
    fi
    CACHE_TO="$cache"
    CACHE_FROM="$cache"
    CACHE_FROM_BASE_BRANCH="$cache"
  else
    # PR 构建:写入 PR 专属缓存,回退链为 base 分支 → main
    CACHE_TO="${TEST_CACHE_ECR}:pr-${BUILDKITE_PULL_REQUEST}-x86_cpu"
    CACHE_FROM="${TEST_CACHE_ECR}:pr-${BUILDKITE_PULL_REQUEST}-x86_cpu"
    if [[ "${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-main}" == "main" ]]; then
      CACHE_FROM_BASE_BRANCH="${MAIN_CACHE_ECR}:latest-x86_cpu"
    else
      clean_base=$(clean_docker_tag "${BUILDKITE_PULL_REQUEST_BASE_BRANCH}")
      CACHE_FROM_BASE_BRANCH="${TEST_CACHE_ECR}:${clean_base}-x86_cpu"
    fi
  fi
  # main 兜底,保证任何构建都能从最近一次 main 缓存开始
  CACHE_FROM_MAIN="${MAIN_CACHE_ECR}:latest-x86_cpu"
}
docker/Dockerfile.cpu infrastructure

CPU 镜像的 Dockerfile,新增公共 base 阶段、将 rust-build 与 base-common 并行化,并在三个编译阶段注入 sccache,是跨 builder 编译缓存能否命中的关键。

# 公共 base 阶段:安装所有子阶段共享的 apt 包与可选 sccache。
# rust-build 刻意不 FROM base-common,而是 FROM base,保持最小依赖,
# 以便与 vllm-build 并行构建;sccache 的 S3 后端让编译缓存跨 BuildKit builder 存活。
FROM ubuntu:22.04 AS baseRUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update -y \
    && apt-get install -y --no-install-recommends ca-certificates curl git
​
ARG TARGETARCH
ARG USE_SCCACHE
ARG SCCACHE_DOWNLOAD_URL
​
# 按目标架构下载 sccache 二进制(amd64 用 x86_64,arm64 用 aarch64)
RUN if [ "$USE_SCCACHE" = "1" ]; then \
      echo "Installing sccache..." \
      && case "${TARGETARCH}" in \
        arm64) SCCACHE_ARCH="aarch64" ;; \
        amd64) SCCACHE_ARCH="x86_64" ;; \
        *) echo "Unsupported TARGETARCH for sccache: ${TARGETARCH}" >&2; exit 1 ;; \
      esac \
      && export SCCACHE_DOWNLOAD_URL="${SCCACHE_DOWNLOAD_URL:-https://github.com/mozilla/sccache/releases/download/v0.8.1/sccache-v0.8.1-${SCCACHE_ARCH}-unknown-linux-musl.tar.gz}" \
      && curl -L -o sccache.tar.gz "${SCCACHE_DOWNLOAD_URL}" \
      && tar -xzf sccache.tar.gz \
      && mv sccache-v0.8.1-${SCCACHE_ARCH}-unknown-linux-musl/sccache /usr/bin/sccache \
      && rm -rf sccache.tar.gz sccache-v0.8.1-${SCCACHE_ARCH}-unknown-linux-musl; \
    fi# rust-build 段的 sccache 接线:通过 ARG 传入 S3 bucket 与 endpoint,
# 用 RUSTC_WRAPPER 包装 cargo 编译,使 rust 产物缓存落在 S3 而非本地 cache mount。
FROM base AS rust-build
ARG USE_SCCACHE
ARG SCCACHE_ENDPOINT
ARG SCCACHE_BUCKET_NAME=vllm-build-sccache
ARG SCCACHE_REGION_NAME=us-west-2
​
ENV SCCACHE_BUCKET=${USE_SCCACHE:+${SCCACHE_BUCKET_NAME}}
ENV SCCACHE_REGION=${USE_SCCACHE:+${SCCACHE_REGION_NAME}}
# 与 vllm-build 阶段的 sccache 守护进程错开端口,避免同一构建机上的端口冲突
ENV SCCACHE_SERVER_PORT=4227RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \
    --mount=type=cache,target=/root/.cargo/git,sharing=locked \
    --mount=type=secret,id=aws-credentials,target=/root/.aws/credentials,required=false \
    if [ "$USE_SCCACHE" = "1" ]; then \
      export RUSTC_WRAPPER=sccache; \
      if [ -n "${SCCACHE_ENDPOINT}" ]; then export SCCACHE_ENDPOINT="${SCCACHE_ENDPOINT}"; fi; \
      sccache --show-stats; \
    fi && \
    bash build_rust.sh && \
    if [ "$USE_SCCACHE" = "1" ]; then sccache --show-stats; fi

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

主要风险集中在构建链路本身:

  • 构建脚本行为变更image_build_cpu.shdocker build 改为 docker buildx build,且每次运行会 docker buildx rm vllm-cpu-builder 重建 builder;若 buildx 初始化或 registry 缓存读写失败(如 ECR 凭证过期),CPU 镜像构建会直接失败,影响 CI 可用性。
  • 缓存隔离依赖命名约定:CPU 与 CUDA 共用同一批 ECR 仓库,仅靠 -x86_cpu tag 后缀隔离,若后续其他平台(如 XPU/HPU)也复用,需要保证后缀唯一;clean_docker_tag 把非法字符替换为 _,分支名碰撞可能让不同分支共享同一缓存 key,存在轻微互相污染风险。
  • sccache 的 S3 依赖Dockerfile.cpu 新增 SCCACHE_BUCKET/SCCACHE_REGION 等环境变量与 aws-credentials secret mount,构建机必须能访问 IMDS 与 S3;在非 AWS 或网络受限环境(如本地调试、部分社区 CI)会出现缓存写失败,虽然 commit 中已做降级(required=false),但仍增加隐式外部依赖。
  • 多架构影响Dockerfile.cpu 的改动涉及 TARGETARCH 分支(arm64 交叉编译路径),base 阶段对 sccache 的架构选择若出错会直接 exit 1,需要 arm64 CPU 构建 CI 覆盖确认。
    • 不涉及运行时与模型代码,无推理延迟/精度回归风险。

影响范围限于 CI/镜像构建:

  • CI 耗时:CPU 镜像构建首次推送缓存后,后续构建的 csrc/rust/triton-cpu 编译层可跨 builder 复用,显著缩短 PR 验证等待时间(PR body 描述原先每次构建都全量重编)。
  • 基础设施:新增对私有 ECR 缓存仓库的读写权限与 vllm-build-sccache S3 bucket 依赖;ECR 存储随 mode=max 缓存层增长。
  • 团队:Intel/CPU CI 维护者受益最大,后续可参照相同模式为 XPU/HPU 或 release 镜像补齐缓存;对普通用户无感。
CI 关键路径变更 新增私有 ECR/S3 依赖 缓存隔离依赖 tag 约定 多架构路径需验证

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论