-
Notifications
You must be signed in to change notification settings - Fork 810
[PyTorch] Type the grouped wgrad from main_grad for distributed weights #3397
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
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 |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ | |
| GroupedLinear -> N; leader is ``weights[0]``) and no-op on plain tensors. | ||
| """ | ||
|
|
||
| from typing import Any, List, Protocol, runtime_checkable | ||
| from typing import Any, List, Protocol, Sequence, runtime_checkable | ||
|
|
||
| import torch | ||
|
|
||
|
|
@@ -19,6 +19,8 @@ | |
| "materialize_weight_for_forward", | ||
| "materialize_weight_for_backward", | ||
| "finalize_weight_grads", | ||
| "weight_grad_buffers", | ||
| "weight_grad_dtype", | ||
| ] | ||
|
|
||
|
|
||
|
|
@@ -50,7 +52,12 @@ def finalize_group_grads(self, wgrads: Any) -> Any: | |
| """ | ||
|
|
||
| def grad_buffer(self) -> torch.Tensor: | ||
| """The gradient accumulation buffer for this weight.""" | ||
| """Where the wgrad GEMM writes this weight's gradient. | ||
|
|
||
| The GEMM overwrites it and :meth:`finalize_group_grads` then reduces it, so it needs the | ||
| full unsharded weight shape and the dtype that reduction should use. Called on every | ||
| member of a group, unlike the group hooks above. | ||
| """ | ||
|
|
||
|
|
||
| def is_distributed_weight(weight: Any) -> bool: | ||
|
|
@@ -101,6 +108,47 @@ def materialize_weight_for_backward(weights: Any) -> List[Any]: | |
| return list(weights) | ||
|
|
||
|
|
||
| def weight_grad_buffers( | ||
| weights: Any, weight_shape: Sequence[int], compute_dtype: torch.dtype, device: torch.device | ||
| ) -> List[torch.Tensor]: | ||
| """Per-weight buffers for the wgrad GEMM to write into. | ||
|
|
||
| A distributed weight brings its own, which skips this allocation and carries ``main_grad``'s | ||
| dtype by construction; anything else gets fresh scratch in the compute dtype. | ||
| """ | ||
| if not isinstance(weights, (list, tuple)): | ||
|
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. this doesn't seem to support single weight? TE as a computing library supports single weight for all, so it's orthogonal to if a certain feature in Mcore doesn't consider single weight yet |
||
| weights = [weights] | ||
| if is_distributed_weight(weights[0]): | ||
| buffers = [w.grad_buffer() for w in weights] | ||
|
Comment on lines
+121
to
+122
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.
When a distributed weight group uses a protocol-bearing leader with ordinary tensor followers, Knowledge Base Used: PyTorch Distributed/Parallel Training Support |
||
| # Spot-check the leader: a shard-shaped buffer would let the GEMM write past the end. | ||
| if tuple(buffers[0].shape) != tuple(weight_shape): | ||
| raise RuntimeError( | ||
| f"grad_buffer() returned shape {tuple(buffers[0].shape)}; " | ||
| f"the wgrad GEMM needs {tuple(weight_shape)}." | ||
| ) | ||
| return buffers | ||
| packed = torch.empty((len(weights), *weight_shape), dtype=compute_dtype, device=device) | ||
| return list(packed) | ||
|
|
||
|
|
||
| def weight_grad_dtype(weights: Any, compute_dtype: torch.dtype) -> torch.dtype: | ||
|
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 can also get rid of this helper? |
||
| """Dtype for a wgrad buffer the caller allocates itself. | ||
|
|
||
| A distributed weight reduces its wgrad before accumulating, so the GEMM must already emit | ||
| ``main_grad``'s dtype -- otherwise the reduction rounds on every rank. Falls back to | ||
| ``compute_dtype`` for plain weights, and for an implementer whose ``main_grad`` the framework | ||
| has not attached yet. | ||
| """ | ||
| if not isinstance(weights, (list, tuple)): | ||
| weights = [weights] | ||
| leader = weights[0] | ||
| if is_distributed_weight(leader): | ||
| main_grad = getattr(leader, "main_grad", None) | ||
| if main_grad is not None: | ||
| return main_grad.dtype | ||
| return compute_dtype | ||
|
|
||
|
|
||
| def finalize_weight_grads(weights: Any, wgrads: List[Any]) -> List[Any]: | ||
| """Finalize a weight group's grad(s), mirroring :func:`materialize_weight_for_backward`. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,8 @@ | |
| materialize_weight_for_forward, | ||
| materialize_weight_for_backward, | ||
| finalize_weight_grads, | ||
| weight_grad_buffers, | ||
| weight_grad_dtype, | ||
| ) | ||
| from ...module.base import _2X_ACC_WGRAD | ||
| from ...quantization import Recipe | ||
|
|
@@ -573,7 +575,7 @@ def _compute_grad_params( | |
| shapes=[weight_shape] * num_groups, | ||
| quantizer=None, | ||
| device=device, | ||
| dtype=dtype, | ||
| dtype=weight_grad_dtype(weights, dtype), | ||
|
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. I dont understand how this will help, since we will never enter this else condition of allocating using torch.empty, if main_grad is attached to the parameter.
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. Actually we will, if accumulate_into_main_grad is False, but why are we attaching main_grad to parameter in that case in Megatron?
Member
Author
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. Hi @vthumbe1503 , for you first question why this line's change is required: we need to follow Details:
Member
Author
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.
for MLM:
|
||
| ) | ||
| wgrad_output = grouped_wgrad | ||
| w_list = [grouped_wgrad.rowwise_data.view(num_groups, *weight_shape)] | ||
|
|
@@ -586,13 +588,7 @@ def _compute_grad_params( | |
| w_list = [get_main_grad_from_param(w, op_label=op_label) for w in weights] | ||
| accumulate_into_main_grad = get_accumulate_flag_in_param(weights[0]) | ||
| else: | ||
| wgrad_packed = torch.empty( | ||
| num_groups, | ||
| *weight_shape, | ||
| dtype=dtype, | ||
| device=device, | ||
| ) | ||
| w_list = [wgrad_packed[i] for i in range(num_groups)] | ||
| w_list = weight_grad_buffers(weights, weight_shape, dtype, device) | ||
| wgrad_output = w_list | ||
|
|
||
| if ctx.weight_requires_grad: | ||
|
|
||
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.
can we get rid of this helper function? doesn't feel necessary