Scatter SS parameters once per solve instead of per residual evaluation - #1214
Scatter SS parameters once per solve instead of per residual evaluation#1214vahid-ahmadi wants to merge 1 commit into
Conversation
`SS.inner_loop` called `client.scatter(p, broadcast=True)` on every invocation, so the Specifications object was re-serialized and re-broadcast to every Dask worker on each outer residual evaluation of a steady-state solve, even though `p` never changes. `TPI.run_TPI` already scatters once before its loop. - Add `SS.scatter_params(p, client)`, which strips the unpicklable schema attributes, scatters, and restores them (the block previously inlined in `inner_loop`). - `inner_loop` takes an optional `scattered_p`; when None it falls back to scattering locally, preserving the existing `(outer_loop_vars, p, client)` call signature. - `SS_solver` takes an optional `scattered_p` keyword and threads it through its iteration loop. - `SS_fsolve` accepts an optional eighth element in `args`; the seven-element form still works. - `run_SS` scatters once before each root solve (after any `p.SS_theta` mutation) and reuses the future for the final `SS_solver` call. - Add tests with a scatter-counting fake client showing the count no longer grows with the number of evaluations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1214 +/- ##
=======================================
Coverage 74.07% 74.07%
=======================================
Files 22 22
Lines 5920 5933 +13
=======================================
+ Hits 4385 4395 +10
- Misses 1535 1538 +3
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
An independent review found no wrong-answer path here, but did land a fair criticism of my evidence that I want on the record rather than discovered later. The tests in this PR never touch a real Dask Future. Closing that gap, run on this branch against a real
Also checked explicitly, since these were the plausible silent-failure modes:
Two non-blocking points I am happy to address if you want them in this PR:
One correction to the description: I wrote "Full |
Why
SS.inner_loopcallsclient.scatter(p, broadcast=True)inside itself, andinner_loopruns once per outer residual evaluation (SS_fsolve, the function handed toopt.root) and once per functional-iteration step (SS_solver). So the entireSpecificationsobject is re-serialised and re-broadcast to every worker dozens of times per steady-state solve, even thoughpnever changes across those calls.TPI.run_TPIalready does it correctly — scatter once before the loop, reuse the future.What changes after merging
One scatter per SS solve instead of one per residual evaluation. Pure overhead removal on any Dask-backed SS solve; the larger the
Specificationsobject and the more workers, the more it saves. No numerical change — same solves, same results.Change
scatter_params(p, client)— the strip-schema / scatter / restore block extracted frominner_loop. ReturnsNonewith no client.inner_loop,SS_solvertake an optionalscattered_p; existing call signatures still work.run_SSscatters once before each root solve and reuses the future.Two points for a reviewer
The
argstuple.SS_fsolvenow accepts an 8-elementargswith the 7-element form still supported. Backward compatible, but packing an optional element into a positional tuple isn't lovely —SS_fsolveis passed toopt.rootasargs, which limits the options. The alternative touching no signatures is caching the future insideinner_loopkeyed on the client. Happy to switch if you prefer that.Scatter placement. Called after any
p.SS_thetamutation, sincepensions.replacement_rate_valsreads it on the workers. In theDEV_FACTOR_LISTretry loop that means one scatter per retry, not literally one perrun_SS.Evidence
ScatterCountingClientfake client: withscattered_p, 3 evaluations → 1 scatter (count no longer grows with iterations); legacy path still scatters per call. 2 tests, ~9s, no real solve.Full
tests/test_SS.py: 44 passed, no failures.ruff format/ruff checkclean.Fixes the second half of #1211. Follows #1212.