Merge build/master dabd0fea into spec-accept-23 (the Intel rotr worker fix taken; /miners rebuilt from the merged bench data)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-08 08:52:59 +00:00
commit a4efbc4657
8 changed files with 258 additions and 74 deletions

File diff suppressed because one or more lines are too long

View file

@ -508,8 +508,17 @@ typedef struct {
static const char* exchangeName(int m) { return m == 1 ? "sub_group_shuffle_xor (cl_khr_subgroup_shuffle)" : m == 2 ? "intel_sub_group_shuffle_xor (cl_intel_subgroups)" : "local-memory exchange with barrier"; }
// Returns 0 on success, 1 on build failure (log printed).
#include "intel_rotr.h" /* the Intel rotate fold: rotr_var rewritten on an Intel platform (7 October 2026) */
static char* intelRotrPatch(const DeviceInfo* di, const char* src, size_t* srcLen, int* patched) {
if (!di) { *patched = 0; return (char*)src; }
return igneum_intel_rotr_patch(di->vendor, di->platformName, src, srcLen, patched, 0);
}
static int buildProgram(Device* dv, const DeviceInfo* di, const char* src, size_t srcLen, int exchangeMode, int groupSize, const char* extra) {
cl_int err = 0;
int patched = 0;
char* psrc = intelRotrPatch(di, src, &srcLen, &patched);
src = psrc;
double tb = wallMs(); /* the pack's compile cost (Counter ASIC 3.0 item 2, 6 October 2026): printed as one line below */
const char* std;
// The sub-group built-ins need OpenCL C 2.0 or 3.0. OpenCL 3.0 devices may report "OpenCL C 1.2" as the default
@ -522,6 +531,7 @@ static int buildProgram(Device* dv, const DeviceInfo* di, const char* src, size_
else std = "-cl-std=CL1.2";
snprintf(dv->buildOptions, sizeof(dv->buildOptions), "%s -D IGNEUM_GROUP=%d -D IGNEUM_EXCHANGE=%d %s", std, groupSize, exchangeMode, extra);
dv->prog = clCreateProgramWithSource(dv->ctx, 1, &src, &srcLen, &err);
if (patched) free(psrc);
CL_CHECK_ERR(err, "clCreateProgramWithSource");
err = clBuildProgram(dv->prog, 1, &di->device, dv->buildOptions, NULL, NULL);
if (err != CL_SUCCESS) {
@ -1315,7 +1325,12 @@ static void prepareRun(PrepareTask* t) {
src = readFile(path, &srcLen);
if (!src) { snprintf(t->error, sizeof(t->error), "cannot read %s", path); releasePair(p); t->doneAt = wallMs(); t->done = 1; return; }
tb = wallMs();
p->prog = clCreateProgramWithSource(t->dv->ctx, 1, (const char**)&src, &srcLen, &err);
{
int patched = 0;
char* psrc = intelRotrPatch(t->di, src, &srcLen, &patched);
p->prog = clCreateProgramWithSource(t->dv->ctx, 1, (const char**)&psrc, &srcLen, &err);
if (patched) free(psrc);
}
free(src);
if (err != CL_SUCCESS) { prepareFail(t, "clCreateProgramWithSource", err); releasePair(p); t->doneAt = wallMs(); t->done = 1; return; }
err = clBuildProgram(p->prog, 1, &t->di->device, t->dv->buildOptions, NULL, NULL);

40
proto-opencl/intel_rotr.h Normal file
View file

@ -0,0 +1,40 @@
/* intel_rotr.h: the Intel rotate fold (7 October 2026, the Arc B580 register-trace bisect, docs/plans/intel-arc.md
* section 7). Intel's OpenCL compiler turns the pack's `rotr_var(x, n) = rotate(x, (0u - n) & 31u)` into a rotate LEFT
* by n (the negation dropped), so every variable right-rotate of every program came out wrong on the Arc while every
* other family and the dataset kernels were bit-exact (lane 0's trace diverged at instruction 6 of iteration 0, rotr,
* and nowhere before; job run-ia-arc-trace-20261007-b). On an Intel platform the worker rewrites that one helper line
* to the shift form the CPU interpreter and the family probe compute, before clCreateProgramWithSource; the text is
* otherwise untouched and no other vendor sees a change. Shared by host.c and test_intel_rotr.c (the gate's test). */
#ifndef IGNEUM_INTEL_ROTR_H
#define IGNEUM_INTEL_ROTR_H
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
static const char* IGNEUM_ROTR_BUILTIN = "static inline uint rotr_var(uint x, uint n) { return rotate(x, (0u - n) & 31u); }";
static const char* IGNEUM_ROTR_SHIFTS = "static inline uint rotr_var(uint x, uint n) { n &= 31u; return (x >> n) | (x << ((32u - n) & 31u)); }";
/* Returns the source to build: the original pointer when nothing applies, else a new buffer (the caller frees it when
* `*patched` is set) with the one line replaced and `*srcLen` updated. `vendor` and `platform` are the device's
* CL_DEVICE_VENDOR and the platform name; only a string holding "Intel" is rewritten. `quiet` suppresses the line. */
static char* igneum_intel_rotr_patch(const char* vendor, const char* platform, const char* src, size_t* srcLen, int* patched, int quiet) {
const char* at;
char* out;
size_t a = strlen(IGNEUM_ROTR_BUILTIN), b = strlen(IGNEUM_ROTR_SHIFTS), pre;
*patched = 0;
if (!((vendor && strstr(vendor, "Intel")) || (platform && strstr(platform, "Intel")))) return (char*)src;
at = strstr(src, IGNEUM_ROTR_BUILTIN);
if (!at) return (char*)src;
pre = (size_t)(at - src);
out = (char*)malloc(*srcLen - a + b + 1);
if (!out) return (char*)src;
memcpy(out, src, pre);
memcpy(out + pre, IGNEUM_ROTR_SHIFTS, b);
memcpy(out + pre + b, at + a, *srcLen - pre - a);
out[*srcLen - a + b] = 0;
*srcLen = *srcLen - a + b;
*patched = 1;
if (!quiet) printf("intel: rotr_var rewritten to the shift form before the build (the rotate fold of 7 October 2026)\n");
return out;
}
#endif

View file

@ -0,0 +1,47 @@
/* test_intel_rotr.c: the gate's test of intel_rotr.h (7 October 2026). Feeds a kernel text holding the pack's rotr_var
* helper line through the rewrite under an Intel vendor string and under AMD's and NVIDIA's, and asserts: the Intel
* output carries the shift form and nothing else changed (the bytes before and after the line equal), the AMD and
* NVIDIA outputs are the input pointer, untouched; a text without the line is returned untouched on Intel too.
* cc -std=c99 -Wall -Wextra -o test_intel_rotr proto-opencl/test_intel_rotr.c && ./test_intel_rotr */
#include "intel_rotr.h"
#include <assert.h>
int main(void) {
const char* pre = "static inline uint rotl_imm(uint x, uint n) { return rotate(x, n); }\n";
const char* post = "\nstatic inline uint ds_elem(uint i, uint d0, uint d1) { return i ^ d0 ^ d1; }\n";
char text[1024];
size_t len, plen, blen = strlen(IGNEUM_ROTR_BUILTIN), slen = strlen(IGNEUM_ROTR_SHIFTS);
int patched = -1;
char* out;
snprintf(text, sizeof(text), "%s%s%s", pre, IGNEUM_ROTR_BUILTIN, post);
len = strlen(text);
/* Intel: the one line becomes the shift form, the rest is byte for byte the same */
plen = len;
out = igneum_intel_rotr_patch("Intel(R) Corporation", "Intel(R) OpenCL Graphics", text, &plen, &patched, 1);
assert(patched == 1);
assert(out != text);
assert(plen == len - blen + slen);
assert(strlen(out) == plen);
assert(memcmp(out, pre, strlen(pre)) == 0);
assert(memcmp(out + strlen(pre), IGNEUM_ROTR_SHIFTS, slen) == 0);
assert(strcmp(out + strlen(pre) + slen, post) == 0);
assert(strstr(out, "rotate(x, (0u - n)") == NULL);
free(out);
/* the platform name alone names Intel too (a vendor string of another spelling) */
plen = len; patched = -1;
out = igneum_intel_rotr_patch("GenuineIntel", "Intel(R) OpenCL Graphics", text, &plen, &patched, 1);
assert(patched == 1 && out != text); free(out);
/* AMD and NVIDIA: untouched, the input pointer back, the length unchanged */
plen = len; patched = -1;
out = igneum_intel_rotr_patch("Advanced Micro Devices, Inc.", "AMD Accelerated Parallel Processing", text, &plen, &patched, 1);
assert(patched == 0 && out == text && plen == len && strstr(text, IGNEUM_ROTR_BUILTIN) != NULL);
plen = len; patched = -1;
out = igneum_intel_rotr_patch("NVIDIA Corporation", "NVIDIA CUDA", text, &plen, &patched, 1);
assert(patched == 0 && out == text && plen == len);
/* Intel with no helper line (a probe kernel): untouched */
plen = strlen(pre); patched = -1;
out = igneum_intel_rotr_patch("Intel(R) Corporation", "Intel(R) OpenCL Graphics", pre, &plen, &patched, 1);
assert(patched == 0 && out == pre && plen == strlen(pre));
printf("test_intel_rotr: ok (Intel rewritten to the shift form, AMD and NVIDIA untouched, no line untouched)\n");
return 0;
}

View file

@ -412,10 +412,12 @@ 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.
// 7 October 2026 (the founder): 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.
// 7 October 2026 (the founder): the CURRENT class is the table (class v4, or class v3 re-measured with its v4 cost, 6 October
// on); cards you can buy first by hash rate, the datacentre rows collapsed, the earlier classes collapsed; one row per
// card; the card's vendor mark and name lead, the rate is the big tabular number, MH per wall watt beside it in the ember
// tint for the best desktop card, watts and the class v4 cost quiet, the tuned state a pill, the date muted mono; the
// Hive flight-sheet values, the source and the note in a row that opens on a tap; headers sort with an arrow glyph, the
// active sort underlined in ember; 56 px rows on desktop, a two-line card per row on a phone.
{
const bj = JSON.parse(readFileSync(join(here, 'miner-bench.json'), 'utf8'));
const isCurrent = (r) => r.generator === 'v2' && r.date >= '2026-10-06';
@ -423,51 +425,58 @@ for (const [file, active] of PAGES) {
const curAll = bj.rows.filter(isCurrent).sort(byRate);
const cur = curAll.filter(r => r.group !== 'datacentre');
const dcRows = curAll.filter(r => r.group === 'datacentre');
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 earlier = bj.rows.filter(r => !isCurrent(r)).sort(byRate);
const rows = bj.rows;
const fmt = (n) => Number(n).toLocaleString('en-GB', { maximumFractionDigits: 1 });
const fmt3 = (n) => Number(n).toLocaleString('en-GB', { maximumFractionDigits: 3 });
// Nine compact columns sort; the long fields (the class v4 cost, the Hive values with their label, the miner and driver,
// the source and the note) sit in a detail row under each card so a row stays one line wide at 1600 px (the 7 October
// capture showed eleven columns clipped at five, every row inflated by off-screen wrapped text). The detail row moves
// with its data row on a sort.
const heads = [
['Card', 'card', 'text'], ['MH/s', 'mh_s', 'num'], ['W', 'watts', 'num'], ['MH per wall watt', 'mh_per_w', 'num'],
['v4 cost', 'v4_cost_short', 'text'], ['Tuned', 'tuned_short', 'text'], ['Hive core / mem / PL', 'hive_short', 'text'], ['Date', 'date', 'text'], ['Who', 'by', 'text'],
];
// the short forms are one clause: the first watts or percent figure of the cost ("+16 W", "+145.3 W unlocked"), the
// tune state's first words; the full sentences live in the detail row
const best = cur[0];
const shortV4 = (r) => { if (r.v4_short) return r.v4_short; const v = r.v4_cost || 'not measured'; if (/^not measured/.test(v)) return 'not measured'; const m = v.match(/^([+-]?[\d.,]+\s*(?:W|percent)(?:\s+(?:unlocked|of rate))?)/); return m ? m[1].replace(' of rate', ' rate') : v.split(/[,;(]| for | at /)[0].trim(); };
const shortTuned = (r) => { const v = r.tuned || 'stock, mining'; return v.split(/[:;(]/)[0].replace('full Ember Tune', 'Ember Tune').replace('stock, bench only', 'stock, bench').replace('no lever on Apple silicon', 'no lever').trim(); };
const shortHive = (r) => { const h = r.hive; return (h && h.core_mhz != null) ? fmt(h.core_mhz) + ' / ' + fmt(h.mem_mhz) + ' / ' + fmt(h.pl_w) + ' W' : 'stock'; };
const cells = (r) => [
[r.card, r.card], [r.mh_s_display || fmt(r.mh_s), r.mh_s], [r.watts_display || (r.watts == null ? 'not read' : fmt(r.watts) + (r.watt_basis === 'chip' ? ' (chip watts, not wall)' : '')), r.watts ?? -1],
[r.mh_per_w == null ? 'not measured' : (r.watt_basis === 'chip' ? '\u25CB ' + fmt3(r.mh_per_w) + ' (chip watts, not ranked)' : fmt3(r.mh_per_w)), r.watt_basis === 'chip' ? -1 : (r.mh_per_w ?? -1)],
[shortV4(r), shortV4(r)], [shortTuned(r), shortTuned(r)], [shortHive(r), r.hive && r.hive.core_mhz != null ? 'a ' + r.hive.core_mhz : 'z stock'], [r.date, r.date], [r.by.replace('measured by the ', '').replace('reported by the ', 'reported, '), r.by],
];
const tunedState = (r) => { const v = r.tuned || 'stock, mining'; if (/Ember Tune|core lock/.test(v)) return ['tuned', 'Tuned']; if (/no lever/.test(v)) return ['nolever', 'No lever']; return ['stock', 'Stock']; };
const effCell = (r) => {
if (r.mh_per_w == null) return ['not measured', -1];
if (r.watt_basis === 'chip') return ['<span class="hollow" title="chip watts (GPU plus DRAM), not wall; not ranked">○ ' + fmt3(r.mh_per_w) + '</span>', -1];
return [(best && r === best ? '<span class="eff best">' : '<span class="eff">') + fmt3(r.mh_per_w) + '</span>', r.mh_per_w];
};
const detail = (r) => {
const parts = [];
parts.push('<b>Generator:</b> ' + esc(r.generator));
parts.push('<b>Class v4 cost:</b> ' + esc(r.v4_cost || 'not measured'));
parts.push('<b>Tuned:</b> ' + esc(r.tuned || 'stock, mining'));
if (r.hive && r.hive.core_mhz != null) parts.push('<b>Hive flight sheet:</b> core lock ' + esc(fmt(r.hive.core_mhz)) + ' MHz, mem ' + esc(fmt(r.hive.mem_mhz)) + ' MHz, PL ' + esc(fmt(r.hive.pl_w)) + ' W (' + esc(r.hive.label) + ')');
else if (r.hive && r.hive.label) parts.push('<b>Hive flight sheet:</b> ' + esc(r.hive.label));
parts.push('<b>Miner:</b> ' + esc(r.miner + (r.driver_os ? ' (' + r.driver_os + ')' : '')));
parts.push('<b>Source:</b> ' + esc(r.source));
if (r.note) parts.push('<b>Note:</b> ' + esc(r.note));
return parts.join(' · ');
if (r.hive && r.hive.core_mhz != null) parts.push('<b>Hive flight sheet</b> core lock ' + esc(fmt(r.hive.core_mhz)) + ' MHz, mem ' + esc(fmt(r.hive.mem_mhz)) + ' MHz, PL ' + esc(fmt(r.hive.pl_w)) + ' W (' + esc(r.hive.label) + ')');
else parts.push('<b>Hive flight sheet</b> stock' + (r.hive && r.hive.label ? ' (' + esc(r.hive.label.replace(/^stock \(|\)$/g, '')) + ')' : ''));
parts.push('<b>Class v4 cost</b> ' + esc(r.v4_cost || 'not measured'));
parts.push('<b>Tuned</b> ' + esc(r.tuned || 'stock, mining'));
parts.push('<b>Generator</b> ' + esc(r.generator) + ' · <b>Miner</b> ' + esc(r.miner + (r.driver_os ? ' (' + r.driver_os + ')' : '')));
parts.push('<b>Source</b> ' + esc(r.source) + ' · <b>Who</b> ' + esc(r.by));
if (r.note) parts.push('<b>Note</b> ' + esc(r.note));
return parts.map(x => '<span class="d">' + x + '</span>').join('');
};
const render = (list, id) => '<div class="tbl"><table class="sortable bench" id="' + id + '"><thead><tr>' +
heads.map(([h, k, t]) => `<th data-key="${k}" data-type="${t}" aria-sort="${k === 'mh_s' ? 'descending' : 'none'}"><button type="button" class="sort">${h}</button></th>`).join('') +
'</tr></thead><tbody>' + list.map(r => '<tr class="row">' + cells(r).map(([c, v]) => `<td data-v="${esc(String(v))}">${esc(String(c))}</td>`).join('') + '</tr>' +
`<tr class="detail"><td colspan="${heads.length}">${detail(r)}</td></tr>`).join('') + '</tbody></table></div>';
const heads = [['Card', 'card', 'text'], ['MH/s', 'mh_s', 'num'], ['MH per wall watt', 'mh_per_w', 'num'], ['Watts', 'watts', 'num'], ['Tuned', 'tuned', 'text'], ['', 'more', 'none']];
const render = (list, id) => {
const body = list.map((r, i) => {
const [effHtml, effV] = effCell(r); const [tcls, tlabel] = tunedState(r);
const sub = '<span class="gen">class v4 ' + esc(shortV4(r)) + ' <span class="dot">\u00B7</span> <span class="date">' + esc(r.date) + '</span></span>';
const cells = [
['lead', r.card, esc(r.card) + sub],
['big tnum', r.mh_s, esc(r.mh_s_display || fmt(r.mh_s))],
['tnum effc', effV, effHtml],
['quiet tnum', r.watts ?? -1, r.watts_display ? esc(r.watts_display) + ' W' : (r.watts == null ? 'not read' : esc(fmt(r.watts)) + ' W' + (r.watt_basis === 'chip' ? ' <span class="basis">chip, not wall</span>' : ''))],
['pillc', tlabel, '<span class="pill state ' + tcls + '">' + tlabel + '</span>'],
['morec', '', '<button type="button" class="more" aria-expanded="false" aria-controls="' + id + '-d' + i + '">Details</button>'],
];
return '<tr class="row">' + cells.map(([c, v, h]) => `<td class="${c}" data-v="${esc(String(v))}">${h}</td>`).join('') + '</tr>' +
`<tr class="detail" id="${id}-d${i}" hidden><td colspan="${heads.length}">${detail(r)}</td></tr>`;
}).join('');
return '<div class="tbl bench2wrap"><table class="bench2" id="' + id + '"><thead><tr>' +
heads.map(([h, k, t]) => t === 'none' ? '<th></th>' : `<th data-key="${k}" data-type="${t}" aria-sort="${k === 'mh_s' ? 'descending' : 'none'}"><button type="button" class="sort">${h}</button></th>`).join('') +
'</tr></thead><tbody>' + body + '</tbody></table></div>';
};
const table = render(cur, 'bench-current');
const dcTable = render(dcRows, 'bench-datacentre');
const earlierTable = render(earlier, 'bench-earlier');
const bestLine = best ? `<p class="lede"><strong>Best desktop card:</strong> ${esc(best.card.replace(/ \(.*$/, ''))}, ${esc(fmt(best.mh_s))} MH/s, ${esc(fmt3(best.mh_per_w))} MH per wall watt tuned (measured, ${esc(best.date)}).</p>` : '';
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;
// each data row carries its detail row with 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(){
document.querySelectorAll('table.bench2').forEach(function(t){
var ths=t.querySelectorAll('th[data-key]'),tb=t.querySelector('tbody');
ths.forEach(function(th){var i=Array.prototype.indexOf.call(th.parentNode.children,th);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.row'));
@ -475,15 +484,48 @@ for (const [file, active] of PAGES) {
pairs.sort(function(a,b){var x=a[0].children[i].getAttribute('data-v'),y=b[0].children[i].getAttribute('data-v');var c=num?(parseFloat(x)-parseFloat(y)):x.localeCompare(y,'en');return dir==='descending'?-c:c;});
pairs.forEach(function(p){tb.appendChild(p[0]); if(p[1]) tb.appendChild(p[1]);});
});});
t.querySelectorAll('button.more').forEach(function(b){b.addEventListener('click',function(){var d=document.getElementById(b.getAttribute('aria-controls'));if(!d)return;var open=d.hasAttribute('hidden');if(open)d.removeAttribute('hidden');else d.setAttribute('hidden','');b.setAttribute('aria-expanded',open?'true':'false');b.textContent=open?'Close':'Details';});});
});
})();
</script>`;
const sortStyle = '<style>th .sort{all:unset;cursor:pointer;font:inherit;color:inherit;white-space:nowrap}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}table.bench{min-width:0;width:100%;table-layout:auto}table.bench tr.row td{border-bottom:0;white-space:normal;overflow-wrap:anywhere}table.bench tr.row td:nth-child(2),table.bench tr.row td:nth-child(3),table.bench tr.row td:nth-child(4),table.bench tr.row td:nth-child(8){white-space:nowrap}table.bench tr.row td:first-child{min-width:150px}table.bench tr.row td:nth-child(5),table.bench tr.row td:nth-child(6){white-space:nowrap}table.bench tr.row td:nth-child(7){white-space:nowrap}table.bench tr.detail td{font-size:13px;color:var(--ash);padding-top:0;overflow-wrap:anywhere;white-space:normal}table.bench tr.detail td b{color:var(--ink-2);font-weight:600}details.earlier{margin:var(--s-4) 0}p.best{font-size:18px;margin:0 0 14px}details.earlier summary{cursor:pointer;color:var(--bone)}</style>';
const table = render(cur, 'bench-current');
const dcTable = render(dcRows, 'bench-datacentre');
const best = cur.slice().sort(byRate)[0];
const bestLine = best ? `<p class="best"><strong>Best desktop card:</strong> ${esc(best.card.replace(/ \(.*$/, ''))}, ${esc(fmt(best.mh_s))} MH/s, ${esc(fmt3(best.mh_per_w))} MH per wall watt tuned (measured, ${esc(best.date)}).</p>` : '';
const earlierTable = render(earlier, 'bench-earlier');
const sortStyle = `<style>
.lede{font-size:18px;line-height:1.5;color:var(--bone);margin:0 0 var(--s-4)}
.grp{font:500 10px/1.4 var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--ash);margin:var(--s-5) 0 10px;padding-bottom:8px;border-bottom:1px solid var(--line)}
details.grp summary{cursor:pointer;list-style:none}details.grp summary::-webkit-details-marker{display:none}details.grp summary::after{content:" \\u25BE";opacity:.6}details.grp[open] summary::after{content:" \\u25B4"}
.tbl.bench2wrap{overflow-x:auto;border:0;background:none;border-radius:0;max-width:100%}
table.bench2{min-width:0;width:100%;border-collapse:collapse;font-size:14px;line-height:1.4}
table.bench2 th{font:500 10px/1.4 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--ash);text-align:left;padding:0 12px 10px;border-bottom:1px solid var(--line);white-space:nowrap}
table.bench2 th .sort{all:unset;cursor:pointer;font:inherit;color:inherit;letter-spacing:inherit;text-transform:inherit;padding-bottom:3px;border-bottom:2px solid transparent}
table.bench2 th .sort::after{content:" \\u2195";opacity:.4}
table.bench2 th[aria-sort="descending"] .sort,table.bench2 th[aria-sort="ascending"] .sort{color:var(--bone);border-bottom-color:var(--ember)}
table.bench2 th[aria-sort="descending"] .sort::after{content:" \\u2193";opacity:1;color:var(--ember)}table.bench2 th[aria-sort="ascending"] .sort::after{content:" \\u2191";opacity:1;color:var(--ember)}
table.bench2 tr.row td{height:56px;padding:0 12px;border-bottom:1px solid var(--line);background:none;vertical-align:middle;white-space:nowrap;color:var(--ink-2)}
table.bench2 tr.row:hover td{background:var(--hover)}
table.bench2 td.lead{color:var(--bone)}@media(min-width:1101px){table.bench2 tr.row td.lead{white-space:normal;min-width:220px;max-width:360px;padding-top:8px;padding-bottom:8px}}
table.bench2 td.lead .cardname{display:inline-flex;align-items:center;gap:10px}
table.bench2 td.lead .gen{display:block;font:12px var(--mono);color:var(--ash);letter-spacing:.02em;margin-top:3px;white-space:normal;overflow-wrap:anywhere}table.bench2 td.lead .gen .dot{color:var(--line-2);margin:0 4px}
table.bench2 td.big{font:600 22px/1 var(--f-display,var(--f-sans));color:var(--bone);font-variant-numeric:tabular-nums}
table.bench2 td.tnum{font-variant-numeric:tabular-nums}
table.bench2 .eff{font:500 15px/1 var(--f-sans);color:var(--ink-2)}table.bench2 .eff.best{color:var(--ember-text,var(--ember));font-weight:600}
table.bench2 .hollow{color:var(--ash)}
table.bench2 td.quiet{color:var(--ash);font-size:13px}table.bench2 .basis{font:10px var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--ash);margin-left:6px}
table.bench2 .pill.state{padding:5px 8px;font-size:9px}table.bench2 .pill.state.tuned{color:var(--ember-text,var(--ember));border-color:color-mix(in srgb,var(--ember) 35%,transparent);background:color-mix(in srgb,var(--ember) 8%,transparent)}table.bench2 .pill.state.stock{color:var(--ash)}table.bench2 .pill.state.nolever{color:var(--ash);border-style:dashed}
table.bench2 button.more{all:unset;cursor:pointer;font:500 10px/1.4 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--ash);padding:6px 0;border-bottom:1px solid var(--line-2)}table.bench2 button.more:hover{color:var(--bone)}
table.bench2 tr.detail td{padding:12px 12px 16px;border-bottom:1px solid var(--line);font-size:13px;line-height:1.55;color:var(--ash);white-space:normal;overflow-wrap:anywhere}
table.bench2 tr.detail .d{display:block;margin:0 0 4px}table.bench2 tr.detail b{color:var(--ink-2);font-weight:600;margin-right:6px}
@media(max-width:1100px){
table.bench2,table.bench2 tbody,table.bench2 tr.detail,table.bench2 tr.detail td{display:block}
table.bench2 thead{display:none}
table.bench2 tr.row{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"lead big" "eff eff" "w pill" "more more";gap:4px 12px;padding:10px 0;min-height:44px;border-bottom:1px solid var(--line);align-items:center}
table.bench2 tr.row td{display:block;height:auto;padding:0;border:0;white-space:normal;min-width:0}
table.bench2 tr.row td.lead{grid-area:lead;max-width:none;min-width:0}table.bench2 td.lead .cardname{display:flex;align-items:center;gap:8px}table.bench2 td.lead .cardname .badge{flex:none}
table.bench2 td.big{grid-area:big;font-size:20px;text-align:right;white-space:nowrap}
table.bench2 td.effc{grid-area:eff;font-size:13px}table.bench2 td.effc .eff::before,table.bench2 td.effc .hollow::before{content:"MH per wall watt ";color:var(--ash);font-weight:400}
table.bench2 td.quiet.tnum{grid-area:w}table.bench2 td.pillc{grid-area:pill;text-align:right}
table.bench2 td.morec{grid-area:more;text-align:right}
table.bench2 tr.detail td{padding:8px 0 14px}
}
</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'));
@ -503,19 +545,20 @@ for (const [file, active] of PAGES) {
sortStyle,
'<h2 id="table">The table</h2>',
bestLine,
'<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. Cards you can buy first, sorted by hash rate; click a column header to sort. MH per wall watt uses board or wall power; a row whose watts are the chip\'s (Apple silicon: GPU plus DRAM from IOReport) says so and is not ranked on that column. Integrated GPUs are not listed. Datacentre cards and 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>',
'<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. MH per wall watt uses board or wall power; a row whose watts are the chip\'s (Apple silicon: GPU plus DRAM from IOReport) says so and is not ranked on that column. Integrated GPUs are not listed.</p>',
'<h3 class="grp" id="buy">Cards you can buy</h3>',
table,
`<p>Cards you can buy on the current class: ${cur.length}. Each row names the engineering log entry or the job it came from.</p>`,
'<details class="earlier"><summary>Datacentre cards (' + dcRows.length + ' rows, rented for the measurement; about three times the rented dollars per hash of a desktop card)</summary>',
'<p>Rented cards measured on the class v4 program by the fleet, stock clocks. They mine; they are not what a home miner buys.</p>',
`<p class="gen">${cur.length} cards. Details opens a card\'s Hive flight-sheet values, its full class v4 cost, miner, source and note.</p>`,
'<details class="grp"><summary>Datacentre (' + dcRows.length + ' cards, rented for the measurement; about three times the rented dollars per hash of a desktop card)</summary>',
dcTable,
'</details>',
'<p><strong>The Hive flight sheet column.</strong> Where a card has a measured tune point, the column gives the core clock lock, the memory clock and the power limit to copy into a HiveOS flight sheet (core / mem / PL); the line under each row carries the label with the date, the class v4 cost in full, the miner and driver, the source and the note. Stock means no tune point has been measured yet. The Hive package mines at these settings through Hive\'s own overclock controls; the desktop app\'s Ember Tune lands on them by itself.</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>',
'<details class="grp"><summary>Earlier classes (' + earlier.length + ' rows: the genesis program, the hourly program, class v3 before the shadow; not comparable with the table above)</summary>',
'<p>The bench numbers of 3 and 4 October 2026. A higher MH/s here is a different hash, not a faster card.</p>',
earlierTable,
'</details>',
'<details class="grp"><summary>Why the rates changed</summary>',
'<p>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>',
'</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" 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>',

File diff suppressed because one or more lines are too long

View file

@ -73,3 +73,4 @@ gh's active account on the pushing Mac is the stored Igneum entry (self-test: an
CI state reader: a commit's newest run, master's last compiled run, a branch's last red (fake gh; the merge rule's reader)
known failure
known success
the Intel rotate-fold rewrite: Intel gets the shift form, AMD and NVIDIA untouched (proto-opencl/test_intel_rotr.c)

View file

@ -156,6 +156,7 @@ tree_checks() {
run "a box or network check gets one retry before it is red (retry-once self-test)" bash tools/ci/retry-once.sh --self-test
run "gh's active account on the pushing Mac is the stored Igneum entry (self-test: another login refused and named; the hook and the merge tool run the check live)" bash tools/ci/gh-account-check.sh --self-test
run "CI state reader: a commit's newest run, master's last compiled run, a branch's last red (fake gh; the merge rule's reader)" node tools/ci/ci-state.mjs --self-test
run "the Intel rotate-fold rewrite: Intel gets the shift form, AMD and NVIDIA untouched (proto-opencl/test_intel_rotr.c)" bash -c 'cc -std=c99 -Wall -Wextra -o "${TMPDIR:-/tmp}/test_intel_rotr.$$" proto-opencl/test_intel_rotr.c && "${TMPDIR:-/tmp}/test_intel_rotr.$$"; s=$?; rm -f "${TMPDIR:-/tmp}/test_intel_rotr.$$"; exit $s'
}
gated_refs() {