You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(FIX-GPU-MEM-UTIL-INERT): --gpu-memory-utilization stops reporting success for a budget it discards (#1165) (#1176)
The flag reached six files and changed nothing. `ResolveNumBlocks` fell
through knob 1 and knob 2 to a bare `return 256`, so
`--gpu-memory-utilization 0.85` produced the same pool as passing
nothing, and the engine said so nowhere. A user who typed it believed
they had sized the KV pool.
The flag is NOT refused, and the resolution is unchanged.
`.agents/roadmap_v1.md:71` records the intent that it keeps vLLM's exact
name and fraction semantics so a published `vllm serve` line ports
unchanged, and refusing it would break that and contradict the mirror
rule. What changes is that the line which discards the value now says
so, naming the 256-block fallback that resolved instead, the two knobs
that do bind today, and the row and issue that own the real fix.
```text
vllm.cpp: WARNING --gpu-memory-utilization 0.85 was accepted but did NOT size the KV cache.
vllm.cpp: The profile run that turns a free-memory fraction into a block count is not
vllm.cpp: implemented yet (ROAD-V1-MEM M3, #83).
vllm.cpp: The pool fell back to 256 blocks. To size it today, pass
vllm.cpp: --kv-cache-memory <bytes> for an absolute KV budget, or --num-blocks <n> for an
vllm.cpp: exact block count.
```
Only a CHOSEN value warns. `EngineParams::gpu_memory_utilization`
becomes `std::optional<double>`, mirroring the `enable_prefix_caching`
tri-state in the same struct, because a plain double pre-filled with
0.92 cannot tell "the user typed 0.92" from "nobody touched it", and a
line on every start is noise rather than a warning. The server's `Args`
field becomes the same tri-state. `examples/cli` now leaves the ABI
field at 0.0, the sentinel `vllm.h` already documents as unset, instead
of overwriting it with 0.92 on every run.
Knobs 1 and 2 return before the notice, so sizing the pool with
`--num-blocks` or `--kv-cache-memory` is silent. Under
`--kv-cache-memory` that also mirrors vLLM, which ignores the fraction
there (`cache.py:189`), so such a caller has nothing to be warned about.
## This does not close#83
`ROAD-V1-MEM` M3 still owns the utilization path itself, it still needs
a device profile run and an oracle-matched pool on `dgx.casa`, and
nothing here stubs one. The spec states that boundary explicitly,
because the obvious misreading of a landed warning is that the feature
arrived.
## The C ABI is unchanged
`vllm_model_params_default()` still pre-fills 0.92, so a C caller who
never touched the field does get the notice: that ABI has no "flag not
typed" state, and a struct carrying 0.92 into an engine that ignores it
is the case the notice exists for. `vllm.h` now says so and names 0.0 as
the opt-out.
## Records
`docs/USAGE.md` documented neither sizing knob and called `--num-blocks`
a 256 default when it defaults to 0. All three entries are corrected,
and the GB10 unified-memory hazard lands there rather than in the
notice, because the notice disappears exactly when that hazard becomes
real.
`.agents/feature-matrix.md:92` and `docs/FEATURES.md:365` were checked
and deliberately not edited: both already record the row as `PARTIAL`
with M3 dgx-gated, neither states anything this change makes false, and
editing the matrix would pull `FEATURES.md` in behind it with nothing
new to say.
## Evidence
Red first, on the merged tree at `469f3839`, with the notice block
removed and the tri-state kept so the test still compiled (`BUILD_RC=0`,
so the red is not a compile failure wearing a pass):
```text
test_loaded_engine_dense.cpp:710: FATAL ERROR: REQUIRE( logged.find(kInertNotice) != std::string::npos )
[doctest] test cases: 19 | 18 passed | 1 failed | 0 skipped
[doctest] assertions: 81 | 80 passed | 1 failed |
```
Restored, `sha256sum -c` OK, green after: 19/19 cases, 87/87 assertions.
Three mutations, each built clean and each restored byte-for-byte:
| Mutation | Expected red | Result |
|---|---|---|
| Delete the `ResolveNumBlocks` call in `MakeKVCacheResolved` | the
explicit case | RED 18/19, 81 assertions |
| Force the guard to `if (true)` | the unset case | RED 18/19, 87
assertions |
| Hoist the notice above the early returns | both precedence cases | RED
17/19, 87 assertions |
The first is the reachability mutation. The test enters through the
loader rather than the resolver, which is private, so deleting the
production call site is what makes it fail.
`scripts/agent-preflight.sh --fail-on-skip` at `c5b198e5`: ok=79,
FAIL=0, SKIP=0, `--`=0, verdict `All gates green.`, exit 0. Counted
after stripping ANSI and slicing at the banner, because `.agents/NOW.md`
follows it and contains those words. Per block: role 1, record gates 27,
mutation suites 46, committed range 3, commit trailers 2.
No checker, CI workflow, or mutation-suite file is touched, and no
budget is raised.
`test_cpu_x86_llamacpp_floor` failed twice during this work and passed
three times on an unmodified tree at loadavg 5 to 33, with failures
taking 18 s and 170 s against 3 s for passes. That is the
`NO_QUIET_WINDOW` retry path of #618. Its harness and script are
byte-identical to `origin/main`, so it is the box, not this diff.
CPU only. No GPU lease was taken and `rc` was not called.
Closes#1165.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
---------
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
Copy file name to clipboardExpand all lines: .agents/issue-index.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -347,6 +347,7 @@ rather than merged. `scripts/check-agent-record.py` gates both.
347
347
| [#1150](https://github.com/mudler/vllm.cpp/issues/1150) | `LTX25-TI2VID-RECIPE` | The sigma SHIFT is derived from the target latent on every arm, where six of upstream's seven `LTX2Scheduler.execute` call sites pass NO latent and take `default_number_of_tokens` = `MAX_SHIFT_ANCHOR` = 4096 (`schedulers.py:11,:29,:31`). `grep -rn '\.execute(' packages/ltx-pipelines/src/ltx_pipelines/` at `fd4ded7f` returns seven and that grep is the whole population: only `ti2vid_two_stages_hq.py:267` passes `latent=empty_latent`. This engine passes `target_tokens` at `src/vllm/multimodal/ltx2_video.cpp:3442-3443`, so it mirrors the exception and diverges from the rule. Correct today: `t2a_one_stage` (passes 0 at `src/vllm/model_executor/models/ltx2_t2a.cpp:178`) and `res2s_two_stage`. DIVERGENT: `one_stage` at four version keys (`ti2vid_one_stage.py:207`), `a2vid_two_stage` stage 1 (`a2vid_two_stage.py:226`) and `retake`'s non-distilled arm (`retake.py:287`). Recipes carrying explicit `sigmas` never reach the derivation and are unaffected. Not a rounding difference: at the recipe default geometry the target latent is 6144 tokens, giving `sigma_shift` 2.78 against upstream's 2.05, so every sigma moves. Invisible because the trajectory changes while the frame count, shapes, sample rate and errors do not, and our goldens were captured from this engine so they PIN it rather than detect it. `.agents/specs/ltx25-res2s-loop.md:80-88` saw the HQ/plain split and concluded the divergence was on the plain two-stage arm alone; that is right about HQ and wrong about the blast radius. Found by row `LTX25-TI2VID-RECIPE`, which added the seam — `Ltx2PhaseRecipe::schedule_tokens`, defaulted to today's behaviour so nothing moves — and set it on the one phase it ships. Not fixed in flow because flipping the other three re-samples five shipped, gated arms and rewrites their goldens, which needs its own spec and fresh review. Listed under `## Owed` in [`ltx25-ti2vid-recipe.md`](specs/ltx25-ti2vid-recipe.md) | bug |
348
348
| [#1151](https://github.com/mudler/vllm.cpp/issues/1151) | `LTX25-TI2VID-RECIPE` | The `requires_distilled_lora` refusal still advertised [#1118](https://github.com/mudler/vllm.cpp/issues/1118) as OPEN, and cited `a2vid_two_stage.py`'s line numbers to every other pipeline. #1118 closed at `4ae0f54ab` (row `LTX25-PHASE-LORA`, PR [#1140](https://github.com/mudler/vllm.cpp/pull/1140)), which added `Ltx2PhaseRecipe::loras` and `Ltx2RebindDitLoras`; the message at `src/vllm/multimodal/ltx2_video.cpp:1039-1041` still ended "upstream fuses that adapter into stage 2 ALONE and this engine fuses once at load, so stage 1 sees it too", every clause of which had become false, and the comment above the refusal said the same. `ltx25-phase-lora.md` repaired the REFERENCE-CONDITIONING refusal, which carried the identical claim ~1100 lines away, and named only that one in its port map, so this site and the `ltx2-gen --help` text (`examples/ltx2_gen/main.cpp:210-212`) were both missed. Second defect at the same site: the refusal is deliberately keyed on the FLAG rather than on the kind string so the next recipe inherits it (the comment names #1093 and #1096 as waiting), yet its body interpolated `im.pipeline_kind` into the first sentence and hard-coded a2vid's `:164`, `:114`, `:107` into the rest — so the first arm to inherit it would be told its own name and then a different pipeline's source lines. `--distilled-lora required=True` lives on `default_2_stage_arg_parser` (`utils/args.py:1123`, `:1140-1155`), which all of these pipelines select, and that shared anchor is what the message now cites. Found and fixed IN FLOW by row `LTX25-TI2VID-RECIPE`, the second user of the flag; `tests/vllm/multimodal/test_ltx2_video.cpp` asserted the string `1118` was PRESENT and now asserts it is absent | bug |
349
349
| [#1152](https://github.com/mudler/vllm.cpp/issues/1152) | `LTX25-TI2VID-RECIPE` | `Ltx2PipelineRecipe::allow_request_latents` (`include/vllm/model_executor/models/ltx2_pipeline.h:705`) is WRITTEN by every recipe and READ by nothing — the "a parameter no caller passes" shape AGENTS.md `## Nothing lands dead` names. Measured at `c83b96934`: `grep -rn allow_request_latents src include examples` minus the declaration returns FIVE lines and all five are assignments (`ltx2_pipeline.cpp:1264` false, `:1345` false, `:1473` true, `:1604` false). Positive control, the field declared one line above and set in the same blocks: `allow_request_sigmas` returns its assignments PLUS a real reader at `src/vllm/multimodal/ltx2_video.cpp:3476`, so the grep is well-formed and the absence is the finding. Consequence: `Res2sTwoStageRecipe` carries `true` where every other recipe carries `false` and nothing can tell the difference — no upstream `__call__` among these pipelines takes an initial-latent parameter, so `false` is what the signatures support and the `true` looks like an oversight, but it is unfalsifiable while nothing reads the field. The tests assert the VALUES, so they gate the record against itself and cannot see that nothing consumes it, which is the tautology shape [#911](https://github.com/mudler/vllm.cpp/issues/911) recorded on the anchor checker. Two closes: give it a reader (a refusal on a request supplying a latent to a recipe whose upstream signature has none) or delete it and its assertions — deleting is defensible, since no request surface carries a latent at all so the refusal could never fire either. Found by row `LTX25-TI2VID-RECIPE` while deriving the same field for a sixth recipe; not fixed in flow because both closes touch five landed recipes and one deletes gated assertions. Listed under `## Owed` in [`ltx25-ti2vid-recipe.md`](specs/ltx25-ti2vid-recipe.md) | bug |
350
+
| [#1165](https://github.com/mudler/vllm.cpp/issues/1165) | `FIX-GPU-MEM-UTIL-INERT` | `--gpu-memory-utilization` is parsed (`src/vllm/entrypoints/openai/server_main.cpp:440-441`), threaded to both engines (`:952`, `:1039`), carried on the C ABI (`include/vllm.h:486`, `src/capi/vllm_c.cpp:577-580`), spelled by `examples/cli/main.cpp:118-119`, defaulted to 0.92 (`include/vllm/entrypoints/model_loader.h:90`) and then read by NOTHING: `LoadedEngine::ResolveNumBlocks` falls through knob 1 (`num_blocks`) and knob 2 (`kv_cache_memory_bytes`) to a bare `return 256` under a `TODO(ROAD-V1-MEM M3)` (`src/vllm/entrypoints/model_loader.cpp:954-959`), so a user who passes `--gpu-memory-utilization 0.85` believes they sized the KV pool and sized nothing. DISTINCT from [#83](https://github.com/mudler/vllm.cpp/issues/83), which owns IMPLEMENTING the utilization path (`ROAD-V1-MEM` M3, dgx-gated on a profile run and an oracle-matched pool). This row owns not lying about it: accept the flag, keeping vLLM's exact name and fraction semantics per `.agents/roadmap_v1.md:71`, and emit one notice per engine load when the caller set it explicitly AND the utilization path is the one that resolved the pool. Fixed in flow. Spec [`gpu-mem-util-inert.md`](specs/gpu-mem-util-inert.md) | bug |
350
351
| [#1166](https://github.com/mudler/vllm.cpp/issues/1166) | `FIX-FP8-BLOCKWISE-REFUSAL` | `Qwen/Qwen3.8-27B-FP8` is block-wise (fine-grained 128x128) FP8 and this tree implements per-tensor FP8 only, so the load stops on a message that names the wrong thing. Measured live at revision `017b9c7af6b5689d5dd426a76e0bc077eb5ca20a` on 2026-08-17: the config declares `quant_method` `fp8`, `weight_block_size` `[128, 128]` and `activation_scheme` `dynamic`, and the safetensors header of `layers-3.safetensors`, read by RANGE REQUEST rather than downloaded, gives `self_attn.q_proj.weight` `F8_E4M3` `[12288, 5120]` beside `self_attn.q_proj.weight_scale_inv` `BF16` `[96, 40]`, which is exactly `[12288/128, 5120/128]`, with ZERO `input_scale` tensors in the shard. `LoadAttnDense` branches on the weight dtype alone (`qwen3_5_dense_weights.cpp:479`) so the block-wise projection enters the per-tensor arm at `:480`, and `LoadFp8Raw` (`qwen3_5_weights.cpp:449`) asks for `<proj>.weight_scale` at `:458`, which this checkpoint spells `weight_scale_inv`, so the resolver at `qwen3_5_dense_weights.cpp:682` raises `tensor not found: ...q_proj.weight_scale`. Nothing is missing from the checkpoint. The reader is sent after a tensor upstream never writes in this mode instead of being told the fine-grained arm is absent. NOT the silently-wrong-numerics case, and the check that rules it out is recorded rather than assumed: `ReadF32Scalar` (`qwen3_5_weights.cpp:312`) bounds its input with `t.nbytes >= sizeof(float)`, a LOWER bound, so a `[96, 40]` scale would pass and read as block `(0,0)`, but the NAME miss stops the load before that scalar read, and upstream makes the spelling strictly conditional on block quant (`weight_scale_inv if self.block_quant else weight_scale`, `fp8.py:511` at pin `555967922`), so no upstream block-wise checkpoint reaches it. FIXED IN FLOW as a named refusal at `ModelRegistry::Load`; the block-wise arm itself stays owed | bug |
351
352
| [#1161](https://github.com/mudler/vllm.cpp/issues/1161) | `ENG-CUDAGRAPH` | SGLang landed **Breakable CUDA Graph (BCG)** and the whole surface was absent from `.agents/sglang-matrix.md`, while `ENG-CUDAGRAPH` carried no verdict on it. Grounded at pin `v0.5.15`/`f63458b5be`: BCG captures one forward as a SEQUENCE of `torch.cuda.CUDAGraph` segments split at eager break points on the attention and recurrent calls, sharing one mempool with weak-ref'd intermediates, with **no `torch.compile`** (`breakable_cuda_graph.py:204-241,244-260,14-23,156-169`; `breakable_cuda_graph_backend.py:14-17`; break points `radix_attention.py:256`, `forward_mla.py:1092`, `radix_linear_attention.py:159`, `nemotron_h.py:1240`, `dsa_indexer.py:2414`). **The public priority claim is narrower than it reads:** vLLM's v1 default is ALREADY `FULL_AND_PIECEWISE`, splitting at `splitting_ops` (`vllm/config/compilation.py:60-63,517,615,630` @ `555967922`), so the capability has been in our benchmark denominator all along — what SGLang landed first is the CONSTRUCTION (piecewise coverage from runtime stream capture plus a decorator, no Dynamo/Inductor/FX), which matters to us only because we have no compiler either. **The load-bearing outcome is a REFUTATION:** prefill graph capture is not a lever on our CUDA gate models, because GB10 2026-07-09 measured steady-state prefill GPU-idle-between-launches at **3.8%** with GPU-busy >96% on both arms, and attributed the 27B prefill gap to **92.5% non-GEMM glue GPU work** with the dominant GEMM the same symbol at the same call count (+0.17%) and attention AHEAD. There are no launch bubbles in our prefill for a graph to collapse, and vLLM's piecewise prefill graph is already inside the denominator we lose to, so it does not explain the gap either. Decode is a separate and already-banked case (conc-64 launch overhead was ~24%, already one `cudaGraphLaunch`/step). FIXED IN FLOW: `SGLANG-BCG` row added under a new "Graph capture and runtime execution" section of `sglang-matrix.md`, the refutation recorded on `ENG-CUDAGRAPH` (which stays `PARTIAL`, because nothing shipped), roadmap track `C12` added, and spec [`sglang-breakable-cuda-graph.md`](specs/sglang-breakable-cuda-graph.md) written to carry the analysis. NOT fixed here and owned by the three issues below: no engine code, no measurement taken, no oracle run, and no advance of the SGLang pin | record |
352
353
| [#1162](https://github.com/mudler/vllm.cpp/issues/1162) | `ENG-CUDAGRAPH-DEDUP` | We instantiate **one `cudaGraphExec` per padded decode bucket, per model**, and `grep -rn "cudaGraphExecUpdate" src include` returns nothing. `src/vt/cuda/cuda_backend.cu:222-232` instantiates a fresh exec per capture; `include/vllm/model_executor/models/decode_graph_sizes.h:32-41` yields 7 buckets at `max_num_seqs=32` and 11 at 64; eight drivers each build their own set. SGLang folds compatible captures onto one executable by hashing graph topology and calling `cudaGraphExecUpdate` on a signature hit (`cuda_graph_dedup_mixin.py:219-242`, logging "captured %d CUDA graphs, deduped to %d execs" at `:358`). Portable to us unchanged, because it is driver-level rather than PyTorch-level. It is a **memory and capture-time** change, NOT a throughput change — a deduped replay launches the same nodes — and it matters because on GB10 unified memory an OOM reboots the box, capture time is startup latency (a recorded gate axis), and bucket count is exactly what widening graph coverage would raise. Owed: the dedup registry behind the `vt` seam, a capture-count/exec-count log line, and a same-binary A/B proving a deduped replay is byte-identical rather than asserting it. Hazard already recorded: capture bakes host source addresses and a clean `compute-sanitizer` run is NOT evidence a capture path is safe (`specs/decode-graph-scratch-uaf-2026-07-18.md`). Spec [`sglang-breakable-cuda-graph.md`](specs/sglang-breakable-cuda-graph.md) `## Owed`. Analysis: [#1161](https://github.com/mudler/vllm.cpp/issues/1161) | perf |
0 commit comments