Skip to content

Commit 86a7f6c

Browse files
localai-botmudler
andauthored
ci: close GC race + cascade-skip + darwin grpc gaps from v4.2.1 (#9781)
* ci: close the GC race + cascade-skip + darwin grpc gaps from v4.2.1 v4.2.1's backend.yml run (#25701862853) exposed three independent issues on top of the singletons fix shipped in ea00199. Address all three plus two related cleanups: 1. quay GC race in backend-merge-jobs-multiarch (12/37 merges failed with "manifest not found"). Even after PR #9746 split multi/single-arch merges, the multiarch matrix itself takes ~2h to drain at max-parallel: 8, and the earliest per-arch digests (push-by-digest, no tag) get reaped by quay's GC before the merge runs. The split bounded the race for multiarch; it doesn't eliminate it. Anchor each per-arch digest immediately to a tag in the internal ci-cache image (`keepalive-<run_id><tag-suffix>-<platform-tag>`). Quay won't GC tagged manifests. backend_merge.yml deletes the keepalive tags via quay REST API after publishing the user-facing manifest list. Cleanup is best-effort: if the quay token is not OAuth-scoped the merge does NOT fail, the orphan tags just persist. 2. cascade-skip on backend-merge-jobs-singlearch. v4.2.1 had 2 failed and 2 cancelled singlearch builds (out of 199); GHA's default `needs:` semantics cascade-skipped the entire singlearch merge matrix, so zero singleton tags were applied even though 197 singletons built successfully. Wrap the merge `if:` in `!cancelled() && ...` for both multi and single arch in backend.yml and backend_pr.yml so partial build failures publish the successful tag-suffixes. 3. Darwin llama-cpp grpc-server build fails with `find_package(absl)` not found. Same shape as the ccache/blake3/fmt/hiredis/xxhash/zstd fix already in `Dependencies`: a brew cache hit restores `/opt/homebrew/Cellar/grpc` so `brew install grpc` no-ops, but abseil isn't in our Cellar cache list and never gets installed alongside, leaving grpc's CMake unable to resolve it. Mirror the `brew reinstall ccache` line with `brew reinstall grpc` to re-validate grpc's full transitive dep closure on every cache-hit run. 4. Move the four heaviest CUDA cpp builds back to bigger-runner. v4.2.1 wall-clock: -gpu-nvidia-cuda-12-llama-cpp 5h36m, -gpu-nvidia-cuda-12-turboquant 6h05m, -gpu-nvidia-cuda-13-llama-cpp 5h37m, -gpu-nvidia-cuda-13-turboquant 6h05m. The cuda-12 turboquant and cuda-13 turboquant entries are over GHA's 6h job timeout. Phase 5.3 of the free-tier migration (PR #9730) had explicitly flagged this batch as 'highest-risk' with a per-entry revert path. All other matrix entries (vulkan-llama-cpp ~47m, ROCm hipblas-llama-cpp ~2h, intel sycl-f32 ~1h49m) stay on free-tier ubuntu-latest. Verified locally: all six edited workflow YAMLs parse cleanly. Real verification has to come from the next tag release run. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * ci: extract keepalive anchor + cleanup into .github/scripts/ The two inline shell blocks from the previous commit are long enough to hurt readability of the workflow YAML and benefit from their own files with self-contained docs. Move them to .github/scripts/: anchor-digest-in-cache.sh backend_build.yml's keepalive anchor cleanup-keepalive-tags.sh backend_merge.yml's best-effort cleanup Workflow steps reduce to a single `run:` invocation each, with all the parameter plumbing handled by env vars on the step. backend_merge.yml also gains a sparse `actions/checkout@v6` step (sparse to .github/scripts only) so the cleanup script is available on the runner — backend_build already checks out for the docker build. Net workflow diff: -36 lines across the two files. Script logic and behavior are byte-identical to the inline version. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
1 parent a57e736 commit 86a7f6c

8 files changed

Lines changed: 157 additions & 8 deletions

File tree

.github/backend-matrix.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,12 @@ include:
389389
tag-latest: 'auto'
390390
tag-suffix: '-gpu-nvidia-cuda-12-llama-cpp'
391391
builder-base-image: 'quay.io/go-skynet/ci-cache:base-grpc-cuda-12-amd64'
392-
runs-on: 'ubuntu-latest'
392+
# bigger-runner: cold builds for this entry consistently take 5h+ on
393+
# ubuntu-latest (observed 5h36m on v4.2.1). Move back to bigger-runner
394+
# so the build finishes well within GHA's 6h job timeout. Phase 5.3 of
395+
# the free-tier migration (PR #9730) flipped this to ubuntu-latest as
396+
# a 'highest-risk batch' with explicit per-entry revert.
397+
runs-on: 'bigger-runner'
393398
base-image: "ubuntu:24.04"
394399
skip-drivers: 'false'
395400
backend: "llama-cpp"
@@ -403,7 +408,9 @@ include:
403408
tag-latest: 'auto'
404409
tag-suffix: '-gpu-nvidia-cuda-12-turboquant'
405410
builder-base-image: 'quay.io/go-skynet/ci-cache:base-grpc-cuda-12-amd64'
406-
runs-on: 'ubuntu-latest'
411+
# bigger-runner: same rationale as -gpu-nvidia-cuda-12-llama-cpp above
412+
# (observed 6h5m wall-clock on v4.2.1, just past the 6h job timeout).
413+
runs-on: 'bigger-runner'
407414
base-image: "ubuntu:24.04"
408415
skip-drivers: 'false'
409416
backend: "turboquant"
@@ -899,7 +906,9 @@ include:
899906
tag-latest: 'auto'
900907
tag-suffix: '-gpu-nvidia-cuda-13-llama-cpp'
901908
builder-base-image: 'quay.io/go-skynet/ci-cache:base-grpc-cuda-13-amd64'
902-
runs-on: 'ubuntu-latest'
909+
# bigger-runner: cold builds for this entry take 5h+ on ubuntu-latest
910+
# (observed 5h37m on v4.2.1). Same rationale as the cuda-12 variant.
911+
runs-on: 'bigger-runner'
903912
base-image: "ubuntu:24.04"
904913
skip-drivers: 'false'
905914
backend: "llama-cpp"
@@ -913,7 +922,8 @@ include:
913922
tag-latest: 'auto'
914923
tag-suffix: '-gpu-nvidia-cuda-13-turboquant'
915924
builder-base-image: 'quay.io/go-skynet/ci-cache:base-grpc-cuda-13-amd64'
916-
runs-on: 'ubuntu-latest'
925+
# bigger-runner: observed 6h5m wall-clock on v4.2.1 — at the GHA timeout.
926+
runs-on: 'bigger-runner'
917927
base-image: "ubuntu:24.04"
918928
skip-drivers: 'false'
919929
backend: "turboquant"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
# Anchor a backend per-arch digest in quay.io/go-skynet/ci-cache so quay's
3+
# garbage collector won't reap the manifest before backend_merge.yml runs.
4+
#
5+
# Context: backend_build.yml pushes by canonical digest only
6+
# (push-by-digest=true). Unreferenced manifests on quay can be reaped within
7+
# ~1-2h, but backend-merge-jobs runs only after the *entire* per-arch build
8+
# matrix drains (max-parallel: 8 × dozens of entries → ~2h+). Without an
9+
# anchoring tag, the earliest digests are gone by the time `imagetools create`
10+
# tries to read them, producing "manifest not found" merge failures.
11+
#
12+
# We tag the digest under our internal ci-cache image; quay does not GC tagged
13+
# manifests. The user-facing manifest list still references the original
14+
# digest in local-ai-backends. backend_merge.yml deletes the anchor tag after
15+
# the user-facing manifest is published — see cleanup-keepalive-tags.sh.
16+
#
17+
# Required env:
18+
# GITHUB_RUN_ID - current workflow run id (set automatically by GHA)
19+
# TAG_SUFFIX - matrix entry's tag-suffix (e.g. -gpu-nvidia-cuda-12-vllm)
20+
# PLATFORM_TAG - amd64 / arm64 / single (single = singleton matrix entry)
21+
# DIGEST - canonical content digest from build step (sha256:...)
22+
#
23+
# Optional env:
24+
# ANCHOR_IMAGE - target image (default: quay.io/go-skynet/ci-cache)
25+
# SOURCE_IMAGE - source image (default: quay.io/go-skynet/local-ai-backends)
26+
# GITHUB_STEP_SUMMARY - if set, an anchored-by line is appended to it
27+
set -euo pipefail
28+
29+
: "${GITHUB_RUN_ID:?}"
30+
: "${TAG_SUFFIX:?}"
31+
: "${PLATFORM_TAG:?}"
32+
: "${DIGEST:?}"
33+
34+
anchor_image="${ANCHOR_IMAGE:-quay.io/go-skynet/ci-cache}"
35+
source_image="${SOURCE_IMAGE:-quay.io/go-skynet/local-ai-backends}"
36+
37+
tag="keepalive-${GITHUB_RUN_ID}${TAG_SUFFIX}-${PLATFORM_TAG}"
38+
39+
docker buildx imagetools create \
40+
-t "${anchor_image}:${tag}" \
41+
"${source_image}@${DIGEST}"
42+
43+
echo "anchored ${DIGEST} as ${anchor_image}:${tag}"
44+
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
45+
echo "anchored \`${DIGEST}\` as \`${anchor_image}:${tag}\`" >> "${GITHUB_STEP_SUMMARY}"
46+
fi
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env bash
2+
# Best-effort cleanup of the keepalive anchor tags written by
3+
# anchor-digest-in-cache.sh. Called from backend_merge.yml after the
4+
# user-facing manifest list has been published.
5+
#
6+
# Quay's docker registry v2 doesn't allow tag deletes — only digest deletes.
7+
# The proper delete is the quay REST API, which requires an OAuth-scoped
8+
# token. We try QUAY_TOKEN as a bearer token: if the secret is an OAuth app
9+
# token (typical for service accounts) the delete succeeds; otherwise this
10+
# is a soft no-op and the tag persists until manually pruned.
11+
#
12+
# Cleanup failure MUST NOT fail the merge — the merge has already produced
13+
# the user-facing manifest list at this point and the keepalive tags are
14+
# pure overhead. We always exit 0.
15+
#
16+
# Required env:
17+
# GITHUB_RUN_ID - current workflow run id (set automatically by GHA)
18+
# TAG_SUFFIX - matrix entry's tag-suffix (e.g. -gpu-nvidia-cuda-12-vllm)
19+
# QUAY_TOKEN - bearer token for quay's REST API
20+
#
21+
# Optional env:
22+
# QUAY_REPO - target repo (default: go-skynet/ci-cache)
23+
# PLATFORM_TAGS - space-separated list of platform-tag values to try
24+
# (default: "amd64 arm64 single")
25+
# We don't know which platform-tag(s) exist for this
26+
# tag-suffix without an extra API call, so we just try
27+
# all three and ignore 404s for the ones that don't.
28+
set -uo pipefail
29+
30+
: "${GITHUB_RUN_ID:?}"
31+
: "${TAG_SUFFIX:?}"
32+
: "${QUAY_TOKEN:?}"
33+
34+
quay_repo="${QUAY_REPO:-go-skynet/ci-cache}"
35+
platform_tags="${PLATFORM_TAGS:-amd64 arm64 single}"
36+
37+
for plat in $platform_tags; do
38+
tag="keepalive-${GITHUB_RUN_ID}${TAG_SUFFIX}-${plat}"
39+
url="https://quay.io/api/v1/repository/${quay_repo}/tag/${tag}"
40+
http=$(curl -sS -o /dev/null -w '%{http_code}' \
41+
-X DELETE -H "Authorization: Bearer ${QUAY_TOKEN}" "$url" || echo "000")
42+
case "$http" in
43+
204|200) echo "deleted $tag" ;;
44+
404) echo "not present: $tag" ;;
45+
401|403) echo "auth not OAuth-scoped (http $http) for $tag - skipping; orphan tag will persist" ;;
46+
*) echo "unexpected http $http deleting $tag - skipping" ;;
47+
esac
48+
done
49+
exit 0

.github/workflows/backend.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,13 @@ jobs:
154154
# digest only — no tags are applied at build time.
155155
backend-merge-jobs-multiarch:
156156
needs: [generate-matrix, backend-jobs-multiarch]
157-
if: needs.generate-matrix.outputs['has-merges-multiarch'] == 'true'
157+
# !cancelled() lets the merge run even when a few build legs failed.
158+
# Without it, GHA's default `needs:` cascade skips the entire merge
159+
# matrix on a single failed/cancelled cell. We still want to publish
160+
# the manifest lists for tag-suffixes whose legs all succeeded.
161+
# Observed in v4.2.1: 2 singlearch build failures cascade-skipped all
162+
# ~199 singlearch merge entries.
163+
if: ${{ !cancelled() && needs.generate-matrix.outputs['has-merges-multiarch'] == 'true' }}
158164
uses: ./.github/workflows/backend_merge.yml
159165
with:
160166
tag-latest: ${{ matrix.tag-latest }}
@@ -170,7 +176,8 @@ jobs:
170176

171177
backend-merge-jobs-singlearch:
172178
needs: [generate-matrix, backend-jobs-singlearch]
173-
if: needs.generate-matrix.outputs['has-merges-singlearch'] == 'true'
179+
# See note on backend-merge-jobs-multiarch above for !cancelled().
180+
if: ${{ !cancelled() && needs.generate-matrix.outputs['has-merges-singlearch'] == 'true' }}
174181
uses: ./.github/workflows/backend_merge.yml
175182
with:
176183
tag-latest: ${{ matrix.tag-latest }}

.github/workflows/backend_build.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,16 @@ jobs:
228228
digest="${{ steps.build.outputs.digest }}"
229229
touch "/tmp/digests/${digest#sha256:}"
230230
231+
# See .github/scripts/anchor-digest-in-cache.sh for why this is needed
232+
# and how it interacts with backend_merge.yml's cleanup step.
233+
- name: Anchor digest in ci-cache so quay GC won't reap before merge
234+
if: github.event_name != 'pull_request'
235+
env:
236+
TAG_SUFFIX: ${{ inputs.tag-suffix }}
237+
PLATFORM_TAG: ${{ inputs.platform-tag || 'single' }}
238+
DIGEST: ${{ steps.build.outputs.digest }}
239+
run: .github/scripts/anchor-digest-in-cache.sh
240+
231241
# Artifact name uses a `--` separator between tag-suffix and platform-tag
232242
# to avoid prefix collisions during the merge job's pattern-based download.
233243
# Tag-suffixes are not prefix-disjoint (e.g. -gpu-nvidia-cuda-12-vllm is a

.github/workflows/backend_build_darwin.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ jobs:
116116
# already), we don't have to chase missing dylibs one at a time.
117117
# The downloads cache makes the reinstall fast (~5s on a hit).
118118
brew reinstall ccache
119+
# Same pattern for grpc: its CMake config (used by the llama-cpp
120+
# `grpc-server` target) does find_package(absl). The cache restores
121+
# /opt/homebrew/Cellar/grpc so brew above no-ops the install, but
122+
# abseil isn't in our Cellar cache list and never gets installed
123+
# alongside, leaving grpc's CMake unable to resolve it. Reinstalling
124+
# grpc re-validates and pulls abseil in, mirroring the ccache fix.
125+
brew reinstall grpc
119126
# The brew cache restores the Cellar dirs but NOT the bin symlinks
120127
# at /opt/homebrew/bin/*. brew install above sees the Cellar present
121128
# and decides "already installed" without re-linking, so on a cache-

.github/workflows/backend_merge.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ jobs:
3434
env:
3535
quay_username: ${{ secrets.quayUsername }}
3636
steps:
37+
# Sparse checkout: the merge job needs `.github/scripts/` (for the
38+
# keepalive cleanup script) but none of the source tree.
39+
- name: Checkout (.github/scripts only)
40+
uses: actions/checkout@v6
41+
with:
42+
sparse-checkout: |
43+
.github/scripts
44+
sparse-checkout-cone-mode: false
45+
3746
# `--` separator anchors the glob so we don't over-match sibling
3847
# backends whose tag-suffix happens to be a prefix of ours
3948
# (e.g. -cpu-vllm vs -cpu-vllm-omni). Must stay in sync with the
@@ -126,6 +135,15 @@ jobs:
126135
docker buildx imagetools inspect "$first_tag"
127136
fi
128137
138+
# See .github/scripts/cleanup-keepalive-tags.sh for why this is
139+
# best-effort and what the failure modes are.
140+
- name: Cleanup keepalive tags in ci-cache
141+
if: github.event_name != 'pull_request' && success()
142+
env:
143+
TAG_SUFFIX: ${{ inputs.tag-suffix }}
144+
QUAY_TOKEN: ${{ secrets.quayPassword }}
145+
run: .github/scripts/cleanup-keepalive-tags.sh
146+
129147
- name: Job summary
130148
if: github.event_name != 'pull_request'
131149
run: |

.github/workflows/backend_pr.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ jobs:
104104
# backend_merge.yml's push-side steps are all gated on
105105
# github.event_name != 'pull_request', so on a PR the merge job would
106106
# do nothing. Skip it entirely to avoid spinning up an empty runner.
107-
if: github.event_name != 'pull_request' && needs.generate-matrix.outputs['has-merges-multiarch'] == 'true'
107+
# !cancelled() lets the merge run even when a few build legs fail —
108+
# see the matching note in backend.yml.
109+
if: ${{ !cancelled() && github.event_name != 'pull_request' && needs.generate-matrix.outputs['has-merges-multiarch'] == 'true' }}
108110
uses: ./.github/workflows/backend_merge.yml
109111
with:
110112
tag-latest: ${{ matrix.tag-latest }}
@@ -118,7 +120,7 @@ jobs:
118120

119121
backend-merge-jobs-singlearch:
120122
needs: [generate-matrix, backend-jobs-singlearch]
121-
if: github.event_name != 'pull_request' && needs.generate-matrix.outputs['has-merges-singlearch'] == 'true'
123+
if: ${{ !cancelled() && github.event_name != 'pull_request' && needs.generate-matrix.outputs['has-merges-singlearch'] == 'true' }}
122124
uses: ./.github/workflows/backend_merge.yml
123125
with:
124126
tag-latest: ${{ matrix.tag-latest }}

0 commit comments

Comments
 (0)