@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / a55caf6ae9
Fork 0

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 8, 00:00 UTC

[LoRA] Support Quantized Adapters (#30286)

Signed-off-by: Yu Gong <yu3.gong@gmail.com> Signed-off-by: wz1qqx <ziqi.wang@novita.ai> Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: wz1qqx <55830058+wz1qqx@users.noreply.github.com> Co-authored-by: wz1qqx <ziqi.wang@novita.ai> Co-authored-by: Michael Goin <mgoin64@gmail.com> Co-authored-by: Jee Jee Li <pandaleefree@gmail.com> Co-authored-by: Robert Shaw <114415538+robertgshaw2-redhat@users.noreply.github.com> Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>

yugong333 committed Feb 20, 2026 at 19:54 UTC a55caf6ae9a561dd816692060ef49681d9d6786d
2 files changed +1040
vllm/lora/ops/triton_ops/__init__.py
+8
@@ -2,6 +2,11 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5 +from vllm.lora.ops.triton_ops.fused_moe_lora_fp8_op import (
6 + fused_moe_lora_expand_fp8,
7 + fused_moe_lora_fp8,
8 + fused_moe_lora_shrink_fp8,
9 +)
10 from vllm.lora.ops.triton_ops.fused_moe_lora_op import (
11 fused_moe_lora,
12 fused_moe_lora_expand,
@@ -18,4 +23,7 @@ __all__ = [
23 "fused_moe_lora",
24 "fused_moe_lora_shrink",
25 "fused_moe_lora_expand",
26 + "fused_moe_lora_fp8",
27 + "fused_moe_lora_shrink_fp8",
28 + "fused_moe_lora_expand_fp8",
29 ]
vllm/lora/ops/triton_ops/fused_moe_lora_fp8_op.py new
+1032
@@ -0,0 +1,1032 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +
5 +from typing import List # noqa: UP035
6 +
7 +import torch
8 +
9 +from vllm.distributed import (
10 + tensor_model_parallel_all_gather,
11 + tensor_model_parallel_all_reduce,
12 +)
13 +from vllm.triton_utils import tl, triton
14 +from vllm.utils.torch_utils import direct_register_custom_op
15 +
16 +from .utils import supports_pdl
17 +
18 +
19 +@triton.jit
20 +def _get_lora_id(
21 + lora_ids,
22 + token_lora_mapping_ptr,
23 + lora_idx,
24 + pid_m,
25 + top_k_num,
26 + naive_block_assignment: tl.constexpr,
27 +):
28 + """Returns lora_id"""
29 + if naive_block_assignment:
30 + token_idx = pid_m // top_k_num
31 + return tl.load(token_lora_mapping_ptr + token_idx)
32 + else:
33 + return tl.load(lora_ids + lora_idx)
34 +
35 +
36 +@triton.jit
37 +def _get_expert_id(
38 + expert_ids_ptr,
39 + lora_id,
40 + pid_m,
41 + stride_el,
42 + max_loras,
43 + naive_block_assignment: tl.constexpr,
44 +):
45 + """Returns expert_id"""
46 + if naive_block_assignment:
47 + return tl.load(expert_ids_ptr + pid_m)
48 + else:
49 + ind = lora_id * stride_el + pid_m
50 + return tl.load(expert_ids_ptr + ind, ind < max_loras * stride_el, -1)
51 +
52 +
53 +@triton.jit
54 +def _get_token_offs(
55 + sorted_token_ids_ptr,
56 + lora_id,
57 + pid_m,
58 + offs,
59 + stride_tl,
60 + max_loras,
61 + num_valid_tokens,
62 + naive_block_assignment: tl.constexpr,
63 + BLOCK_SIZE_M: tl.constexpr,
64 +):
65 + """Returns token offsets"""
66 + if naive_block_assignment:
67 + return tl.where(offs == 0, pid_m, num_valid_tokens)
68 + else:
69 + offs_token_id = pid_m * BLOCK_SIZE_M + offs
70 + token_ind = stride_tl * lora_id + offs_token_id
71 + return tl.load(
72 + sorted_token_ids_ptr + token_ind, token_ind < max_loras * stride_tl, 0
73 + )
74 +
75 +
76 +_LORA_PTR_DICT: dict[tuple[int, ...], torch.tensor] = {}
77 +
78 +
79 +def _get_ptr(lora_weights: list[torch.Tensor], device: torch.device):
80 + """
81 + `_LORA_PTR_DICT` collects the required information during `profile_run`,
82 + After this, it remains constant and subsequent usage is through LUT.
83 + Refer to:
84 + https://github.com/triton-lang/triton/blob/release/3.1.x/python/tutorials/08-grouped-gemm.py
85 + """
86 + key = tuple(lora_weight.data_ptr() for lora_weight in lora_weights)
87 +
88 + if (ptr_tensor := _LORA_PTR_DICT.get(key)) is not None:
89 + return ptr_tensor
90 +
91 + tensor_ptrs = []
92 + for lora_weight in lora_weights:
93 + tensor_ptrs.append(lora_weight.data_ptr())
94 + ptr_tensor = torch.tensor(tensor_ptrs, device=device, dtype=torch.uint64)
95 +
96 + _LORA_PTR_DICT[key] = ptr_tensor
97 + return _LORA_PTR_DICT.get(key)
98 +
99 +
100 +def _adjust_kernel_inputs(
101 + num_active_loras: int,
102 + sorted_token_ids: torch.Tensor | None,
103 + expert_ids: torch.Tensor,
104 +):
105 + """
106 + helper function to adjust kernel inputs when sorted_token_ids is None
107 + """
108 + if sorted_token_ids is None:
109 + stride_tl = 0
110 + stride_el = 0
111 + grid_lora_dim = 1
112 + else:
113 + stride_tl = sorted_token_ids.stride(0)
114 + stride_el = expert_ids.stride(0)
115 + grid_lora_dim = num_active_loras
116 + return grid_lora_dim, stride_tl, stride_el
117 +
118 +
119 +@triton.jit(
120 + do_not_specialize=[
121 + "num_valid_tokens",
122 + "EM",
123 + "stride_tl",
124 + "stride_el",
125 + "slice_a_size",
126 + "slice_c_size",
127 + ]
128 +)
129 +def _fused_moe_lora_kernel_fp8(
130 + a_ptr,
131 + b_ptr,
132 + c_ptr,
133 + a_scale_ptr,
134 + b_scale_ptr,
135 + topk_weights_ptr,
136 + sorted_token_ids_ptr,
137 + expert_ids_ptr,
138 + num_tokens_post_padded_ptr,
139 + token_lora_mapping_ptr,
140 + # Matrix dimensions
141 + N,
142 + K,
143 + EM,
144 + num_valid_tokens,
145 + num_experts,
146 + top_k_num,
147 + lora_ids,
148 + adapter_enabled,
149 + max_loras, # <<< PR2: rename, used for masks when grid axis-2 != max_loras
150 + # The stride variables represent how much to increase the ptr by when
151 + # moving by 1 element in a particular dimension. E.g. `stride_am` is
152 + # how much to increase `a_ptr` by to get the element one row down
153 + # (A has M rows).
154 + stride_am,
155 + stride_ak,
156 + stride_bl,
157 + stride_be,
158 + stride_bk,
159 + stride_bn,
160 + stride_cm,
161 + stride_cn,
162 + stride_tl,
163 + stride_el,
164 + stride_asm,
165 + stride_ask,
166 + stride_bsl,
167 + stride_bse,
168 + stride_bsk,
169 + stride_bsn,
170 + # block size for block-wise quantization
171 + group_n: tl.constexpr,
172 + group_k: tl.constexpr,
173 + slice_a_size,
174 + slice_c_size,
175 + # Meta-parameters
176 + num_slice_a: tl.constexpr,
177 + num_slice_c: tl.constexpr,
178 + # top_k_num or 1 depending on input token
179 + # is expanded by top_k or not
180 + token_mapping_factor: tl.constexpr,
181 + # whether use naive block assignment
182 + naive_block_assignment: tl.constexpr,
183 + MUL_ROUTED_WEIGHT: tl.constexpr,
184 + ADD_INPUTS: tl.constexpr,
185 + USE_B_L2_CACHE: tl.constexpr, # new, enable .ca load for B
186 + BLOCK_SIZE_M: tl.constexpr,
187 + BLOCK_SIZE_N: tl.constexpr,
188 + BLOCK_SIZE_K: tl.constexpr,
189 + GROUP_SIZE_M: tl.constexpr,
190 + SPLIT_K: tl.constexpr,
191 + USE_GDC: tl.constexpr,
192 + launch_pdl: tl.constexpr,
193 + IS_PRIMARY: tl.constexpr,
194 + use_fp8_w8a8: tl.constexpr,
195 + use_int8_w8a8: tl.constexpr,
196 + use_int8_w8a16: tl.constexpr,
197 + per_channel_quant: tl.constexpr,
198 +):
199 + pid = tl.program_id(axis=0)
200 + slice_id = tl.program_id(axis=1)
201 + grid_k = tl.cdiv(K, BLOCK_SIZE_K * SPLIT_K)
202 +
203 + # calculate pid_m,pid_n
204 + lora_idx = tl.program_id(axis=2)
205 + pid_sk = pid % SPLIT_K
206 + pid_m_n = pid // SPLIT_K
207 + num_pid_m = tl.cdiv(EM, BLOCK_SIZE_M)
208 + num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
209 +
210 + num_pid_in_group = GROUP_SIZE_M * num_pid_n
211 + group_id = pid_m_n // num_pid_in_group
212 + first_pid_m = group_id * GROUP_SIZE_M
213 + group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
214 + pid_m = first_pid_m + ((pid_m_n % num_pid_in_group) % group_size_m)
215 + pid_n = (pid_m_n % num_pid_in_group) // group_size_m
216 +
217 + offs = tl.arange(0, BLOCK_SIZE_M).to(tl.int64)
218 +
219 + # Get lora_id
220 + lora_id = _get_lora_id(
221 + lora_ids,
222 + token_lora_mapping_ptr,
223 + lora_idx,
224 + pid_m,
225 + top_k_num,
226 + naive_block_assignment,
227 + )
228 + if lora_id == -1:
229 + return
230 + moe_enabled = tl.load(adapter_enabled + lora_id)
231 + if moe_enabled == 0:
232 + return
233 + if lora_id >= max_loras:
234 + return
235 +
236 + # Non-naive only: check num_tokens_post_padded
237 + if not naive_block_assignment:
238 + num_tokens_post_padded = tl.load(num_tokens_post_padded_ptr + lora_id)
239 + if pid_m * BLOCK_SIZE_M >= num_tokens_post_padded:
240 + return
241 +
242 + # Get expert_id
243 + expert_id = _get_expert_id(
244 + expert_ids_ptr,
245 + lora_id,
246 + pid_m,
247 + stride_el,
248 + max_loras,
249 + naive_block_assignment,
250 + )
251 + if expert_id == -1:
252 + return
253 +
254 + # Get token offsets
255 + offs_token = _get_token_offs(
256 + sorted_token_ids_ptr,
257 + lora_id,
258 + pid_m,
259 + offs,
260 + stride_tl,
261 + max_loras,
262 + num_valid_tokens,
263 + naive_block_assignment,
264 + BLOCK_SIZE_M,
265 + )
266 + # get a_ptr,b_ptr,c_ptr
267 + cur_a_ptr = a_ptr + (slice_id % num_slice_a) * slice_a_size
268 + cur_b_ptr = tl.load(b_ptr + slice_id).to(tl.pointer_type(c_ptr.dtype.element_ty))
269 + cur_c_ptr = c_ptr + (slice_id % num_slice_c) * slice_c_size
270 +
271 + # remove modulo wrap-around
272 + offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int32)
273 + offs_k = pid_sk * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K)
274 + token_mask = offs_token < num_valid_tokens
275 +
276 + # get a_ptrs,b_ptrs
277 + a_ptrs = cur_a_ptr + (
278 + offs_token[:, None] // token_mapping_factor * stride_am
279 + + offs_k[None, :] * stride_ak
280 + )
281 +
282 + b_ptrs = (
283 + cur_b_ptr
284 + + lora_id * stride_bl
285 + + expert_id * stride_be
286 + + offs_k[:, None] * stride_bk
287 + + offs_bn[None, :] * stride_bn
288 + )
289 +
290 + if USE_GDC and IS_PRIMARY:
291 + # GDC launch dependents hints the runtime system to launch dependent kernels.
292 + tl.extra.cuda.gdc_launch_dependents()
293 +
294 + # accumulator
295 + accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
296 +
297 + if USE_GDC and not IS_PRIMARY:
298 + tl.extra.cuda.gdc_wait()
299 +
300 + for k in range(0, grid_k):
301 + k_remaining = K - k * (BLOCK_SIZE_K * SPLIT_K)
302 + # GDC wait waits for ALL programs in the prior kernel to complete
303 + # before continuing.
304 + # pre-fetch lora weight
305 + # add (offs_bn < N) mask; optional .ca for B
306 + b_mask = (offs_k[:, None] < k_remaining) & (offs_bn[None, :] < N)
307 + if USE_B_L2_CACHE:
308 + b = tl.load(b_ptrs, mask=b_mask, other=0.0, cache_modifier=".ca")
309 + else:
310 + b = tl.load(b_ptrs, mask=b_mask, other=0.0)
311 +
312 + if USE_GDC and not IS_PRIMARY:
313 + tl.extra.cuda.gdc_wait()
314 + a = tl.load(
315 + a_ptrs,
316 + mask=token_mask[:, None] & (offs_k[None, :] < k_remaining),
317 + other=0.0,
318 + )
319 + accumulator += tl.dot(a, b)
320 + # Advance the ptrs to the next K block.
321 + a_ptrs += BLOCK_SIZE_K * SPLIT_K * stride_ak
322 + b_ptrs += BLOCK_SIZE_K * SPLIT_K * stride_bk
323 +
324 + if MUL_ROUTED_WEIGHT:
325 + moe_weight = tl.load(topk_weights_ptr + offs_token, mask=token_mask, other=0.0)
326 + accumulator = accumulator * moe_weight[:, None]
327 + accumulator = accumulator.to(c_ptr.dtype.element_ty)
328 + # Write back the block of the output
329 + offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
330 + c_ptrs = cur_c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_cn[None, :]
331 + c_mask = token_mask[:, None] & (offs_cn[None, :] < N)
332 +
333 + if SPLIT_K == 1:
334 + if ADD_INPUTS:
335 + prev = tl.load(c_ptrs, mask=c_mask, other=0.0)
336 + tl.store(c_ptrs, prev + accumulator, mask=c_mask)
337 + else:
338 + tl.store(c_ptrs, accumulator, mask=c_mask)
339 + else:
340 + tl.atomic_add(c_ptrs, accumulator, mask=c_mask, sem="relaxed")
341 +
342 +
343 +@torch.inference_mode()
344 +def _fused_moe_lora_shrink_fp8(
345 + a_intermediate_cache1: torch.Tensor,
346 + # (num_slices, num_tokens, top_k_num, max_lora_rank)
347 + qcurr_hidden_states: torch.Tensor, # (num_tokens, K,)
348 + lora_a_stacked: list[
349 + torch.Tensor
350 + ], # [(max_loras, num_experts, max_lora_rank, K,),...]
351 + topk_weights: torch.Tensor, # (num_tokens, top_k_num)
352 + sorted_token_ids: torch.Tensor | None, # (max_loras, _)
353 + expert_ids: torch.Tensor, # (max_loras, _ ,) or (num_tokens * top_k,)
354 + num_tokens_post_padded: torch.Tensor | None, # (max_loras, )
355 + token_lora_mapping: torch.Tensor,
356 + top_k_num: int,
357 + lora_ids: torch.Tensor,
358 + adapter_enabled: torch.Tensor,
359 + ## adding for kernel
360 + device: torch.device,
361 + N: int,
362 + M: int,
363 + EM: int,
364 + K: int,
365 + num_tokens: int,
366 + num_experts: int,
367 + num_slices: int,
368 + block_size_m: int,
369 + block_size_n: int,
370 + block_size_k: int,
371 + group_size_m: int,
372 + num_warps: int,
373 + num_stages: int,
374 + split_k: int,
375 + num_active_loras: int,
376 + lora_a_scale_stacked: list[torch.Tensor],
377 + mul_routed_weight: bool = False,
378 + use_gdc: bool = False,
379 + act_scale: torch.Tensor | None = None,
380 + use_fp8_w8a8: bool = False,
381 + use_int8_w8a8: bool = False,
382 + use_int8_w8a16: bool = False,
383 + per_channel_quant: bool = False,
384 + block_shape: List[int] | None = None, # noqa: UP006, UP007
385 +) -> None:
386 + if use_fp8_w8a8 or use_int8_w8a8:
387 + assert lora_a_scale_stacked is not None, (
388 + "lora_a_scale_stacked must be provided for w8a8 quantization"
389 + )
390 + assert block_shape is None or triton.cdiv(
391 + lora_a_stacked[0].size(-2), block_shape[0]
392 + ) == lora_a_scale_stacked[0].size(-2), (
393 + "Incompatible block shape for lora_a_scale_stacked.size(-2) "
394 + )
395 + assert block_shape is None or triton.cdiv(
396 + lora_a_stacked[0].size(-1), block_shape[1]
397 + ) == lora_a_scale_stacked[0].size(-1), (
398 + "Incompatible block shape for lora_a_scale_stacked.size(-1) "
399 + )
400 + elif use_int8_w8a16:
401 + assert lora_a_scale_stacked is not None, (
402 + "lora_a_scale_stacked must be provided for w8a16 quantization"
403 + )
404 + assert block_shape is None or block_shape[0] == 0, (
405 + "Block shape for activation must be 0 for w8a16"
406 + )
407 + else:
408 + assert act_scale is None
409 + assert lora_a_scale_stacked is None
410 +
411 + if block_shape is not None:
412 + block_size_k = min(block_size_k, min(block_shape[0], block_shape[1]))
413 +
414 + if lora_a_scale_stacked is not None:
415 + b_scale_ptr = _get_ptr(lora_a_scale_stacked, device)
416 + w1_lora_a_scale_stacked = lora_a_scale_stacked[0]
417 +
418 + w1_lora_a_stacked = lora_a_stacked[0]
419 + shrink_config = {
420 + "BLOCK_SIZE_M": block_size_m,
421 + "BLOCK_SIZE_N": block_size_n,
422 + "BLOCK_SIZE_K": block_size_k,
423 + "GROUP_SIZE_M": group_size_m,
424 + "num_warps": num_warps,
425 + "num_stages": num_stages,
426 + "SPLIT_K": split_k,
427 + "USE_GDC": use_gdc,
428 + "launch_pdl": use_gdc, # triton kernel metadata
429 + }
430 +
431 + b_ptr = _get_ptr(lora_a_stacked, device)
432 +
433 + grid_lora_dim, stride_tl, stride_el = _adjust_kernel_inputs(
434 + num_active_loras, sorted_token_ids, expert_ids
435 + )
436 +
437 + grid = lambda META: (
438 + split_k
439 + * triton.cdiv(EM, META["BLOCK_SIZE_M"])
440 + * triton.cdiv(N, META["BLOCK_SIZE_N"]),
441 + len(lora_a_stacked),
442 + grid_lora_dim,
443 + )
444 + _fused_moe_lora_kernel_fp8[grid](
445 + qcurr_hidden_states,
446 + b_ptr,
447 + a_intermediate_cache1,
448 + act_scale,
449 + b_scale_ptr if lora_a_scale_stacked is not None else None,
450 + topk_weights,
451 + sorted_token_ids,
452 + expert_ids,
453 + num_tokens_post_padded,
454 + token_lora_mapping,
455 + N,
456 + K,
457 + EM,
458 + num_tokens,
459 + num_experts,
460 + top_k_num,
461 + lora_ids,
462 + adapter_enabled,
463 + lora_a_stacked[0].shape[0],
464 + qcurr_hidden_states.stride(0),
465 + qcurr_hidden_states.stride(1),
466 + w1_lora_a_stacked.stride(0),
467 + w1_lora_a_stacked.stride(1),
468 + w1_lora_a_stacked.stride(3),
469 + w1_lora_a_stacked.stride(2),
470 + a_intermediate_cache1.stride(2),
471 + a_intermediate_cache1.stride(3),
472 + stride_tl,
473 + stride_el,
474 + act_scale.stride(0) if act_scale is not None and act_scale.ndim == 2 else 0,
475 + act_scale.stride(1) if act_scale is not None and act_scale.ndim == 2 else 0,
476 + w1_lora_a_scale_stacked.stride(0)
477 + if lora_a_scale_stacked is not None and w1_lora_a_scale_stacked.ndim >= 2
478 + else 0,
479 + w1_lora_a_scale_stacked.stride(1)
480 + if lora_a_scale_stacked is not None and w1_lora_a_scale_stacked.ndim >= 2
481 + else 0,
482 + w1_lora_a_scale_stacked.stride(3)
483 + if lora_a_scale_stacked is not None and w1_lora_a_scale_stacked.ndim == 4
484 + else 0,
485 + w1_lora_a_scale_stacked.stride(2)
486 + if lora_a_scale_stacked is not None and w1_lora_a_scale_stacked.ndim == 4
487 + else 0,
488 + 0 if block_shape is None else block_shape[0],
489 + 0 if block_shape is None else block_shape[1],
490 + slice_a_size=qcurr_hidden_states.numel(),
491 + slice_c_size=a_intermediate_cache1.numel() // num_slices,
492 + num_slice_a=1,
493 + num_slice_c=num_slices,
494 + token_mapping_factor=1 if mul_routed_weight else top_k_num,
495 + naive_block_assignment=sorted_token_ids is None,
496 + MUL_ROUTED_WEIGHT=False,
497 + ADD_INPUTS=False,
498 + USE_B_L2_CACHE=True, # new
499 + IS_PRIMARY=True,
500 + use_fp8_w8a8=use_fp8_w8a8,
501 + use_int8_w8a8=use_int8_w8a8,
502 + use_int8_w8a16=use_int8_w8a16,
503 + per_channel_quant=per_channel_quant,
504 + **shrink_config,
505 + )
506 +
507 +
508 +@torch.inference_mode()
509 +def _fused_moe_lora_expand_fp8(
510 + output: torch.Tensor, # (num_tokens, top_k_num, N*len(lora_a_stacked),)
511 + a_intermediate_cache1: torch.Tensor, # (num_slices, M, top_k_num, max_lora_rank)
512 + lora_b_stacked: list[
513 + torch.Tensor
514 + ], # [(max_loras, num_experts, max_lora_rank, K,),...]
515 + topk_weights: torch.Tensor, # (num_tokens, top_k_num)
516 + sorted_token_ids: torch.Tensor | None, # (max_loras, _)
517 + expert_ids: torch.Tensor, # (max_loras, _ ,) or (num_tokens * top_k,)
518 + num_tokens_post_padded: torch.Tensor | None, # (max_loras, )
519 + token_lora_mapping: torch.Tensor,
520 + top_k_num: int,
521 + lora_ids: torch.Tensor,
522 + adapter_enabled: torch.Tensor,
523 + ## adding for kernel
524 + device: torch.device,
525 + N: int,
526 + M: int,
527 + EM: int,
528 + K: int,
529 + num_tokens: int,
530 + num_experts: int,
531 + num_slices: int,
532 + max_lora_rank: int,
533 + w1_output_dim_size: int,
534 + block_size_m: int,
535 + block_size_n: int,
536 + block_size_k: int,
537 + group_size_m: int,
538 + num_warps: int,
539 + num_stages: int,
540 + split_k: int,
541 + num_active_loras: int,
542 + lora_b_scale_stacked: list[torch.Tensor],
543 + mul_routed_weight: bool = False,
544 + offset: int = 0,
545 + use_gdc: bool = False,
546 + act_scale: torch.Tensor | None = None,
547 + use_fp8_w8a8: bool = False,
548 + use_int8_w8a8: bool = False,
549 + use_int8_w8a16: bool = False,
550 + per_channel_quant: bool = False,
551 + block_shape: List[int] | None = None, # noqa: UP006, UP007
552 +) -> None:
553 + if use_fp8_w8a8 or use_int8_w8a8:
554 + assert lora_b_scale_stacked is not None, (
555 + "lora_b_scale_stacked must be provided for w8a8 quantization"
556 + )
557 + assert block_shape is None or triton.cdiv(
558 + lora_b_stacked[0].size(-2), block_shape[0]
559 + ) == lora_b_scale_stacked[0].size(-2), (
560 + "Incompatible block shape for lora_b_scale_stacked.size(-2) "
561 + )
562 + assert block_shape is None or triton.cdiv(
563 + lora_b_stacked[0].size(-1), block_shape[1]
564 + ) == lora_b_scale_stacked[0].size(-1), (
565 + "Incompatible block shape for lora_b_scale_stacked.size(-1) "
566 + )
567 + elif use_int8_w8a16:
568 + assert lora_b_scale_stacked is not None, (
569 + "lora_b_scale_stacked must be provided for w8a16 quantization"
570 + )
571 + assert block_shape is None or block_shape[0] == 0, (
572 + "Block shape for activation must be 0 for w8a16"
573 + )
574 + else:
575 + assert act_scale is None
576 + assert lora_b_scale_stacked is None
577 +
578 + if lora_b_scale_stacked is not None:
579 + b_scale_ptr = _get_ptr(lora_b_scale_stacked, device)
580 + w1_lora_b_scale_stacked = lora_b_scale_stacked[0]
581 +
582 + if block_shape is not None:
583 + block_size_k = min(block_size_k, min(block_shape[0], block_shape[1]))
584 +
585 + b_ptr = _get_ptr(lora_b_stacked, device)
586 + K = max_lora_rank
587 + N = w1_output_dim_size
588 +
589 + w1_lora_b_stacked = lora_b_stacked[0]
590 +
591 + a_intermediate_cache1 = a_intermediate_cache1.view(
592 + -1, a_intermediate_cache1.shape[3]
593 + )
594 +
595 + expand_config = {
596 + "BLOCK_SIZE_M": block_size_m,
597 + "BLOCK_SIZE_N": block_size_n,
598 + "BLOCK_SIZE_K": block_size_k,
599 + "GROUP_SIZE_M": group_size_m,
600 + "num_warps": num_warps,
601 + "num_stages": num_stages,
602 + "SPLIT_K": 1, # Set split_k = 1 for expand calls
603 + "USE_GDC": use_gdc,
604 + "launch_pdl": use_gdc, # triton kernel metadata
605 + }
606 +
607 + grid_lora_dim, stride_tl, stride_el = _adjust_kernel_inputs(
608 + num_active_loras, sorted_token_ids, expert_ids
609 + )
610 +
611 + grid = lambda META: (
612 + triton.cdiv(EM, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]),
613 + len(lora_b_stacked),
614 + grid_lora_dim,
615 + )
616 +
617 + # Fast path: directly accumulate into the corresponding slice interval of output.
618 + out_view = output[:, :, offset : offset + num_slices * N]
619 + slice_c_size = N * out_view.stride(2)
620 +
621 + _fused_moe_lora_kernel_fp8[grid](
622 + a_intermediate_cache1,
623 + b_ptr,
624 + out_view,
625 + act_scale,
626 + b_scale_ptr if lora_b_scale_stacked is not None else None,
627 + topk_weights,
628 + sorted_token_ids,
629 + expert_ids,
630 + num_tokens_post_padded,
631 + token_lora_mapping,
632 + N,
633 + K,
634 + EM,
635 + num_tokens,
636 + num_experts,
637 + top_k_num,
638 + lora_ids,
639 + adapter_enabled,
640 + lora_b_stacked[0].shape[0],
641 + a_intermediate_cache1.stride(0),
642 + a_intermediate_cache1.stride(1),
643 + w1_lora_b_stacked.stride(0),
644 + w1_lora_b_stacked.stride(1),
645 + w1_lora_b_stacked.stride(3),
646 + w1_lora_b_stacked.stride(2),
647 + out_view.stride(1),
648 + out_view.stride(2),
649 + stride_tl,
650 + stride_el,
651 + act_scale.stride(0) if act_scale is not None and act_scale.ndim == 2 else 0,
652 + act_scale.stride(1) if act_scale is not None and act_scale.ndim == 2 else 0,
653 + w1_lora_b_scale_stacked.stride(0)
654 + if lora_b_scale_stacked is not None and w1_lora_b_scale_stacked.ndim >= 2
655 + else 0,
656 + w1_lora_b_scale_stacked.stride(1)
657 + if lora_b_scale_stacked is not None and w1_lora_b_scale_stacked.ndim >= 2
658 + else 0,
659 + w1_lora_b_scale_stacked.stride(3)
660 + if lora_b_scale_stacked is not None and w1_lora_b_scale_stacked.ndim == 4
661 + else 0,
662 + w1_lora_b_scale_stacked.stride(2)
663 + if lora_b_scale_stacked is not None and w1_lora_b_scale_stacked.ndim == 4
664 + else 0,
665 + 0 if block_shape is None else block_shape[0],
666 + 0 if block_shape is None else block_shape[1],
667 + slice_a_size=a_intermediate_cache1.numel() // num_slices,
668 + slice_c_size=slice_c_size,
669 + num_slice_a=num_slices,
670 + num_slice_c=num_slices,
671 + token_mapping_factor=1,
672 + naive_block_assignment=sorted_token_ids is None,
673 + MUL_ROUTED_WEIGHT=mul_routed_weight,
674 + ADD_INPUTS=True,
675 + USE_B_L2_CACHE=True, # new
676 + IS_PRIMARY=False,
677 + use_fp8_w8a8=use_fp8_w8a8,
678 + use_int8_w8a8=use_int8_w8a8,
679 + use_int8_w8a16=use_int8_w8a16,
680 + per_channel_quant=per_channel_quant,
681 + **expand_config,
682 + )
683 +
684 +
685 +@torch.inference_mode()
686 +def _fused_moe_lora_fp8(
687 + output: torch.Tensor, # (num_tokens, top_k_num, N*len(lora_a_stacked),)
688 + qcurr_hidden_states: torch.Tensor, # (num_tokens, K,)
689 + lora_a_stacked: list[
690 + torch.Tensor
691 + ], # [(max_loras, num_experts, max_lora_rank, K,),...]
692 + lora_b_stacked: list[
693 + torch.Tensor
694 + ], # [(max_loras, num_experts, N, max_lora_rank,),...]
695 + topk_weights: torch.Tensor, # (num_tokens, top_k_num)
696 + sorted_token_ids: torch.Tensor | None, # (max_loras, _)
697 + expert_ids: torch.Tensor, # (max_loras, _ ,) or (num_tokens * top_k,)
698 + num_tokens_post_padded: torch.Tensor | None, # (max_loras, )
699 + token_lora_mapping: torch.Tensor,
700 + max_lora_rank: int,
701 + top_k_num: int,
702 + lora_ids: torch.Tensor,
703 + num_active_loras: int,
704 + adapter_enabled: torch.Tensor,
705 + shrink_block_size_m: int,
706 + shrink_block_size_n: int,
707 + shrink_block_size_k: int,
708 + shrink_group_size_m: int,
709 + shrink_num_warps: int,
710 + shrink_num_stages: int,
711 + shrink_split_k: int,
712 + expand_block_size_m: int,
713 + expand_block_size_n: int,
714 + expand_block_size_k: int,
715 + expand_group_size_m: int,
716 + expand_num_warps: int,
717 + expand_num_stages: int,
718 + expand_split_k: int,
719 + lora_a_scale_stacked: list[torch.Tensor],
720 + lora_b_scale_stacked: list[torch.Tensor],
721 + shrink_act_scale: torch.Tensor | None = None,
722 + expand_act_scale: torch.Tensor | None = None,
723 + mul_routed_weight: bool = False,
724 + fully_sharded: bool = False,
725 + offset: int = 0,
726 + use_fp8_w8a8: bool = False,
727 + use_int8_w8a8: bool = False,
728 + use_int8_w8a16: bool = False,
729 + per_channel_quant: bool = False,
730 + block_shape: List[int] | None = None, # noqa: UP006, UP007
731 +) -> None:
732 + assert len(lora_a_stacked) == len(lora_b_stacked) > 0
733 + assert topk_weights.dim() == qcurr_hidden_states.dim() == 2
734 + if sorted_token_ids is None:
735 + assert expert_ids.dim() == 1
736 + else:
737 + assert sorted_token_ids is not None
738 + assert num_tokens_post_padded is not None
739 + assert (
740 + sorted_token_ids.dim()
741 + == expert_ids.dim()
742 + == topk_weights.dim()
743 + == qcurr_hidden_states.dim()
744 + == 2
745 + )
746 + assert (
747 + sorted_token_ids.shape[0]
748 + == expert_ids.shape[0]
749 + == num_tokens_post_padded.shape[0]
750 + )
751 + assert output.shape[0] == topk_weights.shape[0]
752 + assert top_k_num == topk_weights.shape[1]
753 + device = qcurr_hidden_states.device
754 + num_slices = len(lora_a_stacked)
755 + w1_lora_b_stacked = lora_b_stacked[0]
756 + num_experts = lora_a_stacked[0].shape[1]
757 + N = max_lora_rank
758 + M = topk_weights.shape[0]
759 + K = qcurr_hidden_states.shape[1]
760 + num_tokens = M * top_k_num
761 + w1_output_dim_size = w1_lora_b_stacked.shape[2]
762 + assert shrink_block_size_m == expand_block_size_m
763 + EM = (
764 + sorted_token_ids.shape[1]
765 + if sorted_token_ids is not None
766 + else num_tokens * shrink_block_size_m
767 + )
768 +
769 + a_intermediate_cache1 = torch.zeros(
770 + (num_slices, M, top_k_num, max_lora_rank),
771 + dtype=output.dtype,
772 + device=device,
773 + )
774 +
775 + use_gdc = supports_pdl(device) and not fully_sharded
776 + _fused_moe_lora_shrink_fp8(
777 + a_intermediate_cache1,
778 + qcurr_hidden_states,
779 + lora_a_stacked,
780 + topk_weights,
781 + sorted_token_ids,
782 + expert_ids,
783 + num_tokens_post_padded,
784 + token_lora_mapping,
785 + top_k_num,
786 + lora_ids,
787 + adapter_enabled,
788 + ## adding for kernel
789 + device,
790 + N,
791 + M,
792 + EM,
793 + K,
794 + num_tokens,
795 + num_experts,
796 + num_slices,
797 + shrink_block_size_m,
798 + shrink_block_size_n,
799 + shrink_block_size_k,
800 + shrink_group_size_m,
801 + shrink_num_warps,
802 + shrink_num_stages,
803 + shrink_split_k,
804 + num_active_loras,
805 + lora_a_scale_stacked,
806 + mul_routed_weight=mul_routed_weight,
807 + use_gdc=use_gdc,
808 + act_scale=shrink_act_scale,
809 + use_fp8_w8a8=use_fp8_w8a8,
810 + use_int8_w8a8=use_int8_w8a8,
811 + use_int8_w8a16=use_int8_w8a16,
812 + per_channel_quant=per_channel_quant,
813 + block_shape=block_shape,
814 + )
815 +
816 + if fully_sharded:
817 + if max_lora_rank == w1_lora_b_stacked.shape[-1]:
818 + a_intermediate_cache1 = tensor_model_parallel_all_reduce(
819 + a_intermediate_cache1
820 + )
821 + else:
822 + a_intermediate_cache1 = tensor_model_parallel_all_gather(
823 + a_intermediate_cache1
824 + )
825 +
826 + # reset max_lora_rank to the full rank after allgather
827 + max_lora_rank = a_intermediate_cache1.shape[-1]
828 +
829 + _fused_moe_lora_expand_fp8(
830 + output,
831 + a_intermediate_cache1,
832 + lora_b_stacked,
833 + topk_weights,
834 + sorted_token_ids,
835 + expert_ids,
836 + num_tokens_post_padded,
837 + token_lora_mapping,
838 + top_k_num,
839 + lora_ids,
840 + adapter_enabled,
841 + ## adding for kernel
842 + device,
843 + N,
844 + M,
845 + EM,
846 + K,
847 + num_tokens,
848 + num_experts,
849 + num_slices,
850 + max_lora_rank,
851 + w1_output_dim_size,
852 + expand_block_size_m,
853 + expand_block_size_n,
854 + expand_block_size_k,
855 + expand_group_size_m,
856 + expand_num_warps,
857 + expand_num_stages,
858 + expand_split_k,
859 + num_active_loras,
860 + lora_b_scale_stacked,
861 + mul_routed_weight=mul_routed_weight,
862 + offset=offset,
863 + use_gdc=use_gdc,
864 + act_scale=expand_act_scale,
865 + use_fp8_w8a8=use_fp8_w8a8,
866 + use_int8_w8a8=use_int8_w8a8,
867 + use_int8_w8a16=use_int8_w8a16,
868 + per_channel_quant=per_channel_quant,
869 + block_shape=block_shape,
870 + )
871 +
872 +
873 +def _fused_moe_lora_fp8_fake(
874 + output: torch.Tensor,
875 + qcurr_hidden_states: torch.Tensor,
876 + lora_a_stacked: list[torch.Tensor],
877 + lora_b_stacked: list[torch.Tensor],
878 + topk_weights: torch.Tensor,
879 + sorted_token_ids: torch.Tensor | None,
880 + expert_ids: torch.Tensor,
881 + num_tokens_post_padded: torch.Tensor | None,
882 + token_lora_mapping: torch.Tensor,
883 + max_lora_rank: int,
884 + top_k_num: int,
885 + lora_ids: torch.Tensor,
886 + num_active_loras: int,
887 + adapter_enabled: torch.Tensor,
888 + shrink_block_size_m: int,
889 + shrink_block_size_n: int,
890 + shrink_block_size_k: int,
891 + shrink_group_size_m: int,
892 + shrink_num_warps: int,
893 + shrink_num_stages: int,
894 + shrink_split_k: int,
895 + expand_block_size_m: int,
896 + expand_block_size_n: int,
897 + expand_block_size_k: int,
898 + expand_group_size_m: int,
899 + expand_num_warps: int,
900 + expand_num_stages: int,
901 + expand_split_k: int,
902 + lora_a_scale_stacked: list[torch.Tensor],
903 + lora_b_scale_stacked: list[torch.Tensor],
904 + mul_routed_weight: bool = False,
905 + fully_sharded: bool = False,
906 + offset: int = 0,
907 + shrink_act_scale: torch.Tensor | None = None,
908 + expand_act_scale: torch.Tensor | None = None,
909 + use_fp8_w8a8: bool = False,
910 + use_int8_w8a8: bool = False,
911 + use_int8_w8a16: bool = False,
912 + per_channel_quant: bool = False,
913 + block_shape: List[int] | None = None, # noqa: UP006, UP007
914 +) -> None:
915 + return
916 +
917 +
918 +def _fused_moe_lora_shrink_fp8_fake(
919 + a_intermediate_cache1: torch.Tensor,
920 + qcurr_hidden_states: torch.Tensor,
921 + lora_a_stacked: list[torch.Tensor],
922 + topk_weights: torch.Tensor,
923 + sorted_token_ids: torch.Tensor | None,
924 + expert_ids: torch.Tensor,
925 + num_tokens_post_padded: torch.Tensor | None,
926 + token_lora_mapping: torch.Tensor,
927 + top_k_num: int,
928 + lora_ids: torch.Tensor,
929 + adapter_enabled: torch.Tensor,
930 + device: torch.device,
931 + N: int,
932 + M: int,
933 + EM: int,
934 + K: int,
935 + num_tokens: int,
936 + num_experts: int,
937 + num_slices: int,
938 + block_size_m: int,
939 + block_size_n: int,
940 + block_size_k: int,
941 + group_size_m: int,
942 + num_warps: int,
943 + num_stages: int,
944 + split_k: int,
945 + num_active_loras: int,
946 + lora_a_scale_stacked: list[torch.Tensor],
947 + mul_routed_weight: bool = False,
948 + use_gdc: bool = False,
949 + act_scale: torch.Tensor | None = None,
950 + use_fp8_w8a8: bool = False,
951 + use_int8_w8a8: bool = False,
952 + use_int8_w8a16: bool = False,
953 + per_channel_quant: bool = False,
954 + block_shape: List[int] | None = None, # noqa: UP006, UP007
955 +) -> None:
956 + return
957 +
958 +
959 +def _fused_moe_lora_expand_fp8_fake(
960 + output: torch.Tensor,
961 + a_intermediate_cache1: torch.Tensor,
962 + lora_b_stacked: list[torch.Tensor],
963 + topk_weights: torch.Tensor,
964 + sorted_token_ids: torch.Tensor | None,
965 + expert_ids: torch.Tensor,
966 + num_tokens_post_padded: torch.Tensor | None,
967 + token_lora_mapping: torch.Tensor,
968 + top_k_num: int,
969 + lora_ids: torch.Tensor,
970 + adapter_enabled: torch.Tensor,
971 + device: torch.device,
972 + N: int,
973 + M: int,
974 + EM: int,
975 + K: int,
976 + num_tokens: int,
977 + num_experts: int,
978 + num_slices: int,
979 + max_lora_rank: int,
980 + w1_output_dim_size: int,
981 + block_size_m: int,
982 + block_size_n: int,
983 + block_size_k: int,
984 + group_size_m: int,
985 + num_warps: int,
986 + num_stages: int,
987 + split_k: int,
988 + num_active_loras: int,
989 + act_scale: torch.Tensor,
990 + lora_b_scale_stacked: list[torch.Tensor],
991 + mul_routed_weight: bool = False,
992 + offset: int = 0,
993 + use_fp8_w8a8: bool = False,
994 + use_int8_w8a8: bool = False,
995 + use_int8_w8a16: bool = False,
996 + per_channel_quant: bool = False,
997 + block_shape: List[int] | None = None, # noqa: UP006, UP007
998 + use_gdc: bool = False,
999 +) -> None:
1000 + return
1001 +
1002 +
1003 +try:
1004 + direct_register_custom_op(
1005 + op_name="fused_moe_lora_fp8",
1006 + op_func=_fused_moe_lora_fp8,
1007 + mutates_args=["output"],
1008 + fake_impl=_fused_moe_lora_fp8_fake,
1009 + )
1010 +
1011 + direct_register_custom_op(
1012 + op_name="fused_moe_lora_shrink_fp8",
1013 + op_func=_fused_moe_lora_shrink_fp8,
1014 + mutates_args=["a_intermediate_cache1"],
1015 + fake_impl=_fused_moe_lora_shrink_fp8_fake,
1016 + )
1017 +
1018 + direct_register_custom_op(
1019 + op_name="fused_moe_lora_expand_fp8",
1020 + op_func=_fused_moe_lora_expand_fp8,
1021 + mutates_args=["output"],
1022 + fake_impl=_fused_moe_lora_expand_fp8_fake,
1023 + )
1024 +
1025 + fused_moe_lora_fp8 = torch.ops.vllm.fused_moe_lora_fp8
1026 + fused_moe_lora_shrink_fp8 = torch.ops.vllm.fused_moe_lora_shrink_fp8
1027 + fused_moe_lora_expand_fp8 = torch.ops.vllm.fused_moe_lora_expand_fp8
1028 +
1029 +except AttributeError:
1030 + fused_moe_lora_fp8 = _fused_moe_lora_fp8
1031 + fused_moe_lora_shrink_fp8 = _fused_moe_lora_shrink_fp8
1032 + fused_moe_lora_expand_fp8 = _fused_moe_lora_expand_fp8