Skip to content

Commit c4556e0

Browse files
imreddyTejaclaude
andcommitted
Fix comments and docstrings that no longer match the code
Comments only; no behaviour change. Stale claims corrected: - ext/cuda/operators_finite_difference.jl: the launch-config comment said the kernel "uses block and grid indices instead of computing cartesian indices from a linear index" (it does the opposite now), that the block size is `(n_face_levels, Ni, 1)` (it is `(n_face_levels, threads_dim_y, 1)`), and that the path is used only when a block holds 32-256 threads (that guard is gone). Rewrote to describe the current launch configuration, noted that masked spaces are supported too, explained the `threads_dim_y` heuristic, and dropped the dead commented-out `32 <= n_face_levels * Ni <= 256` line. - Both files referred to a `us::UniversalSize` argument that does not exist. The kernel reads the horizontal extents off the output layout's type parameters via `vijh_params`; the "compile-time constant divisor" point still holds, so it is now stated in terms of what the code actually does. - ext/cuda/operators_fd_eager.jl: "A `SetBoundaryOperator` only ever outputs to faces" is false since `return_space(::SetBoundaryOperator, space) = space` -- the conversion also puts one on a center output (DivergenceF2C + SetDivergence) and on a face input (GradientF2C + SetValue). The reason for gating `idx` on the compile-time staggering is unchanged and kept. - src/MatrixFields/operator_matrices.jl: "the boundary row is empty (`rzero`)". `rzero` returns a zero-filled row of the same bandwidth, not an empty row; it is the multiply's band clipping that makes the wider row harmless. - src/Operators/finitedifference.jl: six comments described inlined stencils as belonging to "(deleted)" operators. `LeftBiasedC2F`/`RightBiasedC2F` are still exported operators -- only their `stencil_interior` methods went away -- so that wording was misleading; the 3rd-order variants really were removed, which is now said explicitly. Outdated signatures and documentation: - All twelve docstring signatures in operators_fd_eager.jl were missing the `hidx` argument added to `calc_level_val`/`get_op_row`/`project_row2_for_mul`, or the `mask` argument added to `eager_copyto_stencil_kernel!`. - `SetBoundaryOperator`'s docstring listed only `SetValue`, though it accepts `SetGradient`, `SetCurl` and `SetDivergence` and projects the first two onto the `Covariant3`/`Contravariant123` axes. All four are now documented with their projection semantics, along with the space-preserving behaviour and the fact that a side without a condition is left untouched. The four `@ref` targets are all listed in docs/src/operators.md, so the docs build resolves them. Verified: docstring parses and renders, and CPU MatrixFields/operator_matrices.jl (275+41) and finitedifference/unit_column.jl still pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BLMKMmk7JezBvagh3AybPQ
1 parent 6474619 commit c4556e0

4 files changed

Lines changed: 75 additions & 50 deletions

File tree

ext/cuda/operators_fd_eager.jl

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ compile-time constant.
104104
space.staggering isa Spaces.CellCenter && !Topologies.isperiodic(space)
105105

106106
"""
107-
eager_copyto_stencil_kernel!(out, bc::BC, space)
107+
eager_copyto_stencil_kernel!(out, bc::BC, mask, space)
108108
109109
CUDA kernel to compute the value of a `Broadcasted` or `StencilBroadcasted` at a single index.
110-
This calls `calc_level_val(bc, space)`, which computes the value of the broadcasted
110+
This calls `calc_level_val(bc, hidx, space)`, which computes the value of the broadcasted
111111
expression at the given index, and then copies the result into `out`.
112112
"""
113113
Base.@propagate_inbounds function eager_copyto_stencil_kernel!(
@@ -119,8 +119,10 @@ Base.@propagate_inbounds function eager_copyto_stencil_kernel!(
119119
v = threadIdx().x
120120
col_idx = threadIdx().y + (blockIdx().x - 1) * blockDim().y
121121
(i, j, h) = if mask isa NoMask
122-
# `Nij` comes from the type of `us`, so it is a compile-time constant and
123-
# the `CartesianIndices` decomposition below uses a fixed-divisor `divrem`.
122+
# `Ni` and `Nj` are read off the output layout's type parameters (see
123+
# `vijh_params`), so they are compile-time constants and the `CartesianIndices`
124+
# decomposition below is a fixed-divisor `divrem`. Only `Nh` is a runtime value,
125+
# and being the last extent it is never divided by.
124126
size_params = ClimaCore.DataLayouts.vijh_params(ClimaCore.Fields.field_values(out))
125127
Nj = size_params.Nj
126128
Ni = size_params.Ni
@@ -149,7 +151,7 @@ end
149151
# All the functions below this line should not be used outside of this file
150152

151153
"""
152-
calc_level_val(bc, space)
154+
calc_level_val(bc, hidx, space)
153155
154156
Call `calc_level_val` on all the arguments of `bc`, and then apply the function `bc.f` to the results.
155157
"""
@@ -166,7 +168,7 @@ Base.@propagate_inbounds function calc_level_val(
166168
end
167169

168170
"""
169-
reconstruct_space_and_call_calc_level_val(arg, space)
171+
reconstruct_space_and_call_calc_level_val(arg, (hidx, space))
170172
171173
If `arg` is a `Broadcasted`, `StencilBroadcasted`, or `Field`,
172174
reconstruct the space for the argument and call `calc_level_val` on it. This allows
@@ -189,7 +191,7 @@ Base.@propagate_inbounds reconstruct_space_and_call_calc_level_val(
189191
) where {A, S} = @inbounds @inline calc_level_val(arg, space_idx_tpl[1], space_idx_tpl[2])
190192

191193
"""
192-
calc_level_val(val::T, space)
194+
calc_level_val(val::T, hidx, space)
193195
194196
If `val` is not a `Broadcasted`, `StencilBroadcasted`, or `Field`, just return `val`.
195197
If it is a `Ref`, return `val[]`. If it is a one element tuple, return the element.
@@ -200,7 +202,7 @@ Base.@propagate_inbounds calc_level_val(val::T, hidx, space) where {V, T <: Tupl
200202
Base.@propagate_inbounds calc_level_val(arg::S, hidx, space) where {S} = arg
201203

202204
"""
203-
calc_level_val(bc::StencilBroadcasted{<:Any, <: MultiplyColumnwiseBandMatrixField}, space)
205+
calc_level_val(bc::StencilBroadcasted{<:Any, <: MultiplyColumnwiseBandMatrixField}, hidx, space)
204206
205207
Call `calc_level_val` on both args of `bc`, place the result of the second arg into shared memory,
206208
and then perform the multiplication.
@@ -284,12 +286,12 @@ Base.@propagate_inbounds function calc_level_val(
284286
end
285287

286288
"""
287-
calc_level_val(bc::StencilBroadcasted{<:Any, <: SetBoundaryOperator}, space)
289+
calc_level_val(bc::StencilBroadcasted{<:Any, <: SetBoundaryOperator}, hidx, space)
288290
289-
A `SetBoundaryOperator` only modifies the two boundary faces, and is the identity
290-
in the interior. At the boundaries we dispatch to `stencil_left_boundary` /
291-
`stencil_right_boundary` (which extract and project the boundary value), and in the
292-
interior we reuse the eagerly-computed value of the argument.
291+
A `SetBoundaryOperator` only modifies the two boundary levels of the space it is applied
292+
to, and is the identity in the interior. At the boundaries we dispatch to
293+
`stencil_left_boundary` / `stencil_right_boundary` (which extract and project the
294+
boundary value), and in the interior we reuse the eagerly-computed value of the argument.
293295
"""
294296
Base.@propagate_inbounds function calc_level_val(
295297
bc::BC,
@@ -303,13 +305,14 @@ Base.@propagate_inbounds function calc_level_val(
303305
op = bc.op
304306
v = threadIdx().x
305307
val_no_bcs = @inline @inbounds calc_level_val(bc.args[1i32], hidx, space)
306-
# A `SetBoundaryOperator` only ever outputs to faces. Gating the boundary logic on
307-
# the (compile-time) staggering type ensures that when this method is compiled for a
308-
# center-output space, the whole face-boundary branch -- including
309-
# `should_call_left_boundary`, whose `idx < left_interior_idx` comparison would
310-
# otherwise mix a `PlusHalf` face index with an integer center index and pull in
311-
# non-GPU-compatible error-formatting code -- is dropped as dead code.
312-
308+
# A `SetBoundaryOperator` is space-preserving (`return_space(op, space) = space`), so
309+
# this method is compiled for both staggerings: the automatic conversion puts one on a
310+
# face output (InterpolateC2F + SetValue), on a center output (DivergenceF2C +
311+
# SetDivergence), and on a face input (GradientF2C + SetValue). Deriving `idx` from
312+
# the compile-time staggering type keeps the two apart, so the `PlusHalf` face index
313+
# only reaches `should_call_*_boundary` when compiling for a face space and its
314+
# `idx < left_interior_idx` comparison never mixes a `PlusHalf` with an integer center
315+
# index -- which would pull in non-GPU-compatible error-formatting code.
313316
idx = space.staggering isa Spaces.CellFace ? (v - half) : v
314317
if Operators.should_call_left_boundary(idx, space, op, bc.args...)
315318
lbw = Operators.left_boundary_window(space)
@@ -338,7 +341,7 @@ Base.@propagate_inbounds function calc_level_val(
338341
end
339342

340343
"""
341-
calc_level_val(bc::StencilBroadcasted{<:Any, <: LinVanLeerC2F}, space)
344+
calc_level_val(bc::StencilBroadcasted{<:Any, <: LinVanLeerC2F}, hidx, space)
342345
343346
Special case of `calc_level_val` for `LinVanLeerC2F`s, which makes the
344347
top and bottom face values not use the fallback `Operators.getidx`, since that
@@ -358,7 +361,7 @@ Base.@propagate_inbounds function calc_level_val(
358361
end
359362

360363
"""
361-
calc_level_val(bc::StencilBroadcasted, space)
364+
calc_level_val(bc::StencilBroadcasted, hidx, space)
362365
363366
Fallback case of `calc_level_val` that calls `Operators.getidx`. This is used for
364367
affine BCs or values that won't fit in shmmem.
@@ -378,7 +381,7 @@ Base.@propagate_inbounds function calc_level_val(
378381
end
379382

380383
"""
381-
calc_level_val(f::Field, space)
384+
calc_level_val(arg::Field, hidx, space)
382385
383386
Returns the value of the field `f` at the thread's index.
384387
When the staggering of `space` is `CellCenter`, the thread with `v == CUDA.blockDim().x` returns `new(eltype(f))`
@@ -400,7 +403,7 @@ Base.@propagate_inbounds function calc_level_val(
400403
end
401404

402405
"""
403-
calc_level_val(bc::StencilBroadcasted{<:Any, <: FDOperatorMatrix}, space)
406+
calc_level_val(bc::StencilBroadcasted{<:Any, <: FDOperatorMatrix}, hidx, space)
404407
405408
Return the correct row of the operator matrix for the current thread
406409
"""
@@ -420,7 +423,7 @@ Base.@propagate_inbounds function calc_level_val(
420423
end
421424

422425
"""
423-
get_op_row(op, args, space)
426+
get_op_row(op, args, hidx, space)
424427
425428
Get the correct row of the operator matrix for the current thread, taking into account boundary conditions.
426429
"""
@@ -476,7 +479,7 @@ end
476479

477480

478481
"""
479-
project_row2_for_mul
482+
project_row2_for_mul(mat1_row, mat2_row, hidx, space)
480483
481484
Projects `mat2_row` onto the correct axis for multiplication with `mat1_row` if necessary, and returns the projected row.
482485
"""

ext/cuda/operators_finite_difference.jl

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -77,28 +77,29 @@ function Base.copyto!(
7777
else
7878
bc′ = disable_shmem_style(bc)
7979
(_, Ni, Nj, Nh) = size(out_fv)
80-
# This uses block and grid indices instead of computing cartesian indices from a
81-
# linear index. The launch configuration is optimized for common use case of 64 face
82-
# levels and Ni = Nj = 4. Both periodic and non-periodic vertical topologies are
83-
# supported (`has_padding_thread` accounts for the extra face level of non-periodic
84-
# spaces); high-resolution columns fall through to `copyto_stencil_kernel!` below.
85-
# `eager_copyto_stencil_kernel!` requires a block size of (n_face_levels, Ni, 1)
86-
# this block config is better for VIJFH. It is only used when the total number of
87-
# threads in a block is between 32 and 256 to avoid underutilization of the GPU and
88-
# errors due to too many registers used when the block size is too large.
80+
# `eager_copyto_stencil_kernel!` requires one x-thread per face level, so a block
81+
# is (n_face_levels, columns_per_block, 1) and the grid indexes the remaining
82+
# columns. Each thread derives a linear column index from its y-thread and block
83+
# index and decomposes it into `(i, j, h)`; this layout suits VIJFH, where the
84+
# vertical axis is contiguous. Both periodic and non-periodic vertical topologies
85+
# are supported (`has_padding_thread` accounts for the extra face level of
86+
# non-periodic spaces), as are masked spaces. High-resolution columns (more face
87+
# levels than fit in a block) fall through to `copyto_stencil_kernel!` below.
8988
# TODO: auto reduce max reg usage when needed because of high res columns
9089
# Size the dynamic shared memory to fit the largest single expression result in
9190
# the broadcasted tree (see `max_eager_shmem_per_thread`). If even that does not
9291
# fit in the device's per-block shared memory, there is no way to eagerly evaluate
9392
# the expression, so error out instead of silently falling back.
9493
eager_shmem_per_thread = max_eager_shmem_per_thread(bc′)
9594
if !high_resolution
96-
# 32 <= n_face_levels * Ni <= 256
9795
# mask.N holds the active column count in a one-element device array;
9896
# reading it on the host needs @allowscalar.
9997
n_columns =
10098
mask isa NoMask ? Ni * Nj * Nh :
10199
CUDA.@allowscalar(mask.N[1])
100+
# One column per block keeps register pressure low, which matters more than
101+
# occupancy until there are enough columns to saturate the device; past that,
102+
# pack as many columns into each block as 256 threads allow.
102103
# 108 is the number of SMs in an A100. TODO: get this value from CUDA.jl to better optimize for different GPUs
103104
threads_dim_y = n_columns > 256 * 108 ? div(256, n_face_levels) : 1
104105
block_dim_x = div(n_columns, threads_dim_y, RoundUp)
@@ -112,11 +113,10 @@ function Base.copyto!(
112113
Split the expression into smaller sub-expressions so that each \
113114
intermediate matrix/vector result is smaller.",
114115
)
115-
# `us` (a `UniversalSize`) encodes `Nij` in its type, so the kernel
116-
# decomposes the linear column index into `(i, j, h)` using a
117-
# `CartesianIndices` whose horizontal extents are compile-time
118-
# constants. This keeps the per-thread `divrem` a cheap fixed-divisor
119-
# operation
116+
# `axes(out)` is passed so the kernel can recover the output layout's
117+
# horizontal extents from its type parameters (see `vijh_params`). The
118+
# `CartesianIndices` the kernel builds from them therefore divides by
119+
# compile-time constants, keeping the per-thread `divrem` cheap.
120120
args = (
121121
strip_space(out, space),
122122
strip_space(bc′, space),

src/MatrixFields/operator_matrices.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,8 +550,10 @@ Operators.stencil_right_boundary(
550550
# An operator matrix can only capture the *linear* part of the operator; the
551551
# constant contributed by the boundary value is zeroed out (see `has_affine_bc`).
552552
# For every operator except GradientF2C/DivergenceF2C, a value-fixing condition
553-
# prescribes the *output* at the boundary as a pure constant, so its linear part is
554-
# zero and the boundary row is empty (`rzero`).
553+
# prescribes the *output* at the boundary as a pure constant, so its linear part is zero
554+
# and the boundary row is all zeros (`rzero` of the row type, which keeps the row's
555+
# bandwidth and zeroes its entries; the multiply clips the out-of-range band entries at
556+
# the column ends, so the row need not be narrowed).
555557
const ValueFixingBoundaryCondition = Union{
556558
Operators.SetValue,
557559
Operators.SetGradient,

src/Operators/finitedifference.jl

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,9 +1007,11 @@ Base.@propagate_inbounds function stencil_left_boundary(
10071007
getidx(space, velocity, idx, hidx),
10081008
Geometry.LocalGeometry(space, idx, hidx),
10091009
)
1010-
# `a⁻` is the (deleted) `LeftBiasedC2F` interior stencil, inlined here.
1010+
# `a⁻` is `LeftBiasedC2F`'s interior stencil, inlined here (the operator remains,
1011+
# but its `stencil_interior` method is now derived from its operator matrix).
10111012
a⁻ = getidx(space, arg, idx - half, hidx)
1012-
# `a⁺` is the (deleted) `RightBiased3rdOrderC2F` interior stencil, inlined here.
1013+
# `a⁺` is the removed `RightBiased3rdOrderC2F` operator's interior stencil, inlined
1014+
# here because this is now its only remaining use.
10131015
a⁺ =
10141016
(
10151017
4 * getidx(space, arg, idx - half, hidx) +
@@ -1034,14 +1036,16 @@ Base.@propagate_inbounds function stencil_right_boundary(
10341036
getidx(space, velocity, idx, hidx),
10351037
Geometry.LocalGeometry(space, idx, hidx),
10361038
)
1037-
# `a⁻` is the (deleted) `LeftBiased3rdOrderC2F` interior stencil, inlined here.
1039+
# `a⁻` is the removed `LeftBiased3rdOrderC2F` operator's interior stencil, inlined
1040+
# here because this is now its only remaining use.
10381041
a⁻ =
10391042
(
10401043
-2 * getidx(space, arg, idx - 1 - half, hidx) +
10411044
10 * getidx(space, arg, idx - half, hidx) +
10421045
4 * getidx(space, arg, idx + half, hidx)
10431046
) / 12
1044-
# `a⁺` is the (deleted) `RightBiasedC2F` interior stencil, inlined here.
1047+
# `a⁺` is `RightBiasedC2F`'s interior stencil, inlined here (the operator remains,
1048+
# but its `stencil_interior` method is now derived from its operator matrix).
10451049
a⁺ = getidx(space, arg, idx + half, hidx)
10461050
return Geometry.Contravariant3Vector(upwind_biased_product(v, a⁻, a⁺))
10471051

@@ -1063,7 +1067,8 @@ Base.@propagate_inbounds function stencil_left_boundary(
10631067
getidx(space, velocity, idx, hidx),
10641068
Geometry.LocalGeometry(space, idx, hidx),
10651069
)
1066-
# `a` is the (deleted) `RightBiased3rdOrderC2F` interior stencil, inlined here.
1070+
# `a` is the removed `RightBiased3rdOrderC2F` operator's interior stencil, inlined
1071+
# here because this is now its only remaining use.
10671072
a =
10681073
(
10691074
4 * getidx(space, arg, idx - half, hidx) +
@@ -1090,7 +1095,8 @@ Base.@propagate_inbounds function stencil_right_boundary(
10901095
getidx(space, velocity, idx, hidx),
10911096
Geometry.LocalGeometry(space, idx, hidx),
10921097
)
1093-
# `a` is the (deleted) `LeftBiased3rdOrderC2F` interior stencil, inlined here.
1098+
# `a` is the removed `LeftBiased3rdOrderC2F` operator's interior stencil, inlined
1099+
# here because this is now its only remaining use.
10941100
a =
10951101
(
10961102
-2 * getidx(space, arg, idx - 1 - half, hidx) +
@@ -1632,9 +1638,23 @@ abstract type BoundaryOperator <: FiniteDifferenceOperator end
16321638
"""
16331639
SetBoundaryOperator(;boundaries...)
16341640
1635-
This operator only modifies the values at the boundary faces, or the center cells adjacent to the boundary faces:
1641+
This operator is the identity in the interior, and replaces the value at each boundary
1642+
for which a condition is given. It preserves the space of its argument, so it modifies
1643+
the boundary faces of a face field or the boundary center cells of a center field. A side
1644+
with no condition is left untouched.
1645+
1646+
The following boundary conditions are supported:
16361647
16371648
- [`SetValue(val)`](@ref): set the value to be `val` on the boundary.
1649+
- [`SetGradient(val)`](@ref): set the value to be `val` on the boundary, projected onto
1650+
the `Covariant3` axis.
1651+
- [`SetCurl(val)`](@ref): set the value to be `val` on the boundary, projected onto the
1652+
`Contravariant123` axis.
1653+
- [`SetDivergence(val)`](@ref): set the value to be `val` on the boundary.
1654+
1655+
The projecting conditions exist so that this operator can reapply the boundary conditions
1656+
of the operator it was derived from when a broadcast is rewritten as an operator matrix
1657+
multiply; see `MatrixFields.modifies_output`.
16381658
"""
16391659
struct SetBoundaryOperator{BCS} <: BoundaryOperator
16401660
bcs::BCS

0 commit comments

Comments
 (0)