Repository navigation
Fuse the GPU frame chain: up to 2.6x on small frames, bit-identical output - #8
Merged
Merged
Conversation
Small GPU frames were launch-bound: about twenty CuPy calls per frame, three of them uploads of a scalar input. The GPU path now runs the photo and total expectations as one ElementwiseKernel and the whole readout (clip, defects, noise terms, gain, offsets, common mode, rint, ADC clip, uint32) as another, keeps scalar inputs and scalar distribution parameters on the host (or a cached device copy), samples Poisson counts directly in the working precision, and reuses the chain's total as the truth. Noise is drawn by the same calls in the same order and the fused kernels repeat the same float operations with FMA contraction off, so seeded frames and truth are unchanged; a GPU test checks this bit for bit against the separate operations. Both paths skip whole-frame identity arithmetic by Python scalars. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Small GPU frames were launch-bound. On the Arm bench (Neoverse-N1), an 80x80 Pyramid frame spent about 430 µs of host time issuing roughly twenty CuPy calls, three of them uploads of a scalar input, while the GPU sat idle. This PR cuts that to about 160 µs. Seeded output stays bit-identical on both CPU and GPU.
What changed
_cuda.fused_expectation):(rate + background) * exposure·QE * PRNU, then+ dark + extra, in oneElementwiseKernel. It writes the photo map and the total._cuda.fused_readout): full-well clip, defects, the reset/avalanche/correlated/read noise terms, the gain divide, bias pedestal, amplifier offset, bias structure, common mode,rint, the ADC clip and theuint32cast, in one kernel. It replaces about 10 launches.noise._working_operandkeeps scalarbackground/extra_electronson the host as working-dtype NumPy scalars. The_CuPyGeneratorwrapper reuses a cached device copy of scalargamma/poissonparameters (EM-gain scale, CIC rate).dtype=on CuPy's sampler). This is the same conversion as.astypebut without theint64intermediate.photo + dark + extra, same operands and order. This applies with no workspace, no ROI and binning 1.+ 0,/ 1and* 1by Python scalars.Why the output is unchanged
clipexpression, Python scalars rounded to the working dtype exactly as CuPy does, and--fmad=false.Evidence
out, ROI, dark/bias/flat, unseeded sequences, series, NDR, CDS andsimulate_frame. It hashed every ADU and truth array. 0 differences vsmainon CPU and on the RTX 4060.test_gpu_fused_kernels_are_bit_identical_to_separate_operations(10 cases) compares the fused path with the forced-unfused path bit for bit, truth included. A mutation (v / gain→v * (1 / gain)) makes it fail.-m gpu23 passed. Locally: ruff 0.16.10, mypy --strict andmkdocs build --strictare clean.Numbers
Method:
bench_devices.py --seconds 2 --warmup 10, Neoverse-N1 pinned to 12 cores, main and this branch interleaved, median of 3 runs (A400: 2 runs). Values are frames/s.What didn't help, or can't be done without changing seeded output
🤖 Generated with Claude Code