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 (
Attention - {knobs.map((knob) => ( - - {knob.label || knob.id.toUpperCase()} - {renderSelect(knobDisplay(knob), knob.values || [null], - (nv) => setKnob(knob.id, nv), base, labelFor(knob), - { hideValues: hideNullFor(knob) })} - - ))} + {knobs.map((knob) => { + const kc = h.evaluateChip(knob, base); + if (kc.hidden) return null; + return ( + + {knob.label || knob.id.toUpperCase()} + {renderSelect(knobDisplay(knob), entriesFor(knob), + (nv) => setKnob(knob.id, nv), base, labelFor(knob), + { hideValues: hideNullFor(knob), + disabled: kc.disabled, + disabledReason: kc.disableReason })} + + ); + })}
); @@ -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 (