junbin666's picture
Upload 2 files
a7faae0 verified
Raw History Blame Contribute Delete
12.8 kB
import { generateTritonConfig, summarizeReport } from "./core.mjs";
let latestConfig = "";
const $ = (selector) => document.querySelector(selector);
function activateTab(name, shouldScroll = true) {
const selectedTab = document.querySelector(`.tab[data-tab="${name}"]`);
if (!selectedTab) return;
document.querySelectorAll(".tab").forEach((tab) => {
const selected = tab === selectedTab;
tab.classList.toggle("active", selected);
tab.setAttribute("aria-selected", String(selected));
});
document.querySelectorAll(".tool-panel").forEach((panel) => {
const selected = panel.id === `panel-${name}`;
panel.classList.toggle("active", selected);
panel.hidden = !selected;
});
if (shouldScroll) document.querySelector(`#panel-${name}`).scrollIntoView({ behavior: "smooth", block: "start" });
}
document.querySelectorAll(".tab").forEach((button) => {
button.addEventListener("click", () => activateTab(button.dataset.tab, false));
});
document.querySelectorAll("[data-open-tab]").forEach((button) => {
button.addEventListener("click", async () => {
if (button.dataset.loadSample === "true") {
await loadSampleReport();
} else {
activateTab(button.dataset.openTab);
}
});
});
$("#start-tour").addEventListener("click", () => activateTab("config"));
$("#load-sample-hero").addEventListener("click", () => loadSampleReport());
function updateBatchExplainer() {
const maxBatch = Number($("input[name='max_batch_size']").value);
const dims = $("input[name='input_dims']").value
.replace(/[\[\]]/g, "")
.split(",")
.map((value) => value.trim())
.filter(Boolean);
const explainer = $("#batch-explainer");
if (!Number.isInteger(maxBatch) || maxBatch < 0 || !dims.length) {
explainer.textContent = "Enter valid dimensions and a non-negative max batch size to preview the tensor shape.";
return;
}
if (maxBatch === 0) {
explainer.textContent = `Batching is disabled. The model dimensions [${dims.join(", ")}] describe the complete input tensor; Triton will omit dynamic_batching.`;
return;
}
const exampleBatch = Math.min(8, maxBatch);
explainer.textContent = `For max_batch_size ${maxBatch}, these are per-sample dimensions. Example request: [${exampleBatch}, ${dims.join(", ")}]. Triton adds the leading batch axis.`;
}
$("input[name='max_batch_size']").addEventListener("input", updateBatchExplainer);
$("input[name='input_dims']").addEventListener("input", updateBatchExplainer);
$("#config-form").addEventListener("submit", (event) => {
event.preventDefault();
const payload = Object.fromEntries(new FormData(event.currentTarget).entries());
const state = $("#config-state");
try {
latestConfig = generateTritonConfig(payload);
$("#config-output code").textContent = latestConfig;
$("#copy-config").disabled = false;
$("#download-config").disabled = false;
state.textContent = "CONFIG READY";
updateBatchExplainer();
} catch (error) {
latestConfig = "";
$("#config-output code").textContent = error.message;
$("#copy-config").disabled = true;
$("#download-config").disabled = true;
state.textContent = "CHECK INPUT";
}
});
$("#copy-config").addEventListener("click", async () => {
if (!latestConfig) return;
try {
await navigator.clipboard.writeText(latestConfig);
const button = $("#copy-config");
button.textContent = "COPIED";
setTimeout(() => { button.textContent = "COPY"; }, 1200);
} catch {
$("#config-state").textContent = "SELECT AND COPY CONFIG";
}
});
$("#download-config").addEventListener("click", () => {
if (!latestConfig) return;
const name = $("input[name='model_name']").value.trim() || "model";
const blob = new Blob([latestConfig], { type: "text/plain;charset=utf-8" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = `${name}-config.pbtxt`;
link.click();
setTimeout(() => URL.revokeObjectURL(link.href), 0);
});
function clearReportResults() {
$("#report-results").replaceChildren();
$("#report-results").classList.add("hidden");
}
$("#report-file").addEventListener("change", async (event) => {
const file = event.target.files?.[0];
if (!file) return;
if (file.size > 2 * 1024 * 1024) {
$("#report-error").textContent = "Please choose a JSON file no larger than 2 MiB.";
$("#report-file").value = "";
clearReportResults();
return;
}
clearReportResults();
$("#report-json").value = await file.text();
$("#report-error").textContent = "";
});
$("#analyze-report").addEventListener("click", analyzeReportInput);
$("#load-sample-report").addEventListener("click", () => loadSampleReport({ shouldScroll: false }));
$("#load-llm-sample").addEventListener("click", () => loadLlmSampleReport());
async function loadSampleReport({ shouldScroll = true } = {}) {
activateTab("report", false);
$("#report-error").textContent = "";
clearReportResults();
try {
const response = await fetch("./sample-report-b1.json", { cache: "no-store" });
if (!response.ok) throw new Error(`Sample report unavailable (${response.status}).`);
const payload = await response.json();
$("#report-json").value = JSON.stringify(payload, null, 2);
renderReport(summarizeReport(payload));
if (shouldScroll) $("#panel-report").scrollIntoView({ behavior: "smooth", block: "start" });
} catch (error) {
$("#report-error").textContent = `${error.message} You can also paste or load a report JSON file.`;
}
}
async function loadLlmSampleReport() {
activateTab("report", false);
$("#report-error").textContent = "";
clearReportResults();
try {
const response = await fetch("./sample-llm-report.json", { cache: "no-store" });
if (!response.ok) throw new Error(`LLM schema example unavailable (${response.status}).`);
const payload = await response.json();
$("#report-json").value = JSON.stringify(payload, null, 2);
renderReport(summarizeReport(payload));
$("#panel-report").scrollIntoView({ behavior: "smooth", block: "start" });
} catch (error) {
$("#report-error").textContent = `${error.message} You can also paste or load a report JSON file.`;
}
}
function analyzeReportInput() {
const raw = $("#report-json").value.trim();
$("#report-error").textContent = "";
clearReportResults();
if (!raw) {
$("#report-error").textContent = "Paste or load a benchmark JSON report first.";
return;
}
if (new Blob([raw]).size > 2 * 1024 * 1024) {
$("#report-error").textContent = "Please keep the JSON report at or below 2 MiB.";
return;
}
try {
renderReport(summarizeReport(JSON.parse(raw)));
} catch (error) {
$("#report-error").textContent = error instanceof SyntaxError
? "Invalid JSON. Check the report syntax and try again."
: error.message;
}
}
$("#clear-report").addEventListener("click", () => {
$("#report-json").value = "";
$("#report-file").value = "";
$("#report-error").textContent = "";
clearReportResults();
});
function displayNumber(value, digits = 2) {
if (typeof value !== "number" || !Number.isFinite(value)) return "—";
return value.toLocaleString(undefined, { maximumFractionDigits: digits });
}
function formatLatencyGroup(group) {
return [group.mean_ms, group.p50_ms, group.p99_ms].map((value) => displayNumber(value)).join(" / ");
}
function buildTable(headers, rows, className) {
const table = document.createElement("table");
table.className = className;
const thead = document.createElement("thead");
const heading = document.createElement("tr");
for (const label of headers) {
const th = document.createElement("th");
th.textContent = label;
heading.append(th);
}
thead.append(heading);
const tbody = document.createElement("tbody");
for (const row of rows) {
const tr = document.createElement("tr");
for (const value of row) {
const td = document.createElement("td");
td.textContent = String(value);
tr.append(td);
}
tbody.append(tr);
}
table.append(thead, tbody);
return table;
}
function renderReport(report) {
const container = $("#report-results");
container.replaceChildren();
const availableCount = report.rows.filter((row) => row.available).length;
const summary = document.createElement("div");
summary.className = "result-takeaway";
const summaryTitle = document.createElement("strong");
summaryTitle.textContent = report.schemaDemo ? "SCHEMA DEMO" : "HOST-SPECIFIC SNAPSHOT";
const summaryText = document.createElement("span");
summaryText.textContent = report.schemaDemo
? "Illustrative values demonstrate accepted fields only. This is not a benchmark and does not indicate backend availability."
: `${availableCount} of ${report.rows.length} backends available on this recorded host. Unavailable means not runnable here—not zero performance.`;
summary.append(summaryTitle, summaryText);
container.append(summary);
const meta = document.createElement("div");
meta.className = "report-meta";
const host = [report.host?.machine, report.host?.processor, report.host?.platform].filter(Boolean).join(" · ") || "Host not recorded";
const values = [["FORMAT", report.format], ["MODEL", report.model], ["HOST", host], ["BATCH", report.batch ?? "—"], ["ITERATIONS", report.iterations ?? "—"], ["WARM-UP", report.warmup ?? "—"], ["REFERENCE", report.reference]];
for (const [key, value] of values) {
const item = document.createElement("span");
const strong = document.createElement("strong");
strong.textContent = `${key}: `;
item.append(strong, document.createTextNode(String(value)));
meta.append(item);
}
container.append(meta);
const resultRows = report.rows.map((row) => [
row.backend,
report.schemaDemo ? "SCHEMA DEMO" : (row.available ? "AVAILABLE" : "UNAVAILABLE"),
displayNumber(row.p50_ms),
displayNumber(row.p99_ms),
displayNumber(row.throughput_ips, 1),
report.schemaDemo ? "Illustrative only; not measured" : (row.reason || (row.available ? "Measured" : "No detail recorded")),
]);
container.append(buildTable(["BACKEND", "STATUS", "P50 MS", "P99 MS", "INF/S", "DETAIL"], resultRows, "report-table"));
if (report.hasLlmMetrics) {
const llmSection = document.createElement("section");
llmSection.className = "llm-metrics";
const title = document.createElement("h3");
title.textContent = "LLM serving metrics";
const note = document.createElement("p");
note.textContent = "Latency cells show mean / p50 / p99 in milliseconds. Throughput and token totals retain the source report's units.";
const llmRows = report.rows.map((row) => [
row.backend,
formatLatencyGroup(row.llm.ttft),
formatLatencyGroup(row.llm.tpot),
formatLatencyGroup(row.llm.itl),
formatLatencyGroup(row.llm.request_latency),
displayNumber(row.llm.request_throughput_rps, 2),
displayNumber(row.llm.output_token_throughput_tps, 2),
displayNumber(row.llm.total_input_tokens, 0),
displayNumber(row.llm.total_output_tokens, 0),
]);
llmSection.append(title, note, buildTable(
["BACKEND", "TTFT AVG / P50 / P99 MS", "TPOT AVG / P50 / P99 MS", "ITL AVG / P50 / P99 MS", "REQUEST LATENCY AVG / P50 / P99 MS", "REQ/S", "OUTPUT TOK/S", "INPUT TOKENS", "OUTPUT TOKENS"],
llmRows,
"report-table llm-report-table",
));
container.append(llmSection);
}
if (report.accuracyRows.length) {
const accuracy = document.createElement("section");
accuracy.className = "accuracy-card";
const title = document.createElement("h3");
title.textContent = "Accuracy check: speed is only useful if the answer still agrees";
const description = document.createElement("p");
description.textContent = "Numerical drift and top-1 agreement are reported alongside latency. A result is specific to this model, host, and sample set—not a universal guarantee.";
const accuracyRows = report.accuracyRows.map((row) => [
row.backend,
row.reference,
row.samples ?? "—",
displayNumber(row.max_abs_diff, 7),
displayNumber(row.mean_abs_diff, 7),
row.top1_agreement === null ? "—" : `${displayNumber(row.top1_agreement * 100, 1)}%`,
]);
accuracy.append(title, description, buildTable(["BACKEND", "REFERENCE", "SAMPLES", "MAX ABS DIFF", "MEAN ABS DIFF", "TOP-1 AGREEMENT"], accuracyRows, "report-table accuracy-table"));
container.append(accuracy);
}
const source = document.createElement("p");
source.className = "report-source";
source.textContent = report.source;
container.append(source);
container.classList.remove("hidden");
}
updateBatchExplainer();
$("#config-form").requestSubmit();