docker: trim redundant comments in the image build files
Comment-only pass over the PR's own files. No executable line changes. - Dockerfile / Dockerfile.studio: drop the decorative stage banner rules, the stale "5)" / "6)" step numbering, and the entrypoint pre-flight list that restated (and had drifted from) entrypoint.sh's own accurate header. Cut the llama.cpp asset bullet list that repeats fetch_llama_prebuilt.py's docstring and the structlog rationale already spelled out at the install site. - entrypoint.sh / studio_launch.sh: fold the section banners into the explanation lines that follow them. - docker-publish.yml: remove the comment rule lines around the job headers. - validate_studio_features.py: same for the numbered section headers. - smoke_test.py: drop the stale "~125M params" note on a 1B model. - unsloth_branding.py, unsloth_nb_view.py, unsloth_nb_pip_magic.py, colabTitle.ts: remove comments that restate the adjacent line.
This commit is contained in:
parent
b47c55be75
commit
9ca7be82c4
11 changed files with 25 additions and 76 deletions
10
.github/workflows/docker-publish.yml
vendored
10
.github/workflows/docker-publish.yml
vendored
|
|
@ -59,12 +59,10 @@ permissions:
|
|||
contents: read
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resolve every upstream ref ONCE (llama tag + unsloth/zoo shas + notebooks
|
||||
# commit) so both arch legs and Studio bake identical bits. A dispatch input
|
||||
# pins a frozen value; else a branch/tag is frozen to a sha via ls-remote, and
|
||||
# llama "latest" follows the /releases/latest redirect (mirrors build.sh).
|
||||
# ---------------------------------------------------------------------------
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
|
@ -154,11 +152,9 @@ jobs:
|
|||
echo "commit=${SHA}" >> "$GITHUB_OUTPUT"
|
||||
echo "notebooks commit: ${SHA}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-arch build: two parallel jobs on native runners, each pushing a single-arch
|
||||
# image by digest (no tag); the merge job stitches them into one manifest. Avoids
|
||||
# the "last push wins" race of two jobs pushing the same tag.
|
||||
# ---------------------------------------------------------------------------
|
||||
build:
|
||||
needs: prepare
|
||||
strategy:
|
||||
|
|
@ -248,10 +244,8 @@ jobs:
|
|||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Merge the two per-arch digests into a multi-platform manifest under the real
|
||||
# user-facing tag(s). Runs only after both build legs succeed.
|
||||
# ---------------------------------------------------------------------------
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
|
@ -326,12 +320,10 @@ jobs:
|
|||
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
|
||||
echo "base manifest: ${TAG} @ ${DIGEST}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Full image: base + Unsloth Studio + JupyterLab + sshd (Dockerfile.studio).
|
||||
# This is :latest. Same by-digest build + merge pattern as the base, FROMing the
|
||||
# base manifest digest from the merge job. The arm64 leg builds Studio's vite
|
||||
# frontend natively (the long pole), hence the larger timeout.
|
||||
# ---------------------------------------------------------------------------
|
||||
build-studio:
|
||||
# `merge` for the freshly-published base manifest digest; `prepare` for the
|
||||
# one resolved zoo ref (job outputs only flow through direct `needs`).
|
||||
|
|
@ -470,10 +462,8 @@ jobs:
|
|||
docker buildx imagetools inspect "$tag"
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional: pull the freshly published image onto a self-hosted GPU runner and
|
||||
# run smoke_test.py. Skipped when no GPU runner is registered.
|
||||
# ---------------------------------------------------------------------------
|
||||
smoke-test:
|
||||
needs: [merge, merge-studio]
|
||||
if: ${{ vars.HAS_GPU_RUNNER == 'true' }}
|
||||
|
|
|
|||
|
|
@ -33,9 +33,7 @@ ARG CUDA_VERSION=12.8.1
|
|||
ARG UBUNTU_VERSION=24.04
|
||||
ARG PYTHON_VERSION=3.12
|
||||
|
||||
# =============================================================================
|
||||
# Stage 1: builder -- toolkit + dev headers, builds any source extensions
|
||||
# =============================================================================
|
||||
# Stage 1: builder -- toolkit + dev headers, builds any source extensions.
|
||||
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-devel-ubuntu${UBUNTU_VERSION} AS builder
|
||||
|
||||
# TARGETARCH (buildx: amd64/arm64) selects the unsloth extras matching the
|
||||
|
|
@ -277,12 +275,12 @@ RUN set -eux \
|
|||
done \
|
||||
&& { du -sh ${VENV}/tf-sidecars || true; }
|
||||
|
||||
# 5) Informational pin record (NOT byte-reproducible: pip freeze omits wheel
|
||||
# hashes and unsloth/vllm --pre float from VCS/nightly).
|
||||
# Informational pin record (NOT byte-reproducible: pip freeze omits wheel hashes
|
||||
# and unsloth/vllm --pre float from VCS/nightly).
|
||||
RUN ${VENV}/bin/pip freeze --exclude-editable > ${VENV}/requirements.lock.txt \
|
||||
&& head -50 ${VENV}/requirements.lock.txt
|
||||
|
||||
# 6) Strip pip cache & __pycache__ to shrink the runtime layer. The `-name tests`
|
||||
# Strip pip cache & __pycache__ to shrink the runtime layer. The `-name tests`
|
||||
# strip excludes numpy's tests dirs (numpy 2.4 needs numpy/_core/tests/ or
|
||||
# `import numpy` breaks). Other verified-safe cuts:
|
||||
# * npp: torchcodec dlopens only libnppicc + libnppc; drop the rest (~388MB).
|
||||
|
|
@ -357,19 +355,14 @@ for pkg in LIGHT_IMPORTS:
|
|||
importlib.import_module(pkg)
|
||||
print(f"OK: {' + '.join(LIGHT_IMPORTS)} import cleanly on no-GPU host")
|
||||
|
||||
# `unsloth train` / `export` / `chat` / `list-checkpoints` all import
|
||||
# studio.backend.core.*, whose dependency closure (structlog, and starlette by
|
||||
# way of the logging handlers) is NOT part of unsloth[huggingface]. Missing any
|
||||
# of it turns every one of those commands into a ModuleNotFoundError traceback,
|
||||
# which no functional test here would otherwise catch. Runs last in the builder,
|
||||
# after vLLM, because that is what pulls starlette in.
|
||||
# Guard for the studio.backend.core.* closure the unsloth CLI needs (structlog,
|
||||
# plus starlette via the logging handlers). Runs last in the builder, after vLLM,
|
||||
# because that is what pulls starlette in.
|
||||
from studio.backend.core.export import ExportBackend # noqa: F401
|
||||
print("OK: the unsloth CLI can reach the studio export backend")
|
||||
PY
|
||||
|
||||
# =============================================================================
|
||||
# Stage 2: runtime -- slim runtime image, no nvcc, no cuDNN/cuBLAS layers
|
||||
# =============================================================================
|
||||
# Stage 2: runtime -- slim, no nvcc, no cuDNN/cuBLAS layers.
|
||||
# The "-base-" variant drops ~2.7 GB of system CUDA libs we never load: torch
|
||||
# wheels bake their own cuDNN/cuBLAS into torch/lib/ and resolve via RPATH. The
|
||||
# base still provides nvidia-smi + libcuda stubs + libnvidia-ml.
|
||||
|
|
@ -472,13 +465,9 @@ RUN set -eux \
|
|||
# export hits install_llama_cpp()'s prompt + slow source build.
|
||||
#
|
||||
# NOT studio/install_llama_prebuilt.py: it selects a bundle for the CURRENT host,
|
||||
# but the build must never introspect the host, so pin release + asset by build
|
||||
# target (see fetch_llama_prebuilt.py):
|
||||
# * amd64 -> app-<tag>-linux-x64-cuda12-portable.tar.gz (sm_70..sm_120)
|
||||
# arm64 -> app-<tag>-linux-arm64-cuda13-portable.tar.gz (sm_90..sm_121)
|
||||
# * portable bundles carry their own ggml backends, so they also run CPU-only
|
||||
# * sha256-verified against the release's llama-prebuilt-sha256.json
|
||||
# * converter + gguf-py from the SAME release's source tarball (mappings match)
|
||||
# but the build must never introspect the host, so release + asset are pinned by
|
||||
# build target instead (see fetch_llama_prebuilt.py).
|
||||
#
|
||||
# /opt (not /root) so it survives `docker run --user`. Default "latest" resolves
|
||||
# the newest release; build.sh pins a concrete tag so the cache busts only on new
|
||||
# releases. --build-arg LLAMA_PREBUILT_TAG=<tag> for a frozen build.
|
||||
|
|
@ -530,7 +519,6 @@ WORKDIR /workspace
|
|||
RUN mkdir -p ${HF_HOME} ${TRITON_CACHE_DIR} \
|
||||
&& chmod -R a+rwX /workspace
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-notebook transformers version activation -- run unslothai/notebooks
|
||||
# UNCHANGED (see unsloth_nb_compat.py). Pieces:
|
||||
# * unsloth_nb_compat.py: tier detection + sidecar resolution + IPython hook.
|
||||
|
|
@ -540,7 +528,6 @@ RUN mkdir -p ${HF_HOME} ${TRITON_CACHE_DIR} \
|
|||
# same shim so in-process installs can't bypass PATH.
|
||||
# * IPython startup hook: activates the right sidecar before the first model cell.
|
||||
# * unsloth-run: headless `unsloth-run <notebook|url>`, the robust driven path.
|
||||
# ---------------------------------------------------------------------------
|
||||
COPY unsloth_nb_compat.py unsloth_pip_shim.py unsloth_nb_pip_magic.py unsloth_ipython_startup.py unsloth_run.py unsloth_sync_notebooks.sh unsloth_nb_content_sig.py unsloth_nb_view.py unsloth_nb_strip_colab.py unsloth_colab_compat.py /opt/unsloth-nb/
|
||||
RUN set -eux \
|
||||
&& SP=/opt/unsloth-venv/lib/python${PYTHON_VERSION}/site-packages \
|
||||
|
|
@ -584,23 +571,17 @@ RUN set -eux \
|
|||
&& rm -rf /opt/unsloth-notebooks/.git \
|
||||
&& du -sh /opt/unsloth-notebooks
|
||||
|
||||
# JupyterLab lives in the venv. notebooks are pre-populated into
|
||||
# /workspace/unsloth-notebooks on boot; mount a volume on /workspace to persist.
|
||||
# Mount a volume on /workspace to persist the notebooks and caches.
|
||||
EXPOSE 8888
|
||||
|
||||
COPY smoke_test.py /workspace/smoke_test.py
|
||||
COPY entrypoint.sh /usr/local/bin/unsloth-entrypoint
|
||||
RUN chmod +x /usr/local/bin/unsloth-entrypoint
|
||||
|
||||
# Entrypoint runs three fast pre-flight checks before user code:
|
||||
# 1. nvidia-smi sees at least one GPU (catches missing --gpus all)
|
||||
# 2. torch.cuda.is_available() is True (catches host driver too old)
|
||||
# 3. compute capability >= sm_80 (catches pre-Ampere GPUs)
|
||||
# Each check fails with an actionable error pointing to the fix.
|
||||
# Bypass for offline tooling: docker run -e UNSLOTH_SKIP_GPU_CHECK=1 ...
|
||||
# Fast GPU pre-flight checks before user code, each with an actionable error (see
|
||||
# entrypoint.sh). Bypass for offline tooling: docker run -e UNSLOTH_SKIP_GPU_CHECK=1
|
||||
ENTRYPOINT ["/usr/local/bin/unsloth-entrypoint"]
|
||||
|
||||
# Default command: interactive python REPL.
|
||||
# Override examples:
|
||||
# docker run --gpus all unsloth/unsloth:latest python /workspace/smoke_test.py
|
||||
# docker run --gpus all -it unsloth/unsloth:latest bash
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
ARG BASE_IMAGE=unsloth-blackwell:test
|
||||
|
||||
# --- builder stage: prebuild the Unsloth JupyterLab extension -----------------
|
||||
# Builds the "Unsloth Dark" (Monokai) theme + Colab-style cell-nav keymap. Node
|
||||
# lives only in this throwaway stage; the final image copies just the prebuilt
|
||||
# labextension (runtime stays Node-free). Uses the base's bundled jlpm+jupyterlab.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
# Bypass for offline tooling/docs/CI: docker run -e UNSLOTH_SKIP_GPU_CHECK=1 ...
|
||||
set -euo pipefail
|
||||
|
||||
# --- CUDA JIT toolchain selection (device-gated) ----------------------------
|
||||
# The image bakes CUDA 13 ptxas + NVRTC only for sm_103 (B300/GB300) and sm_121
|
||||
# (GB10/DGX Spark), which cu12.8 can't target. Both ship on >=580 drivers, which a
|
||||
# cu13 cubin needs. Every other arch uses cu12.8 on the 570-579 floor, where a
|
||||
|
|
@ -87,9 +86,8 @@ if [[ "${UNSLOTH_ALLOW_CPU:-0}" == "1" ]]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
# --- Check 1: nvidia-smi present and enumerates at least one GPU ------------
|
||||
# nvidia-smi is injected by nvidia-container-toolkit on a GPU request, not baked
|
||||
# in; a missing binary means "no GPU attached", same class as an empty -L.
|
||||
# Check 1: nvidia-smi is injected by nvidia-container-toolkit on a GPU request,
|
||||
# not baked in; a missing binary means "no GPU attached", same as an empty -L.
|
||||
if ! command -v nvidia-smi >/dev/null 2>&1 || ! nvidia-smi -L 2>/dev/null | grep -q '^GPU'; then
|
||||
err "No GPU visible inside the container."
|
||||
cat >&2 <<'MSG'
|
||||
|
|
@ -126,8 +124,8 @@ MSG
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# --- Check 2: torch can actually use the GPU --------------------------------
|
||||
# Catches host-driver-too-old (nvidia-smi enumerates but CUDA contexts fail).
|
||||
# Check 2: torch can use the GPU. Catches host-driver-too-old (nvidia-smi
|
||||
# enumerates but CUDA contexts fail).
|
||||
python - >&2 <<'PY' || exit 1
|
||||
import sys
|
||||
import torch
|
||||
|
|
@ -148,7 +146,7 @@ print("Then upgrade the driver to match.")
|
|||
sys.exit(1)
|
||||
PY
|
||||
|
||||
# --- Check 3: compute capability is supported -------------------------------
|
||||
# Check 3: compute capability is supported.
|
||||
python - >&2 <<'PY' || exit 1
|
||||
import sys
|
||||
import torch
|
||||
|
|
@ -193,7 +191,6 @@ for d in range(1, n):
|
|||
print(" exclude it with CUDA_VISIBLE_DEVICES or --gpus device=<supported>.")
|
||||
PY
|
||||
|
||||
# --- arm64 note: baked llama.cpp is a CUDA 13 build -------------------------
|
||||
# Upstream ships no CUDA 12 arm64 llama.cpp, so the arm64 image bakes cu13 while
|
||||
# torch (cu128) runs on 570+. A cu13 cubin can't load on 570-579, so below 580
|
||||
# GGUF export / Studio chat fail even though training works -- warn up front.
|
||||
|
|
|
|||
|
|
@ -28,10 +28,8 @@ import json
|
|||
import os
|
||||
import sys
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Canonical attribution strings. Plain text; keep in sync with the TS mirror
|
||||
# unsloth_labext/src/branding.ts (the guard greps the built bundle for these).
|
||||
# ---------------------------------------------------------------------------
|
||||
PRODUCT = "Unsloth Docker Studio"
|
||||
SHORT_LABEL = "Built by the Unsloth team"
|
||||
# Loading-splash caption; distinct from SHORT_LABEL (see branding.ts).
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ function applyTitle(cell: Cell): void {
|
|||
barEl.className = 'unsloth-title-bar unsloth-collapsed';
|
||||
const caret = document.createElement('span');
|
||||
caret.className = 'unsloth-title-caret';
|
||||
caret.textContent = '▾'; // down-pointing triangle
|
||||
caret.textContent = '▾';
|
||||
const text = document.createElement('span');
|
||||
text.className = 'unsloth-title-text';
|
||||
barEl.appendChild(caret);
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ def check_tiny_train(cap: tuple[int, int]) -> None:
|
|||
from unsloth import FastLanguageModel
|
||||
import torch
|
||||
|
||||
# Small, public, no-gate. ~125M params.
|
||||
# Small, public, no-gate.
|
||||
model_name = "unsloth/Llama-3.2-1B-Instruct-bnb-4bit"
|
||||
print(f"loading {model_name}")
|
||||
model, tokenizer = FastLanguageModel.from_pretrained(
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ for key, value in sorted(os.environ.items()):
|
|||
print(f"export {key}={shlex.quote(value)}")
|
||||
PY
|
||||
|
||||
# --- Jupyter -----------------------------------------------------------------
|
||||
# Hash the password with jupyter's helper; never store plaintext. No fixed
|
||||
# Hash the Jupyter password with jupyter's helper; never store plaintext. No fixed
|
||||
# default: when JUPYTER_PASSWORD is unset, generate a random one and print it once.
|
||||
JUPYTER_CONFIG_DIR=/root/.jupyter
|
||||
JUPYTER_NOTE="password from JUPYTER_PASSWORD env"
|
||||
|
|
@ -79,8 +78,7 @@ EOF
|
|||
fi
|
||||
fi
|
||||
|
||||
# --- sshd (opt-in) -----------------------------------------------------------
|
||||
# Enabled only when a public key is provided; root password login is never
|
||||
# sshd is enabled only when a public key is provided; root password login is never
|
||||
# allowed. Cloud GPU platforms (e.g. runpod-style hosts) inject PUBLIC_KEY.
|
||||
PUBLIC_SSH_KEY="${SSH_KEY:-${PUBLIC_KEY:-}}"
|
||||
export UNSLOTH_ENABLE_SSHD=false
|
||||
|
|
@ -95,7 +93,6 @@ fi
|
|||
|
||||
mkdir -p /workspace
|
||||
|
||||
# --- Branding / AGPLv3 attribution integrity gate (whole container) -----------
|
||||
# This image ships under the GNU AGPLv3. Refuse to start if the Unsloth
|
||||
# attribution (Help/About, splash, login, theme, AGPLv3 license + source links)
|
||||
# is stripped or altered. The same checker runs as a jupyter_server extension and
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ def register_ipython():
|
|||
|
||||
return _magic
|
||||
|
||||
# Override the built-in %pip / %uv so they route through the shim too.
|
||||
ip.register_magic_function(_make("pip"), "line", "pip")
|
||||
ip.register_magic_function(_make("pip"), "line", "pip3")
|
||||
ip.register_magic_function(_make("uv"), "line", "uv")
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ def parse_readme(readme_path):
|
|||
text = f.read()
|
||||
|
||||
rows = []
|
||||
seen_pairs = set() # (section, filename) already emitted
|
||||
seen_pairs = set()
|
||||
section = None
|
||||
# Reset on ANY markdown heading, not just `###`: `#`/`##` domain headers carry
|
||||
# their own nb/*.ipynb tables, so matching only `###` mis-filed those links.
|
||||
|
|
@ -190,7 +190,7 @@ def _clear_view(path, dest_real):
|
|||
for root, dirs, files in os.walk(path, topdown = False):
|
||||
for name in files:
|
||||
p = os.path.join(root, name)
|
||||
if os.path.islink(p) and _points_into(p, dest_real): # our notebook symlinks only
|
||||
if os.path.islink(p) and _points_into(p, dest_real):
|
||||
try:
|
||||
os.remove(p)
|
||||
except OSError:
|
||||
|
|
|
|||
|
|
@ -42,9 +42,7 @@ def check(
|
|||
_failures.append(name)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 1. Colab cell-magic compatibility (#@title then %%capture)
|
||||
# --------------------------------------------------------------------------
|
||||
def test_colab_compat() -> None:
|
||||
print("colab cell-magic compat (unsloth_colab_compat):")
|
||||
m = importlib.import_module("unsloth_colab_compat")
|
||||
|
|
@ -64,9 +62,7 @@ def test_colab_compat() -> None:
|
|||
check("safe magic (%%bash) hoisted", m.colab_cell_magic_fix(bash)[0] == "%%bash\n")
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 2. Notebook categorisation (clean_section) + README parsing
|
||||
# --------------------------------------------------------------------------
|
||||
def test_nb_view() -> None:
|
||||
print("notebook view (unsloth_nb_view):")
|
||||
v = importlib.import_module("unsloth_nb_view")
|
||||
|
|
@ -82,9 +78,7 @@ def test_nb_view() -> None:
|
|||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 3. Colab-intro + stale-widget stripping
|
||||
# --------------------------------------------------------------------------
|
||||
def test_strip() -> None:
|
||||
print("notebook strip (unsloth_nb_strip_colab):")
|
||||
s = importlib.import_module("unsloth_nb_strip_colab")
|
||||
|
|
@ -142,9 +136,7 @@ def test_strip() -> None:
|
|||
check("strip idempotent", not s._strip_intro(nb) and not s._clean_widgets(nb))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 4. Sidecar-log gating
|
||||
# --------------------------------------------------------------------------
|
||||
def test_sidecar_log_gate() -> None:
|
||||
print("sidecar log gate (unsloth_nb_compat):")
|
||||
c = importlib.import_module("unsloth_nb_compat")
|
||||
|
|
@ -161,9 +153,7 @@ def test_sidecar_log_gate() -> None:
|
|||
os.environ["UNSLOTH_ENABLE_LOGGING"] = old
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 5. JupyterLab defaults (overrides.json)
|
||||
# --------------------------------------------------------------------------
|
||||
def test_overrides() -> None:
|
||||
print("jupyterlab defaults (jupyter/overrides.json):")
|
||||
path = os.path.join(JUPYTER, "overrides.json")
|
||||
|
|
@ -200,9 +190,7 @@ def test_overrides() -> None:
|
|||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 6. Labextension source (plugins) + login branding assets
|
||||
# --------------------------------------------------------------------------
|
||||
def test_labext_and_branding() -> None:
|
||||
print("labextension + branding assets:")
|
||||
pkg = os.path.join(LABEXT, "package.json")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue