|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| r"""
|
| Low Ranking Adaptation for LLMs scheme.
|
|
|
| ┌───────────────────┐
|
| ┆ h ┆
|
| └───────────────────┘
|
| ▲
|
| |
|
| +
|
| / \
|
| ┌─────────────────┐ ╭───────────────╮ Matrix initialization:
|
| ┆ ┆ \ B / B = 0
|
| ┆ pretrained ┆ \ r*d / A = N(0, sigma^2)
|
| ┆ weights ┆ ╰─────────╯
|
| ┆ ┆ | r | r - rank
|
| ┆ W e R^(d*d) ┆ | ◀─────▶ |
|
| ┆ ┆ ╭─────────╮
|
| └─────────────────┘ / A \
|
| ▲ / d*r \
|
| \ ╰───────────────╯
|
| \ ▲
|
| \ /
|
| \ /
|
| ┌───────────────────┐
|
| ┆ x ┆
|
| └───────────────────┘
|
|
|
| With LoRA (Low Ranking Adaptation: https://arxiv.org/abs/2106.09685) instead of learning weights of size d*d,
|
| we can freeze the pretrained weights and instead learn two matrices of size d*r and r*d (they will store weight updates
|
| for the pretrained weights): the number of parameters in this case will be reduced drastically (depending on the rank of
|
| course) yet after multiplication of matrices d*r and r*d we will get a matrix d*d which we can sum with frozen
|
| pretrained weights and thus fine-tune the model.
|
|
|
| The goal of this approach is to move weight updates into a separate matrix which is decomposed with
|
| two matrices of a lower rank.
|
| """
|
|
|
| import math
|
| from dataclasses import dataclass
|
| from typing import Any, cast
|
|
|
| import torch
|
| import torch.nn as nn
|
| from torch.nn import functional as F
|
| from typing_extensions import Self
|
|
|
| import litgpt
|
| from litgpt.config import Config as BaseConfig
|
| from litgpt.model import GPT as BaseModel
|
| from litgpt.model import Block as BaseBlock
|
| from litgpt.model import CausalSelfAttention as BaseCausalSelfAttention
|
| from litgpt.scripts.convert_hf_checkpoint import qkv_reassemble
|
| from litgpt.utils import map_old_state_dict_weights
|
|
|
|
|
| class LoRALayer(nn.Module):
|
| def __init__(self, r: int, lora_alpha: int, lora_dropout: float):
|
| """Store LoRA specific attributes in a class.
|
|
|
| Args:
|
| r: rank of the weight update matrices. To make sense of using LoRA the rank should be smaller than the rank of
|
| the weights of the model. The rank can be as low as 1: https://arxiv.org/pdf/2106.09685.pdf (section 7.2)
|
| lora_alpha: alpha is needed for scaling updates as alpha/r
|
| "This scaling helps to reduce the need to retune hyperparameters when we vary r"
|
| https://arxiv.org/pdf/2106.09685.pdf (section 4.1)
|
| lora_dropout: dropout that is applied on the input in the LoRA branch (before multiplying by matrix A)
|
| """
|
| super().__init__()
|
| assert r >= 0
|
| self.r = r
|
| self.lora_alpha = lora_alpha
|
|
|
| if lora_dropout > 0.0:
|
| self.lora_dropout = nn.Dropout(p=lora_dropout)
|
| else:
|
| self.lora_dropout = lambda x: x
|
|
|
| self.merged = False
|
|
|
|
|
| class LoRALinear(LoRALayer):
|
|
|
| def __init__(
|
| self,
|
|
|
| in_features: int,
|
| out_features: int,
|
|
|
| r: int = 0,
|
| lora_alpha: int = 1,
|
| lora_dropout: float = 0.0,
|
| **kwargs: Any,
|
| ):
|
| """LoRA wrapper around linear class.
|
|
|
| This class has three weight matrices:
|
| 1. Pretrained weights are stored as `self.linear.weight`
|
| 2. LoRA A matrix as `self.lora_A`
|
| 3. LoRA B matrix as `self.lora_B`
|
| Only LoRA's A and B matrices are updated, pretrained weights stay frozen.
|
|
|
| Args:
|
| in_features: number of input features of the pretrained weights
|
| out_features: number of output features of the pretrained weights
|
| r: rank of the weight update matrices. To make sense of using LoRA the rank should be smaller than the rank of
|
| the weights of the model. The rank can be as low as 1: https://arxiv.org/pdf/2106.09685.pdf (section 7.2)
|
| lora_alpha: alpha is needed for scaling updates as alpha/r
|
| "This scaling helps to reduce the need to retune hyperparameters when we vary r"
|
| https://arxiv.org/pdf/2106.09685.pdf (section 4.1)
|
| lora_dropout: dropout that is applied on the input in the LoRA branch (before multiplying by matrix A)
|
| """
|
| super().__init__(r=r, lora_alpha=lora_alpha, lora_dropout=lora_dropout)
|
| self.linear = torch.nn.Linear(in_features, out_features, **kwargs)
|
|
|
|
|
| if r > 0:
|
| self.lora_A = nn.Parameter(torch.empty((r, in_features)))
|
| self.lora_B = nn.Parameter(torch.empty((out_features, r)))
|
| self.scaling = self.lora_alpha / self.r
|
| self.reset_parameters()
|
|
|
| def reset_parameters(self) -> None:
|
| """Reset all the weights, even including pretrained ones."""
|
| if hasattr(self, "lora_A"):
|
|
|
|
|
| nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
|
| nn.init.zeros_(self.lora_B)
|
|
|
| def get_lora_AB(self) -> torch.Tensor:
|
| """Return merged lora_A and lora_B matrices with the same shape as the pretrained weights."""
|
| return (self.lora_B @ self.lora_A) * self.scaling
|
|
|
| def merge(self) -> None:
|
| """Merges the LoRA weights into the full-rank weights (W = W + delta_W)."""
|
| if self.r > 0 and not self.merged:
|
| pretrained_dtype = self.linear.weight.data.dtype
|
| lora_data = self.get_lora_AB()
|
|
|
| if pretrained_dtype == torch.uint8:
|
| import bitsandbytes as bnb
|
|
|
| weight = self.linear.weight
|
|
|
| weight_data = bnb.functional.dequantize_4bit(weight.data, weight.quant_state).to(lora_data.dtype)
|
|
|
| weight_data += lora_data
|
|
|
| self.linear.weight = bnb.nn.Params4bit(weight_data, requires_grad=False, **weight.__dict__)
|
| self.linear.weight.cuda(weight.device)
|
| else:
|
|
|
|
|
| self.linear.weight.data += lora_data.to(device=self.linear.weight.data.device)
|
| self.merged = True
|
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
|
|
|
| pretrained = self.linear(x)
|
| if self.r == 0 or self.merged:
|
| return pretrained
|
| lora = (self.lora_dropout(x) @ self.lora_A.transpose(0, 1) @ self.lora_B.transpose(0, 1)) * self.scaling
|
| return pretrained + lora
|
|
|
|
|
| class LoRAQKVLinear(LoRALinear):
|
|
|
| def __init__(
|
| self,
|
|
|
| in_features: int,
|
| out_features: int,
|
|
|
| head_size: int,
|
| n_head: int,
|
| n_query_groups: int,
|
| r: int = 0,
|
| lora_alpha: int = 1,
|
| lora_dropout: float = 0.0,
|
| enable_lora: bool | tuple[bool, bool, bool] = False,
|
| **kwargs: Any,
|
| ):
|
| """LoRA wrapper around linear class that is used for calculation of q, k and v matrices.
|
|
|
| This class has three weight matrices:
|
| 1. Pretrained weights are stored as `self.linear.weight`
|
| 2. LoRA A matrix as `self.lora_A`
|
| 3. LoRA B matrix as `self.lora_B`
|
| Only LoRA's A and B matrices are updated, pretrained weights stay frozen.
|
|
|
| Args:
|
| in_features: number of input features of the pretrained weights
|
| out_features: number of output features of the pretrained weights
|
| head_size: size of a single attention head
|
| n_head: number of attention heads
|
| n_query_groups: number of query groups (see diagram in `litgpt/config.py`)
|
| r: rank of the weight update matrices. To make sense of using LoRA the rank should be smaller than the rank of
|
| the weights of the model. The rank can be as low as 1: https://arxiv.org/pdf/2106.09685.pdf (section 7.2)
|
| lora_alpha: alpha is needed for scaling updates as alpha/r
|
| "This scaling helps to reduce the need to retune hyperparameters when we vary r"
|
| https://arxiv.org/pdf/2106.09685.pdf (section 4.1)
|
| lora_dropout: dropout that is applied on the input in the LoRA branch (before multiplying by matrix A)
|
| enable_lora: MergeLinear class is for attention mechanism where qkv are calculated with a single weight matrix. If we
|
| don't want to apply LoRA we can set it as False. For example if we want to apply LoRA only to `query`
|
| and `value` but keep `key` without weight updates we should pass `[True, False, True]`
|
| """
|
| super(LoRALinear, self).__init__(r=r, lora_alpha=lora_alpha, lora_dropout=lora_dropout)
|
| self.linear = torch.nn.Linear(in_features, out_features, **kwargs)
|
| self.head_size = head_size
|
| self.n_head = n_head
|
| self.n_query_groups = n_query_groups
|
| if isinstance(enable_lora, bool):
|
| enable_lora = [enable_lora] * 3
|
| assert len(enable_lora) == 3
|
| self.enable_lora = enable_lora
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| if r > 0 and any(enable_lora):
|
| self.lora_A = nn.Parameter(torch.empty((r * sum(enable_lora), in_features)))
|
| enable_q, enable_k, enable_v = enable_lora
|
|
|
| qkv_shapes = (
|
|
|
|
|
| head_size * n_head * enable_q,
|
| head_size * n_query_groups * enable_k,
|
| head_size * n_query_groups * enable_v,
|
| )
|
| self.qkv_shapes = [s for s in qkv_shapes if s]
|
| self.lora_B = nn.Parameter(torch.empty(sum(self.qkv_shapes), r))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| self.scaling = self.lora_alpha / self.r
|
|
|
| self.reset_parameters()
|
|
|
| @property
|
| def lora_ind(self) -> torch.Tensor:
|
| """Lazily compute and cache LoRA indices as a non-persistent buffer for FSDP meta-device compatibility.
|
|
|
| Returns a clone so that inference-mode tensors are never passed into autograd.
|
| """
|
|
|
| if not hasattr(self, "_lora_ind"):
|
| enable_q, enable_k, enable_v = self.enable_lora
|
| q_embd_size = self.head_size * self.n_head
|
| kv_embd_size = self.head_size * self.n_query_groups
|
| lora_ind: list[int] = []
|
| if enable_q:
|
| lora_ind.extend(range(0, q_embd_size))
|
| if enable_k:
|
| lora_ind.extend(range(q_embd_size, q_embd_size + kv_embd_size))
|
| if enable_v:
|
| lora_ind.extend(range(q_embd_size + kv_embd_size, self.linear.out_features))
|
| self.register_buffer(
|
| "_lora_ind", torch.tensor(lora_ind, device=self.linear.weight.device), persistent=False
|
| )
|
|
|
| return cast(torch.Tensor, self._lora_ind).clone()
|
|
|
| def zero_pad(self, x: torch.Tensor) -> torch.Tensor:
|
| """Properly pad the last dimension of weight updates with zeros.
|
|
|
| If, based on `self.enable_lora`, we want to fine-tune queries and values, but not keys,
|
| then the weights update should be:
|
|
|
| [[ΔW,ΔW,ΔW, ..., 0,0,0, ..., ΔW,ΔW,ΔW,],
|
| [....................................],
|
| [ΔW,ΔW,ΔW, ..., 0,0,0, ..., ΔW,ΔW,ΔW,]]
|
| ↑ ↑ ↑
|
| ________________________________________
|
| | query | key | value |
|
| ----------------------------------------
|
|
|
| Args:
|
| x: tensor with weights update that will be padded with zeros if necessary
|
|
|
| Returns:
|
| A tensor with weight updates and zeros for deselected q, k or v
|
| """
|
|
|
| if all(self.enable_lora):
|
| return x
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| result = x.new_zeros(*x.shape[:-1], self.linear.out_features)
|
| if result.device.type == "mps":
|
| result[..., self.lora_ind] = x
|
| return result
|
| else:
|
| return result.index_copy_(dim=-1, index=self.lora_ind, source=x)
|
|
|
| def conv1d(self, input: torch.Tensor, weight: torch.Tensor) -> torch.Tensor:
|
| """An extension of the `torch.nn.functional.conv1d` function with a logic specific to grouped queries.
|
|
|
| If the number of heads is equal to the number of query groups - grouped queries are disabled
|
| (see scheme in `litgpt/config.py:Config`). In this case the combined QKV matrix consists of equally sized
|
| query, key and value parts, which means we can utilize `groups` argument from `conv1d`: with this argument the
|
| input and weight matrices will be split in equally sized parts and applied separately (like having multiple
|
| conv layers side by side).
|
|
|
| Otherwise QKV matrix consists of unequally sized parts and thus we have to split input and weight matrices manually,
|
| apply each part of the weight matrix to the corresponding input's part and concatenate the result.
|
|
|
| Args:
|
| input: input matrix of shape (B, C, T)
|
| weight: weight matrix of shape (C_output, rank, 1).
|
| "C_output" is defined as a sum of embedding sizes for each enabled LoRA layer (see init method of the class).
|
|
|
| Returns:
|
| A tensor with a shape (B, C_output, T)
|
|
|
| """
|
| if self.n_head == self.n_query_groups:
|
| return F.conv1d(input, weight, groups=sum(self.enable_lora))
|
|
|
|
|
|
|
|
|
|
|
|
|
| input_splitted = input.chunk(sum(self.enable_lora), dim=1)
|
| weight_splitted = weight.split(self.qkv_shapes)
|
| return torch.cat(
|
| [F.conv1d(a, b) for a, b in zip(input_splitted, weight_splitted)],
|
| dim=1,
|
| )
|
|
|
| def get_lora_AB(self) -> torch.Tensor:
|
| """Return merged lora_A and lora_B matrices with the same shape as the pretrained weights."""
|
|
|
|
|
|
|
|
|
| lora = self.conv1d(
|
| self.lora_A.data.unsqueeze(0),
|
| self.lora_B.data.unsqueeze(-1),
|
| ).squeeze(0)
|
| return self.zero_pad(lora.T * self.scaling).T
|
|
|
| def merge(self) -> None:
|
| """Merges the LoRA weights into the full-rank weights (W = W + delta_W)."""
|
| if self.r > 0 and any(self.enable_lora) and not self.merged:
|
| super().merge()
|
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| """Do the forward pass.
|
|
|
| If LoRA's weights are merged with pretrained ones then it's a simple matrix multiplication.
|
| If not, then multiply pretrained weights with input, apply LoRA on input and do summation.
|
|
|
| Args:
|
| x: input tensor of shape (batch_size, context_length, embedding_size)
|
|
|
| Returns:
|
| Output tensor of shape (batch_size, context_length, 3 * embedding_size)
|
| """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| pretrained = self.linear(x)
|
| if self.r == 0 or not any(self.enable_lora) or self.merged:
|
| return pretrained
|
| after_A = F.linear(self.lora_dropout(x), self.lora_A)
|
|
|
|
|
|
|
| after_B = self.conv1d(
|
| after_A.transpose(-2, -1),
|
| self.lora_B.unsqueeze(-1),
|
| ).transpose(-2, -1)
|
| lora = self.zero_pad(after_B) * self.scaling
|
| return pretrained + lora
|
|
|
|
|
| def mark_only_lora_as_trainable(model: nn.Module, bias: str = "none") -> None:
|
| """Freeze all modules except LoRA's and depending on 'bias' value unfreezes bias weights.
|
|
|
| Args:
|
| model: model with LoRA layers
|
| bias:
|
| ``"none"``: all bias weights will be frozen,
|
| ``"lora_only"``: only bias weight for LoRA layers will be unfrozen,
|
| ``"all"``: all bias weights will be unfrozen.
|
|
|
| Raises:
|
| NotImplementedError: if `bias` not in ["none", "lora_only", "all"]
|
| """
|
|
|
| for n, p in model.named_parameters():
|
| if "lora_" not in n:
|
| p.requires_grad = False
|
|
|
|
|
| if bias == "none":
|
| return
|
| if bias == "all":
|
| for n, p in model.named_parameters():
|
| if "bias" in n:
|
| p.requires_grad = True
|
| elif bias == "lora_only":
|
| for m in model.modules():
|
| if isinstance(m, LoRALayer) and hasattr(m, "bias") and m.bias is not None:
|
| m.bias.requires_grad = True
|
| else:
|
| raise NotImplementedError
|
|
|
|
|
| def lora_filter(key: str, value: Any) -> bool:
|
| return "lora_" in key
|
|
|
|
|
| @dataclass
|
| class Config(BaseConfig):
|
| """
|
| Args:
|
| lora_r: rank of the weight update matrices. To make sense of using LoRA the rank should be smaller than the rank of
|
| the weights of the model. The rank can be as low as 1: https://arxiv.org/pdf/2106.09685.pdf (section 7.2)
|
| lora_alpha: alpha is needed for scaling updates as alpha/r
|
| "This scaling helps to reduce the need to retune hyperparameters when we vary r"
|
| https://arxiv.org/pdf/2106.09685.pdf (section 4.1)
|
| lora_dropout: dropout that is applied on the input in the LoRA branch (before multiplying by matrix A)
|
| lora_*: whether to apply LoRA to the specified weights or not
|
| """
|
|
|
| lora_r: int = 0
|
| lora_alpha: int = 1
|
| lora_dropout: float = 0.0
|
| lora_query: bool = False
|
| lora_key: bool = False
|
| lora_value: bool = False
|
| lora_projection: bool = False
|
| lora_mlp: bool = False
|
| lora_head: bool = False
|
|
|
| @property
|
| def mlp_class(self) -> type:
|
| return getattr(litgpt.lora, self.mlp_class_name)
|
|
|
|
|
| class GPT(BaseModel):
|
|
|
| def __init__(self, config: Config) -> None:
|
| nn.Module.__init__(self)
|
| assert config.padded_vocab_size is not None
|
| self.config = config
|
|
|
| self.lm_head = create_lora_linear(
|
| config,
|
| config.n_embd,
|
| config.padded_vocab_size,
|
| bias=config.lm_head_bias,
|
| use_r=config.lora_head,
|
| )
|
| self.transformer = nn.ModuleDict(
|
| dict(
|
| wte=nn.Embedding(config.padded_vocab_size, config.n_embd),
|
| h=nn.ModuleList(Block(config, block_idx) for block_idx in range(config.n_layer)),
|
| ln_f=config.norm_class(config.n_embd, eps=config.norm_eps),
|
| )
|
| )
|
| self.mask_cache: torch.Tensor | None = None
|
| self.max_seq_length = self.config.block_size
|
|
|
| @classmethod
|
| def from_name(cls, name: str, **kwargs: Any) -> Self:
|
| return cls(Config.from_name(name, **kwargs))
|
|
|
| def _init_weights(self, module: nn.Module) -> None:
|
| """Meant to be used with `gpt.apply(gpt._init_weights)`. Unused method left for completeness."""
|
| super()._init_weights(module)
|
| if isinstance(module, LoRALinear):
|
| module.reset_parameters()
|
|
|
| def _load_from_state_dict(self, state_dict: dict, prefix: str, *args: Any, **kwargs: Any) -> None:
|
| """For compatibility with base checkpoints."""
|
| mapping = {"lm_head.weight": "lm_head.linear.weight", "lm_head.bias": "lm_head.linear.bias"}
|
| state_dict = map_old_state_dict_weights(state_dict, mapping, prefix)
|
| super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
|
|
|
|
|
| class Block(BaseBlock):
|
| def __init__(self, config: Config, block_idx: int) -> None:
|
| super().__init__(config, block_idx)
|
| self.attn = CausalSelfAttention(config, block_idx)
|
| self.mlp = config.mlp_class(config)
|
|
|
|
|
| class CausalSelfAttention(BaseCausalSelfAttention):
|
| def __init__(self, config: Config, block_idx: int) -> None:
|
| super().__init__(config, block_idx)
|
|
|
| shape = (config.n_head + 2 * config.n_query_groups) * config.head_size
|
| self.qkv = LoRAQKVLinear(
|
| in_features=config.n_embd,
|
| out_features=shape,
|
| r=config.lora_r,
|
| lora_alpha=config.lora_alpha,
|
| lora_dropout=config.lora_dropout,
|
| enable_lora=(config.lora_query, config.lora_key, config.lora_value),
|
| bias=config.bias or config.attn_bias,
|
|
|
| head_size=config.head_size,
|
| n_head=config.n_head,
|
| n_query_groups=config.n_query_groups,
|
| )
|
|
|
| self.proj = create_lora_linear(
|
| config,
|
| config.head_size * config.n_head,
|
| config.n_embd,
|
| use_r=config.lora_projection,
|
| )
|
|
|
| def _load_from_state_dict(self, state_dict: dict, prefix: str, *args: Any, **kwargs: Any) -> None:
|
| """For compatibility with base and/or legacy checkpoints."""
|
| mapping = {
|
| "qkv.weight": "qkv.linear.weight",
|
| "qkv.bias": "qkv.linear.bias",
|
| "proj.weight": "proj.linear.weight",
|
| "proj.bias": "proj.linear.bias",
|
| }
|
| state_dict = map_old_state_dict_weights(state_dict, mapping, prefix)
|
|
|
| for attr in ("weight", "bias"):
|
| legacy_key = f"{prefix}attn.linear.{attr}"
|
| current_key = f"{prefix}qkv.linear.{attr}"
|
| if legacy_key in state_dict:
|
| state_dict[current_key] = qkv_reassemble(state_dict.pop(legacy_key), self.config)
|
|
|
| super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
|
|
|
|
|
| def create_lora_linear(
|
| config: Config,
|
| in_size: int,
|
| out_size: int,
|
| bias: float | bool | None = None,
|
| use_r: bool | None = None,
|
| ) -> LoRALinear:
|
| if bias is None:
|
| bias = config.bias
|
| if use_r is None:
|
| use_r = config.lora_mlp
|
| return LoRALinear(
|
| in_size,
|
| out_size,
|
| bias=bias,
|
| r=(config.lora_r if use_r else 0),
|
| lora_alpha=config.lora_alpha,
|
| lora_dropout=config.lora_dropout,
|
| )
|
|
|
|
|
| class GptNeoxMLP(litgpt.model.GptNeoxMLP):
|
| def __init__(self, config: Config) -> None:
|
| nn.Module.__init__(self)
|
| self.fc = create_lora_linear(config, config.n_embd, config.intermediate_size)
|
| self.proj = create_lora_linear(config, config.intermediate_size, config.n_embd)
|
| self.config = config
|
|
|
| def _load_from_state_dict(self, state_dict: dict, prefix: str, *args: Any, **kwargs: Any) -> None:
|
| """For compatibility with base checkpoints."""
|
| mapping = {
|
| "fc.weight": "fc.linear.weight",
|
| "fc.bias": "fc.linear.bias",
|
| "proj.weight": "proj.linear.weight",
|
| "proj.bias": "proj.linear.bias",
|
| }
|
| state_dict = map_old_state_dict_weights(state_dict, mapping, prefix)
|
| super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
|
|
|
|
|
| class LLaMAMLP(litgpt.model.LLaMAMLP):
|
| def __init__(self, config: Config, intermediate_size: int | None = None) -> None:
|
| nn.Module.__init__(self)
|
| self.intermediate_size = intermediate_size or config.intermediate_size
|
| self.fc_1 = create_lora_linear(config, config.n_embd, self.intermediate_size)
|
| self.fc_2 = create_lora_linear(config, config.n_embd, self.intermediate_size)
|
| self.proj = create_lora_linear(config, self.intermediate_size, config.n_embd)
|
| self.config = config
|
|
|
| def _load_from_state_dict(self, state_dict: dict, prefix: str, *args: Any, **kwargs: Any) -> None:
|
| """For compatibility with base checkpoints."""
|
| mapping = {
|
| "fc_1.weight": "fc_1.linear.weight",
|
| "fc_1.bias": "fc_1.linear.bias",
|
| "fc_2.weight": "fc_2.linear.weight",
|
| "fc_2.bias": "fc_2.linear.bias",
|
| "proj.weight": "proj.linear.weight",
|
| "proj.bias": "proj.linear.bias",
|
| }
|
| state_dict = map_old_state_dict_weights(state_dict, mapping, prefix)
|
| super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
|
|
|
|
|
| class GemmaMLP(LLaMAMLP):
|
| def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| x_fc_1 = self.fc_1(x)
|
| x_fc_2 = self.fc_2(x)
|
| x = torch.nn.functional.gelu(x_fc_1, approximate=self.config.gelu_approximate) * x_fc_2
|
| return self.proj(x)
|
|
|
|
|
| class LLaMAMoE(litgpt.model.LLaMAMoE):
|
| def __init__(self, config: Config) -> None:
|
| nn.Module.__init__(self)
|
| self.gate = create_lora_linear(config, config.n_embd, config.n_expert, bias=False)
|
| self.experts = nn.ModuleList(
|
| LLaMAMLP(config, intermediate_size=config.moe_intermediate_size) for _ in range(config.n_expert)
|
| )
|
| self.config = config
|
|
|
| def _load_from_state_dict(self, state_dict: dict, prefix: str, *args: Any, **kwargs: Any) -> None:
|
| """For compatibility with base checkpoints."""
|
| mapping = {"gate.weight": "gate.linear.weight"}
|
| state_dict = map_old_state_dict_weights(state_dict, mapping, prefix)
|
| super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
|
|
|
|
|
| def merge_lora_weights(model: GPT) -> None:
|
| """Merge LoRA weights into the full-rank weights to speed up inference."""
|
| for module in model.modules():
|
| if isinstance(module, LoRALinear):
|
| module.merge()
|
|
|