|
MFC
Exascale flow solver
|
MFC compiles GPU code via OpenACC and in the future OpenMP as well.
In order to swap between OpenACC and OpenMP, custom GPU macros are used that translate to equivalent OpenACC and OpenMP directives. FYPP is used to process the GPU macros.
Note: Ordering is not guaranteed or stable, so use key-value pairing when using macros
GPU_PARALLEL_LOOP – (Execute the following loop on the GPU in parallel)Macro Invocation
In order to parallelize a loop, simply place two macro calls on either end of the loop:
This wraps the lines in code with parallelization calls to openACC or openMP, depending on environment and compiler settings.
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| code | code | Required | Region of code where the GPU parallelizes loops |
| collapse | integer | None | Number of loops to combine into 1 loop |
| parallelism | string list | '[gang,vector]' | Parallelism granularity to use for this loop |
| default | string | 'present' | Implicit assumptions compiler should make |
| private | string list | None | Variables that are private to each iteration/thread |
| firstprivate | string list | None | Initialized variables that are private to each iteration/thread |
| reduction | 2-level string list | None | Variables unique to each iteration and reduced at the end |
| reductionOp | string list | None | Operator that each list of reduction will reduce with |
| copy | string list | None | Allocates and copies data to GPU on entrance, then deallocated and copies to CPU on exit |
| copyin | string list | None | Allocates and copies data to GPU on entrance and then deallocated on exit |
| copyinReadOnly | string list | None | Allocates and copies readonly data to GPU and then deallocated on exit |
| copyout | string list | None | Allocates data on GPU on entrance and then deallocates and copies to CPU on exit |
| create | string list | None | Allocates data on GPU on entrance and then deallocates on exit |
| no_create | string list | None | Use data in CPU memory unless data is already in GPU memory (OpenACC only) |
| present | string list | None | Data that must be present in GPU memory. Increment counter on entrance, decrement on exit |
| deviceptr | string list | None | Pointer variables that are already allocated on GPU memory |
| attach | string list | None | Attaches device pointer to device targets on entrance, then detach on exit |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Parameter Restrictions
| name | Restricted range |
|---|---|
| collapse | Must be greater than 1 |
| parallelism | Valid elements: 'gang', 'worker', 'vector', 'seq' |
| default | 'present' or 'none' |
Additional information
Example
GPU_LOOP – (Execute loop on GPU)Macro Invocation
Uses FYPP eval directive using $:
$:GPU_LOOP(...)
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| collapse | integer | None | Number of loops to combine into 1 loop |
| parallelism | string list | None | Parallelism granularity to use for this loop |
| data_dependency | string | None | 'independent'-> assert loop iterations are independent, 'auto->let compiler analyze dependencies |
| private | string list | None | Variables that are private to each iteration/thread |
| reduction | 2-level string list | None | Variables unique to each iteration and reduced at the end |
| reductionOp | string list | None | Operator that each list of reduction will reduce with |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Parameter Restrictions
| name | Restricted range |
|---|---|
| collapse | Must be greater than 1 |
| parallelism | Valid elements: 'gang', 'worker', 'vector', 'seq' |
| data_dependency | 'auto' or 'independent' |
Additional information
Example
GPU_PARALLEL – (Execute the following on the GPU in parallel)Macro Invocation
Uses FYPP call directive using #:call
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| code | code | Required | Region of code where a kernel is launched on the GPU |
| default | string | 'present' | Implicit assumptions compiler should make |
| private | string list | None | Variables that are private to each iteration/thread |
| firstprivate | string list | None | Initialized variables that are private to each iteration/thread |
| reduction | 2-level string list | None | Variables unique to each iteration and reduced at the end |
| reductionOp | string list | None | Operator that each list of reduction will reduce with |
| copy | string list | None | Allocates and copies data to GPU on entrance, then deallocated and copies to CPU on exit |
| copyin | string list | None | Allocates and copies data to GPU on entrance and then deallocated on exit |
| copyinReadOnly | string list | None | Allocates and copies readonly data to GPU and then deallocated on exit |
| copyout | string list | None | Allocates data on GPU on entrance and then deallocates and copies to CPU on exit |
| create | string list | None | Allocates data on GPU on entrance and then deallocates on exit |
| no_create | string list | None | Use data in CPU memory unless data is already in GPU memory (OpenACC only) |
| present | string list | None | Data that must be present in GPU memory. Increment counter on entrance, decrement on exit |
| deviceptr | string list | None | Pointer variables that are already allocated on GPU memory |
| attach | string list | None | Attaches device pointer to device targets on entrance, then detach on exit |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Parameter Restrictions
| name | Restricted range |
|---|---|
| default | 'present' or 'none' |
Additional information
Example
GPU_DATA – (Make data accessible on GPU in specified region)Macro Invocation
Uses FYPP call directive using #:call
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| code | code | Required | Region of code where defined data is accessible |
| copy | string list | None | Allocates and copies variable to GPU on entrance, then deallocated and copies to CPU on exit |
| copyin | string list | None | Allocates and copies data to GPU on entrance and then deallocated on exit |
| copyinReadOnly | string list | None | Allocates and copies a readonly variable to GPU and then deallocated on exit |
| copyout | string list | None | Allocates data on GPU on entrance and then deallocates and copies to CPU on exit |
| create | string list | None | Allocates data on GPU on entrance and then deallocates on exit |
| no_create | string list | None | Use data in CPU memory unless data is already in GPU memory (OpenACC only) |
| present | string list | None | Data that must be present in GPU memory. Increment counter on entrance, decrement on exit |
| deviceptr | string list | None | Pointer variables that are already allocated on GPU memory |
| attach | string list | None | Attaches device pointer to device targets on entrance, then detach on exit |
| default | string | None | Implicit assumptions compiler should make |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Parameter Restrictions
| name | Restricted range |
|---|---|
| code | Do not assign it manually with key-value pairing |
Example
GPU_ENTER_DATA – (Allocate/move data to GPU until matching GPU_EXIT_DATA or program termination)Macro Invocation
Uses FYPP eval directive using $:
$:GPU_ENTER_DATA(...)
Parameter
| name | data type | Default Value | description |
|---|---|---|---|
| copyin | string list | None | Allocates and copies data to GPU on entrance |
| copyinReadOnly | string list | None | Allocates and copies a readonly variable to GPU on entrance |
| create | string list | None | Allocates data on GPU on entrance |
| attach | string list | None | Attaches device pointer to device targets on entrance |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Example
GPU_EXIT_DATA – (Deallocate/move data from GPU created by GPU_ENTER_DATA)Macro Invocation
Uses FYPP eval directive using $:
$:GPU_EXIT_DATA(...)
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| copyout | string list | None | Deallocates and copies data from GPU to CPU on exit |
| delete | string list | None | Deallocates data on GPU on exit |
| detach | string list | None | Detach device pointer from device targets on exit |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Example
GPU_DECLARE – (Allocate module variables on GPU or for implicit data region )Macro Invocation
Uses FYPP eval directive using $:
$:GPU_DECLARE(...)
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| copy | string list | None | Allocates and copies data to GPU on entrance, then deallocated and copies to CPU on exit |
| copyin | string list | None | Allocates and copies data to GPU on entrance and then deallocated on exit |
| copyinReadOnly | string list | None | Allocates and copies a readonly variable to GPU and then deallocated on exit |
| copyout | string list | None | Allocates data on GPU on entrance and then deallocates and copies to CPU on exit |
| create | string list | None | Allocates data on GPU on entrance and then deallocates on exit |
| present | string list | None | Data that must be present in GPU memory. Increment counter on entrance, decrement on exit |
| deviceptr | string list | None | Pointer variables that are already allocated on GPU memory |
| link | string list | None | Declare global link, and only allocate when variable used in data clause. |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Additional information
Example
GPU_UPDATE – (Updates data from CPU to GPU or GPU to CPU)Macro Invocation
Uses FYPP eval directive using $:
$:GPU_UPDATE(...)
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| host | string list | None | Updates data from GPU to CPU |
| device | string list | None | Updates data from CPU to GPU |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Example
GPU_HOST_DATA – (Make GPU memory address available on CPU)Macro Invocation
Uses FYPP call directive using #:call
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| code | code | Required | Region of code where GPU memory addresses is accessible |
| use_device_addr | string list | None | Use GPU memory address of variable instead of CPU memory address |
| use_device_ptr | string list | None | Use GPU pointer of pointers instead of CPU pointer |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Parameter Restrictions
| name | Restricted range |
|---|---|
| code | Do not assign it manually with key-value pairing |
Example
GPU_WAIT – (Makes CPU wait for async GPU activities)Macro Invocation
Uses FYPP eval directive using $:
$:GPU_WAIT(...)
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Example
GPU_ATOMIC – (Do an atomic operation on the GPU)Macro Invocation
Uses FYPP eval directive using $:
$:GPU_ATOMIC(...)
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| atomic | string | Required | Which atomic operation is performed |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Parameter Restrictions
| name | Restricted range |
|---|---|
| atomic | 'read', 'write', 'update', or 'capture' |
Additional information
Capture is a pair of read/write/update operations with one dependent on the other
Example
GPU_ROUTINE – (Compile a procedure for the GPU)Macro Invocation
Uses FYPP eval directive using $:
$:GPU_ROUTINE(...)
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| function_name | string | None | Name of subroutine/function |
| parallelism | string list | None | Parallelism granularity to use for this routine |
| nohost | boolean | False | Do not compile procedure code for CPU |
| cray_inline | boolean | False | Inline procedure on cray compiler |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
| extraOmpArgs | string | None | String of any extra arguments added to the OpenMP directive |
Parameter Restrictions
| name | Restricted range |
|---|---|
| parallelism | Valid elements: 'gang', 'worker', 'vector', 'seq' |
Additional information
Example
GPU_CACHE – (Data to be cache in software-managed cache)Macro Invocation
Uses FYPP eval directive using $:
$:GPU_CACHE(...)
Parameters
| name | data type | Default Value | description |
|---|---|---|---|
| cache | string list | Required | Data that should to stored in cache |
| extraAccArgs | string | None | String of any extra arguments added to the OpenACC directive |
NOTE Does not do anything for OpenMP currently
Example
GPU_ROUTINE marks a subroutine or function as callable from within a GPU kernel (an OpenACC routine or OpenMP declare target directive). The standard idiom for pure, sequential per-thread helpers is:
When to use it. Extract a block into a GPU_ROUTINE helper when:
Key idioms.
Caller-loads, helper-computes. Callers do all coordinate-indexed array loads from the global state arrays (qL_rs_vf, qR_rs_vf, etc.) before the call; the helper receives only scalars or small arrays with explicit-shape dimensioning. This is required because the SF indexing lambda used in solver loops is defined locally inside each solver's #:for NORM_DIR block and cannot be referenced from a helper.
AMD case-opt compatibility. Under --case-optimization with the AMD backend, arrays that are sized by runtime parameters at compile time must be declared with an explicit constant bound. Use an explicit n argument (e.g., integer, intent(in) :: nf) and dimension helpers as dimension(nf) rather than dimension(num_fluids). See s_compute_interface_reynolds in src/simulation/m_riemann_state.fpp for the #:if not MFC_CASE_OPTIMIZATION and USING_AMD guard pattern: the guard sits on the dummy-argument declaration in the helper's definition, with matching guards on the callers' own local declarations so the actual and dummy bounds agree.
Declare scoping. The GPU_ROUTINE directive must appear in the source file that defines the routine. Helpers added to m_riemann_state.fpp are automatically in scope for every solver module that uses it — no additional declare-target annotations are needed at call sites.
Moving a device helper into another file can silently cost ~25% on NVHPC. NVHPC has no device LTO, so MFC's only cross-file inlining is the two-pass -Mextract=lib: / -Minline=lib: scheme in cmake/MFCTargets.cmake. That inliner refuses any device routine that has a subroutine call anywhere in its call tree, reporting:
The refusal propagates: a caller of a refused routine is refused too. Measured on nvfortran 25.11 (A100), the following hold for a routine that must inline across files:
| in the routine's body | inlines across files? |
|---|---|
| arithmetic, branches on module logicals, module array reads, early return | yes |
| a call to a scalar-returning function that is itself inlinable | yes (the callee need not be inlined) |
| a call to a subroutine | no |
| the routine returns a derived type or an array | no — never inlinable |
There is no build-level escape: -Mextract always captures pre-inline source, so re-extracting in stages, compiling several files in one invocation, and levels:/maxsize:/name:/except: all fail, as does -Mipa (ignored in 25.x). Cray and AMD do their own whole-program IPA and are unaffected, and CPU builds do not care — so this shows up as an NVHPC-only benchmark regression while every other job stays green.
Symptom. Grind time regresses on NVHPC alone, with unchanged source semantics. Confirm by comparing per-routine stack frames: -Minfo=inline and -gpu=ptxinfo are already on, so Function properties for ... lines jumping from ~8 bytes to 200–350 bytes with matching spill stores/spill loads is the fingerprint. Registers per thread going down while the kernel gets slower is the same story seen from ncu.
Rule of thumb. Draw a module boundary where inlining already fails, not in the middle of a chain that currently inlines. Solver kernels never inlined s_compute_mixture_coefficients or s_compute_speed_of_sound even before m_eos existed, which makes that a free cut point; the phase chain those two call (s_phase_coefficients → s_eos_coefficients → s_reference_curve) must stay in the same file as them. This is why those four routines live in src/common/m_eos.fpp alongside the EOS operators even though mixture closure is not, strictly, an equation of state.
amdflang generates device code for the whole image at link time. Once the image carries enough OpenMP target regions, the device link's Attributor pass exceeds its AAPointerInfo access cap on a heavily shared object; pointer information goes pessimistic and OpenMPOpt's __kmpc_parallel cleanup then fails for the whole module. The visible effect: adding (or removing) ANY kernel anywhere silently regenerates UNTOUCHED kernels with far worse ISA — measured 2.4-4.5x slower, with register spills and an extra 512 B of LDS in every kernel. A wall-time A/B between two commits that differ in target-region count is confounded by this whole-image effect.
MFC's build raises the cap (-attributor-max-pi-accesses=16384, passed to the offload linker in cmake/MFCTargets.cmake), which restores full pointer precision for the whole image and makes kernel quality independent of unrelated edits. The cost is a longer device link. If a build's device link is unexpectedly slow, this flag is why — do not remove it; kernel performance becomes nondeterministic across commits without it.
The failure signature without the flag: after adding a kernel, unrelated kernels' resource usage shifts image-wide (uniform LDS increase, scratch/spill jumps visible in rocprofv3 dispatch records) and previously fast kernels slow several-fold.
A GPU_PARALLEL_LOOP (OpenMP target region) written inside a Fortran block ... end block construct compiles cleanly, but amdflang omits it from the device image while the host still registers it. The first launch aborts with
hsa_executable_get_symbol_by_name(__omp_offloading_..._l<line>.kd): HSA_STATUS_ERROR_INVALID_SYMBOL_NAME omptarget error: Failed to load kernel ...
followed by a segmentation fault. Never place a GPU kernel inside a block construct; hoist it into its own (module) subroutine with the locals passed as arguments.
Every entry here was measured. They share a failure mode: the build stays green and the answer is wrong, or one backend diverges from all the others.