Skip to content

feat(qp): pure-qP VTI/TTI on impl='c' + Born pair; acoustic-family fixes - #125

Merged
GeophyAI merged 5 commits into
devfrom
feat/qp-tti-cuda
Oct 23, 2026
Merged

GeophyAI merged 5 commits into
devfrom
feat/qp-tti-cuda

Conversation

@GeophyAI

Copy link
Copy Markdown
Collaborator

Five self-contained commits; reviewing them one by one is easiest. Rebased on dev (#124). The CHANGELOG has the full entries.

Commits

  1. feat(qp): AcousticVTI/TTI on impl='c', s_gradient modes, qP Born pair
    • CUDA kernels for the pure-qP equations of Liang et al. (2023): forward, full and boundary-saving backward (gpu / cpu / disk; fp32 / fp16 / bf16, int8 refused), orders 2-8 and runtime order. Records match eager to 1e-6 – 2e-4.
    • s_gradient='adjoint' (new default), 'frozen', 'exact'. The exact derivative of the S_n step is ill-conditioned (eager fp32 and fp64 differ by 2-3 % on the canonical gradient test).
    • AcousticVTILSRTM / AcousticTTILSRTM: the Born pair, eager and c. The compiled backward returns the mp gradient and refuses the rest.
    • PropTorch warns once when dt breaks the scheme's stability limit (the paper's eq. B-9 for VTI; a safe-side bound for TTI).
    • Docs: user-guide/qp_born.md (forward, adjoint and Born derivation), example notebook 34 (RTM / LSRTM of a tilted bed with the isotropic, VTI and TTI operators).
  2. fix(acoustic): compiled backward is the exact transpose of the forward, CPML band included (Acoustic, Acoustic3D, AcousticVRZ, AcousticVRZ3D, AcousticLSRTM, AcousticLSRTM3D, ViscoAcoustic on impl='c').
    • The forward ran the bare Laplacian where db is nonzero, and the adjoints' interior bounds stopped one cell short.
    • The LSRTM adjoint applied the forward CPML operator to the adjoint field (reflectivity dot test 0.11, now 1e-6).
    • Checkpoint replays now double-buffer the CPML memory.
    • Against an eager twin of each compiled forward: records 5e-7 – 2e-5 and gradients 2e-6 – 2e-5 in every memory mode. Domain-decomposed runs stay bit-identical to one card. V100 wall time changes by -5 % to +2 %.
    • 17 docs notebooks re-run, with inversion accuracy compared before and after. The 07 / 19 helpers now read ReconState.rows.
  3. fix(c): refuse gradients the compiled backward never computes
    • LSRTM / VRZ: the wavelet's gradient (it came back as zeros or wrong values).
    • LSRTM with Ckpt: the RWI vp gradient.
    • ModelParallel refuses a wavelet that requires grad.
  4. fix(dd): LSRTM cpu-staged boundary race under ModelParallel
    • A per-call copy stream left a prefetch queued that overwrote a ring slot being restored. In 3-D, two identical runs differed by up to 12 % of the gradient's peak.
    • Both LSRTM backwards now use the shared BoundarySession. cpu staging is bit-identical to gpu-direct.
    • storage='disk' is refused for the pair.
  5. fix(boundary): 3-D int8/fp16 restore race on edge and corner cells
    • The band-only restore wrote such cells once per band, from differently quantized copies.
    • Now one band owns each cell, so the result equals the full-grid restore bit for bit.
    • The only machine-code change is the new kernel. The 3-D int8 noise floors in gate/noise_floors.json are now null (bit-stable).

Behaviour changes

  • Default eager gradient of AcousticVTI / AcousticTTI (s_gradient='adjoint'; it was 'exact').
  • Records and gradients of the second-order acoustic family on impl='c' (commit 2).
  • New refusals: the LSRTM / VRZ wavelet gradient and the LSRTM Ckpt vp gradient on impl='c'; a wavelet that requires grad under ModelParallel; LSRTM with disk storage under ModelParallel.

Rebase on dev

Tests

  • After the rebase, on V100 with the core rebuilt: visco (fix(visco): per-shot gbar for batched models #124's batched test, CUDA and eager), slot-table and from-pool layout tests, qP — 272 passed. test_forward_scratch_from_pool.py still expected four forward scratch grids for ViscoAcoustic; commit 2 adds a fifth (the step's update), so the count is updated in that commit.
  • Before the rebase, on V100: test_qp_aniso_cuda.py, test_qp_stability.py, test_equation_registry_public_api.py, test_aniso_free_surface_guard.py — 138 passed.
  • When the branch was split into these commits, each intermediate state was built from scratch, imported, and passed the CPU tests.
  • Not run here: the full domain-decomposition test chain.

🤖 Generated with Claude Code

- qp_vti2d / qp_tti2d CUDA kernels: forward, full and boundary-saving backward
  (gpu/cpu/disk; fp32/fp16/bf16, int8 refused), orders 2-8 and runtime order.
- The adjoint reads the forward state in the absorbing layer: full mode keeps
  five fields per step, boundary saving stores the whole CPML frame.
- s_gradient='adjoint' (new default), 'frozen' or 'exact'.
- PropTorch warns once when dt breaks the stability limit (Liang et al. 2023,
  eq. B-9; a safe-side bound for TTI).
- AcousticVTILSRTM / AcousticTTILSRTM (Born pair, eager and c); the compiled
  backward returns mp's gradient and refuses the rest (c_refuse_grads hook).
- Tests: test_qp_aniso_cuda.py, test_qp_stability.py, diagnostics test/qp_*.py;
  docs qp_born.md and the LSRTM example notebook 34 (isotropic / VTI / TTI on a
  tilted bed).
Acoustic, Acoustic3D, VRZ 2-D/3-D, LSRTM 2-D/3-D, ViscoAcoustic on impl='c':
- the forward runs the CPML update on the db leak cell, the adjoints match it;
- the LSRTM adjoint transposes the CPML band (dot test 0.11 -> 1e-6);
- checkpoint replays double-buffer psi (in place they raced);
- ViscoAcoustic keeps the ring update in its history (= Acoustic with Q off).
Records and gradients change: vs eager twins 5e-7..2e-5 / 2e-6..2e-5, DD
bitwise to one card, V100 time -5..+2 %.  Affected notebooks re-run; 07 and
19 read the eager ring from ReconState.rows (broken since 5fb947d).
- LSRTM 2-D/3-D and VRZ 2-D/3-D on impl='c': a wavelet that requires grad,
  and LSRTM's vp under Ckpt, raise NotImplementedError before anything runs
  (they came back zero, wrong or None).
- ModelParallel refuses a wavelet that requires grad, for every equation.
- Gradient matrix: KNOWN_REFUSED_GRADIENTS; a test per refusal.
The stepped LSRTM backward built its own copy stream per call; under DD (one
call per step) the next step's prefetch stayed on a stream nothing waited on
and could overwrite a refilled ring slot (3-D: up to 12 % of the gradient
peak, run to run).  Both LSRTM backwards now use the DD BoundarySession; a
stepped call without one drains its stream.  cpu staging is bitwise equal to
gpu and to itself (dd_offload_check EQ=lsrtm, lsrtm_staging_check.py);
storage='disk' is refused at construction.
The compact 3-D restore wrote a multi-band cell once per band; int8/fp16
quantize each face with its own scales, so the last writer won and gradients
changed run to run (int8 since 0.1.0, fp16 since 0.2.0).
boundary_kernel3d_compact_owned restores such a cell from the band the scan
kernel writes last: equal to SWEEP_BOUNDARY_NO_COMPACT=1 bit for bit, other
kernels' SASS unchanged.  dd_offload_check is now bitwise in every dtype;
gate: the 3-D int8 noise floors (8 passes, Ada and V100, bit-stable) nulled.
@GeophyAI
GeophyAI merged commit 830f9a0 into dev Oct 23, 2026
1 check passed
@GeophyAI
GeophyAI deleted the feat/qp-tti-cuda branch October 23, 2026 12:27
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.

1 participant