Files
llm-atlas/scripts/check-k3-attnres-spike-browser.mjs
T
2026-07-30 12:20:47 +08:00

217 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { writeFileSync } from "node:fs";
const cdpPort = process.env.CDP_PORT ?? "9229";
const baseUrl = process.env.SITE_URL ?? "http://127.0.0.1:4329";
const pages = await fetch(`http://127.0.0.1:${cdpPort}/json/list`).then((response) => response.json());
const page = pages.find((entry) => entry.type === "page");
if (!page) throw new Error(`CDP ${cdpPort} 没有可用页面`);
const socket = new WebSocket(page.webSocketDebuggerUrl);
await new Promise((resolve, reject) => {
socket.addEventListener("open", resolve, { once: true });
socket.addEventListener("error", reject, { once: true });
});
let nextId = 0;
const pending = new Map();
const exceptions = [];
socket.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
if (message.id && pending.has(message.id)) {
const { resolve, reject } = pending.get(message.id);
pending.delete(message.id);
if (message.error) reject(new Error(message.error.message));
else resolve(message.result);
}
if (message.method === "Runtime.exceptionThrown") {
exceptions.push(message.params.exceptionDetails.exception?.description ?? message.params.exceptionDetails.text);
}
});
const command = (method, params = {}) => new Promise((resolve, reject) => {
const id = ++nextId;
pending.set(id, { resolve, reject });
socket.send(JSON.stringify({ id, method, params }));
});
const pause = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
const evaluate = async (expression) => {
const result = await command("Runtime.evaluate", { expression, returnByValue: true, awaitPromise: true });
if (result.exceptionDetails) throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text);
return result.result.value;
};
const navigate = async (path) => {
await command("Page.navigate", { url: `${baseUrl}${path}` });
for (let attempt = 0; attempt < 100; attempt += 1) {
await pause(100);
if (await evaluate("document.readyState === 'complete'")) return;
}
throw new Error(`${path} 加载超时`);
};
const screenshot = async (path) => {
const result = await command("Page.captureScreenshot", { format: "png", captureBeyondViewport: false });
writeFileSync(path, Buffer.from(result.data, "base64"));
};
await command("Page.enable");
await command("Runtime.enable");
await command("Emulation.setDeviceMetricsOverride", {
width: 1440,
height: 1100,
deviceScaleFactor: 1,
mobile: false,
});
await navigate("/k3/");
const desktop = await evaluate(`(() => {
const root = document.querySelector("[data-spike-lab]");
root.scrollIntoView({ block: "start", behavior: "instant" });
window.scrollBy(0, -78);
const text = (selector) => root.querySelector(selector)?.textContent.trim();
const panel = () => root.querySelector("[data-spike-panel]:not([hidden])")?.dataset.spikePanel;
const points = (selector) => root.querySelector(selector)?.getAttribute("points");
const setSelect = (selector, value) => {
const node = root.querySelector(selector);
node.value = value;
node.dispatchEvent(new Event("change", { bubbles: true }));
};
const initial = {
panel: panel(),
tabs: root.querySelectorAll("[data-spike-tab]").length,
panels: root.querySelectorAll("[data-spike-panel]").length,
ledger: root.querySelectorAll(".spike-ledger article").length,
trajectoryPoints: root.querySelectorAll("[data-spike-time-points] circle").length,
trajectoryLine: points("[data-spike-time-line]"),
boundary: root.textContent.includes("不是训练出的 uniform 模型") &&
root.textContent.includes("不是 70.2% 因果贡献"),
};
root.querySelector('[data-spike-time-metric="population_cv"]').click();
setSelect("[data-spike-time-seed]", "2026073003");
const trajectoryChanged = {
title: text("[data-spike-time-title]"),
state: text("[data-spike-time-state]"),
line: points("[data-spike-time-line]"),
thresholdHidden: getComputedStyle(root.querySelector("[data-spike-time-threshold]")).display === "none",
};
root.querySelector('[data-spike-tab="positions"]').click();
root.querySelector('[data-spike-position="pre_mlp_input"]').click();
setSelect("[data-spike-position-seed]", "2026073002");
const positions = {
panel: panel(),
buttons: root.querySelectorAll("[data-spike-position]").length,
state: text("[data-spike-position-state]"),
contrast: text("[data-spike-position-contrast]"),
peak: text("[data-spike-position-peak]"),
peakValue: text("[data-spike-position-peak-value]"),
pointCount: root.querySelectorAll("[data-spike-position-points] circle").length,
};
root.querySelector('[data-spike-tab="reductions"]').click();
setSelect("[data-spike-reduction-seed]", "2026073003");
setSelect("[data-spike-reduction]", "token_rms_median");
const reductions = {
panel: panel(),
rows: root.querySelectorAll(".reduction-table tbody tr").length,
state: text("[data-spike-reduction-state]"),
contrast: text("[data-spike-reduction-contrast]"),
overlap: text("[data-spike-reduction-overlap]"),
rho: text("[data-spike-reduction-rho]"),
pointCount: root.querySelectorAll("[data-spike-reduction-points] circle").length,
};
root.querySelector('[data-spike-tab="intervention"]').click();
setSelect("[data-spike-intervention-seed]", "2026073002");
const intervention = {
panel: panel(),
lines: root.querySelectorAll("[data-spike-intervention-line]").length,
keyDrop: text("[data-spike-key-drop]"),
valueDrop: text("[data-spike-value-drop]"),
peakShift: text("[data-spike-peak-shift]"),
linePoints: [...root.querySelectorAll("[data-spike-intervention-line]")].map((node) => node.getAttribute("points").split(" ").length),
forwardIdentity: root.textContent.includes("same logits · same loss"),
};
root.querySelector('[data-spike-tab="mixer"]').click();
const mixerInitial = {
panel: panel(),
points: root.querySelectorAll("[data-spike-mixer-points] circle").length,
pearson: text("[data-spike-mixer-pearson]"),
spearman: text("[data-spike-mixer-spearman]"),
title: text("[data-spike-mixer-title]"),
};
root.querySelector('[data-spike-mixer-metric="mlp_entropy"]').click();
const mixerChanged = {
points: root.querySelectorAll("[data-spike-mixer-points] circle").length,
pearson: text("[data-spike-mixer-pearson]"),
spearman: text("[data-spike-mixer-spearman]"),
title: text("[data-spike-mixer-title]"),
};
const first = root.querySelector('[data-spike-tab="trajectory"]');
first.focus();
first.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true }));
const keyboard = {
selected: root.querySelector('[data-spike-tab][aria-selected="true"]').dataset.spikeTab,
panel: panel(),
};
return {
initial, trajectoryChanged, positions, reductions, intervention, mixerInitial, mixerChanged, keyboard,
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
rootOverflow: root.scrollWidth - root.clientWidth,
};
})()`);
await pause(180);
await screenshot("/tmp/llm-atlas-k3-attnres-spike-desktop.png");
await command("Emulation.setDeviceMetricsOverride", {
width: 390,
height: 844,
deviceScaleFactor: 1,
mobile: true,
});
await navigate("/k3/");
const mobile = await evaluate(`(() => {
const root = document.querySelector("[data-spike-lab]");
root.scrollIntoView({ block: "start", behavior: "instant" });
window.scrollBy(0, -64);
root.querySelector('[data-spike-tab="intervention"]').click();
return {
tabs: root.querySelectorAll("[data-spike-tab]").length,
ledger: root.querySelectorAll(".spike-ledger article").length,
visiblePanel: root.querySelector("[data-spike-panel]:not([hidden])")?.dataset.spikePanel,
lines: root.querySelectorAll("[data-spike-intervention-line]").length,
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
rootOverflow: root.scrollWidth - root.clientWidth,
};
})()`);
await pause(180);
await screenshot("/tmp/llm-atlas-k3-attnres-spike-mobile.png");
const report = { desktop, mobile, exceptions };
console.log(JSON.stringify(report, null, 2));
const numeric = (value) => Number.parseFloat(value.replace("−", "-").replace("%", ""));
const failures = [];
if (desktop.initial.panel !== "trajectory" || desktop.initial.tabs !== 5 || desktop.initial.panels !== 5 || desktop.initial.ledger !== 6 || desktop.initial.trajectoryPoints !== 6) failures.push("五视图或六点轨迹初始结构异常");
if (!desktop.initial.boundary) failures.push("因果与训练变体边界缺失");
if (!desktop.trajectoryChanged.title.includes("POPULATION CV") || !desktop.trajectoryChanged.state.includes("2026073003") || desktop.trajectoryChanged.line === desktop.initial.trajectoryLine || !desktop.trajectoryChanged.thresholdHidden) failures.push("轨迹 seed / metric 切换异常");
if (desktop.positions.panel !== "positions" || desktop.positions.buttons !== 6 || !desktop.positions.state.includes("MLP 前输入") || Math.abs(numeric(desktop.positions.contrast) - 4.527) > .001 || numeric(desktop.positions.peak) !== 21 || desktop.positions.pointCount !== 32) failures.push("六位置谱或 seed 切换异常");
if (desktop.reductions.panel !== "reductions" || desktop.reductions.rows !== 12 || !desktop.reductions.state.includes("Token RMS 中位数") || Math.abs(numeric(desktop.reductions.contrast) - 1.666) > .001 || desktop.reductions.overlap !== "3 / 5" || Math.abs(numeric(desktop.reductions.rho) - .934) > .001 || desktop.reductions.pointCount !== 32) failures.push("reduction robustness 视图异常");
if (desktop.intervention.panel !== "intervention" || desktop.intervention.lines !== 3 || Math.abs(numeric(desktop.intervention.keyDrop) + 1.93) > .01 || Math.abs(numeric(desktop.intervention.valueDrop) - 76.87) > .01 || desktop.intervention.peakShift !== "21 → 2" || desktop.intervention.linePoints.some((count) => count !== 32) || !desktop.intervention.forwardIdentity) failures.push("same-forward backward 干预视图异常");
if (desktop.mixerInitial.panel !== "mixer" || desktop.mixerInitial.points !== 30 || Math.abs(numeric(desktop.mixerInitial.pearson) - .690) > .001 || Math.abs(numeric(desktop.mixerInitial.spearman) - .693) > .001 || desktop.mixerChanged.points !== 30 || Math.abs(numeric(desktop.mixerChanged.pearson) + .636) > .001 || Math.abs(numeric(desktop.mixerChanged.spearman) + .640) > .001 || desktop.mixerInitial.title === desktop.mixerChanged.title) failures.push("mixer association 散点或指标切换异常");
if (desktop.keyboard.selected !== "positions" || desktop.keyboard.panel !== "positions") failures.push("键盘 tab 导航异常");
if (desktop.documentOverflow > 1 || desktop.rootOverflow > 1 || mobile.documentOverflow > 1 || mobile.rootOverflow > 1) failures.push("桌面或移动端出现文档级横向溢出");
if (mobile.tabs !== 5 || mobile.ledger !== 6 || mobile.visiblePanel !== "intervention" || mobile.lines !== 3) failures.push("移动端交互结构异常");
if (exceptions.length) failures.push(`浏览器异常:${exceptions.join(" | ")}`);
if (failures.length) {
console.error(`\nFAIL\n- ${failures.join("\n- ")}`);
process.exitCode = 1;
} else {
console.log("\nPASS K3 AttnRes spike browser regression");
}
socket.close();