[Feature] support regex strings as a stopping condition (#10635)
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
# ==============================================================================
|
||||
"""Sampling parameters for text generation."""
|
||||
|
||||
import logging
|
||||
import sre_parse
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from sglang.srt.utils import get_bool_env_var
|
||||
@@ -20,6 +22,8 @@ from sglang.srt.utils import get_bool_env_var
|
||||
_SAMPLING_EPS = 1e-6
|
||||
TOP_K_ALL = 1 << 30
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SamplingParams:
|
||||
"""
|
||||
@@ -35,6 +39,7 @@ class SamplingParams:
|
||||
max_new_tokens: int = 128,
|
||||
stop: Optional[Union[str, List[str]]] = None,
|
||||
stop_token_ids: Optional[List[int]] = None,
|
||||
stop_regex: Optional[Union[str, List[str]]] = None,
|
||||
temperature: float = 1.0,
|
||||
top_p: float = 1.0,
|
||||
top_k: int = -1,
|
||||
@@ -63,6 +68,7 @@ class SamplingParams:
|
||||
self.stop_token_ids = set(stop_token_ids)
|
||||
else:
|
||||
self.stop_token_ids = None
|
||||
self.stop_regex_strs = stop_regex
|
||||
self.temperature = temperature
|
||||
self.top_p = top_p
|
||||
self.top_k = top_k
|
||||
@@ -170,3 +176,67 @@ class SamplingParams:
|
||||
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
|
||||
|
||||
|
||||
# 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