A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 14, 05:30 UTC
[Core] Optimize LoRA weight loading (#25403)
Signed-off-by: Jee Jee Li <pandaleefree@gmail.com>
Jee Jee Li committed
Sep 23, 2025 at 18:19 UTC
273690a50ac2a5fa79fa7acc5077e49aa1af427e
10 files changed
+83
-83
tests/lora/test_layers.py
+14
-12
@@ -164,8 +164,8 @@ def populate_loras(
164
weight=layer_weights,
165
generate_embeddings_tensor=generate_embeddings_tensor,
166
)
167
- sublora.lora_b = sublora.lora_b[:, (sublora_len *
168
- i):(sublora_len * (i + 1))]
167
+ sublora.lora_b = sublora.lora_b[(sublora_len *
168
+ i):(sublora_len * (i + 1)), :]
169
sublora.optimize()
170
subloras.append(sublora)
171
@@ -304,9 +304,9 @@ def test_embeddings(dist_init, num_loras, device, vocab_size, stage) -> None:
304
result = embedding(input_)
305
after_a = F.embedding(
306
input_,
307
- lora.lora_a,
307
+ lora.lora_a.T,
308
)
309
- result += (after_a @ lora.lora_b)
309
+ result += (after_a @ lora.lora_b.T)
310
expected_results.append(result)
311
expected_result = torch.cat(expected_results)
312
@@ -445,9 +445,9 @@ def test_embeddings_with_new_embeddings(dist_init, num_loras, device,
445
result = expanded_embedding(input_)
446
after_a = F.embedding(
447
original_input_,
448
- lora.lora_a,
448
+ lora.lora_a.T,
449
)
450
- result += (after_a @ lora.lora_b)
450
+ result += (after_a @ lora.lora_b.T)
451
expected_results.append(result)
452
expected_result = torch.cat(expected_results)
453
@@ -575,7 +575,7 @@ def test_lm_head_logits_processor(dist_init, num_loras, device, vocab_size,
575
lm_head=linear,
576
embedding_bias=None)
577
result[:, vocab_size + embeddings_tensor_len:] = float("-inf")
578
- result += input_ @ lora.lora_a @ lora.lora_b * lora.scaling
578
+ result += input_ @ lora.lora_a.T @ lora.lora_b.T * lora.scaling
579
expected_results.append(result)
580
expected_result = torch.cat(expected_results)
581
logits_processor.org_vocab_size = vocab_size
@@ -692,9 +692,10 @@ def test_linear_replicated(
692
693
expected_results: list[torch.Tensor] = []
694
for input_, lora_id in zip(inputs, prompt_mapping):
695
+
696
lora = lora_dict[lora_id]
697
result = linear(input_)[0]
697
- result += input_ @ lora.lora_a @ lora.lora_b * lora.scaling
698
+ result += input_ @ lora.lora_a.T @ lora.lora_b.T * lora.scaling
699
expected_results.append(result)
700
expected_result = torch.cat(expected_results)
701
@@ -817,7 +818,7 @@ def test_linear_parallel(dist_init, num_loras, orientation, fully_shard,
818
for input_, lora_id in zip(inputs, prompt_mapping):
819
lora = lora_dict[lora_id]
820
result = linear(input_)[0]
820
- result += input_ @ lora.lora_a @ lora.lora_b * lora.scaling
821
+ result += input_ @ lora.lora_a.T @ lora.lora_b.T * lora.scaling
822
expected_results.append(result)
823
expected_result = torch.cat(expected_results)
824
@@ -965,9 +966,10 @@ def test_column_parallel_packed(dist_init, num_loras, repeats, fully_shard,
966
result = linear(input_)[0]
967
subloras = sublora_dict[lora_id]
968
for i, sublora in enumerate(subloras):
968
- result[:, sublora.lora_b.shape[1] * i:sublora.lora_b.shape[1] *
969
- (i + 1)] += (input_ @ sublora.lora_a @ sublora.lora_b *
970
- sublora.scaling)
969
+ result[:, sublora.lora_b.shape[0] * i:sublora.lora_b.shape[0] *
970
+ (i + 1)] += (
971
+ input_ @ sublora.lora_a.T @ sublora.lora_b.T *
972
+ sublora.scaling)
973
expected_results.append(result)
974
expected_result = torch.cat(expected_results)
975
tests/lora/test_lora_manager.py
+6
-6
@@ -63,9 +63,9 @@ def test_from_lora_tensors(sql_lora_files, device):
63
assert lora.lora_b is not None
64
assert lora.lora_a.device == torch.device(device)
65
assert lora.lora_b.device == torch.device(device)
66
- assert (lora.lora_a.shape[1] == lora.lora_b.shape[0]
66
+ assert (lora.lora_a.shape[0] == lora.lora_b.shape[1]
67
), f"{lora.lora_a.shape=}, {lora.lora_b.shape=}"
68
- assert lora.lora_a.shape[1] == 8
68
+ assert lora.lora_a.shape[0] == 8
69
embeddings_module = next(
70
(k for k in EMBEDDING_MODULES if k in module_name), None)
71
if embeddings_module:
@@ -86,8 +86,8 @@ def create_lora(lora_id: int, model: nn.Module, sub_modules: list[str],
86
name,
87
8,
88
16,
89
- torch.rand([w.shape[1], 8], device=device),
90
- torch.rand([8, w.shape[0]], device=device),
89
+ torch.rand([8, w.shape[1]], device=device),
90
+ torch.rand([w.shape[0], 8], device=device),
91
)
92
return LoRAModel(lora_id, 8, loras)
93
@@ -109,8 +109,8 @@ def create_packed_lora(
109
replaced_module_name,
110
8,
111
16,
112
- torch.rand([w.shape[1], 8], device=device),
113
- torch.rand([8, w.shape[0] // len(replaced_module_names)],
112
+ torch.rand([8, w.shape[1]], device=device),
113
+ torch.rand([w.shape[0] // len(replaced_module_names), 8],
114
device=device),
115
)
116
return LoRAModel(lora_id, 8, loras)
tests/lora/utils.py
+4
-4
@@ -36,10 +36,10 @@ class DummyLoRAManager:
36
module_name,
37
rank=rank,
38
lora_alpha=1,
39
- lora_a=torch.rand([weight.shape[1], rank],
39
+ lora_a=torch.rand([rank, weight.shape[1]],
40
dtype=weight.dtype,
41
device=self._device),
42
- lora_b=torch.rand([rank, weight.shape[0]],
42
+ lora_b=torch.rand([weight.shape[0], rank],
43
dtype=weight.dtype,
44
device=self._device),
45
)
@@ -67,8 +67,8 @@ class DummyLoRAManager:
67
module_name,
68
rank=rank,
69
lora_alpha=1,
70
- lora_a=torch.rand([input_dim, rank], device="cuda"),
71
- lora_b=torch.rand([rank, output_dim], device="cuda"),
70
+ lora_a=torch.rand([rank, input_dim], device="cuda"),
71
+ lora_b=torch.rand([output_dim, input_dim], device="cuda"),
72
embeddings_tensor=embeddings_tensor,
73
)
74
self.set_module_lora(module_name, lora)
vllm/lora/layers/base_linear.py
+5
-5
@@ -121,18 +121,18 @@ class BaseLinearLayerWithLoRA(BaseLayerWithLoRA):
121
lora_bias = self.slice_bias(lora_bias)
122
123
self.lora_a_stacked[0][index,
124
- 0, :lora_a.shape[1], :lora_a.shape[0]].copy_(
125
- lora_a.T, non_blocking=True)
124
+ 0, :lora_a.shape[0], :lora_a.shape[1]].copy_(
125
+ lora_a, non_blocking=True)
126
self.lora_b_stacked[0][index,
127
- 0, :lora_b.shape[1], :lora_b.shape[0]].copy_(
128
- lora_b.T, non_blocking=True)
127
+ 0, :lora_b.shape[0], :lora_b.shape[1]].copy_(
128
+ lora_b, non_blocking=True)
129
if lora_bias is not None:
130
131
self.lora_bias_stacked = cast(tuple[torch.Tensor, ...],
132
self.lora_bias_stacked)
133
assert len(self.lora_bias_stacked)
134
self.lora_bias_stacked[0][index, 0, :lora_bias.shape[0]].copy_(
135
- lora_bias.T, non_blocking=True)
135
+ lora_bias, non_blocking=True)
136
137
def apply(self,
138
x: torch.Tensor,
vllm/lora/layers/column_parallel_linear.py
+33
-34
@@ -99,13 +99,13 @@ class ColumnParallelLinearWithLoRA(BaseLinearLayerWithLoRA):
99
if self.is_merged_col_linear:
100
tp_rank = get_tensor_model_parallel_rank()
101
shard_size = self.output_size // 2
102
- offset = lora_b.shape[-1] // 2
102
+ offset = lora_b.shape[0] // 2
103
104
- left_weight = lora_b[:, tp_rank * shard_size:(tp_rank + 1) *
105
- shard_size]
106
- right_weight = lora_b[:, offset + tp_rank * shard_size:offset +
107
- (tp_rank + 1) * shard_size]
108
- lora_b = torch.cat([left_weight, right_weight], dim=1)
104
+ left_weight = lora_b[tp_rank * shard_size:(tp_rank + 1) *
105
+ shard_size, :]
106
+ right_weight = lora_b[offset + tp_rank * shard_size:offset +
107
+ (tp_rank + 1) * shard_size, :]
108
+ lora_b = torch.cat([left_weight, right_weight], dim=0)
109
# Applicable to cases where the base_layer is
110
# ColumnParallelLinear.
111
else:
@@ -113,7 +113,7 @@ class ColumnParallelLinearWithLoRA(BaseLinearLayerWithLoRA):
113
shard_size = self.output_size
114
start_idx = tensor_model_parallel_rank * shard_size
115
end_idx = (tensor_model_parallel_rank + 1) * shard_size
116
- lora_b = lora_b[:, start_idx:end_idx]
116
+ lora_b = lora_b[start_idx:end_idx, :]
117
return lora_b
118
119
def slice_bias(self, bias: torch.Tensor) -> torch.Tensor:
@@ -251,9 +251,8 @@ class MergedColumnParallelLinearWithLoRA(ColumnParallelLinearWithLoRA):
251
for i, (shard_id, shard_size) in enumerate(
252
zip(self.output_ids, self.output_slices)):
253
if (lora_b_i := lora_b[i]) is not None:
254
- sliced_lora_b[i] = lora_b_i[:,
255
- shard_size * shard_id:shard_size *
256
- (shard_id + 1)]
254
+ sliced_lora_b[i] = lora_b_i[shard_size * shard_id:shard_size *
255
+ (shard_id + 1), :]
256
return sliced_lora_b
257
258
def slice_bias(
@@ -285,12 +284,12 @@ class MergedColumnParallelLinearWithLoRA(ColumnParallelLinearWithLoRA):
284
for i in range(self.n_slices):
285
if (lora_a_i := lora_a[i]) is not None:
286
self.lora_a_stacked[i][
288
- index, 0, :lora_a_i.shape[1], :lora_a_i.shape[0]].copy_(
289
- lora_a_i.T, non_blocking=True)
287
+ index, 0, :lora_a_i.shape[0], :lora_a_i.shape[1]].copy_(
288
+ lora_a_i, non_blocking=True)
289
if (lora_b_i := lora_b[i]) is not None:
290
self.lora_b_stacked[i][
292
- index, 0, :lora_b_i.shape[1], :lora_b_i.shape[0]].copy_(
293
- lora_b_i.T, non_blocking=True)
291
+ index, 0, :lora_b_i.shape[0], :lora_b_i.shape[1]].copy_(
292
+ lora_b_i, non_blocking=True)
293
294
if lora_bias is not None:
295
self.lora_bias_stacked = cast(tuple[torch.Tensor, ...],
@@ -299,7 +298,7 @@ class MergedColumnParallelLinearWithLoRA(ColumnParallelLinearWithLoRA):
298
if (lora_bias_i := lora_bias[i]) is not None:
299
self.lora_bias_stacked[i][index,
300
0, :lora_bias_i.shape[0]].copy_(
302
- lora_bias_i.T,
301
+ lora_bias_i,
302
non_blocking=True)
303
304
@classmethod
@@ -345,18 +344,18 @@ class QKVParallelLinearWithLoRA(ColumnParallelLinearWithLoRA):
344
tp_rank = get_tensor_model_parallel_rank()
345
self.q_shard_id = tp_rank
346
self.kv_shard_id = tp_rank // self.base_layer.num_kv_head_replicas
348
- lora_b_q = lora_b[:, self.q_proj_shard_size *
347
+ lora_b_q = lora_b[self.q_proj_shard_size *
348
self.q_shard_id:self.q_proj_shard_size *
350
- (self.q_shard_id + 1)]
349
+ (self.q_shard_id + 1), :]
350
k_offset = self.q_proj_total_size
352
- lora_b_k = lora_b[:, k_offset +
351
+ lora_b_k = lora_b[k_offset +
352
self.kv_proj_shard_size * self.kv_shard_id:k_offset +
354
- self.kv_proj_shard_size * (self.kv_shard_id + 1)]
353
+ self.kv_proj_shard_size * (self.kv_shard_id + 1), :]
354
v_offset = k_offset + self.kv_proj_total_size
356
- lora_b_v = lora_b[:, v_offset +
355
+ lora_b_v = lora_b[v_offset +
356
self.kv_proj_shard_size * self.kv_shard_id:v_offset +
358
- self.kv_proj_shard_size * (self.kv_shard_id + 1)]
359
- lora_b = torch.cat([lora_b_q, lora_b_k, lora_b_v], dim=1)
357
+ self.kv_proj_shard_size * (self.kv_shard_id + 1), :]
358
+ lora_b = torch.cat([lora_b_q, lora_b_k, lora_b_v], dim=0)
359
return lora_b
360
361
def slice_bias(self, bias: torch.Tensor) -> torch.Tensor:
@@ -465,7 +464,7 @@ class ColumnParallelLinearWithShardedLoRA(ColumnParallelLinearWithLoRA):
464
tp_rank = get_tensor_model_parallel_rank()
465
shard_size = self.lora_a_stacked[0].shape[2]
466
start_idx = tp_rank * shard_size
468
- lora_a = lora_a[:, start_idx:start_idx + shard_size]
467
+ lora_a = lora_a[start_idx:start_idx + shard_size, :]
468
return lora_a
469
470
def apply(self,
@@ -508,10 +507,10 @@ class MergedColumnParallelLinearWithShardedLoRA(
507
output_shard_size = self.lora_a_stacked[0].shape[2]
508
output_start_idx = self.tp_rank * output_shard_size
509
lora_a = [
511
- lora_a[0][:, output_start_idx:output_start_idx +
512
- output_shard_size] if lora_a[0] is not None else None,
513
- lora_a[1][:, output_start_idx:output_start_idx +
514
- output_shard_size] if lora_a[1] is not None else None,
510
+ lora_a[0][output_start_idx:output_start_idx +
511
+ output_shard_size, :] if lora_a[0] is not None else None,
512
+ lora_a[1][output_start_idx:output_start_idx +
513
+ output_shard_size, :] if lora_a[1] is not None else None,
514
]
515
return lora_a
516
@@ -551,7 +550,7 @@ class QKVParallelLinearWithShardedLoRA(QKVParallelLinearWithLoRA):
550
tp_rank = get_tensor_model_parallel_rank()
551
shard_size = self.lora_a_stacked[0].shape[2]
552
start_idx = tp_rank * shard_size
554
- lora_a = lora_a[:, start_idx:start_idx + shard_size]
553
+ lora_a = lora_a[start_idx:start_idx + shard_size, :]
554
return lora_a
555
556
def apply(self,
@@ -589,12 +588,12 @@ class MergedQKVParallelLinearWithShardedLoRA(MergedQKVParallelLinearWithLoRA):
588
shard_size = [self.lora_a_stacked[i].shape[2] for i in range(3)]
589
start_idx = [self.tp_rank * shard_size[i] for i in range(3)]
590
lora_a = [
592
- lora_a[0][:, start_idx[0]:start_idx[0] +
593
- shard_size[0]] if lora_a[0] is not None else None,
594
- lora_a[1][:, start_idx[1]:start_idx[1] +
595
- shard_size[1]] if lora_a[1] is not None else None,
596
- lora_a[2][:, start_idx[2]:start_idx[2] +
597
- shard_size[2]] if lora_a[2] is not None else None,
591
+ lora_a[0][start_idx[0]:start_idx[0] +
592
+ shard_size[0], :] if lora_a[0] is not None else None,
593
+ lora_a[1][start_idx[1]:start_idx[1] +
594
+ shard_size[1], :] if lora_a[1] is not None else None,
595
+ lora_a[2][start_idx[2]:start_idx[2] +
596
+ shard_size[2], :] if lora_a[2] is not None else None,
597
]
598
return lora_a
599
vllm/lora/layers/logits_processor.py
+4
-4
@@ -140,11 +140,11 @@ class LogitsProcessorWithLoRA(BaseLayerWithLoRA):
140
):
141
self.reset_lora(index)
142
self.lora_a_stacked[index,
143
- 0, :lora_a.shape[1], :lora_a.shape[0]].copy_(
144
- lora_a.T, non_blocking=True)
143
+ 0, :lora_a.shape[0], :lora_a.shape[1]].copy_(
144
+ lora_a, non_blocking=True)
145
self.lora_b_stacked[index,
146
- 0, :lora_b.shape[1], :lora_b.shape[0]].copy_(
147
- lora_b.T, non_blocking=True)
146
+ 0, :lora_b.shape[0], :lora_b.shape[1]].copy_(
147
+ lora_b, non_blocking=True)
148
if embeddings_tensor is not None:
149
self.embeddings_tensors[
150
index,
vllm/lora/layers/row_parallel_linear.py
+2
-2
@@ -39,7 +39,7 @@ class RowParallelLinearWithLoRA(BaseLinearLayerWithLoRA):
39
shard_size = self.input_size
40
start_idx = self.tp_rank * shard_size
41
end_idx = (self.tp_rank + 1) * shard_size
42
- lora_a = lora_a[start_idx:end_idx, :]
42
+ lora_a = lora_a[:,start_idx:end_idx]
43
return lora_a
44
45
def slice_lora_b(self, lora_b: torch.Tensor) -> torch.Tensor:
@@ -122,7 +122,7 @@ class RowParallelLinearWithShardedLoRA(RowParallelLinearWithLoRA):
122
shard_size = self.lora_b_stacked[0].shape[2]
123
start_idx = self.tp_rank * shard_size
124
end_idx = (self.tp_rank + 1) * shard_size
125
- lora_b = lora_b[:, start_idx:end_idx]
125
+ lora_b = lora_b[ start_idx:end_idx,:]
126
return lora_b
127
128
def slice_bias(self, bias: torch.Tensor) -> torch.Tensor:
vllm/lora/layers/vocal_parallel_embedding.py
+6
-4
@@ -95,11 +95,13 @@ class VocabParallelEmbeddingWithLoRA(BaseLayerWithLoRA):
95
bias: Optional[torch.Tensor] = None,
96
):
97
self.reset_lora(index)
98
- self.lora_a_stacked[index, :lora_a.shape[0], :lora_a.shape[1]].copy_(
99
- lora_a, non_blocking=True)
98
+ # NOTE self.lora_a_stacked is row-major, and lora_a is col-major,
99
+ # so we need transpose here
100
+ self.lora_a_stacked[index, :lora_a.shape[1], :lora_a.shape[0]].copy_(
101
+ lora_a.T, non_blocking=True)
102
self.lora_b_stacked[index,
101
- 0, :lora_b.shape[1], :lora_b.shape[0]].copy_(
102
- lora_b.T, non_blocking=True)
103
+ 0, :lora_b.shape[0], :lora_b.shape[1]].copy_(
104
+ lora_b, non_blocking=True)
105
if embeddings_tensor is not None:
106
self.embeddings_tensors[
107
index,
vllm/lora/lora_weights.py
+2
-2
@@ -86,11 +86,11 @@ class LoRALayerWeights:
86
embeddings_tensor_dim: Optional[int] = None,
87
bias_enabled: Optional[bool] = False) -> "LoRALayerWeights":
88
pin_memory = str(device) == "cpu" and is_pin_memory_available()
89
- lora_a = torch.zeros([input_dim, rank],
89
+ lora_a = torch.zeros([rank, input_dim],
90
dtype=dtype,
91
device=device,
92
pin_memory=pin_memory)
93
- lora_b = torch.zeros([rank, output_dim],
93
+ lora_b = torch.zeros([output_dim, rank],
94
dtype=dtype,
95
device=device,
96
pin_memory=pin_memory)
vllm/lora/models.py
+7
-10
@@ -152,30 +152,29 @@ class LoRAModel:
152
module_name, peft_helper, lora_embeddings_tensor)
153
154
if is_bias:
155
- loras[module_name].bias = tensor.to(device=device,
156
- dtype=dtype).t()
157
- bias = tensor.to(device=device, dtype=dtype).t()
155
+ loras[module_name].bias = tensor.to(device=device, dtype=dtype)
156
+ bias = tensor.to(device=device, dtype=dtype)
157
if pin_memory:
158
bias = bias.pin_memory()
159
loras[module_name].bias = bias
160
elif is_lora_a:
161
loras[module_name].lora_a = tensor.to(device=device,
163
- dtype=dtype).t()
162
+ dtype=dtype)
163
if pin_memory:
164
loras[module_name].lora_a = loras[
165
module_name].lora_a.pin_memory()
166
else:
167
loras[module_name].lora_b = tensor.to(device=device,
169
- dtype=dtype).t()
168
+ dtype=dtype)
169
assert embedding_padding_modules is not None
170
if any(name in module_name
171
for name in embedding_padding_modules
172
) and target_embedding_padding is not None:
173
lora_b = loras[module_name].lora_b
175
- assert target_embedding_padding >= lora_b.shape[1]
176
- addition = target_embedding_padding - lora_b.shape[1]
174
+ assert target_embedding_padding >= lora_b.shape[0]
175
+ addition = target_embedding_padding - lora_b.shape[0]
176
loras[module_name].lora_b = torch.nn.functional.pad(
178
- lora_b, (0, addition))
177
+ lora_b, (0, 0, 0, addition))
178
if pin_memory:
179
loras[module_name].lora_b = loras[
180
module_name].lora_b.pin_memory()
@@ -585,7 +584,6 @@ class LoRAModelManager:
584
"cpu",
585
bias_enabled=bias_enabled,
586
)
588
- lora.optimize()
587
else:
588
parts = module_name.split(".")
589
replacements = self.packed_modules_mapping[parts[-1]]
@@ -600,7 +598,6 @@ class LoRAModelManager:
598
"cpu",
599
bias_enabled=bias_enabled,
600
)
603
- lora.optimize()
601
subloras.append(lora)
602
lora = PackedLoRALayerWeights.pack(subloras)
603
model.loras[module_name] = lora