jupyterjazz
commited on
Commit
•
5ed05aa
1
Parent(s):
77af1c7
feat: support lora
Browse filesSigned-off-by: jupyterjazz <saba.sturua@jina.ai>
- configuration_xlm_roberta.py +3 -1
- modeling_lora.py +327 -0
configuration_xlm_roberta.py
CHANGED
@@ -21,6 +21,7 @@ class XLMRobertaFlashConfig(PretrainedConfig):
|
|
21 |
position_embedding_type="absolute",
|
22 |
use_cache=True,
|
23 |
classifier_dropout=None,
|
|
|
24 |
**kwargs,
|
25 |
):
|
26 |
super().__init__(pad_token_id=pad_token_id, bos_token_id=bos_token_id, eos_token_id=eos_token_id, **kwargs)
|
@@ -39,4 +40,5 @@ class XLMRobertaFlashConfig(PretrainedConfig):
|
|
39 |
self.layer_norm_eps = layer_norm_eps
|
40 |
self.position_embedding_type = position_embedding_type
|
41 |
self.use_cache = use_cache
|
42 |
-
self.classifier_dropout = classifier_dropout
|
|
|
|
21 |
position_embedding_type="absolute",
|
22 |
use_cache=True,
|
23 |
classifier_dropout=None,
|
24 |
+
num_loras=5,
|
25 |
**kwargs,
|
26 |
):
|
27 |
super().__init__(pad_token_id=pad_token_id, bos_token_id=bos_token_id, eos_token_id=eos_token_id, **kwargs)
|
|
|
40 |
self.layer_norm_eps = layer_norm_eps
|
41 |
self.position_embedding_type = position_embedding_type
|
42 |
self.use_cache = use_cache
|
43 |
+
self.classifier_dropout = classifier_dropout
|
44 |
+
self.num_loras = num_loras
|
modeling_lora.py
ADDED
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
import os
|
3 |
+
from functools import partial
|
4 |
+
from typing import Iterator, Optional, Tuple, Union
|
5 |
+
|
6 |
+
import torch
|
7 |
+
import torch.nn.utils.parametrize as parametrize
|
8 |
+
from torch import nn
|
9 |
+
from torch.nn import Parameter
|
10 |
+
from transformers import PretrainedConfig
|
11 |
+
|
12 |
+
from .modeling_xlm_roberta import XLMRobertaModel, XLMRobertaPreTrainedModel, XLMRobertaFlashConfig
|
13 |
+
|
14 |
+
|
15 |
+
def initialized_weights(
|
16 |
+
shape: Tuple[int], num_adaptions: int, init: str = "kaiming"
|
17 |
+
) -> torch.Tensor:
|
18 |
+
weight_data = []
|
19 |
+
for _ in range(num_adaptions):
|
20 |
+
new_adaption = torch.zeros(shape)
|
21 |
+
if init == "kaiming":
|
22 |
+
nn.init.kaiming_uniform_(new_adaption, a=math.sqrt(5))
|
23 |
+
elif init == "normal":
|
24 |
+
nn.init.normal_(new_adaption)
|
25 |
+
else:
|
26 |
+
raise NotImplementedError
|
27 |
+
weight_data.append(new_adaption)
|
28 |
+
return torch.stack(weight_data, dim=0)
|
29 |
+
|
30 |
+
|
31 |
+
class LoRAParametrization(nn.Module):
|
32 |
+
"""
|
33 |
+
This LoRA implementation was inspired by https://github.com/cccntu/minLoRA
|
34 |
+
The MIT License (MIT) Copyright (c) 2020 Andrej Karpathy
|
35 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
36 |
+
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
37 |
+
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
38 |
+
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
39 |
+
subject to the following conditions:
|
40 |
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
41 |
+
portions of the Software.
|
42 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
43 |
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
44 |
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
45 |
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
46 |
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
47 |
+
"""
|
48 |
+
def __init__(
|
49 |
+
self,
|
50 |
+
fan_in: int,
|
51 |
+
fan_out: int,
|
52 |
+
layer_type: str = "linear",
|
53 |
+
num_adaptions: int = 1,
|
54 |
+
rank: int = 4,
|
55 |
+
lora_dropout_p: float = 0.0,
|
56 |
+
lora_alpha: float = 1,
|
57 |
+
):
|
58 |
+
super().__init__()
|
59 |
+
# if weight is stored as (fan_out, fan_in), the memory layout of A & B follows (W + BA)x
|
60 |
+
# otherwise, it's x(W + AB). This allows us to tie the weights between linear layers and embeddings
|
61 |
+
fan_in_fan_out = layer_type == "embedding"
|
62 |
+
self.swap = (lambda x: (x[1], x[0])) if fan_in_fan_out else (lambda x: x)
|
63 |
+
|
64 |
+
# For the officially "correct" LoRA initialization, check here: https://github.com/microsoft/LoRA
|
65 |
+
# TODO: Ensure that the initialization here is correct
|
66 |
+
if layer_type == "linear":
|
67 |
+
self.lora_A = nn.Parameter(
|
68 |
+
initialized_weights((rank, fan_in), num_adaptions, init="kaiming")
|
69 |
+
)
|
70 |
+
self.lora_B = nn.Parameter(torch.zeros((num_adaptions, fan_out, rank)))
|
71 |
+
elif layer_type == "embedding":
|
72 |
+
self.lora_A = nn.Parameter(torch.zeros((num_adaptions, fan_in, rank)))
|
73 |
+
self.lora_B = nn.Parameter(
|
74 |
+
initialized_weights(
|
75 |
+
(rank, fan_out), num_adaptions=num_adaptions, init="normal"
|
76 |
+
)
|
77 |
+
)
|
78 |
+
else:
|
79 |
+
raise NotImplementedError
|
80 |
+
|
81 |
+
self.lora_alpha, self.rank = lora_alpha, rank
|
82 |
+
self.scaling = lora_alpha / rank
|
83 |
+
self.lora_dropout = (
|
84 |
+
nn.Dropout(p=lora_dropout_p) if lora_dropout_p > 0 else lambda x: x
|
85 |
+
)
|
86 |
+
self.dropout_fn = self._dropout if lora_dropout_p > 0 else lambda x: x
|
87 |
+
self.register_buffer(
|
88 |
+
"lora_dropout_mask",
|
89 |
+
torch.ones(self.swap((1, fan_in)), dtype=self.lora_A.dtype),
|
90 |
+
persistent=False,
|
91 |
+
)
|
92 |
+
self.forward_fn = lambda x: x
|
93 |
+
self.current_task = None
|
94 |
+
|
95 |
+
def _dropout(self, A):
|
96 |
+
# to mimic the original implementation: A @ dropout(x), we do (A * dropout(ones)) @ x
|
97 |
+
return A * self.lora_dropout(self.lora_dropout_mask)
|
98 |
+
|
99 |
+
def lora_forward(self, X):
|
100 |
+
assert self.current_task is not None
|
101 |
+
return (
|
102 |
+
X
|
103 |
+
+ torch.matmul(
|
104 |
+
*self.swap(
|
105 |
+
(
|
106 |
+
self.lora_B[self.current_task],
|
107 |
+
self.dropout_fn(self.lora_A[self.current_task]),
|
108 |
+
)
|
109 |
+
)
|
110 |
+
).view(X.shape)
|
111 |
+
* self.scaling
|
112 |
+
)
|
113 |
+
|
114 |
+
def forward(self, X):
|
115 |
+
return self.forward_fn(X)
|
116 |
+
|
117 |
+
@property
|
118 |
+
def current_task(self):
|
119 |
+
return self._current_task
|
120 |
+
|
121 |
+
@current_task.setter
|
122 |
+
def current_task(self, task: Union[None, int]):
|
123 |
+
self._current_task = task
|
124 |
+
if task is None:
|
125 |
+
self.forward_fn = lambda x: x
|
126 |
+
else:
|
127 |
+
self.forward_fn = self.lora_forward
|
128 |
+
|
129 |
+
@classmethod
|
130 |
+
def from_linear(
|
131 |
+
cls,
|
132 |
+
layer: nn.Module,
|
133 |
+
num_adaptions: int = 1,
|
134 |
+
rank: int = 4,
|
135 |
+
lora_dropout_p: float = 0.0,
|
136 |
+
lora_alpha: int = 1,
|
137 |
+
):
|
138 |
+
assert isinstance(layer, nn.Linear)
|
139 |
+
fan_out, fan_in = layer.weight.shape
|
140 |
+
return cls(
|
141 |
+
fan_in,
|
142 |
+
fan_out,
|
143 |
+
num_adaptions=num_adaptions,
|
144 |
+
layer_type="linear",
|
145 |
+
rank=rank,
|
146 |
+
lora_dropout_p=lora_dropout_p,
|
147 |
+
lora_alpha=lora_alpha,
|
148 |
+
)
|
149 |
+
|
150 |
+
@classmethod
|
151 |
+
def from_embedding(
|
152 |
+
cls, layer, num_adaptions=1, rank=4, lora_dropout_p=0.0, lora_alpha=1
|
153 |
+
):
|
154 |
+
assert isinstance(layer, nn.Embedding)
|
155 |
+
fan_in, fan_out = layer.weight.shape
|
156 |
+
return cls(
|
157 |
+
fan_in,
|
158 |
+
fan_out,
|
159 |
+
num_adaptions=num_adaptions,
|
160 |
+
layer_type="embedding",
|
161 |
+
rank=rank,
|
162 |
+
lora_dropout_p=lora_dropout_p,
|
163 |
+
lora_alpha=lora_alpha,
|
164 |
+
)
|
165 |
+
|
166 |
+
@classmethod
|
167 |
+
def add_to_layer(
|
168 |
+
cls, layer, num_adaptions=1, rank=4, lora_dropout_p=0.0, lora_alpha=1
|
169 |
+
):
|
170 |
+
if isinstance(layer, nn.Linear):
|
171 |
+
parametrize.register_parametrization(
|
172 |
+
layer,
|
173 |
+
"weight",
|
174 |
+
cls.from_linear(
|
175 |
+
layer,
|
176 |
+
num_adaptions=num_adaptions,
|
177 |
+
rank=rank,
|
178 |
+
lora_dropout_p=lora_dropout_p,
|
179 |
+
lora_alpha=lora_alpha,
|
180 |
+
),
|
181 |
+
)
|
182 |
+
elif isinstance(layer, nn.Embedding):
|
183 |
+
parametrize.register_parametrization(
|
184 |
+
layer,
|
185 |
+
"weight",
|
186 |
+
cls.from_embedding(
|
187 |
+
layer,
|
188 |
+
num_adaptions=num_adaptions,
|
189 |
+
rank=rank,
|
190 |
+
lora_dropout_p=lora_dropout_p,
|
191 |
+
lora_alpha=lora_alpha,
|
192 |
+
),
|
193 |
+
)
|
194 |
+
|
195 |
+
@staticmethod
|
196 |
+
def select_task_for_layer(layer: nn.Module, task_idx: Optional[int] = None):
|
197 |
+
if isinstance(layer, LoRAParametrization):
|
198 |
+
layer.current_task = task_idx
|
199 |
+
|
200 |
+
@staticmethod
|
201 |
+
def merge_lora_into_layer(layer: nn.Module):
|
202 |
+
if hasattr(layer, "parametrizations"):
|
203 |
+
for attr_name in layer.parametrizations.keys():
|
204 |
+
parametrize.remove_parametrizations(layer, attr_name, leave_parametrized=True)
|
205 |
+
|
206 |
+
|
207 |
+
class XLMRobertaLoRA(XLMRobertaPreTrainedModel):
|
208 |
+
def __init__(self, config: XLMRobertaFlashConfig, roberta: Optional[XLMRobertaModel] = None, add_pooling_layer=True):
|
209 |
+
super().__init__(config)
|
210 |
+
if roberta is None:
|
211 |
+
self.roberta = XLMRobertaModel(config, add_pooling_layer=add_pooling_layer)
|
212 |
+
else:
|
213 |
+
self.roberta = roberta
|
214 |
+
self._is_merged = False
|
215 |
+
self._num_adaptions = config.num_loras
|
216 |
+
self._register_lora(self._num_adaptions)
|
217 |
+
self.main_params_trainable = False
|
218 |
+
self._task_idx = None
|
219 |
+
# By default, we select the first LoRA
|
220 |
+
self.current_task = 0
|
221 |
+
|
222 |
+
@property
|
223 |
+
def main_params_trainable(self):
|
224 |
+
return self._main_params_trainable
|
225 |
+
|
226 |
+
@main_params_trainable.setter
|
227 |
+
def main_params_trainable(self, val: bool):
|
228 |
+
"""Whether the main parameters (i.e. those that are not LoRA) should be trainable.
|
229 |
+
This method sets the `requires_grad_` attribute of the main weights
|
230 |
+
and controls which parameters are returned in `self.parameters()`.
|
231 |
+
:param val: Whether or not to make the parameters trainable.
|
232 |
+
:return: None
|
233 |
+
"""
|
234 |
+
self._main_params_trainable = val
|
235 |
+
for name, param in super().named_parameters():
|
236 |
+
if "lora" not in name:
|
237 |
+
param.requires_grad_(val)
|
238 |
+
|
239 |
+
@classmethod
|
240 |
+
def from_roberta(cls, *args, **kwargs):
|
241 |
+
roberta = XLMRobertaModel.from_pretrained(*args, **kwargs)
|
242 |
+
config = XLMRobertaFlashConfig.from_pretrained(*args, **kwargs)
|
243 |
+
return cls(config, roberta=roberta)
|
244 |
+
|
245 |
+
def merge_lora(self):
|
246 |
+
"""Merges currently selected LoRA into main weights."""
|
247 |
+
if self._is_merged:
|
248 |
+
raise Exception('LoRA has already been merged, cannot merge again')
|
249 |
+
self._is_merged = True
|
250 |
+
self.apply(LoRAParametrization.merge_lora_into_layer)
|
251 |
+
|
252 |
+
@classmethod
|
253 |
+
def from_pretrained(
|
254 |
+
cls,
|
255 |
+
pretrained_model_name_or_path: Optional[Union[str, os.PathLike]],
|
256 |
+
*model_args,
|
257 |
+
config: Optional[Union[PretrainedConfig, str, os.PathLike]] = None,
|
258 |
+
cache_dir: Optional[Union[str, os.PathLike]] = None,
|
259 |
+
ignore_mismatched_sizes: bool = False,
|
260 |
+
force_download: bool = False,
|
261 |
+
local_files_only: bool = False,
|
262 |
+
token: Optional[Union[str, bool]] = None,
|
263 |
+
revision: str = "main",
|
264 |
+
use_safetensors: bool = None,
|
265 |
+
**kwargs,
|
266 |
+
):
|
267 |
+
"""
|
268 |
+
TODO: choose between from_roberta and super().from_pretrained
|
269 |
+
We want to be able to load both a pretrained XLMRoBertaModel, and a trained
|
270 |
+
XLMRobertaLoRA via this method. To this end, we need to check which of these
|
271 |
+
models we are expected to load.
|
272 |
+
"""
|
273 |
+
return cls.from_roberta(pretrained_model_name_or_path)
|
274 |
+
|
275 |
+
def _register_lora(self, num_adaptions=1, rank=4, lora_dropout_p=0.0, lora_alpha=1):
|
276 |
+
self.apply(
|
277 |
+
partial(
|
278 |
+
LoRAParametrization.add_to_layer,
|
279 |
+
num_adaptions=num_adaptions,
|
280 |
+
rank=rank,
|
281 |
+
lora_dropout_p=lora_dropout_p,
|
282 |
+
lora_alpha=lora_alpha,
|
283 |
+
)
|
284 |
+
)
|
285 |
+
|
286 |
+
@property
|
287 |
+
def current_task(self):
|
288 |
+
""" Which LoRA is currently selected
|
289 |
+
:return: Integer or None (when LoRA is disabled)
|
290 |
+
"""
|
291 |
+
return self._task_idx
|
292 |
+
|
293 |
+
@current_task.setter
|
294 |
+
def current_task(self, task_idx: Union[None, int]):
|
295 |
+
"""Set the LoRA that is to be used.
|
296 |
+
The LoRA is specified by `task_idx`, which may be an integer >= 0,
|
297 |
+
indexing the available LoRAs. If it is None, no LoRA is used.
|
298 |
+
:param task_idx: Which LoRA to use
|
299 |
+
:return:
|
300 |
+
"""
|
301 |
+
if self._is_merged:
|
302 |
+
raise Exception('LoRA has been merged, cannot select new task')
|
303 |
+
assert task_idx is None or 0 <= task_idx < self._num_adaptions
|
304 |
+
if self._task_idx != task_idx:
|
305 |
+
# In this case, we need to update the LoRAs everywhere
|
306 |
+
self._task_idx = task_idx
|
307 |
+
self.apply(
|
308 |
+
partial(LoRAParametrization.select_task_for_layer, task_idx=task_idx)
|
309 |
+
)
|
310 |
+
|
311 |
+
def forward(self, *args, current_task: Union[None, int] = -1, **kwargs):
|
312 |
+
if current_task is None or current_task >= 0:
|
313 |
+
self.current_task = current_task
|
314 |
+
return self.bert(*args, **kwargs)
|
315 |
+
|
316 |
+
def parameters(self, recurse: bool = True) -> Iterator[Parameter]:
|
317 |
+
for _, param in self.named_parameters(recurse=recurse):
|
318 |
+
yield param
|
319 |
+
|
320 |
+
def named_parameters(
|
321 |
+
self, prefix: str = "", recurse: bool = True, remove_duplicate: bool = True
|
322 |
+
) -> Iterator[Tuple[str, Parameter]]:
|
323 |
+
for name, param in super().named_parameters(
|
324 |
+
prefix=prefix, recurse=recurse, remove_duplicate=remove_duplicate
|
325 |
+
):
|
326 |
+
if "lora" in name or self.main_params_trainable:
|
327 |
+
yield name, param
|