|
FZGPUModules 2.0
GPU-accelerated modular compression pipelines
|
Header: modules/transforms/cdf97/cdf97_stage.h Class: fz::Cdf97Stage<TInput> Category: Transform (lossless, invertible, size-preserving)
The CDF 9/7 biorthogonal wavelet transform — the DWT front-half of the SPERR compressor (CDF 9/7 DWT -> SPECK bit-plane coding). A dimension-aware, size-preserving, lossless, invertible float -> float basis change: it replaces a field with its multi-level wavelet coefficients (same element type, same element count), separable per axis with dyadic (Mallat) recursion (3-D also supports wavelet-packet decomposition for anisotropic volumes, selected automatically). See THIRD_PARTY.md for the port relationship to NCAR/SPERR (lifting constants, boundary handling, level-count rule, and the 3-D dyadic/wavelet-packet selection rule are direct ports; the GPU kernels are FZGPUModules' own).
Feeds a quantizer, then (optionally) Speck2DStage — see stage_speck2d and examples/presets/sperr_gpu.toml for the full SPERR-style pipeline. Quantizing this stage's coefficients directly does not guarantee a pointwise bound on the reconstructed field — CDF 9/7's synthesis-filter gain differs by level, so a uniform coefficient threshold doesn't translate to a uniform reconstructed-domain error. For an actual guarantee, pair with `Cdf97OutlierCorrectStage` — see that page and examples/presets/sperr_gpu.toml for the complete, bound-guaranteed pipeline.
| Parameter | Constraint |
|---|---|
TInput | float or double |
double reproduces SPERR's coefficients bit-for-bit (sperr::CDF97 runs entirely in double). float is a faster, deliberately non-bit-exact variant (lifting constants are derived in double, but the arithmetic itself is float).
Each transform line is processed in shared memory, so the largest dimension must satisfy maxdim * sizeof(TInput) <= 48 KiB (6144 elements for double, 12288 for float). A larger extent throws at execute(), rather than producing wrong output; the long-line (global-memory) fallback is future work.
Two non-numerical (bit-exact-preserving) optimizations are implemented, tried automatically in this order, each falling back cleanly when it can't be used:
cudaOccupancyMaxActiveBlocksPerMultiprocessor, not a hardcoded compute-capability gate) whenever the needed grid doesn't fit one cooperative wave on the current GPU. Measured on H100: +45% at 256², +29% at 512², ~0% at 1024² and above (occupancy-limited).elem_stride != 1) ran 2.2–2.9x slower than a contiguous pass of the same size purely from memory coalescing. One block now owns several adjacent lines and loads/stores them via coalesced row-major sweeps before calling the same, unchanged lifting arithmetic. Complements (doesn't overlap) the persistent kernel — measured 2048² 265->350 GB/s (+32%), 4096² 320->345 GB/s (+8%).Still ~6-9x behind a single-launch elementwise stage even where both optimizations engage — this is the normal throughput range for a "heavy" per-line transform, faster than GInterpStage, ~2.6-5x behind TiledLorenzoStage.
No other configuration: level count, dyadic-vs-packet selection, and boundary handling are all derived automatically from the pipeline's dims.