Skip to content

bodner but good - #86

Draft
JorgeG94 wants to merge 11 commits into
MOM6-GPU:dev/gpufrom
JorgeG94:feat/bodner_mle
Draft

bodner but good #86
JorgeG94 wants to merge 11 commits into
MOM6-GPU:dev/gpufrom
JorgeG94:feat/bodner_mle

Conversation

@JorgeG94

Copy link
Copy Markdown

This is a fresh implementation of the Bodner mixed layer restrat since I completely destroyed my previous branch when implementing blocking.

JorgeG94 and others added 11 commits July 27, 2026 19:21
Rewrite of mixedlayer_restrat_Bodner as do concurrent loops inside a single
persistent device data region, replacing the earlier naive port.

* The mixed-layer density integral evaluates the equation of state a block of
  MLE_NKBLOCK layers at a time rather than hoisting it to a whole-column 3D
  call.  The block scratch is (SZI,SZJ,nkblock), so the CPU default of 1 keeps
  it to a single slab; the GPU default of 0 puts the column in one block.  The
  row-at-a-time early exit becomes a slab-wide one, which only changes work
  that the htot < big_H guard already discarded.
* No shadow copies of CS/GV/US scalars or of the input fields.  The kernels read
  CS% and tv% directly, with those parents mapped alongside their components as
  nvfortran requires once any component is mapped.
* One target data region covers the whole calculation, so nothing round-trips
  between phases.  Arrays that only one branch or diagnostic touches (the block
  scratch, uhml/vhml, the host-side big_H and wpup variants, the chksums) are
  mapped or synced inside that branch.
* dmu is stashed in uhml/vhml instead of a per-column array, which do concurrent
  cannot give each iteration without a per-thread allocation.

mu is now pure so it can be called from a concurrent loop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
h_MLD, BLD and bflux were left to implicit copyin, which the mapped-component
switch disables along with derived-type copyin, so the kernels read
uninitialized device memory and little_h/wpup came back NaN.  BLD and bflux are
mapped around the w'u' branches that dereference them; h_MLD joins the outer
region.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mapping a derived-type component makes nvfortran create a parent entry that
supersedes an explicit map(to: CS) in the same directive.  The component
descriptors still attach -- CS%Cr_space read correctly -- but the struct body is
never copied, so CS%mstar, CS%nstar and CS%min_wstar2 silently read as zero.
wpup then collapsed to exactly zero, r_wpup went infinite, and psi_mag came out
0*Inf = NaN.

Mapping the parents in their own enclosing region copies the struct body.
benchmark_ALE now reproduces repro_ocean.stats bitwise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With MLE_NKBLOCK=1 the equation-of-state scratch was still a full (SZI,SZJ)
slab, where upstream used a single row rho_ml(SZI).  That loses the locality the
original had, where the EOS fills a row and the integral consumes it straight
back out of cache.  Add MLE_NJBLOCK alongside MLE_NKBLOCK, following
PGF_PLM_NJBLOCK: 1 and 1 on the CPU reduce the scratch to the one row upstream
used, 0 and 0 on the GPU make it the whole halo-1 j domain by the whole column,
which is a single kernel.  Rml_int goes back to being the 1D host array upstream
declares, with the tiled path carrying its running totals in Rml_blk.

The early-exit test compares htot against big_H on the host, so big_H is fetched
once when it can actually run.  Without that it read a stale big_H, concluded
every column was full and stopped calling the equation of state early, which
truncated the integral -- benchmark_ALE energy moved in the 7th digit at
NJBLOCK=NKBLOCK=1 while the untiled default stayed bitwise.

benchmark_ALE reproduces repro_ocean.stats bitwise at both 0/0 and 1/1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dropping it reproduces the zeroed-scalar NaN even with the parent in its own
enclosing region.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous form leaned on `map(always, to: CS, tv)` in an enclosing structured
region, a construct used nowhere else in MOM6.  Replace it with what the rest of
the GPU port does: unstructured `target enter data`, the control structure's array
components mapped once in the init routine as hor_visc_init and friends do, and no
structured `target data` regions anywhere.

CS's scalars cannot be mapped alongside its arrays.  Mapping any component of a
derived type costs that type its implicit copyin, after which its scalars read as
zero on the device while its array components still read correctly -- silent, and
it surfaces only as a NaN much further downstream.  So the eight scalars the
kernels need are hoisted to plain locals, which is the practice the rest of the
port already follows.  Leaving the arrays unmapped instead is not an option: that
faults with an illegal address on the first CS%MLD_filtered_slow read.

benchmark_ALE reproduces repro_ocean.stats bitwise at MLE_NJBLOCK/NKBLOCK of both
0/0 and 1/1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The solo driver puts the whole MOM_control_struct on the device with
`enter data map(alloc: MOM_CSp)`: the address range is claimed but its
contents are never copied.  Any later map(to:) of anything inside that
range -- including the implicit copyin every kernel gets -- is then a
present-table no-op, so kernels read zeros for the scalars of any
control structure held as an inline member.  Mapped array components
were unaffected because their maps copy the heap data and attach the
descriptor into the parent's device block; scalars have no attach
equivalent.  This was the mechanism behind the l_*/tau_* hoists.

`target update` always copies regardless of presence, so issue
`target update to(CS)` in mixedlayer_restrat_init once the parameters
are set, BEFORE the component maps whose attach then fixes this
struct's array descriptors in the device copy.  With that, delete the
eight hoisted locals and read CS% directly in the kernels, like every
other module in the tree.

Verified bitwise against repro_ocean.stats at both MLE_NJBLOCK/NKBLOCK
0/0 and 1/1 (overrides confirmed applied in MOM_parameter_doc.all).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

GPU Port Coverage

Overall: 2129 / 5675 portable executed lines ported (37.5%)
Since base branch: +86 ported lines (+1.4 pp)

Files touched by this PR: 84 / 98 portable executed lines ported (85.7%)

Full per-file / per-routine breakdown: see the "gpu-port-report" job summary and artifact.

Full line-by-line coverage report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant