Containers
Experimental
Container support is experimental. MPI is not supported in containers. Single-node CPU, NVIDIA GPU, and AMD GPU execution have been tested.
Pre-built Apptainer/Singularity containers let you run THOR without compiling from source.
Quick Start
thor-generic is the recommended image — same SIF runs on CPU, NVIDIA, or AMD GPU. Pick the backend at exec time:
# Download
apptainer pull oras://ghcr.io/cbyrohl/thor-generic:dev
# CPU (OpenMP via SSCP JIT)
apptainer run --env ACPP_VISIBILITY_MASK=omp thor-generic_dev.sif config.yaml
# NVIDIA GPU
apptainer run --nv --env ACPP_VISIBILITY_MASK=cuda thor-generic_dev.sif config.yaml
# AMD GPU
apptainer run --rocm --env ACPP_VISIBILITY_MASK=hip thor-generic_dev.sif config.yaml
Set device: in your config YAML to match — cpu, cpu-openmp, or gpu.
Updating an existing image
The :dev tag is updated in place whenever a new build is published, so your local .sif can fall behind. apptainer pull won't overwrite the existing file by default — pass --force:
Setting Thread Count
Control the number of OpenMP threads with OMP_NUM_THREADS:
apptainer run --env OMP_NUM_THREADS=8 --env ACPP_VISIBILITY_MASK=omp \
thor-generic_dev.sif config.yaml
GPU usage
thor-generic carries both the CUDA and ROCm runtimes in one image; backend selection happens at exec time, not build time.
NVIDIA
Requirements:
- NVIDIA driver installed on the host
- The
--nvflag (binds host GPU drivers into the container) device: gpuin your config YAML
AMD
Requirements:
- ROCm-supported AMD GPU (Instinct MI-series, supported Radeon Pro / RX)
amdgpukernel module loaded (/dev/kfdand/dev/dri/renderD*must exist)- The
--rocmflag device: gpuin your config YAML
Fedora hosts: libnuma ABI conflict with --rocm
Apptainer's --rocm binds the host's libnuma.so.1. On Fedora ≥ 39 (glibc ≥ 2.40) this conflicts with the Ubuntu 24.04 glibc inside the container (GLIBC_ABI_GNU2_TLS not found) and every acpp backend fails to load. Workaround:
Unsupported AMD targets
ROCm ships device-libs bitcode only for officially supported gfxes. Consumer iGPUs (e.g. gfx1036 on Ryzen 9000 desktop) hit Code object construction failed at JIT time. Sometimes --env HSA_OVERRIDE_GFX_VERSION=10.3.0 (gfx1030) works as a hack; for production use, target a supported device.
Selecting FP32 or FP64
The GPU-capable images ship two binaries side-by-side — FP64 (thor-fp64, default) and FP32 (thor-fp32) — and a wrapper that picks one based on the THOR_PRECISION env var:
# FP64 (default)
apptainer run --nv --env ACPP_VISIBILITY_MASK=cuda thor-generic_dev.sif config.yaml
# FP32 — typically much faster on consumer GPUs (RTX, GTX, RDNA),
# which have crippled FP64 throughput
apptainer run --nv --env ACPP_VISIBILITY_MASK=cuda --env THOR_PRECISION=fp32 \
thor-generic_dev.sif config.yaml
FP32 is reduced-precision
Float32 is sufficient for many MCRT runs but can degrade results in long photon chains, high optical depths, or tight numerical tolerances. Validate against an FP64 reference for your problem before relying on FP32 production runs.
Available Images
| Image | Description | Precision |
|---|---|---|
thor-generic |
Portable SYCL build. CUDA + ROCm runtimes bundled; backend chosen at exec time. | FP64 + FP32 |
thor-omp |
CPU-only AOT build (AdaptiveCpp OMP backend). Smaller; best raw CPU perf. | FP64 |
thor-env-only |
Build environment (no thor binary) — for compiling THOR yourself. |
— |
Building Custom Containers
For Development Only
Most users should use the pre-built thor-omp image above.
pip install typer pyyaml jinja2
cd apptainer
# List configurations
./build.py list
# Build container
./build.py build omp
# Build from local source (requires initialized submodules)
./build.py build omp --local
Cluster Configurations
Configurations in apptainer/clusters/:
| Configuration | Description |
|---|---|
generic.yaml |
Portable SSCP build with CUDA + ROCm runtimes; FP64 + FP32 binaries |
omp.yaml |
OMP AOT backend, FP64, smaller image |
env-only.yaml |
Build environment only (no THOR binary) |
example-hpc.yaml |
Template for custom cluster-specific configs |
Create a custom config:
cp apptainer/clusters/example-hpc.yaml apptainer/clusters/my-cluster.yaml
# Edit march for your CPU (e.g., znver3 for AMD EPYC Milan)
./build.py build my-cluster
Common march values: x86-64-v3 (generic), znver2/znver3/znver4 (AMD EPYC), skylake-avx512/icelake-server (Intel Xeon).
Shipping FP64 + FP32 binaries
Set dual_precision: true in your cluster YAML to build both binaries into the
same image with a THOR_PRECISION runtime dispatch wrapper (see
Selecting FP32 or FP64 above). The second cmake
build runs in build-fp32/ with -DTHOR_USE_FP32=ON; on-disk overhead is
small (~10 MB compressed) because the two binaries share most of their code.
Useful for any GPU-targeted config; off by default for CPU configs since FP32
yields little benefit on the CPU path.
Environment Container
For development without rebuilding containers:
./build.py build env-only
# Build and run THOR from source
apptainer exec build/thor-env-only.sif bash -c \
"cd /path/to/thor && cmake -B build && cmake --build build -j\$(nproc)"
apptainer exec build/thor-env-only.sif /path/to/thor/build/src/thor config.yaml
Accessing Host Data
By default, Apptainer only mounts your home directory and a few system paths inside the container. If your simulation data or Cloudy tables live elsewhere (e.g. /virgotng/, /scratch/, /data/), you must bind-mount those paths:
# Bind a single path
apptainer run --bind /virgotng thor-omp_dev.sif config.yaml
# Bind multiple paths
apptainer run --bind /virgotng,/scratch thor-omp_dev.sif config.yaml
Alternatively, set the APPTAINER_BIND environment variable (useful in job scripts):
For full details, see the Apptainer Bind Paths documentation.
Troubleshooting
fuse2fs not found / gocryptfs not found warnings: These informational messages from Apptainer can be safely ignored. THOR containers use Docker-based images and do not require EXT3 filesystem mounting or encrypted overlays.
Slow performance: Check march matches your CPU. Set OMP_NUM_THREADS appropriately.