Skip to content

Fix stencils over-transfer of neighbor data - #737

Open
jpsamaroo wants to merge 8 commits into
masterfrom
jps/mpi-bad-scale-stencil
Open

Fix stencils over-transfer of neighbor data#737
jpsamaroo wants to merge 8 commits into
masterfrom
jps/mpi-bad-scale-stencil

Conversation

@jpsamaroo

Copy link
Copy Markdown
Member

This is a regression introduced by prior stencil optimizations, which improved multi-threaded execution but significantly regressed multi-process and multi-rank.

Written by Claude Opus

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Dagger benchmarks: dirty vs master

Multi-threaded benchmarks (4 threads)

Dagger benchmarks: dirty vs master

master dirty master / dirty
array/dagger/N=1024 (block 128)/add (X + X) 23.9 ± 2.6 ms 24.8 ± 2.5 ms 0.963 ± 0.14
array/dagger/N=1024 (block 128)/alloc (rand) 18 ± 0.75 ms 20.1 ± 0.36 ms 0.894 ± 0.041
array/dagger/N=1024 (block 128)/broadcast (X .+ 1) 15.6 ± 0.86 ms 18.2 ± 0.75 ms 0.854 ± 0.059
array/dagger/N=1024 (block 128)/map (sin.(X)) 14.9 ± 1.2 ms 17.6 ± 0.31 ms 0.847 ± 0.068
array/dagger/N=1024 (block 128)/norm 16.6 ± 0.22 ms 18.3 ± 0.59 ms 0.903 ± 0.031
array/dagger/N=1024 (block 128)/reduce (sum) 28.9 ± 0.63 ms 0.036 ± 0.0037 s 0.802 ± 0.085
array/dagger/N=1024 (block 128)/transpose (permutedims) 16.8 ± 3.8 ms 17.9 ± 0.73 ms 0.939 ± 0.22
array/dagger/N=1024 (block 512)/add (X + X) 4.52 ± 0.83 ms 4.96 ± 0.15 ms 0.912 ± 0.17
array/dagger/N=1024 (block 512)/alloc (rand) 3.94 ± 0.17 ms 4.58 ± 0.14 ms 0.86 ± 0.047
array/dagger/N=1024 (block 512)/broadcast (X .+ 1) 3.01 ± 0.1 ms 3.66 ± 0.29 ms 0.821 ± 0.071
array/dagger/N=1024 (block 512)/map (sin.(X)) 7.39 ± 0.22 ms 8.02 ± 0.76 ms 0.921 ± 0.091
array/dagger/N=1024 (block 512)/norm 1.5 ± 0.39 ms 1.78 ± 0.63 ms 0.846 ± 0.37
array/dagger/N=1024 (block 512)/reduce (sum) 3.57 ± 1.9 ms 3.73 ± 1.8 ms 0.956 ± 0.68
array/dagger/N=1024 (block 512)/transpose (permutedims) 7.45 ± 0.36 ms 9.02 ± 1.1 ms 0.826 ± 0.11
array/dagger/N=256 (block 128)/add (X + X) 3.17 ± 0.55 ms 2.89 ± 0.1 ms 1.1 ± 0.19
array/dagger/N=256 (block 128)/alloc (rand) 2.39 ± 0.17 ms 2.76 ± 0.3 ms 0.865 ± 0.11
array/dagger/N=256 (block 128)/broadcast (X .+ 1) 1.58 ± 0.51 ms 1.51 ± 3.8 ms 1.05 ± 2.6
array/dagger/N=256 (block 128)/map (sin.(X)) 1.58 ± 0.2 ms 1.75 ± 0.29 ms 0.904 ± 0.19
array/dagger/N=256 (block 128)/norm 1.72 ± 0.17 ms 1.52 ± 0.073 ms 1.13 ± 0.12
array/dagger/N=256 (block 128)/reduce (sum) 3.98 ± 6.6 ms 7.48 ± 2.9 ms 0.533 ± 0.91
array/dagger/N=256 (block 128)/transpose (permutedims) 2.75 ± 2 ms 2.69 ± 0.51 ms 1.02 ± 0.77
array/dagger/N=256 (block 256)/add (X + X) 1.69 ± 0.2 ms 1.64 ± 0.071 ms 1.03 ± 0.13
array/dagger/N=256 (block 256)/alloc (rand) 1.45 ± 0.12 ms 1.56 ± 0.21 ms 0.928 ± 0.15
array/dagger/N=256 (block 256)/broadcast (X .+ 1) 0.723 ± 0.11 ms 0.836 ± 0.2 ms 0.865 ± 0.24
array/dagger/N=256 (block 256)/map (sin.(X)) 1.73 ± 0.21 ms 1.57 ± 0.23 ms 1.1 ± 0.21
array/dagger/N=256 (block 256)/norm 0.691 ± 0.055 ms 0.886 ± 0.11 ms 0.78 ± 0.11
array/dagger/N=256 (block 256)/reduce (sum) 1.28 ± 0.16 ms 1.72 ± 0.4 ms 0.748 ± 0.2
array/dagger/N=256 (block 256)/transpose (permutedims) 1.62 ± 0.22 ms 1.42 ± 0.33 ms 1.14 ± 0.31
linalg/dagger/N=1024 (block 128)/cholesky 0.132 ± 0.0068 s 0.115 ± 0.0049 s 1.14 ± 0.076
linalg/dagger/N=1024 (block 128)/lu 0.263 ± 0.0092 s 0.27 ± 0.012 s 0.977 ± 0.055
linalg/dagger/N=1024 (block 128)/matmul (A*A) 0.317 ± 0.027 s 0.296 ± 0.015 s 1.07 ± 0.11
linalg/dagger/N=1024 (block 128)/matvec (A*x) 0.034 ± 0.0016 s 0.0337 ± 0.00092 s 1.01 ± 0.056
linalg/dagger/N=1024 (block 128)/qr 0.282 ± 0.05 s 0.221 ± 0.024 s 1.27 ± 0.27
linalg/dagger/N=1024 (block 128)/solve (A\b via lu) 0.375 ± 0.0026 s 0.337 ± 0.0092 s 1.11 ± 0.031
linalg/dagger/N=1024 (block 128)/svd 28.9 s 31.7 s 0.912
linalg/dagger/N=1024 (block 128)/syrk (A'*A) 0.206 ± 0.011 s 0.207 ± 0.029 s 0.992 ± 0.15
linalg/dagger/N=1024 (block 512)/cholesky 25.3 ± 0.82 ms 28.5 ± 2.7 ms 0.887 ± 0.088
linalg/dagger/N=1024 (block 512)/lu 0.0521 ± 0.0058 s 0.0518 ± 0.0031 s 1.01 ± 0.13
linalg/dagger/N=1024 (block 512)/matmul (A*A) 0.0518 ± 0.0089 s 0.0607 ± 0.012 s 0.853 ± 0.22
linalg/dagger/N=1024 (block 512)/matvec (A*x) 4.21 ± 0.27 ms 4.33 ± 1.5 ms 0.971 ± 0.34
linalg/dagger/N=1024 (block 512)/qr 0.116 ± 0.0028 s 0.129 ± 0.0024 s 0.901 ± 0.028
linalg/dagger/N=1024 (block 512)/solve (A\b via lu) 0.0621 ± 0.0014 s 0.0605 ± 0.0049 s 1.03 ± 0.086
linalg/dagger/N=1024 (block 512)/svd 0.0352 h 0.0392 h 0.899
linalg/dagger/N=1024 (block 512)/syrk (A'*A) 0.0421 ± 0.00074 s 0.0433 ± 0.004 s 0.971 ± 0.09
linalg/dagger/N=256 (block 128)/cholesky 13.6 ± 11 ms 7.53 ± 2.2 ms 1.81 ± 1.5
linalg/dagger/N=256 (block 128)/lu 14.5 ± 5.6 ms 16.7 ± 7.2 ms 0.871 ± 0.51
linalg/dagger/N=256 (block 128)/matmul (A*A) 6.44 ± 1.4 ms 7 ± 2.6 ms 0.919 ± 0.39
linalg/dagger/N=256 (block 128)/matvec (A*x) 3.8 ± 0.15 ms 6.2 ± 0.83 ms 0.612 ± 0.086
linalg/dagger/N=256 (block 128)/qr 12.5 ± 2.1 ms 14.6 ± 2.7 ms 0.856 ± 0.21
linalg/dagger/N=256 (block 128)/solve (A\b via lu) 23.4 ± 2.3 ms 26.1 ± 2.5 ms 0.898 ± 0.13
linalg/dagger/N=256 (block 128)/svd 0.691 ± 0.096 s 0.72 ± 0.067 s 0.961 ± 0.16
linalg/dagger/N=256 (block 128)/syrk (A'*A) 8 ± 7.3 ms 8.7 ± 1.7 ms 0.919 ± 0.86
linalg/dagger/N=256 (block 256)/cholesky 3.99 ± 2.1 ms 6.88 ± 2 ms 0.58 ± 0.35
linalg/dagger/N=256 (block 256)/lu 6.42 ± 0.35 ms 10.5 ± 1.6 ms 0.613 ± 0.1
linalg/dagger/N=256 (block 256)/matmul (A*A) 4.86 ± 0.63 ms 4.66 ± 0.65 ms 1.04 ± 0.2
linalg/dagger/N=256 (block 256)/matvec (A*x) 2.01 ± 0.24 ms 1.93 ± 0.56 ms 1.04 ± 0.33
linalg/dagger/N=256 (block 256)/qr 7.49 ± 1.1 ms 7.65 ± 2.2 ms 0.979 ± 0.32
linalg/dagger/N=256 (block 256)/solve (A\b via lu) 13 ± 1.5 ms 12.8 ± 2.2 ms 1.02 ± 0.21
linalg/dagger/N=256 (block 256)/svd 0.546 ± 0.002 s 0.639 ± 0.072 s 0.854 ± 0.097
linalg/dagger/N=256 (block 256)/syrk (A'*A) 5.57 ± 1.3 ms 6.93 ± 0.49 ms 0.804 ± 0.2
stencil/dagger/N=1024 (block 128)/alloc (neighbors Wrap) 0.0488 ± 0.048 s 0.0426 ± 0.0012 s 1.15 ± 1.1
stencil/dagger/N=1024 (block 128)/assign (const) 16.6 ± 0.84 ms 15.8 ± 0.36 ms 1.05 ± 0.058
stencil/dagger/N=1024 (block 128)/multi-expr 0.0345 ± 0.0013 s 0.0371 ± 0.0037 s 0.931 ± 0.099
stencil/dagger/N=1024 (block 128)/neighbors (Clamp) 0.0332 ± 0.002 s 30.6 ± 1.4 ms 1.08 ± 0.081
stencil/dagger/N=1024 (block 128)/neighbors (Pad) 31.1 ± 2.3 ms 0.0325 ± 0.0017 s 0.958 ± 0.086
stencil/dagger/N=1024 (block 128)/neighbors (Reflect) 0.0332 ± 0.0062 s 0.0324 ± 0.0021 s 1.02 ± 0.2
stencil/dagger/N=1024 (block 128)/neighbors (Wrap) 0.0332 ± 0.00011 s 0.0322 ± 0.00072 s 1.03 ± 0.023
stencil/dagger/N=1024 (block 128)/update (+) 22.1 ± 1.8 ms 17.1 ± 1.7 ms 1.3 ± 0.17
stencil/dagger/N=1024 (block 512)/alloc (neighbors Wrap) 12.6 ± 1.1 ms 10.5 ± 0.61 ms 1.2 ± 0.13
stencil/dagger/N=1024 (block 512)/assign (const) 2.13 ± 0.28 ms 1.97 ± 0.076 ms 1.08 ± 0.15
stencil/dagger/N=1024 (block 512)/multi-expr 3.69 ± 0.15 ms 4.52 ± 0.076 ms 0.816 ± 0.036
stencil/dagger/N=1024 (block 512)/neighbors (Clamp) 9.4 ± 1 ms 10.4 ± 0.96 ms 0.904 ± 0.13
stencil/dagger/N=1024 (block 512)/neighbors (Pad) 9.98 ± 0.72 ms 12 ± 1.3 ms 0.829 ± 0.11
stencil/dagger/N=1024 (block 512)/neighbors (Reflect) 9.41 ± 0.39 ms 9.67 ± 0.24 ms 0.973 ± 0.047
stencil/dagger/N=1024 (block 512)/neighbors (Wrap) 8.37 ± 0.55 ms 9.02 ± 0.36 ms 0.928 ± 0.071
stencil/dagger/N=1024 (block 512)/update (+) 2.82 ± 0.32 ms 3.2 ± 0.45 ms 0.883 ± 0.16
stencil/dagger/N=256 (block 128)/alloc (neighbors Wrap) 4.49 ± 0.56 ms 4.32 ± 1.5 ms 1.04 ± 0.38
stencil/dagger/N=256 (block 128)/assign (const) 1.94 ± 0.28 ms 2.19 ± 0.49 ms 0.882 ± 0.23
stencil/dagger/N=256 (block 128)/multi-expr 4.42 ± 0.65 ms 5.45 ± 1.2 ms 0.812 ± 0.21
stencil/dagger/N=256 (block 128)/neighbors (Clamp) 3.37 ± 0.17 ms 3.09 ± 0.24 ms 1.09 ± 0.1
stencil/dagger/N=256 (block 128)/neighbors (Pad) 3.64 ± 0.89 ms 3.39 ± 0.86 ms 1.07 ± 0.38
stencil/dagger/N=256 (block 128)/neighbors (Reflect) 3.32 ± 0.28 ms 3.28 ± 1.3 ms 1.01 ± 0.42
stencil/dagger/N=256 (block 128)/neighbors (Wrap) 3.37 ± 0.19 ms 5.32 ± 0.61 ms 0.634 ± 0.08
stencil/dagger/N=256 (block 128)/update (+) 2.06 ± 0.43 ms 1.97 ± 1.5 ms 1.05 ± 0.83
stencil/dagger/N=256 (block 256)/alloc (neighbors Wrap) 3.21 ± 0.14 ms 3.61 ± 0.09 ms 0.889 ± 0.045
stencil/dagger/N=256 (block 256)/assign (const) 1.22 ± 0.16 ms 1.07 ± 0.082 ms 1.13 ± 0.17
stencil/dagger/N=256 (block 256)/multi-expr 1.84 ± 0.18 ms 1.84 ± 0.11 ms 0.999 ± 0.11
stencil/dagger/N=256 (block 256)/neighbors (Clamp) 2.86 ± 0.14 ms 3.18 ± 0.11 ms 0.899 ± 0.054
stencil/dagger/N=256 (block 256)/neighbors (Pad) 2.74 ± 0.22 ms 2.53 ± 0.33 ms 1.08 ± 0.17
stencil/dagger/N=256 (block 256)/neighbors (Reflect) 3.12 ± 1.7 ms 2.9 ± 0.43 ms 1.08 ± 0.62
stencil/dagger/N=256 (block 256)/neighbors (Wrap) 2.71 ± 0.089 ms 3.13 ± 0.31 ms 0.866 ± 0.092
stencil/dagger/N=256 (block 256)/update (+) 1.51 ± 0.12 ms 1.44 ± 0.22 ms 1.05 ± 0.18
time_to_load 1.31 ± 0.032 s 1.28 ± 0.028 s 1.02 ± 0.033

Plots

⚠️ Regressions (> 25.0%)

  • array/dagger/N=256 (block 128)/reduce (sum): +87.8%
  • linalg/dagger/N=256 (block 256)/cholesky: +72.3%
  • linalg/dagger/N=256 (block 128)/matvec (A*x): +63.3%
  • linalg/dagger/N=256 (block 256)/lu: +63.1%
  • stencil/dagger/N=256 (block 128)/neighbors (Wrap): +57.8%
  • array/dagger/N=256 (block 256)/reduce (sum): +33.6%
  • array/dagger/N=256 (block 256)/norm: +28.3%

Improvements (> 25.0% faster)

  • linalg/dagger/N=256 (block 128)/cholesky: -44.8%
Distributed benchmarks (4 processes)

Dagger benchmarks: dirty vs master

master dirty master / dirty
array/dagger/N=1024 (block 128)/add (X + X) 30.8 ± 0.41 ms 30.4 ± 0.12 ms 1.01 ± 0.014
array/dagger/N=1024 (block 128)/alloc (rand) 16.6 ± 0.14 ms 16.5 ± 0.13 ms 1.01 ± 0.011
array/dagger/N=1024 (block 128)/broadcast (X .+ 1) 15.3 ± 0.13 ms 15.3 ± 0.11 ms 0.999 ± 0.011
array/dagger/N=1024 (block 128)/map (sin.(X)) 24.3 ± 0.36 ms 24.7 ± 0.27 ms 0.983 ± 0.018
array/dagger/N=1024 (block 128)/norm 11.5 ± 0.092 ms 11.7 ± 0.06 ms 0.985 ± 0.0094
array/dagger/N=1024 (block 128)/reduce (sum) 20.7 ± 0.11 ms 20.8 ± 0.26 ms 0.992 ± 0.013
array/dagger/N=1024 (block 128)/transpose (permutedims) 21.6 ± 6.4 ms 21.4 ± 0.18 ms 1.01 ± 0.3
array/dagger/N=1024 (block 512)/add (X + X) 6.28 ± 0.025 ms 3.32 ± 0.06 ms 1.89 ± 0.035
array/dagger/N=1024 (block 512)/alloc (rand) 6.09 ± 0.1 ms 3.35 ± 0.063 ms 1.82 ± 0.046
array/dagger/N=1024 (block 512)/broadcast (X .+ 1) 4.89 ± 0.21 ms 1.69 ± 0.024 ms 2.88 ± 0.13
array/dagger/N=1024 (block 512)/map (sin.(X)) 14.9 ± 0.25 ms 11.8 ± 0.037 ms 1.26 ± 0.022
array/dagger/N=1024 (block 512)/norm 1.27 ± 0.077 ms 1.26 ± 0.047 ms 1.01 ± 0.071
array/dagger/N=1024 (block 512)/reduce (sum) 2.25 ± 0.16 ms 2.11 ± 0.082 ms 1.06 ± 0.088
array/dagger/N=1024 (block 512)/transpose (permutedims) 14.2 ± 0.51 ms 11.4 ± 0.046 ms 1.24 ± 0.045
array/dagger/N=256 (block 128)/add (X + X) 2.29 ± 0.04 ms 2.32 ± 0.82 ms 0.988 ± 0.35
array/dagger/N=256 (block 128)/alloc (rand) 2.07 ± 0.031 ms 2.11 ± 0.041 ms 0.984 ± 0.024
array/dagger/N=256 (block 128)/broadcast (X .+ 1) 1.24 ± 0.079 ms 1.21 ± 0.09 ms 1.03 ± 0.1
array/dagger/N=256 (block 128)/map (sin.(X)) 1.81 ± 0.033 ms 1.79 ± 0.019 ms 1.01 ± 0.021
array/dagger/N=256 (block 128)/norm 1.02 ± 0.046 ms 0.995 ± 0.0022 ms 1.02 ± 0.047
array/dagger/N=256 (block 128)/reduce (sum) 1.95 ± 0.41 ms 2.05 ± 0.39 ms 0.952 ± 0.27
array/dagger/N=256 (block 128)/transpose (permutedims) 1.9 ± 0.18 ms 1.94 ± 0.033 ms 0.979 ± 0.097
array/dagger/N=256 (block 256)/add (X + X) 1.4 ± 0.037 ms 1.4 ± 0.02 ms 1 ± 0.03
array/dagger/N=256 (block 256)/alloc (rand) 1.43 ± 0.019 ms 1.44 ± 0.056 ms 0.996 ± 0.041
array/dagger/N=256 (block 256)/broadcast (X .+ 1) 0.724 ± 0.12 ms 0.733 ± 0.012 ms 0.988 ± 0.16
array/dagger/N=256 (block 256)/map (sin.(X)) 1.36 ± 0.15 ms 1.38 ± 0.04 ms 0.988 ± 0.11
array/dagger/N=256 (block 256)/norm 0.464 ± 0.052 ms 0.479 ± 0.018 ms 0.969 ± 0.11
array/dagger/N=256 (block 256)/reduce (sum) 0.799 ± 0.062 ms 0.879 ± 0.0059 ms 0.909 ± 0.071
array/dagger/N=256 (block 256)/transpose (permutedims) 1.28 ± 0.072 ms 1.31 ± 0.048 ms 0.982 ± 0.066
linalg/dagger/N=1024 (block 128)/cholesky 0.065 ± 0.0058 s 0.074 ± 0.0032 s 0.879 ± 0.087
linalg/dagger/N=1024 (block 128)/lu 0.17 ± 0.00096 s 0.168 ± 0.00097 s 1.01 ± 0.0082
linalg/dagger/N=1024 (block 128)/matmul (A*A) 0.186 ± 0.014 s 0.171 ± 0.00011 s 1.09 ± 0.083
linalg/dagger/N=1024 (block 128)/matvec (A*x) 26.3 ± 0.6 ms 26.9 ± 0.37 ms 0.978 ± 0.026
linalg/dagger/N=1024 (block 128)/qr 0.246 ± 0.0014 s 0.245 ± 9.7e-05 s 1.01 ± 0.0058
linalg/dagger/N=1024 (block 128)/solve (A\b via lu) 0.211 ± 0.002 s 0.209 ± 0.00014 s 1.01 ± 0.0098
linalg/dagger/N=1024 (block 128)/svd 52.3 s 51.1 s 1.02
linalg/dagger/N=1024 (block 128)/syrk (A'*A) 0.112 ± 0.0019 s 0.112 ± 0.0016 s 1 ± 0.022
linalg/dagger/N=1024 (block 512)/cholesky 22.1 ± 0.49 ms 22.1 ± 0.82 ms 1 ± 0.043
linalg/dagger/N=1024 (block 512)/lu 0.0448 ± 0.0015 s 0.0454 ± 0.00023 s 0.988 ± 0.034
linalg/dagger/N=1024 (block 512)/matmul (A*A) 0.0364 ± 3.9e-05 s 0.0403 ± 0.0028 s 0.903 ± 0.062
linalg/dagger/N=1024 (block 512)/matvec (A*x) 3.21 ± 0.11 ms 3.44 ± 0.12 ms 0.935 ± 0.045
linalg/dagger/N=1024 (block 512)/qr 0.104 ± 0.00058 s 0.104 ± 0.00076 s 1.01 ± 0.0093
linalg/dagger/N=1024 (block 512)/solve (A\b via lu) 0.0489 ± 0.00056 s 0.0479 ± 0.00048 s 1.02 ± 0.016
linalg/dagger/N=1024 (block 512)/svd 0.0394 h 0.0393 h 1
linalg/dagger/N=1024 (block 512)/syrk (A'*A) 30.7 ± 0.17 ms 30.7 ± 0.21 ms 1 ± 0.0086
linalg/dagger/N=256 (block 128)/cholesky 5.65 ± 0.53 ms 4.64 ± 0.041 ms 1.22 ± 0.11
linalg/dagger/N=256 (block 128)/lu 8.43 ± 0.18 ms 8.29 ± 0.053 ms 1.02 ± 0.022
linalg/dagger/N=256 (block 128)/matmul (A*A) 5.35 ± 0.35 ms 6.15 ± 0.34 ms 0.87 ± 0.074
linalg/dagger/N=256 (block 128)/matvec (A*x) 2.83 ± 0.031 ms 2.92 ± 0.11 ms 0.971 ± 0.037
linalg/dagger/N=256 (block 128)/qr 8 ± 0.086 ms 8.54 ± 0.37 ms 0.937 ± 0.041
linalg/dagger/N=256 (block 128)/solve (A\b via lu) 14.1 ± 0.17 ms 15.4 ± 0.75 ms 0.92 ± 0.046
linalg/dagger/N=256 (block 128)/svd 0.536 ± 0.056 s 0.874 ± 0.025 s 0.613 ± 0.067
linalg/dagger/N=256 (block 128)/syrk (A'*A) 5.77 ± 0.16 ms 5.97 ± 0.058 ms 0.966 ± 0.028
linalg/dagger/N=256 (block 256)/cholesky 2.38 ± 0.075 ms 2.93 ± 0.21 ms 0.813 ± 0.063
linalg/dagger/N=256 (block 256)/lu 4.21 ± 0.066 ms 4.02 ± 0.043 ms 1.05 ± 0.02
linalg/dagger/N=256 (block 256)/matmul (A*A) 2.1 ± 0.072 ms 2.26 ± 0.21 ms 0.93 ± 0.091
linalg/dagger/N=256 (block 256)/matvec (A*x) 1.48 ± 0.013 ms 1.49 ± 0.012 ms 0.997 ± 0.012
linalg/dagger/N=256 (block 256)/qr 4.63 ± 0.03 ms 4.97 ± 0.25 ms 0.931 ± 0.048
linalg/dagger/N=256 (block 256)/solve (A\b via lu) 6.65 ± 0.1 ms 7.24 ± 0.00082 ms 0.918 ± 0.014
linalg/dagger/N=256 (block 256)/svd 0.622 ± 0.0034 s 0.402 ± 0.062 s 1.55 ± 0.24
linalg/dagger/N=256 (block 256)/syrk (A'*A) 2.94 ± 0.17 ms 3.25 ± 0.073 ms 0.905 ± 0.056
stencil/dagger/N=1024 (block 128)/alloc (neighbors Wrap) 0.041 ± 0.0003 s 0.0404 ± 0.00011 s 1.01 ± 0.0078
stencil/dagger/N=1024 (block 128)/assign (const) 11.1 ± 0.025 ms 11.1 ± 0.027 ms 1 ± 0.0034
stencil/dagger/N=1024 (block 128)/multi-expr 24.4 ± 0.27 ms 24.3 ± 0.12 ms 1 ± 0.012
stencil/dagger/N=1024 (block 128)/neighbors (Clamp) 0.0324 ± 5.3e-05 s 0.0329 ± 0.0001 s 0.985 ± 0.0034
stencil/dagger/N=1024 (block 128)/neighbors (Pad) 0.0327 ± 2.1e-05 s 0.0329 ± 0.00012 s 0.994 ± 0.0036
stencil/dagger/N=1024 (block 128)/neighbors (Reflect) 0.0329 ± 1.9e-05 s 0.0332 ± 3.6e-05 s 0.992 ± 0.0012
stencil/dagger/N=1024 (block 128)/neighbors (Wrap) 0.0332 ± 0.00011 s 0.0334 ± 4.3e-05 s 0.994 ± 0.0034
stencil/dagger/N=1024 (block 128)/update (+) 14 ± 0.051 ms 14 ± 0.029 ms 0.998 ± 0.0042
stencil/dagger/N=1024 (block 512)/alloc (neighbors Wrap) 12.1 ± 0.033 ms 12.5 ± 0.03 ms 0.971 ± 0.0036
stencil/dagger/N=1024 (block 512)/assign (const) 1.46 ± 0.044 ms 1.59 ± 0.013 ms 0.922 ± 0.029
stencil/dagger/N=1024 (block 512)/multi-expr 2.88 ± 0.0014 ms 3.18 ± 0.0099 ms 0.905 ± 0.0028
stencil/dagger/N=1024 (block 512)/neighbors (Clamp) 11.2 ± 0.082 ms 11.6 ± 0.023 ms 0.966 ± 0.0074
stencil/dagger/N=1024 (block 512)/neighbors (Pad) 11.1 ± 0.087 ms 11.6 ± 0.017 ms 0.95 ± 0.0076
stencil/dagger/N=1024 (block 512)/neighbors (Reflect) 11.4 ± 0.00078 ms 11.6 ± 0.029 ms 0.987 ± 0.0025
stencil/dagger/N=1024 (block 512)/neighbors (Wrap) 11.6 ± 0.09 ms 11.6 ± 0.033 ms 1 ± 0.0082
stencil/dagger/N=1024 (block 512)/update (+) 2.27 ± 0.067 ms 2.33 ± 0.0041 ms 0.973 ± 0.029
stencil/dagger/N=256 (block 128)/alloc (neighbors Wrap) 3.29 ± 0.041 ms 3.3 ± 0.049 ms 0.997 ± 0.019
stencil/dagger/N=256 (block 128)/assign (const) 1.27 ± 0.051 ms 1.36 ± 0.043 ms 0.931 ± 0.048
stencil/dagger/N=256 (block 128)/multi-expr 2.43 ± 0.0051 ms 2.5 ± 0.036 ms 0.973 ± 0.014
stencil/dagger/N=256 (block 128)/neighbors (Clamp) 2.83 ± 0.22 ms 2.85 ± 0.047 ms 0.994 ± 0.079
stencil/dagger/N=256 (block 128)/neighbors (Pad) 2.63 ± 0.022 ms 2.76 ± 0.034 ms 0.952 ± 0.014
stencil/dagger/N=256 (block 128)/neighbors (Reflect) 2.67 ± 0.081 ms 2.77 ± 0.041 ms 0.964 ± 0.033
stencil/dagger/N=256 (block 128)/neighbors (Wrap) 2.68 ± 0.09 ms 2.8 ± 0.022 ms 0.957 ± 0.033
stencil/dagger/N=256 (block 128)/update (+) 1.46 ± 0.014 ms 1.51 ± 0.03 ms 0.967 ± 0.021
stencil/dagger/N=256 (block 256)/alloc (neighbors Wrap) 1.83 ± 0.011 ms 1.89 ± 0.037 ms 0.972 ± 0.02
stencil/dagger/N=256 (block 256)/assign (const) 0.816 ± 0.028 ms 0.778 ± 0.0099 ms 1.05 ± 0.038
stencil/dagger/N=256 (block 256)/multi-expr 1.3 ± 0.088 ms 1.43 ± 0.052 ms 0.909 ± 0.07
stencil/dagger/N=256 (block 256)/neighbors (Clamp) 1.62 ± 0.015 ms 1.72 ± 0.0071 ms 0.941 ± 0.0094
stencil/dagger/N=256 (block 256)/neighbors (Pad) 1.61 ± 0.025 ms 1.68 ± 0.00041 ms 0.957 ± 0.015
stencil/dagger/N=256 (block 256)/neighbors (Reflect) 1.75 ± 0.04 ms 1.68 ± 0.02 ms 1.04 ± 0.027
stencil/dagger/N=256 (block 256)/neighbors (Wrap) 1.67 ± 0.02 ms 1.67 ± 0.036 ms 1 ± 0.025
stencil/dagger/N=256 (block 256)/update (+) 0.91 ± 0.02 ms 0.928 ± 0.0063 ms 0.981 ± 0.023
time_to_load 1.11 ± 0.013 s 1.12 ± 0.0056 s 0.995 ± 0.013

Plots

⚠️ Regressions (> 35.0%)

  • linalg/dagger/N=256 (block 128)/svd: +63.1%

Improvements (> 35.0% faster)

  • array/dagger/N=1024 (block 512)/broadcast (X .+ 1): -65.3%
  • array/dagger/N=1024 (block 512)/add (X + X): -47.0%
  • array/dagger/N=1024 (block 512)/alloc (rand): -44.9%
  • linalg/dagger/N=256 (block 256)/svd: -35.4%
MPI benchmarks (4 ranks)

Results unavailable (job did not produce a report).

Full results and plots (download the benchmark-results-* artifacts).

@jpsamaroo
jpsamaroo force-pushed the jps/mpi-bad-scale-stencil branch from 3e7eedb to 3c0b231 Compare August 17, 2026 22:36
…ronment

MPI.mpiexec(identity) only extracted the executable/Cmd; MPI.jl's
mpiexec() sets up the environment the binary needs (library paths for
the bundled MPICH_jll, etc.) only for the dynamic extent of its
callback, so running the extracted command afterwards spawned a
process missing that environment, which died immediately before
worker_mpi.jl could produce any results. Move the run(...; wait=false)
call inside the `do` block, mirroring test/run_mpi.jl.
Sweep BENCHMARK_BLOCKSIZE instead of pinning one tile.

The three benchmark jobs ran BENCHMARK_SCALE='[256, 1024]' with the tile left
at square_block's default of 512, which makes N=256 a *single* chunk (it
returns N when N <= tile) and N=1024 only 2x2 -- so anything touching data
distribution, halo exchange or cross-space transfer was a no-op at one size
and barely exercised at the other.

Simply lowering the tile would trade one blind spot for another: tile size
sets the ratio of per-tile task overhead to bytes moved per transfer, and a
data-movement change can be a large win at one tile and a regression at the
other. Measured under MPI on 4 ranks, commit c60e294 is ~25% slower at tile
128 and ~1.5-2x faster at tile 512 -- opposite conclusions from the same code.

So BENCHMARK_BLOCKSIZE now takes a list, like BENCHMARK_SCALE. common.jl gains
`blocksizes` and `blocks_for(N)`, and the three dense suites gain an inner loop
over it. `blocks_for` de-duplicates tiles that collapse to the same block at a
given N (at N=256 both 512 and 256 give 256), which matters because the groups
are keyed "N=$N (block $b)" and would otherwise collide. CI uses '[128, 512]';
an unset variable still means a single tile of 512, as before.
`gather_free_syncdeps!` decided whether it could inspect a free candidate's
backing buffer via `aliasing_available`, which under MPI was true only on the
rank owning the ref. But `aliasing` is *collective* there -- the owner computes
and broadcasts -- so the owning rank alone would enter a broadcast no other rank
was in. That consumed a tag on one rank, desynchronizing every subsequent
`to_tag`, and then deadlocked in the copy cycles at region end.

Symptom on this branch: 26 errors per rank in `test/mpi.jl` (Cholesky, LU, and
every `@stencil` case), all `ArgumentError: Non-uniform value` raised from
`take_ref_id!`. Bisected to 87273af -- not because that commit is wrong, but
because ranking by written args moves each task onto the rank owning the chunk
it writes, which is what first makes a buffer underlie only wrapper arguments
and so reach this branch at all. Every rank now takes the rank-uniform
`key_ainfo` fallback whenever execution is uniform, so the decision depends
solely on `uniform_execution()`.

That leaves `aliasing_available` with no remaining call site --
`uniform_execution(::MPIAcceleration)` is unconditionally `true`, so the MPIRef
method could never be reached past the short-circuit -- so it is removed along
with its generic fallback.

`test/mpi.jl` goes from 26 errors per rank to 0 (395/397/385/373 passing, no
failures); Distributed datadeps is unchanged at 1272 passing.

Also adds a "Partial slot currency" testset covering the case that motivated
this: writing through a `ChunkView` on a non-owner rank and then reading the
whole parent there, which must see the view's write and pull the rest from the
owner. Its expectation is snapshotted *before* the region -- `A` is a non-Chunk
argument, so rank 0 owns it and Datadeps writes back into it in place; deriving
the baseline afterwards folds the update in and then adds it again (and only
rank 0's copy is written back, so it would also disagree across ranks).
`gather_free_syncdeps!`'s uniform-execution fallback syncs on the cache key
ainfo, but guarded that on the key being tracked:

    haskey(state.ainfos_overlaps, wrapped) &&
        get_write_deps!(state, space, wrapped, write_num, syncdeps)

The key ainfo describes the original object in its *source* space, so it is
frequently not one of the tracked destination-space ainfos -- rank-stamped spans
mean it cannot overlap them, and the interval tree cannot be queried with it
either. When that happens the guard short-circuits and the `unsafe_free!` task is
spawned with an **empty syncdep set**, racing every task still reading the
buffer. Instrumenting a 64x64 / Blocks(16,16) Cholesky under 4 ranks shows a
handful of such frees per rank (`nsyncdeps=0`) among ~100 with 2-3.

This is normally invisible: the freed Libc block is recycled by the next
`alloc_libc_array`, and `move_rewrap` immediately overwrites it with a full copy
of the real data, so a use-after-free read gets plausible -- finite, usually
identical -- values.

The untracked case now syncs against every tracked ainfo in the space instead: a
strict over-approximation of what can overlap the buffer, and cheap, since frees
happen once per buffer at region end and only untracked keys take this branch.
`get_write_deps!` already covers both writers and readers, so the tracked case
was fine as-is.

`test/mpi.jl` is unchanged at 395/397/385/373 passing, no failures or errors.
A Datadeps slot's contents are never assumed to be current. `generate_slot!`
says so outright -- it deliberately does not sync with the owner -- and
`compute_remainder_for_arg!` decides what to copy purely from `arg_history` /
`arg_owner`, never from the buffer. A slot in a space that does not yet appear
in the argument's history is therefore always filled by a copy-to (`FullCopy`,
or a span-exact `MultiRemainderAliasing` once part of it is current) before any
task can read it.

So for a dense, isbits payload crossing a space boundary, `move_rewrap` was
sending bytes that were guaranteed to be overwritten. It now allocates the
destination buffer and sends nothing: only the element type and dimensions
travel (broadcast like a header under MPI, closure arguments under Distributed).
The allocation is Libc-backed straight from `alloc_libc_array` rather than
allocated and then copied into Libc memory by `libc_backed`, since there is
nothing to copy. Under MPI that also fixes a latent no-op -- `libc_backed` was
being applied to a `Chunk`, which hits the identity method.

`from_space != to_space` is the load-bearing guard. When the two coincide the
leaf transfer is the identity, so the "slot" *is* the original data and
`compute_remainder_for_arg!` returns `NoAliasing()`; handing back a fresh buffer
there would silently discard the argument. Allocation also stays routed through
`aliased_object!`, so an already-present parent is still reused rather than
shadowed by a detached buffer -- which is what keeps an array and its view
sharing one destination allocation.

N.B. This depends on the preceding free-syncdeps fix. Transferring the slot was
masking a use-after-free: a recycled Libc block was immediately overwritten with
real data, so a racing read returned plausible values. Without the transfer the
same read hits uninitialized memory, which is how that bug first became visible
(NaN out of a distributed Cholesky).

Measured on 4 MPI ranks, a 1024x1024 `@stencil` sweep with `@neighbors`:
689ms/sweep at 128x128 tiles (872ms before) and 316ms at 512x512 tiles (509ms
before). `test/mpi.jl` 398/403/388/376 passing; Distributed datadeps 1272
passing.

Test contract updated accordingly: cross-space slots are no longer asserted to
hold a copy of the source, only to have the right shape, type, and parent
sharing; same-space slots must still be the real data. Two new testsets cover
the allocation itself and the partial-currency invariant it rests on -- a task
reading spans never written in its own space.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant