[feat] Opt-in flat response format for prompt top logprobs (#32078)
This commit is contained in:
@@ -208,6 +208,9 @@ class GenerateReqInput:
|
||||
return_sampling_mask: Optional[Union[List[bool], bool]] = None
|
||||
# Whether to detokenize tokens in text in the returned logprobs.
|
||||
return_text_in_logprobs: bool = False
|
||||
# Return prompt top logprobs as flat arrays plus shape metadata instead of
|
||||
# the nested per-position [logprob, token_id, text] lists.
|
||||
return_flat_raw_top_logprobs: bool = False
|
||||
# Whether to stream output.
|
||||
stream: bool = False
|
||||
# Whether to log metrics for this request (e.g. health_generate calls do not log metrics)
|
||||
@@ -369,6 +372,15 @@ class GenerateReqInput:
|
||||
raise ValueError(
|
||||
"Either text, input_ids or input_embeds should be provided."
|
||||
)
|
||||
if (
|
||||
self.return_flat_raw_top_logprobs
|
||||
and self.multi_item_delimiter_indices is not None
|
||||
):
|
||||
raise ValueError(
|
||||
"return_flat_raw_top_logprobs does not support multi-item "
|
||||
"scoring: delimiter-sparse top logprob rows have no contiguous "
|
||||
"position mapping."
|
||||
)
|
||||
|
||||
def _determine_batch_size(self):
|
||||
"""Determine if this is a single example or a batch and the batch size."""
|
||||
@@ -724,6 +736,7 @@ class GenerateReqInput:
|
||||
token_ids_logprob=self.token_ids_logprob[i],
|
||||
return_sampling_mask=self.return_sampling_mask[i],
|
||||
return_text_in_logprobs=self.return_text_in_logprobs,
|
||||
return_flat_raw_top_logprobs=self.return_flat_raw_top_logprobs,
|
||||
stream=self.stream,
|
||||
log_metrics=self.log_metrics,
|
||||
return_hidden_states=(
|
||||
|
||||
@@ -226,6 +226,11 @@ class ReqState:
|
||||
output_token_sampling_mask: List = dataclasses.field(default_factory=list)
|
||||
output_token_sampling_logprobs: List = dataclasses.field(default_factory=list)
|
||||
|
||||
# Cached flat-format prompt top logprob fields; rebuilt only when more
|
||||
# prefill chunks arrive, so streaming decode chunks reuse the payload.
|
||||
input_top_logprobs_flat_fields: Optional[Dict[str, Any]] = None
|
||||
input_top_logprobs_flat_num_rows: int = -1
|
||||
|
||||
# For detokenized logprobs
|
||||
input_token_logprobs: List[Any] = dataclasses.field(default_factory=list)
|
||||
output_token_logprobs: List[Any] = dataclasses.field(default_factory=list)
|
||||
@@ -254,6 +259,43 @@ def _slice_streaming_output_meta_info(
|
||||
meta_info[key] = meta_info[key][last_output_offset:]
|
||||
|
||||
|
||||
def _build_flat_input_top_logprobs_fields(
|
||||
input_top_logprobs_val: List[Optional[List[float]]],
|
||||
input_top_logprobs_idx: List[Optional[List[int]]],
|
||||
top_logprobs_num: int,
|
||||
) -> Dict[str, Any]:
|
||||
"""Build the flat raw prompt top logprob response fields.
|
||||
|
||||
`input_top_logprobs_shape` is the literal [rows, k] shape of the flat
|
||||
arrays. The leading null positions (counted by
|
||||
`input_top_logprobs_null_prefix`) precede the arrays, so covered position
|
||||
i, entry j lives at flat[(i - null_prefix) * k + j] and the covered range
|
||||
spans null_prefix + rows positions.
|
||||
"""
|
||||
num_rows = len(input_top_logprobs_val)
|
||||
null_prefix = 0
|
||||
while null_prefix < num_rows and not input_top_logprobs_val[null_prefix]:
|
||||
null_prefix += 1
|
||||
val_rows = input_top_logprobs_val[null_prefix:]
|
||||
idx_rows = input_top_logprobs_idx[null_prefix:]
|
||||
k = len(val_rows[0]) if val_rows else top_logprobs_num
|
||||
for offset, row in enumerate(val_rows):
|
||||
if row is None or len(row) != k:
|
||||
# Not representable by (shape, null_prefix); e.g. multi-item scoring.
|
||||
raise ValueError(
|
||||
"return_flat_raw_top_logprobs requires rectangular top logprob "
|
||||
f"rows with nulls only in the leading prefix; row {null_prefix + offset} "
|
||||
f"has {None if row is None else len(row)} entries (expected {k})."
|
||||
)
|
||||
|
||||
fields: Dict[str, Any] = {}
|
||||
fields["input_top_logprobs_val_flat"] = [v for row in val_rows for v in row]
|
||||
fields["input_top_logprobs_idx_flat"] = [i for row in idx_rows for i in row]
|
||||
fields["input_top_logprobs_shape"] = [len(val_rows), k]
|
||||
fields["input_top_logprobs_null_prefix"] = null_prefix
|
||||
return fields
|
||||
|
||||
|
||||
class InputFormat(Enum):
|
||||
"""Input format types for tokenization handling."""
|
||||
|
||||
@@ -2228,14 +2270,53 @@ class TokenizerManager(TokenizerControlMixin, TokenizerManagerScoreMixin):
|
||||
|
||||
# 2. Handle top logprobs
|
||||
if top_logprobs_num > 0:
|
||||
if len(state.input_top_logprobs_val) > len(state.input_top_logprobs):
|
||||
state.input_top_logprobs.extend(
|
||||
self.detokenize_top_logprobs_tokens(
|
||||
state.input_top_logprobs_val[len(state.input_top_logprobs) :],
|
||||
state.input_top_logprobs_idx[len(state.input_top_logprobs) :],
|
||||
return_text_in_logprobs,
|
||||
# Guarded by the caller's return_logprob check, so obj is a
|
||||
# GenerateReqInput here.
|
||||
use_flat = state.obj.return_flat_raw_top_logprobs
|
||||
if use_flat:
|
||||
# Flat replaces nested for the input side only.
|
||||
if state.input_top_logprobs_flat_num_rows != len(
|
||||
state.input_top_logprobs_val
|
||||
):
|
||||
try:
|
||||
state.input_top_logprobs_flat_fields = (
|
||||
_build_flat_input_top_logprobs_fields(
|
||||
state.input_top_logprobs_val,
|
||||
state.input_top_logprobs_idx,
|
||||
top_logprobs_num,
|
||||
)
|
||||
)
|
||||
except ValueError as e:
|
||||
# A raise here would disrupt unrelated requests in the
|
||||
# shared batch-output loop; degrade to nested instead.
|
||||
state.input_top_logprobs_flat_fields = None
|
||||
logger.error(
|
||||
"Falling back to nested input top logprobs for "
|
||||
"rid=%s: %s",
|
||||
meta_info.get("id"),
|
||||
e,
|
||||
)
|
||||
state.input_top_logprobs_flat_num_rows = len(
|
||||
state.input_top_logprobs_val
|
||||
)
|
||||
)
|
||||
if state.input_top_logprobs_flat_fields is not None:
|
||||
meta_info.update(state.input_top_logprobs_flat_fields)
|
||||
else:
|
||||
use_flat = False
|
||||
if not use_flat:
|
||||
if len(state.input_top_logprobs_val) > len(state.input_top_logprobs):
|
||||
state.input_top_logprobs.extend(
|
||||
self.detokenize_top_logprobs_tokens(
|
||||
state.input_top_logprobs_val[
|
||||
len(state.input_top_logprobs) :
|
||||
],
|
||||
state.input_top_logprobs_idx[
|
||||
len(state.input_top_logprobs) :
|
||||
],
|
||||
return_text_in_logprobs,
|
||||
)
|
||||
)
|
||||
meta_info["input_top_logprobs"] = state.input_top_logprobs
|
||||
if len(state.output_top_logprobs_val) > len(state.output_top_logprobs):
|
||||
state.output_top_logprobs.extend(
|
||||
self.detokenize_top_logprobs_tokens(
|
||||
@@ -2244,8 +2325,6 @@ class TokenizerManager(TokenizerControlMixin, TokenizerManagerScoreMixin):
|
||||
return_text_in_logprobs,
|
||||
)
|
||||
)
|
||||
|
||||
meta_info["input_top_logprobs"] = state.input_top_logprobs
|
||||
meta_info["output_top_logprobs"] = state.output_top_logprobs
|
||||
|
||||
# 3. Handle token_ids_logprob
|
||||
|
||||
Reference in New Issue
Block a user