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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
@@ -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