Migrating from CUDA Fortran#
Who this is for#
You compile with nvfortran and call NVIDIA’s libraries through the CUDA
Fortran interface modules: use cublas_v2, use cufft, use curand.
NVIDIA documents these modules in its
Fortran CUDA Interfaces
guide.
The Fortran bindings shipped in ROCm (see Migrating to the Fortran bindings shipped in ROCm) have one
module per HIP library, and their module, routine and constant names follow
CUDA Fortran’s, with hip in place of cu. The library calls therefore
port by renaming. CUDA Fortran’s language extensions (device arrays, kernels)
do not: they are not library calls, and the code that uses them has to be
rewritten.
There is no automatic tool: HIPIFY does not process Fortran. The renames below
are regular enough for sed, but read the result.
Modules#
CUDA Fortran |
ROCm |
Link (CMake target) |
CMake package |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hipFFT is split the way cuFFT is. Every hipfftXt* and hipfftMp*
routine is in hipfftxt, including hipfftXtSetWorkArea, which the C
header hipfft.h declares, and every other hipfft* routine is in
hipfft. hipfftxt uses and re-exports hipfft, as cufftXt does
cufft, so use hipfftxt alone gives the whole API. Both modules are in
the same archive. The module is named in lowercase, but Fortran names are
case-insensitive, so use hipfftXt works too.
Each library module also needs use hip wherever your code allocates device
memory or synchronizes, as cudafor did. How to compile and link against the
bindings is in the Compile and link section of Migrating to the Fortran bindings shipped in ROCm.
Before the bindings ship in ROCm (see the timeline in Migrating to the Fortran bindings shipped in ROCm),
hipFORT provides the same interfaces under hipfort_ names: hipfort for
hip, hipfort_hipblas for hipblas, hipfort_hipfftxt for
hipfftxt, and so on, with the enumerations in hipfort_<lib>_enums and
the check routines in hipfort_check.
Routines and constants#
Routine and constant names map by replacing the cu or CU prefix with
hip or HIP:
CUDA Fortran |
ROCm |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
The arguments are in the same order. Every library routine takes its handle
first, as in cublas_v2; a call written against the legacy cublas
module, which takes no handle, needs one added.
cusolverDn* routines become hipsolverDn*, which is hipSOLVER’s
compatibility API. Once the code runs, AMD recommends moving to hipSOLVER’s
regular hipsolver* API, which performs better; it is in the same
hipsolver module.
Each library module also has a check routine, hipCheck, hipblasCheck,
hipfftCheck and so on, which prints the name of a failed status and stops
with stop 1. They are ROCm additions with no NVIDIA counterpart; where your
code tests istat itself, you can keep doing so.
What you rewrite#
Handles. CUDA Fortran declares each handle with a type of its own (
type(cublasHandle),type(cusparseHandle),type(cusolverDnHandle),type(curandGenerator)) and a cuFFT plan as aninteger. In the ROCm bindings every handle and plan is atype(c_ptr), fromiso_c_binding.Device memory. CUDA Fortran allocates arrays with the
deviceattribute withallocate, and copies them with assignment (a_d = a). HIP Fortran has no device attribute: a device array is a Fortran pointer, allocated withhipMalloc(hipMalloc(a_d, m, n), orhipMalloc(a_d, source=a)to allocate and copy in one call), copied withhipMemcpy, and freed withhipFree. The library routines accept these pointers directly, or atype(c_ptr).Kernels.
attributes(global)andattributes(device)procedures, the<<<grid, block>>>launch, and!$cuf kernel dodirectives have no HIP Fortran equivalent. Port these loops to OpenMP offload (!$omp target teams distribute parallel do), or write the kernel in HIP C++ and call abind(C)launcher from Fortran.Profiling ranges. The
nvtxmodule’snvtxStartRangeandnvtxEndRangehelpers become ROCTx calls, which follow the C API instead:roctxRangePush('zone'//c_null_char)androctxRangePop().
A cuBLAS call, before and after:
! CUDA Fortran
use cudafor
use cublas_v2
real :: a(m,k), b(k,n), c(m,n), alpha = 1.0, beta = 0.0
real, device, allocatable :: a_d(:,:), b_d(:,:), c_d(:,:)
type(cublasHandle) :: handle
integer :: istat
istat = cublasCreate(handle)
allocate(a_d(m,k), b_d(k,n), c_d(m,n))
a_d = a
b_d = b
istat = cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &
alpha, a_d, m, b_d, k, beta, c_d, m)
c = c_d
deallocate(a_d, b_d, c_d)
istat = cublasDestroy(handle)
! ROCm
use iso_c_binding
use hip
use hipblas
real(c_float) :: a(m,k), b(k,n), c(m,n), alpha = 1.0, beta = 0.0
real(c_float), pointer :: a_d(:,:), b_d(:,:), c_d(:,:)
type(c_ptr) :: handle = c_null_ptr
call hipblasCheck(hipblasCreate(handle))
call hipCheck(hipMalloc(a_d, source=a))
call hipCheck(hipMalloc(b_d, source=b))
call hipCheck(hipMalloc(c_d, m, n))
call hipblasCheck(hipblasSgemm(handle, HIPBLAS_OP_N, HIPBLAS_OP_N, m, n, k, &
alpha, a_d, m, b_d, k, beta, c_d, m))
call hipCheck(hipMemcpy(c, c_d, hipMemcpyDeviceToHost))
call hipCheck(hipFree(a_d))
call hipCheck(hipFree(b_d))
call hipCheck(hipFree(c_d))
call hipblasCheck(hipblasDestroy(handle))
What has no equivalent#
These CUDA Fortran modules have no ROCm Fortran counterpart:
cublasXtandcublasMp(multi-GPU BLAS) andcusolverMp(distributed solvers).curand_deviceandopenacc_curand, which generate random numbers inside device code.cutensor_v2andcutensorex. hipTensor has no Fortran binding.ncclandnvshmem. Their ROCm counterparts, RCCL and rocSHMEM, have no Fortran binding.
Code that needs one of these keeps a C or C++ layer for it, called through
bind(C). If a binding would help your code,
open an issue.