JupyterHub integration
The recommended runtime for these notebooks is the jupyterhub service in the genai-vanilla stack. As of genai-vanilla 10f8402 (pinned in this repo's vendor/genai-vanilla submodule), that image natively ships the ml-eng-lab dependency set:
thekaveh-nnx[lm]==0.2.0python-louvain,nltk,spacy,torchao,prettytable- The
en_core_web_smspaCy model + thevader_lexiconNLTK corpus, downloaded at image-build time
For most workflows you do NOT need this repo's wrapper script or override file — just start the standalone stack and connect from VS Code.
1. Default path: standalone genai-vanilla + VS Code Mode 2
This is the recommended path for most tier-covered ml-eng-lab notebooks — the exception being the from-scratch notebooks/image_classification-mnist-ffnn-numpy/notebook.ipynb (which imports sibling .py modules from its own folder, requiring filesystem access, and needs the §2 path). The quantization notebook is still manual-only under torch>=2.5 + torchao>=0.17.
-
Bring the stack up from a standalone clone of genai-vanilla:
cd ~/repos/genai-vanilla ./start.sh -
Open any ml-eng-lab notebook locally in VS Code (it stays on your host filesystem).
-
Point VS Code at the remote kernel — see vscode-remote-access.md Mode 2.
import nnx resolves for tier-covered notebooks in the current image. Notebook outputs save back to the local .ipynb file because VS Code holds the file on the host.
What this path does NOT give you: notebook code that does pd.read_csv("./data/foo.csv") or NNRun.save() writes to the container's CWD (/home/jovyan/), not to your host repo. Data/run artifacts land in the jupyterhub-data named volume — opaque to git status and lost on docker volume rm. For most Tier-A demos that's fine (small datasets, cheap to re-download). For long-running training where you want host-side persistence, see §2.
2. Persistence path: wrapper script + bind-mount
Use this when you want any of:
- Datasets and
runs/checkpoints to land on your host filesystem (visible ingit status, survivesdocker compose down -v). - The from-scratch
notebooks/image_classification-mnist-ffnn-numpy/notebook.ipynbnotebook to work (it imports sibling.pymodules from its own folder). - A development workflow where you
git commitnotebook edits + dataset downloads from inside the container.
This repo vendors a snapshot of genai-vanilla as a git submodule at vendor/genai-vanilla and ships a wrapper script that layers an ml-eng-lab override onto the standalone compose:
2.1. Clone with submodules
git clone --recurse-submodules https://github.com/thekaveh/ml-eng-lab.git
# Or, if already cloned:
git submodule update --init --recursive
2.2. Run the wrapper
scripts/start-jupyterhub.sh
The wrapper sets ML_REPO_PATH (the ml-eng-lab repo root), exports COMPOSE_FILE to layer deploy/genai-vanilla-jupyterhub.override.yml onto genai-vanilla's base compose, and execs the submodule's ./start.sh. The override bind-mounts ${ML_REPO_PATH}:/home/jovyan/work/ml-eng-lab, so from the running container's perspective, the repo is at /home/jovyan/work/ml-eng-lab/.
By default, the wrapper mounts an empty ignored directory at /home/jovyan/.ssh; host SSH keys are not exposed to notebook code. To opt into a read-only SSH-key mount for git push, set HOST_SSH_DIR explicitly:
HOST_SSH_DIR=/path/to/keys scripts/start-jupyterhub.sh
3. nnx development: editable install override
If you're developing nnx itself (editing source on your host and wanting changes to land in the running kernel without a pip install cycle), clone thekaveh/NNx anywhere outside the ml-eng-lab tree, then bind-mount your clone into the running container alongside ml-eng-lab (extend deploy/genai-vanilla-jupyterhub.override.yml with a second volume) and:
docker exec -it <project>-jupyterhub pip install -e /home/jovyan/work/NNx[lm]
For everyone else, the image's pre-baked thekaveh-nnx[lm]==0.2.0 layer is what you want and this override is unnecessary.
4. Submodule pin / bumping
vendor/genai-vanilla pins a known-good commit on genai-vanilla's main. Standard submodule bump:
cd vendor/genai-vanilla
git fetch origin
git checkout main
git pull origin main
cd ../..
git add vendor/genai-vanilla
git commit -m "ml-eng-lab: bump genai-vanilla submodule to <new-sha>"
The submodule pin matters for the §2 path; the §1 path uses your standalone genai-vanilla checkout and is independent of the submodule.
5. Tested against
genai-vanilla 10f840252404eb5399550f96fbb560153f1a47c7, which includes the ml-eng-lab runtime dependency block, thekaveh-nnx[lm]==0.2.0, and the spaCy/NLTK asset downloads in the JupyterHub image.
6. Common failure modes
Could not find a version that satisfies the requirement nnx-pytorchduringdocker compose build jupyterhub— the checkout is older than the10f8402runtime pin. Pull current genai-vanillamainor update this repo's submodule withgit submodule update --init --recursive.ModuleNotFoundError: No module named 'nnx'in the §1 path — the image was built from an older genai-vanilla checkout. Pull current genai-vanillamain, rebuild thejupyterhubimage, and confirmservices/jupyterhub/build/requirements.txtcontainsthekaveh-nnx[lm]==0.2.0.- Submodule not found at
vendor/genai-vanilla/— rungit submodule update --init --recursiveat the repo root. ML_REPO_PATH variable is not setduring compose up — you rancd vendor/genai-vanilla && ./start.shdirectly instead of using the wrapper. Usescripts/start-jupyterhub.sh.- Relative-path reads/writes go to the wrong place (notebook does
pd.read_csv("./data/foo.csv")but the file is on your host) — you're on the §1 path. Switch to §2 if you want host-side persistence. - Stack service didn't come up — Check
docker compose psfrom insidevendor/genai-vanilla/(§2) or~/repos/genai-vanilla/(§1).