chore: import upstream snapshot with attribution
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import json
|
||||
from abc import ABC, abstractmethod
|
||||
from array import array
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set
|
||||
|
||||
import dill
|
||||
import orjson
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.managers.schedule_batch import Req
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _cache_from_str(json_str: str):
|
||||
"""Deserialize a json string to a Callable object.
|
||||
This function is cached to avoid redundant deserialization.
|
||||
"""
|
||||
data = orjson.loads(json_str)
|
||||
return dill.loads(bytes.fromhex(data["callable"]))
|
||||
|
||||
|
||||
class CustomLogitProcessor(ABC):
|
||||
"""Abstract base class for callable functions."""
|
||||
|
||||
@abstractmethod
|
||||
def __call__(
|
||||
self,
|
||||
logits: torch.Tensor,
|
||||
custom_param_list: Optional[List[Dict[str, Any]]] = None,
|
||||
) -> torch.Tensor:
|
||||
"""Define the callable behavior."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def to_str(cls) -> str:
|
||||
"""Serialize the callable function to a JSON-compatible string."""
|
||||
return json.dumps({"callable": dill.dumps(cls).hex()})
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, json_str: str):
|
||||
"""Deserialize a callable function from a JSON string."""
|
||||
return _cache_from_str(json_str)()
|
||||
|
||||
|
||||
class DisallowedTokensLogitsProcessor(CustomLogitProcessor):
|
||||
def __call__(
|
||||
self,
|
||||
logits: torch.Tensor,
|
||||
custom_param_list: Optional[List[Dict[str, Any]]] = None,
|
||||
) -> torch.Tensor:
|
||||
disallowed_token_ids = custom_param_list[0]["token_ids"]
|
||||
assert all(
|
||||
disallowed_token_ids == c["token_ids"] for c in custom_param_list
|
||||
), f"{custom_param_list=}"
|
||||
logits[..., disallowed_token_ids] = -float("inf")
|
||||
return logits
|
||||
|
||||
|
||||
class ThinkingBudgetLogitProcessor(CustomLogitProcessor):
|
||||
"""A logit processor that controls the length of thinking."""
|
||||
|
||||
THINKING_START_TOKEN_ID: int
|
||||
THINKING_END_TOKEN_ID: int
|
||||
NEW_LINE_TOKEN_ID: int
|
||||
|
||||
def __call__(self, logits, custom_param_list: list[dict[str, Any]]):
|
||||
if custom_param_list is None or not custom_param_list:
|
||||
return logits
|
||||
for i, param_dict in enumerate(custom_param_list):
|
||||
if param_dict is None:
|
||||
continue
|
||||
|
||||
thinking_budget: int | None = param_dict.get("thinking_budget")
|
||||
|
||||
# Skip if thinking_budget is unset, or not an integer, or negative
|
||||
if (
|
||||
thinking_budget is None
|
||||
or not isinstance(thinking_budget, int)
|
||||
or thinking_budget < 0
|
||||
):
|
||||
continue
|
||||
req: Req = param_dict.get("__req__")
|
||||
cur_ids: list[int] = [*req.origin_input_ids, *req.output_ids]
|
||||
|
||||
# Check if out of thinking stage
|
||||
if (
|
||||
self.THINKING_START_TOKEN_ID not in cur_ids
|
||||
or self.THINKING_END_TOKEN_ID in cur_ids
|
||||
):
|
||||
continue
|
||||
|
||||
# Find the index of the thinking start token
|
||||
start_index = cur_ids.index(self.THINKING_START_TOKEN_ID)
|
||||
|
||||
# Count the number of tokens after the thinking start token
|
||||
num_tokens_after_start = len(cur_ids) - start_index - 1
|
||||
|
||||
if num_tokens_after_start < thinking_budget:
|
||||
continue
|
||||
|
||||
# Ensure new line token before thinking end token
|
||||
if not req.output_ids or req.output_ids[-1] != self.NEW_LINE_TOKEN_ID:
|
||||
logits[i, :] = -float("inf")
|
||||
logits[i, self.NEW_LINE_TOKEN_ID] = 0.0
|
||||
continue
|
||||
|
||||
# Assign highest probability to the thinking end token
|
||||
logits[i, :] = -float("inf")
|
||||
logits[i, self.THINKING_END_TOKEN_ID] = 0.0
|
||||
|
||||
return logits
|
||||
|
||||
|
||||
class Glm4MoeThinkingBudgetLogitProcessor(ThinkingBudgetLogitProcessor):
|
||||
"""A logit processor that controls the length of thinking for GLM-4.5 / GLM-4.6 / GLM-4.5V / GLM-4.6V models."""
|
||||
|
||||
THINKING_START_TOKEN_ID: int = 151350
|
||||
THINKING_END_TOKEN_ID: int = 151351
|
||||
NEW_LINE_TOKEN_ID: int = 198
|
||||
|
||||
|
||||
class Qwen3ThinkingBudgetLogitProcessor(ThinkingBudgetLogitProcessor):
|
||||
"""A logit processor that controls the length of thinking for Qwen3 models."""
|
||||
|
||||
THINKING_START_TOKEN_ID: int = 151667
|
||||
THINKING_END_TOKEN_ID: int = 151668
|
||||
NEW_LINE_TOKEN_ID: int = 198
|
||||
|
||||
|
||||
class DeepSeekR1ThinkingBudgetLogitProcessor(ThinkingBudgetLogitProcessor):
|
||||
"""A logit processor that controls the length of thinking for DeepSeek-R1 models."""
|
||||
|
||||
THINKING_START_TOKEN_ID: int = 128798
|
||||
THINKING_END_TOKEN_ID: int = 128799
|
||||
NEW_LINE_TOKEN_ID: int = 201
|
||||
|
||||
|
||||
# Adapted from DeepSeek's implementation: https://github.com/deepseek-ai/DeepSeek-OCR/blob/main/DeepSeek-OCR-master/DeepSeek-OCR-vllm/process/ngram_norepeat.py
|
||||
class DeepseekOCRNoRepeatNGramLogitProcessor(CustomLogitProcessor):
|
||||
"""Block n-gram repetitions within a sliding window for DeepSeek-OCR outputs."""
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
logits: torch.Tensor,
|
||||
custom_param_list: Optional[List[Dict[str, Any]]] = None,
|
||||
) -> torch.Tensor:
|
||||
if not custom_param_list:
|
||||
return logits
|
||||
|
||||
for batch_idx, params in enumerate(custom_param_list):
|
||||
if not params:
|
||||
continue
|
||||
|
||||
req = params.get("__req__")
|
||||
if req is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
ngram_size = int(params.get("ngram_size") or 0)
|
||||
window_size = int(params.get("window_size") or 0)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
|
||||
if ngram_size <= 0 or window_size <= 0:
|
||||
continue
|
||||
|
||||
sequence = req.origin_input_ids + req.output_ids
|
||||
if len(sequence) < ngram_size:
|
||||
continue
|
||||
|
||||
search_start = max(0, len(sequence) - window_size)
|
||||
search_end = len(sequence) - ngram_size + 1
|
||||
if search_end <= search_start:
|
||||
continue
|
||||
|
||||
if ngram_size > 1:
|
||||
current_prefix = sequence[-(ngram_size - 1) :]
|
||||
else:
|
||||
current_prefix = array("q")
|
||||
|
||||
banned_tokens: Set[int] = set()
|
||||
for idx in range(search_start, search_end):
|
||||
ngram = sequence[idx : idx + ngram_size]
|
||||
if ngram_size == 1 or ngram[:-1] == current_prefix:
|
||||
banned_tokens.add(ngram[-1])
|
||||
|
||||
whitelist_ids = params.get("whitelist_token_ids") or []
|
||||
try:
|
||||
whitelist = {int(token_id) for token_id in whitelist_ids}
|
||||
except (TypeError, ValueError):
|
||||
whitelist = set()
|
||||
|
||||
banned_tokens.difference_update(whitelist)
|
||||
|
||||
if not banned_tokens:
|
||||
continue
|
||||
|
||||
indices = list(banned_tokens)
|
||||
logits[batch_idx, indices] = -float("inf")
|
||||
|
||||
return logits
|
||||
@@ -0,0 +1,13 @@
|
||||
from sglang.srt.sampling.penaltylib.frequency_penalty import BatchedFrequencyPenalizer
|
||||
from sglang.srt.sampling.penaltylib.min_new_tokens import BatchedMinNewTokensPenalizer
|
||||
from sglang.srt.sampling.penaltylib.orchestrator import BatchedPenalizerOrchestrator
|
||||
from sglang.srt.sampling.penaltylib.presence_penalty import BatchedPresencePenalizer
|
||||
from sglang.srt.sampling.penaltylib.repetition_penalty import BatchedRepetitionPenalizer
|
||||
|
||||
__all__ = [
|
||||
"BatchedFrequencyPenalizer",
|
||||
"BatchedMinNewTokensPenalizer",
|
||||
"BatchedPresencePenalizer",
|
||||
"BatchedPenalizerOrchestrator",
|
||||
"BatchedRepetitionPenalizer",
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
import torch
|
||||
|
||||
from sglang.srt.sampling.penaltylib.orchestrator import _BatchedPenalizer
|
||||
|
||||
|
||||
class BatchedFrequencyPenalizer(_BatchedPenalizer):
|
||||
"""
|
||||
Frequency penalizer penalizes tokens based on their frequency in the output.
|
||||
"""
|
||||
|
||||
def _is_required(self) -> bool:
|
||||
return any(
|
||||
req.sampling_params.frequency_penalty != 0.0
|
||||
for req in self.orchestrator.reqs()
|
||||
)
|
||||
|
||||
def _prepare(self):
|
||||
self.cumulated_frequency_penalties = torch.zeros(
|
||||
(len(self.orchestrator.reqs()), self.orchestrator.vocab_size),
|
||||
dtype=torch.float32,
|
||||
device=self.orchestrator.device,
|
||||
)
|
||||
|
||||
self.frequency_penalties = (
|
||||
torch.tensor(
|
||||
data=[
|
||||
req.sampling_params.frequency_penalty
|
||||
for req in self.orchestrator.reqs()
|
||||
],
|
||||
dtype=torch.float32,
|
||||
device=self.orchestrator.device,
|
||||
)
|
||||
).unsqueeze_(1)
|
||||
|
||||
def _cumulate_output_tokens(self, output_ids: torch.Tensor):
|
||||
self.cumulated_frequency_penalties.scatter_add_(
|
||||
dim=1,
|
||||
index=output_ids.unsqueeze(1),
|
||||
src=self.frequency_penalties,
|
||||
)
|
||||
|
||||
def _apply(self, logits: torch.Tensor) -> torch.Tensor:
|
||||
logits.sub_(self.cumulated_frequency_penalties)
|
||||
|
||||
def _filter(self, keep_indices: torch.Tensor):
|
||||
self.frequency_penalties = self.frequency_penalties[keep_indices]
|
||||
self.cumulated_frequency_penalties = self.cumulated_frequency_penalties[
|
||||
keep_indices
|
||||
]
|
||||
|
||||
def _merge(self, their: "BatchedFrequencyPenalizer"):
|
||||
self.frequency_penalties = torch.cat(
|
||||
[self.frequency_penalties, their.frequency_penalties], dim=0
|
||||
)
|
||||
self.cumulated_frequency_penalties = torch.cat(
|
||||
[self.cumulated_frequency_penalties, their.cumulated_frequency_penalties],
|
||||
dim=0,
|
||||
)
|
||||
|
||||
def _teardown(self) -> None:
|
||||
for name in ("frequency_penalties", "cumulated_frequency_penalties"):
|
||||
if hasattr(self, name):
|
||||
delattr(self, name)
|
||||
@@ -0,0 +1,96 @@
|
||||
import torch
|
||||
|
||||
from sglang.srt.sampling.penaltylib.orchestrator import _BatchedPenalizer
|
||||
|
||||
|
||||
class BatchedMinNewTokensPenalizer(_BatchedPenalizer):
|
||||
"""
|
||||
Min new tokens penalizer penalizes tokens based on the length of the output.
|
||||
"""
|
||||
|
||||
def _is_required(self) -> bool:
|
||||
return any(
|
||||
req.sampling_params.min_new_tokens > 0 for req in self.orchestrator.reqs()
|
||||
)
|
||||
|
||||
def _prepare(self):
|
||||
self.min_new_tokens = torch.tensor(
|
||||
data=[
|
||||
req.sampling_params.min_new_tokens for req in self.orchestrator.reqs()
|
||||
],
|
||||
dtype=torch.int32,
|
||||
device=self.orchestrator.device,
|
||||
).unsqueeze_(1)
|
||||
|
||||
padded_stop_token_ids = torch.nn.utils.rnn.pad_sequence(
|
||||
sequences=[
|
||||
torch.tensor(
|
||||
data=(
|
||||
list(
|
||||
(req.sampling_params.stop_token_ids or set())
|
||||
| (req.tokenizer.additional_stop_token_ids or set())
|
||||
| {req.tokenizer.eos_token_id}
|
||||
)
|
||||
),
|
||||
dtype=torch.int64,
|
||||
device=self.orchestrator.device,
|
||||
)
|
||||
for req in self.orchestrator.reqs()
|
||||
],
|
||||
batch_first=True,
|
||||
padding_value=self.orchestrator.vocab_size,
|
||||
)
|
||||
self.stop_token_penalties = torch.zeros(
|
||||
size=(len(self.orchestrator.reqs()), self.orchestrator.vocab_size + 1),
|
||||
dtype=torch.float32,
|
||||
device=self.orchestrator.device,
|
||||
).scatter_add_(
|
||||
dim=1,
|
||||
index=padded_stop_token_ids,
|
||||
src=torch.full_like(
|
||||
input=padded_stop_token_ids,
|
||||
dtype=torch.float32,
|
||||
fill_value=float("-inf"),
|
||||
device=self.orchestrator.device,
|
||||
),
|
||||
)[
|
||||
:, : self.orchestrator.vocab_size
|
||||
]
|
||||
|
||||
self.len_output_tokens = torch.zeros(
|
||||
size=(len(self.orchestrator.reqs()), 1),
|
||||
dtype=torch.int32,
|
||||
device=self.orchestrator.device,
|
||||
)
|
||||
|
||||
def _cumulate_output_tokens(self, output_ids: torch.Tensor):
|
||||
self.len_output_tokens += 1
|
||||
|
||||
def _apply(self, logits: torch.Tensor):
|
||||
# Boolean-mask indexing (logits[mask]) is data-dependent and forces a
|
||||
# device-to-host sync every decode step; torch.where is a plain
|
||||
# elementwise select with no sync (and no -inf*0=nan).
|
||||
mask = self.len_output_tokens < self.min_new_tokens
|
||||
logits.add_(torch.where(mask, self.stop_token_penalties, 0.0))
|
||||
|
||||
def _filter(self, keep_indices: torch.Tensor):
|
||||
self.min_new_tokens = self.min_new_tokens[keep_indices]
|
||||
self.stop_token_penalties = self.stop_token_penalties[keep_indices]
|
||||
self.len_output_tokens = self.len_output_tokens[keep_indices]
|
||||
|
||||
def _merge(self, their: "BatchedMinNewTokensPenalizer"):
|
||||
self.min_new_tokens = torch.cat(
|
||||
[self.min_new_tokens, their.min_new_tokens], dim=0
|
||||
)
|
||||
self.stop_token_penalties = torch.cat(
|
||||
[self.stop_token_penalties, their.stop_token_penalties], dim=0
|
||||
)
|
||||
self.len_output_tokens = torch.cat(
|
||||
[self.len_output_tokens, their.len_output_tokens], dim=0
|
||||
)
|
||||
|
||||
# Explicit resource cleanup to aid GC and free CUDA memory promptly
|
||||
def _teardown(self) -> None:
|
||||
for name in ("min_new_tokens", "stop_token_penalties", "len_output_tokens"):
|
||||
if hasattr(self, name):
|
||||
delattr(self, name)
|
||||
@@ -0,0 +1,295 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING, Optional, Set, Type
|
||||
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
||||
|
||||
|
||||
class BatchedPenalizerOrchestrator:
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size: int,
|
||||
batch: ScheduleBatch,
|
||||
penalizers: Set[Type[_BatchedPenalizer]],
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self._batch_ref = weakref.ref(batch)
|
||||
self.device = batch.device
|
||||
self.penalizers = {Penalizer: Penalizer(self) for Penalizer in penalizers}
|
||||
|
||||
is_required = False
|
||||
for penalizer in self.penalizers.values():
|
||||
pen_is_required = penalizer.prepare_if_required()
|
||||
is_required |= pen_is_required
|
||||
self.is_required = is_required
|
||||
|
||||
@property
|
||||
def batch(self) -> ScheduleBatch | None:
|
||||
return self._batch_ref()
|
||||
|
||||
@batch.setter
|
||||
def batch(self, value: Optional[ScheduleBatch]):
|
||||
if value is None:
|
||||
self._batch_ref = lambda: None
|
||||
else:
|
||||
self._batch_ref = weakref.ref(value)
|
||||
|
||||
def reqs(self):
|
||||
return self.batch.reqs
|
||||
|
||||
def cumulate_output_tokens(self, output_ids: torch.Tensor):
|
||||
"""
|
||||
Feed the output tokens to the penalizers.
|
||||
|
||||
Args:
|
||||
output_ids (torch.Tensor): The output tokens.
|
||||
"""
|
||||
for penalizer in self.penalizers.values():
|
||||
penalizer.cumulate_output_tokens(output_ids=output_ids)
|
||||
|
||||
def apply(self, logits: torch.Tensor, repeat: Optional[int] = None):
|
||||
"""
|
||||
Apply all penalizers to the logits in-place.
|
||||
|
||||
Args:
|
||||
logits: The logits tensor to apply penalties to.
|
||||
repeat: If set (speculative decoding), per-request penalties are
|
||||
expanded via repeat_interleave to match the draft token layout.
|
||||
Additive penalties are captured into a zeros tensor, expanded,
|
||||
then added; scaling penalties are accumulated, expanded, then
|
||||
applied directly.
|
||||
"""
|
||||
if repeat is None:
|
||||
for penalizer in self.penalizers.values():
|
||||
penalizer.apply(logits)
|
||||
else:
|
||||
# Additive: capture into zeros, expand, add
|
||||
bs = logits.shape[0] // repeat
|
||||
additive = torch.zeros(
|
||||
(bs, logits.shape[1]), dtype=torch.float32, device=logits.device
|
||||
)
|
||||
self.accumulate_additive_penalties(additive)
|
||||
logits.add_(torch.repeat_interleave(additive, repeat, dim=0))
|
||||
# Scaling: accumulate, expand, apply
|
||||
accumulated = self.accumulate_scaling_penalties()
|
||||
if accumulated is not None:
|
||||
from sglang.srt.sampling.penaltylib.repetition_penalty import (
|
||||
apply_scaling_penalties,
|
||||
)
|
||||
|
||||
expanded = torch.repeat_interleave(accumulated, repeat, dim=0)
|
||||
apply_scaling_penalties(logits, expanded)
|
||||
|
||||
def accumulate_additive_penalties(self, logits: torch.Tensor):
|
||||
"""Apply only additive (non-multiplicative) penalizers."""
|
||||
for penalizer in self.penalizers.values():
|
||||
if not penalizer.is_multiplicative:
|
||||
penalizer.apply(logits)
|
||||
|
||||
def accumulate_scaling_penalties(self) -> Optional[torch.Tensor]:
|
||||
"""Accumulate all multiplicative penalty tensors into one, or None if none active."""
|
||||
result = None
|
||||
for penalizer in self.penalizers.values():
|
||||
if not penalizer._is_prepared or not penalizer.is_multiplicative:
|
||||
continue
|
||||
if result is None:
|
||||
result = penalizer.get_scaling_penalties().clone()
|
||||
else:
|
||||
result *= penalizer.get_scaling_penalties()
|
||||
return result
|
||||
|
||||
def filter(self, keep_indices: torch.Tensor):
|
||||
"""
|
||||
Filter the penalizers based on the indices to keep in the batch.
|
||||
|
||||
Args:
|
||||
keep_indices (torch.Tensor): Tensor of indices to keep in the batch.
|
||||
"""
|
||||
if not self.is_required:
|
||||
return
|
||||
|
||||
if len(keep_indices) == 0:
|
||||
# No requests left in the batch, fully release orchestrator resources
|
||||
self.release()
|
||||
return
|
||||
|
||||
is_required = False
|
||||
for penalizer in self.penalizers.values():
|
||||
tmp_is_required = penalizer.is_required()
|
||||
is_required |= tmp_is_required
|
||||
if tmp_is_required:
|
||||
penalizer.filter(keep_indices=keep_indices)
|
||||
else:
|
||||
penalizer.teardown()
|
||||
self.is_required = is_required
|
||||
|
||||
# Resource management helpers
|
||||
def release(self) -> None:
|
||||
"""Release all penalizers and break references so GC can reclaim promptly."""
|
||||
for penalizer in self.penalizers.values():
|
||||
penalizer.teardown()
|
||||
self.penalizers.clear()
|
||||
# Break reference to ScheduleBatch
|
||||
self._batch_ref = None
|
||||
self.is_required = False
|
||||
|
||||
# Context manager support
|
||||
def __enter__(self) -> BatchedPenalizerOrchestrator:
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb) -> None:
|
||||
self.release()
|
||||
|
||||
def merge(self, their: BatchedPenalizerOrchestrator):
|
||||
"""
|
||||
Merge the penalizers of another orchestrator into this one.
|
||||
|
||||
Note that this function **must** be called _before_ self.batch.reqs is updated (filtered).
|
||||
Each unprepared penalizers would have to be prepared (creating tensors, etc.) first before merging.
|
||||
This step requires the original batch.reqs, before it gets merged with other batch.reqs.
|
||||
|
||||
Args:
|
||||
their (BatchedPenalizerOrchestrator): The orchestrator to merge into this one.
|
||||
"""
|
||||
if not self.is_required and not their.is_required:
|
||||
return
|
||||
|
||||
self.is_required = True
|
||||
for penalizer, their_penalizer in their.penalizers.items():
|
||||
self.penalizers[penalizer].merge(their_penalizer)
|
||||
|
||||
|
||||
class _BatchedPenalizer(abc.ABC):
|
||||
"""
|
||||
An abstract class for a batched penalizer.
|
||||
"""
|
||||
|
||||
is_multiplicative: bool = False
|
||||
|
||||
def __init__(self, orchestrator: BatchedPenalizerOrchestrator):
|
||||
self._orchestrator_ref: weakref.ReferenceType[BatchedPenalizerOrchestrator] = (
|
||||
weakref.ref(orchestrator)
|
||||
)
|
||||
self._is_prepared = False
|
||||
|
||||
@property
|
||||
def orchestrator(self) -> BatchedPenalizerOrchestrator:
|
||||
orch: Optional[BatchedPenalizerOrchestrator] = self._orchestrator_ref()
|
||||
# This should never happen, but we need to handle it gracefully
|
||||
if orch is None:
|
||||
raise RuntimeError(
|
||||
"BatchedPenalizerOrchestrator has been garbage-collected"
|
||||
)
|
||||
return orch
|
||||
|
||||
def is_prepared(self) -> bool:
|
||||
return self._is_prepared
|
||||
|
||||
def is_required(self) -> bool:
|
||||
return self._is_required()
|
||||
|
||||
def prepare(self):
|
||||
if not self._is_prepared:
|
||||
self._prepare()
|
||||
self._is_prepared = True
|
||||
|
||||
def prepare_if_required(self):
|
||||
if self._is_required():
|
||||
self.prepare()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def teardown(self):
|
||||
self._teardown()
|
||||
self._is_prepared = False
|
||||
|
||||
def cumulate_output_tokens(self, output_ids: torch.Tensor):
|
||||
if not self._is_prepared:
|
||||
return
|
||||
|
||||
self._cumulate_output_tokens(output_ids=output_ids)
|
||||
|
||||
def apply(self, logits: torch.Tensor) -> torch.Tensor:
|
||||
if not self._is_prepared:
|
||||
return
|
||||
|
||||
self._apply(logits=logits)
|
||||
|
||||
def filter(self, keep_indices: torch.Tensor):
|
||||
if not self._is_prepared:
|
||||
return
|
||||
|
||||
self._filter(keep_indices=keep_indices)
|
||||
|
||||
def merge(self, their: _BatchedPenalizer):
|
||||
if not self._is_prepared and not their._is_prepared:
|
||||
return
|
||||
|
||||
self.prepare()
|
||||
their.prepare()
|
||||
self._merge(their)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _is_required(self) -> bool:
|
||||
"""
|
||||
Check if the penalizer is required to be prepared.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _prepare(self):
|
||||
"""
|
||||
Prepare the penalizer.
|
||||
Usually, this is where the penalizer initializes its tensors.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _cumulate_output_tokens(self, output_ids: torch.Tensor):
|
||||
"""
|
||||
Cumulate the output tokens.
|
||||
Orchestrator will call this function to feed the output tokens to the penalizer.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _apply(self, logits: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Apply the penalizer to the logits.
|
||||
Penalizers can modify the logits in-place if needed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_scaling_penalties(self) -> torch.Tensor:
|
||||
"""
|
||||
Return the accumulated scaling penalty tensor for multiplicative penalizers.
|
||||
Only meaningful when is_multiplicative is True. Subclasses should override.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def _filter(self, keep_indices: torch.Tensor):
|
||||
"""
|
||||
Filter the penalizer (tensors or underlying data) based on the indices to keep in the batch.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _merge(self, their: _BatchedPenalizer):
|
||||
"""
|
||||
Merge the penalizer with another penalizer.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _teardown(self):
|
||||
"""
|
||||
Teardown the penalizer.
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,63 @@
|
||||
import torch
|
||||
|
||||
from sglang.srt.sampling.penaltylib.orchestrator import _BatchedPenalizer
|
||||
|
||||
|
||||
class BatchedPresencePenalizer(_BatchedPenalizer):
|
||||
"""
|
||||
Presence penalizer penalizes tokens based on their presence in the output.
|
||||
"""
|
||||
|
||||
def _is_required(self) -> bool:
|
||||
return any(
|
||||
req.sampling_params.presence_penalty != 0.0
|
||||
for req in self.orchestrator.reqs()
|
||||
)
|
||||
|
||||
def _prepare(self):
|
||||
self.cumulated_presence_penalties = torch.zeros(
|
||||
(len(self.orchestrator.reqs()), self.orchestrator.vocab_size),
|
||||
dtype=torch.float32,
|
||||
device=self.orchestrator.device,
|
||||
)
|
||||
|
||||
self.presence_penalties = (
|
||||
torch.tensor(
|
||||
data=[
|
||||
req.sampling_params.presence_penalty
|
||||
for req in self.orchestrator.reqs()
|
||||
],
|
||||
dtype=torch.float32,
|
||||
device=self.orchestrator.device,
|
||||
)
|
||||
).unsqueeze_(1)
|
||||
|
||||
def _cumulate_output_tokens(self, output_ids: torch.Tensor):
|
||||
self.cumulated_presence_penalties.scatter_(
|
||||
dim=1,
|
||||
index=output_ids.unsqueeze(1),
|
||||
src=self.presence_penalties,
|
||||
)
|
||||
|
||||
def _apply(self, logits: torch.Tensor) -> torch.Tensor:
|
||||
logits.sub_(self.cumulated_presence_penalties)
|
||||
|
||||
def _filter(self, keep_indices: torch.Tensor):
|
||||
self.presence_penalties = self.presence_penalties[keep_indices]
|
||||
self.cumulated_presence_penalties = self.cumulated_presence_penalties[
|
||||
keep_indices
|
||||
]
|
||||
|
||||
def _merge(self, their: "BatchedPresencePenalizer"):
|
||||
self.presence_penalties = torch.cat(
|
||||
[self.presence_penalties, their.presence_penalties], dim=0
|
||||
)
|
||||
self.cumulated_presence_penalties = torch.cat(
|
||||
[self.cumulated_presence_penalties, their.cumulated_presence_penalties],
|
||||
dim=0,
|
||||
)
|
||||
|
||||
def _teardown(self) -> None:
|
||||
for name in ("presence_penalties", "cumulated_presence_penalties"):
|
||||
if hasattr(self, name):
|
||||
delattr(self, name)
|
||||
@@ -0,0 +1,80 @@
|
||||
import torch
|
||||
|
||||
from sglang.srt.sampling.penaltylib.orchestrator import _BatchedPenalizer
|
||||
from sglang.srt.utils import get_compiler_backend, is_npu
|
||||
|
||||
_is_npu = is_npu()
|
||||
|
||||
|
||||
@torch.compile(dynamic=True, backend=get_compiler_backend(), disable=_is_npu)
|
||||
def apply_scaling_penalties(logits, scaling_penalties):
|
||||
logits[:] = torch.where(
|
||||
logits < 0,
|
||||
logits * scaling_penalties,
|
||||
logits / scaling_penalties,
|
||||
)
|
||||
|
||||
|
||||
class BatchedRepetitionPenalizer(_BatchedPenalizer):
|
||||
"""
|
||||
Repetition penalizer penalizes tokens based on their presence in the generated output.
|
||||
"""
|
||||
|
||||
is_multiplicative: bool = True
|
||||
|
||||
def _is_required(self) -> bool:
|
||||
return any(
|
||||
req.sampling_params.repetition_penalty != 1.0
|
||||
for req in self.orchestrator.reqs()
|
||||
)
|
||||
|
||||
def _prepare(self):
|
||||
self.cumulated_repetition_penalties = torch.ones(
|
||||
(len(self.orchestrator.reqs()), self.orchestrator.vocab_size),
|
||||
dtype=torch.float32,
|
||||
device=self.orchestrator.device,
|
||||
)
|
||||
self.repetition_penalties = (
|
||||
torch.tensor(
|
||||
data=[
|
||||
req.sampling_params.repetition_penalty
|
||||
for req in self.orchestrator.reqs()
|
||||
],
|
||||
dtype=torch.float32,
|
||||
device=self.orchestrator.device,
|
||||
)
|
||||
).unsqueeze_(1)
|
||||
|
||||
def _cumulate_output_tokens(self, output_ids: torch.Tensor):
|
||||
self.cumulated_repetition_penalties.scatter_(
|
||||
dim=1,
|
||||
index=output_ids.unsqueeze(1),
|
||||
src=self.repetition_penalties,
|
||||
)
|
||||
|
||||
def _apply(self, logits: torch.Tensor) -> torch.Tensor:
|
||||
apply_scaling_penalties(logits, self.cumulated_repetition_penalties)
|
||||
return logits
|
||||
|
||||
def get_scaling_penalties(self) -> torch.Tensor:
|
||||
return self.cumulated_repetition_penalties
|
||||
|
||||
def _filter(self, keep_indices: torch.Tensor):
|
||||
self.repetition_penalties = self.repetition_penalties[keep_indices]
|
||||
self.cumulated_repetition_penalties = self.cumulated_repetition_penalties[
|
||||
keep_indices
|
||||
]
|
||||
|
||||
def _merge(self, their: "BatchedRepetitionPenalizer"):
|
||||
self.repetition_penalties = torch.cat(
|
||||
[self.repetition_penalties, their.repetition_penalties], dim=0
|
||||
)
|
||||
self.cumulated_repetition_penalties = torch.cat(
|
||||
[self.cumulated_repetition_penalties, their.cumulated_repetition_penalties],
|
||||
dim=0,
|
||||
)
|
||||
|
||||
def _teardown(self) -> None:
|
||||
for name in ("repetition_penalties", "cumulated_repetition_penalties"):
|
||||
if hasattr(self, name):
|
||||
delattr(self, name)
|
||||
@@ -0,0 +1,464 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
import sglang.srt.sampling.penaltylib as penaltylib
|
||||
from sglang.srt.runtime_context import get_server_args
|
||||
from sglang.srt.sampling.custom_logit_processor import CustomLogitProcessor
|
||||
from sglang.srt.sampling.penaltylib.repetition_penalty import apply_scaling_penalties
|
||||
from sglang.srt.sampling.sampling_params import TOP_K_ALL
|
||||
from sglang.srt.utils.common import is_pin_memory_available
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class SamplingBatchInfo:
|
||||
# Basic batched sampling params
|
||||
temperatures: torch.Tensor
|
||||
top_ps: torch.Tensor
|
||||
top_ks: torch.Tensor
|
||||
min_ps: torch.Tensor
|
||||
|
||||
# Whether all requests use greedy sampling
|
||||
is_all_greedy: bool
|
||||
|
||||
is_any_greedy: bool
|
||||
|
||||
# Whether any requests use top_p sampling
|
||||
need_top_p_sampling: bool
|
||||
|
||||
# Whether any requests use top_k sampling
|
||||
need_top_k_sampling: bool
|
||||
|
||||
# Whether any request needs min_p sampling
|
||||
need_min_p_sampling: bool
|
||||
|
||||
# Masking tensors for grammar-guided structured outputs
|
||||
vocab_size: int
|
||||
grammars: Optional[List] = None
|
||||
rids_int: Optional[torch.Tensor] = None
|
||||
bootstrap_room_ids_int: Optional[torch.Tensor] = None
|
||||
vocab_mask: Optional[torch.Tensor] = None
|
||||
apply_mask_func: Optional[Callable[[torch.Tensor, torch.Tensor], None]] = None
|
||||
|
||||
# Penalizer
|
||||
penalizer_orchestrator: Optional[penaltylib.BatchedPenalizerOrchestrator] = None
|
||||
acc_additive_penalties: Optional[torch.Tensor] = None # Used in the overlap mode
|
||||
acc_scaling_penalties: Optional[torch.Tensor] = (
|
||||
None # Used in the overlap mode for repetition penalty
|
||||
)
|
||||
|
||||
# Whether any request has custom logit processor
|
||||
has_custom_logit_processor: bool = False
|
||||
# Custom parameters
|
||||
custom_params: Optional[List[Optional[Dict[str, Any]]]] = None
|
||||
# Custom logit processor
|
||||
custom_logit_processor: Optional[
|
||||
Dict[int, Tuple[CustomLogitProcessor, torch.Tensor]]
|
||||
] = None
|
||||
|
||||
# Used for deterministic sampling
|
||||
sampling_seed: Optional[torch.Tensor] = None
|
||||
|
||||
# Device
|
||||
device: str = "cuda"
|
||||
|
||||
# Handle logit bias
|
||||
logit_bias: Optional[torch.Tensor] = None
|
||||
|
||||
@classmethod
|
||||
def from_schedule_batch(cls, batch: ScheduleBatch, vocab_size: int):
|
||||
global_server_args = get_server_args()
|
||||
enable_deterministic = global_server_args.enable_deterministic_inference
|
||||
|
||||
reqs = batch.reqs
|
||||
device = batch.device
|
||||
_pin = is_pin_memory_available(device)
|
||||
temperatures = (
|
||||
torch.tensor(
|
||||
[r.sampling_params.temperature for r in reqs],
|
||||
dtype=torch.float,
|
||||
pin_memory=_pin,
|
||||
)
|
||||
.to(device, non_blocking=True)
|
||||
.view(-1, 1)
|
||||
)
|
||||
top_ps = torch.tensor(
|
||||
[r.sampling_params.top_p for r in reqs],
|
||||
dtype=torch.float,
|
||||
pin_memory=_pin,
|
||||
).to(device, non_blocking=True)
|
||||
top_ks = torch.tensor(
|
||||
[r.sampling_params.top_k for r in reqs],
|
||||
dtype=torch.int32,
|
||||
pin_memory=_pin,
|
||||
).to(device, non_blocking=True)
|
||||
min_ps = torch.tensor(
|
||||
[r.sampling_params.min_p for r in reqs],
|
||||
dtype=torch.float,
|
||||
pin_memory=_pin,
|
||||
).to(device, non_blocking=True)
|
||||
sampling_seed = (
|
||||
torch.tensor(
|
||||
[
|
||||
(
|
||||
r.sampling_params.sampling_seed
|
||||
if r.sampling_params.sampling_seed is not None
|
||||
else 42
|
||||
)
|
||||
for r in reqs
|
||||
],
|
||||
dtype=torch.int64,
|
||||
pin_memory=_pin,
|
||||
).to(device, non_blocking=True)
|
||||
if enable_deterministic
|
||||
else None
|
||||
)
|
||||
|
||||
logit_bias = None
|
||||
if any(r.sampling_params.logit_bias is not None for r in reqs):
|
||||
logit_bias = torch.zeros(len(reqs), vocab_size, device=device)
|
||||
for i, r in enumerate(reqs):
|
||||
if r.sampling_params.logit_bias is not None:
|
||||
for key, value in r.sampling_params.logit_bias.items():
|
||||
logit_bias[i, int(key)] = value
|
||||
|
||||
# Check if any request has custom logit processor
|
||||
has_custom_logit_processor = (
|
||||
global_server_args.enable_custom_logit_processor
|
||||
and any(r.custom_logit_processor for r in reqs) # check the flag first.
|
||||
) # then check the requests.
|
||||
|
||||
if has_custom_logit_processor:
|
||||
# Merge the same type of custom logit processors together
|
||||
processor_dict = {}
|
||||
for i, r in enumerate(reqs):
|
||||
if r.custom_logit_processor is None:
|
||||
continue
|
||||
processor_str = r.custom_logit_processor
|
||||
if processor_str not in processor_dict:
|
||||
processor_dict[processor_str] = []
|
||||
processor_dict[processor_str].append(i)
|
||||
|
||||
merged_custom_logit_processor = {
|
||||
hash(processor_str): (
|
||||
# The deserialized custom logit processor object
|
||||
CustomLogitProcessor.from_str(processor_str),
|
||||
# The mask tensor for the requests that use this custom logit processor
|
||||
torch.zeros(len(reqs), dtype=torch.bool)
|
||||
.scatter_(0, torch.tensor(true_indices), True)
|
||||
.to(device, non_blocking=True),
|
||||
)
|
||||
for processor_str, true_indices in processor_dict.items()
|
||||
}
|
||||
custom_params = [r.sampling_params.custom_params for r in reqs]
|
||||
else:
|
||||
merged_custom_logit_processor = None
|
||||
custom_params = None
|
||||
|
||||
# Each penalizers will do nothing if they evaluate themselves as not required by looking at
|
||||
# the sampling_params of the requests (See {_is_required()} of each penalizers). So this
|
||||
# should not add hefty computation overhead other than simple checks.
|
||||
#
|
||||
# While we can choose not to even create the class instances if they are not required, this
|
||||
# could add additional complexity to the {ScheduleBatch} class, especially we need to
|
||||
# handle {filter_batch()} and {merge_batch()} cases as well.
|
||||
penalizer_orchestrator = penaltylib.BatchedPenalizerOrchestrator(
|
||||
vocab_size=vocab_size,
|
||||
batch=batch,
|
||||
penalizers={
|
||||
penaltylib.BatchedFrequencyPenalizer,
|
||||
penaltylib.BatchedMinNewTokensPenalizer,
|
||||
penaltylib.BatchedPresencePenalizer,
|
||||
penaltylib.BatchedRepetitionPenalizer,
|
||||
},
|
||||
)
|
||||
|
||||
ret = cls(
|
||||
temperatures=temperatures,
|
||||
top_ps=top_ps,
|
||||
top_ks=top_ks,
|
||||
min_ps=min_ps,
|
||||
sampling_seed=sampling_seed,
|
||||
is_all_greedy=all(r.sampling_params.top_k <= 1 for r in reqs),
|
||||
is_any_greedy=any(r.sampling_params.top_k <= 1 for r in reqs),
|
||||
need_top_p_sampling=any(r.sampling_params.top_p != 1.0 for r in reqs),
|
||||
need_top_k_sampling=any(r.sampling_params.top_k != TOP_K_ALL for r in reqs),
|
||||
need_min_p_sampling=any(r.sampling_params.min_p > 0 for r in reqs),
|
||||
vocab_size=vocab_size,
|
||||
penalizer_orchestrator=penalizer_orchestrator,
|
||||
has_custom_logit_processor=has_custom_logit_processor,
|
||||
custom_params=custom_params,
|
||||
custom_logit_processor=merged_custom_logit_processor,
|
||||
device=device,
|
||||
logit_bias=logit_bias,
|
||||
)
|
||||
ret.adjusted_from_schedule_batch(batch, vocab_size)
|
||||
return ret
|
||||
|
||||
# placeholder for override
|
||||
def adjusted_from_schedule_batch(self, batch: ScheduleBatch, vocab_size: int):
|
||||
pass
|
||||
|
||||
# placeholder for override
|
||||
def adjusted_merge_batch(self, other: SamplingBatchInfo):
|
||||
pass
|
||||
|
||||
# placeholder for override
|
||||
def adjusted_filter_batch(
|
||||
self, keep_indices: List[int], keep_indices_device: torch.Tensor
|
||||
):
|
||||
pass
|
||||
|
||||
def __len__(self):
|
||||
return len(self.temperatures)
|
||||
|
||||
def update_regex_vocab_mask(self):
|
||||
if not self.grammars:
|
||||
self.vocab_mask = None
|
||||
self.apply_mask_func = None
|
||||
return
|
||||
|
||||
# Find a grammar from the list
|
||||
first_grammar = next(grammar for grammar in self.grammars if grammar)
|
||||
|
||||
# TODO(lianmin): Maybe we can reuse the existing mask?
|
||||
self.vocab_mask = first_grammar.allocate_vocab_mask(
|
||||
vocab_size=self.vocab_size,
|
||||
batch_size=len(self.temperatures),
|
||||
device=self.device,
|
||||
)
|
||||
self.apply_mask_func = (
|
||||
first_grammar.apply_vocab_mask
|
||||
) # force to use static method
|
||||
|
||||
# Apply the mask
|
||||
for i, grammar in enumerate(self.grammars):
|
||||
if grammar and not grammar.finished and not grammar.is_terminated():
|
||||
grammar.fill_vocab_mask(self.vocab_mask, i)
|
||||
|
||||
# Move the mask to the device if needed
|
||||
self.vocab_mask = first_grammar.move_vocab_mask(self.vocab_mask, self.device)
|
||||
|
||||
def update_penalties(self):
|
||||
if self.penalizer_orchestrator.is_required:
|
||||
self.acc_additive_penalties = torch.zeros(
|
||||
(len(self.temperatures), self.vocab_size),
|
||||
dtype=torch.float32,
|
||||
device=self.temperatures.device,
|
||||
)
|
||||
self.penalizer_orchestrator.accumulate_additive_penalties(
|
||||
self.acc_additive_penalties
|
||||
)
|
||||
self.acc_scaling_penalties = (
|
||||
self.penalizer_orchestrator.accumulate_scaling_penalties()
|
||||
)
|
||||
else:
|
||||
self.acc_additive_penalties = None
|
||||
self.acc_scaling_penalties = None
|
||||
|
||||
def apply_logits_bias(self, logits: torch.Tensor):
|
||||
if self.acc_additive_penalties is not None:
|
||||
# Used in the overlap mode
|
||||
logits.add_(self.acc_additive_penalties)
|
||||
|
||||
if self.acc_scaling_penalties is not None:
|
||||
# Used in the overlap mode
|
||||
apply_scaling_penalties(logits, self.acc_scaling_penalties)
|
||||
|
||||
if self.penalizer_orchestrator and self.penalizer_orchestrator.is_required:
|
||||
# Used in the non-overlap mode
|
||||
self.penalizer_orchestrator.apply(logits)
|
||||
|
||||
if self.vocab_mask is not None:
|
||||
self.apply_mask_func(logits=logits, vocab_mask=self.vocab_mask)
|
||||
|
||||
if self.logit_bias is not None:
|
||||
logits.add_(self.logit_bias)
|
||||
|
||||
def filter_batch(self, keep_indices: List[int], keep_indices_device: torch.Tensor):
|
||||
self.penalizer_orchestrator.filter(keep_indices_device)
|
||||
|
||||
if self.has_custom_logit_processor:
|
||||
self._filter_batch_custom_logit_processor(keep_indices, keep_indices_device)
|
||||
|
||||
for item in [
|
||||
"temperatures",
|
||||
"top_ps",
|
||||
"top_ks",
|
||||
"min_ps",
|
||||
"sampling_seed",
|
||||
]:
|
||||
value = getattr(self, item, None)
|
||||
if value is not None:
|
||||
setattr(self, item, value[keep_indices_device])
|
||||
|
||||
if self.logit_bias is not None:
|
||||
self.logit_bias = self.logit_bias[keep_indices_device]
|
||||
|
||||
self.adjusted_filter_batch(keep_indices, keep_indices_device)
|
||||
|
||||
def _filter_batch_custom_logit_processor(
|
||||
self, keep_indices: List[int], keep_indices_device: torch.Tensor
|
||||
):
|
||||
"""Filter the custom logit processor and custom params"""
|
||||
self.custom_logit_processor = {
|
||||
k: (p, mask[keep_indices_device])
|
||||
for k, (p, mask) in self.custom_logit_processor.items()
|
||||
if torch.any(
|
||||
mask[keep_indices_device]
|
||||
) # ignore the custom logit processor whose mask is all False
|
||||
}
|
||||
self.custom_params = [self.custom_params[i] for i in keep_indices]
|
||||
|
||||
# If the custom logit processor is an empty dict, set the flag to False,
|
||||
# and set the custom logit processor and custom params to None.
|
||||
if len(self.custom_logit_processor) == 0:
|
||||
self.custom_logit_processor = None
|
||||
self.custom_params = None
|
||||
self.has_custom_logit_processor = False
|
||||
|
||||
@staticmethod
|
||||
def merge_custom_logit_processor(
|
||||
lhs: Optional[Dict[int, Tuple[CustomLogitProcessor, torch.Tensor]]],
|
||||
rhs: Optional[Dict[int, Tuple[CustomLogitProcessor, torch.Tensor]]],
|
||||
bs1: int,
|
||||
bs2: int,
|
||||
device: str,
|
||||
):
|
||||
if lhs is None and rhs is None:
|
||||
return None
|
||||
lhs, rhs = lhs or {}, rhs or {}
|
||||
|
||||
keys = set(lhs.keys()).union(set(rhs.keys()))
|
||||
merged_dict = {}
|
||||
|
||||
for k in keys:
|
||||
# Get the logit processor object
|
||||
processor = lhs[k][0] if k in lhs else rhs[k][0]
|
||||
# Get and merge the mask tensors from the two dicts
|
||||
left_mask = (
|
||||
lhs[k][1]
|
||||
if k in lhs
|
||||
else torch.zeros(bs1, dtype=torch.bool, device=device)
|
||||
)
|
||||
right_mask = (
|
||||
rhs[k][1]
|
||||
if k in rhs
|
||||
else torch.zeros(bs2, dtype=torch.bool, device=device)
|
||||
)
|
||||
merged_dict[k] = (processor, torch.cat([left_mask, right_mask]))
|
||||
|
||||
assert merged_dict[k][1].shape[0] == bs1 + bs2, (
|
||||
f"The batch size of merged mask ({merged_dict[k][1].shape[0]}) does not match "
|
||||
f"the sum of the batch sizes of the two masks ({bs1 + bs2})"
|
||||
f"\n{left_mask=}\n{right_mask=}\n{bs1=}\n{bs2=}"
|
||||
f"\n{lhs=}\n{rhs=}"
|
||||
)
|
||||
|
||||
return merged_dict
|
||||
|
||||
def merge_batch(self, other: SamplingBatchInfo):
|
||||
self.penalizer_orchestrator.merge(other.penalizer_orchestrator)
|
||||
|
||||
# Merge the custom logit processors and custom params lists
|
||||
if self.has_custom_logit_processor or other.has_custom_logit_processor:
|
||||
# Merge the custom logit processors
|
||||
self.custom_logit_processor = (
|
||||
SamplingBatchInfo.merge_custom_logit_processor(
|
||||
self.custom_logit_processor,
|
||||
other.custom_logit_processor,
|
||||
len(self),
|
||||
len(other),
|
||||
self.device,
|
||||
)
|
||||
)
|
||||
# Merge the custom params lists
|
||||
self.custom_params = self.custom_params or [None] * len(self)
|
||||
other.custom_params = other.custom_params or [None] * len(other)
|
||||
self.custom_params.extend(other.custom_params)
|
||||
|
||||
# Set the flag to True if any of the two has custom logit processor
|
||||
self.has_custom_logit_processor = True
|
||||
|
||||
# Merge logit bias - note this has to come before the temperatures tensor update! Otherwise will cause crashes.
|
||||
# See note below on len(self) and len(other).
|
||||
self.logit_bias = merge_bias_tensor(
|
||||
self.logit_bias, other.logit_bias, len(self), len(other), self.device, 0.0
|
||||
)
|
||||
|
||||
# Note: because the __len()__ operator is defined on the temperatures tensor,
|
||||
# please make sure any merge operation with len(self) or len(other) is done before
|
||||
# the merge operation of the temperatures tensor below.
|
||||
for item in [
|
||||
"temperatures",
|
||||
"top_ps",
|
||||
"top_ks",
|
||||
"min_ps",
|
||||
"sampling_seed",
|
||||
]:
|
||||
self_val = getattr(self, item, None)
|
||||
other_val = getattr(other, item, None)
|
||||
if self_val is not None and other_val is not None:
|
||||
setattr(self, item, torch.cat([self_val, other_val]))
|
||||
|
||||
self.is_all_greedy &= other.is_all_greedy
|
||||
self.is_any_greedy |= other.is_any_greedy
|
||||
self.need_top_p_sampling |= other.need_top_p_sampling
|
||||
self.need_top_k_sampling |= other.need_top_k_sampling
|
||||
self.need_min_p_sampling |= other.need_min_p_sampling
|
||||
|
||||
self.adjusted_merge_batch(other)
|
||||
|
||||
def copy_for_forward(self):
|
||||
# Accumulate the penalty into a pre-allocated buffer to get rid of the dependency of `penalizer_orchestrator` later
|
||||
self.update_penalties()
|
||||
return dataclasses.replace(self, penalizer_orchestrator=None)
|
||||
|
||||
|
||||
def merge_bias_tensor(
|
||||
lhs: Optional[torch.Tensor],
|
||||
rhs: Optional[torch.Tensor],
|
||||
bs1: int,
|
||||
bs2: int,
|
||||
device: str,
|
||||
default: float,
|
||||
):
|
||||
"""Merge two bias tensors for batch merging.
|
||||
|
||||
Args:
|
||||
lhs: Left-hand side tensor
|
||||
rhs: Right-hand side tensor
|
||||
bs1: Batch size of left-hand side tensor
|
||||
bs2: Batch size of right-hand side tensor
|
||||
device: Device to place the merged tensor on
|
||||
default: Default value for missing tensor elements
|
||||
|
||||
Returns:
|
||||
Merged tensor or None if both inputs are None
|
||||
"""
|
||||
if lhs is None and rhs is None:
|
||||
return None
|
||||
|
||||
if lhs is not None and rhs is not None:
|
||||
return torch.cat([lhs, rhs])
|
||||
else:
|
||||
if lhs is not None:
|
||||
shape, dtype = lhs.shape[1:], lhs.dtype
|
||||
else:
|
||||
shape, dtype = rhs.shape[1:], rhs.dtype
|
||||
|
||||
if lhs is None:
|
||||
lhs = torch.empty((bs1, *shape), device=device, dtype=dtype).fill_(default)
|
||||
if rhs is None:
|
||||
rhs = torch.empty((bs2, *shape), device=device, dtype=dtype).fill_(default)
|
||||
return torch.cat([lhs, rhs])
|
||||
@@ -0,0 +1,324 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Sampling parameters for text generation."""
|
||||
|
||||
import logging
|
||||
import math
|
||||
from typing import Dict, List, Optional, Set, Union
|
||||
|
||||
import msgspec
|
||||
|
||||
# sre_parse is deprecated in Python 3.11+, use re._parser instead
|
||||
try:
|
||||
import re._parser as sre_parse
|
||||
except ImportError:
|
||||
import sre_parse # Python < 3.11
|
||||
|
||||
# JSON-safe value types for custom_params. Must survive msgpack IPC
|
||||
# without PickleWrapper. After deserialization on the scheduler side,
|
||||
# Req.__init__ injects "__req__" (a Req object) into the dict in-process;
|
||||
# that augmented dict is never re-serialized.
|
||||
_JsonScalar = Union[None, bool, int, float, str]
|
||||
CustomParamValue = Union[
|
||||
_JsonScalar,
|
||||
List[_JsonScalar],
|
||||
Dict[str, _JsonScalar],
|
||||
]
|
||||
|
||||
_SAMPLING_EPS = 1e-6
|
||||
TOP_K_ALL = 1 << 30
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def raise_if_tokenizer_required(
|
||||
tokenizer, stop_strs, stop_regex_strs, min_new_tokens=0
|
||||
):
|
||||
"""Raise ValueError if tokenizer-dependent features are used without a tokenizer.
|
||||
|
||||
String-based stop conditions (stop_strs, stop_regex_strs) require tokenizer.decode()
|
||||
to convert output token IDs to text for matching. min_new_tokens requires the
|
||||
tokenizer's eos_token_id to penalize. When skip_tokenizer_init=True, these cannot
|
||||
be used.
|
||||
"""
|
||||
if tokenizer is not None:
|
||||
return
|
||||
|
||||
if stop_strs:
|
||||
raise ValueError(
|
||||
f"stop={stop_strs!r} is unavailable when skip_tokenizer_init=True "
|
||||
"(requires tokenizer to decode tokens to text for matching)."
|
||||
)
|
||||
if stop_regex_strs:
|
||||
raise ValueError(
|
||||
f"stop_regex={stop_regex_strs!r} is unavailable when skip_tokenizer_init=True "
|
||||
"(requires tokenizer to decode tokens to text for matching)."
|
||||
)
|
||||
if min_new_tokens > 0:
|
||||
raise ValueError(
|
||||
f"min_new_tokens={min_new_tokens} is unavailable when skip_tokenizer_init=True "
|
||||
"(requires tokenizer for eos_token_id)."
|
||||
)
|
||||
|
||||
|
||||
class SamplingParams(msgspec.Struct, kw_only=True, omit_defaults=True):
|
||||
"""
|
||||
The sampling parameters.
|
||||
|
||||
See docs/backend/sampling_params.md or
|
||||
https://docs.sglang.io/backend/sampling_params.html
|
||||
for the documentation.
|
||||
"""
|
||||
|
||||
# --- API parameters (set by callers) ---
|
||||
max_new_tokens: Optional[int] = 128
|
||||
stop: Optional[Union[str, List[str]]] = (
|
||||
None # API input alias, copied to stop_strs then cleared in normalize()
|
||||
)
|
||||
stop_token_ids: Optional[Set[int]] = None
|
||||
stop_regex: Optional[Union[str, List[str]]] = (
|
||||
None # API input alias, copied to stop_regex_strs then cleared in normalize()
|
||||
)
|
||||
temperature: float = 1.0
|
||||
top_p: float = 1.0
|
||||
top_k: int = TOP_K_ALL
|
||||
min_p: float = 0.0
|
||||
frequency_penalty: float = 0.0
|
||||
presence_penalty: float = 0.0
|
||||
repetition_penalty: float = 1.0
|
||||
min_new_tokens: int = 0
|
||||
n: int = 1
|
||||
json_schema: Optional[str] = None
|
||||
regex: Optional[str] = None
|
||||
ebnf: Optional[str] = None
|
||||
structural_tag: Optional[str] = None
|
||||
ignore_eos: bool = False
|
||||
skip_special_tokens: bool = True
|
||||
spaces_between_special_tokens: bool = True
|
||||
no_stop_trim: bool = False
|
||||
custom_params: Optional[Dict[str, CustomParamValue]] = None
|
||||
stream_interval: Optional[int] = None
|
||||
logit_bias: Optional[Dict[str, float]] = None
|
||||
sampling_seed: Optional[int] = None
|
||||
|
||||
# --- Internal fields (populated by __post_init__ or normalize(), not API-facing) ---
|
||||
stop_strs: Optional[Union[str, List[str]]] = None # from stop
|
||||
stop_regex_strs: Optional[Union[str, List[str]]] = None # from stop_regex
|
||||
stop_str_max_len: int = 0 # set by normalize()
|
||||
stop_regex_max_len: int = 0 # set by normalize()
|
||||
is_normalized: bool = False # set by normalize()
|
||||
|
||||
def __post_init__(self):
|
||||
# For non-optional params, treat None as "use default" so that callers
|
||||
# (e.g. /generate) can pass null without crashing verify().
|
||||
|
||||
# msgspec calls __post_init__ after deserialization. Once normalize()
|
||||
# has populated tokenizer-derived fields, avoid resetting them.
|
||||
if self.is_normalized:
|
||||
return
|
||||
|
||||
self.stop_strs = self.stop
|
||||
if self.stop_token_ids:
|
||||
filtered = {int(t) for t in self.stop_token_ids if t is not None}
|
||||
self.stop_token_ids = filtered or None
|
||||
else:
|
||||
self.stop_token_ids = None
|
||||
self.stop_regex_strs = self.stop_regex
|
||||
self.temperature = self.temperature if self.temperature is not None else 1.0
|
||||
self.top_p = self.top_p if self.top_p is not None else 1.0
|
||||
self.top_k = self.top_k if self.top_k is not None else -1
|
||||
self.min_p = self.min_p if self.min_p is not None else 0.0
|
||||
self.frequency_penalty = (
|
||||
self.frequency_penalty if self.frequency_penalty is not None else 0.0
|
||||
)
|
||||
self.presence_penalty = (
|
||||
self.presence_penalty if self.presence_penalty is not None else 0.0
|
||||
)
|
||||
self.repetition_penalty = (
|
||||
self.repetition_penalty if self.repetition_penalty is not None else 1.0
|
||||
)
|
||||
self.min_new_tokens = (
|
||||
self.min_new_tokens if self.min_new_tokens is not None else 0
|
||||
)
|
||||
self.n = self.n if self.n is not None else 1
|
||||
self.ignore_eos = self.ignore_eos if self.ignore_eos is not None else False
|
||||
self.skip_special_tokens = (
|
||||
self.skip_special_tokens if self.skip_special_tokens is not None else True
|
||||
)
|
||||
self.spaces_between_special_tokens = (
|
||||
self.spaces_between_special_tokens
|
||||
if self.spaces_between_special_tokens is not None
|
||||
else True
|
||||
)
|
||||
self.no_stop_trim = (
|
||||
self.no_stop_trim if self.no_stop_trim is not None else False
|
||||
)
|
||||
|
||||
# Process some special cases
|
||||
if 0 <= self.temperature < _SAMPLING_EPS:
|
||||
# top_k = 1 means greedy sampling
|
||||
self.temperature = 1.0
|
||||
self.top_k = 1
|
||||
if self.top_k == -1:
|
||||
self.top_k = TOP_K_ALL # whole vocabulary
|
||||
|
||||
def verify(self, vocab_size):
|
||||
if not math.isfinite(self.temperature) or self.temperature < 0.0:
|
||||
raise ValueError(
|
||||
f"temperature must be a non-negative finite number, got {self.temperature}."
|
||||
)
|
||||
if not 0.0 < self.top_p <= 1.0:
|
||||
raise ValueError(f"top_p must be in (0, 1], got {self.top_p}.")
|
||||
if not 0.0 <= self.min_p <= 1.0:
|
||||
raise ValueError(f"min_p must be in [0, 1], got {self.min_p}.")
|
||||
if self.top_k < 1 or self.top_k == -1:
|
||||
raise ValueError(
|
||||
f"top_k must be -1 (disable) or at least 1, got {self.top_k}."
|
||||
)
|
||||
if not -2.0 <= self.frequency_penalty <= 2.0:
|
||||
raise ValueError(
|
||||
"frequency_penalty must be in [-2, 2], got "
|
||||
f"{self.frequency_penalty}."
|
||||
)
|
||||
if not -2.0 <= self.presence_penalty <= 2.0:
|
||||
raise ValueError(
|
||||
"presence_penalty must be in [-2, 2], got " f"{self.presence_penalty}."
|
||||
)
|
||||
if not 0.0 < self.repetition_penalty <= 2.0:
|
||||
raise ValueError(
|
||||
"repetition_penalty must be in (0, 2] (1.0 = no penalty), "
|
||||
f"got {self.repetition_penalty}."
|
||||
)
|
||||
if not 0 <= self.min_new_tokens:
|
||||
raise ValueError(
|
||||
f"min_new_tokens must be in [0, max_new_tokens], got "
|
||||
f"{self.min_new_tokens}."
|
||||
)
|
||||
if self.max_new_tokens is not None:
|
||||
if self.max_new_tokens < 0:
|
||||
raise ValueError(
|
||||
f"max_new_tokens must be at least 0, got {self.max_new_tokens}."
|
||||
)
|
||||
if not self.min_new_tokens <= self.max_new_tokens:
|
||||
raise ValueError(
|
||||
f"min_new_tokens must be in [0, max_new_tokens({self.max_new_tokens})], got "
|
||||
f"{self.min_new_tokens}."
|
||||
)
|
||||
if self.logit_bias is not None:
|
||||
for token_id in self.logit_bias:
|
||||
if not 0 <= int(token_id) < vocab_size:
|
||||
raise ValueError(
|
||||
f"logit_bias must has keys in [0, {vocab_size - 1}], got "
|
||||
f"{token_id}."
|
||||
)
|
||||
|
||||
grammars = [
|
||||
self.json_schema,
|
||||
self.regex,
|
||||
self.ebnf,
|
||||
] # since mutually exclusive, only one can be set
|
||||
if sum(x is not None for x in grammars) > 1:
|
||||
raise ValueError("Only one of regex, json_schema, or ebnf can be set.")
|
||||
|
||||
def normalize(self, tokenizer):
|
||||
# Process stop strings
|
||||
if self.stop_strs is None:
|
||||
self.stop_strs = []
|
||||
self.stop_str_max_len = 0
|
||||
else:
|
||||
if isinstance(self.stop_strs, str):
|
||||
self.stop_strs = [self.stop_strs]
|
||||
|
||||
stop_str_max_len = 0
|
||||
for stop_str in self.stop_strs:
|
||||
if tokenizer is not None:
|
||||
stop_str_ids = tokenizer.encode(stop_str, add_special_tokens=False)
|
||||
stop_str_max_len = max(stop_str_max_len, len(stop_str_ids))
|
||||
else:
|
||||
stop_str_max_len = max(stop_str_max_len, len(stop_str))
|
||||
self.stop_str_max_len = stop_str_max_len
|
||||
|
||||
# Process stop regex strings
|
||||
if self.stop_regex_strs is None:
|
||||
self.stop_regex_strs = []
|
||||
self.stop_regex_max_len = 0
|
||||
else:
|
||||
if isinstance(self.stop_regex_strs, str):
|
||||
self.stop_regex_strs = [self.stop_regex_strs]
|
||||
|
||||
stop_regex_max_len = 0
|
||||
for stop_regex in self.stop_regex_strs:
|
||||
stop_regex_max_len = max(
|
||||
stop_regex_max_len, get_max_seq_length(stop_regex)
|
||||
)
|
||||
|
||||
self.stop_regex_max_len = stop_regex_max_len
|
||||
|
||||
# Validate tokenizer is available for tokenizer-dependent features
|
||||
raise_if_tokenizer_required(
|
||||
tokenizer, self.stop_strs, self.stop_regex_strs, self.min_new_tokens
|
||||
)
|
||||
|
||||
# Clear API input aliases so omit_defaults=True drops them from the wire.
|
||||
self.stop = None
|
||||
self.stop_regex = None
|
||||
self.is_normalized = True
|
||||
|
||||
|
||||
# This function gets a strict upperbound on the maximum number of tokens that would need
|
||||
# to be buffered to match the input regex string
|
||||
# NOTE: in the worst case, one character that needs to be buffered corresponds to one
|
||||
# token
|
||||
def get_max_seq_length(regex_str: str):
|
||||
return _max_length_from_subpattern(sre_parse.parse(regex_str))
|
||||
|
||||
|
||||
MAX_LEN = 2**30
|
||||
|
||||
|
||||
def _max_length_from_subpattern(subpattern: sre_parse.SubPattern):
|
||||
total = 0
|
||||
for token, value in subpattern:
|
||||
if token in {
|
||||
sre_parse.LITERAL, # `value` is any one character
|
||||
sre_parse.IN, # Any character within `value`
|
||||
sre_parse.ANY, # "."
|
||||
}:
|
||||
total += 1
|
||||
elif token == sre_parse.SUBPATTERN:
|
||||
# EG: (a\d+) ->
|
||||
# [(SUBPATTERN,
|
||||
# (1, 0, 0, [(LITERAL, 97),
|
||||
# (MAX_REPEAT, (1, MAXREPEAT, [(IN, [(CATEGORY, CATEGORY_DIGIT)])]))]))]
|
||||
_, _, _, inner_subpattern = value
|
||||
total += _max_length_from_subpattern(inner_subpattern)
|
||||
elif token == sre_parse.BRANCH:
|
||||
_, branches = value
|
||||
total += max(_max_length_from_subpattern(branch) for branch in branches)
|
||||
elif token in {sre_parse.MAX_REPEAT, sre_parse.MIN_REPEAT}:
|
||||
_, max_num_repeat, inner_subpattern = value
|
||||
if max_num_repeat == sre_parse.MAXREPEAT:
|
||||
total += MAX_LEN
|
||||
else:
|
||||
total += max_num_repeat * _max_length_from_subpattern(inner_subpattern)
|
||||
elif token == sre_parse.AT:
|
||||
# These are zero-width assertions like ^, $, and \b that don't add to the max
|
||||
# length
|
||||
total += 0
|
||||
else:
|
||||
logger.warning(f"Got unhandled regex token: {token}")
|
||||
|
||||
total += MAX_LEN
|
||||
|
||||
return total
|
||||
Reference in New Issue
Block a user