Skip to content

Commit 95e5eee

Browse files
committed
gpu: batched CUDA wave-speed path (realize_and_speed) — 6.4e-13 vs CPU, ~85x solve-only
Port realize_and_speed (per-cell flux wave speeds) to an alloc-free device function + batched CUDA kernel (fp64), the last per-cell physics piece before a first-order end-to-end GPU residual. - wavespeed_dev.jl: realize_and_speed_dev + jac15 block-only extraction (3x3 [13:15] via eig3, 4x4 [6:9] companion via reused Schur4), closure5 (N=2 Chebyshev), correct_moments (hyperbolicity branch). No @fastmath: GPU fast-math rsqrt flips the has_complex discriminant at the hyperbolicity boundary (~0.5% of states, ~1e-4 swing); correctly-rounded div/sqrt matches CPU to ~1e-13. - wavespeed_gpu.jl: wave_speeds_batched! kernel (one cell/thread) + host wrapper. - validate_wavespeed_gpu.jl: 8192 real Ma=10/100 states x 3 axes. Validation: GPU vs CPU realize_and_speed max REL 6.4e-13, 0/49152 > 1e-6, PASS. Perf (RTX 6000, 2.1M cells x3 axes): 85x solve-only, 48x end-to-end vs 1-thread CPU. Pure addition under gpu/; src/ and main Project untouched.
1 parent 3750ddf commit 95e5eee

3 files changed

Lines changed: 888 additions & 0 deletions

File tree

gpu/validate_wavespeed_gpu.jl

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env julia
2+
# validate_wavespeed_gpu.jl
3+
#
4+
# Validation + benchmark for the batched CUDA wave-speed path
5+
# (`gpu/wavespeed_gpu.jl`, module `WavespeedGPU`), which inlines the validated device
6+
# function `WavespeedDev.realize_and_speed_dev` (the per-cell `realize_and_speed`).
7+
#
8+
# * HEADLINE gate: GPU (vmin,vmax) for axis 1,2,3 vs the CPU `realize_and_speed`
9+
# reference battery (8192 real evolved Ma=10/100 states), max REL error
10+
# |Δv|/max(1,|vref|) over all nb x 3 x 2. GATE ≤ 1e-6 (expect ~1e-8: the 4x4
11+
# Schur path already matches LAPACK to ~1e-8).
12+
# * Benchmark: GPU batched throughput (Mcell/s) solve-only (resident) AND
13+
# end-to-end (incl H2D/D2H) vs a single-thread CPU baseline (the same scalar
14+
# device function looped on CPU), batch ~2e6 (=128^3), all 3 axes.
15+
#
16+
# ENV: home is OVER QUOTA — read inputs from /storage/scratch1/6/sbryngelson3/gpudata,
17+
# write nothing under home. Run with gpuenv2, depot on scratch.
18+
19+
import Pkg
20+
Pkg.activate(joinpath(@__DIR__, "gpuenv2"))
21+
22+
using CUDA, Printf
23+
include(joinpath(@__DIR__, "wavespeed_gpu.jl"))
24+
using .WavespeedGPU
25+
using .WavespeedGPU.WavespeedDev: realize_and_speed_dev
26+
27+
@assert CUDA.functional() "CUDA not functional"
28+
println("GPU: ", CUDA.name(CUDA.device()))
29+
30+
const DATA = "/storage/scratch1/6/sbryngelson3/gpudata"
31+
32+
# ---------------------------------------------------------------------------
33+
# 1. Load real battery (HEADLINE)
34+
# ---------------------------------------------------------------------------
35+
nb = parse(Int, strip(read(joinpath(DATA, "ws.meta"), String)))
36+
M = reshape(reinterpret(Float64, read(joinpath(DATA, "ws_M.f64"))), 35, nb) # (35,nb) col=cell
37+
Wref = reshape(reinterpret(Float64, read(joinpath(DATA, "ws_ref.f64"))), 6, nb) # rows: ax1 vmin,vmax, ax2..., ax3...
38+
@printf("loaded %d real states (M ∈ [%.3g, %.3g])\n", nb, extrema(M)...)
39+
40+
Mh = Matrix{Float64}(collect(M))
41+
42+
maxrel = 0.0; maxabs = 0.0; argax = 0; argk = 0; argwhich = 0; ndiv = 0
43+
for ax in 1:3
44+
vmn, vmx = WavespeedGPU.wave_speeds_batched(Mh, ax)
45+
rmn = @view Wref[2*ax-1, :]
46+
rmx = @view Wref[2*ax, :]
47+
for k in 1:nb
48+
for (g, r, wch) in ((vmn[k], rmn[k], 0), (vmx[k], rmx[k], 1))
49+
a = abs(g - r); e = a / max(1.0, abs(r))
50+
if e > maxrel
51+
global maxrel = e; global argax = ax; global argk = k; global argwhich = wch
52+
end
53+
global maxabs = max(maxabs, a)
54+
if e > 1e-6; global ndiv += 1; end
55+
end
56+
end
57+
end
58+
@printf("\n=== HEADLINE (GPU wave speeds vs CPU realize_and_speed reference) ===\n")
59+
@printf("nb=%d axes=3 (total comparisons=%d)\n", nb, nb*6)
60+
@printf("max REL error |Δv|/max(1,|vref|) = %.3e (gate ≤ 1e-6) [axis %d, cell %d, %s]\n",
61+
maxrel, argax, argk, argwhich == 0 ? "vmin" : "vmax")
62+
@printf("max ABS error = %.3e (# comparisons > 1e-6: %d / %d)\n", maxabs, ndiv, nb*6)
63+
@printf("GATE: %s\n", maxrel <= 1e-6 ? "PASS" : "FAIL")
64+
65+
# ---------------------------------------------------------------------------
66+
# 2. Benchmark — batch ~2e6 (128^3), all 3 axes
67+
# ---------------------------------------------------------------------------
68+
Bbench = 2_097_152
69+
@printf("\n=== BENCHMARK (B=%d, summed over 3 axes) ===\n", Bbench)
70+
Mb = Matrix{Float64}(undef, 35, Bbench)
71+
@inbounds for k in 1:Bbench
72+
src = ((k - 1) % nb) + 1
73+
for m in 1:35; Mb[m, k] = M[m, src]; end
74+
end
75+
76+
# --- CPU 1-thread baseline: same scalar device fn looped on CPU (one axis) ---
77+
function cpu_baseline(Mb, rng, axis)
78+
s = 0.0
79+
@inbounds for k in rng
80+
a, b = realize_and_speed_dev(
81+
Mb[1,k], Mb[2,k], Mb[3,k], Mb[4,k], Mb[5,k], Mb[6,k], Mb[7,k],
82+
Mb[8,k], Mb[9,k], Mb[10,k], Mb[11,k], Mb[12,k], Mb[13,k], Mb[14,k],
83+
Mb[15,k], Mb[16,k], Mb[17,k], Mb[18,k], Mb[19,k], Mb[20,k], Mb[21,k],
84+
Mb[22,k], Mb[23,k], Mb[24,k], Mb[25,k], Mb[26,k], Mb[27,k], Mb[28,k],
85+
Mb[29,k], Mb[30,k], Mb[31,k], Mb[32,k], Mb[33,k], Mb[34,k], Mb[35,k],
86+
axis, 0.0)
87+
s += a + b
88+
end
89+
s
90+
end
91+
cpu_baseline(Mb, 1:1000, 1) # warmup / compile
92+
ncpu = 50_000 # subset for CPU timing; scale to Mcell/s
93+
t_cpu = 0.0
94+
for ax in 1:3
95+
global t_cpu += @elapsed cpu_baseline(Mb, 1:ncpu, ax)
96+
end
97+
cpu_rate = (3 * ncpu) / t_cpu / 1e6
98+
@printf("CPU 1-thread realize_and_speed_dev: %d cell-axes in %.3f s -> %.4f Mcell/s\n",
99+
3*ncpu, t_cpu, cpu_rate)
100+
101+
# --- GPU solve-only (resident) and end-to-end (incl H2D + D2H), summed 3 axes ---
102+
Md = CuArray(Mb)
103+
vmn = CUDA.zeros(Float64, Bbench)
104+
vmx = CUDA.zeros(Float64, Bbench)
105+
for ax in 1:3
106+
WavespeedGPU.wave_speeds_batched!(vmn, vmx, Md, ax) # warmup each axis
107+
end
108+
CUDA.synchronize()
109+
110+
t_solve = CUDA.@elapsed begin
111+
for ax in 1:3
112+
WavespeedGPU.wave_speeds_batched!(vmn, vmx, Md, ax)
113+
end
114+
end
115+
solve_rate = (3 * Bbench) / t_solve / 1e6
116+
117+
t_e2e = CUDA.@elapsed begin
118+
Md2 = CuArray(Mb)
119+
vmn2 = CUDA.zeros(Float64, Bbench)
120+
vmx2 = CUDA.zeros(Float64, Bbench)
121+
for ax in 1:3
122+
WavespeedGPU.wave_speeds_batched!(vmn2, vmx2, Md2, ax)
123+
end
124+
a = Array(vmn2); b = Array(vmx2)
125+
end
126+
e2e_rate = (3 * Bbench) / t_e2e / 1e6
127+
128+
@printf("GPU solve-only (resident): %d cell-axes in %.4f s -> %.2f Mcell/s (%.1f× vs CPU)\n",
129+
3*Bbench, t_solve, solve_rate, solve_rate / cpu_rate)
130+
@printf("GPU end-to-end (incl H2D/D2H): %d cell-axes in %.4f s -> %.2f Mcell/s (%.1f× vs CPU)\n",
131+
3*Bbench, t_e2e, e2e_rate, e2e_rate / cpu_rate)
132+
133+
@printf("\nDONE\n")

0 commit comments

Comments
 (0)