Issues / #3502

#3502 [Other] tuning always fails: a CUDA-init failure in the bench run is recorded as OOM, and that command is then never retried

open · @tcw-znjt · 0 comments · View on GitHub

BenchmarksSetup & installServer & APINVIDIA / CUDAModels & quantsWindows

Description

### Area

[Other] (the tuning/calibration search in strata-control)

### Desktop app, Terminal or source?

Desktop app (Windows)

### What happened

On this PC the app's model tuning ("调优") fails **every time** now, and it fails at the very first measurement of my current settings ("base"). What I saw:

- The benchmark engine that the tuning starts prints `ggml_cuda_init: failed to initialize CUDA: no CUDA-capable device is detected` and exits.
- That run is recorded in `%APPDATA%\Strata\presets\history.jsonl` with `"status": "OOM"` (RAM was fine: the same line says `available 51.2 GiB`), not `FAIL_BOOT`.
- One earlier run at 17:46 the same day, with a **byte-identical command line**, passed (66.2 tok/s decode). After the OOM rows appeared, every later attempt failed the same way, so from the user's side "tuning always fails".

Two separate things are involved:

1. **The trigger (my environment).** The tuning starts a second, full engine process to measure. When I ran it, the served model still held most of the 16 GB card. In the same session a normal engine start also failed with `no VRAM is left for the expert cache: it needs at least 274 slots (695 MiB), about 1615 MiB more than this card has free` (exit 6, `server_exited`), and `nvidia-smi` reports `display_active` Disabled because the machine is driven through a remote-desktop virtual display. So the bench process could not get the card.

2. **The part that looks like a Strata bug.**
   - `strata_control/tune_session.py`, `failure_kind()` (line 129-139): the OOM test searches the whole output for `memory guard`, and the engine's informational line `strata generate: memory guard: limit 63.8 GiB (source: meminfo), current 12.6 GiB ... available 51.2 GiB` was present in all 7 failed runs. So a run that died at CUDA initialisation is stored as `OOM` instead of `FAIL_BOOT`, and the reason line is a 3-line mixture of the CUDA error and two informational lines.
   - `strata_control/presets.py`, `bad_signatures()` (line 510-521): a signature recorded as `UNSUPPORTED` or `OOM` is "never retried". The poisoned signature here is the plain base command that had **passed 4 times before** with the same `hw` hash, `model` digest and `engine` sha (`c2335807…`). One transient condition therefore permanently disables the baseline candidate.
   - `strata_control/autotune.py` (line 469-475): an `OOM` result sets `storm`/`stop` and the whole search ends with "an engine ran out of memory; the search stopped (nothing was retried)". Since the baseline itself is what "OOMs", every later tuning ends immediately.

Expected behaviour: a CUDA-init failure should be classified as `FAIL_BOOT`/CUDA-unavailable with an actionable message (the model must be unloaded, or the card is busy), and a signature that previously passed on the same hardware/engine/model should not be blacklisted permanently by one later `OOM`.

### Smallest steps or command that reproduce it

What I actually did (this is the observed sequence, not a minimal deterministic recipe — I cannot make the CUDA failure happen at will):

1. Installed Qwen3.8-Flash-Next IQ3_S, 131072 context, pictures on, in the Windows desktop app. Tuning during setup finished: 7 `PASS` rows in `history.jsonl` (decode 61.4-69.5 tok/s, prefill 1547.8-1585.1 tok/s), 2026-10-20 17:46-17:53 local.
2. Served the model in the app (the engine loaded ~13-15 GB of the 16 GB card), and let the app update the engine from 0.1.45.3 to 0.1.46 (`%LOCALAPPDATA%\Strata\tuning\recheck.json` has a pending entry with `"reason": "engine_changed"`).
3. Started tuning from the app 7 more times at 19:49, 20:25-20:50 and 03:18. Each one stopped at the first "base" measurement, and each wrote an `OOM` row with `ggml_cuda_init: failed to initialize CUDA: no CUDA-capable device is detected` in the reason.

### Reproduction status

Reproduced locally (7 consecutive failed attempts observed, all 7 written to `history.jsonl`). The suggested code change has **not** been tested, and I have not yet re-run tuning after removing the bad rows — I will report back in this issue.

### Strata version

Strata 0.2.1 (desktop app) · strata-control 0.1.46.2 (bundle commit `5b47f889b0f52f1b7b9cc4b0994193b85f4664b6`) · engine 0.1.46 `windows-x86_64-cuda-cuda13`, measured engine sha `c2335807a15b…`

### Graphics card, memory and system

RTX 5060 Ti 16311 MiB · 64 GB RAM · Windows 11 Pro 10.0.26300 · driver 616.56 (CUDA UMD 13.4) · `nvidia-smi` compute_mode `Default`, `display_active` Disabled (the desktop is served by a remote-desktop virtual display adapter: Oray/GameViewer/Zako virtual display drivers are installed) · CPU i5-12600K

### Model, size and settings

Qwen3.8-Flash-Next GSQ-RCO IQ3_S (pack + `--ple-gguf` split), MTP `mtp\rt`, `--max-context 131072 --kv k8v4 --vision --spec 4 --spec-min-p 0.5 --expert-cache auto --prefill auto --vram-reserve-mib 2048`, data dir on a separate SSD.

### Logs

`%APPDATA%\Strata\presets\history.jsonl` — 7 `PASS` then 7 `OOM`, same `sig`, same `hw`/`model`/`engine` hashes:

```text
{"status": "PASS", "label": "base",     "decode_tok_s": 66.2, "pp_tok_s": 1569.5, "reason": ""}
{"status": "OOM",  "label": "base",     "decode_tok_s": null, "pp_tok_s": null,
 "reason": "ggml_cuda_init: failed to initialize CUDA: no CUDA-capable device is detected | strata generate: memory guard: limit 63.8 GiB (source: meminfo), current 12.6 GiB, reclaimable cache credited 0.0 GiB; available 51.2 GiB (MemAvailable 51.2 GiB), floor 2560 MiB | strata generate: expert arena: locked 13…"}
```

The `PASS` and `OOM` rows above have the same `sig` (`--vram-reserve-mib 2048`), they differ only in the wall-clock time; 6 of the 7 `OOM` rows used 2048 and the last one used `--vram-reserve-mib 700` (Strata chose the "no display attached" reserve because `display_active` is Disabled). The `base` signature itself passed 4 times before (`base`, `base`, `base r2`, `base end`).

Engine log (`%LOCALAPPDATA%\Strata\install\strata-iq3_s.log`), a plain (non-tuning) engine start at 03:32:12 in the same session, showing that the card really had no room for a second engine:

```text
[strata] 2026-10-21 03:32:12 engine started: …\strata.exe --serve … --max-context 131072 --kv k8v4 --vision --vram-reserve-mib 700 --vram-elastic
strata generate: expert arena: locked 1302 MiB via working-set minimum + VirtualLock; cudaHostRegister of the whole arena FAILED (out of memory); 47 slices pinned (45 GiB)
strata generate: expert cache auto: 0.00 GiB free, 700 MiB reserved (+218 MiB for the draft head) -> 0 slots
strata generate: no VRAM is left for the expert cache: it needs at least 274 slots (695 MiB), about 1615 MiB more than this card has free. To make room: a smaller --max-context (the session, mostly its KV cache, takes 1571 MiB at 131072 tokens), --kv q4_0, the English draft subset (setup --draft-vocab en; the draft head takes 218 MiB now), a smaller --vram-reserve-mib (700 now; 300 is enough on a small card), images on the CPU, or close other programs that use the GPU
strata generate: the embedding buffer failed
```

The app's run record for that attempt (`%APPDATA%\Strata\profiles\.ops\app-1\.runs\641aa76c….json`) says `"error": "server_exited", "exit_code": 6`, and the next start (03:32:48, with `--resident-budget-gib 27`) came up fine and is serving now.

The same `ggml_cuda_init: failed to initialize CUDA: no CUDA-capable device is detected` string appears 12 times in that log (always immediately before a `load_hparams: Qwen-VL models require at minimum 1024 image tokens…` line), so it comes from a **second** process trying to init CUDA while the served engine holds the card, not from a driver that is missing the device: `nvidia-smi` on the same machine sees the card and reports `compute_mode Default`. I did not determine which process wrote each of those 12 lines (the tuning bench and the vision encoder both write into this log); `setup.py` line 1345-1354 already notes that the engine, the vision encoder and the tuning run cannot share the card.


The full sanitized `strata-iq3_s.log` and the original `history.jsonl` (14 rows) are available on request.

### Suggested small fixes (not tested)

- `failure_kind()`: check the fatal line first. `ggml_cuda_init: failed to initialize CUDA`, `no CUDA-capable device` and `CUDA-capable device(s) is/are busy or unavailable` should be `FAIL_BOOT`/CUDA-unavailable, with a message pointing at unloading the model or the busy card. Keep `OOM` for allocation failures only, and consider not letting the informational `memory guard:` line decide the status.
- `bad_signatures()`: do not blacklist permanently when the same signature has a later (or earlier) `PASS` with the same `hw`/`model`/`engine`, and clear the blacklist on the re-check events that `tuning/recheck.json` already records (`"reason": "engine_changed"`).
- Before the first measurement, check that the served engine is stopped and enough VRAM is free; the `tuning.lease` marker only blocks other tuning jobs, it does not look at the card.

### Workaround I used

Stopped/unloaded the model and other GPU users before tuning, and moved the 7 `OOM` rows out of `history.jsonl` (kept a backup as `history.jsonl.bak-20261021`) so the base signature is not skipped. I have not yet confirmed that tuning succeeds after this — I will add the result here.

### Searched before posting

`CUDA-capable`, `tuning`, `calibration`. #3144 (Windows calibration fails, `verify: an earlier window never finished on the GPU`), #3490 and #3459 (tuning hangs / `startup_wait_expired`) are tuning failures too, but none of them covers the `OOM` misclassification and the permanent signature blacklist.

Related on this site

Quick links to related pages.