Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions containers/rocker_ml_cuda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# rocker_ml_cuda

GPU-enabled RStudio container: `rocker/ml` plus CUDA PyTorch (used from R
through reticulate), `tabpfn`, and tidymodels packages. Currently in testing
for the OOD RStudio app.

## Pins

- **Base image:** `rocker/ml` is pinned by digest in `rocker_ml_cuda.def`
(it was `py3.14` / `latest` as of 2026-10-04). `rocker/ml` no longer
publishes R version tags, so a tag alone would change under us on rebuild.
- **PyTorch:** `torch==2.14.0` from the `cu126` index. This is the last
release with a wheel for Volta (V100) GPUs, so don't upgrade it while
V100s are in use.
- **Driver:** tested on nodes with NVIDIA driver 525.147.05 (CUDA 12.0).

## Build

On a cluster node with apptainer (needs network access):

```
./build_container.sh
```

This builds the image, checks that `rserver`, `rsession`, `R`, and
`rstudio-server` exist inside it, moves it to
`/hpc/<cluster>/containers/testing/rocker/`, and writes
`modules/testing/rocker_ml_cuda/<version>.lua` in this repo.

## Check the GPU

```
apptainer exec --nv /hpc/<cluster>/containers/testing/rocker/rocker_ml_cuda_1.0.0.sif nvidia-smi
apptainer exec --nv /hpc/<cluster>/containers/testing/rocker/rocker_ml_cuda_1.0.0.sif \
/opt/venv/bin/python -c 'import torch; print(torch.cuda.is_available())'
```

## Notes for OOD

R is installed from apt in this image (`/usr/bin/R`, libR in
`/usr/lib/R/lib`), not under `/usr/local` like other rocker images. The
RStudio app's rsession wrapper must also add `/.singularity.d/libs` and
`/opt/venv/bin` back to the paths after sourcing the host environment, or
the GPU and the Python venv are not visible in RStudio. See
`ood/rstudio/template/script.sh.erb`.
111 changes: 111 additions & 0 deletions containers/rocker_ml_cuda/build_container.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/bin/bash -e
# build GPU-enabled apptainer container for RStudio (rocker/ml + CUDA PyTorch)
# base image and package pins are in rocker_ml_cuda.def

# specify version
VERSION="1.0.0"
IMAGE_NAME="rocker_ml_cuda"
CONTAINER_NAME=${IMAGE_NAME}_${VERSION}.sif

echo "Building ${CONTAINER_NAME}"

# .def and module file paths below are relative to this directory
cd "$(dirname "$0")"

# build the container
module purge
module load apptainer
unset APPTAINER_BIND
apptainer build --fakeroot ${CONTAINER_NAME} ${IMAGE_NAME}.def

# get the paths for rserver, rsession, and R inside the container. We need
# these for the portal, so it's useful to "precompute" and load
RSESSION_EXEC="/usr/lib/rstudio-server/bin/rsession"
RSERVER_EXEC="/usr/lib/rstudio-server/bin/rserver"
R_EXEC=$(apptainer exec ${CONTAINER_NAME} which R)
# verify
for EXEC in ${RSESSION_EXEC} ${RSERVER_EXEC} ${R_EXEC}
do
if ! apptainer exec ${CONTAINER_NAME} test -x ${EXEC} &> /dev/null
then
echo "${EXEC} not found in the container"
exit 12
fi
done
if ! apptainer exec ${CONTAINER_NAME} which rstudio-server &> /dev/null
then
echo "rstudio-server not found in the container (needed for the OOD idle timeout)"
exit 12
fi

# move container to /hpc/{sys}/containers/testing/ (kept out of the main
# containers directory while in testing)
CLUSTER=$(scontrol show config | grep ClusterName | grep -oP '= \K.+')
if [ "$CLUSTER" = "nvidia" ]; then
CLUSTER="mp"
fi
CONTAINER_DIR=/hpc/${CLUSTER}/containers/testing/rocker
mkdir -p ${CONTAINER_DIR}
mv ${CONTAINER_NAME} ${CONTAINER_DIR}/${CONTAINER_NAME}

# create a module file in ../../modules/ (in the git repo)
RUN_COMMAND="apptainer run --nv "

mkdir -p ../../modules/testing/${IMAGE_NAME}
MODULE_FILE=../../modules/testing/${IMAGE_NAME}/${VERSION}.lua
(
sed 's/^ \{2\}//' > "$MODULE_FILE" << EOL

help([[
Name: R + CUDA PyTorch
Version: ${VERSION}
Website: https://www.r-project.org/

R is a language and environment for statistical computing and graphics.
This container includes GPU-enabled PyTorch (via reticulate) for V100 GPUs.

To use R:

R -- launches an R terminal
Rscript script -- run a script

]])
whatis("Name: R + CUDA PyTorch")
whatis("Version: rocker/ml:py3.14 + torch 2.14.0 (cu126)")
whatis("Category: R")
whatis("URL: https://hub.docker.com/r/rocker/ml")
whatis("Description: Provides access to R and Rstudio with GPU-enabled PyTorch through a Rocker container built with Apptainer")
family("R")

always_load('apptainer')
local sif_file = '${CONTAINER_DIR}/${CONTAINER_NAME}'

local work_dir = '/work'
local lustre_work = '/lustre/work/client'
local scratch_dir = os.getenv("SCRATCH")
local home = os.getenv("HOME")
local user_libs = pathJoin(home, 'R/${IMAGE_NAME}/${VERSION}')

function build_command(app)
local cmd = '${RUN_COMMAND} --env R_LIBS_USER=' .. user_libs .. ' -B ' .. scratch_dir .. ',' .. work_dir .. ',' .. lustre_work .. ' ' .. sif_file .. ' ' .. app
local sh_ending = ' "\$@"'
local csh_ending = ' \$*'
local sh_cmd = cmd .. sh_ending
local csh_cmd = cmd .. csh_ending
set_shell_function(app, sh_cmd, csh_cmd)
end

setenv('CONTAINER_RSESSION', '${RSESSION_EXEC}')
setenv('CONTAINER_RSERVER', '${RSERVER_EXEC}')
setenv('CONTAINER_R', '${R_EXEC}')
setenv('CONTAINER_IMAGE', '${CONTAINER_DIR}/${CONTAINER_NAME}')
setenv('R_LIBS_USER', user_libs)
unsetenv('XDG_RUNTIME_DIR')

build_command('R')
build_command('Rscript')
EOL
)

echo "Container: ${CONTAINER_DIR}/${CONTAINER_NAME}"
echo "Module: ${MODULE_FILE}"
22 changes: 22 additions & 0 deletions containers/rocker_ml_cuda/rocker_ml_cuda.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Bootstrap: docker
# rocker/ml:py3.14 (== latest as of 2026-10-04), pinned by digest because
# rocker/ml no longer publishes R version tags
From: rocker/ml@sha256:77443c5ff6422556809910000db4fb3576cb00609f39474bdb9f04edc3eeddca

%labels
Author Mateo Langston Smith <mlangstonsmith@smu.edu>
Version 1.0.0

%post
export DEBIAN_FRONTEND=noninteractive

# GPU PyTorch for V100 (cu126; 2.14.0 is the last release with a Volta wheel)
/opt/venv/bin/pip install --no-cache-dir torch==2.14.0 --index-url https://download.pytorch.org/whl/cu126
/opt/venv/bin/pip install --no-cache-dir tabpfn

# R packages (uses the image's default CRAN/binary repo settings)
Rscript -e 'install.packages(c("tidymodels","probably","ranger","tictoc","modeldata","Lahman","tabpfn"))'

%environment
export CUDA_MODULE_LOADING=LAZY
export RETICULATE_PYTHON=/opt/venv/bin/python
49 changes: 49 additions & 0 deletions modules/testing/rocker_ml_cuda/1.0.0.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

help([[
Name: R + CUDA PyTorch
Version: 1.0.0
Website: https://www.r-project.org/

R is a language and environment for statistical computing and graphics.
This container includes GPU-enabled PyTorch (via reticulate) for V100 GPUs.

To use R:

R -- launches an R terminal
Rscript script -- run a script

]])
whatis("Name: R + CUDA PyTorch")
whatis("Version: rocker/ml:py3.14 + torch 2.14.0 (cu126)")
whatis("Category: R")
whatis("URL: https://hub.docker.com/r/rocker/ml")
whatis("Description: Provides access to R and Rstudio with GPU-enabled PyTorch through a Rocker container built with Apptainer")
family("R")

always_load('apptainer')
local sif_file = '/hpc/m3/containers/testing/rocker/rocker_ml_cuda_1.0.0.sif'

local work_dir = '/work'
local lustre_work = '/lustre/work/client'
local scratch_dir = os.getenv("SCRATCH")
local home = os.getenv("HOME")
local user_libs = pathJoin(home, 'R/rocker_ml_cuda/1.0.0')

function build_command(app)
local cmd = 'apptainer run --nv --env R_LIBS_USER=' .. user_libs .. ' -B ' .. scratch_dir .. ',' .. work_dir .. ',' .. lustre_work .. ' ' .. sif_file .. ' ' .. app
local sh_ending = ' "$@"'
local csh_ending = ' $*'
local sh_cmd = cmd .. sh_ending
local csh_cmd = cmd .. csh_ending
set_shell_function(app, sh_cmd, csh_cmd)
end

setenv('CONTAINER_RSESSION', '/usr/lib/rstudio-server/bin/rsession')
setenv('CONTAINER_RSERVER', '/usr/lib/rstudio-server/bin/rserver')
setenv('CONTAINER_R', '/usr/bin/R')
setenv('CONTAINER_IMAGE', '/hpc/m3/containers/testing/rocker/rocker_ml_cuda_1.0.0.sif')
setenv('R_LIBS_USER', user_libs)
unsetenv('XDG_RUNTIME_DIR')

build_command('R')
build_command('Rscript')
5 changes: 4 additions & 1 deletion ood/rstudio/form.yml.erb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ attributes:
<%- if CheckAccounts.Accounts.any? { |word| word.include?("kbnk") } | CheckAccounts.Accounts.any? { |word| word.include?("jlagrone") }-%>
- [ "R 4.5.1 (custom kbnk)", "kbnk/R/4.5.1" ]
<%- end -%>
<%- if CheckAccounts.Accounts.any? { |word| word.include?("csouth") } | CheckAccounts.Accounts.any? { |word| word.include?("jlagrone") }-%>
- [ "4.6.1 + GPU", "/hpc/m3/hpc_docs/modules/testing/rocker_ml_cuda/1.0.0.lua" ]
<%- end -%>
environment:
widget: text_area
label: "Custom Environment Settings"
Expand All @@ -80,7 +83,7 @@ attributes:
[SLURM partition]: https://southernmethodistuniversity.github.io/hpc_docs/about/queues.html#m3-queues
options:
<%- CheckQueues.Queues.each do |q| -%>
<%- if q[4].to_i == 0 -%> #disable gpu queues
<%- if q[4].to_i == 0 || CheckAccounts.Accounts.any? { |word| word.include?("csouth") } || CheckAccounts.Accounts.any? { |word| word.include?("jlagrone") } -%> #disable gpu queues (except for GPU testers)
- ["<%= q[0] %>", "<%= q[0] %>",
data-max-bc-num-hours: "<%= q[1] %>",
data-max-bc-num-cores: "<%= q[2] %>",
Expand Down
11 changes: 10 additions & 1 deletion ood/rstudio/template/script.sh.erb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ sed 's/^ \{2\}//' > "${RSESSION_WRAPPER_FILE}" << EOL

# make sure the container libR is visible inside container
export LD_LIBRARY_PATH="/usr/local/lib/R/lib${LD_LIBRARY_PATH:+":$LD_LIBRARY_PATH"}"
<%- if context.r_module.include?("rocker_ml_cuda") -%>

# environment.txt and the line above replace the container's paths, so
# restore libR (apt-installed in this image), the host driver libs bound
# by --nv, and the image's python venv (CUDA comes from pip wheels there)
export LD_LIBRARY_PATH="/usr/lib/R/lib:/.singularity.d/libs:\${LD_LIBRARY_PATH}"
export PATH="/opt/venv/bin:\${PATH}:/usr/lib/rstudio-server/bin/quarto/bin"
export CUDA_MODULE_LOADING="\${CUDA_MODULE_LOADING:-LAZY}"
<%- end -%>

# Log all output from this script
export RSESSION_LOG_FILE="${PWD}/rsession.log"
Expand Down Expand Up @@ -195,7 +204,7 @@ unset APPTAINER_BIND

export APPTAINERENV_XDG_CONFIG_DIRS="/etc/"
export APPTAINERENV_RSTUDIO_CONFIG_DIRS=$RSTUDIO_CONFIG_DIRS
apptainer exec \
apptainer exec <%= context.bc_num_gpus.to_i > 0 ? "--nv" : "" %> \
-B ${TMPDIR}:/tmp \
-B ${TMPDIR}/run:/run \
-B ${TMPDIR}/var/lib:/var/lib/rstudio-server \
Expand Down
Loading