[Feat] FSDP Fullgraph Overlap — whole-graph weight all-gather / compute overlap#41
Open
wtr0504 wants to merge 5 commits into
Open
[Feat] FSDP Fullgraph Overlap — whole-graph weight all-gather / compute overlap#41wtr0504 wants to merge 5 commits into
wtr0504 wants to merge 5 commits into
Conversation
jiahy0825
reviewed
Jul 15, 2026
Comment on lines
+266
to
+291
| disable_graph_split: bool = Field( | ||
| False, | ||
| description=( | ||
| "Skip FX-level splitting at the custom subgraph-boundary ops (splitting_ops) and hand the " | ||
| "WHOLE graph to Inductor as a single piecewise submodule." | ||
| ), | ||
| ) | ||
| enable_fsdp_fullgraph_overlap: bool = Field( | ||
| False, | ||
| description=( | ||
| "Whole-graph FSDP weight all-gather / compute overlap (requires disable_graph_split=True, " | ||
| "cudagraph_mode=NONE). Before Inductor, lower SimpleFSDP weight prim_redistribute to explicit " | ||
| "collectives and optionally bucket them per compute-region (fsdp_fullgraph_bucket_mode). Then " | ||
| "install a scheduler-level 'latest-safe-launch' reorder pass (replaces raise_comms/sink_waits): " | ||
| "each weight all-gather launch is placed at the LATEST position whose downstream compute still " | ||
| "hides the collective (compute_window >= comm_runtime + slack), instead of PyTorch's earliest. " | ||
| "Per-snode compute/comm time comes from a profiling estimator installed at config.estimate_op_runtime " | ||
| ), | ||
| ) | ||
| fsdp_fullgraph_bucket_mode: str = Field( | ||
| "none", | ||
| description=( | ||
| "Bucketing strategy for enable_fsdp_fullgraph_overlap: 'none' (N individual all_gather + N waits) " | ||
| "or 'coalesced' (per region: one all_gather_into_tensor_coalesced -> ONE launch, N getitems, N " | ||
| "waits). Regions are " | ||
| "delimited by the model's subgraph-boundary ops (splitting_ops) so weights coalesce per compute " |
Collaborator
There was a problem hiding this comment.
Create a FSDPConfig class to handle all these configs
| from magi_compiler.profiling import ProfilingRuntimeEstimator | ||
|
|
||
| boundary_ops = resolve_defined_ops( | ||
| [] if self.compile_config.disable_graph_split else [] or self.compile_config.splitting_ops |
Collaborator
There was a problem hiding this comment.
We have assert self.compile_config.disable_graph_split=True in line 555
Comment on lines
+619
to
+628
| # Step 1.4: whole-graph FSDP overlap. Lower SimpleFSDP weight redistribute | ||
| # to explicit collectives and (optionally) bucket them per compute-region, | ||
| # then install the latest-safe-launch scheduler reorder pass + profiling | ||
| # runtime estimator. Regions are delimited by the model's real | ||
| # subgraph-boundary ops (the resolved splitting ops) even though | ||
| # disable_graph_split has emptied fx_split_ops -- the graph itself stays | ||
| # unsplit (Step 2 assigns every node one sid -> a single submod), so we | ||
| # pass the model's true boundary ops explicitly for region bucketing. | ||
| # The piecewise Steps 1.5/2.4/2.5 stay OFF (their config flags default | ||
| # False) so they never interfere with this path. |
Collaborator
There was a problem hiding this comment.
- Simplify the comments
- I can not find any Steps 1.5/2.4/2.5
Comment on lines
+601
to
+605
| # When disable_graph_split is set, we deliberately resolve to NO splitting | ||
| # ops so Step 2 assigns every node to a single subgraph_id -> the whole | ||
| # graph becomes one piecewise submod handed to Inductor as a unit (the | ||
| # boundary custom ops remain in the graph as opaque extern calls). The | ||
| # PIECEWISE cudagraph path assumes >=1 split; forbid the combination. |
Collaborator
There was a problem hiding this comment.
Try to simplify these comments
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.
🗂️ PR Category
📝 Description
Summary
This PR adds an end-to-end, compile-time FSDP weight all-gather / compute overlap feature for the single-fullgraph (non-split) compilation path. When
enable_fsdp_fullgraph_overlap=True(requiresdisable_graph_split=Trueandcudagraph_mode=NONE), MagiCompiler:prim_redistributenodes into explicit functionalall_gather_into_tensor+wait_tensorcollectives on the FX graph (passes/fsdp_overlap/redistribute_lowering.py).all_gather_into_tensor_coalescedper (compute-region, process-group, dtype), with an optional per-bucket size cap (passes/fsdp_overlap/bucket_all_gather.py, entry pointlower_and_bucket_full_graph).passes/fsdp_overlap/reorder.py, replaces Inductor's builtinraise_comms/sink_waits): a single back-to-front two-pointer sweep places each all-gather launch (wait stays put) at the latest position whose upstream compute still hides the collective (compute_window >= comm * contention_factor + slack).profiling/runtime_estimator.py): Inductor's analytical roofline is unusable for our nodes (0 for custom ops, ~60x low for deep fused pointwise, ~1500x high for matmul — seescripts/demo/research_estimate_op_runtime_findings.md), so we measure real kernel times instead.Cost model:
ProfilingRuntimeEstimatorA callable
snode -> nanosecondspassed directly to the reorder pass ascost_fn(deliberately not installed at the globalconfig.estimate_op_runtime, which Inductor's own scheduling/caching consults in ways that specialize dynamic shapes).(target op, input shapes/dtypes)— not the per-node name — so isomorphic ops across repeated layers share one measurement (O(#distinct kernels), not O(#nodes)).scheduler.benchmark_fused_nodes(codegens the same fused kernel production emits; verified 0.995–1.15x of the compiled-graph kernel). Skipped (analytical fallback) while the graph still has free symbols, since that path would specialize dynamic dims.size_hint(no guards added), under@torch._dynamo.disable+no_grad, inside a ShapeEnv sandbox (_shapeenv_sandbox+suppress_guards) so measurement never leaks anEq(sym, hint)specialization. Safe even on the dynamic base compile.__call__(per-rank compilation is not co-scheduled; issuing NCCL there desyncs ranks → watchdog hang). They are seeded with Inductor's static analytical estimate and, inprofile_syncmode, later overridden by a real rank-lockstep measurement (below).profiling/benchmark_inputs.py): ops whose replay needs value-consistent metadata (e.g. split sizes feeding an internal CPall_to_all) register a hook viaregister_benchmark_inputs(op_name, fn, has_internal_collective=...); MagiCompiler holds no model-specific op names.Multi-rank correctness
The reorder decides how far to hoist each gather from costs; all FSDP gathers run on one process group, so NCCL matches the Nth call positionally — ranks must issue them in identical relative order or they deadlock (observed and root-caused via flight recorder on 8-GPU gaga4). Rank-consistency requires both:
Shard(0)params no longer give trailing ranks extraconstant_pad_ndnodes.fsdp_overlap_cost_mode:profile_sync(default): the estimator'swarm_and_sync()re-measures every distinct op in rank-lockstep (barrier + fixed iteration counts, so ops with internal collectives issue the same number of NCCL calls on every rank), then MAX-reduces the tables over a dedicated gloo group — real measured costs that are identical on every rank.analytical: Inductor roofline (deterministic by construction, zero overhead, less accurate).profile: per-rank profiling, single-rank runs only.New config options (
CompileConfig)disable_graph_splitFalseenable_fsdp_fullgraph_overlapFalsedisable_graph_split=True,cudagraph_mode=NONE).fsdp_fullgraph_bucket_mode"none""none"(N individual gathers + waits) or"coalesced"(one coalesced gather per region/group/dtype).fsdp_fullgraph_bucket_size_mibfsdp_overlap_cost_mode"profile_sync"profile_sync|analytical|profile(see multi-rank section).fsdp_overlap_slack_ns5000.0fsdp_overlap_comm_contention_factor1.5Observability
repositioned M/N weight all-gather launch(es) (cost table: D distinct ops, measured=X reused=Y);rank-synchronized profiling done (N cost entries reconciled).cur -> target, dep floor,comm/acc_upstream/need, verdicthidden/COMPUTE-LIMITED) answering "why didn't this gather move earlier"; the full op->time table with grep-friendlyESTLINE|kind|label|per_call_us|calls|total_us|measuredrows diffable against an nsys trace (scripts/demo/compare_estimate_vs_nsys.py).Misc
utils/visualize/visualizer.py: graphviz label sanitization/truncation (backslash escapes, 16384-char label limit) and render failures downgraded to a warning — visualization can no longer abort compilation.Tests
tests/feature_tests/test_fsdp_overlap_lowering.py—prim_redistribute→ all_gather+wait lowering (incl. unconditional pad / uneven shard determinism).tests/feature_tests/test_fsdp_overlap_bucket.py— per-region coalesced bucketing, size caps, group/dtype partitioning.tests/feature_tests/test_fsdp_overlap_reorder.py— sweep placement, dep floors, order clamp, validation (viafsdp_overlap_helper/reorder_helper.py).tests/feature_tests/test_fsdp_overlap_e2e.py— end-to-end overlap on a real compile, including a world=2 spawn test exercisingprofile_sync/warm_and_syncrank-lockstep without deadlock.tests/feature_tests/test_profiling_estimator.py— table memoization,_static/_realize_arg, extern measurement accuracy vs independent CUDA-event timing, deepcopy safety;fsdp_overlap_helper/estimator_collective_helper.pycovers the collective seed →warm_and_syncmeasured-override path (distributed).tests/feature_tests/test_profiling_registry.py—register_benchmark_inputshook registry &has_internal_collectiveflagging.