feat(sample): port logprob_token_ids generative scoring (#264) - #267
Merged
Conversation
Commit the spec BEFORE any implementation, as the protocol requires. Row `SAMPLE-LOGPROB-TOKEN-IDS` is `INVENTORIED` on main, not `PARTIAL` as issue #264 states: the `logprobs_mode` half it credits to "#238/#258" has NOT landed — #238 is an issue and PR #258 is still open. Recorded in the spec so the next reader does not re-derive it. Scope, upstream anchors (read at pin `555967922`, not from memory), the port map, the req_id-vs-req_index keying decision, the bounds decision that keeps issue #249's defect class out of the new gather, the written- not-ported test inventory, and the named residuals (OpenAI request field, `logprobs_mode`, engine-time vocab-range validation). Also registers #264 in the roadmap intake table and adds the `CLAIM-SAMPLE-LOGPROB-TOKEN-IDS` prose claim (the claims TABLE keys `SPIKE`/`ACTIVE` rows; this row lands `PARTIAL`). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
A request may now name an EXPLICIT set of vocab ids and get back exactly
those plus the sampled token — vLLM's generative-scoring path, and the
efficient answer to scoring a handful of labels without `logprobs=-1`
and a full-vocab sort.
Ported 1:1 from the pin `555967922`:
- `sampling_params.py:31,278-283` — the field + MAX_LOGPROB_TOKEN_IDS.
- `sampling_params.py:724-729` — the `num_logprobs` property.
- `sampling_params.py:773-782,795-801` — the two validations that need
no model config. The vocab-range check (:783-793) stays engine-time,
exactly like `allowed_token_ids`' already does.
- `gpu_input_batch.py:273,443-444,574,934-951` — req_ID-keyed in the
InputBatch (so condense/swap need do nothing), re-keyed to req_INDEX
in make_sampling_metadata over the live batch only.
- `sampler.py:151-225` — gather_specific_token_logprobs: the padded
`[n, max+1]` row, sampled token in column 0 and valid for every row
including rows absent from the map, `-inf` padding, and the sampled
token's rank over the FULL vocab rather than the requested subset.
- `sampler.py:86` — the raw snapshot now fires when ONLY
logprob_token_ids is set. The stale comment saying otherwise is gone,
and so is the same omission in our device-resident greedy fast path,
which is ours and not upstream's and would have skipped the gather.
- `sampler.py:133-136` — explicit ids WIN over a logprobs count.
Three consumers spelled upstream's `num_logprobs` PROPERTY as the raw
`logprobs` field and are corrected: the scheduler's slice gate
(scheduler.py:1818), `LogprobsProcessor::FromNewRequest`, and
`RequestState::FromNewRequest`. Identical for every request that does not
set the new field; without them a scoring request produced sampler output
that nothing downstream ever read.
Every requested id is bounds-checked into `[0, vocab)` and every map key
into `[0, n)` before it indexes anything — which is what torch.gather
raises on, so it is a mirror, not an addition. Issue #249's instance
(GatherLogprobs' unbounded `k`) is deliberately NOT touched.
RED first, all four binaries:
test_sampler 4 cases, `out.logprobs_tensors` had no value
test_input_batch 2 cases, `batch.logprob_token_ids.count("b") == 0`
test_sampling_params 2 cases, num_logprobs() returned the raw field
test_llm_engine 1 case, `outputs[0].logprobs` had no value
GREEN: 16/16 (86), 29/29 (205), 11/11 (102), 14/14 (271) on a CLEAN
Release CPU build, zero warnings under -Werror.
Row `SAMPLE-LOGPROB-TOKEN-IDS` `INVENTORIED` -> `PARTIAL`: its
`logprobs_mode` half is unported (open PR #258) and the OpenAI request
field is a named residual. STATUS/BENCHMARKS/NOW updated for the move;
the STATUS ratchet is re-pinned DOWN 243571 -> 243479, paid for inside
the same Sampling cell.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
localai-bot
pushed a commit
that referenced
this pull request
Aug 11, 2026
docs/BENCHMARKS.md has a HARD 45,000-char cap and sits at 44,689 on `origin/main`, so its remaining headroom is 311 bytes for ALL concurrent work. The merge that re-applied this row spent 258 of them on narrative the row does not need: the two CPU test counts are the engine-matrix row's job and the per-feature list is docs/FEATURES.md's, while the page's own schema asks only for the key, the verdict, and what would make a number owed. Collapsed to 118 bytes. Every binding claim is kept -- no number is owed, the result is correctness-only, and a grid becomes owed at the W7 model gate. This also leaves PR #267 room for its own no-number-owed row (145 bytes), so both open PRs fit at 44,952 whichever lands first; without this one of them would have arrived over the cap. Re-gated after the edit: test_lora_layers 16/16 (4,498 assertions), test_punica_cpu 8/8 (149 assertions), check-public-doc-tables OK, scripts/agent-preflight.sh --staged all gates green. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
MERGED, never rebased: both heads are published, and `main` is never force-pushed, so a rebase is not available. This keeps 657a63a an ancestor and the push a plain fast-forward. The branch was 98 commits behind; `pr-size` and `agent-record` were failing only on "base must be an ancestor of head". PRODUCT CONFLICTS, resolved by keeping BOTH features. `logprobs_mode` (#238) and `prompt_logprobs` (#223) landed on main after this branch was written, and both touched the same code: - src/vllm/v1/sample/sampler.cpp, step 1. #238 added `processed_mode` and moved the processed snapshot inside `sample()`; this branch made the snapshot fire when ONLY `logprob_token_ids` is set (sampler.py:86 is an `or`). Both kept: `want_logprobs = num_logprobs.has_value() || want_token_ids` AND `processed_mode`. They are ORTHOGONAL -- the mode selects WHICH tensor the snapshot holds, the ids select WHICH entries step 8 reads out of it. Step 8 auto-merged correctly: this branch's precedence structure (explicit ids WIN, sampler.py:133-136) wrapping main's `-1` block verbatim, issue-#231 note and all. - include/vllm/v1/worker/gpu/input_batch.h and .cpp. #223's `num_prompt_logprobs` and this branch's `logprob_token_ids` are independent req_id-keyed maps that landed at the same three sites (add_request, remove_request, the condense() comment). Both kept at all three. - tests/vllm/v1/sample/test_sampler.cpp. Both PRs appended a suite after the same last case. Rebuilt as main's file + this branch's block appended, so main's 4 `logprobs_mode` cases and this branch's 5 `logprob_token_ids` cases both survive. ONE TEST ADDED at the merge, because the interaction it covers did not exist on either PR's base and neither could have written it: "logprob_token_ids reads the PROCESSED snapshot under a processed mode". RED-first proven by mutation -- weakening the processed-snapshot request to `(num_logprobs.has_value() && processed_mode)` leaves 20 of 21 cases green and fails ONLY this one (SIGSEGV: the gather reads an empty buffer). Tree restored byte-for-byte afterwards, md5 26dec0a8c79555864fcbe463512feff9. Keyed records -- main's version taken WHOLESALE, then this branch's ONE scoped edit reapplied with a count==1 anchor assertion. No automatic three-way merge of a keyed record was accepted: - `.agents/engine-matrix.md`: the `SAMPLE-LOGPROB-TOKEN-IDS` row was HAND-MERGED. Main's cell records `logprobs_mode` LANDED with `logprob_token_ids` as its residual; this branch's records the inverse. Both landed, so both bodies are kept, each strikes the other's residual, and the surviving residuals are the OpenAI request field, the vocab-range validation, and the outside-the-library mode selection. Spec and Owner columns carry BOTH claims. **The lifecycle rollup is deliberately NOT touched:** main already carries this row at `PARTIAL` (#238 moved it INVENTORIED -> PARTIAL), so the branch's counter edit is already paid for and re-applying it would double-count. RECOMPUTED against main's actual rows, not carried; check-agent-record green at ENGINE=147. - `.agents/coordination.md`: main's file, plus the one prose claim inserted immediately before the claims TABLE, where its sibling `logprobs_mode` claim sits. It is NOT a table row -- the table keys SPIKE/ACTIVE and this row is PARTIAL. The table itself is byte-for-byte main's. - `.agents/roadmap_v1.md`: main's issue table plus the one #264 row, and the C7 portfolio row's stale gap list corrected (it still listed both halves as gaps). - `.agents/NOW.md`: main already carried a `logprobs_mode` (#238) row whose next step WAS this work, so the scoped edit is that row updated in place rather than a second row added. 5,960 -> 5,979 chars, 91 lines, inside the 6,000 budget. - `.agents/porting-inventory.md`: `logprob_token_ids` leaves the deferred-stub list. The same sentence still listed the `logprobs_mode` variants as stubs after #238 landed; since this edit rewrites the sentence, that staleness is corrected here rather than left knowingly false, and the correction is called out in the text. - `docs/BENCHMARKS.md`: one no-number-owed row. Written TERSE (145 bytes, not the branch's 506) because the page sits at 44,689 of its hard 45,000-char cap and PR #282 is also adding a row; see the note below. - `.agents/benchmark-record.md`: untouched by this branch; main's append-only log taken as-is. STATUS ratchet RE-MEASURED against the MERGED page, never carried: 243278. `test_the_rebased_character_ratchet_is_byte_tight` requires cap == len(page) exactly, and the ceiling test requires strictly-down, so the added line had to be paid for in full. The branch's original payment was NOT available any more: it collapsed the beam-search wiring narrative, and #223 had already spent exactly that collapse -- its guard now PINS the collapsed form. So four different restatements were collapsed instead, each a definition of what an OpenAI field DOES rather than a statement of what we support: what a custom logits processor is, what `n>1` does, what `best_of` does, and how the async beam driver is built. All three substrings #223 and #238 pin are intact, and both their guards still pass. Net -9 against main's 243287. The ratchet CEILING moves down in the same change, 243578 -> 243479, and the branch's extra live-page guard (`test_one_char_of_growth_on_the_LIVE_page_is_rejected`) is carried over. `scripts/__pycache__` was cleared before every checker run. NOTE FOR WHOEVER LANDS SECOND: docs/BENCHMARKS.md has a HARD 45,000-char cap and is at 44,689 on main. This PR's row is 145 bytes and PR #282's is 118, so both fit (44,952) -- but only because both were deliberately written terse. Any third BENCHMARKS row lands over the cap and owes a real collapse. The STATUS ratchet will also need one re-measure at the second merge: this PR pins 243278 and #282 pins 243227, and neither number survives the other landing. Gate re-run on the merged tree (CPU, foreground, unbounded): - cmake --build build-cpu -j 18: clean, 843/843, no -Werror diagnostic - test_sampler: 21 cases | 21 passed | 0 failed | 0 skipped; 114 assertions - test_input_batch: 29 cases | 29 passed | 0 failed | 0 skipped; 205 assertions - test_llm_engine: 24 cases | 24 passed | 0 failed | 0 skipped; 493 assertions - test_sampling_params: 11 cases | 11 passed | 0 failed | 0 skipped; 102 assertions - tests/scripts/test_check_public_doc_tables: 57/57 OK - check-agent-record, check-public-doc-tables, check-now-current, check-fusion-consistency: all OK - scripts/agent-preflight.sh --staged: All gates green (rc=0) FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
localai-bot
pushed a commit
that referenced
this pull request
Aug 11, 2026
…cing (#282) Merges `row/LORA-RUNTIME-W2` `8ff82999` for issue #278. Spec `.agents/specs/lora-adapter.md`. `LORA-RUNTIME` moves `ANCHOR-BACKFILL` -> `ACTIVE`. WHAT IT DOES. W1 landed the punica CPU brick; W2 adds `PackedLoRALayerWeights` (`Pack` optimize-then-scaling-1, per-slice `Optimize`), the multi-slice punica `AddShrink`/`AddExpand` (`output_slices` + `offset_start` window walk), multi-slice `AddLoraLinear`, `AddLoraEmbedding` (expand only -- the embedding shrink is a table gather) and `AddLoraLogits` (sampler-indexed, clamped to the narrower of the adapter vocab and the logits width), plus the wrapped layer family in NEW `include/vllm/lora/layers.h` + `src/vllm/lora/layers.cpp`: replicated, column, row, merged-column/gate_up, qkv, merged-qkv, variable-slice, `VocabParallelEmbeddingWithLoRA` and `LogitsProcessorWithLoRA`, with the TP `SliceLoraA`/`SliceLoraB` rules including the fully-sharded (S-LoRA) rank-dim overrides. Ported against pin `555967922`: `lora_weights.py:126-270`, `punica_cpu.py:166-236,265-312`, `lora_ops.py:42`, `base_linear.py:100-238`, `column_parallel_linear.py:85-746`, `row_parallel_linear.py:22-177`. `tests/vllm/lora/test_lora_layers.cpp` ports `tests/lora/test_layers.py`. DEFERRED WITH A REFUSAL, NOT AN APPROXIMATION: the fully-sharded APPLY (`_mcp_apply`) needs the rank-dim all-gather/all-reduce our TP seam does not expose, so a sharded class at `tp_size > 1` throws `std::logic_error` from `ApplyLoraToOutput` instead of returning a partial delta. `pack_moe` (W7), `LoRAConfig` (W5) and `convert_mapping` (W3) stay out of scope. Nothing is wired to an engine path yet and `docs/USAGE.md` says so: no CLI flag, server flag, config key or C-ABI field for LoRA exists, so no model can be served with an adapter today. WHAT THE REVIEW FOUND, AND THE REPAIR. A fresh scoped review of the first head returned FAIL -- 2 blocking + 3 major findings, and 6 of its 9 mutations SURVIVED the suite. Both blocking findings were ASan-visible memory-safety bugs: the fully-sharded apply returned a PARTIAL delta at `tp_size > 1` (the shrink fills `max_rank/tp_size` of a `max_rank` buffer), and `BgmvExpandSlice` ran off the end of every row whenever the caller's buffer was narrower than the adapter's output -- an lm_head whose gathered logits are narrower than the adapter vocab. A fresh implementer repaired both in-branch, each with a RED-first mutation, and RE-RAN all six surviving mutations against the repaired head: M1 (`AddShrink` `slot < 0` early-exit), M2 (`SetLora` A/B slicer swap), M3 (`AddExpand` per-slice offset), M7 (`CopyIntoSlot` stride), M8 (`AddShrink` A-stride) and M9 (embedding base-token clamp) are now ALL caught. M1 and M7 first tripped only `-Werror=unused-parameter`, so both were re-run with the newly-unused parameter voided; the recorded results are behavioural, not compile artifacts. The tree was restored byte-for-byte after each. KEYED RECORDS -- main's version taken WHOLESALE, the branch's scoped edit reapplied by hand, every deleted anchor asserted unique in main's copy, and every non-reconciled path proven byte-identical to the branch's own edit set. docs/STATUS.md auto-merge accepted only after proving the edit set matches the branch's exactly. Merged page RE-MEASURED: 243,128 = main's 243,188 less the LoRA row's net -60. STATUS ratchet `scripts/check-public-doc-tables.py` CONFLICT. Kept BOTH rationale histories (main's and the branch's, append-only) and pinned the RE-MEASURED 243128. The branch's 243227 and main's 243188 were both DISCARDED: neither was measured against this page -- the branch measured against `5812b8b6`, before main re-pinned. Byte-tight, strictly DOWN. ratchet CEILING `tests/scripts/test_check_public_doc_tables.py` CONFLICT. Lowered to 243128 in the SAME change as the ratchet, never after it; the branch's stale 243482 was discarded for the same reason. ALL mutation guards from both sides kept -- main's `test_the_ratchet_is_exactly_one_byte_wide` and `test_a_repin_can_only_tighten_the_char_ratchet` alongside the byte-tight, no-hidden-headroom, and the two paid-for-by-a-real-collapse guards. 58/58. docs/BENCHMARKS.md the branch's LoRA W2 row took the page to 45,116, over the hard 45,000 cap. PAID BY MOVING, not by raising the cap and not by deleting evidence: the key `Vulkan vs llama.cpp Vulkan (BENCH-VK-LLAMA)` carried TWO rows in a table whose contract is one row per subject updated in place. `468a3876` (2026-08-08) wrote the 0.6B row; `93852c28` (2026-08-09) added a SECOND row for the same key rather than updating it. The superseded 0.6B row (406 chars) moved VERBATIM into `.agents/benchmark-record.md` under its own compaction heading, with the surviving 27B row quoted beside it; byte-for-byte identity asserted in the archive BEFORE removal from the page. The key keeps its handle on the scoreboard. 45,116 -> 44,709. This headroom also pays for PR #267's row next. .agents/engine-matrix.md CONFLICT. Main's `Serving, API, CLI, library` row (10|2|0|1|7|2|1|4) kept wholesale, the branch's `LoRA and adapters` row (0|0|0|0|1|0|0|1) reapplied, and the **Total** RECOMPUTED from the ten actual area rows rather than carried from either side: 147|35|16|4|8|27|8|9|39. Confirmed by `check-agent-record.py` (ENGINE=147). .agents/NOW.md CONFLICT twice. Main dropped `Invocation-parity` for `Muse Glimmer (#333)` and added `Containers #170`; both kept, with the branch's compaction of the MiniMax-H3 and Release cells reapplied on top. The branch's net +16 took the page to 6,011 over the hard 6,000 budget, because main had spent the headroom the branch measured against. Paid inside the page: the `SERVE-METRICS` cell said `/metrics` was dead twice ("was DEAD on the shipped server" and "AsyncLLM folded nothing"); the restatement collapsed. 5,986 / 94 lines. .agents/coordination.md CONFLICT. Claims TABLE -- the table `check-agent-record` cross-references. Main's `CLAIM-MUSE-GLIMMER-SPEC` row and the branch's `CLAIM-LORA-RUNTIME-W2` row are distinct keys, unioned with main's first. Not an automatic three-way merge. .agents/roadmap_v1.md, .agents/specs/lora-adapter.md, docs/USAGE.md, scripts/check-gate-commands.py (+LORA-RUNTIME in `RUNNABLE_BASELINE`, beside main's +ENG-RELEASE-CONTAINERS), CMakeLists.txt, tests/CMakeLists.txt -- each redone by hand and proven equal to the branch's own scoped edit set. GATE, re-run by the operator on the merged tree, CPU Release, foreground: cmake --build build-cpu -j 18 396 targets, 0 errors ./build-cpu/tests/test_lora_layers 16/16 cases, 4498 assertions ./build-cpu/tests/test_punica_cpu 8/8 cases, 149 assertions scripts/check-public-doc-tables.py OK tests/scripts/test_check_public_doc_tables.py 58/58 scripts/check-agent-record.py OK, ENGINE=147 MODEL=362 QUANT=82 KERNEL=51 BACKEND=80 scripts/check-gate-commands.py 116 gated, 31 runnable tests/scripts/test_check_gate_commands.py 26/26 scripts/check-now-current.py OK FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
localai-bot
pushed a commit
that referenced
this pull request
Aug 11, 2026
… re-gated `origin/main` advanced 11 commits WHILE PRs #324, #282 and #267 were being gated -- #249 (a huge `logprobs` crashed the server), #168 Tekken and #347 GPT-4o pre-tokenizers, #359 (Muse Glimmer Q/K RoPE row order), and their two merge commits. A plain `git push` would have been refused as non-fast-forward, which is git protecting those merges, so this fetches, re-merges, re-measures every budgeted record against the page that actually results, and re-gates before the push. No force, no rebase of a published branch. PRODUCT MERGE. `src/vllm/v1/sample/sampler.cpp` is the one file where main and this landing both changed live code, and it auto-merged into the correct result -- verified by inspection, not accepted on git's word. #249's defense-in-depth clamp survives VERBATIM at `sampler.cpp:107` (`const int k = static_cast<int>(std::min<int64_t>(num_logprobs, vocab))`, with its comment), and #267's `want_token_ids` predicate, combined `want_logprobs` and `GatherSpecificTokenLogprobs` call all survive. The only lines DROPPED are the two #267 intended to drop: the stale "logprob_token_ids (generative-scoring) is a deferred stub" comment and the narrower `want_logprobs = num_logprobs.has_value()`. The two changes are orthogonal -- #249 bounds the top-k path's `k`, #267 adds the explicit-id path -- and #267 had already said in its own commit that #249's instance was deliberately not touched. KEYED RECORDS. Main's version taken wholesale and the landing's scoped edits reapplied; 28 of the 30 paths main touched were proven byte-identical to main's own edit set, and the two exceptions are the ratchet pair below. .agents/NOW.md CONFLICT. Main's newer `Muse Glimmer (#333)` row ("#347+#359 FIXED: GGUF COHERENT") replaces the older one wholesale; the landing's compaction of the MiniMax-H3 cell is reapplied on top, because that compaction is part of what paid for PR #282's row. 5,972 of the 6,000-char budget, 94 of 100 lines. STATUS ratchet CONFLICT. FOUR rationale histories now sit in `scripts/check-public-doc-tables.py` -- main's own concurrent Tekken/GPT-4o pair, #282's and #267's -- and ALL FOUR are kept, append-only. None of their numbers survives, because no two were measured against the same page: main re-pinned to 243186 after both branches had measured against `5812b8b6`. RE-MEASURED: `len(open("docs/STATUS.md").read())` == 243117. Byte-tight, strictly DOWN from main's 243186, which is the only direction it may move. Ceiling lowered to 243117 in this same change. docs/BENCHMARKS.md 44,839 of the hard 45,000. Both landed rows still fit inside the headroom PR #282's merge bought by MOVING the superseded 2026-08-08 `BENCH-VK-LLAMA` row into `.agents/benchmark-record.md`; main's own -15 since is carried, and no further row moved here. docs/FEATURES.md 27,385 of 30,000. docs/STATUS.md, docs/USAGE.md, .agents/roadmap_v1.md, .agents/specs/muse-glimmer.md and tests/CMakeLists.txt all auto-merged and were each verified line-for-line against main's edit set before being accepted. CORRECTION to the PR #267 merge commit immediately below this one. It attributed `test_llm_engine`'s 492 assertions (against the 493 the branch measured) to main's sixteen changed engine files. That was wrong, and the correction is recorded here rather than by rewriting the commit: three consecutive runs of the SAME unmodified binary gave 493, 493 and 492. The count is run-to-run NONDETERMINISTIC in this binary. Every run is 24/24 cases, 0 failed, 0 skipped, so no case is unreached and none of the three runs is a killed process. Nothing regressed, and the honest statement is that this binary's assertion count is not a stable quantity to gate on. GATE for the WHOLE landing, re-run by the operator on this tree, CPU Release, foreground, no timeout on any test binary. cmake --build build-cpu -j 18 504 targets, 0 errors, 0 warnings Focused, all seven of the three PRs' declared gates, on the final tree: test_dense_gate_up_seam_forward 4/4 cases | 1940 assertions | 0 skipped test_linear_method 6/6 | 76 | 0 skipped test_lora_layers 16/16 | 4498 | 0 skipped test_punica_cpu 8/8 | 149 | 0 skipped test_sampler 21/21 | 114 | 0 skipped test_input_batch 29/29 | 205 | 0 skipped test_llm_engine 24/24 | 491 | 0 skipped Checkers, with `scripts/__pycache__` cleared before each run: check-agent-record.py OK ENGINE=147 MODEL=362 QUANT=82 KERNEL=51 BACKEND=80 check-public-doc-tables.py OK STATUS 243,117 (byte-tight on its ratchet) BENCHMARKS 44,839 / 45,000 FEATURES 27,385 / 30,000 check-now-current.py OK NOW 5,972 / 6,000, 94 / 100 lines check-fusion-consistency.py OK glue 14 TUs / 12 routed / 2 allowlisted; merged-gemm 10 / 6 / 6, 0 drift test_check_public_doc_tables.py 60/60 test_check_gate_commands.py 26/26 test_check_fusion_consistency.py 20/20 Full `ctest --test-dir build-cpu -j 6 --output-on-failure`, 382 tests. BOTH numbers are reported, as the protocol requires: 379 PASSED under -j 6. 1 FAILED under -j 6: `test_async_llm`, in 0.58 s at load average ~90. 2 never ran: `test_openai_api_server` and `test_openai_conformance`, still queued when the run was cut short. All three are on the known starvation-prone list, and all three pass SERIALLY on an idle box (load average 3.55), in well under the time their -j 6 failure took: test_async_llm 15/15 cases | 444 assertions | wall 0.05 s test_openai_api_server 51/51 | 626 | wall 3.03 s test_openai_conformance 23/23 | 252 | wall 0.71 s That is starvation, not a regression: none of the three loads a file this landing touches, and a 0.58 s failure that becomes a 0.05 s pass alone is the signature. The #274 ASan/UBSan five did not appear -- this gate is Release with no sanitizer, and `test_llm_engine` and `test_capi`, two of that five, both PASS here (1476.63 s and 1238.96 s under -j 6). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
localai-bot
pushed a commit
that referenced
this pull request
Aug 11, 2026
…retired under us `origin/main` advanced a SECOND time during this landing, 31 commits, while the reconciliation merge below was being gated. A plain `git push` was refused as non-fast-forward, which is git protecting those merges, so this fetches, re-merges, reconciles every record against the shape main now has, and re-gates. No force, no rebase of a published branch. THE TWO CONSTRAINTS THIS LANDING WAS BUILT AROUND NO LONGER EXIST. #364/#368 (`ENG-RECORD-CONFLICT-SURFACES`) retired them as a defect, and AGENTS.md gained the rule behind it: *no surface that every PR must write*. Concretely: - `STATUS_RATCHET["chars"]` is DELETED from `scripts/check-public-doc-tables.py`. It was a byte count of one file stored in another and allowed only to fall, so every PR owing STATUS.md a line had to evict unrelated prose AND edit the checker. The three QUALITY keys (`h2_sections`, `long_paragraphs`, `oversized_cells`) are kept. - `MAX_CHARS` is DELETED from `scripts/check-now-current.py`. NOW.md was a fixed-size shared buffer at exactly 6000/6000, so adding a row meant evicting someone else's. `MAX_LINES` and `MAX_ENTRY_CHARS` are kept, and they cap the ENTRY rather than the file. So the ratchet arithmetic this landing carried out three times -- 243188 -> 243128 -> 243119 -> 243117, each byte-tight and re-measured -- is now moot, and the honest resolution is to DROP it rather than defend it. Main's version of both checker files and of `tests/scripts/test_check_public_doc_tables.py` is taken BYTE-FOR-BYTE, which also drops PR #267's `test_one_char_of_growth_on_the_LIVE_page_is_rejected`: that guard asserts on the `chars` key, and the key is gone. Its subject was retired, not its argument. `grep 'STATUS_RATCHET\["chars"\]'` over `scripts/` and `tests/scripts/` returns nothing. Equally, the NOW.md compactions this landing made to buy room -- the `SERVE-METRICS` restatement, the MiniMax-H3 and Release cells, the TP row's `(unblocks #127/#154/#155)` clause -- are reverted to main's fuller text: they were payments for a budget that no longer exists, and carrying them would be unrelated churn in a keyed record. Also fixed on main and no longer owed by anyone: the four conflict markers committed to `.agents/specs/sm120-qwen35-conv-channel-tile-2026-08-08.md`, which this landing found on main and did not touch (`76dfe8dc`). KEYED RECORDS, reconciled against the shape main now has. scripts/check-public-doc-tables.py, tests/scripts/test_check_public_doc_tables.py main's version taken BYTE-FOR-BYTE (`git diff` vs `f64f2b71` is empty for both). The landing's ratchet work is dropped entirely, per above. .agents/roadmap_v1.md CONFLICT. Main SORTED the whole issue table by Row then issue number, which moved every line. Main's sorted table taken wholesale and the landing's SIX rows re-inserted in sorted position -- #299, #314, #337, #338 under `ROAD-V1-C1`, #278 under `LORA-RUNTIME`, #264 beside #238 under `SAMPLE-LOGPROB-TOKEN-IDS`. Verified: all 48 of main's rows present, exactly 6 added, and the only other line that differs from main is the `ROAD-V1-C7` portfolio row, which PR #267 deliberately updates. .agents/engine-matrix.md CONFLICT. Main's `Serving, API, CLI, library` row gained a row and a `READY` (27 -> 28). Main's row kept wholesale, the landing's `LoRA and adapters` row (`ANCHOR-BACKFILL` -> `ACTIVE`) reapplied, and the **Total** RECOMPUTED from the ten actual area rows rather than carried from either side: 148|35|16|4|9|27|8|9|39. `check-agent-record.py` confirms ENGINE=148. .agents/coordination.md auto-merged and verified byte-identical to main's edit set. #368 introduced `.agents/claims/` (one file per claim) but deliberately does NOT migrate existing rows -- the checker still reads the legacy table -- so PR #282's `CLAIM-LORA-RUNTIME-W2` row stays where it is and is removed when the claim closes. scripts/check-gate-commands.py CONFLICT: both sides added a comment block at the same anchor. Unioned; `RUNNABLE_BASELINE` now carries `ENG-RECORD-CONFLICT-SURFACES` (main's), `ENG-RELEASE-CONTAINERS` (main's) and `LORA-RUNTIME` (#282's). 117 gated rows, 32 runnable. docs/FEATURES.md CONFLICT. Main's newer prose wins (37 registered architectures, up from 35) and PR #324's merged-GEMM sentence is reapplied into it. First attempt as a separate paragraph tripped the 21-vs-20 prose-paragraph cap and appending it inline tripped the 700-char paragraph cap at 782, so it is paid for INSIDE the paragraph exactly as #324 originally did -- the lead-in collapsed, total 697 of 700. No cap was raised. .agents/NOW.md CONFLICT. Main's three fuller rows taken wholesale (`BACKEND-ROCM` now records the gfx1100 GDN slice and #269 M0-M4). 94 of 100 lines, every entry inside MAX_ENTRY_CHARS. docs/STATUS.md, docs/BENCHMARKS.md untouched by main this time; 243,117 and 44,839. BENCHMARKS is still 161 chars inside its hard 45,000 cap thanks to the row PR #282's merge moved into `.agents/benchmark-record.md`. GATE for the WHOLE landing, re-run by the operator on THIS tree, CPU Release, foreground, no timeout on any test binary. cmake --build build-cpu -j 18 692 targets, 0 errors, 0 warnings Focused, all seven declared gates of the three PRs: test_dense_gate_up_seam_forward 4/4 cases | 1940 assertions | 0 skipped test_linear_method 6/6 | 76 | 0 skipped test_lora_layers 16/16 | 4498 | 0 skipped test_punica_cpu 8/8 | 149 | 0 skipped test_sampler 21/21 | 114 | 0 skipped test_input_batch 29/29 | 205 | 0 skipped test_llm_engine 24/24 | 494 | 0 skipped Checkers, `scripts/__pycache__` cleared before each: check-agent-record.py OK ENGINE=148 MODEL=362 QUANT=82 KERNEL=51 BACKEND=80 check-public-doc-tables.py OK BENCHMARKS 44,839 / 45,000 FEATURES 27,371, longest prose 697 / 700 check-now-current.py OK 94 / 100 lines check-fusion-consistency.py OK glue 14 / 12 routed / 2 allowlisted; merged-gemm 10 / 6 / 6, 0 drift check-gate-commands.py OK 117 gated rows, 32 runnable check-commit-trailers.py OK over the whole landing range Checker unit suites, all seven: test_check_public_doc_tables 52/52, test_check_gate_commands 28/28, test_check_fusion_consistency 20/20, test_agent_record 29/29, test_check_now_current 11/11, test_record_merge_shape 11/11, test_check_pr_size 38/38. Full `ctest --test-dir build-cpu -j 6 --output-on-failure`, 383 tests, 2771.26 s. BOTH numbers reported: 99% tests passed, 2 failed out of 383 -- `test_async_llm` (0.33 s) and `test_openai_conformance` (163.91 s). Both are on the known starvation-prone list, and both pass SERIALLY on an idle box (load 2.52): test_async_llm 15/15 cases | 443 assertions | wall 0.05 s test_openai_conformance 23/23 | 252 | wall 0.45 s `test_openai_api_server`, which needed a serial re-run last time, PASSED under -j 6 here in 128.64 s -- the same binary, the same tree, a quieter box, which is the clearest available evidence that these are scheduling artifacts rather than defects. Neither failing binary loads a file this landing touches. The #274 ASan/UBSan five did not appear: this gate is Release with no sanitizer, and `test_llm_engine`, `test_capi` and `test_llama_embedding_fold` -- three of that five -- all PASS here (2006.17 s, 1618.64 s, 14.20 s). Note on `test_llm_engine`'s assertion count, corrected earlier in this landing: it is run-to-run NONDETERMINISTIC in this binary (493, 493, 492 measured across three consecutive runs of one unmodified build; 494 here). Every run is 24/24 cases, 0 failed, 0 skipped. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
localai-bot
pushed a commit
that referenced
this pull request
Aug 11, 2026
…stops writing it `origin/main` advanced a THIRD time during this landing (7 commits), and for the third time the change was to the record surfaces this landing writes. A plain `git push` was refused as non-fast-forward, so this fetches, re-merges, reconciles and re-gates. No force, no rebase of a published branch. #374/#376 makes `.agents/NOW.md` DERIVED: `scripts/now.py` renders the live position from the matrices, the claims and GitHub, and **no PR writes the file**. That retires the last of the three shared surfaces #364 identified, after the `STATUS_RATCHET["chars"]` byte count and NOW's own `MAX_CHARS` went in the merge below. So this landing's NOW.md rows -- PR #282's `LORA-RUNTIME` row and PR #267's in-place update of the `logprobs_mode` row -- are DROPPED, and main's derived file is taken wholesale. That is not content lost: both rows' facts live in the matrices `scripts/now.py` reads, which this landing does update. Verified by running `scripts/now.py`, which renders. Across the whole landing the three surfaces the briefing said would bite -- the STATUS char ratchet, the NOW.md 6,000-char budget, and the coordination claims table -- were all retired or reshaped by main WHILE the landing was in flight. The BENCHMARKS.md 45,000-char cap is the one that still binds, and it is still paid: 44,839. KEYED RECORDS. .agents/NOW.md CONFLICT. Main's derived version taken WHOLESALE. No PR writes this file any more. .agents/benchmark-record.md CONFLICT, and the one genuinely APPEND-ONLY log in this landing, so resolved as a UNION: main's new Muse Glimmer benchmark entry (#333) followed by this landing's `BENCH-VK-LLAMA` compaction section. Both kept in full, neither reordered. .agents/engine-matrix.md CONFLICT. Main's `Serving, API, CLI, library` row gained another row and an `ACTIVE` (28 -> 29). Main's row kept wholesale, the landing's `LoRA and adapters` row reapplied, **Total** RECOMPUTED from the ten actual area rows: 149|35|16|4|9|28|8|9|39. `check-agent-record.py` confirms ENGINE=149. Everything else auto-merged; `docs/STATUS.md`, `docs/BENCHMARKS.md`, `docs/FEATURES.md`, `docs/USAGE.md`, `.agents/roadmap_v1.md`, `.agents/coordination.md`, `scripts/check-gate-commands.py` and `tests/CMakeLists.txt` were each verified against main's own edit set. GATE for the WHOLE landing, re-run by the operator on THIS tree, CPU Release, foreground, no timeout on any test binary. cmake --build build-cpu -j 18 547 targets, 0 errors, 0 warnings Focused, all seven declared gates of the three PRs: test_dense_gate_up_seam_forward 4/4 cases | 1940 assertions | 0 skipped test_linear_method 6/6 | 76 | 0 skipped test_lora_layers 16/16 | 4498 | 0 skipped test_punica_cpu 8/8 | 149 | 0 skipped test_sampler 21/21 | 114 | 0 skipped test_input_batch 29/29 | 205 | 0 skipped test_llm_engine 24/24 | 494 | 0 skipped Checkers, `scripts/__pycache__` cleared before each: check-agent-record.py OK ENGINE=149 MODEL=362 QUANT=82 KERNEL=51 BACKEND=80 check-public-doc-tables.py OK BENCHMARKS 44,839 / 45,000 hard cap check-now-current.py OK the derived page, 100-line and per-entry caps check-fusion-consistency.py OK glue 14 / 12 routed / 2 allowlisted; merged-gemm 10 / 6 / 6, 0 drift check-gate-commands.py OK 118 gated rows, 33 runnable check-commit-trailers.py OK over the whole landing range scripts/now.py renders Checker unit suites, all nine: test_check_public_doc_tables 52/52, test_check_gate_commands 30/30, test_check_fusion_consistency 20/20, test_agent_record 29/29, test_check_now_current 15/15, test_now_render 11/11, test_record_merge_shape 11/11, test_check_pr_size 38/38, test_doc_checkpoint 26/26. Full `ctest --test-dir build-cpu -j 6 --output-on-failure`, 384 tests, 1416.34 s. BOTH numbers reported: 99% passed, 1 failed of 384 -- `test_engine_core_proc`, under -j 6 with a SECOND worktree running its own suite on the same box at load average 241. It is on the known starvation-prone list and passes SERIALLY on an idle box (load 0.77): 14/14 cases, 114 assertions, wall 0.02 s. The other three starvation-prone binaries were re-confirmed serially in the same session -- test_async_llm 15/15 (444), test_openai_api_server 51/51 (568), test_openai_conformance 23/23 (252) -- and `test_async_llm`, which failed under -j 6 in both earlier runs of this landing, PASSED under -j 6 here in 1.14 s. A binary that flips with box load and not with the tree is scheduling, not a defect; none of the four loads a file this landing touches. The #274 ASan/UBSan five did not appear: this gate is Release with no sanitizer, and `test_llm_engine`, `test_capi`, `test_llama_embedding_fold` and `test_openai_api_server` -- four of that five -- all PASS here. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: ClaudeCode:claude-opus-5 [ClaudeCode]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #264. Row
SAMPLE-LOGPROB-TOKEN-IDS(.agents/engine-matrix.md:133),INVENTORIED->PARTIAL. Spec:.agents/specs/logprob-token-ids.md,committed in its own commit BEFORE any implementation code.
A request may now name an explicit set of vocab ids and get back exactly
those plus the sampled token — vLLM's generative-scoring path
(
entrypoints/generate/generative_scoring/serving.py:247-255sendsmax_tokens=1+logprob_token_ids=label_token_ids), and the efficient answerto "score these five labels" without
logprobs=-1and a full-vocab sort.What was ported, and from where
Read at the pin
555967922in/home/mudler/_git/vllm, not from memory.sampling_params.py:31,278-283field +MAX_LOGPROB_TOKEN_IDSinclude/vllm/sampling_params.h:168,kMaxLogprobTokenIdssampling_params.py:724-729thenum_logprobspropertySamplingParams::num_logprobs(),src/vllm/sampling_params.cpp:38sampling_params.py:773-782,795-801length +logprobs == nSamplingParams::Verify()gpu_input_batch.py:273,443-444,574,934-951src/vllm/v1/worker/gpu/input_batch.cpp:306sampler.py:151-225gather_specific_token_logprobsGatherSpecificTokenLogprobs,src/vllm/v1/sample/sampler.cpp:160sampler.py:86snapshot condition,:133-136precedenceSampler::forwardscheduler.py:1815-1821property gatesrc/vllm/v1/core/sched/scheduler.cppThree things worth calling out.
The
num_logprobsproperty was the hidden half of this. Three of ourconsumers spelled it as the raw
logprobsfield — the scheduler's slice gate,LogprobsProcessor::FromNewRequest(whose comment already claimed it was theproperty), and
RequestState::FromNewRequest. Identical for every request thatexists today; without fixing them a scoring request produced sampler output that
nothing downstream ever read, so the feature would have been unreachable through
the engine.
include/vllm.his untouched — the C ABI does not exposelogprobsat all, so it exposes no less of this than of the
SAMPLE-LOGPROBSrow.Our device-resident greedy fast path is ours, not upstream's, and it skips
the gather entirely. It is gated on the same combined predicate as the snapshot;
missing that second edit would have made the feature vanish silently on the async
greedy path.
Issue #249's defect class is kept out, its instance untouched.
torch.gatherraises on an out-of-range index; the C++ equivalent reads past the row, so every
requested id is checked into
[0, vocab)and every map key into[0, n)beforeit indexes anything.
GatherLogprobs' unboundedk(the #249 instance) is aseparate row and is deliberately not touched here.
RED, verbatim
Captured on a build carrying only the inert field declarations, with
num_logprobs()written asreturn logprobs;— literally what the engine did.A second RED landed mid-implementation and is worth recording: with the sampler
and
InputBatchfully wired,test_llm_enginewas still red at the sameline, because
RequestState::FromNewRequestgated the wholeLogprobsProcessoron
sp.logprobs.has_value(). Upstream constructs it unconditionally(
output_processor.py:223-229); it is now gated on the property.GREEN
Clean
rm -rf build-cpuRelease rebuild, zero warnings under-Werror:Full gate:
ctest -j 6364/366, 1029.87 s. The two failures weretest_engine_core_procandtest_async_llm, both re-run serially:-j 6(load avg 87-103 on 20 cores)test_engine_core_proctest_async_llmCHECK( Drain(engine, reused) == 3 )got175Both pass serially in well under a second, so both were starvation, not
regressions. Neither touches any file in this change.
Checker change
scripts/check-public-doc-tables.py: thedocs/STATUS.mdchar ratchet isre-pinned DOWN,
243571 -> 243479. The Sampling row owed the page a line andpaid for it inside the same cell — the beam-search paragraph was three clauses of
how-it-is-wired narrative, collapsed to its binding result plus its named
residuals, with the wiring story kept in
.agents/engine-matrix.mdwhere italready lives. Net -92, re-pinned byte-tight.
No checker semantics changed, and the mutation evidence in
tests/scripts/test_check_public_doc_tables.pyis real, not a rubber stamp:STATUS_RATCHET_CEILINGis lowered243578 -> 243479in the same change, sothe cheap way out stays blocked at the new level. MUT A — putting the
ratchet back to
243571— now reds four assertions includingtest_the_status_ratchet_only_ever_moves_down, which at the old ceiling wouldhave stayed green.
test_one_char_of_growth_on_the_LIVE_page_is_rejected. The existing growthtest grows a synthetic page by a whole ratchet, which proves the comparison
exists but not that the cap sits on the boundary of the real page. MUT B —
moving the cap to
243480so it trails the page by one byte — reds only thisnew test and the byte-tight one. It also asserts the error names both numbers,
since "too big" cannot tell an author what to collapse.
A trap worth passing on: the old and new ratchet values are the same byte
length, so after a mutate-and-restore the stale
scripts/__pycache__/*.pycsurvived mtime/size invalidation and the suite kept reading the mutated
number — three red tests against a restored tree.
find . -name __pycache__ -exec rm -rf {} +before trusting any checker-mutation result.Records
.agents/engine-matrix.md(row ->PARTIAL, code/test/spec anchors, section andtotal counters),
.agents/roadmap_v1.md(#264 intake row + the C7 REMAININGnote),
.agents/coordination.md(CLAIM-SAMPLE-LOGPROB-TOKEN-IDS; prose, sincethe claims table keys
SPIKE/ACTIVErows),.agents/porting-inventory.md(theM1.7 "marked stub" line was stale),
docs/STATUS.md,docs/BENCHMARKS.md(nonumber owed, and why),
docs/USAGE.md(the new field, with an example),.agents/NOW.md(5,993 of 6,000 chars).Residuals, which is why the row is
PARTIALand notDONElogprobs_modevariants — the row's other half. Open PR feat(sample): implement the three refused logprobs_mode variants (#238) #258 owns it andedits the same snapshot block in
sampler.cpp; whichever lands secondrebases. The overlap is about five lines of one condition.
logprob_token_idsrequest field on/v1/completionsand/v1/chat/completions, its four cross-field validations, and/v1/generative_scoring.verify(model_config). OurVerify()has no model config, exactly as forallowed_token_ids. The sampler bounds them anyway, so this is a message-quality gap, not a safety one.
Two corrections to the record
PARTIALbecause "logprobs_modelanded inlogprobs_mode: three of four modes are runtime-refused stubs #238/feat(sample): implement the three refused logprobs_mode variants (#238) #258". It had not: logprobs_mode: three of four modes are runtime-refused stubs #238 is an issue and PR feat(sample): implement the three refused logprobs_mode variants (#238) #258 is still open. The row
was
INVENTORIEDon main, and this change is what moves it toPARTIAL..agents/specs/logprob-token-ids.mdalso records that upstream carries asecond implementation of this feature in the V2 worker
(
vllm/v1/worker/gpu/sample/logprob.py:114-235, a Triton kernel plus aLogprobTokenIdsStateGPU buffer). Our sampler mirrors the V1vllm/v1/sample/sampler.py, so the V1 path is the port target — noted so thenext reader does not think an anchor was missed.
🤖 Generated with Claude Code