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();