| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 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 | import torch |
| 10 | |
| 11 | from vllm import envs |
| 12 | from vllm.lora.ops.triton_ops.kernel_utils import do_expand_kernel |
| 13 | from vllm.lora.ops.triton_ops.utils import ( |
| 14 | _get_lora_b_ptr, |
| 15 | get_lora_op_configs, |
| 16 | supports_pdl, |
| 17 | ) |
| 18 | from vllm.triton_utils import tl, triton |
| 19 | from vllm.utils.torch_utils import direct_register_custom_op |
| 20 | |
| 21 | |
| 22 | @triton.jit |
| 23 | def _lora_expand_kernel( |
| 24 | input_ptr, |
| 25 | lora_ptr, |
| 26 | out_ptr, |
| 27 | M, |
| 28 | N, |
| 29 | K, |
| 30 | token_indices_sorted_by_lora_ids, |
| 31 | num_tokens_per_lora, |
| 32 | lora_token_start_loc, |
| 33 | lora_ids, |
| 34 | slice_start_loc, |
| 35 | input_d0_stride, |
| 36 | input_d1_stride, |
| 37 | input_d2_stride, # 1 |
| 38 | ls_d0_ptr, |
| 39 | ls_d1_ptr, |
| 40 | ls_d2_ptr, # 1 |
| 41 | output_d0_stride, |
| 42 | output_d1_stride, # 1 |
| 43 | output_hs_ptr, |
| 44 | BLOCK_M: tl.constexpr, |
| 45 | BLOCK_N: tl.constexpr, |
| 46 | BLOCK_K: tl.constexpr, |
| 47 | EVEN_K: tl.constexpr, |
| 48 | ADD_INPUTS: tl.constexpr, |
| 49 | CAST_TYPE: tl.constexpr, |
| 50 | SLICE_NUM: tl.constexpr, |
| 51 | SAME_STRIDE: tl.constexpr, |
| 52 | USE_GDC: tl.constexpr, |
| 53 | launch_pdl: tl.constexpr, |
| 54 | ): |
| 55 | cta_n_num = tl.cdiv(N, BLOCK_N) |
| 56 | cta_m_num = tl.cdiv(M, BLOCK_M) |
| 57 | |
| 58 | pid_mn = tl.program_id(axis=0) |
| 59 | pid_m = pid_mn % cta_m_num |
| 60 | pid_n = (pid_mn // cta_m_num) % cta_n_num |
| 61 | |
| 62 | slice_id = tl.program_id(axis=1) |
| 63 | lora_idx = tl.program_id(axis=2) |
| 64 | |
| 65 | lora_id = tl.load(lora_ids + lora_idx) |
| 66 | if lora_id == -1: |
| 67 | # Early exit for the no-lora case. |
| 68 | return |
| 69 | |
| 70 | lora_m_size = tl.load(num_tokens_per_lora + lora_idx) |
| 71 | |
| 72 | cta_m_offset = pid_m * BLOCK_M |
| 73 | if cta_m_offset >= lora_m_size: |
| 74 | # Early exit CTA. |
| 75 | return |
| 76 | |
| 77 | # When the output dimensions of each slice are the same,cur_n=N, otherwise |
| 78 | # cur_n=tl.load(output_hs_ptr + slice_id), this situation exists in GQA's |
| 79 | # qkv linear. |
| 80 | curr_N = N if SAME_STRIDE else tl.load(output_hs_ptr + slice_id) |
| 81 | if pid_n * BLOCK_N >= curr_N: |
| 82 | # Early exit CTA. |
| 83 | return |
| 84 | |
| 85 | # num rows this CTA should process. |
| 86 | cta_m_len = min(BLOCK_M, lora_m_size - cta_m_offset) |
| 87 | |
| 88 | # Identify all rows that this CTA should process. |
| 89 | lora_m_indices_start = tl.load(lora_token_start_loc + lora_idx) |
| 90 | cta_lora_seq_indices = ( |
| 91 | token_indices_sorted_by_lora_ids + lora_m_indices_start + cta_m_offset |
| 92 | ) |
| 93 | |
| 94 | # Load all relevant row indices. |
| 95 | offset_m = tl.arange(0, BLOCK_M) % cta_m_len |
| 96 | ram = tl.load(cta_lora_seq_indices + offset_m) |
| 97 | |
| 98 | do_expand_kernel( |
| 99 | pid_n, |
| 100 | lora_id, |
| 101 | slice_id, |
| 102 | input_ptr, |
| 103 | lora_ptr, |
| 104 | out_ptr, |
| 105 | curr_N, |
| 106 | K, |
| 107 | cta_m_len, |
| 108 | ram, # array identifying the rows of Input ptr to operate on |
| 109 | slice_start_loc, |
| 110 | # input ptr strides |
| 111 | input_d0_stride, |
| 112 | input_d1_stride, |
| 113 | input_d2_stride, |
| 114 | # lora ptr strides |
| 115 | ls_d0_ptr, |
| 116 | ls_d1_ptr, |
| 117 | ls_d2_ptr, |
| 118 | # out ptr strides |
| 119 | output_d0_stride, |
| 120 | output_d1_stride, |
| 121 | # constants |
| 122 | BLOCK_M, |
| 123 | BLOCK_N, |
| 124 | BLOCK_K, |
| 125 | SAME_STRIDE, |
| 126 | SLICE_NUM, |
| 127 | EVEN_K, |
| 128 | CAST_TYPE, |
| 129 | ADD_INPUTS, |
| 130 | USE_GDC, |
| 131 | ) |
| 132 | |
| 133 | |
| 134 | @torch.inference_mode() |
| 135 | def _lora_expand( |
| 136 | inputs: torch.Tensor, # shape [num_slices, num_tokens, lora_rank] |
| 137 | lora_b_weights: list[torch.Tensor], # shape [num_lora, hidden_size, lora_rank] |
| 138 | output_tensor: torch.Tensor, # shape [num_tokens, hidden_size * num_slices] |
| 139 | token_lora_mapping: torch.Tensor, # shape [num_tokens] |
| 140 | token_indices_sorted_by_lora_ids: torch.Tensor, # shape [num_tokens] |
| 141 | num_tokens_per_lora: torch.Tensor, # shape [max-loras + 1] |
| 142 | lora_token_start_loc: torch.Tensor, # shape [max-loras + 2] |
| 143 | lora_ids: torch.Tensor, # shape [max-loras + 1] |
| 144 | no_lora_flag_cpu: torch.Tensor, # shape [1] |
| 145 | num_active_loras: torch.Tensor, # CPU tensor [1], number of active LoRAs |
| 146 | offset_start: int = 0, |
| 147 | add_inputs: bool = False, |
| 148 | ) -> None: |
| 149 | """Args: |
| 150 | inputs (torch.Tensor): input tensor |
| 151 | lora_b_weights (list[torch.Tensor]): lora'b weight |
| 152 | output_tensor (torch.Tensor): output tensor |
| 153 | token_lora_mapping (torch.Tensor): A tensor mapping each input token |
| 154 | to the lora-id related to that token. A value of -1 indicates that |
| 155 | LoRA doesn't apply to that token. |
| 156 | token_indices_sorted_by_lora_ids (torch.Tensor): Row/Token indices from |
| 157 | the A matrix grouped by LoRA IDs. |
| 158 | num_tokens_per_lora (torch.Tensor): num_tokens_per_lora[i] is the number |
| 159 | of tokens that are to be processed by LoRA ID lora_ids[i] |
| 160 | lora_token_start_loc (torch.Tensor): A cumulative sum of |
| 161 | num_tokens_per_lora. lora_token_start_loc[0] is always 0 so that |
| 162 | lora_token_start_loc[i], along with num_tokens_per_lora[i] |
| 163 | identifies the region in token_indices_sorted_by_lora_ids that |
| 164 | LoRA lora_ids[i] should process. |
| 165 | lora_ids (torch.Tensor): LoRA ids to process. |
| 166 | no_lora_flag_cpu (torch.Tensor): A CPU tensor of size 1, that indicates |
| 167 | if there are any requests that require LoRA. |
| 168 | offset_start (int, optional): Offset start for output_tensor. |
| 169 | Defaults to 0. |
| 170 | add_inputs (bool, optional): Whether to add the input tensor to the |
| 171 | output tensor. Defaults to False. |
| 172 | |
| 173 | """ |
| 174 | assert no_lora_flag_cpu.numel() == 1 |
| 175 | if no_lora_flag_cpu.item(): |
| 176 | # None of the inputs require LoRA. |
| 177 | return |
| 178 | |
| 179 | assert inputs.dtype in [torch.float16, torch.bfloat16, torch.float32] |
| 180 | for weight in lora_b_weights: |
| 181 | assert weight.dtype in [torch.float16, torch.bfloat16] |
| 182 | |
| 183 | assert inputs.size(0) == len(lora_b_weights) |
| 184 | assert output_tensor.is_contiguous() |
| 185 | |
| 186 | # metadata sanity check. |
| 187 | M = inputs.size(1) |
| 188 | assert token_lora_mapping.size(0) == M |
| 189 | assert token_lora_mapping.size(0) == token_indices_sorted_by_lora_ids.size(0) |
| 190 | assert lora_ids.size(0) == num_tokens_per_lora.size(0) |
| 191 | assert lora_token_start_loc.size(0) == lora_ids.size(0) + 1 |
| 192 | |
| 193 | ( |
| 194 | slice_start_tensor, |
| 195 | lora_ptr_tensor, |
| 196 | lora_strides_d0_tensor, |
| 197 | lora_strides_d1_tensor, |
| 198 | lora_strides_d2_tensor, |
| 199 | hidden_sizes_tensor, |
| 200 | same_stride, |
| 201 | MAX_N, |
| 202 | ) = _get_lora_b_ptr(lora_b_weights, offset_start, inputs.device) |
| 203 | |
| 204 | K = lora_b_weights[0].shape[-1] # K= rank |
| 205 | ADD_INPUTS = add_inputs |
| 206 | MAX_LORAS = lora_ids.size(0) |
| 207 | CAST_TYPE = False |
| 208 | NUM_SLICES = len(lora_b_weights) |
| 209 | |
| 210 | # Triton kernel configs. |
| 211 | kernel_config = get_lora_op_configs( |
| 212 | op_type="expand", |
| 213 | max_loras=MAX_LORAS, |
| 214 | batch=M, |
| 215 | hidden_size=MAX_N, |
| 216 | rank=K, |
| 217 | num_slices=NUM_SLICES, |
| 218 | add_inputs=add_inputs, |
| 219 | ) |
| 220 | BLOCK_M = kernel_config["block_m"] |
| 221 | BLOCK_N = kernel_config["block_n"] |
| 222 | BLOCK_K = kernel_config["block_k"] |
| 223 | NUM_WARPS = kernel_config["num_warps"] |
| 224 | NUM_CTAS = kernel_config["num_ctas"] |
| 225 | NUM_STAGES = kernel_config["num_stages"] |
| 226 | |
| 227 | EVEN_K = K % BLOCK_K == 0 # type: ignore |
| 228 | |
| 229 | if inputs.dtype == torch.float32 and lora_b_weights[0].dtype in [ |
| 230 | torch.float16, |
| 231 | torch.bfloat16, |
| 232 | ]: |
| 233 | CAST_TYPE = True |
| 234 | |
| 235 | # TODO (varun): This grid formulation maximizes parallelization at the |
| 236 | # cost of wasteful thread block launch when only a few input tokens require |
| 237 | # LoRA. This might not be the best in all cases. |
| 238 | grid = ( |
| 239 | triton.cdiv(M, BLOCK_M) * triton.cdiv(MAX_N, BLOCK_N), |
| 240 | NUM_SLICES, |
| 241 | num_active_loras.item(), |
| 242 | ) |
| 243 | |
| 244 | # PDL only works when dual-stream is being used. |
| 245 | use_gdc = supports_pdl(inputs.device) and envs.VLLM_LORA_ENABLE_DUAL_STREAM |
| 246 | _lora_expand_kernel[grid]( |
| 247 | inputs, |
| 248 | lora_ptr_tensor, |
| 249 | output_tensor, |
| 250 | M, |
| 251 | MAX_N, |
| 252 | K, |
| 253 | token_indices_sorted_by_lora_ids, |
| 254 | num_tokens_per_lora, |
| 255 | lora_token_start_loc, |
| 256 | lora_ids, |
| 257 | slice_start_tensor, |
| 258 | inputs.stride(0), |
| 259 | inputs.stride(1), |
| 260 | inputs.stride(2), |
| 261 | lora_strides_d0_tensor, |
| 262 | lora_strides_d1_tensor, |
| 263 | lora_strides_d2_tensor, |
| 264 | output_tensor.stride(0), |
| 265 | output_tensor.stride(1), |
| 266 | hidden_sizes_tensor, |
| 267 | BLOCK_M, |
| 268 | BLOCK_N, |
| 269 | BLOCK_K, |
| 270 | EVEN_K, |
| 271 | ADD_INPUTS, |
| 272 | CAST_TYPE, |
| 273 | NUM_SLICES, |
| 274 | same_stride, |
| 275 | use_gdc, |
| 276 | num_warps=NUM_WARPS, |
| 277 | num_ctas=NUM_CTAS, |
| 278 | num_stages=NUM_STAGES, |
| 279 | launch_pdl=use_gdc, |
| 280 | ) |
| 281 | |
| 282 | return |
| 283 | |
| 284 | |
| 285 | try: |
| 286 | direct_register_custom_op( |
| 287 | op_name="lora_expand", |
| 288 | op_func=_lora_expand, |
| 289 | mutates_args=["output_tensor"], |
| 290 | ) |
| 291 | lora_expand = torch.ops.vllm.lora_expand |
| 292 | |
| 293 | except AttributeError: |
| 294 | lora_expand = _lora_expand |