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 15, 19:45 UTC
[V1] LoRA - Add triton kernels for V1 (#13096)
Signed-off-by: Varun Sundar Rabindranath <varun@neuralmagic.com> Co-authored-by: Varun Sundar Rabindranath <varun@neuralmagic.com>
Varun Sundar Rabindranath committed
Mar 10, 2025 at 17:27 UTC
5ff0d32580eb4daadadece5ee39d33f43f230108
11 files changed
+1162
-188
benchmarks/kernels/benchmark_lora.py
+141
-24
@@ -23,6 +23,7 @@ from vllm.lora.ops.triton_ops.bgmv_shrink import bgmv_shrink
23
from vllm.lora.ops.triton_ops.sgmv_expand import sgmv_expand
24
from vllm.lora.ops.triton_ops.sgmv_shrink import sgmv_shrink
25
from vllm.lora.ops.triton_ops.utils import _LORA_A_PTR_DICT, _LORA_B_PTR_DICT
26
+from vllm.lora.ops.triton_ops.v1 import V1KernelMeta, v1_expand, v1_shrink
27
from vllm.utils import FlexibleArgumentParser
28
29
DEFAULT_MODELS = list(WEIGHT_SHAPES.keys())
@@ -171,6 +172,8 @@ class OpType(Enum):
172
SGMV_EXPAND = auto()
173
BGMV_EXPAND = auto()
174
BGMV_EXPAND_SLICE = auto()
175
+ V1_SHRINK = auto()
176
+ V1_EXPAND = auto()
177
178
@staticmethod
179
def from_str(s: str) -> "OpType":
@@ -184,28 +187,43 @@ class OpType(Enum):
187
return OpType.BGMV_EXPAND
188
if s.lower() == "bgmv_expand_slice":
189
return OpType.BGMV_EXPAND_SLICE
190
+ if s.lower() == "v1_shrink":
191
+ return OpType.V1_SHRINK
192
+ if s.lower() == "v1_expand":
193
+ return OpType.V1_EXPAND
194
raise ValueError(f"Unrecognized str {s} to convert to OpType")
195
196
def is_shrink_fn(self) -> bool:
190
- return self in [OpType.SGMV_SHRINK, OpType.BGMV_SHRINK]
197
+ return self in [
198
+ OpType.SGMV_SHRINK, OpType.BGMV_SHRINK, OpType.V1_SHRINK
199
+ ]
200
201
def is_expand_fn(self) -> bool:
193
- return self in [OpType.SGMV_EXPAND, OpType.BGMV_EXPAND]
202
+ return self in [
203
+ OpType.SGMV_EXPAND, OpType.BGMV_EXPAND, OpType.V1_EXPAND
204
+ ]
205
206
def is_prefill_op(self) -> bool:
196
- return self in [OpType.SGMV_SHRINK, OpType.SGMV_EXPAND]
207
+ return self in [
208
+ OpType.SGMV_SHRINK, OpType.SGMV_EXPAND, OpType.V1_SHRINK,
209
+ OpType.V1_EXPAND
210
+ ]
211
212
def is_decode_op(self) -> bool:
213
return self in [
200
- OpType.BGMV_SHRINK, OpType.BGMV_EXPAND, OpType.BGMV_EXPAND_SLICE
214
+ OpType.BGMV_SHRINK, OpType.BGMV_EXPAND, OpType.BGMV_EXPAND_SLICE,
215
+ OpType.V1_SHRINK, OpType.V1_EXPAND
216
]
217
218
def is_expand_slice_fn(self) -> bool:
219
return self in [OpType.BGMV_EXPAND_SLICE]
220
221
def num_slices(self) -> list[int]:
207
- if self in [OpType.SGMV_EXPAND, OpType.SGMV_SHRINK]:
208
- # SGMV kernels supports slices
222
+ if self in [
223
+ OpType.SGMV_EXPAND, OpType.SGMV_SHRINK, OpType.V1_SHRINK,
224
+ OpType.V1_EXPAND
225
+ ]:
226
+ # SGMV kernels and v1 kernels supports slices
227
return [1, 2, 3]
228
if self in [OpType.BGMV_SHRINK, OpType.BGMV_EXPAND]:
229
return [1]
@@ -250,11 +268,13 @@ class OpType(Enum):
268
m, k, n = self.mkn(batch_size, seq_length, hidden_size, lora_rank)
269
270
b_shape = (num_loras, n, k) # col-major
253
- if self == OpType.SGMV_SHRINK:
254
- # SGMV shrink supports num_slices inherently in the kernel
271
+ if self in [OpType.SGMV_SHRINK, OpType.V1_SHRINK]:
272
+ # SGMV shrink and V1 shrink kernels support num_slices inherently
273
+ # in the kernel.
274
return ((m, k), b_shape, (num_slices, m, n))
256
- if self == OpType.SGMV_EXPAND:
257
- # SGMV expand supports num_slices inherently in the kernel
275
+ if self in [OpType.SGMV_EXPAND, OpType.V1_EXPAND]:
276
+ # SGMV expand and V1 expand kernels support num_slices inherently
277
+ # in the kernel
278
return ((num_slices, m, k), b_shape, (m, n * num_slices))
279
if self == OpType.BGMV_SHRINK:
280
return ((m, k), b_shape, (m, n))
@@ -281,25 +301,30 @@ class OpType(Enum):
301
return bgmv_expand
302
if self == OpType.BGMV_EXPAND_SLICE:
303
return emulate_bgmv_expand_slice
304
+ if self == OpType.V1_SHRINK:
305
+ return v1_shrink
306
+ if self == OpType.V1_EXPAND:
307
+ return v1_expand
308
+
309
raise ValueError(f"Unrecognized optype {self}")
310
311
def run_ref_group_gemm(self, output: torch.Tensor, input: torch.Tensor,
312
lora_weights: list[torch.Tensor],
313
**kwargs) -> Callable:
289
- """Each benchmark operation expected the input, lora_weights and outputs
314
+ """Each benchmark operation expects the input, lora_weights and outputs
315
in a slightly different format. Refer to self.matmul_shapes().
316
run_ref_group_gemm accounts for those differences in executing a
317
reference group gemm for correctness testing.
318
"""
319
w_dtype = lora_weights[0].dtype
320
num_slices = len(lora_weights)
296
- if self == OpType.SGMV_SHRINK:
321
+ if self in [OpType.SGMV_SHRINK, OpType.V1_SHRINK]:
322
for slice_idx in range(num_slices):
323
ref_group_gemm(ref_out=output[slice_idx, :],
324
input=input,
325
lora_weights=lora_weights[slice_idx],
326
**kwargs)
302
- if self == OpType.SGMV_EXPAND:
327
+ elif self in [OpType.SGMV_EXPAND, OpType.V1_EXPAND]:
328
hidden_size = lora_weights[0].shape[1]
329
for slice_idx in range(num_slices):
330
slice_offset = slice_idx * hidden_size
@@ -308,19 +333,19 @@ class OpType(Enum):
333
input=input[slice_idx].clone().to(dtype=w_dtype),
334
lora_weights=lora_weights[slice_idx],
335
**kwargs)
311
- if self == OpType.BGMV_SHRINK:
336
+ elif self == OpType.BGMV_SHRINK:
337
assert num_slices == 1
338
ref_group_gemm(ref_out=output,
339
input=input,
340
lora_weights=lora_weights[0],
341
**kwargs)
317
- if self == OpType.BGMV_EXPAND:
342
+ elif self == OpType.BGMV_EXPAND:
343
assert num_slices == 1
344
ref_group_gemm(ref_out=output,
345
input=input.clone().to(dtype=w_dtype),
346
lora_weights=lora_weights[0],
347
**kwargs)
323
- if self == OpType.BGMV_EXPAND_SLICE:
348
+ elif self == OpType.BGMV_EXPAND_SLICE:
349
hidden_size = lora_weights[0].shape[1]
350
for slice_idx in range(num_slices):
351
slice_offset = slice_idx * hidden_size
@@ -329,7 +354,8 @@ class OpType(Enum):
354
input=input[slice_idx].clone().to(dtype=w_dtype),
355
lora_weights=lora_weights[slice_idx],
356
**kwargs)
332
- raise ValueError(f"Unrecognized optype {self}")
357
+ else:
358
+ raise ValueError(f"Unrecognized optype {self}")
359
360
361
@dataclass
@@ -390,6 +416,8 @@ class BenchmarkTensors:
416
seq_start_loc: torch.Tensor
417
prompt_lora_mapping: torch.Tensor
418
token_lora_mapping: torch.Tensor
419
+ # v1 kernel metadata
420
+ v1_kernel_meta: Optional[V1KernelMeta] = None
421
422
def io_types(self) -> str:
423
return (f"{dtype_to_str(self.input.dtype)}x"
@@ -432,10 +460,19 @@ class BenchmarkTensors:
460
total_tokens, ctx.batch_size, prompt_lora_indices_tensor,
461
seq_len_tensor, "cpu")
462
463
+ v1_kernel_meta = None
464
+ if op_type in [OpType.V1_SHRINK, OpType.V1_EXPAND]:
465
+ v1_kernel_meta = V1KernelMeta.make(
466
+ max_loras=ctx.num_loras,
467
+ max_num_tokens=token_lora_indices_tensor.size(0),
468
+ device="cpu")
469
+ v1_kernel_meta.prepare_tensors(
470
+ token_lora_mapping=token_lora_indices_tensor)
471
+
472
return BenchmarkTensors(input_tensor, lora_weights, output_tensor,
473
seq_len_tensor, seq_start_loc_tensor,
474
prompt_lora_indices_tensor,
438
- token_lora_indices_tensor)
475
+ token_lora_indices_tensor, v1_kernel_meta)
476
477
def sanity_check(self) -> None:
478
"""
@@ -468,6 +505,13 @@ class BenchmarkTensors:
505
for i in range(len(self.lora_weights_lst)):
506
self.lora_weights_lst[i] = to_device(self.lora_weights_lst[i])
507
508
+ # v1 meta
509
+ if self.v1_kernel_meta:
510
+ for field_name in V1KernelMeta.__dataclass_fields__:
511
+ field = getattr(self.v1_kernel_meta, field_name)
512
+ assert isinstance(field, torch.Tensor)
513
+ setattr(self.v1_kernel_meta, field_name, to_device(field))
514
+
515
def metadata(self) -> tuple[int, int, int]:
516
"""
517
Return num_seqs, num_tokens and max_seq_len
@@ -667,6 +711,78 @@ class BenchmarkTensors:
711
})
712
return {'kwargs_list': kwargs_list}
713
714
+ def as_v1_shrink_kwargs(self) -> dict[str, Any]:
715
+ assert self.v1_kernel_meta is not None
716
+ self.sanity_check()
717
+ self.to_device(self.input.device)
718
+
719
+ _, num_tokens, _, num_slices = self.metadata()
720
+
721
+ # Sanity check matrix shapes.
722
+ i_shape, lw_shape, o_shape = self.input.shape, self.lora_weights_lst[
723
+ 0].shape, self.output.shape
724
+ # Expected input shape [num_tokens, hidden_size]
725
+ assert len(i_shape) == 2
726
+ assert i_shape[0] == num_tokens
727
+ hidden_size = i_shape[1]
728
+ # Expected lora weight shape [num_loras, lora_rank, hidden_size]
729
+ assert len(lw_shape) == 3
730
+ assert lw_shape[2] == hidden_size
731
+ lora_rank = lw_shape[1]
732
+ # Expected output shape [num_slices, num_tokens, lora_rank]
733
+ assert len(o_shape) == 3
734
+ assert o_shape == (num_slices, num_tokens, lora_rank)
735
+
736
+ return {
737
+ 'inputs': self.input,
738
+ 'lora_a_weights': self.lora_weights_lst,
739
+ 'output_tensor': self.output,
740
+ 'token_lora_mapping': self.v1_kernel_meta.token_lora_mapping,
741
+ 'token_indices_sorted_by_lora_ids':
742
+ self.v1_kernel_meta.token_indices_sorted_by_lora_ids,
743
+ 'num_tokens_per_lora': self.v1_kernel_meta.num_tokens_per_lora,
744
+ 'lora_token_start_loc': self.v1_kernel_meta.lora_token_start_loc,
745
+ 'lora_ids': self.v1_kernel_meta.active_lora_ids,
746
+ 'scaling': 1.0,
747
+ }
748
+
749
+ def as_v1_expand_kwargs(self, add_inputs: bool) -> dict[str, Any]:
750
+ assert self.v1_kernel_meta is not None
751
+ self.sanity_check()
752
+ self.to_device(self.input.device)
753
+
754
+ _, num_tokens, _, num_slices = self.metadata()
755
+
756
+ # Sanity check matrix shapes.
757
+ i_shape, lw_shape, o_shape = self.input.shape, self.lora_weights_lst[
758
+ 0].shape, self.output.shape
759
+ # Expected input shape : [num_slices, num_tokens, lora_rank]
760
+ assert len(i_shape) == 3
761
+ assert i_shape[0] == num_slices
762
+ assert i_shape[1] == num_tokens
763
+ lora_rank = i_shape[2]
764
+ # Expected lora weight shape : [num_lora, hidden_size, lora_rank]
765
+ assert len(lw_shape) == 3
766
+ assert lw_shape[2] == lora_rank
767
+ hidden_size = lw_shape[1]
768
+ # Expected output shape : [num_tokens, hidden_size * num_slices]
769
+ assert len(o_shape) == 2
770
+ assert o_shape == (num_tokens, hidden_size * num_slices)
771
+
772
+ return {
773
+ 'inputs': self.input,
774
+ 'lora_b_weights': self.lora_weights_lst,
775
+ 'output_tensor': self.output,
776
+ 'token_lora_mapping': self.v1_kernel_meta.token_lora_mapping,
777
+ 'token_indices_sorted_by_lora_ids':
778
+ self.v1_kernel_meta.token_indices_sorted_by_lora_ids,
779
+ 'num_tokens_per_lora': self.v1_kernel_meta.num_tokens_per_lora,
780
+ 'lora_token_start_loc': self.v1_kernel_meta.lora_token_start_loc,
781
+ 'lora_ids': self.v1_kernel_meta.active_lora_ids,
782
+ 'offset_start': 0,
783
+ 'add_inputs': add_inputs,
784
+ }
785
+
786
def bench_fn_kwargs(self,
787
op_type: OpType,
788
add_inputs: Optional[bool] = None) -> dict[str, Any]:
@@ -685,6 +801,10 @@ class BenchmarkTensors:
801
return self.as_bgmv_expand_kwargs(add_inputs)
802
if op_type == OpType.BGMV_EXPAND_SLICE:
803
return self.as_bgmv_expand_slice_kwargs(add_inputs)
804
+ if op_type == OpType.V1_SHRINK:
805
+ return self.as_v1_shrink_kwargs()
806
+ if op_type == OpType.V1_EXPAND:
807
+ return self.as_v1_expand_kwargs(add_inputs)
808
raise ValueError(f"Unrecognized optype {self}")
809
810
def test_correctness(self, op_type: OpType,
@@ -872,12 +992,9 @@ def run(args: argparse.Namespace, bench_ctxs: list[BenchmarkContext]):
992
timers = []
993
for bench_ctx in bench_ctxs:
994
for seq_len in args.seq_lengths:
875
- bench_ops: list[OpType] = []
876
- if seq_len == 1:
877
- # bench all decode ops
878
- bench_ops = [op for op in args.op_types if op.is_decode_op()]
879
- else:
880
- # bench all prefill ops
995
+ bench_ops: list[OpType] = args.op_types
996
+ if seq_len > 1:
997
+ # bench only prefill ops
998
bench_ops = [op for op in args.op_types if op.is_prefill_op()]
999
1000
seq_len_timers = []
tests/lora/test_layers.py
+50
-18
@@ -1,5 +1,6 @@
1
# SPDX-License-Identifier: Apache-2.0
2
3
+import importlib
4
import random
5
from copy import deepcopy
6
from dataclasses import dataclass
@@ -63,6 +64,36 @@ DEVICES = ([
64
# stages, so we need to verify this. prefill stage(True) or decode stage(False)
65
STAGES = [True, False]
66
67
+# With the inclusion of V1 tests (look at the run_with_both_engines_lora),
68
+# the tests in this file run twice, once with the V0 engine and then with
69
+# the V1 engine.
70
+# The NUM_RANDOM_SEEDS value was set to 10 before. It is cut to half
71
+# with the inclusion of V1 tests to maintain the CI test times.
72
+NUM_RANDOM_SEEDS = 5
73
+# The VOCAB_PARALLEL_EMBEDDING_TEST_NUM_RANDOM_SEEDS value was set to
74
+# 256 before. It is cut to half with the inclusion of V1 tests to maintain
75
+# the CI test times.
76
+VOCAB_PARALLEL_EMBEDDING_TEST_NUM_RANDOM_SEEDS = 128
77
+
78
+
79
+@pytest.fixture(autouse=True)
80
+def v1(run_with_both_engines_lora):
81
+ # Simple autouse wrapper to run both engines for each test
82
+ # This can be promoted up to conftest.py to run for every
83
+ # test in a package
84
+
85
+ # Reload punica_gpu as the kernels used are tied to engine type.
86
+ from vllm.lora.punica_wrapper import punica_gpu
87
+ importlib.reload(punica_gpu)
88
+
89
+ # Release any memory we might be holding on to. CI runs OOMs otherwise.
90
+ from vllm.lora.ops.triton_ops.utils import (_LORA_A_PTR_DICT,
91
+ _LORA_B_PTR_DICT)
92
+ _LORA_B_PTR_DICT.clear()
93
+ _LORA_A_PTR_DICT.clear()
94
+
95
+ yield
96
+
97
98
def get_random_id_to_index(num_loras: int,
99
num_slots: int,
@@ -226,7 +257,7 @@ def test_embeddings(dist_init, num_loras, device, vocab_size, stage) -> None:
257
258
torch.set_default_device(device)
259
max_loras = 8
229
- punica_wrapper = get_punica_wrapper(8192, 256, device)
260
+ punica_wrapper = get_punica_wrapper(8192, 256, device, max_loras=max_loras)
261
assert check_punica_wrapper(punica_wrapper)
262
lora_config = LoRAConfig(max_loras=max_loras,
263
max_lora_rank=8,
@@ -241,7 +272,7 @@ def test_embeddings(dist_init, num_loras, device, vocab_size, stage) -> None:
272
273
return embedding, lora_embedding
274
244
- for i in range(10):
275
+ for i in range(NUM_RANDOM_SEEDS):
276
set_random_seed(i)
277
278
id_to_index = get_random_id_to_index(num_loras, max_loras)
@@ -329,7 +360,7 @@ def test_embeddings_with_new_embeddings(dist_init, num_loras, device,
360
361
torch.set_default_device(device)
362
max_loras = 8
332
- punica_wrapper = get_punica_wrapper(8192, 256, device)
363
+ punica_wrapper = get_punica_wrapper(8192, 256, device, max_loras=max_loras)
364
assert check_punica_wrapper(punica_wrapper)
365
lora_config = LoRAConfig(max_loras=max_loras,
366
max_lora_rank=8,
@@ -353,7 +384,7 @@ def test_embeddings_with_new_embeddings(dist_init, num_loras, device,
384
385
return expanded_embedding, lora_embedding
386
356
- for i in range(10):
387
+ for i in range(NUM_RANDOM_SEEDS):
388
set_random_seed(i)
389
390
id_to_index = get_random_id_to_index(num_loras, max_loras)
@@ -468,7 +499,7 @@ def test_lm_head_logits_processor(dist_init, num_loras, device, vocab_size,
499
500
torch.set_default_device(device)
501
max_loras = 8
471
- punica_wrapper = get_punica_wrapper(8192, 256, device)
502
+ punica_wrapper = get_punica_wrapper(8192, 256, device, max_loras=max_loras)
503
assert check_punica_wrapper(punica_wrapper)
504
lora_config = LoRAConfig(max_loras=max_loras,
505
max_lora_rank=8,
@@ -490,7 +521,7 @@ def test_lm_head_logits_processor(dist_init, num_loras, device, vocab_size,
521
522
return linear, logits_processor, lora_logits_processor
523
493
- for i in range(10):
524
+ for i in range(NUM_RANDOM_SEEDS):
525
set_random_seed(i)
526
527
id_to_index = get_random_id_to_index(num_loras, max_loras)
@@ -600,10 +631,10 @@ def test_linear_replicated(dist_init, num_loras, device, stage,
631
if current_platform.is_cuda_alike():
632
torch.cuda.set_device(device)
633
634
+ max_loras = 8
635
torch.set_default_device(device)
604
- punica_wrapper = get_punica_wrapper(8192, 256, device)
636
+ punica_wrapper = get_punica_wrapper(8192, 256, device, max_loras=max_loras)
637
assert check_punica_wrapper(punica_wrapper)
606
- max_loras = 8
638
lora_config = LoRAConfig(max_loras=max_loras,
639
max_lora_rank=8,
640
lora_dtype=torch.float16,
@@ -627,7 +658,7 @@ def test_linear_replicated(dist_init, num_loras, device, stage,
658
assert lora_linear.lora_bias_stacked is None
659
return linear, lora_linear
660
630
- for i in range(10):
661
+ for i in range(NUM_RANDOM_SEEDS):
662
set_random_seed(i)
663
664
id_to_index = get_random_id_to_index(num_loras, max_loras)
@@ -716,10 +747,10 @@ def test_linear_parallel(dist_init, num_loras, orientation, fully_shard,
747
if current_platform.is_cuda_alike():
748
torch.cuda.set_device(device)
749
750
+ max_loras = 8
751
torch.set_default_device(device)
720
- punica_wrapper = get_punica_wrapper(8192, 256, device)
752
+ punica_wrapper = get_punica_wrapper(8192, 256, device, max_loras=max_loras)
753
assert check_punica_wrapper(punica_wrapper)
722
- max_loras = 8
754
lora_config = LoRAConfig(max_loras=max_loras,
755
max_lora_rank=8,
756
fully_sharded_loras=fully_shard,
@@ -753,7 +784,7 @@ def test_linear_parallel(dist_init, num_loras, orientation, fully_shard,
784
assert lora_linear.lora_bias_stacked is None
785
return linear, lora_linear
786
756
- for i in range(10):
787
+ for i in range(NUM_RANDOM_SEEDS):
788
set_random_seed(i)
789
790
id_to_index = get_random_id_to_index(num_loras, max_loras)
@@ -842,10 +873,10 @@ def test_column_parallel_packed(dist_init, num_loras, repeats, fully_shard,
873
if current_platform.is_cuda_alike():
874
torch.cuda.set_device(device)
875
876
+ max_loras = 8
877
torch.set_default_device(device)
846
- punica_wrapper = get_punica_wrapper(8192, 256, device)
878
+ punica_wrapper = get_punica_wrapper(8192, 256, device, max_loras=max_loras)
879
assert check_punica_wrapper(punica_wrapper)
848
- max_loras = 8
880
lora_config = LoRAConfig(max_loras=max_loras,
881
max_lora_rank=8,
882
fully_sharded_loras=fully_shard,
@@ -900,7 +931,7 @@ def test_column_parallel_packed(dist_init, num_loras, repeats, fully_shard,
931
assert lora_linear.lora_bias_stacked is None
932
return linear, lora_linear
933
903
- for i in range(10):
934
+ for i in range(NUM_RANDOM_SEEDS):
935
set_random_seed(i)
936
937
id_to_index = get_random_id_to_index(num_loras, max_loras)
@@ -1002,12 +1033,12 @@ def test_rotary_embedding_long_context(dist_init, num_loras, device,
1033
is_neox_style, rotary_dim, head_size,
1034
seq_len) -> None:
1035
dtype = torch.float16
1036
+ max_loras = 8
1037
seed = 0
1038
current_platform.seed_everything(seed)
1039
torch.set_default_device(device)
1008
- punica_wrapper = get_punica_wrapper(8192, 256, device)
1040
+ punica_wrapper = get_punica_wrapper(8192, 256, device, max_loras=max_loras)
1041
assert check_punica_wrapper(punica_wrapper)
1010
- max_loras = 8
1042
lora_config = LoRAConfig(max_loras=max_loras,
1043
max_lora_rank=8,
1044
long_lora_scaling_factors=scaling_factors,
@@ -1083,7 +1114,8 @@ def test_rotary_embedding_long_context(dist_init, num_loras, device,
1114
1115
1116
@pytest.mark.parametrize("tp_size", [1, 2, 4, 8])
1086
-@pytest.mark.parametrize("seed", list(range(256)))
1117
+@pytest.mark.parametrize(
1118
+ "seed", list(range(VOCAB_PARALLEL_EMBEDDING_TEST_NUM_RANDOM_SEEDS)))
1119
def test_vocab_parallel_embedding_indices(tp_size, seed):
1120
random.seed(seed)
1121
vocab_size = random.randint(4000, 64000)
tests/lora/test_punica_ops.py
+125
-80
@@ -5,10 +5,12 @@ import pytest
5
import torch
6
7
import vllm.lora.ops.triton_ops # noqa: F401
8
+import vllm.lora.ops.triton_ops.v1 # noqa: F401
9
from vllm.lora.ops.torch_ops import (bgmv_expand, bgmv_expand_slice,
10
bgmv_shrink, sgmv_expand,
11
sgmv_expand_slice, sgmv_shrink)
12
from vllm.lora.ops.triton_ops.utils import _LORA_A_PTR_DICT, _LORA_B_PTR_DICT
13
+from vllm.lora.ops.triton_ops.v1 import V1KernelMeta
14
from vllm.platforms import current_platform
15
16
from .utils import (PunicaTensors, assert_close, generate_data,
@@ -91,12 +93,12 @@ def sgmv_expand_for_nslices(nslices: int, hidden_size: int,
93
_dict_lock = Lock()
94
95
94
-def check_sgmv_shrink(batches: int, num_loras: int, rank: int,
95
- hidden_size: int, nslices: int, dtype: torch.dtype,
96
- device: str, seq_length: int, scaling: float):
96
+def check_shrink_kernels(batches: int, num_loras: int, rank: int,
97
+ hidden_size: int, nslices: int, dtype: torch.dtype,
98
+ device: str, seq_length: int, scaling: float):
99
"""
98
- Compare outputs of vllm.sgmv_shrink kernel against a reference
99
- implementation.
100
+ Compare outputs of vllm.sgmv_shrink and vllm.v1_shrink kernel against a
101
+ reference implementation.
102
"""
103
data: PunicaTensors = generate_data_for_nslices(
104
batches,
@@ -111,44 +113,63 @@ def check_sgmv_shrink(batches: int, num_loras: int, rank: int,
113
)
114
max_seq_length, token_nums = data.meta()
115
116
+ # Setup metadata information for SGMV and reference kernels
117
+ sgmv_meta_args = (data.b_seq_start_loc, data.seq_len_tensor,
118
+ data.prompt_lora_mapping, batches, max_seq_length,
119
+ token_nums)
120
+
121
+ # Setup metadata information for the V1 kernel.
122
+ v1_meta = V1KernelMeta.make(max_loras=num_loras,
123
+ max_num_tokens=token_nums,
124
+ device='cuda')
125
+ v1_meta.prepare_tensors(data.token_lora_mapping)
126
+
127
+ ref_out_tensor = data.ref_out_tensor
128
+ sgmv_out_tensor = data.our_out_tensor
129
+ v1_out_tensor = data.our_out_tensor.clone()
130
+
131
# Preventing cache error pointer.
132
with _dict_lock:
133
+ # SGMV shrink kernel
134
_LORA_A_PTR_DICT.clear()
135
torch.ops.vllm.sgmv_shrink(
136
data.inputs_tensor,
137
data.lora_weights,
120
- data.our_out_tensor,
121
- data.b_seq_start_loc,
122
- data.seq_len_tensor,
123
- data.prompt_lora_mapping,
124
- batches,
125
- max_seq_length,
126
- token_nums,
138
+ sgmv_out_tensor,
139
+ *sgmv_meta_args,
140
scaling,
141
)
142
130
- sgmv_shrink_for_nslices(
131
- nslices,
143
+ # V1 shrink kernel
144
+ _LORA_A_PTR_DICT.clear()
145
+ torch.ops.vllm.v1_shrink(
146
data.inputs_tensor,
147
data.lora_weights,
134
- data.ref_out_tensor,
135
- data.b_seq_start_loc,
136
- data.seq_len_tensor,
137
- data.prompt_lora_mapping,
138
- batches,
139
- max_seq_length,
140
- token_nums,
148
+ v1_out_tensor,
149
+ *v1_meta.meta_args(token_nums=token_nums),
150
scaling,
151
)
143
- assert_close(data.our_out_tensor, data.ref_out_tensor)
152
+
153
+ # Reference
154
+ sgmv_shrink_for_nslices(
155
+ nslices,
156
+ data.inputs_tensor,
157
+ data.lora_weights,
158
+ ref_out_tensor,
159
+ *sgmv_meta_args,
160
+ scaling,
161
+ )
162
+
163
+ assert_close(sgmv_out_tensor, ref_out_tensor)
164
+ assert_close(v1_out_tensor, ref_out_tensor)
165
166
146
-def check_sgmv_expand(batches: int, num_loras: int, rank: int,
147
- hidden_size: int, nslices: int, dtype: torch.dtype,
148
- device: str, seq_length: int, add_inputs: bool):
167
+def check_expand_kernels(batches: int, num_loras: int, rank: int,
168
+ hidden_size: int, nslices: int, dtype: torch.dtype,
169
+ device: str, seq_length: int, add_inputs: bool):
170
"""
150
- Compare outputs of vllm.sgmv_expand kernel against a reference
151
- implementation.
171
+ Compare outputs of vllm.sgmv_expand and vllm.v1_expand kernels against a
172
+ reference implementation.
173
"""
174
data: PunicaTensors = generate_data_for_nslices(
175
batches,
@@ -164,36 +185,54 @@ def check_sgmv_expand(batches: int, num_loras: int, rank: int,
185
186
max_seq_length, token_nums = data.meta()
187
188
+ # Setup metadata information for SGMV and reference kernels
189
+ sgmv_meta_args = (data.b_seq_start_loc, data.seq_len_tensor,
190
+ data.prompt_lora_mapping, batches, max_seq_length,
191
+ token_nums)
192
+
193
+ # Setup metadata information for the V1 kernel.
194
+ v1_meta = V1KernelMeta.make(max_loras=num_loras,
195
+ max_num_tokens=token_nums,
196
+ device='cuda')
197
+ v1_meta.prepare_tensors(data.token_lora_mapping)
198
+
199
+ # Setup output tensors
200
+ ref_out_tensor = data.ref_out_tensor
201
+ sgmv_out_tensor = data.our_out_tensor
202
+ v1_out_tensor = data.our_out_tensor.clone()
203
+
204
with _dict_lock:
205
+ # SGMV expand kernel
206
_LORA_B_PTR_DICT.clear()
207
torch.ops.vllm.sgmv_expand(
208
data.inputs_tensor,
209
data.lora_weights,
172
- data.our_out_tensor,
173
- data.b_seq_start_loc,
174
- data.seq_len_tensor,
175
- data.prompt_lora_mapping,
176
- batches,
177
- max_seq_length,
178
- token_nums,
210
+ sgmv_out_tensor,
211
+ *sgmv_meta_args,
212
offset_start=0,
213
add_inputs=add_inputs,
214
)
215
216
+ # V1 expand kernel
217
+ _LORA_B_PTR_DICT.clear()
218
+ torch.ops.vllm.v1_expand(data.inputs_tensor,
219
+ data.lora_weights,
220
+ v1_out_tensor,
221
+ *v1_meta.meta_args(token_nums=token_nums),
222
+ offset_start=0,
223
+ add_inputs=add_inputs)
224
+
225
+ # Reference
226
sgmv_expand_for_nslices(nslices,
227
hidden_size,
228
data.inputs_tensor,
229
data.lora_weights,
187
- data.ref_out_tensor,
188
- data.b_seq_start_loc,
189
- data.seq_len_tensor,
190
- data.prompt_lora_mapping,
191
- batches,
192
- max_seq_length,
193
- token_nums,
230
+ ref_out_tensor,
231
+ *sgmv_meta_args,
232
add_inputs=add_inputs)
233
196
- assert_close(data.our_out_tensor, data.ref_out_tensor)
234
+ assert_close(sgmv_out_tensor, ref_out_tensor)
235
+ assert_close(v1_out_tensor, ref_out_tensor)
236
237
238
def check_bgmv_shrink(batches: int, num_loras: int, rank: int,
@@ -439,7 +478,7 @@ SEED = [0]
478
@pytest.mark.parametrize("device", DEVICES)
479
@pytest.mark.parametrize("seed", SEED)
480
@pytest.mark.parametrize("op_type", ["shrink", "expand"])
442
-def test_punica_sgmv(
481
+def test_kernels(
482
batches: int,
483
num_loras: int,
484
rank: int,
@@ -450,29 +489,32 @@ def test_punica_sgmv(
489
seed: int,
490
op_type: str,
491
):
492
+ """
493
+ Tests SGMV and V1 kernels.
494
+ """
495
torch.set_default_device(device)
496
current_platform.seed_everything(seed)
497
498
if op_type == "shrink":
457
- check_sgmv_shrink(batches=batches,
458
- num_loras=num_loras,
459
- rank=rank,
460
- hidden_size=hidden_size,
461
- nslices=nslices,
462
- dtype=dtype,
463
- device=device,
464
- seq_length=128,
465
- scaling=0.5)
499
+ check_shrink_kernels(batches=batches,
500
+ num_loras=num_loras,
501
+ rank=rank,
502
+ hidden_size=hidden_size,
503
+ nslices=nslices,
504
+ dtype=dtype,
505
+ device=device,
506
+ seq_length=128,
507
+ scaling=0.5)
508
else:
467
- check_sgmv_expand(batches=batches,
468
- num_loras=num_loras,
469
- rank=rank,
470
- hidden_size=hidden_size,
471
- nslices=nslices,
472
- dtype=dtype,
473
- device=device,
474
- seq_length=128,
475
- add_inputs=True)
509
+ check_expand_kernels(batches=batches,
510
+ num_loras=num_loras,
511
+ rank=rank,
512
+ hidden_size=hidden_size,
513
+ nslices=nslices,
514
+ dtype=dtype,
515
+ device=device,
516
+ seq_length=128,
517
+ add_inputs=True)
518
519
520
@pytest.mark.parametrize("batches", hs_test_params['batches'])
@@ -484,7 +526,7 @@ def test_punica_sgmv(
526
@pytest.mark.parametrize("device", DEVICES)
527
@pytest.mark.parametrize("seed", SEED)
528
@pytest.mark.parametrize("op_type", ["shrink", "expand"])
487
-def test_punica_sgmv_hidden_size(
529
+def test_kernels_hidden_size(
530
batches: int,
531
num_loras: int,
532
rank: int,
@@ -495,29 +537,32 @@ def test_punica_sgmv_hidden_size(
537
seed: int,
538
op_type: str,
539
):
540
+ """
541
+ Tests SGMV and V1 kernels.
542
+ """
543
torch.set_default_device(device)
544
current_platform.seed_everything(seed)
545
546
if op_type == "shrink":
502
- check_sgmv_shrink(batches=batches,
503
- num_loras=num_loras,
504
- rank=rank,
505
- hidden_size=hidden_size,
506
- nslices=nslices,
507
- dtype=dtype,
508
- device=device,
509
- seq_length=128,
510
- scaling=0.5)
547
+ check_shrink_kernels(batches=batches,
548
+ num_loras=num_loras,
549
+ rank=rank,
550
+ hidden_size=hidden_size,
551
+ nslices=nslices,
552
+ dtype=dtype,
553
+ device=device,
554
+ seq_length=128,
555
+ scaling=0.5)
556
else:
512
- check_sgmv_expand(batches=batches,
513
- num_loras=num_loras,
514
- rank=rank,
515
- hidden_size=hidden_size,
516
- nslices=nslices,
517
- dtype=dtype,
518
- device=device,
519
- seq_length=128,
520
- add_inputs=True)
557
+ check_expand_kernels(batches=batches,
558
+ num_loras=num_loras,
559
+ rank=rank,
560
+ hidden_size=hidden_size,
561
+ nslices=nslices,
562
+ dtype=dtype,
563
+ device=device,
564
+ seq_length=128,
565
+ add_inputs=True)
566
567
568
@pytest.mark.parametrize("batches", test_params['batches'])
vllm/lora/models.py
+5
-3
@@ -326,9 +326,11 @@ class LoRAModelManager(AdapterModelManager):
326
self.lora_index_to_id: List[Optional[int]] = [None] * self.lora_slots
327
self.vocab_size = vocab_size
328
self.long_lora_context: Optional[LongContextLoRAContext] = None
329
- self.punica_wrapper = get_punica_wrapper(max_num_batched_tokens,
330
- max_batches=self.max_num_seqs,
331
- device=self.device)
329
+ self.punica_wrapper = get_punica_wrapper(
330
+ max_num_batched_tokens,
331
+ max_batches=self.max_num_seqs,
332
+ device=self.device,
333
+ max_loras=self.lora_config.max_loras)
334
# Scaling factor -> offset to the sin_cos_cache to it.
335
# Used for long context lora.
336
self.scaling_factor_to_offset: Dict[float, int] = {}
vllm/lora/ops/triton_ops/utils.py
+2
-2
@@ -54,7 +54,7 @@ _LORA_A_PTR_DICT: Dict[Tuple[int, ...], Tuple[torch.tensor, ...]] = {}
54
_LORA_B_PTR_DICT: Dict[Tuple[int, ...], Tuple[torch.tensor, ...]] = {}
55
56
57
-def _get_lora_a_ptr(lora_a_weights: List[torch.Tensor], device: str):
57
+def _get_lora_a_ptr(lora_a_weights: List[torch.Tensor], device: torch.device):
58
"""
59
`_LORA_A_PTR_DICT` collects the required information during `profile_run`,
60
After this, it remains constant and subsequent usage is through LUT.
@@ -100,7 +100,7 @@ def _get_lora_a_ptr(lora_a_weights: List[torch.Tensor], device: str):
100
101
102
def _get_lora_b_ptr(lora_weights: List[torch.Tensor], offset_start: int,
103
- device: str):
103
+ device: torch.device):
104
"""
105
`_LORA_B_PTR_DICT` collects the required information during `profile_run`,
106
After this, it remains constant and subsequent usage is through LUT.
vllm/lora/ops/triton_ops/v1/__init__.py
new
+11
@@ -0,0 +1,11 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+
3
+from vllm.lora.ops.triton_ops.v1.v1_expand import v1_expand
4
+from vllm.lora.ops.triton_ops.v1.v1_kernel_metadata import V1KernelMeta
5
+from vllm.lora.ops.triton_ops.v1.v1_shrink import v1_shrink
6
+
7
+__all__ = [
8
+ "v1_expand",
9
+ "v1_shrink",
10
+ "V1KernelMeta",
11
+]
\ No newline at end of file
vllm/lora/ops/triton_ops/v1/v1_expand.py
new
+282
@@ -0,0 +1,282 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+"""
3
+Based on:
4
+Chen, L., Ye, Z., Wu, Y., Zhuo, D., Ceze, L., & Krishnamurthy, A. (2023).
5
+Punica: Multi-Tenant LoRA Serving.
6
+https://arxiv.org/abs/2310.18547
7
+"""
8
+
9
+from typing import List
10
+
11
+import torch
12
+import triton
13
+import triton.language as tl
14
+
15
+from vllm.lora.ops.triton_ops.kernel_utils import do_expand_kernel
16
+from vllm.lora.ops.triton_ops.utils import _get_lora_b_ptr
17
+from vllm.utils import direct_register_custom_op
18
+
19
+
20
+@triton.jit
21
+def _v1_expand_kernel(
22
+ input_ptr,
23
+ lora_ptr,
24
+ out_ptr,
25
+ M,
26
+ N,
27
+ K,
28
+ token_indices_sorted_by_lora_ids,
29
+ num_tokens_per_lora,
30
+ lora_token_start_loc,
31
+ lora_ids,
32
+ slice_start_loc,
33
+ input_d0_stride,
34
+ input_d1_stride,
35
+ input_d2_stride, # 1
36
+ ls_d0_ptr,
37
+ ls_d1_ptr,
38
+ ls_d2_ptr, # 1
39
+ output_d0_stride,
40
+ output_d1_stride, # 1
41
+ output_hs_ptr,
42
+ BLOCK_M: tl.constexpr,
43
+ BLOCK_N: tl.constexpr,
44
+ BLOCK_K: tl.constexpr,
45
+ EVEN_K: tl.constexpr,
46
+ ADD_INPUTS: tl.constexpr,
47
+ CAST_TYPE: tl.constexpr,
48
+ SLICE_NUM: tl.constexpr,
49
+ SAME_STRIDE: tl.constexpr):
50
+
51
+ cta_n_num = tl.cdiv(N, BLOCK_N)
52
+ cta_m_num = tl.cdiv(M, BLOCK_M)
53
+
54
+ pid_mn = tl.program_id(axis=0)
55
+ pid_m = pid_mn % cta_m_num
56
+ pid_n = (pid_mn // cta_m_num) % cta_n_num
57
+
58
+ slice_id = tl.program_id(axis=1)
59
+ lora_idx = tl.program_id(axis=2)
60
+
61
+ lora_id = tl.load(lora_ids + lora_idx)
62
+ if lora_id == -1:
63
+ # Early exit for the no-lora case.
64
+ return
65
+
66
+ lora_m_size = tl.load(num_tokens_per_lora + lora_idx)
67
+
68
+ cta_m_offset = pid_m * BLOCK_M
69
+ if cta_m_offset >= lora_m_size:
70
+ # Early exit CTA.
71
+ return
72
+
73
+ # When the output dimensions of each slice are the same,cur_n=N, otherwise
74
+ # cur_n=tl.load(output_hs_ptr + slice_id), this situation exists in GQA's
75
+ # qkv linear.
76
+ curr_N = N if SAME_STRIDE else tl.load(output_hs_ptr + slice_id)
77
+ if pid_n * BLOCK_N >= curr_N:
78
+ # Early exit CTA.
79
+ return
80
+
81
+ # num rows this CTA should process.
82
+ cta_m_len = min(BLOCK_M, lora_m_size - cta_m_offset)
83
+
84
+ # Identify all rows that this CTA should process.
85
+ lora_m_indices_start = tl.load(lora_token_start_loc + lora_idx)
86
+ cta_lora_seq_indices = (token_indices_sorted_by_lora_ids +
87
+ lora_m_indices_start + cta_m_offset)
88
+
89
+ # Load all relevant row indices.
90
+ offset_m = tl.arange(0, BLOCK_M) % cta_m_len
91
+ ram = tl.load(cta_lora_seq_indices + offset_m)
92
+
93
+ do_expand_kernel(
94
+ pid_n,
95
+ lora_id,
96
+ slice_id,
97
+ input_ptr,
98
+ lora_ptr,
99
+ out_ptr,
100
+ curr_N,
101
+ K,
102
+ cta_m_len,
103
+ ram, # array identifying the rows of Input ptr to operate on
104
+ slice_start_loc,
105
+ # input ptr strides
106
+ input_d0_stride,
107
+ input_d1_stride,
108
+ input_d2_stride,
109
+ # lora ptr strides
110
+ ls_d0_ptr,
111
+ ls_d1_ptr,
112
+ ls_d2_ptr,
113
+ # out ptr strides
114
+ output_d0_stride,
115
+ output_d1_stride,
116
+ # constants
117
+ BLOCK_M,
118
+ BLOCK_N,
119
+ BLOCK_K,
120
+ SAME_STRIDE,
121
+ SLICE_NUM,
122
+ EVEN_K,
123
+ CAST_TYPE,
124
+ ADD_INPUTS)
125
+
126
+
127
+@torch.inference_mode()
128
+def _v1_expand(
129
+ inputs: torch.Tensor, # shape [num_slices, num_tokens, lora_rank]
130
+ lora_b_weights: List[
131
+ torch.Tensor], # shape [num_lora, hidden_size, lora_rank]
132
+ output_tensor: torch.
133
+ Tensor, # shape [num_tokens, hidden_size * num_slices]
134
+ token_lora_mapping: torch.Tensor, # shape [num_tokens]
135
+ token_indices_sorted_by_lora_ids: torch.Tensor, # shape [num_tokens]
136
+ num_tokens_per_lora: torch.Tensor, # shape [max-loras + 1]
137
+ lora_token_start_loc: torch.Tensor, # shape [max-loras + 2]
138
+ lora_ids: torch.Tensor, # shape [max-loras + 1]
139
+ offset_start: int = 0,
140
+ add_inputs: bool = False,
141
+) -> None:
142
+ """
143
+ Args:
144
+ inputs (torch.Tensor): input tensor
145
+ lora_b_weights (List[torch.Tensor]): lora'b weight
146
+ output_tensor (torch.Tensor): output tensor
147
+ token_lora_mapping (torch.Tensor): A tensor mapping each input token
148
+ to the lora-id related to that token. A value of -1 indicates that
149
+ LoRA doesn't apply to that token.
150
+ token_indices_sorted_by_lora_ids (torch.Tensor): Row/Token indices from
151
+ the A matrix grouped by LoRA IDs.
152
+ num_tokens_per_lora (torch.Tensor): num_tokens_per_lora[i] is the number
153
+ of tokens that are to be processed by LoRA ID lora_ids[i]
154
+ lora_token_start_loc (torch.Tensor): A cumulative sum of
155
+ num_tokens_per_lora. lora_token_start_loc[0] is always 0 so that
156
+ lora_token_start_loc[i], along with num_tokens_per_lora[i]
157
+ identifies the the region in token_indices_sorted_by_lora_ids that
158
+ LoRA lora_ids[i] should process.
159
+ lora_ids (torch.Tensor): LoRA ids to process.
160
+ offset_start (int, optional): Offset start for output_tensor.
161
+ Defaults to 0.
162
+ add_inputs (bool, optional): Whether to add the input tensor to the
163
+ output tensor. Defaults to False.
164
+ """
165
+ assert inputs.dtype in [torch.float16, torch.bfloat16, torch.float32]
166
+ for weight in lora_b_weights:
167
+ assert weight.dtype in [torch.float16, torch.bfloat16]
168
+
169
+ assert inputs.size(0) == len(lora_b_weights)
170
+ assert output_tensor.is_contiguous()
171
+
172
+ # metadata sanity check.
173
+ assert token_lora_mapping.size(0) == token_indices_sorted_by_lora_ids.size(
174
+ 0)
175
+ assert lora_ids.size(0) == num_tokens_per_lora.size(0)
176
+ assert lora_token_start_loc.size(0) == lora_ids.size(0) + 1
177
+
178
+ (slice_start_tensor, lora_ptr_tensor, lora_strides_d0_tensor,
179
+ lora_strides_d1_tensor, lora_strides_d2_tensor, hidden_sizes_tensor,
180
+ same_stride, MAX_N) = _get_lora_b_ptr(lora_b_weights, offset_start,
181
+ inputs.device)
182
+
183
+ K = lora_b_weights[0].shape[-1] # K= rank
184
+ M = inputs.size(1)
185
+ ADD_INPUTS = add_inputs
186
+ MAX_LORAS = lora_ids.size(0)
187
+ CAST_TYPE = False
188
+ NUM_SLICES = len(lora_b_weights)
189
+
190
+ # Triton kernel configs.
191
+ BLOCK_M = 64
192
+ BLOCK_N = 128
193
+ BLOCK_K = 16
194
+ NUM_WARPS = 4
195
+ NUM_CTAS = 1
196
+ NUM_STAGES = 2
197
+ MAX_NREG = None
198
+
199
+ EVEN_K = K % BLOCK_K == 0 # type: ignore
200
+
201
+ if inputs.dtype == torch.float32 and lora_b_weights[0].dtype in [
202
+ torch.float16,
203
+ torch.bfloat16,
204
+ ]:
205
+ CAST_TYPE = True
206
+
207
+ # TODO (varun): This grid formulation maximizes parallelization at the
208
+ # cost of wasteful thread block launch when only a few input tokens require
209
+ # LoRA. This might not be the best in all cases.
210
+ grid = (
211
+ triton.cdiv(M, BLOCK_M) * triton.cdiv(MAX_N, BLOCK_N),
212
+ NUM_SLICES,
213
+ # Each LoRA receives its own set of thread blocks for output
214
+ # computation. If some LoRA doesn't have any tokens to process, its
215
+ # thread blocks simply exit.
216
+ MAX_LORAS,
217
+ )
218
+
219
+ _v1_expand_kernel[grid](
220
+ inputs,
221
+ lora_ptr_tensor,
222
+ output_tensor,
223
+ M,
224
+ MAX_N,
225
+ K,
226
+ token_indices_sorted_by_lora_ids,
227
+ num_tokens_per_lora,
228
+ lora_token_start_loc,
229
+ lora_ids,
230
+ slice_start_tensor,
231
+ inputs.stride(0),
232
+ inputs.stride(1),
233
+ inputs.stride(2),
234
+ lora_strides_d0_tensor,
235
+ lora_strides_d1_tensor,
236
+ lora_strides_d2_tensor,
237
+ output_tensor.stride(0),
238
+ output_tensor.stride(1),
239
+ hidden_sizes_tensor,
240
+ BLOCK_M,
241
+ BLOCK_N,
242
+ BLOCK_K,
243
+ EVEN_K,
244
+ ADD_INPUTS,
245
+ CAST_TYPE,
246
+ NUM_SLICES,
247
+ same_stride,
248
+ num_warps=NUM_WARPS,
249
+ num_ctas=NUM_CTAS,
250
+ num_stages=NUM_STAGES,
251
+ maxnreg=MAX_NREG,
252
+ )
253
+
254
+ return
255
+
256
+
257
+def _v1_expand_fake(
258
+ inputs: torch.Tensor,
259
+ lora_b_weights: List[torch.Tensor],
260
+ output_tensor: torch.Tensor,
261
+ token_lora_mapping: torch.Tensor,
262
+ token_indices_sorted_by_lora_ids: torch.Tensor,
263
+ num_tokens_per_lora: torch.Tensor,
264
+ lora_token_start_loc: torch.Tensor,
265
+ lora_ids: torch.Tensor,
266
+ offset_start: int = 0,
267
+ add_inputs: bool = False,
268
+) -> None:
269
+ return
270
+
271
+
272
+try:
273
+ direct_register_custom_op(
274
+ op_name="v1_expand",
275
+ op_func=_v1_expand,
276
+ mutates_args=["output_tensor"],
277
+ fake_impl=_v1_expand_fake,
278
+ )
279
+ v1_expand = torch.ops.vllm.v1_expand
280
+
281
+except AttributeError:
282
+ v1_expand = _v1_expand
vllm/lora/ops/triton_ops/v1/v1_kernel_metadata.py
new
+117
@@ -0,0 +1,117 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+"""
3
+V1 LoRA kernels metadata preparation utilities.
4
+"""
5
+
6
+from dataclasses import dataclass
7
+from typing import Tuple, Union
8
+
9
+import torch
10
+
11
+
12
+@dataclass
13
+class V1KernelMeta:
14
+ token_lora_mapping: torch.Tensor
15
+ token_indices_sorted_by_lora_ids: torch.Tensor
16
+ active_lora_ids: torch.Tensor
17
+ num_tokens_per_lora: torch.Tensor
18
+ lora_token_start_loc: torch.Tensor
19
+
20
+ @staticmethod
21
+ def make(max_loras: int, max_num_tokens: int,
22
+ device: Union[torch.device, str]) -> "V1KernelMeta":
23
+
24
+ token_lora_mapping = torch.empty(max_num_tokens,
25
+ dtype=torch.int32,
26
+ device=device)
27
+
28
+ token_indices_sorted_by_lora_ids = torch.empty(max_num_tokens,
29
+ dtype=torch.int32,
30
+ device=device)
31
+
32
+ # +1 because "no-lora" is also a possibility
33
+ # example: let max_loras be 3, active_lora_ids of [-1, 0, 2, 1]
34
+ # is a possibility.
35
+ active_lora_ids = torch.empty(max_loras + 1,
36
+ dtype=torch.int32,
37
+ device=device)
38
+
39
+ # using running example, [3, 10, 5, 2] is a possibility.
40
+ num_tokens_per_lora = torch.zeros(max_loras + 1,
41
+ dtype=torch.int32,
42
+ device=device)
43
+
44
+ # +2 for this because, the first index is always 0.
45
+ # using running example, lora_token_start_loc
46
+ # is [0, 3, 13, 18, 20].
47
+ lora_token_start_loc = torch.zeros(max_loras + 2,
48
+ dtype=torch.int32,
49
+ device=device)
50
+ return V1KernelMeta(
51
+ token_lora_mapping=token_lora_mapping,
52
+ token_indices_sorted_by_lora_ids=token_indices_sorted_by_lora_ids,
53
+ active_lora_ids=active_lora_ids,
54
+ num_tokens_per_lora=num_tokens_per_lora,
55
+ lora_token_start_loc=lora_token_start_loc)
56
+
57
+ def _reset(self):
58
+ self.active_lora_ids.fill_(-1)
59
+ self.num_tokens_per_lora.fill_(0)
60
+ self.lora_token_start_loc.fill_(0)
61
+
62
+ def prepare_tensors(self, token_lora_mapping: torch.Tensor) -> None:
63
+ """
64
+ Prepare kernel metadata tensors for the current forward pass.
65
+
66
+ Args:
67
+ token_lora_tensor (torch.Tensor): Tensor containing lora indices
68
+ for each input token.
69
+ """
70
+
71
+ self._reset()
72
+
73
+ num_tokens = token_lora_mapping.size(0)
74
+
75
+ # copy token lora mapping
76
+ self.token_lora_mapping[:num_tokens].copy_(token_lora_mapping,
77
+ non_blocking=True)
78
+
79
+ # token_indices_sorted_by_lora_ids
80
+ _, token_indices_sorted_by_lora_ids = torch.sort(token_lora_mapping,
81
+ stable=True)
82
+ # start gpu transfer
83
+ self.token_indices_sorted_by_lora_ids[:num_tokens].copy_(
84
+ token_indices_sorted_by_lora_ids, non_blocking=True)
85
+
86
+ # active_lora_ids, num_tokens_per_lora
87
+ lora_ids, num_tokens_per_lora = torch.unique(token_lora_mapping,
88
+ sorted=False,
89
+ return_counts=True)
90
+ self.active_lora_ids[:lora_ids.size(0)].copy_(lora_ids,
91
+ non_blocking=True)
92
+ self.num_tokens_per_lora[:num_tokens_per_lora.size(0)].copy_(
93
+ num_tokens_per_lora, non_blocking=True)
94
+
95
+ # lora_token_start_loc
96
+ lora_token_start_loc = torch.cumsum(num_tokens_per_lora, dim=0)
97
+ self.lora_token_start_loc[1:1 + lora_token_start_loc.size(0)].copy_(
98
+ lora_token_start_loc, non_blocking=True)
99
+
100
+ def meta_args(
101
+ self, token_nums: int
102
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor,
103
+ torch.Tensor]:
104
+ """
105
+ This function returns the kernel metadata required for the current
106
+ forward pass execution of the kernel. The function returns all the
107
+ metadata required by the kernel, in order, as a tuple, so it can be
108
+ unpacked directly during the v1_shrink/v1_expand function call.
109
+
110
+ Args:
111
+ token_nums (int): Number of input tokens in the current forward
112
+ pass.
113
+ """
114
+ return (self.token_lora_mapping[:token_nums],
115
+ self.token_indices_sorted_by_lora_ids[:token_nums],
116
+ self.num_tokens_per_lora, self.lora_token_start_loc,
117
+ self.active_lora_ids)
vllm/lora/ops/triton_ops/v1/v1_shrink.py
new
+236
@@ -0,0 +1,236 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+"""
3
+Based on:
4
+Chen, L., Ye, Z., Wu, Y., Zhuo, D., Ceze, L., & Krishnamurthy, A. (2023).
5
+Punica: Multi-Tenant LoRA Serving.
6
+https://arxiv.org/abs/2310.18547
7
+"""
8
+
9
+from typing import List
10
+
11
+import torch
12
+import triton
13
+import triton.language as tl
14
+
15
+from vllm.lora.ops.triton_ops.kernel_utils import do_shrink_kernel
16
+from vllm.lora.ops.triton_ops.utils import _get_lora_a_ptr
17
+from vllm.utils import direct_register_custom_op
18
+
19
+
20
+@triton.jit
21
+def _v1_shrink_kernel(input_ptr, lora_ptr, out_ptr, M, N, K,
22
+ token_indices_sorted_by_lora_ids, num_tokens_per_lora,
23
+ lora_token_start_loc, lora_ids, scaling, input_d0_stride,
24
+ input_d1_stride, lora_d0_stride, lora_d1_stride,
25
+ lora_d2_stride, output_d0_stride, output_d1_stride,
26
+ output_d2_stride, BLOCK_M: tl.constexpr,
27
+ BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
28
+ EVEN_K: tl.constexpr, SPLIT_K: tl.constexpr,
29
+ SLICE_NUM: tl.constexpr):
30
+
31
+ cta_n_num = tl.cdiv(N, BLOCK_N)
32
+ cta_m_num = tl.cdiv(M, BLOCK_M)
33
+
34
+ pid_sk_m_n = tl.program_id(axis=0)
35
+ pid_sk = pid_sk_m_n % SPLIT_K
36
+ pid_m = (pid_sk_m_n // SPLIT_K) % cta_m_num
37
+ pid_n = pid_sk_m_n // (SPLIT_K * cta_m_num) % cta_n_num
38
+
39
+ slice_id = tl.program_id(axis=1)
40
+ lora_idx = tl.program_id(axis=2)
41
+
42
+ lora_id = tl.load(lora_ids + lora_idx)
43
+ if lora_id == -1:
44
+ # Early exit for the no-lora case.
45
+ return
46
+
47
+ lora_m_size = tl.load(num_tokens_per_lora + lora_idx)
48
+
49
+ cta_m_offset = pid_m * BLOCK_M
50
+ if cta_m_offset >= lora_m_size:
51
+ # Early exit CTA.
52
+ return
53
+
54
+ # num rows this CTA should process.
55
+ cta_m_len = min(BLOCK_M, lora_m_size - cta_m_offset)
56
+
57
+ # Identify all rows that this CTA should process.
58
+ lora_m_indices_start = tl.load(lora_token_start_loc + lora_idx)
59
+ cta_lora_seq_indices = (token_indices_sorted_by_lora_ids +
60
+ lora_m_indices_start + cta_m_offset)
61
+
62
+ # Load all relevant row indices.
63
+ offset_m = tl.arange(0, BLOCK_M) % cta_m_len
64
+ ram = tl.load(cta_lora_seq_indices + offset_m)
65
+
66
+ do_shrink_kernel(
67
+ pid_n,
68
+ pid_sk,
69
+ slice_id,
70
+ lora_id,
71
+ input_ptr,
72
+ lora_ptr,
73
+ out_ptr,
74
+ N,
75
+ K,
76
+ cta_m_len,
77
+ ram, # array identifying the rows of Input ptr to operate on
78
+ # input strides
79
+ input_d0_stride,
80
+ input_d1_stride,
81
+ # lora strides
82
+ lora_d0_stride,
83
+ lora_d1_stride,
84
+ lora_d2_stride,
85
+ # output strides
86
+ output_d0_stride,
87
+ output_d1_stride,
88
+ output_d2_stride,
89
+ scaling,
90
+ BLOCK_M,
91
+ BLOCK_N,
92
+ BLOCK_K,
93
+ EVEN_K,
94
+ SPLIT_K,
95
+ SLICE_NUM)
96
+
97
+
98
+@torch.inference_mode()
99
+def _v1_shrink(
100
+ inputs: torch.Tensor, # shape [num_tokens, hidden_size]
101
+ lora_a_weights: List[
102
+ torch.Tensor], # shape [num_loras, lora_rank, hidden_size]
103
+ output_tensor: torch.Tensor, # shape [num_slices, num_tokens, lora_rank]
104
+ token_lora_mapping: torch.Tensor, # shape [num_tokens]
105
+ token_indices_sorted_by_lora_ids: torch.Tensor, # shape [num_tokens]
106
+ num_tokens_per_lora: torch.Tensor, # shape [max-loras + 1]
107
+ lora_token_start_loc: torch.Tensor, # shape [max-loras + 2]
108
+ lora_ids: torch.Tensor, # shape [max-loras + 1]
109
+ scaling: float,
110
+) -> None:
111
+ """
112
+ Args:
113
+ inputs (torch.Tensor): Input tensor
114
+ lora_a_weights (List[torch.Tensor]): LoRA weights
115
+ output_tensor (torch.Tensor): output tensor
116
+ token_lora_mapping (torch.Tensor): A tensor mapping each input token
117
+ to the lora-id related to that token. A value of -1 indicates that
118
+ LoRA doesn't apply to that token.
119
+ token_indices_sorted_by_lora_ids (torch.Tensor): Row/Token indices from
120
+ the A matrix grouped by LoRA IDs.
121
+ num_tokens_per_lora (torch.Tensor): num_tokens_per_lora[i] is the number
122
+ of tokens that are to be processed by LoRA ID lora_ids[i]
123
+ lora_token_start_loc (torch.Tensor): A cumulative sum of
124
+ num_tokens_per_lora. lora_token_start_loc[0] is always 0 so that
125
+ lora_token_start_loc[i], along with num_tokens_per_lora[i]
126
+ identifies the region in token_indices_sorted_by_lora_ids that
127
+ LoRA lora_ids[i] should process.
128
+ lora_ids (torch.Tensor): LoRA ids to process.
129
+ scaling (float): Scaling factor.
130
+ """
131
+ assert inputs.dtype == lora_a_weights[0].dtype
132
+ assert inputs.dtype in [torch.float16, torch.bfloat16]
133
+ for weight in lora_a_weights:
134
+ assert weight.dtype in [torch.float16, torch.bfloat16]
135
+
136
+ assert inputs.size(1) == lora_a_weights[0].size(-1)
137
+ assert inputs.is_contiguous()
138
+ assert output_tensor.is_contiguous()
139
+
140
+ # metadata sanity check
141
+ assert token_lora_mapping.size(0) == token_indices_sorted_by_lora_ids.size(
142
+ 0)
143
+ assert lora_ids.size(0) == num_tokens_per_lora.size(0)
144
+ assert lora_token_start_loc.size(0) == lora_ids.size(0) + 1
145
+
146
+ (lora_ptr_tensor, lora_strides_d0, lora_strides_d1,
147
+ lora_strides_d2) = _get_lora_a_ptr(lora_a_weights, inputs.device)
148
+ N, K = lora_a_weights[0].shape[-2:] # K=hidden_size,N=rank
149
+ M = inputs.size(0)
150
+ NUM_SLICES = len(lora_a_weights)
151
+ MAX_LORAS = lora_ids.size(0)
152
+
153
+ # Triton kernel configs
154
+ BLOCK_M = 32
155
+ BLOCK_N = 16
156
+ BLOCK_K = 256 if M < 128 else 32
157
+ SPLIT_K = 64 if M < 128 else 8
158
+ NUM_WARPS = 4
159
+ NUM_CTAS = 1
160
+ NUM_STAGES = 2
161
+ MAX_NREG = None
162
+
163
+ EVEN_K = K % (BLOCK_K * SPLIT_K) == 0 # type: ignore
164
+
165
+ # TODO (varun): This grid formulation maximizes parallelization at the
166
+ # cost of wasteful thread block launch when only few of the input tokens
167
+ # require LoRA. This might not be the best in all cases.
168
+ grid = (
169
+ SPLIT_K * triton.cdiv(M, BLOCK_M) * triton.cdiv(N, BLOCK_N),
170
+ NUM_SLICES,
171
+ # Each LoRA receives its own set of thread blocks for output
172
+ # computation. If some LoRA doesn't have any tokens to process, its
173
+ # thread blocks exit early.
174
+ MAX_LORAS,
175
+ )
176
+
177
+ _v1_shrink_kernel[grid](
178
+ inputs,
179
+ lora_ptr_tensor,
180
+ output_tensor,
181
+ M,
182
+ N,
183
+ K,
184
+ token_indices_sorted_by_lora_ids,
185
+ num_tokens_per_lora,
186
+ lora_token_start_loc,
187
+ lora_ids,
188
+ scaling,
189
+ inputs.stride(0),
190
+ inputs.stride(1),
191
+ lora_strides_d0,
192
+ lora_strides_d1,
193
+ lora_strides_d2,
194
+ output_tensor.stride(0),
195
+ output_tensor.stride(1),
196
+ output_tensor.stride(2),
197
+ BLOCK_M,
198
+ BLOCK_N,
199
+ BLOCK_K,
200
+ EVEN_K,
201
+ SPLIT_K,
202
+ NUM_SLICES,
203
+ num_warps=NUM_WARPS,
204
+ num_ctas=NUM_CTAS,
205
+ num_stages=NUM_STAGES,
206
+ maxnreg=MAX_NREG,
207
+ )
208
+
209
+ return
210
+
211
+
212
+def _v1_shrink_fake(
213
+ inputs: torch.Tensor,
214
+ lora_a_weights: List[torch.Tensor],
215
+ output_tensor: torch.Tensor,
216
+ token_lora_mapping: torch.Tensor,
217
+ token_indices_sorted_by_lora_ids: torch.Tensor,
218
+ num_tokens_per_lora: torch.Tensor,
219
+ lora_token_start_loc: torch.Tensor,
220
+ lora_ids: torch.Tensor,
221
+ scaling: float,
222
+) -> None:
223
+ return
224
+
225
+
226
+try:
227
+ direct_register_custom_op(
228
+ op_name="v1_shrink",
229
+ op_func=_v1_shrink,
230
+ mutates_args=["output_tensor"],
231
+ fake_impl=_v1_shrink_fake,
232
+ )
233
+ v1_shrink = torch.ops.vllm.v1_shrink
234
+
235
+except AttributeError:
236
+ v1_shrink = _v1_shrink
vllm/lora/punica_wrapper/punica_gpu.py
+190
-58
@@ -6,24 +6,83 @@ Punica: Multi-Tenant LoRA Serving.
6
https://arxiv.org/abs/2310.18547
7
"""
8
9
-from typing import Optional, Tuple, Union, final
9
+from typing import TYPE_CHECKING, List, Optional, Tuple, Union, final
10
11
import torch
12
13
+import vllm.envs as env
14
+from vllm.lora.layers import LoRAMapping
15
from vllm.triton_utils import HAS_TRITON
16
17
if HAS_TRITON:
16
- from vllm.lora.ops.triton_ops import bgmv_expand
17
- from vllm.lora.ops.triton_ops import bgmv_expand_slice
18
- from vllm.lora.ops.triton_ops import bgmv_shrink
19
- from vllm.lora.ops.triton_ops import sgmv_expand
20
- from vllm.lora.ops.triton_ops import sgmv_shrink
18
+ if env.VLLM_USE_V1:
19
+ from vllm.lora.ops.triton_ops.v1 import (V1KernelMeta, v1_expand,
20
+ v1_shrink)
21
+ else:
22
+ from vllm.lora.ops.triton_ops import bgmv_expand
23
+ from vllm.lora.ops.triton_ops import bgmv_expand_slice
24
+ from vllm.lora.ops.triton_ops import bgmv_shrink
25
+ from vllm.lora.ops.triton_ops import sgmv_expand
26
+ from vllm.lora.ops.triton_ops import sgmv_shrink
27
28
from .punica_base import PunicaWrapperBase
29
30
+if TYPE_CHECKING:
31
+ # avoid circuit import
32
+ from vllm.lora.models import LongContextLoRAContext
33
+
34
+
35
+class V1KernelMixin:
36
+
37
+ def _v1_make_metadata(self, max_loras: int, max_num_batched_tokens: int,
38
+ max_batches: int, device: Union[torch.device, str]):
39
+ self.token_mapping_v1_meta = V1KernelMeta.make(max_loras,
40
+ max_num_batched_tokens,
41
+ device=device)
42
+ self.prompt_mapping_v1_meta = V1KernelMeta.make(max_loras,
43
+ max_batches,
44
+ device=device)
45
+
46
+ def _v1_prepare_metadata_tensors(self, token_lora_indices: torch.Tensor,
47
+ sampler_indices: torch.Tensor):
48
+ self.token_mapping_v1_meta.prepare_tensors(token_lora_indices)
49
+ self.prompt_mapping_v1_meta.prepare_tensors(sampler_indices)
50
+
51
+ def _v1_apply_shrink(
52
+ self,
53
+ y: torch.Tensor,
54
+ x: torch.Tensor,
55
+ w_t_all: Tuple[torch.Tensor, ...],
56
+ scale: float,
57
+ ):
58
+ v1_shrink(
59
+ x,
60
+ w_t_all,
61
+ y,
62
+ *self.token_mapping_v1_meta.meta_args(x.size(0)),
63
+ scale,
64
+ )
65
+
66
+ def _v1_apply_expand(
67
+ self,
68
+ y: torch.Tensor,
69
+ x: torch.Tensor,
70
+ w_t_all: Tuple[torch.Tensor, ...],
71
+ offset_start: int,
72
+ add_inputs: bool,
73
+ ):
74
+ v1_expand(
75
+ x,
76
+ w_t_all,
77
+ y,
78
+ *self.token_mapping_v1_meta.meta_args(x.size(0)),
79
+ offset_start=offset_start,
80
+ add_inputs=add_inputs,
81
+ )
82
+
83
84
@final
26
-class PunicaWrapperGPU(PunicaWrapperBase):
85
+class PunicaWrapperGPU(PunicaWrapperBase, V1KernelMixin):
86
"""
87
PunicaWrapperGPU is designed to manage and provide metadata for the punica
88
kernel. The main function is to maintain the state information for
@@ -35,6 +94,36 @@ class PunicaWrapperGPU(PunicaWrapperBase):
94
PunicaWrapperBase.__init__(self, max_num_batched_tokens, max_batches,
95
device)
96
97
+ self.max_loras = kwargs['max_loras']
98
+
99
+ if env.VLLM_USE_V1:
100
+ self._v1_make_metadata(self.max_loras, max_num_batched_tokens,
101
+ max_batches, device)
102
+
103
+ def update_metadata(
104
+ self,
105
+ mapping: LoRAMapping,
106
+ lora_index_to_id: List[Optional[int]],
107
+ max_loras: int,
108
+ vocab_size: int,
109
+ extra_vocab_size: int,
110
+ long_lora_context: Optional["LongContextLoRAContext"] = None,
111
+ **kwargs):
112
+
113
+ if env.VLLM_USE_V1:
114
+ self.is_prefill = mapping.is_prefill
115
+ self._update_base_metadata(mapping, lora_index_to_id, max_loras,
116
+ vocab_size, extra_vocab_size,
117
+ long_lora_context)
118
+ self._v1_prepare_metadata_tensors(self.token_lora_indices,
119
+ self.sampler_indices)
120
+ else:
121
+ # Forward to base class update_metadata
122
+ PunicaWrapperBase.update_metadata(self, mapping, lora_index_to_id,
123
+ max_loras, vocab_size,
124
+ extra_vocab_size,
125
+ long_lora_context, **kwargs)
126
+
127
def _apply_shrink_prefill(
128
self,
129
y: torch.Tensor,
@@ -66,7 +155,7 @@ class PunicaWrapperGPU(PunicaWrapperBase):
155
self,
156
y: torch.Tensor,
157
x: torch.Tensor,
69
- w_t_all: torch.Tensor,
158
+ w_t_all: Tuple[torch.Tensor, ...],
159
offset_start: int,
160
add_inputs: bool,
161
):
@@ -118,14 +207,21 @@ class PunicaWrapperGPU(PunicaWrapperBase):
207
208
x = x.view(-1, x.shape[-1])
209
121
- if self.is_prefill:
122
- # NOTE fused kernel
123
- self._apply_shrink_prefill(y, x, lora_a_stacked, scale)
210
+ if env.VLLM_USE_V1:
211
+ self._v1_apply_shrink(y, x, lora_a_stacked, scale) # type: ignore
212
else:
125
- # TODO fuse these kernels
126
- for slice_idx in range(len(lora_a_stacked)):
127
- self._apply_shrink_decode(y[slice_idx], x,
128
- lora_a_stacked[slice_idx], scale)
213
+ if self.is_prefill:
214
+ # NOTE fused kernel
215
+ self._apply_shrink_prefill(
216
+ y, # type: ignore
217
+ x,
218
+ lora_a_stacked,
219
+ scale)
220
+ else:
221
+ # TODO fuse these kernels
222
+ for slice_idx in range(len(lora_a_stacked)):
223
+ self._apply_shrink_decode(y[slice_idx], x,
224
+ lora_a_stacked[slice_idx], scale)
225
226
def add_expand(self,
227
y: torch.Tensor,
@@ -160,25 +256,38 @@ class PunicaWrapperGPU(PunicaWrapperBase):
256
if lora_bias_stacked is not None:
257
self._apply_bias(self.token_lora_indices, y, output_slices,
258
lora_bias_stacked)
163
- if self.is_prefill:
164
- # NOTE fused kernel
165
- self._apply_expand_prefill(y,
166
- x,
167
- lora_b_stacked,
168
- offset_start,
169
- add_inputs=True)
259
+
260
+ if env.VLLM_USE_V1:
261
+ # TODO (varun): Profile with add_inputs = False. i.e. move the
262
+ # addition out of the kernel
263
+ self._v1_apply_expand(
264
+ y,
265
+ x, # type: ignore
266
+ lora_b_stacked,
267
+ offset_start,
268
+ add_inputs=True)
269
else:
171
- # TODO fuse these kernels
172
- for slice_idx in range(len(lora_b_stacked)):
173
- self._apply_expand_decode(
270
+
271
+ if self.is_prefill:
272
+ # NOTE fused kernel
273
+ self._apply_expand_prefill(
274
y,
175
- x[slice_idx],
176
- lora_b_stacked[slice_idx],
275
+ x, # type: ignore
276
+ lora_b_stacked,
277
offset_start,
178
- output_slices[slice_idx],
179
- add_inputs=add_inputs,
180
- )
181
- offset_start += output_slices[slice_idx]
278
+ add_inputs=True)
279
+ else:
280
+ # TODO fuse these kernels
281
+ for slice_idx in range(len(lora_b_stacked)):
282
+ self._apply_expand_decode(
283
+ y,
284
+ x[slice_idx],
285
+ lora_b_stacked[slice_idx],
286
+ offset_start,
287
+ output_slices[slice_idx],
288
+ add_inputs=add_inputs,
289
+ )
290
+ offset_start += output_slices[slice_idx]
291
y = y.view_as(y_org)
292
293
def add_lora_embedding(self,
@@ -200,18 +309,24 @@ class PunicaWrapperGPU(PunicaWrapperBase):
309
add_inputs (bool): Default to True.
310
"""
311
203
- if self.is_prefill:
204
- sgmv_expand(
205
- x.unsqueeze(dim=0),
206
- [lora_b_stacked],
207
- y,
208
- *self.prefill_metadata,
209
- offset_start=0,
210
- add_inputs=add_inputs,
211
- )
312
+ if env.VLLM_USE_V1:
313
+ self._v1_apply_expand(y,
314
+ x.unsqueeze(dim=0), (lora_b_stacked, ),
315
+ offset_start=0,
316
+ add_inputs=add_inputs)
317
else:
213
- bgmv_expand(x, lora_b_stacked, y, self.token_lora_indices,
214
- add_inputs)
318
+ if self.is_prefill:
319
+ sgmv_expand(
320
+ x.unsqueeze(dim=0),
321
+ (lora_b_stacked, ),
322
+ y,
323
+ *self.prefill_metadata,
324
+ offset_start=0,
325
+ add_inputs=add_inputs,
326
+ )
327
+ else:
328
+ bgmv_expand(x, lora_b_stacked, y, self.token_lora_indices,
329
+ add_inputs)
330
331
def add_lora_linear(self,
332
y: torch.Tensor,
@@ -257,19 +372,25 @@ class PunicaWrapperGPU(PunicaWrapperBase):
372
r = lora_b_stacked[0].size(-1)
373
# We set the buffer to be float32 by default ,refer to:
374
# https://github.com/triton-lang/triton/issues/1387
260
- buffer = torch.zeros(
375
+ buffer = torch.zeros( # type: ignore
376
(len(output_slices), x.size(0), r),
377
dtype=torch.float32,
378
device=x.device,
379
)
265
- self.add_shrink(buffer, x, lora_a_stacked, scale, **kwargs)
266
- self.add_expand(y,
267
- buffer,
268
- lora_b_stacked,
269
- None,
270
- output_slices,
271
- add_inputs=True,
272
- **kwargs)
380
+ self.add_shrink(
381
+ buffer, # type: ignore
382
+ x,
383
+ lora_a_stacked,
384
+ scale,
385
+ **kwargs)
386
+ self.add_expand(
387
+ y,
388
+ buffer, # type: ignore
389
+ lora_b_stacked,
390
+ None,
391
+ output_slices,
392
+ add_inputs=True,
393
+ **kwargs)
394
395
def add_lora_logits(self,
396
y: torch.Tensor,
@@ -305,11 +426,22 @@ class PunicaWrapperGPU(PunicaWrapperBase):
426
buffer = torch.zeros((x.size(0), r),
427
dtype=torch.float32,
428
device=x.device)
308
- # LogitsProcessorWithLoRA always using bgmv.
309
- bgmv_shrink(x, lora_a_stacked, buffer, self.sampler_indices, scale)
310
- bgmv_expand(buffer,
311
- lora_b_stacked,
312
- y,
313
- self.sampler_indices,
314
- add_inputs=True)
429
+
430
+ if env.VLLM_USE_V1:
431
+ v1_shrink(x, [lora_a_stacked], buffer.unsqueeze(dim=0),
432
+ *self.prompt_mapping_v1_meta.meta_args(x.size(0)), scale)
433
+
434
+ v1_expand(buffer.unsqueeze(dim=0), [lora_b_stacked],
435
+ y,
436
+ *self.prompt_mapping_v1_meta.meta_args(buffer.size(0)),
437
+ add_inputs=True)
438
+ else:
439
+
440
+ # V0 LogitsProcessorWithLoRA always using bgmv.
441
+ bgmv_shrink(x, lora_a_stacked, buffer, self.sampler_indices, scale)
442
+ bgmv_expand(buffer,
443
+ lora_b_stacked,
444
+ y,
445
+ self.sampler_indices,
446
+ add_inputs=True)
447
y = y.view_as(y_org)
vllm/v1/worker/lora_model_runner_mixin.py
+3
-3
@@ -62,9 +62,9 @@ class LoRAModelRunnerMixin:
62
if not self.lora_manager:
63
raise RuntimeError("LoRA is not enabled.")
64
65
- # We dont make any distinction between prefills and decodes in the
66
- # scheduler. To that effect, set is_prefill to True so we use the
67
- # sgmv punica kernels always.
65
+ # Set is_prefill to True, so we always use the SGMV kernels.
66
+ # For cuda platforms, we have specialized triton kernels, and
67
+ # the cuda path ignores `is_prefill`.
68
lora_mapping = LoRAMapping(token_lora_mapping,
69
prompt_lora_mapping,
70
is_prefill=True)