Skip to content

Commit 9c27634

Browse files
committed
perf(BMT): recover the primal tendency from the Rosenbrock Jacobian pass
ForwardDiff's Jacobian pass already evaluates the primal tendency f as the dual .value and discards it, so a separate f = g(x) repeats a full (quadrature- dominated) tendency evaluation. Recover f from the same pass: seed the inputs with static `Dual`s, evaluate g once, and read the values and partials directly into the same `FieldVector` species type as `x` (`MicroState1M` or `MicroState2MP3`). Applies to the 2M+P3 and the 1M ExactJacobian paths; the analytic Donor/CoupledDonor 1M Jacobians (no f by-product) keep their separate evaluation. Static seeding replaces `DiffResults.JacobianResult`, whose mutable result buffer heap-allocates on GPU; the `DiffResults` dependency is dropped. Both paths go through the shared `_tendency_and_jacobian(jacobian, g, x)` method, removing the duplicated code in the 2M+P3 substep loop. Reduces the per-substep cost by roughly 27%. The tendency and Jacobian are unchanged, the path stays type-stable, and it composes with an outer AD pass.
1 parent 90f0aea commit 9c27634

2 files changed

Lines changed: 38 additions & 5 deletions

File tree

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ authors = ["Climate Modeling Alliance"]
55

66
[deps]
77
ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c"
8+
DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
89
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
910
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
1011
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
@@ -27,6 +28,7 @@ EmulatorModelsExt = ["DataFrames", "MLJ"]
2728
[compat]
2829
ClimaParams = "1.0.18"
2930
DataFrames = "1.6"
31+
DiffResults = "1"
3032
DocStringExtensions = "0.8, 0.9"
3133
FastGaussQuadrature = "1"
3234
ForwardDiff = "0.10, 1"

src/BMT_rosenbrock.jl

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,10 @@ fields as the `Instantaneous` entry (without the activation diagnostic).
429429
Tsub = T
430430
for _ in 1:nsub_eff
431431
g = Instantaneous2MP3Tendency(mp, tps, ρ, Tsub, q_tot, logλ)
432-
f = g(x)
433432
x_prev = x
434433
if all(isfinite, x)
435-
J = _apply_growth(mode.growth, FD.jacobian(g, x))
434+
f, J_raw = _tendency_and_jacobian(mode.jacobian, g, x)
435+
J = _apply_growth(mode.growth, J_raw)
436436
z = _species_mask(mode.jacobian, mode.growth)(x)
437437
d = if all(isfinite, J)
438438
_rosenbrock_update(x, f, J, z, h) - x
@@ -442,6 +442,7 @@ fields as the `Instantaneous` entry (without the activation diagnostic).
442442
d = _apply_limiter(mode.limiter, x, d, ρ, Tsub, q_tot, Lv_over_cp, Ls_over_cp, tps)
443443
x = max.(x .+ d, 0)
444444
else
445+
f = g(x)
445446
x = _euler_update(x, f, h)
446447
end
447448
Δ = x - x_prev
@@ -749,6 +750,36 @@ end
749750
"Exact ForwardDiff Jacobian provider for [`_rosenbrock_average_1m`](@ref)."
750751
@inline _ad_jacobian_1m(g, x) = FD.jacobian(g, x)
751752

753+
"""
754+
_tendency_and_jacobian(jacobian, g, x)
755+
756+
The raw substep tendency `f = g(x)` and the substep Jacobian (before the growth
757+
treatment) for a [`Jacobian`](@ref) option, returned as `(f, J)`.
758+
759+
For [`ExactJacobian`](@ref) both the value and the Jacobian are taken from a
760+
single `ForwardDiff` pass: `x` is seeded with unit partials and `g` is evaluated
761+
once, giving the primal `f` and the `N×N` Jacobian as static arrays. The
762+
donor-based matrices ([`DonorJacobian`](@ref), [`CoupledDonorJacobian`](@ref))
763+
produce no tendency by-product, so `f = g(x)` is evaluated separately.
764+
"""
765+
@inline function _tendency_and_jacobian(::ExactJacobian, g, x::SA.FieldVector{N, FT}) where {N, FT}
766+
Tag = typeof(FD.Tag(g, FT))
767+
dx = SA.SVector(ntuple(
768+
i -> FD.Dual{Tag}(x[i], ntuple(j -> ifelse(i == j, one(FT), zero(FT)), Val(N))...),
769+
Val(N),
770+
))
771+
y = g(dx)
772+
f = typeof(x)(ntuple(i -> FD.value(y[i]), Val(N))...)
773+
J = SA.SMatrix{N, N, FT}(ntuple(
774+
k -> FD.partials(y[(k - 1) % N + 1], (k - 1) ÷ N + 1),
775+
Val(N * N),
776+
))
777+
return f, J
778+
end
779+
@inline _tendency_and_jacobian(::DonorJacobian, g, x) = (g(x), _jacobian_1m_linearized(g, x))
780+
@inline _tendency_and_jacobian(::CoupledDonorJacobian, g, x) =
781+
(g(x), _jacobian_1m_relinearized(g, x))
782+
752783
"""
753784
_full_species_mask(x)
754785
@@ -777,18 +808,17 @@ and the increment limiter through [`_jacobian_provider`](@ref),
777808
h = Δt / FT(nsub_eff)
778809
Lv_over_cp = TDI.TD.Parameters.LH_v0(tps) / TDI.TD.Parameters.cp_d(tps)
779810
Ls_over_cp = TDI.TD.Parameters.LH_s0(tps) / TDI.TD.Parameters.cp_d(tps)
780-
jacobian = _jacobian_provider(mode.jacobian)
781811
mask = _species_mask(mode.jacobian, mode.growth)
782812

783813
x = MicroState1M{FT}(q_lcl, q_icl, q_rai, q_sno)
784814
x₀ = x
785815
Tsub = T
786816
for _ in 1:nsub_eff
787817
g = Raw1MTendency(mp, tps, ρ, Tsub, q_tot)
788-
f = g(x)
789818
x_prev = x
790819
if all(isfinite, x)
791-
J = _apply_growth(mode.growth, jacobian(g, x))
820+
f, J_raw = _tendency_and_jacobian(mode.jacobian, g, x)
821+
J = _apply_growth(mode.growth, J_raw)
792822
z = mask(x)
793823
d = if all(isfinite, J)
794824
_rosenbrock_update(x, f, J, z, h) - x
@@ -798,6 +828,7 @@ and the increment limiter through [`_jacobian_provider`](@ref),
798828
d = _apply_limiter(mode.limiter, x, d, ρ, Tsub, q_tot, Lv_over_cp, Ls_over_cp, tps)
799829
x = max.(x .+ d, 0)
800830
else
831+
f = g(x)
801832
x = _euler_update(x, f, h)
802833
end
803834
Δ = x - x_prev

0 commit comments

Comments
 (0)