Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions lightllm/server/core/objs/sampling_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,31 +66,42 @@ def initialize(self, stop_sequences: Union[str, List[Union[List[int], str]]], to
elif isinstance(stop_sequences, str):
stop_sequences = [stop_sequences]

groups: List[List[int]] = self.stop_sentences_to_token_ids(stop_sequences, tokenizer)
# 这里必须使用 (token_ids, 原始条目) 的配对结果:空条目(如 "" 或 [])会被过滤掉,
# 若还按原始下标去取 stop_sequences[group_idx],后面的条目就会和 token id 组错位,
# 导致 sequence_str 挂到别的组上(丢失停止字符串,或凭空多出一个停止字符串)。
groups: List[Tuple[List[int], Union[List[int], str]]] = self._stop_sentences_to_token_id_pairs(
stop_sequences, tokenizer
)
self.size = len(groups)
assert self.size <= MAX_STOP_SEQUENCES, "Too many stop sequence groups."

for group_idx in range(self.size):
if isinstance(stop_sequences[group_idx], str):
self.groups[group_idx].initialize(groups[group_idx], sequence_str=stop_sequences[group_idx])
for group_idx, (token_ids, stop_info) in enumerate(groups):
if isinstance(stop_info, str):
self.groups[group_idx].initialize(token_ids, sequence_str=stop_info)
else:
self.groups[group_idx].initialize(groups[group_idx])
self.groups[group_idx].initialize(token_ids)

def stop_sentences_to_token_ids(self, stop_sequences: List[Union[List[int], str]], tokenizer) -> List[List[int]]:
def _stop_sentences_to_token_id_pairs(
self, stop_sequences: List[Union[List[int], str]], tokenizer
) -> List[Tuple[List[int], Union[List[int], str]]]:
"""返回 (token_ids, 原始 stop 条目) 的列表,保证两者一一对应。"""
new_stop_sequences = []
for stop_info in stop_sequences:
if isinstance(stop_info, str):
stop_str_ids = self._stop_str_to_token_ids(stop_info, tokenizer)
if stop_str_ids is not None and len(stop_str_ids) > 0:
new_stop_sequences.append(stop_str_ids)
new_stop_sequences.append((stop_str_ids, stop_info))
if isinstance(stop_info, list):
if all(isinstance(x, int) for x in stop_info):
if len(stop_info) > 0:
new_stop_sequences.append(stop_info)
new_stop_sequences.append((stop_info, stop_info))
else:
assert False, "stop_sequences item must be type List[int] when it is a list."
return new_stop_sequences

def stop_sentences_to_token_ids(self, stop_sequences: List[Union[List[int], str]], tokenizer) -> List[List[int]]:
return [token_ids for token_ids, _ in self._stop_sentences_to_token_id_pairs(stop_sequences, tokenizer)]

def _stop_str_to_token_ids(self, stop_str: str, tokenizer) -> List[int]:
stop_str_ids = tokenizer.encode(stop_str, add_special_tokens=False)
return stop_str_ids
Expand Down Expand Up @@ -202,7 +213,7 @@ class AllowedTokenIds(ctypes.Structure):
def initialize(self, ids: List[int]):
self.size = len(ids)
assert self.size <= ALLOWED_TOKEN_IDS_MAX_LENGTH, "Too many allowed token IDs."
assert all(isinstance(e, int) for e in self.ids), "all must be int"
assert all(isinstance(e, int) for e in ids), "all must be int"
self.ids[: self.size] = ids[:]

def to_list(self):
Expand Down
53 changes: 36 additions & 17 deletions unit_tests/server/core/objs/test_sampling_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
RegularConstraint,
AllowedTokenIds,
ExponentialDecayLengthPenalty,
DecodeNode,
SamplingParams,
GuidedGrammar,
GuidedJsonSchema,
NodeUUId,
STOP_SEQUENCE_MAX_LENGTH,
REGULAR_CONSTRAINT_MAX_LENGTH,
ALLOWED_TOKEN_IDS_MAX_LENGTH,
Expand Down Expand Up @@ -117,22 +117,41 @@ def test_exponential_decay_length_penalty_initialization():
penalty.initialize((5, 0.5))


def test_decode_node_initialization():
node = DecodeNode()
data = {
"node_id": 12345678901234567890, # 示例 UUID
"ip": "192.168.1.1",
"rpyc_port": 8080,
"max_new_tokens": 10,
}
node.initialize(data)
assert node.exists is True
assert node.node_id.node_id_high == (12345678901234567890 >> 64) & 0xFFFFFFFFFFFFFFFF
assert node.node_id.node_id_low == 12345678901234567890 & 0xFFFFFFFFFFFFFFFF
assert node.ip[0] == 192
assert node.ip[1] == 168
assert node.ip[2] == 1
assert node.ip[3] == 1
def test_node_uuid_roundtrip():
node_id = 12345678901234567890
uuid = NodeUUId()
uuid.initialize(node_id)
assert uuid.node_id_high == (node_id >> 64) & 0xFFFFFFFFFFFFFFFF
assert uuid.node_id_low == node_id & 0xFFFFFFFFFFFFFFFF
assert uuid.get() == node_id


def test_allowed_token_ids_rejects_non_int():
allowed_ids = AllowedTokenIds()
with pytest.raises(AssertionError):
allowed_ids.initialize([1, "2", 3])


@pytest.mark.parametrize(
"stop_sequences, expected_token_ids, expected_strings",
[
# 全部有效,无过滤,token id 组与字符串一一对应。
(["stop1", "stop2"], [[1, 2], [3, 4]], ["stop1", "stop2"]),
# 前置的空字符串会被过滤掉,后面的条目不能因此错位。
(["", "stop1"], [[1, 2]], ["stop1"]),
# 前置的空 id 列表同理。
([[], "stop2"], [[3, 4]], ["stop2"]),
# 被过滤掉的字符串条目不能把自己的字符串挂到后一个条目的 token id 上。
(["unknown", "stop2"], [[3, 4]], ["stop2"]),
# 纯 id 条目不携带字符串。
([[7, 8], "stop1"], [[7, 8], [1, 2]], ["stop1"]),
],
)
def test_stop_sequence_groups_keeps_ids_and_strings_aligned(stop_sequences, expected_token_ids, expected_strings):
groups = StopSequenceGroups()
groups.initialize(stop_sequences, MockTokenizer())
assert groups.to_list() == expected_token_ids
assert sorted(groups.to_strings()) == sorted(expected_strings)


def test_sampling_params_initialization():
Expand Down