[ROCm][CI] Reuse equivalent ROCm CI images (#48646)
Signed-off-by: Andreas Karatzas <Andreas.Karatzas@amd.com> Signed-off-by: Andreas Karatzas <akaratza@amd.com> Co-authored-by: OpenAI Codex <codex@openai.com>
Andreas Karatzas committed
Aug 9, 2026 at 03:25 UTC
7f6432cc0daf97c5b9a2df141641c595a766ff40
13 files changed
+1530
-907
.buildkite/ci_config_rocm.yaml
+13
@@ -2,11 +2,13 @@ name: vllm_rocm_ci
2
job_dirs:
3
- ".buildkite/hardware_tests"
4
run_all_patterns:
5
+ - ".dockerignore"
6
- "docker/Dockerfile.rocm"
7
- "docker/Dockerfile.rocm_base"
8
- "docker/ci-rocm.hcl"
9
- "docker/docker-bake-rocm.hcl"
10
- ".buildkite/hardware_tests/amd.yaml"
11
+ - ".buildkite/ci_config_rocm.yaml"
12
- ".buildkite/scripts/ci-bake-rocm.sh"
13
- ".buildkite/scripts/rocm/"
14
- ".buildkite/scripts/hardware_ci/run-amd-test.py"
@@ -14,9 +16,20 @@ run_all_patterns:
16
- "CMakeLists.txt"
17
- "requirements/common.txt"
18
- "requirements/rocm.txt"
19
+ - "requirements/build/rust.txt"
20
- "requirements/build/rocm.txt"
21
- "requirements/test/rocm.txt"
22
+ - "pyproject.toml"
23
- "setup.py"
24
+ - "build_rust.sh"
25
+ - "rust/"
26
+ - "rust-toolchain.toml"
27
+ - "tools/build_rust.py"
28
+ - "tools/install_protoc.sh"
29
+ - "tools/install_torchcodec_rocm.sh"
30
+ - "tests/vllm_test_utils/"
31
+ - "vllm/envs.py"
32
+ - "vllm/__init__.py"
33
- "csrc/"
34
- "cmake/"
35
run_all_exclude_patterns:
.buildkite/hardware_tests/amd.yaml
+12
-8
@@ -1,9 +1,9 @@
1
group: Hardware - AMD Build
2
3
# ROCm image flow:
4
-# 1. Refresh the long-lived ROCm base image only when Dockerfile.rocm_base changes.
5
-# 2. Build ci_base from either the stable base or the freshly refreshed base.
6
-# 3. Build the per-commit ROCm CI image and smoke-test it before GPU jobs run.
4
+# 1. Select the content-matching ROCm base, building only on a cache miss.
5
+# 2. Build ci_base from either the stable base or the newly selected base.
6
+# 3. Build the build-scoped ROCm CI image and smoke-test it before GPU jobs run.
7
steps:
8
- label: "AMD: :docker: refresh ROCm base"
9
key: refresh-rocm-base-amd
@@ -12,6 +12,8 @@ steps:
12
no_plugin: true
13
commands:
14
- bash .buildkite/scripts/rocm/refresh-base-image.sh
15
+ concurrency: 1
16
+ concurrency_group: "vllm/rocm/base-build/$BUILDKITE_COMMIT"
17
env:
18
DOCKER_BUILDKIT: "1"
19
BUILDKIT_PROGRESS: "tty"
@@ -38,18 +40,21 @@ steps:
40
no_plugin: true
41
commands:
42
- bash .buildkite/scripts/rocm/build-ci-base.sh
43
+ concurrency: 1
44
+ concurrency_group: "vllm/rocm/ci-base-build/$BUILDKITE_COMMIT"
45
env:
46
DOCKER_BUILDKIT: "1"
47
BUILDKIT_PROGRESS: "tty"
48
TERM: "xterm-256color"
49
VLLM_BAKE_FILE: "docker/docker-bake-rocm.hcl"
50
PYTORCH_ROCM_ARCH: "gfx90a;gfx942;gfx950"
47
- REMOTE_VLLM: "1"
48
- VLLM_BRANCH: "$BUILDKITE_COMMIT"
51
+ REMOTE_VLLM: "0"
52
retry:
53
automatic:
54
- exit_status: 1 # Transient Docker/BuildKit failure
55
limit: 1
56
+ - exit_status: 2 # Exact image found but registry handoff failed
57
+ limit: 1
58
- exit_status: -1 # Agent was lost
59
limit: 1
60
- exit_status: -10 # Agent was lost
@@ -71,9 +76,8 @@ steps:
76
TERM: "xterm-256color"
77
VLLM_BAKE_FILE: "docker/docker-bake-rocm.hcl"
78
PYTORCH_ROCM_ARCH: "gfx90a;gfx942;gfx950"
74
- IMAGE_TAG: "rocm/vllm-ci:$BUILDKITE_COMMIT"
75
- REMOTE_VLLM: "1"
76
- VLLM_BRANCH: "$BUILDKITE_COMMIT"
79
+ IMAGE_TAG: "rocm/vllm-ci:build-$BUILDKITE_BUILD_ID"
80
+ REMOTE_VLLM: "0"
81
retry:
82
automatic:
83
- exit_status: 1 # Transient Docker/BuildKit failure
.buildkite/scripts/ci-bake-rocm.sh
+702
-482
@@ -3,7 +3,7 @@
3
#
4
# The wrapper keeps three build concerns separate:
5
# * ci_base builds are content-addressed by vllm.ci_base.content_hash.
6
-# * test image builds are commit-addressed by org.opencontainers.image.revision.
6
+# * test image contents record the commit while runtime tags stay build-scoped.
7
# * ROCm install artifacts are uploaded only for targets that export wheels.
8
#
9
# Usage:
@@ -15,11 +15,20 @@ set -euo pipefail
15
16
DEFAULT_REPO_SLUG="vllm-project/vllm"
17
DEFAULT_CI_HCL_SOURCE="docker/ci-rocm.hcl"
18
-DEFAULT_CI_BASE_CONTENT_FILES="requirements/common.txt requirements/rocm.txt requirements/test/rocm.txt docker/Dockerfile.rocm_base docker/ci-rocm.hcl docker/docker-bake-rocm.hcl tools/install_torchcodec_rocm.sh tools/install_protoc.sh rust-toolchain.toml tests/vllm_test_utils .buildkite/scripts/ci-bake-rocm.sh .buildkite/scripts/rocm/build-ci-base.sh"
18
+DEFAULT_CI_BASE_CONTENT_FILES=".dockerignore requirements/common.txt requirements/rocm.txt requirements/test/rocm.txt tools/install_torchcodec_rocm.sh tools/install_protoc.sh rust-toolchain.toml tests/vllm_test_utils"
19
DEFAULT_CI_BASE_DOCKERFILE="docker/Dockerfile.rocm"
20
-DEFAULT_CI_BASE_DOCKERFILE_STAGES="base rust_toolchain_input_0 rust_toolchain_input_1 rust-toolchain-input rust-toolchain build_nixl build_rocshmem build_deepep mori_base ci_base"
21
-DEFAULT_CI_BASE_METADATA_VERSION="1"
22
-IMAGE_EXISTED_BEFORE_BUILD=0
20
+DEFAULT_CI_BASE_DOCKERFILE_STAGES="base rust_toolchain_input_0 rust-toolchain-input rust-toolchain build_nixl build_rocshmem build_deepep mori_base ci_base"
21
+DEFAULT_CI_BASE_METADATA_VERSION="3"
22
+# ROCm CI forces REMOTE_VLLM=0, so content identity covers only the selected
23
+# local-source stages rather than unreachable remote-fetch alternatives.
24
+DEFAULT_ROCM_CSRC_CONTENT_FILES=".dockerignore requirements/common.txt requirements/rocm.txt pyproject.toml setup.py CMakeLists.txt cmake csrc vllm/envs.py vllm/__init__.py tools/build_rust.py"
25
+DEFAULT_ROCM_CSRC_DOCKERFILE_STAGES="base fetch_vllm_0 fetch_vllm build_vllm_dependencies csrc-build"
26
+DEFAULT_ROCM_RUST_CONTENT_FILES=".dockerignore requirements/build/rust.txt rust/Cargo.lock rust/Cargo.toml rust/proto rust/src rust-toolchain.toml tools/build_rust.py tools/install_protoc.sh build_rust.sh"
27
+DEFAULT_ROCM_RUST_DOCKERFILE_STAGES="base rust_toolchain_input_0 rust-toolchain-input rust_input_0 rust-input rust-toolchain rust-build"
28
+# Docker's 128-character tag limit minus the longest cache prefix
29
+# ("csrc-rocm-branch-" and "rust-rocm-branch-", both 17 characters).
30
+ROCM_CACHE_BRANCH_TAG_MAX_LEN=111
31
+CI_BASE_WRITE_SCOPE=""
32
33
TARGET=""
34
CI_HCL_SOURCE="${CI_HCL_SOURCE:-}"
@@ -86,10 +95,11 @@ compose_cache_branch_tag() {
95
local suffix=""
96
local prefix=""
97
local max_prefix_len=0
98
+ local max_tag_len="${ROCM_CACHE_BRANCH_TAG_MAX_LEN}"
99
100
suffix="$(cache_scope_suffix)"
101
prefix="$(clean_docker_tag "${repo_slug}")-$(clean_docker_tag "${branch}")"
92
- max_prefix_len=$((128 - ${#suffix} - 1))
102
+ max_prefix_len=$((max_tag_len - ${#suffix} - 1))
103
if (( max_prefix_len < 1 )); then
104
max_prefix_len=1
105
fi
@@ -112,6 +122,57 @@ parse_repo_slug() {
122
printf '%s\n' "${repo_slug}"
123
}
124
125
+normalize_repo_slug() {
126
+ local repo_slug="${1:-}"
127
+
128
+ repo_slug="${repo_slug%/}"
129
+ repo_slug="${repo_slug%.git}"
130
+ repo_slug="${repo_slug#https://github.com/}"
131
+ repo_slug="${repo_slug#http://github.com/}"
132
+ repo_slug="${repo_slug#ssh://git@github.com/}"
133
+ repo_slug="${repo_slug#git@github.com:}"
134
+ repo_slug="${repo_slug#github.com/}"
135
+ printf '%s\n' "${repo_slug}"
136
+}
137
+
138
+is_trusted_ci_cache_writer() {
139
+ local actual_repo=""
140
+ local trusted_repo=""
141
+
142
+ [[ "${BUILDKITE:-false}" == "true" ]] || return 1
143
+ [[ "${BUILDKITE_PULL_REQUEST:-false}" == "false" ]] || return 1
144
+ [[ "${BUILDKITE_BRANCH:-}" == "${CI_BASE_STABLE_BRANCH:-main}" ]] || return 1
145
+ actual_repo=$(normalize_repo_slug "${BUILDKITE_REPO:-}")
146
+ trusted_repo=$(normalize_repo_slug \
147
+ "${CI_BASE_STABLE_REPO_SLUG:-${DEFAULT_REPO_SLUG}}")
148
+ [[ -n "${actual_repo}" && "${actual_repo}" == "${trusted_repo}" ]]
149
+}
150
+
151
+ci_base_write_scope() {
152
+ local identity=""
153
+ local source_repo="${BUILDKITE_PULL_REQUEST_REPO:-${BUILDKITE_REPO:-local}}"
154
+
155
+ if is_trusted_ci_cache_writer; then
156
+ return 0
157
+ fi
158
+ identity=$(printf '%s\n' "${source_repo}" | sha256sum | cut -c1-12)
159
+ printf 'preview-%s\n' "${identity}"
160
+}
161
+
162
+configure_ci_base_write_scope() {
163
+ local scope=""
164
+
165
+ scope=$(ci_base_write_scope)
166
+ if [[ -n "${scope}" ]]; then
167
+ CI_BASE_WRITE_SCOPE=$(clean_docker_tag "${scope}")
168
+ echo "Non-canonical cache writes use source scope: ${CI_BASE_WRITE_SCOPE}"
169
+ else
170
+ CI_BASE_WRITE_SCOPE=""
171
+ echo "Trusted main build: publishing canonical ci_base refs"
172
+ fi
173
+ export CI_BASE_WRITE_SCOPE
174
+}
175
+
176
get_buildkite_repo_slug() {
177
parse_repo_slug "${BUILDKITE_PULL_REQUEST_REPO:-${BUILDKITE_REPO:-}}"
178
}
@@ -145,25 +206,100 @@ hash_string_short() {
206
printf '%s' "$1" | sha256sum | cut -c1-16
207
}
208
209
+list_content_files() {
210
+ # Hash the checkout inputs Docker can intentionally consume, not ignored
211
+ # compiler/test debris left behind on a reused worker.
212
+ git ls-files -z --cached --others --exclude-standard -- "$1" \
213
+ | LC_ALL=C sort -z
214
+}
215
+
216
+hash_content_file() {
217
+ local file="$1"
218
+
219
+ if [[ -L "${file}" ]]; then
220
+ printf 'symlink:%s\ntarget:%s\n' "${file}" "$(readlink "${file}")"
221
+ return
222
+ fi
223
+ if [[ ! -f "${file}" ]]; then
224
+ printf 'missing:%s\n' "${file}"
225
+ return
226
+ fi
227
+ printf 'file:%s\nmode:%s\n' "${file}" "$(stat -c '%a' "${file}")"
228
+ sha256sum "${file}"
229
+}
230
+
231
+hash_content_directory() {
232
+ if ! list_content_files "$1" | while IFS= read -r -d '' file; do
233
+ hash_content_file "${file}" || exit $?
234
+ done; then
235
+ echo "Failed to hash content under $1" >&2
236
+ return 1
237
+ fi
238
+}
239
+
240
compute_content_hash() {
149
- local path
150
- local file
241
+ local path=""
242
243
for path in "$@"; do
153
- if [[ -d "${path}" ]]; then
154
- while IFS= read -r -d '' file; do
155
- printf 'file:%s\n' "${file}"
156
- sha256sum "${file}"
157
- done < <(find "${path}" -type f -print0 | sort -z)
158
- elif [[ -f "${path}" ]]; then
159
- printf 'file:%s\n' "${path}"
160
- sha256sum "${path}"
244
+ if [[ -L "${path}" || -f "${path}" ]]; then
245
+ hash_content_file "${path}" || return $?
246
+ elif [[ -d "${path}" ]]; then
247
+ hash_content_directory "${path}" || return $?
248
else
249
printf 'missing:%s\n' "${path}"
250
fi
251
done | sha256sum | cut -d' ' -f1
252
}
253
254
+normalize_ci_worktree_modes() {
255
+ local entry=""
256
+ local metadata=""
257
+ local mode=""
258
+ local stage=""
259
+ local path=""
260
+ local index_modes_file="${SCRIPT_TMP_DIR}/git-index-modes"
261
+ local -a regular_files=()
262
+ local -a executable_files=()
263
+
264
+ [[ "${BUILDKITE:-false}" == "true" ]] || return 0
265
+ [[ "${REMOTE_VLLM:-0}" == "0" ]] || return 0
266
+ if ! git ls-files --stage -z > "${index_modes_file}"; then
267
+ echo "Failed to read Git modes for the CI Docker context" >&2
268
+ return 1
269
+ fi
270
+
271
+ # Some shared workspaces expose 0644/0755 files as 0664/0775. Restore the
272
+ # index modes so both the identity hash and Docker context are canonical.
273
+ while IFS= read -r -d '' entry; do
274
+ metadata="${entry%%$'\t'*}"
275
+ mode="${metadata%% *}"
276
+ stage="${metadata##* }"
277
+ path="${entry#*$'\t'}"
278
+ if [[ "${stage}" != "0" ]]; then
279
+ echo "Unmerged Git entry cannot be used as Docker context: ${path}" >&2
280
+ return 1
281
+ fi
282
+ case "${mode}" in
283
+ 100644)
284
+ regular_files+=("${path}")
285
+ ;;
286
+ 100755)
287
+ executable_files+=("${path}")
288
+ ;;
289
+ 120000|160000)
290
+ ;;
291
+ *)
292
+ echo "Unsupported Git mode ${mode} for ${path}" >&2
293
+ return 1
294
+ ;;
295
+ esac
296
+ done < "${index_modes_file}"
297
+
298
+ ((${#regular_files[@]} == 0)) || chmod 0644 -- "${regular_files[@]}"
299
+ ((${#executable_files[@]} == 0)) || chmod 0755 -- "${executable_files[@]}"
300
+ echo "Normalized Git file modes for the CI Docker context"
301
+}
302
+
303
compose_dependency_cache_key() {
304
local prefix="$1"
305
local material="$2"
@@ -185,7 +321,7 @@ hash_dockerfile_stages() {
321
wanted[stage_list[idx]] = 1
322
}
323
}
188
- emit = 1
324
+ emit = 0
325
}
326
$1 == "FROM" {
327
stage = ""
@@ -285,9 +421,10 @@ get_content_arg_names() {
421
fi | awk 'NF && !seen[$0]++'
422
}
423
288
-compute_ci_base_content_hash_once() {
424
+compute_ci_base_content_hash() {
425
local -a content_paths=()
426
local -a content_args=()
427
+ local content_files_hash=""
428
local dockerfile="${CI_BASE_DOCKERFILE:-}"
429
local stages="${CI_BASE_DOCKERFILE_STAGES:-}"
430
@@ -295,9 +432,13 @@ compute_ci_base_content_hash_once() {
432
mapfile -t content_args < <(
433
get_content_arg_names "${dockerfile}" "${stages}" "${CI_BASE_CONTENT_ARGS:-}"
434
)
435
+ if ! content_files_hash=$(compute_content_hash "${content_paths[@]}"); then
436
+ echo "Failed to hash ci_base content files" >&2
437
+ return 1
438
+ fi
439
440
{
300
- printf 'content-files-hash:%s\n' "$(compute_content_hash "${content_paths[@]}")"
441
+ printf 'content-files-hash:%s\n' "${content_files_hash}"
442
if [[ -n "${dockerfile}" ]]; then
443
printf 'dockerfile:%s\n' "${dockerfile}"
444
printf 'resolved-build-args:\n'
@@ -315,67 +456,52 @@ compute_ci_base_content_hash_once() {
456
} | sha256sum | cut -d' ' -f1
457
}
458
318
-compute_ci_base_content_hash() {
319
- local attempts="${CI_BASE_HASH_ATTEMPTS:-3}"
320
- local delay_secs="${CI_BASE_HASH_RETRY_DELAY:-5}"
459
+extract_dockerfile_arg_default() {
460
+ local dockerfile="$1"
461
+ local arg_name="$2"
462
+
463
+ sed -n -E "s/^[[:space:]]*ARG[[:space:]]+${arg_name}=\"?([^\"[:space:]]+)\"?.*/\\1/p" \
464
+ "${dockerfile}" | head -1
465
+}
466
+
467
+resolve_image_digest() {
468
+ local image_ref="$1"
469
+ local attempts="${ROCM_IMAGE_DIGEST_ATTEMPTS:-4}"
470
+ local delay_secs="${ROCM_IMAGE_DIGEST_RETRY_DELAY:-2}"
471
local attempt=0
322
- local hash=""
323
- local failed=0
324
- local -a hashes=()
472
+ local digest=""
473
+ local output=""
474
+ local status=0
475
326
- if [[ ! "${attempts}" =~ ^[1-9][0-9]*$ ]]; then
327
- echo "Invalid CI_BASE_HASH_ATTEMPTS: ${attempts}" >&2
328
- return 1
476
+ if [[ "${image_ref}" =~ @(sha256:[0-9a-f]{64})$ ]]; then
477
+ printf '%s\n' "${BASH_REMATCH[1]}"
478
+ return
479
fi
330
- if [[ ! "${delay_secs}" =~ ^[0-9]+$ ]]; then
331
- echo "Invalid CI_BASE_HASH_RETRY_DELAY: ${delay_secs}" >&2
480
+ if [[ ! "${attempts}" =~ ^[1-9][0-9]*$ \
481
+ || ! "${delay_secs}" =~ ^[0-9]+$ ]]; then
482
+ echo "Invalid image digest retry configuration" >&2
483
return 1
484
fi
485
486
for ((attempt = 1; attempt <= attempts; attempt++)); do
336
- if ! hash=$(compute_ci_base_content_hash_once); then
337
- echo "ci_base content hash calculation ${attempt}/${attempts} failed" >&2
338
- failed=1
339
- else
340
- hashes+=("${hash}")
341
- echo "ci_base content hash calculation ${attempt}/${attempts}: ${hash}" >&2
487
+ status=0
488
+ output=$(docker buildx imagetools inspect "${image_ref}" 2>&1) || status=$?
489
+ digest=$(awk '$1 == "Digest:" { print $2; exit }' <<< "${output}")
490
+ if ((status == 0)) && [[ "${digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
491
+ printf '%s\n' "${digest}"
492
+ return
493
fi
343
-
494
if ((attempt < attempts)); then
495
+ printf \
496
+ 'Image digest lookup failed for %s (%d/%d, status %d); retrying\n' \
497
+ "${image_ref}" "${attempt}" "${attempts}" "${status}" >&2
498
sleep "${delay_secs}"
499
fi
500
done
501
349
- if ((failed)) || ((${#hashes[@]} != attempts)); then
350
- echo "Could not calculate a reliable ci_base content hash" >&2
351
- return 1
352
- fi
353
-
354
- for hash in "${hashes[@]:1}"; do
355
- if [[ "${hash}" != "${hashes[0]}" ]]; then
356
- echo "ci_base content hash changed between calculations" >&2
357
- printf ' observed: %s\n' "${hashes[@]}" >&2
358
- return 1
359
- fi
360
- done
361
-
362
- printf '%s\n' "${hashes[0]}"
363
-}
364
-
365
-extract_dockerfile_arg_default() {
366
- local dockerfile="$1"
367
- local arg_name="$2"
368
-
369
- sed -n -E "s/^[[:space:]]*ARG[[:space:]]+${arg_name}=\"?([^\"[:space:]]+)\"?.*/\\1/p" \
370
- "${dockerfile}" | head -1
371
-}
372
-
373
-resolve_image_digest() {
374
- local image_ref="$1"
375
-
376
- docker buildx imagetools inspect "${image_ref}" 2>/dev/null \
377
- | sed -n -E 's/^Digest:[[:space:]]+//p' \
378
- | head -1
502
+ printf 'Failed to resolve digest for %s (status %d)\n%s\n' \
503
+ "${image_ref}" "${status}" "${output:-<no output>}" >&2
504
+ return 1
505
}
506
507
resolve_dockerfile_arg_value() {
@@ -388,6 +514,9 @@ resolve_dockerfile_arg_value() {
514
ARG_PYTORCH_ROCM_ARCH)
515
env_name="PYTORCH_ROCM_ARCH"
516
;;
517
+ max_jobs)
518
+ env_name="CI_MAX_JOBS"
519
+ ;;
520
esac
521
522
value="${!env_name:-}"
@@ -411,18 +540,35 @@ hash_dockerfile_arg_values() {
540
for arg_name in "$@"; do
541
[[ -n "${arg_name}" ]] || continue
542
arg_value=$(resolve_dockerfile_arg_value "${dockerfile}" "${arg_name}")
414
- printf 'arg:%s=%s\n' "${arg_name}" "${arg_value:-<empty>}"
543
if [[ "${arg_name}" == "BASE_IMAGE" && -n "${arg_value}" ]]; then
416
- digest=$(resolve_image_digest "${arg_value}")
417
- if [[ -z "${digest}" ]]; then
544
+ if ! digest=$(resolve_image_digest "${arg_value}"); then
545
echo "Failed to resolve digest for BASE_IMAGE=${arg_value}" >&2
546
return 1
547
fi
548
printf 'arg:%s.digest=%s\n' "${arg_name}" "${digest}"
549
+ else
550
+ printf 'arg:%s=%s\n' "${arg_name}" "${arg_value:-<empty>}"
551
fi
552
done
553
}
554
555
+pin_base_image() {
556
+ local dockerfile="${CI_BASE_DOCKERFILE:-${DEFAULT_CI_BASE_DOCKERFILE}}"
557
+ local base_image=""
558
+ local digest=""
559
+
560
+ base_image=$(resolve_dockerfile_arg_value "${dockerfile}" "BASE_IMAGE")
561
+ [[ -n "${base_image}" ]] || return 0
562
+ if ! digest=$(resolve_image_digest "${base_image}"); then
563
+ echo "Error: could not resolve base image digest for ${base_image}" >&2
564
+ echo "Refusing to compute content-addressed cache keys from a mutable tag." >&2
565
+ return 1
566
+ fi
567
+ BASE_IMAGE="${base_image%@*}@${digest}"
568
+ export BASE_IMAGE
569
+ echo "Pinned base image for this build: ${BASE_IMAGE}"
570
+}
571
+
572
is_ci_base_target() {
573
[[ "${TARGET}" == *"ci-base-rocm"* ]]
574
}
@@ -433,11 +579,6 @@ is_commit_image_target() {
579
return 0
580
}
581
436
-image_tag_is_commit_scoped() {
437
- [[ -n "${IMAGE_TAG:-}" && -n "${BUILDKITE_COMMIT:-}" ]] || return 1
438
- [[ "${IMAGE_TAG}" == *"${BUILDKITE_COMMIT}"* ]]
439
-}
440
-
582
should_upload_wheel_artifacts() {
583
[[ "${UPLOAD_ROCM_WHEEL_ARTIFACTS:-0}" == "1" ]] && return 0
584
[[ "${TARGET}" == *"with-wheel"* \
@@ -445,168 +586,99 @@ should_upload_wheel_artifacts() {
586
|| "${TARGET}" == *"artifact"* ]]
587
}
588
448
-set_buildkite_metadata() {
449
- local key="$1"
450
- local value="$2"
451
-
452
- [[ -n "${value}" ]] || return 0
453
- if command -v buildkite-agent >/dev/null 2>&1; then
454
- buildkite-agent meta-data set "${key}" "${value}" || true
455
- fi
456
-}
457
-
589
get_remote_image_label() {
590
local image_ref="$1"
591
local label_key="$2"
592
+ local format="{{ index .Image.Config.Labels \"${label_key}\" }}"
593
462
- docker buildx imagetools inspect "${image_ref}" --raw 2>/dev/null \
463
- | python3 -c '
464
-import json
465
-import subprocess
466
-import sys
467
-import urllib.parse
468
-import urllib.request
469
-
470
-image_ref = sys.argv[1]
471
-label_key = sys.argv[2]
472
-
473
-
474
-def docker_hub_repo(image_name):
475
- image_name = image_name.split("@", 1)[0]
476
- last_component = image_name.rsplit("/", 1)[-1]
477
- if ":" in last_component:
478
- image_name = image_name.rsplit(":", 1)[0]
479
-
480
- parts = image_name.split("/")
481
- if len(parts) > 1 and (
482
- "." in parts[0] or ":" in parts[0] or parts[0] == "localhost"
483
- ):
484
- registry = parts[0]
485
- if registry not in {
486
- "docker.io",
487
- "index.docker.io",
488
- "registry-1.docker.io",
489
- }:
490
- return None
491
- image_name = "/".join(parts[1:])
492
- elif len(parts) == 1:
493
- image_name = f"library/{image_name}"
494
-
495
- return image_name
496
-
497
-
498
-try:
499
- data = json.load(sys.stdin)
500
- if data.get("manifests"):
501
- manifest = next(
502
- (
503
- entry
504
- for entry in data["manifests"]
505
- if entry.get("platform", {}).get("os") != "unknown"
506
- and entry.get("platform", {}).get("architecture") != "unknown"
507
- ),
508
- data["manifests"][0],
509
- )
510
- digest = manifest["digest"]
511
- result = subprocess.run(
512
- [
513
- "docker",
514
- "buildx",
515
- "imagetools",
516
- "inspect",
517
- image_ref + "@" + digest,
518
- "--raw",
519
- ],
520
- capture_output=True,
521
- text=True,
522
- check=False,
523
- )
524
- if result.returncode != 0 or not result.stdout:
525
- raise RuntimeError("digest inspect failed")
526
- data = json.loads(result.stdout)
527
-
528
- annotations = data.get("annotations", {})
529
- if label_key in annotations:
530
- print(annotations[label_key])
531
- raise SystemExit(0)
532
-
533
- config_digest = data.get("config", {}).get("digest")
534
- if not config_digest:
535
- print("")
536
- raise SystemExit(0)
537
-
538
- image_name = docker_hub_repo(image_ref)
539
- if not image_name:
540
- print("")
541
- raise SystemExit(0)
542
-
543
- token_url = (
544
- "https://auth.docker.io/token?"
545
- + urllib.parse.urlencode(
546
- {
547
- "service": "registry.docker.io",
548
- "scope": f"repository:{image_name}:pull",
549
- }
550
- )
551
- )
552
- with urllib.request.urlopen(token_url, timeout=30) as response:
553
- token = json.load(response)["token"]
554
-
555
- request = urllib.request.Request(
556
- f"https://registry-1.docker.io/v2/{image_name}/blobs/{config_digest}",
557
- headers={"Authorization": f"Bearer {token}"},
558
- )
559
- with urllib.request.urlopen(request, timeout=30) as response:
560
- config_blob = json.load(response)
561
-
562
- labels = config_blob.get("config", {}).get("Labels", {})
563
- print(labels.get(label_key, ""))
564
-except Exception:
565
- print("")
566
-' "${image_ref}" "${label_key}" 2>/dev/null || echo ""
594
+ docker buildx imagetools inspect "${image_ref}" \
595
+ --format "${format}" 2>/dev/null | awk 'NF { print; exit }' || true
596
}
597
569
-get_remote_image_label_with_retry() {
598
+remote_ci_base_identity_is_current_with_retry() {
599
local image_ref="$1"
571
- local label_key="$2"
572
- local attempts="${3:-6}"
573
- local delay_secs="${4:-5}"
574
- local label_value=""
575
- local attempt
600
+ local attempts="${CI_BASE_LABEL_ATTEMPTS:-4}"
601
+ local delay_secs="${CI_BASE_LABEL_RETRY_DELAY:-2}"
602
+ local identity=""
603
+ local remote_hash=""
604
+ local remote_version=""
605
+ local content_files_hash=""
606
+ local base_digest=""
607
+ local attempt=0
608
+ local expected_version="${CI_BASE_METADATA_VERSION:-${DEFAULT_CI_BASE_METADATA_VERSION}}"
609
+ local format='{{ index .Image.Config.Labels "vllm.ci_base.content_hash" }}|{{ index .Image.Config.Labels "vllm.ci_base.metadata_version" }}|{{ index .Image.Config.Labels "vllm.ci_base.content_files_hash" }}|{{ index .Image.Config.Labels "vllm.rocm.base_image_digest" }}'
610
+
611
+ if [[ ! "${attempts}" =~ ^[1-9][0-9]*$ \
612
+ || ! "${delay_secs}" =~ ^[0-9]+$ ]]; then
613
+ echo "Invalid ci_base label retry configuration" >&2
614
+ return 1
615
+ fi
616
617
for ((attempt = 1; attempt <= attempts; attempt++)); do
578
- label_value=$(get_remote_image_label "${image_ref}" "${label_key}")
579
- if [[ -n "${label_value}" ]]; then
580
- printf '%s\n' "${label_value}"
618
+ identity=$(docker buildx imagetools inspect "${image_ref}" \
619
+ --format "${format}" 2>/dev/null || true)
620
+ IFS='|' read -r \
621
+ remote_hash remote_version content_files_hash base_digest <<< "${identity}"
622
+ if [[ "${remote_hash}" == "${CI_BASE_CONTENT_HASH:-}" \
623
+ && "${remote_version}" == "${expected_version}" \
624
+ && "${content_files_hash}" =~ ^[0-9a-f]{64}$ \
625
+ && "${base_digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
626
return 0
627
fi
583
- if [[ ${attempt} -lt ${attempts} ]]; then
584
- sleep "${delay_secs}"
585
- fi
628
+ ((attempt == attempts)) || sleep "${delay_secs}"
629
done
630
588
- return 0
631
+ echo "ci_base identity did not match after ${attempts} attempts: ${image_ref}" >&2
632
+ echo " expected hash/version: ${CI_BASE_CONTENT_HASH:-<missing>}/${expected_version}" >&2
633
+ echo " observed hash/version: ${remote_hash:-<missing>}/${remote_version:-<missing>}" >&2
634
+ return 1
635
}
636
591
-remote_ci_base_metadata_is_current() {
592
- local image_ref="$1"
593
- local metadata_version=""
637
+registry_ref_exists_with_retry() {
638
+ local inspect_kind="$1"
639
+ local image_ref="$2"
640
+ local attempts="${ROCM_REGISTRY_PROBE_ATTEMPTS:-2}"
641
+ local delay_secs="${ROCM_REGISTRY_PROBE_RETRY_DELAY:-1}"
642
+ local output=""
643
+ local status=0
644
+ local attempt=0
645
595
- metadata_version=$(get_remote_image_label "${image_ref}" "vllm.ci_base.metadata_version")
596
- [[ "${metadata_version}" == "${CI_BASE_METADATA_VERSION:-${DEFAULT_CI_BASE_METADATA_VERSION}}" ]]
597
-}
646
+ if [[ ! "${attempts}" =~ ^[1-9][0-9]*$ \
647
+ || ! "${delay_secs}" =~ ^[0-9]+$ ]]; then
648
+ echo "Invalid registry probe retry configuration" >&2
649
+ return 1
650
+ fi
651
599
-remote_ci_base_metadata_is_current_with_retry() {
600
- local image_ref="$1"
601
- local metadata_version=""
652
+ for ((attempt = 1; attempt <= attempts; attempt++)); do
653
+ status=0
654
+ if [[ "${inspect_kind}" == "manifest" ]]; then
655
+ output=$(docker manifest inspect "${image_ref}" 2>&1) || status=$?
656
+ else
657
+ output=$(docker buildx imagetools inspect "${image_ref}" 2>&1) \
658
+ || status=$?
659
+ fi
660
+ if ((status == 0)); then
661
+ return 0
662
+ fi
663
+ # A definitive registry miss is normal for a new content key. Retry
664
+ # transport/rate-limit failures, but do not add latency to a real 404.
665
+ if grep -Eiq \
666
+ 'manifest unknown|no such manifest|name unknown|not found|does not exist' \
667
+ <<< "${output}"; then
668
+ return 1
669
+ fi
670
+ if ((attempt < attempts)); then
671
+ echo "Registry probe failed for ${image_ref} (${attempt}/${attempts}); retrying" >&2
672
+ sleep "${delay_secs}"
673
+ fi
674
+ done
675
603
- metadata_version=$(get_remote_image_label_with_retry "${image_ref}" "vllm.ci_base.metadata_version")
604
- [[ "${metadata_version}" == "${CI_BASE_METADATA_VERSION:-${DEFAULT_CI_BASE_METADATA_VERSION}}" ]]
676
+ echo "Registry probe failed after ${attempts} attempts: ${image_ref}" >&2
677
+ return 1
678
}
679
680
remote_image_exists() {
608
- local image_ref="$1"
609
- docker manifest inspect "${image_ref}" >/dev/null 2>&1
681
+ registry_ref_exists_with_retry manifest "$1"
682
}
683
684
use_existing_builder() {
@@ -680,7 +752,7 @@ print_header() {
752
if is_ci_base_target; then
753
echo "Build mode: ci_base"
754
elif is_commit_image_target; then
683
- echo "Build mode: commit image"
755
+ echo "Build mode: build-scoped commit image"
756
else
757
echo "Build mode: generic"
758
fi
@@ -723,13 +795,25 @@ compute_ci_base_hash_if_needed() {
795
if [[ -z "${CI_BASE_CONTENT_FILES:-}" ]]; then
796
return 0
797
fi
798
+ pin_base_image
799
+ if ! is_ci_base_target; then
800
+ return 0
801
+ fi
802
+ if [[ "${REMOTE_VLLM:-0}" != "0" ]]; then
803
+ echo "Error: content-addressed ci_base builds require REMOTE_VLLM=0" >&2
804
+ return 1
805
+ fi
806
807
CI_BASE_CONTENT_HASH=$(compute_ci_base_content_hash)
808
export CI_BASE_CONTENT_HASH
809
echo "ci_base content hash: ${CI_BASE_CONTENT_HASH:0:16}..."
810
}
811
732
-should_push_stable_ci_base_tag() {
812
+wants_stable_ci_base_tag() {
813
+ if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" ]]; then
814
+ return 1
815
+ fi
816
+
817
if [[ "${CI_BASE_PUSH_STABLE_TAG:-}" == "1" ]]; then
818
return 0
819
fi
@@ -740,6 +824,34 @@ should_push_stable_ci_base_tag() {
824
[[ "${NIGHTLY:-0}" == "1" && "${BUILDKITE_BRANCH:-}" == "${CI_BASE_STABLE_BRANCH:-main}" ]]
825
}
826
827
+trusted_ci_base_tip_matches_build() {
828
+ local branch="${CI_BASE_STABLE_BRANCH:-main}"
829
+ local build_commit="${BUILDKITE_COMMIT:-}"
830
+ local remote_tip=""
831
+
832
+ is_trusted_ci_cache_writer || return 1
833
+ if [[ ! "${build_commit}" =~ ^[0-9a-fA-F]{40}$ ]]; then
834
+ echo "Skipping ci_base stable tag: Buildkite commit is missing or invalid" >&2
835
+ return 1
836
+ fi
837
+ remote_tip=$(git ls-remote --exit-code "${BUILDKITE_REPO}" \
838
+ "refs/heads/${branch}" 2>/dev/null | awk 'NR == 1 { print $1 }')
839
+ if [[ ! "${remote_tip}" =~ ^[0-9a-fA-F]{40}$ ]]; then
840
+ echo "Skipping ci_base stable tag: could not resolve remote ${branch} tip" >&2
841
+ return 1
842
+ fi
843
+ if [[ "${remote_tip,,}" != "${build_commit,,}" ]]; then
844
+ echo "Skipping ci_base stable tag: ${branch} advanced from ${build_commit} to ${remote_tip}" >&2
845
+ return 1
846
+ fi
847
+}
848
+
849
+should_push_stable_ci_base_tag() {
850
+ wants_stable_ci_base_tag \
851
+ && is_trusted_ci_cache_writer \
852
+ && trusted_ci_base_tip_matches_build
853
+}
854
+
855
ci_base_tag_with_suffix() {
856
local base_tag="$1"
857
local suffix="$2"
@@ -749,49 +861,67 @@ ci_base_tag_with_suffix() {
861
862
configure_ci_base_image_refs() {
863
local stable_tag="${CI_BASE_IMAGE_TAG:-rocm/vllm-dev:ci_base}"
864
+ local metadata_version="${CI_BASE_METADATA_VERSION:-${DEFAULT_CI_BASE_METADATA_VERSION}}"
865
+ local scope="${CI_BASE_WRITE_SCOPE:-}"
866
+ local trusted_content_tag=""
867
local content_tag=""
753
- local commit_tag=""
868
+ local build_tag=""
869
local primary_tag=""
870
871
+ if [[ ! "${metadata_version}" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]{0,15}$ ]]; then
872
+ echo "Invalid ci_base metadata version: ${metadata_version}" >&2
873
+ return 1
874
+ fi
875
+
876
+ if [[ "${BUILDKITE:-false}" == "true" ]] \
877
+ && ! is_full_git_sha "${BUILDKITE_COMMIT:-}"; then
878
+ echo "Invalid Buildkite commit for ci_base handoff: ${BUILDKITE_COMMIT:-<empty>}" >&2
879
+ return 1
880
+ fi
881
+ if [[ "${BUILDKITE:-false}" == "true" \
882
+ && -z "${BUILDKITE_BUILD_ID:-}" ]]; then
883
+ echo "Buildkite build ID is required for the ci_base runtime handoff" >&2
884
+ return 1
885
+ fi
886
+ if [[ -n "${BUILDKITE_BUILD_ID:-}" ]]; then
887
+ build_tag=$(ci_base_tag_with_suffix \
888
+ "${stable_tag}" "build-${BUILDKITE_BUILD_ID}")
889
+ fi
890
+ CI_BASE_IMAGE_TAG_BUILD_REF="${build_tag}"
891
+ export CI_BASE_IMAGE_TAG_BUILD_REF
892
+
893
if [[ -z "${CI_BASE_CONTENT_HASH:-}" ]]; then
894
+ if is_ci_base_target; then
895
+ echo "Error: ci_base builds require a content hash" >&2
896
+ return 1
897
+ fi
898
CI_BASE_IMAGE="${CI_BASE_IMAGE:-${stable_tag}}"
899
export CI_BASE_IMAGE
900
return 0
901
fi
902
762
- content_tag=$(ci_base_tag_with_suffix "${stable_tag}" "${CI_BASE_CONTENT_HASH}")
763
- CI_BASE_IMAGE_TAG_CONTENT_REF="${content_tag}"
764
- if [[ -n "${BUILDKITE_COMMIT:-}" ]]; then
765
- commit_tag=$(ci_base_tag_with_suffix "${stable_tag}" "${BUILDKITE_COMMIT}")
766
- fi
767
- CI_BASE_IMAGE_TAG_COMMIT_REF="${commit_tag}"
768
-
769
- # *_REF is the logical tag recorded in metadata. *_EXTRA is only passed to
770
- # bake when that tag is not already the primary tag, avoiding duplicates.
771
- if should_push_stable_ci_base_tag; then
772
- primary_tag="${content_tag}"
773
- CI_BASE_IMAGE_TAG_STABLE="${stable_tag}"
903
+ trusted_content_tag=$(ci_base_tag_with_suffix \
904
+ "${stable_tag}" "v${metadata_version}-${CI_BASE_CONTENT_HASH}")
905
+ if [[ -n "${scope}" ]]; then
906
+ content_tag=$(ci_base_tag_with_suffix \
907
+ "${stable_tag}" "v${metadata_version}-${scope}-${CI_BASE_CONTENT_HASH}")
908
else
775
- primary_tag="${commit_tag:-${content_tag}}"
776
- CI_BASE_IMAGE_TAG_STABLE=""
909
+ content_tag="${trusted_content_tag}"
910
fi
911
+ CI_BASE_IMAGE_TAG_CONTENT_REF="${content_tag}"
912
+ CI_BASE_TRUSTED_CONTENT_REF="${trusted_content_tag}"
913
+ primary_tag="${build_tag:-${content_tag}}"
914
+
915
+ # Main writes canonical content refs. Other builds import those refs
916
+ # read-only and publish into a source-scoped preview namespace.
917
+ # A fresh CI build first publishes its unique runtime tag. Content and
918
+ # stable aliases are created only after that image passes identity checks.
919
+ CI_BASE_STABLE_PROMOTION_REF="${stable_tag}"
920
CI_BASE_IMAGE_TAG="${primary_tag}"
779
- if [[ "${primary_tag}" == "${content_tag}" ]]; then
780
- CI_BASE_IMAGE_TAG_CONTENT_EXTRA=""
781
- else
782
- CI_BASE_IMAGE_TAG_CONTENT_EXTRA="${content_tag}"
783
- fi
784
- if [[ -n "${commit_tag}" && "${commit_tag}" != "${primary_tag}" ]]; then
785
- CI_BASE_IMAGE_TAG_COMMIT_EXTRA="${commit_tag}"
786
- else
787
- CI_BASE_IMAGE_TAG_COMMIT_EXTRA=""
788
- fi
921
export CI_BASE_IMAGE_TAG
790
- export CI_BASE_IMAGE_TAG_COMMIT_EXTRA
791
- export CI_BASE_IMAGE_TAG_CONTENT_EXTRA
922
export CI_BASE_IMAGE_TAG_CONTENT_REF
793
- export CI_BASE_IMAGE_TAG_COMMIT_REF
794
- export CI_BASE_IMAGE_TAG_STABLE
923
+ export CI_BASE_TRUSTED_CONTENT_REF
924
+ export CI_BASE_STABLE_PROMOTION_REF
925
926
if is_ci_base_target; then
927
IMAGE_TAG="${primary_tag}"
@@ -800,29 +930,21 @@ configure_ci_base_image_refs() {
930
export IMAGE_TAG
931
932
echo "ci_base primary image tag: ${CI_BASE_IMAGE_TAG}"
803
- if [[ -n "${commit_tag}" ]]; then
804
- if [[ "${commit_tag}" == "${primary_tag}" ]]; then
805
- echo "ci_base commit image tag: ${commit_tag} (primary)"
806
- else
807
- echo "ci_base commit image tag: ${commit_tag}"
808
- fi
933
+ if [[ -n "${build_tag}" ]]; then
934
+ echo "ci_base build image tag: ${build_tag}"
935
fi
936
echo "ci_base content image tag: ${content_tag}"
811
- if [[ -n "${CI_BASE_IMAGE_TAG_STABLE}" ]]; then
812
- echo "ci_base stable alias will also be pushed: ${CI_BASE_IMAGE_TAG_STABLE}"
937
+ if wants_stable_ci_base_tag && is_trusted_ci_cache_writer; then
938
+ echo "ci_base stable alias is eligible for post-build promotion: ${stable_tag}"
939
else
940
echo "ci_base stable alias will not be pushed for this build"
941
echo "Set NIGHTLY=1 on ${CI_BASE_STABLE_BRANCH:-main} to refresh ${stable_tag}"
942
fi
817
- set_buildkite_metadata "rocm-ci-base-image" "${CI_BASE_IMAGE_TAG}"
818
- set_buildkite_metadata "rocm-ci-base-image-content" "${content_tag}"
819
- set_buildkite_metadata "rocm-ci-base-image-commit" "${CI_BASE_IMAGE_TAG_COMMIT_REF:-}"
820
- set_buildkite_metadata "rocm-ci-base-image-stable" "${CI_BASE_IMAGE_TAG_STABLE:-}"
943
return 0
944
fi
945
946
if [[ -z "${CI_BASE_IMAGE:-}" || "${CI_BASE_IMAGE}" == "${stable_tag}" ]]; then
825
- CI_BASE_IMAGE="${primary_tag}"
947
+ CI_BASE_IMAGE="${content_tag}"
948
export CI_BASE_IMAGE
949
echo "Using ci_base image: ${CI_BASE_IMAGE}"
950
else
@@ -830,30 +952,71 @@ configure_ci_base_image_refs() {
952
fi
953
}
954
955
+publish_ci_base_handoff_ref() {
956
+ local source_ref="${1:-${CI_BASE_IMAGE_TAG_BUILD_REF:-${CI_BASE_IMAGE_TAG_CONTENT_REF:-}}}"
957
+ local content_ref="${CI_BASE_IMAGE_TAG_CONTENT_REF:-}"
958
+ local digest=""
959
+ local handoff_ref=""
960
+
961
+ is_ci_base_target || return 0
962
+ if [[ -z "${source_ref}" || -z "${content_ref}" ]]; then
963
+ echo "Cannot publish ci_base handoff without source and content refs" >&2
964
+ return 1
965
+ fi
966
+ if ! digest=$(resolve_image_digest "${source_ref}"); then
967
+ echo "Could not resolve immutable ci_base handoff: ${source_ref}" >&2
968
+ return 1
969
+ fi
970
+
971
+ handoff_ref="${content_ref%@*}@${digest}"
972
+ if ! confirm_remote_image_push "${handoff_ref}"; then
973
+ echo "Could not validate immutable ci_base handoff: ${handoff_ref}" >&2
974
+ return 1
975
+ fi
976
+ if command -v buildkite-agent >/dev/null 2>&1; then
977
+ if ! buildkite-agent meta-data set "rocm-ci-base-image" "${handoff_ref}"; then
978
+ echo "Could not publish required ci_base handoff metadata" >&2
979
+ return 1
980
+ fi
981
+ elif [[ "${BUILDKITE:-false}" == "true" ]]; then
982
+ echo "buildkite-agent not found; cannot publish ci_base handoff" >&2
983
+ return 1
984
+ fi
985
+ echo "Published immutable ci_base handoff: ${handoff_ref}"
986
+}
987
+
988
+ci_base_output_refs() {
989
+ printf '%s\n' \
990
+ "${CI_BASE_IMAGE_TAG_CONTENT_REF:-}" \
991
+ "${CI_BASE_IMAGE_TAG_BUILD_REF:-}" \
992
+ | awk 'NF && !seen[$0]++'
993
+}
994
+
995
ci_base_candidate_refs() {
996
printf '%s\n' \
835
- "${IMAGE_TAG:-}" \
836
- "${CI_BASE_IMAGE_TAG:-}" \
837
- "${CI_BASE_IMAGE_TAG_COMMIT_EXTRA:-}" \
838
- "${CI_BASE_IMAGE_TAG_CONTENT_EXTRA:-}" \
839
- "${CI_BASE_IMAGE_TAG_STABLE:-}" \
997
+ "${CI_BASE_TRUSTED_CONTENT_REF:-}" \
998
+ "${CI_BASE_IMAGE_TAG_CONTENT_REF:-}" \
999
+ "${CI_BASE_STABLE_PROMOTION_REF:-}" \
1000
| awk 'NF && !seen[$0]++'
1001
}
1002
1003
find_matching_ci_base_ref() {
1004
local candidate=""
845
- local candidate_hash=""
1005
+ local candidate_digest=""
1006
+ local immutable_candidate=""
1007
1008
while IFS= read -r candidate; do
1009
[[ -n "${candidate}" ]] || continue
1010
+ # Missing content refs are normal after an input change. Avoid paying
1011
+ # the full digest retry budget for each definitely absent candidate.
1012
remote_image_exists "${candidate}" || continue
850
- candidate_hash=$(get_remote_image_label "${candidate}" "vllm.ci_base.content_hash")
851
- if [[ "${candidate_hash}" == "${CI_BASE_CONTENT_HASH}" ]]; then
852
- if ! remote_ci_base_metadata_is_current "${candidate}"; then
853
- echo "Found matching ci_base content hash but stale metadata: ${candidate}" >&2
854
- continue
855
- fi
856
- printf '%s\n' "${candidate}"
1013
+ if ! candidate_digest=$(resolve_image_digest "${candidate}"); then
1014
+ echo "Could not pin ci_base candidate: ${candidate}" >&2
1015
+ continue
1016
+ fi
1017
+ immutable_candidate="${candidate%@*}@${candidate_digest}"
1018
+ if remote_ci_base_identity_is_current_with_retry "${immutable_candidate}"; then
1019
+ printf '%s\n' "${immutable_candidate}"
1020
return 0
1021
fi
1022
done < <(ci_base_candidate_refs)
@@ -863,26 +1026,100 @@ find_matching_ci_base_ref() {
1026
1027
refresh_ci_base_tags_from_ref() {
1028
local source_ref="$1"
1029
+ local source_digest=""
1030
+ local immutable_source=""
1031
local tag=""
867
- local tag_hash=""
1032
+ local tag_digest=""
1033
+
1034
+ if ! source_digest=$(resolve_image_digest "${source_ref}"); then
1035
+ echo "Could not resolve selected ci_base image: ${source_ref}" >&2
1036
+ return 1
1037
+ fi
1038
+ immutable_source="${source_ref%@*}@${source_digest}"
1039
1040
while IFS= read -r tag; do
1041
[[ -n "${tag}" ]] || continue
1042
[[ "${tag}" != "${source_ref}" ]] || continue
872
- tag_hash=$(get_remote_image_label "${tag}" "vllm.ci_base.content_hash")
873
- if [[ "${tag_hash}" == "${CI_BASE_CONTENT_HASH}" ]]; then
1043
+ tag_digest=""
1044
+ if remote_image_exists "${tag}"; then
1045
+ tag_digest=$(resolve_image_digest "${tag}" || true)
1046
+ fi
1047
+ if [[ "${tag_digest}" == "${source_digest}" ]]; then
1048
echo "ci_base tag is already current: ${tag}"
1049
continue
1050
fi
877
- echo "Updating ci_base tag ${tag} -> ${source_ref}"
878
- docker buildx imagetools create -t "${tag}" "${source_ref}"
879
- done < <(ci_base_candidate_refs)
1051
+ echo "Updating ci_base tag ${tag} -> ${immutable_source}"
1052
+ if ! docker buildx imagetools create --prefer-index=false \
1053
+ -t "${tag}" "${immutable_source}"; then
1054
+ echo "Failed to update ci_base tag ${tag} from ${immutable_source}" >&2
1055
+ return 1
1056
+ fi
1057
+ if ! tag_digest=$(resolve_image_digest "${tag}") \
1058
+ || [[ "${tag_digest}" != "${source_digest}" ]]; then
1059
+ echo "Updated ci_base tag does not resolve to the selected digest: ${tag}" >&2
1060
+ return 1
1061
+ fi
1062
+ done < <(ci_base_output_refs)
1063
+}
1064
+
1065
+promote_stable_ci_base_tag() {
1066
+ local source_ref="${1:-${CI_BASE_IMAGE_TAG_BUILD_REF:-${CI_BASE_IMAGE_TAG_CONTENT_REF:-}}}"
1067
+ local stable_ref="${CI_BASE_STABLE_PROMOTION_REF:-}"
1068
+ local digest=""
1069
+ local immutable_source=""
1070
+
1071
+ is_ci_base_target || return 0
1072
+ wants_stable_ci_base_tag || return 0
1073
+ if ! should_push_stable_ci_base_tag; then
1074
+ echo "Skipping ci_base stable promotion: build is not the trusted current main tip"
1075
+ return 0
1076
+ fi
1077
+ if [[ -z "${source_ref}" || -z "${stable_ref}" ]]; then
1078
+ echo "Cannot promote ci_base stable tag without source and destination refs" >&2
1079
+ return 1
1080
+ fi
1081
+ if ! digest=$(resolve_image_digest "${source_ref}"); then
1082
+ echo "Could not pin ci_base source before stable promotion: ${source_ref}" >&2
1083
+ return 1
1084
+ fi
1085
+ immutable_source="${source_ref%@*}@${digest}"
1086
+ echo "Promoting ci_base stable tag from ${immutable_source}"
1087
+ if ! docker buildx imagetools create --prefer-index=false \
1088
+ -t "${stable_ref}" "${immutable_source}"; then
1089
+ echo "Failed to promote ci_base stable tag" >&2
1090
+ return 1
1091
+ fi
1092
+ if ! confirm_remote_image_push "${stable_ref}"; then
1093
+ echo "Promoted ci_base stable tag did not become visible: ${stable_ref}" >&2
1094
+ return 1
1095
+ fi
1096
+}
1097
+
1098
+maybe_reuse_matching_ci_base_ref() {
1099
+ local matching_ref=""
1100
+
1101
+ matching_ref=$(find_matching_ci_base_ref) || return 1
1102
+
1103
+ echo "Found existing ci_base image with matching content hash: ${matching_ref}"
1104
+ if ! refresh_ci_base_tags_from_ref "${matching_ref}"; then
1105
+ echo "ci_base tag refresh failed after finding an exact image; aborting" >&2
1106
+ return 2
1107
+ fi
1108
+ if ! promote_stable_ci_base_tag "${matching_ref}"; then
1109
+ echo "ci_base stable promotion failed after finding an exact image; aborting" >&2
1110
+ return 2
1111
+ fi
1112
+ if ! publish_ci_base_handoff_ref "${matching_ref}"; then
1113
+ echo "ci_base handoff failed after finding an exact image; aborting" >&2
1114
+ return 2
1115
+ fi
1116
+ echo "Content hashes match -- ci_base is current"
1117
+ return 0
1118
}
1119
1120
maybe_skip_existing_image() {
883
- local remote_hash=""
1121
local remote_revision=""
885
- local matching_ref=""
1122
+ local reuse_status=0
1123
1124
if [[ -z "${IMAGE_TAG:-}" ]]; then
1125
return 0
@@ -892,60 +1129,41 @@ maybe_skip_existing_image() {
1129
echo "FORCE_BUILD=1 set; skipping existing-image check"
1130
return 0
1131
fi
1132
+ if ! is_ci_base_target && should_upload_wheel_artifacts; then
1133
+ echo "Artifact-producing targets always run for the current build"
1134
+ return 0
1135
+ fi
1136
1137
echo "--- :mag: Checking image tag"
1138
echo "Image tag: ${IMAGE_TAG}"
1139
899
- if ! remote_image_exists "${IMAGE_TAG}"; then
900
- if is_ci_base_target && [[ -n "${CI_BASE_CONTENT_HASH:-}" ]]; then
901
- matching_ref=$(find_matching_ci_base_ref || true)
902
- if [[ -n "${matching_ref}" ]]; then
903
- echo "Found existing ci_base image with matching content hash: ${matching_ref}"
904
- if ! refresh_ci_base_tags_from_ref "${matching_ref}"; then
905
- echo "ci_base tag refresh failed; rebuilding to push expected tags"
906
- return 0
907
- fi
908
- echo "Content hashes match -- ci_base is current"
1140
+ if is_ci_base_target && [[ -n "${CI_BASE_CONTENT_HASH:-}" ]]; then
1141
+ maybe_reuse_matching_ci_base_ref || reuse_status=$?
1142
+ case "${reuse_status}" in
1143
+ 0)
1144
echo "Skipping build"
1145
exit 0
911
- fi
912
- fi
1146
+ ;;
1147
+ 1)
1148
+ echo "No current ci_base image matched the expected content hash"
1149
+ echo "Proceeding with build"
1150
+ return 0
1151
+ ;;
1152
+ *)
1153
+ return "${reuse_status}"
1154
+ ;;
1155
+ esac
1156
+ fi
1157
+
1158
+ if ! remote_image_exists "${IMAGE_TAG}"; then
1159
echo "Image not found, proceeding with build"
1160
return 0
1161
fi
1162
917
- IMAGE_EXISTED_BEFORE_BUILD=1
918
-
1163
if is_ci_base_target; then
920
- if [[ -z "${CI_BASE_CONTENT_HASH:-}" ]]; then
921
- echo "ci_base image already exists and no content hash was configured"
922
- echo "Skipping build"
923
- exit 0
924
- fi
925
-
926
- remote_hash=$(get_remote_image_label "${IMAGE_TAG}" "vllm.ci_base.content_hash")
927
- if [[ -n "${remote_hash}" ]]; then
928
- echo "Remote ci_base content hash: ${remote_hash:0:16}..."
929
- if [[ "${remote_hash}" == "${CI_BASE_CONTENT_HASH}" ]]; then
930
- if ! remote_ci_base_metadata_is_current "${IMAGE_TAG}"; then
931
- echo "Content hashes match but ci_base metadata is stale; rebuilding to refresh metadata"
932
- return 0
933
- fi
934
- if ! refresh_ci_base_tags_from_ref "${IMAGE_TAG}"; then
935
- echo "ci_base tag refresh failed; rebuilding to push expected tags"
936
- return 0
937
- fi
938
- echo "Content hashes match -- ci_base is current"
939
- echo "Skipping build"
940
- exit 0
941
- fi
942
-
943
- echo "Content hashes differ -- ci_base is stale, rebuilding"
944
- return 0
945
- fi
946
-
947
- echo "Remote ci_base has no content-hash label; rebuilding to add one"
948
- return 0
1164
+ echo "ci_base image already exists and no content hash was configured"
1165
+ echo "Skipping build"
1166
+ exit 0
1167
fi
1168
1169
if is_commit_image_target; then
@@ -957,12 +1175,6 @@ maybe_skip_existing_image() {
1175
return 0
1176
fi
1177
960
- if should_upload_wheel_artifacts; then
961
- echo "Commit image already exists: ${IMAGE_TAG}"
962
- echo "Continuing build because this target uploads per-build ROCm artifacts"
963
- return 0
964
- fi
965
-
1178
echo "Commit image already exists: ${IMAGE_TAG}"
1179
echo "Skipping build"
1180
exit 0
@@ -1031,6 +1243,18 @@ setup_builder() {
1243
docker buildx ls | grep -E '^\*|^NAME' || docker buildx ls
1244
}
1245
1246
+validate_cache_branch_tag() {
1247
+ local name="$1"
1248
+ local value="$2"
1249
+
1250
+ if [[ -n "${value}" \
1251
+ && (! "${value}" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ \
1252
+ || ${#value} -gt ${ROCM_CACHE_BRANCH_TAG_MAX_LEN}) ]]; then
1253
+ echo "Invalid ${name}; expected a Docker tag component of at most ${ROCM_CACHE_BRANCH_TAG_MAX_LEN} characters" >&2
1254
+ return 1
1255
+ fi
1256
+}
1257
+
1258
prepare_git_cache_metadata() {
1259
local cache_branch_name=""
1260
local cache_base_branch="${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-main}"
@@ -1038,6 +1262,11 @@ prepare_git_cache_metadata() {
1262
local target_repo_url=""
1263
local merge_base_ref=""
1264
1265
+ if is_ci_base_target; then
1266
+ echo "Skipping commit-cache ancestry lookup for content-addressed ci_base"
1267
+ return 0
1268
+ fi
1269
+
1270
if [[ -z "${PARENT_COMMIT:-}" || -z "${VLLM_MERGE_BASE_COMMIT:-}" ]] \
1271
&& git rev-parse --is-shallow-repository 2>/dev/null | grep -q "true"; then
1272
echo "Shallow clone detected - deepening for cache key computation"
@@ -1090,6 +1319,11 @@ prepare_git_cache_metadata() {
1319
echo "Using provided ROCM_CACHE_UPSTREAM_BRANCH_TAG: ${ROCM_CACHE_UPSTREAM_BRANCH_TAG}"
1320
fi
1321
1322
+ validate_cache_branch_tag \
1323
+ ROCM_CACHE_BRANCH_TAG "${ROCM_CACHE_BRANCH_TAG:-}"
1324
+ validate_cache_branch_tag \
1325
+ ROCM_CACHE_UPSTREAM_BRANCH_TAG "${ROCM_CACHE_UPSTREAM_BRANCH_TAG:-}"
1326
+
1327
if [[ -z "${VLLM_MERGE_BASE_COMMIT:-}" ]]; then
1328
target_repo_url=$(get_buildkite_target_repo_url)
1329
merge_base_ref="refs/remotes/vllm-cache-upstream/${cache_base_branch}"
@@ -1119,13 +1353,15 @@ ci_base_metadata_pairs() {
1353
local content_files_hash=""
1354
local base_image=""
1355
local base_image_digest=""
1122
- local git_branch=""
1356
local -a content_paths=()
1357
local -a content_args=()
1358
1359
read -r -a content_paths <<< "${content_files}"
1360
if [[ ${#content_paths[@]} -gt 0 ]]; then
1128
- content_files_hash=$(compute_content_hash "${content_paths[@]}")
1361
+ if ! content_files_hash=$(compute_content_hash "${content_paths[@]}"); then
1362
+ echo "Failed to hash ci_base metadata content files" >&2
1363
+ return 1
1364
+ fi
1365
fi
1366
mapfile -t content_args < <(
1367
get_content_arg_names "${dockerfile}" "${stages}" "${CI_BASE_CONTENT_ARGS:-}"
@@ -1133,9 +1369,11 @@ ci_base_metadata_pairs() {
1369
1370
base_image=$(resolve_dockerfile_arg_value "${dockerfile}" "BASE_IMAGE")
1371
if [[ -n "${base_image}" ]]; then
1136
- base_image_digest=$(resolve_image_digest "${base_image}")
1372
+ if ! base_image_digest=$(resolve_image_digest "${base_image}"); then
1373
+ echo "Failed to resolve ci_base metadata digest for ${base_image}" >&2
1374
+ return 1
1375
+ fi
1376
fi
1138
- git_branch="${BUILDKITE_BRANCH:-${VLLM_BRANCH:-}}"
1377
1378
metadata_pair "vllm.ci_base.metadata_version" "${CI_BASE_METADATA_VERSION:-${DEFAULT_CI_BASE_METADATA_VERSION}}"
1379
metadata_pair "vllm.ci_base.content_hash" "${CI_BASE_CONTENT_HASH:-}"
@@ -1144,21 +1382,16 @@ ci_base_metadata_pairs() {
1382
metadata_pair "vllm.ci_base.content_args" "$(join_words "${content_args[@]}")"
1383
metadata_pair "vllm.ci_base.dockerfile" "${dockerfile}"
1384
metadata_pair "vllm.ci_base.dockerfile_stages" "${stages}"
1147
- metadata_pair "vllm.ci_base.image.primary" "${CI_BASE_IMAGE_TAG:-}"
1148
- metadata_pair "vllm.ci_base.image.content" "${CI_BASE_IMAGE_TAG_CONTENT_REF:-${CI_BASE_IMAGE_TAG_CONTENT_EXTRA:-}}"
1149
- metadata_pair "vllm.ci_base.image.commit" "${CI_BASE_IMAGE_TAG_COMMIT_REF:-${CI_BASE_IMAGE_TAG_COMMIT_EXTRA:-}}"
1150
- metadata_pair "vllm.ci_base.image.stable" "${CI_BASE_IMAGE_TAG_STABLE:-}"
1151
- metadata_pair "vllm.ci_base.git_commit" "${BUILDKITE_COMMIT:-}"
1152
- metadata_pair "vllm.ci_base.git_branch" "${git_branch}"
1153
- metadata_pair "vllm.ci_base.vllm_branch" "${VLLM_BRANCH:-}"
1154
- metadata_pair "vllm.ci_base.stable_branch" "${CI_BASE_STABLE_BRANCH:-main}"
1155
-
1156
- metadata_pair "vllm.rocm.base_image" "${base_image}"
1385
+
1386
+ # The parent identity is digest-only: mutable aliases that resolve to the
1387
+ # same image must produce byte-identical canonical metadata.
1388
+ metadata_pair "vllm.rocm.base_image" "${base_image_digest}"
1389
metadata_pair "vllm.rocm.base_image_digest" "${base_image_digest}"
1390
metadata_pair "vllm.rocm.pytorch_rocm_arch" "${PYTORCH_ROCM_ARCH:-}"
1391
metadata_pair "vllm.rocm.nic_backend" "$(resolve_dockerfile_arg_value "${dockerfile}" "NIC_BACKEND")"
1392
metadata_pair "vllm.rocm.ainic_version" "$(resolve_dockerfile_arg_value "${dockerfile}" "AINIC_VERSION")"
1393
metadata_pair "vllm.rocm.ubuntu_codename" "$(resolve_dockerfile_arg_value "${dockerfile}" "UBUNTU_CODENAME")"
1394
+ metadata_pair "vllm.rocm.max_jobs" "$(resolve_dockerfile_arg_value "${dockerfile}" "max_jobs")"
1395
metadata_pair "vllm.rocm.nixl_repo" "$(resolve_dockerfile_arg_value "${dockerfile}" "NIXL_REPO")"
1396
metadata_pair "vllm.rocm.nixl_commit" "${NIXL_BRANCH:-$(resolve_dockerfile_arg_value "${dockerfile}" "NIXL_BRANCH")}"
1397
metadata_pair "vllm.rocm.ucx_repo" "$(resolve_dockerfile_arg_value "${dockerfile}" "UCX_REPO")"
@@ -1172,22 +1405,18 @@ ci_base_metadata_pairs() {
1405
metadata_pair "vllm.rocm.nixl_cache_key" "${NIXL_CACHE_KEY:-}"
1406
metadata_pair "vllm.rocm.rocshmem_cache_key" "${ROCSHMEM_CACHE_KEY:-}"
1407
metadata_pair "vllm.rocm.deepep_cache_key" "${DEEPEP_CACHE_KEY:-}"
1175
-
1176
- metadata_pair "vllm.buildkite.build_number" "${BUILDKITE_BUILD_NUMBER:-}"
1177
- metadata_pair "vllm.buildkite.build_id" "${BUILDKITE_BUILD_ID:-}"
1408
}
1409
1410
write_ci_base_metadata_annotations() {
1411
local metadata="$1"
1412
local key=""
1413
local value=""
1184
- local annotation=""
1414
1415
[[ -n "${metadata}" ]] || return 0
1416
while IFS=$'\t' read -r key value; do
1417
[[ -n "${key}" && -n "${value}" ]] || continue
1189
- annotation="manifest:${key}=${value}"
1190
- printf ' "%s",\n' "$(hcl_escape_string "${annotation}")"
1418
+ printf ' "%s",\n' \
1419
+ "$(hcl_escape_string "manifest:${key}=${value}")"
1420
done <<< "${metadata}"
1421
}
1422
@@ -1277,23 +1506,16 @@ uses_rocm_rust_cache() {
1506
compute_rocm_csrc_content_hash() {
1507
local bake_dir=""
1508
local dockerfile_rocm=""
1280
- local -a content_paths=(
1281
- "requirements/common.txt"
1282
- "requirements/rocm.txt"
1283
- "pyproject.toml"
1284
- "setup.py"
1285
- "CMakeLists.txt"
1286
- "cmake"
1287
- "csrc"
1288
- "vllm/envs.py"
1289
- "vllm/__init__.py"
1290
- )
1509
+ local content_files="${ROCM_CSRC_CONTENT_FILES:-${DEFAULT_ROCM_CSRC_CONTENT_FILES}}"
1510
+ local stages="${ROCM_CSRC_DOCKERFILE_STAGES:-${DEFAULT_ROCM_CSRC_DOCKERFILE_STAGES}}"
1511
+ local -a content_paths=()
1512
local -a content_args=()
1513
1514
bake_dir=$(dirname "${VLLM_BAKE_FILE}")
1515
dockerfile_rocm="${bake_dir}/Dockerfile.rocm"
1516
+ read -r -a content_paths <<< "${content_files}"
1517
mapfile -t content_args < <(
1296
- get_content_arg_names "${dockerfile_rocm}" "base csrc-build" "${ROCM_CSRC_CONTENT_ARGS:-}"
1518
+ get_content_arg_names "${dockerfile_rocm}" "${stages}" "${ROCM_CSRC_CONTENT_ARGS:-}"
1519
)
1520
1521
{
@@ -1301,9 +1523,9 @@ compute_rocm_csrc_content_hash() {
1523
printf 'dockerfile:%s\n' "${dockerfile_rocm}"
1524
printf 'resolved-build-args:\n'
1525
hash_dockerfile_arg_values "${dockerfile_rocm}" "${content_args[@]}"
1304
- printf 'dockerfile-stages:base csrc-build\n'
1526
+ printf 'dockerfile-stages:%s\n' "${stages}"
1527
if [[ -f "${dockerfile_rocm}" ]]; then
1306
- hash_dockerfile_stages "${dockerfile_rocm}" "base csrc-build"
1528
+ hash_dockerfile_stages "${dockerfile_rocm}" "${stages}"
1529
else
1530
printf 'missing:%s\n' "${dockerfile_rocm}"
1531
fi
@@ -1312,14 +1534,20 @@ compute_rocm_csrc_content_hash() {
1534
1535
compute_rocm_csrc_content_hash_if_needed() {
1536
local cache_repo="${DOCKERHUB_CACHE_REPO:-rocm/vllm-ci-cache}"
1537
+ local write_scope="${CI_BASE_WRITE_SCOPE:-}"
1538
1539
if [[ "${ROCM_CSRC_CONTENT_CACHE:-1}" == "0" ]] || ! uses_rocm_csrc_cache; then
1540
return 0
1541
fi
1542
1543
ROCM_CSRC_CONTENT_HASH=$(compute_rocm_csrc_content_hash)
1321
- ROCM_CSRC_CONTENT_CACHE_REF="${cache_repo}:csrc-rocm-input-${ROCM_CSRC_CONTENT_HASH}"
1544
+ ROCM_CSRC_TRUSTED_CONTENT_CACHE_REF="${cache_repo}:csrc-rocm-input-${ROCM_CSRC_CONTENT_HASH}"
1545
+ ROCM_CSRC_CONTENT_CACHE_REF="${ROCM_CSRC_TRUSTED_CONTENT_CACHE_REF}"
1546
+ if [[ -n "${write_scope}" ]]; then
1547
+ ROCM_CSRC_CONTENT_CACHE_REF="${ROCM_CSRC_CONTENT_CACHE_REF}-${write_scope}"
1548
+ fi
1549
export ROCM_CSRC_CONTENT_HASH
1550
+ export ROCM_CSRC_TRUSTED_CONTENT_CACHE_REF
1551
export ROCM_CSRC_CONTENT_CACHE_REF
1552
echo "ROCm csrc content cache ref: ${ROCM_CSRC_CONTENT_CACHE_REF}"
1553
}
@@ -1327,23 +1555,17 @@ compute_rocm_csrc_content_hash_if_needed() {
1555
compute_rocm_rust_content_hash() {
1556
local bake_dir=""
1557
local dockerfile_rocm=""
1330
- local -a content_paths=(
1331
- "requirements/build/rust.txt"
1332
- "rust/Cargo.lock"
1333
- "rust/Cargo.toml"
1334
- "rust/proto"
1335
- "rust/src"
1336
- "rust-toolchain.toml"
1337
- "tools/build_rust.py"
1338
- "tools/install_protoc.sh"
1339
- "build_rust.sh"
1340
- )
1558
+ local content_files="${ROCM_RUST_CONTENT_FILES:-${DEFAULT_ROCM_RUST_CONTENT_FILES}}"
1559
+ local stages="${ROCM_RUST_DOCKERFILE_STAGES:-${DEFAULT_ROCM_RUST_DOCKERFILE_STAGES}}"
1560
+ local -a content_paths=()
1561
local -a content_args=()
1562
1563
bake_dir=$(dirname "${VLLM_BAKE_FILE}")
1564
dockerfile_rocm="${bake_dir}/Dockerfile.rocm"
1565
+ read -r -a content_paths <<< "${content_files}"
1566
mapfile -t content_args < <(
1346
- get_content_arg_names "${dockerfile_rocm}" "base rust_toolchain_input_0 rust_toolchain_input_1 rust-toolchain-input rust_input_0 rust_input_1 rust-input rust-toolchain rust-build" "${ROCM_RUST_CONTENT_ARGS:-}"
1567
+ get_content_arg_names \
1568
+ "${dockerfile_rocm}" "${stages}" "${ROCM_RUST_CONTENT_ARGS:-}"
1569
)
1570
1571
{
@@ -1351,9 +1573,9 @@ compute_rocm_rust_content_hash() {
1573
printf 'dockerfile:%s\n' "${dockerfile_rocm}"
1574
printf 'resolved-build-args:\n'
1575
hash_dockerfile_arg_values "${dockerfile_rocm}" "${content_args[@]}"
1354
- printf 'dockerfile-stages:base rust_toolchain_input_0 rust_toolchain_input_1 rust-toolchain-input rust_input_0 rust_input_1 rust-input rust-toolchain rust-build\n'
1576
+ printf 'dockerfile-stages:%s\n' "${stages}"
1577
if [[ -f "${dockerfile_rocm}" ]]; then
1356
- hash_dockerfile_stages "${dockerfile_rocm}" "base rust_toolchain_input_0 rust_toolchain_input_1 rust-toolchain-input rust_input_0 rust_input_1 rust-input rust-toolchain rust-build"
1578
+ hash_dockerfile_stages "${dockerfile_rocm}" "${stages}"
1579
else
1580
printf 'missing:%s\n' "${dockerfile_rocm}"
1581
fi
@@ -1362,14 +1584,20 @@ compute_rocm_rust_content_hash() {
1584
1585
compute_rocm_rust_content_hash_if_needed() {
1586
local cache_repo="${DOCKERHUB_CACHE_REPO:-rocm/vllm-ci-cache}"
1587
+ local write_scope="${CI_BASE_WRITE_SCOPE:-}"
1588
1589
if [[ "${ROCM_RUST_CONTENT_CACHE:-1}" == "0" ]] || ! uses_rocm_rust_cache; then
1590
return 0
1591
fi
1592
1593
ROCM_RUST_CONTENT_HASH=$(compute_rocm_rust_content_hash)
1371
- ROCM_RUST_CONTENT_CACHE_REF="${cache_repo}:rust-rocm-input-${ROCM_RUST_CONTENT_HASH}"
1594
+ ROCM_RUST_TRUSTED_CONTENT_CACHE_REF="${cache_repo}:rust-rocm-input-${ROCM_RUST_CONTENT_HASH}"
1595
+ ROCM_RUST_CONTENT_CACHE_REF="${ROCM_RUST_TRUSTED_CONTENT_CACHE_REF}"
1596
+ if [[ -n "${write_scope}" ]]; then
1597
+ ROCM_RUST_CONTENT_CACHE_REF="${ROCM_RUST_CONTENT_CACHE_REF}-${write_scope}"
1598
+ fi
1599
export ROCM_RUST_CONTENT_HASH
1600
+ export ROCM_RUST_TRUSTED_CONTENT_CACHE_REF
1601
export ROCM_RUST_CONTENT_CACHE_REF
1602
echo "ROCm Rust content cache ref: ${ROCM_RUST_CONTENT_CACHE_REF}"
1603
}
@@ -1430,8 +1658,14 @@ write_rocm_build_arg_override() {
1658
"${dockerfile_rocm}" \
1659
"${CI_BASE_DOCKERFILE_STAGES:-${DEFAULT_CI_BASE_DOCKERFILE_STAGES}}" \
1660
"${CI_BASE_CONTENT_ARGS:-}"
1433
- get_content_arg_names "${dockerfile_rocm}" "base csrc-build" "${ROCM_CSRC_CONTENT_ARGS:-}"
1434
- get_content_arg_names "${dockerfile_rocm}" "base rust_toolchain_input_0 rust_toolchain_input_1 rust-toolchain-input rust_input_0 rust_input_1 rust-input rust-toolchain rust-build" "${ROCM_RUST_CONTENT_ARGS:-}"
1661
+ get_content_arg_names \
1662
+ "${dockerfile_rocm}" \
1663
+ "${ROCM_CSRC_DOCKERFILE_STAGES:-${DEFAULT_ROCM_CSRC_DOCKERFILE_STAGES}}" \
1664
+ "${ROCM_CSRC_CONTENT_ARGS:-}"
1665
+ get_content_arg_names \
1666
+ "${dockerfile_rocm}" \
1667
+ "${ROCM_RUST_DOCKERFILE_STAGES:-${DEFAULT_ROCM_RUST_DOCKERFILE_STAGES}}" \
1668
+ "${ROCM_RUST_CONTENT_ARGS:-}"
1669
} | awk 'NF && !seen[$0]++'
1670
)
1671
@@ -1497,6 +1731,7 @@ validate_content_cache_export_mode() {
1731
should_export_content_cache_ref() {
1732
local cache_ref="$1"
1733
local cache_name="$2"
1734
+ local trusted_ref="${3:-${cache_ref}}"
1735
local mode="${ROCM_CONTENT_CACHE_EXPORT_MODE:-missing}"
1736
1737
case "${mode}" in
@@ -1509,8 +1744,13 @@ should_export_content_cache_ref() {
1744
return 1
1745
;;
1746
missing|"")
1512
- if docker buildx imagetools inspect "${cache_ref}" >/dev/null 2>&1; then
1513
- echo "${cache_name} content cache exists; not re-exporting ${cache_ref}"
1747
+ if remote_image_exists "${trusted_ref}"; then
1748
+ echo "${cache_name} trusted content cache exists; not exporting ${cache_ref}"
1749
+ return 1
1750
+ fi
1751
+ if [[ "${cache_ref}" != "${trusted_ref}" ]] \
1752
+ && remote_image_exists "${cache_ref}"; then
1753
+ echo "${cache_name} scoped content cache exists; not re-exporting ${cache_ref}"
1754
return 1
1755
fi
1756
echo "${cache_name} content cache missing; will export ${cache_ref}"
@@ -1555,8 +1795,18 @@ write_rocm_cache_override() {
1795
echo "ROCm final image cache export mode: ${rocm_cache_to_mode}"
1796
1797
if [[ -n "${ROCM_CSRC_CONTENT_CACHE_REF:-}" ]]; then
1558
- csrc_content_cache_from+=("type=registry,ref=${ROCM_CSRC_CONTENT_CACHE_REF}")
1559
- if should_export_content_cache_ref "${ROCM_CSRC_CONTENT_CACHE_REF}" "ROCm csrc"; then
1798
+ csrc_content_cache_from+=(
1799
+ "type=registry,ref=${ROCM_CSRC_TRUSTED_CONTENT_CACHE_REF}"
1800
+ )
1801
+ if [[ "${ROCM_CSRC_CONTENT_CACHE_REF}" != \
1802
+ "${ROCM_CSRC_TRUSTED_CONTENT_CACHE_REF}" ]]; then
1803
+ csrc_content_cache_from+=(
1804
+ "type=registry,ref=${ROCM_CSRC_CONTENT_CACHE_REF}"
1805
+ )
1806
+ fi
1807
+ if should_export_content_cache_ref \
1808
+ "${ROCM_CSRC_CONTENT_CACHE_REF}" "ROCm csrc" \
1809
+ "${ROCM_CSRC_TRUSTED_CONTENT_CACHE_REF}"; then
1810
csrc_cache_to+=(
1811
"type=registry,ref=${ROCM_CSRC_CONTENT_CACHE_REF},mode=${csrc_cache_to_mode},ignore-error=true"
1812
)
@@ -1566,8 +1816,18 @@ write_rocm_cache_override() {
1816
fi
1817
1818
if [[ -n "${ROCM_RUST_CONTENT_CACHE_REF:-}" ]]; then
1569
- rust_content_cache_from+=("type=registry,ref=${ROCM_RUST_CONTENT_CACHE_REF}")
1570
- if should_export_content_cache_ref "${ROCM_RUST_CONTENT_CACHE_REF}" "ROCm Rust"; then
1819
+ rust_content_cache_from+=(
1820
+ "type=registry,ref=${ROCM_RUST_TRUSTED_CONTENT_CACHE_REF}"
1821
+ )
1822
+ if [[ "${ROCM_RUST_CONTENT_CACHE_REF}" != \
1823
+ "${ROCM_RUST_TRUSTED_CONTENT_CACHE_REF}" ]]; then
1824
+ rust_content_cache_from+=(
1825
+ "type=registry,ref=${ROCM_RUST_CONTENT_CACHE_REF}"
1826
+ )
1827
+ fi
1828
+ if should_export_content_cache_ref \
1829
+ "${ROCM_RUST_CONTENT_CACHE_REF}" "ROCm Rust" \
1830
+ "${ROCM_RUST_TRUSTED_CONTENT_CACHE_REF}"; then
1831
rust_cache_to+=(
1832
"type=registry,ref=${ROCM_RUST_CONTENT_CACHE_REF},mode=${rust_cache_to_mode},ignore-error=true"
1833
)
@@ -1772,7 +2032,7 @@ compose_stage_cache_material() {
2032
2033
dependency_cache_ref_exists() {
2034
local cache_ref="$1"
1775
- docker buildx imagetools inspect "${cache_ref}" >/dev/null 2>&1
2035
+ registry_ref_exists_with_retry imagetools "${cache_ref}"
2036
}
2037
2038
dependency_cache_ref_for_target() {
@@ -1914,54 +2174,18 @@ print_bake_config() {
2174
2175
confirm_remote_image_push() {
2176
local image_ref="$1"
1917
- local remote_hash=""
1918
- local remote_revision=""
1919
-
1920
- if ! remote_image_exists "${image_ref}"; then
1921
- return 1
1922
- fi
1923
-
1924
- if is_ci_base_target; then
1925
- if [[ -z "${CI_BASE_CONTENT_HASH:-}" ]]; then
1926
- return 0
1927
- fi
1928
-
1929
- remote_hash=$(get_remote_image_label_with_retry "${image_ref}" "vllm.ci_base.content_hash")
1930
- if [[ -n "${remote_hash}" && "${remote_hash}" == "${CI_BASE_CONTENT_HASH}" ]]; then
1931
- if remote_ci_base_metadata_is_current_with_retry "${image_ref}"; then
1932
- return 0
1933
- fi
1934
-
1935
- echo "Remote image exists with the expected ci_base content hash but stale metadata."
1936
- echo " expected metadata version: ${CI_BASE_METADATA_VERSION:-${DEFAULT_CI_BASE_METADATA_VERSION}}"
1937
- return 1
1938
- fi
2177
1940
- echo "Remote image exists but does not have the expected ci_base content hash."
1941
- echo " expected: ${CI_BASE_CONTENT_HASH:0:16}..."
1942
- echo " found: ${remote_hash:0:16}..."
1943
- return 1
2178
+ if [[ -z "${CI_BASE_CONTENT_HASH:-}" ]]; then
2179
+ remote_image_exists "${image_ref}"
2180
+ return
2181
fi
2182
1946
- if is_commit_image_target; then
1947
- remote_revision=$(get_remote_image_label_with_retry "${image_ref}" "org.opencontainers.image.revision")
1948
- if [[ -n "${remote_revision}" && "${remote_revision}" == "${BUILDKITE_COMMIT}" ]]; then
1949
- return 0
1950
- fi
1951
-
1952
- if [[ -z "${remote_revision}" \
1953
- && ${IMAGE_EXISTED_BEFORE_BUILD} -eq 0 ]] \
1954
- && image_tag_is_commit_scoped; then
1955
- echo "Remote image exists under a commit-scoped tag; accepting push despite missing revision label."
1956
- return 0
1957
- fi
1958
-
1959
- echo "Remote image exists but revision label does not match ${BUILDKITE_COMMIT}."
1960
- echo " found revision: ${remote_revision:-<missing>}"
1961
- return 1
2183
+ if remote_ci_base_identity_is_current_with_retry "${image_ref}"; then
2184
+ return 0
2185
fi
2186
1964
- return 0
2187
+ echo "Remote image does not have the expected complete ci_base identity."
2188
+ return 1
2189
}
2190
2191
verify_dependency_cache_ref() {
@@ -2017,64 +2241,27 @@ seed_dependency_caches_if_needed() {
2241
done
2242
}
2243
2020
-annotate_cache_export_warning() {
2021
- local build_rc="$1"
2244
+run_bake() {
2245
+ local confirmation_ref="${IMAGE_TAG:-}"
2246
2023
- if ! command -v buildkite-agent >/dev/null 2>&1; then
2024
- return 0
2247
+ if is_ci_base_target && [[ -n "${CI_BASE_IMAGE_TAG_BUILD_REF:-}" ]]; then
2248
+ confirmation_ref="${CI_BASE_IMAGE_TAG_BUILD_REF}"
2249
fi
2250
2027
- buildkite-agent annotate \
2028
- --style warning \
2029
- --context "cache-export-warning" \
2030
- "### :warning: Docker cache export failed (non-fatal)
2031
-
2032
-Image was pushed successfully: \`${IMAGE_TAG}\`
2033
-
2034
-The BuildKit build returned exit code ${build_rc}, but the expected image
2035
-is present in the registry. Treating this as a registry cache export failure
2036
-so tests can continue with the pushed image." 2>/dev/null || true
2037
-}
2038
-
2039
-run_bake() {
2040
- local build_rc=0
2041
-
2251
echo "--- :docker: Building ${TARGET}"
2252
docker buildx bake \
2253
"${BAKE_FILES[@]}" \
2254
--progress "${BUILDKIT_PROGRESS:-plain}" \
2046
- "${BAKE_TARGETS[@]}" || build_rc=$?
2255
+ "${BAKE_TARGETS[@]}"
2256
2048
- if [[ ${build_rc} -eq 0 ]]; then
2049
- echo "--- :white_check_mark: Build complete"
2050
- return 0
2051
- fi
2052
-
2053
- echo ""
2054
- echo "WARNING: docker buildx bake exited with code ${build_rc}"
2055
-
2056
- if [[ -n "${IMAGE_TAG:-}" ]]; then
2057
- echo "Checking if image was pushed successfully..."
2058
- if confirm_remote_image_push "${IMAGE_TAG}"; then
2059
- echo ""
2060
- echo "WARNING: Build reported failure (rc=${build_rc}) but the"
2061
- echo " image was pushed successfully: ${IMAGE_TAG}"
2062
- echo ""
2063
- echo " Treating this as a non-fatal registry cache export failure."
2064
- echo " The image is usable, but registry cache may be cold on the next build."
2065
- echo ""
2066
- annotate_cache_export_warning "${build_rc}"
2067
- echo "--- :white_check_mark: Build complete"
2068
- return 0
2257
+ if is_ci_base_target; then
2258
+ if ! confirm_remote_image_push "${confirmation_ref}"; then
2259
+ echo "Fresh ci_base image failed identity validation: ${confirmation_ref}" >&2
2260
+ return 1
2261
fi
2070
-
2071
- echo ""
2072
- echo "ERROR: Build failed and image was NOT confirmed: ${IMAGE_TAG}"
2073
- echo " This is a real build failure, not a cache export warning."
2074
- echo ""
2262
+ refresh_ci_base_tags_from_ref "${confirmation_ref}"
2263
fi
2076
-
2077
- return "${build_rc}"
2264
+ echo "--- :white_check_mark: Build complete"
2265
}
2266
2267
upload_wheel_artifacts_if_present() {
@@ -2082,7 +2269,10 @@ upload_wheel_artifacts_if_present() {
2269
local artifact_dir="artifacts/vllm-rocm-install"
2270
local archive_name="vllm-rocm-install.tar.gz"
2271
local metadata_dir="${wheel_dir}/.vllm-ci-artifact"
2272
+ local build_base_digest=""
2273
+ local expected_native_base_image=""
2274
local native_base_image=""
2275
+ local native_base_digest=""
2276
local whl=""
2277
local whl_name=""
2278
local -a wheels=()
@@ -2100,11 +2290,35 @@ upload_wheel_artifacts_if_present() {
2290
fi
2291
whl="${wheels[0]}"
2292
whl_name=$(basename "${whl}")
2103
- native_base_image="${CI_BASE_IMAGE_TAG_COMMIT_REF:-${CI_BASE_IMAGE:-}}"
2293
+ native_base_image="${CI_BASE_IMAGE_TAG_BUILD_REF:-${CI_BASE_IMAGE:-}}"
2294
if [[ -z "${native_base_image}" ]]; then
2295
echo "Native ROCm artifact requires a ci_base image reference" >&2
2296
return 1
2297
fi
2298
+ if [[ "${BUILDKITE:-false}" == "true" ]]; then
2299
+ expected_native_base_image=$(ci_base_tag_with_suffix \
2300
+ "${CI_BASE_IMAGE_TAG:-rocm/vllm-dev:ci_base}" \
2301
+ "build-${BUILDKITE_BUILD_ID:-}")
2302
+ if [[ "${native_base_image}" != "${expected_native_base_image}" ]]; then
2303
+ echo "Native ROCm artifact requires the exact ci_base build handoff: ${native_base_image}" >&2
2304
+ return 1
2305
+ fi
2306
+ if [[ ! "${CI_BASE_IMAGE:-}" =~ @sha256:[0-9a-f]{64}$ ]]; then
2307
+ echo "ROCm artifact build base must be digest-pinned: ${CI_BASE_IMAGE:-<empty>}" >&2
2308
+ return 1
2309
+ fi
2310
+ build_base_digest="${CI_BASE_IMAGE##*@}"
2311
+ if ! native_base_digest=$(resolve_image_digest "${native_base_image}"); then
2312
+ echo "Could not resolve native ci_base handoff: ${native_base_image}" >&2
2313
+ return 1
2314
+ fi
2315
+ if [[ "${native_base_digest}" != "${build_base_digest}" ]]; then
2316
+ echo "Native ci_base handoff does not match the artifact build base" >&2
2317
+ echo " native: ${native_base_image}@${native_base_digest}" >&2
2318
+ echo " build: ${CI_BASE_IMAGE}" >&2
2319
+ return 1
2320
+ fi
2321
+ fi
2322
2323
echo "--- :package: Uploading ROCm vLLM install artifact"
2324
rm -rf "${artifact_dir}" "${metadata_dir}"
@@ -2141,8 +2355,10 @@ upload_wheel_artifacts_if_present() {
2355
2356
main() {
2357
init_config "$@"
2358
+ configure_ci_base_write_scope
2359
print_header
2360
validate_inputs
2361
+ normalize_ci_worktree_modes
2362
load_ci_hcl
2363
init_bake_files
2364
compute_ci_base_hash_if_needed
@@ -2170,7 +2386,11 @@ main() {
2386
fi
2387
seed_dependency_caches_if_needed
2388
run_bake
2389
+ promote_stable_ci_base_tag
2390
+ publish_ci_base_handoff_ref
2391
upload_wheel_artifacts_if_present
2392
}
2393
2176
-main "$@"
2394
+if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
2395
+ main "$@"
2396
+fi
.buildkite/scripts/hardware_ci/run-amd-test.sh
+247
@@ -36,6 +36,19 @@ set -o pipefail
36
: "${ROCM_DOCKER_TTY:=1}"
37
: "${PYTHONFAULTHANDLER:=1}"
38
: "${PYTEST_TIMEOUT:=2400}"
39
+
40
+# Preserve runner-only values before clear_ci_orchestration_env removes them
41
+# from the environment inherited by the test process.
42
+amd_diagnostics_expected_gpu_count="${VLLM_CI_EXPECTED_GPU_COUNT:-1}"
43
+amd_diagnostics_dir="${VLLM_CI_DIAGNOSTICS_DIR:-artifacts/amd-gpu-diagnostics}"
44
+amd_diagnostics_checkout_root="${BUILDKITE_BUILD_CHECKOUT_PATH:-$(pwd -P)}"
45
+amd_diagnostics_execution_mode="${VLLM_CI_EXECUTION_MODE:-single-node}"
46
+amd_diagnostics_test_group="${VLLM_TEST_GROUP_NAME:-${BUILDKITE_STEP_KEY:-unknown}}"
47
+amd_diagnostics_k8s_node_name="${VLLM_CI_K8S_NODE_NAME:-}"
48
+amd_diagnostics_collected=0
49
+amd_diagnostics_probe_budget_seconds=25
50
+amd_diagnostics_command_timeout_seconds=5
51
+amd_diagnostics_upload_timeout_seconds=20
52
if [[ " ${PYTEST_ADDOPTS:-} " != *" --color"* ]]; then
53
PYTEST_ADDOPTS="${PYTEST_ADDOPTS:+${PYTEST_ADDOPTS} }--color=yes"
54
fi
@@ -84,9 +97,11 @@ clear_ci_orchestration_env() {
97
VLLM_CI_BASE_IMAGE \
98
VLLM_CI_FALLBACK_IMAGE \
99
VLLM_CI_DOCKER_DISABLED \
100
+ VLLM_CI_DIAGNOSTICS_DIR \
101
VLLM_CI_ARTIFACT_GLOB \
102
VLLM_CI_ARTIFACT_CHECKSUM_GLOB \
103
VLLM_CI_EXPECTED_GPU_COUNT \
104
+ VLLM_CI_K8S_NODE_NAME \
105
VLLM_CI_USE_ARTIFACTS \
106
VLLM_CI_RESULTS_ROOT \
107
VLLM_ALLOW_DEPRECATED_BEAM_SEARCH
@@ -350,6 +365,11 @@ prepare_native_workspace() {
365
echo "ROCm artifact commit ${recorded_commit} does not match ${BUILDKITE_COMMIT:-unset}" >&2
366
return 1
367
fi
368
+ if [[ -z "${BUILDKITE_BUILD_ID:-}" \
369
+ || "${recorded_base}" != *":ci_base-build-${BUILDKITE_BUILD_ID}" ]]; then
370
+ echo "ROCm artifact base is not scoped to Buildkite build ${BUILDKITE_BUILD_ID:-unset}: ${recorded_base}" >&2
371
+ return 1
372
+ fi
373
if [[ -z "${VLLM_CI_BASE_IMAGE:-}" || "${recorded_base}" != "${VLLM_CI_BASE_IMAGE}" ]]; then
374
echo "ROCm artifact base ${recorded_base} does not match ${VLLM_CI_BASE_IMAGE:-unset}" >&2
375
return 1
@@ -495,12 +515,220 @@ is_multi_node() {
515
return 1
516
}
517
518
+run_amd_diagnostic() {
519
+ local log_file=$1
520
+ local probe_deadline=$2
521
+ shift 2
522
+ local command_status=0
523
+ local command_timeout_seconds=${amd_diagnostics_command_timeout_seconds}
524
+ local remaining_seconds=$((probe_deadline - SECONDS))
525
+
526
+ {
527
+ printf '\n$'
528
+ printf ' %q' "$@"
529
+ printf '\n'
530
+ } | tee -a "${log_file}"
531
+
532
+ if [[ "${remaining_seconds}" -le 0 ]]; then
533
+ echo "[diagnostic probe budget exhausted; command skipped]" \
534
+ | tee -a "${log_file}"
535
+ return 0
536
+ fi
537
+ if [[ "${remaining_seconds}" -lt "${command_timeout_seconds}" ]]; then
538
+ command_timeout_seconds=${remaining_seconds}
539
+ fi
540
+
541
+ timeout --kill-after=1s "${command_timeout_seconds}s" "$@" 2>&1 \
542
+ | tee -a "${log_file}"
543
+ command_status=${PIPESTATUS[0]}
544
+ if [[ "${command_status}" -ne 0 ]]; then
545
+ echo "[diagnostic command exited with status ${command_status}]" \
546
+ | tee -a "${log_file}"
547
+ fi
548
+ return 0
549
+}
550
+
551
+collect_rocm_failure_diagnostics() {
552
+ local exit_code=$1
553
+ local job_id="${BUILDKITE_JOB_ID:-local}"
554
+ local retry_count="${BUILDKITE_RETRY_COUNT:-0}"
555
+ local parallel_job="${BUILDKITE_PARALLEL_JOB:-0}"
556
+ local diagnostics_relative_path=""
557
+ local diagnostics_path=""
558
+ local upload_root="${amd_diagnostics_checkout_root}"
559
+ local upload_path=""
560
+ local exit_signal=""
561
+ local probe_deadline=0
562
+
563
+ if [[ "${amd_diagnostics_collected}" == "1" ]]; then
564
+ return 0
565
+ fi
566
+ amd_diagnostics_collected=1
567
+
568
+ if [[ "${amd_diagnostics_expected_gpu_count}" == "0" ]]; then
569
+ echo "Skipping AMD GPU diagnostics for a CPU-only job."
570
+ return 0
571
+ fi
572
+
573
+ if ! command -v timeout >/dev/null 2>&1; then
574
+ echo "WARNING: timeout is unavailable; skipping failure diagnostics rather than risking a hung job."
575
+ return 0
576
+ fi
577
+
578
+ job_id="${job_id//[^A-Za-z0-9_.-]/_}"
579
+ retry_count="${retry_count//[^A-Za-z0-9_.-]/_}"
580
+ parallel_job="${parallel_job//[^A-Za-z0-9_.-]/_}"
581
+
582
+ # Buildkite keeps the supplied artifact path. Restrict the configurable
583
+ # directory to a checkout-relative path so the UI shows a clean artifact key.
584
+ if [[ -z "${amd_diagnostics_dir}" \
585
+ || "${amd_diagnostics_dir}" == /* \
586
+ || "${amd_diagnostics_dir}" == *".."* ]]; then
587
+ echo "WARNING: ignoring unsafe VLLM_CI_DIAGNOSTICS_DIR"
588
+ amd_diagnostics_dir="artifacts/amd-gpu-diagnostics"
589
+ fi
590
+ diagnostics_relative_path="${amd_diagnostics_dir}/${job_id}/amd-gpu-diagnostics.log"
591
+ diagnostics_path="${amd_diagnostics_checkout_root}/${diagnostics_relative_path}"
592
+ upload_path="${diagnostics_relative_path}"
593
+
594
+ if ! mkdir -p "$(dirname "${diagnostics_path}")" \
595
+ || ! : > "${diagnostics_path}"; then
596
+ diagnostics_path=$(mktemp -t amd-gpu-diagnostics.XXXXXX.log) || {
597
+ echo "WARNING: unable to create an AMD GPU diagnostics log."
598
+ return 0
599
+ }
600
+ upload_root=$(dirname "${diagnostics_path}")
601
+ upload_path=$(basename "${diagnostics_path}")
602
+ fi
603
+
604
+ if [[ "${exit_code}" -gt 128 && "${exit_code}" -le 192 ]]; then
605
+ exit_signal=$(kill -l "$((exit_code - 128))" 2>/dev/null || true)
606
+ fi
607
+
608
+ {
609
+ echo "AMD GPU failure diagnostics"
610
+ echo "timestamp_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
611
+ echo "exit_code=${exit_code}"
612
+ echo "exit_signal=${exit_signal:-none}"
613
+ echo "build_url=${BUILDKITE_BUILD_URL:-unknown}"
614
+ echo "job_id=${BUILDKITE_JOB_ID:-unknown}"
615
+ echo "step_key=${BUILDKITE_STEP_KEY:-unknown}"
616
+ echo "step_label=${BUILDKITE_LABEL:-unknown}"
617
+ echo "test_group=${amd_diagnostics_test_group}"
618
+ echo "retry_count=${retry_count}"
619
+ echo "parallel_job=${parallel_job}"
620
+ echo "execution_mode=${amd_diagnostics_execution_mode}"
621
+ echo "expected_gpu_count=${amd_diagnostics_expected_gpu_count}"
622
+ echo "probe_budget_seconds=${amd_diagnostics_probe_budget_seconds}"
623
+ echo "command_timeout_seconds=${amd_diagnostics_command_timeout_seconds}"
624
+ echo "upload_timeout_seconds=${amd_diagnostics_upload_timeout_seconds}"
625
+ echo "agent_name=${BUILDKITE_AGENT_NAME:-unknown}"
626
+ echo "k8s_node=${amd_diagnostics_k8s_node_name:-unknown}"
627
+ echo "kernel=$(uname -srmo 2>/dev/null || echo unknown)"
628
+ } > "${diagnostics_path}"
629
+
630
+ echo "--- :rotating_light: AMD GPU failure diagnostics"
631
+ cat "${diagnostics_path}"
632
+
633
+ probe_deadline=$((SECONDS + amd_diagnostics_probe_budget_seconds))
634
+
635
+ # Collect the most useful evidence first so auxiliary probes cannot consume
636
+ # the shared deadline before GPU health is captured.
637
+ echo "AMD GPU diagnostics" | tee -a "${diagnostics_path}"
638
+ if command -v amd-smi >/dev/null 2>&1; then
639
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" amd-smi version
640
+ # Bus data identifies a card within the public node without publishing its
641
+ # persistent UUID, serial number, or process list.
642
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" amd-smi static -b -g all
643
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" amd-smi metric -e -k -P -x -g all
644
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" amd-smi bad-pages -p -r -u -g all
645
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" amd-smi metric -p -t -u -m -v -g all
646
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" amd-smi xgmi -l -g all
647
+ elif command -v rocm-smi >/dev/null 2>&1; then
648
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" rocm-smi \
649
+ --showbus --showreplaycount --showrasinfo --showpagesinfo
650
+ else
651
+ echo "Neither amd-smi nor rocm-smi is available." \
652
+ | tee -a "${diagnostics_path}"
653
+ fi
654
+
655
+ echo "Node resource diagnostics (runner-visible scope)" \
656
+ | tee -a "${diagnostics_path}"
657
+ if command -v df >/dev/null 2>&1; then
658
+ echo "checkout_filesystem:" | tee -a "${diagnostics_path}"
659
+ if [[ -d "${amd_diagnostics_checkout_root}" ]]; then
660
+ (
661
+ cd "${amd_diagnostics_checkout_root}" || exit 0
662
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" df -h \
663
+ --output=fstype,size,used,avail,pcent,itotal,iused,iavail,ipcent \
664
+ -- .
665
+ )
666
+ else
667
+ echo "[checkout directory unavailable]" | tee -a "${diagnostics_path}"
668
+ fi
669
+ echo "tmp_filesystem:" | tee -a "${diagnostics_path}"
670
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" df -h \
671
+ --output=fstype,size,used,avail,pcent,itotal,iused,iavail,ipcent \
672
+ -- /tmp
673
+ fi
674
+ if command -v free >/dev/null 2>&1; then
675
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" free -h
676
+ fi
677
+ if [[ -r /sys/fs/cgroup/memory.events ]]; then
678
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" cat \
679
+ /sys/fs/cgroup/memory.events
680
+ fi
681
+
682
+ echo "Unauthenticated outer-runner network reachability diagnostics" \
683
+ | tee -a "${diagnostics_path}"
684
+ if command -v curl >/dev/null 2>&1; then
685
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" curl -q \
686
+ --silent --location --proto '=https' --proto-redir '=https' \
687
+ --max-redirs 3 --output /dev/null --connect-timeout 2 --max-time 4 \
688
+ --write-out 'target=huggingface http_code=%{http_code} dns_done_s=%{time_namelookup} connect_done_s=%{time_connect} tls_done_s=%{time_appconnect} first_byte_s=%{time_starttransfer} total_s=%{time_total}\n' \
689
+ https://huggingface.co/api/models/gpt2
690
+ run_amd_diagnostic "${diagnostics_path}" "${probe_deadline}" curl -q \
691
+ --silent --location --proto '=https' --proto-redir '=https' \
692
+ --max-redirs 3 --output /dev/null --connect-timeout 2 --max-time 4 \
693
+ --write-out 'target=github_git_smart_http http_code=%{http_code} dns_done_s=%{time_namelookup} connect_done_s=%{time_connect} tls_done_s=%{time_appconnect} first_byte_s=%{time_starttransfer} total_s=%{time_total}\n' \
694
+ 'https://github.com/vllm-project/ci-infra.git/info/refs?service=git-upload-pack'
695
+ else
696
+ echo "curl unavailable; reachability probes skipped." \
697
+ | tee -a "${diagnostics_path}"
698
+ fi
699
+
700
+ if command -v buildkite-agent >/dev/null 2>&1 \
701
+ && [[ -n "${BUILDKITE_JOB_ID:-}" ]]; then
702
+ if (
703
+ cd "${upload_root}" \
704
+ && BUILDKITE_AGENT_DEBUG=false \
705
+ BUILDKITE_AGENT_DEBUG_HTTP=false \
706
+ BUILDKITE_AGENT_TRACE_HTTP=false \
707
+ BUILDKITE_AGENT_LOG_LEVEL=error \
708
+ timeout --kill-after=2s "${amd_diagnostics_upload_timeout_seconds}s" \
709
+ buildkite-agent artifact upload "${upload_path}" \
710
+ >/dev/null 2>&1
711
+ ); then
712
+ echo "Uploaded AMD GPU diagnostics artifact: ${upload_path}"
713
+ else
714
+ echo "WARNING: failed to upload AMD GPU diagnostics artifact: ${upload_path}"
715
+ fi
716
+ else
717
+ echo "Buildkite agent unavailable; diagnostics artifact was not uploaded."
718
+ fi
719
+
720
+ return 0
721
+}
722
+
723
handle_pytest_exit() {
724
local exit_code=$1
725
if [ "$exit_code" -eq 5 ]; then
726
echo "Pytest exit code 5 (no tests collected) - treating as success."
727
exit 0
728
fi
729
+ if [[ "${exit_code}" -ne 0 ]]; then
730
+ collect_rocm_failure_diagnostics "${exit_code}"
731
+ fi
732
exit "$exit_code"
733
}
734
@@ -659,6 +887,19 @@ re_quote_pytest_markers() {
887
echo "${output% }"
888
}
889
890
+# shellcheck disable=SC2317 # Called indirectly by the EXIT trap.
891
+handle_amd_runner_exit() {
892
+ local exit_code=${1:-$?}
893
+ if [[ "${exit_code}" -ne 0 ]]; then
894
+ collect_rocm_failure_diagnostics "${exit_code}"
895
+ fi
896
+ return 0
897
+}
898
+
899
+# Catch both test failures and wrapper/setup failures. Runtime-specific cleanup
900
+# traps below replace this trap but call the same handler before cleaning up.
901
+trap handle_amd_runner_exit EXIT
902
+
903
###############################################################################
904
# Main
905
###############################################################################
@@ -667,7 +908,10 @@ if is_native_runtime; then
908
echo "--- Native in-pod ROCm CI (AMD_CI_RUNTIME=${AMD_CI_RUNTIME:-unset}, NATIVE_CI=${NATIVE_CI:-unset})"
909
artifact_work_dir=""
910
911
+ # shellcheck disable=SC2317 # Called indirectly by the EXIT trap.
912
cleanup_native_workspace() {
913
+ local exit_code=$?
914
+ handle_amd_runner_exit "${exit_code}"
915
if [[ -n "${artifact_work_dir}" ]]; then
916
rm -rf "${artifact_work_dir}"
917
fi
@@ -727,7 +971,10 @@ image_name="${VLLM_CI_FALLBACK_IMAGE:-rocm/vllm-ci:${BUILDKITE_COMMIT:-local}}"
971
artifact_work_dir=""
972
container_name="rocm_${BUILDKITE_COMMIT}_$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 10; echo)"
973
974
+# shellcheck disable=SC2317 # Called indirectly by the EXIT trap.
975
remove_docker_container() {
976
+ local exit_code=$?
977
+ handle_amd_runner_exit "${exit_code}"
978
if docker container inspect "${container_name}" >/dev/null 2>&1; then
979
docker rm -f "${container_name}" || true
980
fi
.buildkite/scripts/rocm/build-ci-base.sh
+19
-3
@@ -12,17 +12,33 @@ metadata_get() {
12
13
main() {
14
local base_refreshed=""
15
+ local selected_base=""
16
+
17
+ # The content identity below describes this checkout. Do not let legacy
18
+ # external pipeline settings switch the build to a remote source tree.
19
+ export REMOTE_VLLM=0
20
+ unset VLLM_BRANCH
21
22
base_refreshed="$(metadata_get rocm-base-refresh)"
23
+ selected_base="$(metadata_get rocm-base-image)"
24
+ if [[ -n "${selected_base}" ]]; then
25
+ if [[ ! "${selected_base}" =~ @sha256:[0-9a-f]{64}$ ]]; then
26
+ echo "Selected ROCm base handoff is not digest-pinned: ${selected_base}" >&2
27
+ return 1
28
+ fi
29
+ export BASE_IMAGE="${selected_base}"
30
+ echo "Using selected ROCm base image for ci_base: ${BASE_IMAGE}"
31
+ elif [[ "${BUILDKITE:-false}" == "true" ]]; then
32
+ echo "Required ROCm base handoff metadata is missing" >&2
33
+ return 1
34
+ fi
35
+
36
if [[ "${base_refreshed}" == "1" ]]; then
18
- export BASE_IMAGE
37
export CI_BASE_PUSH_STABLE_TAG
38
21
- BASE_IMAGE="$(metadata_get rocm-base-image)"
39
CI_BASE_PUSH_STABLE_TAG="$(metadata_get rocm-base-push-stable-tag)"
40
CI_BASE_PUSH_STABLE_TAG="${CI_BASE_PUSH_STABLE_TAG:-0}"
41
25
- echo "Using refreshed ROCm base image for ci_base: ${BASE_IMAGE}"
42
echo "Push stable ci_base tag: ${CI_BASE_PUSH_STABLE_TAG}"
43
fi
44
.buildkite/scripts/rocm/build-test-image.sh
+39
-30
@@ -1,8 +1,8 @@
1
#!/usr/bin/env bash
2
# Build the ROCm CI test image or wheel artifact.
3
#
4
-# When Dockerfile.rocm_base changes, always build the full image so downstream
5
-# ROCm tests can validate the freshly rebuilt base -> ci_base -> ci image chain.
4
+# When base selection changes, build the full image so downstream ROCm tests
5
+# validate the selected base -> ci_base -> CI image chain.
6
7
set -euo pipefail
8
@@ -13,46 +13,55 @@ metadata_get() {
13
fi
14
}
15
16
-use_ci_base_if_present() {
17
- local ci_base_image=""
16
+load_digest_handoff() {
17
+ local metadata_key="$1"
18
+ local env_name="$2"
19
+ local description="$3"
20
+ local image_ref=""
21
19
- ci_base_image="$(metadata_get rocm-ci-base-image)"
20
- if [[ -z "${ci_base_image}" ]]; then
22
+ image_ref="$(metadata_get "${metadata_key}")"
23
+ if [[ -z "${image_ref}" ]]; then
24
return 1
25
fi
23
-
24
- export CI_BASE_IMAGE="${ci_base_image}"
25
- echo "Using ROCm ci_base image selected by the preceding build step: ${CI_BASE_IMAGE}"
26
-}
27
-
28
-use_refreshed_base_if_present() {
29
- local base_refreshed=""
30
-
31
- base_refreshed="$(metadata_get rocm-base-refresh)"
32
- if [[ "${base_refreshed}" != "1" ]]; then
26
+ if [[ ! "${image_ref}" =~ @sha256:[0-9a-f]{64}$ ]]; then
27
+ echo "${description} is not digest-pinned: ${image_ref}" >&2
28
return 1
29
fi
30
36
- export BASE_IMAGE
37
- export IMAGE_TAG_LATEST
38
-
39
- BASE_IMAGE="$(metadata_get rocm-base-image)"
40
- IMAGE_TAG_LATEST="$(metadata_get rocm-ci-image-descriptive)"
41
-
42
- echo "Using refreshed ROCm base image for test image: ${BASE_IMAGE}"
43
- if [[ -n "${IMAGE_TAG_LATEST}" ]]; then
44
- echo "Also tagging full ROCm CI image as: ${IMAGE_TAG_LATEST}"
45
- fi
46
-
47
- return 0
31
+ printf -v "${env_name}" '%s' "${image_ref}"
32
+ export "${env_name?}"
33
+ echo "Using ${description}: ${image_ref}"
34
}
35
36
main() {
37
local base_refreshed=0
38
53
- use_ci_base_if_present || true
39
+ # This job always builds the checked-out commit. Some externally generated
40
+ # pipeline templates still inject remote-fetch settings; do not let those
41
+ # settings make the source identity commit-specific or bypass local edits.
42
+ export REMOTE_VLLM=0
43
+ unset VLLM_BRANCH
44
+
45
+ if ! load_digest_handoff \
46
+ rocm-ci-base-image CI_BASE_IMAGE "ROCm ci_base handoff"; then
47
+ if [[ "${BUILDKITE:-false}" == "true" ]]; then
48
+ echo "Required ROCm ci_base handoff metadata is missing or invalid" >&2
49
+ return 1
50
+ fi
51
+ echo "No ROCm ci_base handoff metadata found; using the local default"
52
+ fi
53
+
54
+ if ! load_digest_handoff \
55
+ rocm-base-image BASE_IMAGE "ROCm base handoff"; then
56
+ if [[ "${BUILDKITE:-false}" == "true" ]]; then
57
+ echo "Required ROCm base handoff metadata is missing or invalid" >&2
58
+ return 1
59
+ fi
60
+ echo "No ROCm base handoff metadata found; using the local default"
61
+ fi
62
55
- if use_refreshed_base_if_present; then
63
+ if [[ "$(metadata_get rocm-base-refresh)" == "1" ]]; then
64
+ echo "The selected ROCm base differs from the current stable base"
65
base_refreshed=1
66
fi
67
.buildkite/scripts/rocm/refresh-base-image.sh
+398
-323
@@ -1,19 +1,20 @@
1
#!/usr/bin/env bash
2
-# Build and publish a fresh ROCm base image when Dockerfile.rocm_base changes.
3
-#
4
-# Normal AMD CI builds should not pay for this path. The script no-ops unless
5
-# docker/Dockerfile.rocm_base changed relative to the branch base, the previous
6
-# main commit, or ROCM_BASE_REFRESH_FORCE=1 is set.
2
+# Select the content-addressed ROCm base for this checkout, building it only
3
+# when the exact content ref is absent.
4
5
set -euo pipefail
6
7
DOCKERFILE="${ROCM_BASE_DOCKERFILE:-docker/Dockerfile.rocm_base}"
8
BASE_REPO="${ROCM_BASE_IMAGE_REPO:-rocm/vllm-dev}"
12
-CI_IMAGE_REPO="${ROCM_CI_IMAGE_REPO:-rocm/vllm-ci}"
9
+CACHE_REPO="${ROCM_BASE_CACHE_REPO:-${DOCKERHUB_CACHE_REPO:-rocm/vllm-ci-cache}}"
10
BUILDER_NAME="${ROCM_BASE_BUILDER_NAME:-vllm-rocm-base-builder}"
14
-DEFAULT_ROCM_BASE_METADATA_VERSION="1"
11
+DEFAULT_ROCM_BASE_METADATA_VERSION="2"
12
DEFAULT_ROCM_BASE_CONTENT_FILES="${DOCKERFILE}"
16
-DEFAULT_ROCM_BASE_CONTENT_ARGS="BASE_IMAGE TRITON_BRANCH TRITON_REPO PYTORCH_BRANCH PYTORCH_REPO PYTORCH_VISION_BRANCH PYTORCH_VISION_REPO PYTORCH_AUDIO_BRANCH PYTORCH_AUDIO_REPO FA_BRANCH FA_REPO AITER_BRANCH AITER_REPO MORI_BRANCH MORI_REPO PYTORCH_ROCM_ARCH PYTHON_VERSION USE_SCCACHE"
13
+
14
+ROCM_BASE_LAYER_CACHE_REF=""
15
+ROCM_BASE_TRUSTED_LAYER_CACHE_REF="${CACHE_REPO}:rocm-base-main"
16
+ROCM_BASE_STABLE_TAG_UPDATED=0
17
+declare -a ROCM_BASE_CACHE_ARGS=()
18
19
metadata_set() {
20
local key="$1"
@@ -21,7 +22,10 @@ metadata_set() {
22
23
[[ -n "${value}" ]] || return 0
24
if command -v buildkite-agent >/dev/null 2>&1; then
24
- buildkite-agent meta-data set "${key}" "${value}" || true
25
+ buildkite-agent meta-data set "${key}" "${value}"
26
+ elif [[ "${BUILDKITE:-false}" == "true" ]]; then
27
+ echo "buildkite-agent is required to publish ROCm base metadata" >&2
28
+ return 1
29
fi
30
}
31
@@ -56,249 +60,263 @@ tag_component() {
60
clean_docker_tag "${input:-unknown}" | cut -c1-"${max_chars}"
61
}
62
59
-extract_arg_default() {
60
- local arg_name="$1"
61
-
62
- sed -n -E "s/^[[:space:]]*ARG[[:space:]]+${arg_name}=\"?([^\"[:space:]]+)\"?.*/\\1/p" \
63
- "${DOCKERFILE}" | head -1
63
+normalize_repo_slug() {
64
+ local repo_slug="${1:-}"
65
+
66
+ repo_slug="${repo_slug%/}"
67
+ repo_slug="${repo_slug%.git}"
68
+ repo_slug="${repo_slug#https://github.com/}"
69
+ repo_slug="${repo_slug#http://github.com/}"
70
+ repo_slug="${repo_slug#ssh://git@github.com/}"
71
+ repo_slug="${repo_slug#git@github.com:}"
72
+ repo_slug="${repo_slug#github.com/}"
73
+ printf '%s\n' "${repo_slug}"
74
}
75
66
-resolve_image_digest() {
67
- local image_ref="$1"
68
-
69
- docker buildx imagetools inspect "${image_ref}" 2>/dev/null \
70
- | sed -n -E 's/^Digest:[[:space:]]+//p' \
71
- | head -1 || true
72
-}
73
-
74
-resolve_rocm_base_arg_value() {
75
- local arg_name="$1"
76
- local use_sccache="$2"
77
-
78
- case "${arg_name}" in
79
- USE_SCCACHE)
80
- printf '%s\n' "${use_sccache}"
81
- ;;
82
- *)
83
- extract_arg_default "${arg_name}"
84
- ;;
85
- esac
76
+is_trusted_main_build() {
77
+ local actual_repo=""
78
+ local trusted_repo=""
79
+
80
+ [[ "${BUILDKITE:-false}" == "true" ]] || return 1
81
+ [[ "${BUILDKITE_PULL_REQUEST:-false}" == "false" ]] || return 1
82
+ [[ "${BUILDKITE_BRANCH:-}" == "${ROCM_BASE_STABLE_BRANCH:-main}" ]] || return 1
83
+ actual_repo=$(normalize_repo_slug "${BUILDKITE_REPO:-}")
84
+ trusted_repo=$(normalize_repo_slug \
85
+ "${ROCM_BASE_STABLE_REPO_SLUG:-vllm-project/vllm}")
86
+ [[ -n "${actual_repo}" && "${actual_repo}" == "${trusted_repo}" ]]
87
}
88
88
-hash_rocm_base_arg_values() {
89
- local use_sccache="$1"
90
- local base_image_digest="$2"
91
- local arg_name=""
92
- local arg_value=""
93
- shift 2 || true
94
-
95
- for arg_name in "$@"; do
96
- [[ -n "${arg_name}" ]] || continue
97
- arg_value=$(resolve_rocm_base_arg_value "${arg_name}" "${use_sccache}")
98
- printf 'arg:%s=%s\n' "${arg_name}" "${arg_value:-<empty>}"
99
- if [[ "${arg_name}" == "BASE_IMAGE" && -n "${arg_value}" ]]; then
100
- printf 'arg:%s.digest=%s\n' "${arg_name}" "${base_image_digest:-unknown}"
101
- fi
102
- done
103
-}
89
+rocm_base_layer_cache_scope() {
90
+ local pull_request="${BUILDKITE_PULL_REQUEST:-false}"
91
+ local branch="${BUILDKITE_PULL_REQUEST_HEAD_BRANCH:-${BUILDKITE_BRANCH:-local}}"
92
+ local identity=""
93
+ local repo_slug=""
94
105
-rocm_version_from_base_image() {
106
- local base_image="$1"
107
- local version=""
95
+ if is_trusted_main_build; then
96
+ printf 'main\n'
97
+ return 0
98
+ fi
99
+ if [[ "${pull_request}" != "false" && -n "${pull_request}" ]]; then
100
+ repo_slug=$(normalize_repo_slug "${BUILDKITE_REPO:-local}")
101
+ identity=$(printf '%s\n' "${repo_slug:-local}" | sha256sum | cut -c1-12)
102
+ printf 'pr-%s-%s\n' \
103
+ "$(tag_component "${pull_request}" 32)" "${identity}"
104
+ return 0
105
+ fi
106
109
- version="$(sed -n -E 's/.*:([0-9]+\.[0-9]+(\.[0-9]+)?)-.*/\1/p' <<<"${base_image}")"
110
- tag_component "${version:-${base_image}}" 16
107
+ identity=$(printf '%s\n%s\n' "${BUILDKITE_REPO:-local}" "${branch}" \
108
+ | sha256sum | cut -c1-12)
109
+ printf 'preview-%s-%s\n' "$(tag_component "${branch}" 24)" "${identity}"
110
}
111
113
-git_diff_changed_base() {
114
- local range="$1"
115
- [[ -n "$(git diff --name-only "${range}" -- "${DOCKERFILE}" 2>/dev/null)" ]]
116
-}
112
+configure_rocm_base_layer_cache() {
113
+ local scope=""
114
118
-short_git_ref() {
119
- local ref="$1"
115
+ ROCM_BASE_CACHE_ARGS=()
116
+ if [[ "${ROCM_BASE_NO_CACHE:-0}" == "1" ]]; then
117
+ ROCM_BASE_CACHE_ARGS+=(--no-cache)
118
+ ROCM_BASE_LAYER_CACHE_REF="disabled"
119
+ return 0
120
+ fi
121
121
- git rev-parse --short "${ref}" 2>/dev/null || printf '%s\n' "${ref}"
122
+ scope=$(rocm_base_layer_cache_scope)
123
+ ROCM_BASE_LAYER_CACHE_REF="${CACHE_REPO}:rocm-base-${scope}"
124
+ ROCM_BASE_CACHE_ARGS+=(
125
+ --cache-from "type=registry,ref=${ROCM_BASE_LAYER_CACHE_REF}"
126
+ )
127
+ if [[ "${ROCM_BASE_LAYER_CACHE_REF}" != \
128
+ "${ROCM_BASE_TRUSTED_LAYER_CACHE_REF}" ]]; then
129
+ ROCM_BASE_CACHE_ARGS+=(
130
+ --cache-from "type=registry,ref=${ROCM_BASE_TRUSTED_LAYER_CACHE_REF}"
131
+ )
132
+ fi
133
+ ROCM_BASE_CACHE_ARGS+=(
134
+ --cache-to \
135
+ "type=registry,ref=${ROCM_BASE_LAYER_CACHE_REF},mode=max,ignore-error=true"
136
+ )
137
}
138
124
-extract_arg_default_from_ref() {
125
- local ref="$1"
126
- local arg_name="$2"
127
- local content=""
139
+extract_arg_default() {
140
+ local arg_name="$1"
141
129
- content="$(git show "${ref}:${DOCKERFILE}" 2>/dev/null || true)"
142
sed -n -E "s/^[[:space:]]*ARG[[:space:]]+${arg_name}=\"?([^\"[:space:]]+)\"?.*/\\1/p" \
131
- <<<"${content}" | head -1
143
+ "${DOCKERFILE}" | head -1
144
}
145
134
-log_arg_default_changes() {
135
- local old_ref="$1"
136
- local new_ref="$2"
137
- local content_args="${ROCM_BASE_CONTENT_ARGS:-${DEFAULT_ROCM_BASE_CONTENT_ARGS}}"
138
- local arg_name=""
139
- local old_value=""
140
- local new_value=""
141
- local changed=0
142
-
143
- echo "Changed ROCm base ARG defaults:"
144
- for arg_name in ${content_args}; do
145
- old_value="$(extract_arg_default_from_ref "${old_ref}" "${arg_name}")"
146
- new_value="$(extract_arg_default_from_ref "${new_ref}" "${arg_name}")"
147
- if [[ "${old_value}" != "${new_value}" ]]; then
148
- echo " - ${arg_name}: ${old_value:-<unset>} -> ${new_value:-<unset>}"
149
- changed=1
150
- fi
151
- done
152
-
153
- if [[ "${changed}" == "0" ]]; then
154
- echo " - none detected; Dockerfile instructions changed outside tracked ARG defaults"
146
+resolve_image_digest() {
147
+ local image_ref="$1"
148
+ local attempts="${ROCM_IMAGE_DIGEST_ATTEMPTS:-4}"
149
+ local delay_secs="${ROCM_IMAGE_DIGEST_RETRY_DELAY:-2}"
150
+ local attempt=0
151
+ local output=""
152
+ local digest=""
153
+ local status=0
154
+
155
+ if [[ "${image_ref}" =~ @(sha256:[0-9a-f]{64})$ ]]; then
156
+ printf '%s\n' "${BASH_REMATCH[1]}"
157
+ return 0
158
+ fi
159
+ if [[ ! "${attempts}" =~ ^[1-9][0-9]*$ \
160
+ || ! "${delay_secs}" =~ ^[0-9]+$ ]]; then
161
+ echo "Invalid image digest retry configuration" >&2
162
+ return 1
163
fi
156
-}
157
-
158
-log_arg_line_diff() {
159
- local range="$1"
160
- local arg_diff=""
164
162
- arg_diff="$(
163
- git diff --unified=0 "${range}" -- "${DOCKERFILE}" 2>/dev/null \
164
- | awk '/^[+-][[:space:]]*ARG[[:space:]]/ && $0 !~ /^(---|\+\+\+)/ { print " " $0 }' \
165
- || true
166
- )"
165
+ for ((attempt = 1; attempt <= attempts; attempt++)); do
166
+ status=0
167
+ output=$(docker buildx imagetools inspect "${image_ref}" 2>&1) || status=$?
168
+ digest=$(awk '$1 == "Digest:" { print $2; exit }' <<< "${output}")
169
+ if ((status == 0)) && [[ "${digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
170
+ printf '%s\n' "${digest}"
171
+ return 0
172
+ fi
173
+ if ((attempt < attempts)); then
174
+ printf \
175
+ 'Image digest lookup failed for %s (%d/%d, status %d); retrying\n' \
176
+ "${image_ref}" "${attempt}" "${attempts}" "${status}" >&2
177
+ sleep "${delay_secs}"
178
+ fi
179
+ done
180
168
- if [[ -n "${arg_diff}" ]]; then
169
- echo "Changed Dockerfile ARG lines:"
170
- printf '%s\n' "${arg_diff}"
171
- fi
181
+ printf 'Failed to resolve digest for %s (status %d)\n%s\n' \
182
+ "${image_ref}" "${status}" "${output:-<no output>}" >&2
183
+ return 1
184
}
185
174
-log_rocm_base_change_check() {
175
- local context="$1"
176
- local range="$2"
177
- local old_ref="$3"
178
- local old_short=""
179
- local head_short=""
180
-
181
- old_short="$(short_git_ref "${old_ref}")"
182
- head_short="$(short_git_ref HEAD)"
186
+trusted_base_content_ref() {
187
+ local base_hash="$1"
188
+ local metadata_version="$2"
189
184
- echo "--- :mag: ROCm base refresh check"
185
- echo "Context: ${context}"
186
- echo "Dockerfile: ${DOCKERFILE}"
187
- echo "Base revision: ${old_short}"
188
- echo "Head revision: ${head_short}"
189
- echo "Git diff range: ${range}"
190
+ printf '%s:base-v%s-%s\n' "${BASE_REPO}" "${metadata_version}" "${base_hash}"
191
}
192
192
-log_rocm_base_rebuild_reason() {
193
- local context="$1"
194
- local range="$2"
195
- local old_ref="$3"
196
- local changed_files=""
197
-
198
- log_rocm_base_change_check "${context}" "${range}" "${old_ref}"
193
+scoped_base_content_ref() {
194
+ local base_hash="$1"
195
+ local metadata_version="$2"
196
200
- changed_files="$(git diff --name-only "${range}" -- "${DOCKERFILE}" 2>/dev/null || true)"
201
- echo "Changed files:"
202
- if [[ -n "${changed_files}" ]]; then
203
- sed 's/^/ - /' <<<"${changed_files}"
204
- else
205
- echo " - ${DOCKERFILE}"
206
- fi
207
- log_arg_default_changes "${old_ref}" HEAD
208
- log_arg_line_diff "${range}"
209
- echo "Decision: rebuilding ROCm base image because ${DOCKERFILE} changed."
197
+ printf '%s:base-v%s-preview-%s\n' \
198
+ "${BASE_REPO}" "${metadata_version}" "${base_hash}"
199
}
200
212
-rocm_base_changed_in_range() {
213
- local context="$1"
214
- local range="$2"
215
- local old_ref="$3"
216
-
217
- if git_diff_changed_base "${range}"; then
218
- log_rocm_base_rebuild_reason "${context}" "${range}" "${old_ref}"
219
- return 0
201
+find_matching_base_content_ref() {
202
+ local expected_hash="$1"
203
+ local expected_version="$2"
204
+ local image_ref=""
205
+ local digest=""
206
+ local immutable_ref=""
207
+ local label_values=""
208
+ local remote_hash=""
209
+ local remote_version=""
210
+ local attempts="${ROCM_IMAGE_DIGEST_ATTEMPTS:-4}"
211
+ local delay_secs="${ROCM_IMAGE_DIGEST_RETRY_DELAY:-2}"
212
+ local attempt=0
213
+ local inspect_status=0
214
+ shift 2
215
+
216
+ if [[ ! "${attempts}" =~ ^[1-9][0-9]*$ \
217
+ || ! "${delay_secs}" =~ ^[0-9]+$ ]]; then
218
+ echo "Invalid image identity retry configuration" >&2
219
+ return 1
220
fi
221
222
- log_rocm_base_change_check "${context}" "${range}" "${old_ref}"
223
- echo "Decision: ROCm base refresh not required; ${DOCKERFILE} is unchanged."
222
+ for image_ref in "$@"; do
223
+ if ! digest=$(resolve_image_digest "${image_ref}"); then
224
+ continue
225
+ fi
226
+ immutable_ref="${image_ref}@${digest}"
227
+ label_values=""
228
+ for ((attempt = 1; attempt <= attempts; attempt++)); do
229
+ inspect_status=0
230
+ label_values=$(docker buildx imagetools inspect "${immutable_ref}" \
231
+ --format '{{ index .Image.Config.Labels "vllm.rocm_base.content_hash" }} {{ index .Image.Config.Labels "vllm.rocm_base.metadata_version" }}' \
232
+ 2>/dev/null) || inspect_status=$?
233
+ remote_hash=""
234
+ remote_version=""
235
+ if ((inspect_status == 0)) && [[ -n "${label_values}" ]]; then
236
+ read -r remote_hash remote_version _ <<< "${label_values}"
237
+ if [[ "${remote_hash}" == "${expected_hash}" \
238
+ && "${remote_version}" == "${expected_version}" ]]; then
239
+ printf '%s\n' "${immutable_ref}"
240
+ return 0
241
+ fi
242
+ fi
243
+ if ((attempt < attempts)); then
244
+ printf \
245
+ 'ROCm base identity lookup incomplete or mismatched for %s (%d/%d); retrying\n' \
246
+ "${immutable_ref}" "${attempt}" "${attempts}" >&2
247
+ sleep "${delay_secs}"
248
+ fi
249
+ done
250
+ if ((inspect_status == 0)) \
251
+ && [[ -n "${remote_hash}" && -n "${remote_version}" ]]; then
252
+ echo "ROCm base content ref has unexpected identity: ${immutable_ref}" >&2
253
+ echo " expected hash/version: ${expected_hash} / ${expected_version}" >&2
254
+ echo " found hash/version: ${remote_hash} / ${remote_version}" >&2
255
+ # A content-addressed ref with the wrong identity is corrupt. The
256
+ # stable alias is allowed to describe older content and is only a
257
+ # migration/recovery candidate.
258
+ if [[ "${image_ref}" != "${BASE_REPO}:base" ]]; then
259
+ return 2
260
+ fi
261
+ fi
262
+ echo "Failed to read complete ROCm base identity labels: ${immutable_ref}" >&2
263
+ done
264
return 1
265
}
266
227
-rocm_base_changed() {
228
- local base_branch="${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-main}"
229
- local base_ref="refs/remotes/origin/${base_branch}"
230
- local merge_base=""
231
-
232
- if [[ "${ROCM_BASE_REFRESH_SKIP:-0}" == "1" ]]; then
233
- echo "ROCM_BASE_REFRESH_SKIP=1 set; skipping ROCm base refresh"
234
- return 1
235
- fi
267
+rocm_version_from_base_image() {
268
+ local base_image="$1"
269
+ local version=""
270
237
- if [[ "${ROCM_BASE_REFRESH_FORCE:-0}" == "1" ]]; then
238
- echo "ROCM_BASE_REFRESH_FORCE=1 set; refreshing ROCm base image"
239
- return 0
240
- fi
271
+ version="$(sed -n -E 's/.*:([0-9]+\.[0-9]+(\.[0-9]+)?)-.*/\1/p' <<<"${base_image}")"
272
+ tag_component "${version:-${base_image}}" 16
273
+}
274
242
- if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
243
- echo "Not in a git checkout; skipping ROCm base refresh unless forced"
275
+should_push_stable_tag() {
276
+ if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" ]]; then
277
return 1
278
fi
279
247
- if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" ]]; then
248
- git fetch --no-tags --depth=200 origin \
249
- "+refs/heads/${base_branch}:${base_ref}" >/dev/null 2>&1 || true
250
- merge_base=$(git merge-base HEAD "${base_ref}" 2>/dev/null || true)
251
- if [[ -z "${merge_base}" ]]; then
252
- echo "Unable to determine merge base with PR base ${base_ref}; skipping ROCm base refresh unless forced"
253
- return 1
254
- fi
255
- if rocm_base_changed_in_range \
256
- "pull request build against ${base_ref}" \
257
- "${merge_base}...HEAD" \
258
- "${merge_base}"; then
259
- return 0
260
- fi
261
- elif [[ "${BUILDKITE_BRANCH:-}" == "${ROCM_BASE_STABLE_BRANCH:-main}" ]] \
262
- && git rev-parse --verify HEAD~1 >/dev/null 2>&1; then
263
- if rocm_base_changed_in_range \
264
- "stable branch build; comparing against previous ${ROCM_BASE_STABLE_BRANCH:-main} commit" \
265
- "HEAD~1..HEAD" \
266
- "HEAD~1"; then
267
- return 0
268
- fi
269
- else
270
- git fetch --no-tags --depth=200 origin \
271
- "+refs/heads/${base_branch}:${base_ref}" >/dev/null 2>&1 || true
272
- merge_base=$(git merge-base HEAD "${base_ref}" 2>/dev/null || true)
273
- if [[ -z "${merge_base}" ]]; then
274
- echo "Unable to determine merge base with branch base ${base_ref}; skipping ROCm base refresh unless forced"
275
- return 1
276
- fi
277
- if rocm_base_changed_in_range \
278
- "branch build against ${base_ref}" \
279
- "${merge_base}...HEAD" \
280
- "${merge_base}"; then
281
- return 0
282
- fi
280
+ if [[ "${ROCM_BASE_PUSH_STABLE_TAG:-}" == "0" ]]; then
281
+ return 1
282
fi
283
285
- return 1
284
+ is_trusted_main_build
285
}
286
288
-should_push_stable_tag() {
289
- if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" ]]; then
287
+trusted_main_tip_matches_build() {
288
+ local branch="${ROCM_BASE_STABLE_BRANCH:-main}"
289
+ local build_commit="${BUILDKITE_COMMIT:-}"
290
+ local remote_tip=""
291
+
292
+ is_trusted_main_build || return 1
293
+ if [[ ! "${build_commit}" =~ ^[0-9a-fA-F]{40}$ ]]; then
294
+ echo "Skipping ROCm stable tag: Buildkite commit is missing or invalid" >&2
295
return 1
296
fi
292
-
293
- if [[ "${ROCM_BASE_PUSH_STABLE_TAG:-}" == "1" ]]; then
294
- return 0
297
+ remote_tip=$(git ls-remote --exit-code "${BUILDKITE_REPO}" \
298
+ "refs/heads/${branch}" 2>/dev/null | awk 'NR == 1 { print $1 }')
299
+ if [[ ! "${remote_tip}" =~ ^[0-9a-fA-F]{40}$ ]]; then
300
+ echo "Skipping ROCm stable tag: could not resolve remote ${branch} tip" >&2
301
+ return 1
302
fi
296
- if [[ "${ROCM_BASE_PUSH_STABLE_TAG:-}" == "0" ]]; then
303
+ if [[ "${remote_tip,,}" != "${build_commit,,}" ]]; then
304
+ echo "Skipping ROCm stable tag: ${branch} changed after ${build_commit} (${remote_tip})" >&2
305
return 1
306
fi
307
+ return 0
308
+}
309
300
- [[ "${BUILDKITE_PULL_REQUEST:-false}" == "false" \
301
- && "${BUILDKITE_BRANCH:-}" == "${ROCM_BASE_STABLE_BRANCH:-main}" ]]
310
+promote_stable_base_tag() {
311
+ local source_ref="$1"
312
+ local stable_tag="$2"
313
+
314
+ ROCM_BASE_STABLE_TAG_UPDATED=0
315
+ should_push_stable_tag && trusted_main_tip_matches_build || return 0
316
+
317
+ docker buildx imagetools create --prefer-index=false \
318
+ -t "${stable_tag}" "${source_ref}"
319
+ ROCM_BASE_STABLE_TAG_UPDATED=1
320
}
321
322
setup_builder() {
@@ -315,29 +333,43 @@ compute_base_content_hash() {
333
local use_sccache="$1"
334
local base_image_digest="$2"
335
local content_files="${ROCM_BASE_CONTENT_FILES:-${DEFAULT_ROCM_BASE_CONTENT_FILES}}"
318
- local content_args="${ROCM_BASE_CONTENT_ARGS:-${DEFAULT_ROCM_BASE_CONTENT_ARGS}}"
336
+ local sccache_arg=""
337
+ local sccache_value=""
338
local -a content_paths=()
320
- local -a content_arg_names=()
339
340
read -r -a content_paths <<< "${content_files}"
323
- read -r -a content_arg_names <<< "${content_args}"
341
342
{
343
printf 'content-files-hash:%s\n' "$(compute_content_hash "${content_paths[@]}")"
344
printf 'dockerfile:%s\n' "${DOCKERFILE}"
328
- printf 'resolved-build-args:\n'
329
- hash_rocm_base_arg_values \
330
- "${use_sccache}" "${base_image_digest}" "${content_arg_names[@]}"
345
+ printf 'base-image-digest:%s\n' "${base_image_digest}"
346
+ printf 'use-sccache:%s\n' "${use_sccache}"
347
+ if [[ "${use_sccache}" == "1" ]]; then
348
+ # These values can change the installed binary or final image
349
+ # configuration. SCCACHE_ENDPOINT is transport-only and therefore
350
+ # deliberately excluded from image identity.
351
+ for sccache_arg in \
352
+ SCCACHE_DOWNLOAD_URL \
353
+ SCCACHE_BUCKET_NAME \
354
+ SCCACHE_REGION_NAME \
355
+ SCCACHE_S3_NO_CREDENTIALS; do
356
+ sccache_value="${!sccache_arg:-}"
357
+ if [[ -z "${sccache_value}" ]]; then
358
+ sccache_value=$(extract_arg_default "${sccache_arg}")
359
+ fi
360
+ printf 'arg:%s=%s\n' \
361
+ "${sccache_arg}" "${sccache_value:-<empty>}"
362
+ done
363
+ fi
364
} | sha256sum | cut -d' ' -f1
365
}
366
367
build_base_image() {
368
local use_sccache="${ROCM_BASE_USE_SCCACHE:-${USE_SCCACHE:-0}}"
369
local base_hash=""
337
- local build_date=""
338
- local build_suffix=""
370
local base_image_arg=""
371
local base_image_digest=""
372
+ local pinned_base_image=""
373
local rocm_version=""
374
local triton_arg=""
375
local pytorch_arg=""
@@ -348,20 +380,19 @@ build_base_image() {
380
local mori_arg=""
381
local python_version_arg=""
382
local pytorch_rocm_arch_arg=""
351
- local pytorch_branch=""
352
- local aiter_branch=""
383
local dependency_summary=""
354
- local descriptor=""
355
- local ci_descriptor=""
356
- local descriptive_tag=""
384
local stable_tag="${BASE_REPO}:base"
358
- local ci_descriptive_tag=""
385
+ local trusted_content_tag=""
386
+ local scoped_content_tag=""
387
+ local writable_content_tag=""
388
+ local build_ref=""
389
+ local immutable_ref=""
390
+ local selected_digest=""
391
+ local stable_digest=""
392
+ local reuse_status=0
393
local content_files="${ROCM_BASE_CONTENT_FILES:-${DEFAULT_ROCM_BASE_CONTENT_FILES}}"
360
- local content_args="${ROCM_BASE_CONTENT_ARGS:-${DEFAULT_ROCM_BASE_CONTENT_ARGS}}"
394
local content_files_hash=""
395
local metadata_version="${ROCM_BASE_METADATA_VERSION:-${DEFAULT_ROCM_BASE_METADATA_VERSION}}"
363
- local -a tags=()
364
- local -a no_cache_args=()
396
local -a sccache_args=()
397
local -a content_paths=()
398
@@ -369,13 +400,21 @@ build_base_image() {
400
echo "Error: ROCm base Dockerfile not found: ${DOCKERFILE}" >&2
401
exit 1
402
fi
372
-
373
- build_date="${ROCM_BASE_TAG_DATE:-$(date -u +%Y%m%d)}"
374
- if [[ -n "${BUILDKITE_BUILD_NUMBER:-}" ]]; then
375
- build_suffix="_bk_${BUILDKITE_BUILD_NUMBER}"
403
+ if [[ ! "${metadata_version}" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]{0,15}$ ]]; then
404
+ echo "Invalid ROCm base metadata version: ${metadata_version}" >&2
405
+ return 1
406
+ fi
407
+ if [[ "${use_sccache}" != "0" && "${use_sccache}" != "1" ]]; then
408
+ echo "ROCm base USE_SCCACHE must be 0 or 1: ${use_sccache}" >&2
409
+ return 1
410
fi
411
+
412
base_image_arg="$(extract_arg_default BASE_IMAGE)"
378
- base_image_digest="$(resolve_image_digest "${base_image_arg}")"
413
+ if ! base_image_digest=$(resolve_image_digest "${base_image_arg}"); then
414
+ echo "Failed to resolve ROCm base input image: ${base_image_arg}" >&2
415
+ return 1
416
+ fi
417
+ pinned_base_image="${base_image_arg%@*}@${base_image_digest}"
418
read -r -a content_paths <<< "${content_files}"
419
content_files_hash="$(compute_content_hash "${content_paths[@]}")"
420
base_hash=$(compute_base_content_hash "${use_sccache}" "${base_image_digest}")
@@ -389,125 +428,161 @@ build_base_image() {
428
mori_arg="$(extract_arg_default MORI_BRANCH)"
429
python_version_arg="$(extract_arg_default PYTHON_VERSION)"
430
pytorch_rocm_arch_arg="$(extract_arg_default PYTORCH_ROCM_ARCH)"
392
- pytorch_branch="$(tag_component "${pytorch_arg}" 16)"
393
- aiter_branch="$(tag_component "${aiter_arg}" 24)"
431
dependency_summary="base=${base_image_arg},rocm=${rocm_version},python=${python_version_arg},pytorch=${pytorch_arg},torchvision=${pytorch_vision_arg},torchaudio=${pytorch_audio_arg},triton=${triton_arg},flash-attn=${fa_arg},aiter=${aiter_arg},mori=${mori_arg},pytorch-rocm-arch=${pytorch_rocm_arch_arg}"
395
- descriptor="$(clean_docker_tag "base_custom_aiter_${aiter_branch}_torch_${pytorch_branch}_${build_date}${build_suffix}")"
396
- ci_descriptor="$(clean_docker_tag "ci_custom_aiter_${aiter_branch}_torch_${pytorch_branch}_${build_date}${build_suffix}")"
397
-
398
- descriptive_tag="${BASE_REPO}:${descriptor}"
399
- ci_descriptive_tag="${CI_IMAGE_REPO}:${ci_descriptor}"
400
-
401
- tags=(-t "${descriptive_tag}")
402
- if should_push_stable_tag; then
403
- tags+=(-t "${stable_tag}")
404
- metadata_set "rocm-base-push-stable-tag" "1"
432
+ trusted_content_tag=$(trusted_base_content_ref "${base_hash}" "${metadata_version}")
433
+ scoped_content_tag=$(scoped_base_content_ref "${base_hash}" "${metadata_version}")
434
+ # Preview writes share an exact-content namespace so stacked PRs can reuse
435
+ # identical images. They may import the trusted ref, but never overwrite it.
436
+ if is_trusted_main_build; then
437
+ scoped_content_tag="${trusted_content_tag}"
438
+ writable_content_tag="${trusted_content_tag}"
439
else
406
- metadata_set "rocm-base-push-stable-tag" "0"
440
+ writable_content_tag="${scoped_content_tag}"
441
fi
442
409
- if [[ "${ROCM_BASE_NO_CACHE:-1}" == "1" ]]; then
410
- no_cache_args=(--no-cache)
443
+ configure_rocm_base_layer_cache
444
+
445
+ if [[ "${use_sccache}" == "1" ]]; then
446
+ for env_name in \
447
+ SCCACHE_DOWNLOAD_URL \
448
+ SCCACHE_ENDPOINT \
449
+ SCCACHE_BUCKET_NAME \
450
+ SCCACHE_REGION_NAME \
451
+ SCCACHE_S3_NO_CREDENTIALS; do
452
+ if [[ -n "${!env_name:-}" ]]; then
453
+ sccache_args+=(--build-arg "${env_name}=${!env_name}")
454
+ fi
455
+ done
456
fi
457
413
- for env_name in \
414
- SCCACHE_DOWNLOAD_URL \
415
- SCCACHE_ENDPOINT \
416
- SCCACHE_BUCKET_NAME \
417
- SCCACHE_REGION_NAME \
418
- SCCACHE_S3_NO_CREDENTIALS; do
419
- if [[ -n "${!env_name:-}" ]]; then
420
- sccache_args+=(--build-arg "${env_name}=${!env_name}")
421
- fi
422
- done
423
-
424
- echo "--- :docker: Building ROCm base image"
458
+ echo "--- :docker: Preparing ROCm base image"
459
echo "Dockerfile: ${DOCKERFILE}"
426
- echo "Descriptive tag: ${descriptive_tag}"
460
+ echo "Trusted content tag: ${trusted_content_tag}"
461
+ echo "Writable content tag: ${writable_content_tag}"
462
echo "Stable tag: ${stable_tag} ($(should_push_stable_tag && echo enabled || echo disabled))"
463
echo "Content hash: ${base_hash}"
464
echo "Dependency summary: ${dependency_summary}"
465
echo "USE_SCCACHE: ${use_sccache}"
466
+ echo "BuildKit layer cache: ${ROCM_BASE_LAYER_CACHE_REF}"
467
+
468
+ if [[ "${ROCM_BASE_REFRESH_FORCE:-0}" != "1" \
469
+ && "${ROCM_BASE_NO_CACHE:-0}" != "1" ]]; then
470
+ reuse_status=0
471
+ if [[ "${trusted_content_tag}" == "${scoped_content_tag}" ]]; then
472
+ immutable_ref=$(find_matching_base_content_ref \
473
+ "${base_hash}" "${metadata_version}" \
474
+ "${trusted_content_tag}" "${stable_tag}") || reuse_status=$?
475
+ else
476
+ immutable_ref=$(find_matching_base_content_ref \
477
+ "${base_hash}" "${metadata_version}" \
478
+ "${trusted_content_tag}" "${scoped_content_tag}" \
479
+ "${stable_tag}") \
480
+ || reuse_status=$?
481
+ fi
482
+ else
483
+ reuse_status=1
484
+ echo "Forced/no-cache ROCm base build; bypassing content image reuse"
485
+ fi
486
432
- docker buildx build \
433
- "${no_cache_args[@]}" \
434
- --pull \
435
- --progress "${BUILDKIT_PROGRESS:-plain}" \
436
- --file "${DOCKERFILE}" \
437
- --build-arg "USE_SCCACHE=${use_sccache}" \
438
- "${sccache_args[@]}" \
439
- --label "org.opencontainers.image.source=https://github.com/vllm-project/vllm" \
440
- --label "org.opencontainers.image.vendor=vLLM" \
441
- --label "org.opencontainers.image.title=vLLM ROCm base" \
442
- --label "org.opencontainers.image.revision=${BUILDKITE_COMMIT:-}" \
443
- --label "vllm.rocm_base.metadata_version=${metadata_version}" \
444
- --label "vllm.rocm_base.content_hash=${base_hash}" \
445
- --label "vllm.rocm_base.content_files_hash=${content_files_hash}" \
446
- --label "vllm.rocm_base.dockerfile=${DOCKERFILE}" \
447
- --label "vllm.rocm_base.image.descriptive=${descriptive_tag}" \
448
- --label "vllm.rocm_base.image.stable=${stable_tag}" \
449
- --label "vllm.rocm_base.git_commit=${BUILDKITE_COMMIT:-}" \
450
- --label "vllm.rocm_base.stable_branch=${ROCM_BASE_STABLE_BRANCH:-main}" \
451
- --label "vllm.rocm_base.descriptor=${descriptor}" \
452
- --label "vllm.rocm_base.dependency_summary=${dependency_summary}" \
453
- --label "vllm.rocm_base.base_image=${base_image_arg}" \
454
- --label "vllm.rocm_base.base_image_digest=${base_image_digest}" \
455
- --label "vllm.rocm_base.dependency.rocm=${rocm_version}" \
456
- --label "vllm.rocm_base.dependency.python=${python_version_arg}" \
457
- --label "vllm.rocm_base.dependency.pytorch=${pytorch_arg}" \
458
- --label "vllm.rocm_base.dependency.torchvision=${pytorch_vision_arg}" \
459
- --label "vllm.rocm_base.dependency.torchaudio=${pytorch_audio_arg}" \
460
- --label "vllm.rocm_base.dependency.triton=${triton_arg}" \
461
- --label "vllm.rocm_base.dependency.flash_attention=${fa_arg}" \
462
- --label "vllm.rocm_base.dependency.aiter=${aiter_arg}" \
463
- --label "vllm.rocm_base.dependency.mori=${mori_arg}" \
464
- --label "vllm.rocm_base.pytorch_rocm_arch=${pytorch_rocm_arch_arg}" \
465
- "${tags[@]}" \
466
- --push \
467
- .
468
-
469
- docker buildx imagetools inspect "${descriptive_tag}" >/dev/null
487
+ if ((reuse_status > 1)); then
488
+ return "${reuse_status}"
489
+ fi
490
491
+ if [[ ${reuse_status} -eq 0 && -n "${immutable_ref}" ]]; then
492
+ echo "Reusing ROCm base image with matching content: ${immutable_ref}"
493
+ if [[ "${immutable_ref%@*}" == "${stable_tag}" \
494
+ && "${writable_content_tag}" != "${stable_tag}" ]]; then
495
+ echo "Backfilling ROCm base content tag: ${writable_content_tag}"
496
+ docker buildx imagetools create --prefer-index=false \
497
+ -t "${writable_content_tag}" "${immutable_ref}"
498
+ if ! immutable_ref=$(find_matching_base_content_ref \
499
+ "${base_hash}" "${metadata_version}" \
500
+ "${writable_content_tag}"); then
501
+ echo "Backfilled ROCm base content tag failed identity validation" >&2
502
+ return 1
503
+ fi
504
+ fi
505
+ else
506
+ build_ref="${writable_content_tag}"
507
+ echo "No reusable ROCm base content image found; building ${build_ref}"
508
+ setup_builder
509
+ docker buildx build \
510
+ "${ROCM_BASE_CACHE_ARGS[@]}" \
511
+ --pull \
512
+ --provenance=false \
513
+ --progress "${BUILDKIT_PROGRESS:-plain}" \
514
+ --file "${DOCKERFILE}" \
515
+ --build-arg "BASE_IMAGE=${pinned_base_image}" \
516
+ --build-arg "USE_SCCACHE=${use_sccache}" \
517
+ "${sccache_args[@]}" \
518
+ --label "org.opencontainers.image.source=https://github.com/vllm-project/vllm" \
519
+ --label "org.opencontainers.image.vendor=vLLM" \
520
+ --label "org.opencontainers.image.title=vLLM ROCm base" \
521
+ --label "vllm.rocm_base.metadata_version=${metadata_version}" \
522
+ --label "vllm.rocm_base.content_hash=${base_hash}" \
523
+ --label "vllm.rocm_base.content_files_hash=${content_files_hash}" \
524
+ --label "vllm.rocm_base.dockerfile=${DOCKERFILE}" \
525
+ --label "vllm.rocm_base.dependency_summary=${dependency_summary}" \
526
+ --label "vllm.rocm_base.base_image=${pinned_base_image}" \
527
+ --label "vllm.rocm_base.base_image_digest=${base_image_digest}" \
528
+ --label "vllm.rocm_base.dependency.rocm=${rocm_version}" \
529
+ --label "vllm.rocm_base.dependency.python=${python_version_arg}" \
530
+ --label "vllm.rocm_base.dependency.pytorch=${pytorch_arg}" \
531
+ --label "vllm.rocm_base.dependency.torchvision=${pytorch_vision_arg}" \
532
+ --label "vllm.rocm_base.dependency.torchaudio=${pytorch_audio_arg}" \
533
+ --label "vllm.rocm_base.dependency.triton=${triton_arg}" \
534
+ --label "vllm.rocm_base.dependency.flash_attention=${fa_arg}" \
535
+ --label "vllm.rocm_base.dependency.aiter=${aiter_arg}" \
536
+ --label "vllm.rocm_base.dependency.mori=${mori_arg}" \
537
+ --label "vllm.rocm_base.pytorch_rocm_arch=${pytorch_rocm_arch_arg}" \
538
+ -t "${build_ref}" \
539
+ --push \
540
+ .
541
+ if ! immutable_ref=$(find_matching_base_content_ref \
542
+ "${base_hash}" "${metadata_version}" "${build_ref}"); then
543
+ echo "Published ROCm base image failed identity validation: ${build_ref}" >&2
544
+ return 1
545
+ fi
546
+ fi
547
+
548
+ selected_digest="${immutable_ref##*@}"
549
+ metadata_set "rocm-base-image" "${immutable_ref}"
550
+ metadata_set "rocm-base-push-stable-tag" "0"
551
+ stable_digest=$(resolve_image_digest "${stable_tag}" 2>/dev/null || true)
552
+ if [[ -n "${stable_digest}" && "${stable_digest}" == "${selected_digest}" ]]; then
553
+ echo "--- :white_check_mark: Existing ROCm base is current"
554
+ echo "Stable ${stable_tag} already resolves to ${selected_digest}"
555
+ return 0
556
+ fi
557
+
558
+ promote_stable_base_tag "${immutable_ref}" "${stable_tag}"
559
+
560
+ metadata_set "rocm-base-push-stable-tag" "${ROCM_BASE_STABLE_TAG_UPDATED}"
561
metadata_set "rocm-base-refresh" "1"
472
- metadata_set "rocm-base-image" "${descriptive_tag}"
473
- metadata_set "rocm-base-image-descriptive" "${descriptive_tag}"
474
- metadata_set "rocm-base-image-stable" "${stable_tag}"
475
- metadata_set "rocm-base-image-ci-descriptive" "${ci_descriptive_tag}"
476
- metadata_set "rocm-base-metadata-version" "${metadata_version}"
477
- metadata_set "rocm-base-content-hash" "${base_hash}"
478
- metadata_set "rocm-base-content-files-hash" "${content_files_hash}"
479
- metadata_set "rocm-base-content-files" "${content_files}"
480
- metadata_set "rocm-base-content-args" "${content_args}"
481
- metadata_set "rocm-base-base-image-digest" "${base_image_digest}"
482
- metadata_set "rocm-base-dockerfile" "${DOCKERFILE}"
483
- metadata_set "rocm-base-descriptor" "${descriptor}"
484
- metadata_set "rocm-base-dependency-summary" "${dependency_summary}"
485
- metadata_set "rocm-base-dependency-rocm" "${rocm_version}"
486
- metadata_set "rocm-base-dependency-python" "${python_version_arg}"
487
- metadata_set "rocm-base-dependency-pytorch" "${pytorch_arg}"
488
- metadata_set "rocm-base-dependency-torchvision" "${pytorch_vision_arg}"
489
- metadata_set "rocm-base-dependency-torchaudio" "${pytorch_audio_arg}"
490
- metadata_set "rocm-base-dependency-triton" "${triton_arg}"
491
- metadata_set "rocm-base-dependency-flash-attention" "${fa_arg}"
492
- metadata_set "rocm-base-dependency-aiter" "${aiter_arg}"
493
- metadata_set "rocm-base-dependency-mori" "${mori_arg}"
494
- metadata_set "rocm-base-pytorch-rocm-arch" "${pytorch_rocm_arch_arg}"
495
- metadata_set "rocm-ci-image-descriptive" "${ci_descriptive_tag}"
562
563
echo "--- :white_check_mark: ROCm base image published"
498
- echo "Use BASE_IMAGE=${descriptive_tag} for downstream ROCm CI builds"
564
+ echo "Use BASE_IMAGE=${immutable_ref} for downstream ROCm CI builds"
565
}
566
567
main() {
568
metadata_set "rocm-base-refresh" "0"
569
504
- if ! rocm_base_changed; then
505
- echo "ROCm base Dockerfile did not change; skipping base image refresh"
570
+ if [[ "${ROCM_BASE_REFRESH_SKIP:-0}" == "1" ]]; then
571
+ local stable_ref="${BASE_REPO}:base"
572
+ local stable_digest=""
573
+
574
+ if ! stable_digest=$(resolve_image_digest "${stable_ref}"); then
575
+ echo "Could not pin explicitly skipped ROCm base: ${stable_ref}" >&2
576
+ return 1
577
+ fi
578
+ metadata_set "rocm-base-image" "${stable_ref}@${stable_digest}"
579
+ metadata_set "rocm-base-push-stable-tag" "0"
580
+ echo "ROCM_BASE_REFRESH_SKIP=1; pinned current stable base at ${stable_digest}"
581
return 0
582
fi
508
-
509
- setup_builder
583
build_base_image
584
}
585
513
-main "$@"
586
+if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
587
+ main "$@"
588
+fi
.buildkite/scripts/rocm/smoke-test-image.sh
+12
-1
@@ -3,7 +3,18 @@
3
4
set -euo pipefail
5
6
-image_ref="${VLLM_CI_SMOKE_IMAGE:-rocm/vllm-ci:${BUILDKITE_COMMIT:?BUILDKITE_COMMIT is required}}"
6
+if [[ "${ROCM_CI_ARTIFACT_ONLY:-0}" == "1" ]]; then
7
+ base_refreshed=""
8
+ if command -v buildkite-agent >/dev/null 2>&1; then
9
+ base_refreshed="$(buildkite-agent meta-data get rocm-base-refresh 2>/dev/null || true)"
10
+ fi
11
+ if [[ "${base_refreshed}" != "1" ]]; then
12
+ echo "ROCM_CI_ARTIFACT_ONLY=1; no full image was built, skipping smoke test"
13
+ exit 0
14
+ fi
15
+fi
16
+
17
+image_ref="${VLLM_CI_SMOKE_IMAGE:-${IMAGE_TAG:-rocm/vllm-ci:${BUILDKITE_COMMIT:?BUILDKITE_COMMIT is required}}}"
18
19
docker run --rm --network=none --entrypoint /bin/bash "${image_ref}" -ec '
20
if [ ! -d /vllm-workspace ]; then echo Missing directory: /vllm-workspace >&2; exit 1; fi
.dockerignore
+3
@@ -1,5 +1,8 @@
1
/.venv
2
/build
3
+/artifacts/vllm-rocm-install/
4
+/bake-config-build-*.json
5
+/wheel-export/
6
dist
7
vllm/*.so
8
vllm/vllm-rs
docker/Dockerfile.rocm
+23
-16
@@ -254,20 +254,31 @@ RUN --mount=type=cache,id=vllm-rocm-cargo-registry,target=/root/.cargo/registry,
254
fi
255
256
# -----------------------
257
-# vLLM native build stages
257
+# Shared vLLM build dependencies
258
#
259
-# csrc-build intentionally copies only files that affect ROCm native extension
260
-# compilation. That keeps unrelated CI/test/docs edits from invalidating the
261
-# expensive HIP/C++ build layer.
262
-FROM base AS csrc-build
259
+# Install requirements once, before bringing source-specific inputs into either
260
+# consumer. Native compilation and wheel packaging then share this expensive
261
+# layer while retaining their independent source cache boundaries.
262
+FROM base AS build_vllm_dependencies
263
ARG COMMON_WORKDIR
264
+ENV VLLM_TARGET_DEVICE=rocm
265
WORKDIR ${COMMON_WORKDIR}/vllm
266
266
-COPY requirements/rocm.txt requirements/rocm.txt
267
-COPY requirements/common.txt requirements/common.txt
267
+COPY --from=fetch_vllm \
268
+ ${COMMON_WORKDIR}/vllm/requirements/common.txt \
269
+ ${COMMON_WORKDIR}/vllm/requirements/rocm.txt \
270
+ requirements/
271
+
272
RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \
273
uv pip install --system -r requirements/rocm.txt
274
275
+# -----------------------
276
+# vLLM native build stages
277
+#
278
+# csrc-build intentionally copies only files that affect ROCm native extension
279
+# compilation. That keeps unrelated CI/test/docs edits from invalidating the
280
+# expensive HIP/C++ build layer.
281
+FROM build_vllm_dependencies AS csrc-build
282
# pyproject.toml is bind-mounted so it is not persisted in the csrc-build image,
283
# but it still participates in the BuildKit cache key for this RUN step.
284
COPY setup.py CMakeLists.txt ./
@@ -277,7 +288,6 @@ COPY csrc csrc/
288
COPY vllm/envs.py vllm/envs.py
289
COPY vllm/__init__.py vllm/__init__.py
290
280
-ENV VLLM_TARGET_DEVICE=rocm
291
ENV SETUPTOOLS_SCM_PRETEND_VERSION="0.0.0+rocm.csrc.build"
292
293
RUN --mount=type=bind,source=pyproject.toml,target=${COMMON_WORKDIR}/vllm/pyproject.toml \
@@ -296,11 +306,10 @@ RUN --mount=type=bind,source=pyproject.toml,target=${COMMON_WORKDIR}/vllm/pyproj
306
307
# Build the full vLLM ROCm wheel by reusing the native extension wheel from
308
# csrc-build. This stage still rebuilds for Python/package changes, but skips
299
-# the expensive HIP/C++ compile when native inputs are unchanged.
300
-FROM fetch_vllm AS build_vllm
301
-ARG COMMON_WORKDIR
302
-ENV VLLM_TARGET_DEVICE=rocm
303
-
309
+# the expensive dependency installation and HIP/C++ compilation when their
310
+# respective inputs are unchanged.
311
+FROM build_vllm_dependencies AS build_vllm
312
+COPY --from=fetch_vllm ${COMMON_WORKDIR}/vllm ${COMMON_WORKDIR}/vllm
313
COPY --from=csrc-build ${COMMON_WORKDIR}/vllm/dist /precompiled-wheels
314
315
# Drop the pre-built Rust artifacts into the source tree. setup.py detects
@@ -308,9 +317,7 @@ COPY --from=csrc-build ${COMMON_WORKDIR}/vllm/dist /precompiled-wheels
317
COPY --from=rust-build ${COMMON_WORKDIR}/vllm/vllm/vllm-rs ${COMMON_WORKDIR}/vllm/vllm/vllm-rs
318
COPY --from=rust-build ${COMMON_WORKDIR}/vllm/vllm/_rust_*.so ${COMMON_WORKDIR}/vllm/vllm/
319
311
-RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \
312
- cd vllm \
313
- && uv pip install --system -r requirements/rocm.txt \
320
+RUN cd "${COMMON_WORKDIR}/vllm" \
321
&& export VLLM_USE_PRECOMPILED=1 \
322
&& export VLLM_PRECOMPILED_WHEEL_LOCATION="$(ls /precompiled-wheels/*.whl)" \
323
&& export VLLM_DOCKER_BUILD_CONTEXT=1 \
docker/Dockerfile.rocm_base
+47
-16
@@ -134,11 +134,7 @@ RUN mkdir -p /app/install && cp /opt/rocm/share/amd_smi/dist/*.whl /app/install
134
###
135
FROM base AS build_pytorch
136
ARG PYTORCH_BRANCH
137
-ARG PYTORCH_VISION_BRANCH
138
-ARG PYTORCH_AUDIO_BRANCH
137
ARG PYTORCH_REPO
140
-ARG PYTORCH_VISION_REPO
141
-ARG PYTORCH_AUDIO_REPO
138
ARG USE_SCCACHE
139
140
RUN apt-get update && apt-get install -y pkg-config liblzma-dev
@@ -156,8 +152,27 @@ RUN cd pytorch && python3 tools/amd_build/build_amd.py \
152
&& sccache --show-stats; \
153
fi \
154
&& CMAKE_PREFIX_PATH=$(python3 -c 'import sys; print(sys.prefix)') python3 setup.py bdist_wheel --dist-dir=dist \
159
- && if [ "$USE_SCCACHE" = "1" ]; then sccache --show-stats; fi \
160
- && pip install dist/*.whl
155
+ && if [ "$USE_SCCACHE" = "1" ]; then sccache --show-stats; fi
156
+RUN mkdir -p /app/install && cp /app/pytorch/dist/*.whl /app/install
157
+
158
+
159
+###
160
+### Shared PyTorch runtime for dependent builds
161
+###
162
+FROM base AS build_pytorch_runtime
163
+RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 130 --slave /usr/bin/g++ g++ /usr/bin/g++-13
164
+RUN --mount=type=bind,from=build_pytorch,src=/app/install/,target=/install,ro \
165
+ pip install /install/*.whl
166
+
167
+
168
+###
169
+### TorchVision build
170
+###
171
+FROM build_pytorch_runtime AS build_torchvision
172
+ARG PYTORCH_VISION_BRANCH
173
+ARG PYTORCH_VISION_REPO
174
+ARG USE_SCCACHE
175
+
176
RUN git clone ${PYTORCH_VISION_REPO} vision
177
RUN cd vision && git checkout ${PYTORCH_VISION_BRANCH} \
178
&& if [ "$USE_SCCACHE" = "1" ]; then \
@@ -166,23 +181,31 @@ RUN cd vision && git checkout ${PYTORCH_VISION_BRANCH} \
181
&& export CMAKE_CXX_COMPILER_LAUNCHER=sccache; \
182
fi \
183
&& python3 setup.py bdist_wheel --dist-dir=dist \
169
- && if [ "$USE_SCCACHE" = "1" ]; then sccache --show-stats; fi \
170
- && pip install dist/*.whl
184
+ && if [ "$USE_SCCACHE" = "1" ]; then sccache --show-stats; fi
185
+RUN mkdir -p /app/install && cp /app/vision/dist/*.whl /app/install
186
+
187
+
188
+###
189
+### TorchAudio build
190
+###
191
+FROM build_pytorch_runtime AS build_torchaudio
192
+ARG PYTORCH_AUDIO_BRANCH
193
+ARG PYTORCH_AUDIO_REPO
194
+ARG USE_SCCACHE
195
+
196
RUN git clone ${PYTORCH_AUDIO_REPO} audio
197
RUN cd audio && git checkout ${PYTORCH_AUDIO_BRANCH} \
173
- && git submodule update --init --recursive \
174
- && pip install -r requirements.txt \
198
+ && git submodule update --init --recursive
199
+RUN cd audio && pip install -r requirements.txt
200
+RUN cd audio \
201
&& if [ "$USE_SCCACHE" = "1" ]; then \
202
export HIP_CLANG_PATH=/opt/sccache-wrappers \
203
&& export CMAKE_C_COMPILER_LAUNCHER=sccache \
204
&& export CMAKE_CXX_COMPILER_LAUNCHER=sccache; \
205
fi \
206
&& python3 setup.py bdist_wheel --dist-dir=dist \
181
- && if [ "$USE_SCCACHE" = "1" ]; then sccache --show-stats; fi \
182
- && pip install dist/*.whl
183
-RUN mkdir -p /app/install && cp /app/pytorch/dist/*.whl /app/install \
184
- && cp /app/vision/dist/*.whl /app/install \
185
- && cp /app/audio/dist/*.whl /app/install
207
+ && if [ "$USE_SCCACHE" = "1" ]; then sccache --show-stats; fi
208
+RUN mkdir -p /app/install && cp /app/audio/dist/*.whl /app/install
209
210
211
###
@@ -263,6 +286,10 @@ RUN --mount=type=bind,from=build_amdsmi,src=/app/install/,target=/install \
286
cp /install/*.whl /app/debs
287
RUN --mount=type=bind,from=build_pytorch,src=/app/install/,target=/install \
288
cp /install/*.whl /app/debs
289
+RUN --mount=type=bind,from=build_torchvision,src=/app/install/,target=/install \
290
+ cp /install/*.whl /app/debs
291
+RUN --mount=type=bind,from=build_torchaudio,src=/app/install/,target=/install \
292
+ cp /install/*.whl /app/debs
293
RUN --mount=type=bind,from=build_aiter,src=/app/install/,target=/install \
294
cp /install/*.whl /app/debs
295
@@ -277,6 +304,10 @@ RUN --mount=type=bind,from=build_amdsmi,src=/app/install/,target=/install \
304
cp /install/*.whl /app/debs
305
RUN --mount=type=bind,from=build_pytorch,src=/app/install/,target=/install \
306
cp /install/*.whl /app/debs
307
+RUN --mount=type=bind,from=build_torchvision,src=/app/install/,target=/install \
308
+ cp /install/*.whl /app/debs
309
+RUN --mount=type=bind,from=build_torchaudio,src=/app/install/,target=/install \
310
+ cp /install/*.whl /app/debs
311
RUN --mount=type=bind,from=build_aiter,src=/app/install/,target=/install \
312
cp /install/*.whl /app/debs
313
RUN --mount=type=bind,from=build_mori,src=/app/install/,target=/install \
@@ -315,4 +346,4 @@ RUN echo "BASE_IMAGE: ${BASE_IMAGE}" > /app/versions.txt \
346
&& echo "AITER_BRANCH: ${AITER_BRANCH}" >> /app/versions.txt \
347
&& echo "AITER_REPO: ${AITER_REPO}" >> /app/versions.txt \
348
&& echo "MORI_BRANCH: ${MORI_BRANCH}" >> /app/versions.txt \
318
- && echo "MORI_REPO: ${MORI_REPO}" >> /app/versions.txt
\ No newline at end of file
349
+ && echo "MORI_REPO: ${MORI_REPO}" >> /app/versions.txt
docker/ci-rocm.hcl
+15
-24
@@ -338,33 +338,22 @@ group "test-rocm-ci-with-artifacts" {
338
targets = ["rust-rocm-ci", "csrc-rocm-ci", "export-wheel-rocm"]
339
}
340
341
-# Full test image + wheel export. Kept for fallback/debugging when a pushed
342
-# per-commit image is useful.
341
+# Full test image + wheel export. Kept for fallback/debugging when a pushed,
342
+# build-scoped image is useful.
343
group "test-rocm-ci-with-wheel" {
344
targets = ["rust-rocm-ci", "csrc-rocm-ci", "test-rocm-ci", "export-wheel-rocm"]
345
}
346
347
-# Image tags for the ci_base build. ci-bake-rocm.sh rewrites CI_BASE_IMAGE_TAG
348
-# to the primary tag for this build. Builds always publish a content-scoped tag
349
-# when the ci_base content hash is available. Builds with BUILDKITE_COMMIT also
350
-# publish a commit-scoped tag, either as the primary tag or an additional alias.
351
-# NIGHTLY=1 builds on the stable branch can additionally set
352
-# CI_BASE_IMAGE_TAG_STABLE to refresh rocm/vllm-dev:ci_base.
347
+# Primary output tag for the ci_base build. In Buildkite this is a unique,
348
+# build-scoped tag; ci-bake-rocm.sh validates it before creating content and
349
+# stable aliases.
350
variable "CI_BASE_IMAGE_TAG" {
351
default = "rocm/vllm-dev:ci_base"
352
}
353
357
-# Supplemental tags only. ci-bake-rocm.sh leaves these empty when the same ref
358
-# is already the primary CI_BASE_IMAGE_TAG.
359
-variable "CI_BASE_IMAGE_TAG_COMMIT_EXTRA" {
360
- default = ""
361
-}
362
-
363
-variable "CI_BASE_IMAGE_TAG_CONTENT_EXTRA" {
364
- default = ""
365
-}
366
-
367
-variable "CI_BASE_IMAGE_TAG_STABLE" {
354
+# Versioned, content-addressed trusted ref. Preview builds import it read-only;
355
+# the wrapper creates their source-scoped content alias after validation.
356
+variable "CI_BASE_TRUSTED_CONTENT_REF" {
357
default = ""
358
}
359
@@ -403,21 +392,23 @@ target "deepep-rocm-ci" {
392
# Uses inline cache metadata on the ci_base image itself instead of exporting a
393
# separate registry cache artifact.
394
target "ci-base-rocm-ci" {
406
- inherits = ["_common-rocm", "_ci-rocm", "_labels"]
395
+ inherits = ["_common-rocm"]
396
target = "ci_base"
397
+ args = {
398
+ max_jobs = CI_MAX_JOBS
399
+ }
400
cache-from = concat(
401
compact([
402
CI_BASE_IMAGE_TAG != "" ? "type=registry,ref=${CI_BASE_IMAGE_TAG}" : "",
411
- CI_BASE_IMAGE_TAG_COMMIT_EXTRA != "" ? "type=registry,ref=${CI_BASE_IMAGE_TAG_COMMIT_EXTRA}" : "",
412
- CI_BASE_IMAGE_TAG_CONTENT_EXTRA != "" ? "type=registry,ref=${CI_BASE_IMAGE_TAG_CONTENT_EXTRA}" : "",
413
- CI_BASE_IMAGE_TAG_STABLE != "" ? "type=registry,ref=${CI_BASE_IMAGE_TAG_STABLE}" : "",
403
+ CI_BASE_TRUSTED_CONTENT_REF != "" ? "type=registry,ref=${CI_BASE_TRUSTED_CONTENT_REF}" : "",
404
]),
405
# Import upstream dependency caches so NIXL/ROCShmem/DeepEP stages
406
# are cache hits even when ci_base itself needs rebuilding.
407
get_cache_from_rocm_deps(),
408
)
409
cache-to = ["type=inline"]
420
- tags = compact([CI_BASE_IMAGE_TAG, CI_BASE_IMAGE_TAG_COMMIT_EXTRA, CI_BASE_IMAGE_TAG_CONTENT_EXTRA, CI_BASE_IMAGE_TAG_STABLE])
410
+ tags = compact([CI_BASE_IMAGE_TAG])
411
+ attest = ["type=provenance,disabled=true"]
412
output = ["type=registry"]
413
}
414
tests/tools/test_docker_build_metadata_args.py
-4
@@ -171,10 +171,6 @@ def test_rocm_ci_base_metadata_inputs_cover_ci_base_files() -> None:
171
"requirements/common.txt",
172
"requirements/rocm.txt",
173
"requirements/test/rocm.txt",
174
- "docker/Dockerfile.rocm_base",
174
"docker/Dockerfile.rocm",
176
- "docker/ci-rocm.hcl",
177
- "docker/docker-bake-rocm.hcl",
178
- ".buildkite/scripts/ci-bake-rocm.sh",
175
):
176
assert expected in ci_bake