I’ve been experimenting with do concurrent and NVIDIA’s automatic GPU offload (nvfortran -stdpar=gpu), and I’ve run into a pattern that makes me think external module procedures called from a do concurrent construct aren’t currently supported for offload. I’d like to check with the community whether this matches your understanding, or whether I’m missing a flag/attribute somewhere.
Here’s what I tested, going from “definitely works” to “breaks”:
Case 1 — Internal (contained) pure subroutine, single file
Everything — the main program and a pure subroutine — lives in one file, with the subroutine in a contains block of the program.
program random_2d_example
implicit none
! Declare variables
integer, parameter :: n_rows = 4
integer, parameter :: n_cols = 4
integer :: i, j
real, dimension(n_rows, n_cols) :: r
real, dimension(n_rows, n_cols) :: c, p
! Initialize random number generator array
call random_number(r)
! Generate values and process them through the pure routine
do concurrent (i=1:n_cols,j=1:n_rows)
call initial_micro(r(i, j), c(i, j))
p(i, j) = c(i, j)
end do
contains
pure subroutine initial_micro(r_val, c_val)
real, intent(in) :: r_val
real, intent(out) :: c_val
c_val = r_val
end subroutine initial_micro
end program random_2d_example
Result: compiles and offloads fine.
nvfortran -stdpar=gpu -gpu=cc61 -gpu=mem:managed -Minfo=accel test.f90 -o test
random_2d_example:
15, Generating NVIDIA GPU code
15, ! blockidx%x threadidx%x auto-collapsed
Loop parallelized across CUDA thread blocks, CUDA threads(32) collapse(2) ! blockidx%x threadidx%x collapsed-innermost
15, Generating implicit copy(c(:,:)) [if not already present]
Generating implicit copyin(r(:,:)) [if not already present]
Generating implicit copyout(p(:,:)) [if not already present]
initial_micro:
22, Generating implicit acc routine seq
Generating acc routine seq
Generating NVIDIA GPU code
Case 2 — pure subroutine inside an internal module, still single file
Same file, but now routine lives in a module (in the same source file).
! --- Microphysics Module ---
module micro_mod
implicit none
! Keep public interfaces clean and structured
private
public :: initial_micro
contains
pure subroutine initial_micro(r_val, c_val)
real, intent(in) :: r_val
real, intent(out) :: c_val
c_val = r_val
end subroutine initial_micro
end module micro_mod
! --- Main Program ---
program random_2d_example
use micro_mod, only: initial_micro
implicit none
integer, parameter :: n_rows = 5
integer, parameter :: n_cols = 4
integer :: i, j
real, dimension(n_rows, n_cols) :: r
real, dimension(n_rows, n_cols) :: c, p
! Initialize random numbers
call random_number(r)
! Offload execution to GPU via do concurrent
do concurrent (i = 1:n_rows, j = 1:n_cols)
call initial_micro(r(i, j), c(i, j))
p(i, j) = c(i, j)
end do
print *, "Program executed successfully. First element p(1,1):", p(1, 1)
end program random_2d_example
Result: still compiles and offloads fine.
nvfortran -stdpar=gpu -gpu=cc61 -gpu=mem:managed -Minfo=accel test_module.f90 -o test_module
initial_micro:
11, Generating implicit acc routine seq
Generating acc routine seq
Generating NVIDIA GPU code
random_2d_example:
36, Generating NVIDIA GPU code
36, ! blockidx%x threadidx%x auto-collapsed
Loop parallelized across CUDA thread blocks, CUDA threads(32) collapse(2) ! blockidx%x threadidx%x collapsed-innermost
36, Generating implicit copy(c(:,:)) [if not already present]
Generating implicit copyin(r(:,:)) [if not already present]
Generating implicit copyout(p(:,:)) [if not already present]
Case 3 — Same module, but split into a separate source file
module micro_mod
implicit none
private
public :: initial_micro
contains
elemental subroutine initial_micro(r_val, c_val)
real, intent(in) :: r_val
real, intent(out) :: c_val
c_val = r_val
end subroutine initial_micro
end module micro_mod
program random_2d_example
use micro_mod, only: initial_micro
implicit none
integer, parameter :: n_rows = 5
integer, parameter :: n_cols = 4
integer :: i, j
real, dimension(n_rows, n_cols) :: r
real, dimension(n_rows, n_cols) :: c, p
call random_number(r)
do concurrent (i = 1:n_rows, j = 1:n_cols)
call initial_micro(r(i, j), c(i, j))
p(i, j) = c(i, j)
end do
print *, "Program executed successfully. First element p(1,1):", p(1, 1)
end program random_2d_example
Result:
nvfortran -stdpar=gpu -gpu=cc61 -gpu=mem:managed -c micro_mod.f90
nvfortran -stdpar=gpu -gpu=cc61 -gpu=mem:managed -c main.f90
NVFORTRAN-S-1074-Procedure call in Do Concurrent is not supported yet (main.f90: 14)
0 inform, 0 warnings, 1 severes, 0 fatal for random_2d_example
My question
Based on this, it looks like offloading a do concurrent loop that calls an external module procedure (i.e. a procedure compiled in a separate translation unit and pulled in via use) is not currently supported, even though the exact same procedure works fine when it’s defined internally to the program or in a module within the same file.
Is this a known/expected limitation of nvfortran’s -stdpar GPU offload today (e.g. related to whole-program analysis or LTO requirements for offload regions), or is there a way to make external procedures visible to the offload compiler (some combination of IPA/LTO flags, or explicit interface attributes) that I’m missing?