Unify over-length errors into the PayloadTooLargeError 413 format
Over-long inputs produced two different client errors depending on
which bound rejected them: the TokenizerManager pre-check (raw
context_len) returned 413 PayloadTooLargeError ('The input (N tokens)
is longer than the model's context length (M tokens).'), while inputs
between that and the scheduler's stricter effective limit hit
validate_input_length and returned 400 BAD_REQUEST with different
wording (and a confusing 'X exceeds X' message since the check is >=).
Unify on the 413 format end to end:
- validate_input_length wording now matches the TokenizerManager
message, reporting the effective per-request limit.
- set_finish_with_abort takes status_code/err_type; the scheduler
length-rejection sites abort with REQUEST_ENTITY_TOO_LARGE +
PayloadTooLargeError. The batch handler previously queued the
over-long request WITHOUT marking it aborted (it proceeded to
prefill) — also fixed.
- Non-streaming aborts with 413 raise PayloadTooLargeError (now a
ValueError subclass so raw /generate-style endpoints that only
catch ValueError still respond; the OpenAI layer's except clause
is reordered to win and emit the 413 format).
- Streaming abort responses prefer the scheduler-provided err_type
over the HTTPStatus name.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -130,18 +130,19 @@ class OpenAIServingBase(ABC):
|
||||
return self.create_error_response(
|
||||
message=e.detail, err_type=str(e.status_code), status_code=e.status_code
|
||||
)
|
||||
except PayloadTooLargeError as e:
|
||||
# Must precede ValueError: PayloadTooLargeError subclasses it.
|
||||
return self.create_error_response(
|
||||
message=str(e),
|
||||
err_type="PayloadTooLargeError",
|
||||
status_code=413,
|
||||
)
|
||||
except ValueError as e:
|
||||
return self.create_error_response(
|
||||
message=str(e),
|
||||
err_type="BadRequest",
|
||||
status_code=400,
|
||||
)
|
||||
except PayloadTooLargeError as e:
|
||||
return self.create_error_response(
|
||||
message=str(e),
|
||||
err_type="PayloadTooLargeError",
|
||||
status_code=413,
|
||||
)
|
||||
except DS32EncodingError as e:
|
||||
logger.info(f"DS32EncodingError: {e}")
|
||||
return self.create_error_response(
|
||||
|
||||
@@ -970,6 +970,9 @@ class OpenAIServingChat(OpenAIServingBase):
|
||||
err_type, status_code = (
|
||||
self._streaming_http_error_type_and_status(code)
|
||||
)
|
||||
# Prefer the scheduler-provided error type (e.g.
|
||||
# PayloadTooLargeError) over the HTTPStatus name.
|
||||
err_type = finish_reason.get("err_type") or err_type
|
||||
error = self.create_streaming_error_response(
|
||||
finish_reason.get("message", "Generation aborted."),
|
||||
err_type,
|
||||
|
||||
@@ -281,6 +281,9 @@ class OpenAIServingCompletion(OpenAIServingBase):
|
||||
err_type, status_code = self._streaming_http_error_type_and_status(
|
||||
code
|
||||
)
|
||||
# Prefer the scheduler-provided error type (e.g.
|
||||
# PayloadTooLargeError) over the HTTPStatus name.
|
||||
err_type = finish_reason.get("err_type") or err_type
|
||||
error = self.create_streaming_error_response(
|
||||
finish_reason.get("message", "Generation aborted."),
|
||||
err_type,
|
||||
|
||||
@@ -1203,7 +1203,12 @@ class Req(ReqDllmMixin):
|
||||
self.extend_input_len,
|
||||
)
|
||||
|
||||
def set_finish_with_abort(self, error_msg: str):
|
||||
def set_finish_with_abort(
|
||||
self,
|
||||
error_msg: str,
|
||||
status_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
|
||||
err_type: str = "BadRequestError",
|
||||
):
|
||||
if get_tensor_model_parallel_rank() == 0:
|
||||
logger.error(f"{error_msg}, {self.rid=}")
|
||||
self.multimodal_inputs = None
|
||||
@@ -1211,9 +1216,7 @@ class Req(ReqDllmMixin):
|
||||
self.origin_input_ids = [0] # set it to one token to skip the long prefill
|
||||
self.return_logprob = False
|
||||
self.logprob_start_len = -1
|
||||
self.to_finish = FINISH_ABORT(
|
||||
error_msg, HTTPStatus.BAD_REQUEST, "BadRequestError"
|
||||
)
|
||||
self.to_finish = FINISH_ABORT(error_msg, status_code, err_type)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
|
||||
@@ -1976,7 +1976,11 @@ class Scheduler(
|
||||
self.server_args.allow_auto_truncate,
|
||||
)
|
||||
if error_msg:
|
||||
req.set_finish_with_abort(error_msg)
|
||||
req.set_finish_with_abort(
|
||||
error_msg,
|
||||
status_code=HTTPStatus.REQUEST_ENTITY_TOO_LARGE,
|
||||
err_type="PayloadTooLargeError",
|
||||
)
|
||||
self._add_request_to_queue(req)
|
||||
return
|
||||
|
||||
@@ -2225,6 +2229,13 @@ class Scheduler(
|
||||
self.server_args.allow_auto_truncate,
|
||||
)
|
||||
if error_msg:
|
||||
# NOTE: this path previously queued the over-long request without
|
||||
# marking it aborted, letting it proceed to prefill.
|
||||
req.set_finish_with_abort(
|
||||
error_msg,
|
||||
status_code=HTTPStatus.REQUEST_ENTITY_TOO_LARGE,
|
||||
err_type="PayloadTooLargeError",
|
||||
)
|
||||
self._add_request_to_queue(req)
|
||||
return
|
||||
|
||||
|
||||
@@ -122,8 +122,13 @@ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
_REQUEST_STATE_WAIT_TIMEOUT = envs.SGLANG_REQUEST_STATE_WAIT_TIMEOUT.get()
|
||||
|
||||
|
||||
class PayloadTooLargeError(Exception):
|
||||
"""Exception raised when a request payload exceeds the model context length."""
|
||||
class PayloadTooLargeError(ValueError):
|
||||
"""Exception raised when a request payload exceeds the model context length.
|
||||
|
||||
Subclasses ValueError so callers that only handle ValueError (e.g. the raw
|
||||
/generate endpoint) still return an error response; the OpenAI serving
|
||||
layer catches it first to produce the 413 PayloadTooLargeError format.
|
||||
"""
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1207,12 +1212,20 @@ class TokenizerManager(TokenizerCommunicatorMixin, TokenizerManagerMultiItemMixi
|
||||
# Check if this was an abort/error created by scheduler
|
||||
if isinstance(out["meta_info"].get("finish_reason"), dict):
|
||||
finish_reason = out["meta_info"]["finish_reason"]
|
||||
if (
|
||||
finish_reason.get("type") == "abort"
|
||||
and finish_reason.get("status_code")
|
||||
== HTTPStatus.BAD_REQUEST
|
||||
if finish_reason.get("type") == "abort" and finish_reason.get(
|
||||
"status_code"
|
||||
) in (
|
||||
HTTPStatus.BAD_REQUEST,
|
||||
HTTPStatus.REQUEST_ENTITY_TOO_LARGE,
|
||||
):
|
||||
if not is_stream:
|
||||
if (
|
||||
finish_reason.get("status_code")
|
||||
== HTTPStatus.REQUEST_ENTITY_TOO_LARGE
|
||||
):
|
||||
raise PayloadTooLargeError(
|
||||
finish_reason["message"]
|
||||
)
|
||||
raise ValueError(finish_reason["message"])
|
||||
else:
|
||||
yield out
|
||||
|
||||
@@ -133,10 +133,13 @@ def validate_input_length(
|
||||
req.origin_input_ids = req.origin_input_ids[:max_req_input_len]
|
||||
return None
|
||||
else:
|
||||
# Keep the wording identical to the TokenizerManager-side
|
||||
# PayloadTooLargeError so clients see one over-length format
|
||||
# regardless of which bound (raw context length there, effective
|
||||
# per-request limit here) rejected the request.
|
||||
error_msg = (
|
||||
f"Input length ({len(req.origin_input_ids)} tokens) exceeds "
|
||||
f"the maximum allowed length ({max_req_input_len} tokens). "
|
||||
f"Use a shorter input or enable --allow-auto-truncate."
|
||||
f"The input ({len(req.origin_input_ids)} tokens) is longer "
|
||||
f"than the model's context length ({max_req_input_len} tokens)."
|
||||
)
|
||||
return error_msg
|
||||
|
||||
|
||||
Reference in New Issue
Block a user