How-to Guide
Task-oriented recipes for common ClimaComms uses. If you are new to ClimaComms, start with Getting Started.
How to run a script on a GPU
Set the CLIMACOMMS_DEVICE environment variable to CUDA, either in your shell (note: no spaces around =)
export CLIMACOMMS_DEVICE="CUDA"or at the top of your Julia script, before the device is constructed:
ENV["CLIMACOMMS_DEVICE"] = "CUDA"CUDA.jl must be installed in your environment and loaded before the device is used; adding ClimaComms.@import_required_backends at the top of the script (after import ClimaComms) takes care of loading.
How to run a script with MPI
Set CLIMACOMMS_CONTEXT to MPI and launch Julia with an MPI launcher:
CLIMACOMMS_CONTEXT=MPI mpiexec -n 4 julia --project script.jlMPI.jl must be installed in your environment; use ClimaComms.@import_required_backends to load it. Call ClimaComms.init on the context before performing any communication; it initializes MPI and, for GPU runs, assigns a GPU to each rank on a node.
How to allocate arrays on the right device
Use ClimaComms.array_type, which returns Array for CPU devices and CuArray for CUDADevice:
ArrayType = ClimaComms.array_type(ClimaComms.device())
x = ArrayType([1.0, 2.0, 3.0])
zeros_on_device = ArrayType(zeros(100))How to write functions that specialize on the device
Devices are empty structs, so you can dispatch on them like any other type. This is how low-level CliMA code provides CPU and GPU implementations of the same operation:
import ClimaComms: AbstractCPUDevice, CUDADevice
import CUDA
my_allocate(::AbstractCPUDevice, data) = Array(data)
my_allocate(::CUDADevice, data) = CUDA.CuArray(data)Higher-level code can then call my_allocate(device, data) without knowing what hardware it runs on.
How to parallelize a loop on any device
Use ClimaComms.@threaded, a device-flexible generalization of Threads.@threads. The same loop runs serially on a CPUSingleThreaded device, across threads on a CPUMultiThreaded device, and as a CUDA kernel on a CUDADevice:
function threaded_add!(a, b, device)
ClimaComms.@threaded device for i in eachindex(a, b)
a[i] += b[i]
end
endThe macro accepts options for performance tuning:
coarsencontrols how many loop iterations each thread evaluates, reducing the overhead of launching threads;block_sizecontrols the number of threads per block on GPUs.
Lazy iterators (zip, enumerate, Iterators.product, generator expressions) and multiple loop variables are supported. See the ClimaComms.@threaded docstring for details and for the type-inference requirements of GPU execution.
On GPUs, all values used in the loop body must have statically inferrable types. In particular, wrap @threaded loops in functions rather than executing them at global scope.
How to time and synchronize device code
GPU kernels launch asynchronously, so Base.@time measures only the launch cost. Use the device-flexible macros, which fall back to the Base versions on CPUs and use the CUDA.jl versions on GPUs:
device = ClimaComms.device()
ClimaComms.@time device my_kernel!(x) # @time / CUDA.@time
dt = ClimaComms.@elapsed device my_kernel!(x) # @elapsed / CUDA.@elapsed
ClimaComms.@sync device my_kernel!(x) # @sync / CUDA.@syncUse ClimaComms.@cuda_sync when you only need to synchronize GPU work (it is a no-op on CPUs, avoiding the overhead of Base.@sync).
How to print or log only from the root process
Guard output with ClimaComms.iamroot:
ClimaComms.iamroot(context) && @info "This prints once, not once per rank"Or install a logger that silences non-root processes globally:
using Logging
Logging.global_logger(ClimaComms.OnlyRootLogger(context))See Logging for per-rank log files (ClimaComms.FileLogger) and rank-prefixed messages (ClimaComms.MPILogger).
How to query available device memory
ClimaComms.free_memory and ClimaComms.total_memory report bytes of memory on the device (system memory for CPU devices, GPU memory for CUDADevice):
device = ClimaComms.device()
frac_free = ClimaComms.free_memory(device) / ClimaComms.total_memory(device)How to inspect the current configuration
Use Base.summary on a context to print the context type, device, and (for MPI runs) the rank layout across nodes:
summary(stdout, context)To verify that MPI and CUDA modules are set up correctly on a cluster, see this guide.