Skip to content

NASTO — GMP Backend

GPU-accelerated backend: MPI + OpenMP target offloading via the adam_gmp_library.

Status: In active development. API and build modes may change between releases.

The gmp/ directory provides the OpenMP target GPU implementation of the NASTO solver. It extends nasto_common_object with GPU-resident device arrays and compute loops written using OpenMP 5.x target directives: !$omp target teams distribute parallel do with has_device_addr for zero-copy access to already-mapped device data, and !$omp declare target for device-callable procedures.

Source layout

Source fileModule / ProgramRole
adam_nasto_gmp.F90program adam_nasto_gmpEntry point — same CLI as CPU backend
adam_nasto_gmp_object.F90module adam_nasto_gmp_objectBackend object — device pointers, data management, all methods
adam_nasto_gmp_kernels.F90module adam_nasto_gmp_kernelsDevice kernel library — OpenMP target loops
adam_nasto_gmp_cns_kernels.F90module adam_nasto_gmp_cns_kernelsDevice-callable CNS routines (!$omp declare target)

OpenMP target conventions

The GMP backend uses standard OpenMP 5.x target offloading. No additional abstraction library is required beyond adam_gmp_library for device memory management.

!$omp target teams distribute parallel do collapse(N) has_device_addr(...) — the primary parallel loop construct. has_device_addr asserts that the listed arrays are already resident on the device (allocated via alloc_var_gpu), avoiding implicit map(tofrom:) copies at each kernel invocation:

fortran
!$omp target teams distribute parallel do collapse(4) has_device_addr(weno_a_gpu,weno_p_gpu,  &
!$omp&                                                                q_aux_gpu,fluxes_gpu)
do b = 1, blocks_number
do k = 1-si(3), nk
do j = 1-si(2), nj
do i = 1-si(1), ni
   call compute_fluxes_convective_device(...)
enddo
enddo
enddo
enddo

!$omp target data map(to:...) — used to map small local arrays (e.g., stencil direction vectors) for a scoped set of target regions before entering a loop.

!$omp declare target(...) — marks procedures as device-callable so they can be invoked from within target regions:

fortran
subroutine compute_conservatives_device(b, i, j, k, ngc, q_aux_gpu, q)
!$omp declare target(compute_conservatives_device)
  ...

alloc_var_gpu / assign_allocatable_gpu — GMP SDK helper routines from adam_gmp_library for device-side allocation, taking an explicit omp_dev device ID:

fortran
call alloc_var_gpu(var=self%q_aux_gpu,                                                         &
                   ulb=reshape([1,nb, 1-ngc,ni+ngc, 1-ngc,nj+ngc, 1-ngc,nk+ngc, 1,nv_aux], [2,5]), &
                   omp_dev=mydev, init_val=0._R8P, msg=ms)

call assign_allocatable_gpu(lhs=self%q_bc_vars_gpu, rhs=self%bc%q, omp_dev=mydev, msg=msg_//' q_bc_vars_gpu ')

Unlike the NVF backend, device arrays are plain Fortran pointer variables — there is no device attribute. The OpenMP runtime manages the device residency of the underlying storage.


Array layout transposition

The GMP backend uses the same transposed layout as all GPU backends, optimised for coalescing:

BackendLayoutStride-1 indexRationale
CPUq(nv, ni, nj, nk, nb)nv (variables)SIMD vectorisation over variables
GMP/GPUq_gpu(nb, ni, nj, nk, nv)nb (blocks)GPU coalescing: adjacent threads access adjacent blocks

An explicit transpose is performed at every host↔device data transfer:

fortran
! CPU → GPU  (transpose q_cpu → q_gpu)
call self%field_gpu%copy_transpose_cpu_gpu(nv=self%nv, q_cpu=self%field%q, q_gpu=self%q_gpu)

! GPU → CPU  (transpose q_gpu → q_cpu)
call self%field_gpu%copy_transpose_gpu_cpu(nv=self%nv, q_gpu=self%q_gpu, q_cpu=self%field%q)

nasto_gmp_object

Extends nasto_common_object with GPU companion objects and device-allocated working arrays.

Inheritance

nasto_common_object          (common/)
    └── nasto_gmp_object     (gmp/)   ← backend object

GPU companion objects

CPU object (inherited)GPU companionRole
mpih (mpih_object)mpih_gpu (mpih_gmp_object)Device initialisation; exposes mydev (device ID)
field (field_object)field_gpu (field_gmp_object)Ghost-cell exchange on GPU; x/y/z_cell_gpu, dxyz_gpu
ib (ib_object)ib_gpu (ib_gmp_object)phi_gpu device array; eikonal on device
rk (rk_object)rk_gpu (rk_gmp_object)RK stage arrays on device (q_rk_gpu)
weno (weno_object)weno_gpu (weno_gmp_object)WENO coefficient arrays on device (a_gpu, p_gpu, d_gpu)

All companion objects receive an explicit mpih=self%mpih_gpu argument during initialisation so they can query the device ID and issue device-aware messages.

Device data members

All arrays are plain Fortran pointer variables whose storage is allocated by alloc_var_gpu and mapped to mpih_gpu%mydev via the OpenMP runtime:

MemberGPU shapeDescription
q_gpu(nb, 1-ngc:ni+ngc, …, nv)Conservative variables (alias to field_gpu%q_gpu)
q_aux_gpu(nb, 1-ngc:ni+ngc, …, nv_aux)Primitive / auxiliary variables
dq_gpu(nb, 1-ngc:ni+ngc, …, nv)Residual (RHS)
flx_gpu(nb, 1-ngc:ni+ngc, …, nv)Face fluxes in x
fly_gpu(nb, 1-ngc:ni+ngc, …, nv)Face fluxes in y
flz_gpu(nb, 1-ngc:ni+ngc, …, nv)Face fluxes in z
q_bc_vars_gpu(6, 6)Boundary condition primitive states (copied once at init)

Methods

Lifecycle

ProcedurePurpose
initialize(filename)Calls mpih_gpu%initialize(do_device_init=.true.); initialize_common; initialises all GPU companion objects (passing mpih=self%mpih_gpu); calls allocate_gpu
allocate_gpu(q_gpu)Assigns q_gpu pointer; calls alloc_var_gpu (with omp_dev=mydev) for q_aux_gpu, dq_gpu, flx/fly/flz_gpu; copies BC state via assign_allocatable_gpu
copy_cpu_gpu()Transpose-copies qq_gpu; copies grid/field metadata to device
copy_gpu_cpu([compute_copy_q_aux] [,copy_phi])Transpose-copies q_gpuq; optionally computes and copies q_aux_gpu, and copies phi_gpuphi
simulate(filename)Full simulation driver (identical structure to CPU backend)

AMR — requires a CPU roundtrip because the AMR tree lives on the host:

ProcedurePurpose
amr_update()Ghost sync on GPU → mark blocks → copy_gpu_cpuadam%amr_updatecopy_cpu_gpu → recompute phi
mark_by_geo(delta_fine, delta_coarse)CPU-side marking using phi from the last GPU→CPU copy
mark_by_grad_var(grad_tol, …)Ghost sync + compute_q_auxiliary on GPU; gradient computed via field_gpu%compute_q_gradient
refine_uniform(levels)Uniform refinement; delegates to adam%amr_update
compute_phi()Runs compute_phi_analytical_sphere_gmp on device; applies reduce_cell_order_phi_gmp near solids; builds aggregate phi_gpu via compute_phi_all_solids_gmp
move_phi(velocity)Calls move_phi_gmp — OpenMP target kernel translating the IB distance field

Immersed boundary

ProcedurePurpose
integrate_eikonal(q_gpu)Runs n_eikonal Godunov sweeps on device via ib_gpu%evolve_eikonal; applies invert_eikonal on device

I/O — save path always copies GPU → CPU before writing:

ProcedurePurpose
save_hdf5([output_basename])Writes field%q and q_aux (CPU copies) to HDF5; optionally includes ib%phi
save_restart_files()Saves ADAM binary restart + HDF5 snapshot from CPU data
load_restart_files(t, time)Reads restart binary into CPU arrays; rebuilds MPI maps; calls copy_cpu_gpu
save_residuals()Computes L2 norm of dq_gpu via compute_normL2_residuals_gmp on GPU; reduces with MPI_ALLREDUCE
save_simulation_data()Schedules output: syncs ghost cells on GPU → copy_gpu_cpu(compute_copy_q_aux=.true., copy_phi=.true.) → saves HDF5/restart/slices

Initial and boundary conditions

ProcedurePurpose
set_initial_conditions()Calls ic%set_initial_conditions on CPU; then copy_cpu_gpu
set_boundary_conditions(q_gpu)Calls set_bc_q_gpu_gmp using field_gpu%maps%local_map_bc_crown_gpu — all on device
update_ghost(q_gpu [,step])GPU-local inter-block copy → GPU-aware MPI exchange → BC application; optional async step parameter

Numerical methods — all primary compute on GPU:

ProcedurePurpose
compute_dt()Calls compute_umax_gmp on GPU; global minimum via MPI_ALLREDUCE
compute_q_auxiliary(q_gpu, q_aux_gpu)Calls compute_q_aux_gmp on GPU
compute_residuals(q_gpu, dq_gpu)Ghost sync → eikonal → aux vars → convective fluxes → diffusive correction → flux divergence; all on GPU
integrate()RK time advance on GPU via rk_gpu; same scheme dispatch (low-storage / SSP) as CPU backend

Residual computation pipeline (GPU)

1. update_ghost(q_gpu)                      ! GPU ghost sync + BC
2. integrate_eikonal(q_gpu)                 ! eikonal sweeps on device
3. compute_q_auxiliary(q_gpu, q_aux_gpu)    ! q_gpu → primitive on device
4. compute_fluxes_convective_gmp(x,y,z)     ! WENO + LLF — omp target teams collapse(4)
5. compute_fluxes_diffusive_gmp(…)          ! viscous stress tensor — omp target (if μ > 0)
6. compute_fluxes_difference_gmp(…)         ! flux divergence → dq_gpu; IB correction

adam_nasto_gmp_kernels

Device kernel library. All procedures accept only primitive-type arguments (no derived types) and annotate parallel regions with OpenMP target directives.

Public procedures

Convective fluxes

fortran
subroutine compute_fluxes_convective_gmp(dir, blocks_number, ni, nj, nk, ngc, nv, &
                                         weno_s, weno_a_gpu, weno_p_gpu, weno_d_gpu, &
                                         weno_zeps, ror_number, ror_schemes_gpu, &
                                         ror_threshold, ror_ivar_gpu, ror_stats_gpu, &
                                         g, q_aux_gpu, fluxes_gpu)

Launches a !$omp target teams distribute parallel do collapse(4) region over the 4D cell space (b, k, j, i with b innermost for coalescing). Uses has_device_addr to avoid data movement for already-resident arrays. Calls compute_fluxes_convective_device (from adam_nasto_gmp_cns_kernels) at each interface, which performs WENO reconstruction with optional Reduced-Order-Reconstruction (ROR) near shocks.

Diffusive fluxes

fortran
subroutine compute_fluxes_diffusive_gmp(blocks_number, ni, nj, nk, ngc, nv, mu, kd, &
                                        q_aux_gpu, dx_gpu, dy_gpu, dz_gpu,            &
                                        flx_gpu, fly_gpu, flz_gpu)

Computes the full viscous stress tensor τij and heat flux at each face interface, with cross-derivative terms approximated by 4-point stencils. Three separate !$omp target teams distribute parallel do collapse(4) loops (one per direction) add the result to the convective flux arrays in-place. Skipped entirely when μ=0.

Flux divergence

fortran
subroutine compute_fluxes_difference_gmp(blocks_number, ni, nj, nk, ngc, nv, ib_eps, &
                                         dx_gpu, dy_gpu, dz_gpu,                       &
                                         flx_gpu, fly_gpu, flz_gpu [, phi_gpu],        &
                                         dq_gpu)

Computes the conservative flux divergence into dq_gpu. When phi_gpu is present (IB run), adjusts the effective cell width at cut cells based on the signed-distance function, replacing Δx with the distance to the immersed boundary surface.

Auxiliary variables and CFL

ProcedurePurpose
compute_q_aux_gmp(…, q_gpu, q_aux_gpu)Full-field conservative → primitive conversion on device
compute_umax_gmp(…, dxyz_gpu, q_aux_gpu, umax)CFL + Fourier wave-speed reduction on device
set_bc_q_gpu_gmp(…, l_map_bc_gpu, q_bc_vars_gpu, q_gpu)Applies extrapolation and inflow BCs on device using pre-built crown maps

adam_nasto_gmp_cns_kernels

Single-cell device-callable procedures. Each routine is annotated with !$omp declare target(...) so it can be called from within an !$omp target teams region without launching a new kernel.

ProcedurePurpose
compute_conservatives_device(b,i,j,k,ngc,q_aux_gpu,q)Single-cell q_aux_gpu → q
compute_conservative_fluxes_device(sir,b,i,j,k,ngc,q_aux_gpu,f)Single-cell Euler flux in direction sir
compute_max_eigenvalues_device(si,sir,weno_s,…,q_aux_gpu,evmax)LLF wave-speed estimate over WENO stencil
compute_eigenvectors_device(…,el,er)Flux Jacobian eigenvector matrices (Roe-average implementation)
compute_q_aux_gmp(ni,nj,nk,ngc,nb,R,cv,g,q_gpu,q_aux_gpu)Full-field q_gpu → q_aux_gpu (also exported by adam_nasto_gmp_kernels)

Build

bash
# Intel oneAPI (ifx) — OpenMP target offloading
FoBiS.py build -mode nasto-gmp-ifx

# Debug build
FoBiS.py build -mode nasto-gmp-ifx-debug

The executable adam_nasto_gmp is placed in exe/.

Running

bash
# One MPI rank per GPU
mpirun -np 4 exe/adam_nasto_gmp

# With custom INI file
mpirun -np 4 exe/adam_nasto_gmp my_case.ini

Set the device binding before launch (Intel GPU example):

bash
export ZE_AFFINITY_MASK=0,1,2,3   # Intel Level Zero device selection
mpirun -np 4 exe/adam_nasto_gmp

Copyrights

NASTO is part of the ADAM framework, released under the GNU Lesser General Public License v3.0 (LGPLv3).

Copyright (C) Andrea Di Mascio, Federico Negro, Giacomo Rossi, Francesco Salvadore, Stefano Zaghi.