Files
llm-atlas/scripts/check-training-systems-browser.mjs
T
2026-07-29 03:07:40 +08:00

244 lines
10 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 ?? "9225";
const baseUrl = process.env.SITE_URL ?? "http://127.0.0.1:4324";
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.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.text);
return result.result.value;
};
const navigate = async (path) => {
await command("Page.navigate", { url: `${baseUrl}${path}` });
for (let attempt = 0; attempt < 50; attempt += 1) {
await pause(100);
if (await evaluate("document.readyState === 'complete'")) return;
}
throw new Error(`${path} 加载超时`);
};
const screenshot = async (path, full = false) => {
const params = { format: "png", captureBeyondViewport: full };
if (full) {
const metrics = await command("Page.getLayoutMetrics");
params.clip = {
x: 0,
y: 0,
width: metrics.cssContentSize.width,
height: metrics.cssContentSize.height,
scale: 1,
};
}
const result = await command("Page.captureScreenshot", params);
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("/training-systems/");
await screenshot("/tmp/llm-atlas-training-systems-desktop.png");
const memory = await evaluate(`(() => {
const root = document.querySelector("[data-systems-lab]");
root.querySelector('[data-memory-preset="single"]').click();
const single = {
total: root.querySelector("[data-memory-total]").textContent,
verdict: root.querySelector("[data-memory-verdict]").textContent,
overflow: root.querySelector("[data-hbm-card]").classList.contains("is-overflow"),
};
root.querySelector('[data-memory-preset="k3"]').click();
return {
single,
k3: {
total: root.querySelector("[data-memory-total]").textContent,
verdict: root.querySelector("[data-memory-verdict]").textContent,
offload: root.querySelector('[data-memory-metric="offload"]').textContent,
formula: root.querySelector("[data-memory-formula]").textContent,
},
};
})()`);
const mesh = await evaluate(`(() => {
const root = document.querySelector("[data-systems-lab]");
root.querySelector('[data-system-tab="mesh"]').click();
root.querySelector('[data-mesh-preset="v3"]').click();
const v3 = {
equation: root.querySelector("[data-mesh-equation]").textContent,
status: root.querySelector("[data-mesh-capacity]").textContent,
invalid: root.querySelector("[data-mesh-status]").classList.contains("is-invalid"),
};
root.querySelector('[data-mesh-preset="invalid"]').click();
return {
v3,
invalid: {
equation: root.querySelector("[data-mesh-equation]").textContent,
status: root.querySelector("[data-mesh-capacity]").textContent,
invalid: root.querySelector("[data-mesh-status]").classList.contains("is-invalid"),
},
};
})()`);
const pipeline = await evaluate(`(() => {
const root = document.querySelector("[data-systems-lab]");
root.querySelector('[data-system-tab="pipeline"]').click();
root.querySelector('[data-schedule="1f1b"]').click();
const one = {
copies: root.querySelector('[data-pipeline-metric="copies"]').textContent,
reading: root.querySelector("[data-pipeline-reading]").textContent,
rows: root.querySelectorAll(".timeline-row").length,
};
root.querySelector('[data-schedule="dual"]').click();
return {
one,
dual: {
copies: root.querySelector('[data-pipeline-metric="copies"]').textContent,
reading: root.querySelector("[data-pipeline-reading]").textContent,
note: root.querySelector("[data-pipeline-note]").textContent,
},
};
})()`);
const communication = await evaluate(`(() => {
const root = document.querySelector("[data-systems-lab]");
root.querySelector('[data-system-tab="communication"]').click();
const overlap = root.querySelector('[data-comm-input="overlap"]');
overlap.value = "0";
overlap.dispatchEvent(new Event("input", { bubbles: true }));
const exposed = {
bytes: root.querySelector('[data-comm-metric="bytes"]').textContent,
time: root.querySelector('[data-comm-metric="exposed"]').textContent,
};
overlap.value = "95";
overlap.dispatchEvent(new Event("input", { bubbles: true }));
return {
exposed,
hidden: {
bytes: root.querySelector('[data-comm-metric="bytes"]').textContent,
time: root.querySelector('[data-comm-metric="exposed"]').textContent,
},
moe: {
deep: root.querySelector('[data-moe-metric="deep-buffer"]').textContent,
moon: root.querySelector('[data-moe-metric="moon-buffer"]').textContent,
slots: root.querySelector('[data-moe-metric="slots"]').textContent,
bars: root.querySelectorAll("[data-expert-rank]").length,
},
};
})()`);
const layout = await evaluate(`(() => {
const nav = document.querySelector(".top-nav");
const meta = document.querySelector(".header-meta");
return {
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
navGap: Number((meta.getBoundingClientRect().left - nav.getBoundingClientRect().right).toFixed(1)),
navLinks: document.querySelectorAll(".top-nav a").length,
articleSections: document.querySelectorAll(".article-section").length,
paperLinks: document.querySelectorAll(".paper-chain a").length,
labTabs: document.querySelectorAll("[data-system-tab]").length,
};
})()`);
await evaluate(`(() => {
document.documentElement.style.scrollBehavior = "auto";
document.querySelector("[data-systems-lab]").scrollIntoView({ block: "start", behavior: "instant" });
})()`);
await pause(150);
await screenshot("/tmp/llm-atlas-training-systems-lab-desktop.png");
await command("Emulation.setDeviceMetricsOverride", {
width: 390,
height: 844,
deviceScaleFactor: 1,
mobile: true,
});
await navigate("/training-systems/");
await screenshot("/tmp/llm-atlas-training-systems-mobile-closed.png");
const mobile = await evaluate(`(() => {
const toggle = document.querySelector("#menu-toggle");
toggle.click();
return {
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
menuVisible: getComputedStyle(toggle).display !== "none",
menuOpen: toggle.getAttribute("aria-expanded"),
title: document.querySelector("h1").innerText,
};
})()`);
await command("Emulation.setDeviceMetricsOverride", {
width: 1440,
height: 1100,
deviceScaleFactor: 1,
mobile: false,
});
await navigate("/");
const home = await evaluate(`({
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
releaseCards: document.querySelectorAll(".release-card").length,
firstRelease: document.querySelector(".release-card h2").textContent,
navLinks: document.querySelectorAll(".top-nav a").length,
})`);
await evaluate(`document.querySelector("#new-chapters").scrollIntoView({ block: "start", behavior: "instant" })`);
await pause(100);
await screenshot("/tmp/llm-atlas-home-training-systems-release.png");
const report = { memory, mesh, pipeline, communication, layout, mobile, home, exceptions };
console.log(JSON.stringify(report, null, 2));
const numeric = (value) => Number.parseFloat(value);
const failures = [];
if (!memory.single.overflow || !memory.single.verdict.includes("OOM")) failures.push("单卡 OOM 场景未触发");
if (!memory.k3.formula.includes("14P") || !memory.k3.verdict.includes("可以放入")) failures.push("K3 分层存储预设异常");
if (mesh.v3.invalid || !mesh.v3.equation.includes("2,048")) failures.push("V3 设备网格异常");
if (!mesh.invalid.invalid || !mesh.invalid.status.includes("相差")) failures.push("无效设备网格没有被拒绝");
if (pipeline.one.rows !== 4 || pipeline.one.copies !== "1×") failures.push("1F1B 时间轴异常");
if (pipeline.dual.copies !== "2×" || !pipeline.dual.note.includes("near-zero")) failures.push("DualPipe 边界解释缺失");
if (communication.exposed.bytes !== communication.hidden.bytes) failures.push("overlap 错误改变了通信字节");
if (numeric(communication.exposed.time) <= numeric(communication.hidden.time)) failures.push("overlap 没有降低 exposed time");
if (communication.moe.bars !== 12 || !communication.moe.slots.includes("E/R")) failures.push("MoonEP 对照异常");
if (layout.articleSections !== 16 || layout.paperLinks !== 37 || layout.labTabs !== 4) failures.push("章节、论文或实验数量异常");
if (layout.documentOverflow > 0 || mobile.documentOverflow > 0 || home.documentOverflow > 0) failures.push("页面存在横向溢出");
if (layout.navGap < 0) failures.push(`桌面导航碰撞:${layout.navGap}px`);
if (!mobile.menuVisible || mobile.menuOpen !== "true") failures.push("移动端菜单不可用");
if (home.releaseCards !== 6 || !home.firstRelease.includes("一万亿 Token")) failures.push("首页新章入口异常");
if (exceptions.length) failures.push(`浏览器脚本异常:${exceptions.join("; ")}`);
socket.close();
if (failures.length) {
failures.forEach((failure) => console.error(`- ${failure}`));
process.exit(1);
}