Issues / #3417
#3417 Work resource profile sets --pool-workers from logical threads, not physical cores (more workers than cores on any SMT machine)
open · @squidwardboom · 1 commentaires · Sur GitHub
Description
## What happens
On any SMT/hyper-threaded machine, choosing the **"Work"** resource profile (the wording in the UI is *"We reserve GPU memory and limit CPU expert workers to leave headroom for other apps"*) produces a `--pool-workers` value that is **larger than the number of physical cores**.
On this PC:
| | value |
|---|---|
| CPU | AMD Ryzen 7 7800X3D, **8 physical cores / 16 logical** threads |
| profile chosen in the UI | `work` |
| `--pool-workers` the app writes | **12** |
| physical cores available | 8 |
| setup's own recommendation for this CPU | `--pool-workers 7` |
So the profile whose stated purpose is to *limit* CPU workers ends up adding workers — more than either the "Full speed" profile or no setting at all would produce.
The visible symptom is a **permanently pegged CPU** (100 % on all threads) while the model is loaded and generating, even while idle between requests.
## Where it comes from
Three small steps. Each one looks reasonable on its own.
**1. The Python hardware report counts logical processors.**
`control/tools/strata_mcp.py`, `Hardware._hardware()` (line 837 before the change):
```python
hw["cpu"] = {"name": name, "cores": os.cpu_count(), "avx2": avx2, "avx512": avx512}
```
`os.cpu_count()` is documented as the number of **logical** CPUs — 16 here. (`_mac_hardware()` at line 927 has the same shape.)
**2. The host treats that number as physical cores.**
`host/server/native-resources.mjs:11-16`, in `resourceSettings()`:
```js
const cores=Number(hardware?.cpu?.cores)||0;
if(profile==='work'&&cores>1){
const at=args.indexOf('--pool-workers'),previous=at>=0?Number(args[at+1]):0;
const workers=Math.max(1,Math.floor(cores*.8));remove('--pool-workers');
args.push('--pool-workers',String(previous>0?Math.min(previous,workers):workers));
}
```
`Math.floor(16*0.8)` = **12**.
That this field is meant to be *physical* cores is confirmed elsewhere in the same codebase — `host/server/engine.mjs:288` reads "CPU threads exceed physical cores (`hw.cpu.cores`)", and `host/server/catalog.mjs:63` says "cores: physical cores detected on this machine". Note also the fallback at `host/server/native-control.mjs:103-107`, `const cores=os.cpus().length`, which is logical as well, so the fallback path has the same result.
**3. The value is never checked against what the CPU can actually use.**
Nothing between these two functions compares the result with the physical core count. Strata already *knows* this is harmful, in two places that this path does not consult:
* `control/strata_control/tune_knobs.py:158` lists `--pool-workers` as a known-bad setting:
`{"flag": "--pool-workers", "test": "above_engine_default", "reason": "more helper threads than the engine chose made it 36% slower"}`
* `control/setup.py`'s `pool_workers_note()` warns, for exactly this configuration:
`the config asks for 12 CPU expert workers, more than its physical cores: extra workers can make Strata slower. Recommended: --pool-workers 7`
So the installer will warn a user who typed `--pool-workers 12` by hand, and the app will hand that same value to every user who picks the "Work" profile.
## Measured effect (same model, same prompt, one variable changed)
Model IQ3_S on a Radeon RX 7900 XTX. Same request each run (`max_tokens 256`, `temperature 0`), one warm-up run then one measured run. Only `--pool-workers` differs.
| | `--pool-workers 12` (app's `work` profile) | `--pool-workers 7` (this CPU's value) |
|---|---|---|
| generation | 27.85 tok/s | **38.12 tok/s** (+36.9 %) |
| `strata` CPU busy50 | 188.3 % | 95.8 % (−49 %) |
| `strata` CPU peak | 1162.3 % | 789.2 % (−32 %) |
| whole PC peak | 98.2 % | 83.1 % |
The **+36.9 %** matches the note in `tune_knobs.py:158` almost exactly: the app's "Work" profile reproduces, by default and on every SMT machine, the one setting upstream measured as 36 % slower.
## Suggested fix
Report physical cores. `strata_control.tune_machine.cpu()` already returns them (`{'model': 'AMD Ryzen 7 7800X3D 8-Core Processor', 'logical': 16, 'physical': 8, 'hybrid': False}`), so the change is local:
```python
# control/tools/strata_mcp.py — replacing both os.cpu_count() calls in the cpu dict
try:
import strata_control.tune_machine as tm
n = tm.cpu().get("physical")
if isinstance(n, int) and n > 0:
return n
except Exception:
pass
return os.cpu_count() or 1
```
With physical cores reported, `Math.floor(8*0.8)` = 6 workers, and the `Math.min(previous, workers)` already in `native-resources.mjs` keeps any hand-set value safer than that.
Two further hardening suggestions, separate from the fix above:
1. Have `resourceSettings()` clamp workers to the physical core count even when the OS cannot report one, so a bad hardware probe degrades to "no `--pool-workers`" instead of "one per thread".
2. Surface `pool_workers_note()`'s warning in the UI when the profile itself produces a setting that the note would complain about. Right now the app never sees it.
## Environment
* Strata **0.2.0-beta**, installed with the Windows app (`Strata-0.2.0-beta-windows-hip-rdna3.exe`), not from git.
* Windows 11, engine `0.1.45.3-windows-x86_64-hip-rdna3`.
* Ryzen 7 7800X3D (8c/16t) + Radeon RX 7900 XTX, 63 GB RAM.
* The resulting run configuration after picking "Work":
`--pool-workers 12 --vram-reserve-mib 4915 --resident-budget-gib 40`
## Aside worth knowing
Editing `install/strata-iq3_s.json` while the app is running does **not** stick. The host keeps the model configuration in memory and writes it back from there (`native-control.mjs:268`, and `resourceSettings()` runs again on every start), so any manual `--pool-workers` edit is regenerated from the profile on the next start. That is why the local workaround had to be the source change above, plus restarting the host: no amount of editing the JSON survives.Sur le site
Liens install, modèles, releases.