-
Notifications
You must be signed in to change notification settings - Fork 430
Fix tile-wise fp8 FSDP parallel size 1 all-gather #1948
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -378,6 +378,11 @@ def __repr__(self): | |
|
|
||
| def fsdp_pre_all_gather(self, mesh): | ||
| if self._precomputed_w is not None: | ||
| if mesh.size() == 1: | ||
| # FSDP2 bypasses the collective when shard mesh size is 1 and only | ||
| # copies all_gather_inputs[0] into all_gather_outputs. Keep scale in | ||
| # metadata so post_all_gather can recover the full Float8Tensor. | ||
| return (self._precomputed_w,), (self._precomputed_scale,) | ||
| return (self._precomputed_w, self._precomputed_scale), None | ||
| assert self._precomputed_scale is not None | ||
| if self._tensor.shape[0] >= 128 and self._tensor.shape[0] % 128 == 64: | ||
|
|
@@ -396,6 +401,11 @@ def fsdp_pre_all_gather(self, mesh): | |
| float8_dtype=self._dtype, | ||
| ) | ||
|
|
||
| # FSDP2 bypasses the collective when shard mesh size is 1 and only | ||
| # copies all_gather_inputs[0] into all_gather_outputs. Keep scale in | ||
| # metadata so post_all_gather can recover the full Float8Tensor. | ||
| if mesh.size() == 1: | ||
| return (w_fp8_data,), (self._precomputed_scale,) | ||
| return (w_fp8_data, self._precomputed_scale), None | ||
|
|
||
| def fsdp_post_all_gather( | ||
|
|
@@ -406,7 +416,12 @@ def fsdp_post_all_gather( | |
| *, | ||
| out: Optional[torch.Tensor] = None, | ||
| ): | ||
| data, scale = all_gather_outputs | ||
| scale_from_metadata = len(all_gather_outputs) == 1 | ||
| if scale_from_metadata: | ||
| (data,) = all_gather_outputs | ||
| (scale,) = metadata | ||
| else: | ||
| data, scale = all_gather_outputs | ||
| dim0, dim1 = data.shape | ||
| assert dim1 == self._tensor.shape[1], ( | ||
| f"Expected data.shape[1] == self._tensor[1], got data.shape = {data.shape}, self._tensor.shape = {self._tensor.shape}" | ||
|
|
@@ -451,13 +466,21 @@ def fsdp_post_all_gather( | |
| else: | ||
| raise RuntimeError(f"out must be a Float8Tensor or DTensor(_local_tensor=Float8Tensor), but got {out}") | ||
| return | ||
| # FSDP frees tensors returned as unsharded inner_tensors after reshard. | ||
| # If scale came from metadata, it aliases persistent _precomputed_scale | ||
| # that the next pre_all_gather will reuse. The Python Tensor reference | ||
| # still exists, but FSDP frees by resizing the backing storage to 0. | ||
| # Returning (data, scale) here would let FSDP free that persistent | ||
| # storage after the first reshard, and the next quant cast may read | ||
| # freed CUDA memory. | ||
| inner_tensors = (data,) if scale_from_metadata else (data, scale) | ||
|
Comment on lines
466
to
+476
Contributor
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. Claude: Good catch — this comment accurately describes the hazard and the fix is correct. One subtle detail worth noting: the |
||
| return Float8Tensor( | ||
| data, | ||
| scale, | ||
| param_dtype, | ||
| scaling_granularity=ScalingGranularity.BLOCKWISE, # tilewise fp8: weight is blockwise quantized | ||
| group_size=128, | ||
| ), (data, scale) # afterwards will be freed | ||
| ), inner_tensors # afterwards will be freed | ||
|
|
||
|
|
||
| def tensor_to_per_tensor_fp8_scales( | ||
|
|
||
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.
Claude: Nit: The ep_size=8 case compares against the same
losses_refas ep_size=1 but with 12x higher relative tolerance (rtol=0.12vs0.01). At 12% tolerance the assertion becomes quite loose — it mostly checks that loss decreases, not that it matches a known-good trajectory.Consider capturing a dedicated
losses_reffrom a validated ep_size=8 run and tighteningrtolback down. That would catch regressions that a 12% window would miss (e.g., subtle numerical drift in expert routing under FP8).