main
py 409 lines 12.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import json
5 import os
6 from dataclasses import dataclass
7
8 import torch
9 from safetensors.torch import save_file
10
11 from vllm.lora.lora_weights import LoRALayerWeights, PackedLoRALayerWeights
12 from vllm.platforms import current_platform
13
14 DEVICE_TYPE = current_platform.device_type
15
16
17 class DummyLoRAManager:
18 def __init__(self, device: torch.device = f"{DEVICE_TYPE}:0"):
19 super().__init__()
20 self._loras: dict[str, LoRALayerWeights] = {}
21 self._device = device
22
23 def set_module_lora(self, module_name: str, lora: LoRALayerWeights):
24 self._loras[module_name] = lora
25
26 def get_module_lora(self, module_name: str) -> LoRALayerWeights:
27 return self._loras[module_name]
28
29 def init_random_lora(
30 self,
31 module_name: str,
32 weight: torch.Tensor,
33 rank: int = 8,
34 ):
35 lora = LoRALayerWeights(
36 module_name,
37 rank=rank,
38 lora_alpha=1,
39 lora_a=torch.rand(
40 [rank, weight.shape[1]], dtype=weight.dtype, device=self._device
41 ),
42 lora_b=torch.rand(
43 [weight.shape[0], rank], dtype=weight.dtype, device=self._device
44 ),
45 )
46 self.set_module_lora(module_name, lora)
47
48 return lora
49
50 def init_lora(
51 self,
52 module_name: str,
53 input_dim: int,
54 output_dim: int,
55 rank=8,
56 noop=False,
57 embeddings_tensor=None,
58 ):
59 lora = LoRALayerWeights(
60 module_name,
61 rank=rank,
62 lora_alpha=1,
63 lora_a=torch.rand([rank, input_dim], device=DEVICE_TYPE),
64 lora_b=torch.rand([output_dim, input_dim], device=DEVICE_TYPE),
65 embeddings_tensor=embeddings_tensor,
66 )
67 self.set_module_lora(module_name, lora)
68 return lora
69
70 def reset_lora(self):
71 self._loras = {}
72
73 def init_packed_lora(
74 self,
75 module_name: str,
76 input_dim: int,
77 output_dims: list[int],
78 noop_lora_index: list[int] | None = None,
79 rank: int = 8,
80 ):
81 base_loras: list[LoRALayerWeights] = []
82 noop_lora_index_set = set(noop_lora_index or [])
83
84 for i, out_dim in enumerate(output_dims):
85 base_lora = self.init_lora(
86 module_name + "_000_" + str(i),
87 input_dim,
88 out_dim,
89 rank=rank,
90 noop=i in noop_lora_index_set,
91 )
92 base_loras.append(base_lora)
93 packed_lora = PackedLoRALayerWeights.pack(base_loras)
94 self.set_module_lora(module_name, packed_lora)
95 return packed_lora
96
97
98 def assert_close(a, b):
99 rtol, atol = {
100 torch.float16: (6e-2, 6e-2),
101 torch.bfloat16: (6e-2, 6e-2),
102 torch.float32: (1e-2, 1e-2),
103 }[a.dtype]
104 torch.testing.assert_close(a, b, rtol=rtol, atol=atol)
105
106
107 @dataclass
108 class PunicaTensors:
109 inputs_tensor: torch.Tensor
110 lora_weights: torch.Tensor | list[torch.Tensor]
111 our_out_tensor: torch.Tensor
112 ref_out_tensor: torch.Tensor
113 b_seq_start_loc: torch.Tensor
114 prompt_lora_mapping: torch.Tensor
115 seq_len_tensor: torch.Tensor
116 token_lora_mapping: torch.Tensor
117
118 def meta(self) -> tuple[int, int]:
119 """Infer max_seq_length and token_nums from the tensors
120 and return them.
121 """
122 max_seq_length = self.seq_len_tensor.max()
123 token_nums = self.seq_len_tensor.sum().item()
124 if isinstance(max_seq_length, tuple):
125 max_seq_length = max_seq_length[0].item()
126 else:
127 max_seq_length = max_seq_length.item()
128 return max_seq_length, token_nums
129
130
131 def generate_data(
132 batches,
133 hidden_size,
134 lora_nums,
135 max_rank,
136 seq_length,
137 dtype,
138 op_type,
139 device,
140 ) -> PunicaTensors:
141 seq_len_tensor = torch.randint(seq_length, seq_length + 1, (batches,)).to(device)
142 b_seq_start_loc = torch.cumsum(
143 torch.tensor([0] + seq_len_tensor[:-1].tolist(), dtype=torch.long),
144 dim=0,
145 ).to(device)
146 total_tokens = seq_len_tensor.sum()
147 if op_type == "shrink":
148 inputs_tensor = torch.rand((total_tokens, hidden_size), dtype=dtype).to(device)
149 lora_weights = torch.rand(
150 (lora_nums, max_rank, hidden_size), # col-major
151 dtype=dtype,
152 ).to(device)
153 # shrink op need atomic_add, so output is initinized by 0
154 ref_out_tensor = torch.zeros(
155 (total_tokens, max_rank), dtype=dtype, device=inputs_tensor.device
156 )
157 # NOTE shrink kernel using torch.float32 as output type
158 our_out_tensor = torch.zeros((total_tokens, max_rank), dtype=torch.float32).to(
159 device
160 )
161 else:
162 inputs_tensor = torch.rand(
163 (total_tokens, max_rank),
164 dtype=dtype,
165 ).to(device)
166 lora_weights = torch.rand(
167 (lora_nums, hidden_size, max_rank), # col-major
168 dtype=dtype,
169 ).to(device)
170 # expand op needs to complete y+=a@lora_b, so output is
171 # initinized randomly
172 ref_out_tensor = torch.rand(
173 (total_tokens, hidden_size),
174 dtype=dtype,
175 ).to(device)
176 # Ensure the same input.
177 our_out_tensor = ref_out_tensor.clone()
178 lora_indices_tensor = torch.randint(
179 0, lora_nums - 1 if lora_nums > 1 else 1, (batches,)
180 ).to(device)
181 indices = torch.zeros((total_tokens), dtype=torch.long).to(device)
182 current_offset = 0
183 for b_id in range(batches):
184 lora_index = lora_indices_tensor[b_id]
185 indices[current_offset : current_offset + seq_len_tensor[b_id]].copy_(
186 lora_index
187 )
188 current_offset += seq_len_tensor[b_id].item()
189
190 return PunicaTensors(
191 inputs_tensor,
192 lora_weights,
193 our_out_tensor,
194 ref_out_tensor,
195 b_seq_start_loc,
196 lora_indices_tensor,
197 seq_len_tensor,
198 indices,
199 )
200
201
202 def generate_data_for_expand_nslices(
203 batches,
204 hidden_size,
205 lora_nums,
206 max_rank,
207 seq_length,
208 dtype,
209 nslices,
210 device,
211 ) -> PunicaTensors:
212 seq_len_tensor = torch.randint(seq_length, seq_length + 1, (batches,)).to(device)
213 b_seq_start_loc = torch.cumsum(
214 torch.tensor([0] + seq_len_tensor[:-1].tolist(), dtype=torch.long),
215 dim=0,
216 ).to(device)
217 total_tokens = seq_len_tensor.sum()
218 inputs_tensor = torch.rand(
219 (total_tokens, max_rank),
220 dtype=dtype,
221 ).to(device)
222 lora_weights_lst = []
223 for _ in range(nslices):
224 lora_weights_lst.append(
225 torch.rand(
226 (lora_nums, hidden_size, max_rank), # col-major
227 dtype=dtype,
228 ).to(device)
229 )
230 # expand op needs to complete y+=a@lora_b, so output is
231 # initinized randomly
232 ref_out_tensor = torch.rand((total_tokens, hidden_size * nslices), dtype=dtype).to(
233 device
234 )
235 # Ensure the same input.
236 our_out_tensor = ref_out_tensor.clone()
237 lora_indices_tensor = torch.randint(
238 0, lora_nums - 1 if lora_nums > 1 else 1, (batches,)
239 )
240 indices = torch.zeros((total_tokens), dtype=torch.long).to(device)
241 current_offset = 0
242 for b_id in range(batches):
243 lora_index = lora_indices_tensor[b_id]
244 indices[current_offset : current_offset + seq_len_tensor[b_id]] = (
245 lora_index.item()
246 )
247 current_offset += seq_len_tensor[b_id].item()
248
249 lora_indices_tensor = lora_indices_tensor.to(device)
250 return PunicaTensors(
251 inputs_tensor,
252 lora_weights_lst,
253 our_out_tensor,
254 ref_out_tensor,
255 b_seq_start_loc,
256 lora_indices_tensor,
257 seq_len_tensor,
258 indices,
259 )
260
261
262 def generate_data_for_nslices(
263 batches,
264 hidden_size,
265 lora_nums,
266 max_rank,
267 seq_length,
268 nslices,
269 dtype,
270 op_type,
271 device,
272 ) -> PunicaTensors:
273 seq_len_tensor = torch.randint(seq_length, seq_length + 1, (batches,)).to(device)
274 b_seq_start_loc = torch.cumsum(
275 torch.tensor([0] + seq_len_tensor[:-1].tolist(), dtype=torch.long),
276 dim=0,
277 ).to(device)
278 total_tokens = seq_len_tensor.sum()
279
280 lora_weights_lst = []
281 if op_type == "shrink":
282 inputs_tensor = torch.rand((total_tokens, hidden_size), dtype=dtype).to(device)
283
284 for _ in range(nslices):
285 if op_type == "shrink":
286 lora_weights_lst.append(
287 torch.rand(
288 (lora_nums, max_rank, hidden_size), # col-major
289 dtype=dtype,
290 ).to(device)
291 )
292 # NOTE shrink kernel using torch.float32 as output type
293 # shrink op need atomic_add, so output is initinized by 0
294 our_out_tensor = torch.zeros(
295 (nslices, total_tokens, max_rank),
296 dtype=torch.float32,
297 ).to(device)
298 else:
299 inputs_tensor = torch.rand(
300 (nslices, total_tokens, max_rank),
301 dtype=dtype,
302 ).to(device)
303 for _ in range(nslices):
304 lora_weights_lst.append(
305 torch.rand(
306 (lora_nums, hidden_size, max_rank), # col-major
307 dtype=dtype,
308 ).to(device)
309 )
310 # expand op needs to complete y+=a@lora_b, so output is
311 # initinized randomly
312 our_out_tensor = torch.rand(
313 (total_tokens, hidden_size * nslices), dtype=dtype
314 ).to(device)
315
316 # Ensure the same input.
317 ref_out_tensor = our_out_tensor.clone()
318 lora_indices_tensor = torch.randint(
319 0, lora_nums - 1 if lora_nums > 1 else 1, (batches,)
320 )
321 indices = torch.zeros((total_tokens), dtype=torch.long).to(device)
322 current_offset = 0
323 for b_id in range(batches):
324 lora_index = lora_indices_tensor[b_id]
325 indices[current_offset : current_offset + seq_len_tensor[b_id]] = (
326 lora_index.item()
327 )
328 current_offset += seq_len_tensor[b_id].item()
329
330 lora_indices_tensor = lora_indices_tensor.to(device)
331 return PunicaTensors(
332 inputs_tensor,
333 lora_weights_lst,
334 our_out_tensor,
335 ref_out_tensor,
336 b_seq_start_loc,
337 lora_indices_tensor,
338 seq_len_tensor,
339 indices,
340 )
341
342
343 def create_peft_lora(
344 model: torch.nn.Module,
345 save_dir: str,
346 target_modules: list[str],
347 rank: int = 8,
348 alpha: int = 16,
349 dropout: float = 0.1,
350 lora_dtype: torch.dtype = torch.float16,
351 ) -> dict[str, torch.Tensor]:
352 lora_weights = {}
353 adapter_config = {
354 "peft_type": "LORA",
355 "auto_mapping": None,
356 "base_model_name_or_path": "dummy_model",
357 "revision": None,
358 "task_type": "CAUSAL_LM",
359 "inference_mode": False,
360 "r": rank,
361 "lora_alpha": alpha,
362 "lora_dropout": dropout,
363 "fan_in_fan_out": False,
364 "bias": "none",
365 "modules_to_save": None,
366 "init_lora_weights": True,
367 "layers_to_transform": None,
368 "layers_pattern": None,
369 "target_modules": target_modules,
370 "exclude_modules": None,
371 "use_rslora": False,
372 "use_dora": False,
373 "loftq_config": None,
374 }
375
376 for module_name in target_modules:
377 module = model
378 for attr in module_name.split("."):
379 module = getattr(module, attr)
380
381 if hasattr(module, "input_size") and hasattr(module, "output_size"):
382 in_features = module.input_size
383 out_features = module.output_size
384
385 elif hasattr(module, "embedding_dim") and hasattr(module, "num_embeddings"):
386 # ParallelLMHead
387 in_features = module.embedding_dim
388 out_features = module.num_embeddings
389 else:
390 raise ValueError(f"Unable to determine dimensions for module {module_name}")
391
392 lora_A = torch.randn(rank, in_features, dtype=lora_dtype)
393
394 torch.nn.init.kaiming_uniform_(lora_A, a=5**0.5)
395
396 lora_B = torch.zeros(out_features, rank, dtype=lora_dtype)
397
398 # PEFT style
399 lora_weights[f"base_model.model.{module_name}.lora_A.weight"] = lora_A
400 lora_weights[f"base_model.model.{module_name}.lora_B.weight"] = lora_B
401
402 config_path = os.path.join(save_dir, "adapter_config.json")
403 with open(config_path, "w", encoding="utf-8") as f:
404 json.dump(adapter_config, f, indent=2, ensure_ascii=False)
405
406 weights_path = os.path.join(save_dir, "adapter_model.safetensors")
407 save_file(lora_weights, weights_path)
408
409 return lora_weights