fullseye

GPU Optimization Design-Pattern Catalog (for RTX 5090 / Blackwell sm_120)

日本語 · English

Target GPU (confirmed): NVIDIA RTX 5090, Blackwell, compute capability sm_120, 32 GB VRAM, driver 610.74. Current state (confirmed): Only torch 2.11.0+cpu and jax 0.7.1 (CpuDevice) are installed, so nothing runs on the GPU right now. sm_120 requires a build from the CUDA 12.8+ generation. Nature of this catalog: Written without web search, from the RAD corpus (D:/docs/*_corpus_v2/, existence confirmed) plus a knowledge base. Guesses are explicitly marked “(guess)”. Each workload is tied back to real code that was read (physarum_search.py / shapematch.py / afterman/eco_world.py).

Source notation:


0. Structure of our three workloads (measured from the code)

# Workload Compute core Current bottleneck Batch dimension Precision requirement
1 Physarum solver physarum_search.py Linear solve of the Laplacian L(D)p=b × time iteration (conductivity D updated every iteration) Dense n×n solved every iteration with torch.linalg.solve (O(n³)). Worse, A is rebuilt every iteration with torch.zeros(n,n) Many mazes × many parameters (μ, dt, D_init) fp64 (measured from code: fixed to torch.float64. The Laplacian is prone to poor conditioning)
2 Shape matching shapematch.py Inner product of template edge-gradient vectors with image gradients, evaluated over many positions × scales × angles Python double loop _scan_flat / _score_at (fancy-index inner products). Scale/angle are also Python loops Position × scale × angle × multiple instances fp32 is enough (inner product of gradient directions; the dynamic range of the correlation is narrow)
3 Afterman evolution afterman/eco_world.py Parallel forward evaluation of a population (RNN policies) + structural evolution Already cleanly batched in JAX (per-individual weights w_in/w_rec/w_out via einsum("nij,nj->ni"), T steps via lax.scan, jit(static_argnums=1)) Population size n fp32/bf16 is enough (evolutionary fitness only needs to be approximate)

Key asymmetry: #3 is already written GPU-ready (SoA + batched einsum + scan). #1 and #2 are “Python loops + sequential dense solve” and have the most headroom for GPU acceleration. Priority: #1 and #2 first; #3 mostly just works once the wheel is installed and jax.devices() becomes cuda (details in §5).


1. Design-pattern catalog

Each pattern = name / when to use / pitfall / which of ours it helps.

Types of parallelism

P1. Data-parallel (straightforward SIMT parallelism)

P2. Batched-kernel (fold many small problems into one kernel) ★ most important

P3. Warp cooperation (reduction via shared memory + warp shuffle)

P4. Pipeline parallelism

Memory hierarchy

P5. Coalesced access (adjacent threads read adjacent addresses)

P6. Shared-memory tiling

P7. Reduce host↔device transfers (return only the result) ★ a frequent pitfall

P8. Occupancy (don’t leave SMs idle)

Batching best practices

P9. Fold loops into a batch axis

P10. GPU-ify correlation/convolution (FFT vs direct vs im2col)

P11. Variable length via padding + mask

P12. AoS → SoA (array of structs → struct of arrays)

Choosing numerical libraries

P13. Climb in the order “off-the-shelf API → cupy/torch batched → write Triton/CUDA”

P14. Get onto Tensor Cores (turn it into GEMM)

P15. Kernel fusion (torch.compile / Triton / CUDA Graphs)

P16 (pitfall). Memory explosion from densification

Precision

P17. Choosing precision (where fp64 is required vs. where fp32/bf16/TF32 suffices)

Iterative-solver best practices (directly relevant to #1)

P18. Matrix-free (give the action without building the matrix)

P19. Preconditioning

P20. Warm start (use the previous step’s solution as the initial guess) ★ directly relevant to time iteration


2. Pitfall checklist (summary)

Pitfall Symptom Fix Applies to
Submitting small problems to the GPU one at a time Slower than CPU Batch them with batched-kernel (P2) #1, #2
.item()/float()/.cpu() inside the loop GPU waits every iteration Batch the convergence check every K iterations, accumulate history on the device (P7) #1
Allocating dense A every iteration OOM / bandwidth-bound Sparse + matrix-free (P16/P18) #1
Materializing all scale×angle×position at once VRAM explosion Stage it with a pyramid (P9) #2
Careless switch to fp32 Fails to converge / the path changes Mixed-precision iterative refinement, check against an fp64 reference (P17) #1
Recompilation under dynamic shapes jit/compile runs every time Fixed shapes + padding/mask (P11/P15) #2, #3
Branches that cause warp divergence Effective parallelism drops Same branch across the whole batch (P1) all
Writing Triton, then realizing off-the-shelf sufficed Wasted effort Measure a baseline → climb in stages (P13) all

3. Numerical-library quick reference

What you want First choice Alternatives Source
Dense batched linear systems (B,n,n) torch.linalg.solve (batch-capable) cuSOLVER batched, cupy.linalg [knowledge]
Sparse batched linear systems (shared/per-instance sparsity) torch-sla (auto-dispatch across cuDSS/CuPy/torch-iterative) cupyx.scipy.sparse.linalg.cg/spsolve [corpus: arXiv 2601.13994]
Sparse CG/BiCGSTAB + preconditioner cupyx.scipy.sparse.linalg torch-sla iterative backend [knowledge/corpus]
Fast triangular solve (preconditioner application) Measure off-the-shelf → subdomain approach if insufficient Hand-written Triton/CUDA [corpus: arXiv 2508.04917]
Correlation/convolution (shape match) F.conv2d (small template) FFT (large template), im2col+GEMM [knowledge]
Parallel population forward + scan JAX vmap/lax.scan/jit (#3 already practices this) torch.vmap + torch.compile [knowledge]
Random numbers (population, stochastic events) jax.random.split (#3 already practices this, eco_world.py:244) torch.Generator per-stream [knowledge]
Elementwise fusion is the bottleneck torch.compile / Triton CUDA Graph (fixed iterations) [corpus: mlops doc_0717/0521]
Mixed-precision iterative refinement fp32 inner + fp64 correction (fp16 also possible with rescaling) [corpus: arXiv 2602.14450]
Enable TF32 (accelerate fp32 GEMM) torch.set_float32_matmul_precision("high") allow_tf32=True [knowledge]

JAX essentials (for #3) [knowledge]:


4. Notes on introducing a CUDA build (investigation only; do not actually install)

Current state: torch 2.11.0+cpu / jax 0.7.1 CpuDevice. sm_120 (Blackwell) requires the CUDA 12.8+ generation. The following are hunches; confirm sm_120 support in the release notes before installing.

PyTorch (guess-based, needs confirmation):

# Assumes uninstalling the existing CPU build, then installing the CUDA 12.8 wheel (version needs confirmation)
py -3.11 -m pip uninstall torch
py -3.11 -m pip install torch --index-url https://download.pytorch.org/whl/cu128
# If a cu129-family build exists, it may have newer Blackwell support (guess)

JAX (guess-based, needs confirmation):

py -3.11 -m pip install -U "jax[cuda12]"

Common pitfalls (knowledge):


5. “What to GPU-ify first” — priority and rationale

Order of attack (cost-effectiveness)

First move: rewrite the Physarum solver (#1) from “dense direct → sparse + matrix-free iterative + warm start”. But sparsify on CPU/numpy first and lock in correctness before going to GPU.

Second move: turn shape matching (#2) from “Python double loop → conv2d/im2col batch”.

Third move: Afterman (#3) just needs the wheel installed. Almost no code change.

One-line summary

The first move is “rebuild the Physarum solver into sparse + matrix-free iterative + warm start (nail down correctness on CPU first).” It crushes the triple structural defect of the current dense O(n³) / per-iteration reallocation / per-iteration sync, and the tailwinds — the affinity of time iteration with warm start, and the existence of a batched sparse-solve library (torch-sla) — all line up, so the return on the GPU investment is fastest.


Appendix: RAD corpus sources whose existence was confirmed for this catalog

All confirmed to exist by grep under D:/docs/numerical_methods_corpus_v2/ and D:/docs/mlops_corpus_v2/ (file paths are the clusters in the body’s footnotes).

The specifics of TF32/FP8/Blackwell sm_120, wheel versions, and the fp64 rate are from the out-of-corpus knowledge base + guesses, and must be confirmed at actual install time against release notes / measurement.


Appendix 2: Measured results of GPU-izing the Physarum solver (2026-08-26, run on the RTX 5090)

The results of implementing the “first move” listed in the catalog above and actually running it on the GPU (loco venv’s torch 2.11.0+cu128, CUDA 12.8, RTX 5090). physarum_search.py / tests/test_physarum_search.py under packages (the imgevolve root).

What was done (patterns applied)

Measurements (honest)

| Scale | CPU sequential sparse (baseline) | GPU best (FP32+CUDA graph) | Speedup | |—|—|—|—| | k=64 (n=4,096, B=16) | 27.9 s | 1.12 s | 24.9x | | k=128 (n=16,384, B=16) | 89.1 s | 1.09 s | 82.2x | | k=200 (n=40,000, B=16) | 230.7 s | 1.42 s | 162.3x |

Remaining moves (not yet started)