Optimizations to multi-process and multi-rank Datadeps - #739
Open
jpsamaroo wants to merge 6 commits into
Open
Conversation
Three costs that each task paid regardless of how much work it did, and which therefore grew to dominate as chunks got smaller with more workers or ranks: * `compatible_processors` scanned every processor in the cluster and constrained the scope against each. A scope built from `ExactScope`s already names its processors, so `scope_processors` reads them off directly and only checks that they still exist and are enabled. This is the shape Datadeps hands the scheduler for every pinned task. * `estimate_task_costs!` walked a task's chunks to rank candidate processors even when there was only one candidate to rank. Datadeps pins each task to a single space, so this was pure overhead. * MPI's uniform dispatch ran `promote_op` and `infer_effects` per task per rank to decide whether the result needs a status broadcast. Uncached, each dispatch allocated megabytes of inference state on every rank, and because ranks advance in lock-step the resulting GC pauses did not overlap with anything. Both queries now go through memos (`cached_return_type`, the new `cached_nothrow`). Co-authored-by: Cursor <cursoragent@cursor.com>
`slot_is_already_in_place` decides whether a chunk can serve as its own slot instead of being copied. It required the chunk to be locally owned and unwrappable here, because it answered the question by unwrapping the value and asking whether `move_rewrap` would treat it as a leaf. That excluded exactly the cases where a needless copy is most expensive: under MPI every rank plans every task but owns only some of the data, and under Distributed the chunk is often homed on another worker. In both, the alternative is a `move_rewrap` that reproduces, message by message, data already sitting where it is needed -- for a wrapper like `HaloArray`, a header broadcast plus one transfer per child, per argument, per region. `slot_rewrap_is_identity` answers the same question from `chunktype` alone: a rewrap is the identity unless the type contains a handle whose resolution is the point of the rewrap (`Chunk`, `DTask`, `ChunkView`), so wrappers over plain data now pass through. `chunktype` is uniform across ranks, so the decision is too. Co-authored-by: Cursor <cursoragent@cursor.com>
…anning Turning a prepared spec into scheduler thunks needs nothing from the planner, but it was running inline and accounted for ~40% of per-task planning cost in a multi-worker region. `AsyncEnqueueQueue` hands each batch to a submitter task instead, preserving FIFO order (the syncdeps recorded during planning rely on it) and keeping a synchronous drain for the two points that need a task to really exist: a value dependency's `fetch`, and the end of the region. It is used only when there is a spare thread to submit on, and never under uniform execution, where a rank's submission runs collectives that must stay ordered against planning's own. `DATADEPS_BATCH_LIMIT` goes from 4 to 16, which is where the scheduler round-trip stops amortizing (256 independent `InOut` tasks over 4 workers: 69 us/task unbatched, 55 at 16, 53.5 unbounded) while still bounding how far planning runs ahead of execution. Together these take that region from 31.2 ms to 16.9 ms. Attributing planning cost is hard from a profile, because the expensive parts are blocking waits inside communication rather than hot loops, so this also adds per-phase timing behind `JULIA_DAGGER_HIER_TIMING=1` (off by default, one `Ref` read per phase) and records what it found in the module header. The MPI hang warning now carries a backtrace, since which call site is waiting is the whole diagnosis for a wait cycle. Co-authored-by: Cursor <cursoragent@cursor.com>
A chunk's aliasing info cannot be computed locally: under Distributed it is a `remotecall_fetch` to the owner, and under MPI a broadcast from the owner that every rank must join. Planning asks the same questions repeatedly -- once per unique argument to build the DAG, again for every slot, again for the write-back epilogue -- so a region spent hundreds of round-trips re-deriving a handful of distinct answers. Under MPI each is a global synchronization point, which is what made replicated planning scale so poorly with rank count. Three changes, all invisible to the user: * `ChunkAinfoMemo`, a per-region memo keyed on argument identity, the dependency modifier and the acceleration. Per-region because aliasing info describes where a value's memory currently is: stable while one region plans, but a later region's chunk may reuse a freed address. Keys are rank-uniform, so every rank hits and misses on exactly the same calls and the remaining broadcasts are still collective. * `batch_aliasing` / `batch_ainfos`, which resolve a whole uniform list of arguments in one exchange per owning rank rather than one broadcast each, and seed the memo with the results. Phase 1 now goes through these, so the rest of planning finds its answers already computed. * Copies made by Datadeps recorded their own destination-side ainfo eagerly, costing a second rendezvous per slot on top of the transfer. That ainfo only matters when the copy is itself the source of a later move, which most regions never do, so copies are now recorded unresolved and resolved lazily on the first `derived` miss, as one batch. Safe under SPMD because the trigger is uniform. Slot generation halved for a 4-rank stencil sweep (1.97 -> 0.96 ms/sweep), and the 2-rank MPI test suite went from 12m20s to 10m07s. Co-authored-by: Cursor <cursoragent@cursor.com>
A slot is the per-memory-space buffer Datadeps allocates to stand in for an argument a task will touch from somewhere other than where the argument lives. Slots were thrown away at the end of every region, so an iterative workload -- a stencil loop, an iterative solver -- rebuilt the same buffers every sweep, paying an allocation, a whole-buffer transfer, an aliasing exchange and a free task per buffer. Under MPI the transfer is worse than its bytes suggest: it is a rendezvous every rank has to reach, in the middle of planning. Slots are now kept in a bounded cache keyed on (origin chunk identity hash, destination space) and handed to the next region that wants the same pair. The transfer this removes is the part that looks unsafe, so: a fresh slot's populating transfer is already known to be redundant for the data the region writes, because `generate_slot!` deliberately does not synchronize with the owner (a slot is only ever "some version" of the data) and the region's own copy-to brings it current before any task reads it. What the transfer does provide is coverage of the parts the region's copies never refresh. So reuse is restricted to arguments used only under `dep_mod === identity`, whose first copy is a whole-buffer copy; an argument touched under any partial modifier is excluded outright, including its `identity` uses, since they all share one slot. Establishing that needs the region's whole argument list before any slot is built, which is why this rides on the hierarchical path's Phase 1 -- the flat path discovers arguments task by task and stays as it was. Retention is keyed on `Chunk.handle`, not on the `Chunk`: a slot is retained as the object in `state.remote_args` and freed as the object in the aliased-object cache, and those are not always the same wrapper over the one buffer. Every input to a reuse decision is rank-uniform -- the identity hash, the chunk type, the eligibility set, and the cache contents, which evolve identically because every rank replays the same regions in the same order -- so all ranks hit and miss together, which they must, since a hit skips a rendezvous. Eviction is FIFO rather than LRU so a hit does not reorder the eviction sequence. For a 4-worker, 4x4-block sweep of 256^2 blocks, steady-state planning goes 110 -> 14 ms and the whole sweep 86 -> 32 ms; the same benchmark on 2 MPI ranks goes 15.4 -> 13.3 ms. `Dagger.DATADEPS_SLOT_REUSE[]` (or `JULIA_DAGGER_SLOT_REUSE=0`) disables it. Co-authored-by: Cursor <cursoragent@cursor.com>
`stencil_region_info` extracts each cross-space halo region in its owner's memory space, so that only the region crosses the boundary rather than the whole neighboring chunk. It extracted into a freshly allocated array every sweep, and a new array is a new `Chunk` that Datadeps has never seen in the reader's space: for a 4x4-block sweep over 4 workers that is 96 slots per sweep, each needing a full transfer of the region during planning, on top of the copy the region already schedules to bring that slot up to date. The cross-region slot cache could not help, since it keys on the origin chunk and the origin was new every time. Those buffers are now kept, keyed on the source chunk and the region (which corner, how deep, under which boundary condition, since a boundary region of the same corner holds different values), and re-extracted into with `halo_region_into!`. The chunk identity stops changing, so the slot cache recognizes it and keeps the reader-side buffer too; the two caches compose, and on this workload neither does anything without the other. Only the allocation is reused. Unlike a slot, a halo region is a source: nothing downstream refreshes it, so its contents are re-extracted every sweep. A source is not always a `Chunk` -- a `DArray`'s chunk entry is a `DTask` until something collects it -- and both forms are stable across sweeps, so the cache recognizes either. The self-writing form `A[idx] = f(@neighbors(A[idx]))` reads a pre-sweep snapshot instead, which is a fresh object every sweep, so `stencil_source_chunks` now reports what each snapshot stands for and the cache keys on the chunk behind it. Eviction is FIFO and deliberately does not drop entries whose source array has been collected, tempting as that is: liveness is rank-local, so ranks would evict different entries and stop agreeing on which extractions to spawn. `check_uniform` asserts the hit/miss decision agrees across ranks. A buffer stays checked out from when it is taken until the region reading it has finished, so two `@stencil` blocks running concurrently over one array cannot have one overwriting what the other reads; the loser extracts into a fresh array. Median of 15 sweeps, 4x4 blocks of 256^2: 246 -> 170 ms on 4 Distributed workers, 306 -> 246 ms on 4 MPI ranks. In the Distributed case slot generation goes 111 -> 33 ms as all 96 moves become cache hits. `Dagger.STENCIL_HALO_REUSE[]` (or `JULIA_DAGGER_STENCIL_HALO_REUSE=0`) disables it. Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
Dagger benchmarks:
|
| master | dirty | master / dirty | |
|---|---|---|---|
| array/dagger/N=1024 (block 512)/add (X + X) | 3.85 ± 0.86 ms | 3.63 ± 0.81 ms | 1.06 ± 0.33 |
| array/dagger/N=1024 (block 512)/alloc (rand) | 3.32 ± 0.088 ms | 3.54 ± 0.25 ms | 0.938 ± 0.07 |
| array/dagger/N=1024 (block 512)/broadcast (X .+ 1) | 2.78 ± 0.45 ms | 2.66 ± 0.43 ms | 1.04 ± 0.24 |
| array/dagger/N=1024 (block 512)/map (sin.(X)) | 7.17 ± 0.98 ms | 6.79 ± 0.94 ms | 1.06 ± 0.21 |
| array/dagger/N=1024 (block 512)/norm | 1.29 ± 0.025 ms | 1.62 ± 0.42 ms | 0.795 ± 0.2 |
| array/dagger/N=1024 (block 512)/reduce (sum) | 2.47 ± 0.4 ms | 2.4 ± 0.077 ms | 1.03 ± 0.17 |
| array/dagger/N=1024 (block 512)/transpose (permutedims) | 7.02 ± 0.29 ms | 7.2 ± 0.59 ms | 0.974 ± 0.09 |
| array/dagger/N=256 (block 256)/add (X + X) | 1.06 ± 0.17 ms | 1.22 ± 0.2 ms | 0.869 ± 0.2 |
| array/dagger/N=256 (block 256)/alloc (rand) | 1.02 ± 0.12 ms | 1.07 ± 0.22 ms | 0.956 ± 0.22 |
| array/dagger/N=256 (block 256)/broadcast (X .+ 1) | 0.713 ± 0.046 ms | 0.786 ± 0.073 ms | 0.907 ± 0.1 |
| array/dagger/N=256 (block 256)/map (sin.(X)) | 1.22 ± 0.048 ms | 1.09 ± 0.15 ms | 1.12 ± 0.16 |
| array/dagger/N=256 (block 256)/norm | 0.63 ± 0.18 ms | 0.703 ± 0.65 ms | 0.896 ± 0.87 |
| array/dagger/N=256 (block 256)/reduce (sum) | 3.86 ± 2.9 ms | 1.07 ± 1.8 ms | 3.59 ± 6.6 |
| array/dagger/N=256 (block 256)/transpose (permutedims) | 1.11 ± 0.3 ms | 1.04 ± 0.23 ms | 1.07 ± 0.38 |
| linalg/dagger/N=1024 (block 512)/cholesky | 22.5 ± 9.8 ms | 23.2 ± 1.8 ms | 0.971 ± 0.43 |
| linalg/dagger/N=1024 (block 512)/lu | 0.0417 ± 0.0043 s | 0.0414 ± 0.0059 s | 1.01 ± 0.18 |
| linalg/dagger/N=1024 (block 512)/matmul (A*A) | 0.0567 ± 0.0043 s | 0.0597 ± 0.017 s | 0.95 ± 0.27 |
| linalg/dagger/N=1024 (block 512)/matvec (A*x) | 2.9 ± 0.23 ms | 2.92 ± 0.23 ms | 0.99 ± 0.11 |
| linalg/dagger/N=1024 (block 512)/qr | 0.113 ± 0.006 s | 0.111 ± 0.0041 s | 1.02 ± 0.066 |
| linalg/dagger/N=1024 (block 512)/solve (A\b via lu) | 0.0568 ± 0.0013 s | 0.0531 ± 0.0042 s | 1.07 ± 0.087 |
| linalg/dagger/N=1024 (block 512)/svd | 0.0365 h | 0.0356 h | 1.03 |
| linalg/dagger/N=1024 (block 512)/syrk (A'*A) | 0.0366 ± 0.0013 s | 0.0374 ± 0.0056 s | 0.978 ± 0.15 |
| linalg/dagger/N=256 (block 256)/cholesky | 4.6 ± 1.7 ms | 4.35 ± 2.6 ms | 1.06 ± 0.75 |
| linalg/dagger/N=256 (block 256)/lu | 5.1 ± 1.2 ms | 4.18 ± 1 ms | 1.22 ± 0.42 |
| linalg/dagger/N=256 (block 256)/matmul (A*A) | 2.78 ± 1.4 ms | 2.42 ± 0.23 ms | 1.15 ± 0.59 |
| linalg/dagger/N=256 (block 256)/matvec (A*x) | 1.27 ± 0.075 ms | 1.33 ± 0.27 ms | 0.952 ± 0.2 |
| linalg/dagger/N=256 (block 256)/qr | 5.12 ± 1.2 ms | 5.03 ± 0.27 ms | 1.02 ± 0.25 |
| linalg/dagger/N=256 (block 256)/solve (A\b via lu) | 9.37 ± 0.51 ms | 18.9 ± 6.3 ms | 0.496 ± 0.17 |
| linalg/dagger/N=256 (block 256)/svd | 0.553 ± 0.039 s | 0.519 ± 0.0084 s | 1.07 ± 0.077 |
| linalg/dagger/N=256 (block 256)/syrk (A'*A) | 3.9 ± 2.6 ms | 4.26 ± 2 ms | 0.917 ± 0.75 |
| stencil/dagger/N=1024 (block 512)/alloc (neighbors Wrap) | 8.87 ± 0.69 ms | 9.43 ± 0.47 ms | 0.94 ± 0.087 |
| stencil/dagger/N=1024 (block 512)/assign (const) | 1.39 ± 0.15 ms | 1.41 ± 0.063 ms | 0.99 ± 0.12 |
| stencil/dagger/N=1024 (block 512)/multi-expr | 3.8 ± 0.6 ms | 4.24 ± 1.3 ms | 0.896 ± 0.3 |
| stencil/dagger/N=1024 (block 512)/neighbors (Clamp) | 7.23 ± 1.5 ms | 7.13 ± 0.65 ms | 1.01 ± 0.23 |
| stencil/dagger/N=1024 (block 512)/neighbors (Pad) | 7.18 ± 0.53 ms | 7.15 ± 0.52 ms | 1.01 ± 0.1 |
| stencil/dagger/N=1024 (block 512)/neighbors (Reflect) | 7.64 ± 1.8 ms | 7.01 ± 0.18 ms | 1.09 ± 0.26 |
| stencil/dagger/N=1024 (block 512)/neighbors (Wrap) | 7.12 ± 1.8 ms | 8.05 ± 0.58 ms | 0.885 ± 0.23 |
| stencil/dagger/N=1024 (block 512)/update (+) | 2.7 ± 0.44 ms | 2.26 ± 0.19 ms | 1.2 ± 0.22 |
| stencil/dagger/N=256 (block 256)/alloc (neighbors Wrap) | 1.93 ± 0.087 ms | 2.23 ± 0.29 ms | 0.864 ± 0.12 |
| stencil/dagger/N=256 (block 256)/assign (const) | 0.648 ± 0.083 ms | 0.779 ± 0.065 ms | 0.832 ± 0.13 |
| stencil/dagger/N=256 (block 256)/multi-expr | 1.27 ± 0.55 ms | 1.3 ± 0.069 ms | 0.978 ± 0.43 |
| stencil/dagger/N=256 (block 256)/neighbors (Clamp) | 1.52 ± 0.14 ms | 1.59 ± 0.12 ms | 0.957 ± 0.12 |
| stencil/dagger/N=256 (block 256)/neighbors (Pad) | 1.57 ± 0.11 ms | 1.76 ± 0.23 ms | 0.892 ± 0.13 |
| stencil/dagger/N=256 (block 256)/neighbors (Reflect) | 1.54 ± 0.19 ms | 1.77 ± 0.15 ms | 0.866 ± 0.13 |
| stencil/dagger/N=256 (block 256)/neighbors (Wrap) | 1.49 ± 0.058 ms | 1.71 ± 0.1 ms | 0.87 ± 0.063 |
| stencil/dagger/N=256 (block 256)/update (+) | 0.909 ± 0.057 ms | 1.01 ± 0.11 ms | 0.903 ± 0.11 |
| time_to_load | 1.03 ± 0.0031 s | 1.03 ± 0.042 s | 0.994 ± 0.041 |
Plots
⚠️ Regressions (> 25.0%)
linalg/dagger/N=256 (block 256)/solve (A\b via lu): +101.8%array/dagger/N=1024 (block 512)/norm: +25.8%
Improvements (> 25.0% faster)
array/dagger/N=256 (block 256)/reduce (sum): -72.2%
Distributed benchmarks (4 processes)
Dagger benchmarks: dirty vs master
| master | dirty | master / dirty | |
|---|---|---|---|
| array/dagger/N=1024 (block 512)/add (X + X) | 6 ± 0.14 ms | 6.15 ± 0.091 ms | 0.975 ± 0.027 |
| array/dagger/N=1024 (block 512)/alloc (rand) | 5.81 ± 0.09 ms | 5.87 ± 0.049 ms | 0.991 ± 0.018 |
| array/dagger/N=1024 (block 512)/broadcast (X .+ 1) | 4.6 ± 0.038 ms | 4.73 ± 0.074 ms | 0.971 ± 0.017 |
| array/dagger/N=1024 (block 512)/map (sin.(X)) | 13.1 ± 0.028 ms | 13.1 ± 0.051 ms | 0.995 ± 0.0044 |
| array/dagger/N=1024 (block 512)/norm | 1.36 ± 0.07 ms | 1.44 ± 0.12 ms | 0.941 ± 0.092 |
| array/dagger/N=1024 (block 512)/reduce (sum) | 2.45 ± 0.076 ms | 2.32 ± 0.14 ms | 1.06 ± 0.072 |
| array/dagger/N=1024 (block 512)/transpose (permutedims) | 12.5 ± 0.048 ms | 12 ± 0.13 ms | 1.04 ± 0.012 |
| array/dagger/N=256 (block 256)/add (X + X) | 0.977 ± 0.015 ms | 1.08 ± 0.11 ms | 0.902 ± 0.095 |
| array/dagger/N=256 (block 256)/alloc (rand) | 1.25 ± 0.03 ms | 1.28 ± 0.036 ms | 0.974 ± 0.036 |
| array/dagger/N=256 (block 256)/broadcast (X .+ 1) | 0.574 ± 0.022 ms | 0.479 ± 0.03 ms | 1.2 ± 0.088 |
| array/dagger/N=256 (block 256)/map (sin.(X)) | 1.1 ± 0.0089 ms | 1.2 ± 0.042 ms | 0.922 ± 0.034 |
| array/dagger/N=256 (block 256)/norm | 0.413 ± 0.052 ms | 0.472 ± 0.013 ms | 0.875 ± 0.11 |
| array/dagger/N=256 (block 256)/reduce (sum) | 0.753 ± 0.044 ms | 0.821 ± 0.043 ms | 0.918 ± 0.073 |
| array/dagger/N=256 (block 256)/transpose (permutedims) | 1.02 ± 0.11 ms | 1.2 ± 0.072 ms | 0.852 ± 0.11 |
| linalg/dagger/N=1024 (block 512)/cholesky | 18.2 ± 0.29 ms | 18.2 ± 0.29 ms | 0.999 ± 0.023 |
| linalg/dagger/N=1024 (block 512)/lu | 0.0437 ± 0.0028 s | 0.0426 ± 0.0022 s | 1.03 ± 0.085 |
| linalg/dagger/N=1024 (block 512)/matmul (A*A) | 0.0339 ± 0.00016 s | 0.0333 ± 0.00025 s | 1.02 ± 0.009 |
| linalg/dagger/N=1024 (block 512)/matvec (A*x) | 3.2 ± 0.023 ms | 2.9 ± 0.011 ms | 1.1 ± 0.0091 |
| linalg/dagger/N=1024 (block 512)/qr | 0.105 ± 0.0012 s | 0.104 ± 0.0021 s | 1.02 ± 0.023 |
| linalg/dagger/N=1024 (block 512)/solve (A\b via lu) | 0.0448 ± 2.6e-05 s | 0.0444 ± 0.00034 s | 1.01 ± 0.0077 |
| linalg/dagger/N=1024 (block 512)/svd | 0.0368 h | 0.0369 h | 0.995 |
| linalg/dagger/N=1024 (block 512)/syrk (A'*A) | 0.0348 ± 0.0026 s | 28.1 ± 0.29 ms | 1.24 ± 0.093 |
| linalg/dagger/N=256 (block 256)/cholesky | 2.63 ± 0.086 ms | 2.62 ± 0.013 ms | 1 ± 0.033 |
| linalg/dagger/N=256 (block 256)/lu | 4.26 ± 0.2 ms | 4.36 ± 0.17 ms | 0.976 ± 0.059 |
| linalg/dagger/N=256 (block 256)/matmul (A*A) | 2.19 ± 0.18 ms | 2.14 ± 0.063 ms | 1.02 ± 0.088 |
| linalg/dagger/N=256 (block 256)/matvec (A*x) | 1.34 ± 0.023 ms | 1.37 ± 0.066 ms | 0.985 ± 0.05 |
| linalg/dagger/N=256 (block 256)/qr | 4.03 ± 0.11 ms | 4.03 ± 0.076 ms | 1 ± 0.033 |
| linalg/dagger/N=256 (block 256)/solve (A\b via lu) | 6.62 ± 0.1 ms | 6.85 ± 0.13 ms | 0.966 ± 0.024 |
| linalg/dagger/N=256 (block 256)/svd | 0.57 ± 0.032 s | 0.547 ± 0.037 s | 1.04 ± 0.092 |
| linalg/dagger/N=256 (block 256)/syrk (A'*A) | 2.92 ± 0.066 ms | 2.8 ± 0.14 ms | 1.04 ± 0.058 |
| stencil/dagger/N=1024 (block 512)/alloc (neighbors Wrap) | 14.9 ± 0.17 ms | 14.9 ± 0.094 ms | 1 ± 0.013 |
| stencil/dagger/N=1024 (block 512)/assign (const) | 1.57 ± 0.083 ms | 1.34 ± 0.046 ms | 1.18 ± 0.074 |
| stencil/dagger/N=1024 (block 512)/multi-expr | 3.19 ± 0.31 ms | 3.07 ± 0.1 ms | 1.04 ± 0.11 |
| stencil/dagger/N=1024 (block 512)/neighbors (Clamp) | 10.5 ± 0.15 ms | 10.3 ± 0.039 ms | 1.02 ± 0.015 |
| stencil/dagger/N=1024 (block 512)/neighbors (Pad) | 10.6 ± 0.035 ms | 10.3 ± 0.082 ms | 1.03 ± 0.0089 |
| stencil/dagger/N=1024 (block 512)/neighbors (Reflect) | 11 ± 0.02 ms | 10.3 ± 0.082 ms | 1.07 ± 0.0087 |
| stencil/dagger/N=1024 (block 512)/neighbors (Wrap) | 11.2 ± 0.098 ms | 11 ± 0.0087 ms | 1.02 ± 0.0089 |
| stencil/dagger/N=1024 (block 512)/update (+) | 2.23 ± 0.039 ms | 2.12 ± 0.02 ms | 1.05 ± 0.021 |
| stencil/dagger/N=256 (block 256)/alloc (neighbors Wrap) | 1.95 ± 0.061 ms | 1.69 ± 0.055 ms | 1.15 ± 0.052 |
| stencil/dagger/N=256 (block 256)/assign (const) | 0.6 ± 0.054 ms | 0.63 ± 0.0086 ms | 0.953 ± 0.087 |
| stencil/dagger/N=256 (block 256)/multi-expr | 1.27 ± 0.098 ms | 1.2 ± 0.0058 ms | 1.05 ± 0.082 |
| stencil/dagger/N=256 (block 256)/neighbors (Clamp) | 1.63 ± 0.14 ms | 1.5 ± 0.053 ms | 1.08 ± 0.098 |
| stencil/dagger/N=256 (block 256)/neighbors (Pad) | 1.67 ± 0.039 ms | 1.5 ± 0.022 ms | 1.11 ± 0.031 |
| stencil/dagger/N=256 (block 256)/neighbors (Reflect) | 1.72 ± 0.055 ms | 1.55 ± 0.052 ms | 1.11 ± 0.051 |
| stencil/dagger/N=256 (block 256)/neighbors (Wrap) | 1.65 ± 0.033 ms | 1.58 ± 0.059 ms | 1.05 ± 0.044 |
| stencil/dagger/N=256 (block 256)/update (+) | 0.893 ± 0.095 ms | 0.816 ± 0.0056 ms | 1.09 ± 0.12 |
| time_to_load | 1.08 ± 0.0038 s | 1.09 ± 0.01 s | 0.994 ± 0.01 |
Plots
No regressions beyond 35.0% 🎉
MPI benchmarks (4 ranks)
Dagger benchmarks: dirty vs master
| master | dirty | master / dirty | |
|---|---|---|---|
| array/dagger/N=1024 (block 512)/add (X + X) | 0.0362 ± 0.012 s | ||
| array/dagger/N=1024 (block 512)/alloc (rand) | 10.3 ± 2.5 ms | ||
| array/dagger/N=1024 (block 512)/broadcast (X .+ 1) | 4.28 ± 0.54 ms | ||
| array/dagger/N=1024 (block 512)/map (sin.(X)) | 6.81 ± 0.5 ms | ||
| array/dagger/N=1024 (block 512)/norm | 4.93 ± 0.059 ms | ||
| array/dagger/N=1024 (block 512)/reduce (sum) | 7.25 ± 0.0062 ms | ||
| array/dagger/N=1024 (block 512)/transpose (permutedims) | 27.2 ± 0.4 ms | ||
| array/dagger/N=256 (block 256)/add (X + X) | 10.4 ± 0.082 ms | ||
| array/dagger/N=256 (block 256)/alloc (rand) | 2.65 ± 0.054 ms | ||
| array/dagger/N=256 (block 256)/broadcast (X .+ 1) | 1.32 ± 0.012 ms | ||
| array/dagger/N=256 (block 256)/map (sin.(X)) | 2.05 ± 0.00059 ms | ||
| array/dagger/N=256 (block 256)/norm | 1.91 ± 0.00094 ms | ||
| array/dagger/N=256 (block 256)/reduce (sum) | 2.07 ± 0.033 ms | ||
| array/dagger/N=256 (block 256)/transpose (permutedims) | 6.76 ± 0.011 ms | ||
| linalg/dagger/N=1024 (block 512)/cholesky | 0.503 ± 0.019 s | ||
| linalg/dagger/N=1024 (block 512)/lu | 0.0382 h | ||
| linalg/dagger/N=1024 (block 512)/matmul (A*A) | 0.956 ± 0.012 s | ||
| linalg/dagger/N=1024 (block 512)/matvec (A*x) | 0.0648 ± 0.00054 s | ||
| linalg/dagger/N=1024 (block 512)/qr | 0.991 ± 0.054 s | ||
| linalg/dagger/N=1024 (block 512)/solve (A\b via lu) | 0.0359 h | ||
| linalg/dagger/N=1024 (block 512)/syrk (A'*A) | 0.778 ± 0.0074 s | ||
| linalg/dagger/N=256 (block 256)/cholesky | 0.0372 ± 0.022 s | ||
| linalg/dagger/N=256 (block 256)/lu | 19.8 s | ||
| linalg/dagger/N=256 (block 256)/matmul (A*A) | 16.3 ± 2.8 ms | ||
| linalg/dagger/N=256 (block 256)/matvec (A*x) | 18.1 ± 2.8 ms | ||
| linalg/dagger/N=256 (block 256)/qr | 0.0474 ± 0.00096 s | ||
| linalg/dagger/N=256 (block 256)/solve (A\b via lu) | 20.5 s | ||
| linalg/dagger/N=256 (block 256)/syrk (A'*A) | 0.128 ± 0.00099 s | ||
| stencil/dagger/N=1024 (block 512)/alloc (neighbors Wrap) | 0.168 ± 0.00076 s | ||
| stencil/dagger/N=1024 (block 512)/assign (const) | 11.3 ± 0.2 ms | ||
| stencil/dagger/N=1024 (block 512)/multi-expr | 28.3 ± 0.31 ms | ||
| stencil/dagger/N=1024 (block 512)/neighbors (Clamp) | 0.155 ± 0.0012 s | ||
| stencil/dagger/N=1024 (block 512)/neighbors (Pad) | 0.152 ± 0.001 s | ||
| stencil/dagger/N=1024 (block 512)/neighbors (Reflect) | 0.156 ± 0.0044 s | ||
| stencil/dagger/N=1024 (block 512)/neighbors (Wrap) | 0.163 ± 0.0016 s | ||
| stencil/dagger/N=1024 (block 512)/update (+) | 17.9 ± 0.038 ms | ||
| stencil/dagger/N=256 (block 256)/alloc (neighbors Wrap) | 23.8 ± 4.1 ms | ||
| stencil/dagger/N=256 (block 256)/assign (const) | 3.5 ± 0.052 ms | ||
| stencil/dagger/N=256 (block 256)/multi-expr | 8.29 ± 0.23 ms | ||
| stencil/dagger/N=256 (block 256)/neighbors (Clamp) | 17.2 ± 0.026 ms | ||
| stencil/dagger/N=256 (block 256)/neighbors (Pad) | 16.9 ± 0.08 ms | ||
| stencil/dagger/N=256 (block 256)/neighbors (Reflect) | 17 ± 0.21 ms | ||
| stencil/dagger/N=256 (block 256)/neighbors (Wrap) | 17.3 ± 0.02 ms | ||
| stencil/dagger/N=256 (block 256)/update (+) | 4.91 ± 0.14 ms | ||
| time_to_load | 1.1 ± 0.0043 s | 1.1 ± 0.0011 s | 0.993 ± 0.004 |
No regressions beyond 35.0% 🎉
Full results and plots (download the benchmark-results-* artifacts).
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.
Written by Claude Opus