Skip to content

fix(server): refuse n != 1 on /v1/completions instead of ignoring it - #842

Merged
jason-fxz merged 2 commits into
FlashML-org:mainfrom
A5bhinav:fix/completions-refuse-n
Oct 30, 2026
Merged

jason-fxz merged 2 commits into
FlashML-org:mainfrom
A5bhinav:fix/completions-refuse-n

Conversation

@A5bhinav

Copy link
Copy Markdown
Contributor

Fixes #682

/v1/completions now answers n != 1 with a 400, the same refusal /v1/chat/completions already gives, instead of a 200 with one choice. No change for n = 1.

Summary

CompletionRequest declares n, but handle_completion never read it: n=2 got HTTP 200 and a single choice, so a client asking for several samples (best-of selection, self-consistency, eval harnesses) silently got one and had no way to tell. Chat already refuses the field with 400 "Only n=1 is supported".

  • server/openai_api.py: handle_completion returns create_error_response("Only n=1 is supported", param="n") when n != 1. The check sits after _completion_unsupported_reason and before anything is sent to the engine, ahead of the stream / non-stream split, so both paths refuse it, the same way the route already refuses logprobs, echo, suffix and logit_bias. The body matches chat's:
    {"error": {"message": "Only n=1 is supported", "type": "invalid_request_error", "param": "n", "code": null}}
  • n <= 0, which was accepted before, is refused too.

Why now: a silent wrong answer is worse than a refusal, and this is the smallest change that makes the two routes agree. Serving n > 1 on completions is a separate, larger change in a follow-up PR.

Tests

tests/server/test_openai_api.py::test_completion_refuses_n_other_than_one, parametrized over n in {0, 2} and stream on and off: the response is a 400 with param "n", and nothing is sent to the engine (state.sent is None). With main's handle_completion, all four cases fail.

Test environment

  • GPU: none. I don't have access to an NVIDIA GPU yet, so this has not been run against a live ft serve.
  • CPU: Apple M4 Pro, 24 GB RAM, macOS 26.6.2
  • torch 2.14.1 (CPU), Python 3.12.13
  • This branch at 8fe8c42, A/B against main a2ce636.

Unit tests

PYTHONPATH=python pytest tests/server/test_openai_api.py

This branch: 15 passed, 17 failed, 1 error. Main: 11 passed, 17 failed, 1 error. The 4 extra passes are the new test; with only the test file from this branch, main fails all 4. The 17 failures and the error are all "ModuleNotFoundError: No module named 'flashlib'", which needs CUDA, and are the same on main.

End-to-end A/B against main

Not run yet: it needs a GPU, which I don't have access to yet. Once I do, I will run it and add the results here; if a maintainer or reviewer has one sooner, the repro attached to #682 should show HTTP 400 and PASS on the four completions rows on this branch, and HTTP 200 with one choice (FAIL) on main:

ft serve --model <checkpoint>
python repro_completions_n_ignored.py --base-url http://127.0.0.1:1941

@A5bhinav A5bhinav mentioned this pull request Oct 28, 2026
@jason-fxz
jason-fxz merged commit ee43b2a into FlashML-org:main Oct 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/v1/completions accepts n and silently returns one choice; /v1/chat/completions refuses n != 1 with 400

2 participants