Pull requests / #2838

#2838 serve: cap the expert arena at a share of RAM ("arena_budget"), and a STOP.bat that only stops Strata

open · @regdiliKUN · 0 comentarios · En GitHub

BenchmarksSetup & installServer & APINVIDIA / CUDADocumentationWindows

Descripción

## What this changes

Two things, both from one machine's problem: a 63.7 GiB PC with a 68 GB IQ2_XS model that could not hold its own expert arena.

### 1. `"arena_budget"` — a cap on the arena, as a share of RAM (opt-in)

The engine sizes the arena from what the machine has free at start, which is the wrong size on a PC that is also used for something else. On the machine above it asked for 33.02 GiB and said so:

```
WARNING: the expert arena needs 33.02 GiB of RAM but 26.21 GiB is available (6.81 GiB short):
         the load may swap or be stopped by the OS
```

and from there the load swapped, `cudaHostRegister` of the whole arena failed, the large pages were refused (`VirtualAlloc error 1450`) and the run fell back to 4 KB pages. Decode ran beside 2.6 MB/s of disk and 43% CPU.

A **fixed** number is the wrong shape for a fix — 28 GiB is generous on a 32 GB PC, wasteful on a 128 GB one, and wrong again the moment the PC is used for something else. So the key is a share of physical RAM, and the GiB are computed at every start from the RAM that PC actually has:

```json
"arena_budget": 0.45
"arena_budget": {"share": 0.45, "min_gib": 6, "headroom_gib": 8}
"arena_budget": "auto"
```

| key | default | range | what it is |
|---|---|---|---|
| the whole key | `"auto"` (an absent key is the same) | `"auto"`, `true`, a number, or an object | `"auto"`/absent: no cap, the engine sizes the arena itself |
| `share` | `0.45` (also what `true` means) | `0.10`–`0.85` | the share of physical RAM the arena may take |
| `min_gib` | `6` | `>= 1` | the floor |
| `headroom_gib` | `8` | `>= 0` | the RAM the rest of the PC keeps |

The cap is `clamp(share × total RAM, min_gib, total RAM − headroom_gib)`, rounded **down** to whole GiB, so it follows the hardware: `0.45` is 7 GiB on a 16 GB PC, 14 on 32 GB, 28 on 64 GB, 57 on 128 GB. `min_gib` never beats `headroom_gib`: on a PC too small for both, the cap is skipped rather than made impossible.

**How it takes effect.** Computed at start and passed as the engine's own `--resident-budget-gib N`. `--resident-budget-gib` alone — `generate.cpp:2223` already sets `mmap_experts` / `resident_cpu_experts` / `resident_pin` when it parses it, so `--mmap-experts` would be redundant, and it is refused beside some pack layouts. Read at start, so a change is used from the next start; the server prints the arithmetic once:

```
[strata] arena_budget: 0.45 x 63.7 GiB of RAM = 28 GiB for the expert arena -> --resident-budget-gib 28; the rest of the PC keeps 35.7 GiB
```

A value out of range, of the wrong type or with an unknown key is said once and then ignored, with the engine sizing the arena as before: a typo never stops a start.

### 2. `STOP.bat` / `stop-strata.ps1` — stop Strata, and only Strata

Replaces the recipe it was written to replace, which ends **every** `python.exe` on the PC and with it a Jupyter kernel, a build script or another model server:

```bat
for /f "tokens=2" %%a in ('tasklist ^| findstr "python.exe"') do taskkill /f /pid %%a
```

A process is now stopped only when one of two rules names it:

1. **launcher** — the python running *this install's* `serve\server.py` (its own interpreter inside the install, or the install's absolute script path in the command line);
2. **engine** — `strata.exe` / `strata-vision.exe` whose executable sits inside the install.

The launcher goes first, so a supervisor cannot restart the engine behind us. Each process gets a plain `taskkill` (`WM_CLOSE`) and only then `/F`. A process whose path cannot be read is reported and left alone rather than guessed. No match prints `没有找到正在运行的 Strata` and exits 0, so running it twice is safe. `-WhatIf` lists what would be stopped and stops nothing; `-Root` points it at another install.

## What it leaves alone

- **The default path is byte-identical.** An absent `arena_budget` adds nothing to the engine's arguments — `serve/test_arena_budget.py::Wiring::test_engine_args_without_the_key_is_byte_identical` asserts exactly that.
- A `--resident-budget-gib` already written into the config's `"args"` is **never** overridden: the number written by hand wins.
- No other config key is touched, and nothing is written back to the file.
- `warn_budget_over_ram` (#1080) is unchanged and still reports a share that ends up larger than the RAM this PC has free.
- Nothing in `src/`, `include/` or the setup path changes: no engine rebuild, no backend touched.

## How it was checked

- `python -m unittest serve.test_arena_budget` — 22 pass, no GPU and no model needed (shares on 16/32/64/128/256 GB, both clamps, the floor never beating the headroom, out-of-range and wrong-type refusals, a hand-written budget winning, the line said once per start, and the `engine_args` wiring).
- `python -m unittest serve.test_runconfig` — 9 pass (no regression in the Settings view).
- `from serve import server` imports clean; `engine_args({"args": [...]})` without the key returns the list unchanged.
- **The stop script, against a fake install:** a launcher and an engine placed under the root were both stopped, launcher first, exit code 0. A decoy `python.exe` whose command line mentions `serve\server.py` and the install path but whose executable lives outside the install was **left running**. A second run found nothing and exited 0. The decoy case is the regression that the first version of the rule failed, which is why rule 1 now needs the full absolute script path, not just the install path somewhere in the command line.
- Both scripts were parsed with `[System.Management.Automation.Language.Parser]::ParseFile` (0 errors) and are UTF-8 with BOM (`stop-strata.ps1`) / pure ASCII (`STOP.bat`), CRLF.

**Not tested:** a real engine start with the cap on a second machine, and the stop script against a genuine `strata.exe` (the engine had already exited when the fake-install run was made). The cap's effect on decode speed on this model is the trade the user chooses — the numbers in `bench/results/2026-10-01-iq4xs-64gb` say a RAM budget costs ~30% against a warm mapped run at that size, which is why this stays opt-in rather than a new default.

## Files

| file | |
|---|---|
| `serve/arena_budget.py` | new — the share, the two clamps, the line, the argument |
| `serve/test_arena_budget.py` | new — 22 tests |
| `serve/server.py` | +2 lines: the import, and one call in `engine_args` |
| `STOP.bat`, `stop-strata.ps1` | new — stop Strata, and only Strata |
| `docs/DETAILS.md` | the key, its defaults, its range, how it takes effect |
| `docs/AI_SETUP.md` | stopping the server on Windows |

En el sitio

Enlaces a install, modelos, releases.