From d5df6c00de5e4b8f31188b36bbbe49dd5eb59d43 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Wed, 24 Jun 2026 01:21:17 +0000 Subject: [PATCH] docker: track latest llama.cpp release + show its update banner in Studio Two related changes to the baked llama.cpp prebuilt. 1. Dynamically follow the newest unslothai/llama.cpp release. build.sh resolves the latest release tag (following the /releases/latest redirect, no API token) to a concrete tag and passes it as LLAMA_PREBUILT_TAG, so the layer cache busts only when upstream publishes. The Dockerfile default is now "latest" and fetch_llama_prebuilt.py resolves it the same way, so a plain `docker build .` also tracks latest. Pin LLAMA_PREBUILT_TAG to a concrete tag for a reproducible, frozen build. 2. Make the in-app "newer llama.cpp available" banner work inside the image. Studio's freshness check (utils.llama_cpp_freshness.check_prebuilt_freshness) keys off tag / release_tag / published_repo in UNSLOTH_PREBUILT_INFO.json -- the schema install_llama_prebuilt.py writes. The image bakes the bundle directly, so the marker was the release tarball's own, which only carries upstream_tag / source_repo; the freshness check then bailed with installed_tag=None and could never report "behind", hiding the banner. fetch_llama_prebuilt.py now augments the baked marker with those keys (setdefault, no build timestamp so the layer stays byte-identical). A fresh build is on latest -> no banner; once upstream publishes a newer release the banner appears, as verified against the real freshness backend. --- docker/Dockerfile | 7 ++++- docker/build.sh | 21 ++++++++++++++ docker/fetch_llama_prebuilt.py | 50 +++++++++++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1dca2799e1..70c910ab1e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -617,7 +617,12 @@ RUN set -eux \ # so the python-side tensor mappings match the binaries # /opt (not /root) so the install survives a `docker run --user` override; # UNSLOTH_LLAMA_CPP_PATH makes zoo find it regardless of $HOME. -ARG LLAMA_PREBUILT_TAG=b9596-mix-e6f2453 +# Default "latest" -> fetch_llama_prebuilt.py resolves the newest +# unslothai/llama.cpp release at build time (follows the /releases/latest +# redirect, no API token). build.sh resolves this to a concrete tag before +# invoking docker build so the layer cache busts only when upstream publishes; +# pass --build-arg LLAMA_PREBUILT_TAG= for a frozen, reproducible build. +ARG LLAMA_PREBUILT_TAG=latest COPY fetch_llama_prebuilt.py /tmp/fetch_llama_prebuilt.py RUN /opt/unsloth-venv/bin/python /tmp/fetch_llama_prebuilt.py \ "${LLAMA_PREBUILT_TAG}" "${TARGETARCH:-amd64}" /opt/unsloth/llama.cpp \ diff --git a/docker/build.sh b/docker/build.sh index 592a52da49..f84aa9f36e 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -18,10 +18,30 @@ PYTHON_VERSION="${PYTHON_VERSION:-3.12}" UNSLOTH_REF="${UNSLOTH_REF:-main}" UNSLOTH_ZOO_REF="${UNSLOTH_ZOO_REF:-main}" +# llama.cpp prebuilt: default to the newest unslothai/llama.cpp release, resolved +# here to a concrete tag so the build-arg changes only when upstream publishes a +# new release (correct Docker layer caching) and the build stays reproducible. +# Pin it explicitly for a frozen build: LLAMA_PREBUILT_TAG=b9596-mix-e6f2453 ./build.sh +resolve_latest_llama_tag() { + curl -fsSL -o /dev/null -w '%{url_effective}' \ + "https://github.com/unslothai/llama.cpp/releases/latest" 2>/dev/null \ + | sed -n 's#.*/releases/tag/##p' +} +if [ -z "${LLAMA_PREBUILT_TAG:-}" ]; then + LLAMA_PREBUILT_TAG="$(resolve_latest_llama_tag || true)" + if [ -n "$LLAMA_PREBUILT_TAG" ]; then + echo "Resolved latest llama.cpp release: ${LLAMA_PREBUILT_TAG}" + else + LLAMA_PREBUILT_TAG="latest" + echo "Could not resolve latest llama.cpp tag here; passing 'latest' (resolved inside the build)" + fi +fi + echo "Building ${IMAGE_NAME}:${TAG}" echo " CUDA ${CUDA_VERSION} Ubuntu ${UBUNTU_VERSION} Python ${PYTHON_VERSION}" echo " unsloth @${UNSLOTH_REF}" echo " unsloth-zoo @${UNSLOTH_ZOO_REF}" +echo " llama.cpp ${LLAMA_PREBUILT_TAG}" echo " arch list 8.0;8.6;8.9;9.0;10.0;12.0+PTX" echo @@ -32,6 +52,7 @@ DOCKER_BUILDKIT=1 docker build \ --build-arg PYTHON_VERSION="${PYTHON_VERSION}" \ --build-arg UNSLOTH_REF="${UNSLOTH_REF}" \ --build-arg UNSLOTH_ZOO_REF="${UNSLOTH_ZOO_REF}" \ + --build-arg LLAMA_PREBUILT_TAG="${LLAMA_PREBUILT_TAG}" \ -t "${IMAGE_NAME}:${TAG}" \ . diff --git a/docker/fetch_llama_prebuilt.py b/docker/fetch_llama_prebuilt.py index 503723c9cc..3eb50d67a6 100644 --- a/docker/fetch_llama_prebuilt.py +++ b/docker/fetch_llama_prebuilt.py @@ -19,8 +19,13 @@ tensor mappings match the binaries -- the layout unsloth_zoo's check_llama_cpp() expects: binaries, converter and gguf-py/ at the install dir root. +The tag may be the literal "latest" (or empty), in which case the newest +published release of RELEASE_REPO is resolved at build time by following the +/releases/latest redirect (no API token, no API rate limit). Pass a concrete +tag for a reproducible build. + Usage (in the Dockerfile): - python fetch_llama_prebuilt.py + python fetch_llama_prebuilt.py """ import hashlib @@ -36,6 +41,20 @@ import urllib.request RELEASE_REPO = "unslothai/llama.cpp" +def resolve_latest_tag(repo: str) -> str: + # Follow the /releases/latest redirect to /releases/tag/. This needs no + # API token and is not subject to the GitHub API rate limit, so it works on + # any build host (CI, laptop, B200) without configuration. + url = f"https://github.com/{repo}/releases/latest" + request = urllib.request.Request(url, headers = {"User-Agent": "unsloth-docker-build"}) + with urllib.request.urlopen(request, timeout = 60) as response: + final_url = response.geturl() + marker = "/releases/tag/" + if marker not in final_url: + raise SystemExit(f"FAIL: could not resolve latest release of {repo} (landed on {final_url})") + return final_url.rsplit(marker, 1)[1].strip("/") + + def fetch(url: str, dest: str) -> None: request = urllib.request.Request(url, headers = {"User-Agent": "unsloth-docker-build"}) with urllib.request.urlopen(request, timeout = 600) as response, open(dest, "wb") as f: @@ -72,6 +91,9 @@ def extracted_root(extract_dir: str) -> str: def main() -> None: tag, target_arch, install_dir = sys.argv[1], sys.argv[2] or "amd64", sys.argv[3] + if tag in ("", "latest"): + tag = resolve_latest_tag(RELEASE_REPO) + print(f"resolved latest {RELEASE_REPO} release: {tag}") base_url = f"https://github.com/{RELEASE_REPO}/releases/download/{tag}" assets = { "amd64": f"app-{tag}-linux-x64-cuda12-portable.tar.gz", @@ -122,6 +144,32 @@ def main() -> None: if os.path.isdir(conversion): shutil.copytree(conversion, os.path.join(install_dir, "conversion"), dirs_exist_ok = True) + # Make the baked marker readable by Studio's llama.cpp freshness check + # (utils.llama_cpp_freshness.check_prebuilt_freshness) so the in-app + # "newer llama.cpp available" banner works inside the Docker image. + # The release tarball's UNSLOTH_PREBUILT_INFO.json carries upstream_tag / + # source_repo, but the freshness reader keys off tag / release_tag / + # published_repo -- the schema Studio's install_llama_prebuilt.py writes, + # which the image bypasses by baking the bundle directly. Without these + # keys the freshness check bails and can never report "behind", so the + # banner stays hidden even when a newer release exists. setdefault() so a + # future tarball that already ships these keys is left untouched, and we + # add no build timestamp -- behind/update_available do not need one, and + # omitting it keeps the layer byte-identical across build hosts. + marker_path = os.path.join(install_dir, "UNSLOTH_PREBUILT_INFO.json") + try: + with open(marker_path) as f: + marker = json.load(f) + except (OSError, ValueError): + marker = {} + marker.setdefault("tag", tag) + marker.setdefault("release_tag", tag) + marker.setdefault("published_repo", RELEASE_REPO) + with open(marker_path, "w") as f: + json.dump(marker, f, indent = 2) + f.write("\n") + print(f"marker augmented for freshness: tag={tag} published_repo={RELEASE_REPO}") + # Mirror the install into build/bin/ via hardlinks (zero extra bytes). # Studio's setup.sh treats an executable build/bin/llama-server + # build/bin/llama-quantize as a complete local build and skips its