import datetime from functools import partial import inspect import importlib import math import os 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 from torch.nn.attention.flex_attention import flex_attention from einops import rearrange from .rotary import apply_rotary_emb_func, apply_rotary_emb_kv_ flex_attention = torch.compile(flex_attention) def is_ddp(): return int(os.environ.get("RANK", -1)) != -1 def is_master(): if is_ddp(): return int(os.environ["RANK"]) == 0 return True def print_with_time(content): """Print the content with the current time.""" print(f"[{datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')}]: {content}") def print_with_time_master(content): if is_master(): print_with_time(content) FA_V_MAJOR = None try: from flash_attn_interface import ( flash_attn_varlen_func as _flash_attn_varlen_func, flash_attn_func as _flash_attn_func, ) FA_V_MAJOR = 3 # Wrap with torch.compiler.disable to prevent compilation flash_attn_varlen_func = _flash_attn_varlen_func flash_attn_func = _flash_attn_func except ImportError: print_with_time_master("Failed to import flash_attn_3.") try: from flash_attn import ( flash_attn_varlen_func as _flash_attn_varlen_func, flash_attn_func as _flash_attn_func, ) FA_V_MAJOR = 2 # Wrap with torch.compiler.disable to prevent compilation flash_attn_varlen_func = torch.compiler.disable(_flash_attn_varlen_func) flash_attn_func = torch.compiler.disable(_flash_attn_func) except ImportError: print_with_time_master("Failed to import flash_attn. You won't be able to use the GPT model.") flash_attn_func = None flash_attn_varlen_func = None 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) 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, train_config, layer_idx: int): 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.layer_idx = layer_idx 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.rope_theta = config.rope_theta 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 config.use_qk_norm: self.q_ln = LayerNorm(config.d_head) self.k_ln = LayerNorm(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(0.0) self.resid_dropout = nn.Dropout(train_config.dropout) self.dropout = train_config.dropout self.use_qk_norm = config.use_qk_norm self.attention_type = train_config.attention_type assert self.attention_type in ("torch", "tao") if self.layer_idx % config.global_every_n_layers != 0: self.attention_sliding_window_size = config.attention_sliding_window_size else: self.attention_sliding_window_size = -1 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") self.use_rotary_pos_emb = config.use_rotary_pos_emb self.block_size = config.block_size def init_weights(self, init_std: float): for linear in (self.c_attn_k, self.c_attn_v, self.c_attn_q): nn.init.trunc_normal_(linear.weight, mean=0.0, std=0.02) nn.init.trunc_normal_(self.c_proj.weight, mean=0.0, std=init_std) def forward(self, x, rope_cache=None, cu_seqlen=None, block_mask=None, causal=True, max_seqlen=None): 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 self.use_qk_norm: q = self.q_ln(q) k = self.k_ln(k) if self.use_rotary_pos_emb: if rope_cache is None: raise ValueError("rope_cache must be provided when using rotary embeddings") cos, sin, cos_k, sin_k, interleaved = rope_cache 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 = apply_rotary_emb_func(q, cos, sin, interleaved=interleaved) kv = apply_rotary_emb_kv_( kv, cos_k if cos_k is not None else cos, sin_k if sin_k is not None else sin, interleaved=interleaved, ) 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 fa_dtype_in = q.dtype q, k, v = q.to(torch.bfloat16), k.to(torch.bfloat16), v.to(torch.bfloat16) if self.attention_type == "tao" and block_mask is None: # tao flash 2 needs (B, S, H, D) == (B, T, nh, hs) if B == 1: if FA_V_MAJOR == 3: seqused_q, seqused_k = None, None try: # old flash 3 api fa_args = ( q[0], k[0], v[0], cu_seqlen, cu_seqlen, max_seqlen, max_seqlen, ) y = flash_attn_varlen_func( *fa_args, # dropout_p=dropout, # softmax_scale=None, causal=causal, window_size=( self.attention_sliding_window_size, self.attention_sliding_window_size, ), ) except Exception as e: # new flash 3 api fa_args = ( q[0], k[0], v[0], cu_seqlen, cu_seqlen, seqused_q, seqused_k, max_seqlen, max_seqlen, ) y = flash_attn_varlen_func( *fa_args, # dropout_p=dropout, # softmax_scale=None, causal=causal, window_size=( self.attention_sliding_window_size, self.attention_sliding_window_size, ), ) if isinstance(y, tuple): y = y[0] else: fa_args = ( q[0], k[0], v[0], cu_seqlen, cu_seqlen, max_seqlen, max_seqlen, ) y = flash_attn_varlen_func( *fa_args, # dropout_p=dropout, # softmax_scale=None, causal=causal, window_size=( self.attention_sliding_window_size, self.attention_sliding_window_size, ), ) if isinstance(y, tuple): y = y[0] else: # B is not 1. not packing y = flash_attn_func( q, k, v, causal=causal, window_size=( self.attention_sliding_window_size, self.attention_sliding_window_size, ), ) if isinstance(y, tuple): y = y[0] else: # tao flash 2 needs (B, S, H, D) == (B, T, nh, hs) q = rearrange(q, "b n h d -> b h n d") k = rearrange(k, "b n h d -> b h n d") v = rearrange(v, "b n h d -> b h n d") y = flex_attention( q, k, v, enable_gqa=True, block_mask=block_mask, ) y = rearrange(y, "b h n d -> b n h d") y = y.to(fa_dtype_in) # 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 Relu2(nn.Module): def forward(self, x): return torch.pow(F.relu(x), 2) class MLP(nn.Module): def __init__(self, config, train_config): super().__init__() embd_inner = 4 * config.n_embd if config.activation_f == "silu": self.activation = nn.SiLU() elif config.activation_f == "gelu": self.activation = nn.GELU() elif config.activation_f == "relu2": self.activation = Relu2() else: raise NotImplementedError(f"activation function `{config.activation_f}` not supported") self.activation_func_str = config.activation_f if self.activation_func_str == "silu": multiple_of = 256 embd_inner = int(2 * embd_inner / 3) embd_inner = multiple_of * ((embd_inner + multiple_of - 1) // multiple_of) self.c_fc_2 = nn.Linear(config.n_embd, embd_inner, bias=config.bias) 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(train_config.dropout) def init_weights(self, init_std: float): nn.init.trunc_normal_(self.c_fc.weight, mean=0.0, std=0.02) for linear in [self.c_proj] + [self.c_fc_2] if self.activation_func_str == "silu" else []: nn.init.trunc_normal_(linear.weight, mean=0.0, std=init_std) def forward(self, x): if self.activation_func_str == "silu": x = self.c_proj(self.activation(self.c_fc(x)) * self.c_fc_2(x)) else: x = self.c_proj(self.activation(self.c_fc(x))) x = self.dropout(x) return x class Block(nn.Module): def __init__(self, config, train_config, layer_id: int): super().__init__() self.ln_1 = LayerNorm(config.n_embd) self.attn = CausalSelfAttention(config, train_config, layer_id) self.ln_2 = LayerNorm(config.n_embd) self.mlp = MLP(config, train_config) self.weight_init_std = 0.02 / (2 * (layer_id + 1)) ** 0.5 def init_weights(self): self.attn.init_weights(self.weight_init_std) self.mlp.init_weights(self.weight_init_std) def forward(self, x, rope_cache=None, cu_seqlen=None, block_mask=None, causal=True, max_seqlen=None): x = x + self.attn( self.ln_1(x), rope_cache=rope_cache, cu_seqlen=cu_seqlen, block_mask=block_mask, causal=causal, max_seqlen=max_seqlen, ) 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 # FSDP2 doesn't use _fsdp_wrapped_module prefix, parameter names remain unchanged # FSDP1 uses _fsdp_wrapped_module prefix # Filter out layernorm and bias parameters from weight decay 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_with_time_master( f"num decayed parameter tensors: {len(decay_params)}, with {num_decay_params:,} parameters" ) print_with_time_master( 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(torch.optim.AdamW).parameters use_fused = fused_available and device_type == "cuda" and use_fused extra_args = dict(fused=True) if use_fused else dict() optimizer = torch.optim.AdamW( optim_groups, lr=learning_rate, betas=betas, eps=1e-10, **extra_args ) # eps just incase: https://github.com/pytorch/pytorch/issues/26218 print_with_time_master(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_with_time_master("applying fsdp activation checkpointing...") apply_activation_checkpointing(model, checkpoint_wrapper_fn=non_reentrant_wrapper, check_fn=check_fn)