[graph_trainer] FX memory/runtime/transfer estimators + naive outer ILP for joint AC + offload#3881
Draft
nnurlan008 wants to merge 1 commit into
Draft
[graph_trainer] FX memory/runtime/transfer estimators + naive outer ILP for joint AC + offload#3881nnurlan008 wants to merge 1 commit into
nnurlan008 wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[graph_trainer] FX memory / runtime / transfer estimators + naive outer ILP for joint AC + offloading
Summary
Milestone 1 of the joint activation-checkpointing + offloading project. This PR lands
(1) the three FX-graph estimators the solver's cost model needs, and (2) a first
outer ILP that allocates a per-block keep / recompute / offload budget under a
peak-memory constraint, extending torch's two-level SAC MILP (
sac_ilp.py) withactivation offloading (D2H/H2D copy-engine modeling).
All estimators operate on the joint fwd+loss+bwd FX graph produced by
minimal_fx_tracer.The outer ILP currently computes and logs the per-block plan; tagging /
materialization is the next PR (see Limitations).
Files
memory_estimator.pyruntime_estimator.pycost-model(roofline),benchmark(per-op CUDA timing),interpreter(real graph execution)transfertime_estimator.pynaive_autoAC_outer.pynew_autoAC_full_by_hand.py)The estimators
untyped_storageliveness (birth/death), categorizes each storage (param / grad / activation / temporary /
optimizer-state), reports peak + the schedule point + per-category breakdown, and exposes
live_bytes[t]and per-tensor[last_fwd_use, first_bwd_use)gaps for the solver.max(compute, HBM)by default, withbenchmark / interpreter alternatives.
(
get_transfer_bw, benchmarked once per device, pinned host memory).The outer ILP (
naive_autoAC_outer.py)Per transformer block
b, continuous fractionsr_b(recompute),o_b(offload); keep isthe residual
k_b = 1 - r_b - o_b. Minimize added runtime under the peak budget:single point:
m[b] = fixed + act_b + sum_{k<b} (1-r_k-o_k)*act_k <= eff_budget.fixed = peak - sum(act)is the global non-freeable baseline;act_bis unconditional(block
bis re-materialized at its own backward), so the last layer's discard yields nopeak benefit and is correctly kept. Reduces to the single boundary point at full-keep.
engine. For every block
j, the offload of blocksk>=jmust fit the time still availableafter
j:sum_{k>=j} o_k*act_k <= bw*window[j]. D2H is hard (a slow evict leaves thetensor resident at the peak -> memory, not runtime); H2D is soft with a stall variable
(a late reload only delays backward -> runtime). This one family is the exact single-machine
feasibility condition and subsumes both per-block windows and global bandwidth.
sum r_b*fwd_rt[b] + h2d_stall + keep_eps*(freed). Offload that fits itswindow is free; keep is preferred when the budget allows.
This mirrors
sac_ilp.py's per-positionm_i+ convex structure, adding the offload optionand its non-monotonic, timing-sensitive profile.
Results
Estimator validation (llama3 1B, via
measure_traced)41.277 GB== measured liveness peak41.277 GB(ratio 1.0000);real allocator peak
43.78 GB(+6%, workspace/fragmentation); activation-at-peakstatic 21.86 vs measured 20.75 GB.
233.9 ms (-14%), total 304 vs 344 ms (-12%) -- consistent underestimate, order preserved.
activation lowered the measured peak by 10.8 GiB and added +58 ms (~0% overlap at that op
count) -- see Limitations.
Outer ILP: model x budget x runtime mode (GiB)
Single H100,
AUTOAC_MODE=autoac_byhand,b4/s2048. Dense models only (deepseek MoE hasdynamic shapes; out of scope). Runtime cost model: cost-model (roofline) and benchmark
(per-op CUDA timing). (The
interpretermode is omitted here -- its per-node times includePython/dispatch overhead that inflates the overlap windows; use cost-model or benchmark for
the solver.)
Memory accounting. Optimizer state (
opt) is resident the whole step and is not partof the keep/recompute/offload decision, so it's subtracted up front:
eff_budget = budget - opt.The ILP constrains
modeled peak = fixed + kept-activation <= eff_budget. The real GPU peak= modeled peak + opt = the budget (last column).
opt = 9.21 GiB(llama3_1b),12.82 GiB(qwen3_1.7b);fixed(params+grads+temp+non-block acts)= 7.67/10.06 GiB.Findings:
the copy-engine windows and the recompute/offload split agree.
peak/budget (memory) constraint; only the freed split (recompute vs offload) depends on
runtime.
(qwen3) of activation can be evicted within the forward window, so the remainder recomputes.
Modeled peak lands exactly on
eff_budgetin every case (per-position constraint is honest).How to run
Knobs:
AUTOAC_BUDGET_GIB(target peak),AUTOAC_RT_MODE=operator-level-{cost-model,benchmark,interpreter}.Known limitations / follow-ups
None; wiring the fractions toper-tensor
MUST_SAVE/RECOMPUTE/CPU_OFFLOADtags + a real-peak validate-and-tighten loop isthe next PR. Nothing is applied to the graph yet.
act_b(a lower bound); should use thefull re-materialized footprint (sac_ilp's ACM/IA split).
arbitrary tie-break (only totals are pinned); a symmetry-break / convex per-block curve would
make it deterministic and handle heterogeneous (MoE) models.
current
apply_cpu_offload_passachieves ~0% overlap at high op counts (prefetch depth 1);the pass needs improvement before offload-heavy plans realize their modeled benefit.
naive_autoAC_outer.py: unused params (mem_est,time_fwd/time_bwd,t_fwd_total/t_bwd_total), deadd2h_stall,cpu_budget_bytesnaming(GiB not bytes).
Per-layer allocation (keep / recompute / offload fractions)
Full per-block plan for every run (cost-model and benchmark runtime modes).
k/r/oper layer. Note: for identical layers the per-block distribution is atie-break (only column totals are pinned); read the totals and the structural
patterns (keep = budget headroom; last layer kept). The
peakin each totals rowis the modeled peak (fixed + kept); the real GPU peak = modeled peak + opt =
the budget (opt = 9.21 GiB llama3_1b, 12.82 GiB qwen3_1.7b).
llama3_1b / 20 GiB
llama3_1b / 24 GiB
qwen3_1.7b / 30 GiB