[CI/Build] Fix flaky failures in CPU CI image building (#55317)

Signed-off-by: jiang1.li <jiang1.li@intel.com>

Li, Jiang committed Sep 4, 2026 at 20:27 UTC 8340fe1bb99c187ebec08ad03bbe0d06b45d1d40
4 files changed +67 -13
.buildkite/scripts/hardware_ci/run-cpu-test.sh
+33 -4
@@ -15,12 +15,15 @@ TEST_COMMAND=$2
15 # Disk hygiene knobs. Reclaim space only once the Docker root filesystem crosses
16 # DISK_USAGE_THRESHOLD percent, and cap the shared BuildKit cache at
17 # BUILDKIT_CACHE_MAX so subsequent builds keep reusing the hottest layers.
18 -DISK_USAGE_THRESHOLD=${DISK_USAGE_THRESHOLD:-70}
19 -BUILDKIT_CACHE_MAX=${BUILDKIT_CACHE_MAX:-80GB}
18 +DISK_USAGE_THRESHOLD=${DISK_USAGE_THRESHOLD:-80}
19 +BUILDKIT_CACHE_MAX=${BUILDKIT_CACHE_MAX:-200GB}
20
21 # Reclaim disk only when the host is under pressure. We trim (not purge) the
22 # shared BuildKit cache so cross-job/cross-agent reuse stays intact, and only
23 # touch dangling images; other agents' uniquely tagged images are left alone.
24 +# Cache mounts (exec.cachemount: uv/cargo/apt) are excluded so they survive
25 +# pruning -- they're what let installs reuse packages instead of hitting the
26 +# network, and are otherwise reclaimable like any other build cache record.
27 prune_if_disk_pressure() {
28 local docker_root disk_usage
29 docker_root=$(docker info -f '{{.DockerRootDir}}' 2>/dev/null || true)
@@ -31,7 +34,7 @@ prune_if_disk_pressure() {
34 if [ "${disk_usage:-0}" -gt "$DISK_USAGE_THRESHOLD" ]; then
35 echo "--- :broom: Disk usage ${disk_usage}% exceeds ${DISK_USAGE_THRESHOLD}%, reclaiming space"
36 docker image prune -f || true
34 - docker builder prune -f --keep-storage="$BUILDKIT_CACHE_MAX" || true
37 + docker builder prune -f --keep-storage="$BUILDKIT_CACHE_MAX" --filter type!=exec.cachemount || true
38 else
39 echo "Disk usage ${disk_usage:-unknown}% within ${DISK_USAGE_THRESHOLD}% threshold; skipping prune"
40 fi
@@ -50,9 +53,35 @@ trap cleanup EXIT
53 # Free space up front so a nearly-full host doesn't fail the build.
54 prune_if_disk_pressure
55
56 +# Opportunistically warm the local base image cache; `docker build` below
57 +# omits `--pull` so it already prefers a cached image over the network. A
58 +# single attempt only -- the retry loop below covers a miss here too.
59 +echo "--- :docker: Pre-fetching base image"
60 +docker pull ubuntu:22.04 || true
61 +
62 # building the docker image
63 echo "--- :docker: Building Docker image"
55 -docker build --progress plain --tag "$IMAGE_NAME" --target vllm-test -f docker/Dockerfile.cpu .
64 +BUILD_RETRY_PATTERN='dial tcp|i/o timeout|failed to authorize|TLS handshake timeout|connection reset|Could not resolve host|Temporary failure in name resolution|dns error: failed to lookup address information|client error \(Connect\)|error sending request for url|Failed to fetch:'
65 +BUILD_MAX_ATTEMPTS=4 # 1 initial + 3 retries
66 +BUILD_RETRY_WAITS=(10 20 40) # seconds to wait before retry 1/2/3
67 +build_log="$(mktemp)"
68 +attempt=1
69 +while true; do
70 + if docker build --progress plain --tag "$IMAGE_NAME" --target vllm-test \
71 + --build-arg USE_SCCACHE=1 --build-arg SCCACHE_LOCAL_ONLY=1 \
72 + -f docker/Dockerfile.cpu . 2>&1 | tee "$build_log"; then
73 + break
74 + fi
75 + if [ "$attempt" -ge "$BUILD_MAX_ATTEMPTS" ] || ! grep -qE "$BUILD_RETRY_PATTERN" "$build_log"; then
76 + rm -f "$build_log"
77 + exit 1
78 + fi
79 + wait_s="${BUILD_RETRY_WAITS[$((attempt - 1))]}"
80 + echo "--- :docker: Transient network error during build (attempt $attempt/$BUILD_MAX_ATTEMPTS), retrying in ${wait_s}s"
81 + sleep "$wait_s"
82 + attempt=$((attempt + 1))
83 +done
84 +rm -f "$build_log"
85
86 # Run the image, setting --shm-size=4g for tensor parallel.
87 docker run --rm --cpuset-cpus="$CORE_RANGE" --cpuset-mems="$NUMA_NODE" -v ~/.cache/huggingface:/root/.cache/huggingface --privileged=true -e HF_TOKEN -e VLLM_CPU_KVCACHE_SPACE=16 -e VLLM_CPU_CI_ENV=1 -e VLLM_CPU_SIM_MULTI_NUMA=1 -e VLLM_CPU_ATTN_SPLIT_KV=0 --shm-size=4g "$IMAGE_NAME" \
csrc/cpu/sgl-kernels/mla_cache.cpp
+3 -3
@@ -59,9 +59,9 @@ void concat_and_cache_mla_kernel_impl(
59
60 // kv_c_normed : [num_tokens, kv_lora_rank]
61 // k_pe : [num_tokens, qk_rope_head_dim] or [num_tokens, 1,
62 -// qk_rope_head_dim] kv_cache : [num_blocks, block_size, kv_lora_rank +
63 -// qk_rope_head_dim] slot_mapping: [num_tokens] int32/int64, absolute physical
64 -// row index
62 +// qk_rope_head_dim]
63 +// kv_cache : [num_blocks, block_size, kv_lora_rank + qk_rope_head_dim]
64 +// slot_mapping: [num_tokens] int32/int64, absolute physical row index
65 // (block_id * block_size + block_offset); negative entries are
66 // skipped (padded tokens), matching `concat_and_cache_mla`.
67 void concat_and_cache_mla_cpu(const at::Tensor& kv_c_normed,
docker/Dockerfile.cpu
+27 -3
@@ -165,6 +165,9 @@ ARG USE_SCCACHE
165 ARG SCCACHE_ENDPOINT
166 ARG SCCACHE_BUCKET_NAME=vllm-build-sccache
167 ARG SCCACHE_REGION_NAME=us-west-2
168 +# When set, sccache skips the S3 backend and uses a local disk cache instead
169 +# (for builds without S3 credentials, e.g. a developer's local `docker build`).
170 +ARG SCCACHE_LOCAL_ONLY=0
171
172 ENV SCCACHE_BUCKET=${USE_SCCACHE:+${SCCACHE_BUCKET_NAME}}
173 ENV SCCACHE_REGION=${USE_SCCACHE:+${SCCACHE_REGION_NAME}}
@@ -186,10 +189,16 @@ ENV CARGO_BUILD_JOBS=4
189 # inputs. The child stage below relinks the crates that embed the exact version.
190 RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \
191 --mount=type=cache,target=/root/.cargo/git,sharing=locked \
192 + --mount=type=cache,target=/root/.cache/sccache-local,sharing=locked \
193 --mount=type=secret,id=aws-credentials,target=/root/.aws/credentials,required=false \
194 if [ "$USE_SCCACHE" = "1" ]; then \
195 export RUSTC_WRAPPER=sccache; \
192 - if [ -n "${SCCACHE_ENDPOINT}" ]; then export SCCACHE_ENDPOINT="${SCCACHE_ENDPOINT}"; fi; \
196 + if [ "$SCCACHE_LOCAL_ONLY" = "1" ]; then \
197 + unset SCCACHE_BUCKET SCCACHE_REGION; \
198 + export SCCACHE_DIR=/root/.cache/sccache-local SCCACHE_CACHE_SIZE=20G; \
199 + elif [ -n "${SCCACHE_ENDPOINT}" ]; then \
200 + export SCCACHE_ENDPOINT="${SCCACHE_ENDPOINT}"; \
201 + fi; \
202 sccache --show-stats; \
203 fi && \
204 SETUPTOOLS_SCM_PRETEND_VERSION="0.0.0+docker.cache" bash build_rust.sh && \
@@ -272,13 +281,20 @@ FROM vllm-src AS vllm-build
281
282 ARG USE_SCCACHE
283 ARG SCCACHE_ENDPOINT
284 +ARG SCCACHE_LOCAL_ONLY=0
285
286 RUN --mount=type=cache,target=/root/.cache/uv \
287 --mount=type=cache,target=/root/.cache/ccache \
288 + --mount=type=cache,target=/root/.cache/sccache-local,sharing=locked \
289 --mount=type=cache,target=/vllm-workspace/.deps,sharing=locked \
290 --mount=type=secret,id=aws-credentials,target=/root/.aws/credentials,required=false \
291 if [ "$USE_SCCACHE" = "1" ]; then \
281 - if [ -n "${SCCACHE_ENDPOINT}" ]; then export SCCACHE_ENDPOINT="${SCCACHE_ENDPOINT}"; fi; \
292 + if [ "$SCCACHE_LOCAL_ONLY" = "1" ]; then \
293 + unset SCCACHE_BUCKET SCCACHE_REGION; \
294 + export SCCACHE_DIR=/root/.cache/sccache-local SCCACHE_CACHE_SIZE=20G; \
295 + elif [ -n "${SCCACHE_ENDPOINT}" ]; then \
296 + export SCCACHE_ENDPOINT="${SCCACHE_ENDPOINT}"; \
297 + fi; \
298 sccache --show-stats; \
299 fi && \
300 VLLM_TARGET_DEVICE=cpu python3 setup.py bdist_wheel --dist-dir=dist --py-limited-api=cp38 && \
@@ -296,6 +312,7 @@ ARG VLLM_CPU_X86=0
312
313 ARG USE_SCCACHE
314 ARG SCCACHE_ENDPOINT
315 +ARG SCCACHE_LOCAL_ONLY=0
316
317 WORKDIR /vllm-workspace
318
@@ -303,15 +320,22 @@ RUN mkdir dist
320
321 RUN --mount=type=cache,target=/root/.cache/uv \
322 --mount=type=cache,target=/root/.cache/ccache \
323 + --mount=type=cache,target=/root/.cache/sccache-local,sharing=locked \
324 --mount=type=cache,target=/vllm-workspace/.deps,sharing=locked \
325 --mount=type=cache,target=/root/.triton \
326 --mount=type=secret,id=aws-credentials,target=/root/.aws/credentials,required=false \
327 + set -ex; \
328 if [ "$TARGETARCH" = "amd64" ] || [ "$VLLM_CPU_X86" != "0" ]; then \
329 git clone --recurse-submodules --shallow-submodules --filter=blob:none "https://github.com/triton-lang/triton-cpu.git"; \
330 cd triton-cpu; \
331 git checkout "270e696d"; \
332 if [ "$USE_SCCACHE" = "1" ]; then \
314 - if [ -n "${SCCACHE_ENDPOINT}" ]; then export SCCACHE_ENDPOINT="${SCCACHE_ENDPOINT}"; fi; \
333 + if [ "$SCCACHE_LOCAL_ONLY" = "1" ]; then \
334 + unset SCCACHE_BUCKET SCCACHE_REGION; \
335 + export SCCACHE_DIR=/root/.cache/sccache-local SCCACHE_CACHE_SIZE=20G; \
336 + elif [ -n "${SCCACHE_ENDPOINT}" ]; then \
337 + export SCCACHE_ENDPOINT="${SCCACHE_ENDPOINT}"; \
338 + fi; \
339 export TRITON_APPEND_CMAKE_ARGS="-DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache"; \
340 sccache --show-stats; \
341 fi; \
tests/models/language/pooling/test_max_tokens_per_doc.py
+4 -3
@@ -14,8 +14,6 @@ import requests
14 from tests.utils import VLLM_PATH, RemoteOpenAIServer
15 from vllm.entrypoints.pooling.scoring.protocol import RerankResponse
16
17 -os.environ["VLLM_LOGGING_LEVEL"] = "WARNING"
18 -
17 TEMPLATE_DIR = str(VLLM_PATH / "examples/pooling/score/template")
18 ExpectedPromptTokens = int | tuple[int, ...]
19
@@ -128,7 +126,10 @@ def assert_prompt_tokens(actual: int, expected: ExpectedPromptTokens) -> None:
126 @pytest.fixture(scope="module", params=RERANK_CONFIGS, ids=lambda c: c.model)
127 def server(request):
128 config: TestConfig = request.param
131 - with RemoteOpenAIServer(config.model, config.args) as remote_server:
129 + env_dict = {"VLLM_LOGGING_LEVEL": "WARNING"}
130 + with RemoteOpenAIServer(
131 + config.model, config.args, env_dict=env_dict
132 + ) as remote_server:
133 yield config, remote_server
134
135