Commit graph

17 commits

Author SHA1 Message Date
Daniel Han
9ca7be82c4 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.
2026-07-26 15:45:58 +00:00
Daniel Han
b67a3b039f docker: tighten comments 2026-07-19 15:32:20 +00:00
Daniel Han
a26ead4957 docker: tighten comments across the Blackwell image and helpers
Condense the verbose explanatory comments added by this branch to their essential
points without dropping any load-bearing rationale. Touches comments and
docstrings only, no code changes. Leaves the stable-tag gate rationale, the
byte-identical enable= sync notes, and the update-alternatives pin comment as is.
2026-07-18 11:49:15 +00:00
Daniel Han
8c901e7216 docker: preflight every GPU, classify all uv/pip value flags, volume-safe llama update
Preflight (entrypoint.sh) now scans every visible device: an unsupported device
0 stays fatal as before, an unsupported secondary GPU (mixed rig) warns at
startup with its index and the CUDA_VISIBLE_DEVICES remedy, instead of
surfacing only when a job pins to it or a multi-GPU launch fans out.

The pip shim's _VALUE_FLAGS now covers every value-taking flag of uv pip
install and pip install (generated from both tools' --help). The separated
form `uv pip install --torch-backend cu128 torch` used to drop the protected
torch but exec uv with no install target at all (uv hard-errors) instead of
no-oping like the attached `=` form, and `--extra torch peft` misread the
extra name as a protected target, leaving a dangling --extra that swallowed
peft. Adds parametrized regressions plus help-derived drift guards so a future
uv/pip value flag cannot silently reintroduce the misparse.

unsloth-llama-update now detects when the install dir is itself a mount point
(the documented -v unsloth_llama:/opt/unsloth/llama.cpp persistence recipe,
where rename(2) fails EBUSY) and swaps the bundle CONTENTS inside the mounted
tree, so the update lands in the volume and stays persistent. Work and backup
dirs live under the mount (same-fs renames), the abort trap restores the old
contents even mid-swap, and the non-mounted path keeps the whole-dir rename.
Verified: in-place swap preserves the dir inode and ownership marker, failed
fetch leaves the install untouched, simulated mid-swap abort restores fully.
2026-07-16 05:51:15 +00:00
Daniel Han
1254fdf3ad docker: close pip-shim bypasses and warn on arm64 cu13 llama.cpp mismatch
Four follow-ups to the shim/entrypoint audit fixes:

1. unsloth_pip_shim.py let a local project directory install through: `pip
   install ./transformers` / `-e ./unsloth` is not a requirement spec, so
   _canon returned None and both the arg filter and the constraints file
   (which only rejects a version MISMATCH) passed it, letting a same-version
   local build silently replace the baked wheel. _canon now resolves the
   project name from pyproject [project].name, then setup.cfg, then the
   directory basename when it is an installable project, so a local checkout
   of a protected package is dropped like every other artifact form. Names
   match exactly after normalization, so a user dir named my-torch-utils is
   untouched, and a metadata-less directory still passes through.

2. unsloth_nb_pip_magic.py only rewrote literal `!python -m pip`, so the
   `!{sys.executable} -m pip ...` form notebooks use to target the running
   kernel (and absolute interpreter paths) bypassed the PATH shim entirely.
   Input transformers see the raw cell text before IPython expands the
   braces, so the matcher now also covers {sys.executable} (quoted or bare)
   and quoted/bare interpreter paths ending in python[0-9.]*(.exe) before
   -m pip|uv.

3. unsloth_pip_shim.py did not strip uv's --exact, which performs an exact
   sync that removes every installed package outside the kept target's
   closure (vLLM, bitsandbytes, the NVIDIA libs); `uv pip install --exact
   peft` would strip the baked stack after the filter kept it. --exact now
   joins the resolver-wide destructive flags dropped in shim mode.

4. entrypoint.sh: the arm64 image bakes a CUDA 13 llama.cpp because upstream
   (unslothai/llama.cpp) publishes no CUDA 12 arm64 asset, while the torch
   stack (cu128) runs on a 570-series driver. A CUDA 13 cubin cannot load on
   a 570-579 driver, so on GH200/GB200 hosts below 580 GGUF export and Studio
   chat fail while training works. The entrypoint now warns up front on
   aarch64 + driver < 580 instead of letting llama-server fail later.

Tests: shim + nb-pip-magic suites at 81 (18 new, including local-project
name resolution, the executable/brace forms, and --exact stripping).
2026-07-13 03:42:38 +00:00
Daniel Han
47d66ecb53 docker: harden rollback, publish, shim, and view-cleanup paths
Ten verified fixes from a 12-reviewer audit of the image tooling, each
reproduced before fixing:

1. install_llama_prebuilt.py move_install_dir_aside: the EXDEV fallback
   copied straight into the rollback path, so a copy that died halfway
   (ENOSPC, I/O error) left a partial tree that activation recovery would
   later restore over the intact install while deleting the good copy.
   Copy to a temp sibling and publish with one atomic rename; dst.exists()
   is now a truthful complete-tree signal.

2. unsloth_run.py --out truncated the existing output before nbconvert
   ran, so a timeout, missing kernel, or failed cell irreversibly
   destroyed the previous result. The input copy and executed result are
   staged as temp files next to the destination and published with
   os.replace only on exit code 0.

3. unsloth_nb_view.py cleanup treated every symlink in the view as its
   own: user-created links (and an operator's view-root routing symlink)
   were deleted on every rebuild. Cleanup now removes only links that
   resolve into the notebooks tree it links from, and builds inside a
   view-root symlink's target instead of unlinking it.

4. unsloth_llama_update.sh: the unconditional EXIT trap deleted the .old
   backup even when it was the only remaining copy (signal between the two
   renames, or a failed swap whose restore also failed). The handler now
   restores the backup first when the install dir is missing and removes
   it only after the new tree is verifiably active; HUP/INT/TERM route
   through the same handler.

5. unsloth_pip_shim.py: transitive dependencies could replace the baked
   torch stack (reproduced with a wheel requiring torch==99.0). Every
   forwarded install now carries a constraints file pinning the installed
   protected set, turning the swap into ResolutionImpossible.

6. unsloth_pip_shim.py: ${UPPER} env references in requirements files were
   classified before pip expanded them, bypassing the protected-package
   filter; the shim now expands with pip's exact regex first.

7. unsloth_pip_shim.py: a failure writing the filtered requirements copy
   returned the ORIGINAL file, forwarding exactly the protected pins it
   had detected; it now fails closed.

8. docker-publish.yml: workflow_dispatch defaulted unsloth_ref to 'main'
   while the stable-tag gates require '', so UI-default manual runs could
   never advance :core/:latest/:studio; the default is now empty.

9. entrypoint.sh: the sm_103/sm_121 branch rewrote libnvrtc.so.12 to the
   CUDA-13 build but the ordinary-GPU branch never restored it, so a
   container moved to an older GPU kept the stale link; it is now reversed
   when it points exactly at the .cu13 target.

Rejected after verification (no code change): timeout=0 semantics are
documented at the site with no zero callers, TORCHINDUCTOR_COMPILE_THREADS
override is deliberate, fetchNews is a string enum per JupyterLab's schema,
:base tag appears in no in-tree doc, install-cell digest exclusion is the
module's stated contract, transformers ceiling semantics are documented,
and the cloudflared download mirrors the pre-existing Studio downloader
(Cloudflare publishes no checksum asset). The UNSLOTH_ALLOW_CPU import
crash lives in unsloth_zoo (compiler.py / loss_utils.py capability probes),
not in this diff; the image consumes the zoo fix automatically once merged
there.

Tests: shim suite extended to 63 (constraints, env expansion, fail-closed),
jit-selector suite to 14 (NVRTC reversal transitions), plus staged-publish
and ownership repros; wider studio install suite green except failures
reproduced at the unmodified head.
2026-07-13 03:03:03 +00:00
Daniel Han
6a078b1a45 docker: close more pip-shim bypasses and make cu12.8 NVRTC the default
Notebook pip/uv shim (docker/unsloth_pip_shim.py, active only under
UNSLOTH_NB_SHIM=1):
  - Parse protected source archives (sdist/zip) by basename too, e.g.
    `pip install https://.../unsloth-2026.7.1.tar.gz` or `./torch-2.11.0.tar.gz`,
    mirroring the wheel-basename handling. A first-hyphen-before-digit split
    keeps hyphenated names like flashinfer-python intact.
  - Recognise uv's PLURAL long flags --requirements / --constraints, so those
    files go through the same protected-package filter as the singular names.
  - Drop --upgrade-strategy eager in shim mode so a kept target cannot eagerly
    rebuild already-satisfied baked deps (falls back to pip's only-if-needed).

NVRTC default (docker/Dockerfile, docker/Dockerfile.studio, docker/entrypoint.sh):
  - Make cu12.8 the immutable baked default (libnvrtc.so.12 -> .cu128.orig) with
    a staged .cu13 alias, and have select_cuda_jit_tools retarget to cu13 ONLY
    for sm_103/sm_121. Previously cu13 was baked as the default and restored to
    cu12.8 at runtime, so a non-root `docker run --user` container that cannot
    rewrite the symlink stayed on cu13 NVRTC and emitted cubins a 570-579 driver
    cannot load. The safe default now needs no runtime write.

Adds regression tests for each case (tests/python/test_unsloth_pip_shim.py,
tests/sh/test_select_cuda_jit_tools.sh).
2026-07-08 08:06:45 +00:00
Daniel Han
b3649d40cc docker: close notebook pip-shim bypasses and scan all GPUs for cu13
Notebook pip/uv shim (docker/unsloth_pip_shim.py), all active only under
UNSLOTH_NB_SHIM=1:
  - Parse a bare wheel filename (torch-*.whl in the CWD, no ./ or / prefix) so
    it is matched against _KEEP instead of passing through as an opaque
    positional and reinstalling the baked torch.
  - Infer the distribution from an egg-less VCS URL by repo basename
    (git+https://github.com/huggingface/transformers.git -> transformers) so
    the egg-less form the repo itself recommends cannot clobber the baked stack.
  - Refuse remote (URL) -r/-c requirement/constraint files -- top-level and
    nested includes -- since their pins cannot be inspected before the real
    tool would fetch and install them.
  - Strip resolver-wide reinstall/ignore-installed switches
    (--force-reinstall, --ignore-installed, -I, uv --reinstall) so they cannot
    rebuild already-satisfied baked deps pulled in by a kept target.
  - Route uv --reinstall-package through the same _KEEP handling as
    -P/--upgrade-package (both attached and separated forms; no dangling flag).

Entrypoint (docker/entrypoint.sh): select_cuda_jit_tools() now scans every
visible GPU's compute_cap instead of only the first, so a datacenter Blackwell
(sm_103/sm_121) behind an H100/B200 still enables the cu13 JIT tools it needs.

Adds regression tests for each case (tests/python/test_unsloth_pip_shim.py,
tests/sh/test_select_cuda_jit_tools.sh).
2026-07-08 07:16:30 +00:00
Daniel Han
251e3edf93 docker: address review round 3 (requirement-file shim edges + device-gate cu13 JIT tools)
unsloth_pip_shim.py: close three more ways a protected package slipped past
_KEEP. An editable line (-e/--editable <target>) inside a -r requirements file
is a real install target, so a protected editable there is now classified and
dropped like the command-line case (new _parse_editable). pip/uv accept the
attached short forms -rreqs.txt / -cconstraints.txt / -epath / -Pname as one
token; these were falling through as opaque options (so an attached -r-only cell
no-op'd and an attached -c/-e/-P value bypassed _KEEP), so the 2-char flag is now
split from its value and routed through the separated-form handling. And a nested
-c constraint inside a -r file no longer records its transformers pin as an
install request (a constraint is not a request; mirrors the top-level -c path).

entrypoint.sh / Dockerfile: gate the CUDA 13 ptxas + NVRTC to sm_103 / sm_121 at
runtime instead of a global build-time default. A cu13 cubin needs a >= 580
driver to LOAD even when it targets an older arch (CUDA has forward, not
backward, cross-major driver compatibility), but the image supports Turing..
sm_120 on a 570+ driver, so the previous global TRITON_PTXAS_PATH ENV + cu13
NVRTC symlink would break ordinary Triton/NVRTC JIT on 570-579 driver hosts. The
build still bakes cu13 (saving the cu12.8 NVRTC as .cu128.orig); a new
select_cuda_jit_tools() in the entrypoint reads the device compute_cap and only
activates cu13 for sm_103/sm_121 (which ship >= 580 drivers), otherwise leaving
Triton on its bundled cu12.8 ptxas and restoring the cu12.8 NVRTC in both the
base and Studio venvs. The base ENTRYPOINT runs for the Studio image too.

Adds 9 pip-shim regression tests and tests/sh/test_select_cuda_jit_tools.sh
(7 device-gating cases); registers the latter in CI and tests/run_all.sh.
2026-07-08 06:20:31 +00:00
Daniel Han
2c316862f8 docker: address review round 4 (jupyter probe, CPU messaging, llama EXDEV, %pip shim)
- docker-publish smoke + docker_confirm.sh probe Jupyter /login, not /api: the
  launcher always configures a password hash so /api returns 403 and curl -f
  would never flip the health flag (false build failure).
- entrypoint.sh CPU messaging: CPU mode covers Jupyter, GGUF tooling and
  llama.cpp (GGUF) Studio chat; training AND loading an Unsloth model
  (FastLanguageModel) still need a GPU, since from_pretrained runs CUDA probes.
- install_llama_prebuilt.py: rollback/activation moves used bare os.replace,
  which fails with EXDEV across overlayfs in a Docker build and fell back to a
  broken source build (no nvcc). Add is_cross_device_error + move_install_dir_aside
  (os.replace fast path, copy+remove on EXDEV; busy errors still re-raise).
- notebooks: %pip / %uv line magics and the `!python -m pip` form bypassed the
  PATH pip/uv shim and could overwrite the baked cu128 torch/vLLM stack. Add
  unsloth_nb_pip_magic.py to re-point them at the shim, wired via the IPython
  startup hook and installed into the venv site-packages.
2026-06-27 08:46:47 +00:00
Daniel Han
d0d5f3c27f docker: pre-load unslothai/notebooks into JupyterLab, edit-safe refresh
JupyterLab now opens with the unslothai/notebooks collection already present,
so people can open and run a notebook directly without a git clone or wget.

- Bake the repo into the image as a read-only template at /opt/unsloth-notebooks
  (~206MB, .git stripped, build commit recorded). Inherited by the studio image.
- On boot the entrypoint populates /workspace/unsloth-notebooks from the template
  (instant, works offline) and best-effort refreshes from GitHub, but only when
  upstream has actually advanced (cheap git ls-remote gate, no download otherwise).
- The user's edits always win. We record the content hash of every file we write;
  on refresh a file whose hash differs from what we last wrote is treated as
  user-modified and is left untouched, so the refresh only updates files the user
  has not changed and adds new ones. It never overwrites an edited notebook and
  never produces merge conflicts. Verified: an edited notebook stays the user's
  version across repeated upstream changes.
- Fully best-effort and gated: UNSLOTH_SKIP_NOTEBOOK_SYNC=1 disables it,
  UNSLOTH_SKIP_NOTEBOOK_REFRESH=1 keeps the baked copy and never hits the network.
  Offline boots keep what is there and never error.

base 18.45 -> 18.67GB, studio 24.88 -> 25.10GB (+~206MB baked notebooks).
2026-06-15 06:35:08 +00:00
danielhanchen
f4e378e8b5 docker: review fixes from the 8-reviewer pass and staging CI
entrypoint.sh: a container started without a GPU request has no
nvidia-smi at all (the toolkit injects it), so the old check 1 reported
'CUDA runtime in this image is broken, re-pull' for the most common user
error. Fold the missing-binary case into the actionable 'No GPU visible'
message and document the CPU-only option (UNSLOTH_ALLOW_CPU=1).

run.sh / test_locally.sh: guard empty-array expansions with the
${arr[@]+...} form; bash 3.2 (macOS /bin/bash) treats "${empty[@]}"
as unbound under set -u, which broke the documented macOS CPU path.

studio_launch.sh: exclude *_TOKEN, *_API_KEY, *_PASSWORD, *_SECRET,
*_LICENSE from the env snapshot written for SSH sessions; secrets stay
in process env only, never on disk.

supervisord.conf / Dockerfile.studio: pin HOME=/root for the studio and
jupyter programs (jupyter would silently fall back to token auth if HOME
were unset), default JUPYTER_PORT and UNSLOTH_ENABLE_SSHD at the image
level so a direct supervisord invocation cannot hit a bad %(ENV_*)s
expansion, and document the root-services decision (non-root parity with
the previous production image is a tracked follow-up).

docker_confirm.ps1: mirror the bash script's GPU selector translation so
GPUS=0 / 0,1 select devices instead of silently using all GPUs.

docker-publish.yml: studio cache scope moves to mode=min; a mode=max
cache of a ~24GB image would evict everything else in the 10GB GHA
quota for no hit-rate gain.
2026-06-12 05:31:24 +00:00
danielhanchen
f1a63db6fa docker: ship Jupyter, Studio and prebuilt llama.cpp out of the box
Base image (docker/Dockerfile):
- Install JupyterLab + notebook + ipywidgets in a separate pure-Python uv
  pass so the cu128 pin set cannot move; EXPOSE 8888.
- Bake the prebuilt llama.cpp bundle into /opt/unsloth/llama.cpp at the
  runtime stage using studio/install_llama_prebuilt.py from the same
  UNSLOTH_REF (sha256-verified, portable CUDA bundle since the build host
  has no GPU; arm64 resolves the linux-arm64-cuda13 bundle). Export
  UNSLOTH_LLAMA_CPP_PATH so unsloth_zoo's save_pretrained_gguf finds it
  and never reaches the interactive install prompt or a source build.
- Optional github_token BuildKit secret for the resolver's API calls on
  shared CI runner IPs.

Entrypoint: UNSLOTH_ALLOW_CPU=1 degrades a missing GPU to a warning so
Docker Desktop on macOS / Windows-without-WSL2-GPU and plain CPU hosts can
run Jupyter, GGUF tooling and Studio chat; with a GPU visible the normal
pre-flight still runs.

Full image (docker/Dockerfile.studio): now mirrors the production service
set under supervisord - Studio on 8000, JupyterLab on 8888, key-only sshd
on 22 (enabled only when PUBLIC_KEY/SSH_KEY is set). Points Studio's
llama.cpp dir at the baked bundle to skip a duplicate download, accepts
any git ref via fetch+checkout (CI passes commit SHAs), and FROMs a
digest-pinned BASE_IMAGE.

Publish workflow: base image moves to the base-* tag namespace; new
build-studio/merge-studio jobs publish the full image as :latest (hub
parity with the previous production image, which shipped Studio + Jupyter
+ SSH). Studio builds FROM the exact base manifest digest published by the
same run. GPU smoke job now also boots the full image and probes Studio
/api/health and Jupyter /api.

run.sh: UNSLOTH_GPUS=none, UNSLOTH_ALLOW_CPU forwarding, UNSLOTH_PORTS
publish flags, CPU-mode and Jupyter usage examples.
2026-06-12 05:06:51 +00:00
Daniel Han
c463d58277 entrypoint.sh: correct driver-floor message (570+ unconditionally on cu128)
The earlier message had per-arch driver minimums (525/535/555/570) that
came from when each chip first got driver support. That's not how CUDA
toolkit floors work -- cu128 imposes 570.26+ on EVERY GPU regardless of
arch. Only B300 (sm_103) and DGX Spark (sm_121) need a newer driver
(580+), and they ship factory with those drivers anyway.

External HF README has the same correction applied in temp/hf_readme.md
(updated separately when published).
2026-05-24 11:36:36 +00:00
Daniel Han
1769204ade Dockerfile: arm64 DGX Spark NVRTC + ptxas fix (cu13 alongside cu128)
Empirically (cu128 wheel SASS list `sm_80;90;90a;100;100a;120;120a` on
aarch64) the cu128 wheel covers DGX Spark sm_121 via sm_120 binary
forward-compat. BUT two CPU-side compilers shipped at cu12.8 do not know
sm_121 and need a cu13 swap:

  (1) torch's bundled libnvrtc.so.12 from CUDA 12.8 rejects sm_121 as a
      --gpu-architecture. Symlinks libnvrtc.so.13 over it.

  (2) Triton's nvidia backend runs ptxas. Wheels older than 3.6.0 bundled
      cu12.8 ptxas which silently downgrades sm_121 to sm_80 (see
      triton-lang/triton#8335). Bump pin triton>=3.6.0 (3.6 bundles cu13
      ptxas) AND install cuda-nvcc-13-0 so the entrypoint can point
      TRITON_PTXAS_PATH at it as defense in depth.

Both fixes are arm64-only (gated on TARGETARCH, ~400 MB on the arm64
image; amd64 is untouched, no sm_121 hardware exists on x86_64). Neither
component talks to libcuda, so this does NOT bump the toolkit driver
floor away from cu128's 570+.

TRITON_PTXAS_PATH is set from the entrypoint (only when the cu13 ptxas
actually exists in the image) rather than via a Dockerfile ENV, because
ENV is unconditional and Triton errors out if TRITON_PTXAS_PATH points
at a nonexistent file.

Sources: martimramos/dgx-spark-ml-guide Challenge 14; triton-lang/triton
issue #8335; ptrblck PyTorch forum thread on sm_121 fwd-compat from
sm_120.
2026-05-24 11:35:07 +00:00
Daniel Han
dde5170e7a Expand arch list to every current x86_64 NVIDIA CC per developer.nvidia.com/cuda/gpus
TORCH_CUDA_ARCH_LIST now covers the full set of compute capabilities
NVIDIA publishes on https://developer.nvidia.com/cuda/gpus for x86_64
hardware, from Turing onward:

  sm_75    Turing       T4, RTX 20-series, Quadro RTX
  sm_80    Ampere DC    A100, A30
  sm_86    Ampere       A40, RTX A6000, RTX 30-series
  sm_89    Ada          L4, L40, L40S, RTX 40-series
  sm_90    Hopper       H100, H200, GH200
  sm_100   Blackwell DC B100, B200, GB200
  sm_103   Blackwell DC B300, GB300
  sm_120   Blackwell    RTX 50-series, RTX PRO 6000 Blackwell
  sm_121   Blackwell    GB10 (DGX Spark)

with +PTX on the highest entry so future arch revisions can JIT.

Setting TORCH_CUDA_ARCH_LIST only affects nvcc invocations for any
source build the user adds on top of this image (e.g. flash-attn, a
custom CUDA op). The prebuilt cu128 wheels already include SASS for
sm_70/75/80/86/90/100/120 (verified at build time via
torch._C._cuda_getArchFlags()). Ada (sm_89), B300 (sm_103) and DGX
Spark (sm_121) GPUs run via JIT-PTX from the nearest available arch.

Jetson archs (sm_87 Orin, sm_110 Thor) are intentionally NOT included
-- they require aarch64 wheels and this image is linux/amd64 only.

Also lower the entrypoint's compute-capability gate from sm_80 to
sm_75. Turing GPUs work, with the caveat that bfloat16 is unavailable;
the entrypoint prints a NOTE in that case so Unsloth's fp16 fallback
isn't a surprise.
2026-05-24 08:31:15 +00:00
Daniel Han
58693c4c73 Add entrypoint with GPU pre-flight checks + opinionated run.sh wrapper
When someone launches the unsloth container, the common failure modes are not
unsloth bugs -- they're Docker / nvidia-container-toolkit / driver issues that
surface as cryptic CUDA errors deep in torch. The entrypoint catches the three
that cover ~95% of "it doesn't work" reports up front:

1. nvidia-smi inside the container sees no GPU
   -> user forgot --gpus all, or host is missing nvidia-container-toolkit
   -> entrypoint prints the exact docker run flag and the toolkit install URL
2. nvidia-smi works but torch.cuda.is_available() is False
   -> host driver is older than CUDA 12.8 supports
   -> entrypoint prints the minimum driver version per architecture
3. compute capability < sm_80
   -> entrypoint prints the supported architecture table and exits

Each check fails with a clear, actionable message rather than a stack trace.
Set UNSLOTH_SKIP_GPU_CHECK=1 to bypass (for docs builds, offline tooling, CI).

run.sh wraps `docker run` with the flags people most often forget:
  --gpus all           (without it, the new entrypoint refuses to start)
  --ipc=host           (DataLoader workers need >64MB shm)
  --ulimit memlock=-1  (NCCL + CUDA pinned host buffers)
  --ulimit stack=64MB  (some torch kernels OOM the default 8MB stack)

Plus it mounts the host HF cache + Triton JIT cache so model downloads and
compiled kernels persist across container runs, and forwards HF_TOKEN /
WANDB_API_KEY / UNSLOTH_LICENSE only when they are set on the host.

Usage:
  bash docker/run.sh                                  # interactive python REPL
  bash docker/run.sh bash                             # shell in container
  bash docker/run.sh python /workspace/smoke_test.py
  bash docker/run.sh python /workspace/host/train.py  # $PWD mounted at /workspace/host

Verified locally:
- No GPU visible: entrypoint refuses with driver-version message, exit 1
- B200 sm_100 visible: entrypoint prints GPU banner, exits cleanly into the
  user command (rc=0)
2026-05-24 07:04:48 +00:00