Skip to content

Performance: 1D Transport ​

The 1D plans of 1D Transport, on batches of 256 independent problems: the threads of the host, or the card, share them. Two kinds of measures only: diracs, and images of affine pixels ( Image( degree = 1 ), a piecewise linear density ). Three cases:

  1. semi-discrete transports: diracs against an image;
  2. transports between two images, two continuous densities;
  3. against POT, on the transport between two images, discretized for POT.

The images sample sums of two thin gaussians ( standard deviation 0.05 ), shifted from one problem to the next. The diracs are drawn uniformly. Everything is in double; the inputs are already where the computation runs, and the time stops when the costs are on the host.

Semi-discrete transports ​

SdotPlan1d on a batch: each problem's diracs sorted, then one sweep along its target, an image of 1e4 pixels. The costs are exact.

sdot, EPYC 7763, 128 coressdot, H100
256 semi-discrete transports20 ms50 ms100 ms200 ms500 ms1 s1e31e41e51e6diracs per problemsdot, H100, n = 1e3: 31.8 ms at 1.98 GHzsdot, H100, n = 1e4: 30.3 ms at 1.98 GHzsdot, H100, n = 1e5: 37 ms at 1.98 GHzsdot, H100, n = 1e6: 92.9 ms at 1.98 GHzsdot, 128 threads, n = 1e3: 36.2 ms at 2.48 GHzsdot, 128 threads, n = 1e4: 47.4 ms at 2.48 GHzsdot, 128 threads, n = 1e5: 159 ms at 2.48 GHzsdot, 128 threads, n = 1e6: 818 ms at 2.48 GHz

Total time for 256 transports against images of 1e4 pixels: a slope of one decade per decade is linear. Log-log; hover a point for its time.

256 transports of 1e6 diracs take 0.82 s on 128 cores, 0.09 s on an H100.

Between two images ​

ContinuousOtPlan1d between two images: no sort, the merge of the pixels of both, O( pixels ).

sdot, EPYC 7763, 128 coressdot, H100
256 transports between two images30 ms100 ms300 ms1 s1e31e41e51e6pixels per imagesdot, H100, n = 1e3: 24.7 ms at 1.98 GHzsdot, H100, n = 1e4: 39.8 ms at 1.98 GHzsdot, H100, n = 1e5: 193 ms at 1.98 GHzsdot, H100, n = 1e6: 1.73 s at 1.98 GHzsdot, 128 threads, n = 1e3: 21.3 ms at 2.48 GHzsdot, 128 threads, n = 1e4: 50.1 ms at 2.48 GHzsdot, 128 threads, n = 1e5: 247 ms at 2.48 GHzsdot, 128 threads, n = 1e6: 1.59 s at 2.48 GHz

Total time for 256 pairs of images: a slope of one decade per decade is linear. Log-log; hover a point for its time.

256 pairs of images of 1e6 pixels take 1.6 s on 128 cores, 1.7 s on the H100.

The cost is exact up to the quadrature of each merged piece: against an affine target, the transport map involves a square root, and so does its integral ( a closed form with an arcsinh, unstable when a slope vanishes ). Seven Gauss-Legendre points per piece agree with a 64-point reference to 7e-15 on images of 1024 pixels.

Over the threads ​

The problems are independent: the threads share them.

sdotsdot, H100 ( 132 SMs )ideal scaling
Diracs against an image30M100M300M1G3G1248163264128threads · SMssdot, 1 thread: 4.6G cycles, 1.33 s at 3.42 GHzsdot, 2 threads: 2.4G cycles, 706 ms at 3.39 GHzsdot, 4 threads: 1.3G cycles, 382 ms at 3.33 GHzsdot, 8 threads: 760M cycles, 233 ms at 3.27 GHzsdot, 16 threads: 490M cycles, 160 ms at 3.04 GHzsdot, 32 threads: 380M cycles, 127 ms at 2.99 GHzsdot, 64 threads: 350M cycles, 123 ms at 2.87 GHzsdot, 128 threads: 400M cycles, 159 ms at 2.48 GHzsdot, H100 ( 132 SMs ): 73M cycles, 37 ms at 1.98 GHz
Two images100M300M1G3G10G1248163264128threads · SMssdot, 1 thread: 10G cycles, 3.06 s at 3.42 GHzsdot, 2 threads: 5.4G cycles, 1.6 s at 3.39 GHzsdot, 4 threads: 2.9G cycles, 872 ms at 3.33 GHzsdot, 8 threads: 1.7G cycles, 516 ms at 3.27 GHzsdot, 16 threads: 1G cycles, 342 ms at 3.04 GHzsdot, 32 threads: 750M cycles, 252 ms at 2.99 GHzsdot, 64 threads: 670M cycles, 233 ms at 2.87 GHzsdot, 128 threads: 610M cycles, 247 ms at 2.48 GHzsdot, H100 ( 132 SMs ): 380M cycles, 193 ms at 1.98 GHz

Cycles for 256 problems of 1e5 diracs ( or pixels ): the time times the clock, the median of the clocks measured on the machine at that thread count ( the cores' on the CPU, the SMs' on a card ): one run's sample is too noisy. Log-log; hover a point for its time and clock.

The images gain 13 times on 128 cores, the diracs 11 times; past 32 threads both gain little, bound by the memory.

Against POT ​

POT has no continuous transport. The fair comparison is therefore on two continuous densities, both discretized alike for POT: each image becomes m diracs, the centres of a regular grid of its support weighted by the masses of their cells, the same number on both sides, then ot.wasserstein_1d( p = 2 ), one column per pair. The more diracs, the closer POT gets to the continuous W2, which sdot computes exactly: the chart puts the time of each method against the error on W2. The two do not solve the same problem; the chart gives the time POT needs for a given accuracy, at as many threads: sdot on one thread against POT on numpy, on all the cores against POT on torch, and on the card against POT on its jax backend.

sdot, one threadsdot, all the cores ( the card )POT, numpy ( one thread )POT, torch on all the cores ( jax on the card )
EPYC 7763100 ms1 s10 s100 s1e-111e-101e-91e-81e-71e-6relative error on W2sdot, one thread: 320 mssdot, all the cores ( the card ): 47.1 msPOT, numpy ( one thread ), CPU, error 2.4e-6: 48.9 ms at 3.42 GHzPOT, numpy ( one thread ), CPU, error 2.6e-7: 171 ms at 3.42 GHzPOT, numpy ( one thread ), CPU, error 2.4e-8: 603 ms at 3.42 GHzPOT, numpy ( one thread ), CPU, error 2.6e-9: 1.99 s at 3.42 GHzPOT, numpy ( one thread ), CPU, error 2.3e-10: 7.77 s at 3.42 GHzPOT, numpy ( one thread ), CPU, error 2.6e-11: 24.1 s at 3.42 GHzPOT, numpy ( one thread ), CPU, error 2.7e-12: 83.6 s at 3.42 GHzPOT, torch on all the cores ( jax on the card ), CPU, error 2.4e-6: 56.1 ms at 2.48 GHzPOT, torch on all the cores ( jax on the card ), CPU, error 2.6e-7: 87.2 ms at 2.48 GHzPOT, torch on all the cores ( jax on the card ), CPU, error 2.4e-8: 306 ms at 2.48 GHzPOT, torch on all the cores ( jax on the card ), CPU, error 2.6e-9: 763 ms at 2.48 GHzPOT, torch on all the cores ( jax on the card ), CPU, error 2.3e-10: 2.74 s at 2.48 GHzPOT, torch on all the cores ( jax on the card ), CPU, error 2.6e-11: 8.12 s at 2.48 GHzPOT, torch on all the cores ( jax on the card ), CPU, error 2.7e-12: 24.8 s at 2.48 GHz
H1003 ms10 ms30 ms100 ms300 ms1 s1e-111e-101e-91e-81e-71e-6relative error on W2sdot, all the cores ( the card ): 39.8 msPOT, torch on all the cores ( jax on the card ), H100, error 2.4e-6: 3.36 ms at 1.98 GHzPOT, torch on all the cores ( jax on the card ), H100, error 2.6e-7: 3.5 ms at 1.98 GHzPOT, torch on all the cores ( jax on the card ), H100, error 2.4e-8: 7.49 ms at 1.98 GHzPOT, torch on all the cores ( jax on the card ), H100, error 2.6e-9: 33.6 ms at 1.98 GHzPOT, torch on all the cores ( jax on the card ), H100, error 2.3e-10: 317 ms at 1.98 GHzPOT, torch on all the cores ( jax on the card ), H100, error 2.6e-11: 1.63 s at 1.98 GHz

Time against the relative error, 256 pairs of images of 1e4 pixels, in double; a curve joins the runs of a method in the order of their size. Log-log; hover a point for its time.

POT's error on W2 decreases as 1 / m² ( two digits per decade of diracs ). sdot, exact, takes 0.047 s on 128 cores: POT on torch, on the same cores, takes as long for an error of about 1e-6, 0.31 s for 2.4e-8 and 25 s for 2.7e-12. On one thread, 0.32 s for sdot against 0.6 s for POT on numpy at 2.4e-8. On the H100, POT is faster down to about 1e-9 ( 7 ms for 2.4e-8, 34 ms for 2.6e-9, against 40 ms for sdot, of which 18 ms of kernels: the rest is the cost of the calls ), and slower beyond ( 1.6 s for 2.6e-11 ).

The tables ​

Semi-discrete transports
sdot
all the coresH100
1e30.0362 s0.0318 s
1e40.0474 s0.0303 s
1e50.159 s0.0370 s
1e60.818 s0.0929 s

Seconds, 256 semi-discrete transports: n diracs each against an image of 1e4 affine pixels, the inputs already where the computation runs, until the costs are on the host.

Between two images
sdot
all the coresH100
1e30.0213 s0.0247 s
1e40.0501 s0.00749 s
1e50.247 s0.193 s
1e61.59 s1.73 s

Seconds, 256 transports between two images of n affine pixels each.

Over the threads
threads
1 / 8 / 32 / 128 threads
diracs1.33 s / 0.233 s / 0.127 s / 0.159 s
images3.06 s / 0.516 s / 0.252 s / 0.247 s

Seconds, over the threads: 256 transports of 1e5 diracs against 1e4 pixels, of two images of 1e5 pixels.

Against POT
POT, numpy, one threadPOT, torch, all the coresPOT, jax
timeerrortimeH100
m = 1e30.0489 s2.4e-60.0561 s0.00336 s
m = 1e40.603 s2.4e-80.306 s0.00749 s
m = 1e57.77 s2.3e-102.74 s0.317 s
m = 1e683.6 s2.7e-1224.8 s–
m = 3e6257 s2.9e-1367.2 s–

Seconds and median relative error on W2 ( against sdot ), 256 pairs of images of 1e4 affine pixels, in double. POT: each image replaced by m diracs ( the same number on both sides, made beforehand ), one column per pair; on a card, with its jax backend.

one threadall the coressdot
timetimeH100
sdot0.320 s0.0471 s0.0398 s

sdot on the same 256 pairs, exact: seconds.

The numbers come from bench/bench_1d.py, jobs one_d* of bench/page.py. The machines and the protocol are on the overview.

SDOT — H. Leclerc, Q. Mérigot, T. Gallouët · LMO / INRIA PARMA