Repository navigation
Conversation
roytsmart
changed the base branch from
feature/transpose-conservative-device
to
main
October 6, 2026 18:54
Assembles the weights of every orthogonal element into one CSR matrix, applied in one pass over its rows on the host (numba, row-parallel) or on a CUDA device (one warp per row), summing every row in a fixed order. The shapes of the values going in and coming out say what happens along each orthogonal axis: a length of one in the input where the weights vary shares the input among the elements, a length of one in the output sums them, and an axis the weights do not vary along is a batch axis. The exact transpose swaps the shapes; the conservative transpose scales each entry of it by its own element's cells, sharing its indices. The cell factors of `transpose_weights_conservative` move into `_factors` so that both use them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GqceVSp8s2msxj1Dr9MrGU
With `device="cuda"`, `from_weights` reads the weights where they are and assembles the matrix in torch tensors on the device: one pass counts the entries of each row with integer atomics, and a second places them, each element's entries sorted stably by row and written past the row's cursor. Each row then holds its entries in the order of the elements, as on the host, so the device gives exactly the host's matrix from the same weights. The transpose is the same placement over the entries in chunks, and the conservative scaling is a numba kernel, so an operator on the device stays there. A `dtype` option stores the values in single precision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GqceVSp8s2msxj1Dr9MrGU
roytsmart
force-pushed
the
feature/regridder
branch
from
October 6, 2026 18:55
4d358c4 to
40d58a0
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #70 +/- ##
==========================================
+ Coverage 99.71% 99.77% +0.05%
==========================================
Files 65 70 +5
Lines 4251 5266 +1015
==========================================
+ Hits 4239 5254 +1015
Misses 12 12
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…'t fit. Weights with a unit, an element with no entries, and grids whose orthogonal axes do not fit the weights were untested. The last raised numpy's own broadcasting error rather than saying which grid does not fit, since `numpy.broadcast_shapes()` raises for shapes that cannot broadcast at all; the shapes are now compared axis by axis. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GqceVSp8s2msxj1Dr9MrGU
… entries. The device product gave every row a full warp, so a backprojection, with about three entries a row, left most of each warp idle. Rows are now summed by groups of threads, a power of two up to a warp, sized to the average row of the matrix: two threads for a backprojection and a warp for a forward operator. The size depends only on the matrix, so the product still repeats exactly, and is the same for a column alone or in a batch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GqceVSp8s2msxj1Dr9MrGU
This branch has not been deployed
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.
A prototype of
regridding.Regridder: a sparse linear operator that resamples arrays from one grid onto another. It holds the weights of every orthogonal element in one CSR matrix, which it applies in a single pass over its rows. This is M3 of the CUDA MART roadmap.Builds on #69 (now merged), whose cell volumes, grid check and unit handling it reuses.
Why
Running MART with
regrid_from_weights()launches one kernel per orthogonal element, 80 per resample in ctis's tutorial. The GPU spends most of an iteration waiting on those launches, and its scatter adds in no fixed order. A single CSR matrix needs one launch per product and sums every row in a fixed order. The result is then the same on every run, on the host and on a device.API
Broadcasting is declared by shapes. Each orthogonal axis's role follows from its length in the weights, the input and the output:
regrid_from_weights()does today.(wavelength, x, y)to(channel, x′, y′).The conservative transpose scales each entry of the exact transpose by its own element's cells. It isn't two diagonal scalings, because a shared input can have cells of a different size in each element: ctis's scene, distorted, differs by channel. The result shares the indices of
.T.How it is built
transpose_weights_conservative()'s cell factors move into a shared_factors(), which both it and the operator use.Tests
There are 52 host tests and 27 device tests:
regrid_from_weights()followed by a sum. When no axis is shared or summed, they are equal bit for bit..Tpasses a dot-product test, andT.T is self. The conservative transpose equalstranspose_weights_conservative()bit for bit..Tand the conservative transpose equal the host's bit for bit. Device products agree with the host's to rounding and repeat exactly. Each group size, from 1 to 32 threads, matches a dense product and gives each column of a batch exactly what it gives alone.weights_input, float32, weights built on the device, elements with no entries, and invalid shapes and grids. Codecov reports every changed line covered.The full suite passes locally, with 99 device tests. ruff and black are clean. pyright passes in CI.
Performance
These are MART iterations on an RTX 4090, against ctis's
MartInverter, with every path matching ctis to 1e-10 or better:Regridder, hostRegridder, GPUAssembling 145M weights takes about 0.5 s on the GPU or on 48 host threads, and the transpose another 0.5 s.
Sizing the thread groups to the rows (03f9e67) halves the larger problems. These are the same MART runs, timed back to back on 2026-10-08 with the GPU 10 to 17% busy, so they run slower than the table above:
Why not cuSPARSE
torch's sparse product could replace the kernel, but three things rule it out:
torch.use_deterministic_algorithms(True), which neither raised nor fixed it. On rows of any length, a column's result changes with the batch width.The kernel here repeats exactly in all of those cases.
Not done yet
transpose_conservative()raisesNotImplementedError.cudaextra) to lay out the values.🤖 Generated with Claude Code
https://claude.ai/code/session_01GqceVSp8s2msxj1Dr9MrGU