[lora] Share MoE LoRA Info (#24160)
This commit is contained in:
@@ -300,4 +300,6 @@ class AscendLoRABackend(BaseLoRABackend):
|
|||||||
scalings_tensor, non_blocking=True
|
scalings_tensor, non_blocking=True
|
||||||
)
|
)
|
||||||
batch_info.weight_indices[:bs].copy_(weight_indices_tensor, non_blocking=True)
|
batch_info.weight_indices[:bs].copy_(weight_indices_tensor, non_blocking=True)
|
||||||
|
|
||||||
|
batch_info = self._add_moe_lora_info(forward_batch, batch_info)
|
||||||
self.batch_info = batch_info
|
self.batch_info = batch_info
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
from typing import Tuple, Union
|
from typing import Tuple, Union
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
import triton
|
||||||
|
import triton.language as tl
|
||||||
|
|
||||||
from sglang.srt.lora.backend.lmhead_mixing import LoRABackendLmHeadMixing
|
from sglang.srt.lora.backend.lmhead_mixing import LoRABackendLmHeadMixing
|
||||||
|
from sglang.srt.lora.utils import LoRABatchInfo, MoELoRABatchInfo
|
||||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||||
|
|
||||||
|
|
||||||
@@ -20,6 +23,7 @@ class BaseLoRABackend(LoRABackendLmHeadMixing):
|
|||||||
self.max_loras_per_batch = max_loras_per_batch
|
self.max_loras_per_batch = max_loras_per_batch
|
||||||
self.device = device
|
self.device = device
|
||||||
self.init_lm_head_config()
|
self.init_lm_head_config()
|
||||||
|
self._is_moe_lora = False
|
||||||
|
|
||||||
def run_lora_a_embedding(
|
def run_lora_a_embedding(
|
||||||
self,
|
self,
|
||||||
@@ -157,6 +161,14 @@ class BaseLoRABackend(LoRABackendLmHeadMixing):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_moe_lora(self) -> bool:
|
||||||
|
return self._is_moe_lora
|
||||||
|
|
||||||
|
@is_moe_lora.setter
|
||||||
|
def is_moe_lora(self, value: bool):
|
||||||
|
self._is_moe_lora = value
|
||||||
|
|
||||||
def init_cuda_graph_moe_buffers(
|
def init_cuda_graph_moe_buffers(
|
||||||
self,
|
self,
|
||||||
max_bs: int,
|
max_bs: int,
|
||||||
@@ -236,8 +248,68 @@ class BaseLoRABackend(LoRABackendLmHeadMixing):
|
|||||||
),
|
),
|
||||||
"max_num_tokens_padded": max_num_tokens_padded,
|
"max_num_tokens_padded": max_num_tokens_padded,
|
||||||
"max_num_m_blocks": max_num_m_blocks,
|
"max_num_m_blocks": max_num_m_blocks,
|
||||||
|
"token_lora_mapping": torch.full(
|
||||||
|
(max_bs,), -1, dtype=torch.int32, device=device
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _add_moe_lora_info(
|
||||||
|
self, forward_batch: ForwardBatch, batch_info: LoRABatchInfo
|
||||||
|
) -> LoRABatchInfo:
|
||||||
|
if not self.is_moe_lora:
|
||||||
|
return batch_info
|
||||||
|
|
||||||
|
if batch_info.use_cuda_graph:
|
||||||
|
adapter_enabled = self.moe_cg_buffers["adapter_enabled"]
|
||||||
|
token_lora_mapping = self.moe_cg_buffers["token_lora_mapping"]
|
||||||
|
else:
|
||||||
|
adapter_enabled = None
|
||||||
|
token_lora_mapping = None
|
||||||
|
|
||||||
|
num_tokens = (
|
||||||
|
sum(forward_batch.extend_seq_lens_cpu)
|
||||||
|
if forward_batch.forward_mode.is_extend()
|
||||||
|
else forward_batch.batch_size
|
||||||
|
)
|
||||||
|
max_len = (
|
||||||
|
max(forward_batch.extend_seq_lens_cpu)
|
||||||
|
if forward_batch.forward_mode.is_extend()
|
||||||
|
else 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
batch_info.req_seg_indptr is not None
|
||||||
|
or batch_info.req_weight_indices is not None
|
||||||
|
):
|
||||||
|
assert batch_info.req_seg_indptr is not None
|
||||||
|
assert batch_info.req_weight_indices is not None
|
||||||
|
num_moe_segments = batch_info.bs
|
||||||
|
seg_indptr = batch_info.req_seg_indptr[: num_moe_segments + 1]
|
||||||
|
req_to_lora = batch_info.req_weight_indices[:num_moe_segments]
|
||||||
|
else:
|
||||||
|
num_moe_segments = batch_info.num_segments
|
||||||
|
seg_indptr = batch_info.seg_indptr[: num_moe_segments + 1]
|
||||||
|
req_to_lora = batch_info.weight_indices[:num_moe_segments]
|
||||||
|
|
||||||
|
adapter_enabled, token_lora_mapping = _compute_moe_lora_info(
|
||||||
|
num_tokens,
|
||||||
|
seg_indptr,
|
||||||
|
batch_info.lora_ranks,
|
||||||
|
req_to_lora,
|
||||||
|
adapter_enabled,
|
||||||
|
token_lora_mapping,
|
||||||
|
max_len=max_len,
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_info.moe_lora_info = MoELoRABatchInfo(
|
||||||
|
seg_indptr=seg_indptr,
|
||||||
|
req_to_lora=req_to_lora,
|
||||||
|
adapter_enabled=adapter_enabled,
|
||||||
|
token_lora_mapping=token_lora_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
return batch_info
|
||||||
|
|
||||||
def prepare_lora_batch(
|
def prepare_lora_batch(
|
||||||
self,
|
self,
|
||||||
forward_batch: ForwardBatch,
|
forward_batch: ForwardBatch,
|
||||||
@@ -259,3 +331,117 @@ class BaseLoRABackend(LoRABackendLmHeadMixing):
|
|||||||
use_cuda_graph: whether to use CUDA Graph for this batch
|
use_cuda_graph: whether to use CUDA Graph for this batch
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@triton.jit
|
||||||
|
def _compute_moe_lora_info_kernel(
|
||||||
|
seg_indptr_ptr,
|
||||||
|
lora_ranks_ptr,
|
||||||
|
weight_indices_ptr,
|
||||||
|
adapter_enabled_ptr,
|
||||||
|
token_lora_mapping_ptr,
|
||||||
|
num_segments,
|
||||||
|
max_len,
|
||||||
|
BLOCK_SIZE: tl.constexpr,
|
||||||
|
):
|
||||||
|
pid = tl.program_id(0)
|
||||||
|
num_pid_m = tl.cdiv(max_len, BLOCK_SIZE)
|
||||||
|
|
||||||
|
pid_seg = pid // num_pid_m
|
||||||
|
pid_m = pid % num_pid_m
|
||||||
|
seg_start = tl.load(seg_indptr_ptr + pid_seg)
|
||||||
|
seg_end = tl.load(seg_indptr_ptr + pid_seg + 1)
|
||||||
|
seg_len = seg_end - seg_start
|
||||||
|
|
||||||
|
offs = pid_m * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
||||||
|
valid = offs < seg_len
|
||||||
|
lora_id = tl.load(weight_indices_ptr + pid_seg)
|
||||||
|
lora_rank = tl.load(lora_ranks_ptr + lora_id)
|
||||||
|
tl.store(
|
||||||
|
adapter_enabled_ptr + lora_id,
|
||||||
|
(lora_rank > 0).to(tl.int32),
|
||||||
|
mask=pid_m == 0,
|
||||||
|
)
|
||||||
|
tl.store(token_lora_mapping_ptr + seg_start + offs, lora_id, mask=valid)
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_moe_lora_info(
|
||||||
|
num_tokens: int,
|
||||||
|
seg_indptr: torch.Tensor,
|
||||||
|
lora_ranks: torch.Tensor,
|
||||||
|
weight_indices: torch.Tensor,
|
||||||
|
adapter_enabled: torch.Tensor | None,
|
||||||
|
token_lora_mapping: torch.Tensor | None,
|
||||||
|
max_len: int,
|
||||||
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||||
|
if token_lora_mapping is not None:
|
||||||
|
assert (
|
||||||
|
num_tokens <= token_lora_mapping.shape[0]
|
||||||
|
), "num_tokens must be less than or equal to the shape of token_lora_mapping"
|
||||||
|
token_lora_mapping = token_lora_mapping[:num_tokens]
|
||||||
|
else:
|
||||||
|
token_lora_mapping = torch.empty(
|
||||||
|
(num_tokens,), dtype=torch.int32, device=seg_indptr.device
|
||||||
|
)
|
||||||
|
|
||||||
|
if adapter_enabled is not None:
|
||||||
|
assert (
|
||||||
|
len(lora_ranks) <= adapter_enabled.shape[0]
|
||||||
|
), "lora_ranks must be less than or equal to the shape of adapter_enabled"
|
||||||
|
else:
|
||||||
|
adapter_enabled = torch.empty(
|
||||||
|
len(lora_ranks), dtype=torch.int32, device=lora_ranks.device
|
||||||
|
)
|
||||||
|
|
||||||
|
adapter_enabled.zero_()
|
||||||
|
|
||||||
|
has_segments = weight_indices.numel() != 0
|
||||||
|
use_cuda_kernel = (
|
||||||
|
num_tokens != 0 and has_segments and seg_indptr.device.type == "cuda"
|
||||||
|
)
|
||||||
|
if use_cuda_kernel:
|
||||||
|
block_size = 256
|
||||||
|
tiles_per_segment = triton.cdiv(max_len, block_size)
|
||||||
|
grid_size = tiles_per_segment * weight_indices.numel()
|
||||||
|
assert grid_size * block_size >= num_tokens, (
|
||||||
|
f"MoE LoRA token-mapping launch under-covers tokens: "
|
||||||
|
f"{grid_size=} {block_size=} {num_tokens=}"
|
||||||
|
)
|
||||||
|
_compute_moe_lora_info_kernel[(grid_size,)](
|
||||||
|
seg_indptr,
|
||||||
|
lora_ranks,
|
||||||
|
weight_indices,
|
||||||
|
adapter_enabled,
|
||||||
|
token_lora_mapping,
|
||||||
|
weight_indices.numel(),
|
||||||
|
max_len,
|
||||||
|
BLOCK_SIZE=block_size,
|
||||||
|
)
|
||||||
|
return adapter_enabled, token_lora_mapping
|
||||||
|
|
||||||
|
if has_segments:
|
||||||
|
active_ranks = lora_ranks[weight_indices.long()]
|
||||||
|
adapter_enabled.scatter_(
|
||||||
|
0, weight_indices.long(), (active_ranks > 0).to(torch.int32)
|
||||||
|
)
|
||||||
|
if num_tokens == 0:
|
||||||
|
return adapter_enabled, token_lora_mapping
|
||||||
|
if not has_segments:
|
||||||
|
token_lora_mapping.fill_(-1)
|
||||||
|
return adapter_enabled, token_lora_mapping
|
||||||
|
|
||||||
|
token_positions = torch.arange(
|
||||||
|
num_tokens, device=seg_indptr.device, dtype=torch.int32
|
||||||
|
)
|
||||||
|
# There is a torch.compile bug so we can't use seg_indptr[1:] here.
|
||||||
|
# Instead we pass seg_indptr and then subtract 1 from the result.
|
||||||
|
# This works because seg_indptr[0] == 0.
|
||||||
|
req_indices = (
|
||||||
|
torch.searchsorted(seg_indptr.to(torch.int32), token_positions, right=True) - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
token_lora_mapping = torch.index_select(
|
||||||
|
weight_indices.to(torch.int32), 0, req_indices, out=token_lora_mapping
|
||||||
|
)
|
||||||
|
|
||||||
|
return adapter_enabled, token_lora_mapping
|
||||||
|
|||||||
@@ -324,6 +324,8 @@ class ChunkedSgmvLoRABackend(BaseLoRABackend):
|
|||||||
batch_info.req_seg_indptr[: bs + 1].copy_(req_seg_indptr_cpu, non_blocking=True)
|
batch_info.req_seg_indptr[: bs + 1].copy_(req_seg_indptr_cpu, non_blocking=True)
|
||||||
batch_info.req_weight_indices[:bs].copy_(req_wi_tensor, non_blocking=True)
|
batch_info.req_weight_indices[:bs].copy_(req_wi_tensor, non_blocking=True)
|
||||||
|
|
||||||
|
batch_info = self._add_moe_lora_info(forward_batch, batch_info)
|
||||||
|
|
||||||
self.batch_info = batch_info
|
self.batch_info = batch_info
|
||||||
self.lm_head_batch_info, self.lm_head_pass_batch_infos = (
|
self.lm_head_batch_info, self.lm_head_pass_batch_infos = (
|
||||||
self._prepare_lm_head_batch_info(forward_batch, weight_indices, batch_info)
|
self._prepare_lm_head_batch_info(forward_batch, weight_indices, batch_info)
|
||||||
|
|||||||
@@ -298,4 +298,5 @@ class TorchNativeLoRABackend(BaseLoRABackend):
|
|||||||
batch_info.weight_indices_cpu = weight_indices_tensor
|
batch_info.weight_indices_cpu = weight_indices_tensor
|
||||||
batch_info.scalings_cpu = scalings_tensor
|
batch_info.scalings_cpu = scalings_tensor
|
||||||
|
|
||||||
|
batch_info = self._add_moe_lora_info(forward_batch, batch_info)
|
||||||
self.batch_info = batch_info
|
self.batch_info = batch_info
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ class TritonLoRABackend(BaseLoRABackend):
|
|||||||
)
|
)
|
||||||
batch_info.weight_indices[:bs].copy_(weight_indices_tensor, non_blocking=True)
|
batch_info.weight_indices[:bs].copy_(weight_indices_tensor, non_blocking=True)
|
||||||
|
|
||||||
|
batch_info = self._add_moe_lora_info(forward_batch, batch_info)
|
||||||
self.batch_info = batch_info
|
self.batch_info = batch_info
|
||||||
|
|
||||||
# Biggest win is in decode.
|
# Biggest win is in decode.
|
||||||
|
|||||||
@@ -876,6 +876,8 @@ class FusedMoEWithLoRA(BaseLayerWithLoRA):
|
|||||||
# initializes FusedMoE with its own moe_runner for base path
|
# initializes FusedMoE with its own moe_runner for base path
|
||||||
super().__init__(base_layer, lora_backend)
|
super().__init__(base_layer, lora_backend)
|
||||||
|
|
||||||
|
lora_backend.is_moe_lora = True
|
||||||
|
|
||||||
self.experts_shared_outer_loras: bool = False
|
self.experts_shared_outer_loras: bool = False
|
||||||
self.lora_use_virtual_experts: bool = False
|
self.lora_use_virtual_experts: bool = False
|
||||||
self.quant_method = base_layer.quant_method
|
self.quant_method = base_layer.quant_method
|
||||||
@@ -951,34 +953,12 @@ class FusedMoEWithLoRA(BaseLayerWithLoRA):
|
|||||||
from sglang.srt.lora.lora_moe_runners import LoRAInfo
|
from sglang.srt.lora.lora_moe_runners import LoRAInfo
|
||||||
|
|
||||||
batch_info = self.lora_backend.batch_info
|
batch_info = self.lora_backend.batch_info
|
||||||
|
|
||||||
lora_ranks = batch_info.lora_ranks
|
lora_ranks = batch_info.lora_ranks
|
||||||
|
|
||||||
max_lora_rank = self.down_lora_a_weights.shape[2]
|
max_lora_rank = self.down_lora_a_weights.shape[2]
|
||||||
|
|
||||||
cg_buffers = getattr(self.lora_backend, "moe_cg_buffers", None)
|
cg_buffers = getattr(self.lora_backend, "moe_cg_buffers", None)
|
||||||
wi = (
|
moe_lora_info = batch_info.moe_lora_info
|
||||||
batch_info.req_weight_indices
|
assert moe_lora_info is not None
|
||||||
if batch_info.req_weight_indices is not None
|
|
||||||
else batch_info.weight_indices
|
|
||||||
)
|
|
||||||
if cg_buffers is not None and batch_info.use_cuda_graph:
|
|
||||||
adapter_enabled = cg_buffers["adapter_enabled"]
|
|
||||||
adapter_enabled.zero_()
|
|
||||||
idx_buf = cg_buffers["weight_indices_long"]
|
|
||||||
idx_buf[: batch_info.bs] = wi[: batch_info.bs]
|
|
||||||
adapter_enabled.index_fill_(0, idx_buf[: batch_info.bs], 1)
|
|
||||||
else:
|
|
||||||
adapter_enabled = torch.zeros(
|
|
||||||
len(lora_ranks), dtype=torch.int32, device=lora_ranks.device
|
|
||||||
)
|
|
||||||
adapter_enabled.index_fill_(0, wi.long(), 1)
|
|
||||||
|
|
||||||
seg_indptr = (
|
|
||||||
batch_info.req_seg_indptr
|
|
||||||
if batch_info.req_seg_indptr is not None
|
|
||||||
else batch_info.seg_indptr
|
|
||||||
)
|
|
||||||
req_to_lora = wi
|
|
||||||
|
|
||||||
# Single source of truth: lora_manager precomputes this per-batch from
|
# Single source of truth: lora_manager precomputes this per-batch from
|
||||||
# the Python weight_indices list, no GPU sync needed.
|
# the Python weight_indices list, no GPU sync needed.
|
||||||
@@ -989,13 +969,14 @@ class FusedMoEWithLoRA(BaseLayerWithLoRA):
|
|||||||
gate_up_lora_b_weights=self.gate_up_lora_b_weights,
|
gate_up_lora_b_weights=self.gate_up_lora_b_weights,
|
||||||
down_lora_a_weights=self.down_lora_a_weights,
|
down_lora_a_weights=self.down_lora_a_weights,
|
||||||
down_lora_b_weights=self.down_lora_b_weights,
|
down_lora_b_weights=self.down_lora_b_weights,
|
||||||
seg_indptr=seg_indptr,
|
seg_indptr=moe_lora_info.seg_indptr,
|
||||||
req_to_lora=req_to_lora,
|
req_to_lora=moe_lora_info.req_to_lora,
|
||||||
lora_ranks=lora_ranks,
|
lora_ranks=lora_ranks,
|
||||||
adapter_enabled=adapter_enabled,
|
adapter_enabled=moe_lora_info.adapter_enabled,
|
||||||
has_active_lora=has_active_lora,
|
token_lora_mapping=moe_lora_info.token_lora_mapping,
|
||||||
max_lora_rank=max_lora_rank,
|
max_lora_rank=max_lora_rank,
|
||||||
num_experts=self.base_layer.num_experts,
|
num_experts=self.base_layer.num_experts,
|
||||||
|
has_active_lora=has_active_lora,
|
||||||
experts_shared_outer_loras=self.experts_shared_outer_loras,
|
experts_shared_outer_loras=self.experts_shared_outer_loras,
|
||||||
cg_buffers=cg_buffers,
|
cg_buffers=cg_buffers,
|
||||||
tp_size=self.tp_size,
|
tp_size=self.tp_size,
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ class LoRAInfo:
|
|||||||
# LoRA config per adapter
|
# LoRA config per adapter
|
||||||
lora_ranks: torch.Tensor # [num_loras]
|
lora_ranks: torch.Tensor # [num_loras]
|
||||||
adapter_enabled: torch.Tensor # [num_loras] - which adapters are enabled
|
adapter_enabled: torch.Tensor # [num_loras] - which adapters are enabled
|
||||||
|
token_lora_mapping: torch.Tensor # [num_tokens] - adapter used by each token
|
||||||
max_lora_rank: int # Maximum LoRA rank across all adapters
|
max_lora_rank: int # Maximum LoRA rank across all adapters
|
||||||
|
|
||||||
num_experts: int
|
num_experts: int
|
||||||
@@ -202,22 +203,6 @@ class LoRAHooks:
|
|||||||
) = None
|
) = None
|
||||||
|
|
||||||
|
|
||||||
def _compute_token_lora_mapping(
|
|
||||||
hidden_states: torch.Tensor,
|
|
||||||
lora_info: LoRAInfo,
|
|
||||||
) -> torch.Tensor:
|
|
||||||
"""Map each token to its LoRA adapter index (-1 for no LoRA)."""
|
|
||||||
token_positions = torch.arange(
|
|
||||||
hidden_states.shape[0], device=hidden_states.device, dtype=torch.int32
|
|
||||||
)
|
|
||||||
req_indices = torch.searchsorted(
|
|
||||||
lora_info.seg_indptr[1:].to(torch.int32),
|
|
||||||
token_positions,
|
|
||||||
right=True,
|
|
||||||
)
|
|
||||||
return lora_info.req_to_lora.to(torch.int32)[req_indices]
|
|
||||||
|
|
||||||
|
|
||||||
def _compute_lora_alignment(
|
def _compute_lora_alignment(
|
||||||
topk_ids: torch.Tensor,
|
topk_ids: torch.Tensor,
|
||||||
lora_info: LoRAInfo,
|
lora_info: LoRAInfo,
|
||||||
@@ -520,7 +505,7 @@ def build_lora_hooks(
|
|||||||
lora_ids: torch.Tensor | None = None
|
lora_ids: torch.Tensor | None = None
|
||||||
|
|
||||||
if lora_info.lora_use_virtual_experts:
|
if lora_info.lora_use_virtual_experts:
|
||||||
token_lora_mapping = _compute_token_lora_mapping(hidden_states, lora_info)
|
token_lora_mapping = lora_info.token_lora_mapping
|
||||||
else:
|
else:
|
||||||
(
|
(
|
||||||
sorted_token_ids_reshaped,
|
sorted_token_ids_reshaped,
|
||||||
|
|||||||
@@ -8,6 +8,22 @@ from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
|||||||
from sglang.srt.utils.hf_transformers_utils import AutoConfig
|
from sglang.srt.utils.hf_transformers_utils import AutoConfig
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MoELoRABatchInfo:
|
||||||
|
# Per-request segment indptrs used by MoE LoRA routing, shape (bs + 1,).
|
||||||
|
seg_indptr: torch.Tensor
|
||||||
|
|
||||||
|
# Per-request adapter index used by MoE LoRA routing, shape (bs,).
|
||||||
|
req_to_lora: torch.Tensor
|
||||||
|
|
||||||
|
# A mask indicating if lora adapter is enabled. Shape (num_loras,)
|
||||||
|
adapter_enabled: torch.Tensor
|
||||||
|
|
||||||
|
# A mapping of which lora adapter is used for each token. Shape (num_tokens,)
|
||||||
|
# If a token has no lora adapter, the value is -1.
|
||||||
|
token_lora_mapping: torch.Tensor
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LoRABatchInfo:
|
class LoRABatchInfo:
|
||||||
# The forward mode is using CUDA Graph.
|
# The forward mode is using CUDA Graph.
|
||||||
@@ -58,6 +74,9 @@ class LoRABatchInfo:
|
|||||||
# Per-request adapter index, shape (bs,).
|
# Per-request adapter index, shape (bs,).
|
||||||
req_weight_indices: Optional[torch.Tensor] = None
|
req_weight_indices: Optional[torch.Tensor] = None
|
||||||
|
|
||||||
|
# MoE LoRA batch info
|
||||||
|
moe_lora_info: Optional[MoELoRABatchInfo] = None
|
||||||
|
|
||||||
|
|
||||||
class LoRAType(Enum):
|
class LoRAType(Enum):
|
||||||
LORA_A = 0
|
LORA_A = 0
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.lora.backend.base_backend import _compute_moe_lora_info
|
||||||
|
from sglang.test.ci.ci_register import register_cuda_ci
|
||||||
|
|
||||||
|
register_cuda_ci(est_time=5, stage="base-b", runner_config="1-gpu-small")
|
||||||
|
|
||||||
|
|
||||||
|
def _expected_adapter_enabled(
|
||||||
|
lora_ranks: torch.Tensor,
|
||||||
|
weight_indices: torch.Tensor,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
expected = torch.zeros_like(lora_ranks)
|
||||||
|
expected.scatter_(
|
||||||
|
0,
|
||||||
|
weight_indices.long(),
|
||||||
|
(lora_ranks[weight_indices.long()] > 0).to(torch.int32),
|
||||||
|
)
|
||||||
|
return expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("use_preallocated_buffers", [False, True])
|
||||||
|
def test_compute_moe_lora_info_expands_segments(use_preallocated_buffers: bool):
|
||||||
|
device = "cuda"
|
||||||
|
seg_lens = torch.tensor([5, 1, 7, 3, 9, 2], dtype=torch.int32, device=device)
|
||||||
|
seg_indptr = torch.zeros((seg_lens.numel() + 1,), dtype=torch.int32, device=device)
|
||||||
|
seg_indptr[1:] = torch.cumsum(seg_lens, dim=0)
|
||||||
|
|
||||||
|
weight_indices = torch.tensor([2, 0, 5, 2, 3, 7], dtype=torch.int32, device=device)
|
||||||
|
lora_ranks = torch.tensor(
|
||||||
|
[0, 12, 16, 32, 24, 8, 0, 4], dtype=torch.int32, device=device
|
||||||
|
)
|
||||||
|
num_tokens = int(seg_indptr[-1].item())
|
||||||
|
|
||||||
|
if use_preallocated_buffers:
|
||||||
|
adapter_enabled = torch.full_like(lora_ranks, 123)
|
||||||
|
token_lora_mapping = torch.full(
|
||||||
|
(num_tokens + 11,), 456, dtype=torch.int32, device=device
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
adapter_enabled = None
|
||||||
|
token_lora_mapping = None
|
||||||
|
|
||||||
|
actual_enabled, actual_mapping = _compute_moe_lora_info(
|
||||||
|
num_tokens,
|
||||||
|
seg_indptr,
|
||||||
|
lora_ranks,
|
||||||
|
weight_indices,
|
||||||
|
adapter_enabled,
|
||||||
|
token_lora_mapping,
|
||||||
|
max_len=int(seg_lens.max().item()),
|
||||||
|
)
|
||||||
|
torch.cuda.synchronize()
|
||||||
|
|
||||||
|
expected_mapping = torch.repeat_interleave(weight_indices, seg_lens)
|
||||||
|
expected_enabled = _expected_adapter_enabled(lora_ranks, weight_indices)
|
||||||
|
|
||||||
|
torch.testing.assert_close(actual_mapping, expected_mapping)
|
||||||
|
torch.testing.assert_close(actual_enabled, expected_enabled)
|
||||||
|
|
||||||
|
if use_preallocated_buffers:
|
||||||
|
assert actual_mapping.data_ptr() == token_lora_mapping.data_ptr()
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_moe_lora_info_rejects_undercovered_launch():
|
||||||
|
device = "cuda"
|
||||||
|
seg_indptr = torch.tensor([0, 300], dtype=torch.int32, device=device)
|
||||||
|
weight_indices = torch.tensor([0], dtype=torch.int32, device=device)
|
||||||
|
lora_ranks = torch.tensor([16], dtype=torch.int32, device=device)
|
||||||
|
|
||||||
|
with pytest.raises(AssertionError, match="under-covers tokens"):
|
||||||
|
_compute_moe_lora_info(
|
||||||
|
300,
|
||||||
|
seg_indptr,
|
||||||
|
lora_ranks,
|
||||||
|
weight_indices,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
max_len=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(pytest.main([__file__]))
|
||||||
Reference in New Issue
Block a user