From 07f04243d5f8d53c5eb5decf3f2140e347dda7b3 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:53:43 +0000 Subject: [PATCH] =?UTF-8?q?Ember=202:=20the=20plan's=20section=206b=20(the?= =?UTF-8?q?=20clocks,=20the=20hill-climb,=20the=20goal,=20the=20price,=20t?= =?UTF-8?q?he=20curve,=20the=20fleet=20prior,=20what=20is=20owed);=20Setti?= =?UTF-8?q?ngs:=20the=20goal,=20the=20electricity=20price,=20the=20climb?= =?UTF-8?q?=20switch;=20the=20card=20detail:=20=C2=A3=20a=20day=20for=20th?= =?UTF-8?q?e=20tuned=20point=20and=20the=20measured=20curve;=20UI=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- app/igneum-app/ui/app.js | 20 +++++++++++++++++++- app/igneum-app/ui/index.html | 3 +++ app/igneum-app/ui/tune-line.test.mjs | 5 +++++ docs/plans/ember-tune.md | 26 ++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/app/igneum-app/ui/app.js b/app/igneum-app/ui/app.js index 5b8c301d..1df3cc39 100644 --- a/app/igneum-app/ui/app.js +++ b/app/igneum-app/ui/app.js @@ -441,7 +441,9 @@ var TuneLine = (function () { return { kind: 'idle', text: cd.sweep_note || 'tuning: not run yet (starts after 120 s of steady mining)' }; } function rel(s) { s = Math.max(0, Math.floor(s)); return s < 60 ? s + ' s' : s < 3600 ? Math.floor(s / 60) + ' min' : s < 86400 ? Math.floor(s / 3600) + ' h' : Math.floor(s / 86400) + ' d'; } - return { model: model, point: point, rel: rel }; + // Ember 2: £ a day for a draw at pence per kWh (watts × 24 / 1000 × price / 100) + function pounds(watts, pence) { var v = watts * 24 / 1000 * pence / 100; return '£' + v.toFixed(2); } + return { model: model, point: point, rel: rel, pounds: pounds }; })(); if (typeof module === 'object' && module && module.exports) { module.exports = Notices; module.exports.UpdateCard = UpdateCard; module.exports.View = View; module.exports.TuneLine = TuneLine; module.exports.tuneNotice = Notices.tuneNotice; } @@ -630,6 +632,9 @@ if (typeof document !== 'undefined') (function () { $('s-login').addEventListener('change', function () { var on = this.checked; api('api/settings', { start_at_login: on }).then(function (r) { if (!r.ok) { toast(r.error || 'could not change'); $('s-login').checked = !on; } }); }); $('s-jobs-allow').addEventListener('change', function () { api('api/jobs/allow', { on: this.checked }); }); $('s-sweep').addEventListener('change', function () { api('api/sweep/enable', { on: this.checked }).then(function (r) { if (r.ok) toast($('s-sweep').checked ? 'Ember Tune on: once after install, then weekly' : 'Ember Tune off'); }); }); + $('s-tune-goal').addEventListener('change', function () { var g = this.value; api('api/tune/goal', { goal: g }).then(function (r) { if (r.ok) toast('Goal: ' + g); }); }); + $('s-power-price').addEventListener('change', function () { var p = parseFloat(this.value); if (isNaN(p)) return; api('api/tune/goal', { price_pence: p }).then(function (r) { if (r.ok) toast('Electricity: ' + p + ' p/kWh'); }); }); + $('s-tune-climb').addEventListener('change', function () { var on = this.checked; api('api/tune/goal', { climb: on }).then(function (r) { if (r.ok) toast(on ? 'Ember 2: the clocks climb from the next tune' : 'The ladders run from the next tune'); }); }); $('s-power-control').addEventListener('change', function () { var on = this.checked; api('api/power/control', { on: on }).then(function (r) { if (r.ok) toast(on ? 'Power control on: Windows asks for administrator rights once' : 'Power control off; nothing asks'); }); }); $('s-trust').addEventListener('change', function () { var on = this.checked; api('api/settings', { proof_verify_trust: on }).then(function (r) { if (r.ok) toast(on ? 'Trust mode on (devnet only); the node restarts' : 'Trust mode off; the node restarts'); else { toast(r.error || 'could not change'); $('s-trust').checked = !on; } }); }); $('s-live').addEventListener('click', function () { if (state && state.live_page) api('api/open', { url: state.live_page }); }); @@ -1177,6 +1182,10 @@ if (typeof document !== 'undefined') (function () { sw('s-jobs-allow', s.jobs && s.jobs.allowed); sw('s-sweep', s.settings.sweep); sw('s-trust', s.settings.proof_verify_trust); sw('s-power-control', s.settings.power_control); setText('s-power-note', s.settings.tuning_off ? s.settings.tuning_note : (s.settings.power_note || (s.settings.power_control ? '' : 'Off: NVIDIA cards measure only; AMD cards need no rights.'))); setText('s-devfee-text', View.devFeeText(s)); + if (document.activeElement !== $('s-tune-goal')) $('s-tune-goal').value = (s.settings.tune_goal || 'balanced'); + if (document.activeElement !== $('s-power-price')) $('s-power-price').value = s.settings.power_price_pence > 0 ? s.settings.power_price_pence : ''; + setText('s-power-price-note', s.settings.power_price_pence > 0 ? 'the card rows read in £ a day' : 'set it to see £ a day per card'); + sw('s-tune-climb', s.settings.tune_climb); if (document.activeElement !== $('s-name')) $('s-name').value = s.display_name || ''; setText('s-mid', (s.machine_id || '').slice(0, 8)); setText('s-node-dir', 'chain data: ' + s.node_dir); @@ -1200,6 +1209,7 @@ if (typeof document !== 'undefined') (function () { } // Ember Tune's line (TuneLine.model): running, tuned, measured, stopped or not run yet, for every card h += tuneLineHtml(cd); + h += tuneCurveHtml(cd, state && state.settings ? state.settings.power_price_pence : 0); h += '
Identities

Each identity votes and is paid on its own. 8 suits a big card, 2 a small one, 1 an integrated GPU. Changing it restarts that card’s worker.

'; return h + ''; } @@ -1211,6 +1221,14 @@ if (typeof document !== 'undefined') (function () { var btn = running ? '' : '' + (cd.pinned ? ' ' : ''); return '
' + t + ' ' + btn + '
'; } + // Ember 2: £ a day for the tuned point and the measured curve (every row of the last plan) + function tuneCurveHtml(cd, price) { + var h = ''; + if (price > 0 && cd.sweep_watts > 0) h += '
' + esc(TuneLine.pounds(cd.sweep_watts, price)) + ' a day at this point' + (cd.power_w > 0 && Math.abs(cd.power_w - cd.sweep_watts) > 5 ? ' · ' + esc(TuneLine.pounds(cd.power_w, price)) + ' at the draw now' : '') + '
'; + var c = cd.tune_curve || []; + if (c.length > 1) h += '
curve: ' + c.map(function (r) { return esc((r.clock_mhz ? r.clock_mhz + ' MHz' : 'unlocked') + (r.mem_mhz ? ' mem ' + r.mem_mhz : '') + ' ' + r.power_pct + '%: ' + Number(r.mhs).toFixed(1) + ' MH/s ' + Math.round(r.watts) + ' W ' + Number(r.eff).toFixed(3) + (r.mark && r.mark !== 'ok' ? ' (' + r.mark + ')' : '')); }).join(' · ') + '
'; + return h; + } function renderSetCards(s) { var cards = shownCards(s.mining.cards), box = $('s-cards'); if (box.querySelector('input[type=range]:active') || box.contains(document.activeElement)) return; diff --git a/app/igneum-app/ui/index.html b/app/igneum-app/ui/index.html index 7b423fdf..4695883e 100644 --- a/app/igneum-app/ui/index.html +++ b/app/igneum-app/ui/index.html @@ -369,6 +369,9 @@
No card yet.

Once after install, then weekly and after a driver or program change: the power limit steps from 100% down to 50%, then the core clock from its maximum down to 60%, 75 s a step on the live program; the memory clock is never touched. The card keeps the point with the most hashes per watt within 1% of its top rate. A step with a rejected hash, a hot GPU or a dragged memory clock is reverted. A card whose model the fleet already knows starts at that point and confirms it in two steps. Every result goes back to the fleet without anything that identifies you. A cap you set by hand is left alone.

+
Goal
+
Electricity
+

Windows asks for administrator rights once; the NVIDIA cap and the tune need them. Off, the app never asks and NVIDIA cards measure only. AMD cards need no rights.

diff --git a/app/igneum-app/ui/tune-line.test.mjs b/app/igneum-app/ui/tune-line.test.mjs index 18fafce3..ff684f4f 100644 --- a/app/igneum-app/ui/tune-line.test.mjs +++ b/app/igneum-app/ui/tune-line.test.mjs @@ -75,3 +75,8 @@ test('a card never shows 0 MH/s without a reason word: tuning, held and tuned ro assert.equal(View.tuneEta(30), 'under a minute'); assert.equal(View.tuneEta(700), 'about 12 min'); }); + +test('Ember 2: £ a day from watts and pence per kWh', () => { + assert.equal(mod.exports.TuneLine.pounds(310, 28.5), '£2.12'); + assert.equal(mod.exports.TuneLine.pounds(102.7, 28.5), '£0.70'); +}); diff --git a/docs/plans/ember-tune.md b/docs/plans/ember-tune.md index 40bc4be9..6d771c17 100644 --- a/docs/plans/ember-tune.md +++ b/docs/plans/ember-tune.md @@ -176,6 +176,32 @@ live MH/s and W, the seconds left and the plan; a POST of state, never a quit, p reads `Tuned: at W ()` with the point and the plan. Tests: `ui/tune-line.test.mjs` (tuning, held, tuned rows; the button; the strip line). +## 6b. Ember 2: the clocks, the hill-climb, the goal (6 October 2026) + +Why: run 5 measured the 5090's power ladder flat (310 to 314 W under every cap from 575 to 400 W, 0.41 MH/W), so +on this memory-latency-bound hash the power cap is a safety, not a lever; the pair that matters is the memory +clock UP and the core clock DOWN. Ember 2 adds both and replaces the fixed ladder with a hill-climb. + +| Piece | What | Where | +|---|---|---| +| The memory knob | NVIDIA: `nvidia-smi -lmc ,` locks the memory clock to one value, `-rmc` resets; the range is the driver's own (`clocks.mem` under load = the floor, `clocks.max.mem` = the ceiling; PC 1's 5090: 13,801 to 14,001 MHz); directly from an elevated engine or through the Power Helper's `lmc`/`rmc` verbs. AMD: ADLX on RDNA 4 exposes no memory setter through the helper's path, so an AMD card climbs on core and power only (the row says so). Linux NVIDIA: `-lmc` through pkexec as the cap is | `Point.mem_mhz`, `Limits.mem_default_mhz`, `Limits.mem_max_mhz`, `tune_apply`, `powertask::HelperCmd::MemClock`, `sweep::helper_script_*` | +| The hill-climb | from the start point (the fleet prior for the model, else the card's point): each probe moves memory up by 5% of the range above the default (at least 25 MHz) or core down by 5% of the maximum (at least 25 MHz); a probe that improves the goal's score keeps that direction, else the climb turns to the other knob, then to both; 5 probes of 60 s (10 s settle) converge in under 10 minutes | `Plan::climb`, `Climb`, `Plan::climb_next` | +| The stability guard | a rejected or mismatched hash during a probe marks it `faulted`; a faulted probe backs that knob off for good in this climb (memory never goes above, core never below, the last good point); the hash fingerprint is the worker's own self-test at every pack (a variant that is not bit-exact never serves), so a step that keeps mining with 0 mismatches is stable by the only test that matters | `Row::from_samples`, `climb_next`'s `refused_mem` / `refused_core`, docs/design/miner-tuning.md | +| The goal | Settings > goal: `efficiency` (the most MH/W within 10% of the top rate), `balanced` (within 1%, lever 3's rule), `rate` (the fastest point; MH/W breaks ties); one goal for every card | `Goal`, `Settings.tune_goal`, `POST /api/tune/goal {goal, price_pence, climb}` | +| The price | Settings > electricity, pence per kWh: the card's row reads the chosen point as £ a day (`watts × 24 / 1000 × price / 100`: 310 W at 28.5 p = £2.12) | `pounds_per_day`, `Settings.power_price_pence`, the UI | +| The curve | every row of the last plan on the card (`tune_curve`: point, MH/s, W, MH/W, clocks, hottest reading, mark) so a manual tuner sees the points the climb measured | `CardState.tune_curve`, the card detail | +| The fleet | the record's `steps` already carry the curve; the prior gains `mem_mhz` (the median memory clock, 10 MHz steps); a stranger's card of a known model starts its climb at the prior's point | `relay/lib/ember.mjs`, `ember::prior_of` | +| Re-check | weekly (the manifest's period), after a driver major or program-class change, and when the card's temperature band changes (owed: the band trigger) | `tick_sweep` | + +Tests: `ember::tests::the_climb_walks_memory_up_and_core_down_and_converges_in_five_probes` (a synthetic +memory-bound card: every probe moves memory up or core down inside the range, the chosen point beats the start in +five probes), `a_refused_probe_backs_that_knob_off_for_good`, `each_goal_picks_its_point` (and the £/day formula), +`powertask::tests` (the `lmc`/`rmc` verbs reach nvidia-smi as `-lmc m,m` and `-rmc`), `sweep::tests` (the helper +scripts carry them), `relay/test/ember.test.mjs` (climb records aggregate into a prior with the memory clock). + +Owed: the measured comparison on PC 1 (one climb per card against run 5's ladder rows, no prompt through the +Power Helper), the temperature-band trigger, NVAPI/nvidia-settings for cards whose driver refuses `-lmc`. + ## 7a. One administrator approval, ever (0.3.13; the project lead, 6 October 2026, 11:50 UTC) What 0.3.12 does: Power control on raises one prompt and sets every cap in that step; every later cap (an app start, a