2641 lines
112 KiB
React
2641 lines
112 KiB
React
// ENGINE half of the SGLang cookbook deployment-command generator. Reads a
|
||
// per-model `config` prop (no model-specific code here). Full field semantics
|
||
// (resolution rules, key layering) live in the cookbook-add-model skill:
|
||
// .claude/skills/cookbook-add-model/references/authoring-reference.md.
|
||
//
|
||
// Config fields the engine reads:
|
||
// modelName display label
|
||
// supportedHardware hw ids shown in the catalog (subset of HARDWARE_CATALOG ∪ config.hardware)
|
||
// hardware optional — per-model GPUs the shared HARDWARE_CATALOG lacks:
|
||
// {id, label, vram, vendor}[] merged into the catalog at render
|
||
// (so a model-specific GPU never needs an engine-catalog edit);
|
||
// vendor picks the selector group: blackwell | hopper | amd | npu.
|
||
// `multiNodeDockerFlags: string[]` (either source) adds
|
||
// `docker run` flags the platform's fabric needs
|
||
// groupHardware optional — set false to show one flat hardware row
|
||
// variants/quantizations/strategies/nodesOptions LEGACY 4-dim option lists,
|
||
// used when `matchDims` is absent (nodesOptions id is
|
||
// `single` or `multi-N` → --nnodes N)
|
||
// matchDims optional — replaces the legacy four. {id, title, options}[]
|
||
// where each option is {id, label, showWhen?(sel), disabled?,
|
||
// disableReason?, soft?, softReason?}. `hw` is always the
|
||
// implicit first dim. Cells are then keyed on (hw × <these
|
||
// ids>). `disabled` is for combinations that cannot work;
|
||
// an option that runs but sits outside the verified matrix
|
||
// should declare `soft` instead — it stays selectable and
|
||
// announces itself as unverified (tooltip + in-cell note).
|
||
// Blocked options flash their disableReason under the row
|
||
// when tapped, so the reason also reaches touch readers.
|
||
// overlayDims optional — rows that do NOT participate in cell lookup; the
|
||
// picked option layers onto the matched cell, so an orthogonal
|
||
// knob does not multiply the cell count. Same option shape plus
|
||
// `flags` / `env` / `hints` (each a literal array or a function
|
||
// of the whole selection), and a row-level `default` / `showWhen`.
|
||
// `hints` render as `# ...` lines above the command.
|
||
// Builder-aware dimensions may additionally declare
|
||
// `scope: "base" | "serve" | "request"`, `description`,
|
||
// `quality`, `verifiedWhen`, and `learnMore`. Legacy configs
|
||
// omit these fields and keep the original renderer.
|
||
// commandBuilder optional — opts this config into the responsive diffusion
|
||
// builder while reusing this engine's overlay composition and
|
||
// command rendering. Shape:
|
||
// {defaultSelection, resource: {limits, verifiedRecipes,
|
||
// autoTopology(sel), validateTopology(sel)},
|
||
// resolveDeployment(sel)}. The resolver returns a cell plus
|
||
// `builder` metadata (topologySummary, errors, warnings,
|
||
// verification, resolvedSettings). UI-only scope/expand and
|
||
// local head-address/rank state never enter the URL hash.
|
||
// cells {match, verified?, verificationStatus?, nnodes?, warn?, redirect?,
|
||
// env, flags}[] — one per
|
||
// (hw × match dims); env/flags are flat literals, only
|
||
// {{PLACEHOLDER}} subst applied. `nnodes` supplies the node
|
||
// count for configs with no `nodes` dim (default 1). `warn`
|
||
// renders as a ⚠️ banner under the cell's command; it may
|
||
// embed [label](#anchor) links. `redirect: true` renders the
|
||
// banner ALONE — no command, header, or copy buttons — for
|
||
// cells that only point somewhere else.
|
||
// `verified` is the boolean badge baseline.
|
||
// `verificationStatus` overrides it with a third state —
|
||
// "verified" | "in-progress" | "unverified" — for a recipe
|
||
// whose verification round is open rather than absent. It
|
||
// may also be a function of the selection, for a cell whose
|
||
// verification depends on an overlay pick (e.g. one
|
||
// speculative option still being validated).
|
||
// modelNames HF slug lookup, `hw|variant|quant`, `variant|quant`,
|
||
// `hw|quant`, `quant`, `hw`, then `default`
|
||
// placeholders {{KEY}} → {target: 'command'|'curl', label, default?}
|
||
// curl cURL template (uses {{MODEL_NAME}} + placeholders), or
|
||
// `(selection, cell) => template` when the request payload
|
||
// depends on a custom match/overlay dimension
|
||
// benchmarkCommands optional — powers the "⚡ Reproduce" modal (speed +
|
||
// per-eval accuracy templates)
|
||
// defaultAccuracy optional — per-variant accuracy merged under cell.accuracy
|
||
// accuracyLabels [key, label, unit][] — the eval set shown in the
|
||
// benchmark card + "⚡ Reproduce". NO engine default:
|
||
// required whenever benchmarks carry accuracy data
|
||
// latencyPercentile optional, TEMPORARY — "Mean" | "P50" (default "P50"); the
|
||
// percentile the TTFT/TPOT values are, shown as "TTFT (<pct>)".
|
||
// A benchmarks entry may carry its own latencyPercentile to
|
||
// override the page value per cell (entry → config → "P50").
|
||
// Legacy "Mean" data is being re-measured to P50; drop once done
|
||
// multiNodeHints optional — {[hwId]: string[]} prepended as `# ...` lines
|
||
// dockerImages optional — `docker run` image, keyed by
|
||
// `hw|variant|quant` then `variant|quant` then
|
||
// `hw|quant|strategy` then `hw|quant` then `hw`;
|
||
// falls back to `lmsysorg/sglang:dev`
|
||
// dockerHostNetworkWhen optional — `(selection, {flags, env}) => boolean`
|
||
// dockerMounts optional — additional `-v` mount specs
|
||
// dockerRunCommand optional — command placed after the image and before
|
||
// generated server flags; string or `(selection) => string`
|
||
// runModes optional — command output tabs to show (`python` and/or
|
||
// `docker`), as an array or `(selection) => array`;
|
||
// defaults to both, in that order
|
||
// showPlaygroundLink optional — false hides the "Open the Playground" footer
|
||
// for cookbooks that only expose the deployment matrix
|
||
// github optional — "Submit verified cell" issue-template overrides
|
||
// playgroundFeatures optional — consumed by _playground.jsx (see its header)
|
||
//
|
||
// Mintlify caveats this file routes around:
|
||
// - Module-level statements are stripped — everything lives inside the
|
||
// wrapper function body.
|
||
// - Capitalized JSX tags get rebound by _provideComponents() — lowercase
|
||
// HTML tags only; factor into helper functions, not sub-components.
|
||
// - Import plain-data config from the MDX file, pass through as a prop.
|
||
|
||
export const Deployment = ({ config, benchmarks }) => {
|
||
if (!config) {
|
||
return <div style={{padding: 12, color: "#b91c1c"}}>Deployment: missing <code>config</code> prop</div>;
|
||
}
|
||
|
||
// ==== 1. Hardware catalog (shared across cookbooks) ====
|
||
// VRAM is per-GPU on-chip memory, not per-module.
|
||
const AMD_RDMA_DOCKER_FLAGS = [
|
||
"--device /dev/infiniband", "--cap-add IPC_LOCK",
|
||
"--ulimit memlock=-1", "--ulimit stack=67108864",
|
||
"--ulimit nofile=1048576:1048576",
|
||
];
|
||
const HARDWARE_CATALOG = {
|
||
blackwell: [
|
||
{ id: "b300", label: "B300", vram: "288GB" },
|
||
{ id: "gb300", label: "GB300", vram: "288GB" },
|
||
{ id: "b200", label: "B200", vram: "192GB" },
|
||
{ id: "gb200", label: "GB200", vram: "192GB" },
|
||
// GB10 Grace Blackwell — 128 GB coherent unified system memory (not discrete VRAM).
|
||
// Multi-node runs over ConnectX-7 RDMA (pinned memory + IB passthrough).
|
||
{ id: "dgx-spark", label: "DGX Spark", vram: "128GB",
|
||
multiNodeDockerFlags: [
|
||
"--ulimit memlock=-1:-1", "--cap-add IPC_LOCK", "--device /dev/infiniband",
|
||
] },
|
||
],
|
||
hopper: [
|
||
{ id: "h200", label: "H200", vram: "141GB" },
|
||
{ id: "h100", label: "H100", vram: "80GB" },
|
||
{ id: "h20-3e", label: "H20-3e", vram: "141GB" },
|
||
{ id: "h800", label: "H800", vram: "80GB" },
|
||
],
|
||
// ROCm multi-node runs the RDMA NICs straight through: /dev/infiniband
|
||
// covers rdma_cm plus the per-NIC uverbsN nodes, IPC_LOCK + an unlimited
|
||
// memlock let the transport pin its registered buffers, and the stack /
|
||
// nofile raises are for the per-QP file descriptors a full 8-NIC mesh opens.
|
||
amd: [
|
||
{ id: "mi300x", label: "MI300X", vram: "192GB",
|
||
multiNodeDockerFlags: [...AMD_RDMA_DOCKER_FLAGS] },
|
||
{ id: "mi325x", label: "MI325X", vram: "256GB",
|
||
multiNodeDockerFlags: [...AMD_RDMA_DOCKER_FLAGS] },
|
||
{ id: "mi350x", label: "MI350X", vram: "288GB",
|
||
multiNodeDockerFlags: [...AMD_RDMA_DOCKER_FLAGS] },
|
||
{ id: "mi355x", label: "MI355X", vram: "288GB",
|
||
multiNodeDockerFlags: [...AMD_RDMA_DOCKER_FLAGS] },
|
||
],
|
||
// Ascend A3 Series: 1 card = 2 dies, so --tp-size is 2× the card
|
||
// count (32 cards -> --tp-size 64).
|
||
npu: [
|
||
{ id: "a3", label: "Ascend A3 Series", vram: "64GB/die" },
|
||
],
|
||
};
|
||
|
||
// ==== 2. Style helper (dark-mode-aware) ====
|
||
const makeStyles = (isDark) => ({
|
||
container: { maxWidth: "900px", margin: "0 auto", display: "flex", flexDirection: "column", gap: "3px" },
|
||
card: {
|
||
padding: "5px 10px",
|
||
border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
|
||
borderLeft: `3px solid ${isDark ? "#E85D4D" : "#D45D44"}`,
|
||
borderRadius: "4px",
|
||
display: "flex", alignItems: "center", gap: "10px",
|
||
background: isDark ? "#1f2937" : "#fff",
|
||
},
|
||
cardColumn: {
|
||
padding: "5px 10px",
|
||
border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
|
||
borderLeft: `3px solid ${isDark ? "#E85D4D" : "#D45D44"}`,
|
||
borderRadius: "4px",
|
||
display: "flex", flexDirection: "column", gap: "4px",
|
||
background: isDark ? "#1f2937" : "#fff",
|
||
},
|
||
title: { fontSize: "12px", fontWeight: "600", minWidth: "108px", flexShrink: 0, color: isDark ? "#e5e7eb" : "inherit" },
|
||
vendorRow: { display: "flex", alignItems: "center", gap: "6px" },
|
||
// Fixed width so every row's chips start at the same x regardless of the
|
||
// group name ("BLACKWELL" is the widest).
|
||
vendorLabel: {
|
||
fontSize: "10px", fontWeight: "600",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
width: "68px", flexShrink: 0, textTransform: "uppercase", letterSpacing: "0.04em",
|
||
},
|
||
// auto-fit + a real min width: columns wrap on narrow screens instead of
|
||
// shrinking below their label (the old minmax(0,1fr) let buttons overlap on
|
||
// mobile). `cols` no longer needed — auto-fit never exceeds the item count.
|
||
itemsGrid: () => ({
|
||
display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(72px, 1fr))",
|
||
gap: "4px", flex: 1,
|
||
}),
|
||
labelBase: {
|
||
padding: "2px 8px",
|
||
border: `1px solid ${isDark ? "#9ca3af" : "#d1d5db"}`,
|
||
borderRadius: "3px", cursor: "pointer",
|
||
display: "inline-flex", flexDirection: "column",
|
||
alignItems: "center", justifyContent: "center",
|
||
fontWeight: "500", fontSize: "12px",
|
||
transition: "all 0.2s", userSelect: "none",
|
||
minHeight: "26px", textAlign: "center",
|
||
background: isDark ? "#374151" : "#fff",
|
||
color: isDark ? "#e5e7eb" : "inherit",
|
||
},
|
||
checked: { background: "#D45D44", color: "white", borderColor: "#D45D44" },
|
||
disabled: { cursor: "not-allowed", opacity: 0.4 },
|
||
subtitle: { display: "block", fontSize: "9px", marginTop: "1px", lineHeight: "1.1", opacity: 0.7 },
|
||
commandWrap: {
|
||
position: "relative", flex: 1,
|
||
background: isDark ? "#111827" : "#f5f5f5",
|
||
borderRadius: "6px",
|
||
border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
|
||
overflow: "hidden",
|
||
},
|
||
commandHeader: {
|
||
display: "flex", flexWrap: "wrap", justifyContent: "space-between", alignItems: "center",
|
||
gap: "6px 10px",
|
||
padding: "6px 10px",
|
||
borderBottom: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
|
||
background: isDark ? "#1f2937" : "#fafafa",
|
||
},
|
||
commandPre: {
|
||
padding: "12px 16px",
|
||
fontFamily: "'Menlo', 'Monaco', 'Courier New', monospace",
|
||
fontSize: "12px", lineHeight: "1.5",
|
||
color: isDark ? "#e5e7eb" : "#374151",
|
||
whiteSpace: "pre-wrap", overflowX: "auto", margin: 0,
|
||
},
|
||
// Amber callout under the command when speculative decoding (MTP, DSpark, ...)
|
||
// is on but --max-running-requests isn't set (SGLang then caps it at 48).
|
||
mtpWarn: {
|
||
margin: "8px 0 0", padding: "8px 12px", borderRadius: "8px",
|
||
fontSize: "12px", lineHeight: "1.45",
|
||
background: isDark ? "#78350f" : "#fef3c7",
|
||
color: isDark ? "#fde68a" : "#92400e",
|
||
border: `1px solid ${isDark ? "#92400e" : "#fcd34d"}`,
|
||
},
|
||
// Takes either a boolean (legacy `cell.verified`) or a status id — see
|
||
// VERIFY_LABEL / verifyStatusOf in section 3.
|
||
badge: (status) => ({
|
||
display: "inline-flex", alignItems: "center", gap: "6px",
|
||
padding: "2px 8px", borderRadius: "10px",
|
||
background: {
|
||
verified: isDark ? "#064e3b" : "#d1fae5",
|
||
"in-progress": isDark ? "#1e3a8a" : "#dbeafe",
|
||
unverified: isDark ? "#78350f" : "#fef3c7",
|
||
}[verifyStatusOf(status)],
|
||
color: {
|
||
verified: isDark ? "#a7f3d0" : "#065f46",
|
||
"in-progress": isDark ? "#bfdbfe" : "#1e40af",
|
||
unverified: isDark ? "#fde68a" : "#92400e",
|
||
}[verifyStatusOf(status)],
|
||
// The in-progress label is long; keep the pill on one line and let the
|
||
// header row wrap around it instead of breaking the text mid-badge.
|
||
fontSize: "11px", fontWeight: 600, whiteSpace: "nowrap",
|
||
}),
|
||
badgeDot: (status) => ({
|
||
width: "8px", height: "8px", borderRadius: "50%",
|
||
background: { verified: "#10b981", "in-progress": "#3b82f6", unverified: "#f59e0b" }[
|
||
verifyStatusOf(status)
|
||
],
|
||
}),
|
||
iconButton: {
|
||
padding: "4px 10px",
|
||
border: `1px solid ${isDark ? "#4b5563" : "#d1d5db"}`,
|
||
borderRadius: "4px",
|
||
background: isDark ? "#1f2937" : "#fff",
|
||
color: isDark ? "#e5e7eb" : "#374151",
|
||
fontSize: "11px", fontWeight: 500, cursor: "pointer",
|
||
display: "inline-flex", alignItems: "center", gap: "4px",
|
||
},
|
||
iconRow: { display: "inline-flex", flexWrap: "wrap", gap: "6px" },
|
||
runModeWrap: {
|
||
display: "inline-flex",
|
||
border: `1px solid ${isDark ? "#4b5563" : "#d1d5db"}`,
|
||
borderRadius: "10px",
|
||
overflow: "hidden",
|
||
fontSize: "11px",
|
||
fontWeight: 600,
|
||
userSelect: "none",
|
||
},
|
||
runModeChip: (active) => ({
|
||
padding: "2px 10px",
|
||
cursor: "pointer",
|
||
background: active
|
||
? (isDark ? "#1f2937" : "#fff")
|
||
: "transparent",
|
||
color: active
|
||
? (isDark ? "#e5e7eb" : "#111827")
|
||
: (isDark ? "#9ca3af" : "#6b7280"),
|
||
borderRight: `1px solid ${isDark ? "#4b5563" : "#d1d5db"}`,
|
||
}),
|
||
runModeChipLast: (active) => ({
|
||
padding: "2px 10px",
|
||
cursor: "pointer",
|
||
background: active
|
||
? (isDark ? "#1f2937" : "#fff")
|
||
: "transparent",
|
||
color: active
|
||
? (isDark ? "#e5e7eb" : "#111827")
|
||
: (isDark ? "#9ca3af" : "#6b7280"),
|
||
}),
|
||
headerLeft: { display: "inline-flex", flexWrap: "wrap", alignItems: "center", gap: "8px" },
|
||
modalBackdrop: {
|
||
position: "fixed", inset: 0,
|
||
background: "rgba(0,0,0,0.5)",
|
||
display: "flex", alignItems: "center", justifyContent: "center",
|
||
zIndex: 9999,
|
||
},
|
||
modalBox: {
|
||
background: isDark ? "#1f2937" : "#fff",
|
||
color: isDark ? "#e5e7eb" : "#111827",
|
||
borderRadius: "8px", padding: "20px",
|
||
maxWidth: "720px", width: "92%", maxHeight: "85vh", overflowY: "auto",
|
||
border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
|
||
boxShadow: "0 10px 25px rgba(0,0,0,0.25)",
|
||
},
|
||
modalHeader: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "12px" },
|
||
modalTitle: { fontSize: "15px", fontWeight: 600 },
|
||
modalCloseBtn: {
|
||
background: "transparent", border: "none", color: "inherit",
|
||
fontSize: "20px", cursor: "pointer", padding: "0 6px", lineHeight: 1,
|
||
},
|
||
formField: { display: "flex", flexDirection: "column", gap: "4px", marginBottom: "10px" },
|
||
formLabel: { fontSize: "12px", fontWeight: 500, color: isDark ? "#9ca3af" : "#4b5563" },
|
||
formInput: {
|
||
padding: "6px 10px", fontSize: "13px",
|
||
border: `1px solid ${isDark ? "#4b5563" : "#d1d5db"}`,
|
||
borderRadius: "4px",
|
||
background: isDark ? "#111827" : "#fff",
|
||
color: isDark ? "#e5e7eb" : "#111827",
|
||
fontFamily: "'Menlo', 'Monaco', 'Courier New', monospace",
|
||
},
|
||
sectionHeading: {
|
||
fontSize: "12px", fontWeight: 600, textTransform: "uppercase",
|
||
letterSpacing: "0.04em",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
margin: "12px 0 6px 0",
|
||
},
|
||
primaryBtn: {
|
||
padding: "6px 14px", background: "#D45D44", color: "white",
|
||
border: "none", borderRadius: "4px", cursor: "pointer",
|
||
fontSize: "13px", fontWeight: 500,
|
||
},
|
||
|
||
benchCard: {
|
||
padding: "8px 12px",
|
||
border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
|
||
borderLeft: `3px solid ${isDark ? "#E85D4D" : "#D45D44"}`,
|
||
borderRadius: "4px",
|
||
background: isDark ? "#1f2937" : "#fff",
|
||
display: "flex", flexDirection: "column", gap: "8px",
|
||
},
|
||
benchHeader: {
|
||
display: "flex", flexWrap: "wrap", alignItems: "baseline", justifyContent: "space-between",
|
||
gap: "6px 12px",
|
||
},
|
||
benchTitle: {
|
||
fontSize: "13px", fontWeight: 600,
|
||
color: isDark ? "#e5e7eb" : "inherit",
|
||
},
|
||
benchVersion: {
|
||
fontSize: "11px",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
},
|
||
benchHeaderRight: {
|
||
display: "flex", flexWrap: "wrap", alignItems: "center", gap: "6px 10px", flexShrink: 0,
|
||
},
|
||
benchChipRow: {
|
||
display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap",
|
||
margin: "2px 0 8px",
|
||
},
|
||
benchChip: {
|
||
padding: "2px 10px", fontSize: "12px", cursor: "pointer",
|
||
border: `1px solid ${isDark ? "#4b5563" : "#d1d5db"}`,
|
||
borderRadius: "4px",
|
||
background: isDark ? "#1f2937" : "#fff",
|
||
color: isDark ? "#e5e7eb" : "#374151",
|
||
fontFamily: "'Menlo', 'Monaco', 'Courier New', monospace",
|
||
},
|
||
benchChipActive: { background: "#D45D44", color: "white", borderColor: "#D45D44" },
|
||
benchBlock: {
|
||
border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
|
||
borderRadius: "4px",
|
||
padding: "8px 10px",
|
||
background: isDark ? "#111827" : "#fafafa",
|
||
},
|
||
benchBlockTitle: {
|
||
fontSize: "11px", fontWeight: 600, textTransform: "uppercase",
|
||
letterSpacing: "0.04em",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
marginBottom: "4px",
|
||
},
|
||
benchWorkload: {
|
||
fontSize: "11px", fontStyle: "italic",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
marginBottom: "6px",
|
||
lineHeight: "1.3",
|
||
},
|
||
benchRow: {
|
||
display: "flex", justifyContent: "space-between",
|
||
fontSize: "12px", padding: "2px 0",
|
||
},
|
||
benchKey: { color: isDark ? "#9ca3af" : "#6b7280" },
|
||
benchVal: {
|
||
color: isDark ? "#e5e7eb" : "#111827",
|
||
fontFamily: "'Menlo', 'Monaco', 'Courier New', monospace",
|
||
fontWeight: 500,
|
||
},
|
||
benchNotes: {
|
||
fontSize: "11px", fontStyle: "italic",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
},
|
||
benchLegend: {
|
||
fontSize: "10px", fontStyle: "italic",
|
||
color: isDark ? "#6b7280" : "#9ca3af",
|
||
marginTop: "6px",
|
||
fontFamily: "'Menlo', 'Monaco', 'Courier New', monospace",
|
||
},
|
||
benchEmpty: {
|
||
fontSize: "12px", fontStyle: "italic",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
},
|
||
|
||
// grid (not <table>) — Mintlify wraps <table> with scroll wrappers.
|
||
// gridTemplateColumns set inline (depends on measurements.length).
|
||
benchTable: {
|
||
display: "grid",
|
||
// columnGap 0 so cells' bottom borders form one continuous line.
|
||
columnGap: 0,
|
||
rowGap: "3px",
|
||
marginTop: "4px",
|
||
alignItems: "baseline",
|
||
},
|
||
benchTableHead: {
|
||
textAlign: "right",
|
||
fontWeight: 500, fontSize: "11px",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
paddingLeft: "16px",
|
||
paddingBottom: "4px",
|
||
whiteSpace: "nowrap",
|
||
},
|
||
benchTableCornerHead: {
|
||
paddingBottom: "4px",
|
||
},
|
||
// Header underline — one div spanning all columns (continuous line).
|
||
benchTableSeparator: {
|
||
gridColumn: "1 / -1",
|
||
height: "1px",
|
||
background: isDark ? "#374151" : "#e5e7eb",
|
||
marginTop: "-3px", // negate the rowGap so it hugs the header row
|
||
},
|
||
benchTableLabel: {
|
||
textAlign: "left", fontSize: "12px",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
whiteSpace: "nowrap",
|
||
},
|
||
benchTableValue: {
|
||
textAlign: "right", fontSize: "12px",
|
||
color: isDark ? "#e5e7eb" : "#111827",
|
||
fontFamily: "'Menlo', 'Monaco', 'Courier New', monospace",
|
||
fontWeight: 500,
|
||
paddingLeft: "16px",
|
||
whiteSpace: "nowrap",
|
||
},
|
||
benchTableValueMissing: {
|
||
color: isDark ? "#6b7280" : "#9ca3af",
|
||
},
|
||
});
|
||
|
||
// ==== 3. Pure helpers (no React state) ====
|
||
// Verification badge state. A cell's boolean `verified` is the baseline;
|
||
// `cell.verificationStatus` overrides it, which is how a recipe whose
|
||
// verification round is open reports that instead of collapsing into the flat
|
||
// Verified / Not Verified pair.
|
||
const VERIFY_LABEL = {
|
||
verified: "Verified",
|
||
"in-progress": "Final Verification In Progress",
|
||
unverified: "Not Verified",
|
||
};
|
||
// Booleans keep their historical meaning; an unrecognized status id falls
|
||
// back to "unverified" rather than to truthiness (a typo must never read as
|
||
// a green Verified badge).
|
||
const verifyStatusOf = (v) =>
|
||
typeof v === "string"
|
||
? (VERIFY_LABEL[v] ? v : "unverified")
|
||
: (v ? "verified" : "unverified");
|
||
const cellVerifyStatus = (c, sel) => {
|
||
if (!c) return "unverified";
|
||
const v =
|
||
typeof c.verificationStatus === "function"
|
||
? c.verificationStatus(sel)
|
||
: c.verificationStatus;
|
||
return verifyStatusOf(v ?? c.verified);
|
||
};
|
||
|
||
// Two kinds of selector row:
|
||
// match dims participate in cell lookup (cell.match[dim] === sel[dim])
|
||
// overlay dims never touch cell lookup; the picked option contributes flags
|
||
// on top of the matched cell (so an orthogonal knob like
|
||
// speculative decoding does not multiply the cell count)
|
||
// A config that declares neither keeps the legacy fixed 5-dim shape, so model
|
||
// pages written before this existed render unchanged.
|
||
const LEGACY_MATCH_DIMS = [
|
||
{ id: "variant", title: "Model Variant", optionsKey: "variants" },
|
||
{ id: "quant", title: "Quantization", optionsKey: "quantizations" },
|
||
{ id: "strategy", title: "Strategy", optionsKey: "strategies" },
|
||
{ id: "nodes", title: "Nodes", optionsKey: "nodesOptions" },
|
||
];
|
||
// `hw` is always the first match dim; it has its own vendor-grouped renderer.
|
||
const matchDimSpecs = (config.matchDims || LEGACY_MATCH_DIMS).map((d) => ({
|
||
...d,
|
||
options: d.options || config[d.optionsKey] || [],
|
||
}));
|
||
const overlayDimSpecs = config.overlayDims || [];
|
||
const commandBuilder = config.commandBuilder || null;
|
||
// DIMENSIONS is ordered by priority — higher-index dims adapt to lower-index
|
||
// picks, never the reverse. Drives the grey-out/snap logic below.
|
||
const DIMENSIONS = ["hw", ...matchDimSpecs.map((d) => d.id)];
|
||
|
||
// An option is visible when it declares no `showWhen`, or its predicate accepts
|
||
// the current selection. Hidden options are excluded from snapping and from the
|
||
// grey-out scan, so a stale pick can never survive a dependent-row switch.
|
||
// ==== MIRROR in _playground.jsx — keep the two copies identical ====
|
||
// Snippets cannot import each other, so the overlay-resolution rule is written
|
||
// twice. A divergence makes the Deploy command and the playground base disagree,
|
||
// which shows up as phantom +/- lines in the diff and no error anywhere.
|
||
// Guarded by docs/scripts/check_cookbook_configs.mjs.
|
||
const optionVisible = (opt, sel) =>
|
||
typeof opt.showWhen !== "function" || opt.showWhen(sel);
|
||
const optionDisabled = (opt, sel) =>
|
||
typeof opt.disabled === "function" ? opt.disabled(sel) : !!opt.disabled;
|
||
const visibleOptions = (spec, sel) =>
|
||
(spec.options || []).filter((o) => optionVisible(o, sel));
|
||
const rowVisible = (spec, sel) =>
|
||
(typeof spec.showWhen !== "function" || spec.showWhen(sel)) &&
|
||
visibleOptions(spec, sel).length > 0;
|
||
const overlayPick = (sel) => {
|
||
const picked = [];
|
||
for (const spec of (config.overlayDims || [])) {
|
||
if (!rowVisible(spec, sel)) continue;
|
||
const opt = (spec.options || []).find((o) => o.id === sel[spec.id]);
|
||
if (opt && !optionDisabled(opt, sel)) picked.push(opt);
|
||
}
|
||
return picked;
|
||
};
|
||
const overlayPart = (sel, key) => {
|
||
const out = [];
|
||
for (const opt of overlayPick(sel)) {
|
||
const add = typeof opt[key] === "function" ? opt[key](sel) : opt[key];
|
||
if (add) out.push(...add);
|
||
}
|
||
return out;
|
||
};
|
||
// An overlay option may also REMOVE cell flags, declared as `stripPrefixes`
|
||
// (a static list, or a function of the selection). L3 uses it to drop the
|
||
// whole DCP operating point, which the server rejects with an L3 backend.
|
||
//
|
||
// Overlay flags append, except a flag whose family the same overlay stripped:
|
||
// that one is spliced back where the stripped flag was, so a rewritten
|
||
// parallelism block (DSPARK folding a pipeline flat) stays put instead of
|
||
// landing past the --host/--port tail with the multi-node trio behind it.
|
||
const overlayCompose = (cellFlags, sel) => {
|
||
const strip = overlayPart(sel, "stripPrefixes");
|
||
const add = overlayPart(sel, "flags");
|
||
if (!strip.length) return [...(cellFlags || []), ...add];
|
||
const used = new Set();
|
||
// Consumed once, so a family the cell carries twice is not emitted twice.
|
||
const replacementsFor = (tok) => {
|
||
const out = [];
|
||
add.forEach((f, i) => {
|
||
if (used.has(i) || f.split(/[\s=]/)[0] !== tok) return;
|
||
used.add(i);
|
||
out.push(f);
|
||
});
|
||
return out;
|
||
};
|
||
const out = [];
|
||
for (const f of (cellFlags || [])) {
|
||
const tok = f.split(/[\s=]/)[0];
|
||
if (!strip.includes(tok)) out.push(f);
|
||
else out.push(...replacementsFor(tok));
|
||
}
|
||
add.forEach((f, i) => { if (!used.has(i)) out.push(f); });
|
||
return out;
|
||
};
|
||
// ==== end MIRROR ====
|
||
// `soft` marks an option that is plausible but outside the verified matrix:
|
||
// it stays selectable (the status badge reports verification separately),
|
||
// where `disabled` is reserved for combinations that cannot work at all.
|
||
const optionSoft = (opt, sel) =>
|
||
typeof opt.soft === "function" ? opt.soft(sel) : !!opt.soft;
|
||
const findCell = (cells, sel) =>
|
||
cells.find((c) => DIMENSIONS.every((d) => c.match[d] === sel[d]));
|
||
|
||
// Entries may also key on overlay dims (e.g. kvDsaPair): an entry applies
|
||
// only when every declared key equals the selection, and the most specific
|
||
// match wins, so plain hw×strategy entries stay the fallback.
|
||
const findBenchmark = (list, sel) => {
|
||
const hits = (list || []).filter((b) =>
|
||
Object.entries(b.match || {}).every(([k, v]) => sel[k] === v));
|
||
return hits.sort((a, b) => Object.keys(b.match).length - Object.keys(a.match).length)[0] || null;
|
||
};
|
||
|
||
// Accepts a single measurement object or an array; always returns an array.
|
||
const normalizeSpeed = (speed) => {
|
||
if (!speed) return [];
|
||
return Array.isArray(speed) ? speed : [speed];
|
||
};
|
||
|
||
// Variant default accuracy merged UNDER per-cell measured accuracy — but ONLY when
|
||
// a benchmark entry exists for the cell. A cell with no entry was never measured, so
|
||
// it shows the empty/"pending" state instead of borrowing the variant's accuracy.
|
||
const effectiveAccuracy = (entry, sel) =>
|
||
entry
|
||
? {
|
||
...((config.defaultAccuracy && config.defaultAccuracy[sel.variant]) || {}),
|
||
...(entry.accuracy || {}),
|
||
}
|
||
: {};
|
||
|
||
// Empty = every speed measurement null-only AND accuracy null-only. `workload`
|
||
// is metadata, not a measurement — skip it so a workload-only stub stays empty.
|
||
const benchmarkIsEmpty = (entry, accuracy) => {
|
||
for (const m of normalizeSpeed(entry && entry.speed)) {
|
||
if (m && typeof m === "object") {
|
||
for (const [key, v] of Object.entries(m)) {
|
||
if (key === "workload") continue;
|
||
if (v !== null && v !== undefined) return false;
|
||
}
|
||
}
|
||
}
|
||
if (accuracy && typeof accuracy === "object") {
|
||
for (const v of Object.values(accuracy)) {
|
||
if (v !== null && v !== undefined) return false;
|
||
}
|
||
}
|
||
return true;
|
||
};
|
||
|
||
// Grey-out predicate: (dim, value) is enabled iff some cell matches every
|
||
// HIGHER-priority dim in `sel` AND has dim === value. Lower dims may differ
|
||
// (snapToValidCell adapts them on click).
|
||
const isOptionAvailable = (cells, sel, dim, value) => {
|
||
const idx = DIMENSIONS.indexOf(dim);
|
||
const higher = DIMENSIONS.slice(0, idx);
|
||
return cells.some(
|
||
(c) => c.match[dim] === value && higher.every((d) => c.match[d] === sel[d]),
|
||
);
|
||
};
|
||
|
||
// Snap to a real cell on click: higher dims stay locked, `dim` := value,
|
||
// lower dims adopt the best-fit cell (the one preserving the most lower picks).
|
||
const snapToValidCell = (cells, sel, dim, value) => {
|
||
const idx = DIMENSIONS.indexOf(dim);
|
||
const higher = DIMENSIONS.slice(0, idx);
|
||
const lower = DIMENSIONS.slice(idx + 1);
|
||
let best = null, bestLowerMatches = -1;
|
||
for (const c of cells) {
|
||
if (c.match[dim] !== value) continue;
|
||
if (!higher.every((d) => c.match[d] === sel[d])) continue;
|
||
let s = 0;
|
||
for (const d of lower) if (c.match[d] === sel[d]) s++;
|
||
if (s > bestLowerMatches) { bestLowerMatches = s; best = c; }
|
||
}
|
||
if (!best) return sel; // defensive — shouldn't be reachable
|
||
const next = { ...sel, [dim]: value };
|
||
for (const d of lower) next[d] = best.match[d];
|
||
return next;
|
||
};
|
||
|
||
// Snap a parsed (possibly stale) URL-hash selection to a real cell, walking
|
||
// dims in priority order and falling back per-dim to the first consistent cell.
|
||
const validateSelection = (cells, parsed) => {
|
||
const valid = {};
|
||
for (const dim of DIMENSIONS) {
|
||
const want = parsed[dim];
|
||
const works = cells.some(
|
||
(c) =>
|
||
c.match[dim] === want &&
|
||
DIMENSIONS.slice(0, DIMENSIONS.indexOf(dim)).every((d) => c.match[d] === valid[d]),
|
||
);
|
||
if (works) {
|
||
valid[dim] = want;
|
||
} else {
|
||
const fallback = cells.find((c) =>
|
||
DIMENSIONS.slice(0, DIMENSIONS.indexOf(dim)).every((d) => c.match[d] === valid[d]),
|
||
);
|
||
valid[dim] = fallback ? fallback.match[dim] : want;
|
||
}
|
||
}
|
||
// Overlay dims ride along: they never key cells, so snapping must not drop
|
||
// them (it did — a strict-mode hash round-trip lost the spec default).
|
||
// Keep the parsed value when it names a real option, else the row default.
|
||
// A hash can also name an option that showWhen hides (or a rule disables)
|
||
// for the composed selection; snap those like an interactive reseat would.
|
||
for (const spec of overlayDimSpecs) {
|
||
const want = parsed[spec.id];
|
||
const opts = spec.options || [];
|
||
const picked = opts.some((o) => o.id === want)
|
||
? want
|
||
: spec.default ?? (opts[0] && opts[0].id) ?? "";
|
||
const withPick = { ...valid, [spec.id]: picked };
|
||
const usable = visibleOptions(spec, withPick).filter((o) => !optionDisabled(o, withPick));
|
||
valid[spec.id] = usable.some((o) => o.id === picked)
|
||
? picked
|
||
: (usable[0] && usable[0].id) ?? picked;
|
||
}
|
||
return valid;
|
||
};
|
||
|
||
// Lookup walks most-specific to least so a config that drops the variant/quant
|
||
// dims can key its HF slug on `hw` alone, or on the single "default" entry.
|
||
// The `hw|quant` and bare `quant` rungs cover a `matchDims` config that declares
|
||
// no variant dim at all — there `sel.variant` is undefined, so the two leading
|
||
// keys can never hit.
|
||
const resolveModelName = (sel) => {
|
||
const keys = [
|
||
`${sel.hw}|${sel.variant}|${sel.quant}`,
|
||
`${sel.variant}|${sel.quant}`,
|
||
`${sel.hw}|${sel.quant}`,
|
||
sel.quant,
|
||
sel.hw,
|
||
"default",
|
||
];
|
||
for (const k of keys) {
|
||
const hit = config.modelNames[k];
|
||
if (hit) return hit;
|
||
}
|
||
return "";
|
||
};
|
||
|
||
const interpolate = (text, env, modelName) =>
|
||
text.replace(/{{(\w+)}}/g, (_, key) =>
|
||
key === "MODEL_NAME" ? modelName : (env[key] ?? `{{${key}}}`));
|
||
|
||
// Node count comes from the `nodes` dim when the config has one; without that
|
||
// dim it is a property of the cell itself (`nnodes`), since the deployment
|
||
// shape is then fixed by the hardware rather than picked by the reader.
|
||
const parseNnodes = (id) => {
|
||
if (Number.isInteger(id)) return id;
|
||
if (/^\d+$/.test(id || "")) return parseInt(id, 10);
|
||
if (id === "single") return 1;
|
||
const m = /^multi-(\d+)$/.exec(id || "");
|
||
return m ? parseInt(m[1], 10) : 1;
|
||
};
|
||
const cellNnodes = (cell, sel) =>
|
||
sel.nodes !== undefined ? parseNnodes(sel.nodes) : (cell.nnodes || 1);
|
||
|
||
// Role-specific serving ports for PD deployments — keep in sync with PD_PORTS
|
||
// in _playground.jsx, which the generated router command targets. Each role
|
||
// derives 5 ZMQ/dist ports from its --port, so the serve ports are spaced 100
|
||
// apart to keep those ranges from overlapping on a same-host deployment.
|
||
const PD_SERVE_PORTS = { prefill: 30000, decode: 30100 };
|
||
|
||
// `flags` / `env` / `hints` may each be a function of the whole selection, so an
|
||
// "Auto" option can resolve against another row (draft tokens per strategy).
|
||
const overlayEnv = (sel) => overlayPart(sel, "env");
|
||
const overlayHints = (sel) => overlayPart(sel, "hints");
|
||
|
||
// python mode → bare `sglang serve`; docker mode → wrapped in `docker run`.
|
||
const renderCommand = (cell, sel, envValues, mode = "python") => {
|
||
if (!cell) return "# No command available for the current selection.";
|
||
const modelName = resolveModelName(sel);
|
||
const nnodes = cellNnodes(cell, sel);
|
||
const multinode = nnodes > 1;
|
||
const cellEnv = [...(cell.env || []), ...overlayEnv(sel)];
|
||
const flags = overlayCompose(cell.flags, sel);
|
||
if (multinode) {
|
||
// Insert the multi-node trio after the last parallelism flag,
|
||
// falling back to right after --model-path.
|
||
const PARALLELISM_ANCHORS = new Set([
|
||
"--enable-dp-attention", "--dp-size", "--dp", "--tp-size", "--tp",
|
||
"--sp-degree", "--ulysses-degree", "--ring-degree",
|
||
]);
|
||
let i = flags.reduce(
|
||
(last, flag, index) => PARALLELISM_ANCHORS.has(flag.split(/[\s=]/)[0]) ? index : last,
|
||
-1,
|
||
);
|
||
if (i === -1) i = flags.findIndex((f) => f.startsWith("--model-path"));
|
||
flags.splice(i + 1, 0,
|
||
`--nnodes ${nnodes}`,
|
||
`--node-rank {{NODE_RANK}}`,
|
||
`--dist-init-addr {{NODE0_IP}}:20000`);
|
||
}
|
||
|
||
const pdServePort = PD_SERVE_PORTS[sel.pdMode];
|
||
if (pdServePort !== undefined) {
|
||
for (let j = 0; j < flags.length; j++) {
|
||
if (flags[j].split(/[\s=]/)[0] === "--port") {
|
||
flags[j] = `--port ${pdServePort}`;
|
||
}
|
||
}
|
||
}
|
||
|
||
let cmd;
|
||
if (mode === "docker") {
|
||
// Image keyed by `hw|variant|quant` (most specific), then `variant|quant`,
|
||
// then `hw|quant|strategy`, `hw|quant`, `hw`; `:dev` if unmapped. The
|
||
// variant keys cover a checkpoint that needs its own build (e.g. a
|
||
// new-variant preview image); the strategy key covers a tier that needs
|
||
// one (e.g. a spec-decoding preview image).
|
||
const di = config.dockerImages || {};
|
||
const image = di[`${sel.hw}|${sel.variant}|${sel.quant}`]
|
||
|| di[`${sel.variant}|${sel.quant}`]
|
||
|| di[`${sel.hw}|${sel.quant}|${sel.strategy}`]
|
||
|| di[`${sel.hw}|${sel.quant}`] || di[sel.hw] || "lmsysorg/sglang:dev";
|
||
const dockerRunCommand = typeof config.dockerRunCommand === "function"
|
||
? config.dockerRunCommand(sel)
|
||
: (config.dockerRunCommand || "sglang serve");
|
||
const portFlag = flags.find((x) => x.split(/[\s=]/)[0] === "--port");
|
||
const servePort = portFlag ? portFlag.slice("--port".length).trim() : "{{PORT}}";
|
||
const hostNetwork = multinode || (typeof config.dockerHostNetworkWhen === "function"
|
||
&& config.dockerHostNetworkWhen(sel, { flags, env: cellEnv }));
|
||
const vendorOf = (hwId) => {
|
||
for (const [vendor, list] of Object.entries(HARDWARE_CATALOG)) {
|
||
if (list.some((h) => h.id === hwId)) return vendor;
|
||
}
|
||
const extra = (config.hardware || []).find((h) => h.id === hwId);
|
||
return (extra && extra.vendor) || "nvidia";
|
||
};
|
||
// `config.hardware` overrides by id, as in buildHardwareGroups.
|
||
const fabricFlagsOf = (hwId) => {
|
||
const extra = (config.hardware || []).find((h) => h.id === hwId);
|
||
if (extra) return extra.multiNodeDockerFlags || [];
|
||
for (const list of Object.values(HARDWARE_CATALOG)) {
|
||
const hit = list.find((h) => h.id === hwId);
|
||
if (hit) return hit.multiNodeDockerFlags || [];
|
||
}
|
||
return [];
|
||
};
|
||
const gpuAccessLines = vendorOf(sel.hw) === "amd"
|
||
? [
|
||
"docker run",
|
||
" --device=/dev/kfd --device=/dev/dri",
|
||
" --group-add video",
|
||
" --cap-add=SYS_PTRACE --security-opt seccomp=unconfined",
|
||
" --shm-size 32g",
|
||
]
|
||
: vendorOf(sel.hw) === "npu"
|
||
? [
|
||
// NPU: --privileged grants the davinci devices (16 dies on an
|
||
// 8-card Ascend A3 Series node); the host CANN driver/firmware/state
|
||
// must be mounted in.
|
||
"docker run --privileged --shm-size=16g",
|
||
" --device=/dev/davinci0 --device=/dev/davinci1 --device=/dev/davinci2 --device=/dev/davinci3",
|
||
" --device=/dev/davinci4 --device=/dev/davinci5 --device=/dev/davinci6 --device=/dev/davinci7",
|
||
" --device=/dev/davinci8 --device=/dev/davinci9 --device=/dev/davinci10 --device=/dev/davinci11",
|
||
" --device=/dev/davinci12 --device=/dev/davinci13 --device=/dev/davinci14 --device=/dev/davinci15",
|
||
" --device=/dev/davinci_manager",
|
||
" --device=/dev/hisi_hdc",
|
||
" -v /usr/local/sbin:/usr/local/sbin",
|
||
" -v /usr/local/Ascend/driver:/usr/local/Ascend/driver",
|
||
" -v /usr/local/Ascend/firmware:/usr/local/Ascend/firmware",
|
||
" -v /etc/ascend_install.info:/etc/ascend_install.info",
|
||
" -v /var/queue_schedule:/var/queue_schedule",
|
||
" -v ~/.cache/:/root/.cache/",
|
||
]
|
||
: [
|
||
"docker run --gpus all",
|
||
" --shm-size 32g",
|
||
];
|
||
const dockerLines = [
|
||
...gpuAccessLines,
|
||
// Multi-node needs host networking so the cross-node rendezvous port
|
||
// (--dist-init-addr) and NCCL/GLOO traffic are reachable; single-node
|
||
// just maps the serve port.
|
||
hostNetwork ? " --network host" : ` -p ${servePort}:${servePort}`,
|
||
...(multinode ? fabricFlagsOf(sel.hw).map((f) => " " + f) : []),
|
||
// The NPU device block already mounts ~/.cache/.
|
||
...(vendorOf(sel.hw) === "npu"
|
||
? [] : [" -v ~/.cache/huggingface:/root/.cache/huggingface"]),
|
||
...(config.dockerMounts || []).map((mount) => ` -v ${mount}`),
|
||
// HF token only for gated checkpoints — configs that declare an HF_TOKEN placeholder.
|
||
...(config.placeholders && config.placeholders.HF_TOKEN
|
||
? [` --env "HF_TOKEN={{HF_TOKEN}}"`] : []),
|
||
...cellEnv.map((e) => ` --env ${e}`),
|
||
" --ipc=host",
|
||
` ${image}`,
|
||
` ${dockerRunCommand}`,
|
||
...flags.map((f) => " " + f),
|
||
];
|
||
cmd = dockerLines.join(" \\\n");
|
||
} else {
|
||
const flagBlock = flags.map((f) => " " + f).join(" \\\n");
|
||
const envBlock = cellEnv.length ? cellEnv.join(" \\\n") + " \\\n" : "";
|
||
cmd = `${envBlock}sglang serve \\\n${flagBlock}`;
|
||
}
|
||
|
||
const hintLines = [
|
||
...overlayHints(sel),
|
||
...(multinode && config.multiNodeHints && config.multiNodeHints[sel.hw]
|
||
? config.multiNodeHints[sel.hw]
|
||
: []),
|
||
];
|
||
if (hintLines.length) {
|
||
const hint = hintLines
|
||
.map((line) => (line.length ? "# " + line : "#")).join("\n");
|
||
cmd = `${hint}\n${cmd}`;
|
||
}
|
||
cmd = interpolate(cmd, envValues, modelName);
|
||
if (multinode) {
|
||
const header =
|
||
`# Multi-node (${nnodes} nodes). Run the same command on every node with:\n` +
|
||
`# <node-rank> = 0 on the head node, 1..${nnodes - 1} on the others\n` +
|
||
`# <node0-ip> = IP of the head node (reachable from all others)`;
|
||
cmd = `${header}\n${cmd}`;
|
||
}
|
||
return cmd;
|
||
};
|
||
|
||
// Accuracy labels: [field-key, display-label, unit]. Declared per model via
|
||
// `config.accuracyLabels` — the engine ships NO default eval set. A config
|
||
// without it renders no accuracy rows (and no Accuracy section in the
|
||
// "⚡ Reproduce" modal). Keys must match the `accuracy` fields in the
|
||
// benchmarks file + `benchmarkCommands.accuracy`.
|
||
const ACCURACY_LABELS = config.accuracyLabels || [];
|
||
|
||
const renderBenchmarkCard = (entry) => {
|
||
// [key, label, unit, compute?]. Optional compute(measurement) supplies
|
||
// derived metrics (preferred over measurement[key] when present).
|
||
const pct = (entry && entry.latencyPercentile) || config.latencyPercentile || "P50";
|
||
const SPEED_LABELS = [
|
||
["ttft_ms", `TTFT (${pct})`, "ms"],
|
||
["tpot_ms", `TPOT (${pct})`, "ms"],
|
||
// throughput per gpu = total(input+output)/elapsed/GPU;
|
||
// stored directly in the benchmarks file (= output tok/s/GPU × (isl+osl)/osl).
|
||
["tokens_per_sec_per_gpu", "throughput per gpu", "tok/s"],
|
||
["interactivity", "interactivity", "tokens/s/user",
|
||
(m) => (m.tpot_ms != null && m.tpot_ms !== 0)
|
||
? Math.round((1000 / m.tpot_ms) * 10) / 10
|
||
: null],
|
||
];
|
||
const WORKLOAD_KEYS = ["dataset", "isl", "osl", "max_concurrency"];
|
||
|
||
const fmt = (val, unit) => {
|
||
if (val === null || val === undefined) return null;
|
||
return `${val}${unit ? " " + unit : ""}`;
|
||
};
|
||
|
||
// Format a subset of workload keys into a comma-separated phrase.
|
||
const formatWorkloadParts = (workload, keys) => {
|
||
if (!workload) return "";
|
||
const parts = [];
|
||
if (keys.has("dataset") && workload.dataset) parts.push(workload.dataset);
|
||
// in/out rendered as one token even if only one of isl/osl is present.
|
||
if (keys.has("isl") || keys.has("osl")) {
|
||
if (workload.isl != null || workload.osl != null) {
|
||
parts.push(`in/out=${workload.isl != null ? workload.isl : "?"}/${workload.osl != null ? workload.osl : "?"}`);
|
||
}
|
||
}
|
||
if (keys.has("max_concurrency") && workload.max_concurrency != null) {
|
||
parts.push(`max-concurrency=${workload.max_concurrency}`);
|
||
}
|
||
return parts.join(", ");
|
||
};
|
||
|
||
// Split workload fields into shared (uniform → context line) vs differing
|
||
// (→ per-column header). max_concurrency is always per-column.
|
||
const ALWAYS_PER_COLUMN = new Set(["max_concurrency"]);
|
||
const partitionWorkload = (measurements) => {
|
||
const shared = new Set();
|
||
const differing = new Set();
|
||
for (const k of WORKLOAD_KEYS) {
|
||
const seen = new Set();
|
||
let anyPresent = false;
|
||
for (const m of measurements) {
|
||
const v = m && m.workload ? m.workload[k] : undefined;
|
||
if (v != null) anyPresent = true;
|
||
seen.add(v);
|
||
}
|
||
if (!anyPresent) continue;
|
||
if (ALWAYS_PER_COLUMN.has(k) || seen.size > 1) differing.add(k);
|
||
else shared.add(k);
|
||
}
|
||
return { shared, differing };
|
||
};
|
||
|
||
const renderBenchTable = ({ title, sharedText, colHeaders, rows, colCount, legend }) => {
|
||
if (rows.length === 0) return null;
|
||
const showColHeaders = colHeaders.length > 0
|
||
&& colHeaders.some((h) => h !== "");
|
||
return (
|
||
<div style={s.benchBlock}>
|
||
<div style={s.benchBlockTitle}>{title}</div>
|
||
{sharedText && (
|
||
<div style={s.benchWorkload}>{sharedText}</div>
|
||
)}
|
||
<div
|
||
style={{
|
||
...s.benchTable,
|
||
gridTemplateColumns:
|
||
`max-content repeat(${colCount}, minmax(0, 1fr))`,
|
||
}}
|
||
>
|
||
{showColHeaders && (
|
||
<div key="corner" style={s.benchTableCornerHead}></div>
|
||
)}
|
||
{showColHeaders && colHeaders.map((h, i) => (
|
||
<div key={`hdr-${i}`} style={s.benchTableHead}>{h}</div>
|
||
))}
|
||
{showColHeaders && (
|
||
<div key="sep" style={s.benchTableSeparator}></div>
|
||
)}
|
||
{rows.map((r) => [
|
||
<div key={`lbl-${r.label}`} style={s.benchTableLabel}>{r.label}</div>,
|
||
...r.values.map((v, i) => (
|
||
<div key={`val-${r.label}-${i}`} style={
|
||
v === null
|
||
? { ...s.benchTableValue, ...s.benchTableValueMissing }
|
||
: s.benchTableValue
|
||
}>
|
||
{v !== null ? v : "—"}
|
||
</div>
|
||
)),
|
||
])}
|
||
</div>
|
||
{legend && (
|
||
<div style={s.benchLegend}>
|
||
{(Array.isArray(legend) ? legend : [legend]).map((line, i) => (
|
||
<div key={`legend-${i}`}>{line}</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// All four metric rows always render (unmeasured cells show "—") so the
|
||
// table shape is identical across every cell.
|
||
const buildSpeedTable = (measurements) => {
|
||
if (measurements.length === 0) return null;
|
||
const { shared, differing } = partitionWorkload(measurements);
|
||
const sharedText = formatWorkloadParts(
|
||
measurements[0] && measurements[0].workload, shared);
|
||
const colHeaders = measurements.map((m) =>
|
||
formatWorkloadParts(m && m.workload, differing));
|
||
const rows = SPEED_LABELS.map((tup) => {
|
||
const [key, label, unit, compute] = tup;
|
||
const values = measurements.map((m) => {
|
||
const raw = compute ? compute(m) : m[key];
|
||
return fmt(raw, unit);
|
||
});
|
||
return { label, values };
|
||
});
|
||
return { title: "Speed", sharedText, colHeaders, rows,
|
||
colCount: measurements.length,
|
||
legend: [
|
||
`throughput per gpu = (input+output tokens)/elapsed/GPU`,
|
||
`interactivity = 1000/TPOT(ms) (tokens/s/user)`,
|
||
] };
|
||
};
|
||
|
||
// One row per ACCURACY_LABELS entry with a non-null value; single value column.
|
||
const buildAccuracyTable = (accuracy) => {
|
||
if (!accuracy) return null;
|
||
const rows = ACCURACY_LABELS
|
||
.map(([key, label, unit]) => {
|
||
const v = fmt(accuracy[key], unit);
|
||
if (v === null) return null;
|
||
return { label, values: [v] };
|
||
})
|
||
.filter((r) => r !== null);
|
||
if (rows.length === 0) return null;
|
||
return { title: "Accuracy", sharedText: null, colHeaders: [],
|
||
rows, colCount: 1 };
|
||
};
|
||
|
||
const accuracy = effectiveAccuracy(entry, sel);
|
||
const isEmpty = benchmarkIsEmpty(entry, accuracy);
|
||
const measurements = !isEmpty ? normalizeSpeed(entry && entry.speed) : [];
|
||
const accuracyTable = !isEmpty ? buildAccuracyTable(accuracy) : null;
|
||
const speedTable = !isEmpty ? buildSpeedTable(measurements) : null;
|
||
const hasBenchCmds = !isEmpty && buildBenchCommands(entry, sel) !== null;
|
||
|
||
return (
|
||
<div style={s.benchCard}>
|
||
<div style={s.benchHeader}>
|
||
<div style={s.benchTitle}>Benchmark</div>
|
||
<div style={s.benchHeaderRight}>
|
||
{!isEmpty && entry && entry.sglang_version && (
|
||
<div style={s.benchVersion}>measured on sglang <code>{entry.sglang_version}</code></div>
|
||
)}
|
||
{hasBenchCmds && (
|
||
<button style={s.iconButton} onClick={() => setModal("bench")}>⚡ Reproduce</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
{isEmpty ? (
|
||
<div style={s.benchEmpty}>
|
||
Benchmark data pending for this combination — submit yours via the Playground's Submit ↗ button.
|
||
</div>
|
||
) : (
|
||
<>
|
||
{accuracyTable && renderBenchTable(accuracyTable)}
|
||
{speedTable && renderBenchTable(speedTable)}
|
||
{entry && entry.notes && (
|
||
<div style={s.benchNotes}>{entry.notes}</div>
|
||
)}
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// Build "⚡ Reproduce" modal data (raw templates + fill metadata). The {{...}}
|
||
// interpolation happens in the modal where `env` is in scope. Returns null
|
||
// when nothing is renderable (caller hides the button).
|
||
const buildBenchCommands = (entry, sel) => {
|
||
const bc = config.benchmarkCommands;
|
||
if (!bc) return null;
|
||
|
||
// One entry per eval with a value AND a template. A template is a string,
|
||
// or a {[variant]: string} object resolved by sel.variant.
|
||
const acc = effectiveAccuracy(entry, sel);
|
||
const accuracy = [];
|
||
if (bc.accuracy) {
|
||
for (const [key, label] of ACCURACY_LABELS) {
|
||
if (acc[key] == null) continue;
|
||
const tmpl = bc.accuracy[key];
|
||
const resolved = (typeof tmpl === "string")
|
||
? tmpl
|
||
: (tmpl && tmpl[sel.variant]) || null;
|
||
if (resolved) accuracy.push({ key, label, template: resolved });
|
||
}
|
||
}
|
||
|
||
let speed = null;
|
||
if (bc.speed && entry) {
|
||
const ms = normalizeSpeed(entry.speed)
|
||
.filter((m) => m && m.workload && m.workload.max_concurrency != null);
|
||
const concurrencies = [...new Set(ms.map((m) => m.workload.max_concurrency))]
|
||
.sort((a, b) => a - b);
|
||
if (concurrencies.length) {
|
||
speed = {
|
||
template: bc.speed,
|
||
concurrencies,
|
||
workload: ms[0].workload,
|
||
// {{NUM_PROMPTS}} priority: per-row override → numPromptsByConc[c]
|
||
// → max(c*2, 200).
|
||
numPromptsOf: (c) => {
|
||
const m = ms.find((x) => x.workload.max_concurrency === c);
|
||
if (m && m.workload.num_prompts != null) return m.workload.num_prompts;
|
||
const tbl = bc.numPromptsByConc;
|
||
if (tbl && tbl[c] != null) return tbl[c];
|
||
return Math.max(c * 2, 200);
|
||
},
|
||
};
|
||
}
|
||
}
|
||
|
||
if (accuracy.length === 0 && !speed) return null;
|
||
return { accuracy, speed };
|
||
};
|
||
|
||
const buildHardwareGroups = () => {
|
||
const supported = new Set(config.supportedHardware);
|
||
// Effective catalog = shared common GPUs + the model's own `config.hardware`
|
||
// (model-specific / desktop / future GPUs the shared catalog doesn't carry).
|
||
// A model-specific GPU is therefore pure config data — no engine-catalog edit.
|
||
const catalog = {};
|
||
for (const [vendor, list] of Object.entries(HARDWARE_CATALOG)) catalog[vendor] = [...list];
|
||
for (const hw of (config.hardware || [])) {
|
||
const vendor = hw.vendor || "nvidia";
|
||
const list = catalog[vendor] || (catalog[vendor] = []);
|
||
const entry = { id: hw.id, label: hw.label, vram: hw.vram };
|
||
const i = list.findIndex((x) => x.id === hw.id);
|
||
if (i >= 0) list[i] = entry; else list.push(entry); // config overrides by id
|
||
}
|
||
const groups = [];
|
||
for (const [vendor, list] of Object.entries(catalog)) {
|
||
const items = list.filter((hw) => supported.has(hw.id))
|
||
.map((hw) => ({ id: hw.id, label: hw.label, subtitle: hw.vram }));
|
||
if (items.length) groups.push({ label: vendor.toUpperCase(), items });
|
||
}
|
||
if (config.groupHardware === false) {
|
||
return [{ label: null, items: groups.flatMap((group) => group.items) }];
|
||
}
|
||
return groups;
|
||
};
|
||
|
||
// Match dims seed from the first cell (authoring convention: put the flagship
|
||
// verified cell first). Overlay dims seed from their own `default`, or the
|
||
// first option, since no cell carries them.
|
||
const initialSelectionFromCells = () => {
|
||
const first = (config.cells || [])[0];
|
||
const sel = Object.fromEntries(
|
||
DIMENSIONS.map((d) => [d, first ? first.match[d] : ""]),
|
||
);
|
||
for (const spec of overlayDimSpecs) {
|
||
const opts = spec.options || [];
|
||
sel[spec.id] = spec.default ?? (opts[0] && opts[0].id) ?? "";
|
||
}
|
||
if (!commandBuilder) return sel;
|
||
return {
|
||
...sel,
|
||
hw: commandBuilder.defaultSelection?.hw || config.supportedHardware?.[0] || "",
|
||
...(commandBuilder.defaultSelection || {}),
|
||
};
|
||
};
|
||
|
||
// Builder hashes are semantic rather than cell ids. Keep known dimension
|
||
// values, clamp bounded resources, and discard stale topology overrides.
|
||
// UI state (active scope, expanded cards, head IP, node rank) is deliberately
|
||
// absent from this object, so shared links stay portable and credential-free.
|
||
const normalizeBuilderSelection = (parsed) => {
|
||
const out = { ...initialSelectionFromCells(), ...parsed };
|
||
if (!(config.supportedHardware || []).includes(out.hw)) {
|
||
out.hw = commandBuilder.defaultSelection?.hw || config.supportedHardware?.[0] || "";
|
||
}
|
||
for (const spec of overlayDimSpecs) {
|
||
if (spec.kind === "number") {
|
||
const value = Number.parseInt(out[spec.id], 10);
|
||
out[spec.id] = Math.min(
|
||
spec.max,
|
||
Math.max(spec.min, Number.isFinite(value) ? value : Number(spec.default ?? spec.min)),
|
||
);
|
||
continue;
|
||
}
|
||
const options = spec.options || [];
|
||
if (!options.some((option) => option.id === out[spec.id])) {
|
||
out[spec.id] = spec.default ?? options[0]?.id ?? "";
|
||
}
|
||
}
|
||
for (const [key, bounds] of Object.entries(commandBuilder.resource?.limits || {})) {
|
||
const fallback = Number(commandBuilder.defaultSelection?.[key] ?? bounds.min ?? 1);
|
||
const value = Number.parseInt(out[key], 10);
|
||
out[key] = Math.min(bounds.max, Math.max(bounds.min, Number.isFinite(value) ? value : fallback));
|
||
}
|
||
for (const key of ["tp_size", "ulysses_degree", "ring_degree"]) {
|
||
const value = Number.parseInt(out[key], 10);
|
||
out[key] = Number.isFinite(value) && value > 0 ? value : 1;
|
||
}
|
||
out.topology_mode = out.topology_mode === "manual" ? "manual" : "auto";
|
||
return out;
|
||
};
|
||
|
||
const placeholderDefaults = (schema) => {
|
||
const out = {};
|
||
for (const [k, v] of Object.entries(schema || {})) out[k] = v.default ?? "";
|
||
return out;
|
||
};
|
||
|
||
// ==== 4. React state + effects ====
|
||
const [isDark, setIsDark] = useState(false);
|
||
useEffect(() => {
|
||
const check = () => {
|
||
const html = document.documentElement;
|
||
setIsDark(
|
||
html.classList.contains("dark") ||
|
||
html.getAttribute("data-theme") === "dark" ||
|
||
html.style.colorScheme === "dark"
|
||
);
|
||
};
|
||
check();
|
||
const observer = new MutationObserver(check);
|
||
observer.observe(document.documentElement, {
|
||
attributes: true,
|
||
attributeFilter: ["class", "data-theme", "style"],
|
||
});
|
||
return () => observer.disconnect();
|
||
}, []);
|
||
|
||
const STORAGE_KEY = "sglang-deploy-env";
|
||
const [env, setEnv] = useState(() => placeholderDefaults(config.placeholders));
|
||
useEffect(() => {
|
||
try {
|
||
const raw = window.localStorage.getItem(STORAGE_KEY);
|
||
if (raw) {
|
||
const parsed = JSON.parse(raw);
|
||
setEnv({ ...placeholderDefaults(config.placeholders), ...parsed });
|
||
}
|
||
} catch {}
|
||
}, []);
|
||
const saveEnv = (next) => {
|
||
setEnv(next);
|
||
try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next)); } catch {}
|
||
};
|
||
|
||
const [sel, setSel] = useState(() => initialSelectionFromCells());
|
||
const INTERNAL_HASH_STATE_KEY = "__sglangDeployInternalHash";
|
||
const DEPLOYMENT_COMPONENT_ID = "deployment-configurator";
|
||
useEffect(() => {
|
||
const hydrate = () => {
|
||
const raw = window.location.hash.replace(/^#/, "");
|
||
if (!raw) return;
|
||
const params = new URLSearchParams(raw);
|
||
const initial = initialSelectionFromCells();
|
||
const parsed = { ...initial };
|
||
let touched = false;
|
||
params.forEach((value, key) => {
|
||
if (key in parsed) { parsed[key] = value; touched = true; }
|
||
});
|
||
if (!touched) return;
|
||
// Cell configs snap to a real recipe; builders normalize their semantic
|
||
// resource state without forcing a custom-but-valid topology to a preset.
|
||
setSel(
|
||
commandBuilder
|
||
? normalizeBuilderSelection(parsed)
|
||
: validateSelection(config.cells, parsed),
|
||
);
|
||
const historyState = window.history.state;
|
||
const isInternalHash =
|
||
historyState &&
|
||
typeof historyState === "object" &&
|
||
historyState[INTERNAL_HASH_STATE_KEY] === `#${raw}`;
|
||
if (isInternalHash) return;
|
||
// External selection hashes land on the interactive configurator. Hashes
|
||
// written internally while initializing or changing chips do not scroll.
|
||
const el = document.getElementById(DEPLOYMENT_COMPONENT_ID);
|
||
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||
};
|
||
hydrate();
|
||
window.addEventListener("hashchange", hydrate);
|
||
return () => window.removeEventListener("hashchange", hydrate);
|
||
}, []);
|
||
// history.replaceState does NOT fire hashchange — dispatch a custom event so
|
||
// the Playground hears chip-click selection changes.
|
||
useEffect(() => {
|
||
const target = "#" + new URLSearchParams(sel).toString();
|
||
if (window.location.hash !== target) {
|
||
const historyState =
|
||
window.history.state && typeof window.history.state === "object"
|
||
? window.history.state
|
||
: {};
|
||
window.history.replaceState(
|
||
{ ...historyState, [INTERNAL_HASH_STATE_KEY]: target },
|
||
"",
|
||
target
|
||
);
|
||
}
|
||
window.dispatchEvent(new CustomEvent("sglang-deploy-sel", { detail: sel }));
|
||
}, [sel]);
|
||
|
||
const [modal, setModal] = useState(null); // 'curl' | 'env' | 'bench' | null
|
||
useEffect(() => {
|
||
if (modal === null) return;
|
||
const onKey = (e) => { if (e.key === "Escape") setModal(null); };
|
||
const prev = document.body.style.overflow;
|
||
document.body.style.overflow = "hidden";
|
||
window.addEventListener("keydown", onKey);
|
||
return () => {
|
||
window.removeEventListener("keydown", onKey);
|
||
document.body.style.overflow = prev;
|
||
};
|
||
}, [modal]);
|
||
|
||
const [copied, setCopied] = useState(false);
|
||
const [curlCopied, setCurlCopied] = useState(false);
|
||
const [envDraft, setEnvDraft] = useState(env);
|
||
// "⚡ Reproduce" modal: chip-selected concurrency / eval / last-copied block.
|
||
// null falls back to the first option in the render.
|
||
const [benchConc, setBenchConc] = useState(null);
|
||
const [benchAcc, setBenchAcc] = useState(null);
|
||
const [benchCopied, setBenchCopied] = useState(null);
|
||
const configuredRunModes = typeof config.runModes === "function"
|
||
? config.runModes(sel)
|
||
: config.runModes;
|
||
const runModes = configuredRunModes || ["python", "docker"];
|
||
const [runMode, setRunMode] = useState(runModes[0]); // "python" | "docker"
|
||
const [builderScope, setBuilderScope] = useState("base");
|
||
const [builderServerSetting, setBuilderServerSetting] = useState(null);
|
||
const [builderAdvanced, setBuilderAdvanced] = useState(false);
|
||
const [serveExpanded, setServeExpanded] = useState(false);
|
||
const [requestExpanded, setRequestExpanded] = useState(false);
|
||
const [builderHeadAddress, setBuilderHeadAddress] = useState("<head-node-ip>");
|
||
const [builderNodeRank, setBuilderNodeRank] = useState(0);
|
||
// Tapping a disabled option surfaces its reason under the row — hover-only
|
||
// tooltips never reach touch readers. {dim, reason}; each note clears only
|
||
// itself, so a newer note is never cut short by an older timer.
|
||
const [blockedNote, setBlockedNote] = useState(null);
|
||
const flashBlockedNote = (dim, reason) => {
|
||
const note = { dim, reason };
|
||
setBlockedNote(note);
|
||
setTimeout(() => setBlockedNote((cur) => (cur === note ? null : cur)), 4000);
|
||
};
|
||
useEffect(() => {
|
||
if (builderNodeRank >= Number(sel.nodes || 1)) setBuilderNodeRank(0);
|
||
}, [sel.nodes, builderNodeRank]);
|
||
const hasRunMode = runModes.includes(runMode);
|
||
const fallbackRunMode = runModes[0];
|
||
const activeRunMode = hasRunMode ? runMode : fallbackRunMode;
|
||
useEffect(() => {
|
||
if (!hasRunMode) setRunMode(fallbackRunMode);
|
||
}, [hasRunMode, fallbackRunMode]);
|
||
useEffect(() => { if (modal === "env") setEnvDraft(env); }, [modal, env]);
|
||
|
||
// Live --mamba-full-memory-ratio from the ratio calculator (K3 pages):
|
||
// pool sizing is consolidated into this one flag, computed from the
|
||
// calculator's request length plus the current panel selection.
|
||
const [mambaRatio, setMambaRatio] = useState(null);
|
||
useEffect(() => {
|
||
// Deploy shows base flags only, so it takes the base-config ratio (the
|
||
// effective one belongs to the playground's composed command).
|
||
const onRatio = (e) =>
|
||
setMambaRatio((e.detail && (e.detail.baseRatio || e.detail.ratio)) || null);
|
||
window.addEventListener("sglang-k3-mamba-ratio", onRatio);
|
||
return () => window.removeEventListener("sglang-k3-mamba-ratio", onRatio);
|
||
}, []);
|
||
|
||
// ==== 5. Derived values ====
|
||
const s = makeStyles(isDark);
|
||
const cell = commandBuilder
|
||
? commandBuilder.resolveDeployment(sel)
|
||
: findCell(config.cells, sel);
|
||
const builderMeta = (cell && cell.builder) || {};
|
||
const verifyStatus = cellVerifyStatus(cell, sel);
|
||
// Pin the calculator-computed ratio into the rendered command (before the
|
||
// host/port tail); cells themselves stay ratio-free. Cells sizing the pool
|
||
// with --max-mamba-cache-size opt out.
|
||
const cellWithRatio = (() => {
|
||
if (!cell || !mambaRatio) return cell;
|
||
if (cell.flags.some((f) =>
|
||
f.startsWith("--mamba-full-memory-ratio") || f.startsWith("--max-mamba-cache-size"))) return cell;
|
||
const flags = [...cell.flags];
|
||
const line = `--mamba-full-memory-ratio ${mambaRatio}`;
|
||
const i = flags.findIndex((f) => f.startsWith("--host"));
|
||
if (i >= 0) flags.splice(i, 0, line);
|
||
else flags.push(line);
|
||
return { ...cell, flags };
|
||
})();
|
||
const commandEnv = commandBuilder
|
||
? {
|
||
...env,
|
||
NODE_RANK: String(builderNodeRank),
|
||
NODE0_IP: builderHeadAddress || "<head-node-ip>",
|
||
}
|
||
: env;
|
||
const command = renderCommand(cellWithRatio, sel, commandEnv, activeRunMode);
|
||
// Speculative-decoding hint on the EFFECTIVE flags — speculation can arrive via
|
||
// the Spec Decode overlay as well as the cell. SGLang resets
|
||
// --max-running-requests to 48 when spec is on and it's unset; verified for both
|
||
// EAGLE/MTP and DSPARK (server_args reports max_running_requests=48 either way).
|
||
const effFlags = cell ? overlayCompose(cell.flags, sel) : [];
|
||
const specAlgoFlag = effFlags.find(
|
||
(f) => f.split(/[\s=]/)[0] === "--speculative-algorithm");
|
||
const specMrrFlag = effFlags.find(
|
||
(f) => f.split(/[\s=]/)[0] === "--max-running-requests");
|
||
// Two cases, both worth surfacing when speculation is on:
|
||
// mtpHint — the flag is MISSING, so SGLang silently caps at 48 (a hazard)
|
||
// specPinnedHint— the recipe PINS it, which is safe but is a fixed number the
|
||
// reader still has to match to their own concurrency
|
||
const mtpHint = !!specAlgoFlag && !specMrrFlag;
|
||
const specPinnedHint = !!specAlgoFlag && !!specMrrFlag;
|
||
const specMrrValue = specMrrFlag
|
||
? (specMrrFlag.split(/[\s=]/).filter(Boolean)[1] || "")
|
||
: "";
|
||
// Name the algorithm in the banner rather than hardcoding "MTP" — the same reset
|
||
// applies to DSpark and friends, and a DSpark user reading "(MTP)" would be
|
||
// misled. The cookbook calls the EAGLE-based path MTP, so keep that mapping.
|
||
const SPEC_ALGO_LABEL = {
|
||
EAGLE: "MTP", EAGLE3: "MTP", FROZEN_KV_MTP: "MTP",
|
||
DSPARK: "DSpark", DFLASH: "DFlash", NGRAM: "N-gram",
|
||
STANDALONE: "standalone draft",
|
||
};
|
||
const specAlgoName = (() => {
|
||
if (!specAlgoFlag) return "MTP";
|
||
const v = specAlgoFlag.split(/[\s=]/).filter(Boolean)[1] || "";
|
||
return SPEC_ALGO_LABEL[v.toUpperCase()] || v || "MTP";
|
||
})();
|
||
// cell.warn may embed [label](#anchor) links — rendered as scrollIntoView
|
||
// buttons, not hrefs, so the hash (which carries the selection) isn't overwritten.
|
||
const renderWarn = (text) => {
|
||
const out = [];
|
||
const re = /\[([^\]]+)\]\(#([^)]+)\)/g;
|
||
let last = 0;
|
||
for (let m; (m = re.exec(text)); last = m.index + m[0].length) {
|
||
if (m.index > last) out.push(text.slice(last, m.index));
|
||
const anchor = m[2];
|
||
out.push(
|
||
<button
|
||
key={m.index}
|
||
type="button"
|
||
onClick={() => {
|
||
const el = document.getElementById(anchor);
|
||
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||
}}
|
||
style={{
|
||
background: "transparent",
|
||
border: "none",
|
||
padding: 0,
|
||
color: isDark ? "#FDBA74" : "#C2410C",
|
||
cursor: "pointer",
|
||
font: "inherit",
|
||
fontWeight: 600,
|
||
textDecoration: "underline",
|
||
textUnderlineOffset: "2px",
|
||
}}
|
||
>
|
||
{m[1]}
|
||
</button>
|
||
);
|
||
}
|
||
if (last < text.length) out.push(text.slice(last));
|
||
return out;
|
||
};
|
||
const modelName = resolveModelName(sel);
|
||
const curlTemplate =
|
||
typeof config.curl === "function" ? config.curl(sel, cell) : config.curl;
|
||
const curlText = interpolate(curlTemplate || "", env, modelName);
|
||
const hwGroups = buildHardwareGroups();
|
||
const benchEntry = benchmarks ? findBenchmark(benchmarks, sel) : null;
|
||
|
||
// Overlay dims have no cells to constrain them, so an option is selectable
|
||
// unless it says otherwise; only match dims get the grey-out scan.
|
||
const isOverlayDim = (dim) => overlayDimSpecs.some((d) => d.id === dim);
|
||
const findOption = (dim, value) => {
|
||
const spec = [...matchDimSpecs, ...overlayDimSpecs].find((d) => d.id === dim);
|
||
return spec && (spec.options || []).find((o) => o.id === value);
|
||
};
|
||
const isEnabled = (dim, value) => {
|
||
const opt = findOption(dim, value);
|
||
if (opt && optionDisabled(opt, sel)) return false;
|
||
if (commandBuilder && dim === "hw") return true;
|
||
return isOverlayDim(dim) || isOptionAvailable(config.cells || [], sel, dim, value);
|
||
};
|
||
|
||
// Switching a match dim can hide the option a dependent row currently holds
|
||
// (Strategy's option set differs per PD mode). Re-seat any overlay/match pick
|
||
// that just became invisible onto the first visible option of its row.
|
||
const reseatHiddenPicks = (next) => {
|
||
let out = next;
|
||
for (const spec of [...matchDimSpecs, ...overlayDimSpecs]) {
|
||
const opts = visibleOptions(spec, out).filter((o) => !optionDisabled(o, out));
|
||
if (!opts.length) continue;
|
||
if (!opts.some((o) => o.id === out[spec.id])) {
|
||
out = { ...out, [spec.id]: opts[0].id };
|
||
}
|
||
}
|
||
return out;
|
||
};
|
||
|
||
const recommendedBuilderRecipe = (hw) => {
|
||
const recipes = commandBuilder.resource?.verifiedRecipes || [];
|
||
return recipes.find((entry) => entry.hw === hw && entry.default)
|
||
|| recipes.find((entry) => entry.hw === hw);
|
||
};
|
||
|
||
const handleSelect = (dim, value) => {
|
||
if (commandBuilder) {
|
||
setSel((prev) => {
|
||
let next = { ...prev, [dim]: value };
|
||
if (dim === "hw") {
|
||
const currentRecipe = recommendedBuilderRecipe(prev.hw);
|
||
const nextRecipe = recommendedBuilderRecipe(value);
|
||
const resourcesFollowPlatformDefault = !!currentRecipe
|
||
&& Number(prev.nodes) === Number(currentRecipe.nodes)
|
||
&& Number(prev.gpus_per_node) === Number(currentRecipe.gpus_per_node);
|
||
next = {
|
||
...next,
|
||
nodes: resourcesFollowPlatformDefault
|
||
? (nextRecipe?.nodes ?? next.nodes)
|
||
: next.nodes,
|
||
gpus_per_node: resourcesFollowPlatformDefault
|
||
? (nextRecipe?.gpus_per_node ?? next.gpus_per_node)
|
||
: next.gpus_per_node,
|
||
topology_mode: "auto",
|
||
tp_size: resourcesFollowPlatformDefault
|
||
? (nextRecipe?.tp_size ?? 1)
|
||
: next.tp_size,
|
||
ulysses_degree: resourcesFollowPlatformDefault
|
||
? (nextRecipe?.ulysses_degree ?? 1)
|
||
: next.ulysses_degree,
|
||
ring_degree: resourcesFollowPlatformDefault
|
||
? (nextRecipe?.ring_degree ?? 1)
|
||
: next.ring_degree,
|
||
// Placement and encoder are per-hardware recipe facts just like
|
||
// the resource shape: keeping the previous card's picks produces
|
||
// a command the new card cannot run (e.g. a resident 61.7 GB DiT
|
||
// on a single consumer GPU) shown as "unverified".
|
||
placement: resourcesFollowPlatformDefault
|
||
? (nextRecipe?.placement || "auto")
|
||
: next.placement,
|
||
encoder: resourcesFollowPlatformDefault
|
||
? (nextRecipe?.encoder || "auto")
|
||
: next.encoder,
|
||
};
|
||
}
|
||
return reseatHiddenPicks(normalizeBuilderSelection(next));
|
||
});
|
||
return;
|
||
}
|
||
setSel((prev) =>
|
||
reseatHiddenPicks(
|
||
isOverlayDim(dim)
|
||
? { ...prev, [dim]: value }
|
||
: snapToValidCell(config.cells, prev, dim, value),
|
||
),
|
||
);
|
||
};
|
||
|
||
const commitBuilderNumber = (event, currentValue, bounds, commit) => {
|
||
const parsed = Number(event.currentTarget.value);
|
||
if (!Number.isInteger(parsed)) {
|
||
event.currentTarget.value = String(currentValue);
|
||
return;
|
||
}
|
||
const value = Math.min(bounds.max, Math.max(bounds.min, parsed));
|
||
event.currentTarget.value = String(value);
|
||
commit(value);
|
||
};
|
||
|
||
const renderBuilderNumberInput = ({ identity, value, min, max, label, onCommit }) => (
|
||
<input
|
||
key={identity}
|
||
type="number"
|
||
inputMode="numeric"
|
||
min={min}
|
||
max={max}
|
||
step="1"
|
||
defaultValue={value}
|
||
aria-label={label}
|
||
onFocus={(event) => event.currentTarget.select()}
|
||
onBlur={(event) => commitBuilderNumber(
|
||
event,
|
||
value,
|
||
{ min, max },
|
||
onCommit,
|
||
)}
|
||
onKeyDown={(event) => {
|
||
if (event.key === "Enter") event.currentTarget.blur();
|
||
}}
|
||
/>
|
||
);
|
||
|
||
const updateBuilderResource = (key, delta) => {
|
||
if (!commandBuilder) return;
|
||
const bounds = commandBuilder.resource?.limits?.[key] || { min: 1, max: 8 };
|
||
setSel((prev) => {
|
||
const value = Math.min(bounds.max, Math.max(bounds.min, Number(prev[key]) + delta));
|
||
return normalizeBuilderSelection({ ...prev, [key]: value, topology_mode: "auto" });
|
||
});
|
||
};
|
||
|
||
const setBuilderResource = (key, rawValue) => {
|
||
if (!commandBuilder) return;
|
||
const value = Number.parseInt(rawValue, 10);
|
||
if (!Number.isFinite(value)) return;
|
||
const bounds = commandBuilder.resource?.limits?.[key] || { min: 1, max: 8 };
|
||
setSel((prev) => normalizeBuilderSelection({
|
||
...prev,
|
||
[key]: Math.min(bounds.max, Math.max(bounds.min, value)),
|
||
topology_mode: "auto",
|
||
}));
|
||
};
|
||
|
||
const editBuilderTopology = (key, value) => {
|
||
if (!commandBuilder) return;
|
||
setSel((prev) => normalizeBuilderSelection({
|
||
...prev,
|
||
topology_mode: "manual",
|
||
[key]: Number.parseInt(value, 10) || 1,
|
||
}));
|
||
};
|
||
|
||
const handleCopy = () => {
|
||
navigator.clipboard.writeText(command);
|
||
setCopied(true);
|
||
setTimeout(() => setCopied(false), 1200);
|
||
};
|
||
const copyCurl = () => {
|
||
navigator.clipboard.writeText(curlText);
|
||
setCurlCopied(true);
|
||
setTimeout(() => setCurlCopied(false), 1200);
|
||
};
|
||
// `key` identifies which Reproduce-modal block flashed "✓ Copied".
|
||
const copyBench = (key, text) => {
|
||
navigator.clipboard.writeText(text);
|
||
setBenchCopied(key);
|
||
setTimeout(() => setBenchCopied(null), 1200);
|
||
};
|
||
|
||
// Group placeholders by `target` for the Env modal.
|
||
const placeholderGroups = (() => {
|
||
const out = { command: [], curl: [] };
|
||
for (const [key, meta] of Object.entries(config.placeholders || {})) {
|
||
(out[meta.target] || (out[meta.target] = [])).push({ key, ...meta });
|
||
}
|
||
return out;
|
||
})();
|
||
|
||
// ==== 6. JSX render ====
|
||
const renderButton = (item, dim, selectedId) => {
|
||
const checked = selectedId === item.id;
|
||
const disabled = !isEnabled(dim, item.id);
|
||
return (
|
||
<label
|
||
key={item.id}
|
||
className="sg-command-visualizer-choice"
|
||
role="radio"
|
||
aria-checked={checked}
|
||
aria-disabled={disabled}
|
||
tabIndex={disabled ? -1 : 0}
|
||
style={{
|
||
...s.labelBase,
|
||
...(checked ? s.checked : {}),
|
||
...(disabled ? s.disabled : {}),
|
||
}}
|
||
title={
|
||
disabled
|
||
? (typeof item.disableReason === "function"
|
||
? item.disableReason(sel)
|
||
: item.disableReason) || "Not supported for current selection"
|
||
: ""
|
||
}
|
||
onClick={(e) => {
|
||
if (disabled) { e.preventDefault(); return; }
|
||
handleSelect(dim, item.id);
|
||
}}
|
||
onKeyDown={(e) => {
|
||
if (disabled || (e.key !== "Enter" && e.key !== " ")) return;
|
||
e.preventDefault();
|
||
handleSelect(dim, item.id);
|
||
}}
|
||
>
|
||
<input type="radio" checked={checked} disabled={disabled} readOnly style={{ display: "none" }} />
|
||
<span>{item.label}</span>
|
||
{item.subtitle && (
|
||
<small style={{ ...s.subtitle, color: checked ? "rgba(255,255,255,0.85)" : "inherit" }}>
|
||
{item.subtitle}
|
||
</small>
|
||
)}
|
||
</label>
|
||
);
|
||
};
|
||
|
||
const renderFlatSection = (title, options, dim, selectedId) => (
|
||
<div style={s.card}>
|
||
<div style={s.title}>{title}</div>
|
||
<div style={s.itemsGrid(options.length)}>
|
||
{options.map((item) => renderButton(item, dim, selectedId))}
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
const maxHwCols = Math.max(...hwGroups.map((x) => x.items.length));
|
||
|
||
if (commandBuilder) {
|
||
const scopeLabel = { base: "Setup", serve: "Server", request: "Request" };
|
||
const scopedDims = (scope) => overlayDimSpecs.filter((dim) => {
|
||
if ((dim.scope || "base") !== scope) return false;
|
||
if (dim.kind === "number") {
|
||
return typeof dim.showWhen !== "function" || dim.showWhen(sel);
|
||
}
|
||
return rowVisible(dim, sel);
|
||
});
|
||
const baseDims = scopedDims("base");
|
||
const serveDims = scopedDims("serve");
|
||
const requestDims = scopedDims("request");
|
||
const errors = builderMeta.errors || [];
|
||
const warnings = builderMeta.warnings || [];
|
||
const invalid = errors.length > 0;
|
||
const totalGpus = Number(sel.nodes) * Number(sel.gpus_per_node);
|
||
const topology = builderMeta.topology || {};
|
||
const verification = builderMeta.verification || {};
|
||
const scopeIsVerified = (scope) => scopedDims(scope).every((dim) => {
|
||
const option = (dim.options || []).find((entry) => entry.id === sel[dim.id]);
|
||
// A pick whose `soft` predicate is active is by definition outside the
|
||
// verified matrix — the scope badge must not keep reading Verified.
|
||
if (option && optionSoft(option, sel)) return false;
|
||
const predicate = option?.verifiedWhen ?? dim.verifiedWhen;
|
||
return typeof predicate === "function" ? !!predicate(sel) : predicate !== false;
|
||
});
|
||
const serveStatus = invalid
|
||
? "error"
|
||
: (scopeIsVerified("serve") ? (verification.serve || verifyStatus) : "unverified");
|
||
const requestStatus = invalid
|
||
? "error"
|
||
: (scopeIsVerified("request") ? (verification.request || verifyStatus) : "unverified");
|
||
const statusText = (status) => ({
|
||
verified: "Verified",
|
||
unverified: "Unverified",
|
||
"in-progress": "Verification in progress",
|
||
error: "Invalid configuration",
|
||
}[status] || "Unverified");
|
||
const activeServerSetting = serveDims.find((dim) => dim.id === builderServerSetting) || serveDims[0];
|
||
const selectedOption = (dim) => (dim.options || []).find((option) => option.id === sel[dim.id]);
|
||
const effectiveSetting = (dim) =>
|
||
builderMeta.resolvedSettings?.[dim.id]
|
||
|| selectedOption(dim)?.label
|
||
|| sel[dim.id]
|
||
|| "—";
|
||
const recommendedRecipe = recommendedBuilderRecipe(sel.hw);
|
||
const recommendedInUse = !!recommendedRecipe
|
||
&& Number(sel.nodes) === recommendedRecipe.nodes
|
||
&& Number(sel.gpus_per_node) === recommendedRecipe.gpus_per_node
|
||
&& sel.topology_mode === "auto"
|
||
&& ["auto", recommendedRecipe.placement].includes(sel.placement)
|
||
&& sel.attention === "platform"
|
||
&& sel.precision === "native"
|
||
&& ["auto", recommendedRecipe.encoder].includes(sel.encoder)
|
||
&& sel.execution === "eager";
|
||
|
||
const restoreRecommendedRecipe = () => {
|
||
if (!recommendedRecipe) return;
|
||
setSel((prev) => reseatHiddenPicks(normalizeBuilderSelection({
|
||
...prev,
|
||
nodes: recommendedRecipe.nodes,
|
||
gpus_per_node: recommendedRecipe.gpus_per_node,
|
||
topology_mode: "auto",
|
||
tp_size: recommendedRecipe.tp_size,
|
||
ulysses_degree: recommendedRecipe.ulysses_degree,
|
||
ring_degree: recommendedRecipe.ring_degree,
|
||
placement: recommendedRecipe.placement || "auto",
|
||
attention: "platform",
|
||
precision: "native",
|
||
encoder: recommendedRecipe.encoder || "auto",
|
||
execution: "eager",
|
||
})));
|
||
};
|
||
|
||
const renderBuilderChoice = (item, dim) => {
|
||
const checked = sel[dim.id] === item.id;
|
||
const disabled = !isEnabled(dim.id, item.id);
|
||
const soft = !disabled && optionSoft(item, sel);
|
||
const reason = disabled
|
||
? item.disableReason || "Not available for this configuration"
|
||
: soft
|
||
? item.softReason || "Runs, but this combination is not a verified recipe yet."
|
||
: "";
|
||
// aria-disabled instead of the disabled attribute: the control stays
|
||
// focusable and hoverable, so the reason is reachable by tooltip, by
|
||
// keyboard, and by the tap-feedback note below the row.
|
||
return (
|
||
<button
|
||
key={item.id}
|
||
type="button"
|
||
className="sgd-builder-choice"
|
||
data-selected={checked ? "true" : "false"}
|
||
data-blocked={disabled ? "true" : undefined}
|
||
data-soft={soft ? "true" : undefined}
|
||
aria-disabled={disabled}
|
||
aria-pressed={checked}
|
||
title={reason}
|
||
onClick={() => {
|
||
if (disabled) { flashBlockedNote(dim.id, reason); return; }
|
||
handleSelect(dim.id, item.id);
|
||
}}
|
||
>
|
||
<span className="sgd-builder-choice-dot" aria-hidden="true" />
|
||
<span>{item.label}</span>
|
||
{item.subtitle && <small>{item.subtitle}</small>}
|
||
</button>
|
||
);
|
||
};
|
||
|
||
const renderBuilderDimension = (dim) => (
|
||
<section className="sgd-builder-section" key={dim.id}>
|
||
<div className="sgd-builder-section-heading">
|
||
<span>{dim.title}</span>
|
||
{dim.description && <small>{dim.description}</small>}
|
||
</div>
|
||
<div className="sgd-builder-choice-grid" data-density={(dim.options || []).length > 5 ? "compact" : "normal"}>
|
||
{visibleOptions(dim, sel).map((option) => renderBuilderChoice(option, dim))}
|
||
</div>
|
||
{blockedNote && blockedNote.dim === dim.id && (
|
||
<p className="sgd-builder-blocked-note" role="status">{blockedNote.reason}</p>
|
||
)}
|
||
</section>
|
||
);
|
||
|
||
const renderStepper = (key, label, detail) => {
|
||
const bounds = commandBuilder.resource?.limits?.[key] || { min: 1, max: 8 };
|
||
return (
|
||
<div className="sgd-builder-stepper-field">
|
||
<div>
|
||
<span>{label}</span>
|
||
{detail && <small>{detail}</small>}
|
||
</div>
|
||
<div className="sgd-builder-stepper" aria-label={label}>
|
||
<button
|
||
type="button"
|
||
aria-label={`Decrease ${label}`}
|
||
disabled={Number(sel[key]) <= bounds.min}
|
||
onClick={() => updateBuilderResource(key, -1)}
|
||
>−</button>
|
||
{renderBuilderNumberInput({
|
||
identity: `${key}-${sel[key]}`,
|
||
value: sel[key],
|
||
min: bounds.min,
|
||
max: bounds.max,
|
||
label,
|
||
onCommit: (value) => setBuilderResource(key, value),
|
||
})}
|
||
<button
|
||
type="button"
|
||
aria-label={`Increase ${label}`}
|
||
disabled={Number(sel[key]) >= bounds.max}
|
||
onClick={() => updateBuilderResource(key, 1)}
|
||
>+</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const renderBaseScope = () => (
|
||
<div className="sgd-builder-scope-panel" data-scope="base">
|
||
{recommendedRecipe && (
|
||
<section className="sgd-builder-recipe">
|
||
<div>
|
||
{/* This is the verified operating point, not sizing advice — a
|
||
hardware whose validation ran on 8 GPUs is not "recommending"
|
||
8 over a smaller deployment. */}
|
||
<span>{recommendedRecipe.unverified ? "Derived recipe" : "Verified recipe"} · {sel.hw.toUpperCase()}</span>
|
||
<strong>
|
||
{[
|
||
`${recommendedRecipe.nodes * recommendedRecipe.gpus_per_node} GPUs`,
|
||
recommendedRecipe.tp_size > 1 && `TP ${recommendedRecipe.tp_size}`,
|
||
`Ulysses ${recommendedRecipe.ulysses_degree}`,
|
||
recommendedRecipe.ring_degree > 1 && `Ring ${recommendedRecipe.ring_degree}`,
|
||
{ resident: "Resident", fsdp: "FSDP", offload: "Layerwise offload" }[recommendedRecipe.placement],
|
||
].filter(Boolean).join(" · ")}
|
||
</strong>
|
||
</div>
|
||
<div>
|
||
{renderStatus(recommendedRecipe.unverified ? "unverified" : "verified")}
|
||
{recommendedInUse
|
||
? <small>In use</small>
|
||
: <button type="button" className="sgd-builder-text-action" onClick={restoreRecommendedRecipe}>{recommendedRecipe.unverified ? "Use derived recipe" : "Use verified recipe"}</button>}
|
||
</div>
|
||
</section>
|
||
)}
|
||
<section className="sgd-builder-section">
|
||
<div className="sgd-builder-section-heading"><span>Hardware</span></div>
|
||
<div className="sgd-builder-hardware-grid">
|
||
{hwGroups.flatMap((group) => group.items).map((item) => {
|
||
const selected = sel.hw === item.id;
|
||
return (
|
||
<button
|
||
key={item.id}
|
||
type="button"
|
||
className="sgd-builder-hardware"
|
||
data-selected={selected ? "true" : "false"}
|
||
aria-pressed={selected}
|
||
onClick={() => handleSelect("hw", item.id)}
|
||
>
|
||
<span className="sgd-builder-choice-dot" aria-hidden="true" />
|
||
<strong>{item.label}</strong>
|
||
<small>{item.subtitle}</small>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</section>
|
||
|
||
{/* Topology lives with Resources: its summary is the heading's detail
|
||
line, so the two rows that used to repeat each other are one
|
||
section. The advanced editor and its messages stay here too. */}
|
||
<section className="sgd-builder-section">
|
||
<div className="sgd-builder-section-heading">
|
||
<span>Resources</span>
|
||
<small>{builderMeta.topologySummary || "No valid topology"}</small>
|
||
</div>
|
||
<div className="sgd-builder-resource-grid">
|
||
{renderStepper("nodes", "Nodes")}
|
||
{renderStepper("gpus_per_node", "GPUs / node")}
|
||
</div>
|
||
{Number(sel.nodes) > 1 && (
|
||
<p className="sgd-builder-resource-summary">
|
||
{sel.nodes} nodes × {sel.gpus_per_node} {sel.hw.toUpperCase()} = {totalGpus} GPUs
|
||
</p>
|
||
)}
|
||
<button
|
||
type="button"
|
||
className="sgd-builder-text-action sgd-builder-topology-toggle"
|
||
aria-expanded={builderAdvanced}
|
||
onClick={() => setBuilderAdvanced((open) => !open)}
|
||
>
|
||
Advanced topology <span aria-hidden="true">{builderAdvanced ? "↗" : "↘"}</span>
|
||
</button>
|
||
{builderAdvanced && (
|
||
<div className="sgd-builder-advanced">
|
||
<p>Auto uses an exact verified recipe when one exists; manual values are allowed when the model constraints remain valid.</p>
|
||
<div className="sgd-builder-topology-inputs">
|
||
{[
|
||
["tp_size", "Tensor parallel", [1, 2, 4, 8]],
|
||
["ulysses_degree", "Ulysses", [1, 2, 4, 8, 16]],
|
||
["ring_degree", "Ring", [1, 2, 4, 8]],
|
||
].map(([key, label, values]) => (
|
||
<label key={key}>
|
||
<span>{label}</span>
|
||
<select
|
||
value={sel.topology_mode === "manual" ? sel[key] : (topology[key] || 1)}
|
||
onChange={(event) => editBuilderTopology(key, event.target.value)}
|
||
>
|
||
{values.map((value) => <option value={value} key={value}>{value}</option>)}
|
||
</select>
|
||
</label>
|
||
))}
|
||
</div>
|
||
<button
|
||
type="button"
|
||
className="sgd-builder-text-action"
|
||
disabled={sel.topology_mode === "auto"}
|
||
onClick={() => setSel((prev) => normalizeBuilderSelection({ ...prev, topology_mode: "auto" }))}
|
||
>Use automatic topology</button>
|
||
</div>
|
||
)}
|
||
{(errors.length > 0 || warnings.length > 0) && (
|
||
<div className="sgd-builder-messages" data-state={errors.length ? "error" : "warning"}>
|
||
{(errors.length ? errors : warnings).map((message, index) => <p key={index}>{message}</p>)}
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
{baseDims.map(renderBuilderDimension)}
|
||
</div>
|
||
);
|
||
|
||
const renderSettingEditor = (dim, className = "", direct = false) => {
|
||
if (!dim) return null;
|
||
const options = visibleOptions(dim, sel);
|
||
const currentOption = selectedOption(dim);
|
||
return (
|
||
<section className={`sgd-builder-context ${className}`} aria-live={direct ? undefined : "polite"}>
|
||
<div className="sgd-builder-context-heading">
|
||
<div>
|
||
<span>{direct ? dim.title : `${dim.title} options`}</span>
|
||
{dim.description && <p>{dim.description}</p>}
|
||
</div>
|
||
{dim.quality && <small>{dim.quality}</small>}
|
||
</div>
|
||
{dim.kind === "number" ? (
|
||
<div className="sgd-builder-request-stepper">
|
||
<button
|
||
type="button"
|
||
aria-label={`Decrease ${dim.title}`}
|
||
disabled={Number(sel[dim.id]) <= dim.min}
|
||
onClick={() => setSel((prev) => ({
|
||
...prev,
|
||
[dim.id]: Math.max(dim.min, Number(prev[dim.id]) - 1),
|
||
}))}
|
||
>−</button>
|
||
{renderBuilderNumberInput({
|
||
identity: `${dim.id}-${sel[dim.id]}`,
|
||
value: sel[dim.id],
|
||
min: dim.min,
|
||
max: dim.max,
|
||
label: dim.title,
|
||
onCommit: (value) => setSel((prev) => ({ ...prev, [dim.id]: value })),
|
||
})}
|
||
<button
|
||
type="button"
|
||
aria-label={`Increase ${dim.title}`}
|
||
disabled={Number(sel[dim.id]) >= dim.max}
|
||
onClick={() => setSel((prev) => ({
|
||
...prev,
|
||
[dim.id]: Math.min(dim.max, Number(prev[dim.id]) + 1),
|
||
}))}
|
||
>+</button>
|
||
<span>{dim.unit || "outputs"}</span>
|
||
</div>
|
||
) : (
|
||
<div className="sgd-builder-context-options">
|
||
{options.map((option) => renderBuilderChoice(option, dim))}
|
||
</div>
|
||
)}
|
||
{blockedNote && blockedNote.dim === dim.id && (
|
||
<p className="sgd-builder-blocked-note" role="status">{blockedNote.reason}</p>
|
||
)}
|
||
{(currentOption?.description || dim.learnMore) && (
|
||
<div className="sgd-builder-context-note">
|
||
{currentOption?.description && <p>{currentOption.description}</p>}
|
||
{/* Icons name the destination: section lines = an anchor on
|
||
this page, book = the runtime documentation. */}
|
||
{dim.learnMore && (
|
||
<a href={dim.learnMore}>
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="4" y1="6" x2="20" y2="6" /><line x1="4" y1="12" x2="16" y2="12" /><line x1="4" y1="18" x2="11" y2="18" /></svg>
|
||
Learn more
|
||
</a>
|
||
)}
|
||
{dim.docsHref && (
|
||
<a href={dim.docsHref}>
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" /><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" /></svg>
|
||
SGLang docs
|
||
</a>
|
||
)}
|
||
</div>
|
||
)}
|
||
</section>
|
||
);
|
||
};
|
||
|
||
const renderServerScope = () => (
|
||
<div className="sgd-builder-scope-panel" data-scope="serve">
|
||
<div className="sgd-builder-setting-layout">
|
||
<div className="sgd-builder-setting-list">
|
||
{serveDims.map((dim) => {
|
||
const isActive = dim.id === activeServerSetting?.id;
|
||
const option = selectedOption(dim);
|
||
const recommended = typeof option?.recommendedWhen === "function"
|
||
? option.recommendedWhen(sel)
|
||
: !!option?.recommended;
|
||
return (
|
||
<div className="sgd-builder-setting-item" key={dim.id}>
|
||
<button
|
||
type="button"
|
||
className="sgd-builder-setting-row"
|
||
data-active={isActive ? "true" : "false"}
|
||
aria-expanded={isActive}
|
||
onClick={() => setBuilderServerSetting(dim.id)}
|
||
>
|
||
<span>{dim.title}</span>
|
||
<strong>{effectiveSetting(dim)}</strong>
|
||
{recommended && <small>Recommended</small>}
|
||
<span aria-hidden="true">{isActive ? "⌄" : "›"}</span>
|
||
</button>
|
||
{isActive && renderSettingEditor(dim, "sgd-builder-context--inline")}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
{renderSettingEditor(activeServerSetting, "sgd-builder-context--rail")}
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
const renderRequestScope = () => (
|
||
<div className="sgd-builder-scope-panel sgd-builder-request-direct" data-scope="request">
|
||
{requestDims.map((dim) => (
|
||
<div className="sgd-builder-request-setting" key={dim.id}>
|
||
{renderSettingEditor(dim, "", true)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
|
||
const renderScopeControls = () => {
|
||
if (builderScope === "base") return renderBaseScope();
|
||
if (builderScope === "serve") return renderServerScope();
|
||
return renderRequestScope();
|
||
};
|
||
|
||
const renderStatus = (status) => (
|
||
<span className="sgd-builder-status" data-status={status}>
|
||
<span aria-hidden="true" />{statusText(status)}
|
||
</span>
|
||
);
|
||
|
||
const renderOutputCard = (type) => {
|
||
const serve = type === "serve";
|
||
const text = serve ? command : curlText;
|
||
const canExpand = text.split("\n").length > 9;
|
||
const expanded = serve ? serveExpanded : requestExpanded;
|
||
const setExpanded = serve ? setServeExpanded : setRequestExpanded;
|
||
const status = serve ? serveStatus : requestStatus;
|
||
const emphasized = builderScope === "base" || builderScope === type;
|
||
return (
|
||
<section
|
||
className="sgd-builder-output"
|
||
data-output={type}
|
||
data-emphasized={emphasized ? "true" : "false"}
|
||
>
|
||
<header>
|
||
<div className="sgd-builder-output-index">{serve ? "1" : "2"}</div>
|
||
<div className="sgd-builder-output-title">
|
||
<strong>{serve ? "Serve" : "Request"}</strong>
|
||
<span>
|
||
{serve
|
||
? `${sel.hw.toUpperCase()} · ${activeRunMode === "docker" ? "Docker" : "Python"}`
|
||
: "cURL"}
|
||
</span>
|
||
</div>
|
||
{renderStatus(status)}
|
||
</header>
|
||
{serve && runModes.length > 1 && (
|
||
<div className="sgd-builder-output-tabs" role="tablist" aria-label="Serve command format">
|
||
{runModes.map((mode) => (
|
||
<button
|
||
type="button"
|
||
role="tab"
|
||
aria-selected={activeRunMode === mode}
|
||
data-selected={activeRunMode === mode ? "true" : "false"}
|
||
key={mode}
|
||
onClick={() => setRunMode(mode)}
|
||
>{mode === "docker" ? "Docker" : "Python"}</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
{serve && Number(sel.nodes) > 1 && (
|
||
<div className="sgd-builder-node-fields">
|
||
<label>
|
||
<span>Head address</span>
|
||
<input value={builderHeadAddress} onChange={(event) => setBuilderHeadAddress(event.target.value)} />
|
||
</label>
|
||
<label>
|
||
<span>Node rank</span>
|
||
{renderBuilderNumberInput({
|
||
identity: `node-rank-${builderNodeRank}-${sel.nodes}`,
|
||
value: builderNodeRank,
|
||
min: 0,
|
||
max: Number(sel.nodes) - 1,
|
||
label: "Node rank",
|
||
onCommit: setBuilderNodeRank,
|
||
})}
|
||
</label>
|
||
</div>
|
||
)}
|
||
<div className="sgd-builder-code">
|
||
<pre className={expanded ? "is-expanded" : ""}><code>{text}</code></pre>
|
||
<button
|
||
type="button"
|
||
className="sgd-builder-copy"
|
||
disabled={invalid}
|
||
aria-label={(serve ? copied : curlCopied) ? "Copied" : "Copy command"}
|
||
data-copied={(serve ? copied : curlCopied) ? "true" : undefined}
|
||
onClick={serve ? handleCopy : copyCurl}
|
||
>
|
||
<svg className="sgd-builder-copy-glyph" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2.5" /><path d="M15 5v-.25A2.75 2.75 0 0 0 12.25 2h-7.5A2.75 2.75 0 0 0 2 4.75v7.5A2.75 2.75 0 0 0 4.75 15H5" /></svg>
|
||
<svg className="sgd-builder-copy-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
||
</button>
|
||
</div>
|
||
{invalid && <div className="sgd-builder-output-error">{errors[0]}</div>}
|
||
<footer>
|
||
{canExpand && (
|
||
<button type="button" className="sgd-builder-text-action" onClick={() => setExpanded(!expanded)}>
|
||
{expanded ? "Collapse" : "Expand"}
|
||
</button>
|
||
)}
|
||
<div>
|
||
<button type="button" className="sgd-builder-text-action" onClick={() => setModal("env")}>Variables</button>
|
||
</div>
|
||
</footer>
|
||
</section>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<section
|
||
id={DEPLOYMENT_COMPONENT_ID}
|
||
className="not-prose sg-command-visualizer sgd-command-builder"
|
||
style={{ scrollMarginTop: "104px" }}
|
||
aria-label={`${config.modelName} command builder`}
|
||
>
|
||
<nav className="sgd-builder-scope-tabs" role="tablist" aria-label="Command builder scope">
|
||
{["base", "serve", "request"].map((scope) => (
|
||
<button
|
||
type="button"
|
||
role="tab"
|
||
key={scope}
|
||
aria-label={scopeLabel[scope]}
|
||
aria-selected={builderScope === scope}
|
||
aria-controls={`${DEPLOYMENT_COMPONENT_ID}-controls`}
|
||
data-active={builderScope === scope ? "true" : "false"}
|
||
onClick={() => setBuilderScope(scope)}
|
||
>
|
||
{scopeLabel[scope]}
|
||
</button>
|
||
))}
|
||
</nav>
|
||
|
||
<div className="sgd-builder-main" data-scope={builderScope}>
|
||
<div
|
||
id={`${DEPLOYMENT_COMPONENT_ID}-controls`}
|
||
className="sgd-builder-controls"
|
||
role="tabpanel"
|
||
aria-label={`${scopeLabel[builderScope]} settings`}
|
||
>
|
||
{renderScopeControls()}
|
||
</div>
|
||
<div className="sgd-builder-output-rail">
|
||
{builderScope !== "request" && renderOutputCard("serve")}
|
||
{builderScope !== "serve" && renderOutputCard("request")}
|
||
</div>
|
||
</div>
|
||
|
||
{modal === "env" && (
|
||
<div style={s.modalBackdrop} onClick={() => setModal(null)}>
|
||
<div style={s.modalBox} onClick={(event) => event.stopPropagation()}>
|
||
<div style={s.modalHeader}>
|
||
<div style={s.modalTitle}>Command variables</div>
|
||
<button style={s.modalCloseBtn} onClick={() => setModal(null)} aria-label="Close">×</button>
|
||
</div>
|
||
{["command", "curl"].map((target) => placeholderGroups[target].length > 0 && (
|
||
<div key={target}>
|
||
<div style={s.sectionHeading}>{target === "command" ? "Serve" : "Request"}</div>
|
||
{placeholderGroups[target].map(({ key, label }) => (
|
||
<div key={key} style={s.formField}>
|
||
<label style={s.formLabel}>{label}</label>
|
||
<input
|
||
style={s.formInput}
|
||
value={envDraft[key] ?? ""}
|
||
onChange={(event) => setEnvDraft({ ...envDraft, [key]: event.target.value })}
|
||
/>
|
||
</div>
|
||
))}
|
||
</div>
|
||
))}
|
||
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 16 }}>
|
||
<button style={{ ...s.iconButton, padding: "6px 14px" }} onClick={() => setModal(null)}>Cancel</button>
|
||
<button style={s.primaryBtn} onClick={() => { saveEnv(envDraft); setModal(null); }}>Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</section>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
id={DEPLOYMENT_COMPONENT_ID}
|
||
style={{ ...s.container, scrollMarginTop: "104px" }}
|
||
className="not-prose sg-command-visualizer"
|
||
>
|
||
{/* Hardware section (2 vendor rows in one card, equal-width grid) */}
|
||
<div style={s.cardColumn}>
|
||
<div style={{ ...s.title, marginBottom: "2px" }}>Hardware Platform</div>
|
||
{hwGroups.map((g) => (
|
||
<div key={g.label || "hardware"} style={s.vendorRow}>
|
||
{g.label && <div style={s.vendorLabel}>{g.label}</div>}
|
||
<div style={s.itemsGrid(maxHwCols)}>
|
||
{g.items.map((item) => renderButton(item, "hw", sel.hw))}
|
||
{Array.from({ length: maxHwCols - g.items.length }).map((_, i) => (
|
||
<div key={`pad-${i}`} />
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{matchDimSpecs
|
||
.filter((d) => rowVisible(d, sel))
|
||
.map((d) => (
|
||
<div key={d.id}>
|
||
{renderFlatSection(d.title, visibleOptions(d, sel), d.id, sel[d.id])}
|
||
</div>
|
||
))}
|
||
{overlayDimSpecs
|
||
.filter((d) => rowVisible(d, sel))
|
||
.map((d) => (
|
||
<div key={d.id}>
|
||
{renderFlatSection(d.title, visibleOptions(d, sel), d.id, sel[d.id])}
|
||
</div>
|
||
))}
|
||
|
||
{/* Command box */}
|
||
<div style={s.card}>
|
||
<div style={s.title}>Command:</div>
|
||
<div style={s.commandWrap}>
|
||
{cell && cell.redirect ? (
|
||
cell.warn && <div style={s.mtpWarn}>⚠️ {renderWarn(cell.warn)}</div>
|
||
) : (<>
|
||
<div style={s.commandHeader}>
|
||
<div style={s.headerLeft}>
|
||
<div style={s.badge(verifyStatus)}>
|
||
<span style={s.badgeDot(verifyStatus)} />
|
||
{VERIFY_LABEL[verifyStatus]}
|
||
</div>
|
||
<div style={s.runModeWrap} role="tablist" aria-label="Output format">
|
||
{runModes.map((mode, index) => (
|
||
<span
|
||
key={mode}
|
||
className="sg-command-visualizer-tab"
|
||
style={{
|
||
...(index === runModes.length - 1
|
||
? s.runModeChipLast(activeRunMode === mode)
|
||
: s.runModeChip(activeRunMode === mode)),
|
||
...(runModes.length === 1 ? { borderRadius: 7 } : {}),
|
||
}}
|
||
onClick={() => setRunMode(mode)}
|
||
onKeyDown={(e) => {
|
||
if (e.key !== "Enter" && e.key !== " ") return;
|
||
e.preventDefault();
|
||
setRunMode(mode);
|
||
}}
|
||
role="tab"
|
||
tabIndex={0}
|
||
aria-selected={activeRunMode === mode}
|
||
>
|
||
{mode === "docker" ? "Docker" : "Python"}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div style={s.iconRow}>
|
||
<button style={s.iconButton} onClick={handleCopy}>
|
||
{copied ? "✓ Copied" : "⧉ Copy"}
|
||
</button>
|
||
<button style={s.iconButton} onClick={() => setModal("curl")}>$ cURL</button>
|
||
<button style={s.iconButton} onClick={() => setModal("env")}>⚙ Env</button>
|
||
</div>
|
||
</div>
|
||
<pre style={s.commandPre}>{command}</pre>
|
||
{cell && cell.warn && <div style={s.mtpWarn}>⚠️ {renderWarn(cell.warn)}</div>}
|
||
{mtpHint && (
|
||
<div style={s.mtpWarn}>
|
||
⚠️ Speculative decoding ({specAlgoName}) is on — SGLang resets <code>--max-running-requests</code> to <strong>48</strong> when it isn't set. Add <code>--max-running-requests <N></code> sized for your target concurrency.
|
||
</div>
|
||
)}
|
||
{specPinnedHint && (
|
||
<div style={s.mtpWarn}>
|
||
ℹ️ Speculative decoding ({specAlgoName}) is on and this recipe pins <code>--max-running-requests</code> to <strong>{specMrrValue}</strong>. Adjust it to match your target concurrency — if you remove the flag, SGLang falls back to <strong>48</strong>.
|
||
</div>
|
||
)}
|
||
</>)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Benchmark card (only with a `benchmarks` prop + matched cell). */}
|
||
{benchmarks && cell && renderBenchmarkCard(benchEntry)}
|
||
|
||
{/* Playground link — scrollIntoView, not an href, so the hash (which
|
||
carries the selection) isn't overwritten. */}
|
||
{config.showPlaygroundLink !== false && (
|
||
<div
|
||
style={{
|
||
padding: "6px 12px",
|
||
fontSize: "12px",
|
||
color: isDark ? "#9ca3af" : "#6b7280",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: "6px",
|
||
}}
|
||
>
|
||
<span>Need to go beyond the verified matrix?</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const el = document.getElementById("playground");
|
||
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||
}}
|
||
style={{
|
||
background: "transparent",
|
||
border: "none",
|
||
padding: 0,
|
||
color: isDark ? "#FDBA74" : "#C2410C",
|
||
cursor: "pointer",
|
||
fontSize: "12px",
|
||
fontWeight: 600,
|
||
textDecoration: "underline",
|
||
textUnderlineOffset: "2px",
|
||
}}
|
||
>
|
||
Open the Playground →
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{/* cURL modal */}
|
||
{modal === "curl" && (
|
||
<div style={s.modalBackdrop} onClick={() => setModal(null)}>
|
||
<div style={s.modalBox} onClick={(e) => e.stopPropagation()}>
|
||
<div style={s.modalHeader}>
|
||
<div style={s.modalTitle}>cURL example</div>
|
||
<button style={s.modalCloseBtn} onClick={() => setModal(null)} aria-label="Close">×</button>
|
||
</div>
|
||
<div style={s.commandWrap}>
|
||
<div style={s.commandHeader}>
|
||
<div style={{ fontSize: 11, opacity: 0.7 }}>
|
||
Model: <code>{modelName || "(unresolved)"}</code>
|
||
</div>
|
||
<button style={s.iconButton} onClick={copyCurl}>
|
||
{curlCopied ? "✓ Copied" : "⧉ Copy"}
|
||
</button>
|
||
</div>
|
||
<pre style={s.commandPre}>{curlText}</pre>
|
||
</div>
|
||
<p style={{ fontSize: 11, opacity: 0.7, marginTop: 8 }}>
|
||
Edit <code>CURL_HOST</code> / <code>CURL_PORT</code> in the Env panel.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Env modal */}
|
||
{modal === "env" && (
|
||
<div style={s.modalBackdrop} onClick={() => setModal(null)}>
|
||
<div style={s.modalBox} onClick={(e) => e.stopPropagation()}>
|
||
<div style={s.modalHeader}>
|
||
<div style={s.modalTitle}>Env / placeholder values</div>
|
||
<button style={s.modalCloseBtn} onClick={() => setModal(null)} aria-label="Close">×</button>
|
||
</div>
|
||
{placeholderGroups.curl.length > 0 && (
|
||
<div>
|
||
<div style={s.sectionHeading}>cURL placeholders</div>
|
||
{placeholderGroups.curl.map(({ key, label }) => (
|
||
<div key={key} style={s.formField}>
|
||
<label style={s.formLabel}>
|
||
{label} <code style={{ opacity: 0.6 }}>{`{{${key}}}`}</code>
|
||
</label>
|
||
<input
|
||
style={s.formInput}
|
||
value={envDraft[key] ?? ""}
|
||
onChange={(e) => setEnvDraft({ ...envDraft, [key]: e.target.value })}
|
||
/>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
{placeholderGroups.command.length > 0 && (
|
||
<div>
|
||
<div style={s.sectionHeading}>Command placeholders</div>
|
||
{placeholderGroups.command.map(({ key, label }) => (
|
||
<div key={key} style={s.formField}>
|
||
<label style={s.formLabel}>
|
||
{label} <code style={{ opacity: 0.6 }}>{`{{${key}}}`}</code>
|
||
</label>
|
||
<input
|
||
style={s.formInput}
|
||
value={envDraft[key] ?? ""}
|
||
onChange={(e) => setEnvDraft({ ...envDraft, [key]: e.target.value })}
|
||
/>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 16 }}>
|
||
<button style={{ ...s.iconButton, padding: "6px 14px" }} onClick={() => setModal(null)}>Cancel</button>
|
||
<button style={s.primaryBtn} onClick={() => { saveEnv(envDraft); setModal(null); }}>Save</button>
|
||
</div>
|
||
<p style={{ fontSize: 11, opacity: 0.7, marginTop: 10 }}>
|
||
Values persist in localStorage and are reused the next time you visit any cookbook.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* "⚡ Reproduce" modal — benchmark commands for the current selection. */}
|
||
{modal === "bench" && benchEntry && (() => {
|
||
const bc = buildBenchCommands(benchEntry, sel);
|
||
if (!bc) return null;
|
||
const selSummary = [
|
||
sel.hw && sel.hw.toUpperCase(),
|
||
sel.variant,
|
||
sel.quant && sel.quant.toUpperCase(),
|
||
sel.strategy,
|
||
sel.kvDsaPair,
|
||
sel.nodes,
|
||
]
|
||
.filter((part) => part !== undefined && part !== null && part !== "")
|
||
.join(" · ");
|
||
let selConc = null;
|
||
let speedCmd = null;
|
||
if (bc.speed) {
|
||
selConc = bc.speed.concurrencies.includes(benchConc)
|
||
? benchConc : bc.speed.concurrencies[0];
|
||
const w = bc.speed.workload;
|
||
speedCmd = interpolate(bc.speed.template, {
|
||
...env,
|
||
DATASET: w.dataset,
|
||
ISL: w.isl,
|
||
OSL: w.osl,
|
||
MAX_CONCURRENCY: selConc,
|
||
NUM_PROMPTS: bc.speed.numPromptsOf(selConc),
|
||
}, modelName);
|
||
}
|
||
// Accuracy chip-selected; stale benchAcc falls back to the first eval.
|
||
let selAcc = null;
|
||
let accCmd = null;
|
||
if (bc.accuracy.length > 0) {
|
||
selAcc = bc.accuracy.find((a) => a.key === benchAcc) || bc.accuracy[0];
|
||
accCmd = interpolate(selAcc.template, env, modelName);
|
||
}
|
||
return (
|
||
<div style={s.modalBackdrop} onClick={() => setModal(null)}>
|
||
<div style={s.modalBox} onClick={(e) => e.stopPropagation()}>
|
||
<div style={s.modalHeader}>
|
||
<div style={s.modalTitle}>Benchmark commands</div>
|
||
<button style={s.modalCloseBtn} onClick={() => setModal(null)} aria-label="Close">×</button>
|
||
</div>
|
||
<p style={{ fontSize: 11, opacity: 0.7, margin: "0 0 12px" }}>
|
||
For <code>{selSummary}</code>. Start the server with the Deploy command above, then run these against it.
|
||
</p>
|
||
|
||
{selAcc && (
|
||
<div>
|
||
<div style={s.sectionHeading}>Accuracy</div>
|
||
{bc.accuracy.length > 1 && (
|
||
<div style={s.benchChipRow}>
|
||
<span style={{ fontSize: 11, opacity: 0.7 }}>benchmark:</span>
|
||
{bc.accuracy.map((a) => (
|
||
<button
|
||
key={a.key}
|
||
style={{ ...s.benchChip, ...(a.key === selAcc.key ? s.benchChipActive : {}) }}
|
||
onClick={() => setBenchAcc(a.key)}
|
||
>
|
||
{a.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
<div style={{ ...s.commandWrap, marginBottom: 6 }}>
|
||
<div style={s.commandHeader}>
|
||
<div style={{ fontSize: 11, opacity: 0.7 }}>{selAcc.label}</div>
|
||
<button style={s.iconButton} onClick={() => copyBench("acc", accCmd)}>
|
||
{benchCopied === "acc" ? "✓ Copied" : "⧉ Copy"}
|
||
</button>
|
||
</div>
|
||
<pre style={s.commandPre}>{accCmd}</pre>
|
||
</div>
|
||
{bc.accuracy.length > 1 && (
|
||
<p style={{ fontSize: 11, opacity: 0.7, margin: "0 0 4px" }}>
|
||
Switch the benchmark chip to see each eval's command.
|
||
</p>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{bc.speed && (
|
||
<div>
|
||
<div style={s.sectionHeading}>Speed</div>
|
||
{bc.speed.concurrencies.length > 1 && (
|
||
<div style={s.benchChipRow}>
|
||
<span style={{ fontSize: 11, opacity: 0.7 }}>max-concurrency:</span>
|
||
{bc.speed.concurrencies.map((c) => (
|
||
<button
|
||
key={c}
|
||
style={{ ...s.benchChip, ...(c === selConc ? s.benchChipActive : {}) }}
|
||
onClick={() => setBenchConc(c)}
|
||
>
|
||
{c}
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
<div style={{ ...s.commandWrap, marginBottom: 6 }}>
|
||
<div style={s.commandHeader}>
|
||
<div style={{ fontSize: 11, opacity: 0.7 }}>max-concurrency = {selConc}</div>
|
||
<button style={s.iconButton} onClick={() => copyBench("speed", speedCmd)}>
|
||
{benchCopied === "speed" ? "✓ Copied" : "⧉ Copy"}
|
||
</button>
|
||
</div>
|
||
<pre style={s.commandPre}>{speedCmd}</pre>
|
||
</div>
|
||
<p style={{ fontSize: 11, opacity: 0.7, margin: "0 0 4px" }}>
|
||
One command — switch the concurrency chip (or edit <code>--max-concurrency</code>) to reproduce each Speed column.
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
<p style={{ fontSize: 11, opacity: 0.7, marginTop: 12 }}>
|
||
Edit <code>CURL_HOST</code> / <code>CURL_PORT</code> in the Env panel.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
})()}
|
||
</div>
|
||
);
|
||
};
|