GPU acceleration
The command-line tool has an optional CUDA path for the fusion and the aligner. It is a build feature, and it roughly halves to thirds the wall time of a run.
(The browser and desktop apps use WebGPU instead, on any GPU, and need none of this.)
Building
cargo build --release --features gpu -p lapstack-clicudarc is built with dynamic loading, so CUDA is not needed at build time, and a binary
built with the feature still runs on a machine without CUDA. Only --gpu and --gpu-align
fail there, with a message; everything else works.
Running
lapstack --gpu --gpu-align --align-coarsen 2 --save-depth -o stacked.tif frames/*.tif| Option | What moves to the GPU |
|---|---|
--gpu | The fusion — the same kernels transcribed to CUDA. The accumulator pyramid, the best-energy planes and the winner map stay on the device; only the three RGB planes go up per frame and the tiny residual comes back. The depth pass runs there too |
--gpu-align | The aligner's cost search — and with it, the frames themselves |
--gpu-align is the bigger win, and not only for the search. With it, a decoded frame is
uploaded once and its luma and registration pyramid, the Nelder–Mead cost search, the warp,
the brightness gains and the depth pass's focus slice are all computed on the device. The fuser
folds the warped planes in place, and a frame comes back to the host only when something there
asks for it — --save-aligned, or the weighted average.
What is left of a GPU frame is the decode and the upload, and the host holds no warped copies at all. That is where the peak memory goes.
Runtime requirements
| Platform | What is needed |
|---|---|
| Linux | libcuda from the driver and libnvrtc from the toolkit on the library path, and the nvidia_uvm module loaded |
| NixOS | LD_LIBRARY_PATH=/run/opengl-driver/lib:<cudaPackages.cuda_nvrtc's lib output>/lib |
| Windows | nvcuda.dll ships with the display driver. Drop the two DLLs from NVIDIA's cuda_nvrtc redistributable zip — bin/nvrtc64_120_0.dll and nvrtc-builtins — next to lapstack.exe |
On Windows, match the nvrtc major.minor to the driver's CUDA version (check
nvidia-smi) or the PTX just-in-time compiler will reject the kernels.
What it buys
On 8280×5520 sixteen-bit frames, an RTX 3060 and a 128-thread host — the whole run, depth pass included:
| Run | Wall | Fold (align + fuse + focus measure) | Depth | Peak RSS |
|---|---|---|---|---|
| 25 frames, CPU | 26 s | 19 s (0.75 s/frame) | 6.0 s | 8.7 GB |
25 frames, --gpu --gpu-align | 9.6 s | 5.1 s (0.20 s/frame) | 1.0 s | 6.2 GB |
| 100 frames, CPU | 85 s | 74 s (0.74 s/frame) | 8.0 s | 12.0 GB |
100 frames, --gpu --gpu-align | 26 s | 21 s (0.21 s/frame) | 1.9 s | 9.4 GB |
The depth pass on the device runs the same stages as the browser's, multigrid included: slices are uploaded one at a time, only the two medians (the noise floor and the confidence scale) are taken on the host, and the maps come back.
How closely GPU and CPU agree
This matters if you care about reproducibility, so here it is precisely.
- Fusion output is bit-exact with the CPU output. (One run in four differed in 637 of 45.7 million pixels, on energy ties, and could not be reproduced.)
- With halo control, the weights are made, REDUCEd and folded on the device too, and the output differs from the CPU's by float rounding only.
- CPU reruns are byte-identical to each other.
- The depth maps are within 4 × 10⁻⁴ of full scale of the CPU's. A few pixels in a million of the confidence map differ, where a near-tie in the peak search falls the other way in float order.
- GPU alignment evaluates the cost in FP32 and warps on the device (coordinates in double
like the CPU warp, taps in FP32), so
--gpu-alignresults differ from CPU-aligned ones in about 0.5 % of pixels. The transforms it finds are the same as the CPU's, to the last printed digit.
In short: --gpu alone is reproducible against the CPU path; --gpu-align changes the
resampling arithmetic slightly, as any change of warp precision would.
When it does not help
The --no-align path on compressed input is decode-bound — about a second per frame for
sixteen-bit PNG with four decoder threads — and the GPU cannot help with that. Uncompressed
TIFF input decodes an order of magnitude faster.