Fix tokenizer state cleanup on dispatch failure (#28694)

Co-authored-by: Yinghai Lu <yinghai@meta.com>
This commit is contained in:
Lianmin Zheng
2026-06-19 21:55:39 -07:00
committed by GitHub
co-authored by Yinghai Lu
parent 28e2096d1c
commit 45d203fb08
3 changed files with 179 additions and 21 deletions
@@ -531,6 +531,10 @@ def run_one_case(
gsp_system_prompt_len=gsp_system_prompt_len,
gsp_question_len=gsp_question_len,
gsp_output_len=gsp_output_len,
# The generated-shared-prefix dataset's from_args requires these; the
# batch-bench path only ever uses the uniform group distribution.
gsp_group_distribution="uniform",
gsp_zipf_alpha=None,
)
tok_inner = getattr(tokenizer, "tokenizer", tokenizer)
dataset_model_id = model_name or getattr(tok_inner, "name_or_path", None)
+45 -20
View File
@@ -597,30 +597,41 @@ class TokenizerManager(TokenizerControlMixin, TokenizerManagerScoreMixin):
if self.server_args.tokenizer_worker_num > 1:
self._attach_multi_http_worker_info(obj)
self._init_req_state(obj, request)
if self.server_args.language_only:
self._handle_epd_disaggregation_encode_request(obj)
try:
if self.server_args.language_only:
self._handle_epd_disaggregation_encode_request(obj)
# Log the request
self.request_logger.log_received_request(obj, self.tokenizer, request)
# Log the request
self.request_logger.log_received_request(obj, self.tokenizer, request)
async with self.is_pause_cond:
await self.is_pause_cond.wait_for(lambda: not self.is_pause)
async with self.is_pause_cond:
await self.is_pause_cond.wait_for(lambda: not self.is_pause)
async with self.model_update_lock.reader_lock:
await self._validate_and_resolve_lora(obj)
async with self.model_update_lock.reader_lock:
await self._validate_and_resolve_lora(obj)
# Tokenize the request and send it to the scheduler
if obj.is_single:
tokenized_obj = await self._tokenize_one_request(obj)
state = self.rid_to_state[obj.rid]
if obj.return_prompt_token_ids:
state.prompt_token_ids = list(tokenized_obj.input_ids)
self._send_one_request(tokenized_obj)
async for response in self._wait_one_response(obj, request):
yield response
else:
async for response in self._handle_batch_request(obj, request):
yield response
# Tokenize the request and send it to the scheduler
if obj.is_single:
tokenized_obj = await self._tokenize_one_request(obj)
state = self.rid_to_state[obj.rid]
if obj.return_prompt_token_ids:
state.prompt_token_ids = list(tokenized_obj.input_ids)
self._send_one_request(tokenized_obj)
async for response in self._wait_one_response(obj, request):
yield response
else:
async for response in self._handle_batch_request(obj, request):
yield response
except Exception:
# _init_req_state created a rid_to_state entry per (sub-)request up
# front. The normal remover is the scheduler-response path
# (_handle_batch_output), so a failure *before* a request reaches the
# scheduler -- e.g. input-length validation rejecting an over-context
# request -- would otherwise leak those entries forever. Drop any that
# are still pending; entries already removed on the normal completion
# path are left untouched (pop is a no-op).
self._discard_pending_req_states(obj)
raise
def _detect_input_format(
self, texts: Union[str, List[str]], is_cross_encoder: bool
@@ -2838,6 +2849,20 @@ class TokenizerManager(TokenizerControlMixin, TokenizerManagerScoreMixin):
time_stats.init_trace_ctx(rid, bootstrap_room, external_trace_header)
time_stats.set_created_time(created_time)
def _discard_pending_req_states(self, obj):
"""Drop rid_to_state entries created by _init_req_state for *obj*.
Safe to call after a partial/failed dispatch: only entries still present
are removed, and the scheduler-response path looks up state with
``.get(...)`` so a later output for a discarded rid is ignored, not fatal.
"""
if not hasattr(obj, "is_single") or obj.is_single:
rids = [obj.rid]
else:
rids = obj.rid
for rid in rids:
self.rid_to_state.pop(rid, None)
def _should_dispatch_to_encoder(
self, obj: Union[GenerateReqInput, EmbeddingReqInput]
) -> bool: