Build Packages / build:windows:nocuda (push) Successful in 16m31s
Build Packages / build:windows:cuda (push) Successful in 19m15s
Build Packages / build:viewer-tgz:cpu (push) Successful in 19m58s
Build Packages / build:viewer-tgz:cuda (push) Successful in 22m51s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 24m5s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 25m3s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 28m51s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 28m55s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 28m35s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 21m19s
Build Packages / XDS test (durin plugin) (push) Successful in 11m17s
Build Packages / build:rpm (rocky9) (push) Successful in 23m30s
Build Packages / Generate python client (push) Successful in 33s
Build Packages / build:rpm (rocky8) (push) Successful in 25m35s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 1m37s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 21m40s
Build Packages / XDS test (neggia plugin) (push) Successful in 10m17s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 26m44s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 11m10s
Build Packages / DIALS test (push) Successful in 23m27s
Build Packages / Unit tests (push) Successful in 1h19m59s
Two changes to how the per-image loop is set up, neither of which touches what it computes. Device buffers were taken with cudaMalloc and returned with cudaFree, both of which are on CUDA's implicit-synchronisation list: each one synchronises the device across every stream. One analysis engine per worker, each making a few dozen of them, means the workers still constructing stall the workers already processing images, and the cost grows with the worker count. They are now stream-ordered allocations from the device's memory pool, with the synchronous pair kept as the fallback where no pool is available. Two deliberate limits on that. The pool's release threshold is one gibibyte rather than unbounded: holding the small per-worker buffers is the whole point, but the card also has to fit the merge afterwards, which asks for several gigabytes of its own. And the shared geometry tables keep the synchronous allocator, because their deleter runs on whichever thread drops the last reference, so an asynchronous free there would be ordered on a stream that says nothing about the engine streams whose kernels read the table; they are allocated once per card, so the pool bought them nothing. The loop's worker cap per card goes from eight to sixteen. The comment beside it already recorded where the measurement put the minimum - the loop's time falls to sixteen workers and then rises - and a later measurement on a single card agrees: at eight the loop waits on the queue rather than on the card. An explicit -N is still obeyed as given. Reflection files are byte-identical on four crystals with the worker count doubled, which is the property the frame-ordered mosaicity smoothing and the deterministic prediction order were built to give. Thirty consecutive runs of one crystal on the pooled allocator: no failure, every file identical to the first. Peak device memory over the whole rotation test set is 4.9 of 16 gibibytes. Four minutes seventeen to four minutes one over thirty-eight crystals, each binary repeating itself to within half a per cent; nineteen crystals faster, nineteen level, none slower, and every column of the comparison table identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EGpGdgmJ8MyY9pCGWjktyi