-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
docker: Colab-grade JupyterLab and Studio UX for the Unsloth image #6681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: docker-blackwell-build
Are you sure you want to change the base?
Changes from all commits
c9ff52b
7b5bb24
e496095
50f7170
b963403
dde7a26
2eba8a4
30e7e09
9a53256
a56a7a8
2e4699c
38f1115
6fd9744
3fb8804
d1a5b58
9cd1951
2482719
0a8ae80
2c190aa
04452eb
67c0a8e
5d41a03
38df387
b558bc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,3 +15,23 @@ | |
| !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 | ||
| !jupyter | ||
| !jupyter/unsloth_branding.py | ||
| !jupyter/jupyter_server_config.d | ||
| !jupyter/jupyter_server_config.d/** | ||
| !jupyter/overrides.json | ||
| !jupyter/favicon.ico | ||
| !jupyter/logo.png | ||
| !jupyter/login.html | ||
|
Comment on lines
+25
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When building Useful? React with 👍 / 👎. |
||
| !jupyter/install_sloth_stickers.py | ||
| !jupyter/unsloth_labext | ||
| !jupyter/unsloth_labext/package.json | ||
| !jupyter/unsloth_labext/tsconfig.json | ||
| !jupyter/unsloth_labext/.yarnrc.yml | ||
| !jupyter/unsloth_labext/src | ||
| !jupyter/unsloth_labext/src/** | ||
| !jupyter/unsloth_labext/style | ||
| !jupyter/unsloth_labext/style/** | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| # Full Unsloth image: base training stack + Studio + JupyterLab + sshd. | ||
| # | ||
| # This is the image published as docker.io/unsloth/unsloth:latest. It layers | ||
| # Unsloth Studio on top of the lean base image (Dockerfile, published under | ||
| # the `base` tags) and runs the same service trio as the previous production | ||
| # 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. | ||
| # | ||
| # Build (local): | ||
|
|
@@ -28,6 +29,27 @@ | |
| # images always ship the same stack. | ||
|
|
||
| ARG BASE_IMAGE=unsloth-blackwell:test | ||
|
|
||
| # --- builder stage: prebuild the Unsloth JupyterLab extension ----------------- | ||
| # Builds the named "Unsloth Dark" (Monokai) theme + the Colab-style Down/Up | ||
| # cell-navigation keymap. Node lives ONLY in this throwaway stage; the final | ||
| # image copies just the prebuilt static labextension, so the runtime stays | ||
| # Node-free. Uses the base image's bundled jlpm + jupyterlab (version-matched). | ||
| FROM ${BASE_IMAGE} AS labext-builder | ||
| ENV DEBIAN_FRONTEND=noninteractive | ||
| # JupyterLab 4.6's build tooling declares a Node >=20 engine; Ubuntu 24.04's | ||
| # distro nodejs is 18, so pull Node 20 LTS from NodeSource (it bundles npm). | ||
| # This stage is thrown away, so the extra apt sources never reach the runtime. | ||
| RUN apt-get update \ | ||
| && apt-get install -y --no-install-recommends ca-certificates curl gnupg git \ | ||
| && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ | ||
| && apt-get install -y --no-install-recommends nodejs \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
Comment on lines
+43
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On the Ubuntu 24.04 CUDA base, Useful? React with 👍 / 👎. |
||
| COPY jupyter/unsloth_labext /opt/labext-src | ||
| RUN cd /opt/labext-src \ | ||
| && /opt/unsloth-venv/bin/jlpm install \ | ||
| && /opt/unsloth-venv/bin/jlpm build:prod | ||
|
|
||
| FROM ${BASE_IMAGE} | ||
|
|
||
| # Studio source ref to clone. Defaults to `main`, but a CI publish pipeline | ||
|
|
@@ -176,6 +198,68 @@ COPY fetch_llama_prebuilt.py /usr/local/lib/unsloth/fetch_llama_prebuilt.py | |
| # Optional public Cloudflare tunnel for JupyterLab (UNSLOTH_JUPYTER_CLOUDFLARE=1, | ||
| # or `unsloth-jupyter-tunnel --force`); supervisord runs it as jupyter-cloudflare. | ||
| COPY unsloth_jupyter_tunnel.sh /usr/local/bin/unsloth-jupyter-tunnel | ||
| # JupyterLab defaults baked for every container: the named "Unsloth Dark" | ||
| # (Monokai) theme with adaptive light/dark by system preference, a per-cell run | ||
| # button that does NOT auto-advance, a labeled "Restart & Run All", windowing | ||
| # disabled so collapsing a long output does not snap to the cell top, | ||
| # ArrowDown/Up jumping to the TOP of the next/previous cell, and the official | ||
| # Jupyter "get notified about news" prompt suppressed (fetchNews/checkForUpdates | ||
| # off). overrides.json is the system-wide settings override (read from the base | ||
| # venv's share/jupyter/lab/settings); the theme + keymap + Unsloth top-bar logo | ||
| # ship as the prebuilt labextension built in the labext-builder stage above. | ||
| COPY jupyter/overrides.json /opt/unsloth-venv/share/jupyter/lab/settings/overrides.json | ||
| COPY --from=labext-builder /opt/labext-src/unsloth-jupyterlab/labextension /opt/unsloth-venv/share/jupyter/labextensions/unsloth-jupyterlab | ||
| # Unsloth branding (all served by jupyter_server, so applied to its site-packages | ||
| # the same way): replace the browser-tab favicon and the page logo with the | ||
| # Unsloth logo, and brand the login screen (dark Unsloth-themed login.html). | ||
| # Also disable + lock the stock top-left Jupyter logo plugin so the Unsloth logo | ||
| # widget shipped by the labextension is the only one rendered in the top bar | ||
| # (lock keeps users from re-enabling it in the UI). | ||
| # 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. | ||
| 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 | ||
| COPY jupyter/install_sloth_stickers.py /tmp/unsloth-branding/install_sloth_stickers.py | ||
| RUN JS="$(/opt/unsloth-venv/bin/python -c 'import os, jupyter_server; print(os.path.dirname(jupyter_server.__file__))')" \ | ||
| && for n in favicon.ico favicon-notebook.ico favicon-file.ico favicon-terminal.ico; do \ | ||
| cp /tmp/unsloth-branding/favicon.ico "${JS}/static/favicons/${n}"; \ | ||
| done \ | ||
| && cp /tmp/unsloth-branding/logo.png "${JS}/static/logo/logo.png" \ | ||
| && cp /tmp/unsloth-branding/login.html "${JS}/templates/login.html" \ | ||
| && { /opt/unsloth-venv/bin/python /tmp/unsloth-branding/install_sloth_stickers.py \ | ||
| --src "${UNSLOTH_STUDIO_HOME}/src/studio/frontend/public/Sloth emojis" \ | ||
| --dest "${JS}/static/sloth" \ | ||
| || echo ">> sloth stickers not installed (login falls back to the Unsloth logo)"; } \ | ||
| && rm -rf /tmp/unsloth-branding \ | ||
| && /opt/unsloth-venv/bin/jupyter labextension disable @jupyterlab/application-extension:logo \ | ||
| && /opt/unsloth-venv/bin/jupyter labextension lock @jupyterlab/application-extension:logo \ | ||
| && /opt/unsloth-venv/bin/jupyter labextension disable @jupyterlab/apputils-extension:splash \ | ||
| && /opt/unsloth-venv/bin/jupyter labextension lock @jupyterlab/apputils-extension:splash \ | ||
| && /opt/unsloth-venv/bin/jupyter labextension lock unsloth-jupyterlab | ||
| # Branding integrity guard: the canonical attribution checker (also a | ||
| # jupyter_server extension), the full AGPLv3 license text, and the config that | ||
| # enables the extension. Installed into the base venv so it is on the jupyter | ||
| # process's import + config search path. The stock @apputils-extension:splash is | ||
| # disabled+locked above so the labextension's spinning-logo splash is the sole | ||
| # ISplashScreen provider. The build-time --verify FAILS the image build if any | ||
| # Unsloth attribution / license asset is missing or altered. | ||
| COPY jupyter/unsloth_branding.py /tmp/unsloth-branding-guard/unsloth_branding.py | ||
| COPY jupyter/jupyter_server_config.d/unsloth_branding_guard.json /tmp/unsloth-branding-guard/unsloth_branding_guard.json | ||
| RUN SP="$(/opt/unsloth-venv/bin/python -c 'import sysconfig; print(sysconfig.get_path("purelib"))')" \ | ||
| && cp /tmp/unsloth-branding-guard/unsloth_branding.py "${SP}/unsloth_branding.py" \ | ||
| && mkdir -p /opt/unsloth-venv/etc/jupyter/jupyter_server_config.d \ | ||
| && cp /tmp/unsloth-branding-guard/unsloth_branding_guard.json \ | ||
| /opt/unsloth-venv/etc/jupyter/jupyter_server_config.d/unsloth_branding_guard.json \ | ||
| && cp "${UNSLOTH_STUDIO_HOME}/src/studio/LICENSE.AGPL-3.0" \ | ||
| /opt/unsloth-venv/share/jupyter/UNSLOTH_LICENSE.AGPL-3.0 \ | ||
| && rm -rf /tmp/unsloth-branding-guard \ | ||
| && /opt/unsloth-venv/bin/python -m unsloth_branding --verify | ||
| RUN chmod +x /usr/local/bin/unsloth-studio-launch \ | ||
| /usr/local/bin/unsloth-studio-update \ | ||
| /usr/local/bin/unsloth-llama-update \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ | |
| # Studio chat / Jupyter / GGUF tooling still validate. | ||
| # | ||
| # Env overrides: IMAGE (default unsloth/unsloth:latest) | ||
| # BASE_IMAGE (default unsloth/unsloth:base) | ||
| # BASE_IMAGE (default unsloth/unsloth:core) | ||
| # GPUS=all|none|0|0,1 (default: auto-detect) | ||
| # PORT_STUDIO=18000 PORT_JUPYTER=18888 | ||
| # WORK=~/unsloth_docker_test (logs) | ||
|
|
@@ -33,7 +33,7 @@ | |
| set -uo pipefail | ||
|
|
||
| IMAGE="${IMAGE:-unsloth/unsloth:latest}" | ||
| BASE_IMAGE="${BASE_IMAGE:-unsloth/unsloth:base}" | ||
| BASE_IMAGE="${BASE_IMAGE:-unsloth/unsloth:core}" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This default now pulls Useful? React with 👍 / 👎. |
||
| GPUS="${GPUS:-auto}" | ||
| PORT_STUDIO="${PORT_STUDIO:-18000}" | ||
| PORT_JUPYTER="${PORT_JUPYTER:-18888}" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For PRs that only change the Docker/Jupyter files this step is meant to validate, it will not run: the workflow's
pull_request.pathsfilter includesstudio/**,unsloth/**,tests/**, etc., but notdocker/**. I checked this same workflow, so a regression indocker/jupyter/overrides.json, the labextension, or the notebook helpers can still pass PR CI unless the PR also happens to touchtests/**or the workflow file.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Added docker/** to the pull_request paths in b558bc7 so the Docker JupyterLab/notebook feature validation runs on docker-only changes.