[Bugfix][ROCm][CI] Stabilize build context and source caches (#51721)

Signed-off-by: Andreas Karatzas <akaratza@amd.com> Signed-off-by: Andreas Karatzas <Andreas.Karatzas@amd.com>

Andreas Karatzas committed Aug 10, 2026 at 21:51 UTC 419b51b38517bd446c6e359d37b1ee8674cd7e84
2 files changed +345 -61
.buildkite/scripts/ci-bake-rocm.sh
+313 -57
@@ -22,7 +22,7 @@ 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"
25 +DEFAULT_ROCM_CSRC_DOCKERFILE_STAGES="base fetch_vllm_0 fetch_vllm build_vllm_dependencies rocm-triton-kernels 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
@@ -36,9 +36,14 @@ CI_HCL_PATH=""
36 CI_BASE_LABEL_OVERRIDE_PATH=""
37 CSRC_CACHE_OVERRIDE_PATH=""
38 ROCM_ARG_OVERRIDE_PATH=""
39 +BUILD_CONTEXT_OVERRIDE_PATH=""
40 SCRIPT_TMP_DIR=""
41 BAKE_CONFIG_FILE=""
42 +ROCM_BUILD_CONTEXT_ROOT=""
43 +ROCM_BUILD_CONTEXT_INDEX=""
44 +ROCM_BUILD_CONTEXT_COMMIT=""
45 BAKE_FILES=()
46 +BAKE_ALLOW_ARGS=()
47 BAKE_TARGETS=()
48 DEPENDENCY_CACHE_TARGETS=()
49
@@ -192,16 +197,20 @@ get_buildkite_target_repo_url() {
197 printf 'https://github.com/%s.git\n' "${DEFAULT_REPO_SLUG}"
198 }
199
195 -git_fetch_for_cache() {
200 +git_fetch_with_timeout() {
201 local timeout_secs="${ROCM_CACHE_GIT_FETCH_TIMEOUT:-60}"
202
203 if command -v timeout >/dev/null 2>&1; then
199 - timeout "${timeout_secs}s" git fetch "$@" 2>/dev/null
204 + timeout "${timeout_secs}s" git fetch "$@"
205 else
201 - git fetch "$@" 2>/dev/null
206 + git fetch "$@"
207 fi
208 }
209
210 +git_fetch_for_cache() {
211 + git_fetch_with_timeout "$@" 2>/dev/null
212 +}
213 +
214 hash_string_short() {
215 printf '%s' "$1" | sha256sum | cut -c1-16
216 }
@@ -209,23 +218,51 @@ hash_string_short() {
218 list_content_files() {
219 # Hash the checkout inputs Docker can intentionally consume, not ignored
220 # 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
221 + if [[ -n "${ROCM_BUILD_CONTEXT_ROOT:-}" ]]; then
222 + GIT_INDEX_FILE="${ROCM_BUILD_CONTEXT_INDEX}" \
223 + git ls-files -z --cached -- "$1" | LC_ALL=C sort -z
224 + else
225 + git ls-files -z --cached --others --exclude-standard -- "$1" \
226 + | LC_ALL=C sort -z
227 + fi
228 }
229
216 -hash_content_file() {
230 +content_regular_file() {
231 local file="$1"
232 + local physical_file=""
233
219 - if [[ -L "${file}" ]]; then
220 - printf 'symlink:%s\ntarget:%s\n' "${file}" "$(readlink "${file}")"
234 + physical_file="${file}"
235 + if [[ -n "${ROCM_BUILD_CONTEXT_ROOT:-}" && "${file}" != /* ]]; then
236 + physical_file="${ROCM_BUILD_CONTEXT_ROOT}/${file}"
237 + fi
238 + [[ -f "${physical_file}" ]]
239 +}
240 +
241 +hash_content_file() {
242 + local file="$1"
243 + local physical_file=""
244 + local checksum=""
245 + local file_mode=""
246 +
247 + physical_file="${file}"
248 + if [[ -n "${ROCM_BUILD_CONTEXT_ROOT:-}" && "${file}" != /* ]]; then
249 + physical_file="${ROCM_BUILD_CONTEXT_ROOT}/${file}"
250 + fi
251 + if [[ -L "${physical_file}" ]]; then
252 + printf 'symlink:%s\ntarget:' "${file}"
253 + readlink -n -- "${physical_file}" || return $?
254 + printf '\n'
255 return
256 fi
223 - if [[ ! -f "${file}" ]]; then
257 + if [[ ! -f "${physical_file}" ]]; then
258 printf 'missing:%s\n' "${file}"
259 return
260 fi
227 - printf 'file:%s\nmode:%s\n' "${file}" "$(stat -c '%a' "${file}")"
228 - sha256sum "${file}"
261 + file_mode=$(stat -c '%a' "${physical_file}") || return $?
262 + printf 'file:%s\nmode:%s\n' "${file}" "${file_mode}"
263 + checksum=$(sha256sum < "${physical_file}") || return $?
264 + checksum="${checksum%% *}"
265 + printf '%s %s\n' "${checksum}" "${file}"
266 }
267
268 hash_content_directory() {
@@ -239,11 +276,16 @@ hash_content_directory() {
276
277 compute_content_hash() {
278 local path=""
279 + local physical_path=""
280
281 for path in "$@"; do
244 - if [[ -L "${path}" || -f "${path}" ]]; then
282 + physical_path="${path}"
283 + if [[ -n "${ROCM_BUILD_CONTEXT_ROOT:-}" && "${path}" != /* ]]; then
284 + physical_path="${ROCM_BUILD_CONTEXT_ROOT}/${path}"
285 + fi
286 + if [[ -L "${physical_path}" || -f "${physical_path}" ]]; then
287 hash_content_file "${path}" || return $?
246 - elif [[ -d "${path}" ]]; then
288 + elif [[ -d "${physical_path}" ]]; then
289 hash_content_directory "${path}" || return $?
290 else
291 printf 'missing:%s\n' "${path}"
@@ -251,25 +293,69 @@ compute_content_hash() {
293 done | sha256sum | cut -d' ' -f1
294 }
295
254 -normalize_ci_worktree_modes() {
296 +validate_ci_build_context_source() {
297 + local source_root="$1"
298 + local context_commit=""
299 + local source_head=""
300 local entry=""
301 local metadata=""
302 local mode=""
303 local stage=""
304 local path=""
305 local index_modes_file="${SCRIPT_TMP_DIR}/git-index-modes"
261 - local -a regular_files=()
262 - local -a executable_files=()
306 + local info_attributes=""
307
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
308 + if ! context_commit=$(git -C "${source_root}" rev-parse \
309 + --verify "${BUILDKITE_COMMIT:-HEAD}^{commit}"); then
310 + echo "Failed to resolve the CI Docker context revision" >&2
311 + return 1
312 + fi
313 + if ! source_head=$(git -C "${source_root}" rev-parse --verify HEAD); then
314 + echo "Failed to resolve the checked-out CI revision" >&2
315 + return 1
316 + fi
317 + if [[ "${context_commit}" != "${source_head}" ]]; then
318 + echo "BUILDKITE_COMMIT does not match the checked-out CI revision" >&2
319 + return 1
320 + fi
321 + ROCM_BUILD_CONTEXT_COMMIT="${context_commit}"
322 + if ! git -C "${source_root}" diff \
323 + --quiet --no-ext-diff --ignore-submodules=none --; then
324 + echo "Tracked worktree changes cannot be omitted from the CI Docker context" \
325 + >&2
326 + return 1
327 + fi
328 + if ! git -C "${source_root}" diff --cached \
329 + --quiet --no-ext-diff --ignore-submodules=none HEAD --; then
330 + echo "Staged changes cannot be omitted from the CI Docker context" >&2
331 + return 1
332 + fi
333 + # Untracked and ignored worker outputs are intentionally absent: the
334 + # pinned Git tree, rather than mutable checkout contents, is the contract.
335 + if ! info_attributes=$(git -C "${source_root}" \
336 + rev-parse --path-format=absolute --git-path info/attributes); then
337 + echo "Failed to locate repository-local Git attributes" >&2
338 + return 1
339 + fi
340 + if [[ -s "${info_attributes}" ]]; then
341 + echo "Repository-local Git attributes cannot define the CI Docker context" \
342 + >&2
343 return 1
344 fi
345
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.
346 + ROCM_BUILD_CONTEXT_INDEX="${SCRIPT_TMP_DIR}/docker-context.index"
347 + if ! GIT_INDEX_FILE="${ROCM_BUILD_CONTEXT_INDEX}" \
348 + git -C "${source_root}" -c core.splitIndex=false \
349 + read-tree "${context_commit}^{tree}"; then
350 + echo "Failed to create the CI Docker context index" >&2
351 + return 1
352 + fi
353 + if ! GIT_INDEX_FILE="${ROCM_BUILD_CONTEXT_INDEX}" \
354 + git -C "${source_root}" ls-files --stage -z \
355 + > "${index_modes_file}"; then
356 + echo "Failed to read the Git index for the CI Docker context" >&2
357 + return 1
358 + fi
359 while IFS= read -r -d '' entry; do
360 metadata="${entry%%$'\t'*}"
361 mode="${metadata%% *}"
@@ -280,13 +366,12 @@ normalize_ci_worktree_modes() {
366 return 1
367 fi
368 case "${mode}" in
283 - 100644)
284 - regular_files+=("${path}")
369 + 100644|100755|120000)
370 ;;
286 - 100755)
287 - executable_files+=("${path}")
288 - ;;
289 - 120000|160000)
371 + 160000)
372 + echo "Git submodule cannot be materialized in the CI Docker context: ${path}" \
373 + >&2
374 + return 1
375 ;;
376 *)
377 echo "Unsupported Git mode ${mode} for ${path}" >&2
@@ -294,10 +379,130 @@ normalize_ci_worktree_modes() {
379 ;;
380 esac
381 done < "${index_modes_file}"
382 +}
383
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"
384 +describe_ci_revision() {
385 + git -C "$1" describe --tags --long --abbrev=10 \
386 + --match '*[0-9]*' "$2" 2>/dev/null
387 +}
388 +
389 +write_ci_git_archival_metadata() {
390 + local source_root="$1"
391 + local context_root="$2"
392 + local commit="${ROCM_BUILD_CONTEXT_COMMIT}"
393 + local commit_date=""
394 + local describe=""
395 + local is_shallow="false"
396 +
397 + commit_date=$(git -C "${source_root}" show -s --format=%cI "${commit}") \
398 + || return $?
399 + is_shallow=$(git -C "${source_root}" rev-parse --is-shallow-repository) \
400 + || return $?
401 + if git -C "${source_root}" remote get-url origin >/dev/null 2>&1; then
402 + # AMD agents use shallow, no-tag clones. Reach a release tag so one
403 + # commit cannot acquire different versions from different depths.
404 + # Versioning only needs tags and commits, not historical source trees.
405 + echo "Synchronizing version tags for the canonical CI Docker context"
406 + (cd "${source_root}" && git_fetch_with_timeout --quiet \
407 + --filter=tree:0 --prune origin '+refs/tags/*:refs/tags/*') \
408 + || return $?
409 + if [[ "${is_shallow}" == "true" ]] \
410 + && ! describe_ci_revision "${source_root}" "${commit}" >/dev/null; then
411 + echo "Deepening history to reach a version tag"
412 + (cd "${source_root}" && git_fetch_with_timeout --quiet \
413 + --filter=tree:0 --no-tags --deepen=1000 origin "${commit}") \
414 + || return $?
415 + is_shallow=$(git -C "${source_root}" \
416 + rev-parse --is-shallow-repository) || return $?
417 + if [[ "${is_shallow}" == "true" ]] \
418 + && ! describe_ci_revision "${source_root}" "${commit}" >/dev/null; then
419 + echo "No version tag within 1,000 commits; fetching full history"
420 + (cd "${source_root}" && git_fetch_with_timeout --quiet \
421 + --filter=tree:0 --no-tags --unshallow origin "${commit}") \
422 + || return $?
423 + fi
424 + fi
425 + fi
426 + if ! describe=$(describe_ci_revision "${source_root}" "${commit}"); then
427 + echo "No numeric version tag is reachable from the CI revision" >&2
428 + return 1
429 + fi
430 + if [[ -e "${context_root}/.git_archival.txt" \
431 + || -L "${context_root}/.git_archival.txt" ]]; then
432 + echo "The source tree already contains .git_archival.txt" >&2
433 + return 1
434 + fi
435 + printf 'node: %s\nnode-date: %s\ndescribe-name: %s\n' \
436 + "${commit}" "${commit_date}" "${describe}" \
437 + > "${context_root}/.git_archival.txt" \
438 + && chmod 0644 -- "${context_root}/.git_archival.txt"
439 +}
440 +
441 +write_build_context_override() {
442 + local escaped_context=""
443 +
444 + [[ -n "${ROCM_BUILD_CONTEXT_ROOT:-}" ]] || return 0
445 + escaped_context=$(hcl_escape_string "${ROCM_BUILD_CONTEXT_ROOT}") || return $?
446 + if ! {
447 + printf 'target "_common-rocm" {\n'
448 + printf ' context = "%s"\n' "${escaped_context}"
449 + printf '}\n'
450 + } > "${BUILD_CONTEXT_OVERRIDE_PATH}"; then
451 + echo "Failed to write the CI Docker context override" >&2
452 + return 1
453 + fi
454 + BAKE_FILES+=(-f "${BUILD_CONTEXT_OVERRIDE_PATH}")
455 +}
456 +
457 +prepare_ci_build_context() {
458 + local source_root=""
459 + local context_root=""
460 +
461 + [[ "${BUILDKITE:-false}" == "true" ]] || return 0
462 + [[ "${REMOTE_VLLM:-0}" == "0" ]] || return 0
463 +
464 + # BuildKit includes file modes in cache keys. Export the pinned revision to
465 + # an owned context instead of changing modes in the shared checkout.
466 + if ! source_root=$(git rev-parse --show-toplevel); then
467 + echo "Failed to locate the CI source checkout" >&2
468 + return 1
469 + fi
470 + validate_ci_build_context_source "${source_root}" || return $?
471 + context_root="${SCRIPT_TMP_DIR}/docker-context"
472 + if ! mkdir -m 0700 -- "${context_root}"; then
473 + echo "Failed to create the owned CI Docker context" >&2
474 + return 1
475 + fi
476 + if ! (
477 + umask 0022
478 + unset GIT_LFS_SKIP_SMUDGE
479 + export GIT_ATTR_NOSYSTEM=1
480 + export GIT_INDEX_FILE="${ROCM_BUILD_CONTEXT_INDEX}"
481 + git -C "${source_root}" \
482 + -c core.attributesFile=/dev/null \
483 + -c core.autocrlf=false \
484 + -c core.eol=lf \
485 + -c core.symlinks=true \
486 + --work-tree="${context_root}" \
487 + checkout-index --all
488 + ); then
489 + echo "Failed to materialize the CI Docker context from the Git index" >&2
490 + return 1
491 + fi
492 +
493 + # setuptools-scm understands Git's stable archive format, so full-source
494 + # wheels retain their exact version without copying mutable Git history.
495 + if ! is_ci_base_target; then
496 + write_ci_git_archival_metadata "${source_root}" "${context_root}" \
497 + || return $?
498 + fi
499 + if [[ -e "${context_root}/.git" ]]; then
500 + echo "Canonical CI Docker context unexpectedly contains .git" >&2
501 + return 1
502 + fi
503 + ROCM_BUILD_CONTEXT_ROOT="${context_root}"
504 + BAKE_ALLOW_ARGS+=(--allow "fs.read=${ROCM_BUILD_CONTEXT_ROOT}")
505 + echo "Using canonical CI Docker context: ${ROCM_BUILD_CONTEXT_ROOT}"
506 }
507
508 compose_dependency_cache_key() {
@@ -312,6 +517,12 @@ compose_dependency_cache_key() {
517 hash_dockerfile_stages() {
518 local dockerfile="$1"
519 local stages="$2"
520 + local physical_dockerfile=""
521 +
522 + physical_dockerfile="${dockerfile}"
523 + if [[ -n "${ROCM_BUILD_CONTEXT_ROOT:-}" && "${dockerfile}" != /* ]]; then
524 + physical_dockerfile="${ROCM_BUILD_CONTEXT_ROOT}/${dockerfile}"
525 + fi
526
527 awk -v wanted_stages="${stages}" '
528 BEGIN {
@@ -335,14 +546,19 @@ hash_dockerfile_stages() {
546 emit {
547 print
548 }
338 - ' "${dockerfile}"
549 + ' "${physical_dockerfile}"
550 }
551
552 discover_dockerfile_stage_args() {
553 local dockerfile="$1"
554 local stages="$2"
555 + local physical_dockerfile=""
556
345 - [[ -f "${dockerfile}" ]] || return 0
557 + physical_dockerfile="${dockerfile}"
558 + if [[ -n "${ROCM_BUILD_CONTEXT_ROOT:-}" && "${dockerfile}" != /* ]]; then
559 + physical_dockerfile="${ROCM_BUILD_CONTEXT_ROOT}/${dockerfile}"
560 + fi
561 + [[ -f "${physical_dockerfile}" ]] || return 0
562
563 awk -v wanted_stages="${stages}" '
564 function add_arg(name) {
@@ -406,7 +622,7 @@ discover_dockerfile_stage_args() {
622 }
623 }
624 }
409 - ' "${dockerfile}"
625 + ' "${physical_dockerfile}"
626 }
627
628 get_content_arg_names() {
@@ -446,7 +662,7 @@ compute_ci_base_content_hash() {
662 || return 1
663 if [[ -n "${stages}" ]]; then
664 printf 'dockerfile-stages:%s\n' "${stages}"
449 - if [[ -f "${dockerfile}" ]]; then
665 + if content_regular_file "${dockerfile}"; then
666 hash_dockerfile_stages "${dockerfile}" "${stages}"
667 else
668 printf 'missing:%s\n' "${dockerfile}"
@@ -459,9 +675,14 @@ compute_ci_base_content_hash() {
675 extract_dockerfile_arg_default() {
676 local dockerfile="$1"
677 local arg_name="$2"
678 + local physical_dockerfile=""
679
680 + physical_dockerfile="${dockerfile}"
681 + if [[ -n "${ROCM_BUILD_CONTEXT_ROOT:-}" && "${dockerfile}" != /* ]]; then
682 + physical_dockerfile="${ROCM_BUILD_CONTEXT_ROOT}/${dockerfile}"
683 + fi
684 sed -n -E "s/^[[:space:]]*ARG[[:space:]]+${arg_name}=\"?([^\"[:space:]]+)\"?.*/\\1/p" \
464 - "${dockerfile}" | head -1
685 + "${physical_dockerfile}" | head -1
686 }
687
688 resolve_image_digest() {
@@ -523,7 +744,7 @@ resolve_dockerfile_arg_value() {
744 if [[ -z "${value}" && "${env_name}" != "${arg_name}" ]]; then
745 value="${!arg_name:-}"
746 fi
526 - if [[ -z "${value}" && -f "${dockerfile}" ]]; then
747 + if [[ -z "${value}" ]] && content_regular_file "${dockerfile}"; then
748 value=$(extract_dockerfile_arg_default "${dockerfile}" "${arg_name}")
749 fi
750
@@ -741,6 +962,7 @@ init_config() {
962 CI_BASE_LABEL_OVERRIDE_PATH="${SCRIPT_TMP_DIR}/ci-base-label-override.hcl"
963 CSRC_CACHE_OVERRIDE_PATH="${SCRIPT_TMP_DIR}/rocm-csrc-cache-override.hcl"
964 ROCM_ARG_OVERRIDE_PATH="${SCRIPT_TMP_DIR}/rocm-arg-override.hcl"
965 + BUILD_CONTEXT_OVERRIDE_PATH="${SCRIPT_TMP_DIR}/build-context-override.hcl"
966 BAKE_CONFIG_FILE="bake-config-build-${BUILDKITE_BUILD_NUMBER:-local}.json"
967 }
968
@@ -1270,7 +1492,8 @@ prepare_git_cache_metadata() {
1492 if [[ -z "${PARENT_COMMIT:-}" || -z "${VLLM_MERGE_BASE_COMMIT:-}" ]] \
1493 && git rev-parse --is-shallow-repository 2>/dev/null | grep -q "true"; then
1494 echo "Shallow clone detected - deepening for cache key computation"
1273 - git_fetch_for_cache --deepen=1 origin || true
1495 + git_fetch_for_cache --filter=tree:0 --no-tags --deepen=1 \
1496 + origin "$(git rev-parse HEAD)" || true
1497 fi
1498
1499 if [[ -z "${PARENT_COMMIT:-}" ]]; then
@@ -1524,7 +1747,7 @@ compute_rocm_csrc_content_hash() {
1747 printf 'resolved-build-args:\n'
1748 hash_dockerfile_arg_values "${dockerfile_rocm}" "${content_args[@]}"
1749 printf 'dockerfile-stages:%s\n' "${stages}"
1527 - if [[ -f "${dockerfile_rocm}" ]]; then
1750 + if content_regular_file "${dockerfile_rocm}"; then
1751 hash_dockerfile_stages "${dockerfile_rocm}" "${stages}"
1752 else
1753 printf 'missing:%s\n' "${dockerfile_rocm}"
@@ -1574,7 +1797,7 @@ compute_rocm_rust_content_hash() {
1797 printf 'resolved-build-args:\n'
1798 hash_dockerfile_arg_values "${dockerfile_rocm}" "${content_args[@]}"
1799 printf 'dockerfile-stages:%s\n' "${stages}"
1577 - if [[ -f "${dockerfile_rocm}" ]]; then
1800 + if content_regular_file "${dockerfile_rocm}"; then
1801 hash_dockerfile_stages "${dockerfile_rocm}" "${stages}"
1802 else
1803 printf 'missing:%s\n' "${dockerfile_rocm}"
@@ -1736,24 +1959,24 @@ should_export_content_cache_ref() {
1959
1960 case "${mode}" in
1961 always)
1739 - echo "${cache_name} content cache export mode is always; exporting ${cache_ref}"
1962 + echo "${cache_name} content cache export mode is always"
1963 return 0
1964 ;;
1965 never)
1743 - echo "${cache_name} content cache export mode is never; not exporting ${cache_ref}"
1966 + echo "${cache_name} content cache export mode is never"
1967 return 1
1968 ;;
1969 missing|"")
1747 - if remote_image_exists "${trusted_ref}"; then
1748 - echo "${cache_name} trusted content cache exists; not exporting ${cache_ref}"
1970 + if registry_ref_exists_with_retry imagetools "${trusted_ref}"; then
1971 + echo "${cache_name} trusted content cache is visible: ${trusted_ref}"
1972 return 1
1973 fi
1974 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}"
1975 + && registry_ref_exists_with_retry imagetools "${cache_ref}"; then
1976 + echo "${cache_name} scoped content cache is visible: ${cache_ref}"
1977 return 1
1978 fi
1756 - echo "${cache_name} content cache missing; will export ${cache_ref}"
1979 + echo "${cache_name} content cache is missing: ${cache_ref}"
1980 return 0
1981 ;;
1982 *)
@@ -1791,7 +2014,7 @@ write_rocm_cache_override() {
2014 validate_cache_export_mode "${rocm_cache_to_mode}" "ROCM_FINAL_CACHE_TO_MODE"
2015 echo "ROCm content cache export mode: ${content_cache_export_mode}"
2016 echo "ROCm csrc cache export mode: ${csrc_cache_to_mode}"
1794 - echo "ROCm Rust cache export mode: ${rust_cache_to_mode}"
2017 + echo "ROCm Rust fallback cache export mode: ${rust_cache_to_mode}"
2018 echo "ROCm final image cache export mode: ${rocm_cache_to_mode}"
2019
2020 if [[ -n "${ROCM_CSRC_CONTENT_CACHE_REF:-}" ]]; then
@@ -1825,14 +2048,20 @@ write_rocm_cache_override() {
2048 "type=registry,ref=${ROCM_RUST_CONTENT_CACHE_REF}"
2049 )
2050 fi
1828 - if should_export_content_cache_ref \
2051 + # Legacy commit/branch exports are only needed while the exact-input
2052 + # content ref is absent. The exact ref itself is refreshed below.
2053 + if ! should_export_content_cache_ref \
2054 "${ROCM_RUST_CONTENT_CACHE_REF}" "ROCm Rust" \
2055 "${ROCM_RUST_TRUSTED_CONTENT_CACHE_REF}"; then
2056 + export_rust_cache=0
2057 + fi
2058 + if [[ "${content_cache_export_mode}" != "never" ]]; then
2059 + # Refresh the exact-input ref in the original solve regardless of
2060 + # which local or remote cache supplied the Rust result.
2061 rust_cache_to+=(
1832 - "type=registry,ref=${ROCM_RUST_CONTENT_CACHE_REF},mode=${rust_cache_to_mode},ignore-error=true"
2062 + "type=registry,ref=${ROCM_RUST_CONTENT_CACHE_REF},mode=min,ignore-error=true"
2063 )
1834 - else
1835 - export_rust_cache=0
2064 + echo "ROCm Rust exact-input cache will be refreshed (mode=min): ${ROCM_RUST_CONTENT_CACHE_REF}"
2065 fi
2066 fi
2067
@@ -1872,6 +2101,16 @@ write_rocm_cache_override() {
2101 )
2102 fi
2103
2104 + # Standalone image/wheel targets reach rust-build but not the cache-only
2105 + # exporter unless it is requested explicitly.
2106 + if ((${#rust_cache_to[@]} > 0)); then
2107 + case "${TARGET}" in
2108 + test-rocm-ci|export-wheel-rocm)
2109 + BAKE_TARGETS=("rust-rocm-ci" "${BAKE_TARGETS[@]}")
2110 + ;;
2111 + esac
2112 + fi
2113 +
2114 if [[ "${TARGET}" == "test-rocm-ci-with-wheel" ]]; then
2115 export_wheel_cache_to=()
2116 else
@@ -1937,12 +2176,17 @@ EOF
2176 extract_dependency_pins() {
2177 local bake_dir=""
2178 local dockerfile_rocm=""
2179 + local physical_dockerfile=""
2180 local var=""
2181 local val=""
2182
2183 bake_dir=$(dirname "${VLLM_BAKE_FILE}")
2184 dockerfile_rocm="${bake_dir}/Dockerfile.rocm"
1945 - if [[ ! -f "${dockerfile_rocm}" ]]; then
2185 + physical_dockerfile="${dockerfile_rocm}"
2186 + if [[ -n "${ROCM_BUILD_CONTEXT_ROOT:-}" && "${dockerfile_rocm}" != /* ]]; then
2187 + physical_dockerfile="${ROCM_BUILD_CONTEXT_ROOT}/${dockerfile_rocm}"
2188 + fi
2189 + if [[ ! -f "${physical_dockerfile}" ]]; then
2190 return 0
2191 fi
2192
@@ -1954,7 +2198,7 @@ extract_dependency_pins() {
2198
2199 val=$(
2200 sed -n -E "s/^[[:space:]]*ARG[[:space:]]+${var}=\"?([^\"[:space:]]+)\"?.*/\\1/p" \
1957 - "${dockerfile_rocm}" | head -1
2201 + "${physical_dockerfile}" | head -1
2202 )
2203 if [[ -n "${val}" ]]; then
2204 export "${var}=${val}"
@@ -2162,7 +2406,8 @@ print_bake_config() {
2406
2407 echo "--- :page_facing_up: Resolved bake configuration"
2408 mapfile -t print_targets < <(bake_config_targets)
2165 - docker buildx bake "${BAKE_FILES[@]}" --print "${print_targets[@]}" | tee "${BAKE_CONFIG_FILE}"
2409 + docker buildx bake "${BAKE_ALLOW_ARGS[@]}" \
2410 + "${BAKE_FILES[@]}" --print "${print_targets[@]}" | tee "${BAKE_CONFIG_FILE}"
2411
2412 if command -v buildkite-agent >/dev/null 2>&1 && [[ -n "${BUILDKITE_BUILD_NUMBER:-}" ]]; then
2413 buildkite-agent artifact upload "${BAKE_CONFIG_FILE}" || true
@@ -2234,6 +2479,7 @@ seed_dependency_caches_if_needed() {
2479 echo "--- :docker: Seeding ${target}"
2480 echo "Expected cache ref: ${cache_ref}"
2481 docker buildx bake \
2482 + "${BAKE_ALLOW_ARGS[@]}" \
2483 "${BAKE_FILES[@]}" \
2484 --progress "${BUILDKIT_PROGRESS:-plain}" \
2485 "${target}"
@@ -2250,6 +2496,7 @@ run_bake() {
2496
2497 echo "--- :docker: Building ${TARGET}"
2498 docker buildx bake \
2499 + "${BAKE_ALLOW_ARGS[@]}" \
2500 "${BAKE_FILES[@]}" \
2501 --progress "${BUILDKIT_PROGRESS:-plain}" \
2502 "${BAKE_TARGETS[@]}"
@@ -2358,14 +2605,21 @@ main() {
2605 configure_ci_base_write_scope
2606 print_header
2607 validate_inputs
2361 - normalize_ci_worktree_modes
2608 load_ci_hcl
2609 init_bake_files
2610 + if is_ci_base_target; then
2611 + prepare_ci_build_context
2612 + fi
2613 compute_ci_base_hash_if_needed
2614 configure_ci_base_image_refs
2615 maybe_skip_existing_image
2616 setup_builder
2617 prepare_git_cache_metadata
2618 + # Non-ci_base builds may deepen a shallow checkout above. Derive archival
2619 + # version metadata only after that lookup sees the available tag history.
2620 + if ! is_ci_base_target; then
2621 + prepare_ci_build_context
2622 + fi
2623 extract_dependency_pins
2624 write_rocm_build_arg_override
2625 compute_dependency_cache_keys
@@ -2373,6 +2627,8 @@ main() {
2627 compute_rocm_csrc_content_hash_if_needed
2628 compute_rocm_rust_content_hash_if_needed
2629 write_rocm_cache_override
2630 + # Keep the context override last so every bake target uses the owned tree.
2631 + write_build_context_override
2632 resolve_ci_base_dependency_targets
2633 print_bake_config
2634 if [[ "${BAKE_PRINT_ONLY:-0}" == "1" ]]; then
docker/Dockerfile.rocm
+32 -4
@@ -5,6 +5,7 @@ ARG VLLM_BRANCH="main"
5 ARG COMMON_WORKDIR=/app
6 ARG BASE_IMAGE=rocm/vllm-dev:base
7 ARG CI_BASE_IMAGE=rocm/vllm-dev:ci_base
8 +ARG ROCM_TRITON_KERNELS_COMMIT=0f380657dbf3ee86eb57558ff71df24f03b5d4e7
9 # NIC backend for MoRI RDMA support.
10 # By default (all), drivers and userspace libraries for all supported NIC types
11 # (ainic and bnxt) are installed; MoRI selects the appropriate one at runtime.
@@ -272,6 +273,20 @@ COPY --from=fetch_vllm \
273 RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \
274 uv pip install --system -r requirements/rocm.txt
275
276 +# Fetch only the pinned ROCm Triton kernels package. Keeping this before the
277 +# vLLM source COPYs makes the download reusable when native sources change.
278 +FROM base AS rocm-triton-kernels
279 +ARG ROCM_TRITON_KERNELS_COMMIT
280 +ADD https://codeload.github.com/ROCm/triton/tar.gz/${ROCM_TRITON_KERNELS_COMMIT} \
281 + /tmp/rocm-triton.tar.gz
282 +RUN mkdir -p /opt/rocm-triton-kernels \
283 + && tar --extract --gzip --file /tmp/rocm-triton.tar.gz \
284 + --directory /opt/rocm-triton-kernels --strip-components=4 \
285 + --no-same-owner \
286 + "triton-${ROCM_TRITON_KERNELS_COMMIT}/python/triton_kernels/triton_kernels" \
287 + && test -f /opt/rocm-triton-kernels/__init__.py \
288 + && rm /tmp/rocm-triton.tar.gz
289 +
290 # -----------------------
291 # vLLM native build stages
292 #
@@ -279,6 +294,11 @@ RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \
294 # compilation. That keeps unrelated CI/test/docs edits from invalidating the
295 # expensive HIP/C++ build layer.
296 FROM build_vllm_dependencies AS csrc-build
297 +ARG ROCM_TRITON_KERNELS_COMMIT
298 +COPY --from=rocm-triton-kernels \
299 + /opt/rocm-triton-kernels /opt/rocm-triton-kernels
300 +ENV TRITON_KERNELS_SRC_DIR=/opt/rocm-triton-kernels
301 +
302 # pyproject.toml is bind-mounted so it is not persisted in the csrc-build image,
303 # but it still participates in the BuildKit cache key for this RUN step.
304 COPY setup.py CMakeLists.txt ./
@@ -292,13 +312,15 @@ ENV SETUPTOOLS_SCM_PRETEND_VERSION="0.0.0+rocm.csrc.build"
312
313 RUN --mount=type=bind,source=pyproject.toml,target=${COMMON_WORKDIR}/vllm/pyproject.toml \
314 --mount=type=cache,id=vllm-rocm-ccache,target=/root/.cache/ccache \
295 - export CCACHE_BASEDIR="$PWD" \
315 + grep -Fq "set(TRITON_KERNELS_TAG \"${ROCM_TRITON_KERNELS_COMMIT}\")" \
316 + cmake/external_projects/triton_kernels.cmake \
317 + && export CCACHE_BASEDIR="$PWD" \
318 && echo "=== ccache stats before ROCm native build ===" \
319 && (ccache --show-stats || true) \
320 && (ccache --zero-stats || true) \
321 && EFFECTIVE_MAX_JOBS="${MAX_JOBS:-$(nproc)}" \
322 && echo "Building ROCm native extension wheel with MAX_JOBS=${EFFECTIVE_MAX_JOBS}" \
301 - && LDFLAGS="-fuse-ld=mold" MAX_JOBS="${EFFECTIVE_MAX_JOBS}" python3 setup.py bdist_wheel --dist-dir=dist \
323 + && PYTHONDONTWRITEBYTECODE=1 LDFLAGS="-fuse-ld=mold" MAX_JOBS="${EFFECTIVE_MAX_JOBS}" python3 setup.py bdist_wheel --dist-dir=dist \
324 && test -d dist \
325 && ls dist/*.whl >/dev/null \
326 && echo "=== ccache stats after ROCm native build ===" \
@@ -322,7 +344,7 @@ RUN cd "${COMMON_WORKDIR}/vllm" \
344 && export VLLM_PRECOMPILED_WHEEL_LOCATION="$(ls /precompiled-wheels/*.whl)" \
345 && export VLLM_DOCKER_BUILD_CONTEXT=1 \
346 && echo "Packaging vLLM ROCm wheel using precompiled extensions from ${VLLM_PRECOMPILED_WHEEL_LOCATION}" \
325 - && python3 setup.py bdist_wheel --dist-dir=dist \
347 + && PYTHONDONTWRITEBYTECODE=1 python3 setup.py bdist_wheel --dist-dir=dist \
348 && test -d dist \
349 && ls dist/*.whl >/dev/null
350 FROM scratch AS export_vllm
@@ -551,6 +573,10 @@ RUN /bin/bash -lc 'set -euo pipefail; \
573 FROM fetch_vllm AS build_vllm_wheel_release
574
575 ARG COMMON_WORKDIR
576 +ARG ROCM_TRITON_KERNELS_COMMIT
577 +COPY --from=rocm-triton-kernels \
578 + /opt/rocm-triton-kernels /opt/rocm-triton-kernels
579 +ENV TRITON_KERNELS_SRC_DIR=/opt/rocm-triton-kernels
580
581 # Drop the pre-built Rust artifacts into the source tree. setup.py detects
582 # them and ships them as-is, skipping the local Rust build.
@@ -590,7 +616,9 @@ RUN if [ "$GIT_REPO_CHECK" != "0" ]; then \
616 # This ensures setuptools_scm sees clean repo state for version detection
617 RUN --mount=type=bind,source=.git,target=vllm/.git \
618 --mount=type=cache,target=/root/.cache/uv \
593 - cd vllm \
619 + grep -Fq "set(TRITON_KERNELS_TAG \"${ROCM_TRITON_KERNELS_COMMIT}\")" \
620 + vllm/cmake/external_projects/triton_kernels.cmake \
621 + && cd vllm \
622 && uv pip install --system setuptools_scm regex \
623 && VLLM_VERSION=$(python3 -c "import setuptools_scm; print(setuptools_scm.get_version())") \
624 && echo "Detected vLLM version: ${VLLM_VERSION}" \