378 lines
16 KiB
Docker
378 lines
16 KiB
Docker
{#
|
||
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||
# SPDX-License-Identifier: Apache-2.0
|
||
#}
|
||
# === BEGIN templates/dev.Dockerfile ===
|
||
# ======================================================================
|
||
# STAGE: dynamo_tools for developers
|
||
# ======================================================================
|
||
# Why this is a separate stage (not merged into `dev`):
|
||
# - `dev` is built FROM the framework `runtime` image. Installing lots of tooling with apt in that stage is slow and
|
||
# makes rebuilds expensive when iterating on later dev layers.
|
||
# - Keeping tooling installation in `dynamo_tools` lets Docker cache the tools layer independently; `dev` can then
|
||
# pull those binaries/configs in via COPY.
|
||
FROM runtime AS dynamo_tools
|
||
|
||
ARG TARGETARCH
|
||
ARG DEVICE
|
||
|
||
ENV DEBIAN_FRONTEND=noninteractive
|
||
ENV PATH=/usr/local/bin:${PATH}
|
||
|
||
USER root
|
||
SHELL ["/bin/bash", "-c"]
|
||
|
||
# NOTE: We intentionally disable the NVIDIA CUDA apt repo for this stage.
|
||
# The upstream runtime images may ship CUDA apt sources that occasionally go out of sync (mirror updates),
|
||
# causing apt-get update to fail with "File has unexpected size ... Mirror sync in progress".
|
||
# This stage only installs generic developer tools that are available from Ubuntu repos, so CUDA repos are unnecessary.
|
||
#
|
||
# We also add a small retry/backoff to make transient apt metadata issues less disruptive.
|
||
# Estimated layer size: ~800MB–1.0GB (build-essential+clang ~500MB, the rest ~300MB)
|
||
# Cache apt downloads; sharing=locked avoids apt/dpkg races with concurrent builds.
|
||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||
set -eux; \
|
||
if [ -d /etc/apt/sources.list.d ]; then \
|
||
mkdir -p /tmp/apt-disabled; \
|
||
for f in /etc/apt/sources.list.d/*.list; do \
|
||
[ -e "$f" ] || continue; \
|
||
if grep -q "developer.download.nvidia.com/compute/cuda/repos" "$f"; then \
|
||
mv "$f" "/tmp/apt-disabled/$(basename "$f")"; \
|
||
fi; \
|
||
done; \
|
||
fi; \
|
||
for i in 1 2 3 4 5; do \
|
||
apt-get update -y && break; \
|
||
rm -rf /var/lib/apt/lists/*; \
|
||
sleep $((i * 5)); \
|
||
done; \
|
||
apt-get install -y --no-install-recommends \
|
||
# Core CLI utilities
|
||
ca-certificates \
|
||
curl \
|
||
wget \
|
||
git \
|
||
git-lfs \
|
||
less \
|
||
grep \
|
||
sed \
|
||
# Editors / shells
|
||
vim \
|
||
nano \
|
||
htop \
|
||
tmux \
|
||
screen \
|
||
zsh \
|
||
fish \
|
||
bash-completion \
|
||
# Networking / transfers
|
||
net-tools \
|
||
openssh-client \
|
||
iproute2 \
|
||
iputils-ping \
|
||
zip \
|
||
unzip \
|
||
rsync \
|
||
# Build toolchain
|
||
build-essential \
|
||
cmake \
|
||
autoconf \
|
||
automake \
|
||
libtool \
|
||
meson \
|
||
ninja-build \
|
||
pybind11-dev \
|
||
pkg-config \
|
||
protobuf-compiler \
|
||
# Debugging / tracing
|
||
gdb \
|
||
valgrind \
|
||
strace \
|
||
ltrace \
|
||
# JSON/YAML + filesystem helpers
|
||
jq \
|
||
yq \
|
||
tree \
|
||
fd-find \
|
||
ripgrep \
|
||
# Privilege escalation + crypto tooling
|
||
sudo \
|
||
gnupg2 \
|
||
gnupg1 \
|
||
# GPU / perf helpers
|
||
nvtop \
|
||
# Python
|
||
python3 \
|
||
python3-pip \
|
||
python3-venv \
|
||
# Native deps for Python/Rust wheels
|
||
patchelf \
|
||
clang \
|
||
libclang-dev \
|
||
libfontconfig-dev && \
|
||
rm -rf /var/lib/apt/lists/* && \
|
||
# Initialize Git LFS for the dynamo user (required for requirements with lfs=true)
|
||
git lfs install
|
||
|
||
# Install awk separately with fault tolerance (~2MB).
|
||
# awk is a virtual package with multiple implementations (gawk, mawk, original-awk).
|
||
# Cache apt downloads; sharing=locked avoids apt/dpkg races with concurrent builds.
|
||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||
(apt-get update && \
|
||
(apt-get install -y --no-install-recommends gawk || \
|
||
apt-get install -y --no-install-recommends mawk || \
|
||
apt-get install -y --no-install-recommends original-awk || \
|
||
echo "Warning: Could not install any awk implementation") && \
|
||
rm -rf /var/lib/apt/lists/*) && \
|
||
(command -v awk >/dev/null 2>&1 && echo "awk available: $(command -v awk)" || echo "awk not available")
|
||
|
||
# Add NVIDIA devtools repository and install development tools (nsight-systems).
|
||
# Estimated layer size: ~500MB–1.5GB (nsight-systems is a full profiling suite)
|
||
# Cache apt downloads; sharing=locked avoids apt/dpkg races with concurrent builds.
|
||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||
wget -qO - "https://developer.download.nvidia.com/devtools/repos/ubuntu2404/${TARGETARCH}/nvidia.pub" \
|
||
| gpg --dearmor -o /etc/apt/keyrings/nvidia-devtools.gpg && \
|
||
echo "deb [signed-by=/etc/apt/keyrings/nvidia-devtools.gpg] https://developer.download.nvidia.com/devtools/repos/ubuntu2404/${TARGETARCH} /" \
|
||
| tee /etc/apt/sources.list.d/nvidia-devtools.list && \
|
||
apt-get update && \
|
||
apt-get install -y --no-install-recommends nsight-systems-2025.5.1 && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# TODO: Add GitHub CLI (gh) for development. Estimated layer size: ~50MB
|
||
|
||
# ======================================================================
|
||
# TARGET: dev (root-based development)
|
||
# ======================================================================
|
||
#
|
||
# USAGE: This dev image ships /workspace EMPTY. You MUST:
|
||
#
|
||
# 1) Bind-mount your Dynamo repo checkout into the container:
|
||
# docker run --gpus all -v /path/to/dynamo:/workspace ...
|
||
#
|
||
# 2) Build from source inside the container:
|
||
# cargo build --features dynamo-llm/block-manager
|
||
# cd /workspace/lib/bindings/python && maturin develop --uv
|
||
# uv pip install --no-deps -e /workspace
|
||
#
|
||
# The pre-built ai-dynamo / ai-dynamo-runtime wheels from the runtime
|
||
# stage are uninstalled below to avoid conflicts with the source build.
|
||
# ======================================================================
|
||
FROM runtime AS dev
|
||
|
||
# Redeclare ARGs for use in this stage
|
||
ARG FRAMEWORK
|
||
|
||
USER root
|
||
|
||
# Redeclare build args for use in this stage
|
||
ARG PYTHON_VERSION
|
||
|
||
# Ensure the runtime stage always has /usr/bin/python3.
|
||
# - vLLM/TRTLLM runtime images may only have Python in /opt/dynamo/venv/bin/{python,python3}
|
||
# - SGLang runtime images typically have /usr/bin/python3 already
|
||
# - framework=none runtime stage now installs /usr/bin/python3
|
||
RUN if [ ! -e /usr/bin/python3 ]; then \
|
||
if [ -x /opt/dynamo/venv/bin/python3 ]; then \
|
||
ln -s /opt/dynamo/venv/bin/python3 /usr/bin/python3; \
|
||
elif [ -x /opt/dynamo/venv/bin/python ]; then \
|
||
ln -s /opt/dynamo/venv/bin/python /usr/bin/python3; \
|
||
elif command -v python3 >/dev/null 2>&1; then \
|
||
ln -s $(command -v python3) /usr/bin/python3; \
|
||
elif command -v python >/dev/null 2>&1; then \
|
||
ln -s $(command -v python) /usr/bin/python3; \
|
||
else \
|
||
echo "ERROR: Could not find Python to symlink to /usr/bin/python3" >&2; \
|
||
exit 1; \
|
||
fi; \
|
||
fi
|
||
|
||
# Copy UCX and NIXL libraries for dev stage compilation.
|
||
# The upstream SGLang runtime image doesn't include NIXL, but cargo build needs to link against
|
||
# -lnixl, -lnixl_build, and -lnixl_common. Runtime stage doesn't need this since it uses pre-built
|
||
# wheels, but dev stage needs it for maturin develop and cargo build from source.
|
||
# - SGLang: Copy NIXL/UCX/libfabric/gdrcopy binaries from wheel_builder (not in upstream lmsysorg/sglang runtime).
|
||
# - vllm/trtllm/none: NIXL/UCX are already present in runtime (no-op).
|
||
ARG TARGETARCH
|
||
RUN --mount=from=wheel_builder,target=/wheel_builder \
|
||
if [ "${FRAMEWORK}" = "sglang" ]; then \
|
||
if [ -d /wheel_builder/usr/local/ucx ] && [ -d /wheel_builder/opt/nvidia/nvda_nixl ]; then \
|
||
mkdir -p /opt/nvidia /usr/include /usr/lib64 /etc/ld.so.conf.d; \
|
||
cp -r /wheel_builder/opt/nvidia/nvda_nixl /opt/nvidia/; \
|
||
cp -r /wheel_builder/usr/local/ucx /usr/local/; \
|
||
cp -r /wheel_builder/usr/local/libfabric /usr/local/; \
|
||
cp /wheel_builder/usr/include/gdrapi.h /usr/include/; \
|
||
cp /wheel_builder/usr/lib64/libgdrapi.so* /usr/lib64/; \
|
||
echo "/usr/lib64" >> /etc/ld.so.conf.d/gdrcopy.conf; \
|
||
fi; \
|
||
fi
|
||
|
||
{% if device == "xpu" %}
|
||
ENV NIXL_LIB_DIR=/opt/intel/intel_nixl/lib/x86_64-linux-gnu \
|
||
NIXL_PLUGIN_DIR=/opt/intel/intel_nixl/lib/x86_64-linux-gnu/plugins \
|
||
NIXL_PREFIX=/opt/intel/intel_nixl
|
||
{% else %}
|
||
# NIXL is installed under lib64 (manylinux/AlmaLinux convention used by the wheel_builder).
|
||
# All frameworks reference NIXL_LIB_DIR=/opt/nvidia/nvda_nixl/lib64.
|
||
# For vllm/trtllm/none: This resets the same values already set in runtime (no harm).
|
||
# For sglang: This sets them for the first time (required).
|
||
ENV NIXL_PREFIX=/opt/nvidia/nvda_nixl \
|
||
NIXL_LIB_DIR=/opt/nvidia/nvda_nixl/lib64 \
|
||
NIXL_PLUGIN_DIR=/opt/nvidia/nvda_nixl/lib64/plugins
|
||
|
||
# Set universal CUDA development environment variables (all frameworks)
|
||
# vLLM: Dockerfile.vllm line 533, 597
|
||
# TRT-LLM: Dockerfile.trtllm lines 600-606
|
||
ENV CUDA_HOME=/usr/local/cuda \
|
||
CPATH=/usr/local/cuda/include \
|
||
CUDA_DEVICE_ORDER=PCI_BUS_ID \
|
||
TRITON_CUPTI_PATH=/usr/local/cuda/include \
|
||
TRITON_CUDACRT_PATH=/usr/local/cuda/include \
|
||
TRITON_CUOBJDUMP_PATH=/usr/local/cuda/bin/cuobjdump \
|
||
TRITON_NVDISASM_PATH=/usr/local/cuda/bin/nvdisasm \
|
||
TRITON_PTXAS_PATH=/usr/local/cuda/bin/ptxas \
|
||
TRITON_CUDART_PATH=/usr/local/cuda/include \
|
||
NVIDIA_DRIVER_CAPABILITIES=video,compute,utility
|
||
{% endif %}
|
||
|
||
# Base LD_LIBRARY_PATH with universal paths (all frameworks have these)
|
||
# Framework-specific paths are conditionally added in /etc/profile.d/50-framework-paths.sh
|
||
ARG PYTHON_VERSION
|
||
ENV LD_LIBRARY_PATH=\
|
||
${NIXL_LIB_DIR}:\
|
||
${NIXL_PLUGIN_DIR}:\
|
||
/usr/local/ucx/lib:\
|
||
/usr/local/ucx/lib/ucx:\
|
||
${LD_LIBRARY_PATH}
|
||
|
||
# Copy shell profile script for framework-specific environment variables
|
||
# This script conditionally adds PATH/LD_LIBRARY_PATH entries based on what exists
|
||
COPY --chmod=755 container/dev/50-framework-paths.sh /etc/profile.d/50-framework-paths.sh
|
||
|
||
# Set umask for group-writable files in dev stage (runs as root)
|
||
RUN mkdir -p /etc/profile.d && echo 'umask 002' > /etc/profile.d/00-umask.sh
|
||
SHELL ["/bin/bash", "-l", "-o", "pipefail", "-c"]
|
||
|
||
# Developer tools are installed in the dynamo_tools layer and copied into the runtime-based dev image.
|
||
# This keeps dev builds fast and avoids apt-get in runtime-derived stages.
|
||
#
|
||
# IMPORTANT: Do not clobber runtime /usr/bin/python3 (SGLang depends on system python3 being present).
|
||
# We stash the pre-tools python3 (which may be a real binary or a symlink we created earlier for vLLM/TRTLLM)
|
||
# and restore it after copying toolchains from dynamo_tools.
|
||
RUN if [ -e /usr/bin/python3 ]; then cp -a /usr/bin/python3 /tmp/python3.pretools; fi
|
||
COPY --from=dynamo_tools /usr/bin/ /usr/bin/
|
||
COPY --from=dynamo_tools /usr/sbin/ /usr/sbin/
|
||
COPY --from=dynamo_tools /usr/lib/ /usr/lib/
|
||
COPY --from=dynamo_tools /usr/libexec/ /usr/libexec/
|
||
COPY --from=dynamo_tools /lib/ /lib/
|
||
COPY --from=dynamo_tools /usr/share/ /usr/share/
|
||
COPY --from=dynamo_tools /etc/alternatives/ /etc/alternatives/
|
||
COPY --from=dynamo_tools /etc/bash_completion.d/ /etc/bash_completion.d/
|
||
COPY --from=dynamo_tools /etc/sudoers /etc/sudoers
|
||
COPY --from=dynamo_tools /etc/sudoers.d/ /etc/sudoers.d/
|
||
COPY --from=dynamo_tools /opt/nvidia/ /opt/nvidia/
|
||
|
||
# Restore the pre-tools python3 (keeps SGLang system python intact and avoids venv symlink loops).
|
||
RUN if [ -e /tmp/python3.pretools ]; then cp -af /tmp/python3.pretools /usr/bin/python3; fi
|
||
|
||
ARG WORKSPACE_DIR=/workspace
|
||
|
||
# Dev environment variables (aligned with framework dev stages)
|
||
# Framework-specific PATH additions are handled in /etc/profile.d/50-framework-paths.sh
|
||
ENV WORKSPACE_DIR=${WORKSPACE_DIR} \
|
||
DYNAMO_HOME=${WORKSPACE_DIR} \
|
||
RUSTUP_HOME=/home/dynamo/.rustup \
|
||
CARGO_HOME=/usr/local/cargo \
|
||
CARGO_TARGET_DIR=/workspace/target \
|
||
VIRTUAL_ENV=/opt/dynamo/venv \
|
||
PATH=/opt/dynamo/venv/bin:/usr/local/cargo/bin:$PATH
|
||
|
||
# Copy Rust/Cargo/Maturin from the concatenated framework stages.
|
||
# - Rust/Cargo: from `wheel_builder` (already installed there)
|
||
# - maturin: from `wheel_builder` venv (installed there via uv pip)
|
||
COPY --from=wheel_builder --chown=dynamo:0 --chmod=775 /usr/local/rustup /home/dynamo/.rustup
|
||
COPY --from=wheel_builder --chown=dynamo:0 --chmod=775 /usr/local/cargo /usr/local/cargo
|
||
COPY --from=wheel_builder --chown=dynamo:0 --chmod=775 /workspace/.venv/bin/maturin /usr/local/bin/maturin
|
||
|
||
{% if framework == "sglang" %}
|
||
# SGLang: Create venv with --system-site-packages to inherit runtime packages
|
||
COPY --from=ghcr.io/astral-sh/uv:0.10.7 /uv /tmp/uv-binary
|
||
RUN mkdir -p /opt/dynamo/venv && \
|
||
python3 -m venv --system-site-packages /opt/dynamo/venv && \
|
||
cp -r --no-preserve=mode /usr/local/lib/python${PYTHON_VERSION}/dist-packages/* \
|
||
/opt/dynamo/venv/lib/python${PYTHON_VERSION}/site-packages/ && \
|
||
cp /tmp/uv-binary /opt/dynamo/venv/bin/uv && \
|
||
chmod +x /opt/dynamo/venv/bin/uv && \
|
||
pip install --ignore-installed maturin[patchelf]
|
||
{% elif framework == "dynamo" %}
|
||
# framework=none: Create venv if runtime stage didn't already provide one
|
||
RUN if [ ! -d /opt/dynamo/venv ]; then \
|
||
mkdir -p /opt/dynamo && \
|
||
python3 -m venv /opt/dynamo/venv; \
|
||
fi
|
||
{% endif %}
|
||
|
||
# Initialize Git LFS for the dynamo user (required for requirements with lfs=true)
|
||
RUN git lfs install
|
||
|
||
# Install only the ADDITIONAL dev/test dependencies.
|
||
# Runtime deps (common, framework, planner, benchmark) are already installed
|
||
# in the parent runtime image — re-resolving them here would risk version drift.
|
||
# SGLang specific: Reinstall pytest to ensure venv has pytest executable with correct shebang
|
||
ARG FRAMEWORK
|
||
RUN --mount=type=bind,source=./container/deps/requirements.dev.txt,target=/tmp/requirements.dev.txt \
|
||
--mount=type=bind,source=./container/deps/requirements.test.txt,target=/tmp/requirements.test.txt \
|
||
# Cache uv downloads; uv handles its own locking for this cache.
|
||
--mount=type=cache,target=/root/.cache/uv \
|
||
export UV_CACHE_DIR=/root/.cache/uv UV_GIT_LFS=1 UV_HTTP_TIMEOUT=300 UV_HTTP_RETRIES=5 && \
|
||
uv pip install \
|
||
--index-strategy unsafe-best-match \
|
||
--extra-index-url https://download.pytorch.org/whl/cu130 \
|
||
--requirement /tmp/requirements.dev.txt \
|
||
--requirement /tmp/requirements.test.txt && \
|
||
if [ "${FRAMEWORK}" = "sglang" ]; then \
|
||
uv pip install --force-reinstall --no-deps pytest; \
|
||
fi
|
||
|
||
# Copy entire workspace (old design - simpler for CI)
|
||
# .dockerignore filters out unwanted files (.git, build artifacts, etc.)
|
||
WORKDIR ${WORKSPACE_DIR}
|
||
# We don't actually need /workspace because for development, this must be mounted as a volume.
|
||
#COPY --chmod=775 --chown=dynamo:0 ./ ${WORKSPACE_DIR}/
|
||
|
||
RUN mkdir -p ${WORKSPACE_DIR} && chmod g+w ${WORKSPACE_DIR}
|
||
|
||
# Remove pre-built dynamo packages inherited from the runtime stage.
|
||
# The dev image builds from source, so these would conflict with the editable installs.
|
||
# NOTE: This does NOT reclaim disk space in the image (files still exist in lower layers).
|
||
# Space is only recovered if the image is later squashed / compacted (e.g. docker-squash,
|
||
# `docker build --squash`, or export/import).
|
||
RUN uv pip uninstall ai-dynamo ai-dynamo-runtime kvbm 2>/dev/null || true
|
||
|
||
# Install maturin only (no editable install of the dynamo package).
|
||
# /workspace is empty at build time — the repo is bind-mounted at container start, not COPYed.
|
||
# `uv pip install -e .` would fail here because there is no pyproject.toml in /workspace yet.
|
||
# The editable install must be done at runtime after the volume mount (e.g. `maturin develop`).
|
||
RUN if command -v uv >/dev/null 2>&1; then \
|
||
uv pip install maturin[patchelf] ; \
|
||
else \
|
||
python3 -m pip install maturin[patchelf] ; \
|
||
fi
|
||
|
||
# Set commit SHA for tests (passed via docker build as --build-arg)
|
||
ARG DYNAMO_COMMIT_SHA
|
||
ENV DYNAMO_COMMIT_SHA=$DYNAMO_COMMIT_SHA
|
||
|
||
# Setup dev launch banner (displayed on interactive shell entry)
|
||
RUN --mount=type=bind,source=./container/launch_message/dev.txt,target=/opt/dynamo/launch_message.txt \
|
||
sed '/^#\s/d' /opt/dynamo/launch_message.txt > /opt/dynamo/.launch_screen && \
|
||
chmod 755 /opt/dynamo/.launch_screen && \
|
||
echo 'cat /opt/dynamo/.launch_screen' >> /etc/bash.bashrc
|
||
|
||
{% if device == "xpu" %}
|
||
SHELL ["bash", "-c"]
|
||
CMD ["bash", "-c", "source /root/.bashrc && exec bash"]
|
||
{% else %}
|
||
ENTRYPOINT ["/opt/nvidia/nvidia_entrypoint.sh"]
|
||
CMD []
|
||
{% endif %}
|