| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const fs = require("fs"); |
| const path = require("path"); |
| const V = require("./public/verified_core.js"); |
| const L = { mul: new Int16Array(fs.readFileSync(path.join(__dirname, "public", "mul_lut.bin")).buffer.slice(0)) }; |
|
|
| const f32 = Math.fround; |
| const randf = (n) => Float32Array.from({ length: n }, () => Math.random() * 2 - 1); |
|
|
| |
| |
| |
| function kernel(bug) { |
| return (Xq, Wq, rs, cs, d) => { |
| const { m, k, n } = d, batch = d.batch || 1, relu = !!d.relu, mul = L.mul, raw = !!d.acc; |
| const out = raw ? new Int32Array(batch * m * n) : new Float32Array(batch * m * n); |
| const acc = new Int32Array(n); |
| for (let bz = 0; bz < batch; bz++) { |
| const xo = bz * m * k, wo = bz * k * n, oo = bz * m * n, co = bz * n; |
| for (let i = 0; i < m; i++) { |
| acc.fill(0); |
| const xrow = xo + i * k; |
| for (let p = 0; p < k; p++) { |
| const au = (Xq[xrow + p] & 0xFF) * 256, wrow = wo + p * n; |
| for (let j = 0; j < n; j++) { |
| const prod = mul[au + (Wq[wrow + j] & 0xFF)]; |
| |
| if (bug === "accOverwrite") acc[j] = prod; else acc[j] += prod; |
| } |
| } |
| const orow = oo + i * n; |
| if (raw) { |
| for (let j = 0; j < n; j++) out[orow + j] = acc[j]; |
| continue; |
| } |
| const rscale = rs[bz * m + i]; |
| for (let j = 0; j < n; j++) { |
| |
| |
| |
| if (bug === "boundsMult8" && j >= (n - (n % 8))) { out[orow + j] = 0; continue; } |
| let v = V.epi(acc[j], rscale, cs[co + j]); |
| |
| if (bug === "droppedFactor") v = f32(v * 2); |
| |
| if (relu && bug === "leakyAlpha") v = v < 0 ? f32(v * 0.1) : v; |
| else if (relu && v < 0) v = 0; |
| out[orow + j] = v; |
| } |
| } |
| } |
| return out; |
| }; |
| } |
|
|
| |
| const TAXONOMY = { |
| accOverwrite: { from: "matmul_triton_buggy", where: "loop", desc: "acc= instead of acc+= (only correct at K=1)" }, |
| boundsMult8: { from: "softmax_llm_buggy", where: "loop", desc: "correct only when n is a multiple of the workgroup width" }, |
| droppedFactor: { from: "gelu_triton_buggy", where: "math", desc: "constant factor dropped, uniformly 2x" }, |
| leakyAlpha: { from: "leaky_relu_buggy", where: "math", desc: "wrong negative-slope alpha" }, |
| }; |
|
|
| |
| const SHAPES = { m: 6, k: 32, n: 5, batch: 1 }; |
| function quantize(Xf, Wf, d) { |
| const batch = d.batch || 1; |
| const x = V.quantizeRows(Xf, batch * d.m, d.k); |
| let wq, ws; |
| if (batch === 1) { const w = V.quantizeCols(Wf, d.k, d.n); wq = w.q; ws = w.s; } |
| else { |
| wq = new Int8Array(batch * d.k * d.n); ws = new Float32Array(batch * d.n); |
| for (let bz = 0; bz < batch; bz++) { |
| const w = V.quantizeCols(Wf.subarray(bz * d.k * d.n, (bz + 1) * d.k * d.n), d.k, d.n); |
| wq.set(w.q, bz * d.k * d.n); ws.set(w.s, bz * d.n); |
| } |
| } |
| return { x, wq, ws }; |
| } |
| const call = (K, Xf, Wf, d) => { const q = quantize(Xf, Wf, d); return K(q.x.q, q.wq, q.x.s, q.ws, d); }; |
|
|
| function propertySuite(K) { |
| |
| |
| |
| |
| { |
| const d = { ...SHAPES }; |
| const A = randf(d.m * d.k), B = randf(d.k * d.n); |
| const o = call(K, A, B, d); |
| let nz = 0; |
| for (let i = 0; i < o.length; i++) if (o[i] !== 0) nz++; |
| if (nz < o.length / 4) return "nonTriviality"; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| { |
| const d = { ...SHAPES, acc: true }; |
| const A = randf(d.m * d.k), B = randf(d.k * d.n); |
| for (let p = 0; p < d.k; p++) A[1 * d.k + p] = 0.3; |
| A[1 * d.k + 0] = 1.0; |
| const q0 = quantize(A, B, d); |
| const base = K(q0.x.q, q0.wq, q0.x.s, q0.ws, d); |
| for (let p = 1; p < d.k; p++) { |
| const A2 = Float32Array.from(A); |
| A2[1 * d.k + p] = -0.3; |
| const q2 = quantize(A2, B, d); |
| if (q2.x.s[1] !== q0.x.s[1]) continue; |
| const o2 = K(q2.x.q, q2.wq, q2.x.s, q2.ws, d); |
| let moved = false; |
| for (let j = 0; j < d.n; j++) if (o2[1 * d.n + j] !== base[1 * d.n + j]) { moved = true; break; } |
| if (!moved) return `sensitivity(A[.,${p}] ignored)`; |
| } |
| } |
| |
| |
| |
| |
| { |
| const d = { ...SHAPES, relu: true }; |
| const A = randf(d.m * d.k), B = randf(d.k * d.n); |
| const o = call(K, A, B, d); |
| for (let i = 0; i < o.length; i++) if (o[i] < 0) return "reluRange"; |
| } |
| |
| |
| |
| |
| |
| |
| { |
| const d = { m: 3, k: 5, n: 4, batch: 1 }; |
| const Xq = new Int8Array(d.m * d.k), Wq = new Int8Array(d.k * d.n); |
| for (let i = 0; i < Xq.length; i++) Xq[i] = ((i * 37 + 11) % 25) - 12; |
| for (let i = 0; i < Wq.length; i++) Wq[i] = ((i * 53 + 7) % 25) - 12; |
| const rs = new Float32Array(d.m).fill(1), cs = new Float32Array(d.n).fill(1); |
| const o = K(Xq, Wq, rs, cs, d); |
| for (let i = 0; i < d.m; i++) |
| for (let j = 0; j < d.n; j++) { |
| let dot = 0; |
| for (let p = 0; p < d.k; p++) dot += Xq[i * d.k + p] * Wq[p * d.n + j]; |
| if (o[i * d.n + j] !== dot) return "unitScaleAnchor"; |
| } |
| } |
| |
| { |
| const d = { ...SHAPES }; |
| const A = randf(d.m * d.k), B = randf(d.k * d.n); |
| for (let p = 0; p < d.k; p++) A[2 * d.k + p] = 0; |
| const o = call(K, A, B, d); |
| for (let j = 0; j < d.n; j++) if (o[2 * d.n + j] !== 0) return "zeroRow"; |
| } |
| |
| { |
| const d = { ...SHAPES }; |
| const A = randf(d.m * d.k), B = randf(d.k * d.n); |
| const perm = [3, 1, 5, 0, 4, 2]; |
| const Ap = new Float32Array(A.length); |
| perm.forEach((src, dst) => Ap.set(A.subarray(src * d.k, (src + 1) * d.k), dst * d.k)); |
| const o = call(K, A, B, d), op = call(K, Ap, B, d); |
| for (let r = 0; r < d.m; r++) for (let j = 0; j < d.n; j++) |
| if (op[r * d.n + j] !== o[perm[r] * d.n + j]) return "rowPermutation"; |
| } |
| |
| { |
| const d = { ...SHAPES }; |
| const A = randf(d.m * d.k), B = randf(d.k * d.n); |
| const perm = [2, 0, 4, 1, 3]; |
| const Bp = new Float32Array(B.length); |
| for (let p = 0; p < d.k; p++) perm.forEach((src, dst) => { Bp[p * d.n + dst] = B[p * d.n + src]; }); |
| const o = call(K, A, B, d), op = call(K, A, Bp, d); |
| for (let r = 0; r < d.m; r++) for (let j = 0; j < d.n; j++) |
| if (op[r * d.n + j] !== o[r * d.n + perm[j]]) return "colPermutation"; |
| } |
| |
| { |
| const d = { m: 4, k: 32, n: 5, batch: 3 }; |
| const A = randf(d.batch * d.m * d.k), B = randf(d.batch * d.k * d.n); |
| const together = call(K, A, B, d); |
| for (let bz = 0; bz < d.batch; bz++) { |
| const alone = call(K, A.subarray(bz * d.m * d.k, (bz + 1) * d.m * d.k), |
| B.subarray(bz * d.k * d.n, (bz + 1) * d.k * d.n), { ...d, batch: 1 }); |
| for (let i = 0; i < alone.length; i++) |
| if (alone[i] !== together[bz * d.m * d.n + i]) return "batchDecomposition"; |
| } |
| } |
| return null; |
| } |
|
|
| |
| function differentialGate(K) { |
| for (const d of [{ m: 5, k: 9, n: 6, batch: 3, relu: true }, |
| { m: 32, k: 64, n: 32, batch: 1, relu: false }, |
| { m: 7, k: 253, n: 5, batch: 2, relu: true }, |
| { m: 17, k: 33, n: 9, batch: 1, relu: true }]) { |
| const A = randf(d.batch * d.m * d.k), B = randf(d.batch * d.k * d.n); |
| const q = quantize(A, B, d); |
| for (const acc of [true, false]) { |
| const dd = { ...d, acc }; |
| const hw = K(q.x.q, q.wq, q.x.s, q.ws, dd); |
| const ref = V.bgemmJS(q.x.q, q.wq, q.x.s, q.ws, dd, L); |
| for (let i = 0; i < ref.length; i++) if (hw[i] !== ref[i]) return (acc ? "accumulator" : "epilogue"); |
| } |
| } |
| return null; |
| } |
|
|
| |
| module.exports = { propertySuite, differentialGate, quantize, call, kernel }; |
| if (require.main !== module) return; |
|
|
| |
| console.log("\nBugs from dipankarsarkar/gpuemu-corpus, re-injected into my kernel."); |
| console.log("Scoring the ORACLES, not the kernel.\n"); |
| console.log("bug lives in properties differential"); |
| console.log("---------------------------------------------------------------"); |
| const score = { prop: 0, diff: 0, total: 0 }; |
| for (const [bug, meta] of Object.entries(TAXONOMY)) { |
| const K = kernel(bug); |
| const p = propertySuite(K), dgate = differentialGate(K); |
| score.total++; |
| if (p) score.prop++; |
| if (dgate) score.diff++; |
| console.log(`${bug.padEnd(16)} ${meta.where.padEnd(10)} ${(p ? "CAUGHT (" + p + ")" : "MISSED").padEnd(17)} ${dgate ? "CAUGHT (" + dgate + ")" : "MISSED"}`); |
| } |
| |
| const cleanP = propertySuite(kernel(null)), cleanD = differentialGate(kernel(null)); |
| console.log(`${"(no bug)".padEnd(16)} ${"-".padEnd(10)} ${(cleanP ? "FALSE POSITIVE" : "clean").padEnd(17)} ${cleanD ? "FALSE POSITIVE" : "clean"}`); |
|
|
| console.log(`\nmutation score, properties: ${score.prop}/${score.total}`); |
| console.log(`mutation score, differential: ${score.diff}/${score.total}`); |
| console.log("\nby where the bug lives:"); |
| for (const w of ["loop", "math"]) { |
| const bugs = Object.entries(TAXONOMY).filter(([, m]) => m.where === w); |
| const caught = bugs.filter(([b]) => propertySuite(kernel(b)) !== null).length; |
| console.log(` ${w.padEnd(5)} properties caught ${caught}/${bugs.length}`); |
| } |
| const ok = !cleanP && !cleanD; |
| console.log(ok ? "\nCONTROL OK (clean kernel passes both oracles)" : "\nCONTROL FAILED"); |
| process.exit(ok ? 0 : 1); |
|
|