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>
9.7 KiB
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
- Scheduler (every 10 s): a card is due when
sweepis on, it is not pinned and its last sweep is older than 7 days (or never). "Sweep now" and--sweepqueue a card regardless. It starts only while the card ismining, 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). - 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.ps1through the existingrun_elevated(one UAC prompt), polling<app>\sweep\cmd.txttwice a second for<seq> <watts>lines,quitto end, and restoring the entry limit by itself after 20 idle minutes. - 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_pctbecomes 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. - Abort: the cap goes back to the limit in force before the sweep,
SWEEP abortedis 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 --checkpasses; the Rust side serialises the new fields). - The Windows build: checked on macOS only (
cargo testin the crate); the Windows-only paths are runtimecfg!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.