Repository navigation
Avoid length-n allocations when evaluating the ProxGrad objective - #186
Conversation
The old test compared one FISTA fit with 10 length-n vectors. It did not catch one extra length-n allocation in each iteration under ISTA. The test now checks that one call of smooth_objective! or smooth_fg! allocates less than one length-n vector. It also checks that a FISTA fit and an ISTA fit each allocate less than 4 length-n vectors. The scratch space holds 3.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #186 +/- ##
==========================================
- Coverage 94.62% 94.55% -0.08%
==========================================
Files 20 21 +1
Lines 875 900 +25
==========================================
+ Hits 828 851 +23
- Misses 47 49 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@tlienart I have bumped the version to 0.10.4 in Project.toml. The PR is ready to merge. |
|
Thank you @matthew-liu-1 for your contribution and @tlienart for the prompt review. The quality of the MLJ ecosystem depends crucially on voluntary engagement such as yours. |
|
Okay, I never tagged a release - or rather, when I tried to I got an error that 0.10.4 was already tagged. @tlienart Do you merge onto master and tag a release at General? |
|
Yes, sorry if that's not how things should be done these days 🥴 |
|
No all good, I was just doing due diligence. |
Removes most length-
nallocations from theProxGradobjective. The fitted coefficients do not change.Xof size(100000, 200),FISTA(): 470 ms → 377 ms, and 184 MiB → 2.6 MiB.Note: I've generated this PR with Claude assisting me. I've thoroughly & manually validated the effort of this change.
Why
LassoRegressionfits withProxGradinside a larger pipeline, often with a highmax_iter.Where the allocations come from
smooth_fg!) already reusesscratch.n.smooth_objective(glr, X, y). For L2 loss, each call allocates:apply_X:X * θ[1:p], then.+ θ[end]LPLoss:y .- ŷlp:abs2.(v)smooth_fg!returns allocates one moreabs2.(r).nvectors per iteration.What changes
smooth_objective!(glr, X, y, scratch). It writesX*θintoscratch.n2.L2Loss, a specific method forms the residuals in place withget_residuals!.ProxGradusessmooth_objective!whenc == 0. The multinomial path (c > 0) does not change.sum(r .= abs2.(r)) / 2:sum(abs2, r)is neater, but it rounds differently fromsum(abs2.(r))in the last bit.X*θallocations go. The losses themselves still allocate.Length-
nvectors that one objective call allocates (L1 penalty, with intercept):Benchmark
Xof size(100000, 200),FISTA().LassoRegression(0.1):ElasticNetRegression(0.1, 0.1; scale_penalty_with_samples=false):I ran this script once on
dev(1071e68) and once on this branch (f044a36):versioninfo(), BLAS and package versionsChecks
test/fit/ols-ridge-lasso-elnet.jlchecks that:smooth_objective!gives the same value assmooth_objectivesmooth_objective!orsmooth_fg!allocates no length-nvectorFISTA()orISTA()fit allocates less than 4 length-nvectors (the scratch space holds 3)