Skip to content

madspace GPU runtime: histogram scratch, matrix element stream, rank-4 kernels, view rank check - #256

Merged
oliviermattelaer merged 4 commits into
mainfrom
claude/madspace-gpu-runtime-fixes
Oct 12, 2026
Merged

oliviermattelaer merged 4 commits into
mainfrom
claude/madspace-gpu-runtime-fixes

Conversation

@oliviermattelaer

@oliviermattelaer oliviermattelaer commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Found by the review that followed the HIP cut crash (#254). These bugs are of the same class: silent or lucky on one platform, a fault on another.

Fixes

  1. op_histogram (GPU): reduce_by_key writes n_bins + 2 keys (the bins, plus the underflow and overflow bins) into a scratch buffer of n_bins, so every histogram fill wrote two doubles past its end.
  2. op_matrix_element (GPU): heap overflow, and the stream was never passed.
    • output_keys had output_count entries, but the stream key was written at index output_count.
    • The call passed output_count, so umami never received madspace's stream. Every matrix element has run on the default stream since ffb1610, which meant to pass it (it moved the stream from an input key to an output key).
    • Now passed, so the matrix element runs in stream order with the rest of the function. The unused extra input key is dropped.
  3. run_kernel (GPU), rank 4: the bound check omitted size3, so only the first slice of the last dimension was computed. This affects elementwise kernels, copies, zero and add-in-place of rank-4 tensors (e.g. PDF coefficient grids).
  4. op_histogram (GPU), squared values, found by the new madspace_ops check on lemaitre4 (GPU vs CPU up to 16% in a bin). The bin indices were sorted with the weights, then the already sorted indices were sorted again with the squared weights, which therefore stayed in event order. Each bin summed the squared weights of the wrong events, so the per-bin statistical errors of GPU histograms were wrong while the values and totals were right. One sort_by_key now carries both arrays (zip iterator).
  5. Tensor::view / flat_view: a view with more dimensions than its tensor now throws, instead of reading strides and shapes that were never set. That was the mechanism of the cut-kernel crash. Sizes is now zero-initialised and push_back is bounded.

Merge order with #255. #255 (madmatrix GPU, stacked on #244) makes the madmatrix GPU template put all of its work on the stream it is given; before it, some of the work went to the default stream.

Not in this PR:

  • From the same review (lower priority): the GPU VEGAS and discrete histograms have no range guard (the CPU skips out-of-range samples), and some 32-bit index products.
  • On both CPU and GPU, a value in (min − bin width, min) lands in the first bin instead of the underflow, because the index is truncated toward zero instead of floored.

Tests

  • madspace/tests/test_histogram_op.py: CPU semantics of the histogram (underflow, bins, overflow, per-event range).
  • GPU CI job madspace_ops (madspace_ops_gpu.sh, madspace_checks.py) runs the histogram and a rank-4 elementwise kernel (x + x on batch × 2 × 3 × 4) on the GPU runtime and compares them with the CPU runtime. The inputs are torch device tensors, so torch must be importable after the modules are loaded; otherwise the job fails and says so.
  • Local CPU results:
    • madspace suite: 1722 passed, 11 skipped, no view rank error;
    • mg7 CPU runs of p p > j j and p p > e+ e- j;
    • madspace_checks.py with a numpy stand-in for torch.
  • GPU, all jobs green on both clusters (pp_ttx, pp_jj, madspace_ops):

…4 kernels, view rank check

Found by the review that followed the HIP cut crash (#254), same class: silent
or lucky on one platform, a fault on another.

- op_histogram (GPU): reduce_by_key writes one key per bin plus the underflow
  and overflow bins, n_bins + 2, into a scratch buffer of n_bins: two doubles
  past its end at every histogram fill.
- op_matrix_element (GPU): output_keys had output_count entries but the stream
  key was written at index output_count (heap overflow), and the call passed
  output_count, so umami never got the stream: every matrix element has run on
  the default stream since ffb1610, which meant to pass it. Now passed,
  so the matrix element runs in stream order with the rest of the function.
  (The madmatrix GPU template only puts all of its work on the given stream
  since the companion madmatrix PR; with madspace's own, blocking, streams the
  older libraries stay correct.) The unused extra input key is dropped.
- run_kernel (GPU), rank 4: the bound check omitted size3, so only the first
  slice of the last dimension was computed (elementwise kernels, copies, zero
  and add-in-place of rank-4 tensors).
- Tensor::view / flat_view: a view of more dimensions than the tensor has now
  throws instead of reading strides and shapes that were never set (the
  mechanism of the cut kernel crash); Sizes is zero-initialised and push_back
  bounded.

Tests: madspace/tests/test_histogram_op.py (CPU semantics of the histogram:
underflow, bins, overflow, per-event range); GPU CI job madspace_ops
(madspace_ops_gpu.sh, madspace_checks.py) runs the histogram and a rank-4
elementwise kernel on the GPU runtime (inputs as torch device tensors) and
compares them with the CPU runtime. Locally: the madspace suite (1722 passed,
11 skipped, no view rank error) and mg7 CPU runs of p p > j j and
p p > e+ e- j.
torch imports typing_extensions, which EasyBuild 2024a provides through
EBPYTHONPREFIXES (Python-bundle-PyPI): only the python3 of the modules reads
it, not a venv made from it (manneback and lemaitre4: No module named
'typing_extensions'). madspace has no dependency and was built with that same
interpreter.
Found by the new madspace_ops check on lemaitre4 (GPU vs CPU up to 16% in a
bin): op_histogram sorted the bin indices with the weights, then sorted the
now sorted indices again with the squared weights, which therefore stayed in
the event order: each bin summed the squared weights of the wrong events (its
statistical error), while the totals and the values were right. One
sort_by_key now carries both arrays (zip iterator).

madspace_checks.py reports the values and the squared values separately, and
skips (visibly, not a failure) when the torch of the modules has no GPU
support (manneback: the default PyTorch module is the CPU build).

@theoheimel theoheimel left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fixes look good.

What I would change (but that's work for a separate PR): instead of adding GPU-specific unit tests for madspace as done here, we should move the madspace unit tests from numpy to torch (which we already use for some tests anyways), so that the same tests run on CPU and GPU, and then run the full madspace unit test suite on CUDA and HIP through the new GPU CI.

…untime-fixes

GPU CI: keep both new jobs, madspace_ops (this branch) and interference (main);
stop_runner needs both.
@oliviermattelaer
oliviermattelaer merged commit 07d8e88 into main Oct 12, 2026
167 checks passed
@oliviermattelaer
oliviermattelaer deleted the claude/madspace-gpu-runtime-fixes branch October 12, 2026 08:32
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.

2 participants