feat(chat-completions): server-side fan-out for n>1 choices - #4841
feat(chat-completions): server-side fan-out for n>1 choices#4841lvhan028 wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the OpenAI-compatible /v1/chat/completions endpoint to support n > 1 by performing server-side fan-out (N independent engine.generate() calls aggregated into one response), and also includes the chat-completions “package migration” refactor (protocol model relocation + top-level re-exports for backward compatibility).
Changes:
- Add server-side fan-out for
n > 1in chat completions, including concurrent collection and streaming interleaving with aggregated usage. - Introduce request validation specific to chat completions, including a cap for
nand non-negativeseedvalidation. - Add unit/integration tests covering
n > 1fan-out behavior plus migration/package invariants.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
lmdeploy/serve/openai/endpoints/chat_completions/serving.py |
New packaged handler implementation, including fan-out collect + stream logic and session cleanup behavior. |
lmdeploy/serve/openai/endpoints/chat_completions/validation.py |
New endpoint-specific request validation (fan-out n cap, seed validation, etc.). |
lmdeploy/serve/openai/endpoints/chat_completions/protocol.py |
New home for chat-specific Pydantic models (moved out of top-level protocol). |
lmdeploy/serve/openai/endpoints/chat_completions/logprobs.py |
New helper module for building chat logprobs structures. |
lmdeploy/serve/openai/endpoints/chat_completions/logits_processors.py |
New helper module for logit-bias processor construction. |
lmdeploy/serve/openai/endpoints/chat_completions/__init__.py |
Lazily exposes register to avoid circular imports during protocol re-export. |
lmdeploy/serve/openai/protocol.py |
Removes inlined chat models and re-exports them from the new chat_completions protocol module. |
lmdeploy/serve/openai/endpoints/chat_completions.py |
Deletes the old flat chat_completions module in favor of the package layout. |
lmdeploy/serve/openai/endpoints/__init__.py |
Lazily exposes create_openai_router to avoid circular imports with protocol re-exports. |
tests/test_lmdeploy/serve/openai/chat_completions/test_n_completions.py |
Adds fan-out aggregation + end-to-end handler tests for n > 1 (streaming and non-streaming). |
tests/test_lmdeploy/serve/openai/chat_completions/conftest.py |
Shared fake engine/session/context and endpoint fixture for chat handler tests. |
tests/test_chat_completions_package_migration.py |
Migration equivalence tests ensuring package structure + re-export invariants. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # check sampling settings | ||
| if request.n <= 0: | ||
| return f'The n {request.n!r} must be a positive int.' | ||
| # n > 1 is implemented as server-side fan-out (N independent engine | ||
| # generate() calls). Cap it to prevent unbounded resource use. | ||
| if request.n > _MAX_FANOUT_N: | ||
| return (f'The n {request.n!r} exceeds the maximum supported ' | ||
| f'choices ({_MAX_FANOUT_N}).') |
| - **n** (int): How many chat completion choices to generate for each input | ||
| message. **Only support one here**. | ||
| - **stream**: whether to stream the results or not. Default to false. |
| the fan-out is therefore engine-agnostic and works for both pytorch and | ||
| turbomind. If any generator raises, the whole request fails (OpenAI-style: | ||
| a single n>1 request is all-or-nothing). ``completion_tokens`` is the sum | ||
| across choices; ``prompt_tokens`` is counted once (taken from | ||
| ``prompt_tokens`` if provided, else from the first choice's | ||
| ``input_token_len`` since all choices share the same prompt). |
b50b631 to
3d7e19f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
lmdeploy/serve/openai/chat_completions/fanout.py:255
- Minor typo in the inline comment:
iterm[2]should beitem[2].
# item[1]: index, iterm[2]: usage payload
usages[item[1]] = item[2]
lmdeploy/serve/openai/chat_completions/validation.py:15
- The PR description mentions adding “non-negative seed validation”, but
check_request()currently does not validaterequest.seedat all (and the added tests exercise negative seeds being accepted and wrapped). Either update the PR description to match the implemented behavior, or add explicit seed validation/normalization in the request contract so single-choice and fan-out behavior are clearly defined.
def check_request(request: ChatCompletionRequest,
server_context,
json_request: dict | None = None) -> str:
engine_config = server_context.engine_config
4bd2cd0 to
ebe717a
Compare
Fans a single n>1 request into N independent engine.generate() calls with distinct random_seeds, collating into N choices. Works for both pytorch and turbomind (engine-agnostic handler-layer approach). n==1 keeps the original single-generator fast path. Co-Authored-By: Claude <noreply@anthropic.com>
…ancellation Fix round 1 (code-review findings): - Non-streaming fan-out now wraps _fanout_nonstream in try/finally calling cleanup_result_generators so N fan-out sessions are removed on every exit path (success, parse-error, disconnect, generator-error). Previously leaked N sessions per non-streaming n>1 request. - Fan-out sub-sessions are auto-generated (create_session(None)) instead of reusing request.session_id N times, which collided in SessionManager.map_user_session_id on the 2nd call for explicit session_ids. - _fanout_generate_collect now runs _consume as explicit Tasks and cancels pending siblings on first exception (asyncio.gather does not cancel siblings by default), then awaits cancellations so engine generators close. - _consume wraps the generator in aclosing() for prompt closure on cancel. - Non-streaming fan-out now propagates with_cache cache_block_ids / remote_token_ids response fields (mirrors n==1 path). Tests: added explicit-session-id, sibling-cancellation, multi-chunk stream, and session-cleanup assertions. All 12 n_completions tests pass; 81 serve tests green. Co-Authored-By: Claude <noreply@anthropic.com>
a92102d to
f525848
Compare
Motivation
The OpenAI-compatible Chat Completions API exposes the n parameter for generating multiple choices, but LMDeploy previously supported only n=1. Users therefore had to send multiple client-side requests and manually
combine their responses.
This PR adds native n > 1 support while reusing the existing single-choice endpoint, preserving its preprocessing, generation, streaming, and output-parsing behavior. The existing n=1 path remains unchanged.
Modification