// Ember Tune, the fleet side (docs/plans/ember-tune.md): the TUNE records every app uploads with its log are folded // into one prior per (card model, driver major, program class): the median chosen point, its spread and the sample // count. The publisher writes the priors into the signed manifest's `tuning` section beside the kernel-variant // cards (tools/tuning.mjs --write), the console shows them (api/console.mjs fn=tuning, tools/console.mjs tuning), // and the public bench table lists them per model (site/miner-priors.json). No dependencies; the tests in // relay/test/ember.test.mjs drive these functions with a fixture of captured records. // // A record (app/igneum-app/src/ember.rs record_json): {ts, machine (a hash of the install id), app, os, card, vendor, // driver, driver_major, class, key, plan: full|confirm|baseline, steps: [{clock_mhz, power_pct, limit_w, watts, mhs, // eff, gclk, mclk, tmax, faults, mark}], chosen: {...}, before: {...}|null, eff, mhs, watts}. Nothing identifies the // owner: no address, no hostname, no raw machine id. /// The TUNE records inside uploaded log text, de-duplicated on (machine, card, ts) because the log is re-sent every /// minute. Baseline records (measure only) are kept apart: they say what a card does untuned, never what to set. export function parseRecords(text) { const out = []; for (const line of String(text || '').split('\n')) { const i = line.indexOf('TUNE {'); if (i < 0) continue; let rec; try { rec = JSON.parse(line.slice(i + 5)); } catch { continue; } if (!rec || !rec.card || !rec.key || !rec.plan) continue; out.push(rec); } return out; } export function dedupe(records) { const seen = new Set(); const out = []; for (const r of records) { const k = `${r.machine}|${r.card}|${r.ts}`; if (seen.has(k)) continue; seen.add(k); out.push(r); } return out; } export const median = xs => { const s = xs.filter(x => Number.isFinite(x)).sort((a, b) => a - b); return s.length ? (s.length % 2 ? s[(s.length - 1) / 2] : (s[s.length / 2 - 1] + s[s.length / 2]) / 2) : 0; }; /// The median absolute deviation as a percent of the median (0 for one sample or a zero median). export function spreadPct(xs) { const m = median(xs); if (!m || xs.length < 2) return 0; return Number((median(xs.map(x => Math.abs(x - m))) / m * 100).toFixed(2)); } const usable = r => r && r.chosen && r.chosen.mark === 'ok' && r.chosen.eff > 0 && (r.plan === 'full' || r.plan === 'confirm' || r.plan === 'climb'); /// Folds records into priors: one per key, from the full and confirm records with a usable chosen point. The point /// is the median clock cap and the median power percent (each rounded to the step the apps use: 10 MHz, 1%), the /// efficiency, rate and draw are medians, the spread is the MAD of the efficiency in percent, `samples` counts the /// records and `machines` the distinct install hashes. An outlier (one bad card, one hot room) moves the median by /// at most one rank, never by its size. Baseline records are summarised beside the prior as `baseline` (median /// MH/W untuned) so the console can show the gain. export function aggregate(records, { minSamples = 1 } = {}) { const byKey = new Map(); for (const r of dedupe(records)) { const g = byKey.get(r.key) || { key: r.key, card: r.card, vendor: r.vendor || '', driver_major: r.driver_major || '', class: r.class || 'v2', tuned: [], baseline: [], machines: new Set() }; byKey.set(r.key, g); g.machines.add(r.machine); if (usable(r)) g.tuned.push(r); else if (r.plan === 'baseline' && r.chosen && r.chosen.eff > 0) g.baseline.push(r); } const priors = {}; const table = []; for (const g of byKey.values()) { const t = g.tuned; const row = { key: g.key, card: g.card, vendor: g.vendor, driver_major: g.driver_major, class: g.class, samples: t.length, machines: g.machines.size, baseline_samples: g.baseline.length, baseline_eff: g.baseline.length ? Number(median(g.baseline.map(r => r.chosen.eff)).toFixed(4)) : null, baseline_mhs: g.baseline.length ? Number(median(g.baseline.map(r => r.chosen.mhs)).toFixed(2)) : null, baseline_watts: g.baseline.length ? Number(median(g.baseline.map(r => r.chosen.watts)).toFixed(1)) : null, }; if (t.length) { const effs = t.map(r => r.chosen.eff); const prior = { clock_mhz: Math.round(median(t.map(r => r.chosen.clock_mhz)) / 10) * 10, power_pct: Math.round(median(t.map(r => r.chosen.power_pct))), // Ember 2: the memory clock (0 = the driver's default); older records carry none mem_mhz: Math.round(median(t.map(r => r.chosen.mem_mhz || 0)) / 10) * 10, eff: Number(median(effs).toFixed(4)), mhs: Number(median(t.map(r => r.chosen.mhs)).toFixed(2)), watts: Number(median(t.map(r => r.chosen.watts)).toFixed(1)), spread_pct: spreadPct(effs), samples: t.length, machines: g.machines.size, card: g.card, vendor: g.vendor, driver_major: g.driver_major, class: g.class, updated: new Date(Math.max(...t.map(r => Number(r.ts) || 0)) * 1000).toISOString().replace(/\.\d{3}Z$/, 'Z'), }; // the untuned reference: the full plan's first step (the power ladder's 100%), else the baseline records const befores = t.map(r => r.before && r.before.eff > 0 ? r.before.eff : null).filter(x => x !== null); if (befores.length) prior.before_eff = Number(median(befores).toFixed(4)); else if (row.baseline_eff) prior.before_eff = row.baseline_eff; if (prior.before_eff) prior.gain_pct = Number(((prior.eff / prior.before_eff - 1) * 100).toFixed(1)); Object.assign(row, prior); if (t.length >= minSamples) priors[g.key] = prior; } table.push(row); } table.sort((a, b) => (b.samples - a.samples) || (a.key < b.key ? -1 : 1)); return { priors, table }; } /// The manifest's tuning section with the priors folded in: the kernel-variant `cards` object is kept as is, /// `priors` replaces the previous priors (a key that lost its samples drops out), `ember` carries the settings. export function mergeTuning(existing, priors, ember = {}) { const base = existing && typeof existing === 'object' ? existing : {}; const cards = base.cards && typeof base.cards === 'object' && !Array.isArray(base.cards) ? base.cards : {}; const settings = { enabled: true, min_samples: 5, rate_tolerance_pct: 1, ...(base.ember && typeof base.ember === 'object' ? base.ember : {}), ...ember }; return { ...base, updated: new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'), cards, ember: settings, priors: priors || {} }; } /// A prior as a card starts from it (app/igneum-app/src/ember.rs prior_of): None under the sample floor. export function priorFor(tuning, key, minSamples) { const p = tuning && tuning.priors && tuning.priors[key]; const floor = Number.isFinite(minSamples) ? minSamples : (tuning && tuning.ember && tuning.ember.min_samples) || 5; if (!p || !(p.samples >= floor)) return null; return { clock_mhz: p.clock_mhz || 0, power_pct: Math.min(100, Math.max(50, p.power_pct || 100)), mem_mhz: p.mem_mhz || 0, eff: p.eff, samples: p.samples }; } /// One text line per prior for the console and the CLI. export function priorLine(p) { const point = p.clock_mhz ? `${p.clock_mhz} MHz at ${p.power_pct}%` : `${p.power_pct}% (clock unlocked)`; const gain = p.gain_pct != null ? ` (${p.gain_pct >= 0 ? '+' : ''}${p.gain_pct}% over untuned ${p.before_eff} MH/W)` : ''; return `${p.card.replace(/_/g, ' ')} | driver ${p.driver_major} | ${p.class}: ${point}, ${p.eff} MH/W${gain}, ${p.mhs} MH/s at ${p.watts} W, spread ${p.spread_pct}%, ${p.samples} sample(s) from ${p.machines} machine(s)`; }