Site: the GPU bench table sorts on every header (default MH per watt), shows the current class (31 rows, class v4 or class v3 re-measured with its v4 cost) and folds the six earlier-class rows into a collapsed section, with the note on why the rate fell from the genesis program to class v3 and v4 (the project lead, 7 October 2026)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e0a294cf04
commit
81ebffa1b6
2 changed files with 75 additions and 14 deletions
|
|
@ -411,17 +411,49 @@ for (const [file, active] of PAGES) {
|
|||
}
|
||||
|
||||
// the public bench table (/miners): one row per card, generator version and miner version, from site/miner-bench.json
|
||||
// (the bench-log numbers that exist today, and rows later jobs append); rendered through the same scrub as /bench
|
||||
// (the bench-log numbers that exist today, and rows later jobs append); rendered through the same scrub as /bench.
|
||||
// 7 October 2026 (the project lead): the CURRENT class (rows measured on the class v4 program, or class v3 rows re-measured with their
|
||||
// class v4 cost, 6 October on) is the table; the earlier classes (the genesis program, the hourly program, class v3 before
|
||||
// the shadow) sit collapsed below so no reader compares a 228 MH/s genesis row with a 136 MH/s class v3 row as one thing.
|
||||
// Every header sorts on a click (default MH per watt, high first); the sort is a few lines of script in the page.
|
||||
{
|
||||
const bj = JSON.parse(readFileSync(join(here, 'miner-bench.json'), 'utf8'));
|
||||
const rows = bj.rows.slice().sort((a, b) => (a.card < b.card ? -1 : a.card > b.card ? 1 : a.generator < b.generator ? -1 : a.generator > b.generator ? 1 : b.mh_s - a.mh_s));
|
||||
const isCurrent = (r) => r.generator === 'v2' && r.date >= '2026-10-06';
|
||||
const byMhW = (a, b) => ((b.mh_per_w ?? -1) - (a.mh_per_w ?? -1)) || (b.mh_s - a.mh_s) || (a.card < b.card ? -1 : 1);
|
||||
const cur = bj.rows.filter(isCurrent).sort(byMhW);
|
||||
const earlier = bj.rows.filter(r => !isCurrent(r)).sort((a, b) => (a.card < b.card ? -1 : a.card > b.card ? 1 : a.generator < b.generator ? -1 : a.generator > b.generator ? 1 : b.mh_s - a.mh_s));
|
||||
const rows = bj.rows;
|
||||
const fmt = (n) => Number(n).toLocaleString('en-GB', { maximumFractionDigits: 1 });
|
||||
const fmt3 = (n) => Number(n).toLocaleString('en-GB', { maximumFractionDigits: 3 });
|
||||
const cell = (r) => [
|
||||
r.card, r.generator, fmt(r.mh_s), r.watts == null ? 'not read' : fmt(r.watts), r.mh_per_w == null ? 'not measured' : fmt3(r.mh_per_w), r.v4_cost || 'not measured', r.tuned || 'stock, mining', r.miner + (r.driver_os ? ' (' + r.driver_os + ')' : ''), r.date, r.source, r.by + (r.note ? '. ' + r.note : ''),
|
||||
const heads = [
|
||||
['Card', 'card', 'text'], ['Generator', 'generator', 'text'], ['Best MH/s', 'mh_s', 'num'], ['Watts', 'watts', 'num'], ['MH per watt', 'mh_per_w', 'num'],
|
||||
['Class v4 cost', 'v4_cost', 'text'], ['Tuned', 'tuned', 'text'], ['Miner', 'miner', 'text'], ['Date', 'date', 'text'], ['Source', 'source', 'text'], ['Who measured it', 'by', 'text'],
|
||||
];
|
||||
const table = '<div class="tbl"><table><thead><tr>' + ['Card', 'Generator', 'Best MH/s', 'Watts', 'MH per watt', 'Class v4 cost', 'Tuned', 'Miner', 'Date', 'Source', 'Who measured it'].map(h => `<th>${h}</th>`).join('') + '</tr></thead><tbody>' +
|
||||
rows.map(r => '<tr>' + cell(r).map(c => `<td>${esc(String(c))}</td>`).join('') + '</tr>').join('') + '</tbody></table></div>';
|
||||
const cells = (r) => [
|
||||
[r.card, r.card], [r.generator, r.generator], [fmt(r.mh_s), r.mh_s], [r.watts == null ? 'not read' : fmt(r.watts), r.watts ?? -1], [r.mh_per_w == null ? 'not measured' : fmt3(r.mh_per_w), r.mh_per_w ?? -1],
|
||||
[r.v4_cost || 'not measured', r.v4_cost || ''], [r.tuned || 'stock, mining', r.tuned || ''], [r.miner + (r.driver_os ? ' (' + r.driver_os + ')' : ''), r.miner], [r.date, r.date], [r.source, r.source], [r.by + (r.note ? '. ' + r.note : ''), r.by],
|
||||
];
|
||||
const render = (list, id) => '<div class="tbl"><table class="sortable" id="' + id + '"><thead><tr>' +
|
||||
heads.map(([h, k, t], i) => `<th data-key="${k}" data-type="${t}" aria-sort="${k === 'mh_per_w' ? 'descending' : 'none'}"><button type="button" class="sort">${h}</button></th>`).join('') +
|
||||
'</tr></thead><tbody>' + list.map(r => '<tr>' + cells(r).map(([c, v]) => `<td data-v="${esc(String(v))}">${esc(String(c))}</td>`).join('') + '</tr>').join('') + '</tbody></table></div>';
|
||||
const table = render(cur, 'bench-current');
|
||||
const earlierTable = render(earlier, 'bench-earlier');
|
||||
const sortScript = `<script>
|
||||
(function(){
|
||||
// header sort on the bench tables: a click sorts by that column (numbers by value, text by locale), a second click flips it
|
||||
document.querySelectorAll('table.sortable').forEach(function(t){
|
||||
var ths=t.querySelectorAll('th'),tb=t.querySelector('tbody');
|
||||
ths.forEach(function(th,i){th.querySelector('button').addEventListener('click',function(){
|
||||
var cur=th.getAttribute('aria-sort'),dir=cur==='descending'?'ascending':'descending',num=th.getAttribute('data-type')==='num';
|
||||
ths.forEach(function(o){o.setAttribute('aria-sort','none');});th.setAttribute('aria-sort',dir);
|
||||
var rows=Array.prototype.slice.call(tb.querySelectorAll('tr'));
|
||||
rows.sort(function(a,b){var x=a.children[i].getAttribute('data-v'),y=b.children[i].getAttribute('data-v');var c=num?(parseFloat(x)-parseFloat(y)):x.localeCompare(y,'en');return dir==='descending'?-c:c;});
|
||||
rows.forEach(function(r){tb.appendChild(r);});
|
||||
});});
|
||||
});
|
||||
})();
|
||||
</script>`;
|
||||
const sortStyle = '<style>th .sort{all:unset;cursor:pointer;font:inherit;color:inherit}th .sort::after{content:" \\2195";opacity:.45}th[aria-sort="descending"] .sort::after{content:" \\2193";opacity:1}th[aria-sort="ascending"] .sort::after{content:" \\2191";opacity:1}details.earlier{margin:var(--s-4) 0}details.earlier summary{cursor:pointer;color:var(--bone)}</style>';
|
||||
// Ember Tune's fleet priors (site/miner-priors.json, tools/tuning.mjs --priors --site): one row per card model,
|
||||
// driver major and program class; a row under the sample floor shows its count and no point
|
||||
const pj = JSON.parse(readFileSync(join(here, 'miner-priors.json'), 'utf8'));
|
||||
|
|
@ -438,14 +470,21 @@ for (const [file, active] of PAGES) {
|
|||
prows.map(r => '<tr>' + pcell(r).map(c => `<td>${esc(String(c))}</td>`).join('') + '</tr>').join('') + '</tbody></table></div>'
|
||||
: '<p>No tune reports yet. The first rows appear once five machines with the same card model have reported.</p>';
|
||||
const body = scrubBench([
|
||||
sortStyle,
|
||||
'<h2 id="table">The table</h2>',
|
||||
'<p>One row per card, generator version and miner version. The rate is the best one measured. Integrated GPUs are not listed. Prototype rows are bench numbers from before the devnet and say so in the miner column.</p>',
|
||||
'<p>One row per card on the current class: the class v4 program (the latency-shadow block over the class v3 hash), or a class v3 row re-measured with its class v4 cost on 6 October 2026 or later. Click a column header to sort; the table opens by MH per watt. Integrated GPUs are not listed. The earlier classes sit below, collapsed.</p>',
|
||||
'<p><strong>Why the rate fell from the first bench to today.</strong> The genesis program did 104 dependent random 4-byte loads per hash over a 1 GiB dataset; the hourly program and class v3 do 128, with the mixer between them; class v4 adds about 100,000 integer operations per hash that ride in the memory wait. So the hash is bound by random-read bandwidth by design, and a card\'s MH/s is a relative number: the difficulty follows it, and the same card earns the same share of blocks at 136 MH/s on class v3 as it did at 228 MH/s on the genesis program. What a miner compares is hash per watt, and what the chain cares about is the chip edge, which the shadow work is there to cut.</p>',
|
||||
table,
|
||||
`<p>Rows on the current class: ${cur.length}. Each row names the engineering log entry or the job it came from.</p>`,
|
||||
'<details class="earlier"><summary>Earlier classes (the genesis program, the hourly program, class v3 before the shadow): ' + earlier.length + ' rows, not comparable with the table above</summary>',
|
||||
'<p>These rows are the bench numbers of 3 and 4 October 2026: the genesis program (104 loads per hash), the hourly program and the first class v3 miner. A higher MH/s here is a different hash, not a faster card.</p>',
|
||||
earlierTable,
|
||||
'</details>',
|
||||
'<h2 id="how">How a row gets here</h2>',
|
||||
'<p>Every row names the engineering log entry or the job it came from. "Measured by the team" means our own hardware and our own log. "Reported by the fleet" means a machine we do not own, read from the status lines its miner uploads.</p>',
|
||||
'<p>Every row names the engineering log entry or the job it came from. "Measured by the team" means our own hardware and our own log. "Reported by the fleet" or "measured by the fleet" means a machine we rent or do not own, read from the status lines its miner uploads or from a bench run on it.</p>',
|
||||
'<p>MH per watt needs the card\'s power draw during the run. The app reads it on NVIDIA cards through the driver. Rows get the figure when a run records it. The class v4 cost column is what the latency-shadow work costs that card against the class v3 control, in watts and rate, where it was measured. The tuned column is the card\'s state at the row: a full Ember Tune names its point; stock means the card as it came, bench only or mining.</p>',
|
||||
'<p>There is no other Igneum miner to compare with yet, so this table compares cards, not miners. The app that produces these rows: <a href="/miner">the miner page</a>.</p>',
|
||||
`<p>Rows: ${rows.length}. Each row names the engineering log entry it came from.</p>`,
|
||||
sortScript,
|
||||
'<h2 id="priors">Fleet tuning priors</h2>',
|
||||
'<p>Ember Tune runs on every card the app mines with: the power limit and the core clock are stepped on the live program and the card keeps the point with the best MH per watt within 1% of its top rate. Every finished tune is reported back without anything that identifies the owner, and the fleet\'s median point per card model, driver major and program class comes back down inside the signed update manifest as the starting point for the next card of that model. A model needs ' + pj.min_samples + ' reports before its prior is used.</p>',
|
||||
ptable,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue