docker: tighten comments

This commit is contained in:
Daniel Han 2026-07-14 14:08:32 +00:00
commit 4c8be5a1be
10 changed files with 71 additions and 118 deletions

View file

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

View file

@ -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.

View file

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

View file

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

View file

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

View file

@ -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"

View file

@ -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()

View file

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

View file

@ -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"

View file

@ -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)"