docker: tighten comments
This commit is contained in:
parent
e089b04b0e
commit
4c8be5a1be
10 changed files with 71 additions and 118 deletions
20
.github/workflows/docker-publish.yml
vendored
20
.github/workflows/docker-publish.yml
vendored
|
|
@ -231,12 +231,10 @@ jobs:
|
|||
# arm64 image lacks /usr/share/dotnet, hence `|| true`.
|
||||
- name: Reclaim disk
|
||||
run: |
|
||||
# The hosted runners keep ~14-20 GB free, which is not enough for
|
||||
# the image plus buildkit state (empirically confirmed: the Studio
|
||||
# layer install died with ENOSPC on a staging run before this list
|
||||
# was extended). None of these preinstalled toolchains are used
|
||||
# here; some paths differ between the amd64 and arm64 runner
|
||||
# images, hence `|| true`.
|
||||
# Hosted runners keep only ~14-20 GB free -- not enough for the image
|
||||
# plus buildkit state (Studio install hit ENOSPC before this list grew).
|
||||
# None of these toolchains are used here; paths differ across the amd64
|
||||
# and arm64 runners, hence `|| true`.
|
||||
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
|
||||
/opt/hostedtoolcache "$AGENT_TOOLSDIRECTORY" \
|
||||
/usr/local/.ghcup /usr/share/swift \
|
||||
|
|
@ -426,12 +424,10 @@ jobs:
|
|||
|
||||
- name: Reclaim disk
|
||||
run: |
|
||||
# The hosted runners keep ~14-20 GB free, which is not enough for
|
||||
# the image plus buildkit state (empirically confirmed: the Studio
|
||||
# layer install died with ENOSPC on a staging run before this list
|
||||
# was extended). None of these preinstalled toolchains are used
|
||||
# here; some paths differ between the amd64 and arm64 runner
|
||||
# images, hence `|| true`.
|
||||
# Hosted runners keep only ~14-20 GB free -- not enough for the image
|
||||
# plus buildkit state (Studio install hit ENOSPC before this list grew).
|
||||
# None of these toolchains are used here; paths differ across the amd64
|
||||
# and arm64 runners, hence `|| true`.
|
||||
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
|
||||
/opt/hostedtoolcache "$AGENT_TOOLSDIRECTORY" \
|
||||
/usr/local/.ghcup /usr/share/swift \
|
||||
|
|
|
|||
|
|
@ -586,21 +586,15 @@ COPY --from=builder /opt/unsloth-venv /opt/unsloth-venv
|
|||
# triton-lang/triton#8335. Fix: install cu13 ptxas and point Triton at
|
||||
# it with TRITON_PTXAS_PATH.
|
||||
#
|
||||
# Both cu13 tools are activated ONLY for sm_103/sm_121, at runtime (see
|
||||
# select_cuda_jit_tools in entrypoint.sh), NOT baked as a global ENV/symlink
|
||||
# default: cu13 emits a cubin that a 570-579 driver cannot LOAD even when it
|
||||
# targets an older arch (CUDA 13 requires a >= 580 driver), so forcing every
|
||||
# host's JIT through cu13 would break the Ampere/Ada/Hopper/Turing GPUs this
|
||||
# image still supports on 570+ drivers. sm_103/sm_121 launched after cu12.8 and
|
||||
# only ship on >= 580 drivers, so gating cu13 to them is always safe.
|
||||
#
|
||||
# NVRTC and ptxas are CPU-side compilers; they do NOT call into libcuda, so
|
||||
# cu13 installs alongside the cu128 runtime with no driver-floor bump at INSTALL
|
||||
# time (570+). Their OUTPUT is a different story: a cu13 cubin needs a >= 580
|
||||
# driver to LOAD, so the tools are ACTIVATED per device at runtime (only for the
|
||||
# sm_103/sm_121 hosts, which ship >= 580 drivers) -- see select_cuda_jit_tools
|
||||
# in entrypoint.sh. Both arches carry the ~400 MB now: amd64 needs it for
|
||||
# sm_103, arm64 for sm_121.
|
||||
# Both cu13 tools are CPU-side compilers (no libcuda call), so they install
|
||||
# alongside the cu128 runtime with no driver-floor bump at INSTALL time (570+).
|
||||
# But their OUTPUT cubin needs a >= 580 driver to LOAD, so they are NOT baked as a
|
||||
# global ENV/symlink default -- forcing every host's JIT through cu13 would break
|
||||
# the Ampere/Ada/Hopper/Turing GPUs this image still supports on 570-579 drivers.
|
||||
# They are activated per device at runtime only for sm_103/sm_121 (which launched
|
||||
# after cu12.8 and only ship on >= 580 drivers, so gating cu13 to them is always
|
||||
# safe) -- see select_cuda_jit_tools in entrypoint.sh. Both arches carry the
|
||||
# ~400 MB: amd64 needs it for sm_103, arm64 for sm_121.
|
||||
RUN set -eux; \
|
||||
# The nvidia/cuda base already configures the CUDA apt repo (x86_64 or
|
||||
# sbsa) with its own Signed-By keyring at
|
||||
|
|
@ -632,15 +626,10 @@ RUN set -eux; \
|
|||
ln -s libnvrtc.so.12.cu128.orig "${NVRTC_DIR}/libnvrtc.so.12"; \
|
||||
ln -s /usr/local/cuda-13.0/lib64/libnvrtc.so.13 "${NVRTC_DIR}/libnvrtc.so.12.cu13"; \
|
||||
fi
|
||||
# (2) ptxas override. triton 3.6.0's own ptxas is cu12.8 (no sm_103/sm_121), so
|
||||
# those two arches need the cu13 ptxas installed above. It is NOT baked as a
|
||||
# global ENV: cu13 ptxas emits a cubin whose ABI a 570-579 driver cannot LOAD
|
||||
# (CUDA 13 needs a >= 580 driver), even when targeting an older arch like sm_80,
|
||||
# so pointing every host's Triton at it would break training on the Ampere/Ada/
|
||||
# Hopper/Turing GPUs this image still supports on 570+ drivers. TRITON_PTXAS_PATH
|
||||
# is therefore selected per device at boot (only sm_103/sm_121, which ship >= 580
|
||||
# drivers, get cu13; everything else keeps Triton's bundled cu12.8 ptxas) -- see
|
||||
# select_cuda_jit_tools in entrypoint.sh.
|
||||
# (2) ptxas override. triton 3.6.0's ptxas is cu12.8 (no sm_103/sm_121), so those
|
||||
# two arches need the cu13 ptxas installed above. Not baked as a global ENV for the
|
||||
# same driver-floor reason as NVRTC (a cu13 cubin needs a >= 580 driver to load);
|
||||
# TRITON_PTXAS_PATH is selected per device at boot -- see select_cuda_jit_tools.
|
||||
|
||||
# Register the venv's torch + NVIDIA lib dirs with the loader so torchcodec
|
||||
# (installed in the builder, see the bake comment there) can dlopen them.
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
#
|
||||
# This is the image published as docker.io/unsloth/unsloth:studio (and the
|
||||
# default :latest). It layers Unsloth Studio on top of the lean core image
|
||||
# (Dockerfile, published under the `core` tags) and runs the same service trio
|
||||
# as the previous production
|
||||
# image: Studio on 8000, JupyterLab on 8888, key-only sshd on 22.
|
||||
# (Dockerfile, published under the `core` tags) and runs the same service trio as
|
||||
# the previous production image: Studio on 8000, JupyterLab on 8888, sshd on 22.
|
||||
#
|
||||
# Build (local):
|
||||
# docker buildx build \
|
||||
|
|
@ -233,10 +232,8 @@ COPY --from=labext-builder /opt/labext-src/unsloth-jupyterlab/labextension /opt/
|
|||
# The sloth-sticker install is the ONLY fail-soft branding step: it is scoped to
|
||||
# its own { ...; } group with a `|| echo` fallback below, so a missing Studio
|
||||
# "Sloth emojis" folder does not break the build, while the REQUIRED steps above
|
||||
# it (JS resolve, favicon/logo/login copy) stay fatal. (The comment is kept out
|
||||
# of the RUN body so no comment line sits inside a backslash continuation, which
|
||||
# some Dockerfile parsers choke on.) login.html's onerror falls back to the
|
||||
# Unsloth logo if the sticker dir is ever absent.
|
||||
# it (JS resolve, favicon/logo/login copy) stay fatal. login.html's onerror falls
|
||||
# back to the Unsloth logo if the sticker dir is ever absent.
|
||||
COPY jupyter/favicon.ico /tmp/unsloth-branding/favicon.ico
|
||||
COPY jupyter/logo.png /tmp/unsloth-branding/logo.png
|
||||
COPY jupyter/login.html /tmp/unsloth-branding/login.html
|
||||
|
|
|
|||
|
|
@ -81,10 +81,9 @@ banner "Block 1: host pre-flight"
|
|||
command -v docker >/dev/null 2>&1 || fail "docker not found on PATH"
|
||||
echo " docker: $(docker --version)"
|
||||
|
||||
# Verify we can actually talk to the docker daemon as the current user.
|
||||
# This catches the "user not in docker group" case up front, instead of
|
||||
# letting docker buildx blow up with a "permission denied on /var/run/docker.sock"
|
||||
# error that looks like a build failure but is really a host permissions issue.
|
||||
# Verify we can talk to the docker daemon as the current user -- catches the
|
||||
# "user not in docker group" case up front, instead of a later buildx
|
||||
# "permission denied on /var/run/docker.sock" that masquerades as a build failure.
|
||||
DOCKER_INFO_OUT=$(docker info 2>&1)
|
||||
DOCKER_INFO_RC=$?
|
||||
if [[ $DOCKER_INFO_RC -ne 0 ]]; then
|
||||
|
|
|
|||
|
|
@ -34,11 +34,9 @@ from __future__ import annotations
|
|||
import sys
|
||||
|
||||
|
||||
# Cell magics whose body is executed as code (Python or shell), so a hoisted
|
||||
# `#@title`/`#@param`/comment line stays an inert comment. We ONLY hoist these.
|
||||
# Anything not listed (content/data magics like %%writefile, %%file, %%html,
|
||||
# %%javascript, %%latex, %%markdown, %%svg) is left untouched, because injecting
|
||||
# the Colab form comment into its body would corrupt the written file / output.
|
||||
# Cell magics whose body runs as code (Python or shell), so a hoisted comment
|
||||
# stays inert. We ONLY hoist these; content/data magics (%%writefile, %%html,
|
||||
# ...) are left untouched (see the module docstring).
|
||||
_SAFE_CELL_MAGICS = frozenset(
|
||||
{
|
||||
"capture", # the Colab install pattern: suppress pip/install output
|
||||
|
|
|
|||
|
|
@ -20,14 +20,12 @@
|
|||
# unsloth_nb_strip_colab.py <a.ipynb> [b.ipynb ...]
|
||||
# strip the listed notebooks in place (idempotent).
|
||||
# unsloth_nb_strip_colab.py --state <STATE> --dest <DEST>
|
||||
# STATE-aware sync migration. STATE is the "<sha256> <relpath>" file that
|
||||
# unsloth_sync_notebooks.sh records for every file it wrote. For each
|
||||
# .ipynb entry that still hashes to its recorded value (i.e. WE own it and
|
||||
# the user has not edited it), strip the intro and update the recorded hash
|
||||
# in place. User-edited notebooks (current hash != recorded) are left
|
||||
# untouched. This is the safe "rewrite, then record" step the sync runs
|
||||
# after every STATE write, so it covers first-boot populate, deleted-file
|
||||
# restore, GitHub refresh, and in-place image upgrades in one pass.
|
||||
# STATE-aware sync migration. For each .ipynb in the "<sha256> <relpath>"
|
||||
# STATE file (written by unsloth_sync_notebooks.sh) that still hashes to its
|
||||
# recorded value (WE own it, unedited), strip the intro and update the
|
||||
# recorded hash in place; user-edited notebooks (hash != recorded) are left
|
||||
# untouched. Runs after every STATE write, covering first-boot populate,
|
||||
# deleted-file restore, GitHub refresh and in-place image upgrades.
|
||||
#
|
||||
# Safe with refresh decisions: unsloth_nb_content_sig.py already classifies the
|
||||
# intro cell as boilerplate, so the body digest used to detect "only boilerplate
|
||||
|
|
@ -43,15 +41,12 @@ import sys
|
|||
# The stable identifier for the offending line (covers every GPU/Cloud variant).
|
||||
_INTRO_PREFIX = "to run this, press"
|
||||
|
||||
# ipywidgets MIME types. The baked notebooks ship example tqdm/progress-bar
|
||||
# widget outputs (model.safetensors download bars, dataset Map bars, ...) plus a
|
||||
# metadata.widgets state block. JupyterLab's ipywidgets manager cannot always
|
||||
# rebuild the Colab-saved state, so those outputs render as a stuck
|
||||
# "Loading widget..." placeholder. Dropping the widget outputs + orphan state
|
||||
# removes the placeholder; running the cell yourself still creates a fresh,
|
||||
# working widget. Outputs are not part of the refresh signature
|
||||
# (unsloth_nb_content_sig.middle_digest hashes only cell type+source), so this is
|
||||
# safe for edit/refresh detection.
|
||||
# The baked notebooks ship example tqdm/progress-bar widget outputs plus a
|
||||
# metadata.widgets state block; JupyterLab's ipywidgets manager cannot always
|
||||
# rebuild the Colab-saved state, so they render as a stuck "Loading widget..."
|
||||
# placeholder. Dropping the widget outputs + orphan state removes it; running the
|
||||
# cell still creates a fresh widget. Outputs are not part of the refresh signature
|
||||
# (content_sig hashes only cell type+source), so this is safe for edit detection.
|
||||
_WIDGET_VIEW_MIME = "application/vnd.jupyter.widget-view+json"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -48,12 +48,10 @@ _OTHER = "Other Notebooks"
|
|||
|
||||
def clean_section(title):
|
||||
"""README header text -> a filesystem-friendly folder label."""
|
||||
# Drop a trailing run of '#', surrounding whitespace and any emoji/symbols
|
||||
# that sometimes lead a header; keep ASCII text, digits and a few separators.
|
||||
# Drop trailing '#' and surrounding whitespace.
|
||||
title = title.strip().strip("#").strip()
|
||||
# Strip a leading run of emoji / symbols / punctuation that some domain
|
||||
# headers lead with (e.g. "🐧 AMD Notebooks", "📒 Kaggle Notebooks") so the
|
||||
# folder label is clean text.
|
||||
# Strip a leading run of emoji / symbols some domain headers lead with (e.g.
|
||||
# "🐧 AMD Notebooks", "📒 Kaggle Notebooks") so the folder label is clean text.
|
||||
title = re.sub(r"^[^\w]+", "", title)
|
||||
title = title.replace("-", " ").replace("/", " ")
|
||||
title = re.sub(r"\s+", " ", title).strip()
|
||||
|
|
|
|||
|
|
@ -95,14 +95,11 @@ _CONSTRAINT_FILE_FLAGS = {"-c", "--constraint", "--constraints"}
|
|||
# drop BOTH the flag and its value; dropping the value alone leaves pip a
|
||||
# dangling `-e` that swallows the next kept package and fails the whole cell.
|
||||
_EDITABLE_FLAGS = {"-e", "--editable"}
|
||||
# -P/--upgrade-package <name> is uv's selective-upgrade flag and
|
||||
# --reinstall-package <name> is uv's selective-reinstall flag: naming a baked
|
||||
# package (e.g. `uv pip install -P torch peft` or
|
||||
# `uv pip install --reinstall-package torch peft`) lets an ordinary install
|
||||
# target refresh/reinstall that package and clobber the pinned stack. Filter the
|
||||
# value through _KEEP too, dropping the flag+value pair for a protected name so
|
||||
# no dangling selector is left to swallow the next kept target. Unlike -e none of
|
||||
# these is itself an install target (no has_target).
|
||||
# -P/--upgrade-package and --reinstall-package are uv's selective upgrade/reinstall
|
||||
# flags: naming a baked package (`uv pip install -P torch peft`) lets an ordinary
|
||||
# target refresh/reinstall it and clobber the pinned stack. Filter the value through
|
||||
# _KEEP too, dropping the flag+value pair for a protected name so no dangling
|
||||
# selector swallows the next target. Unlike -e, none is itself an install target.
|
||||
_UPGRADE_PKG_FLAGS = {"-P", "--upgrade-package", "--reinstall-package"}
|
||||
# Short value-flags pip/uv accept in the ATTACHED form, i.e. the 2-char flag
|
||||
# glued to its value in one token: `-rreqs.txt`, `-cconstraints.txt`, `-epath`,
|
||||
|
|
@ -112,25 +109,17 @@ _UPGRADE_PKG_FLAGS = {"-P", "--upgrade-package", "--reinstall-package"}
|
|||
# attached `-c`/`-e`/`-P` value bypasses _KEEP.
|
||||
_ATTACHED_SHORT_FLAGS = {"-r", "-c", "-e", "-P"}
|
||||
# Resolver-wide reinstall / ignore-installed switches (pip --force-reinstall,
|
||||
# --ignore-installed, -I; uv --reinstall) force the tool to REINSTALL packages
|
||||
# that are already satisfied -- including the baked torch/transformers pulled in
|
||||
# as dependencies of a kept target. Drop them in shim mode so a
|
||||
# `pip install --force-reinstall peft` cannot rebuild the pinned stack under the
|
||||
# guise of installing an unprotected package. The kept target still installs; its
|
||||
# already-satisfied protected deps are left untouched. Per-package selectors
|
||||
# (--reinstall-package / -P) are handled through _UPGRADE_PKG_FLAGS instead.
|
||||
# uv's --exact is destructive the other way around: it performs an exact SYNC,
|
||||
# REMOVING every installed package outside the kept target's closure (vLLM,
|
||||
# bitsandbytes, the NVIDIA libs, ...), so `uv pip install --exact peft` would
|
||||
# strip the baked stack after the argument filter kept it off the command line.
|
||||
# --ignore-installed, -I; uv --reinstall) REINSTALL already-satisfied packages,
|
||||
# including the baked torch/transformers a kept target pulls in as deps. Drop them
|
||||
# so an unprotected install cannot rebuild the pinned stack; the kept target still
|
||||
# installs. Per-package selectors (-P / --reinstall-package) go via _UPGRADE_PKG_FLAGS.
|
||||
# uv's --exact is destructive the other way: an exact SYNC that REMOVES everything
|
||||
# outside the kept target's closure (vLLM, bitsandbytes, NVIDIA libs), so drop it too.
|
||||
_REINSTALL_FLAGS = {"--force-reinstall", "--ignore-installed", "-I", "--reinstall", "--exact"}
|
||||
# Value-flags whose flag+value pair is dropped outright in shim mode.
|
||||
# `--upgrade-strategy eager` makes pip upgrade EVERY dependency of a kept target
|
||||
# regardless of whether the installed version already satisfies it, which would
|
||||
# refresh the baked torch/transformers under the pinned CUDA stack. Dropping the
|
||||
# flag falls back to pip's default `only-if-needed`, so a kept target still
|
||||
# installs but already-satisfied protected deps stay put. (`only-if-needed` is
|
||||
# the default, so dropping a `--upgrade-strategy only-if-needed` is a no-op.)
|
||||
# `--upgrade-strategy eager` makes pip upgrade EVERY dependency of a kept target,
|
||||
# refreshing the baked torch/transformers. Dropping it falls back to pip's default
|
||||
# `only-if-needed`, so the target still installs but satisfied protected deps stay.
|
||||
_DROP_VALUE_FLAGS = {"--upgrade-strategy"}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -157,12 +157,11 @@ if [ ! -f "$STATE" ]; then
|
|||
rel="${rel#./}"
|
||||
case "$rel" in .unsloth_template_commit) continue ;; esac
|
||||
mkdir -p "$DEST/$(dirname "$rel")" 2>/dev/null || true
|
||||
# A pre-existing file at this path (bind-mounted or hand-created before
|
||||
# the first boot) is user data: never clobber it, and -- crucially -- do
|
||||
# NOT record it in the sync state. If it were recorded, the GitHub refresh
|
||||
# below would see its hash match the recorded hash, treat it as pristine
|
||||
# and overwrite it with upstream. Only files we actually lay down (or that
|
||||
# are already byte-identical to the template) are recorded as managed.
|
||||
# A pre-existing file here (bind-mounted or hand-created before first boot)
|
||||
# is user data: keep it, and do NOT record it in the sync state -- if
|
||||
# recorded, the GitHub refresh below would see the hash match, treat it as
|
||||
# pristine and overwrite it. Only files we lay down (or that already match
|
||||
# the template byte-for-byte) are recorded as managed.
|
||||
if [ -e "$DEST/$rel" ] \
|
||||
&& [ "$(hash_of "$DEST/$rel")" != "$(hash_of "$TEMPLATE/$rel")" ]; then
|
||||
echo "[unsloth-nb] kept existing user file: $DEST/$rel"
|
||||
|
|
|
|||
|
|
@ -3,19 +3,12 @@
|
|||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
# Unit tests for select_cuda_jit_tools() from docker/entrypoint.sh.
|
||||
#
|
||||
# The image bakes CUDA 13 ptxas + NVRTC, but a cu13 cubin cannot LOAD on a
|
||||
# 570-579 driver even when it targets an old arch like sm_80 (CUDA has forward,
|
||||
# not backward, driver compatibility across major versions). So cu12.8 is the
|
||||
# IMMUTABLE baked default (libnvrtc.so.12 -> .cu128.orig), and the cu13 tools are
|
||||
# switched on ONLY for the two Blackwell datacenter arches that require them --
|
||||
# sm_103 (B300 / GB300) and sm_121 (GB10 / DGX Spark), which only ship on >= 580
|
||||
# drivers. Every other supported arch (Turing..sm_120) keeps the cu12.8 default,
|
||||
# untouched, so a 570+ driver host -- including a non-root --user container that
|
||||
# cannot rewrite the symlink -- is never broken.
|
||||
#
|
||||
# The function picks per device via nvidia-smi compute_cap: DC -> retarget
|
||||
# libnvrtc.so.12 -> the staged .cu13 alias (and point Triton at cu13 ptxas);
|
||||
# anything else -> leave the cu12.8 default in place and ptxas unset.
|
||||
# cu12.8 is the immutable baked default (libnvrtc.so.12 -> .cu128.orig); the cu13
|
||||
# tools are switched on ONLY for sm_103 (B300 / GB300) and sm_121 (GB10 / DGX
|
||||
# Spark), which ship on >= 580 drivers -- see the rationale in docker/entrypoint.sh.
|
||||
# The function picks per device via nvidia-smi compute_cap: those two arches
|
||||
# retarget libnvrtc.so.12 -> the staged .cu13 alias (and point Triton at cu13
|
||||
# ptxas); every other arch keeps the cu12.8 default and leaves ptxas unset.
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue