Skip to content

Commit f4036fa

Browse files
committed
ci(python-backends): add weekly DEPS_REFRESH cache-buster
The shared backend/Dockerfile.python ends in: RUN cd /${BACKEND} && PORTABLE_PYTHON=true make which `pip install`s each backend's requirements*.txt. A scan of all 34 Python backends shows every single one ships at least some unpinned deps (torch, transformers, vllm, diffusers, ...). With the registry cache now enabled, that `make` layer's BuildKit hash depends only on Dockerfile instructions + COPYed source — not on what pip resolves at runtime — so a warm cache would freeze upstream versions indefinitely. DEPS_REFRESH is an ARG declared right before that RUN. backend_build.yml computes `date -u +%Y-W%V` (ISO week, e.g. `2026-W17`) and passes it as a build-arg, so the install layer invalidates at most once per week and re-resolves PyPI / nightly indexes. Within a week, builds stay warm. Only Dockerfile.python is affected: Go (go.sum) and Rust (Cargo.lock) already lock their deps, and the C++ backends pull gRPC at a pinned tag and llama.cpp at a pinned commit. Add .agents/ci-caching.md documenting the cache layout (quay.io/go-skynet/ci-cache:cache<tag-suffix>), read/write semantics (master writes, PRs read-only), DEPS_REFRESH semantics, and how to manually evict tags. Index it from AGENTS.md (CLAUDE.md is a symlink). Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: claude-code:claude-opus-4-7-1m
1 parent 3810fe1 commit f4036fa

4 files changed

Lines changed: 106 additions & 0 deletions

File tree

.agents/ci-caching.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# CI Build Caching
2+
3+
Container builds — both the root LocalAI image (`Dockerfile`) and the per-backend images (`backend/Dockerfile.*`) — share a registry-backed BuildKit cache. This file explains how that cache is laid out, what invalidates it, and how to bypass it.
4+
5+
## Cache layout
6+
7+
- **Cache registry**: `quay.io/go-skynet/ci-cache`
8+
- **One tag per matrix entry**, derived from the existing `tag-suffix`:
9+
- Backend builds (`backend_build.yml`): `cache<tag-suffix>`
10+
- e.g. `cache-gpu-nvidia-cuda-12-llama-cpp`, `cache-cpu-vllm`, `cache-nvidia-l4t-cuda-13-arm64-vllm`
11+
- Root image builds (`image_build.yml`): `cache-localai<tag-suffix>`
12+
- e.g. `cache-localai-gpu-nvidia-cuda-12`, `cache-localai-gpu-vulkan`
13+
- Each tag stores a multi-arch BuildKit cache manifest (`mode=max`), so every intermediate stage is re-usable, not just the final image.
14+
15+
## Read/write semantics
16+
17+
| Trigger | `cache-from` | `cache-to` |
18+
|---|---|---|
19+
| `push` to `master` / tag | yes | yes (`mode=max,ignore-error=true`) |
20+
| `pull_request` | yes | **no** |
21+
22+
PR builds read master's warm cache but never write — this prevents PRs from polluting the shared cache with their experimental state. After merge, the master build for that matrix entry refreshes the cache.
23+
24+
`ignore-error=true` on the write side means a transient quay push failure does not fail the build; the next master push retries.
25+
26+
## Self-warming, no separate populator
27+
28+
There is no cron job that pre-warms the cache. The production builds *are* the populator. The first master build of a given matrix entry pays the cold cost; subsequent same-entry master builds reuse everything that hasn't changed (apt installs, gRPC compile in `Dockerfile.{llama-cpp,ik-llama-cpp,turboquant}`, Python wheel installs, etc.).
29+
30+
Historically there was a `generate_grpc_cache.yaml` cron that targeted a `grpc` stage in the root Dockerfile. That stage was removed in July 2025 and the cron silently failed every night for 9 months without writing anything. It was deleted along with the registry-cache rollout.
31+
32+
## The `DEPS_REFRESH` cache-buster (Python backends)
33+
34+
Every Python backend goes through the shared `backend/Dockerfile.python`, which ends with:
35+
36+
```dockerfile
37+
ARG DEPS_REFRESH=initial
38+
RUN cd /${BACKEND} && PORTABLE_PYTHON=true make
39+
```
40+
41+
Most Python backends ship `requirements*.txt` files that **do not pin every transitive dep** (`torch`, `transformers`, `vllm`, `diffusers`, etc. are listed without a `==` pin, or with `>=` lower bounds only). With a warm BuildKit cache, the `make` layer hashes only on Dockerfile instructions + COPYed source — not on what `pip install` resolves at runtime. So a warm cache would ship the *first* version of `vllm` ever cached and never pick up upstream releases.
42+
43+
`DEPS_REFRESH` defends against that:
44+
45+
- `backend_build.yml` computes `date -u +%Y-W%V` (ISO week, e.g. `2026-W17`) before each build and passes it as a build-arg.
46+
- The `RUN ... make` layer's BuildKit hash now includes that string, so the layer invalidates **at most once per week**, automatically picking up newer wheels.
47+
- Within a week, builds stay warm.
48+
49+
This applies only to `Dockerfile.python` because:
50+
- Go (`Dockerfile.golang`) pins versions in `go.mod` / `go.sum`.
51+
- Rust (`Dockerfile.rust`) pins via `Cargo.lock`.
52+
- C++ backends (`Dockerfile.{llama-cpp,ik-llama-cpp,turboquant}`) clone gRPC at a pinned tag (`v1.65.0`) and llama.cpp at a pinned commit; their inputs don't drift between rebuilds.
53+
54+
### Adjusting the cadence
55+
56+
If you need a faster refresh (e.g. while debugging an upstream flake), bump the format to daily (`+%Y-%m-%d`) or hourly (`+%Y-%m-%d-%H`). If you need a one-shot rebuild for a specific backend without changing the schedule, append a marker to the tag-suffix in the matrix or temporarily delete that backend's cache tag in quay.
57+
58+
## Manually evicting cache
59+
60+
To force a fully cold build for one backend or the whole image:
61+
62+
```bash
63+
# Delete a single tag (requires quay credentials with admin on the repo)
64+
curl -X DELETE \
65+
-H "Authorization: Bearer ${QUAY_TOKEN}" \
66+
https://quay.io/api/v1/repository/go-skynet/ci-cache/tag/cache-gpu-nvidia-cuda-12-vllm
67+
68+
# List all tags
69+
curl -s -H "Authorization: Bearer ${QUAY_TOKEN}" \
70+
"https://quay.io/api/v1/repository/go-skynet/ci-cache/tag/?limit=100" | jq '.tags[].name'
71+
```
72+
73+
Eviction is rarely needed in normal operation — `DEPS_REFRESH` handles weekly drift, source changes invalidate naturally, and `mode=max` keeps the cache scoped per matrix entry so a stale tag never bleeds into a different build.
74+
75+
## What the cache **does not** cover
76+
77+
- The "Free Disk Space" / "Release space from worker" steps run on every job — these reclaim ~6 GB on `ubuntu-latest` runners. They are runner-state cleanup, not Docker, and BuildKit caches don't apply.
78+
- Intermediate artifacts of `Build and push (PR)` are not pushed anywhere — PRs only build for verification.
79+
80+
## Touching the cache pipeline
81+
82+
When changing `image_build.yml`, `backend_build.yml`, or any of the `backend/Dockerfile.*` files:
83+
84+
1. **Don't drop `DEPS_REFRESH=...` from the build-args** without a replacement strategy (lockfiles, pinned requirements). Otherwise master will silently freeze on whichever versions were cached at the time.
85+
2. **Keep `tag-suffix` unique per matrix entry** — it's the cache namespace. Two matrix entries sharing a tag-suffix would clobber each other's cache.
86+
3. **Keep `cache-to` gated on `github.event_name != 'pull_request'`** — PRs must not write.
87+
4. **Keep `ignore-error=true` on `cache-to`** — quay registry hiccups must not fail builds.

.github/workflows/backend_build.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ jobs:
208208
username: ${{ secrets.quayUsername }}
209209
password: ${{ secrets.quayPassword }}
210210

211+
# Weekly cache-buster for the per-backend `make` step. Most Python
212+
# backends list unpinned deps (torch, transformers, vllm, ...), so a
213+
# warm cache freezes upstream versions indefinitely. Rolling this
214+
# weekly forces a re-resolve of the install layer at most once per
215+
# week, picking up newer wheels without a full cold rebuild.
216+
- name: Compute deps refresh key
217+
id: deps_refresh
218+
run: echo "key=$(date -u +%Y-W%V)" >> "$GITHUB_OUTPUT"
219+
211220
- name: Build and push
212221
uses: docker/build-push-action@v7
213222
if: github.event_name != 'pull_request'
@@ -222,6 +231,7 @@ jobs:
222231
BACKEND=${{ inputs.backend }}
223232
UBUNTU_VERSION=${{ inputs.ubuntu-version }}
224233
AMDGPU_TARGETS=${{ inputs.amdgpu-targets }}
234+
DEPS_REFRESH=${{ steps.deps_refresh.outputs.key }}
225235
context: ${{ inputs.context }}
226236
file: ${{ inputs.dockerfile }}
227237
cache-from: type=registry,ref=quay.io/go-skynet/ci-cache:cache${{ inputs.tag-suffix }}
@@ -245,6 +255,7 @@ jobs:
245255
BACKEND=${{ inputs.backend }}
246256
UBUNTU_VERSION=${{ inputs.ubuntu-version }}
247257
AMDGPU_TARGETS=${{ inputs.amdgpu-targets }}
258+
DEPS_REFRESH=${{ steps.deps_refresh.outputs.key }}
248259
context: ${{ inputs.context }}
249260
file: ${{ inputs.dockerfile }}
250261
cache-from: type=registry,ref=quay.io/go-skynet/ci-cache:cache${{ inputs.tag-suffix }}

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ LocalAI follows the Linux kernel project's [guidelines for AI coding assistants]
1919
|------|-------------|
2020
| [.agents/ai-coding-assistants.md](.agents/ai-coding-assistants.md) | Policy for AI-assisted contributions — licensing, DCO, attribution |
2121
| [.agents/building-and-testing.md](.agents/building-and-testing.md) | Building the project, running tests, Docker builds for specific platforms |
22+
| [.agents/ci-caching.md](.agents/ci-caching.md) | CI build cache layout (registry-backed BuildKit cache on quay.io/go-skynet/ci-cache), `DEPS_REFRESH` weekly cache-buster for unpinned Python deps, manual eviction |
2223
| [.agents/adding-backends.md](.agents/adding-backends.md) | Adding a new backend (Python, Go, or C++) — full step-by-step checklist, including importer integration (the `/import-model` dropdown is server-driven from `GET /backends/known`) |
2324
| [.agents/coding-style.md](.agents/coding-style.md) | Code style, editorconfig, logging, documentation conventions |
2425
| [.agents/llama-cpp-backend.md](.agents/llama-cpp-backend.md) | Working on the llama.cpp backend — architecture, updating, tool call parsing |

backend/Dockerfile.python

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@ COPY scripts/build/package-gpu-libs.sh /package-gpu-libs.sh
203203
ARG FROM_SOURCE=""
204204
ENV FROM_SOURCE=${FROM_SOURCE}
205205

206+
# Cache-buster for the per-backend `make` step. Most Python backends list
207+
# unpinned deps (torch, transformers, vllm, ...), so a warm registry cache
208+
# would otherwise freeze upstream versions indefinitely. CI passes a value
209+
# that rolls weekly so the install layer is rebuilt at most once per week
210+
# and picks up newer wheels from PyPI / nightly indexes.
211+
ARG DEPS_REFRESH=initial
212+
206213
RUN cd /${BACKEND} && PORTABLE_PYTHON=true make
207214

208215
# Package GPU libraries into the backend's lib directory

0 commit comments

Comments
 (0)