Skip to content

correct_topography: add batched-Cholesky GPU solver (opt-in via mintpy.topographicResidual.solver)#20

Merged
s-sasaki-earthsea-wizard merged 5 commits into
mainfrom
perf/correct-topography-torch
May 16, 2026
Merged

correct_topography: add batched-Cholesky GPU solver (opt-in via mintpy.topographicResidual.solver)#20
s-sasaki-earthsea-wizard merged 5 commits into
mainfrom
perf/correct-topography-torch

Conversation

@s-sasaki-earthsea-wizard

Copy link
Copy Markdown
Owner

Summary

Adds an opt-in GPU dispatch on the correct_topography step, mirroring the invert_network GPU solver pattern shipped earlier in this fork (PR #8 / upstream insarlab/MintPy#1490).

Toggle: mintpy.topographicResidual.solver = torch (template) or --solver torch (CLI). Default autocpu, so existing setups are unaffected.

Closes #17.

What's in this PR

Five commits, structured top-down for review:

Commit Subject
0214a3f gpu: add batched DEM-error pixelwise solver — new src/mintpy/gpu/dem_error.py with estimate_dem_error_pixelwise_batch (batched normal-equation + Cholesky on (K, P, P) per chunk, auto VRAM sizing, rank-deficient pixel handling) and is_solver_available
373135a dem_error: dispatch pixelwise branch to torch solver — in correct_dem_error_patch, elif solver != 'cpu': routes to the GPU path; CPU range_dist.size == 1 mean-geometry branch and scipy per-pixel loop are byte-for-byte unchanged
60c9e3a gpu: factor shared helpers into mintpy.gpu._commonauto_chunk_size, is_solver_available, etc. lifted out of ifgram_inversion so both GPU steps share them
adc0504 dem_error: add CLI flag + cfg key for solver dispatchcli/dem_error.py gains --solver + --gpu-chunk-size, dem_error.py reads inps.solver / inps.gpuChunkSize and passes them into data_kwargs, defaults cfg files wire mintpy.topographicResidual.solver + gpuChunkSize keys
c01a399 docs/gpu.md: document correct_topography GPU dispatch — title generalised, new §3 + §5 subsection, performance / storage caveat / wiki link

Plus 1 new test file (tests/test_gpu_dem_error.py, 4 tests) that pin the solver / fallback / phase_velocity contracts.

Why now — bench evidence

Across 5 InSAR scenes (sibling PR #9, Issue #19 Tier 1) the GPU dispatch is shown to be:

  1. Numerically equivalent to CPU within float32 round-offrms / |cpu|.max < 1e-5 on every scene; tightest margin is 4.33e-6 (SanFranBay delta_z), loosest is 3.12e-10 (SanFranSF ts_cor).
  2. Processor-independent — works identically against ISCE2/topsStack, GMTSAR, ARIA, and ROI_PAC ingest paths.
  3. Wavelength-independent — C-band Sentinel-1 and L-band ALOS PALSAR produce equivalent agreement.
  4. Coordinate-frame-independentgeometryGeo.h5 (geo) and geometryRadar.h5 (radar) both work without code changes.

Speedup curve (all on local SSD, RTX 5080):

Scene K (pixels) D P processor speedup
Kuju 226k 24 4 ROI_PAC (L-band) 2.53×
SanFranSF 260k 114 4 ARIA 2.43×
Fernandina 270k 98 6 ISCE2 2.56×
SanFranBay 326k 333 4 GMTSAR 1.76×
Galapagos 3.4M 98 4 ISCE2 6.15×

The closed-form model behind this curve is on the wiki: GPU Speedup Scaling Model. Short version: K is the GPU-parallelisable batch dimension; D and P scale CPU/GPU walls similarly. Expect ≳ 4× speedup once K ≳ 1M.

Design choices

  • einsum not used — matches the existing mintpy.gpu.ifgram_inversion convention (idiom not used elsewhere in upstream codebase).
  • Phase-velocity path not implementedphase_velocity=True raises NotImplementedError in the GPU branch. Production default is False; covering velocity-domain residuals can come later if there's demand.
  • No silent CPU fallback — selecting solver=torch without a visible CUDA device raises immediately; the GPU Quick Start wiki page explains setup.
  • Mean-geometry branch (pixelwiseGeometry=no) stays on CPU — it's already pixel-batched in numpy and no per-pixel loop exists to accelerate.
  • solver / gpuChunkSize not added to config_keys for the update-mode skip check, matching invert_network's decision; numerical equivalence within float32 round-off means rebuilding an existing demErr.h5 because the user flipped the solver is unnecessary.

Test plan

  • pytest tests/test_gpu_dem_error.py — 4 passed
  • dem_error.py --help shows new --solver / --gpu-chunk-size flags under a solver argument group
  • E2E dem_error.py --solver cpu (11.2 s) vs --solver torch (32.2 s) on FernandinaSenDT128 SSD copy with explicit -s 20170910 20180613 (P=6); CPU/GPU agree at delta_z rms/|cpu|.max = 1.48e-6 and ts_cor 6.70e-8
  • pre-commit run --all-files — all 13 hooks pass + 1 skip (json)
  • 5-scene survey (sibling PR #9) — gate holds, processor/wavelength/coord-frame independence verified

Out of scope (deliberate follow-ups)

Related

🤖 Generated with Claude Code

Add estimate_dem_error_pixelwise_batch in src/mintpy/gpu/dem_error.py,
a CUDA-batched replacement for the per-pixel scipy.linalg.lstsq loop
in the pixelwise-geometry branch of correct_dem_error_patch
(mintpy.dem_error). The solver materializes a (K, num_date, num_param)
design tensor per chunk and solves the normal equations via
cuSolver-batched Cholesky, mirroring the style of
mintpy.gpu.ifgram_inversion (no einsum; @ + .transpose(-1, -2)).
Rank-deficient pixels are detected via cholesky_ex info codes and
zeroed so NaN/Inf cannot propagate.

phase_velocity=True is not yet implemented and raises explicitly. The
caller-side dispatch in mintpy.dem_error is added separately so the
CPU path is byte-for-byte unchanged here.

tests/test_gpu_dem_error.py covers the scene-mean (D, 1) and per-pixel
(D, K) baseline cases against a per-pixel CPU reference loop on small
synthetic fixtures (num_date=98, num_pixel=1000). The rms / max-abs
differences are printed for inspection; numerical equivalence is not
asserted at this stage. Unsupported solver and phase_velocity=True
error paths are also covered.

Assisted-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a solver kwarg (default 'cpu') to correct_dem_error_patch and
route the pixelwise-geometry branch to
mintpy.gpu.dem_error.estimate_dem_error_pixelwise_batch when
solver != 'cpu'. The CPU per-pixel loop is preserved byte-for-byte
when solver='cpu', and the mean-geometry branch (already pixel-batched
on CPU via scipy multi-RHS lstsq) is untouched.

The mintpy.gpu.dem_error import is local to the dispatch branch so
mintpy.dem_error remains importable on torch-less environments.

CLI flag and template-key wiring for the new solver kwarg are
intentionally deferred to a follow-up commit; for now the dispatch is
reachable only via direct kwarg passthrough from a future
correct_dem_error caller.

Assisted-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both mintpy.gpu.ifgram_inversion and mintpy.gpu.dem_error reduce a
per-pixel weighted least-squares problem to batched normal equations
plus cuSolver-batched Cholesky. They duplicated the torch/CUDA probe,
SUPPORTED_SOLVERS, the VRAM-aware chunk sizer, and the Cholesky core
with rank-deficient fallback. Move those into _common.py and import
them from each solver. ifgram_inversion._solve_cholesky is reduced to
a thin wrapper that assembles the weighted system; dem_error's
_solve_cholesky_batched is replaced with a direct call to the shared
core. Public APIs (estimate_timeseries_batch, estimate_dem_error_
pixelwise_batch, is_solver_available) are unchanged.

Verified by running tests/test_gpu_ifgram_inversion.py (6) and
tests/test_gpu_dem_error.py (4): 10/10 pass; the synthetic CPU vs GPU
diffs for dem_error are unchanged (delta_z rms/|cpu|max ~ 3e-6,
ts_cor ~ 6e-9, ts_res ~ 7e-5).

Assisted-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire mintpy.topographicResidual.solver = cpu|torch (and gpuChunkSize)
through the CLI and template-read path so users can opt into the GPU
dispatch shipped in the prior three commits via smallbaselineApp.py /
dem_error.py without monkeypatching.

Mirrors the mintpy.networkInversion.solver pattern from the
invert_network step:

* cli/dem_error.py adds --solver / --gpu-chunk-size argparse flags and
  reads both as template keys in read_template2inps.
* dem_error.py: correct_dem_error_patch accepts a new chunk_size kwarg
  and passes it through to mintpy.gpu.dem_error.estimate_dem_error_pixelwise_batch;
  correct_dem_error reads inps.solver and inps.gpuChunkSize and packs
  them into data_kwargs alongside the existing G_defo / phase_velocity.
* defaults/smallbaselineApp.cfg adds mintpy.topographicResidual.solver
  and .gpuChunkSize entries with the same doc block style as the
  networkInversion variant.
* defaults/smallbaselineApp_auto.cfg resolves auto to cpu / 0.

solver and gpuChunkSize are deliberately NOT added to dem_error.py's
config_keys list (used for update-mode skip metadata), matching
invert_network's choice: CPU and GPU outputs are numerically
equivalent within float32 round-off (verified across 5 scenes at
rms / |cpu|.max < 1e-5; see sibling mintpy-benchmark PR #9), so an
existing demErr.h5 from solver=cpu does not need rebuilding when the
user later switches to solver=torch.

Smoke checks:
- pytest tests/test_gpu_dem_error.py: 4 passed
- dem_error.py --help shows new flags in a solver argument group
- E2E on FernandinaSenDT128 SSD: --solver cpu (11.2s) vs --solver torch
  (32.2s), delta_z rms/|cpu|.max = 1.48e-6, ts_cor rms/|cpu|.max = 6.70e-8
- pre-commit run --all-files: all hooks pass

Assisted-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a parallel section to the existing invert_network coverage:

* Title generalised from "for the network inversion" to "for GPU
  acceleration", since two steps now ship a torch solver.
* Intro paragraph lists both opted-in steps + which branch each
  affects (invert_network: all weighted-LSQ; correct_topography:
  pixelwise-geometry only — the mean-geometry branch is already
  pixel-batched on CPU and stays there).
* New §3 "Enable on correct_topography" mirrors §2's CLI / template /
  example-data subsections.
* §5 Performance gets a subsection for correct_topography pointing at
  the Issue #19 Tier 1 survey + per-scene reports on the sibling repo.
  Headline numbers (2-2.5x on tutorial-scale, 6.15x on Galapagos
  K=3.4M, gate <1e-5 across 5 scenes) and the storage caveat (NAS can
  erase the speedup at small scenes; Fernandina 2.56x SSD vs 0.97x NAS)
  are quoted so users have realistic expectations before opting in.
* Wiki link to the GPU Speedup Scaling Model page for the closed-form
  model behind the empirical curve.

Assisted-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

perf: GPU acceleration of correct_topography (pixelwise path)

1 participant