Repository navigation
[Bugfix][Pooling] Reject an empty cache_salt like every other endpoint - #56199
Tejasvichand wants to merge 1 commit into
Conversation
`cache_salt` pins min_length=1 on completions, chat completions, responses, the Anthropic messages API and the scale-out protocol. The pooling mixin did not, and generate_block_hash_extra_keys tests the salt for truthiness rather than for None, so an empty salt produced no extra key and shared the unsalted namespace instead of being rejected. A client emitting "" by mistake was therefore silently sharing prefix-cache blocks with every unsalted request. Signed-off-by: Tejasvi Addagada <tejasvichand@gmail.com>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
rishabhsinha17
left a comment
There was a problem hiding this comment.
Looked at 1f11723. The mixin is the right place. Every pooling request family inherits PoolingBasicRequestMixin, EmbeddingBatchChatRequest included, which puts this one line in front of everything from embed and classify through score and rerank. I swept every cache_salt field definition at this head and all six Field definitions now pin min_length=1. One bare definition is left, BatchChatCompletionRequest.cache_salt, but that path rebuilds each conversation through ChatCompletionRequest.model_validate in to_chat_completion_request, and an empty salt fails validation there as well. Once this merges, every user-facing path refuses the empty string. The tests check that the empty string fails validation while omission and a real salt still work, and the parity case against CompletionRequest pins the agreement the change is for. Placement matches the other top level tests in tests/entrypoints/pooling, a directory the entrypoints CI job runs.
One number in the description is off, and it matters for the sibling arm. A server built by vllm serve answers this rejection with a 400. FastAPI only sends its default 422 when nothing intercepts it, and vLLM does, the RequestValidationError handler in vllm/entrypoints/serve/exception_handling/handlers/validation.py maps body validation failures to HTTPStatus.BAD_REQUEST with the OpenAI error envelope. Existing pooling tests expect that already, see the status_code == 400 checks in tests/entrypoints/pooling/embed/test_online.py. Nothing in this diff depends on the number since the new tests assert ValidationError at the model layer, the right layer for them. But the #55886 arm checks the status code itself, its assert reads 422, and a 400 keeps it xfailing even after this merges, with nothing for strict to catch. I put the details on that PR. Merging this first and rebasing #55886 on top lets the strict marker force the flip there.
|
This pull request has merge conflicts that must be resolved before it can be |
|
Closing this: it is already fixed on main, and was before I opened it.
The reproduction in the description was real, but against a stale tree. I built and tested @rishabhsinha17 — the gap you described was an accurate read of this PR's base, so the stale The four validation tests here have no equivalent on main that I can find — #51444 covered |
Purpose
cache_saltpinsmin_length=1on every protocol that accepts it —completions, chat completions, responses, the Anthropic messages API and the
scale-out protocol — except the pooling mixin, which omitted it.
That matters because
generate_block_hash_extra_keystests the salt fortruthiness rather than for
None:An empty string is falsy, so it produces no extra key at all — identical to
sending no salt. Combined with the missing validation,
cache_salt: ""on apooling endpoint is accepted and lands in the unsalted namespace rather than
being rejected.
A tenant whose client emits
""by mistake is silently sharing prefix-cacheblocks with every unsalted request, which is the outcome the parameter exists
to prevent.
Reproduction
Against
vllm serve --runner pooling --convert embed --enable-prefix-caching,warming the unsalted namespace and then probing it:
Change
One line,
min_length=1onPoolingBasicRequestMixin.cache_salt, so thepooling endpoints reject the empty string with a 422 like every other endpoint
already does. Omission stays valid; only
""is refused.Four validation tests, no server or GPU needed. They fail without the one-line
change and pass with it — I checked by reverting it.
Credit
Found by @rishabhsinha17 while reviewing #55886, which is the conformance suite
for
cache_saltpartitioning across entrypoints. Their suggestion was to fixthe footgun rather than pin the current behaviour in a test, which is what this
does. #55886 carries a matching arm as a strict xfail until this lands.