Skip to content

Add CUDA 13.3 / DOCA multi-arch NCCL test image for GB300 fleets - #8

Open
sirajrauff wants to merge 1 commit into
mainfrom
feat/cuda13-doca-image
Open

Add CUDA 13.3 / DOCA multi-arch NCCL test image for GB300 fleets#8
sirajrauff wants to merge 1 commit into
mainfrom
feat/cuda13-doca-image

Conversation

@sirajrauff

Copy link
Copy Markdown

What

Adds docker/Dockerfile.cuda13, the image we use for NCCL testing on Grace-Blackwell (GB300, aarch64) hardware, plus a docker/README.md section describing it.

It differs from docker/Dockerfile in four ways:

  • Multi-arch. Builds linux/amd64 and linux/arm64; every arch-specific path derives from TARGETARCH or dpkg-architecture, so there is no hardcoded x86_64.
  • DOCA-OFED userspace instead of the HPC-X tarball plus Ubuntu rdma-core, matching hosts that run a DOCA stack on CX7 and CX8. OpenMPI is installed after the DOCA repo so apt resolves it against DOCA's libibverbs.
  • CUDA 13. GB300 is sm_103, and ptxas in 12.9 and earlier rejects it.
  • sshd included, so one image serves as both MPIJob launcher and worker (mpi-operator v2 execs workers over ssh).

Build inputs are pinned by ARGDOCA_VERSION, LIBNCCL_VERSION, NCCL_TESTS_REF — so a rebuild reproduces the same image; each is overridable at build time.

The open-files limit

The line worth reviewing closely:

RUN printf '* soft nofile 1048576\nroot soft nofile 1048576\n' > /etc/security/limits.d/99-nofile.conf

pam_limits clamps sshd sessions to Ubuntu's default soft RLIMIT_NOFILE of 1024. NCCL's proxy service polls an fd array sized roughly one entry per rank, so above 1024 ranks the poll() fails with EINVAL, the proxy thread exits, lazy transport connect never completes, and the job hangs silently — TCP bootstrap chatter continues, so it still looks alive. Since mpirun reaches workers as mpirun -> sshd -> orted, every rank inherits the clamped limit.

It reproduces purely by scale: jobs at or below 1024 ranks pass, jobs above 1024 ranks hang indefinitely. With this override in place, the same job completes.

The entries are soft only, deliberately. Raising the hard limit requires CAP_SYS_RESOURCE, which pods do not get, and sshd's PAM stack runs pam_limits as required — so on any runtime whose hard ceiling sits below the requested value, setrlimit fails and the module returns PAM_PERM_DENIED, refusing every worker login. A soft-only entry is clamped to the runtime's own ceiling instead, so it degrades to "as high as this host allows" rather than failing closed. Raise the hard ceiling in the CRI (default_ulimits) where a host needs more.

Testing

Built with docker buildx for linux/arm64 and published as ghcr.io/voltagepark/nccl-tests:cuda13.3.0-ubuntu24.04-doca-ssh-server.

Exercised as a Kubeflow MPIJob (all_reduce_perf, 512M–8G) across a scale ramp — single node, single NVL72 rack, multiple racks, then a full cluster of 1k+ GPUs. Every stage completed with zero out-of-bounds errors, and bus bandwidth at the largest scale stayed in the expected band relative to the single-rack baseline. The >1024-rank hang described above was the only failure mode encountered, and it disappeared once the limit override was added.

Note that the published image was built from an earlier revision of this file; the pins, the soft-only limits change, and the sshd CMD have not themselves been rebuilt yet.

Successor to docker/Dockerfile for GB300/Grace fleets: builds for amd64 and
arm64, takes libibverbs from the DOCA-OFED repo instead of the HPC-X tarball,
and moves to CUDA 13 (sm_103 is rejected by ptxas in 12.9 and earlier). Bundles
sshd so one image serves as both MPIJob launcher and worker. DOCA, NCCL and the
nccl-tests ref are pinned by ARG so rebuilds are reproducible.

Also raises the PAM open-files limit. pam_limits clamps sshd sessions to the
Ubuntu default soft RLIMIT_NOFILE=1024, and NCCL's proxy service polls roughly
one fd per rank, so jobs above 1024 ranks fail with poll() EINVAL and hang
silently until the limit is lifted. Soft only: raising the hard limit needs
CAP_SYS_RESOURCE, and sshd runs pam_limits as required, so a hard bump the
runtime cannot satisfy would refuse the login outright.
@sirajrauff sirajrauff self-assigned this Jul 25, 2026
@sirajrauff sirajrauff added the enhancement New feature or request label Jul 25, 2026
Comment thread docker/Dockerfile.cuda13
Comment on lines +1 to +139
# NCCL tests image — CUDA 13.3 / Ubuntu 24.04 / DOCA-OFED userspace / multi-arch.
#
# Successor to docker/Dockerfile (CUDA 12.6 / MLNX_OFED-era HPC-X, x86_64-only):
# - Multi-arch: amd64 + arm64 (GB300/Grace fleets are aarch64). All arch-specific
# paths derive from TARGETARCH / dpkg multiarch — no hardcoded x86_64.
# - DOCA-OFED userspace replaces the HPC-X tarball + Ubuntu rdma-core (the
# "upgrade to DOCA_OFED" note in the legacy Dockerfile; our fleets run
# DOCA host stacks, CX7 and CX8 alike).
# - CUDA 13.3 devel base: GB300 (sm_103) needs CUDA >= 13; ptxas in <= 12.9
# rejects sm_103a.
# - NCCL from the CUDA 13 apt repo (>= 2.30; MNNVL-capable), pinned by
# default via LIBNCCL_VERSION.
# - openssh-server for Kubeflow MPIJob workers (mpi-operator v2 execs
# workers over ssh); mpirun from Ubuntu OpenMPI for the launcher.
# - limits.d nofile override (1048576 soft) so pam_limits stops clamping sshd
# sessions to 1024 fds — required for >1024-rank NCCL jobs.
#
# Build inputs pinned by ARG: DOCA_VERSION, LIBNCCL_VERSION, NCCL_TESTS_REF.
# Override any of them at build time.
#
# Build (multi-arch, push):
# docker buildx build -f docker/Dockerfile.cuda13 \
# --platform linux/arm64,linux/amd64 \
# -t ghcr.io/voltagepark/nccl-tests:cuda13.3.0-ubuntu24.04-doca-ssh-server \
# --push docker/

ARG CUDA_VERSION=13.3.0
ARG UBUNTU_VERSION=24.04
FROM nvcr.io/nvidia/cuda:${CUDA_VERSION}-devel-ubuntu${UBUNTU_VERSION}

# ARGs above the first FROM are visible only to FROM; re-declare the ones the
# build stage interpolates (the DOCA repo path carries the Ubuntu release).
ARG UBUNTU_VERSION
ARG TARGETARCH
ENV DEBIAN_FRONTEND=noninteractive

# Base tooling + OpenMPI + sshd. rdma/ibverbs come from the DOCA repo below,
# NOT Ubuntu's rdma-core, so keep them out of this layer.
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl git build-essential \
libnuma1 libnuma-dev \
openssh-server openssh-client \
&& rm -rf /var/lib/apt/lists/*

# DOCA-OFED userspace (libibverbs/mlx5 provider/librdmacm from NVIDIA's DOCA
# repo). Repo arch dirs: x86_64 | arm64-sbsa. Pinned to an explicit release:
# userspace/host-driver skew is the failure class this image exists to
# diagnose, so a rebuild must not silently swap the verbs stack. Build with
# --build-arg DOCA_VERSION=latest to float deliberately.
ARG DOCA_VERSION=3.4.0
RUN set -eux; \
case "${TARGETARCH}" in \
amd64) DOCA_ARCH=x86_64 ;; \
arm64) DOCA_ARCH=arm64-sbsa ;; \
*) echo "unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \
esac; \
DOCA_REPO="https://linux.mellanox.com/public/repo/doca/${DOCA_VERSION}/ubuntu${UBUNTU_VERSION}/${DOCA_ARCH}"; \
curl -fsSL "${DOCA_REPO}/doca_keyring.gpg" \
-o /usr/share/keyrings/doca.gpg; \
echo "deb [signed-by=/usr/share/keyrings/doca.gpg] ${DOCA_REPO} ./" \
> /etc/apt/sources.list.d/doca.list; \
apt-get update; \
apt-get install -y --no-install-recommends \
libibverbs1 libibverbs-dev ibverbs-providers ibverbs-utils \
librdmacm1 librdmacm-dev rdma-core infiniband-diags; \
dpkg -s libibverbs1 | grep ^Version; \
rm -rf /var/lib/apt/lists/*

# OpenMPI AFTER the DOCA verbs stack so apt resolves against DOCA's libibverbs
# (the doca-ofed-userspace meta is deliberately NOT used: it hard-depends on
# DOCA's own OpenMPI rc + UCX builds, which NCCL does not need — NCCL drives
# libibverbs directly; MPI is only the test launcher).
RUN apt-get update && apt-get install -y --no-install-recommends \
openmpi-bin libopenmpi-dev && rm -rf /var/lib/apt/lists/*

# NCCL from the CUDA apt repo, pinned to the build validated at scale on GB300.
# Pass an empty string to take whatever is newest for CUDA 13 instead.
ARG LIBNCCL_VERSION=2.30.7-1+cuda13.3
RUN set -eux; \
apt-get update; \
if [ -n "${LIBNCCL_VERSION}" ]; then \
apt-get install -y --no-install-recommends "libnccl2=${LIBNCCL_VERSION}" "libnccl-dev=${LIBNCCL_VERSION}"; \
else \
apt-get install -y --no-install-recommends libnccl2 libnccl-dev; \
fi; \
apt-mark hold libnccl2 libnccl-dev; \
dpkg -s libnccl2 | grep ^Version; \
rm -f /etc/apt/sources.list.d/doca.list; \
rm -rf /var/lib/apt/lists/*

# MPI-enabled NCCL test binaries, built against a pinned upstream ref: the
# default NVCC_GENCODE list lives in the cloned tree's src/common.mk, so an
# unpinned clone would let an upstream edit drop the sm_100 cubin GB300 runs
# on. /opt/openmpi is an arch-independent symlink so MPI_HOME can be a fixed
# ENV for in-container rebuilds.
ARG NCCL_TESTS_REF=v2.19.6
RUN set -eux; \
ln -s "/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/openmpi" /opt/openmpi; \
git clone --depth 1 --branch "${NCCL_TESTS_REF}" \
https://github.com/NVIDIA/nccl-tests.git /opt/nccl-tests; \
cd /opt/nccl-tests; \
make -j"$(nproc)" MPI=1 MPI_HOME=/opt/openmpi

ENV MPI_HOME=/opt/openmpi

# sshd runtime prereqs for MPIJob workers (mpi-operator injects keys/config),
# plus non-interactive ssh client defaults for the launcher: pod hostnames are
# ephemeral, so host-key pinning is meaningless inside a job and the launcher
# must never block on verification.
# The greps assert the substitutions landed: sed exits 0 on zero matches, so a
# future openssh package that renames these directives would otherwise ship an
# image where StrictModes silently reverts to yes and every worker rejects the
# mpi-operator-mounted key on mode grounds.
RUN mkdir -p /run/sshd && \
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config && \
sed -i 's/^#\?StrictModes.*/StrictModes no/' /etc/ssh/sshd_config && \
grep -qE '^PermitRootLogin yes' /etc/ssh/sshd_config && \
grep -qE '^StrictModes no' /etc/ssh/sshd_config && \
printf 'Host *\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null\n LogLevel ERROR\n' >> /etc/ssh/ssh_config

# Raise the PAM open-files limit for ssh sessions. pam_limits clamps sshd
# sessions (mpirun -> sshd -> orted) to the Ubuntu default soft
# RLIMIT_NOFILE=1024, and NCCL's proxy service needs ~one fd per rank —
# >1024-rank jobs fail with proxy poll() EINVAL without this.
#
# Soft only, deliberately. Raising the HARD limit needs CAP_SYS_RESOURCE, which
# pods do not get; sshd's PAM stack runs pam_limits as `required`, so a
# setrlimit that fails on a runtime whose hard ceiling is below this value
# returns PAM_PERM_DENIED and every worker login is refused. A soft-only entry
# is clamped to the runtime's own hard limit instead, so it degrades to "as high
# as this host allows". Raise the hard ceiling in the CRI (default_ulimits).
RUN printf '* soft nofile 1048576\nroot soft nofile 1048576\n' > /etc/security/limits.d/99-nofile.conf

WORKDIR /opt/nccl-tests

# sshd by default so the image matches its -ssh-server tag: mpi-operator only
# injects the sshd command when the worker container declares neither command
# nor args, and the launcher overrides CMD with its own args either way.
CMD ["/usr/sbin/sshd", "-D", "-e"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Docker container runs as default root user - medium severity
By default, containers are run with root privileges and also run as the root user inside the container. Running the app as root gives a hacker who was able to hack the application instant root access to the Docker host, which could help them to escalate a hack.

Show fix

Remediation: Add 'USER username' to the end of your file.

Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant