From 8fa588db2ce980969f8a33b76f850e984c50257e Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Thu, 16 Jul 2026 06:53:44 +0000 Subject: [PATCH] docker: pin xformers explicitly, forward the llama tag to the Studio build The amd64 base install named the cu128-ampere-torch2110 extra, which does not exist on main yet (the CUDA extras stop at torch2100): pip/uv only warn on an unknown extra, so plain unsloth installed without xformers and the required- package check failed the build. Both arches now take the plain huggingface extra and amd64 pins xformers==0.0.35 explicitly in the same resolve (it requires torch>=2.10 without an exact pin, pairing with the baked 2.11.0; verified on PyPI, x86_64 wheels only, matching the arm64 exclusion). This decouples the base image from the pending extras PR. The Studio build now receives the SAME llama.cpp tag the base image baked: Dockerfile.studio grows a LLAMA_PREBUILT_TAG arg exported as UNSLOTH_LLAMA_TAG to install.sh (setup.sh honours it; the "latest" default is byte-identical to setup.sh's own default for local builds), and the publish workflow forwards the prepare job's resolved tag in the studio build-args. Without the pin a dispatch override or an upstream release landing between the two jobs let the no-GPU Studio build re-resolve "latest" and replace the pinned CUDA bundle. The Studio venv-match assertion also needs installer support for torch 2.11 on the CUDA path; that lands in a separate installer PR and is now declared as a merge-order dependency in the PR description (the publish workflow only runs on main pushes, so nothing builds before both are merged). --- .github/workflows/docker-publish.yml | 10 ++++++---- docker/Dockerfile | 28 +++++++++++++--------------- docker/Dockerfile.studio | 7 +++++++ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 6288d59a7d..8a661d7807 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -438,14 +438,16 @@ jobs: cache-from: type=gha,scope=studio-${{ matrix.platform }} cache-to: type=gha,scope=studio-${{ matrix.platform }},mode=min outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - # Both refs are the SAME resolved shas the base build baked (prepare - # job), so the Studio tree + its zoo overlay match the base venv even - # if the branch moved mid-run. (Prose stays out of build-args -- - # forwarded lines must be KEY=VALUE only.) + # All three pins are the SAME resolved values the base build baked + # (prepare job), so the Studio tree, its zoo overlay AND its llama.cpp + # bundle match the base image even if a branch or upstream release + # moved mid-run. (Prose stays out of build-args -- forwarded lines + # must be KEY=VALUE only.) build-args: | BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.merge.outputs.digest }} UNSLOTH_STUDIO_REF=${{ needs.prepare.outputs.unsloth_ref }} UNSLOTH_STUDIO_ZOO_REF=${{ needs.prepare.outputs.zoo_ref }} + LLAMA_PREBUILT_TAG=${{ needs.prepare.outputs.llama_tag }} - name: Export digest run: | diff --git a/docker/Dockerfile b/docker/Dockerfile index 23c717ea55..5bc6067154 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -137,18 +137,15 @@ RUN python -m venv ${VENV} && ${VENV}/bin/pip install -U pip wheel setuptools # Where torch's +cu128 wheels live, plus the xformers/cu128 URLs referenced # by unsloth's `cu128onlytorch2110` extra. # -# Why the extra is `cu128-ampere-torch2110` (not `cu128-torch2110-ampere`): -# The ordering is ampere-then-torch-ver (see the `cu*-ampere-torch2110` -# extras in unsloth's pyproject.toml). The torch2110 family pulls -# xformers 0.0.35, which does not pin torch and so pairs with the torch -# 2.11.0 line held below; the older torch2100 extra pins xformers 0.0.34 -> -# torch==2.10.0 and would conflict. Needs an unsloth that carries the -# torch2110 CUDA extras on main. -# -# Why arm64 uses a different extra: -# `cu128-ampere-torch2110` transitively pulls `cu128onlytorch2110` whose -# xformers wheel URL is hardcoded to manylinux_2_28_x86_64 (the aarch64 -# wheel gap -- see header), so arm64 takes the plain `huggingface` extra. +# Why the plain `huggingface` extra plus an EXPLICIT xformers pin (amd64): +# The cu128 CUDA extras on main stop at the torch2100 family, which pins +# xformers 0.0.34 -> torch==2.10.0 and would conflict with the torch 2.11.0 +# line held below; a not-yet-existing extra name would only WARN (pip/uv +# install plain unsloth) and silently drop xformers until the required- +# package check below failed the build. Pinning `xformers==0.0.35` directly +# (it does not pin torch, pairing with 2.11.0) keeps this build +# self-contained on today's main; arm64 stays xformers-less (no cu128 +# aarch64 wheel -- see header). # # Why no `flash-attn` here: # - FA3 is hard-refused on Blackwell (Dao-AILab/flash-attention#1810). @@ -161,17 +158,18 @@ ARG UNSLOTH_REF=main ARG UNSLOTH_ZOO_REF=main RUN set -eux \ && case "${TARGETARCH:-amd64}" in \ - amd64) UNSLOTH_EXTRA="cu128-ampere-torch2110" ;; \ - arm64) UNSLOTH_EXTRA="huggingface" ;; \ + amd64) UNSLOTH_EXTRA="huggingface"; XFORMERS_PIN="xformers==0.0.35" ;; \ + arm64) UNSLOTH_EXTRA="huggingface"; XFORMERS_PIN="" ;; \ *) echo "ERROR: unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ esac \ - && echo ">> TARGETARCH=${TARGETARCH:-amd64}, unsloth extra=[${UNSLOTH_EXTRA}]" \ + && echo ">> TARGETARCH=${TARGETARCH:-amd64}, unsloth extra=[${UNSLOTH_EXTRA}], xformers=[${XFORMERS_PIN}]" \ && ${VENV}/bin/pip install uv \ && ${VENV}/bin/uv pip install \ --python ${VENV}/bin/python \ --index-strategy unsafe-best-match \ --extra-index-url https://download.pytorch.org/whl/cu128 \ "torch==2.11.0" "torchvision==0.26.0" "torchaudio==2.11.0" \ + ${XFORMERS_PIN} \ "triton>=3.6.0" \ "bitsandbytes>=0.49.2,!=0.46.0,!=0.48.0" \ "unsloth_zoo @ git+https://github.com/unslothai/unsloth-zoo@${UNSLOTH_ZOO_REF}" \ diff --git a/docker/Dockerfile.studio b/docker/Dockerfile.studio index 122742037b..2ec9daac16 100644 --- a/docker/Dockerfile.studio +++ b/docker/Dockerfile.studio @@ -60,6 +60,12 @@ ARG UNSLOTH_STUDIO_REF=main # Studio builds, so the Studio backend runs the same zoo as the base image and # the operator-requested ref instead of always tracking main. ARG UNSLOTH_STUDIO_ZOO_REF=main +# The SAME llama.cpp tag the base image baked (the prepare job resolves it +# once). install.sh -> setup.sh honours UNSLOTH_LLAMA_TAG; without this pin a +# dispatch override, or an upstream release landing between the base and +# Studio jobs, lets the no-GPU Studio build re-resolve "latest" and replace +# the base's pinned CUDA bundle instead of reusing it. +ARG LLAMA_PREBUILT_TAG=latest ARG TARGETARCH # Services run as root in this revision (the base image is root-only by @@ -134,6 +140,7 @@ RUN set -eux \ && UNSLOTH_STUDIO_HOME="${UNSLOTH_STUDIO_HOME}" \ UNSLOTH_TORCH_INDEX_FAMILY="${TORCH_FAMILY}" \ UNSLOTH_ZOO_REF="${UNSLOTH_STUDIO_ZOO_REF}" \ + UNSLOTH_LLAMA_TAG="${LLAMA_PREBUILT_TAG}" \ UNSLOTH_PYTHON=3.12 \ bash install.sh --local \ # Fail loud unless the Studio venv torch EXACTLY matches the base venv