-
Notifications
You must be signed in to change notification settings - Fork 26
[Feat] FSDP Fullgraph Overlap β whole-graph weight all-gather / compute overlap #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
cc8f966
36b8cdc
6ef1ea7
b5ac999
341a97a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -542,14 +542,93 @@ def _init_cache(self) -> str: | |
| self.local_magi_cache_path.mkdir(parents=True, exist_ok=True) | ||
| self.compiler_manager.initialize_cache(self.local_magi_cache_path) | ||
|
|
||
| def _apply_fsdp_fullgraph_overlap(self, graph: fx.GraphModule) -> None: | ||
| """Whole-graph FSDP all-gather / compute overlap (disable_graph_split path). | ||
|
|
||
| 1. Lower SimpleFSDP weight prim_redistribute -> explicit collectives and | ||
| optionally bucket them per compute-region. | ||
| 2. Install the profiling runtime estimator at ``config.estimate_op_runtime`` | ||
| (the analytical roofline is unusable for our sizing decisions). | ||
| 3. Install the latest-safe-launch reorder pass, REPLACING PyTorch's builtin | ||
| raise_comms/sink_waits, and enable reorder_for_compute_comm_overlap. | ||
| """ | ||
| if not self.compile_config.disable_graph_split: | ||
| raise ValueError("enable_fsdp_fullgraph_overlap requires disable_graph_split=True") | ||
|
|
||
| if self.compile_config.cudagraph_mode != CudaGraphMode.NONE: | ||
| raise ValueError("enable_fsdp_fullgraph_overlap requires cudagraph_mode=NONE") | ||
|
|
||
| from magi_compiler.passes.fsdp_overlap import FsdpOverlapReorder, lower_and_bucket_full_graph | ||
| 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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have assert |
||
| ) | ||
| bucket_size_bytes = int(self.compile_config.fsdp_fullgraph_bucket_size_mib) * 1024 * 1024 | ||
| n_buckets = lower_and_bucket_full_graph( | ||
| graph, | ||
| self.compile_config.fsdp_fullgraph_bucket_mode, | ||
| boundary_ops=boundary_ops, | ||
| bucket_size_bytes=bucket_size_bytes, | ||
| ) | ||
| magi_logger.info( | ||
| "FSDP fullgraph overlap: bucket_mode=%s bucket_size=%d MiB created %d buckets", | ||
| self.compile_config.fsdp_fullgraph_bucket_mode, | ||
| self.compile_config.fsdp_fullgraph_bucket_size_mib, | ||
| n_buckets, | ||
| ) | ||
|
|
||
| _mode = self.compile_config.fsdp_overlap_cost_mode | ||
| if _mode == "analytical": | ||
| self._fsdp_overlap_estimator = None | ||
| cost_fn = None # reorder pass defaults to Inductor's analytical estimate | ||
| else: | ||
| self._fsdp_overlap_estimator = ProfilingRuntimeEstimator() | ||
| self._fsdp_overlap_estimator._sync_across_ranks = _mode == "profile_sync" | ||
| cost_fn = self._fsdp_overlap_estimator | ||
|
|
||
| reorder = FsdpOverlapReorder( | ||
| slack_ns=self.compile_config.fsdp_overlap_slack_ns, | ||
| cost_fn=cost_fn, | ||
| comm_contention_factor=self.compile_config.fsdp_overlap_comm_contention_factor, | ||
| ) | ||
| self.inductor_compile_config["reorder_for_compute_comm_overlap"] = True | ||
| self.inductor_compile_config["reorder_for_compute_comm_overlap_passes"] = [reorder] | ||
|
|
||
| @observe_lifecycle("graph_split") | ||
| def _split_graph(self, graph: fx.GraphModule) -> tuple[fx.GraphModule, list[SplitItem]]: | ||
| # Step 1: resolve the splitting ops | ||
| fx_split_ops = self.compile_config.splitting_ops or [] | ||
| # Step 1: resolve the splitting ops. | ||
| # 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. | ||
|
Comment on lines
+601
to
+605
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try to simplify these comments |
||
| if self.compile_config.disable_graph_split: | ||
| if self.compile_config.cudagraph_mode == CudaGraphMode.PIECEWISE: | ||
| raise ValueError("disable_graph_split is incompatible with cudagraph_mode=PIECEWISE") | ||
| fx_split_ops = [] | ||
| magi_logger.info( | ||
| "disable_graph_split=True: skipping FX-level graph split; compiling the whole graph as one submod" | ||
| ) | ||
| else: | ||
| fx_split_ops = self.compile_config.splitting_ops or [] | ||
| resolved_ops: list[torch._ops.OpOverload] = resolve_defined_ops(fx_split_ops) | ||
| magi_logger.info(f"Setting up FX-level graph split with ops: {fx_split_ops=}") | ||
| magi_logger.info(f"Resolved splitting ops for FX-level graph split: {resolved_ops=}") | ||
|
|
||
| # 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. | ||
|
Comment on lines
+619
to
+628
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if self.compile_config.enable_fsdp_fullgraph_overlap: | ||
| self._apply_fsdp_fullgraph_overlap(graph) | ||
|
|
||
| # Step 2: split graph by ops, we split graph based on resolved_ops, which becomes the partitioned single graph. | ||
| subgraph_id = 0 | ||
| node_to_subgraph_id = {} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Copyright (c) 2026 SandAI. All Rights Reserved. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """Whole-graph FSDP all-gather / compute overlap (``enable_fsdp_fullgraph_overlap``). | ||
|
|
||
| The pipeline, all for the ``disable_graph_split=True`` path (there is no FX-level | ||
| graph split here despite the historical name): | ||
|
|
||
| 1. :mod:`.redistribute_lowering` -- lower SimpleFSDP weight ``prim_redistribute`` | ||
| into explicit ``all_gather`` + ``wait`` collectives. | ||
| 2. :mod:`.bucket_all_gather` -- coalesce the per-region weight gathers into one | ||
| ``all_gather_into_tensor_coalesced`` per (region, group, dtype), optionally | ||
| size-capped. :mod:`.lower_and_bucket` is the entry point wrapping steps 1-2. | ||
| 3. :mod:`.reorder` -- the scheduler-level ``FsdpOverlapReorder`` pass that hoists | ||
| each weight all-gather launch into upstream compute so the collective is hidden. | ||
|
|
||
| The per-op cost model that feeds the reorder its compute/comm timings lives in the | ||
| general :mod:`magi_compiler.profiling` package (``ProfilingRuntimeEstimator`` + | ||
| ``register_benchmark_inputs``), not here -- it is not FSDP-specific. | ||
| """ | ||
|
|
||
| from .bucket_all_gather import bucket_weight_all_gather_coalesced_per_region | ||
| from .lower_and_bucket import lower_and_bucket_full_graph | ||
| from .redistribute_lowering import lower_prim_redistribute_to_collectives | ||
| from .reorder import FsdpOverlapReorder | ||
|
|
||
| __all__ = [ | ||
| "bucket_weight_all_gather_coalesced_per_region", | ||
| "lower_prim_redistribute_to_collectives", | ||
| "lower_and_bucket_full_graph", | ||
| "FsdpOverlapReorder", | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create a
FSDPConfigclass to handle all these configs