Probe baseline plus the ncu experiments on binning and accumulator
layout (E1-E4), which are not stored as files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For the cache-footprint experiments: --range-scale widens the bins
without changing n_bins, and --center subtracts each pixel's median so
neighbouring pixels share bins, while u16 frames keep their H2D size.
perf/hist/ mirrors perf/cf/: a driver that runs each config under nsys,
the traced process, settings and a README. The fill (H2D + fill_bin_major
per frame) and the readout (bin_to_pixel_major + one D2H per values())
are reported separately. Only one chunk of frames is held in host memory
and re-queued until the requested frame count.
gpu_span.analyze() takes an optional engines subset, so the one-off
readout D2H doesn't count towards the fill window or roofline. Default
output is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PixelHistogramCUDA_<input>_<storage> for uint16, float32 and float64
frames and the six storage types of PixelHistogram, behind an
aare.PixelHistogramCUDA() factory. Frames are never converted: a wrong
dtype raises TypeError, so a registered buffer is always the one copied.
The GIL is released during GPU work.
- C++: fill_histogram() and submit_batch() take const views, so
read-only arrays (np.memmap mode="r") work; register_output_buffer()
pins the destination of values(out).
- Python: register_input_buffer()/register_output_buffer() keep their
array alive; values(out=...) writes into a given array. With a pinned
out, reading back a 640 MB histogram takes 28 ms instead of 230 ms.
- Tests in test_PixelHistogramCUDA.py; PixelHistogramCUDA.ipynb compares
CPU and GPU on 100k Moench frames (identical, ~13x faster on the GPU).
throughput.py measures wall-clock FPS without nsys, for the copy-out
(find_clusters_batched) and zero-copy (BatchView) paths, at 3x3 to 9x9
and 1 and 4 streams. On the A100, zero-copy with 4 streams reaches
94-102 % of the bound predicted by the nsys probes.
- common.nsys_profile_flags(): shared nsys profile flags, adding
--cuda-event-trace=false on nsys 2025.1+ (on by default there, it widens
the host gaps between steps; kernel times are unchanged). nsys 2024.5
rejects the flag, so it is detected from the help text.
- cf/run_probes.py: 3x3, 5x5, 7x7, 9x9 at 1, 4 and 8 streams.
- cf/results/A100/: Merlin7 A100 runs, f32 and f64 with lround, and f32
with round (104e284) at 20k/2000 and 50k/3000. README lists them.
Make room for probes of other CUDA classes. common.py and gpu_span.py
stay in perf/ and become shared; the cluster-finder driver, traced
process, README and results move to perf/cf/.
- common.py: assert_build_fresh() takes the header list, capture_env()
takes extra fields, results_dir() takes the probe folder.
- cf/cf_common.py: pedestal frames, n_sigma, rebuild-guard headers,
DEVICE_PED_TYPE and the <date>_<f32|f64> results naming.
- gpu_span.py: optional kernel-name filter; tolerates an engine with no
ops. Output without a filter is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- std::array for per-slot state; std::exchange in BatchView::steal
- [[nodiscard]]/noexcept on the public API and release path
- cuda_check moved to aare::cuda::detail (was reserved __cuda_check)
- _v type traits; drop redundant std::move and `using namespace aare`
- Add pytest for BatchView slot release
## Summary
Makes the minimizer behind `FitModel` selectable and adds two built-in,
dependency-free solvers next to Minuit2's Migrad and Fumili.
`Minimizer.Migrad` stays the default, so existing fits behave as before.
- **`Minimizer` enum** (C++ `FitModel::SetMinimizer`, Python `minimizer`
constructor argument and property): `Migrad`, `Fumili`,
`LevenbergMarquardt`, `VarPro`.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Khalil Ferjaoui <khalilferjaoui@yahoo.fr>
- For set_nSigma only call update_pedestal if we have accumulated enough
samples otherwise just write it
- For update threshold check if pd is ready and if not throw
closes #390
- Introduce a standard way to bind numpy arrays: py::array_t<T> and
.noconvert() on the argument
- Take py::array_t by value since it's just a small wrapper
- Update bindings
- main moved ClusterFinder to FastPedestal and dropped Pedestal's
push_fast/get_sum/get_sum2, which ClusterFinderFrozen and both CUDA
drivers still called.
- Move all three to FastPedestal. Its uniform sample count is what the
CUDA kernel already assumes, and matching ClusterFinder keeps CPU and
GPU warm-up identical, which ClusterFinderFrozen exists to verify.
FastPedestal gains get_sum/get_sum2 to de-interleave its internal
Entry{sum,sum2} for the CUDA upload.
- Also expose min_pedestal_samples on these constructors and factories,
make the factories keyword-only after image_size. Same API in C++ and Python.
Three files conflicted:
- ClusterFinderMT::collect(): both sides rewrote it. Keep this branch's
version (f4bbbc9), which reads the stop flags before sweeping, over
main's (afce91b), which still samples queue emptiness mid-condition.
The staged splice had also lost a brace, swallowing popFront().
- python/aare/ClusterFinder.py: restore the CUDA API deleted by the
resolution (_cuda_available, _CUDA_ALGORITHMS, ClusterFinderCUDA,
find_cluster_views_batched_iter, ClusterFinderCUDAGraph), and main's
min_pedestal_samples forwarding, dropped as an adjacent hunk.
- bind_ClusterVector.hpp: keep py::module_local() (133cedf) alongside
main's new docstrings; without it _aare and _aare_cuda cannot both
register Cluster/ClusterVector under pybind11 3.
Does not build on its own: ClusterFinderFrozen and the CUDA drivers
still call Pedestal APIs main removed. Fixed in the follow-up.
Cleaned up the helper functions by:
- Added template for make_view (and new make_const_view) instead of the
copy paste implementation
- checks c_style and dimensions inside make_view
- test binding to be able to probe the checks in make_view
Call sites can be reviewed in a second PR since usage of const or
strictness of conversion depends...
We have no code in aare to deal with RawFiles that have missing packets
if they are not padded to full size or frames with packet loss dropped.
Added:
- check that we don't try to read files that are not supported
- python bindings for frame discard policy
at least we get the check in:
closes #171
---------
Co-authored-by: AliceMazzoleni99 <alice.mazzoleni@psi.ch>
- seek to the edge of a file + read_into would select the wrong file
- Visible for example in f.read_frame(index) in python
Added fix, release notes and a small test.
closes #371
Co-authored-by: AliceMazzoleni99 <alice.mazzoleni@psi.ch>
- renamed pedestal.hpp to bind_Pedestal.hpp for python bindings
- variance is now always calculated in double and made private
- cost correctness in a few places
- removed push_fast and explicit updates of mean from Pedestal. If
performance is needed use FastPedestal
- Pedestal no longer caches std
Improved API for ClusterFile
- read_frame now returns a std::optional<ClusterVector> to differentiate
between an empty frame and end of file
- f.frames() produces an iterator over frames
- f.chunks() produces an iterator reading chunks of clusters
- python string repr for Cluster and Eta
- Context manager for RawMasterFile
- slice operator for ROI
---------
Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
ClusterFinderCUDA mixed three things: which kernel runs and what device
state it needs, the per-stream buffers, and the batching pipeline. A
second algorithm meant a second copy of all of it. The pieces are now
separate and the driver is templated on the algorithm.
cuda::FixedWindow (clusterfinder_algo.cuh) the fixed-size window
search: DeviceState pointer bundle, launch(), and the pedestal
transforms. Stateless; every pointer is supplied by the caller,
so external code can drive the kernel on its own memory and
stream without touching the driver.
StreamContext<Algo> one stream's buffers, RAII, non-movable.
ClusterFinderCUDA<Algo> the pipeline, unchanged in behaviour.
no_2x2_cluster moves to Cluster.hpp so the kernel and algorithm headers
no longer drag in ClusterFinder.hpp. The Python factory gains
algorithm="fixed_window"; memory stays internal.
Kernel SASS identical to before. Positions identical against
ClusterFinderFrozen for 3x3, 5x5, 9x9 (stream and graph finders). New
Catch2 test drives FixedWindow::launch on raw cudaMalloc buffers and
requires exact agreement with the driver.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Updated error message to mention out of range and which file it
applies to
- If there are different number of frames in the subfiles, take the
minimum and print a warning
Closes: #363
Closes: #362
- Default `ClusterVector` move operations simplifying the code and
fixing a bug.
- Fixed inconsistent frame number type (uint64/int32)
- Validate Python masks as one-dimensional, C-contiguous Boolean arrays,
handle empty masks safely, and reserve filtered storage based on the
selected cluster count.
- Align the C++ and Python API documentation with the implementation,
including concise `hitmap` and reduction documentation and a correctly
rendered constructor example.
collect() cached queue emptiness across sweeps, so a worker's last
write could land after the final sweep but before shutdown was
observed, stranding that frame's clusters. Read the stop flags before
sweeping instead: once workers are joined, a sweep that moves nothing
proves the queues are drained.
Found via ClusterFinderCUDA.ipynb's drift plot, which jumped by one
frame's worth of clusters at the very end.
- handles file reading of disabled udp ports
- adds members disabled_udp_ports in master file as an optional
- adds member diasbled_udp_port_types in master file as an optional
- Treats enabled udp ports as ROIs
- merges/combines consecutive ROis into one
### To discuss:
- right now can only handle either ROI or disabled udp ports (disabled
udp ports has higher precedence)
- Should Frames in File be removed from Master File as value is nonsense
for disabled udp ports
---------
Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
Doxygen never read *.cuh and needs EXTENSION_MAPPING to parse it; add
both, plus C++/Python pages and AARE_CUDA build docs.
Delete ClusterFinderCUDA_old.hpp (declared the same class, polluting
the API page), utils/batch.hpp, and the deck. All unreferenced.
TODO:
- Frozen + CUDAGraph: bound in Python, headers not installed. Ship or drop?
- test_ClusterFinderCUDA.py guarded on _cuda_available()
- AARE_CUDA_ARCHITECTURES=native needs CMake 3.24, project min is 3.15
- make docs somewhere with sphinx+breathe; new .rst files never rendered
- fmt nvcc warnings: gone at -std=c++20 or with -isystem
Host-side pipeline work ported from the benchmark branch. The kernel is
unchanged, so cluster results are identical.
- collect_view()/BatchView: read clusters in place from the pinned D2H
buffer instead of copying one ClusterVector per frame. The view is
released back to the finder, so keep nothing that borrows it.
- reserve_output_slots(), chunk_size_for(): pre-pin the output slots and
size a batch to fit one.
- find_cluster_views_batched_iter(): submits chunk i+1 before collecting
chunk i.
- time_kernels, default off. avg_kernel_time_ms() returns NaN unless
enabled, and always for the Graph variant.
- n_streams defaults to 4 everywhere.
ClusterFinderCUDA.ipynb cut to CPU MT vs batched CUDA plus a cluster-set
diff, and it now reports peak clusters/frame.
Removed src/ClusterFinderCUDA{,_old}.test.cu and src/Makefile (early C++
benchmarks, never in the CMake build), the perf and Frozen-vs-CUDA
notebooks, and helper.py.
Optimized pedestal tracking with FastPedestal and performance improvements to the cluster finder.
- ~2x faster hitting 14k FPS on benchmark dataset on 16 core threadripper pro
**Changes**
- No bounds check on interior of frame. (only within half of a cluster)
(~15% improvement)
- Cache threshold (std*n_rms) Biggest improvement comes from not
computing std for each access
- Cache pedestal subtracted frame
- Switched to FastPedestal which assumes we reached steady state
**Things tried but rejected:**
- Always store cluster values, commit on val==max (~15% drop in frame
rate)
**Options**
- using 16 bit clusters and 16 bit pedestal. (slows down the single
threaded case but allow us to reach 12k with multi threading)
**Other notes on performance**
- Passing in memory frames from python and doing nothing: 0.8us/frame
- Passing + pedestal subtraction: 28us/frame
- Passing + pedestal + cluster finding (no store): 652us/frame
- Passing + pedestal + cluster finding: 886 us/frame
- Reading files from nfs shares tops out at ~1GB/s for single threaded
reads.
- MultiThreadedFileReader uses our File wrapper to read a generic file
in parallel
- Placed in aare::experimental to show that it's not production ready
- Diagnostic twin of ClusterFinder: identical decisions, but the pedestal is
frozen per frame (snapshot at frame start, updates deferred to frame end),
matching the CUDA kernel's update model.
- Isolates pedestal-update timing as the sole remaining CPU/GPU mismatch.
- Adds class + bindings + factory, validation notebook, and helper utilities.
- Move Chi2.hpp from include/aare/ to src/ (private)
- Pimpl on FitModel<Model>: MnUserParameters/MnStrategy behind opaque
src/FitModelImpl.hpp, no Minuit2 includes in public headers
- Move fit_pixel/fit_3d bodies to Fit.cpp with explicit instantiations
for all 8 models; drop FCN template param from public API
- CMake: aare::Minuit2 wrapped in $<BUILD_INTERFACE:...> (hidden from
exported targets, same pattern as lmfit), MINUIT2_INSTALL OFF, Chi2.hpp
removed from PUBLICHEADERS
- Update python bindings and benchmark callsites accordingly
---------
Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
Co-authored-by: Alice <alice.mazzoleni@psi.ch>
Collection of small improvements in usability:
- NDView works for large arrays
- Direct subtraction of Pedestal from np.array
- len() support for python bindings of files
- reshape image directly in decoder such that first dimension is num
counters
- take into account chip artefact in decoder.
---------
Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
- Eliminate the ~200–300 µs inter-batch idle gap by allowing two batches
to be in-flight simultaneously:
- submit_batch() enqueues H2D+kernel+D2H without blocking
- collect() syncs via cudaEventSynchronize (not
cudaStreamSynchronize) so a queued second batch runs uninterrupted.
- Two ping-pong output slots (NUM_SLOTS=2) with per-slot pinned buffers
and cudaEventDisableTiming sync events.
- find_clusters_batched() keeps its direct implementation.
* Measured: 0.026 -> 0.022 ms/frame (~18%).
- Device pedestal arrays (mean/sum/sum2) are now float instead of
double: halves global-memory bandwidth for pedestal reads/writes and
eliminates FP64 arithmetic in the kernel (3.3x kernel speedup,
15µs -> 4.6µs).
- Replace the per-cluster push_back loop in the D2H drain with a
single resize()+memcpy().