Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7e4dc10
Adding support for FP8 training
romilshahtri Feb 21, 2024
e8cad2a
Linter changes
shahromil16 Feb 21, 2024
0514087
Converting all Linears to TE Linears except output Linear
shahromil16 Feb 29, 2024
ff8e8c8
Fix linter errors
shahromil16 Mar 1, 2024
298471f
Merge remote-tracking branch 'origin/main' into feature/fp8
shahromil16 Apr 12, 2024
9224b0e
Rebase from main and update FP8 changes
shahromil16 Apr 12, 2024
4563be2
Linter changes
shahromil16 Apr 12, 2024
937927a
Adding asserts for FP8
shahromil16 Apr 13, 2024
1594b9f
Asserts for FP8
shahromil16 Apr 13, 2024
740f2b1
Predefine all_gpus for TE
shahromil16 Apr 13, 2024
ccc7eef
Merge remote-tracking branch 'origin/main' into feature/fp8
shahromil16 Apr 17, 2024
3713f61
Remove if/else for fp8 checks
shahromil16 Apr 17, 2024
14e8278
Remove extra asserts
shahromil16 Apr 17, 2024
e572510
Removing unused deps
shahromil16 Apr 17, 2024
8350cb9
Update routine for converting NN layers to TE equivalents
shahromil16 Apr 17, 2024
a907b3c
Merge remote-tracking branch 'origin/main' into feature/fp8
shahromil16 Apr 24, 2024
4e582a0
Update FP8 flags and checks for layers
shahromil16 Apr 24, 2024
cdb0cf7
Linter checks
shahromil16 Apr 24, 2024
afb46cb
Add checks for autocast function
shahromil16 Apr 24, 2024
00c9e5b
Minor edit to model
shahromil16 Apr 24, 2024
40c7a6d
Adding default args as Params to SwiGLUTorch
shahromil16 Apr 24, 2024
8dbd1d8
Linter fixes
shahromil16 Apr 24, 2024
ec91746
Adding Torch Attention TE
shahromil16 Apr 30, 2024
afb7a66
Merge remote-tracking branch 'origin/main' into feature/fp8
shahromil16 May 6, 2024
29000f3
Fixing FP8+FSDP memory issues by removing FP8 from all activations un…
shahromil16 May 6, 2024
936cd9a
Merge remote-tracking branch 'origin/main' into feature/fp8
shahromil16 Jun 12, 2024
aca1b75
Updating deps and config
shahromil16 Jun 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion open_lm/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
terminate_sync_process,
)


LATEST_CHECKPOINT_NAME = "epoch_latest.pt"


Expand Down Expand Up @@ -439,6 +438,8 @@ def main(args):

random_seed(args.seed, args.rank)

all_gpus = dist.new_group(backend="nccl")

if args.distributed:
if args.fsdp:
transformer_layer_cls = None
Expand Down Expand Up @@ -498,12 +499,14 @@ def main(args):
random_seed(args.seed, rank=0)
model = FSDP(
model,
process_group=all_gpus,
auto_wrap_policy=transformer_auto_wrapper_policy,
device_id=device,
mixed_precision=mp_policy,
cpu_offload=CPUOffload(offload_params=args.fsdp_cpu_offload),
use_orig_params=args.fsdp_use_orig_params,
limit_all_gathers=args.fsdp_limit_all_gathers,
sync_module_states=True,
**fsdp_kwargs,
)

Expand Down Expand Up @@ -754,6 +757,7 @@ def main(args):
total_steps=total_steps,
args=args,
tb_writer=writer,
all_gpus=all_gpus,
)

if args.distributed:
Expand Down
144 changes: 105 additions & 39 deletions open_lm/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:

Copy link
Copy Markdown
Collaborator

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?

Copy link
Copy Markdown
Collaborator

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 m that is set to either te or nn

Copy link
Copy Markdown
Collaborator Author

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.

Copy link
Copy Markdown
Collaborator

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?

Copy link
Copy Markdown
Collaborator

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)

Copy link
Copy Markdown
Collaborator Author

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.

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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We dont need float cast. Removing that in next commit.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -202,9 +245,14 @@ def __init__(self, layer_id, args: Params):
elif args.ffn_type == "gelu":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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:
https://github.com/mlfoundations/open_clip/blob/73fa7f03a33da53653f61841eb6d69aef161e521/src/open_clip/utils.py#L65

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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,
Expand All @@ -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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just add te.LayerNorm as one of the args.norm_type?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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()

Expand All @@ -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(
Expand Down
46 changes: 38 additions & 8 deletions open_lm/norms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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 (
Expand All @@ -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):
Expand Down
11 changes: 11 additions & 0 deletions open_lm/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this used? if so can we have a more specific name?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also not seeing where --use-smp-flash-attention is used

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used for FP8. Just placeholder flags defaulted to None for Sagemaker Model Parallel.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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)

Expand Down
Loading