From 1b9f228838b325455c1661722e0dd4e5031dc541 Mon Sep 17 00:00:00 2001 From: zijiexia <37504505+zijiexia@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:07:34 -0700 Subject: [PATCH] [Docs] Playground: migrate CP knob to canonical prefill-CP flags, align gating with runtime semantics (#31411) --- .../references/authoring-reference.md | 32 +++ .../cookbook/autoregressive/Tencent/Hy3.mdx | 2 +- docs_new/src/snippets/_playground.jsx | 272 +++++++++++++++--- .../snippets/configs/MiniMaxAI/minimax-m3.jsx | 5 +- .../configs/deepseek-ai/deepseek-v4.jsx | 13 +- .../snippets/configs/poolside/laguna-m1.jsx | 5 +- docs_new/src/snippets/configs/tencent/hy3.jsx | 5 +- .../src/snippets/configs/zai-org/glm-5.2.jsx | 35 ++- 8 files changed, 326 insertions(+), 43 deletions(-) diff --git a/.claude/skills/cookbook-add-model/references/authoring-reference.md b/.claude/skills/cookbook-add-model/references/authoring-reference.md index d4474d685..d2eedd6f8 100644 --- a/.claude/skills/cookbook-add-model/references/authoring-reference.md +++ b/.claude/skills/cookbook-add-model/references/authoring-reference.md @@ -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 diff --git a/docs_new/cookbook/autoregressive/Tencent/Hy3.mdx b/docs_new/cookbook/autoregressive/Tencent/Hy3.mdx index 945fe8bf7..859c0f79b 100644 --- a/docs_new/cookbook/autoregressive/Tencent/Hy3.mdx +++ b/docs_new/cookbook/autoregressive/Tencent/Hy3.mdx @@ -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. diff --git a/docs_new/src/snippets/_playground.jsx b/docs_new/src/snippets/_playground.jsx index 8d4fb4ea7..098b09d5b 100644 --- a/docs_new/src/snippets/_playground.jsx +++ b/docs_new/src/snippets/_playground.jsx @@ -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,8 +133,20 @@ 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") { - disabled = matchConstraint(base, entry.disable); + 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, @@ -139,7 +154,7 @@ export const Playground = ({ config }) => { 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 (