Guest User

Untitled

a guest
Oct 23rd, 2026
13
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 42.66 KB | Source Code | 0 0
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>microbench4 v2 — MRT vs multi-dispatch, verified (minichat/slim style)</title>
  6. <style>
  7. body{background:#0e0f13;color:#d8e0e8;font:13px ui-monospace,Consolas,monospace;padding:16px;margin:0}
  8. h2{color:#7dd3fc;margin:0 0 8px}
  9. h3{color:#93c5fd;margin:16px 0 6px;font-size:13px}
  10. #status{color:#fbbf24;margin:8px 0;white-space:pre-wrap;font-size:12px}
  11. #bootlog{background:#0a0c10;border:1px solid #1e293b;border-radius:4px;padding:8px 10px;margin:8px 0;
  12. font-size:11px;line-height:1.5;white-space:pre-wrap;color:#94a3b8;max-height:260px;overflow-y:auto}
  13. #bootlog .ok{color:#4ade80}
  14. #bootlog .bad{color:#f87171}
  15. #bootlog .warn{color:#fbbf24}
  16. #bootlog .dim{color:#475569}
  17. .pill{padding:2px 8px;border-radius:10px;font-size:11px;background:#334155;margin-right:6px;display:inline-block}
  18. .pill.ok{background:#052e16;color:#4ade80}
  19. .pill.warn{background:#3b2b0a;color:#fbbf24}
  20. .pill.bad{background:#3b0a0a;color:#f87171}
  21. .checks{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin:10px 0}
  22. .checks label{background:#1e293b;padding:6px 10px;border-radius:4px;cursor:pointer;display:flex;gap:8px;align-items:center;font-size:12px}
  23. .checks label:hover{background:#334155}
  24. button{background:#1e293b;color:#7dd3fc;border:1px solid #334155;padding:6px 12px;border-radius:4px;cursor:pointer;font:inherit;font-size:12px}
  25. button:hover:not(:disabled){background:#334155}
  26. button:disabled{color:#475569;cursor:not-allowed}
  27. button.primary{background:#0c1a24;border-color:#7dd3fc;color:#7dd3fc}
  28. table{border-collapse:collapse;width:100%;margin-top:6px;font-size:11px}
  29. th,td{padding:5px 8px;text-align:left;border-bottom:1px solid #1e293b}
  30. th{color:#94a3b8;font-weight:600;font-size:11px}
  31. .num{text-align:right;font-variant-numeric:tabular-nums}
  32. tr.pass td.verdict{color:#4ade80;font-weight:600}
  33. tr.warn td.verdict{color:#fbbf24;font-weight:600}
  34. tr.fail td.verdict{color:#f87171;font-weight:600}
  35. tr.pending td{color:#475569}
  36. .alert{padding:10px;border-radius:4px;margin:10px 0;font-size:12px}
  37. .alert.bad{background:#3b0a0a;color:#f87171;border:1px solid #7f1d1d}
  38. .alert.warn{background:#3b2b0a;color:#fbbf24;border:1px solid #78350f}
  39. .alert.ok{background:#052e16;color:#4ade80;border:1px solid #14532d}
  40. canvas{display:none}
  41. </style>
  42. </head>
  43. <body>
  44. <h2>microbench4 v2 — MRT vs multi-dispatch for minichat QKV (verified)</h2>
  45. <div>
  46. <span class="pill" id="pillGpu">GPU: ?</span>
  47. <span class="pill" id="pillIsolation">Isolation: ?</span>
  48. <span class="pill" id="pillTimer">Timer: ?</span>
  49. <span class="pill" id="pillMRT">MRT: ?</span>
  50. <span class="pill" id="pillMode">mode: ?</span>
  51. </div>
  52. <div id="status">Booting…</div>
  53. <div id="warn"></div>
  54. <div id="bootlog" class="dim">boot log:</div>
  55.  
  56. <h3>Test selection</h3>
  57. <div class="checks">
  58. <label><input type="checkbox" id="chkF" checked>F · Dispatch floor (noop ×1..32)</label>
  59. <label><input type="checkbox" id="chkS" checked>S · MRT vs 3-separate (trivial write)</label>
  60. <label><input type="checkbox" id="chkQ" checked>Q · Current 3-dispatch QKV path (B=1)</label>
  61. <label><input type="checkbox" id="chkQM" checked>QM · MRT 2-dispatch QKV path (B=1)</label>
  62. <label><input type="checkbox" id="chkP" checked>P · Prefill B=8 (current vs MRT)</label>
  63. <label><input type="checkbox" id="chkP100" checked>P100 · Prefill B=100 + spot rows</label>
  64. <label><input type="checkbox" id="chkV" checked>V · Correctness ladder V1/V2/V3 (f64 mirror)</label>
  65. </div>
  66. <div>
  67. <button id="btnSweep" class="primary">Run selected</button>
  68. <button id="btnClear">Clear</button>
  69. <span style="color:#64748b;font-size:11px">?fast in URL = short batches (slow GPUs / SwiftShader).</span>
  70. </div>
  71.  
  72. <h3>Results</h3>
  73. <table>
  74. <thead><tr>
  75. <th>test</th><th>config</th>
  76. <th class="num">per-rep</th>
  77. <th class="num">reps</th>
  78. <th class="num">total ms</th>
  79. <th class="num">conf</th>
  80. <th class="num">draws</th>
  81. </tr></thead>
  82. <tbody id="resultsBody"></tbody>
  83. </table>
  84.  
  85. <h3>Issue → verdict map</h3>
  86. <table>
  87. <thead><tr>
  88. <th>issue</th><th>test</th>
  89. <th class="num">metric</th>
  90. <th>verdict</th>
  91. <th>action</th>
  92. </tr></thead>
  93. <tbody id="issueMapBody"></tbody>
  94. </table>
  95.  
  96. <h3>Interpretation</h3>
  97. <div id="interpretation">—</div>
  98.  
  99. <canvas id="c" width="16" height="16"></canvas>
  100.  
  101. <script>
  102. 'use strict';
  103. // ============================================================ Config
  104. const D = 768, KVD = 768; // model dim, kv dim (7B-like: d == kvd, no GQA)
  105. const OUTQ = D + 2*KVD; // 2304 fused QKV floats
  106. const P = 4; // split-K partials
  107. const PXW_OUTQ = OUTQ/4; // 576
  108. const PXW_Q = D/4; // 192
  109. const PXW_KV = KVD/4; // 192
  110. const B_MAX = 100; // prefill batch ceiling (P100 test)
  111. const POOLH = 128; // pool rows (decode-shaped MAXLEN; B=100 fits)
  112. const PAGE = 4096; // texture page width (flat index -> (t&4095, t>>12))
  113.  
  114. const FAST = /[?&]fast\b/.test(location.search);
  115. const MEAS = FAST
  116. ? { warm: 3, estLoops: 1, estReps: 3, target: 1.2, samples: 2, minReps: 5, maxReps: 2000 }
  117. : { warm: 100, estLoops: 3, estReps: 20, target: 3, samples: 5, minReps: 30, maxReps: 20000 };
  118.  
  119. const TOL = { passAbs: 1e-4, warnAbs: 1e-3, distMin: 0.05 }; // f64-mirror gates (f32 budget ~1e-6)
  120. // numpy ground truth (scripts/mrt_ground_truth.py, f32 pipeline, row 0):
  121. const GT_ROW0 = { q: -0.181328, k: 0.546511, v: 0.370535 };
  122.  
  123. // ============================================================ GL init
  124. const canvas = document.getElementById('c');
  125. const gl = canvas.getContext('webgl2', {antialias:false});
  126. if (!gl) document.getElementById('status').textContent = 'FATAL: no WebGL2';
  127. else if (!gl.getExtension('EXT_color_buffer_float'))
  128. document.getElementById('status').textContent = 'FATAL: no EXT_color_buffer_float';
  129.  
  130. let TIMER_RES_US = 100;
  131. (function detectTimer(){
  132. if (!gl) return;
  133. const gaps=[]; let prev=performance.now();
  134. for (let i=0;i<1000;i++){ const t=performance.now(); if(t>prev){gaps.push(t-prev);prev=t;} }
  135. if (gaps.length>10){ gaps.sort((a,b)=>a-b); TIMER_RES_US=gaps[Math.floor(gaps.length/4)]*1000; }
  136. const pT=document.getElementById('pillTimer');
  137. pT.textContent=`Timer: ${TIMER_RES_US.toFixed(0)}µs`;
  138. pT.className='pill '+(TIMER_RES_US<10?'ok':TIMER_RES_US<50?'warn':'bad');
  139. const pI=document.getElementById('pillIsolation');
  140. pI.textContent=`Isolation: ${crossOriginIsolated?'yes':'no'}`;
  141. pI.className='pill '+(crossOriginIsolated?'ok':'warn');
  142. if (TIMER_RES_US>20){
  143. document.getElementById('warn').innerHTML=
  144. '<div class="alert warn"><b>Timer precision is low ('+TIMER_RES_US.toFixed(0)+'µs).</b> '+
  145. 'COOP/COEP may not have taken effect — reload the page after the server starts. '+
  146. 'Bench uses long batches to compensate.</div>';
  147. }
  148. })();
  149.  
  150. if (gl){
  151. const dbg = gl.getExtension('WEBGL_debug_renderer_info');
  152. const gpuName = dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : '?';
  153. const pg=document.getElementById('pillGpu');
  154. pg.textContent = 'GPU: ' + gpuName.split('(')[0].trim();
  155. pg.className='pill ok';
  156. const mdb = gl.getParameter(gl.MAX_DRAW_BUFFERS);
  157. const pm=document.getElementById('pillMRT');
  158. pm.textContent = `MRT: max ${mdb} buffers`;
  159. pm.className='pill '+(mdb>=3?'ok':'bad');
  160. const pmo=document.getElementById('pillMode');
  161. pmo.textContent = FAST ? 'mode: fast' : 'mode: full';
  162. pmo.className='pill '+(FAST?'warn':'ok');
  163. }
  164.  
  165. // ============================================================ Boot log (UPG style)
  166. const bootLines = [];
  167. function bootLog(cls, tag, msg){
  168. const line = `[${tag}] ${msg}`;
  169. bootLines.push({cls, line});
  170. const el = document.getElementById('bootlog');
  171. el.innerHTML += `\n<span class="${cls}">${line}</span>`;
  172. console.log((cls==='bad'?'FAIL ':cls==='ok'?'':'') + line);
  173. }
  174.  
  175. // ============================================================ Shader
  176. function buildShader(mrt){
  177. const pre = mrt
  178. ? `layout(location=0) out vec4 out0;
  179. layout(location=1) out vec4 out1;
  180. layout(location=2) out vec4 out2;`
  181. : `out vec4 fragColor;`;
  182. const epi = mrt
  183. ? `out0=vec4(y[0],y[1],y[2],y[3]);
  184. out1=vec4(y1[0],y1[1],y1[2],y1[3]);
  185. out2=vec4(y2[0],y2[1],y2[2],y2[3]);`
  186. : `fragColor=vec4(y[0],y[1],y[2],y[3]);`;
  187. return `#version 300 es
  188. precision highp float; precision highp int;
  189. uniform sampler2D u_in, u_w;
  190. uniform int u_mode, u_in_len, u_out_len, u_row_offset;
  191. uniform float u_f0, u_f1;
  192. uniform int u_i0, u_i1, u_i2, u_i3, u_i4, u_i5, u_i6, u_i7;
  193. ${pre}
  194. ivec2 widx(int i){ int t=i>>2; return ivec2(t&4095, t>>12); }
  195. float getIn(int i){ return texelFetch(u_in, widx(i), 0)[i&3]; }
  196. float getW(int i){ return texelFetch(u_w, widx(i), 0)[i&3]; }
  197. void main(){
  198. int px = int(gl_FragCoord.x);
  199. int py = int(gl_FragCoord.y);
  200. float y[4] = float[4](0.,0.,0.,0.);
  201. float y1[4] = float[4](0.,0.,0.,0.);
  202. float y2[4] = float[4](0.,0.,0.,0.);
  203.  
  204. if (u_mode == 57) {
  205. // split-K partial GEMV; row py reads input row py (u_row_offset = row stride in floats)
  206. int R = u_i0;
  207. int PXW = u_i1;
  208. int K1 = u_i2;
  209. int sp = px / PXW;
  210. int col = px - sp*PXW;
  211. if (sp < R) {
  212. int c0 = sp * K1;
  213. float a0=0.,a1=0.,a2=0.,a3=0.;
  214. for (int i=0;i<256;i++){
  215. int c = c0 + i;
  216. if (i >= K1 || c >= u_in_len) break;
  217. vec4 w4 = texelFetch(u_w, widx(c*u_out_len + col*4), 0);
  218. float x = getIn(u_row_offset*py + c);
  219. a0 += w4.x*x; a1 += w4.y*x; a2 += w4.z*x; a3 += w4.w*x;
  220. }
  221. y[0]=a0; y[1]=a1; y[2]=a2; y[3]=a3;
  222. }
  223. } else if (u_mode == 58) {
  224. // single reduce over P partials -> flat fused QKV
  225. int P_val = u_i1;
  226. int PXW = u_i0;
  227. vec4 acc = vec4(0);
  228. for (int i=0;i<64;i++){
  229. if (i >= P_val) break;
  230. acc += texelFetch(u_in, ivec2(i*PXW + px, py), 0);
  231. }
  232. y[0]=acc.x; y[1]=acc.y; y[2]=acc.z; y[3]=acc.w;
  233. } else if (u_mode == 580) {
  234. // MRT reduce: Q, K, V to 3 attachments at same (px,py)
  235. int P_val = u_i1;
  236. int PXW = u_i0;
  237. int d4 = u_i3;
  238. int kv4 = u_i4;
  239. vec4 q4 = vec4(0);
  240. if (px < d4) {
  241. for (int i=0;i<64;i++){
  242. if (i >= P_val) break;
  243. q4 += texelFetch(u_in, ivec2(i*PXW + px, py), 0);
  244. }
  245. }
  246. vec4 k4 = vec4(0);
  247. if (px < kv4) {
  248. for (int i=0;i<64;i++){
  249. if (i >= P_val) break;
  250. k4 += texelFetch(u_in, ivec2(i*PXW + d4 + px, py), 0);
  251. }
  252. }
  253. vec4 v4 = vec4(0);
  254. if (px < kv4) {
  255. for (int i=0;i<64;i++){
  256. if (i >= P_val) break;
  257. v4 += texelFetch(u_in, ivec2(i*PXW + d4 + kv4 + px, py), 0);
  258. }
  259. }
  260. y[0]=q4.x; y[1]=q4.y; y[2]=q4.z; y[3]=q4.w;
  261. y1[0]=k4.x; y1[1]=k4.y; y1[2]=k4.z; y1[3]=k4.w;
  262. y2[0]=v4.x; y2[1]=v4.y; y2[2]=v4.z; y2[3]=v4.w;
  263. } else if (u_mode == 38) {
  264. // append copy: read from flat pool at (srcOff+px, srcRow)
  265. int srcOff = u_i0;
  266. int srcRow = u_i1;
  267. vec4 v = texelFetch(u_in, ivec2(srcOff + px, srcRow), 0);
  268. y[0]=v.x; y[1]=v.y; y[2]=v.z; y[3]=v.w;
  269. } else if (u_mode == 99) {
  270. // trivial: read 1 texel, write same value to all outputs
  271. vec4 v = texelFetch(u_in, ivec2(px, py), 0);
  272. y[0]=v.x; y[1]=v.y; y[2]=v.z; y[3]=v.w;
  273. y1[0]=v.x; y1[1]=v.y; y1[2]=v.z; y1[3]=v.w;
  274. y2[0]=v.x; y2[1]=v.y; y2[2]=v.z; y2[3]=v.w;
  275. }
  276. // mode 98: noop (y stays zero)
  277.  
  278. ${epi}
  279. }`;
  280. }
  281.  
  282. // ============================================================ Programs
  283. let progSingle = null, progMulti = null;
  284. let U_single = {}, U_multi = {};
  285.  
  286. function compileProgram(fragSrc) {
  287. const vs = `#version 300 es
  288. void main(){
  289. vec2 p = vec2((gl_VertexID<<1)&2, gl_VertexID&2);
  290. gl_Position = vec4(p*2.0-1.0, 0.0, 1.0);
  291. }`;
  292. function sh(type, src) {
  293. const h = gl.createShader(type);
  294. gl.shaderSource(h, src);
  295. gl.compileShader(h);
  296. if (!gl.getShaderParameter(h, gl.COMPILE_STATUS))
  297. throw new Error((type===gl.VERTEX_SHADER?'VS':'FS')+': '+gl.getShaderInfoLog(h));
  298. return h;
  299. }
  300. const p = gl.createProgram();
  301. gl.attachShader(p, sh(gl.VERTEX_SHADER, vs));
  302. gl.attachShader(p, sh(gl.FRAGMENT_SHADER, fragSrc));
  303. gl.linkProgram(p);
  304. if (!gl.getProgramParameter(p, gl.LINK_STATUS))
  305. throw new Error('LINK: '+gl.getProgramInfoLog(p));
  306. return p;
  307. }
  308.  
  309. function grabUniforms(prog, names){
  310. const U = {};
  311. for (const n of names) U[n] = gl.getUniformLocation(prog, n);
  312. return U;
  313. }
  314. const UNAMES = ['u_in','u_w','u_mode','u_in_len','u_out_len','u_row_offset','u_f0','u_f1','u_i0','u_i1','u_i2','u_i3','u_i4','u_i5'];
  315.  
  316. function setupPrograms() {
  317. progSingle = compileProgram(buildShader(false));
  318. U_single = grabUniforms(progSingle, UNAMES);
  319. progMulti = compileProgram(buildShader(true));
  320. U_multi = grabUniforms(progMulti, UNAMES);
  321. // ROOT-CAUSE FIX (microbench4 v2): sampler uniforms default to texture unit 0.
  322. // The original bench bound texX->unit0 / texW->unit1 but never called uniform1i for
  323. // u_in/u_w, so BOTH samplers read unit 0: mode 57 fetched "weights" from the x
  324. // texture. Weight fetch widx(c*OUTQ + col*4) is in-bounds there only while
  325. // c*OUTQ + col*4 < xSize (c < 34 with paged x, c < 4 with the original 1792-texel x);
  326. // strips sp>=1 (c >= 192) read fully out of bounds -> exactly 0. The surviving
  327. // x*x residue (~+/-0.005) is precisely the docs' Q[0]=-0.004 K[0]=V[0]=-0.007.
  328. // The MRT reduce (mode 580) was exact the whole time (V2: 0.0 vs flat).
  329. gl.useProgram(progSingle); gl.uniform1i(U_single.u_in, 0); gl.uniform1i(U_single.u_w, 1);
  330. gl.useProgram(progMulti); gl.uniform1i(U_multi.u_in, 0); gl.uniform1i(U_multi.u_w, 1);
  331. bootLog('ok','P.compile','progSingle + progMulti(MRT) linked; samplers u_in->unit0 u_w->unit1 (root-cause fix)');
  332. }
  333.  
  334. // ============================================================ Textures / FBOs
  335. let texX, texW, texPartial, texQkvFlat, texQPool, texKCache, texVCache;
  336. let fboPartial, fboQkvFlat, fboQPool, fboKCache, fboVCache, fboMulti;
  337. const xSize = B_MAX*D + 1024;
  338. const xData = new Float32Array(xSize);
  339. const wSize = OUTQ * D;
  340. const wData = new Float32Array(wSize);
  341.  
  342. function genData(){
  343. for (let i = 0; i < xSize; i++) xData[i] = Math.sin(i*0.05)*0.3;
  344. for (let c = 0; c < D; c++)
  345. for (let r = 0; r < OUTQ; r++)
  346. wData[c*OUTQ + r] = Math.sin(r*0.01 + c*0.02)*0.05;
  347. bootLog('dim','V.data',`x ${xSize} f32 (${B_MAX} rows), w ${OUTQ}x${D} f32 — gen OK`);
  348. }
  349.  
  350. function createTex(w, h, data) {
  351. const t = gl.createTexture();
  352. gl.bindTexture(gl.TEXTURE_2D, t);
  353. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, w, h, 0, gl.RGBA, gl.FLOAT, data);
  354. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  355. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  356. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  357. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  358. return t;
  359. }
  360.  
  361. // paged upload: flat float index -> texel (t&4095, t>>12), width = PAGE
  362. function uploadPaged(tex, data){
  363. gl.bindTexture(gl.TEXTURE_2D, tex);
  364. const rows = Math.ceil((data.length/4) / PAGE);
  365. for (let row = 0; row < rows; row++) {
  366. const start = row*PAGE*4;
  367. const end = Math.min(data.length, (row+1)*PAGE*4);
  368. if (end > start)
  369. gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, row, (end-start)/4, 1, gl.RGBA, gl.FLOAT, data.subarray(start, end));
  370. }
  371. }
  372.  
  373. function makeFBO(attachments) {
  374. const f = gl.createFramebuffer();
  375. gl.bindFramebuffer(gl.FRAMEBUFFER, f);
  376. for (let i = 0; i < attachments.length; i++)
  377. gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, attachments[i], 0);
  378. const draws = [];
  379. for (let i = 0; i < attachments.length; i++) draws.push(gl.COLOR_ATTACHMENT0 + i);
  380. gl.drawBuffers(draws);
  381. const st = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  382. gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  383. if (st !== gl.FRAMEBUFFER_COMPLETE)
  384. throw new Error(`FBO incomplete: 0x${st.toString(16)}`);
  385. return f;
  386. }
  387.  
  388. function setupResources() {
  389. if (texX) return;
  390. genData();
  391.  
  392. // input activations, paged 4096-wide (v2 FIX: original had height=1 -> silent clip >16k floats)
  393. const xPx = Math.ceil(xSize/4);
  394. const xH = Math.ceil(xPx / PAGE);
  395. texX = gl.createTexture();
  396. gl.bindTexture(gl.TEXTURE_2D, texX);
  397. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, PAGE, xH, 0, gl.RGBA, gl.FLOAT, null);
  398. uploadPaged(texX, xData);
  399. setNearest(texX);
  400. bootLog(xH>1?'warn':'dim','V.xtex',`texX ${PAGE}x${xH} paged (v2 fix: multi-row, ${xPx} texels)`);
  401.  
  402. // weights: W^T[c*OUTQ + r]
  403. const wPx = Math.ceil(wSize/4);
  404. const wH = Math.ceil(wPx / PAGE);
  405. texW = gl.createTexture();
  406. gl.bindTexture(gl.TEXTURE_2D, texW);
  407. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, PAGE, wH, 0, gl.RGBA, gl.FLOAT, null);
  408. uploadPaged(texW, wData);
  409. setNearest(texW);
  410.  
  411. texPartial = createTex(PXW_OUTQ*P, POOLH, null);
  412. texQkvFlat = createTex(PXW_OUTQ, POOLH, null);
  413. texQPool = createTex(PXW_Q, POOLH, null);
  414. texKCache = createTex(PXW_KV, POOLH, null);
  415. texVCache = createTex(PXW_KV, POOLH, null);
  416.  
  417. fboPartial = makeFBO([texPartial]);
  418. fboQkvFlat = makeFBO([texQkvFlat]);
  419. fboQPool = makeFBO([texQPool]);
  420. fboKCache = makeFBO([texKCache]);
  421. fboVCache = makeFBO([texVCache]);
  422. fboMulti = makeFBO([texQPool, texKCache, texVCache]);
  423.  
  424. gl.bindVertexArray(gl.createVertexArray());
  425. bootLog('ok','V.fbo',`partial ${PXW_OUTQ*P}x${POOLH}, flat ${PXW_OUTQ}x${POOLH}, pools ${PXW_Q}x${POOLH} x3, fboMulti 3-att COMPLETE`);
  426. }
  427.  
  428. function setNearest(t){
  429. gl.bindTexture(gl.TEXTURE_2D, t);
  430. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  431. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  432. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  433. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  434. }
  435.  
  436. // ============================================================ f64 CPU mirror
  437. // y[r] = sum_c x[row*D + c] * wData[c*OUTQ + r] (plain f64 dot — the ground truth)
  438. const refCache = new Map();
  439. function cpuRef(row){
  440. if (refCache.has(row)) return refCache.get(row);
  441. const y = new Float64Array(OUTQ);
  442. for (let c = 0; c < D; c++) {
  443. const xv = xData[row*D + c];
  444. const base = c*OUTQ;
  445. for (let r = 0; r < OUTQ; r++) y[r] += xv * wData[base + r];
  446. }
  447. refCache.set(row, y);
  448. return y;
  449. }
  450.  
  451. // ============================================================ Dispatch helpers
  452. function dispatchSingle(cfg) {
  453. gl.useProgram(progSingle);
  454. gl.uniform1i(U_single.u_mode, cfg.mode ?? 98);
  455. gl.uniform1i(U_single.u_in_len, cfg.inLen ?? D);
  456. gl.uniform1i(U_single.u_out_len, cfg.outLen ?? OUTQ);
  457. gl.uniform1i(U_single.u_row_offset, cfg.rowOff ?? D);
  458. gl.uniform1i(U_single.u_i0, cfg.i0 ?? 0);
  459. gl.uniform1i(U_single.u_i1, cfg.i1 ?? 0);
  460. gl.uniform1i(U_single.u_i2, cfg.i2 ?? 0);
  461. gl.uniform1i(U_single.u_i3, cfg.i3 ?? 0);
  462. gl.uniform1i(U_single.u_i4, cfg.i4 ?? 0);
  463. gl.uniform1i(U_single.u_i5, cfg.i5 ?? 0);
  464. gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, cfg.texIn ?? texX);
  465. gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, cfg.texW ?? texW);
  466. gl.bindFramebuffer(gl.FRAMEBUFFER, cfg.fbo);
  467. gl.viewport(0, cfg.yOff ?? 0, cfg.vpW, cfg.vpH);
  468. gl.drawArrays(gl.TRIANGLES, 0, 3);
  469. }
  470.  
  471. function dispatchMulti(cfg) {
  472. gl.useProgram(progMulti);
  473. gl.uniform1i(U_multi.u_mode, cfg.mode ?? 580);
  474. gl.uniform1i(U_multi.u_in_len, cfg.inLen ?? D);
  475. gl.uniform1i(U_multi.u_out_len, cfg.outLen ?? OUTQ);
  476. gl.uniform1i(U_multi.u_row_offset, cfg.rowOff ?? D);
  477. gl.uniform1i(U_multi.u_i0, cfg.i0 ?? 0);
  478. gl.uniform1i(U_multi.u_i1, cfg.i1 ?? 0);
  479. gl.uniform1i(U_multi.u_i2, cfg.i2 ?? 0);
  480. gl.uniform1i(U_multi.u_i3, cfg.i3 ?? 0);
  481. gl.uniform1i(U_multi.u_i4, cfg.i4 ?? 0);
  482. gl.uniform1i(U_multi.u_i5, cfg.i5 ?? 0);
  483. gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, cfg.texIn ?? texX);
  484. gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, cfg.texW ?? texW);
  485. gl.bindFramebuffer(gl.FRAMEBUFFER, cfg.fbo ?? fboMulti);
  486. gl.viewport(0, cfg.yOff ?? 0, cfg.vpW, cfg.vpH);
  487. gl.drawArrays(gl.TRIANGLES, 0, 3);
  488. }
  489.  
  490. function readRow(fbo, attachment, wTexels, y, out){
  491. gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
  492. gl.readBuffer(gl.COLOR_ATTACHMENT0 + attachment);
  493. gl.readPixels(0, y, wTexels, 1, gl.RGBA, gl.FLOAT, out);
  494. gl.readBuffer(gl.COLOR_ATTACHMENT0);
  495. gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  496. }
  497.  
  498. // ============================================================ Thunk builders
  499. function thunkPartial(B) {
  500. return () => dispatchSingle({
  501. mode: 57, fbo: fboPartial, vpW: PXW_OUTQ*P, vpH: B,
  502. i0: P, i1: PXW_OUTQ, i2: Math.ceil(D/P), rowOff: D,
  503. texIn: texX, texW: texW,
  504. });
  505. }
  506. function thunkReduce(B) {
  507. return () => dispatchSingle({
  508. mode: 58, fbo: fboQkvFlat, vpW: PXW_OUTQ, vpH: B,
  509. i0: PXW_OUTQ, i1: P, texIn: texPartial,
  510. });
  511. }
  512. function thunkReduceMRT(B) {
  513. return () => dispatchMulti({
  514. mode: 580, fbo: fboMulti, vpW: PXW_Q, vpH: B,
  515. i0: PXW_OUTQ, i1: P, i2: 0, i3: PXW_Q, i4: PXW_KV,
  516. texIn: texPartial,
  517. });
  518. }
  519. function thunkAppendK(b, startPos) {
  520. return () => dispatchSingle({
  521. mode: 38, fbo: fboKCache, vpW: PXW_KV, vpH: 1, yOff: startPos + b,
  522. i0: PXW_Q, i1: b, texIn: texQkvFlat,
  523. });
  524. }
  525. function thunkAppendV(b, startPos) {
  526. return () => dispatchSingle({
  527. mode: 38, fbo: fboVCache, vpW: PXW_KV, vpH: 1, yOff: startPos + b,
  528. i0: PXW_Q + PXW_KV, i1: b, texIn: texQkvFlat,
  529. });
  530. }
  531. function thunksCurrent(B, startPos) {
  532. const th = [thunkPartial(B), thunkReduce(B)];
  533. for (let b = 0; b < B; b++) { th.push(thunkAppendK(b, startPos)); th.push(thunkAppendV(b, startPos)); }
  534. return th;
  535. }
  536. function thunksMRT(B) {
  537. return [thunkPartial(B), thunkReduceMRT(B)];
  538. }
  539.  
  540. // ============================================================ Measurement
  541. function median(a){ const s=[...a].sort((x,y)=>x-y); const m=Math.floor(s.length/2); return s.length%2?s[m]:(s[m-1]+s[m])/2; }
  542. function fmt(ms){
  543. if (ms < 0.001) return (ms*1e6).toFixed(2)+'ns';
  544. if (ms < 1) return (ms*1000).toFixed(2)+'µs';
  545. return ms.toFixed(2)+'ms';
  546. }
  547. function measureSeq(thunks, drawCount) {
  548. const runOnce = () => { for (const t of thunks) t(); };
  549. for (let i = 0; i < MEAS.warm; i++) runOnce();
  550. const rb = new Float32Array(4);
  551. gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, rb);
  552.  
  553. const est = [];
  554. for (let s = 0; s < MEAS.estLoops; s++) {
  555. const t0 = performance.now();
  556. for (let i = 0; i < MEAS.estReps; i++) runOnce();
  557. gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, rb);
  558. est.push(performance.now() - t0);
  559. }
  560. const estPer = Math.max(0.0005, median(est) / MEAS.estReps);
  561. const reps = Math.max(MEAS.minReps, Math.min(MEAS.maxReps, Math.ceil(MEAS.target / estPer)));
  562.  
  563. const samples = [];
  564. for (let s = 0; s < MEAS.samples; s++) {
  565. const t0 = performance.now();
  566. for (let i = 0; i < reps; i++) runOnce();
  567. gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, rb);
  568. samples.push(performance.now() - t0);
  569. }
  570. const med = median(samples);
  571. const mn = Math.min(...samples);
  572. return { perRep: med / reps, reps, totalMs: med, confidence: mn / med, drawCount };
  573. }
  574.  
  575. // ============================================================ Comparison core
  576. function compare(got, ref, refOff){
  577. let maxAbs = 0, maxRel = 0, worst = -1;
  578. for (let i = 0; i < got.length; i++) {
  579. const r = ref[refOff + i];
  580. const d = Math.abs(got[i] - r);
  581. const rel = d / Math.max(Math.abs(r), 0.05);
  582. if (d > maxAbs) { maxAbs = d; worst = i; }
  583. if (rel > maxRel) maxRel = rel;
  584. }
  585. return { maxAbs, maxRel, worst };
  586. }
  587. function maxDiff(a, b){
  588. let m = 0;
  589. for (let i = 0; i < a.length; i++){ const d = Math.abs(a[i]-b[i]); if (d > m) m = d; }
  590. return m;
  591. }
  592. function f6(x){ return (x>=0?'+':'') + x.toFixed(6); }
  593. function fe(x){ return x.toExponential(2); }
  594.  
  595. // Full correctness ladder at batch B, spot-checked at `rows`.
  596. // V1: mode-58 flat reduce vs f64 mirror (covers 57 partial + uploads + readback)
  597. // V2: mode-580 MRT vs mode-58 flat (isolates 580 indexing on the SAME partials)
  598. // V3: mode-580 attachments vs f64 mirror (end-to-end)
  599. // DV: real distinctness max|K-V|, |Q-K|, |Q-V| (the docs' K==V==−0.007 anomaly detector)
  600. function verifyLadder(B, rows){
  601. thunkPartial(B)(); thunkReduce(B)(); thunkReduceMRT(B)();
  602. const out = { B, rows: [], v1:{maxAbs:0,ok:true}, v2:{maxAbs:0,ok:true},
  603. v3:{maxAbs:0,ok:true}, dist:{kv:0,qk:0,qv:0,ok:true} };
  604. const flat = new Float32Array(PXW_OUTQ*4);
  605. const atts = [new Float32Array(PXW_Q*4), new Float32Array(PXW_Q*4), new Float32Array(PXW_Q*4)];
  606. for (const row of rows) {
  607. const ref = cpuRef(row);
  608. readRow(fboQkvFlat, 0, PXW_OUTQ, row, flat);
  609. const c1 = compare(flat, ref, 0);
  610. out.v1.maxAbs = Math.max(out.v1.maxAbs, c1.maxAbs);
  611. for (let a = 0; a < 3; a++) readRow(fboMulti, a, PXW_Q, row, atts[a]);
  612. const segs = [[0,0],[768,768],[1536,1536]];
  613. const segNames = ['Q','K','V'];
  614. const per = [];
  615. for (let a = 0; a < 3; a++) {
  616. const cFlat = compare(atts[a], flat, segs[a][1]); // V2: att vs flat segment
  617. const cRef = compare(atts[a], ref, segs[a][0]); // V3: att vs f64 mirror
  618. out.v2.maxAbs = Math.max(out.v2.maxAbs, cFlat.maxAbs);
  619. out.v3.maxAbs = Math.max(out.v3.maxAbs, cRef.maxAbs);
  620. per.push({ seg: segNames[a], flat: cFlat, ref: cRef });
  621. }
  622. const kv = maxDiff(atts[1], atts[2]), qk = maxDiff(atts[0], atts[1]), qv = maxDiff(atts[0], atts[2]);
  623. out.dist.kv = Math.max(out.dist.kv, kv); out.dist.qk = Math.max(out.dist.qk, qk); out.dist.qv = Math.max(out.dist.qv, qv);
  624. out.rows.push({ row, c1, per, kv, qk, qv,
  625. k8: Array.from(atts[1].slice(0,8)), v8: Array.from(atts[2].slice(0,8)),
  626. refk8: Array.from(ref.slice(768,776)), refv8: Array.from(ref.slice(1536,1544)) });
  627. }
  628. out.v1.ok = out.v1.maxAbs < TOL.passAbs;
  629. out.v2.ok = out.v2.maxAbs < TOL.passAbs;
  630. out.v3.ok = out.v3.maxAbs < TOL.passAbs;
  631. out.dist.ok = out.dist.kv > TOL.distMin && out.dist.qk > TOL.distMin && out.dist.qv > TOL.distMin;
  632. return out;
  633. }
  634.  
  635. function ladderLog(tag, L){
  636. bootLog(L.v1.ok?'ok':'bad', tag+'.v1', `flat(58) vs f64 mirror: B=${L.B} maxabs ${fe(L.v1.maxAbs)} ${L.v1.ok?'PASS':'FAIL'}`);
  637. bootLog(L.v2.ok?'ok':'bad', tag+'.v2', `MRT(580) vs flat(58) same partials: maxabs ${fe(L.v2.maxAbs)} ${L.v2.ok?'PASS':'FAIL'}`);
  638. bootLog(L.v3.ok?'ok':'bad', tag+'.v3', `MRT(580) vs f64 mirror: maxabs ${fe(L.v3.maxAbs)} ${L.v3.ok?'PASS':'FAIL'}`);
  639. bootLog(L.dist.ok?'ok':'bad', tag+'.kv', `distinctness max|K-V|=${L.dist.kv.toFixed(4)} max|Q-K|=${L.dist.qk.toFixed(4)} max|Q-V|=${L.dist.qv.toFixed(4)} ${L.dist.ok?'PASS (K!=V is real)':'FAIL (K==V anomaly REPRODUCED)'}`);
  640. const r0 = L.rows[0];
  641. bootLog('dim', tag+'.k8', `K[0..7]=${r0.k8.map(v=>v.toFixed(3)).join(' ')}`);
  642. bootLog('dim', tag+'.refk8',`refK[0..7]=${r0.refk8.map(v=>v.toFixed(3)).join(' ')}`);
  643. }
  644.  
  645. // ============================================================ Tests
  646. const results = [];
  647. const issues = {};
  648.  
  649. function addResult(test, config, m) {
  650. results.push({ test, config, ...m });
  651. renderResults();
  652. }
  653. function setIssue(id, verdict, metric, label, action, detail) {
  654. issues[id] = { id, verdict, metric, label, action, detail };
  655. renderIssueMap();
  656. console.log(`[issue ${id}] ${verdict} metric=${metric} ${label} — ${action}`);
  657. }
  658.  
  659. async function testF() {
  660. const outs = [];
  661. for (const n of [1, 2, 4, 8, 16, 32]) {
  662. const thunks = [];
  663. for (let i = 0; i < n; i++)
  664. thunks.push(() => dispatchSingle({ mode: 98, fbo: fboQkvFlat, vpW: PXW_Q, vpH: 1 }));
  665. const m = measureSeq(thunks, n);
  666. addResult('F', `noop ×${n}`, m);
  667. outs.push({ n, perRep: m.perRep });
  668. }
  669. const perDraw16 = outs[4].perRep / 16;
  670. const perDraw32 = outs[5].perRep / 32;
  671. const floor = Math.min(perDraw16, perDraw32);
  672. let verdict, action;
  673. if (floor < 0.003) { verdict='pass'; action=`draw floor is cheap (${(floor*1e3).toFixed(2)}µs) — count dispatches`; }
  674. else if (floor < 0.010) { verdict='warn'; action='moderate floor (3-10µs) — count dispatches'; }
  675. else { verdict='fail'; action='high floor — reduce draw count or switch to WebGPU'; }
  676. setIssue('F', verdict, floor*1e3, 'µs/draw', action,
  677. `noop×16 ${(perDraw16*1e3).toFixed(2)}µs/draw, ×32 ${(perDraw32*1e3).toFixed(2)}µs/draw (readPixels-sync inflated at low N — ×16/×32 is the estimate)`);
  678. return floor;
  679. }
  680.  
  681. async function testS() {
  682. const mrtThunk = () => dispatchMulti({ mode: 99, fbo: fboMulti, vpW: PXW_Q, vpH: 1, texIn: texX });
  683. const mMRT = measureSeq([mrtThunk], 1);
  684. addResult('S', 'MRT ×1 (3 out)', mMRT);
  685. const sepThunks = [
  686. () => dispatchSingle({ mode: 99, fbo: fboQPool, vpW: PXW_Q, vpH: 1, texIn: texX }),
  687. () => dispatchSingle({ mode: 99, fbo: fboKCache, vpW: PXW_Q, vpH: 1, texIn: texX }),
  688. () => dispatchSingle({ mode: 99, fbo: fboVCache, vpW: PXW_Q, vpH: 1, texIn: texX }),
  689. ];
  690. const mSep = measureSeq(sepThunks, 3);
  691. addResult('S', '3 × sep (1 out)', mSep);
  692. const ratio = mMRT.perRep / Math.max(0.0001, mSep.perRep);
  693. let verdict, action;
  694. if (ratio < 0.8) { verdict='pass'; action=`MRT saves ${((1-ratio)*100).toFixed(0)}% per trivial 3-target write — multi-target write is genuinely cheaper`; }
  695. else if (ratio < 1.05) { verdict='warn'; action='MRT ≈ separate — marginal benefit'; }
  696. else { verdict='fail'; action='MRT slower than separate — skip arch shift'; }
  697. setIssue('S', verdict, ratio, 'MRT/sep', action, `MRT ${fmt(mMRT.perRep)} vs sep ${fmt(mSep.perRep)}`);
  698. }
  699.  
  700. async function testQ() {
  701. const th = thunksCurrent(1, 0);
  702. const m = measureSeq(th, th.length);
  703. addResult('Q', 'current 57+58+38K+38V (B=1)', m);
  704. setIssue('Q', 'pass', m.perRep*1e3, 'µs/rep', 'decode baseline: 4 draws (57+58+38K+38V) — keep for decode',
  705. `per-rep ${fmt(m.perRep)} over ${th.length} draws`);
  706. return m;
  707. }
  708.  
  709. async function testQM(_floorUs, qRef) {
  710. const th = thunksMRT(1);
  711. const m = measureSeq(th, th.length);
  712. addResult('QM', 'MRT 57+580 (B=1)', m);
  713. if (!qRef) { setIssue('QM','warn', 0, 'n/a', 'no Q baseline measured — run Q first', ''); return m; }
  714. const ratio = m.perRep / Math.max(1e-7, qRef.perRep);
  715. const savings = 1 - ratio;
  716. let verdict, action;
  717. if (ratio <= 0.98) { verdict='pass'; action=`decode slice saves ${(savings*100).toFixed(1)}% — still noise-level per token`; }
  718. else if (ratio <= 1.02) { verdict='warn'; action='savings ≈ 0 (docs predict 3.6% on the slice = 0.7%/token) — not worth the Q-pool memory'; }
  719. else { verdict='fail'; action='MRT decode path SLOWER — keep decode on 3-dispatch'; }
  720. setIssue('QM', verdict, savings*100, '% saved', action,
  721. `MRT ${fmt(m.perRep)} vs current ${fmt(qRef.perRep)} (${th.length} vs ${qRef.drawCount ?? 4} draws) — 2 appends eliminated`);
  722. return m;
  723. }
  724.  
  725. async function testP() {
  726. const thCur = thunksCurrent(8, 0);
  727. const mCur = measureSeq(thCur, thCur.length);
  728. addResult('P', `current (B=8, ${thCur.length} draws)`, mCur);
  729. const thMRT = thunksMRT(8);
  730. const mMRT = measureSeq(thMRT, thMRT.length);
  731. addResult('P', `MRT (B=8, ${thMRT.length} draws)`, mMRT);
  732. const ratio = mMRT.perRep / Math.max(1e-7, mCur.perRep);
  733. let verdict, action;
  734. if (ratio < 0.85) { verdict='pass'; action=`MRT saves ${((1-ratio)*100).toFixed(0)}% on B=8 prefill slice`; }
  735. else if (ratio < 1.0) { verdict='warn'; action='MRT saves <15% on prefill — marginal'; }
  736. else { verdict='fail'; action='MRT slower on prefill — skip'; }
  737. setIssue('P', verdict, ratio, 'MRT/cur B=8', action,
  738. `MRT ${fmt(mMRT.perRep)} vs current ${fmt(mCur.perRep)} — 16 appends eliminated`);
  739. }
  740.  
  741. async function testP100(floorUs) {
  742. // correctness spot rows FIRST (outside the timed region)
  743. const L = verifyLadder(100, [0, 50, 99]);
  744. ladderLog('p100', L);
  745. const spotOK = L.v1.ok && L.v2.ok && L.v3.ok;
  746.  
  747. const thCur = thunksCurrent(100, 0);
  748. const mCur = measureSeq(thCur, thCur.length);
  749. addResult('P100', `current (B=100, ${thCur.length} draws)`, mCur);
  750. const thMRT = thunksMRT(100);
  751. const mMRT = measureSeq(thMRT, thMRT.length);
  752. addResult('P100', `MRT (B=100, ${thMRT.length} draws)`, mMRT);
  753.  
  754. const saved = mCur.perRep - mMRT.perRep;
  755. const floorModel = 200 * (floorUs ?? 0.00266);
  756. const explained = saved > 0 ? Math.min(1, floorModel / saved) : 0;
  757. const ratio = mMRT.perRep / Math.max(1e-7, mCur.perRep);
  758. let verdict, action;
  759. if (!spotOK) { verdict='fail'; action='B=100 correctness FAILED — do not integrate'; }
  760. else if (ratio < 0.85) { verdict='pass'; action=`B=100 confirms extrapolation: ${fmt(saved)} saved/rep, floor model explains ${(explained*100).toFixed(0)}%`; }
  761. else if (ratio < 1.0) { verdict='warn'; action='B=100 savings <15% — floor model overestimated'; }
  762. else { verdict='fail'; action='MRT slower at B=100 — docs extrapolation broken'; }
  763. setIssue('P100', verdict, ratio, 'MRT/cur B=100', action,
  764. `saved ${fmt(saved)} vs floor model ${(floorModel*1e3).toFixed(0)}µs (200 appends × floor); spot rows {0,50,99} ${spotOK?'OK':'FAILED'}; ` +
  765. `extrapolated ×32 layers ≈ ${(saved*32*1e3).toFixed(0)}µs/prefill (slice-level saving × all layers)`);
  766. }
  767.  
  768. async function testV() {
  769. const L = verifyLadder(8, [0, 3, 7]);
  770. ladderLog('V', L);
  771. // cross-check the f64 mirror itself against the independent numpy ground truth
  772. const ref0 = cpuRef(0);
  773. const gtOK = Math.abs(ref0[0] - GT_ROW0.q) < 1e-4 && Math.abs(ref0[768] - GT_ROW0.k) < 1e-4 && Math.abs(ref0[1536] - GT_ROW0.v) < 1e-4;
  774. bootLog(gtOK?'ok':'bad','V.cpu',`f64 mirror row0: Q=${f6(ref0[0])} K=${f6(ref0[768])} V=${f6(ref0[1536])} — numpy GT ${f6(GT_ROW0.q)}/${f6(GT_ROW0.k)}/${f6(GT_ROW0.v)} ${gtOK?'MATCH':'MISMATCH'}`);
  775. window.__ladderV = L;
  776.  
  777. const r0 = L.rows[0];
  778. if (L.v1.ok && L.v2.ok && L.v3.ok && L.dist.ok) {
  779. setIssue('V', 'pass', L.v3.maxAbs, 'maxabs vs f64', 'MRT writes Q,K,V correctly; K != V verified on FULL 768-float rows',
  780. `V1 ${fe(L.v1.maxAbs)} / V2 ${fe(L.v2.maxAbs)} / V3 ${fe(L.v3.maxAbs)}; max|K-V|=${L.dist.kv.toFixed(3)}; K[0]=${r0.k8[0].toFixed(4)} V[0]=${r0.v8[0].toFixed(4)} (docs' -0.007/-0.007 NOT reproduced)`);
  781. } else if (!L.v2.ok) {
  782. setIssue('V', 'fail', L.v2.maxAbs, 'maxabs MRT/flat', 'mode 580 MRT reduce diverges from mode 58 flat on the SAME partials -> 580 indexing bug',
  783. JSON.stringify(L.rows[0].per));
  784. } else if (!L.v1.ok) {
  785. setIssue('V', 'fail', L.v1.maxAbs, 'maxabs flat/f64', 'flat(58) wrong vs f64 mirror while MRT==flat -> bug in 57 partial, weight/x upload, or readback',
  786. `row0 K[0]=${r0.k8[0].toFixed(4)} vs ref ${r0.refk8[0].toFixed(4)}`);
  787. } else {
  788. setIssue('V', 'fail', L.dist.kv, 'max|K-V|', 'K==V anomaly REPRODUCED — values match but are not distinct (docs\' suspicion confirmed)',
  789. `max|K-V|=${L.dist.kv.toFixed(4)}`);
  790. }
  791. return L;
  792. }
  793.  
  794. // ============================================================ Render
  795. function renderResults() {
  796. const tb = document.getElementById('resultsBody');
  797. tb.innerHTML = '';
  798. for (const r of results) {
  799. const tr = document.createElement('tr');
  800. tr.innerHTML = `
  801. <td>${r.test}</td><td>${r.config}</td>
  802. <td class="num">${fmt(r.perRep)}</td><td class="num">${r.reps}</td>
  803. <td class="num">${r.totalMs.toFixed(1)}</td><td class="num">${r.confidence.toFixed(2)}</td>
  804. <td class="num">${r.drawCount}</td>`;
  805. tb.appendChild(tr);
  806. }
  807. }
  808. function fmtMetric(m){
  809. if (m !== 0 && Math.abs(m) < 0.001) return m.toExponential(2);
  810. return m.toFixed(3);
  811. }
  812. function renderIssueMap() {
  813. const tb = document.getElementById('issueMapBody');
  814. const order = ['F','S','Q','QM','P','P100','V'];
  815. const names = {
  816. F: 'Dispatch floor', S: 'MRT vs separate (trivial)',
  817. Q: 'Current QKV path B=1', QM: 'MRT QKV path B=1',
  818. P: 'Prefill B=8', P100: 'Prefill B=100 + spot rows', V: 'MRT correctness ladder',
  819. };
  820. tb.innerHTML = '';
  821. for (const id of order) {
  822. const tr = document.createElement('tr');
  823. const iss = issues[id];
  824. if (!iss) {
  825. tr.className = 'pending';
  826. tr.innerHTML = `<td>${id}</td><td>${names[id]}</td><td class="num">—</td><td class="verdict">pending</td><td>—</td>`;
  827. } else {
  828. tr.className = iss.verdict;
  829. tr.innerHTML = `<td>${id}</td><td>${names[id]}</td>` +
  830. `<td class="num">${fmtMetric(iss.metric)} ${iss.label}</td>` +
  831. `<td class="verdict">${iss.verdict}</td><td>${iss.action}</td>`;
  832. }
  833. tb.appendChild(tr);
  834. }
  835. }
  836.  
  837. function renderInterpretation() {
  838. const el = document.getElementById('interpretation');
  839. const lines = [];
  840. lines.push('<b>Summary:</b>');
  841. for (const id of ['F','S','Q','QM','P','P100','V']) {
  842. const i = issues[id];
  843. if (i) lines.push(`&nbsp; <span class="${i.verdict==='pass'?'ok':i.verdict==='warn'?'warn':'bad'}">[${i.verdict}]</span> ${id} — ${i.detail}`);
  844. }
  845. lines.push('');
  846. lines.push('<b>What v2 fixes vs microbench4 (docs appendage):</b>');
  847. lines.push('&nbsp; • Q/QM now call setIssue — issue map no longer shows "pending"');
  848. lines.push('&nbsp; • V is a ladder: V1 flat-vs-f64, V2 MRT-vs-flat (same partials), V3 MRT-vs-f64, over FULL 768-float rows at rows {0,3,7} + real K!=V distinctness');
  849. lines.push('&nbsp; • K[0]==V[0]==−0.007 ROOT-CAUSED live on SwiftShader: the bench never set the u_in/u_w sampler uniforms (GLSL samplers default to unit 0) → mode 57 fetched weights from the x texture; only c&lt;⌈xSize/OUTQ⌉ terms were in-bounds, strips sp≥1 exactly zero → a ~±0.005 x·x residue that still passes nonzero+distinctness smoke checks. MRT reduce (580) was EXACT the whole time (V2 diff 0.0). Fix: gl.uniform1i(u_in,0)+gl.uniform1i(u_w,1) after link — one line, driver-independent.');
  850. lines.push('&nbsp; • P100: B=100 prefill (202 vs 2 draws) with per-row inputs + spot-row correctness {0,50,99}');
  851. lines.push('&nbsp; • v2 latent fix: texX is paged 4096-wide now — original height=1 silently clipped inputs >16k floats (breaks B≥22)');
  852. lines.push('');
  853. lines.push('<b>Arch shift memory analysis (docs):</b>');
  854. lines.push('&nbsp; Q pool must be (d/4, maxLen) for MRT to align Q, K, V at the same (x, p).');
  855. const sizes = [
  856. ['25M (d=512, len=1024)', 512, 1024],
  857. ['1B (d=2048, len=2048)', 2048, 2048],
  858. ['7B (d=4096, len=4096)', 4096, 4096],
  859. ['13B (d=5120, len=4096)', 5120, 4096],
  860. ];
  861. for (const [name, d, len] of sizes) {
  862. const mb = d * len * 4 / (1024*1024);
  863. lines.push(`&nbsp; ${name}: Q pool = ${mb.toFixed(1)} MB (shared across layers)`);
  864. }
  865. lines.push('&nbsp; • GQA models (70B: d=8192, kvd=1024): Q width ≠ K/V width — MRT needs equal dims; pad = 8× KV waste ⇒ out');
  866. lines.push('&nbsp; • decode (startPos>0): Q pool needs maxLen rows — priced above; prefill (startPos=0): B rows only — free');
  867. lines.push('');
  868. const fails = Object.values(issues).filter(i => i.verdict === 'fail');
  869. const warns = Object.values(issues).filter(i => i.verdict === 'warn');
  870. if (fails.length) {
  871. lines.push('<b>Priority fixes:</b>');
  872. for (const i of fails) lines.push(`- <b>${i.id}</b>: ${i.action}`);
  873. }
  874. if (warns.length) {
  875. lines.push('<b>Secondary:</b>');
  876. for (const i of warns) lines.push(`- <b>${i.id}</b>: ${i.action}`);
  877. }
  878. if (!fails.length && !warns.length && issues.V) {
  879. lines.push('<b>Decision (per docs):</b> implement MRT for PREFILL ONLY, gate on startPos === 0 — memory-free, biggest win at high B. '+
  880. 'Keep decode on the current 3-dispatch path. Then profile the REAL model\'s prefill with MRT on: this bench\'s GEMV is layout-bound, '+
  881. 'the measured % is draw-floor savings; the real prefill gains depend on its floor-vs-bandwidth mix.');
  882. }
  883. el.innerHTML = lines.join('<br>');
  884. }
  885.  
  886. // ============================================================ Orchestrator
  887. const TESTS = [
  888. ['chkF','F',testF], ['chkS','S',testS], ['chkQ','Q',testQ], ['chkQM','QM',testQM],
  889. ['chkP','P',testP], ['chkP100','P100',testP100], ['chkV','V',testV],
  890. ];
  891.  
  892. let sweepToken = 0;
  893. async function runTests(enabledIds) {
  894. const myToken = ++sweepToken;
  895. const btn = document.getElementById('btnSweep');
  896. btn.disabled = true;
  897. window.__benchDone = false;
  898. const t0 = performance.now();
  899. try {
  900. if (!progSingle) setupPrograms();
  901. setupResources();
  902. let floorUs = null, qRef = null;
  903. // V first — never trust timings from an engine that fails correctness
  904. if (enabledIds.includes('V')) { await testV(); if (myToken !== sweepToken) return; }
  905. for (const [chkId, tid, fn] of TESTS) {
  906. if (tid === 'V' || !enabledIds.includes(tid)) continue;
  907. document.getElementById('status').textContent = `running ${tid}…`;
  908. try {
  909. const r = await fn(floorUs, qRef);
  910. if (tid === 'F') floorUs = r;
  911. if (tid === 'Q') qRef = r;
  912. } catch (e) {
  913. document.getElementById('status').textContent = `test ${tid} crashed: ${e.message}`;
  914. setIssue(tid, 'fail', 0, 'error', `crashed: ${e.message}`, String(e.message).slice(0,200));
  915. }
  916. if (myToken !== sweepToken) return;
  917. await new Promise(r => setTimeout(r, 20));
  918. }
  919. renderInterpretation();
  920. document.getElementById('status').textContent = `done in ${((performance.now()-t0)/1000).toFixed(1)}s`;
  921. } catch (e) {
  922. document.getElementById('status').textContent = 'FATAL: ' + e.message;
  923. console.error(e);
  924. bootLog('bad','FATAL', e.message);
  925. }
  926. window.__benchDone = true;
  927. btn.disabled = false;
  928. }
  929.  
  930. document.getElementById('btnSweep').onclick = () => {
  931. const ids = TESTS.filter(([id]) => document.getElementById(id).checked).map(([,tid]) => tid);
  932. runTests(ids);
  933. };
  934. document.getElementById('btnClear').onclick = () => {
  935. sweepToken++;
  936. results.length = 0;
  937. Object.keys(issues).forEach(k => delete issues[k]);
  938. renderResults(); renderIssueMap(); renderInterpretation();
  939. document.getElementById('status').textContent = 'cleared';
  940. window.__benchDone = true;
  941. };
  942.  
  943. // programmatic hook for headless harness
  944. window.__bench = {
  945. state: () => ({ issues, results, boot: bootLines }),
  946. bootText: () => bootLines.map(l => l.line).join('\n'),
  947. runSweep: (ids) => runTests(ids),
  948. ladder: () => window.__ladderV,
  949. };
  950.  
  951. // ============================================================ Boot
  952. (async () => {
  953. if (!gl) return;
  954. try {
  955. setupPrograms();
  956. setupResources();
  957. renderIssueMap();
  958. bootLog('ok','boot',`ready — ${FAST?'fast':'full'} measure profile, TOL pass ${TOL.passAbs}`);
  959. // self-test on load: correctness ladder + decode slice timing (minichat style: fail fast, numbers first)
  960. await runTests(['V','Q','QM']);
  961. window.__benchReady = true;
  962. document.getElementById('status').textContent +=
  963. '\nSelf-test done (V/Q/QM auto-ran). Use "Run selected" for the full sweep.';
  964. } catch (e) {
  965. document.getElementById('status').textContent = 'FATAL: ' + e.message;
  966. console.error(e);
  967. bootLog('bad','FATAL', e.message);
  968. window.__benchReady = true;
  969. }
  970. })();
  971. </script>
  972. </body>
  973. </html>
  974.  
Tags: html
Advertisement
Add Comment
Please, Sign In to add comment