-
Notifications
You must be signed in to change notification settings - Fork 79
[WIP] Adding support for FP8 training #218
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 2 commits
7e4dc10
e8cad2a
0514087
ff8e8c8
298471f
9224b0e
4563be2
937927a
1594b9f
740f2b1
ccc7eef
3713f61
14e8278
e572510
8350cb9
a907b3c
4e582a0
cdb0cf7
afb46cb
00c9e5b
40c7a6d
8dbd1d8
ec91746
afb7a66
29000f3
936cd9a
aca1b75
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 |
|---|---|---|
|
|
@@ -35,6 +35,18 @@ | |
| except ImportError: | ||
| MambaLMHeadModel = None | ||
|
|
||
| # Adding flag if using TE FP8 | ||
| using_te = False | ||
| try: | ||
| import transformer_engine.pytorch as te | ||
| from transformer_engine.common import recipe | ||
|
|
||
| fp8_format = recipe.Format.HYBRID | ||
| fp8_recipe = recipe.DelayedScaling(fp8_format=fp8_format, amax_history_len=32, amax_compute_algo="max") | ||
| using_te = True | ||
| except ImportError as ie: | ||
| using_te = False | ||
|
|
||
| # from openclip | ||
| _MODEL_CONFIG_PATHS = [Path(__file__).parent / f"model_configs/"] | ||
| _MODEL_CONFIGS = {} # directory (model_name: config) of model architecture configs | ||
|
|
@@ -117,41 +129,72 @@ def __init__(self, layer_id, args: Params): | |
| super().__init__() | ||
| self.n_heads = args.n_heads | ||
| self.head_dim = args.dim // args.n_heads | ||
| self.in_proj = nn.Linear(args.dim, 3 * args.n_heads * self.head_dim, bias=False) | ||
| self.out_proj = nn.Linear(args.n_heads * self.head_dim, args.dim, bias=False) | ||
| if using_te: | ||
| self.in_proj = te.Linear(args.dim, 3 * args.n_heads * self.head_dim, bias=False, device="cuda") | ||
| self.out_proj = te.Linear(args.n_heads * self.head_dim, args.dim, bias=False, device="cuda") | ||
| else: | ||
| self.in_proj = nn.Linear(args.dim, 3 * args.n_heads * self.head_dim, bias=False) | ||
| self.out_proj = nn.Linear(args.n_heads * self.head_dim, args.dim, bias=False) | ||
| self.pos_embed = get_pos_embed(args) | ||
| self.attn_fn = args.attn_func | ||
| self.apply_qk_norm = args.apply_qk_norm | ||
|
|
||
| # initialize norm layers for queries and keys if needed | ||
| self.q_norm = ( | ||
| args.norm_type( | ||
| args.n_heads * self.head_dim, | ||
| eps=args.norm_eps, | ||
| if using_te: | ||
| # initialize norm layers for queries and keys if needed | ||
| self.q_norm = ( | ||
| te.LayerNorm( | ||
| args.n_heads * self.head_dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| if self.apply_qk_norm | ||
| else nn.Identity() | ||
| ) | ||
| if self.apply_qk_norm | ||
| else nn.Identity() | ||
| ) | ||
| self.k_norm = ( | ||
| args.norm_type( | ||
| args.n_heads * self.head_dim, | ||
| eps=args.norm_eps, | ||
| self.k_norm = ( | ||
| te.LayerNorm( | ||
| args.n_heads * self.head_dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| if self.apply_qk_norm | ||
| else nn.Identity() | ||
| ) | ||
| else: | ||
| # initialize norm layers for queries and keys if needed | ||
| self.q_norm = ( | ||
| args.norm_type( | ||
| args.n_heads * self.head_dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| if self.apply_qk_norm | ||
| else nn.Identity() | ||
| ) | ||
| self.k_norm = ( | ||
| args.norm_type( | ||
| args.n_heads * self.head_dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| if self.apply_qk_norm | ||
| else nn.Identity() | ||
| ) | ||
| if self.apply_qk_norm | ||
| else nn.Identity() | ||
| ) | ||
|
|
||
| self.layer_id = layer_id | ||
| self.dim = args.dim | ||
| self.reset_parameters() | ||
|
|
||
| def reset_parameters(self): | ||
| # initialize weights by trunc_normal(1/sqrt(fan_in)) | ||
| std = 1.0 / math.sqrt(self.dim) | ||
| torch.nn.init.trunc_normal_(self.in_proj.weight, std=std, a=-3 * std, b=3 * std) | ||
| # scale init by depth as in https://arxiv.org/abs/1908.11365 -- worked slightly better. | ||
| std = std / math.sqrt(2 * (self.layer_id + 1)) | ||
| torch.nn.init.trunc_normal_(self.out_proj.weight, std=std, a=-3 * std, b=3 * std) | ||
| if using_te: | ||
| # initialize weights by trunc_normal(1/sqrt(fan_in)) | ||
| std = 1.0 / math.sqrt(self.dim) | ||
| torch.nn.init.trunc_normal_(self.in_proj.weight_tensor.float(), std=std, a=-3 * std, b=3 * std) | ||
| # scale init by depth as in https://arxiv.org/abs/1908.11365 -- worked slightly better. | ||
| std = std / math.sqrt(2 * (self.layer_id + 1)) | ||
| torch.nn.init.trunc_normal_(self.out_proj.weight_tensor.float(), std=std, a=-3 * std, b=3 * std) | ||
|
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. why do we need to cast to float? does a float cast happen in place?
Collaborator
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. We dont need float cast. Removing that in next commit.
Collaborator
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. Removed completely as we are recursively changing NN.Linear to TE.Linear |
||
| else: | ||
| # initialize weights by trunc_normal(1/sqrt(fan_in)) | ||
| std = 1.0 / math.sqrt(self.dim) | ||
| torch.nn.init.trunc_normal_(self.in_proj.weight, std=std, a=-3 * std, b=3 * std) | ||
| # scale init by depth as in https://arxiv.org/abs/1908.11365 -- worked slightly better. | ||
| std = std / math.sqrt(2 * (self.layer_id + 1)) | ||
| torch.nn.init.trunc_normal_(self.out_proj.weight, std=std, a=-3 * std, b=3 * std) | ||
|
|
||
| def forward(self, x: torch.Tensor, is_causal=True, past_key_value=None, use_cache=False): | ||
| batchsize, q_len, _ = x.shape | ||
|
|
@@ -202,9 +245,14 @@ def __init__(self, layer_id, args: Params): | |
| elif args.ffn_type == "gelu": | ||
|
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. Could we also support fp8 for swiglu above? We can make a copy of the Swiglu class in this file. Here's the source for Swiglu https://github.com/facebookresearch/xformers/blob/7f8c290183344343771f4e1d945a8ce10a9500ff/xformers/ops/swiglu_op.py#L430
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. @rams16592 seems like the recursive replace linear patten should take care of this automatically. a function like this seems like it would be great and we can exclude certain linears that need to be higher precision for stability. this function has an include field instead of exclude, but hopefully that's easy to flip:
Collaborator
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. Applied this change in the latest commit. Excluding the last output Linear layer from the conversion to TE Linear as its running into errors. |
||
| # Follows mosaic mpt7b, but without a bias. | ||
| self.hidden_dim = args.dim * 4 | ||
| self._ff_w1 = nn.Linear(args.dim, self.hidden_dim, bias=False) | ||
| self._ff_w2 = nn.Linear(self.hidden_dim, args.dim, bias=False) | ||
| self.feed_forward = nn.Sequential(self._ff_w1, nn.GELU(approximate="none"), self._ff_w2) | ||
| if using_te: | ||
| self._ff_w1 = te.Linear(args.dim, self.hidden_dim, bias=False, device="cuda") | ||
| self._ff_w2 = te.Linear(self.hidden_dim, self.dim, bias=False, device="cuda") | ||
| self.feed_forward = nn.Sequential(self._ff_w1, nn.GELU(approximate="none"), self._ff_w2) | ||
| else: | ||
| self._ff_w1 = nn.Linear(args.dim, self.hidden_dim, bias=False) | ||
| self._ff_w2 = nn.Linear(self.hidden_dim, args.dim, bias=False) | ||
| self.feed_forward = nn.Sequential(self._ff_w1, nn.GELU(approximate="none"), self._ff_w2) | ||
| elif args.ffn_type == "moe": | ||
| moe_args = MoEArgs( | ||
| hidden_size=args.dim, | ||
|
|
@@ -222,14 +270,24 @@ def __init__(self, layer_id, args: Params): | |
| self.feed_forward = MoE(moe_args) | ||
|
|
||
| self.layer_id = layer_id | ||
| self.attention_norm = args.norm_type( | ||
| args.dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| self.ffn_norm = args.norm_type( | ||
| args.dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| if using_te: | ||
| self.attention_norm = te.LayerNorm( | ||
| args.dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| self.ffn_norm = te.LayerNorm( | ||
| args.dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| else: | ||
| self.attention_norm = args.norm_type( | ||
|
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. can we just add te.LayerNorm as one of the
Collaborator
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. Added this in the latest commit based on presence of TE, TE.LayerNorm or NN.LayerNorm will be considered. |
||
| args.dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| self.ffn_norm = args.norm_type( | ||
| args.dim, | ||
| eps=args.norm_eps, | ||
| ) | ||
| self.attention.seq_len = args.seq_len | ||
| self.reset_parameters() | ||
|
|
||
|
|
@@ -243,12 +301,20 @@ def reset_parameters(self): | |
| std = std / math.sqrt(2 * (self.layer_id + 1)) | ||
| torch.nn.init.trunc_normal_(self.feed_forward.w3.weight, std=std, a=-3 * std, b=3 * std) | ||
| elif self._ffn_type == "gelu": | ||
| std = 1.0 / math.sqrt(self.dim) | ||
| torch.nn.init.trunc_normal_(self._ff_w1.weight, std=std, a=-3 * std, b=3 * std) | ||
| if using_te: | ||
| std = 1.0 / math.sqrt(self.dim) | ||
| torch.nn.init.trunc_normal_(self._ff_w1.weight_tensor.float(), std=std, a=-3 * std, b=3 * std) | ||
|
|
||
| std = 1.0 / math.sqrt(self.hidden_dim) | ||
| std = std / math.sqrt(2 * (self._layer_id + 1)) | ||
| torch.nn.init.trunc_normal_(self._ff_w2.weight, std=std, a=-3 * std, b=3 * std) | ||
| std = 1.0 / math.sqrt(self.hidden_dim) | ||
| std = std / math.sqrt(2 * (self._layer_id + 1)) | ||
| torch.nn.init.trunc_normal_(self._ff_w2.weight_tensor.float(), std=std, a=-3 * std, b=3 * std) | ||
| else: | ||
| std = 1.0 / math.sqrt(self.dim) | ||
| torch.nn.init.trunc_normal_(self._ff_w1.weight, std=std, a=-3 * std, b=3 * std) | ||
|
|
||
| std = 1.0 / math.sqrt(self.hidden_dim) | ||
| std = std / math.sqrt(2 * (self._layer_id + 1)) | ||
| torch.nn.init.trunc_normal_(self._ff_w2.weight, std=std, a=-3 * std, b=3 * std) | ||
|
|
||
| def forward(self, x, past_key_value=None, use_cache=False): | ||
| h, past_key_value = self.attention( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,18 @@ | |
| import torch.nn.functional as F | ||
| from torch.nn.parameter import Parameter | ||
|
|
||
| # Adding flag if using TE FP8 | ||
| using_te = False | ||
| try: | ||
| import transformer_engine.pytorch as te | ||
| from transformer_engine.common import recipe | ||
|
|
||
| fp8_format = recipe.Format.HYBRID | ||
| fp8_recipe = recipe.DelayedScaling(fp8_format=fp8_format, amax_history_len=32, amax_compute_algo="max") | ||
| using_te = True | ||
| except ImportError as ie: | ||
| using_te = False | ||
|
|
||
|
|
||
| class LayerNorm(nn.Module): | ||
| # NOTE: taken from official pytorch implementation and modified | ||
|
|
@@ -55,7 +67,16 @@ def reset_parameters(self) -> None: | |
| self.bias.zero_() | ||
|
|
||
| def forward(self, input: Tensor) -> Tensor: | ||
| return F.layer_norm(input, self.normalized_shape, self.weight, self.bias, self.eps) | ||
| if using_te: | ||
|
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. @achalddave do u think we should have a seperate class for TeLayerNorm? or do u prefer combining it with existing layer norm
Collaborator
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. We can have a separate class for TeLayerNorm. |
||
| layer_norm_module = te.LayerNorm( | ||
| self.normalized_shape, eps=self.eps, device="cuda", params_dtype=input.dtype | ||
| ) | ||
| output_tensor = layer_norm_module(input) | ||
| if self.weight is not None and self.bias is not None: | ||
| output_tensor = output_tensor * self.weight + self.bias | ||
| return output_tensor | ||
| else: | ||
| return F.layer_norm(input, self.normalized_shape, self.weight, self.bias, self.eps) | ||
|
|
||
| def extra_repr(self) -> str: | ||
| return ( | ||
|
|
@@ -77,13 +98,22 @@ def forward(self, x): | |
| downcast_weight = _cast_if_autocast_enabled(self.weight) if self.weight is not None else self.weight | ||
| downcast_bias = _cast_if_autocast_enabled(self.bias) if self.bias is not None else self.bias | ||
| with torch.autocast(enabled=False, device_type=module_device.type): | ||
| return F.layer_norm( | ||
| downcast_x, | ||
| self.normalized_shape, | ||
| downcast_weight, | ||
| downcast_bias, | ||
| self.eps, | ||
| ) | ||
| if using_te: | ||
| layer_norm_module = te.LayerNorm( | ||
| self.normalized_shape, eps=self.eps, device="cuda", params_dtype=downcast_x.dtype | ||
| ) | ||
| output_tensor = layer_norm_module(downcast_x) | ||
| if downcast_weight is not None and downcast_bias is not None: | ||
| output_tensor = output_tensor * downcast_weight + downcast_bias | ||
| return output_tensor | ||
| else: | ||
| return F.layer_norm( | ||
| downcast_x, | ||
| self.normalized_shape, | ||
| downcast_weight, | ||
| downcast_bias, | ||
| self.eps, | ||
| ) | ||
|
|
||
|
|
||
| def _cast_if_autocast_enabled(tensor): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -742,6 +742,17 @@ def parse_args(args): | |
| action="store_true", | ||
| help="If set, allow model to do multiple data passes over our dataset, in order to reach the desired number of tokens.", | ||
| ) | ||
| parser.add_argument( | ||
| "--use-smp-flash-attention", | ||
| type=int, | ||
| default=None, | ||
| help="Using SMP Flash Attention.", | ||
| ) | ||
| parser.add_argument( | ||
| "--sharding-strategy", | ||
|
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. is this used? if so can we have a more specific name?
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. also not seeing where
Collaborator
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. This is not used for FP8. Just placeholder flags defaulted to
Collaborator
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. Removed this to avoid confusion |
||
| default=None, | ||
| help="Sharding Strategy", | ||
| ) | ||
|
|
||
| add_model_args(parser) | ||
|
|
||
|
|
||
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.
instead of if/else's can we have a single helper function that recursively searches the model and replaces the linears?
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.
another option is having a linear/layernorm module
mthat is set to eitherteornnThere 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.
Ideally, yes. But that would replace the Linear layer found here which breaks the training. So until a fix is not found, need to isolate that particular layer.
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't we just not recurse for special cases?
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.
missed this earlier, but yeah this might also address my swiglu comment below (since it would recurse and replace the linear within the swiglu)
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.
Completed in the latest commit.