这个提交包含在:
Donato Capitella
2025-12-20 11:37:06 +00:00
父节点 f19932b360
当前提交 5e8b6bb545
修改 20 个文件,包含 3612 行新增248 行删除
+401
查看文件
@@ -0,0 +1,401 @@
:root {
--bg: #f8fafc;
--surface: #ffffff;
--text: #0f172a;
--text-sub: #64748b;
--accent: #d90007;
/* AMD Red */
--accent-fade: #fff0f0;
--border: #e2e8f0;
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font);
display: flex;
flex-direction: column;
height: 100vh;
overflow-y: auto;
}
header,
.controls,
.panel-split,
#tables {
max-width: 860px;
margin: 0 auto;
width: 100%;
box-sizing: border-box;
}
table {
border-collapse: collapse;
width: 100%;
table-layout: fixed;
}
h1 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
p {
margin: 4px 0 0;
font-size: 0.875rem;
color: var(--text-sub);
}
.legend {
margin-top: 12px;
display: flex;
align-items: center;
gap: 12px;
}
.legend label {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
color: var(--text-sub);
}
.legend-pills {
display: flex;
gap: 8px;
}
.legend-pill {
cursor: default !important;
}
.legend-pill-default::before {
content: "";
display: inline-block;
width: 8px;
height: 8px;
background: #cbd5e1;
border-radius: 50%;
margin-right: 6px;
}
.legend-pill-dual::before {
content: "";
display: inline-block;
width: 8px;
height: 8px;
background: #d90007;
border-radius: 50%;
margin-right: 6px;
}
.controls {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 12px 24px;
display: flex;
gap: 24px;
align-items: center;
flex-shrink: 0;
}
.control {
display: flex;
flex-direction: column;
gap: 4px;
}
.control label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-sub);
}
input[type="text"],
select {
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: 4px;
font-size: 0.875rem;
background: var(--bg);
min-width: 180px;
}
.range-wrap {
position: relative;
width: 200px;
height: 20px;
}
.range-track {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 4px;
background: #e3e7f1;
border-radius: 2px;
transform: translateY(-50%);
}
input[type=range] {
position: absolute;
width: 100%;
pointer-events: none;
appearance: none;
background: none;
margin: 0;
top: 50%;
transform: translateY(-50%);
}
input[type=range]::-webkit-slider-thumb {
pointer-events: auto;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--surface);
border: 2px solid var(--accent);
cursor: pointer;
}
.range-values {
font-size: 0.75rem;
color: var(--text-sub);
margin-top: 4px;
text-align: center;
}
.panel {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.panel.compact {
flex: 0 0 auto;
}
#tables-panel {
flex: 1;
background: var(--bg);
padding: 0;
overflow-y: auto;
margin-top: 24px;
margin-bottom: 40px;
}
.panel-split {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 24px;
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.backend-header {
display: flex;
flex-direction: column;
gap: 8px;
}
.backend-label {
display: flex;
align-items: center;
gap: 12px;
}
.backend-label label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-sub);
text-transform: uppercase;
}
.backend-list {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.backend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.875rem;
cursor: pointer;
user-select: none;
}
.test-block {
margin-bottom: 32px;
background: var(--surface);
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
}
h2 {
padding: 16px 24px;
margin: 0;
font-size: 1rem;
background: #f1f5f9;
color: var(--text);
border-bottom: 1px solid var(--border);
}
.table-wrap {
position: relative;
overflow: hidden;
}
.table-scroll {
overflow-x: auto;
padding-bottom: 12px;
/* Scrollbar space */
}
/* ... */
.best {
background: #f0fdf4;
}
.cell-error {
color: #ef4444;
font-size: 0.75rem;
}
.cell-empty {
color: var(--border);
font-size: 0.75rem;
font-style: italic;
}
/* Resize Overlay */
.resize-overlay {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: 5;
}
.resize-bar {
position: absolute;
top: 0;
width: 6px;
height: 100%;
cursor: col-resize;
pointer-events: auto;
/* invisible usually, but can hover */
}
.resize-bar:hover {
background: rgba(0, 0, 0, 0.05);
}
.resize-handle {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 4px;
cursor: col-resize;
}
.backend-header.dragging {
opacity: 0.5;
}
.backend-header.drop-target {
border-left: 2px solid var(--accent);
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
/* Modal Styles */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.4);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 1;
transition: opacity 0.2s;
}
.modal.hidden {
opacity: 0;
pointer-events: none;
}
.modal-content {
background: var(--surface);
padding: 24px 32px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
max-width: 500px;
width: 90%;
position: relative;
transform: translateY(0);
transition: transform 0.2s;
}
.modal.hidden .modal-content {
transform: translateY(20px);
}
.modal-close {
position: absolute;
top: 12px;
right: 12px;
background: transparent;
border: none;
font-size: 1.5rem;
line-height: 1;
color: var(--text-sub);
cursor: pointer;
padding: 4px;
}
.modal-close:hover {
color: var(--text);
}
.modal-content h2 {
margin-top: 0;
background: none;
border: none;
padding: 0;
font-size: 1.25rem;
margin-bottom: 12px;
}
.modal-content p {
margin-bottom: 12px;
line-height: 1.5;
}
+542
查看文件
@@ -0,0 +1,542 @@
const K_SIGMA = 1.0;
const MIN_TOL = 0.25;
const MODEL_COL_WIDTH = 300;
// Winner column removed
const state = {
envs: ["TP1", "TP2"],
backendOrder: ["TP1", "TP2"],
columnWidths: { "TP1": 260, "TP2": 260 },
filters: {
search: "",
quant: "",
backends: new Set(["TP1", "TP2"]),
sizeLo: null,
sizeHi: null,
},
ui: {},
sizeStats: { min: Infinity, max: -Infinity },
draggingEnv: null,
quantOptions: [],
};
document.addEventListener("DOMContentLoaded", async () => {
cacheUI();
setupModals();
try {
const res = await fetch("results.json");
const data = await res.json();
prepareData(data?.runs || []);
initializeControls();
renderTables();
} catch (err) {
console.error("Failed to load results.json", err);
state.ui.stats.textContent = "Failed to load results.json";
}
});
function cacheUI() {
state.ui = {
search: document.getElementById("filter-search"),
quant: document.getElementById("filter-quant"),
backendList: document.getElementById("backend-list"),
backendAll: document.getElementById("backend-all"),
backendNone: document.getElementById("backend-none"),
sizeLo: document.getElementById("sizeLo"),
sizeHi: document.getElementById("sizeHi"),
sizeTrack: document.getElementById("sizeTrack"),
sizeLoVal: document.getElementById("sizeLoVal"),
sizeHiVal: document.getElementById("sizeHiVal"),
stats: document.getElementById("stats-line"),
resetBtn: document.getElementById("reset-layout"),
tables: document.getElementById("tables"),
// Modal hooks
tp1ModalOpen: document.getElementById("tp1-modal-open"),
tp2ModalOpen: document.getElementById("tp2-modal-open"),
tp1Modal: document.getElementById("tp1-modal"),
tp2Modal: document.getElementById("tp2-modal"),
tp1ModalClose: document.getElementById("tp1-modal-close"),
tp2ModalClose: document.getElementById("tp2-modal-close"),
};
}
function setupModals() {
const modalConfigs = [
{ open: state.ui.tp1ModalOpen, modal: state.ui.tp1Modal, close: state.ui.tp1ModalClose },
{ open: state.ui.tp2ModalOpen, modal: state.ui.tp2Modal, close: state.ui.tp2ModalClose },
];
modalConfigs.forEach(({ open, modal, close }) => {
if (!open || !modal) return;
const openModal = () => modal.classList.remove("hidden");
const closeModal = () => modal.classList.add("hidden");
open.addEventListener("click", openModal);
close?.addEventListener("click", closeModal);
modal.addEventListener("click", (e) => {
if (e.target === modal) closeModal();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && !modal.classList.contains("hidden")) {
closeModal();
}
});
});
}
function prepareData(runs) {
const quantSet = new Set();
// Tests map: TestName -> { name: ..., models: Map(ModelName -> Row) }
const testsMap = new Map();
for (const run of runs) {
if (!run.test) continue;
const testKey = run.test;
if (run.quant) quantSet.add(run.quant.toUpperCase());
if (!testsMap.has(testKey)) {
testsMap.set(testKey, { name: testKey, models: new Map() });
}
const testEntry = testsMap.get(testKey);
const modelName = run.model_clean || run.model;
if (!testEntry.models.has(modelName)) {
testEntry.models.set(modelName, {
model: modelName,
quant: (run.quant || "Unknown").toUpperCase(),
sizeB: run.name_params_b ?? run.params_b ?? null,
backends: {},
search_blob: [modelName, run.quant, run.env, run.test]
.filter(Boolean)
.map((s) => s.toString().toLowerCase())
.join(" "),
});
}
const row = testEntry.models.get(modelName);
// Update stats
if (row.sizeB != null) {
state.sizeStats.min = Math.min(state.sizeStats.min, row.sizeB);
state.sizeStats.max = Math.max(state.sizeStats.max, row.sizeB);
}
// Add backend data
// run.env comes from python script as "TP1" or "TP2"
const env = run.env;
row.backends[env] = {
mean: typeof run.tps_mean === "number" ? run.tps_mean : null,
std: 0, // Not currently parsed
error: Boolean(run.error),
error_type: run.error_type || null,
};
}
state.tests = [...testsMap.values()].sort((a, b) => a.name.localeCompare(b.name));
state.quantOptions = [...quantSet].sort();
}
function initializeControls() {
const { quant, backendList, search, resetBtn, sizeLo, sizeHi } = state.ui;
quant.innerHTML = "";
const anyOpt = document.createElement("option");
anyOpt.value = "";
anyOpt.textContent = "Any";
quant.appendChild(anyOpt);
state.quantOptions.forEach((q) => {
const opt = document.createElement("option");
opt.value = q;
opt.textContent = q;
quant.appendChild(opt);
});
renderBackendList();
setupSizeSlider();
search.addEventListener("input", (e) => {
state.filters.search = (e.target.value || "").trim().toLowerCase();
renderTables();
});
quant.addEventListener("change", (e) => {
state.filters.quant = e.target.value;
renderTables();
});
backendList.addEventListener("change", (e) => {
const checkbox = e.target.closest("input[data-env]");
if (!checkbox) return;
const env = checkbox.dataset.env;
if (checkbox.checked) {
state.filters.backends.add(env);
} else {
state.filters.backends.delete(env);
}
renderTables();
});
state.ui.backendAll.addEventListener("click", () => {
state.filters.backends = new Set(state.envs);
renderBackendList();
renderTables();
});
state.ui.backendNone.addEventListener("click", () => {
state.filters.backends = new Set();
renderBackendList();
renderTables();
});
sizeLo.addEventListener("input", () => updateSizeUI(true));
sizeHi.addEventListener("input", () => updateSizeUI(true));
resetBtn.addEventListener("click", () => {
state.filters.search = "";
state.filters.quant = "";
state.filters.backends = new Set(state.envs);
search.value = "";
quant.value = "";
renderBackendList();
setupSizeSlider();
renderTables();
});
}
function renderBackendList() {
const container = state.ui.backendList;
container.innerHTML = "";
state.backendOrder.forEach((env) => {
const label = document.createElement("label");
label.className = "backend-item";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.dataset.env = env;
checkbox.checked = state.filters.backends.has(env);
label.appendChild(checkbox);
const baseSpan = document.createElement("span");
baseSpan.textContent = env;
label.appendChild(baseSpan);
container.appendChild(label);
});
}
function setupSizeSlider() {
const { sizeLo, sizeHi } = state.ui;
const minRaw = state.sizeStats.min === Infinity ? 0 : Math.floor(state.sizeStats.min || 0);
const maxRaw = state.sizeStats.max === -Infinity ? 0 : Math.ceil(state.sizeStats.max || 0);
const minB = Math.max(0, minRaw);
const maxB = Math.max(minB, maxRaw);
[sizeLo, sizeHi].forEach((inp) => {
inp.min = minB;
inp.max = maxB;
inp.step = 1;
});
sizeLo.value = minB;
sizeHi.value = maxB;
sizeLo.style.zIndex = 2;
sizeHi.style.zIndex = 1;
updateSizeUI(false);
}
function updateSizeUI(triggerRender) {
const { sizeLo, sizeHi, sizeLoVal, sizeHiVal, sizeTrack } = state.ui;
if (+sizeLo.value > +sizeHi.value) {
if (document.activeElement === sizeLo) {
sizeHi.value = sizeLo.value;
} else {
sizeLo.value = sizeHi.value;
}
}
sizeLo.style.zIndex = +sizeLo.value >= +sizeHi.max - 1 ? 4 : 2;
sizeHi.style.zIndex = +sizeHi.value <= +sizeLo.min + 1 ? 3 : 1;
state.filters.sizeLo = +sizeLo.value;
state.filters.sizeHi = +sizeHi.value;
sizeLoVal.textContent = formatSizeLabel(state.filters.sizeLo);
sizeHiVal.textContent = formatSizeLabel(state.filters.sizeHi);
const range = (sizeHi.max - sizeLo.min) || 1;
const minB = +sizeLo.min;
const start = ((state.filters.sizeLo - minB) / range) * 100;
const end = ((state.filters.sizeHi - minB) / range) * 100;
sizeTrack.style.background = `linear-gradient(to right, #e3e7f1 ${start}%, var(--accent) ${start}%, var(--accent) ${end}%, #e3e7f1 ${end}%)`;
if (triggerRender) renderTables();
}
function renderTables() {
const backendList = state.backendOrder.filter((env) => state.filters.backends.has(env));
const frag = document.createDocumentFragment();
let totalRows = 0;
for (const test of state.tests) {
const models = filterModels(test.models);
if (!models.length) continue;
totalRows += models.length;
const block = document.createElement("div");
block.className = "test-block";
const heading = document.createElement("h2");
heading.textContent = test.name;
block.appendChild(heading);
const tableWrap = document.createElement("div");
tableWrap.className = "table-wrap";
const scroller = document.createElement("div");
scroller.className = "table-scroll";
const table = buildSingleTable(models, backendList);
scroller.appendChild(table);
tableWrap.appendChild(scroller);
block.appendChild(tableWrap);
setupResizeOverlay(scroller, backendList, table);
frag.appendChild(block);
}
state.ui.tables.innerHTML = "";
if (frag.childNodes.length) {
state.ui.tables.appendChild(frag);
} else {
state.ui.tables.innerHTML = "<p>No models match the current filters.</p>";
}
state.ui.stats.textContent = `Showing ${totalRows.toLocaleString()} model rows across ${backendList.length} configurations`;
}
function buildSingleTable(models, backendList) {
const table = document.createElement("table");
const colgroup = document.createElement("colgroup");
const colModel = document.createElement("col");
colModel.style.width = `${MODEL_COL_WIDTH}px`;
colgroup.appendChild(colModel);
// Winner colGroup removed
backendList.forEach((env) => {
const col = document.createElement("col");
col.style.width = `${state.columnWidths[env] || 200}px`;
col.dataset.env = env;
colgroup.appendChild(col);
});
table.appendChild(colgroup);
const thead = document.createElement("thead");
const headRow = document.createElement("tr");
headRow.appendChild(makeHeaderCell("Model", "model"));
// Winner header removed
backendList.forEach((env) => {
const th = makeHeaderCell(env, ""); // REMOVED "backend-header" class
attachHeaderInteractions(th, env);
headRow.appendChild(th);
});
thead.appendChild(headRow);
table.appendChild(thead);
const tbody = document.createElement("tbody");
models.forEach((model) => {
const tr = document.createElement("tr");
const tdModel = document.createElement("td");
tdModel.className = "model";
const head = document.createElement("div");
head.className = "model-head";
const nameSpan = document.createElement("span");
nameSpan.className = "model-name";
nameSpan.textContent = model.model;
head.appendChild(nameSpan);
tdModel.appendChild(head);
const meta = document.createElement("div");
meta.className = "meta";
meta.textContent = `${model.quant} · ${formatSize(model.sizeB)}`;
tdModel.appendChild(meta);
tr.appendChild(tdModel);
// Winner cell removed
backendList.forEach((env) => {
const td = document.createElement("td");
td.className = "data-cell";
td.dataset.env = env;
const cell = model.backends[env];
if (!cell) {
td.innerHTML = `<span class="cell-empty">N/A</span>`;
} else if (cell.error || cell.mean == null) {
td.innerHTML = `<span class="cell-error">FAIL</span>`;
} else {
td.innerHTML = `<div class="measure">${cell.mean.toFixed(2)}</div>`;
}
tr.appendChild(td);
});
tbody.appendChild(tr);
});
table.appendChild(tbody);
return table;
}
function makeHeaderCell(label, extra = "") {
const th = document.createElement("th");
th.textContent = label;
if (extra) th.className = extra;
return th;
}
function attachHeaderInteractions(th, env) {
const width = state.columnWidths[env] || 200;
th.style.width = `${width}px`;
th.style.minWidth = `${width}px`;
th.draggable = true;
th.addEventListener("dragstart", (e) => {
state.draggingEnv = env;
th.classList.add("dragging");
e.dataTransfer.effectAllowed = "move";
});
th.addEventListener("dragend", () => {
state.draggingEnv = null;
th.classList.remove("dragging");
document.querySelectorAll("th.drop-target").forEach((el) => el.classList.remove("drop-target"));
});
th.addEventListener("dragover", (e) => {
if (!state.draggingEnv || state.draggingEnv === env) return;
e.preventDefault();
th.classList.add("drop-target");
});
th.addEventListener("dragleave", () => th.classList.remove("drop-target"));
th.addEventListener("drop", (e) => {
if (!state.draggingEnv || state.draggingEnv === env) return;
e.preventDefault();
moveBackend(state.draggingEnv, env);
th.classList.remove("drop-target");
});
const handle = document.createElement("span");
handle.className = "resize-handle";
handle.addEventListener("mousedown", (e) => startResize(e, env));
th.appendChild(handle);
}
function moveBackend(from, to) {
const order = state.backendOrder;
const fromIdx = order.indexOf(from);
const toIdx = order.indexOf(to);
if (fromIdx === -1 || toIdx === -1) return;
const [col] = order.splice(fromIdx, 1);
order.splice(toIdx, 0, col);
renderBackendList();
renderTables();
}
function filterModels(modelsMap) {
const models = [];
for (const model of modelsMap.values()) {
if (state.filters.search && !model.search_blob.includes(state.filters.search)) continue;
if (state.filters.quant && model.quant !== state.filters.quant) continue;
if (model.sizeB != null) {
if (state.filters.sizeLo != null && model.sizeB < state.filters.sizeLo - 1e-6) continue;
if (state.filters.sizeHi != null && model.sizeB > state.filters.sizeHi + 1e-6) continue;
}
models.push(model);
}
models.sort((a, b) => a.model.localeCompare(b.model));
return models;
}
function formatSize(size) {
if (size == null) return "—";
return `${Number(size).toFixed(1)}B`;
}
function formatSizeLabel(size) {
if (size >= 1000) return `${(size / 1000).toFixed(1)}kB`;
return `${Math.round(size)}B`;
}
function startResize(event, env) {
event.preventDefault();
event.stopPropagation();
const column = state.columnWidths[env] || 200;
const startX = event.clientX;
const shellRect = state.ui.tables.getBoundingClientRect();
const guide = document.createElement("div");
guide.className = "resize-line";
guide.style.position = "fixed";
guide.style.top = `${shellRect.top}px`;
guide.style.bottom = `${window.innerHeight - shellRect.bottom}px`;
guide.style.left = `${startX}px`;
guide.style.width = "2px";
guide.style.background = "var(--accent)";
guide.style.zIndex = "10";
document.body.appendChild(guide);
let nextWidth = column;
const onMove = (e) => {
const delta = e.clientX - startX;
nextWidth = Math.max(80, column + delta);
guide.style.left = `${e.clientX}px`;
};
const onUp = () => {
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
guide.remove();
state.columnWidths[env] = nextWidth;
renderTables();
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
}
function setupResizeOverlay(tableWrap, backendList, table) {
let overlay = tableWrap.querySelector(".resize-overlay");
if (!overlay) {
overlay = document.createElement("div");
overlay.className = "resize-overlay";
tableWrap.appendChild(overlay);
} else {
overlay.innerHTML = "";
}
overlay.style.width = `${tableWrap.clientWidth}px`;
overlay.style.height = `${table.offsetHeight}px`;
const bars = [];
let offset = MODEL_COL_WIDTH; // Winner column width removed
backendList.forEach((env) => {
const width = state.columnWidths[env] || 200;
const bar = document.createElement("div");
bar.className = "resize-bar";
bar.dataset.env = env;
bar.addEventListener("mousedown", (e) => startResize(e, env));
overlay.appendChild(bar);
bars.push({ bar, offset, width, env });
offset += width;
});
const positionBars = () => {
bars.forEach(({ bar, offset, width }) => {
const left = offset + width - 3 - tableWrap.scrollLeft;
bar.style.left = `${left}px`;
});
};
positionBars();
if (tableWrap._overlayScroll) {
tableWrap.removeEventListener("scroll", tableWrap._overlayScroll);
}
const onScroll = () => positionBars();
tableWrap.addEventListener("scroll", onScroll);
tableWrap._overlayScroll = onScroll;
if (tableWrap._overlayResize) {
tableWrap._overlayResize.disconnect();
}
const resizeObserver = new ResizeObserver(() => {
overlay.style.width = `${tableWrap.clientWidth}px`;
overlay.style.height = `${table.offsetHeight}px`;
positionBars();
});
resizeObserver.observe(tableWrap);
tableWrap._overlayResize = resizeObserver;
}
+782
查看文件
@@ -0,0 +1,782 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AMD Strix Halo (gfx1151) vLLM Benchmarks</title>
<style>
:root {
--bg-body: #f9fafb;
--bg-card: #ffffff;
--text-main: #111827;
--text-muted: #6b7280;
--border: #e5e7eb;
--primary: #ef4444;
/* AMD Red-ish */
--primary-bg: #fef2f2;
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
body {
background-color: var(--bg-body);
color: var(--text-main);
font-family: var(--font-sans);
margin: 0;
padding: 20px;
line-height: 1.5;
}
.container {
max-width: 1000px;
margin: 20px auto;
}
/* Header */
header {
margin-bottom: 20px;
text-align: center;
}
h1 {
font-size: 2.25rem;
font-weight: 800;
margin: 0 0 10px 0;
letter-spacing: -0.05rem;
}
p.subtitle {
color: var(--text-muted);
font-size: 1.1rem;
margin: 0;
}
/* Controls */
.controls {
display: flex;
gap: 16px;
margin-bottom: 24px;
background: var(--bg-card);
padding: 16px;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
border: 1px solid var(--border);
align-items: center;
flex-wrap: wrap;
}
input[type="text"],
select {
padding: 10px 14px;
border: 1px solid var(--border);
border-radius: 8px;
font-size: 0.95rem;
outline: none;
transition: border-color 0.15s;
}
input[type="text"]:focus,
select:focus {
border-color: var(--primary);
box-shadow: 0 0 0 2px var(--primary-bg);
}
.search {
flex: 1;
min-width: 200px;
}
/* Section Cards */
.section-card {
background: var(--bg-card);
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
border: 1px solid var(--border);
margin-bottom: 32px;
overflow: hidden;
}
.section-header {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
background: #fcfcfc;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-header h2 {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
}
/* Table */
.table-responsive {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.95rem;
}
th,
td {
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid var(--border);
}
th {
background: #f9fafb;
color: var(--text-muted);
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
tr:last-child td {
border-bottom: none;
}
/* Columns */
.col-model {
width: auto;
}
.col-data {
text-align: right;
width: 1%;
white-space: nowrap;
font-family: var(--font-mono);
font-feature-settings: "tnum";
font-variant-numeric: tabular-nums;
}
/* Model Cell Styling */
.model-cell {
display: flex;
flex-direction: column;
}
.model-name {
font-weight: 600;
color: var(--text-main);
}
.model-meta {
font-size: 0.8rem;
color: var(--text-muted);
margin-top: 4px;
display: flex;
gap: 8px;
align-items: center;
}
/* Tags */
.tag {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
background: #f3f4f6;
color: #4b5563;
font-size: 0.7rem;
font-weight: 500;
}
/* Data Styling */
.val {
font-weight: 600;
}
.val-na {
color: #d1d5db;
font-weight: 400;
}
.highlight {
color: var(--primary);
}
/* Modal/Overlay */
#loading {
text-align: center;
padding: 40px;
color: var(--text-muted);
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.modal-overlay.active {
opacity: 1;
pointer-events: auto;
}
.modal {
background: var(--bg-card);
width: 90%;
max-width: 600px;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
max-height: 85vh;
overflow: hidden;
}
.modal-header {
padding: 20px 24px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
background: #f9fafb;
}
.modal-header h3 {
margin: 0;
font-size: 1.25rem;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--text-muted);
line-height: 1;
}
.modal-body {
padding: 24px;
overflow-y: auto;
}
.modal-section {
margin-bottom: 24px;
}
.modal-section h4 {
margin: 0 0 8px 0;
font-size: 0.9rem;
text-transform: uppercase;
color: var(--text-muted);
letter-spacing: 0.05em;
}
.modal-section p {
margin: 0;
font-size: 0.95rem;
color: var(--text-main);
}
.code-block {
background: #f3f4f6;
padding: 12px;
border-radius: 6px;
font-family: var(--font-mono);
font-size: 0.85rem;
color: #374151;
margin-top: 8px;
white-space: pre-wrap;
}
/* Help Button */
.btn-help {
background: none;
border: 1px solid var(--border);
color: var(--text-muted);
width: 24px;
height: 24px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
margin-left: 10px;
transition: all 0.2s;
}
.btn-help:hover {
border-color: var(--primary);
color: var(--primary);
background: var(--primary-bg);
}
.section-title-row {
display: flex;
align-items: center;
}
.section-desc {
color: var(--text-muted);
font-size: 0.9rem;
font-weight: 400;
margin-left: 12px;
}
/* Footer */
footer {
margin-top: 60px;
padding-top: 20px;
border-top: 1px solid var(--border);
color: var(--text-muted);
font-size: 0.85rem;
line-height: 1.6;
}
.sys-config {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 12px;
max-width: 800px;
}
.sys-item {
display: grid;
grid-template-columns: 140px 1fr;
align-items: baseline;
}
.sys-label {
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #9ca3af;
}
/* Tabs */
.tab-nav {
display: flex;
gap: 8px;
margin-bottom: 24px;
border-bottom: 1px solid var(--border);
padding-bottom: 0px;
}
.tab-btn {
background: none;
border: none;
padding: 12px 20px;
font-size: 1rem;
font-weight: 500;
color: var(--text-muted);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.2s;
}
.tab-btn:hover {
color: var(--text-main);
}
.tab-btn.active {
color: var(--primary);
border-bottom-color: var(--primary);
font-weight: 600;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>AMD Strix Halo (gfx1151) vLLM Benchmarks</h1>
<p style="margin: 4px 0 0 0; font-size: 0.9rem;">
<a href="https://github.com/kyuz0/amd-strix-halo-vllm-toolboxes/" target="_blank"
style="color: var(--primary); text-decoration: none;">View on GitHub &rarr;</a>
</p>
</header>
<div class="controls">
<input type="text" id="searchInput" class="search" placeholder="Search models (e.g. 'llama', 'fp8')..."
autocomplete="off">
<select id="quantFilter">
<option value="">All Quantizations</option>
</select>
</div>
<nav id="tabNav" class="tab-nav">
<!-- Dynamic Tabs -->
</nav>
<div id="dashboard">
<div id="loading">Loading benchmark results...</div>
</div>
<footer>
<div style="font-weight: 600; margin-bottom: 8px;">System Configuration</div>
<div class="sys-config">
<div class="sys-item">
<span class="sys-label">System</span>
<span>Framework Desktop · AMD Ryzen AI MAX 395+ · 128GB unified RAM</span>
</div>
<div class="sys-item">
<span class="sys-label">OS/Kernel</span>
<span>Fedora 42 · Linux 6.18.0-0.rc6.243.vanilla.fc42.x86_64</span>
</div>
</div>
</footer>
</div>
<!-- Modal Overlay -->
<div id="modalOverlay" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3 id="modalTitle">Benchmark Info</h3>
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-body" id="modalContent">
<!-- Dynamic Content -->
</div>
</div>
</div>
<script>
// State
let rawRuns = [];
let tests = [];
let state = {
search: "",
quant: "",
activeTab: "Throughput"
};
// Benchmark Metadata
const BENCHMARK_INFO = {
"Throughput": {
short: "Maximum raw compute capacity (Tokens/Sec).",
desc: "Measures the absolute maximum number of tokens the system can generate per second by fully saturating the GPU compute capability.",
usecase: "Demonstrates the raw horsepower and architectural efficiency of the hardware/model combo under Heavy Load. This is the theoretical speed limit of the system.",
details: "Command: `vllm bench throughput`\nParams: --num-prompts 100 --output-len 512\nMetric: Tokens per Second (higher is better).",
unit: " tok/s"
},
"TTFT": {
short: "Time To First Token (Response Latency).",
desc: "The 'Time To First Token' is the delay between sending a request and seeing the first character of the response.",
usecase: "<b>Responsiveness</b>. Low TTFT makes the AI feel 'snappy' and instant. High TTFT feels like the AI is ignoring you or lagging. We measure at different QPS loads to ensure the server doesn't 'choke' when busy.",
context: "<b>QPS = Queries Per Second (Traffic Load)</b>.<br>• QPS 1.0 = 1 user sending a request every second.<br>• QPS 4.0 = 4 users sending requests every second (Simulates High Load).",
details: "Command: `vllm bench serve`\nParams: --random-input-len 1024 --random-output-len 512\nMetric: Milliseconds (lower is better).",
unit: " ms"
},
"TPOT": {
short: "Time Per Output Token (Streaming Speed).",
desc: "The 'Time Per Output Token' measures how fast the text generates *after* the first token appears.",
usecase: "<b>1. Fluidity</b>: Industry standard is <50ms (>20 tok/s) for a 'fluid' feeling. Slower feels laggy.<br><b>2. Bottlenecks</b>: We test at <b>QPS 4.0</b> to find memory bandwidth bottlenecks where the GPU can't keep up with multiple users.",
context: "<b>QPS = Queries Per Second (Traffic Load)</b>.<br>• QPS 1.0 = Light Load (Ideal conditions)<br>• QPS 4.0 = Heavy Load (Stress Test)",
details: "Command: `vllm bench serve`\nParams: --random-input-len 1024 --random-output-len 512\nMetric: Milliseconds (lower is better).",
unit: " ms"
}
};
const $ = id => document.getElementById(id);
async function init() {
try {
const res = await fetch('results.json');
const data = await res.json();
rawRuns = data.runs || [];
processData();
render();
populateFilters();
} catch (e) {
$('loading').textContent = "Error loading results.json: " + e.message;
console.error(e);
}
}
function processData() {
const testGroups = {};
rawRuns.forEach(run => {
if (!run.test) return;
if (!testGroups[run.test]) {
testGroups[run.test] = {
name: run.test,
models: {}
};
}
// Normalize model name
const modelName = run.model_clean || run.model;
if (!testGroups[run.test].models[modelName]) {
testGroups[run.test].models[modelName] = {
name: modelName,
quant: run.quant,
params: run.params_b || run.name_params_b,
tp1: null,
tp2: null
};
}
const m = testGroups[run.test].models[modelName];
// Assign TP value
if (run.env === "TP1") m.tp1 = run.tps_mean;
if (run.env === "TP2") m.tp2 = run.tps_mean;
});
// Convert map to array for sorting
tests = Object.values(testGroups).map(group => {
return {
name: group.name,
models: Object.values(group.models)
};
});
// Sort tests: Throughput first, then others alphabetically
tests.sort((a, b) => {
if (a.name === "Throughput") return -1;
if (b.name === "Throughput") return 1;
return a.name.localeCompare(b.name);
});
// Set default tab if not set
if (!state.activeTab && tests.length > 0) {
state.activeTab = tests[0].name;
}
}
function populateFilters() {
const quants = new Set(rawRuns.map(r => r.quant).filter(Boolean));
const sel = $('quantFilter');
[...quants].sort().forEach(q => {
const opt = document.createElement('option');
opt.value = q;
opt.textContent = q;
sel.appendChild(opt);
});
$('searchInput').addEventListener('input', e => {
state.search = e.target.value.toLowerCase();
render();
});
sel.addEventListener('change', e => {
state.quant = e.target.value;
render();
});
}
function getBenchmarkMeta(testName) {
if (testName.includes("Throughput")) return BENCHMARK_INFO["Throughput"];
if (testName.includes("TTFT")) return BENCHMARK_INFO["TTFT"];
if (testName.includes("TPOT")) return BENCHMARK_INFO["TPOT"];
return null;
}
function render() {
const container = $('dashboard');
const tabNav = $('tabNav');
// Render Tabs
tabNav.innerHTML = "";
tests.forEach(test => {
const btn = document.createElement('button');
btn.className = `tab-btn ${test.name === state.activeTab ? 'active' : ''}`;
btn.textContent = test.name;
btn.onclick = () => {
state.activeTab = test.name;
render();
};
tabNav.appendChild(btn);
});
// Ensure active tab exists (if search filtered it out logic?)
// Actually tabs are based on 'tests' which is processed from raw data, so they exist regardless of filters unless we want to hide tabs with no results.
// For now, let's keep tabs static based on available data types.
container.innerHTML = "";
// Find active test
const activeTest = tests.find(t => t.name === state.activeTab);
if (!activeTest) {
// If invalid tab (e.g. on first load if default doesn't exist), switch to first
if (tests.length > 0) {
state.activeTab = tests[0].name;
// Re-render immediately
setTimeout(render, 0);
}
container.innerHTML = '<div id="loading">No data available.</div>';
return;
}
// Render Active Tab Content
const test = activeTest;
// Filter models within this test
const models = test.models.filter(m => {
const s = state.search;
const matchSearch = !s || m.name.toLowerCase().includes(s);
const q = state.quant;
const matchQuant = !q || m.quant === q;
return matchSearch && matchQuant;
});
if (models.length === 0) {
container.innerHTML = '<div id="loading">No models match current filters in this category.</div>';
return;
}
// Sorting models by size (small to large), then name
models.sort((a, b) => {
const pA = parseFloat(a.params) || 0;
const pB = parseFloat(b.params) || 0;
if (pA !== pB) return pA - pB;
return a.name.localeCompare(b.name);
});
const card = document.createElement('div');
card.className = "section-card";
// Metadata resolution
const meta = getBenchmarkMeta(test.name);
const shortDesc = meta ? `<span class="section-desc">${meta.short}</span>` : "";
const helpBtn = meta ? `<button class="btn-help" onclick="openModal('${test.name}')">?</button>` : "";
const header = document.createElement('div');
header.className = "section-header";
header.innerHTML = `
<div class="section-title-row">
<h2>${test.name}</h2>
${helpBtn}
</div>
${shortDesc}
`;
card.appendChild(header);
const tableResp = document.createElement('div');
tableResp.className = "table-responsive";
const table = document.createElement('table');
const thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th class="col-model">Model</th>
<th class="col-data">TP1</th>
</tr>
`;
table.appendChild(thead);
const tbody = document.createElement('tbody');
models.forEach(m => {
const tr = document.createElement('tr');
// Meta tags
let metaHtml = "";
if (m.quant) metaHtml += `<span class="tag">${m.quant}</span>`;
if (m.params) metaHtml += `<span class="tag">${m.params}B</span>`;
// Values
// Pass unit from meta
const unit = meta ? meta.unit : "";
const val1 = formatVal(m.tp1, unit);
tr.innerHTML = `
<td>
<div class="model-cell">
<a href="https://huggingface.co/${m.name}" target="_blank" class="model-name" style="text-decoration: none; color: inherit; border-bottom: 1px dotted #ccc;">${m.name}</a>
<div class="model-meta">${metaHtml}</div>
</div>
</td>
<td class="col-data">${val1}</td>
`;
tbody.appendChild(tr);
});
table.appendChild(tbody);
tableResp.appendChild(table);
card.appendChild(tableResp);
container.appendChild(card);
}
function formatVal(v, unit) {
if (v === null || v === undefined) return '<span class="val-na">N/A</span>';
if (v === 0) return '<span class="val-na">FAIL</span>';
return `<span class="val">${v.toFixed(2)}<span style="font-size:0.8em; color:#888;">${unit}</span></span>`;
}
// Modal Logic
function openModal(testName) {
const meta = getBenchmarkMeta(testName);
if (!meta) return;
$('modalTitle').textContent = testName;
let content = `
<div class="modal-section">
<h4>What is this?</h4>
<p>${meta.desc}</p>
</div>
<div class="modal-section">
<h4>Why it matters?</h4>
<p>${meta.usecase}</p>
</div>`;
if (meta.context) {
content += `
<div class="modal-section">
<h4>Terminology</h4>
<p>${meta.context}</p>
</div>`;
}
content += `
<div class="modal-section">
<h4>Technical Details</h4>
<div class="code-block">${meta.details}</div>
</div>
`;
$('modalContent').innerHTML = content;
$('modalOverlay').classList.add('active');
}
function closeModal() {
$('modalOverlay').classList.remove('active');
}
// Close on click outside
$('modalOverlay').addEventListener('click', e => {
if (e.target === $('modalOverlay')) closeModal();
});
// Close on Escape
document.addEventListener('keydown', e => {
if (e.key === "Escape") closeModal();
});
init();
</script>
</body>
</html>
+181
查看文件
@@ -0,0 +1,181 @@
import os
import json
import re
from pathlib import Path
# Config
BENCHMARK_DIR = Path("../benchmarks/benchmark_results")
OUTPUT_FILE = Path("results.json")
# Regex to parse model name for quantization and parameters
# Examples:
# "meta-llama/Meta-Llama-3.1-8B-In
# struct"
# "cpatonn/Qwen3-Coder-30B-A3B-Instruct-GPTQ-4bit"
# "RedHatAI/Llama-3.1-8B-Instruct-FP8-block"
PARAMS_REGEX = r"(\d+(?:\.\d+)?)B"
QUANT_REGEX = r"(FP8|AWQ|GPTQ|BF16|4bit|Int4)"
def extract_meta(model_name):
# Params
params_match = re.search(PARAMS_REGEX, model_name, re.IGNORECASE)
params_b = float(params_match.group(1)) if params_match else None
# Quant
quant_match = re.search(QUANT_REGEX, model_name, re.IGNORECASE)
quant = quant_match.group(1).upper() if quant_match else "BF16" # Default assumption if no tag? Or unknown.
# Refine quant if 4bit
if quant == "4BIT" or quant == "INT4":
if "GPTQ" in model_name: quant = "GPTQ-4bit"
elif "AWQ" in model_name: quant = "AWQ-4bit"
else: quant = "4-bit"
return params_b, quant
def parse_logs():
runs = []
if not BENCHMARK_DIR.exists():
print(f"Error: {BENCHMARK_DIR} does not exist!")
return []
print(f"Scanning {BENCHMARK_DIR}...")
# Files are flat in the dir: {model_safe}_tp{tp}_{type}.json
# or latency: {model_safe}_tp{tp}_qps{q}_latency.json
# We need to group by (model, tp) to form cohesive records if we want,
# BUT the webapp expects a list of "runs".
# Looking at the example JSON, each "run" is a single test point (e.g. "pp2048 @ d16384" OR "tg32 @ d16384")
# Actually, looking at the provided valid example:
# "test": "pp512", "tps_mean": 2708.86 ...
# Our data:
# throughput.json -> tokens_per_second. This is usually "decoding" or a mix?
# vLLM bench throughput usually streams tokens.
# Let's look at what run_vllm_bench.py produces.
# Throughput: --input-len 1024 --output-len 512.
# This is effectively a mixed batch.
# We'll label it "Throughput (1024/512)" or just "Throughput"
# Latency: qps-based.
files = list(BENCHMARK_DIR.glob("*.json"))
for f in files:
fname = f.name
try:
data = json.loads(f.read_text())
except:
print(f"Skipping bad JSON: {fname}")
continue
# Infer metadata from filename
# Format: {model_safe}_tp{tp}_{suffix}
# Suffix can be: "throughput.json" or "qps{q}_latency.json"
# We need model name. The script replaces / with _ in filenames.
# But we verify against the known models list? Or just parse string.
# We can reconstruct roughly.
# Split by "_tp" which is a strong delimiter
parts = fname.split("_tp")
if len(parts) < 2: continue
model_part = parts[0]
rest = parts[1] # "1_throughput.json" or "2_qps1.0_latency.json"
# TP
tp_match = re.match(r"^(\d+)", rest)
if not tp_match: continue
tp = int(tp_match.group(1))
# Env mapping
env = f"TP{tp}"
# Model Name Restoration (best effort or matching)
# In the script: model.replace("/", "_")
# We can reverse this if we have the list, but for now let's just use the clean string?
# The webapp uses "model_clean" and "model".
# Let's assume standard "org_model" format -> "org/model"
if "_" in model_part:
# Heuristic: First _ is likely the slash
model_display = model_part.replace("_", "/", 1)
else:
model_display = model_part
params_b, quant = extract_meta(model_display)
base_run = {
"model": model_display,
"model_clean": model_display,
"env": env,
"gpu_config": "dual" if tp > 1 else "single",
"quant": quant,
"params_b": params_b,
"name_params_b": params_b,
# Defaults
"backend": "vLLM",
"error": False
}
if "throughput" in fname:
# Throughput run
# data has "tokens_per_second"
tps = data.get("tokens_per_second", 0)
run = base_run.copy()
run["test"] = "Throughput"
run["tps_mean"] = tps
# If tps is 0 or missing, it might be an error?
if tps == 0 and "error" in str(data).lower():
run["error"] = True
runs.append(run)
elif "latency" in fname:
# Latency run
# raw_output has strings like "Mean TTFT: 12.3 ms", "Mean TPOT: 45.6 ms"
raw = data.get("raw_output", "")
qps_match = re.search(r"_qps([\d\.]+)_", fname)
qps = qps_match.group(1) if qps_match else "?"
# Extract metrics
ttft = 0.0
tpot = 0.0
ttft_m = re.search(r"(?:Mean TTFT|TTFT).*?([\d\.]+)", raw)
if ttft_m: ttft = float(ttft_m.group(1))
tpot_m = re.search(r"(?:Mean TPOT|TPOT).*?([\d\.]+)", raw)
if tpot_m: tpot = float(tpot_m.group(1))
# We create TWO entries? Or how does the webapp handle multiple metrics?
# Example webapp table columns are "Backends" showing ONE value.
# But grouping is by "Test".
# So we can have a test called "TTFT (QPS 1.0)" and "TPOT (QPS 1.0)"
# Entry 1: TTFT
r1 = base_run.copy()
r1["test"] = f"TTFT @ QPS {qps}"
r1["tps_mean"] = ttft # Using tps_mean field for the numeric value
runs.append(r1)
# Entry 2: TPOT
r2 = base_run.copy()
r2["test"] = f"TPOT @ QPS {qps}"
r2["tps_mean"] = tpot
runs.append(r2)
return runs
if __name__ == "__main__":
data = {"runs": parse_logs()}
runs_count = len(data["runs"])
print(f"Parsed {runs_count} runs.")
with open(OUTPUT_FILE, "w") as f:
json.dump(data, f, indent=2)
print(f"Written to {OUTPUT_FILE}")
+95
查看文件
@@ -0,0 +1,95 @@
{
"runs": [
{
"model": "Qwen/Qwen3-14B-AWQ",
"model_clean": "Qwen/Qwen3-14B-AWQ",
"env": "TP1",
"gpu_config": "single",
"quant": "AWQ",
"params_b": 14.0,
"name_params_b": 14.0,
"backend": "vLLM",
"error": false,
"test": "Throughput",
"tps_mean": 112.69232830266365
},
{
"model": "meta-llama/Meta-Llama-3.1-8B-Instruct",
"model_clean": "meta-llama/Meta-Llama-3.1-8B-Instruct",
"env": "TP1",
"gpu_config": "single",
"quant": "BF16",
"params_b": 8.0,
"name_params_b": 8.0,
"backend": "vLLM",
"error": false,
"test": "Throughput",
"tps_mean": 278.99494393048457
},
{
"model": "google/gemma-3-12b-it",
"model_clean": "google/gemma-3-12b-it",
"env": "TP1",
"gpu_config": "single",
"quant": "BF16",
"params_b": 12.0,
"name_params_b": 12.0,
"backend": "vLLM",
"error": false,
"test": "Throughput",
"tps_mean": 162.71078485804028
},
{
"model": "dazipe/Qwen3-Next-80B-A3B-Instruct-GPTQ-Int4A16",
"model_clean": "dazipe/Qwen3-Next-80B-A3B-Instruct-GPTQ-Int4A16",
"env": "TP1",
"gpu_config": "single",
"quant": "GPTQ",
"params_b": 80.0,
"name_params_b": 80.0,
"backend": "vLLM",
"error": false,
"test": "Throughput",
"tps_mean": 112.62418795067208
},
{
"model": "openai/gpt-oss-20b",
"model_clean": "openai/gpt-oss-20b",
"env": "TP1",
"gpu_config": "single",
"quant": "BF16",
"params_b": 20.0,
"name_params_b": 20.0,
"backend": "vLLM",
"error": false,
"test": "Throughput",
"tps_mean": 313.85817605876395
},
{
"model": "cpatonn/Qwen3-Coder-30B-A3B-Instruct-GPTQ-4bit",
"model_clean": "cpatonn/Qwen3-Coder-30B-A3B-Instruct-GPTQ-4bit",
"env": "TP1",
"gpu_config": "single",
"quant": "GPTQ",
"params_b": 30.0,
"name_params_b": 30.0,
"backend": "vLLM",
"error": false,
"test": "Throughput",
"tps_mean": 271.7264154071495
},
{
"model": "openai/gpt-oss-120b",
"model_clean": "openai/gpt-oss-120b",
"env": "TP1",
"gpu_config": "single",
"quant": "BF16",
"params_b": 120.0,
"name_params_b": 120.0,
"backend": "vLLM",
"error": false,
"test": "Throughput",
"tps_mean": 109.73523843987172
}
]
}