Download app.js from junbin666/tensorrt-triton-workbench: direct link, hf CLI and curl.
- Browser
- Download file 12.8 kB
-
https://huggingface.co/spaces/junbin666/tensorrt-triton-workbench/resolve/main/app.js
- Command line
-
hf download hf://spaces/junbin666/tensorrt-triton-workbench/app.js
-
curl -L -o app.js https://huggingface.co/spaces/junbin666/tensorrt-triton-workbench/resolve/main/app.js
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(); | |