Use as the top entry point when the user wants to add a whole new model architecture to ServingStudio Sim end to end — explore it, split its forward into kernels, categorize every element into L1/L2/L3/L4, build the kernels, ops, worklets, and model_arch, then add an independent model.work necessary-work label. The one criterion running through the timing layers is the timing-prediction wiring — where each element's timing comes from: L1 is the only layer that MEASURES a number (profile.db); L2/L3/L4 only COMPOSE measured L1 timings. Adding an arch is paired with alignment: capture a real vLLM/SGLang run first and let the measured kernel inventory — not source-reading guesswork — decide which kernels to add, largest first. Skipping the capture requires the user's explicit confirmation. This is a routing/sequencing skill; it delegates each phase to a lower skill and does not implement leaves itself. Landing the finished model on main is dev-finalize-model-addition.
Install
npx skillscat add syfi-servingstudio/servingstudiosim/top-add-new-arch Install via the SkillsCat registry.
Top Add New Arch
Top-level umbrella orchestrator for standing up a brand-new model architecture in
ServingStudio Sim end to end. You sequence lower skills and build bottom-up. Do not implement
kernels, ops, worklets, or the arch file from here — each phase routes to a
dedicated skill.
The one criterion: where does the timing come from?
Every element you create answers a single question — is its timing measured
or composed? That answer is its layer.
- Measured → L1 kernel. A real GPU launch you benchmark into
profile.db
and look up. L1 is the only layer that produces a number; it is the timing
source of truth. Theelementwisebyte-placeholder is still L1 (a measured
memory-bound floor), just approximate. - Composed → L2 / L3 / L4. Everything above L1 predicts timing by wiring
measured L1 timings together — no new numbers, only composition. Which layer
depends on the kind of composition:- L2 op — sum a fixed, 0-overlap kernel sequence, plus op-specific
shape normalization (model shape → kernel sweep coordinates). - L3 worklet — compose ops across one sync section: partition
derivation + cross-op overlap math (sum/max/pipeline). - L4 arch — compose worklets: rank fan-out (
max), layer stitching
(Scale), andWorkletGroupboundaries.
- L2 op — sum a fixed, 0-overlap kernel sequence, plus op-specific
This is both the categorization rule and the through-line of the whole build:
build bottom-up, and at each element ask "measure it, or wire it from below?"
Modeling rules
Check every design choice, and every subagent brief, against these rules.
- Build the simplest model that matches the measurements. Before adding a
mechanism that mirrors a framework rule, measure its effect where it applies
(for example small batches, not only large ones). If the effect is within
repeat-measurement noise, leave the mechanism out. - Set no artificial limits. Never cap batch size, tokens per step, or
context below what the framework allows. That means no hardcoded token cap
and no refusal of a step the framework would run. When the framework switches
path above a threshold (for example an unfused all-reduce past a fused
kernel's token cap), price that path instead. - Align across deployment settings, not one template value. The alignment
cases must span chunk size, concurrency, and context length;operate-run-alignmentlists what a pack must cover. - Key kernel rows on model geometry and dtypes only. A deployment setting
(max_num_seqs, a CUDA-graph capture bound,max_model_len) enters a row key
only with measured evidence that it changes kernel time. Otherwise every new
deployment needs a new set of rows. - Use generic grids over the full deployment range. Never pick grid points
from the shapes one workload happened to produce. - Decide kernels and paths from evidence. Cite the framework source by
file and line and confirm it in a capture. Profile rows on the same framework
build the serving run used. - Keep shared code generic and model code config-derived. Put no model
names in kernel kinds or shared layers, and derive every dimension from the
checkpoint config. After a shared change, confirm that other models' runs are
unchanged per iteration. - Report honestly. Give every out-of-tolerance result a cause you checked.
When a later measurement contradicts an earlier claim, correct the claim
explicitly.
Phase 0 — Capture the real kernel sequence first
Run this before Phase 1. Nearly every architecture worth adding already runs
in vLLM or SGLang, so the kernels you are about to model can be watched instead
of inferred. A capture answers two questions no amount of source reading answers
reliably: which math is really one fused launch, and which launches are big
enough to matter. Without them, every kernel gets the same effort and the
fusion guesses stay untested until Phase 5.
It can run this early because the profile phase stands alone — it needs only the
real engine running the checkpoint, not any ServingStudio Sim code. (Only the
later timing-predict phase reads a simulation.yaml preset, and that preset
needs the arch you have not written yet.)
- How. Route to
operate-run-alignmentand run only its profile phase,profile_kind: nsys. Stop there: the label and analyze phases compare against
a CostTree you do not have yet. They run in Phase 5, on this same capture. - MoE models get a second pass,
profile_kind: token_corpus. Routing skew
redistributes tokens into fuller and emptier expert groups, which changes the
grouped-GEMM cost, and the arch you are about to write needs atoken_corpus_filefield to consume the measurement. Discovering that after L4
is a rewrite, not an addition. (expert_popularityreduces the same run to a
per-layer marginal and is deprecated: it is only correct where the batch is
independent, i.e. no speculative decoding.) - What it gives you. The folded measured kernel sequence per engine phase —
real launch granularity, plus each kernel's share of iteration time. That share
is the priority order for the entire build. And Phase 5 reuses the same capture,
so this costs one GPU profile, not two. - What it does not give you. Semantics. A demangled kernel name and its
suggested_categoryare hints, not proof —top-align-with-frameworktreats
every apparent gap as guilty until proven. The capture tells you where the
launch edges are and which ones are large;dev-lookup-transformers-model
still tells you what each one computes.
Missing prerequisites — weights not yet downloaded, an engine environment not
yet installed, a GPU allocation still queued — are setup work for this phase,
not reasons to skip it.
Skipping the capture requires the user's explicit confirmation. If it truly
cannot run, ask the user, stating the blocker and what skipping costs; do not
decide it yourself. With that confirmation, Phase 1's source-reading path still
works, but mark every granularity verdict inferred, unverified in the decision
table — Phase 5 becomes the first and only place those guesses are tested, and a
fusion edge found wrong there is a rebuild of L1/L2, not a retune.
Phase 1 — Explore & split against the capture
Route to top-split-model-into-kernels (which first calls top-explore-models
to establish the architecture), and hand it the Phase 0 capture. Output: the
per-op decision table — each op is reuse an existing kernel / new dedicated
kernel / elementwise placeholder / fold into a neighbor. That table is
your L1 vocabulary and your worklist for the build.
With a capture in hand the split changes character:
- The measured sequence answers "one fused launch or several?" directly, so
dev-explore-kernelnarrows to the question it is actually good at: which
public callable to wrap and howKernelArgsmap onto it — the wrapper plantop-add-kernelneeds. Do not re-derive granularity from source when the trace
already shows it. - The table carries the evidence: two extra columns for the measured
kernel(s) (engine phase / track / folded position) and each op's share of
iteration time. - Check completeness both ways. Every measured kernel with material duration
lands in some row, or is named explicitly as framework plumbing (bookkeeping,
alloc/fill/copy, launch prep, sampling). Every row has a measured counterpart,
or states why it has none — simulator-only work, or a fold whose cost already
sits inside its host kernel. - Sort the table by measured share, descending. That order is the build
order.
Phase 2 — Build bottom-up (the timing wiring)
Build in strict bottom-up order; each layer only wires the layer below.
L1 — kernels (get the timing), largest first. For each new dedicated
kernel verdict, runtop-add-kernel(Python profiling + Rust timing/cache).
Reuse / elementwise / fold verdicts need no new L1. Done whenprofile.dbhas
the rows and the Rust cache resolves. Scale each kernel's cache-fidelity
effort to its measured share (impl-validate-kernel-cache).Work the Phase 1 table top-down, biggest measured share first. Ops you do not
build keep their Phase 1 home, anelementwiseplaceholder or a fold, so L1
can stop before the end of the table without leaving a gap.Not forward order. Iteration time is dominated by a handful of kernels, so error
retires fastest at the top of the table: a placeholder standing in for a 0.5% op
costs you 0.5%, while a mismodeled attention or expert GEMM costs you the model.
Building embed → norm → QKV → … in forward order spends the expensivetop-add-kernelbudget on ops that cannot move the number.Stop rule: choose the set to build from the measured shares before starting,
and leave the rest as placeholders. L1 is done when every kernel in the set hasprofile.dbrows and a resolving Rust cache; only then move to L2. Do not
start L2 while L1 work is still open. After Phase 5, its comparison ranks which
placeholder to promote next; that starts a new L1 round, followed by the L2–L4
rewiring it needs.The stop rule picks which kernels to build, not the order you build them in.
Hand the chosen set totop-add-kerneltogether and let it fan out — see its
"Several kernels at once".L2 — ops (sum + normalize). Compose kernels into named ops: atomic
(Op<K>, one kernel, naming only) or compound (hand-written, multi-kernel +
op normalization). Boundary — a fixed 0-overlap sequence; comm merges into the
op only if it shares the op's algorithm state, else it stays a separate atomic
op. Boundary examples:lm_head=Op<SingleGemmKernel>— one kernel, atomic, naming only.FlashInferAttentionOpfolds the prefill / append / decode sub-kernels under
one attention three-formula → one compound op.- MoE dispatch's inter + intra all-to-all merge into one op because they
share the routing math (shared algorithm state). - a TP all-reduce stays its own
Op<AllReduceKernel>— a parallelism
artifact with no shared state, so it does not merge into the compute op.
Skill
impl-compose-op
(contract:simulator/src/op/README.md; design.md is intent only).L3 — worklets (partition + overlap). Compose ops into sync-section
worklets on a declared GPU group (Local/TP/HP/EP/…): partition
derivation, inclusion conditionals, and cross-op overlap math. Boundary — one
sync section (ends at a collective, or at single-GPU self-completion).
Boundary examples:FfnDenseTPWorkletsums up_gate → activation → down ops, then a terminatingtp_allreduceincluded only whentp_size > 1— the sync section ends at
that collective.FlashInferAttnHPWorkletputs the attention compound op in a slot, derives
the GQA head split (HP partition), and is fanned outnum_hp_groupstimes by
L4.- the line against L2:
RingAttnWorkletis a worklet, not an op, because
its K rounds overlap compute and comm (pipeline) — overlap can only be
expressed at L3.
Skill
impl-compose-worklet(contract:simulator/src/worklet/README.md).L4 — arch (fan-out + stitch). Wire the chosen worklets into the
model_archcost file: prologue / per-layer / epilogue groups, multi-rank
fan-out, andWorkletGroupboundaries. It computes no partition itself.
The arch also declares one request's KV state part by part
(kv_cache_layout: per-token, sliding-window, per-request), which sizes
capacity, admission and prefix hits; check it against the Phase 0 capture's
logged vLLM KV concurrency.
Skillimpl-compose-arch(contract:simulator/src/arch/README.md).
Phase 3 — Wire (make it selectable & predictable)
The L4 step produces an arch model that builds in isolation; it is not yet
reachable from a config or the predictor. This phase does the cross-file
integration that turns it into a selectable, predictable arch — and paves the road
for a future worker. Route to impl-wire-new-arch. It connects the new arch to
the shared dispatch sites:
arch/build.rs— a concrete builder + its predictor arm inbuild_iter_model/build_attn_model/build_ffn_model(one uniform seam per
kind, each boxed). This is what the offline predictor calls;timing_predict.rs
needs no per-arch edit.deployment/{unified,pd,afd}.rs— the worker-pairing arm, or an explicitbail!"worker not wired yet" so future worker dev is a drop-in.
Uniform rule: one predictor arm in arch/build.rs + one deployment arm, for
every arch kind.
Most sites are compiler-forced (exhaustive matches) — adding the selector
variant breaks the build until each arm exists, so the compiler is the checklist.
Done when timing-predict runs green on the new arch (the Phase 5 proof). This is
the point where "where does the timing come from?" becomes observable — the
composed L1→L4 cost is now evaluable without a worker or a DES run.
Phase 4 — Add the independent necessary-work label
Route to impl-add-model-work-label after the L4 arch and its exact location
names are stable. This phase gives Optimality an independent denominator for R6
necessary-work coverage and R7 redundancy:
- derive the model's compulsory FLOPs/bytes from the true architecture and
workload, never from the simulator's timing tree; - compose/register the
model.workbuilder and add hand-derived parameter/FLOP
goldens; - map stable semantic work rows to every exact non-communication CostTree
location for each supported deployment.
The architecture evidence comes from Phase 1; the location names come from the
finished Phase 2/3 wiring. Those inputs have different roles and must not be
mixed: simulator shapes may identify where semantic work lands, but never define
how much minimum work exists.
Phase 5 — Validate
Confirm the wired arch predicts sane timing before standing up a full
deployment — you do not need a workload / pools / trace to check the cost model.
First, the offline predictor — route to
operate-run-timing-predict. It
costs the new arch's compiledCostTreeover a handful of explicit batch shapes
(prefill-only, decode-only, mixed) with no scheduler/clock. Pick the selector
that matches the arch (iterfor a whole-iteration arch;attn/ffnfor an
AFD half), give a few representative cases, and readreports/iter_breakdown.ans:
every element from the Phase 1 table should appear as a leaf with a non-zero,
plausibly-scaled timing (no missing kernel, no absurd µs). This is the tightest
loop for catching a mis-wired L2/L3/L4 composition.Then close the loop on the Phase 0 capture — the arch now exists, so the
alignment phases that were blocked in Phase 0 can run. Resume that experiment inoperate-run-alignment:timing-predict→ label →analyze(kernel-align),
then hand the artifacts totop-align-with-frameworkfor Check 1, the
per-kernel comparison against measured. Never re-run the GPU profile — the
capture is still valid; only the simulated side changed.This is what turns Phase 0's importance ranking into an error ranking, and
it feeds straight back into the L1 stop rule: the largest remaining deviation
names the next placeholder to promote. Its two structural findings map onto
Phase 1 verdicts — a large simulated slot with no measured kernel usually means
a fold verdict was wrong, and a large unmapped measured kernel usually means
the split missed an op.Then a real run — once the per-building-block numbers look right, graduate to
operate-run-simulation: build +--dry-runto confirm the expanded plan, then
launch a small workload and sanity-check end-to-end throughput. This is also what
the alignment run's duty-cycle and TTFT/TPOT checks need, so if you want Checks
2–3 they follow here, not above.
Delegation boundary
You are the umbrella, not an implementer. Issue each phase to the named skill and
verify its closeout before moving up a layer. In single-agent mode, to run the
phases with parallel subagents (capture beside the build, one lane per kernel
family and per worklet), follow dev-parallelize-arch-addition. Stop and return
to the user between phases whenever a design decision surfaces — a new args schema, a boundary call,
or a deviation from the design docs — rather than deciding it silently. The build
is complete only when every element from the Phase 1 table has a home at its
correct layer, the arch is wired (Phase 3), model.work support and the intended
deployment maps are complete (Phase 4), and the Phase 5 checks pass — including,
unless the user explicitly declined Phase 0, a Check 1 comparison with no
missing large kernel and every material deviation attributed. To publish and
merge the finished model, continue with dev-finalize-model-addition.