Repository navigation
Conversation
|
Hardware rerun at
Both ran Qwen3.8-Flash-Next IQ3_S with
|
|
@gdevenyi Really interested in this PR and would love to see this functionality make it upstream! I'm running Strata with Qwen3.8-Flash-Next on dual Titan Xp GPUs (Pascal, sm_61), and I'm happy to offer some time for testing, benchmarking, or troubleshooting on older NVIDIA hardware. I also noticed #4059 and would be happy to help investigate whether the logprob consistency issue appears on Pascal. If there are any tests or validation that would be useful, I'm glad to help however I can. Appreciate the work going into this! |
|
Make sure to express your interest in the interrelated PRs and issues. So far the real blocker is reproducibility. Ideally I'd like dynamic response to respond properly to certain calls rather than disabling some performance caching features as suggested in the issue. |
…etions (#3200) /v1/chat/completions on the CUDA and HIP engine accepted logprobs and returned an empty choices[0].logprobs.content. Now "logprobs": true with "top_logprobs" 0 to 20 (and the legacy "logprobs": N) returns one entry per token with its alternatives, streamed or not, and "allowed_token_ids" returns a distribution renormalised over exactly the ids the caller named. Engine (src/program/generate.cpp): the GEN line takes logprobs=K and allowed=a,b,c. After each "T id" the engine writes one "LP <logprob> [id:logprob ...]" line from the verify window's head logits before sampling, penalties and temperature. The SYCL engine already wrote LP lines for logprobs=K; it now reads allowed= as well, with the same line format. A request without logprobs reaches none of this: both call sites are behind "req_logprobs >= 0", a local int of the request, so the default path prints what it printed before and does no extra work per token. Changes from the PR: the call sites are guarded instead of calling the function for every token, the function is static, the token index is checked against the row, and the Chinese comment is in English. Server (serve/server.py): LP lines are read before the token is yielded and belong to the request that asked (tagged with its cancel Event), the entries spell the reply, json_schema replies return no logprobs rather than a mismatching set, an allowed id past the vocabulary is a 400, and logprobs on a --parallel server is answered for max_tokens 1 only (a slot's BT lines carry no LP line). serve/structured.py: _extract_json is now extract_json (the logprobs alignment uses it); the two uses added by the #2893 retry follow the rename. Left out: the PR's bench and result files (none in the diff). #3332 (/v1/completions) and #4056 (logprobs on it) build on this change; this one needs nothing from them. Tests: serve.test_logprobs and serve.test_logprobs_parallel (30 tests with the PR's), serve.test_structured, serve.test_responses: 94 tests OK. Not covered: no model ran here. The PR author reports 28 of 28 on an RTX 5080 with MTP and on a GTX 1080 Ti; the layer-split pipelined decode path (the first call site) was not run by anyone, and the LP values were not compared with a CPU softmax of dumped logits. Co-authored-by: Leonardo Murillo <4869242+leonardomurillo@users.noreply.github.com>
…ken-id prompts /v1/completions now takes `logprobs: N` (or `logprobs: true` with `top_logprobs`), `allowed_token_ids` and `return_tokens_as_token_ids`, and returns vLLM's legacy `logprobs` object: tokens, token_logprobs, top_logprobs and text_offset. Every generated token is listed, including the stop token. `allowed_token_ids` reports the named ids' full-vocabulary values without renormalising them. The prompt can also be a list of token ids. The logprobs checks from /v1/chat/completions (a bounded max_tokens, no batch slots, named ids inside the vocabulary) are moved into check_logprobs() and shared by both routes. Logprobs are refused with stream: true on this route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cix1XJhNhNNuKXi26KvXnU
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cix1XJhNhNNuKXi26KvXnU
…two-request ownership test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cix1XJhNhNNuKXi26KvXnU
e0eb04e to
d032be9
Compare
|
Rebased onto @xiezhipeng-git, following your point: the length bound is gone.
The one deliberate difference is that this route refuses logprobs with Tested on epic (RTX 5080) and monster (GTX 1080 Ti, CUDA 12.9 SM60), engine 0.1.53 built from this branch. Under |
Problem and result
Decision clients such as AnyJev render their own chat prompt and read the label tokens' log-probabilities from
vllm serve's legacy/v1/completions. Strata 0.2.7 has logprobs on/v1/chat/completions(#3200, landed as9ea04496), and #3332 adds/v1/completionsbut refuseslogprobs. So a client can have its own prompt or the logprobs, but not both.This PR puts the 0.2.7 logprobs on #3332's route.
/v1/completionstakeslogprobs: N(ortruewithtop_logprobs),allowed_token_idsandreturn_tokens_as_token_ids, and the prompt can be a list of token ids. It returns vLLM's legacylogprobsobject.Related discussion
StrataBackend, which reads this route withallowed_token_idsandreturn_tokens_as_token_ids) and New: anyjev.serve --backend strata, the gateway's Decider mode (stacks on #20) nokia-applied-research/AnyJev#21 (its HTTP decision gateway,anyjev.serve --backend strata, one gateway per Strata server). The scope discussion on the AnyJev side is Strata backend (anyjev/backends/strata.py) and a Decider mode for the gateway: scope check (#20, #21) nokia-applied-research/AnyJev#22.main(46c152a1, after 0.2.7.1) + [Other] add legacy /v1/completions for raw-prompt clients (#2277) #3332 merged unchanged (clean merge) + three commits,b6132c9a..d032be99(+216/−20 inserve/server.py,serve/test_logprobs.py,docs/DETAILS.md). When [Other] add legacy /v1/completions for raw-prompt clients (#2277) #3332 lands, it is just these commits.maininstead of re-merging [Engine] OpenAI logprobs / top_logprobs / allowed_token_ids for the CUDA/HIP engine #3200's branch.logprobs/top_logprobson/v1/chat/completions(typed decisions from one forward pass) #675. Related: [Engine] typed decisions: score a schema's token paths instead of generating them #3743 (a scored typed-decision endpoint; this PR gives existing vLLM-style decision clients the field they already read). Repeatability: [Engine] identical temperature-0 requests give different logprobs on ~100-token prompts, even with the #410 switches #4059.Approach
check_logprobs(req, svc, max_req)holds 0.2.7's two checks, now shared by both routes:--parallelrefuses logprobs unlessmax_tokensis 1.CompletionLogprobs. It reads only the LP lines tagged with this request's cancelEvent, the ownership rule 0.2.7'sLogprobEntriesuses. That includes the lines a batch admission keeps on theEvent(_strata_lp_lines). It lists every generated token, the stop token too, so a one-token read whose top token is<|im_end|>still has its row.allowed_token_idsreports the named ids' full-vocabulary values. For those ids these are the numbers vLLM's defaultraw_logprobsgives. A decision client checks how much of the mass its labels hold, and renormalised values always sum to 1. Chat keeps 0.2.7's renormalised shape. Both are documented indocs/DETAILS.md.stream: trueare refused (400) to keep the change small.Validation
d032be99, engine 0.1.53 built from this branch, with Qwen3.8-Flash-Next IQ3_S and the argsstrata launchwrites (--expert-cache auto --prefill auto --spec 4 --spec-min-p 0.5 --mtp … --kv int8 --ple-io ram --kv-resident 32768, no--vision). Two machines:-DSTRATA_EXPERIMENTAL_SM60=ON,g++-14Passed:
python -m unittest discover -s serve -p 'test_*.py'(as CI runs it): 1,234 tests, 2 failures.GpuChoice.test_hip_ordinalfails onmainhere too.LiveRate.test_the_live_number_is_a_ratefailed once while the machine was under memory pressure; it passes 3/3 on this branch and onmainwhen re-run.serve.test_logprobs: 41 OK. That is main's tests plus 11 for completions:allowed_token_ids, token-id keys, token-id prompts;logprobs: false, no length cap, the refusals;max_tokensand with ajson_schema;allowed_token_idsraw values equal to the top-K read, token-id prompt,max_tokens: 0,logprobs: false;--adapt-swaps 0andSTRATA_PREFILL_CPU_SHARE=0.5: 12/12 on both. Chat, text-prompt completions and token-id completions give the same values to 6 decimals (epicB−0.027356, monster −0.035613).--parallel 2on epic: completionsmax_tokens: 1withallowed_token_ids→ 200 with both ids.max_tokens: 2→ 400 naming--parallel. Two overlapping one-token reads (fruit/vegetable) each returned their own answer (B,A) through batch admission.StrataBackend(New: StrataBackend (draft, needs Strata logprobs on /v1/completions) nokia-applied-research/AnyJev#20), on epic with--adapt-swaps 0andSTRATA_PREFILL_CPU_SHARE=0.5: engine smoke test 7/7. Its parity check (named-id read vs the server's own top-20) givesmax_abs_diff 0.000000in two runs, and theDeciderat L0 decides 5 states × 3 questions in 14 s.Failed:
test_hip_ordinal.Not run:
--parallelon monster (11 GB).Limits
--adapt-swaps 0 --pcie-frac 0withSTRATA_PREFILL_CPU_SHARE=0.5makes them identical, the first run after a start included. Without--pcie-frac 0that first run still differs. The recipe costs about 38% of decode speed on an RTX 5080 (none for one-token reads); the measurements are on [Engine] identical temperature-0 requests give different logprobs on ~100-token prompts, even with the #410 switches #4059.prompt: [[…], […]]). AnyJev'sTacit(engine="server")client batches, so it still needs its own path; itsDeciderbackend (New: StrataBackend (draft, needs Strata logprobs on /v1/completions) nokia-applied-research/AnyJev#20) sends one prompt per request.Merge danger
Door: two-way. It is server-only Python behind an opt-in request field.
Blast radius: small. Callers of
/v1/completionsthat sendlogprobsused to get a 400 and now get logprobs. Chat logprobs go through the moved, otherwise unchanged checks.🤖 Generated with Claude Code
https://claude.ai/code/session_01Cix1XJhNhNNuKXi26KvXnU