Pure JAX implementation of ormqr for faster QR solves#2247
Pure JAX implementation of ormqr for faster QR solves#2247jpbrodrick89 wants to merge 7 commits into
Conversation
Memory benchmark result| Test Name | %Δ | Master (MB) | PR (MB) | Δ (MB) | Time PR (s) | Time Master (s) |
| -------------------------------------- | ------------ | ------------------ | ------------------ | ------------ | ------------------ | ------------------ |
test_objective_jac_w7x | -0.55 % | 4.046e+03 | 4.024e+03 | -22.36 | 32.66 | 29.03 |
test_proximal_jac_w7x_with_eq_update | -1.37 % | 6.623e+03 | 6.532e+03 | -90.54 | 153.99 | 153.09 |
test_proximal_freeb_jac | 0.41 % | 1.333e+04 | 1.338e+04 | 53.99 | 82.03 | 81.92 |
test_proximal_freeb_jac_blocked | 0.90 % | 7.620e+03 | 7.689e+03 | 68.26 | 69.68 | 68.38 |
test_proximal_freeb_jac_batched | -0.64 % | 7.649e+03 | 7.600e+03 | -48.97 | 67.96 | 68.28 |
test_proximal_jac_ripple | -0.60 % | 3.549e+03 | 3.528e+03 | -21.27 | 54.05 | 53.25 |
test_proximal_jac_ripple_bounce1d | -0.16 % | 3.755e+03 | 3.748e+03 | -6.18 | 67.31 | 69.02 |
test_eq_solve | 1.65 % | 1.926e+03 | 1.957e+03 | 31.71 | 52.66 | 58.29 |For the memory plots, go to the summary of |
| benchmark_name | dt(%) | dt(s) | t_new(s) | t_old(s) |
| -------------------------------------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- |
test_build_transform_fft_lowres | +0.30 +/- 4.11 | +2.48e-03 +/- 3.35e-02 | 8.16e-01 +/- 2.6e-02 | 8.14e-01 +/- 2.1e-02 |
test_equilibrium_init_medres | -0.07 +/- 2.82 | -4.81e-03 +/- 1.83e-01 | 6.47e+00 +/- 1.3e-01 | 6.48e+00 +/- 1.3e-01 |
test_equilibrium_init_highres | +0.54 +/- 2.11 | +3.90e-02 +/- 1.54e-01 | 7.31e+00 +/- 1.2e-01 | 7.27e+00 +/- 1.0e-01 |
test_objective_compile_dshape_current | +1.65 +/- 2.84 | +6.73e-02 +/- 1.16e-01 | 4.15e+00 +/- 6.8e-02 | 4.08e+00 +/- 9.4e-02 |
test_objective_compute_dshape_current | -6.06 +/- 17.85 | -4.44e-05 +/- 1.31e-04 | 6.88e-04 +/- 8.7e-05 | 7.33e-04 +/- 9.8e-05 |
test_objective_jac_dshape_current | +6.40 +/- 25.72 | +1.43e-03 +/- 5.75e-03 | 2.38e-02 +/- 4.7e-03 | 2.23e-02 +/- 3.3e-03 |
test_perturb_2 | +0.13 +/- 0.65 | +2.45e-02 +/- 1.24e-01 | 1.91e+01 +/- 7.0e-02 | 1.90e+01 +/- 1.0e-01 |
test_proximal_jac_atf_with_eq_update | -0.21 +/- 1.17 | -2.52e-02 +/- 1.38e-01 | 1.18e+01 +/- 7.4e-02 | 1.18e+01 +/- 1.2e-01 |
test_proximal_freeb_jac | +0.37 +/- 3.72 | +1.71e-02 +/- 1.73e-01 | 4.68e+00 +/- 8.3e-02 | 4.66e+00 +/- 1.5e-01 |
+test_solve_fixed_iter_compiled | -5.63 +/- 1.34 | -3.46e-01 +/- 8.22e-02 | 5.80e+00 +/- 7.2e-02 | 6.15e+00 +/- 3.9e-02 |
test_LinearConstraintProjection_build | +0.76 +/- 4.56 | +5.06e-02 +/- 3.03e-01 | 6.69e+00 +/- 1.8e-01 | 6.64e+00 +/- 2.5e-01 |
test_objective_compute_ripple_bounce1d | +0.29 +/- 1.97 | +8.54e-04 +/- 5.83e-03 | 2.97e-01 +/- 4.1e-03 | 2.96e-01 +/- 4.1e-03 |
test_objective_grad_ripple_bounce1d | +1.71 +/- 2.01 | +1.55e-02 +/- 1.83e-02 | 9.22e-01 +/- 1.6e-02 | 9.07e-01 +/- 9.6e-03 |
test_build_transform_fft_midres | +0.52 +/- 4.06 | +4.69e-03 +/- 3.67e-02 | 9.10e-01 +/- 1.3e-02 | 9.05e-01 +/- 3.4e-02 |
test_build_transform_fft_highres | +1.42 +/- 2.69 | +1.69e-02 +/- 3.20e-02 | 1.21e+00 +/- 2.3e-02 | 1.19e+00 +/- 2.2e-02 |
test_equilibrium_init_lowres | +1.33 +/- 3.15 | +8.81e-02 +/- 2.08e-01 | 6.70e+00 +/- 1.5e-01 | 6.61e+00 +/- 1.4e-01 |
test_objective_compile_atf | +2.00 +/- 3.79 | +1.26e-01 +/- 2.39e-01 | 6.43e+00 +/- 1.2e-01 | 6.31e+00 +/- 2.1e-01 |
test_objective_compute_atf | +11.81 +/- 15.89 | +2.51e-04 +/- 3.38e-04 | 2.38e-03 +/- 1.7e-04 | 2.13e-03 +/- 2.9e-04 |
test_objective_jac_atf | +5.08 +/- 2.44 | +8.10e-02 +/- 3.89e-02 | 1.68e+00 +/- 2.9e-02 | 1.60e+00 +/- 2.6e-02 |
test_perturb_1 | +1.79 +/- 1.34 | +2.89e-01 +/- 2.16e-01 | 1.65e+01 +/- 1.7e-01 | 1.62e+01 +/- 1.4e-01 |
test_proximal_jac_atf | +0.99 +/- 2.11 | +5.20e-02 +/- 1.10e-01 | 5.29e+00 +/- 8.7e-02 | 5.23e+00 +/- 6.8e-02 |
test_proximal_freeb_compute | +1.09 +/- 2.69 | +1.79e-03 +/- 4.44e-03 | 1.67e-01 +/- 3.7e-03 | 1.65e-01 +/- 2.5e-03 |
test_solve_fixed_iter | +3.12 +/- 2.02 | +7.54e-01 +/- 4.88e-01 | 2.49e+01 +/- 3.6e-01 | 2.42e+01 +/- 3.3e-01 |
test_objective_compute_ripple | +0.36 +/- 7.46 | +8.46e-04 +/- 1.78e-02 | 2.39e-01 +/- 1.6e-02 | 2.38e-01 +/- 7.4e-03 |
test_objective_grad_ripple | -0.89 +/- 3.13 | -7.94e-03 +/- 2.79e-02 | 8.82e-01 +/- 2.4e-02 | 8.90e-01 +/- 1.5e-02 |Github CI performance can be noisy. When evaluating the benchmarks, developers should take this into account. |
|
So I tried to get a handle on what was going on with tests, but I'm not very familiar with the codebase so might be a poor take.
|
That can be true. That test was prone to slight numerical changes that new jax versions introduce. It tends to be more unstable on CI, I am not sure why. But I think the problem here may be solved by changing the method that we solve the trust region problem to But otherwise, how much numerical difference do you think your method has compared to before? For other benchmarks, we can rerun the CI since it can be noisy due to GitHub runners. For example, this PR shouldn't have any effect on Memory/runtime/compile time is a hard one. Since these can also be problem dependent, we try to decide based on the most common problem types. |
|
Also, it might be worth to try jit compiling the new implementation, there seems to be multiple slicing operations that will trigger individual dispatches and compiling. We've previously seen that these individual costs can be overcome by jitting the outer function. For example, see #2153
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2247 +/- ##
==========================================
+ Coverage 94.30% 94.32% +0.01%
==========================================
Files 101 102 +1
Lines 28866 28912 +46
==========================================
+ Hits 27223 27270 +47
+ Misses 1643 1642 -1
🚀 New features to boost your workflow:
|
|
Ah, this is a good spot! I didn't know you were running your loop eagerly (I'm used to optimistix where all our loops are equinox while loops which jits the step function, any reason why don't jit your whole step function?). Looks a lot better with no jit, no memory issues. Would be interested to see how it performs on your production GPU as the laptop GPU may not be optimised enough to benefit from it (it relies on reasonable GPU kernel dispatch (we are launching a lot of kernels whereas cusolver ormqr is theoretically just one), lightning fast GEMM's (which NVIDIA GPU's have) and fairly quick TRSV's). If you just get 5% speedup like on your laptop GPU this is probably not worth it but I'mhoping it will be better than that. |
There are some objectives and routines that are very hard to make jit-friendly in DESC. Also, we want to keep our optimizers compatible with non-JAX functions. There is still some effort to make it possible, but it is not a priority right now. I see around 10% improvement in speed and a 30-40% reduction in the max memory of the QR-related region. I think this is good even on my small GPU. We usually use A100's on Princeton clusters and NERSC Perlmutter, so I think your own benchmarks will be reflected here. In general, computing the Jacobian takes significantly more memory than trust region operations, but this might be useful to minimize the total memory footprint when we compute the Jacobian column by column (then QR becomes the dominant memory bottleneck). Given that, I think we need to discuss/benchmark this further to make sure that it is worth the additional complexity. I will trigger CI tests with older JAX versions to make sure that the dependency range is still correct. |




Ported from jax-ml/jax#36575, this was found to beat cusolver by a factor of 2 for your matrix sizes, so compounded with ormqr outperforming orgqr (raw vs economic) you should see at least a 3x speedup in each QR
ormqr(notqr_multiplyas a whole as thegeqrfcall is not sped up) call and probably a lower memory usage too. Crucially, it does not require a jaxlib upgrade or rebuild.This employs a blocked householder multiplication tuned to an A100, your mileage may vary on other devices.
I tested this on my Mac CPU and observed speedups of 1.6-1.9x on each
qr_multiplycall.Let me know if you think the added (hopefully temporary) maintenance burden is worth the performance gain to you?
I wasn't sure where in your codebase to put this so feel free to move this wherever you see fit.