Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
c6d9216
Add Docker build for Blackwell that runs on any NVIDIA GPU host
danielhanchen May 24, 2026
a75aef0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 24, 2026
58693c4
Add entrypoint with GPU pre-flight checks + opinionated run.sh wrapper
danielhanchen May 24, 2026
acbb16c
Add docker/test_locally.sh: one-shot end-to-end Docker validation
danielhanchen May 24, 2026
f7b3479
test_locally.sh: use docker buildx (or DOCKER_BUILDKIT=1) for the build
danielhanchen May 24, 2026
56d2701
test_locally.sh: require docker buildx, no legacy fallback
danielhanchen May 24, 2026
23a5b43
test_locally.sh: pre-flight check for docker daemon connectivity
danielhanchen May 24, 2026
fd55ed0
Dockerfile: drop system-python pip/uv bootstrap (PEP 668)
danielhanchen May 24, 2026
00cbc82
smoke_test.py: import unsloth before unsloth_zoo / transformers / trl…
danielhanchen May 24, 2026
1cdc5f1
Dockerfile: install gcc + g++ + python3-dev in runtime stage
danielhanchen May 24, 2026
dde5170
Expand arch list to every current x86_64 NVIDIA CC per developer.nvid…
danielhanchen May 24, 2026
4bfb4b8
Add docker/hf_{push,pull}.sh: simulate docker push/pull against HF Hub
danielhanchen May 24, 2026
7354642
hf_{push,pull}.sh: use new `hf` CLI, fall back to deprecated `hugging…
danielhanchen May 24, 2026
8344fa0
test_locally.sh: use nbformat directly, drop fragile jupyter nbconver…
danielhanchen May 24, 2026
391532c
test_locally.sh: skip notebook install cells, strip stray jupyter magic
danielhanchen May 24, 2026
e7cfcea
Add linux/arm64 (DGX Spark / Grace) support via QEMU at build time
danielhanchen May 24, 2026
131f1d3
docker-publish.yml: native arm64 runner + per-arch digest merge
danielhanchen May 24, 2026
897e5e7
Dockerfile: tighten arch-flag assertion + correct fat-binary claims
danielhanchen May 24, 2026
1769204
Dockerfile: arm64 DGX Spark NVRTC + ptxas fix (cu13 alongside cu128)
danielhanchen May 24, 2026
e728eed
Dockerfile: switch runtime base cudnn-runtime -> base (~2.7 GB lighter)
danielhanchen May 24, 2026
c463d58
entrypoint.sh: correct driver-floor message (570+ unconditionally on …
danielhanchen May 24, 2026
fa9609d
Dockerfile: arm64 install cu13 nvrtc/nvcc directly without cuda-keyri…
danielhanchen May 24, 2026
34fb65f
Dockerfile: install vLLM nightly on amd64 for GRPO fast_inference
danielhanchen May 24, 2026
215ed9b
Dockerfile: arm64 build aborted by set -e in vLLM auto-gate
danielhanchen May 24, 2026
6d536d8
Dockerfile: re-upgrade numpy after vLLM install (2.2.6 wheel is broken)
danielhanchen May 24, 2026
79e9363
Fix two upstream regressions surfaced by the Blackwell Docker validation
danielhanchen May 24, 2026
a01fa21
docker: add Dockerfile.studio extending the Blackwell image with Unsl…
danielhanchen May 24, 2026
29a6bde
Address reviewer findings on PR #5748: 4 release-path bugs
danielhanchen May 24, 2026
5cb5eb7
Remove async_task_outputs from repo (accidentally committed)
danielhanchen May 24, 2026
6e45c27
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 24, 2026
291e2cf
docker/Dockerfile.studio: keep source for the editable install
danielhanchen May 24, 2026
914f91c
docker: add timm + addict to the base image
danielhanchen May 24, 2026
0d574d8
Address reviewer-2 findings on PR #5748
danielhanchen May 24, 2026
a9b8d68
Remove individual_reviews from repo (accidentally committed)
danielhanchen May 24, 2026
f116f78
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 24, 2026
c91fa26
tests/studio: assert losses_per_step matches max_steps, not stale 7
danielhanchen May 24, 2026
cceeeb1
Address 3 MAJOR review findings on the docker PR
danielhanchen May 25, 2026
2faf827
docker: round-3 review fixes (concurrency, lockfile wording)
danielhanchen May 25, 2026
6b91708
Merge branch 'main' into docker-blackwell-build
danielhanchen May 27, 2026
9723d72
docker-publish: pin UNSLOTH_ZOO_REF on tag pushes
danielhanchen May 27, 2026
4d34845
docker/run.sh: translate UNSLOTH_GPUS index selectors to device= form
danielhanchen May 27, 2026
10f0a03
docker/test_locally.sh: fail fast + pin notebook fetch to immutable SHA
danielhanchen May 27, 2026
f34a4cd
Merge branch 'main' into docker-blackwell-build
danielhanchen Jun 12, 2026
6448587
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
f1a63db
docker: ship Jupyter, Studio and prebuilt llama.cpp out of the box
danielhanchen Jun 12, 2026
9e9877e
docker: pin the llama.cpp bake by target arch, add docker_confirm.sh
danielhanchen Jun 12, 2026
38d7b5e
docker: whitelist new build-context files, add docker_confirm.ps1
danielhanchen Jun 12, 2026
e8ac40f
docker/studio: deterministic Studio install inside the image build
danielhanchen Jun 12, 2026
d431f3c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
6fd1220
docker: mirror the llama.cpp bake into build/bin so Studio setup reus…
danielhanchen Jun 12, 2026
c62bb19
docker_confirm.sh: rename unused poll counter for shellcheck SC2034
danielhanchen Jun 12, 2026
f4e378e
docker: review fixes from the 8-reviewer pass and staging CI
danielhanchen Jun 12, 2026
81b0d1e
docker: second review pass fixes
danielhanchen Jun 12, 2026
3d56379
docker ci: aggressive runner disk reclaim before image builds
danielhanchen Jun 12, 2026
96edc89
gpu_init: satisfy the import-hoist lint in the compile-thread patch
danielhanchen Jun 12, 2026
b2fe9f4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
25d95c0
docker: zstd + matplotlib for out-of-the-box notebook coverage
danielhanchen Jun 12, 2026
c515aa0
docker: audio decode out of the box (ffmpeg + matched torchcodec bake)
danielhanchen Jun 12, 2026
e06b1fb
docker: split the torchcodec bake across build stages
danielhanchen Jun 12, 2026
3e6d37c
docker: install vLLM on the arm64 leg too and probe it in the confirm…
danielhanchen Jun 12, 2026
cfeb772
Merge remote-tracking branch 'origin/main' into pr-5748-head
danielhanchen Jun 12, 2026
9f9cd41
docker: add wget to the runtime image
danielhanchen Jun 12, 2026
09c9c95
ci: retrigger after bulk-cancelled runs
danielhanchen Jun 12, 2026
11430aa
docker: unbreak standalone vllm serve (ninja-build + flashinfer-jit-c…
danielhanchen Jun 12, 2026
487ea4f
dataprep: detect vllm 0.19 server readiness (renamed log line, stderr)
danielhanchen Jun 12, 2026
9987323
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
9d39aee
studio: honor UNSLOTH_TORCH_INDEX_FAMILY in CUDA repair path, assert …
danielhanchen Jun 12, 2026
6f6b638
Fix import on driverless hosts under UNSLOTH_ALLOW_CPU=1
danielhanchen Jun 12, 2026
8242b73
docker: mirror soname symlinks into llama.cpp build/bin, assert the r…
danielhanchen Jun 12, 2026
5b4eb34
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
eba071f
docker/studio: make the quantizer build assertion content based
danielhanchen Jun 12, 2026
5c5b534
Merge remote-tracking branch 'origin/main' into pr-5748-head
danielhanchen Jun 12, 2026
d01da4c
docker_confirm: accept locally built images when pull fails
danielhanchen Jun 12, 2026
2cd58d5
docker: ship cuda-nvcc and cudart-dev in the runtime image
danielhanchen Jun 12, 2026
5bb47cf
docker: bake soundfile, evaluate, tensorboard for notebook deps
danielhanchen Jun 13, 2026
ea91c7a
docker: add jiwer, langid, easydict, protobuf to baked notebook deps
danielhanchen Jun 13, 2026
dec240a
Merge remote-tracking branch 'origin/main' into pr-5748-head
Jun 14, 2026
aba16af
docker: notebook deps, image size cuts, per-notebook transformers
danielhanchen Jun 15, 2026
448e225
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 15, 2026
d0d5f3c
docker: pre-load unslothai/notebooks into JupyterLab, edit-safe refresh
danielhanchen Jun 15, 2026
338aff5
docker: notebook refresh ignores header/footer-only upstream changes
danielhanchen Jun 16, 2026
7e2e842
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 16, 2026
921ab18
docker: heal deleted notebooks on boot + fix notebooks helper dockeri…
danielhanchen Jun 16, 2026
d5df6c0
docker: track latest llama.cpp release + show its update banner in St…
danielhanchen Jun 24, 2026
b897cf8
docker: add unsloth-studio-update for in-place Studio updates
danielhanchen Jun 24, 2026
7606081
docker: add unsloth-llama-update for in-place llama.cpp prebuilt updates
danielhanchen Jun 24, 2026
08f9b67
docker: optional Cloudflare tunnel for JupyterLab (UNSLOTH_JUPYTER_CL…
danielhanchen Jun 24, 2026
c0abb0a
Merge remote-tracking branch 'origin/main' into dbb-merge-main
danielhanchen Jun 26, 2026
053a4f3
docker-publish: clean build-args + add least-privilege default permis…
danielhanchen Jun 26, 2026
52067fb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 26, 2026
8f693c6
docker: fix notebook pip-shim drops, first-boot overwrite, base :late…
danielhanchen Jun 26, 2026
f45c455
Merge remote-tracking branch 'origin/main' into dbb-merge-main
danielhanchen Jun 26, 2026
8402dce
docker-publish: pin one llama.cpp prebuilt release across both arch legs
danielhanchen Jun 26, 2026
0ebbdbb
docker: address review follow-ups (pip-shim flags, sync ownership, ta…
danielhanchen Jun 26, 2026
d476c77
docker: address review round 3 (notebook -r filter, studio zoo ref, p…
danielhanchen Jun 26, 2026
7083a2d
docker: pip-shim catches direct-reference protected installs + --opt=…
danielhanchen Jun 26, 2026
2ee7f4b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 26, 2026
2c31686
docker: address review round 4 (jupyter probe, CPU messaging, llama E…
danielhanchen Jun 27, 2026
f152569
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 27, 2026
68f5394
docker_confirm.ps1: probe JupyterLab /login, not /api
danielhanchen Jun 29, 2026
8fc483e
Merge remote-tracking branch 'origin/main' into docker-blackwell-build
danielhanchen Jul 1, 2026
08a1bf6
Merge remote-tracking branch 'origin/main' into docker-blackwell-build
danielhanchen Jul 5, 2026
034fbc9
Docker notebook safety hardening and vLLM startup timeout fix
danielhanchen Jul 5, 2026
6cb2201
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 5, 2026
326f57e
docker-publish: freeze the unsloth-zoo ref to a concrete sha before f…
danielhanchen Jul 6, 2026
d63b6c4
docker/run.sh: forward Studio service env to the container
danielhanchen Jul 6, 2026
19e3bc3
docker: move the image torch stack to 2.11.0 and document the amd64 s…
danielhanchen Jul 6, 2026
782a7d5
Merge remote-tracking branch 'origin/main' into docker-blackwell-build
danielhanchen Jul 6, 2026
0a41967
Normalize kwarg spacing in loader.py after the main merge
danielhanchen Jul 6, 2026
cba7223
docker: Colab-grade JupyterLab and Studio UX for the Unsloth image (#…
danielhanchen Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Builds and publishes the Blackwell-compatible Unsloth Docker image.
#
# The build runs on a free GitHub-hosted Ubuntu runner with NO GPU attached.
# This is possible because:
# 1. cu128 PyTorch wheels are fat binaries -- they already ship sm_70 through
# sm_120 SASS, cross-compiled upstream by the PyTorch team.
# 2. The Dockerfile pins explicit wheel URLs (no --torch-backend=auto, no
# install.sh that introspects the host driver).
# 3. The build-time sanity check uses torch._C._cuda_getArchFlags(), which
# reads compiled wheel metadata and does NOT require a CUDA device.
# 4. UNSLOTH_COMPILE_DISABLE=1 prevents Unsloth from JIT-compiling a Triton
# kernel cache keyed to the (non-existent) build-host GPU.
#
# Required repository secrets:
# DOCKERHUB_USERNAME, DOCKERHUB_TOKEN
#
# Optional repository variable (gates the smoke-test job):
# HAS_GPU_RUNNER = 'true' if a self-hosted GPU runner is available

name: Publish Blackwell Docker image

on:
push:
branches: [main]
tags: ['v*']
schedule:
- cron: '17 4 * * 1' # weekly Mon 04:17 UTC (off-the-hour on purpose)
workflow_dispatch:
inputs:
unsloth_ref:
description: 'unsloth git ref to bake in'
required: false
default: 'main'
unsloth_zoo_ref:
description: 'unsloth-zoo git ref to bake in'
required: false
default: 'main'

env:
REGISTRY: docker.io
IMAGE_NAME: unsloth/unsloth

jobs:
build:
runs-on: ubuntu-latest # no GPU, 16GB RAM, 4 vCPU
timeout-minutes: 60
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v4

# Free up ~20GB on the runner so cu128 wheels + cudnn fit.
- name: Reclaim disk
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
/opt/hostedtoolcache/CodeQL "$AGENT_TOOLSDIRECTORY"
df -h /

- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Resolve tags
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prevent workflow_dispatch builds from overwriting latest

This tag rule publishes latest whenever the run is on the default branch, but workflow_dispatch also allows overriding UNSLOTH_REF/UNSLOTH_ZOO_REF to arbitrary refs. A manual test run from main can therefore push a non-release image as latest, which makes downstream users pull an unintended build. Gate latest to trusted release flows (or only when both refs are the defaults) to avoid accidental retagging.

Useful? React with 👍 / 👎.

type=ref,event=tag
type=schedule,pattern=nightly
type=sha,prefix=sha-,format=short

- name: Build and push
uses: docker/build-push-action@v6
with:
context: ./docker
file: ./docker/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
CUDA_VERSION=12.8.1
UBUNTU_VERSION=24.04
PYTHON_VERSION=3.12
UNSLOTH_REF=${{ github.event.inputs.unsloth_ref || 'main' }}
UNSLOTH_ZOO_REF=${{ github.event.inputs.unsloth_zoo_ref || 'main' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Build from triggering ref instead of hardcoding main

For non-workflow_dispatch events (including tag pushes), github.event.inputs.* is unset, so these build args always resolve to main. That means images produced for v* tags can contain unsloth and unsloth-zoo code from main rather than the release ref that triggered the run, which breaks release correctness and reproducibility.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pin unsloth-zoo to the triggering ref for release builds

The workflow now pins UNSLOTH_REF to the tag/SHA that triggered the run, but UNSLOTH_ZOO_REF is still hardcoded to main. On tag pushes and SHA-based rebuilds this can produce images with unsloth from the release ref and unsloth-zoo from a moving branch head, which breaks release reproducibility and can introduce incompatibilities unrelated to the tagged source.

Useful? React with 👍 / 👎.


- name: Image digest
run: echo "${{ steps.meta.outputs.tags }} -> ${{ steps.meta.outputs.digest }}"

# Optional: pull the freshly published image onto a self-hosted GPU runner
# and run smoke_test.py. Keeps "did the image actually work" decoupled from
# "was a GPU available at build time". Skipped automatically when no GPU
# runner is registered.
smoke-test:
needs: build
if: ${{ vars.HAS_GPU_RUNNER == 'true' }}
runs-on: [self-hosted, gpu]
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Pull and smoke-test
run: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker run --rm --gpus all \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Smoke-test the image built in this run, not latest

The smoke-test job always pulls :latest, but this workflow also runs on tag pushes where latest is not guaranteed to be among the tags produced by metadata-action (it is only enabled on the default branch in this workflow). In that case, the smoke test can validate an older image and miss regressions in the freshly built tag from the current run.

Useful? React with 👍 / 👎.

python /workspace/smoke_test.py

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
4 changes: 4 additions & 0 deletions docker/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**
!Dockerfile
!entrypoint.sh
!smoke_test.py
237 changes: 237 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# syntax=docker/dockerfile:1.7
# -----------------------------------------------------------------------------
# Unsloth + unsloth-zoo for Blackwell (sm_100 B200 + sm_120 RTX 50-series / 6000 Pro)
#
# Why this image works:
# * cu128 wheels are fat binaries: SASS for sm_80;86;89;90;100;120.
# * Unsloth's runtime kernels are Triton, which JIT-compiles per device at first run.
# * Anything that DOES need to be source-built (rare on this pin set) compiles
# against TORCH_CUDA_ARCH_LIST="7.5;8.0;8.6;8.9;9.0;10.0;10.3;12.0;12.1+PTX",
# covering every current x86_64 NVIDIA compute capability per
# https://developer.nvidia.com/cuda/gpus.
# The host GPU is irrelevant for compilation; nvcc emits whatever the arch
# list says.
#
# Build host requirements:
# * Docker with buildkit (default since 23.x)
# * nvidia-container-toolkit (only needed for `docker run --gpus all` at test time)
# * A GPU is NOT required at build time.
# -----------------------------------------------------------------------------

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
# =============================================================================
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-devel-ubuntu${UBUNTU_VERSION} AS builder

ARG PYTHON_VERSION
ENV DEBIAN_FRONTEND=noninteractive \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
# Cross-compile for every current x86_64 NVIDIA arch per
# https://developer.nvidia.com/cuda/gpus:
# 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)
# +PTX on the highest lets future arch revisions run via JIT-PTX.
TORCH_CUDA_ARCH_LIST="7.5;8.0;8.6;8.9;9.0;10.0;10.3;12.0;12.1+PTX" \
MAX_JOBS=4 \
CUDA_HOME=/usr/local/cuda \
# Build-host-independence guards. The build must NEVER introspect a GPU,
# because the build host may have a B200, RTX 6000, or no GPU at all
# (GitHub Actions ubuntu-latest). All three must yield byte-identical images.
#
# 1) Stop unsloth from JIT-compiling kernels at import time and writing a
# sm_NNN-specific blob into /opt/unsloth-venv/.../unsloth_compiled_cache/.
UNSLOTH_COMPILE_DISABLE=1 \
UNSLOTH_COMPILE_OVERWRITE=0 \
# 2) Stop unsloth-zoo / vllm from probing torch.cuda.is_available() during
# setup. There's no GPU here, and we don't want it to silently skip a wheel.
UNSLOTH_DISABLE_GPU_PROBE=1 \
# 3) Force CUDA_VISIBLE_DEVICES empty so any stray torch.cuda call during
# `pip install` returns "no devices" rather than triggering host-specific
# code paths (we re-enable at runtime via `docker run --gpus all`).
CUDA_VISIBLE_DEVICES=""

RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common ca-certificates curl git build-essential \
ninja-build cmake pkg-config \
&& add-apt-repository -y ppa:deadsnakes/ppa \
&& apt-get update && apt-get install -y --no-install-recommends \
python${PYTHON_VERSION} python${PYTHON_VERSION}-venv python${PYTHON_VERSION}-dev \
&& ln -sf /usr/bin/python${PYTHON_VERSION} /usr/local/bin/python \
&& ln -sf /usr/bin/python${PYTHON_VERSION} /usr/local/bin/python3 \
&& rm -rf /var/lib/apt/lists/*

# Build into an isolated prefix. NOTE: we do NOT install pip or uv into the
# system Python -- on Ubuntu 24.04 the system interpreter is marked
# externally-managed (PEP 668) and `pip install` is refused. Instead, the new
# venv bootstraps its own pip via ensurepip (provided by the python3.12-venv
# apt package), and we install uv into the venv a few lines below.
ENV VENV=/opt/unsloth-venv
RUN python -m venv ${VENV} && ${VENV}/bin/pip install -U pip wheel setuptools

# Unified install: torch + triton + bitsandbytes + unsloth + unsloth_zoo
# resolve in a SINGLE uv pip pass. This is mandatory -- splitting it across
# multiple `pip install` calls causes bnb's transitive `cuda-toolkit` dep to
# silently upgrade torch to 2.12.0+cu130 in a later pass, breaking the cu128
# xformers wheel that was pinned earlier. (Empirically discovered; the cu cascade
# happens AFTER xformers is already on disk, leaving a working-but-mismatched env.)
#
# uv-specific flags explained:
# --index-strategy unsafe-best-match
# The PyTorch index serves an old `requests==2.28.1` which conflicts with
# `datasets>=2.32.2`. uv's default is "first index wins per package" to
# prevent dependency confusion; we override here because both indexes
# (pytorch.org/whl/cu128 + pypi.org) are equally trusted.
# --extra-index-url https://download.pytorch.org/whl/cu128
# Where torch's +cu128 wheels live, plus the xformers/cu128 URLs referenced
# by unsloth's `cu128onlytorch2100` extra.
#
# Why the extra is `cu128-ampere-torch2100` (not `cu128-torch2100-ampere`):
# See unsloth_src/pyproject.toml:835. The ordering is ampere-then-torch-ver.
#
# Why no `flash-attn` here:
# - FA3 is hard-refused on Blackwell (Dao-AILab/flash-attention#1810).
# - FA2 has no prebuilt wheel for cu128+torch2.10+cp312 -> would require
# a ~30min source build, fragile on the 16GB ubuntu-latest CI runner.
# - Unsloth gracefully falls back to xformers/SDPA on Blackwell anyway.
# - Users on Ampere/Ada/Hopper who want FA2 can `pip install flash-attn`
# on top of this image at deploy time.
ARG UNSLOTH_REF=main
ARG UNSLOTH_ZOO_REF=main
RUN ${VENV}/bin/pip install uv \
&& ${VENV}/bin/uv pip install \
--python ${VENV}/bin/python \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The uv tool is already installed at the system level in line 63. Installing it again inside the virtual environment at line 99 is redundant. Using the system-wide uv binary to install packages into the venv is more efficient and avoids unnecessary layers.

RUN uv pip install \
        --python ${VENV}/bin/python \

--index-strategy unsafe-best-match \
--extra-index-url https://download.pytorch.org/whl/cu128 \
"torch==2.10.0" "torchvision==0.25.0" "torchaudio==2.11.0" \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There appears to be a version mismatch for torchaudio. While torch is pinned to 2.10.0 and torchvision to 0.25.0 (which correctly follows the standard 0.(Y+15) mapping for Torch 2.10), torchaudio is set to 2.11.0. Typically, PyTorch and Torchaudio versions are released in sync (e.g., Torch 2.6.0 with Torchaudio 2.6.0). Using 2.10.0 ensures consistency across the stack.

        "torch==2.10.0" "torchvision==0.25.0" "torchaudio==2.10.0" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Align torchaudio pin with torch 2.10 stack

The install set mixes torch==2.10.0 with torchaudio==2.11.0, but TorchAudio wheels are release-coupled to specific Torch versions; this can make dependency resolution fail or force an unexpected Torch change, which breaks the Docker build’s stated guarantee that Torch stays on 2.10.0. This is especially risky here because the same layer also installs many transitive deps from multiple indexes, so one incompatible pin can fail the image build on CI.

Useful? React with 👍 / 👎.

"triton>=3.3.1" \
"bitsandbytes>=0.49.2,!=0.46.0,!=0.48.0" \
"unsloth_zoo @ git+https://github.com/unslothai/unsloth-zoo@${UNSLOTH_ZOO_REF}" \
"unsloth[cu128-ampere-torch2100] @ git+https://github.com/unslothai/unsloth@${UNSLOTH_REF}"

# 5) Emit a lockfile so the next rebuild can be byte-identical even if PyPI
# has moved on. Bake it into the image at /opt/unsloth-venv/requirements.lock.txt
# so `docker run ... cat /opt/unsloth-venv/requirements.lock.txt > pins.txt`
# gives you the input to a fully-pinned rebuild.
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 layer copied to runtime.
RUN find ${VENV} -depth -type d -name __pycache__ -exec rm -rf {} + \
&& find ${VENV} -depth -type d -name tests -exec rm -rf {} + \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep NumPy _core/tests files required by runtime imports

This cleanup step removes every tests directory from the venv, which also strips numpy/_core/tests; earlier in the same Dockerfile you explicitly note that missing numpy._core.tests._natype causes import failures in real Unsloth paths. Deleting all tests here can recreate that exact runtime breakage (for example during import unsloth via SciPy/Transformers paths), so the published image may pass build checks but fail when users run workloads.

Useful? React with 👍 / 👎.

&& rm -rf /root/.cache/pip /root/.cache/uv

# Build-time verification.
#
# (1) arch-list check uses the RAW C++ accessor (not torch.cuda.get_arch_list()).
# The Python wrapper checks torch.cuda.is_available() first and returns []
# when no GPU is visible -- which is always the case here because
# CUDA_VISIBLE_DEVICES is empty by design.
#
# (2) We verify required packages via package metadata only -- we do NOT import
# unsloth or unsloth_zoo here. Their __init__ calls torch.cuda.get_device_
# properties(0) which requires an actual CUDA device (UNSLOTH_ALLOW_CPU=1
# only bypasses the first gate, not the deeper init). Import-time
# correctness is exercised at deploy time by smoke_test.py with --gpus all.
RUN ${VENV}/bin/python - <<'PY'
import torch
arches = torch._C._cuda_getArchFlags().split()
print("torch", torch.__version__, "cuda", torch.version.cuda)
print("arches:", arches)
assert torch.__version__.startswith("2.10.0"), f"torch silently moved: {torch.__version__}"
assert "+cu128" in torch.__version__, f"cu build silently changed: {torch.__version__}"
assert "sm_100" in arches, f"sm_100 (B200) missing: {arches}"
assert "sm_120" in arches, f"sm_120 (RTX 5090) missing: {arches}"
print("OK: torch 2.10.0+cu128 with sm_100 + sm_120 fat binary intact")

from importlib.metadata import version, PackageNotFoundError
REQUIRED = ("torch", "triton", "xformers", "bitsandbytes", "unsloth",
"unsloth_zoo", "transformers", "trl", "peft", "accelerate")
missing = []
for pkg in REQUIRED:
try:
v = version(pkg.replace("_", "-"))
print(f" {pkg:14s} {v}")
except PackageNotFoundError:
missing.append(pkg)
if missing:
raise SystemExit(f"FAIL: missing wheels: {missing}")
print("OK: all required wheels present (xformers, bnb, unsloth metadata visible)")

# Lightweight imports: these init without touching CUDA, unlike unsloth.
import importlib
for pkg in ("xformers", "bitsandbytes", "triton"):
importlib.import_module(pkg)
print("OK: xformers + bitsandbytes + triton import cleanly on no-GPU host")
PY

# =============================================================================
# Stage 2: runtime -- slim runtime image, no nvcc, no headers
# =============================================================================
FROM nvidia/cuda:${CUDA_VERSION}-cudnn-runtime-ubuntu${UBUNTU_VERSION} AS runtime

ARG PYTHON_VERSION
ENV DEBIAN_FRONTEND=noninteractive \
PIP_NO_CACHE_DIR=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH=/opt/unsloth-venv/bin:${PATH} \
HF_HOME=/workspace/.cache/huggingface \
TRITON_CACHE_DIR=/workspace/.cache/triton \
Comment on lines +511 to +512

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Setting HF_HOME and TRITON_CACHE_DIR to subdirectories of /workspace (the WORKDIR) can lead to issues when users mount a host directory to /workspace at runtime. The mount will obscure the directories created during the build, forcing the application to recreate them at runtime, which can cause permission issues or redundant downloads. Moving these caches to a location outside of the workspace, such as /opt/cache, avoids these issues.

    HF_HOME=/opt/cache/huggingface \
    TRITON_CACHE_DIR=/opt/cache/triton \

# Keep the arch list visible at runtime in case the user source-builds anything
# extra inside the container (e.g. a custom CUDA op).
TORCH_CUDA_ARCH_LIST="8.0;8.6;8.9;9.0;10.0;12.0+PTX"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include sm_75 in runtime arch list

The runtime image advertises support from Turing onward (see entrypoint.sh supported list), but TORCH_CUDA_ARCH_LIST here starts at 8.0. Any CUDA extension compiled inside the running container (the exact use case this env var comment describes) will be built without sm_75, so on T4/RTX20 hosts those kernels can fail at runtime with “no kernel image is available” despite the container claiming that architecture is supported.

Useful? React with 👍 / 👎.


RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common ca-certificates curl git libgomp1 \
gcc g++ \
&& add-apt-repository -y ppa:deadsnakes/ppa \
&& apt-get update && apt-get install -y --no-install-recommends \
python${PYTHON_VERSION} python${PYTHON_VERSION}-venv python${PYTHON_VERSION}-dev \
&& ln -sf /usr/bin/python${PYTHON_VERSION} /usr/local/bin/python \
&& ln -sf /usr/bin/python${PYTHON_VERSION} /usr/local/bin/python3 \
&& rm -rf /var/lib/apt/lists/*
# Why gcc + g++ + python3.12-dev in the RUNTIME stage:
# Triton's nvidia backend lazily compiles a small C extension (CudaUtils) on
# first GPU access. Without a C compiler + Python headers the very first
# forward pass of any Unsloth model dies with:
# RuntimeError: Failed to find C compiler. Please specify via CC env var.
# Adds ~250MB to the runtime image, which is the cost of letting every kernel
# JIT correctly. (Pre-compiling CudaUtils at build time would need a GPU, so
# shipping the toolchain is the right trade-off.)

COPY --from=builder /opt/unsloth-venv /opt/unsloth-venv

WORKDIR /workspace
RUN mkdir -p ${HF_HOME} ${TRITON_CACHE_DIR}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When moving the cache directories outside of /workspace, creating them with broad permissions (e.g., 777) ensures that non-root users can write to the cache at runtime without encountering permission errors.

RUN mkdir -p ${HF_HOME} ${TRITON_CACHE_DIR} && chmod -R 777 /opt/cache


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 ...
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
CMD ["python"]
Loading
Loading