[Docs] Playground: migrate CP knob to canonical prefill-CP flags, align gating with runtime semantics (#31411)

This commit is contained in:
zijiexia
2026-07-16 01:07:34 -07:00
committed by GitHub
parent 01b003255a
commit 1b9f228838
8 changed files with 326 additions and 43 deletions
@@ -147,6 +147,38 @@ schemas (full reference in the `_playground.jsx` header):
- Constraints are AND across keys, OR within each key's array.
- Bare `disabled: true` / `disable: true` is a static always-disabled form
(used for "Coming soon" chips).
- `disable` may also be an ARRAY of `{when: constraint, reason}` items (OR
across items, first match wins and supplies its own tooltip) — for
conditions that need OR across keys or per-condition reasons, e.g. the
GLM-5.2 CP knob (grayed on non-Hopper hw OR multi-node, different reasons).
- Constraint keys are the 5 cell dims (`hw`/`variant`/`quant`/`strategy`/
`nodes`) plus cross-axis live facts: `dpAttnOn` (effective DP-Attention on),
`cpOn` (effective prefill-CP on), `cpStrategy` (effective CP layout),
`cpSizeTarget` (the only enable-able CP size — see below), `effTp`
(effective TP degree — override else derived), and `pdMode` (live
PD-Disagg role).
- On the `attention` axis, knob-level `hide`/`disable` (on the knob object,
not a value entry) hides/grays the whole select; apply() skips
knobs/values that are disabled under the live facts, so stale picks never
emit a blocked combination. Interleave prefill-CP + DP-Attention is
deliberately NOT grayed (combined support is planned upstream even though
current releases assert `dp_size == 1` for interleave) — the engine shows
a warning hint below the command box instead.
- The CP knob emits `--attn-cp-size N --enable-prefill-cp --cp-strategy S`,
where S is: an optional `{ id: "cpStrategy", values: [null, "interleave",
"zigzag"] }` knob's pick > the strategy already baked in the base cell
(legacy mode flags map in: in-seq-split → zigzag, round-robin-split →
interleave) > "interleave". Declare the cpStrategy knob only on models
whose runtime accepts both layouts (DeepSeek-V4 rejects zigzag; DSA
zigzag forces deepep + ep=tp + batch_size=1 — label it experimental).
- CP sizes auto-gate in the engine to the runtime derivation
`attn_cp_size = tp/dp` (both in the grayed options and in apply), so
configs list plain size values — no per-value `effTp` constraints needed.
A model whose runtime honors arbitrary `--attn-cp-size` opts out with
`freeSize: true` on the cp knob.
- Only expose a CP knob on models with model-side CP integration in SGLang
(DeepSeek-family / Qwen-MoE / Mellum); on others the emitted flags do
nothing or crash (that's why Hy3 and MiniMax-M3 have no CP knob).
## 2.4 Create the MDX page
@@ -79,7 +79,7 @@ The Playground lets you turn on additional knobs on top of whichever Deploy cell
The knobs come in two flavors:
- **Built-in SGLang features** — parallelism overrides (TP / CP / DP-Attention), MoE backend + EP, reasoning / tool-call parsers, speculative-decoding presets, prefill/decode disaggregation, and HiCache tiers.
- **Built-in SGLang features** — parallelism overrides (TP / DP-Attention), MoE backend + EP, reasoning / tool-call parsers, speculative-decoding presets, prefill/decode disaggregation, and HiCache tiers.
- **Hy3 specific** — `--tool-call-parser auto` / `--reasoning-parser auto` (auto-detect Hy3's suffix-aware `hunyuan` parsers from the chat template; resolve the real special tokens from the tokenizer vocab at runtime).
Lines highlighted **green** are added by your overrides; lines with **red strikethrough** were in the verified base but stripped by an override. When no override differs from the base cell, the playground inherits the base's **Verified** badge; any actual change flips it to **Not Verified** until the new configuration is run end-to-end and submitted back.
+235 -27
View File
@@ -110,6 +110,9 @@ export const Playground = ({ config }) => {
// mapping base-cell dims to allowed-value arrays; matches when every key
// matches (AND across keys, OR within a key). Empty/malformed never match.
// `disabled: true` / `disable: true` are static always-disabled forms.
// `disable` may also be an ARRAY of `{when: constraint, reason?}` items
// (OR across items, first match wins and supplies its own reason) for
// conditions that need OR across keys or per-condition tooltips.
const matchConstraint = (base, constraint) => {
if (!constraint || typeof constraint !== "object") return false;
const entries = Object.entries(constraint);
@@ -130,16 +133,28 @@ export const Playground = ({ config }) => {
}
const hidden = entry.hide ? matchConstraint(base, entry.hide) : false;
let disabled = entry.disabled === true || entry.disable === true;
let disableReason = entry.disableReason || "";
if (!disabled && entry.disable && typeof entry.disable === "object") {
if (Array.isArray(entry.disable)) {
for (const item of entry.disable) {
const cond = (item && item.when) || item;
if (matchConstraint(base, cond)) {
disabled = true;
if (item && item.reason) disableReason = item.reason;
break;
}
}
} else {
disabled = matchConstraint(base, entry.disable);
}
}
return {
...entry,
value: entry.id !== undefined ? entry.id : entry.value,
label: entry.label,
hidden,
disabled,
disableReason: entry.disableReason || "",
disableReason,
};
};
@@ -239,6 +254,37 @@ export const Playground = ({ config }) => {
ANCHOR_NEAR_DPATTN, ANCHOR_NEAR_MOE,
};
// -------- Prefill-CP flag family (shared by the attention axis) --------
// Every flag head that toggles/parameterizes prefill context parallelism:
// the canonical pair plus all per-family legacy spellings.
const CP_ENABLE_HEADS = [
"--enable-prefill-cp",
"--enable-nsa-prefill-context-parallel",
"--enable-dsa-prefill-context-parallel",
"--enable-prefill-context-parallel",
];
const CP_MODE_HEADS = [
"--nsa-prefill-cp-mode", "--dsa-prefill-cp-mode", "--prefill-cp-mode",
];
const CP_OWNED_HEADS = [
...CP_ENABLE_HEADS, ...CP_MODE_HEADS, "--cp-strategy", "--attn-cp-size",
];
// Legacy mode spellings → new-style --cp-strategy values.
const CP_MODE_TO_STRATEGY = {
"in-seq-split": "zigzag",
"round-robin-split": "interleave",
};
const cpEnabledIn = (flags) =>
CP_ENABLE_HEADS.some((head) => hasFlag(flags, head));
// Strategy a cell's flags carry: --cp-strategy first, else a mapped legacy
// mode flag, else null (no strategy baked).
const bakedCpStrategy = (flags) =>
findFlagArg(flags, "--cp-strategy")
|| CP_MODE_TO_STRATEGY[findFlagArg(flags, "--nsa-prefill-cp-mode")]
|| CP_MODE_TO_STRATEGY[findFlagArg(flags, "--dsa-prefill-cp-mode")]
|| CP_MODE_TO_STRATEGY[findFlagArg(flags, "--prefill-cp-mode")]
|| null;
// ==========================================================================
// 5. AXIS_HANDLERS — the built-in playground axis registry
// ==========================================================================
@@ -255,12 +301,17 @@ export const Playground = ({ config }) => {
// ---- Axis: Attention Parallelism ----------------------------------------
// TP / CP / DP-Attention sub-knobs; `null` = inherit. DP-Attention is
// combined: a numeric value emits `--dp N --enable-dp-attention`, `false`
// strips both.
// strips both. An optional `cpStrategy` knob (values from --cp-strategy:
// "zigzag" / "interleave") picks the CP layout; without it the strategy
// baked in the base is preserved, defaulting to "interleave" (the legacy
// knob's round-robin-split).
attention: {
initState: () => ({ tp: null, cp: null, dpAttn: null }),
initState: () => ({ tp: null, cp: null, cpStrategy: null, dpAttn: null }),
// DP-Attention: `--dp N --enable-dp-attention` → N; neither → false;
// bare `--enable-dp-attention` → 1. CP only resolves on/off (→ 2).
// bare `--enable-dp-attention` → 1. CP: any enable spelling →
// `--attn-cp-size N` (bare enable → 2, the legacy convention), plus the
// baked strategy (legacy mode flags mapped to zigzag/interleave).
deriveFromBase: (cell, fc, h) => {
const flags = (cell && cell.flags) || [];
const dpVal = h.parseIntFlag(flags, "--dp");
@@ -269,9 +320,11 @@ export const Playground = ({ config }) => {
if (dpVal !== null) dpAttn = dpVal;
else if (hasDpAttn) dpAttn = 1;
else dpAttn = false;
const cpSize = h.parseIntFlag(flags, "--attn-cp-size");
return {
tp: h.parseIntFlag(flags, "--tp"),
cp: h.hasFlag(flags, "--enable-nsa-prefill-context-parallel") ? 2 : null,
cp: cpEnabledIn(flags) ? (cpSize !== null ? cpSize : 2) : null,
cpStrategy: bakedCpStrategy(flags),
dpAttn,
};
},
@@ -289,12 +342,84 @@ export const Playground = ({ config }) => {
return changed ? next : value;
},
apply: ({ flags, env, value, h }) => {
if (value.tp !== null) {
apply: ({ flags, env, value, fc, sel, h }) => {
// Live facts for constraint checks (same keys the render-side
// constraintBase exposes), recomputed after each mutation.
const knobEntry = (id) => (fc.knobs || []).find((k) => k.id === id) || {};
const factsNow = () => ({
...(sel || {}),
dpAttnOn: h.hasFlag(flags, "--enable-dp-attention"),
cpOn: cpEnabledIn(flags),
cpStrategy: bakedCpStrategy(flags) || "interleave",
effTp: h.parseIntFlag(flags, "--tp"),
});
// The runtime derives the prefill-CP size as attn_cp_size = tp/dp
// (a mismatched --attn-cp-size is overridden), so with DP-Attention
// off only CP == TP is real. With DP-Attention on the sizes are NOT
// gated: CP + DP-Attention is an allowed experiment (warning hint
// below the command box). Mirrors the render-side auto-gating; a
// config opts out entirely with `freeSize: true` on the cp knob.
const cpSizeTargetNow = () => {
if (knobEntry("cp").freeSize) return null;
const dpIntent = (value.dpAttn !== null && value.dpAttn !== undefined)
? value.dpAttn
: (h.hasFlag(flags, "--enable-dp-attention")
? (h.parseIntFlag(flags, "--dp") ?? 1) : false);
if (typeof dpIntent === "number" && dpIntent > 1) return null;
return h.parseIntFlag(flags, "--tp");
};
// Skip a knob whose entry or picked value is hidden/disabled under
// the live facts — mirrors the grayed controls, so stale state never
// emits a blocked combination.
const blocked = (id, v) => {
const facts = factsNow();
const kc = h.evaluateChip(knobEntry(id), facts);
if (kc.hidden || kc.disabled) return true;
if (id === "cp" && typeof v === "number" && v > 1) {
const target = cpSizeTargetNow();
if (target !== null && v !== target) return true;
}
const e = h.findEntry(knobEntry(id).values || [], v);
return !!(e !== null && e !== undefined
&& h.evaluateChip(e, facts).disabled);
};
// NOTE: interleave prefill-CP + DP-Attention currently fails the
// runtime's dp_size == 1 assert, but combined support is planned
// upstream — the combination is allowed here (with a warning hint
// below the command box) rather than banned.
if (value.tp !== null && !blocked("tp", value.tp)) {
flags = h.stripFlagsByFirstToken(flags, ["--tp"]);
flags = h.insertAfter(flags, h.ANCHOR_NEAR_MODEL_PATH, [`--tp ${value.tp}`]);
}
if (value.dpAttn !== null && value.dpAttn !== undefined) {
// CP override: an explicit size pick, or a strategy-only pick on a
// base that already carries CP. Strategy precedence: explicit knob >
// baked-in-base > "interleave" (the legacy knob's round-robin-split).
const cpStrategyOverride =
(value.cpStrategy && !blocked("cpStrategy", value.cpStrategy))
? value.cpStrategy : null;
const cpPick = (value.cp !== null)
? value.cp
: ((cpStrategyOverride && cpEnabledIn(flags))
? (h.parseIntFlag(flags, "--attn-cp-size") ?? 2)
: null);
const cpStrategyPick =
cpStrategyOverride || bakedCpStrategy(flags) || "interleave";
if (cpPick !== null && !blocked("cp", cpPick)) {
// Own the whole CP flag family (canonical + every legacy spelling)
// so an override fully replaces (or removes) whatever the base
// recipe baked in.
flags = h.stripFlagsByFirstToken(flags, CP_OWNED_HEADS);
if (cpPick > 1) {
flags = h.insertAfter(flags, h.ANCHOR_NEAR_DPATTN, [
`--attn-cp-size ${cpPick}`,
"--enable-prefill-cp",
`--cp-strategy ${cpStrategyPick}`,
]);
}
}
if (value.dpAttn !== null && value.dpAttn !== undefined
&& !blocked("dpAttn", value.dpAttn)) {
flags = h.stripFlagsByFirstToken(flags, ["--dp", "--enable-dp-attention"]);
if (typeof value.dpAttn === "number" && value.dpAttn > 0) {
flags = h.insertAfter(flags, h.ANCHOR_NEAR_TP, [
@@ -303,24 +428,17 @@ export const Playground = ({ config }) => {
]);
}
}
if (value.cp !== null) {
flags = h.stripFlagsByFirstToken(flags, [
"--enable-nsa-prefill-context-parallel", "--nsa-prefill-cp-mode",
]);
if (value.cp > 1) {
flags = h.insertAfter(flags, h.ANCHOR_NEAR_DPATTN, [
"--enable-nsa-prefill-context-parallel",
"--nsa-prefill-cp-mode round-robin-split",
]);
}
}
return { flags, env };
},
render: ({ axisId, value, setValue, fc, base, s, renderSelect, derived }) => {
render: ({ axisId, value, setValue, fc, base, s, h, renderSelect, derived }) => {
const knobs = fc.knobs || [];
if (!knobs.length) return null;
const setKnob = (k, v) => setValue({ ...value, [k]: v });
// Interleave prefill-CP + DP-Attention is deliberately NOT grayed:
// current releases assert dp_size == 1 for interleave, but combined
// support is planned upstream — a warning hint below the command box
// covers it instead.
const labelFor = (knob) => (c) => {
if (c.label !== undefined) return c.label;
if (knob.id === "dpAttn") {
@@ -341,18 +459,44 @@ export const Playground = ({ config }) => {
const d = derived ? derived[knob.id] : null;
return (d !== null && d !== undefined) ? [null] : [];
};
// Auto-gate CP sizes to the runtime derivation attn_cp_size = tp/dp
// (mirrors apply's cpSizeTargetNow; `freeSize: true` opts out).
const entriesFor = (knob) => {
const vals = knob.values || [null];
if (knob.id !== "cp" || knob.freeSize) return vals;
const target = base.cpSizeTarget;
if (target === null || target === undefined) return vals;
return vals.map((entry) => {
const v = (entry === null || typeof entry !== "object")
? entry : (entry.id !== undefined ? entry.id : entry.value);
if (typeof v !== "number" || v <= 1 || v === target) return entry;
const wrapped = (entry === null || typeof entry !== "object")
? { value: entry } : { ...entry };
return {
...wrapped,
disabled: true,
disableReason: `SGLang derives the prefill-CP size as attn_cp_size = TP / DP-Attention (= ${target} here), so only that size can be enabled.`,
};
});
};
return (
<div key={axisId} style={s.card}>
<div style={s.compactRow}>
<span style={s.axisTitle}>Attention</span>
{knobs.map((knob) => (
{knobs.map((knob) => {
const kc = h.evaluateChip(knob, base);
if (kc.hidden) return null;
return (
<span key={knob.id} style={s.field}>
<span style={s.fieldLabel}>{knob.label || knob.id.toUpperCase()}</span>
{renderSelect(knobDisplay(knob), knob.values || [null],
{renderSelect(knobDisplay(knob), entriesFor(knob),
(nv) => setKnob(knob.id, nv), base, labelFor(knob),
{ hideValues: hideNullFor(knob) })}
{ hideValues: hideNullFor(knob),
disabled: kc.disabled,
disabledReason: kc.disableReason })}
</span>
))}
);
})}
</div>
</div>
);
@@ -1638,16 +1782,63 @@ export const Playground = ({ config }) => {
// (render path only; revertHidden keeps the clean 5-dim base).
// dpAttnOn — effective DP-Attention resolves to "on" (positive degree
// or true), explicit override else derived-from-base.
// cpOn — effective prefill-CP resolves to "on" (degree > 1),
// explicit override else derived-from-base.
// cpStrategy — effective CP layout ("zigzag" / "interleave"; explicit
// override else baked-in-base else "interleave").
// cpSizeTarget — the only enable-able CP size (runtime derives
// attn_cp_size = tp/dp); null when TP is unknown or the cp
// knob opts out via `freeSize: true`.
// effTp — effective TP degree (override else derived).
// pdMode — live PD-Disagg role; gates the decode-only HiSparse card.
const attnDelta = deltas.attention || {};
const attnDerived = derivedMap.attention || {};
const attnKnobs = ((pgFeatures.attention || {}).knobs) || [];
const effTp = (attnDelta.tp !== null && attnDelta.tp !== undefined)
? attnDelta.tp
: (attnDerived.tp !== undefined ? attnDerived.tp : null);
// A picked value whose entry is disabled under the live facts is skipped
// by apply() and must not count as "on" (stale-state corner: e.g. CP=8
// picked, then TP switched to 4). Only explicit picks can go stale;
// derived-from-base values are always real.
const staleExplicit = (knobId, picked) => {
const knob = attnKnobs.find((k) => k.id === knobId);
const e = knob ? findEntry(knob.values || [], picked) : null;
return !!(e !== null && e !== undefined
&& evaluateChip(e, { ...base, effTp }).disabled);
};
const effDpAttn = (attnDelta.dpAttn !== null && attnDelta.dpAttn !== undefined)
? attnDelta.dpAttn
? (staleExplicit("dpAttn", attnDelta.dpAttn) ? null : attnDelta.dpAttn)
: (attnDerived.dpAttn !== undefined ? attnDerived.dpAttn : null);
const dpAttnOn = (effDpAttn === true)
|| (typeof effDpAttn === "number" && effDpAttn > 0);
// Runtime derivation attn_cp_size = tp/dp: with DP-Attention off, the only
// enable-able CP size is TP. With DP-Attention on, sizes are NOT gated —
// CP + DP-Attention is an allowed experiment covered by a warning hint
// (null also when TP is unknown or the cp knob opts out via `freeSize`).
const dpDegEff = (typeof effDpAttn === "number" && effDpAttn > 0)
? effDpAttn : 1;
const cpKnobFreeSize = !!(attnKnobs.find((k) => k.id === "cp") || {}).freeSize;
const cpSizeTarget =
(!cpKnobFreeSize && dpDegEff === 1
&& typeof effTp === "number" && effTp > 0)
? effTp : null;
const cpSizeStale = (v) => typeof v === "number" && v > 1
&& cpSizeTarget !== null && v !== cpSizeTarget;
const effCp = (attnDelta.cp !== null && attnDelta.cp !== undefined)
? ((staleExplicit("cp", attnDelta.cp) || cpSizeStale(attnDelta.cp))
? null : attnDelta.cp)
: (attnDerived.cp !== undefined ? attnDerived.cp : null);
const cpOn = typeof effCp === "number" && effCp > 1;
const cpStrategy = ((attnDelta.cpStrategy
&& !staleExplicit("cpStrategy", attnDelta.cpStrategy))
? attnDelta.cpStrategy
: (attnDerived.cpStrategy !== undefined ? attnDerived.cpStrategy : null))
|| "interleave";
const pdMode = (deltas.pdDisagg && deltas.pdDisagg.mode) || "off";
const constraintBase = { ...base, dpAttnOn, pdMode };
const constraintBase = {
...base, dpAttnOn, cpOn, cpStrategy, cpSizeTarget, effTp, pdMode,
};
let baseCommand = "";
let playgroundCommand = "";
@@ -1678,6 +1869,14 @@ export const Playground = ({ config }) => {
pgFlagsLatest.some((f) => f.split(/[\s=]/)[0] === "--speculative-algorithm") &&
!pgFlagsLatest.some((f) => f.split(/[\s=]/)[0] === "--max-running-requests");
// Interleave prefill-CP + DP-Attention hint on the EFFECTIVE command:
// deliberately allowed (combined support is planned upstream), but current
// releases assert dp_size == 1 for the interleave layout at startup.
const pgCpDpHint =
cpEnabledIn(pgFlagsLatest)
&& (bakedCpStrategy(pgFlagsLatest) || "interleave") === "interleave"
&& pgFlagsLatest.some((f) => f.split(/[\s=]/)[0] === "--enable-dp-attention");
// Submission snippets: proposed cell + existing cell at the same match.
const proposedCellSnippet = baseCell
? serializeCell(base, pgEnvLatest, pgFlagsLatest) : "";
@@ -1766,6 +1965,8 @@ export const Playground = ({ config }) => {
// value to dodge form-value serialization; `onPick` gets the original
// value. `labelFor` is an optional label resolver; `opts.hideValues`
// suppresses values (e.g. the inherit sentinel when a base default exists).
// `opts.disabled` grays the whole select (knob-level gating), with
// `opts.disabledReason` as the hover tooltip.
const renderSelect = (current, entries, onPick, base, labelFor, opts = {}) => {
const hideSet = new Set(opts.hideValues || []);
const items = [];
@@ -1783,7 +1984,9 @@ export const Playground = ({ config }) => {
if (idx === -1) idx = 0;
return (
<select
style={s.select}
style={{ ...s.select, ...(opts.disabled ? s.chipDisabled : {}) }}
disabled={!!opts.disabled}
title={opts.disabled ? (opts.disabledReason || "Not available") : ""}
value={idx}
onChange={(e) => {
const next = items[parseInt(e.target.value, 10)];
@@ -1931,6 +2134,11 @@ export const Playground = ({ config }) => {
Speculative decoding (MTP) is on SGLang resets <code>--max-running-requests</code> to <strong>48</strong> when it isn't set. Add <code>--max-running-requests &lt;N&gt;</code> sized for your target concurrency.
</div>
)}
{pgCpDpHint && (
<div style={s.mtpWarn}>
⚠️ Interleave prefill-CP together with DP-Attention: current SGLang releases assert <code>dp_size == 1</code> for the interleave layout, so this command fails at startup. Combined CP + DP-Attention support is planned upstream — keep one of the two off until it lands.
</div>
)}
</div>
</div>
@@ -114,10 +114,13 @@ sgl-eval run mmmu_pro \\
playgroundFeatures: {
// ----- Attention Parallelism -----
// No CP knob: prefill Context Parallel needs model-side integration in
// SGLang (DeepSeek-family / Qwen-MoE / Mellum have it) and
// MiniMaxM3SparseForCausalLM has none — the engine's CP knob would emit
// --enable-prefill-cp flags that don't work on this model.
attention: {
knobs: [
{ id: "tp", label: "TP", values: [null, 1, 2, 4, 8] },
{ id: "cp", label: "CP", values: [null, 1, 2, 4] },
{ id: "dpAttn", label: "DP-Attention",
values: [null, false, 1, 2, 4, 8],
labels: { "auto": "Auto", "false": "Off" } },
@@ -173,6 +173,12 @@ sgl-eval run aime25 \\
// ----- Card 1: "Attention Parallelism" -----
// DP-Attention is a combined knob: value is the DP degree AND toggles `--enable-dp-attention`.
// CP sizes auto-gate in the engine to the runtime derivation
// attn_cp_size = tp/dp (a user-passed --attn-cp-size is overridden).
// CP is single-machine only (tp_size <= 8). Interleave CP + DP-Attention
// currently fails the runtime's dp_size == 1 assert but is allowed here
// with a warning (combined support is planned upstream). No `cpStrategy`
// knob: DeepSeek-V4 supports only interleave (the runtime rejects zigzag).
attention: {
knobs: [
{ id: "tp", label: "TP", values: [
@@ -184,7 +190,12 @@ sgl-eval run aime25 \\
{ value: 16, disable: { nodes: ["single"] },
disableReason: "TP=16 requires 16 ranks — switch the Deploy panel's Nodes to Multi-Nodes first." },
]},
{ id: "cp", label: "CP", values: [null, 1, 2, 4] },
{ id: "cp", label: "CP",
values: [null, { value: 1, label: "Off" }, 2, 4, 8],
disable: [
{ when: { nodes: ["multi-2"] },
reason: "Prefill Context Parallel is single-machine only (SGLang asserts tp_size <= 8; cross-machine CP has precision issues)." },
] },
{ id: "dpAttn", label: "DP-Attention",
values: [
null,
@@ -120,8 +120,9 @@ sgl-eval run gsm8k \\
// M.1 is global-attention (no SWA); expose TP + DP-Attention here. No CP: the default
// trtllm_mha backend has no CP-aware KV-store (crashes), and the engine's built-in attention CP
// knob emits NSA flags (--enable-nsa-prefill-context-parallel) that apply to DeepSeek-family
// models, not M.1. (CP works only via the fa3 backend, which is Hopper SM90 — left out here.)
// knob emits prefill-CP flags (--enable-prefill-cp / --cp-strategy / --attn-cp-size) that apply
// to DeepSeek-family models, not M.1. (CP works only via the fa3 backend, which is Hopper
// SM90 — left out here.)
// DP-Attention: VERIFIED functionally correct on 8×B200 BF16 (GSM8K 0.94, identical to the TP
// baseline) but ~1528% slower on this GQA model (8 KV heads). Playground experiment only —
// deliberately NOT in the shipped Balanced recipe.
@@ -109,6 +109,10 @@ sgl-eval run aime26 \\
playgroundFeatures: {
// ----- Card 1: "Attention Parallelism" -----
// No CP knob: prefill Context Parallel needs model-side integration in
// SGLang (DeepSeek-family / Qwen-MoE / Mellum have it) and HYV3ForCausalLM
// has none — the engine's CP knob would emit --enable-prefill-cp flags
// that don't work on this model.
attention: {
knobs: [
{ id: "tp", label: "TP", values: [
@@ -120,7 +124,6 @@ sgl-eval run aime26 \\
{ value: 16, disable: { nodes: ["single"] },
disableReason: "TP=16 requires 16 ranks — switch the Deploy panel's Nodes to Multi-Nodes first." },
]},
{ id: "cp", label: "CP", values: [null, 1, 2, 4] },
{ id: "dpAttn", label: "DP-Attention",
values: [
null,
@@ -107,14 +107,39 @@ sgl-eval run aime25 \\
// ----- Card 1: "Attention Parallelism" -----
// DSA prefill Context Parallelism (CP) splits the long-prefill attention across
// `cp` ranks — verified on Hopper (H200). On Blackwell the DSA-CP FP8 rope kernel
// is not yet adapted, so keep CP off there for now.
// `cp` ranks — runs on Hopper (H200) and Blackwell (B200/GB300/B300).
// CP sizes auto-gate in the engine to the runtime derivation
// attn_cp_size = tp/dp (a user-passed --attn-cp-size is overridden).
// CP is single-machine only (tp_size <= 8). Interleave CP + DP-Attention
// currently fails the runtime's dp_size == 1 assert but is allowed here
// with a warning (combined support is planned upstream).
// Strategy knob: interleave (ex round-robin-split) is the layout verified
// here and the default; zigzag (ex in-seq-split) is exposed as an
// experiment — the runtime auto-configures deepep + ep=tp for it and
// restricts it to batch_size=1 (long-context single-request runs).
attention: {
knobs: [
{ id: "tp", label: "TP", values: [null, 4, 8] },
{ id: "cp", label: "CP (DSA prefill)", values: [null, 1, 2, 4, 8],
disable: { hw: ["b200", "gb300", "b300", "mi355x", "mi325x", "mi300x"] },
disableReason: "DSA prefill Context Parallel is verified on Hopper (H200); the Blackwell sm100 DSA-CP FP8 rope kernel is not yet adapted, and the ROCm DSA-CP path is not yet validated on AMD (MI300X/MI325X/MI355X)." },
{ id: "cp", label: "CP (DSA prefill)",
values: [null, { value: 1, label: "Off" }, 4, 8],
disable: [
{ when: { hw: ["mi355x", "mi325x", "mi300x"] },
reason: "The ROCm DSA-CP path is not yet validated on AMD (MI300X/MI325X/MI355X) — keep CP off there for now." },
{ when: { nodes: ["multi-2"] },
reason: "Prefill Context Parallel is single-machine only (SGLang asserts tp_size <= 8; cross-machine CP has precision issues)." },
] },
{ id: "cpStrategy", label: "CP Strategy",
values: [
null,
"interleave",
{ value: "zigzag", label: "zigzag (experimental)" },
],
disable: [
{ when: { hw: ["mi355x", "mi325x", "mi300x"] },
reason: "The ROCm DSA-CP path is not yet validated on AMD (MI300X/MI325X/MI355X) — keep CP off there for now." },
{ when: { nodes: ["multi-2"] },
reason: "Prefill Context Parallel is single-machine only (SGLang asserts tp_size <= 8; cross-machine CP has precision issues)." },
] },
{ id: "dpAttn", label: "DP-Attention",
values: [null, false, 4, 8],
labels: { "auto": "Auto", "false": "Off" } },