perf(efficient-did, had): chunk multiplier-bootstrap weight generation to bound peak memory#563
Conversation
…n to bound peak memory Wire the EfficientDiD and HeterogeneousAdoptionDiD multiplier bootstraps through the bootstrap_chunking.py helper (PR-B2 of the memory-scaling work), so the dense (n_bootstrap x n_units) multiplier-weight matrix -- the dominant allocation at large n_units (~40 GB at 5M units x 999 reps) -- is generated and consumed one draw-block at a time instead of materialized in full. - EfficientDiD: tile the per-(g,t) EIF perturbation loop over draw-blocks across the survey-PSU / cluster / unit weight paths; the overall/event-study/group aggregations and all stats read the small bootstrap_atts matrix unchanged. eif scaling stays keyed on unit_level_weights (a separate axis from the weight path) so a weights-only SurveyDesign keeps the survey-score perturbation. - HAD: accumulate weights @ influence into the small (B, n_horizons) perturbations one block at a time on both the survey (psu_block @ Psi_psu) and iid paths; Psi_psu aggregation, stratum centering, and the sup-t reduction are unchanged. The weight stream is bit-identical on both backends; end-to-end bootstrap SEs and sup-t critical values match the un-chunked path to within BLAS floating-point reassociation (~1 ULP). Adds chunk-invariance tests (EfficientDiD x4 paths incl. the weights-only decoupling; HAD survey + a direct iid-branch test). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Overall Assessment: ✅ Looks Good Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
…ng work Adds benchmarks/speed_review/bench_memory_scaling.py -- a subprocess-isolated peak-RSS sweep (ru_maxrss, median of repeats) for the bootstrap-chunking and within-transform memory wins (B1 #561 / B2 #563 / C #567) -- plus a "Memory scaling" section in docs/performance-plan.md with the honest before/after table. Measured pre-#561 (un-optimized) vs current main, 999 bootstrap reps: - CallawaySantAnna bootstrap: 500k 12.9->2.1 GB (-84%), 1M 13.5->3.0 GB (-78%) - EfficientDiD bootstrap 500k: 8.3->1.6 GB (-81%) - HeterogeneousAdoptionDiD cband 500k: 7.7->1.2 GB (-84%) - TWFE within-transform fit: 500k 1.0->0.93 GB (-8%), 1M 1.7->1.5 GB (-9%) Closes the 3-PR memory-scaling initiative. The baseline is re-measured against the pre-#561 tree (not a monkeypatch of current code, which would be contaminated by the same-line optimizations). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng work Adds benchmarks/speed_review/bench_memory_scaling.py -- a subprocess-isolated peak-RSS sweep (ru_maxrss, median of repeats) for the bootstrap-chunking and within-transform memory wins (B1 #561 / B2 #563 / C #567) -- plus a "Memory scaling" section in docs/performance-plan.md with the honest before/after table. Measured pre-#561 (un-optimized) vs current main, 999 bootstrap reps: - CallawaySantAnna bootstrap: 500k 12.9->2.1 GB (-84%), 1M 13.5->3.0 GB (-78%) - EfficientDiD bootstrap 500k: 8.3->1.6 GB (-81%) - HeterogeneousAdoptionDiD cband 500k: 7.7->1.2 GB (-84%) - TWFE within-transform fit: 500k 1.0->0.93 GB (-8%), 1M 1.7->1.5 GB (-9%) Closes the 3-PR memory-scaling initiative. The baseline is re-measured against the pre-#561 tree (not a monkeypatch of current code, which would be contaminated by the same-line optimizations). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
diff_diff/bootstrap_chunking.pyhelper (PR-B2 of the memory-scaling work), so the dense(n_bootstrap × n_units)multiplier-weight matrix — the dominant allocation at largen_units(~40 GB at 5M units × 999 reps) — is generated and consumed one draw-block at a time instead of materialized in full. These were the two remaining consumers of the same matrix that CallawaySantAnna chunking (perf(callaway-santanna): chunk multiplier-bootstrap weight generation to bound peak memory #561) already eliminated.(g,t)EIF perturbation loop over draw-blocks across the survey-PSU / cluster / unit weight paths; the overall/event-study/group aggregations and all stats read the smallbootstrap_attsmatrix unchanged. eif scaling stays keyed onunit_level_weights(a separate axis from the weight path) so a weights-onlySurveyDesignkeeps the survey-score perturbation. The per-(g,t)scaled EIF is a boundedO(n_units)temporary, not anO(n_gt × n_units)cache.weights @ influenceinto the small(B, n_horizons)perturbations one block at a time on both the survey (psu_block @ Psi_psu) and iid paths; thePsi_psuaggregation, stratum centering, and sup-t reduction (max-over-horizons then quantile) are unchanged.TODO.md).Methodology references
δ = ξ @ Ψwith analytical-SE normalization).docs/methodology/REGISTRY.mdEfficientDiD multiplier-bootstrap and HAD sup-t bootstrap sections. This is a numerical implementation change (draw-block tiling of weight generation/consumption), not a methodology change.Validation
tests/test_bootstrap_chunking.py— EfficientDiD chunk-invariance across 4 paths (unit / cluster / survey-PSU / weights-onlySurveyDesigndecoupling) and HAD (survey cband + a direct iid-branch test), each comparing tiny-chunk vs single-chunk toassert_allclose.Security / privacy
🤖 Generated with Claude Code