Repository navigation
[Engine] OpenAI logprobs / top_logprobs / allowed_token_ids for the CUDA/HIP engine - #2254
xiezhipeng-git wants to merge 1 commit into
Conversation
|
Some scope notes from the diff: (1) |
40c0f16 to
55d5200
Compare
Thanks for the review. All three points were correct, and the branch is rebuilt around them — it is now 1. The README banner, the LF→CRLF rewrite, and the launcher filesThe
The launcher files ( 2. The C++ could not be reviewed or built as part of the normal tree — fixedThat was true of the old version, and it was the most important thing wrong with it: the engine source was It is fixed in the way your point implies: the C++ now lives in
3.
|
ef9a225 to
0e823c5
Compare
…ngine Adds per-request `logprobs` / `top_logprobs` to `/v1/chat/completions` (streamed or not) on the CUDA/HIP engine, plus `allowed_token_ids` with vLLM's semantics. Upstream already has this on SYCL (835d30b) and Metal (31f2668); CUDA/HIP is the backend still missing it, and this fills it in the same shape: `logprobs=K` and `allowed=a,b,c` on the engine's GEN line, and an `LP <logprob> [id:logprob ...]` line after each `T <id>`, written in the same flush -- the same protocol the SYCL port speaks, so one server-side reader serves both. Engine half (src/program/generate.cpp): - two request keys in the `--serve` key=value line, skipped as unknown by an engine that predates them; - `emit_lp_line()` takes the logprob from the verify window's head logits before sampling, penalties and temperature, a max, a logsumexp and, for `top_logprobs`, a partial_sort over the vocabulary -- one printf per token; - the pipeline path asks `V1(A)` (the verifier of window A, whose tokens that loop prints) and the serial path `ver`, matching each token's own window; - off by default: a request without `logprobs` produces today's output, byte for byte, and the per-token cost is one integer compare. Server half (serve/server.py): - `top_logprobs` / `allowed_ids` translate the request; `sampling_keys` appends `logprobs=K` and `allowed=` only when asked; - `generate` reads each token's `LP` line before yielding the token (want_lp off = the path is unchanged); a held line is consumed before the next read, so an engine that emits no LP lines loses no lines, and a DONE read while looking for one still ends the request; - `LogprobEntries` decodes the token bytes incrementally (a character split across tokens lands in the one that completes it), so the entries, joined, spell the reply; the stop token is not listed; - `allowed_token_ids` keeps only the named ids and renormalises over them, so a caller asking about specific candidates gets a distribution over exactly those; - `structured_chunks` re-aligns the entries when `response_format: json_schema` rewrites the content: if a run of tokens spells what is returned, or spells the model's own JSON when that is the same JSON value as the canonical form, the content is returned as the model wrote it so the tokens still spell it; otherwise there are no logprobs -- never a set that does not match. Tests (serve/test_logprobs.py, no GPU): 17 items in python -m unittest serve.test_logprobs -- the request keys, the LP read (including the drain), the reply shape byte for byte, streaming, thinking's two parts each spelled by its own tokens, allowed_token_ids renormalised to 1, the schema cases, and the span helper. serve's own suites (server, structured, responses, logit_bias, security) pass unchanged: 392 tests. Off by default, additive only: a request that does not ask for logprobs gets the same bytes as before.
0e823c5 to
8049478
Compare
|
Thanks for the pull request. We're resetting the tracker for 0.2.0-beta. There are more than 1,300 open issues and pull requests, too many for us to review properly, so we're closing everything that isn't being worked on right now. Closed here does not mean rejected. To reopen it takes about two minutes:
The rules are in CONTRIBUTING.md. |
|
/reopen |
|
The check passed, but GitHub would not reopen it (Validation Failed: ). If the branch was deleted, restore it or open a new pull request and link this one. |
|
Rebased onto |
Problem and result.
/v1/chat/completionson the CUDA/HIP engine acceptedlogprobs/top_logprobsand returned HTTP 200, butchoices[0].logprobs.contentwas always[]. A client that ranks candidate tokens from the model's own distribution — constrained decoding, candidate scoring, tool-choice re-ranking — silently got nothing: the request succeeds, the content is correct, only the field is missing, so the failure is invisible unless the caller asserts on the field. After this change,logprobs: truewithtop_logprobs0..20 (the legacy completions formlogprobs: Nis also accepted) returns one entry per token with its top alternatives, andallowed_token_idsreturns a distribution renormalised over exactly the ids the caller named.Related discussion. #1916 proposed the same feature for CUDA/HIP and is closed. This implementation follows the protocol the SYCL port already speaks (835d30b) and the Metal port (31f2668), so one server-side reader serves all three backends.
Approach.
src/program/generate.cpp:logprobs=Kandallowed=a,b,care two extra keys on the--serveGEN line (an engine that predates them skips them as unknown), and oneLP <logprob> [id:logprob ...]line is written after eachT <id>in the same flush. The value comes from the verify window's head logits before sampling, penalties and temperature, so it is the model's distribution rather than the sampler's. Off by default: a request withoutlogprobsproduces the previous output byte for byte, and the per-token cost is one integer compare.serve/server.py:sampling_keysappends the two keys only when asked;generatereads each token'sLPline before yielding the token (a held line is consumed before the next read, so an engine without LP lines loses no lines, and aDONEread while looking for one still ends the request);LogprobEntriesdecodes token bytes incrementally so the entries joined spell the reply;allowed_token_idskeeps the named ids and renormalises over them;structured_chunksre-aligns the entries whenresponse_format: json_schemarewrites the content and returns no logprobs rather than a set that does not match.Validation.
Rebased onto
upstream/maine88ee39d(which containsv0.2.0-betab73dbce3); head104becfd. The diff is 3 files, +570/−8, unchanged by the rebase.Passed:
104becfd:cmake --build build-lp --target strata— Release,CMAKE_CUDA_ARCHITECTURES=89, CUDA 13.3 (V13.3.73), MSVC 14.41, Windows 11, RTX 4090.[154/154] Linking CXX executable strata.exe, exit 0.python -m unittest serve.test_logprobs— 17 tests, OK, on104becfd.strata.exebuilt from it, model Swift-1.5 IQ3_XXS,--spec 4 --spec-min-p 0.5 --kv int8 --kv-resident 32768),max_tokens: 1,temperature: 0,chat_template_kwargs.enable_thinking: false:top_logprobs: 20→logprobs.contentlength 1,top_logprobswidth 20; entry tokenAat -0.1028 withB -3.121,C -3.201,D -5.1109.toolsandtool_choice: "auto"→ entries present, width 20, labelsA/D/C/B.stream: true→ the delta carrieslogprobs.content(1 entry, tokenA, -0.0801, top width 20).80494788); the endpoint behaviour it depends on is the same code path exercised above.Failed: none observed.
Not run:
src/program/generate.cppis shared by the CUDA and HIP builds; only the CUDA configuration was built here (no AMD GPU or toolchain on this machine).install_logprobsinsycl/serve/server_intel.py) and this change does not touch it; no Intel GPU or toolchain here.--batchmode: see Limits.Limits.
--batchdoes not return logprobs.generate_batchednever readsLPlines; only the solo/serial path (generate) does, so a server started with--batchanswerslogprobs.content: []. Giving the batched path the same read-before-yield step is the remaining work if that combination is wanted.response_format: json_schemathe returned content may not be spelled by the tokens; in that case no logprobs are returned, by design.