perf(workers): slim the remaining ML images via real multi-stage builds - #163
Merged
Merged
Conversation
Applies the pattern from PR #160 (SAM/SAM2) to the eight other images built by build_machine_learning_workers.sh: cellpose, cellpose_train, cellposesam, cellposesam_train, stardist, condensatenet, piscis/predict, piscis/train. All eight carried the same lineage: a *-devel CUDA base, a no-op `FROM base as build` (nothing was ever copied between stages, so the whole build environment shipped), no PIP_NO_CACHE_DIR, no conda clean, full-history clones, and a copy-pasted apt block installing r-base twice plus software-properties-common and python3 -- none of which any worker uses. Per worker: - Real multi-stage build. The build stage keeps the devel image as the only available compiler; the runtime stage ships on *-runtime and copies over the finished conda env, the trees that were pip install -e'd, and the baked-in model cache. Worker .py files are copied into the runtime stage from the build context. - ENV PIP_NO_CACHE_DIR=1 and conda clean --all --yes, each of which was keeping a full second copy of the wheels / env. - Shallow clones with .git removed. - Dropped r-base (x2), software-properties-common and python3. Model caches carried into the runtime stage, so first-run downloads are still avoided: /root/.cellpose (cellpose 3.x and 4.x both use ~/.cellpose/models), /root/.keras (csbdeep's from_pretrained goes through keras.utils.get_file), /models (condensatenet, real files -- snapshot_download uses local_dir_use_symlinks=False) and /root/.piscis. stardist is the one worker whose runtime stage keeps the cudnn8 tag. TensorFlow 2.11 predates the tensorflow[and-cuda] extra (2.14), so its wheel bundles no nvidia-* packages and dlopens libcudnn.so.8 from the image. A plain -runtime tag would not fail the build -- TF logs "Could not load dynamic library" and silently falls back to CPU. The seven torch workers are the opposite: torch loads the cuDNN/cuBLAS/cuFFT/NCCL inside its own pip wheels, so the image's copy was never used and the cudnn tag is dropped. Also fixed: - Both piscis images cloned ImageAnalysisProject from GitHub and installed annotation_utilities / worker_client from that clone, so the build used the default branch rather than the tree being built -- the same bug PR #160 fixed in sam_automatic_mask_generator. They now COPY the local directories. - piscis moved from Miniconda to Miniforge, matching every other worker. That drops the defaults channel and the two `conda tos accept` calls; its environment.yml is only python=3.11 + pip, all from conda-forge. - The DeepTile checkout (cellpose, cellposesam, stardist, condensatenet) is ~140 MB but the importable package is 160 KB; the rest is .git, sample data (56 MB) and notebooks (37 MB). It is an editable install so the tree must stay, but data/notebooks/tests are now pruned after the clone. No environment.yml was touched: unlike the SAM workers, none of these had a dependency that was provably unused. Not yet built or measured -- no Docker daemon in this environment. See todo/ml-worker-image-size.md for the per-worker verification steps required before merging, including the GPU check for stardist, which degrades to CPU silently rather than failing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E6gFQEG27c5b6EbNrp24Lq
…ding Review catch on the previous commit. Dropping the copy-pasted `r-base` from the apt list is not purely cosmetic: `r-base-core` depends on libglib2.0-0, libx11-6, libxt6 (-> libsm6, libice6), libtk8.6 (-> libxext6) and libcairo2 (-> libxcb1), so every worker carrying `r-base` had been getting those shared libraries by accident. That matters for piscis specifically. It depends on `opencv-python`, not `opencv-python-headless`, and `piscis/transforms.py` does `import cv2 as cv` at module scope, so `import piscis` needs libGL.so.1, libglib-2.0.so.0, libgthread-2.0.so.0, libX11.so.6, libSM.so.6, libICE.so.6, libXext.so.6 and libxcb.so.1 from the image (the Qt5 libs vendored into the wheel pull them in; auditwheel deliberately does not vendor libGL). Without them the worker cannot start -- and the *build* breaks too, since download_models.py imports piscis. Both stages of both piscis images now install libgl1, libglib2.0-0, libsm6, libice6, libx11-6 and libxext6 explicitly. libgl1 was never in r-base's tree, so it had been coming from whatever the CUDA devel base happened to carry; listing it removes that dependency on base-image internals. Verified the rest of the fleet is unaffected by reading DT_NEEDED off every wheel's .so files against what each wheel vendors: - opencv-python-headless (cellpose x4): nothing beyond glibc/libstdc++ - torch: nothing, vendors its own libgomp and the entire CUDA stack - tensorflow 2.11 (stardist): nothing linked; dlopens libcudnn.so.8 by name, which is why that runtime stage keeps the cudnn8 tag - stardist 0.9.1, rtree, shapely: nothing beyond glibc/libstdc++ libgomp1 is safe to lose everywhere: conda-forge ships libgomp inside the env and the torch wheels vendor their own. Also verified no Docker label or ENV changed across all eight images (the dispatcher routes on them); the only ENV difference is the intended PATH move from the base conda bin to the env bin, matching the merged SAM workers. Documented in PISCIS.md and todo/ml-worker-image-size.md, including the implication for deconwolf, which links fftw/gsl/png/tiff/OpenCL directly and will need `ldd /usr/bin/dw` to enumerate its runtime libs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E6gFQEG27c5b6EbNrp24Lq
Dropping the CUDA devel base for *-runtime also drops /usr/local/cuda/bin, so ptxas and nvcc are no longer in these images. It surfaces in exactly one place: TensorFlow logs "Couldn't invoke ptxas --version / Relying on driver to perform ptx compilation" on every stardist run. The fallback is correct -- a real segmentation returned the right object count with cuDNN 8906 loaded on the GPU -- so this is accepted rather than fixed, since pulling cuda-nvcc back into the runtime stage would cost a few hundred MB to silence log noise. The torch workers are unaffected: the triton wheel bundles its own ptxas. Also replaces the "none of this has been built" section with the measured results now that the eight images have been built and exercised: 178.5 G -> 72.7 G (-59%), model caches confirmed intact across the stage copy, GPU confirmed in use from the runtime base, and real jobs run against a live NimbusImage stack (including one through the UI, whose job log shows runtime: nvidia, so isGPUWorker still routes correctly). An actual retrain remains unverified and is called out as such. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DGVianXJET5VVja76jahvj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Applies the pattern from #160 (SAM/SAM2) to the eight other images built by
build_machine_learning_workers.sh:cellpose,cellpose_train,cellposesam,cellposesam_train,stardist,condensatenet,piscis/predict,piscis/train.Note
Built, measured and exercised on x86_64 + RTX 3060 against a live NimbusImage stack. The size table below is measured, not estimated. One checklist item — an actual retrain — remains unverified; see Verification.
The problem
All eight carried the exact lineage #160 diagnosed:
*-develCUDA base whose CUDA stack is shipped twice (PyTorch loads the cuDNN/cuBLAS/cuFFT/NCCL inside its ownnvidia-*pip wheels, never the image's copy)FROM base as build— nothing was ever copied between stages, so the whole build environment shippedPIP_NO_CACHE_DIR(a full second copy of the wheels) and noconda clean(a second copy of the env)r-basetwice, plussoftware-properties-commonandpython3— none of which any of these workers useWhat changed
Per worker: a real two-stage build (devel base for the build stage,
*-runtimefor the final stage, copying only the finished conda env, the editable-install trees and the baked-in model cache),ENV PIP_NO_CACHE_DIR=1,conda clean --all --yes, shallow clones, and the dead apt packages dropped. Worker.pyfiles now land in the final stage, so editing one no longer invalidates theannotation_utilities/worker_clientinstalls.Model caches are carried across the stage boundary so first-run downloads are still avoided —
/root/.cellpose(both cellpose 3.x and 4.x resolve from~/.cellpose/models),/root/.keras(csbdeep'sfrom_pretrained→keras.utils.get_file),/models(condensatenet — real files,snapshot_downloaduseslocal_dir_use_symlinks=False) and/root/.piscis.No
environment.ymlwas touched. Unlike the SAM workers, none of these had a dependency that was provably unused.stardist is the exception on the cuDNN tag
Its runtime stage keeps
cudnn8; the other seven drop it. TensorFlow 2.11 predates thetensorflow[and-cuda]extra (2.14), so its wheel declares nonvidia-*dependencies and dlopenslibcudnn.so.8from the image. A plain-runtimetag would not fail the build — TF logsCould not load dynamic library libcudnn.so.8and silently falls back to CPU.Confirmed against the built images:
pip listshowsnvidia-cudnn-cu12/cu13wheels in all seven torch workers and none in stardist.Two piscis bugs fixed along the way
git clone https://github.com/arjunrajlaboratory/ImageAnalysisProject/and installedannotation_utilities/worker_clientfrom that clone, so the build used whatever was on the default branch rather than the tree being built — the same bug Reduce ML worker image sizes via multi-stage builds and cache cleanup #160 fixed insam_automatic_mask_generator. They nowCOPYthe local directories.defaultschannel and bothconda tos acceptcalls;piscis/environment.ymlis onlypython=3.11+pip.The r-base trap (second commit)
Dropping
r-baseis not purely cosmetic.r-base-coredepends onlibglib2.0-0,libx11-6,libxt6(→libsm6,libice6),libtk8.6(→libxext6) andlibcairo2(→libxcb1), so every worker carrying it had been getting those shared libraries by accident.That breaks exactly one worker:
piscisdepends onopencv-python, not-headless, andpiscis/transforms.pydoesimport cv2 as cvat module scope. Without those libsimport piscisraisesImportError— in the build stage too, sincedownload_models.pyimports the package. Both piscis stages now installlibgl1 libglib2.0-0 libsm6 libice6 libx11-6 libxext6explicitly.The rest of the fleet was cleared by reading
DT_NEEDEDoff every wheel's.sofiles against what each wheel vendors:opencv-python(piscis)opencv-python-headless(cellpose ×4)torchtensorflow==2.11.0(stardist)stardist==0.9.1,rtree,shapelyptxas/nvccare gone from the runtime stage (third commit)The
*-runtimeimages have no/usr/local/cuda/bin, so the toolkit binaries disappear. Visible in one place: stardist logsCouldn't invoke ptxas --version/Relying on driver to perform ptx compilationon every run. The fallback is correct — a real segmentation returned the right object count with cuDNN 8906 loaded on the GPU — so this is accepted rather than fixed; addingcuda-nvccback would cost a few hundred MB to silence log noise.The seven torch workers are unaffected, and not because they never JIT: the
tritonwheel bundles its ownptxasundersite-packages/triton/backends/nvidia/bin/. Documented intodo/ml-worker-image-size.mdand inline in the stardist Dockerfile.Measured impact
Shipped image size, unpacked, from
docker image inspect .Size(base-10).An earlier revision of this description estimated a 215 G → 96 G fleet (−55%). That was written without a Docker daemon and overstated the baseline: the real saving is smaller in absolute terms (−105.8 G, not −119 G) but a larger fraction (59%, not 55%).
Build times were 136–293 s per worker. Compressed pull sizes were not measured — every figure above is unpacked. The earlier claim that cold build time is "roughly a wash" is unverified and withdrawn: the pre-change images were not rebuilt, so there is no before-time to compare against.
Verification
/root/.cellpose/models(cyto*/nuclei*for cellpose 3,cpsam+cpsam_v2for cellpose 4),/root/.keras/models/StarDist2D,/models/condensatenet-v1,/root/.piscis/models(four dated models + therajlabcollection). No first-run download.torch.cuda.is_available()true for the seven torch workers; stardist reports[PhysicalDevice('/physical_device:GPU:0')]withLoaded cuDNN version 8906and noCould not load dynamic library libcudnnline.import cv2succeeds in both piscis images — the check that would have caught the r-base trap.runtime: nvidia— so theisGPUWorkerlabel still routes to the GPU queue through girder_worker.from cellpose import trainimports.Not verified
An actual retrain. Item 5 covers the preconditions but no training run was executed, so nothing has yet written a real checkpoint into a directory that arrives via
COPY --from. This is the weakest point in the verification.Out of scope
deconwolfis the last GPU worker on this lineage. It is an image-processing worker rather than an ML one and the only one compiling a native binary, so it needs its own pass —todo/ml-worker-image-size.mdrecords what that involves, including usingldd /usr/bin/dwto enumerate its runtime libs rather than assuming the base image carries them.Generated by Claude Code