GPU Troubleshooting
The basics of GPU code are explained in Chapter 9. This page collects what to check when a problem occurs during building or running. Source paths are relative to the folder where you cloned MISO. The error messages were checked with nvcc from CUDA 12.9 and an RTX 4090, and may differ slightly between versions.
1. Stops during CMake configuration
Symptom |
What to check |
|---|---|
|
Whether |
The settings are not applied after switching from the CPU version |
Whether you are reusing the same build folder. Use separate folders for the CPU and the GPU, or delete the folder and recreate it |
|
Whether, in your own |
An error that no GPU is found with |
The machine you build on has no GPU (for example, a login node). Specify the value of the GPU you will use explicitly (such as |
2. It does not compile
Error message (excerpt) |
Cause and remedy |
|---|---|
|
|
|
You wrote |
|
You use an owning array (such as |
|
You call a function without |
|
You declare |
g++ reports |
Whether you wrote the kernel outside |
Extended lambdas (lambdas marked __host__ __device__) have constraints on where they can be placed and how they capture, beyond those listed here. Check the section on extended lambdas in the NVIDIA CUDA C++ Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html) for the version you use.
3. It stops at run time
3.1 Common errors
Message |
Possible causes |
|---|---|
|
The lambda captures |
|
|
|
The CUDA Toolkit is newer than the driver. Use a CUDA Toolkit of a version the driver supports (the upper right of the |
|
MISO’s |
Among illegal memory access errors, the hardest to find is capturing this. It works correctly in a CPU build, and it also happens to work in some GPU environments where HMM is enabled (MISO issue #170). Go back to the declaration and check whether each name used inside the lambda is a local variable or an argument (Chapter 9, Section 5).
3.2 Locating the error
Kernel launches are asynchronous, so the file and line printed by MISO_CUDA_CHECK are where the error was noticed. The kernel in which the error occurred is one launched earlier (Chapter 9, Section 7).
If you run with the environment variable CUDA_LAUNCH_BLOCKING=1, a kernel launch waits until the kernel finishes, and the error is reported by MISO_CUDA_CHECK(cudaGetLastError()) right after the launch. In the example of Chapter 9, Section 5.1, the output changed as follows.
# run as is
CUDA Error: an illegal memory access was encountered .../miso/include/miso/array1d.hpp 109
# run with CUDA_LAUNCH_BLOCKING=1
CUDA Error: an illegal memory access was encountered .../miso/include/miso/execution.hpp 91
array1d.hpp is inside a later copy_from, and execution.hpp is the check right after the launch of the CUDA version of for_each. To find further where for_each was called, temporarily add MISO_CUDA_CHECK(cudaDeviceSynchronize()); right after a for_each in your own code to narrow down the range.
If you run with compute-sanitizer (included in the CUDA Toolkit), it shows the kernel and thread that made the invalid read or write.
compute-sanitizer ./this_capture
mpiexec -np 2 compute-sanitizer ./build/mhd2d_vortex --config=config.yaml # with MPI
This is the output (excerpt) for the example of Chapter 9, Section 5.1.
========= Invalid __global__ read of size 4 bytes
========= at void miso::for_each_kernel<Scaler::apply_wrong(miso::Array1DView<float>) const::[lambda(int) (instance 1)]>(miso::Range1D, T1)+0xd0
========= by thread (0,0,0) in block (0,0,0)
========= Access to 0x7fff1107e0a4 is out of bounds
You can see that for_each_kernel, which runs the lambda inside apply_wrong, tried to read 4 bytes (the float member factor). An address starting with 0x7fff... is commonly used on Linux for the host stack (where local variables are placed), which is a clue that it tried to read a host object. Execution is much slower under compute-sanitizer, so try it with a smaller grid and fewer time steps.
4. When combined with MPI
Are you using CUDA-aware MPI? The CUDA version of MISO’s halo exchange passes the addresses of device buffers directly to
MPI_IsendandMPI_Irecv(miso/include/miso/mhd_halo_exchange.hpp). With an MPI that does not support CUDA, the program hangs inside the communication or receives wrong values. Use a CUDA-aware MPI, such as the Open MPI included in the NVIDIA HPC SDK.Mapping between ranks and GPUs. MISO selects the GPU by the remainder of the rank number within the node divided by the number of GPUs (
cuda::Envinmiso/include/miso/env.hpp). If there are more ranks than GPUs, multiple ranks share one GPU.Order of waiting for completion. Before passing a buffer written by a GPU kernel to MPI, wait with
cudaDeviceSynchronize. Before using the received values in a kernel, wait withMPI_Waitall. The two waits are different things.When running inside a container. MISO’s demos load the environment variables in
demo/shared/openmpi_config.envbefore runningmpiexec.
5. When the numbers differ between the CPU and the GPU
First, make the input (config.yaml), the grid, the MPI decomposition, and the Real type (float/double) the same. Then examine the size and location of the difference.
How the difference looks |
What to check |
|---|---|
The last few digits of reduced values such as sums and means |
A difference in the order of addition (the Host version goes in order from the front; the CUDA version uses a tournament within a block). It is not a mistake |
The result of |
Whether |
Values at each grid point differ by about the rounding error |
|
It differs only near the boundary |
The boundary condition, the update of the ghost cells, the halo exchange |
It drifts along a particular axis |
The order of indices, the range of |
It differs greatly from the first step |
Whether the initial condition has been copied to the device (the direction of |
For how to compare and how to decide the tolerance, see Testing and Comparing Results. We do not assume that the CPU and GPU results match bit for bit.