from functools import partial import math import inspect from typing import Optional import torch import torch.nn as nn from torch.distributed.algorithms._checkpoint.checkpoint_wrapper import ( checkpoint_wrapper, CheckpointImpl, apply_activation_checkpointing, ) from torch.nn import functional as F try: from flash_attn import flash_attn_func except ImportError: print("Failed to import flash_attn. You won't be able to use the GPT model.") flash_attn_func = None try: import xformers.ops as xops # from xformers.components.attention.core import scaled_dot_product_attention except ImportError: print("Failed to import xformers.") xops = None QK_LAYERNORM = False ActivationFunc = nn.GELU # ActivationFunc = nn.SiLU class RMSNorm(torch.nn.Module): def __init__(self, dim: int, eps: float = 1e-6): super().__init__() self.eps = eps self.weight = nn.Parameter(torch.ones(dim)) def _norm(self, x): return x / (torch.sqrt(x.pow(2).mean(-1, keepdim=True)) + self.eps) def forward(self, x): output = self._norm(x.float()).type_as(x) return output * self.weight class LayerNorm(nn.Module): """LayerNorm but with an optional bias. PyTorch doesn't support simply bias=False""" def __init__(self, ndim): super().__init__() self.weight = nn.Parameter(torch.ones(ndim)) def forward(self, input): # TODO: actually bias is fixed to None here, can we just use built in layernorm? return F.layer_norm(input, self.weight.shape, self.weight, None, 1e-5) NormFunc = LayerNorm # NormFunc = RMSNorm OptimFunc = torch.optim.AdamW USE_CUSTOM_ATTN = False def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor: """torch.repeat_interleave(x, dim=2, repeats=n_rep) https://github.com/facebookresearch/llama/blob/main/llama/model.py""" bs, slen, n_kv_heads, head_dim = x.shape if n_rep == 1: return x return ( x[:, :, :, None, :] .expand(bs, slen, n_kv_heads, n_rep, head_dim) .reshape(bs, slen, n_kv_heads * n_rep, head_dim) ) class CausalSelfAttention(nn.Module): def __init__(self, config): super().__init__() assert config.n_embd % config.n_head == 0 assert config.n_embd / config.n_head == config.d_head assert config.n_head % config.n_kv_head == 0 # main self.n_head = config.n_head self.n_kv_head = config.n_kv_head self.d_head = config.d_head self.n_embd = config.n_embd self.c_attn_k = nn.Linear(config.n_embd, config.n_kv_head * config.d_head, bias=config.bias) self.c_attn_v = nn.Linear(config.n_embd, config.n_kv_head * config.d_head, bias=config.bias) self.c_attn_q = nn.Linear(config.n_embd, config.n_head * config.d_head, bias=config.bias) if QK_LAYERNORM: self.q_ln = NormFunc(config.d_head) self.k_ln = NormFunc(config.d_head) # output projection self.c_proj = nn.Linear(config.n_embd, config.n_embd, bias=config.bias) # regularization self.attn_dropout = nn.Dropout(config.dropout) self.resid_dropout = nn.Dropout(config.dropout) self.dropout = config.dropout self.attention_type = config.attention_type assert self.attention_type in ("torch", "tao", "xformers") self.attention_sliding_window_size = config.attention_sliding_window_size if self.attention_sliding_window_size != -1 and self.attention_type != "tao": raise ValueError("attention_sliding_window_size variation is only enabled for tao attention") # custom attn mask if USE_CUSTOM_ATTN: if self.attention_type == "xformers": # TODO: this should be inf not 100? right now this OOMs anyways attn_mask = ( torch.ones(config.block_size, config.block_size, dtype=torch.float32).tril( diagonal=0 ) * 100 ) attn_mask[: config.t_text, : config.t_text] = 100 else: attn_mask = torch.ones(config.block_size, config.block_size, dtype=torch.bool).tril( diagonal=0 ) attn_mask[: config.t_text, : config.t_text] = True self.register_buffer("attn_mask", attn_mask, persistent=False) self.use_rotary_pos_emb = config.use_rotary_pos_emb if config.use_rotary_pos_emb: # pip install "git+https://github.com/Dao-AILab/flash-attention.git#subdirectory=csrc/rotary from flash_attn.layers.rotary import RotaryEmbedding self.rotary_pos_emb = RotaryEmbedding(config.d_head) def forward(self, x): B, T, C = x.size() # b_size, sequ_len, emb_dim (n_embd) dropout = self.dropout if self.training else 0.0 k = self.c_attn_k(x) v = self.c_attn_v(x) q = self.c_attn_q(x) k = k.view(B, T, self.n_kv_head, self.d_head) v = v.view(B, T, self.n_kv_head, self.d_head) q = q.view(B, T, self.n_head, self.d_head) if QK_LAYERNORM: # applying per head as per: 2309.14322 Figure E.8 q = self.q_ln(q) k = self.k_ln(k) if self.use_rotary_pos_emb: k = k.unsqueeze(2) v = v.unsqueeze(2) kv = torch.cat([k, v], dim=2) # this is gross, prob want to merge kv projections q, kv = self.rotary_pos_emb(q, kv) # kv: (batch, seqlen, 2, nheads, headdim) k = kv[:, :, 0, :, :] v = kv[:, :, 1, :, :] # repeat k/v heads if n_kv_heads < n_heads n_rep = self.n_head // self.n_kv_head if self.attention_type == "tao": # tao flash 2 needs (B, S, H, D) == (B, T, nh, hs) y = flash_attn_func( q, k, v, dropout_p=dropout, softmax_scale=None, causal=True, window_size=(self.attention_sliding_window_size, 0), ) elif self.attention_type == "xformers": k = repeat_kv(k, n_rep) v = repeat_kv(v, n_rep) # Input format ``[B, M, H, K]``; B is batch size, M is sequence length, H is number of heads, K is embeding size per head if USE_CUSTOM_ATTN: y = xops.memory_efficient_attention( q, k, v, attn_bias=self.attn_mask[None][None].repeat(q.shape[0], q.shape[2], 1, 1), ) else: y = xops.memory_efficient_attention(q, k, v, attn_bias=xops.LowerTriangularMask()) else: k = repeat_kv(k, n_rep) v = repeat_kv(v, n_rep) # causal self-attention; Self-attend: (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T) k = k.transpose(1, 2) # (B, nh, T, hs) v = v.transpose(1, 2) # (B, nh, T, hs) q = q.transpose(1, 2) # (B, nh, T, hs) attn_mask = None if not USE_CUSTOM_ATTN else self.attn_mask is_causal = False if USE_CUSTOM_ATTN else True with torch.backends.cuda.sdp_kernel( enable_flash=True, enable_math=False, enable_mem_efficient=False ): # TODO: for compile to work we have to remove this context manager y = torch.nn.functional.scaled_dot_product_attention( q, k, v, attn_mask=attn_mask, dropout_p=dropout, is_causal=is_causal ) y = y.transpose(1, 2) # re-assemble head outputs side by side y = y.contiguous().view(B, T, C) # output projection y = self.resid_dropout(self.c_proj(y)) return y class MLP(nn.Module): def __init__(self, config): super().__init__() embd_inner = 4 * config.n_embd if "silu" in str(ActivationFunc.__name__).lower(): multiple_of = 256 embd_inner = int(2 * embd_inner / 3) embd_inner = multiple_of * ((embd_inner + multiple_of - 1) // multiple_of) self.embd_inner = embd_inner self.c_fc = nn.Linear(config.n_embd, self.embd_inner, bias=config.bias) self.c_proj = nn.Linear(self.embd_inner, config.n_embd, bias=config.bias) self.dropout = nn.Dropout(config.dropout) self.activation = ActivationFunc() def forward(self, x): x = self.c_fc(x) x = self.activation(x) x = self.c_proj(x) x = self.dropout(x) return x class Block(nn.Module): def __init__(self, config): super().__init__() self.ln_1 = NormFunc(config.n_embd) self.attn = CausalSelfAttention(config) self.ln_2 = NormFunc(config.n_embd) self.mlp = MLP(config) def forward(self, x): x = x + self.attn(self.ln_1(x)) x = x + self.mlp(self.ln_2(x)) return x ### # helper methods (shared across models) ### def init_weights_simple(self, module): if isinstance(module, nn.Linear): torch.nn.init.normal_(module.weight, mean=0.0, std=0.02) if module.bias is not None: torch.nn.init.zeros_(module.bias) elif isinstance(module, nn.Embedding): torch.nn.init.normal_(module.weight, mean=0.0, std=0.02) def get_init_fn(input_dim: int, init_depth: Optional[int] = None): """LM layer initialization.""" std = 1 / math.sqrt(input_dim) if init_depth is not None: std = std / math.sqrt(2 * init_depth) return partial(torch.nn.init.trunc_normal_, mean=0.0, std=std, a=-3 * std, b=3 * std) def _needs_weight_decay(param_name, param, is_fsdp): if not is_fsdp: return param.dim() >= 2 # at this point it should be an FSDP module assert param_name.startswith("_fsdp_wrapped_module") patterns = ("ln_", "bias") for pattern in patterns: if pattern in param_name: return False return True def configure_optimizers( self, weight_decay, learning_rate, betas, device_type, use_fused=True, is_fsdp=False ): # start with all of the candidate parameters param_dict = {pn: p for pn, p in self.named_parameters()} # filter out those that do not require grad param_dict = {pn: p for pn, p in param_dict.items() if p.requires_grad} # create optim groups. Any parameters that is 2D will be weight decayed, otherwise no. # i.e. all weight tensors in matmuls + embeddings decay, all biases and layernorms don't. decay_params = [p for n, p in param_dict.items() if _needs_weight_decay(n, p, is_fsdp=is_fsdp)] nodecay_params = [p for n, p in param_dict.items() if not _needs_weight_decay(n, p, is_fsdp=is_fsdp)] optim_groups = [ {"params": decay_params, "weight_decay": weight_decay}, {"params": nodecay_params, "weight_decay": 0.0}, ] num_decay_params = sum(p.numel() for p in decay_params) num_nodecay_params = sum(p.numel() for p in nodecay_params) print(f"num decayed parameter tensors: {len(decay_params)}, with {num_decay_params:,} parameters") print( f"num non-decayed parameter tensors: {len(nodecay_params)}," f" with {num_nodecay_params:,} parameters" ) # Create AdamW optimizer and use the fused version if it is available fused_available = "fused" in inspect.signature(OptimFunc).parameters use_fused = fused_available and device_type == "cuda" and use_fused extra_args = dict(fused=True) if use_fused else dict() optimizer = OptimFunc( optim_groups, lr=learning_rate, betas=betas, eps=1e-07, **extra_args ) # eps just incase: https://github.com/pytorch/pytorch/issues/26218 print(f"using fused Optimizer: {use_fused}") return optimizer def estimate_mfu(self, fwdbwd_per_iter, dt): """estimate model flops utilization (MFU) in units of A100 bfloat16 peak FLOPS""" # first estimate the number of flops we do per iteration. # see PaLM paper Appendix B as ref: https://arxiv.org/abs/2204.02311 N = self.get_num_params() cfg = self.config L, H, Q, T = cfg.n_layer, cfg.n_head, cfg.n_embd // cfg.n_head, cfg.block_size flops_per_token = 6 * N + 12 * L * H * Q * T flops_per_fwdbwd = flops_per_token * T flops_per_iter = flops_per_fwdbwd * fwdbwd_per_iter # express our flops throughput as ratio of A100 bfloat16 peak flops flops_achieved = flops_per_iter * (1.0 / dt) # per second flops_promised = 312e12 # A100 GPU bfloat16 peak flops is 312 TFLOPS mfu = flops_achieved / flops_promised return mfu def estimate_mfu_no_model(n_params, n_layer, n_head, n_embd, block_size, fwdbwd_per_iter, dt): """estimate model flops utilization (MFU) in units of A100 bfloat16 peak FLOPS""" # first estimate the number of flops we do per iteration. # see PaLM paper Appendix B as ref: https://arxiv.org/abs/2204.02311 L, H, Q, T = n_layer, n_head, n_embd // n_head, block_size flops_per_token = 6 * n_params + 12 * L * H * Q * T flops_per_fwdbwd = flops_per_token * T flops_per_iter = flops_per_fwdbwd * fwdbwd_per_iter # express our flops throughput as ratio of A100 bfloat16 peak flops flops_achieved = flops_per_iter * (1.0 / dt) # per second flops_promised = 312e12 # A100 GPU bfloat16 peak flops is 312 TFLOPS mfu = flops_achieved / flops_promised return mfu non_reentrant_wrapper = partial( checkpoint_wrapper, checkpoint_impl=CheckpointImpl.NO_REENTRANT, ) def check_fn(submodule): return isinstance(submodule, Block) def apply_fsdp_checkpointing(model): """apply activation checkpointing to model returns None as model is updated directly """ print("applying fsdp activation checkpointing...") apply_activation_checkpointing(model, checkpoint_wrapper_fn=non_reentrant_wrapper, check_fn=check_fn)