Merge remote-tracking branch 'origin/docker-blackwell-build' into pr-jupyter-studio-ux
This commit is contained in:
commit
2e4699c920
5 changed files with 173 additions and 45 deletions
95
.github/workflows/docker-publish.yml
vendored
95
.github/workflows/docker-publish.yml
vendored
|
|
@ -48,6 +48,10 @@ on:
|
|||
description: 'unslothai/llama.cpp prebuilt release tag to bake (blank = newest)'
|
||||
required: false
|
||||
default: ''
|
||||
notebooks_ref:
|
||||
description: 'unslothai/notebooks git ref to bake (resolved to one commit)'
|
||||
required: false
|
||||
default: 'main'
|
||||
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
|
|
@ -85,6 +89,12 @@ jobs:
|
|||
contents: read
|
||||
outputs:
|
||||
llama_tag: ${{ steps.llama.outputs.tag }}
|
||||
# One zoo ref + one notebooks commit, resolved here so BOTH arch legs of
|
||||
# the base build (and the Studio build) bake the identical bits. Resolving
|
||||
# them per-leg would let upstream advance between the amd64 and arm64
|
||||
# builds, putting different content under one published tag.
|
||||
zoo_ref: ${{ steps.zoo_ref.outputs.ref }}
|
||||
notebooks_commit: ${{ steps.notebooks.outputs.commit }}
|
||||
steps:
|
||||
- name: Resolve llama.cpp prebuilt tag
|
||||
id: llama
|
||||
|
|
@ -100,6 +110,46 @@ jobs:
|
|||
echo "tag=${TAG:-latest}" >> "$GITHUB_OUTPUT"
|
||||
echo "llama.cpp prebuilt tag: ${TAG:-latest}"
|
||||
|
||||
# Mirror the unsloth tag into the zoo ONLY when that tag actually exists
|
||||
# there. unsloth's v* tags are Studio releases the zoo never cuts (the zoo
|
||||
# repo currently has no tags at all), so blindly mirroring github.ref_name
|
||||
# made every tag publish fail inside the Dockerfile's zoo install. Resolved
|
||||
# once here and forwarded to the base build AND the Studio build, so the
|
||||
# full image's Studio venv runs the same zoo as the base image.
|
||||
- name: Resolve unsloth-zoo ref
|
||||
id: zoo_ref
|
||||
run: |
|
||||
REF="${{ github.event.inputs.unsloth_zoo_ref }}"
|
||||
if [ -z "$REF" ] && [ "${{ startsWith(github.ref, 'refs/tags/') }}" = "true" ]; then
|
||||
if git ls-remote --exit-code --tags https://github.com/unslothai/unsloth-zoo \
|
||||
"refs/tags/${{ github.ref_name }}" >/dev/null 2>&1; then
|
||||
REF="${{ github.ref_name }}"
|
||||
fi
|
||||
fi
|
||||
echo "ref=${REF:-main}" >> "$GITHUB_OUTPUT"
|
||||
echo "unsloth-zoo ref: ${REF:-main}"
|
||||
|
||||
# Freeze unslothai/notebooks to ONE concrete commit so both arch legs (and
|
||||
# release reruns) bake the identical baked-notebook templates and
|
||||
# .unsloth_template_commit, even if upstream advances mid-build. A 40-char
|
||||
# sha input is already frozen; a branch/tag (default main) is resolved to
|
||||
# its current sha via ls-remote, falling back to the bare ref on a lookup
|
||||
# miss so the Dockerfile can still fetch it by name.
|
||||
- name: Resolve unsloth/notebooks commit
|
||||
id: notebooks
|
||||
env:
|
||||
INPUT_REF: ${{ github.event.inputs.notebooks_ref }}
|
||||
run: |
|
||||
REF="${INPUT_REF:-main}"
|
||||
if printf '%s' "$REF" | grep -Eq '^[0-9a-f]{40}$'; then
|
||||
SHA="$REF"
|
||||
else
|
||||
SHA="$(git ls-remote https://github.com/unslothai/notebooks "$REF" | awk 'NR==1{print $1}')"
|
||||
[ -n "$SHA" ] || SHA="$REF"
|
||||
fi
|
||||
echo "commit=${SHA}" >> "$GITHUB_OUTPUT"
|
||||
echo "notebooks commit: ${SHA}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-arch build. The matrix fans out two parallel jobs on the matching
|
||||
# native runner. Each pushes a single-arch image *by digest* (no human-
|
||||
|
|
@ -164,24 +214,6 @@ jobs:
|
|||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
# Mirror the unsloth tag into the zoo ONLY when that tag actually
|
||||
# exists there. unsloth's v* tags are Studio releases the zoo never
|
||||
# cuts (the zoo repo currently has no tags at all), so blindly
|
||||
# mirroring github.ref_name made every tag publish fail inside the
|
||||
# Dockerfile's zoo install.
|
||||
- name: Resolve unsloth-zoo ref
|
||||
id: zoo_ref
|
||||
run: |
|
||||
REF="${{ github.event.inputs.unsloth_zoo_ref }}"
|
||||
if [ -z "$REF" ] && [ "${{ startsWith(github.ref, 'refs/tags/') }}" = "true" ]; then
|
||||
if git ls-remote --exit-code --tags https://github.com/unslothai/unsloth-zoo \
|
||||
"refs/tags/${{ github.ref_name }}" >/dev/null 2>&1; then
|
||||
REF="${{ github.ref_name }}"
|
||||
fi
|
||||
fi
|
||||
echo "ref=${REF:-main}" >> "$GITHUB_OUTPUT"
|
||||
echo "unsloth-zoo ref: ${REF:-main}"
|
||||
|
||||
- name: Build and push (per-arch by digest)
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
|
|
@ -202,18 +234,21 @@ jobs:
|
|||
# pushes bake the tag's source ref (e.g. v1.2.3) so the published
|
||||
# image actually contains that release; branch + scheduled runs bake
|
||||
# the triggering commit SHA; any other event falls back to main.
|
||||
# UNSLOTH_ZOO_REF (from the resolve step above): explicit dispatch
|
||||
# input, else the pushed tag IF the zoo repo has it, else main -- a
|
||||
# branch SHA does not exist in the zoo repo.
|
||||
# LLAMA_PREBUILT_TAG (from the prepare job): one concrete tag shared
|
||||
# by both arch legs so the published manifest is reproducible.
|
||||
# UNSLOTH_ZOO_REF (from the prepare job): explicit dispatch input,
|
||||
# else the pushed tag IF the zoo repo has it, else main -- a branch
|
||||
# SHA does not exist in the zoo repo. Resolved once in `prepare` and
|
||||
# shared with the Studio build so both venvs run the same zoo.
|
||||
# LLAMA_PREBUILT_TAG / UNSLOTH_NOTEBOOKS_REF (from the prepare job):
|
||||
# one concrete tag / commit shared by both arch legs so the
|
||||
# published manifest is byte-reproducible across platforms.
|
||||
build-args: |
|
||||
CUDA_VERSION=12.8.1
|
||||
UBUNTU_VERSION=24.04
|
||||
PYTHON_VERSION=3.12
|
||||
UNSLOTH_REF=${{ github.event.inputs.unsloth_ref || (startsWith(github.ref, 'refs/tags/') && github.ref_name) || github.sha || 'main' }}
|
||||
UNSLOTH_ZOO_REF=${{ steps.zoo_ref.outputs.ref }}
|
||||
UNSLOTH_ZOO_REF=${{ needs.prepare.outputs.zoo_ref }}
|
||||
LLAMA_PREBUILT_TAG=${{ needs.prepare.outputs.llama_tag }}
|
||||
UNSLOTH_NOTEBOOKS_REF=${{ needs.prepare.outputs.notebooks_commit }}
|
||||
|
||||
# Stash the per-arch digest as an artifact for the merge job to pick up.
|
||||
# Filenames need to be unique across the matrix; `platform` contains a
|
||||
|
|
@ -319,7 +354,9 @@ jobs:
|
|||
# that is the long pole, hence the larger timeout.
|
||||
# ---------------------------------------------------------------------------
|
||||
build-studio:
|
||||
needs: merge
|
||||
# `merge` for the freshly-published base manifest digest; `prepare` for the
|
||||
# one resolved zoo ref (job outputs only flow through direct `needs`).
|
||||
needs: [prepare, merge]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -382,11 +419,15 @@ jobs:
|
|||
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
|
||||
# UNSLOTH_STUDIO_REF mirrors the base job's UNSLOTH_REF resolution so the
|
||||
# Studio tree matches the unsloth baked into the base venv. (Prose stays
|
||||
# out of build-args -- forwarded lines must be KEY=VALUE only.)
|
||||
# Studio tree matches the unsloth baked into the base venv.
|
||||
# UNSLOTH_STUDIO_ZOO_REF is the SAME resolved zoo ref the base build
|
||||
# baked, so install.sh --local overlays the Studio venv with that zoo
|
||||
# instead of always tracking main. (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=${{ github.event.inputs.unsloth_ref || (startsWith(github.ref, 'refs/tags/') && github.ref_name) || github.sha || 'main' }}
|
||||
UNSLOTH_STUDIO_ZOO_REF=${{ needs.prepare.outputs.zoo_ref }}
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
|
|
|
|||
|
|
@ -697,8 +697,20 @@ ENV PATH=/opt/unsloth-nb/bin:${PATH}
|
|||
# / announcements / footer moved upstream (the tutorial body is unchanged) --
|
||||
# see unsloth_sync_notebooks.sh + unsloth_nb_content_sig.py. Inherited as-is by
|
||||
# the studio image (FROM base).
|
||||
#
|
||||
# UNSLOTH_NOTEBOOKS_REF pins ONE commit/branch/tag so a multi-arch publish bakes
|
||||
# identical templates into both arch legs even if unslothai/notebooks advances
|
||||
# mid-build. The publish workflow resolves the live HEAD sha once (like
|
||||
# LLAMA_PREBUILT_TAG) and passes it here; the default "main" keeps a plain
|
||||
# `docker build` tracking the tip. We fetch the single resolved ref (a full
|
||||
# 40-char sha fetches by object; a branch/tag fetches by name) at depth 1, so
|
||||
# the bake stays a shallow one-commit pull.
|
||||
ARG UNSLOTH_NOTEBOOKS_REF=main
|
||||
RUN set -eux \
|
||||
&& git clone --depth 1 https://github.com/unslothai/notebooks /opt/unsloth-notebooks \
|
||||
&& git init -q /opt/unsloth-notebooks \
|
||||
&& git -C /opt/unsloth-notebooks remote add origin https://github.com/unslothai/notebooks \
|
||||
&& git -C /opt/unsloth-notebooks fetch -q --depth 1 origin "${UNSLOTH_NOTEBOOKS_REF}" \
|
||||
&& git -C /opt/unsloth-notebooks checkout -q FETCH_HEAD \
|
||||
&& git -C /opt/unsloth-notebooks rev-parse HEAD > /opt/unsloth-notebooks/.unsloth_template_commit \
|
||||
&& rm -rf /opt/unsloth-notebooks/.git \
|
||||
&& du -sh /opt/unsloth-notebooks
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ FROM ${BASE_IMAGE}
|
|||
# that pins BASE_IMAGE to a digest should pin this too (same UNSLOTH_REF as
|
||||
# the base) so the published image is reproducible against a known ref.
|
||||
ARG UNSLOTH_STUDIO_REF=main
|
||||
# unsloth-zoo ref overlaid into the Studio venv by install.sh --local. The
|
||||
# publish workflow resolves ONE zoo ref and passes it to both the base and
|
||||
# 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
|
||||
ARG TARGETARCH
|
||||
|
||||
# Services run as root in this revision (the base image is root-only by
|
||||
|
|
@ -128,6 +133,7 @@ RUN set -eux \
|
|||
&& git checkout -q FETCH_HEAD \
|
||||
&& UNSLOTH_STUDIO_HOME="${UNSLOTH_STUDIO_HOME}" \
|
||||
UNSLOTH_TORCH_INDEX_FAMILY="${TORCH_FAMILY}" \
|
||||
UNSLOTH_ZOO_REF="${UNSLOTH_STUDIO_ZOO_REF}" \
|
||||
UNSLOTH_PYTHON=3.12 \
|
||||
bash install.sh --local \
|
||||
# Fail loud if the Studio venv torch missed the pinned CUDA family (an
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ are not intercepted -- the driven `unsloth-run` handles those by parsing the
|
|||
notebook directly.
|
||||
"""
|
||||
|
||||
import os, re, sys, subprocess
|
||||
import os, re, sys, subprocess, tempfile
|
||||
|
||||
REAL = {"pip": "/opt/unsloth-venv/bin/pip", "uv": "/opt/unsloth-venv/bin/uv"}
|
||||
MARKER = os.environ.get("UNSLOTH_NB_TF_MARKER", "/tmp/unsloth_nb/requested_transformers")
|
||||
|
|
@ -95,6 +95,56 @@ def _version_pin(token):
|
|||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def _filter_requirements_file(path):
|
||||
"""Strip baked/protected packages out of a `-r` requirements file.
|
||||
|
||||
Returns (path_to_use, recorded_transformers_version, dropped_specs). The same
|
||||
_KEEP / transformers rules the inline args get are applied to each requirement
|
||||
line, so a notebook `pip install -r reqs.txt` cannot overwrite the cu128 torch
|
||||
/ vLLM / transformers stack with versions pinned inside the file. When nothing
|
||||
is protected, or the file cannot be read/written, the original path is returned
|
||||
unchanged. Comments, blank lines, option lines and nested `-r`/`-c` includes are
|
||||
kept verbatim (nested includes are passed through, i.e. filtered one level).
|
||||
"""
|
||||
try:
|
||||
with open(path, encoding = "utf-8") as f:
|
||||
lines = f.readlines()
|
||||
except OSError:
|
||||
return path, None, [] # remote URL / unreadable -> let the real tool handle it
|
||||
out, dropped, recorded, changed = [], [], None, False
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith(("#", "-")):
|
||||
out.append(line) # comment / blank / option / nested include -> keep
|
||||
continue
|
||||
spec = stripped.split(" #", 1)[0].strip() # drop any inline comment
|
||||
name = _canon(spec)
|
||||
if name is None:
|
||||
out.append(line) # url / path / vcs / unparseable -> keep
|
||||
continue
|
||||
if name == "transformers":
|
||||
v = _version_pin(spec)
|
||||
if v and not recorded:
|
||||
recorded = v
|
||||
dropped.append(spec)
|
||||
changed = True
|
||||
continue
|
||||
if name in _KEEP or name.startswith(_KEEP_PREFIX):
|
||||
dropped.append(spec)
|
||||
changed = True
|
||||
continue
|
||||
out.append(line)
|
||||
if not changed:
|
||||
return path, None, []
|
||||
try:
|
||||
fd, tmp = tempfile.mkstemp(prefix = "unsloth-nb-req-", suffix = ".txt")
|
||||
with os.fdopen(fd, "w", encoding = "utf-8") as f:
|
||||
f.writelines(out)
|
||||
except OSError:
|
||||
return path, None, [] # can't write temp -> pass the file through unchanged
|
||||
return tmp, recorded, dropped
|
||||
|
||||
|
||||
def main():
|
||||
tool = "uv" if os.path.basename(sys.argv[0]).startswith("uv") else "pip"
|
||||
argv = sys.argv[1:]
|
||||
|
|
@ -125,12 +175,21 @@ def main():
|
|||
prev_flag = None
|
||||
for tok in tail:
|
||||
if skip_next:
|
||||
keep_args.append(tok)
|
||||
# The value of -r/--requirement pulls real requirements (a target);
|
||||
# the value of an index-url / find-links / constraint / etc. flag is
|
||||
# an option, not something to install.
|
||||
# The value of -r/--requirement pulls real requirements (a target); the
|
||||
# value of an index-url / find-links / constraint / etc. flag is an
|
||||
# option, not something to install.
|
||||
if prev_flag in _REQ_FILE_FLAGS:
|
||||
# Filter baked/protected packages out of the requirements file so a
|
||||
# notebook `pip install -r reqs.txt` cannot clobber the cu128 stack
|
||||
# or push transformers into the base venv.
|
||||
_req_path, _req_rec, _req_drp = _filter_requirements_file(tok)
|
||||
keep_args.append(_req_path)
|
||||
has_target = True
|
||||
if _req_rec and not recorded:
|
||||
recorded = _req_rec
|
||||
dropped.extend(_req_drp)
|
||||
else:
|
||||
keep_args.append(tok)
|
||||
skip_next = False
|
||||
prev_flag = None
|
||||
continue
|
||||
|
|
|
|||
34
install.sh
34
install.sh
|
|
@ -1866,6 +1866,16 @@ fi
|
|||
# ── Resolve repo root (for --local installs) ──
|
||||
_REPO_ROOT="$(cd "$(dirname "$0" 2>/dev/null || echo ".")" && pwd)"
|
||||
|
||||
# ── unsloth-zoo overlay ref (for --local installs) ──
|
||||
# --local installs overlay unsloth-zoo straight from git so the Studio venv
|
||||
# tracks the same zoo as the editable unsloth checkout. Honor UNSLOTH_ZOO_REF
|
||||
# (the Docker publish workflow resolves one ref and forwards it to BOTH the base
|
||||
# and Studio builds) so the published image runs the operator-requested zoo, not
|
||||
# whatever main happens to be at build time. Unset -> main, byte-identical to the
|
||||
# previous bare git URL (pip treats no @ref as the repo's default branch).
|
||||
_ZOO_REF="${UNSLOTH_ZOO_REF:-main}"
|
||||
_ZOO_GIT_SPEC="unsloth-zoo @ git+https://github.com/unslothai/unsloth-zoo@${_ZOO_REF}"
|
||||
|
||||
# ── Helper: find no-torch-runtime.txt (local repo or site-packages) ──
|
||||
_find_no_torch_runtime() {
|
||||
# Check local repo first (for --local installs)
|
||||
|
|
@ -2666,10 +2676,10 @@ if [ "$_MIGRATED" = true ]; then
|
|||
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
||||
substep "overlaying local repo (editable)..."
|
||||
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
|
||||
substep "overlaying unsloth-zoo from git main..."
|
||||
run_install_cmd_retry "overlay unsloth-zoo (git main)" uv pip install --python "$_VENV_PY" \
|
||||
substep "overlaying unsloth-zoo from git ${_ZOO_REF}..."
|
||||
run_install_cmd_retry "overlay unsloth-zoo (git ${_ZOO_REF})" uv pip install --python "$_VENV_PY" \
|
||||
--no-deps --reinstall-package unsloth-zoo \
|
||||
"unsloth-zoo @ git+https://github.com/unslothai/unsloth-zoo"
|
||||
"$_ZOO_GIT_SPEC"
|
||||
fi
|
||||
# AMD ROCm: install bitsandbytes even in migrated environments so
|
||||
# existing ROCm installs gain the AMD bitsandbytes build without a
|
||||
|
|
@ -2876,20 +2886,20 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
|
|||
if [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
||||
substep "overlaying local repo (editable)..."
|
||||
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
|
||||
substep "overlaying unsloth-zoo from git main..."
|
||||
run_install_cmd_retry "overlay unsloth-zoo (git main)" uv pip install --python "$_VENV_PY" \
|
||||
substep "overlaying unsloth-zoo from git ${_ZOO_REF}..."
|
||||
run_install_cmd_retry "overlay unsloth-zoo (git ${_ZOO_REF})" uv pip install --python "$_VENV_PY" \
|
||||
--no-deps --reinstall-package unsloth-zoo \
|
||||
"unsloth-zoo @ git+https://github.com/unslothai/unsloth-zoo"
|
||||
"$_ZOO_GIT_SPEC"
|
||||
fi
|
||||
elif [ "$STUDIO_LOCAL_INSTALL" = true ]; then
|
||||
run_install_cmd_retry "install unsloth (local)" uv pip install --python "$_VENV_PY" \
|
||||
--upgrade-package unsloth "unsloth>=2026.6.9" "unsloth-zoo>=2026.6.7"
|
||||
substep "overlaying local repo (editable)..."
|
||||
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
|
||||
substep "overlaying unsloth-zoo from git main..."
|
||||
run_install_cmd_retry "overlay unsloth-zoo (git main)" uv pip install --python "$_VENV_PY" \
|
||||
substep "overlaying unsloth-zoo from git ${_ZOO_REF}..."
|
||||
run_install_cmd_retry "overlay unsloth-zoo (git ${_ZOO_REF})" uv pip install --python "$_VENV_PY" \
|
||||
--no-deps --reinstall-package unsloth-zoo \
|
||||
"unsloth-zoo @ git+https://github.com/unslothai/unsloth-zoo"
|
||||
"$_ZOO_GIT_SPEC"
|
||||
else
|
||||
run_install_cmd_retry "install unsloth" uv pip install --python "$_VENV_PY" \
|
||||
--upgrade-package unsloth -- "$PACKAGE_NAME"
|
||||
|
|
@ -2918,10 +2928,10 @@ else
|
|||
run_install_cmd_retry "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" "unsloth-zoo>=2026.6.7" "unsloth>=2026.6.9" --torch-backend=auto
|
||||
substep "overlaying local repo (editable)..."
|
||||
run_install_cmd "overlay local repo" uv pip install --python "$_VENV_PY" -e "$_REPO_ROOT" --no-deps
|
||||
substep "overlaying unsloth-zoo from git main..."
|
||||
run_install_cmd_retry "overlay unsloth-zoo (git main)" uv pip install --python "$_VENV_PY" \
|
||||
substep "overlaying unsloth-zoo from git ${_ZOO_REF}..."
|
||||
run_install_cmd_retry "overlay unsloth-zoo (git ${_ZOO_REF})" uv pip install --python "$_VENV_PY" \
|
||||
--no-deps --reinstall-package unsloth-zoo \
|
||||
"unsloth-zoo @ git+https://github.com/unslothai/unsloth-zoo"
|
||||
"$_ZOO_GIT_SPEC"
|
||||
else
|
||||
run_install_cmd_retry "install unsloth (auto torch backend)" uv pip install --python "$_VENV_PY" --torch-backend=auto -- "$PACKAGE_NAME"
|
||||
fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue