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.
This commit is contained in:
Daniel Han 2026-06-24 01:21:17 +00:00
commit d5df6c00de
3 changed files with 76 additions and 2 deletions

View file

@ -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=<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 \

View file

@ -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}" \
.

View file

@ -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 <tag> <targetarch> <install_dir>
python fetch_llama_prebuilt.py <tag|latest> <targetarch> <install_dir>
"""
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/<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