Enable embedding lookup/lora_a logic for chunked backend (#17692)
Co-authored-by: Bruce Wu <mogicianwu@fb.com> Co-authored-by: Baizhou Zhang <sobereddiezhang@gmail.com> Co-authored-by: Ethan (Yusheng) Su <yushengsu.thu@gmail.com>
This commit is contained in:
co-authored by
Bruce Wu
Baizhou Zhang
Ethan Su
parent
061ec582bf
commit
70a6fb53af
@@ -688,6 +688,12 @@ class LogitsProcessor(nn.Module):
|
|||||||
start_idx = i * chunk_size
|
start_idx = i * chunk_size
|
||||||
end_idx = min((i + 1) * chunk_size, total_size)
|
end_idx = min((i + 1) * chunk_size, total_size)
|
||||||
|
|
||||||
|
# Notify lm_head LoRA about the current chunk so it can swap
|
||||||
|
# to the precomputed per-chunk batch_info. This is a no-op
|
||||||
|
# for non-LoRA lm_head modules.
|
||||||
|
if hasattr(lm_head, "set_lm_head_pass"):
|
||||||
|
lm_head.set_lm_head_pass(i)
|
||||||
|
|
||||||
# Get indices for this chunk
|
# Get indices for this chunk
|
||||||
chunk_mask = (input_logprob_indices >= start_idx) & (
|
chunk_mask = (input_logprob_indices >= start_idx) & (
|
||||||
input_logprob_indices < end_idx
|
input_logprob_indices < end_idx
|
||||||
@@ -792,6 +798,13 @@ class LogitsProcessor(nn.Module):
|
|||||||
]
|
]
|
||||||
input_token_logprobs.append(chunk_input_token_logprobs)
|
input_token_logprobs.append(chunk_input_token_logprobs)
|
||||||
|
|
||||||
|
# Restore the full-pruned lm_head batch_info after chunk iteration.
|
||||||
|
if hasattr(lm_head, "reset_lm_head_pass"):
|
||||||
|
assert hasattr(
|
||||||
|
lm_head, "set_lm_head_pass"
|
||||||
|
), "lm_head must have set_lm_head_pass method and reset_lm_head_pass method at the same time"
|
||||||
|
lm_head.reset_lm_head_pass()
|
||||||
|
|
||||||
# Concatenate the results
|
# Concatenate the results
|
||||||
input_token_logprobs = torch.cat(input_token_logprobs, dim=0)
|
input_token_logprobs = torch.cat(input_token_logprobs, dim=0)
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ from typing import Tuple, Union
|
|||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.lora.backend.lmhead_mixing import LoRABackendLmHeadMixing
|
||||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||||
|
|
||||||
|
|
||||||
class BaseLoRABackend:
|
class BaseLoRABackend(LoRABackendLmHeadMixing):
|
||||||
"""Base class for different Lora backends.
|
"""Base class for different Lora backends.
|
||||||
Each backend has its own implementation of Lora kernels.
|
Each backend has its own implementation of Lora kernels.
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ class BaseLoRABackend:
|
|||||||
def __init__(self, max_loras_per_batch: int, device: torch.device):
|
def __init__(self, max_loras_per_batch: int, device: torch.device):
|
||||||
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()
|
||||||
|
|
||||||
def run_lora_a_embedding(
|
def run_lora_a_embedding(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
|
import dataclasses
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from sglang.srt.lora.backend.base_backend import BaseLoRABackend
|
from sglang.srt.lora.backend.base_backend import BaseLoRABackend
|
||||||
from sglang.srt.lora.triton_ops import (
|
from sglang.srt.lora.triton_ops import (
|
||||||
|
chunked_embedding_lora_a_forward,
|
||||||
chunked_sgmv_lora_expand_forward,
|
chunked_sgmv_lora_expand_forward,
|
||||||
chunked_sgmv_lora_shrink_forward,
|
chunked_sgmv_lora_shrink_forward,
|
||||||
)
|
)
|
||||||
from sglang.srt.lora.utils import LoRABatchInfo, generate_sequence_lengths
|
from sglang.srt.lora.utils import (
|
||||||
|
LoRABatchInfo,
|
||||||
|
generate_sequence_lengths,
|
||||||
|
get_lm_head_pruned_lens,
|
||||||
|
merge_and_chunk_segments,
|
||||||
|
)
|
||||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||||
from sglang.srt.server_args import ServerArgs
|
from sglang.srt.server_args import ServerArgs
|
||||||
|
|
||||||
@@ -33,13 +42,40 @@ class ChunkedSgmvLoRABackend(BaseLoRABackend):
|
|||||||
super().__init__(max_loras_per_batch, device)
|
super().__init__(max_loras_per_batch, device)
|
||||||
self.max_chunk_size = server_args.max_lora_chunk_size
|
self.max_chunk_size = server_args.max_lora_chunk_size
|
||||||
|
|
||||||
def run_lora_a_sgemm(
|
def run_lora_a_embedding(
|
||||||
self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs
|
self,
|
||||||
|
input_ids: torch.Tensor,
|
||||||
|
weights: torch.Tensor,
|
||||||
|
vocab_size: int,
|
||||||
|
extra_embeddings: torch.Tensor = None,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
|
assert (
|
||||||
|
extra_embeddings is None
|
||||||
|
), "Extra embeddings for lora a is not supported yet in chunked backend"
|
||||||
|
return chunked_embedding_lora_a_forward(
|
||||||
|
input_ids=input_ids,
|
||||||
|
weights=weights,
|
||||||
|
batch_info=self.batch_info,
|
||||||
|
vocab_size=vocab_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run_lora_a_sgemm(
|
||||||
|
self,
|
||||||
|
x: torch.Tensor,
|
||||||
|
weights: torch.Tensor,
|
||||||
|
pruned_batch_info: LoRABatchInfo = None,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
batch_info = (
|
||||||
|
pruned_batch_info if pruned_batch_info is not None else self.batch_info
|
||||||
|
)
|
||||||
return chunked_sgmv_lora_shrink_forward(
|
return chunked_sgmv_lora_shrink_forward(
|
||||||
x=x,
|
x=x,
|
||||||
weights=weights,
|
weights=weights,
|
||||||
batch_info=self.batch_info,
|
batch_info=batch_info,
|
||||||
num_slices=1,
|
num_slices=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,16 +85,20 @@ class ChunkedSgmvLoRABackend(BaseLoRABackend):
|
|||||||
weights: torch.Tensor,
|
weights: torch.Tensor,
|
||||||
output_offset: torch.Tensor,
|
output_offset: torch.Tensor,
|
||||||
base_output: torch.Tensor = None,
|
base_output: torch.Tensor = None,
|
||||||
|
pruned_batch_info: LoRABatchInfo = None,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
# For simple lora B, we use slice offsets [0, output_dim]
|
# For simple lora B, we use slice offsets [0, output_dim]
|
||||||
output_dim = weights.shape[-2]
|
output_dim = weights.shape[-2]
|
||||||
max_slice_size = output_dim
|
max_slice_size = output_dim
|
||||||
|
batch_info = (
|
||||||
|
pruned_batch_info if pruned_batch_info is not None else self.batch_info
|
||||||
|
)
|
||||||
return chunked_sgmv_lora_expand_forward(
|
return chunked_sgmv_lora_expand_forward(
|
||||||
x=x,
|
x=x,
|
||||||
weights=weights,
|
weights=weights,
|
||||||
batch_info=self.batch_info,
|
batch_info=batch_info,
|
||||||
slice_offsets=output_offset,
|
slice_offsets=output_offset,
|
||||||
max_slice_size=max_slice_size,
|
max_slice_size=max_slice_size,
|
||||||
base_output=base_output,
|
base_output=base_output,
|
||||||
@@ -141,15 +181,18 @@ class ChunkedSgmvLoRABackend(BaseLoRABackend):
|
|||||||
Returns:
|
Returns:
|
||||||
The determined chunk size
|
The determined chunk size
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.max_chunk_size <= MIN_CHUNK_SIZE:
|
|
||||||
return MIN_CHUNK_SIZE
|
|
||||||
|
|
||||||
num_tokens = (
|
num_tokens = (
|
||||||
forward_batch.extend_num_tokens
|
forward_batch.extend_num_tokens
|
||||||
if forward_batch.forward_mode.is_extend()
|
if forward_batch.forward_mode.is_extend()
|
||||||
else forward_batch.batch_size
|
else forward_batch.batch_size
|
||||||
)
|
)
|
||||||
|
return self._determine_chunk_size_for_tokens(num_tokens)
|
||||||
|
|
||||||
|
def _determine_chunk_size_for_tokens(self, num_tokens: int) -> int:
|
||||||
|
"""Determine chunk size given a token count directly."""
|
||||||
|
if self.max_chunk_size <= MIN_CHUNK_SIZE:
|
||||||
|
return MIN_CHUNK_SIZE
|
||||||
|
|
||||||
if num_tokens >= 256:
|
if num_tokens >= 256:
|
||||||
chunk_size = 128
|
chunk_size = 128
|
||||||
elif num_tokens >= 64:
|
elif num_tokens >= 64:
|
||||||
@@ -253,6 +296,85 @@ class ChunkedSgmvLoRABackend(BaseLoRABackend):
|
|||||||
batch_info.permutation[: len(permutation)].copy_(permutation, non_blocking=True)
|
batch_info.permutation[: len(permutation)].copy_(permutation, non_blocking=True)
|
||||||
|
|
||||||
self.batch_info = batch_info
|
self.batch_info = batch_info
|
||||||
|
self.lm_head_batch_info, self.lm_head_pass_batch_infos = (
|
||||||
|
self._prepare_lm_head_batch_info(forward_batch, weight_indices, batch_info)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _prepare_lm_head_batch_info(
|
||||||
|
self,
|
||||||
|
forward_batch: ForwardBatch,
|
||||||
|
weight_indices: list[int],
|
||||||
|
batch_info: LoRABatchInfo,
|
||||||
|
) -> Tuple[Optional[LoRABatchInfo], Optional[List[LoRABatchInfo]]]:
|
||||||
|
|
||||||
|
# Precompute lm_head_batch_info for pruned lm_head LoRA
|
||||||
|
pruned_lens = get_lm_head_pruned_lens(forward_batch)
|
||||||
|
lm_head_batch_info = None
|
||||||
|
lm_head_pass_batch_infos = None
|
||||||
|
|
||||||
|
if pruned_lens is not None:
|
||||||
|
pruned_total = sum(pruned_lens)
|
||||||
|
chunk_size = self._determine_chunk_size_for_tokens(pruned_total)
|
||||||
|
lm_head_segments = merge_and_chunk_segments(
|
||||||
|
weight_indices, pruned_lens, chunk_size=chunk_size
|
||||||
|
)
|
||||||
|
lm_head_batch_info = self._build_lm_head_batch_info(
|
||||||
|
lm_head_segments, batch_info, chunk_size, pruned_total
|
||||||
|
)
|
||||||
|
|
||||||
|
# Precompute per-pass batch_infos for logprobs chunking
|
||||||
|
pass_segments = self._get_lm_head_pass_segments(weight_indices, pruned_lens)
|
||||||
|
if pass_segments is not None:
|
||||||
|
lm_head_pass_batch_infos = []
|
||||||
|
for seg_wi, seg_lens_list in pass_segments:
|
||||||
|
pass_total = sum(seg_lens_list)
|
||||||
|
pass_chunk_size = self._determine_chunk_size_for_tokens(pass_total)
|
||||||
|
chunked_segments = merge_and_chunk_segments(
|
||||||
|
seg_wi, seg_lens_list, chunk_size=pass_chunk_size
|
||||||
|
)
|
||||||
|
lm_head_pass_batch_infos.append(
|
||||||
|
self._build_lm_head_batch_info(
|
||||||
|
chunked_segments,
|
||||||
|
batch_info,
|
||||||
|
pass_chunk_size,
|
||||||
|
pass_total,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return lm_head_batch_info, lm_head_pass_batch_infos
|
||||||
|
|
||||||
|
def _build_lm_head_batch_info(
|
||||||
|
self,
|
||||||
|
lm_head_segments: Tuple[List[int], List[int]],
|
||||||
|
batch_info: LoRABatchInfo,
|
||||||
|
chunk_size: int,
|
||||||
|
expected_tokens: int,
|
||||||
|
) -> LoRABatchInfo:
|
||||||
|
seg_weight_indices_cpu, seg_lens_cpu = lm_head_segments
|
||||||
|
pruned_total = sum(seg_lens_cpu)
|
||||||
|
num_segments = len(seg_weight_indices_cpu)
|
||||||
|
|
||||||
|
weight_indices = torch.tensor(
|
||||||
|
seg_weight_indices_cpu, dtype=torch.int32, device=self.device
|
||||||
|
)
|
||||||
|
seg_lens = torch.tensor(seg_lens_cpu, dtype=torch.int32, device=self.device)
|
||||||
|
seg_indptr = torch.zeros(
|
||||||
|
(num_segments + 1,), dtype=torch.int32, device=self.device
|
||||||
|
)
|
||||||
|
seg_indptr[1:] = torch.cumsum(seg_lens, dim=0)
|
||||||
|
|
||||||
|
# Identity permutation (lm_head tokens are in original order)
|
||||||
|
permutation = torch.arange(pruned_total, dtype=torch.int32, device=self.device)
|
||||||
|
|
||||||
|
return dataclasses.replace(
|
||||||
|
batch_info,
|
||||||
|
num_segments=num_segments,
|
||||||
|
max_len=chunk_size,
|
||||||
|
seg_indptr=seg_indptr,
|
||||||
|
weight_indices=weight_indices,
|
||||||
|
permutation=permutation,
|
||||||
|
expected_tokens=expected_tokens,
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_permutation(seq_weight_indices, forward_batch: ForwardBatch):
|
def _get_permutation(seq_weight_indices, forward_batch: ForwardBatch):
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
from sglang.srt.environ import envs
|
||||||
|
from sglang.srt.lora.utils import LoRABatchInfo, build_lm_head_pass_segments
|
||||||
|
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||||
|
|
||||||
|
|
||||||
|
class LoRABackendLmHeadMixing:
|
||||||
|
def init_lm_head_config(self):
|
||||||
|
self.lm_head_batch_info = None
|
||||||
|
# Precomputed per-pass lm_head batch_infos. When the logits processor
|
||||||
|
# calls lm_head in multiple passes (chunked logprobs), each pass gets
|
||||||
|
# its own batch_info from this list.
|
||||||
|
self.lm_head_pass_batch_infos = None
|
||||||
|
# Current pass index. When set, apply_lora uses
|
||||||
|
# lm_head_pass_batch_infos[idx] instead of lm_head_batch_info.
|
||||||
|
self._lm_head_pass_idx = None
|
||||||
|
|
||||||
|
def _get_lm_head_pass_segments(
|
||||||
|
self,
|
||||||
|
weight_indices: list[int],
|
||||||
|
pruned_lens: List[int],
|
||||||
|
) -> Optional[List[Tuple[List[int], List[int]]]]:
|
||||||
|
"""Compute per-pass segment info for lm_head LoRA logprobs chunking.
|
||||||
|
|
||||||
|
When LogitsProcessor splits pruned states into fixed-size passes,
|
||||||
|
each pass needs its own segmentation so that lm_head LoRA operates
|
||||||
|
on the correct adapter assignments. This method returns the generic
|
||||||
|
per-pass (seg_weight_indices, seg_lens) tuples; each backend is
|
||||||
|
responsible for converting them into backend-specific LoRABatchInfo.
|
||||||
|
|
||||||
|
Returns None if logprobs chunking is disabled or the pruned token
|
||||||
|
count does not exceed the logprobs chunk size.
|
||||||
|
"""
|
||||||
|
logprobs_chunk_size = envs.SGLANG_LOGITS_PROCESSER_CHUNK_SIZE.get()
|
||||||
|
enable_logprobs_chunk = envs.SGLANG_ENABLE_LOGITS_PROCESSER_CHUNK.get()
|
||||||
|
pruned_total = sum(pruned_lens)
|
||||||
|
|
||||||
|
if not enable_logprobs_chunk or pruned_total <= logprobs_chunk_size:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return build_lm_head_pass_segments(
|
||||||
|
weight_indices, pruned_lens, logprobs_chunk_size
|
||||||
|
)
|
||||||
|
|
||||||
|
def _prepare_lm_head_batch_info(
|
||||||
|
self,
|
||||||
|
forward_batch: ForwardBatch,
|
||||||
|
weight_indices: list[int],
|
||||||
|
batch_info: LoRABatchInfo,
|
||||||
|
) -> Tuple[Optional[LoRABatchInfo], Optional[List[LoRABatchInfo]]]:
|
||||||
|
"""Prepare the lm_head batch info for the current forward batch."""
|
||||||
|
"""It returns a tuple of (lm_head_batch_info, lm_head_pass_batch_infos)."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _build_lm_head_batch_info(
|
||||||
|
self,
|
||||||
|
lm_head_segments: Tuple[List[int], List[int]],
|
||||||
|
batch_info: LoRABatchInfo,
|
||||||
|
chunk_size: int,
|
||||||
|
expected_tokens: int,
|
||||||
|
) -> LoRABatchInfo:
|
||||||
|
"""Build a LoRABatchInfo for pruned lm_head input."""
|
||||||
|
pass
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import dataclasses
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from sglang.srt.lora.backend.base_backend import BaseLoRABackend
|
from sglang.srt.lora.backend.base_backend import BaseLoRABackend
|
||||||
@@ -8,7 +11,11 @@ from sglang.srt.lora.triton_ops import (
|
|||||||
sgemm_lora_a_fwd,
|
sgemm_lora_a_fwd,
|
||||||
sgemm_lora_b_fwd,
|
sgemm_lora_b_fwd,
|
||||||
)
|
)
|
||||||
from sglang.srt.lora.utils import LoRABatchInfo
|
from sglang.srt.lora.utils import (
|
||||||
|
LoRABatchInfo,
|
||||||
|
get_lm_head_pruned_lens,
|
||||||
|
merge_and_chunk_segments,
|
||||||
|
)
|
||||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||||
|
|
||||||
|
|
||||||
@@ -42,19 +49,31 @@ class TritonLoRABackend(BaseLoRABackend):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def run_lora_a_sgemm(
|
def run_lora_a_sgemm(
|
||||||
self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs
|
self,
|
||||||
|
x: torch.Tensor,
|
||||||
|
weights: torch.Tensor,
|
||||||
|
pruned_batch_info: LoRABatchInfo = None,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
return sgemm_lora_a_fwd(x, weights, self.batch_info)
|
batch_info = (
|
||||||
|
pruned_batch_info if pruned_batch_info is not None else self.batch_info
|
||||||
|
)
|
||||||
|
return sgemm_lora_a_fwd(x, weights, batch_info)
|
||||||
|
|
||||||
def run_lora_b_sgemm(
|
def run_lora_b_sgemm(
|
||||||
self,
|
self,
|
||||||
x: torch.Tensor,
|
x: torch.Tensor,
|
||||||
weights: torch.Tensor,
|
weights: torch.Tensor,
|
||||||
base_output: torch.Tensor = None,
|
base_output: torch.Tensor = None,
|
||||||
|
pruned_batch_info: LoRABatchInfo = None,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
return sgemm_lora_b_fwd(x, weights, self.batch_info, base_output)
|
batch_info = (
|
||||||
|
pruned_batch_info if pruned_batch_info is not None else self.batch_info
|
||||||
|
)
|
||||||
|
return sgemm_lora_b_fwd(x, weights, batch_info, base_output)
|
||||||
|
|
||||||
def run_qkv_lora(
|
def run_qkv_lora(
|
||||||
self,
|
self,
|
||||||
@@ -214,3 +233,72 @@ 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)
|
||||||
|
|
||||||
self.batch_info = batch_info
|
self.batch_info = batch_info
|
||||||
|
self.lm_head_batch_info, self.lm_head_pass_batch_infos = (
|
||||||
|
self._prepare_lm_head_batch_info(forward_batch, weight_indices, batch_info)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _prepare_lm_head_batch_info(
|
||||||
|
self,
|
||||||
|
forward_batch: ForwardBatch,
|
||||||
|
weight_indices: list[int],
|
||||||
|
batch_info: LoRABatchInfo,
|
||||||
|
) -> Tuple[Optional[LoRABatchInfo], Optional[List[LoRABatchInfo]]]:
|
||||||
|
|
||||||
|
# Precompute lm_head_batch_info for pruned lm_head LoRA
|
||||||
|
pruned_lens = get_lm_head_pruned_lens(forward_batch)
|
||||||
|
lm_head_batch_info = None
|
||||||
|
lm_head_pass_batch_infos = None
|
||||||
|
|
||||||
|
if pruned_lens is not None:
|
||||||
|
pruned_total = sum(pruned_lens)
|
||||||
|
lm_head_segments = merge_and_chunk_segments(
|
||||||
|
weight_indices, pruned_lens, chunk_size=pruned_total
|
||||||
|
)
|
||||||
|
lm_head_batch_info = self._build_lm_head_batch_info(
|
||||||
|
lm_head_segments, batch_info, pruned_total
|
||||||
|
)
|
||||||
|
|
||||||
|
# Precompute per-pass batch_infos for logprobs chunking
|
||||||
|
pass_segments = self._get_lm_head_pass_segments(weight_indices, pruned_lens)
|
||||||
|
if pass_segments is not None:
|
||||||
|
lm_head_pass_batch_infos = []
|
||||||
|
for seg_wi, seg_lens_list in pass_segments:
|
||||||
|
pass_total = sum(seg_lens_list)
|
||||||
|
merged_segments = merge_and_chunk_segments(
|
||||||
|
seg_wi, seg_lens_list, chunk_size=pass_total
|
||||||
|
)
|
||||||
|
self.lm_head_pass_batch_infos.append(
|
||||||
|
self._build_lm_head_batch_info(
|
||||||
|
merged_segments, batch_info, pass_total
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return lm_head_batch_info, lm_head_pass_batch_infos
|
||||||
|
|
||||||
|
def _build_lm_head_batch_info(
|
||||||
|
self,
|
||||||
|
lm_head_segments: Tuple[List[int], List[int]],
|
||||||
|
batch_info: LoRABatchInfo,
|
||||||
|
expected_tokens: int,
|
||||||
|
) -> LoRABatchInfo:
|
||||||
|
seg_weight_indices_cpu, seg_lens_cpu = lm_head_segments
|
||||||
|
num_segments = len(seg_weight_indices_cpu)
|
||||||
|
|
||||||
|
seg_lens = torch.tensor(seg_lens_cpu, dtype=torch.int32, device=self.device)
|
||||||
|
seg_indptr = torch.zeros(
|
||||||
|
(num_segments + 1,), dtype=torch.int32, device=self.device
|
||||||
|
)
|
||||||
|
seg_indptr[1:] = torch.cumsum(seg_lens, dim=0)
|
||||||
|
|
||||||
|
return dataclasses.replace(
|
||||||
|
batch_info,
|
||||||
|
bs=num_segments,
|
||||||
|
num_segments=num_segments,
|
||||||
|
max_len=max(seg_lens_cpu),
|
||||||
|
seg_lens=seg_lens,
|
||||||
|
seg_indptr=seg_indptr,
|
||||||
|
weight_indices=torch.tensor(
|
||||||
|
seg_weight_indices_cpu, dtype=torch.int32, device=self.device
|
||||||
|
),
|
||||||
|
expected_tokens=expected_tokens,
|
||||||
|
)
|
||||||
|
|||||||
@@ -240,8 +240,46 @@ class ParallelLMHeadWithLoRA(BaseLayerWithLoRA):
|
|||||||
self.lm_head_A_buffer = lm_head_A_buffer # (num_loras, rank, hidden_dim)
|
self.lm_head_A_buffer = lm_head_A_buffer # (num_loras, rank, hidden_dim)
|
||||||
self.lm_head_B_buffer = lm_head_B_buffer # (num_loras, vocab_size, rank)
|
self.lm_head_B_buffer = lm_head_B_buffer # (num_loras, vocab_size, rank)
|
||||||
|
|
||||||
|
def _get_lm_head_batch_info(self, num_tokens: int):
|
||||||
|
"""Resolve and validate the active lm_head batch_info.
|
||||||
|
|
||||||
|
When the logits processor calls lm_head in multiple passes
|
||||||
|
(chunked logprobs), _lm_head_pass_idx selects a precomputed
|
||||||
|
per-pass batch_info. Otherwise the full-pruned batch_info is used.
|
||||||
|
|
||||||
|
Returns None when no lm_head pruning applies (decode, no LoRA, etc.).
|
||||||
|
"""
|
||||||
|
pass_idx = self.lora_backend._lm_head_pass_idx
|
||||||
|
if (
|
||||||
|
pass_idx is not None
|
||||||
|
and self.lora_backend.lm_head_pass_batch_infos is not None
|
||||||
|
):
|
||||||
|
batch_info = self.lora_backend.lm_head_pass_batch_infos[pass_idx]
|
||||||
|
else:
|
||||||
|
batch_info = self.lora_backend.lm_head_batch_info
|
||||||
|
|
||||||
|
if batch_info is not None:
|
||||||
|
if batch_info.use_cuda_graph:
|
||||||
|
raise RuntimeError(
|
||||||
|
"lm_head LoRA with pruned batch info is not supported "
|
||||||
|
"under CUDA graph. lm_head pruning should only occur "
|
||||||
|
"during extend, which does not use CUDA graph."
|
||||||
|
)
|
||||||
|
if num_tokens != batch_info.expected_tokens:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"lm_head LoRA input token count mismatch: got "
|
||||||
|
f"{num_tokens} tokens but lm_head_batch_info expects "
|
||||||
|
f"{batch_info.expected_tokens}. This likely means "
|
||||||
|
f"a pruning step in LogitsProcessor._get_pruned_states is "
|
||||||
|
f"not reflected in get_lm_head_pruned_lens()."
|
||||||
|
)
|
||||||
|
|
||||||
|
return batch_info
|
||||||
|
|
||||||
def apply_lora(
|
def apply_lora(
|
||||||
self, base_output: torch.Tensor, hidden_states: torch.Tensor
|
self,
|
||||||
|
base_output: torch.Tensor,
|
||||||
|
hidden_states: torch.Tensor,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
"""
|
"""
|
||||||
Apply LoRA to LM head layer.
|
Apply LoRA to LM head layer.
|
||||||
@@ -250,9 +288,13 @@ class ParallelLMHeadWithLoRA(BaseLayerWithLoRA):
|
|||||||
= hidden @ W^T + hidden @ A^T @ B^T
|
= hidden @ W^T + hidden @ A^T @ B^T
|
||||||
= base_output + (hidden @ A^T) @ B^T
|
= base_output + (hidden @ A^T) @ B^T
|
||||||
"""
|
"""
|
||||||
|
lm_head_batch_info = self._get_lm_head_batch_info(hidden_states.shape[0])
|
||||||
|
|
||||||
# Apply lora_A^T: hidden_states @ A^T
|
# Apply lora_A^T: hidden_states @ A^T
|
||||||
lora_a_output = self.lora_backend.run_lora_a_sgemm(
|
lora_a_output = self.lora_backend.run_lora_a_sgemm(
|
||||||
hidden_states, self.lm_head_A_buffer
|
hidden_states,
|
||||||
|
self.lm_head_A_buffer,
|
||||||
|
pruned_batch_info=lm_head_batch_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply lora_B^T: lora_a_output @ B^T
|
# Apply lora_B^T: lora_a_output @ B^T
|
||||||
@@ -261,6 +303,7 @@ class ParallelLMHeadWithLoRA(BaseLayerWithLoRA):
|
|||||||
weights=self.lm_head_B_buffer,
|
weights=self.lm_head_B_buffer,
|
||||||
output_offset=self.output_offset,
|
output_offset=self.output_offset,
|
||||||
base_output=base_output,
|
base_output=base_output,
|
||||||
|
pruned_batch_info=lm_head_batch_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
return lora_output
|
return lora_output
|
||||||
@@ -277,6 +320,23 @@ class ParallelLMHeadWithLoRA(BaseLayerWithLoRA):
|
|||||||
|
|
||||||
return base_output
|
return base_output
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Multi-pass lm_head support (chunked logprobs)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def set_lm_head_pass(self, pass_idx: int):
|
||||||
|
"""Set the active lm_head pass index before a logprobs chunk.
|
||||||
|
|
||||||
|
Called by LogitsProcessor.process_input_logprobs_by_chunk() before
|
||||||
|
each chunk's _get_logits call. _get_lm_head_batch_info() will
|
||||||
|
resolve to lm_head_pass_batch_infos[pass_idx].
|
||||||
|
"""
|
||||||
|
self.lora_backend._lm_head_pass_idx = pass_idx
|
||||||
|
|
||||||
|
def reset_lm_head_pass(self):
|
||||||
|
"""Reset the lm_head pass index after all passes are done."""
|
||||||
|
self.lora_backend._lm_head_pass_idx = None
|
||||||
|
|
||||||
def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int):
|
def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int):
|
||||||
# For TP=1, no slicing needed
|
# For TP=1, no slicing needed
|
||||||
# For TP>1, need to modify code in: sglang/python/sglang/srt/lora/mem_pool.py
|
# For TP>1, need to modify code in: sglang/python/sglang/srt/lora/mem_pool.py
|
||||||
|
|||||||
@@ -533,6 +533,22 @@ class LoRAMemoryPool:
|
|||||||
:lora_rank,
|
:lora_rank,
|
||||||
]
|
]
|
||||||
load_lora_weight_tensor(buffer_view, lora_b_weights)
|
load_lora_weight_tensor(buffer_view, lora_b_weights)
|
||||||
|
else:
|
||||||
|
# Zero out embedding/lm_head buffers for adapters without embedding LoRA
|
||||||
|
# to avoid using garbage values from uninitialized memory
|
||||||
|
for k in self.embedding_A_buffer.keys():
|
||||||
|
self.embedding_A_buffer[k][buffer_id].zero_()
|
||||||
|
for k in self.embedding_B_buffer.keys():
|
||||||
|
self.embedding_B_buffer[k][buffer_id].zero_()
|
||||||
|
for k in self.lm_head_A_buffer.keys():
|
||||||
|
self.lm_head_A_buffer[k][buffer_id].zero_()
|
||||||
|
for k in self.lm_head_B_buffer.keys():
|
||||||
|
self.lm_head_B_buffer[k][buffer_id].zero_()
|
||||||
|
if (
|
||||||
|
self.lora_added_tokens_size > 0
|
||||||
|
and "input_embeddings" in self.new_embeddings_buffer
|
||||||
|
):
|
||||||
|
self.new_embeddings_buffer["input_embeddings"][buffer_id].zero_()
|
||||||
|
|
||||||
def get_embedding_tensor(
|
def get_embedding_tensor(
|
||||||
self, target_module: str, lora_type: LoRAType
|
self, target_module: str, lora_type: LoRAType
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from .chunked_embedding_lora_a import chunked_embedding_lora_a_forward
|
||||||
from .chunked_sgmv_expand import chunked_sgmv_lora_expand_forward
|
from .chunked_sgmv_expand import chunked_sgmv_lora_expand_forward
|
||||||
from .chunked_sgmv_shrink import chunked_sgmv_lora_shrink_forward
|
from .chunked_sgmv_shrink import chunked_sgmv_lora_shrink_forward
|
||||||
from .embedding_lora_a import embedding_lora_a_fwd
|
from .embedding_lora_a import embedding_lora_a_fwd
|
||||||
@@ -13,5 +14,6 @@ __all__ = [
|
|||||||
"sgemm_lora_b_fwd",
|
"sgemm_lora_b_fwd",
|
||||||
"chunked_sgmv_lora_shrink_forward",
|
"chunked_sgmv_lora_shrink_forward",
|
||||||
"chunked_sgmv_lora_expand_forward",
|
"chunked_sgmv_lora_expand_forward",
|
||||||
|
"chunked_embedding_lora_a_forward",
|
||||||
"embedding_lora_a_fwd",
|
"embedding_lora_a_fwd",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import torch
|
||||||
|
import triton
|
||||||
|
import triton.language as tl
|
||||||
|
|
||||||
|
from sglang.srt.lora.utils import LoRABatchInfo
|
||||||
|
|
||||||
|
|
||||||
|
@triton.jit
|
||||||
|
def _chunked_embedding_lora_a_kernel(
|
||||||
|
# Pointers to tensors
|
||||||
|
input_ids,
|
||||||
|
weights,
|
||||||
|
output,
|
||||||
|
# Dimensions
|
||||||
|
vocab_size,
|
||||||
|
rank,
|
||||||
|
num_loras,
|
||||||
|
# Strides
|
||||||
|
w_stride_0, # stride for lora index
|
||||||
|
w_stride_1, # stride for rank
|
||||||
|
w_stride_2, # stride for vocab
|
||||||
|
output_stride_0,
|
||||||
|
output_stride_1,
|
||||||
|
# Chunk info
|
||||||
|
seg_indptr,
|
||||||
|
weight_indices,
|
||||||
|
lora_ranks,
|
||||||
|
num_segments,
|
||||||
|
permutation,
|
||||||
|
# Meta-parameters
|
||||||
|
BLOCK_RANK: tl.constexpr,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Embedding lookup for LoRA A weights without support for extra tokens.
|
||||||
|
|
||||||
|
Each program handles one chunk of tokens across rank dimension
|
||||||
|
"""
|
||||||
|
chunk_idx = tl.program_id(axis=0)
|
||||||
|
# If chunk id is larger than actual number of chunks, skip
|
||||||
|
if chunk_idx >= num_segments:
|
||||||
|
return
|
||||||
|
# Load LoRA adapter index for this segment, then look up the rank
|
||||||
|
lora_index = tl.load(weight_indices + chunk_idx)
|
||||||
|
rank_val = tl.load(lora_ranks + lora_index)
|
||||||
|
# If rank is 0, skip
|
||||||
|
if rank_val == 0:
|
||||||
|
return
|
||||||
|
# for each token in chunk, load embedding across rank dimension
|
||||||
|
chunk_start = tl.load(seg_indptr + chunk_idx)
|
||||||
|
chunk_end = tl.load(seg_indptr + chunk_idx + 1)
|
||||||
|
for c in range(chunk_start, chunk_end):
|
||||||
|
s_index = tl.load(permutation + c)
|
||||||
|
# Load the token ID
|
||||||
|
token_id = tl.load(input_ids + s_index)
|
||||||
|
# Process in chunks of BLOCK_RANK dimensions
|
||||||
|
num_blocks = tl.cdiv(rank_val, BLOCK_RANK)
|
||||||
|
|
||||||
|
for block_id in range(num_blocks):
|
||||||
|
rank_offset = tl.arange(0, BLOCK_RANK) + block_id * BLOCK_RANK
|
||||||
|
rank_mask = rank_offset < rank_val
|
||||||
|
|
||||||
|
# Use regular LoRA A weights
|
||||||
|
# weights shape: (num_loras, rank, vocab_size)
|
||||||
|
# We need to load weights[lora_index, rank_offset, token_id]
|
||||||
|
weight_ptr = (
|
||||||
|
weights
|
||||||
|
+ lora_index * w_stride_0
|
||||||
|
+ rank_offset * w_stride_1
|
||||||
|
+ token_id * w_stride_2
|
||||||
|
)
|
||||||
|
emb_values = tl.load(weight_ptr, mask=rank_mask, other=0.0)
|
||||||
|
|
||||||
|
# Write to output
|
||||||
|
output_ptr = (
|
||||||
|
output + s_index * output_stride_0 + rank_offset * output_stride_1
|
||||||
|
)
|
||||||
|
tl.store(output_ptr, emb_values, mask=rank_mask)
|
||||||
|
|
||||||
|
|
||||||
|
def chunked_embedding_lora_a_forward(
|
||||||
|
input_ids: torch.Tensor,
|
||||||
|
weights: torch.Tensor,
|
||||||
|
batch_info: LoRABatchInfo,
|
||||||
|
vocab_size: int,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
"""
|
||||||
|
Chunked Forward pass for LoRA A embedding lookup; each program handles one chunk of embedding lookup work
|
||||||
|
belonging to the same adapter
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_ids: (s,) token IDs
|
||||||
|
weights: (num_loras, rank, vocab_size) LoRA A embedding weights
|
||||||
|
batch_info: LoRABatchInfo containing batch information
|
||||||
|
vocab_size: base vocabulary size
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
output: (s, rank) embedded features
|
||||||
|
"""
|
||||||
|
assert input_ids.is_contiguous()
|
||||||
|
assert weights.is_contiguous()
|
||||||
|
assert len(input_ids.shape) == 1
|
||||||
|
assert len(weights.shape) == 3
|
||||||
|
|
||||||
|
S = input_ids.shape[0]
|
||||||
|
num_loras = weights.shape[0]
|
||||||
|
rank = weights.shape[1]
|
||||||
|
|
||||||
|
# Block size for rank dimension
|
||||||
|
BLOCK_RANK = 128
|
||||||
|
num_segments = batch_info.num_segments
|
||||||
|
# 1D Grid: one program per chunk of embedding lookup work
|
||||||
|
grid = (batch_info.bs if batch_info.use_cuda_graph else num_segments,)
|
||||||
|
output = torch.zeros((S, rank), device=input_ids.device, dtype=weights.dtype)
|
||||||
|
|
||||||
|
_chunked_embedding_lora_a_kernel[grid](
|
||||||
|
input_ids,
|
||||||
|
weights,
|
||||||
|
output,
|
||||||
|
vocab_size,
|
||||||
|
rank,
|
||||||
|
num_loras,
|
||||||
|
weights.stride(0),
|
||||||
|
weights.stride(1),
|
||||||
|
weights.stride(2),
|
||||||
|
output.stride(0),
|
||||||
|
output.stride(1),
|
||||||
|
batch_info.seg_indptr,
|
||||||
|
batch_info.weight_indices,
|
||||||
|
batch_info.lora_ranks,
|
||||||
|
batch_info.num_segments,
|
||||||
|
batch_info.permutation,
|
||||||
|
BLOCK_RANK,
|
||||||
|
)
|
||||||
|
|
||||||
|
return output
|
||||||
@@ -8,7 +8,9 @@ from sglang.srt.lora.utils import LoRABatchInfo
|
|||||||
from sglang.srt.utils import cached_triton_kernel
|
from sglang.srt.utils import cached_triton_kernel
|
||||||
|
|
||||||
|
|
||||||
@cached_triton_kernel(lambda _, kwargs: (kwargs["NUM_SLICES"], kwargs["BLOCK_M"]))
|
@cached_triton_kernel(
|
||||||
|
lambda _, kwargs: (kwargs["NUM_SLICES"], kwargs["BLOCK_M"], kwargs["OUTPUT_DIM"])
|
||||||
|
)
|
||||||
@triton.jit(do_not_specialize=["num_segs"])
|
@triton.jit(do_not_specialize=["num_segs"])
|
||||||
def _chunked_lora_expand_kernel(
|
def _chunked_lora_expand_kernel(
|
||||||
# Pointers to matrices
|
# Pointers to matrices
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Iterable, Optional, Set, Tuple, Union
|
from typing import Iterable, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
@@ -40,6 +40,10 @@ class LoRABatchInfo:
|
|||||||
# The logical (re)ordering of input rows (tokens), in shape (num_tokens,)
|
# The logical (re)ordering of input rows (tokens), in shape (num_tokens,)
|
||||||
permutation: Optional[torch.Tensor]
|
permutation: Optional[torch.Tensor]
|
||||||
|
|
||||||
|
# Total number of tokens this batch info expects (host-side int).
|
||||||
|
# Used by lm_head LoRA to validate input shape without GPU sync.
|
||||||
|
expected_tokens: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
class LoRAType(Enum):
|
class LoRAType(Enum):
|
||||||
LORA_A = 0
|
LORA_A = 0
|
||||||
@@ -193,3 +197,130 @@ def generate_sequence_lengths(
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported forward mode: {forward_batch.forward_mode}")
|
raise ValueError(f"Unsupported forward mode: {forward_batch.forward_mode}")
|
||||||
return seg_lens
|
return seg_lens
|
||||||
|
|
||||||
|
|
||||||
|
def get_lm_head_pruned_lens(
|
||||||
|
forward_batch: ForwardBatch,
|
||||||
|
) -> Optional[List[int]]:
|
||||||
|
"""
|
||||||
|
Compute per-sequence pruned lengths for lm_head LoRA.
|
||||||
|
|
||||||
|
Returns a list of pruned lengths (one per sequence) if pruning applies,
|
||||||
|
or None if lm_head pruning is not applicable for this batch.
|
||||||
|
|
||||||
|
Pruning rules:
|
||||||
|
- Extend without logprobs: 1 token per sequence
|
||||||
|
- Extend with logprobs: max(extend_len - logprob_start_len, 1) per sequence
|
||||||
|
- Decode / target_verify / draft_extend_v2: no pruning
|
||||||
|
|
||||||
|
IMPORTANT: This must stay in sync with LogitsProcessor._get_pruned_states()
|
||||||
|
in sglang/srt/layers/logits_processor.py, which determines how many tokens
|
||||||
|
per sequence are passed to lm_head. If the pruning conditions or lengths
|
||||||
|
there change, this function must be updated to match, otherwise the
|
||||||
|
lm_head LoRA will operate on incorrectly shaped inputs.
|
||||||
|
"""
|
||||||
|
lm_head_pruning = (
|
||||||
|
forward_batch.forward_mode.is_extend()
|
||||||
|
and not forward_batch.forward_mode.is_target_verify()
|
||||||
|
and not forward_batch.forward_mode.is_draft_extend_v2()
|
||||||
|
)
|
||||||
|
|
||||||
|
if not lm_head_pruning:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if forward_batch.return_logprob:
|
||||||
|
pruned_lens = []
|
||||||
|
for ext_len, start_len in zip(
|
||||||
|
forward_batch.extend_seq_lens_cpu,
|
||||||
|
forward_batch.extend_logprob_start_lens_cpu,
|
||||||
|
):
|
||||||
|
pruned_lens.append(1 if ext_len == start_len else ext_len - start_len)
|
||||||
|
else:
|
||||||
|
pruned_lens = [1] * forward_batch.batch_size
|
||||||
|
|
||||||
|
return pruned_lens
|
||||||
|
|
||||||
|
|
||||||
|
def merge_and_chunk_segments(
|
||||||
|
weight_indices: list[int],
|
||||||
|
pruned_lens: List[int],
|
||||||
|
chunk_size: int,
|
||||||
|
) -> Tuple[List[int], List[int]]:
|
||||||
|
"""
|
||||||
|
Merge consecutive same-adapter sequences and chunk at chunk_size boundaries.
|
||||||
|
|
||||||
|
Merges consecutive sequences that use the same adapter into single
|
||||||
|
segments, splitting any segment that exceeds chunk_size.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
weight_indices: Per-sequence adapter indices.
|
||||||
|
pruned_lens: Per-sequence pruned token counts.
|
||||||
|
chunk_size: Maximum segment length before splitting.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(seg_weight_indices, seg_lens): Merged and chunked segments.
|
||||||
|
"""
|
||||||
|
seg_weight_indices: List[int] = []
|
||||||
|
seg_lens: List[int] = []
|
||||||
|
for wi, pl in zip(weight_indices, pruned_lens):
|
||||||
|
if seg_weight_indices and seg_weight_indices[-1] == wi:
|
||||||
|
seg_lens[-1] += pl
|
||||||
|
else:
|
||||||
|
seg_weight_indices.append(wi)
|
||||||
|
seg_lens.append(pl)
|
||||||
|
# Split the last segment if it exceeds chunk_size
|
||||||
|
while seg_lens[-1] > chunk_size:
|
||||||
|
remainder = seg_lens[-1] - chunk_size
|
||||||
|
seg_lens[-1] = chunk_size
|
||||||
|
seg_weight_indices.append(wi)
|
||||||
|
seg_lens.append(remainder)
|
||||||
|
|
||||||
|
return seg_weight_indices, seg_lens
|
||||||
|
|
||||||
|
|
||||||
|
def build_lm_head_pass_segments(
|
||||||
|
weight_indices: List[int],
|
||||||
|
pruned_lens: List[int],
|
||||||
|
logprobs_chunk_size: int,
|
||||||
|
) -> List[Tuple[List[int], List[int]]]:
|
||||||
|
"""
|
||||||
|
Precompute per-pass segment info for lm_head LoRA logprobs processing.
|
||||||
|
|
||||||
|
When LogitsProcessor uses chunked logprobs processing
|
||||||
|
(process_input_logprobs_by_chunk), pruned hidden states are split into
|
||||||
|
fixed-size passes. Each pass needs its own segmentation
|
||||||
|
(weight_indices, seg_lens) so that lm_head LoRA operates on the
|
||||||
|
correct adapter assignments per pass.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
weight_indices: Per-sequence adapter indices.
|
||||||
|
pruned_lens: Per-sequence pruned token counts.
|
||||||
|
logprobs_chunk_size: Fixed pass size used by LogitsProcessor.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of (seg_weight_indices, seg_lens) tuples, one per pass.
|
||||||
|
"""
|
||||||
|
# Expand to per-token weight index
|
||||||
|
token_wi: List[int] = []
|
||||||
|
for wi, pl in zip(weight_indices, pruned_lens):
|
||||||
|
token_wi.extend([wi] * pl)
|
||||||
|
total = len(token_wi)
|
||||||
|
num_passes = (total + logprobs_chunk_size - 1) // logprobs_chunk_size
|
||||||
|
|
||||||
|
result: List[Tuple[List[int], List[int]]] = []
|
||||||
|
for i in range(num_passes):
|
||||||
|
start = i * logprobs_chunk_size
|
||||||
|
end = min((i + 1) * logprobs_chunk_size, total)
|
||||||
|
|
||||||
|
# Run-length encode the pass's adapter indices
|
||||||
|
seg_wi: List[int] = []
|
||||||
|
seg_lens: List[int] = []
|
||||||
|
for t in range(start, end):
|
||||||
|
if seg_wi and seg_wi[-1] == token_wi[t]:
|
||||||
|
seg_lens[-1] += 1
|
||||||
|
else:
|
||||||
|
seg_wi.append(token_wi[t])
|
||||||
|
seg_lens.append(1)
|
||||||
|
result.append((seg_wi, seg_lens))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
@@ -5931,17 +5931,6 @@ class ServerArgs:
|
|||||||
), "If 'all' is specified in --lora-target-modules, it should be the only module specified."
|
), "If 'all' is specified in --lora-target-modules, it should be the only module specified."
|
||||||
self.lora_target_modules = set(SUPPORTED_LORA_TARGET_MODULES)
|
self.lora_target_modules = set(SUPPORTED_LORA_TARGET_MODULES)
|
||||||
|
|
||||||
# When using the chunked SGMV backend, skip embedding / lm_head layers for now,
|
|
||||||
# since it does not support these yet (TODO: implement embedding / lm_head support)
|
|
||||||
if self.lora_backend == "csgmv":
|
|
||||||
logger.warning(
|
|
||||||
"LoRA backend 'csgmv' does not yet support embedding or lm_head layers; "
|
|
||||||
"dropping 'embed_tokens' and 'lm_head' from --lora-target-modules=all. "
|
|
||||||
"To apply LoRA to these, use --lora-backend triton."
|
|
||||||
)
|
|
||||||
self.lora_target_modules.discard("embed_tokens")
|
|
||||||
self.lora_target_modules.discard("lm_head")
|
|
||||||
|
|
||||||
# Ensure sufficient information is provided for LoRA initialization.
|
# Ensure sufficient information is provided for LoRA initialization.
|
||||||
assert self.lora_paths or (
|
assert self.lora_paths or (
|
||||||
self.max_lora_rank and self.lora_target_modules
|
self.max_lora_rank and self.lora_target_modules
|
||||||
|
|||||||
@@ -201,6 +201,67 @@ def reference_sgmv_shrink(
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def reference_embedding_lora_a_shrink(
|
||||||
|
input_ids: torch.Tensor,
|
||||||
|
weights: torch.Tensor,
|
||||||
|
weight_indices: torch.Tensor,
|
||||||
|
seq_lengths: torch.Tensor,
|
||||||
|
lora_ranks: torch.Tensor,
|
||||||
|
vocab_size: int,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
"""
|
||||||
|
Simple sequence-level reference implementation of embedding LoRA A shrink operation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_ids: (total_seq_len,) - Token IDs
|
||||||
|
weights: (num_loras, max_rank, vocab_size) - LoRA A embedding weights
|
||||||
|
weight_indices: LoRA idx for each sequence
|
||||||
|
seq_lengths: Length of each sequence
|
||||||
|
lora_ranks: LoRA rank for each LoRA adapters
|
||||||
|
vocab_size: Base vocabulary size
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
output: (total_seq_len, max_rank) - Embedded features
|
||||||
|
"""
|
||||||
|
if weights.numel() == 0:
|
||||||
|
total_tokens = input_ids.shape[0]
|
||||||
|
return torch.zeros(total_tokens, 0, dtype=weights.dtype, device=weights.device)
|
||||||
|
|
||||||
|
total_tokens = input_ids.shape[0]
|
||||||
|
_, max_rank, _ = weights.shape
|
||||||
|
|
||||||
|
output = torch.zeros(
|
||||||
|
total_tokens, max_rank, dtype=weights.dtype, device=weights.device
|
||||||
|
)
|
||||||
|
|
||||||
|
token_offset = 0
|
||||||
|
for lora_idx, seq_len, rank in zip(
|
||||||
|
weight_indices,
|
||||||
|
seq_lengths,
|
||||||
|
lora_ranks[weight_indices],
|
||||||
|
):
|
||||||
|
if seq_len == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if rank > 0:
|
||||||
|
# Get token IDs for this sequence
|
||||||
|
seq_input_ids = input_ids[token_offset : token_offset + seq_len]
|
||||||
|
|
||||||
|
# Clamp token IDs to vocab size
|
||||||
|
clamped_ids = torch.clamp(seq_input_ids, max=vocab_size - 1)
|
||||||
|
|
||||||
|
# Lookup embeddings: weights[lora_idx, :rank, token_ids] -> (seq_len, rank)
|
||||||
|
# weights shape: (num_loras, max_rank, vocab_size)
|
||||||
|
lora_weights = weights[lora_idx, :rank, :] # (rank, vocab_size)
|
||||||
|
embeddings = lora_weights[:, clamped_ids].t() # (seq_len, rank)
|
||||||
|
|
||||||
|
output[token_offset : token_offset + seq_len, :rank] = embeddings
|
||||||
|
|
||||||
|
token_offset += seq_len
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
def reference_sgmv_expand(
|
def reference_sgmv_expand(
|
||||||
x: torch.Tensor,
|
x: torch.Tensor,
|
||||||
weights: torch.Tensor,
|
weights: torch.Tensor,
|
||||||
|
|||||||
+178
-2
@@ -5,18 +5,28 @@ from typing import List, Optional, Tuple
|
|||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.layers.logits_processor import LogitsMetadata, LogitsProcessor
|
||||||
from sglang.srt.lora.backend.chunked_backend import ChunkedSgmvLoRABackend
|
from sglang.srt.lora.backend.chunked_backend import ChunkedSgmvLoRABackend
|
||||||
from sglang.srt.lora.triton_ops import (
|
from sglang.srt.lora.triton_ops import (
|
||||||
|
chunked_embedding_lora_a_forward,
|
||||||
chunked_sgmv_lora_expand_forward,
|
chunked_sgmv_lora_expand_forward,
|
||||||
chunked_sgmv_lora_shrink_forward,
|
chunked_sgmv_lora_shrink_forward,
|
||||||
)
|
)
|
||||||
from sglang.srt.lora.triton_ops.chunked_sgmv_expand import _chunked_lora_expand_kernel
|
from sglang.srt.lora.triton_ops.chunked_sgmv_expand import _chunked_lora_expand_kernel
|
||||||
from sglang.srt.lora.triton_ops.chunked_sgmv_shrink import _chunked_lora_shrink_kernel
|
from sglang.srt.lora.triton_ops.chunked_sgmv_shrink import _chunked_lora_shrink_kernel
|
||||||
from sglang.srt.lora.utils import LoRABatchInfo
|
from sglang.srt.lora.utils import LoRABatchInfo, get_lm_head_pruned_lens
|
||||||
from sglang.test.lora_utils import reference_sgmv_expand, reference_sgmv_shrink
|
from sglang.srt.model_executor.forward_batch_info import ForwardMode
|
||||||
|
from sglang.test.ci.ci_register import register_cuda_ci
|
||||||
|
from sglang.test.lora_utils import (
|
||||||
|
reference_embedding_lora_a_shrink,
|
||||||
|
reference_sgmv_expand,
|
||||||
|
reference_sgmv_shrink,
|
||||||
|
)
|
||||||
|
|
||||||
CHUNK_SIZE = 16
|
CHUNK_SIZE = 16
|
||||||
|
|
||||||
|
register_cuda_ci(est_time=60, suite="nightly-1-gpu", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
def reset_kernel_cache():
|
def reset_kernel_cache():
|
||||||
_chunked_lora_shrink_kernel._clear_cache()
|
_chunked_lora_shrink_kernel._clear_cache()
|
||||||
@@ -100,6 +110,7 @@ class TestChunkedSGMV(unittest.TestCase):
|
|||||||
self.dtype = torch.float16
|
self.dtype = torch.float16
|
||||||
self.input_dim = 2560 # Hidden dimension
|
self.input_dim = 2560 # Hidden dimension
|
||||||
self.max_seq_len = 1024
|
self.max_seq_len = 1024
|
||||||
|
self.vocab_size = 32000 # Vocabulary size for embedding tests
|
||||||
|
|
||||||
# LoRA configurations: name -> (rank, output_q, output_k, output_v)
|
# LoRA configurations: name -> (rank, output_q, output_k, output_v)
|
||||||
self.lora_configs = {
|
self.lora_configs = {
|
||||||
@@ -285,6 +296,42 @@ class TestChunkedSGMV(unittest.TestCase):
|
|||||||
|
|
||||||
return stacked
|
return stacked
|
||||||
|
|
||||||
|
def create_embedding_lora_a_weights(self, lora_ranks: torch.Tensor) -> torch.Tensor:
|
||||||
|
"""Create LoRA A weights for embedding lookup.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lora_ranks: Tensor of ranks for each LoRA adapter
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tensor of shape (num_loras, max_rank, vocab_size)
|
||||||
|
"""
|
||||||
|
lora_ranks_cpu = lora_ranks.cpu().numpy()
|
||||||
|
num_loras = len(lora_ranks_cpu)
|
||||||
|
max_rank = int(lora_ranks_cpu.max()) if num_loras > 0 else 0
|
||||||
|
|
||||||
|
if max_rank == 0:
|
||||||
|
return torch.empty(
|
||||||
|
num_loras, 0, self.vocab_size, dtype=self.dtype, device=self.device
|
||||||
|
)
|
||||||
|
|
||||||
|
weights = torch.zeros(
|
||||||
|
num_loras, max_rank, self.vocab_size, dtype=self.dtype, device=self.device
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, rank in enumerate(lora_ranks_cpu):
|
||||||
|
if rank > 0:
|
||||||
|
weights[i, :rank, :] = torch.randn(
|
||||||
|
rank, self.vocab_size, dtype=self.dtype, device=self.device
|
||||||
|
)
|
||||||
|
|
||||||
|
return weights
|
||||||
|
|
||||||
|
def create_test_input_ids(self, total_tokens: int) -> torch.Tensor:
|
||||||
|
"""Create random token IDs for embedding test."""
|
||||||
|
return torch.randint(
|
||||||
|
0, self.vocab_size, (total_tokens,), dtype=torch.int64, device=self.device
|
||||||
|
)
|
||||||
|
|
||||||
def create_test_batch(
|
def create_test_batch(
|
||||||
self,
|
self,
|
||||||
batch_composition: BatchComposition,
|
batch_composition: BatchComposition,
|
||||||
@@ -461,6 +508,36 @@ class TestChunkedSGMV(unittest.TestCase):
|
|||||||
chunked_shrink, reference_shrink, rtol=self.RTOL, atol=self.ATOL
|
chunked_shrink, reference_shrink, rtol=self.RTOL, atol=self.ATOL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Test chunked embedding LoRA A forward
|
||||||
|
# Create embedding-specific LoRA A weights with shape (num_loras, rank, vocab_size)
|
||||||
|
embedding_lora_a = self.create_embedding_lora_a_weights(
|
||||||
|
batch_info.lora_ranks
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create input_ids (token indices) instead of hidden states
|
||||||
|
total_tokens = x.shape[0]
|
||||||
|
input_ids = self.create_test_input_ids(total_tokens)
|
||||||
|
|
||||||
|
chunked_shrink_embeddings = chunked_embedding_lora_a_forward(
|
||||||
|
input_ids, embedding_lora_a, batch_info, self.vocab_size
|
||||||
|
)
|
||||||
|
|
||||||
|
reference_shrink_embeddings = reference_embedding_lora_a_shrink(
|
||||||
|
input_ids,
|
||||||
|
embedding_lora_a,
|
||||||
|
lora_assignments_tensor,
|
||||||
|
seq_lengths_tensor,
|
||||||
|
lora_ranks_tensor,
|
||||||
|
self.vocab_size,
|
||||||
|
)
|
||||||
|
torch.testing.assert_close(
|
||||||
|
chunked_shrink_embeddings,
|
||||||
|
reference_shrink_embeddings,
|
||||||
|
rtol=self.RTOL,
|
||||||
|
atol=self.ATOL,
|
||||||
|
msg=f"Shrink test embedding loRA A operation failed for batch_size={batch_size}",
|
||||||
|
)
|
||||||
|
|
||||||
def test_expand_basic(self):
|
def test_expand_basic(self):
|
||||||
"""Test basic expand operation against PyTorch reference"""
|
"""Test basic expand operation against PyTorch reference"""
|
||||||
for batch_size in [1, 2, 16, 64]:
|
for batch_size in [1, 2, 16, 64]:
|
||||||
@@ -644,5 +721,104 @@ class TestChunkedSGMV(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLmHeadPruningConsistency(unittest.TestCase):
|
||||||
|
"""Verify get_lm_head_pruned_lens (LoRA) stays consistent with
|
||||||
|
LogitsProcessor._get_pruned_states (logits_processor).
|
||||||
|
|
||||||
|
If this test fails, it likely means one side was changed without
|
||||||
|
updating the other. See cross-references in both functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _make_mock_forward_batch(
|
||||||
|
self,
|
||||||
|
forward_mode,
|
||||||
|
extend_seq_lens_cpu,
|
||||||
|
return_logprob=False,
|
||||||
|
logprob_start_lens_cpu=None,
|
||||||
|
):
|
||||||
|
class MockForwardBatch:
|
||||||
|
pass
|
||||||
|
|
||||||
|
batch = MockForwardBatch()
|
||||||
|
batch.forward_mode = forward_mode
|
||||||
|
batch.batch_size = len(extend_seq_lens_cpu)
|
||||||
|
batch.return_logprob = return_logprob
|
||||||
|
batch.extend_seq_lens_cpu = extend_seq_lens_cpu
|
||||||
|
batch.extend_logprob_start_lens_cpu = logprob_start_lens_cpu
|
||||||
|
return batch
|
||||||
|
|
||||||
|
def _count_pruned_states_tokens(
|
||||||
|
self,
|
||||||
|
forward_mode,
|
||||||
|
extend_seq_lens_cpu,
|
||||||
|
return_logprob=False,
|
||||||
|
logprob_start_lens_cpu=None,
|
||||||
|
):
|
||||||
|
"""Call _get_pruned_states and return the number of output tokens."""
|
||||||
|
total_tokens = sum(extend_seq_lens_cpu)
|
||||||
|
hidden_states = torch.zeros(total_tokens, 4)
|
||||||
|
|
||||||
|
logits_meta = LogitsMetadata(
|
||||||
|
forward_mode=forward_mode,
|
||||||
|
extend_return_logprob=return_logprob,
|
||||||
|
extend_seq_lens=torch.tensor(extend_seq_lens_cpu, dtype=torch.int64),
|
||||||
|
extend_seq_lens_cpu=extend_seq_lens_cpu,
|
||||||
|
extend_logprob_start_lens_cpu=logprob_start_lens_cpu,
|
||||||
|
)
|
||||||
|
|
||||||
|
# _get_pruned_states does not use self, so pass None
|
||||||
|
result = LogitsProcessor._get_pruned_states(
|
||||||
|
None, hidden_states, None, None, logits_meta
|
||||||
|
)
|
||||||
|
pruned_states = result[0]
|
||||||
|
return pruned_states.shape[0]
|
||||||
|
|
||||||
|
def _assert_consistency(
|
||||||
|
self,
|
||||||
|
forward_mode,
|
||||||
|
extend_seq_lens_cpu,
|
||||||
|
return_logprob=False,
|
||||||
|
logprob_start_lens_cpu=None,
|
||||||
|
):
|
||||||
|
mock_batch = self._make_mock_forward_batch(
|
||||||
|
forward_mode,
|
||||||
|
extend_seq_lens_cpu,
|
||||||
|
return_logprob,
|
||||||
|
logprob_start_lens_cpu,
|
||||||
|
)
|
||||||
|
pruned_lens = get_lm_head_pruned_lens(mock_batch)
|
||||||
|
|
||||||
|
actual_count = self._count_pruned_states_tokens(
|
||||||
|
forward_mode,
|
||||||
|
extend_seq_lens_cpu,
|
||||||
|
return_logprob,
|
||||||
|
logprob_start_lens_cpu,
|
||||||
|
)
|
||||||
|
|
||||||
|
if pruned_lens is None:
|
||||||
|
expected_count = sum(extend_seq_lens_cpu)
|
||||||
|
else:
|
||||||
|
expected_count = sum(pruned_lens)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
expected_count,
|
||||||
|
actual_count,
|
||||||
|
f"get_lm_head_pruned_lens expects {expected_count} tokens, "
|
||||||
|
f"but _get_pruned_states produces {actual_count}. "
|
||||||
|
f"These functions must stay in sync — see their cross-reference comments.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_extend_no_logprob(self):
|
||||||
|
self._assert_consistency(ForwardMode.EXTEND, [4, 5, 6])
|
||||||
|
|
||||||
|
def test_extend_with_logprob(self):
|
||||||
|
self._assert_consistency(
|
||||||
|
ForwardMode.EXTEND,
|
||||||
|
[4, 5, 6],
|
||||||
|
return_logprob=True,
|
||||||
|
logprob_start_lens_cpu=[0, 5, 3],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@@ -28,6 +28,7 @@ Usage:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
@@ -47,10 +48,13 @@ register_amd_ci(
|
|||||||
suite="stage-b-test-small-1-gpu-amd",
|
suite="stage-b-test-small-1-gpu-amd",
|
||||||
)
|
)
|
||||||
# Test configuration constants
|
# Test configuration constants
|
||||||
LORA_BACKEND = "triton"
|
BASE_MODEL = "meta-llama/Llama-2-7b-hf"
|
||||||
|
LORA_PATHS = ["yushengsu/sglang_lora_logprob_diff_without_tuning"]
|
||||||
|
LORA_BACKEND = "csgmv"
|
||||||
DISABLE_CUDA_GRAPH = False
|
DISABLE_CUDA_GRAPH = False
|
||||||
LORA_TARGET_MODULES = None
|
LORA_TARGET_MODULES = None
|
||||||
LOGPROB_THRESHOLD = 1e-01
|
LOGPROB_THRESHOLD = 1e-01
|
||||||
|
MAX_NEW_TOKENS = 32
|
||||||
|
|
||||||
# Default test prompts
|
# Default test prompts
|
||||||
DEFAULT_TEST_PROMPTS = [
|
DEFAULT_TEST_PROMPTS = [
|
||||||
@@ -442,7 +446,7 @@ class TestLoRAHFSGLLogprobDifference(CustomTestCase):
|
|||||||
model_path: str,
|
model_path: str,
|
||||||
lora_paths: List[str],
|
lora_paths: List[str],
|
||||||
prompts: List[str],
|
prompts: List[str],
|
||||||
max_new_tokens: int = 32,
|
max_new_tokens: int = MAX_NEW_TOKENS,
|
||||||
torch_dtype: torch.dtype = torch.float16,
|
torch_dtype: torch.dtype = torch.float16,
|
||||||
lora_backend: str = LORA_BACKEND,
|
lora_backend: str = LORA_BACKEND,
|
||||||
port: int = DEFAULT_PORT_FOR_SRT_TEST_RUNNER,
|
port: int = DEFAULT_PORT_FOR_SRT_TEST_RUNNER,
|
||||||
@@ -506,32 +510,51 @@ class TestLoRAHFSGLLogprobDifference(CustomTestCase):
|
|||||||
"""
|
"""
|
||||||
Basic test comparing HF and SGLang LoRA logprobs with small model.
|
Basic test comparing HF and SGLang LoRA logprobs with small model.
|
||||||
"""
|
"""
|
||||||
model_path = "meta-llama/Llama-2-7b-hf"
|
|
||||||
lora_paths = ["yushengsu/sglang_lora_logprob_diff_without_tuning"]
|
|
||||||
prompts = DEFAULT_TEST_PROMPTS[:2] # Use fewer prompts for faster testing
|
prompts = DEFAULT_TEST_PROMPTS[:2] # Use fewer prompts for faster testing
|
||||||
|
|
||||||
self._run_comparison_test(
|
self._run_comparison_test(
|
||||||
model_path=model_path,
|
model_path=BASE_MODEL,
|
||||||
lora_paths=lora_paths,
|
lora_paths=LORA_PATHS,
|
||||||
prompts=prompts,
|
prompts=prompts,
|
||||||
max_new_tokens=32,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_lora_logprob_comparison_full(self):
|
def test_lora_logprob_comparison_full(self):
|
||||||
"""
|
"""
|
||||||
Full test comparing HF and SGLang LoRA logprobs with all prompts.
|
Full test comparing HF and SGLang LoRA logprobs with all prompts.
|
||||||
"""
|
"""
|
||||||
model_path = "meta-llama/Llama-2-7b-hf"
|
|
||||||
lora_paths = ["yushengsu/sglang_lora_logprob_diff_without_tuning"]
|
|
||||||
prompts = DEFAULT_TEST_PROMPTS
|
|
||||||
|
|
||||||
self._run_comparison_test(
|
self._run_comparison_test(
|
||||||
model_path=model_path,
|
model_path=BASE_MODEL,
|
||||||
lora_paths=lora_paths,
|
lora_paths=LORA_PATHS,
|
||||||
prompts=prompts,
|
prompts=DEFAULT_TEST_PROMPTS,
|
||||||
max_new_tokens=32,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_lora_logprob_comparison_chunked(self):
|
||||||
|
"""
|
||||||
|
Test with logprobs chunking enabled and a small chunk size so that
|
||||||
|
even short prompts trigger the multi-pass lm_head LoRA path.
|
||||||
|
"""
|
||||||
|
saved = {}
|
||||||
|
env_overrides = {
|
||||||
|
"SGLANG_ENABLE_LOGITS_PROCESSER_CHUNK": "true",
|
||||||
|
"SGLANG_LOGITS_PROCESSER_CHUNK_SIZE": "4",
|
||||||
|
}
|
||||||
|
for key, val in env_overrides.items():
|
||||||
|
saved[key] = os.environ.get(key)
|
||||||
|
os.environ[key] = val
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._run_comparison_test(
|
||||||
|
model_path=BASE_MODEL,
|
||||||
|
lora_paths=LORA_PATHS,
|
||||||
|
prompts=DEFAULT_TEST_PROMPTS,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
for key, orig in saved.items():
|
||||||
|
if orig is None:
|
||||||
|
os.environ.pop(key, None)
|
||||||
|
else:
|
||||||
|
os.environ[key] = orig
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user