igneum/docs/plans/miner-eff.md
igneum-labs 7eed16a29a Pre-public scrub, the text pass (7 October 2026, 19:5x UK): no founder name, personal login, earlier business or personal address in any tracked text file, and a gate check that keeps it so
The sweep (main's item 1): 199 tracked text files, 783 lines. The founder's full name, first name and possessive become "the founder" (sentence starts capitalised); the lowercase operating-system user name in WSL paths and commands becomes <user>; the second owner login becomes "the second owner login"; the three earlier businesses and the two other brands become "the other business", "the earlier entity", "the earlier business" and "another brand"; the Chrome profile rule names the igneum.network profile, not the profile's label. The standing commit login igneum-labs is not a founder term here: the fresh-repository step renames it in the history (docs/plans/history-rewrite.md, tools/repo/fresh-repo.sh).

The patterns never appear in plain text in the tree (a plaintext list would be the hit): tools/ci/founder-strings.b64 (perl regex, tab, a sample per row) is read by tools/ci/founder-strings-check.sh (every tracked text file, perl, known-failed first: the self-test plants each row's sample in a fixture and the hit must name the file), by tools/community/discord-hooks.mjs (the guard's founder and business rows; the test takes its fixtures from the samples) and by tools/repo/fresh-repo.sh (the business names of the rewrite rules). site/forbidden-strings.txt carries the same patterns as b64: lines, decoded case-insensitive by site/scrub.mjs and tools/ci/launch-gates-check.mjs (whose fixture now plants an encoded made-up name). The check runs in the gate's tree checks on every merge.

Not in this commit, by main's word: the 105 commit messages and 40 personal-identity commits that need the history rewrite (listed, not run), and the secrets found by gitleaks over the history (reported with owners).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 18:39:50 +00:00

120 lines
9.7 KiB
Markdown

# Hash per watt: the efficiency sweep (lever 3)
4 October 2026, evening. The founder: "make our miner better than anything else can be". Miners pay for electricity; the
number they compare is MH per watt. The app already caps NVIDIA cards (default 80% of the default limit, `nvidia-smi
-pl`, readback, Retry). This adds the sweep that finds the best cap per card, the live MH/W on every tile, a `--sweep`
mode for the engine, and the PC 1 measurement job. Branch `miner-eff`, worktree `../igneum-wt-eff`.
## What is built
| Piece | Where | State |
|---|---|---|
| Sweep logic: step plan (100% to 50% in 10% steps, clamped to the card's min and max, duplicate watts dropped), per-step rows, the choice (best MH/W; within 1% the higher rate; within 1% on both the lower cap), the nvidia-smi power parser, the state machine on an explicit clock, the elevated helper scripts, the unsupported reasons | `app/igneum-app/src/sweep.rs` | 9 unit tests pass on the Mac (36 in the crate) |
| Engine: scheduler (one card at a time, once after install then weekly, never under a remote job hold, a pause, or inside 600 s of the hour boundary; the card must have mined 120 s), the cap-mode probe (direct when the engine is elevated, else one elevated helper polling a command file: one administrator prompt per sweep, not six), fault abort (card leaves "mining", worker error line, GPU 90 C, job, pause, quit), restore on abort, record and hold on finish, `--sweep` mode, the table in the app log and on stdout | `app/igneum-app/src/engine.rs` (`tick_sweep`, `sweep_*`, `Cmd::Sweep*`) | compiles; not run on a card |
| Settings: `sweep` (default on), `installed_at`, per card `pinned`, `sweep_at`, `sweep_pct`, `sweep_eff`, `sweep_watts`, `sweep_mhs`; cap floor 50% (was 60) | `config.rs`, `detect.rs`, `state.rs`, `server.rs` (`/api/sweep/start`, `stop`, `pin`, `enable`) | |
| Dashboard: `eff <MH/W>` on the tile's telemetry line (live hash over draw), the sweep line under it (phase while running, else "best N% · x MH/W (rate at watts, when)", or why unsupported), Sweep now / Stop / Unpin, the cap line says "pinned" or "chosen by the sweep"; Settings toggle "Find each NVIDIA card's best MH per watt (once after install, then weekly)"; the cards page note; slider min 50 with a "pinned" / "sweep N%" tag | `app/igneum-app/ui/` | |
| Readback self-heal: a cap that matches what was asked counts as applied when the telemetry reads it back (PC 1's log said "cap NOT applied" for hours while the limit read 460 W) | `engine.rs` `telemetry_line` | |
| The PC 1 job script | `relay/playbooks/sweep-5090.ps1` | parse-checked by `windows.yml`; UNTESTED on a PC |
The window hosts (Swift, WebView2) are untouched: no host string changes.
## The log lines
SWEEP start card=nvidia-ae432dc7-1 name=NVIDIA_GeForce_RTX_5090 steps=100,90,80,70,60 default=575 min=400 max=600 before=460 mode=direct
SWEEP card=nvidia-ae432dc7-1 cap=100 limit=575 watts=290.4 mhs=124.10 eff=0.4273
SWEEP card=nvidia-ae432dc7-1 cap=90 limit=518 watts=290.1 mhs=124.00 eff=0.4274
...
SWEEP chosen card=nvidia-ae432dc7-1 cap=70 limit=403 watts=289.9 mhs=123.90 eff=0.4274
SWEEP aborted card=nvidia-ae432dc7-1 reason=the_card_left_mining_(worker_restart,_likely_the_hour_boundary)
`watts` is the mean draw during the 60 s hold (what the miner pays for), `limit` the cap set, `mhs` the mean of the
worker's interval rates (`now=` in the STATUS lines) during the hold, `eff` = mhs / watts. A step with under 3 draw
readings or no rate prints `eff=0 reason=no_readings` and cannot win. The numbers above are the shape, not a
measurement.
## How a sweep runs
1. Scheduler (every 10 s): a card is due when `sweep` is on, it is not pinned and its last sweep is older than 7 days
(or never). "Sweep now" and `--sweep` queue a card regardless. It starts only while the card is `mining`, its
worker has run 120 s, no remote job holds the GPU, nothing is paused, and over 600 s remain to the hour boundary
(the tile says what it waits for).
2. Probe: one `nvidia-smi -i <dev> -pl <current limit>` from the engine. "All done" means this process may set caps
(it runs elevated, as the PC job does); otherwise the helper starts: `powershell -File <app>\sweep\helper.ps1`
through the existing `run_elevated` (one UAC prompt), polling `<app>\sweep\cmd.txt` twice a second for
`<seq> <watts>` lines, `quit` to end, and restoring the entry limit by itself after 20 idle minutes.
3. Each step: set the cap, wait for the telemetry readback (`power.limit`, every 5 s) to match (30 s limit), settle
15 s, hold 60 s collecting draws and rates, write the row. Then the chosen cap is set and read back, the card and
the settings record it (`power_pct` becomes the chosen percent unless the card is pinned), the helper is told to
quit. The worker is never restarted; the hour's program is never lost.
4. Abort: the cap goes back to the limit in force before the sweep, `SWEEP aborted` is logged, the card waits an
hour before the scheduler tries again (under `--sweep`: 30 s, up to 3 attempts).
`IGNEUM_APP_SWEEP_FAST=1` makes a step 2 s settle + 6 s hold (plumbing check only; its numbers mean nothing).
## The PC 1 measurement (do not publish from this branch; the main session schedules PC jobs)
The job runs `relay/playbooks/sweep-5090.ps1` elevated with the installed app's miners stopped and held. The script
starts a second engine from the same install with `--sweep` in `%LOCALAPPDATA%\igneum-sweep` (the real
`settings.json`, `machine-id` and `wallet.json` copied in; remote jobs, auto-update and proving switched off in the
copy so the second engine cannot run this job again or update itself). That engine finds the installed app's node on
127.0.0.1:26610 (external), exports the pack, mines on the 5090 with `--status-secs 10` (6 rate samples per hold),
sweeps, prints the table on stdout, leaves the chosen cap in force and quits. The script re-emits every `SWEEP` line
as `RESULT SWEEP ...`, adds `RESULT SWEEP before ...` / `after ...` (the nvidia-smi limits around the run) and the
sweep engine's log tail. Budget 40 minutes (worst case: 10 minutes waiting for the hour boundary, 2 minutes steady,
5 steps of about 80 s). The installed app's miners restart when the job ends.
Needs: the build that carries `src/sweep.rs` installed on PC 1 (ship it with `tools/ship-app.mjs`; the OTA applies it,
or `publish-jobs.sh add --kind update-now --target ae432dc7`). An older engine ignores `--sweep` and the script
reports `no_rows`.
Publish command (from the repo root, the main session, after the version is on PC 1):
packaging/ota/publish-jobs.sh add --kind run --target ae432dc7 --platform windows --requires nvidia \
--script relay/playbooks/sweep-5090.ps1 --elevated --stop-miners --timeout-minutes 45 \
--title "Efficiency sweep on the RTX 5090 (PC 1)" --deploy
Reading it back:
node tools/jobs.mjs watch <job id> every 30 s until final; the RESULT SWEEP lines are the table
node tools/jobs.mjs <job id> the latest report
## What PC 1's own log already says (4 October 2026, run win-ae432dc7-20261004-164723)
stability: NVIDIA GeForce RTX 5090: draw p95 290 W, max 295 W, limit 460 W (cap NOT applied), max GPU 65 C, max memory 0 C
The 5090 draws 290 W under a 460 W limit (80% of 575 W). The cap does not bind at any step down to the card's floor
(the 5090's `power.min_limit` is expected near 400 W; the sweep reads the real value and clamps). Expect a flat table:
the same draw and rate at every step, and the choice falling to the lowest cap by the tie rule. That is a result in
itself: for this kernel the power cap is a safety, not an efficiency lever. The lever would be the clocks
(`nvidia-smi -lgc` / `-lmc`, core and memory offsets), which this sweep does not touch; a clock sweep is the next
step once the table confirms the flat line. "memory 0 C" is `temperature.memory` reading `[N/A]` on this driver.
## Unit tests (`cargo test -p igneum-app sweep`)
- `plan_clamps_to_the_card_floor_and_drops_duplicates`: 575 / 400 / 600 W plans 100, 90, 80, 70, 60 (60% and 50%
both clamp to 400 W; one kept).
- `parses_nvidia_smi_power_draw`: the recorded telemetry shape from PC 1 (`0, 290.12`), CRLF, `[N/A]`, with units and
a header, the driver-failure text, empty.
- `chooses_the_best_mh_per_watt_then_rate_then_the_lower_cap`: a card that improves then collapses; PC 1's flat case;
the 1% ties; unusable rows never win.
- `row_lines_carry_the_table_fields`, `state_machine_runs_a_sweep` (five steps with a 4 s readback lag, 395 to 420 s),
`state_machine_fails_when_a_cap_never_takes`, `state_machine_fails_without_readings`, `unsupported_reasons`,
`helper_scripts_carry_the_protocol`.
## Untested
- The real sweep on a card: the probe, the helper and its UAC prompt, the readback timing, the table. Only the PC job
above measures it.
- The second-engine arrangement of the job (two engines from one install, one holding its miners).
- The dashboard rendering (the UI was edited by hand; `node --check` passes; the Rust side serialises the new fields).
- The Windows build: checked on macOS only (`cargo test` in the crate); the Windows-only paths are runtime `cfg!`
branches, so they compile here too, but the GitHub runner has not built this branch.
- AMD on Windows: reported unsupported with the reason (no power reading or cap in the app). Apple silicon: unsupported
(no cap to set; powermetrics needs root).
## Merge notes for the other miner agents
Touched outside the new module: `engine.rs` (new `Cmd` variants, engine fields, the sweep block before the tick, one
line each in `miner_line` STATUS and `telemetry_line`, `derive`, `shutdown`, the `apply_power_limits` guard, the
50% clamps), `config.rs`, `state.rs`, `detect.rs`, `server.rs`, `main.rs`, `ui/`. Nothing in the workers, the manifest,
the payout, the template path or the fault guards.