Run, resume, or inspect the shared ServingStudio Sim↔framework evidence pipeline for vLLM or SGLang. Its artifacts support both simulator validation through top-align-with-framework and framework optimization through top-compose-real-framework-from-sim.
Resources
1Install
npx skillscat add syfi-servingstudio/servingstudiosim/operate-run-alignment Install via the SkillsCat registry.
Run Alignment
Operate the existing alignment pipeline to produce one reproducible evidence
exchange between ServingStudio Sim and a measured vLLM or SGLang run. Keep every matching
decision in the labeled kernel-sequence JSON; never create a separate mapping
YAML.
Read alignment/README.md and alignment/profiler/README.md first — they own
the phase configs, artifact roots, and commands. This skill covers only the
operator judgment those docs leave open.
Choose the consuming direction
Run the same capture, normalization, labeling, and comparison path in both
directions. Choose the consumer from the user's question before interpreting the
result:
- Align ServingStudio Sim to framework: determine whether the simulator faithfully explains
the measured framework. Hand the completed artifacts totop-align-with-frameworkfor judgment and simulator-side repair routing. - Align framework to ServingStudio Sim: use the simulated target and its measured-only /
simulated-only gaps to locate framework implementation opportunities. Hand
the same artifacts totop-compose-real-framework-from-sim, which owns trial
selection, implementation, controlled baseline/trial measurement, and the
retain/reject decision.
This operate skill produces and checks evidence. It does not decide which side
is wrong, select a framework optimization, or edit either implementation.
Respect the supported boundary
The server pipeline supports one deployment: unified target, one main group,
and one replica. The real engine may be vllm or sglang; its explicit world
size is server.tp_size * server.dp_size and must equal both the visible-device
population and the simulation topology. Use the shared engine_text builder
(vllm_text and sglang_text remain accepted adapter spellings):group_assignment: single for one attention-DP group, or per_dp_rank when
each attention-DP rank schedules its own batch. Stop and report an unsupported
replica, deployment, topology, or input-builder request; never silently reduce
it to TP1 or collapse DP batches.
Run the shared phases
From ServingStudioSim/, run each phase through the launcher (uv run python -m launcher alignment {profile,timing-predict,analyze,sim}; see README for the configs).
Each phase is an explicit checkpoint with a disjoint artifact root; no phase
launches the next. The duty-cycle gpu_time_multiplier is no longer hand-derived
before the simulation — the analyzer's kernel-align pass emitsrecommended_gpu_time_multiplier
(Σ measured_gpu_cycle_ms / Σ measured_ms),
and the simulation phase injects it automatically. So analyze splits into two
semantic passes and runs on both sides of the simulation. Dry-run profile and sim
before the real launch.
Require launcher metadata to declare the producer/artifact kind explicitly
(framework_capture, timing_predict, or simulation). Never infer it from an
incidental file such as raw/run_meta.json. A consumer must reject absent or
unknown producer kinds rather than choosing semantics from directory contents.
When several cases have independent artifact roots, run the same phase for all
ready cases concurrently. Complete shared prerequisites once (build, profile DB,
labels or common configuration), then fan out the cases and collect their exit
statuses separately. Do not serialize independent experiments merely to make
failure attribution easier: their disjoint output roots already provide that
attribution. Respect real resource conflicts, such as a bounded GPU pool or an
exclusive launcher lease, but let the repository's resource manager schedule
those constraints instead of serializing the whole campaign in advance.
Do not hand-drive a multi-case matrix, and do not write a generator script for
one. launcher alignment-campaign already implements the paragraph above:
declare the matrix in a pack under presets/alignment/<pack>/, then
uv run python -m launcher alignment-campaign check --pack <pack>
uv run python -m launcher alignment-campaign render --pack <pack> --host <host> --out-root <root>
uv run python -m launcher alignment-campaign run --pack <pack> --out-root <root> --phase <P>One run invocation is one phase across every ready case, scheduled through
the repository's resource manager, with each case's exit status journaled
separately. Completed case×phase pairs are skipped, so a labeling or analysis
failure never costs a repeated capture; --refresh re-runs one deliberately.
Preview the plan and each excluded case's reason with --dry-run.
There is no --all and you must not build one. The batch axis is one phase over
many cases; chaining phases would delete the explicit checkpoint between them.
Advance the pipeline by issuing the next --phase yourself after inspecting the
previous one, exactly as for a single case.
A pack's cases must span the knobs that change step shapes. Do not copy one
template value across the matrix. At least run prefill-heavy and mixed cases atchunk_size 2048, 4096 and 8192 (as the GLM-5.3 DFlash2 and GLM-5.2 spec5 packs
do), and vary max_num_seqs, max_model_len or the parallel layout where the
deployment allows it. A path no case reaches, such as a step above a fused
kernel's token cap, is unvalidated, and the simulator must price it rather than
refuse it.
Set every vLLM capture's server like a real deployment. Getting these
wrong cost a full recapture:
api_server_count: 4. One API server queues requests at the frontend under
high concurrency and distorts TTFT.max_cudagraph_capture_sizeequals the case'schunk_size. vLLM runs any
step above the cap with no CUDA graph (vllm/v1/cudagraph_dispatcher.py,num_tokens > max_size→CUDAGraphMode.NONE). That includes prefill and
mixed steps, so a 2048 cap makes chunk-4096 steps host-bound.- Pass an explicit
cudagraph_capture_sizes: vLLM's own list up to 2048, then
one size every 128 up to the chunk size. Do not let vLLM fill the range up to
8192 with its default stride of 16. That gives 531 graphs, about 15 extra
minutes of startup per pass, and several GiB less KV pool. Render the same
list into the simulator's archcudagraph_capture_sizes, and check it
against the list the server logs.
Run passes concurrently and warm. Give each Slurm job its own GPU, port
and scratch, and submit them together. Never delay a start with --begin and
never chain passes with afterany; both leave GPUs idle. A new chunk size or
capture list brings new GEMM shapes, so its first pass runs on a cold autotune
cache. Submit one pass per new configuration first, and submit the rest once
its log shows the server is ready. Discard any pass that logs "No tuned config"
or a smaller KV pool, and rerun it. Compare the KV pool by the token count the
server logs, not the rounded GiB, which varies slightly between starts.
launcher/alignment_campaign/README.md owns the pack schema. Reach for the
single-config path below when there is one workload and no matrix.
Profile — instrumented vLLM or SGLang. Preflight the GPU, port, NSYS,
model cache, and fork venv (fork_pythonmust importtorchand the
selected engine).profile_kindselects one of four passes, and they are not
interchangeable — each buys one kind of evidence at the cost of another, so
pick by which check will consume it (seealignment/README.mdfor the
config fields):profile_kindNSYS Buys Costs nsys(default)yes, bounded window per-kernel/segment truth for Check 1 a short window, plus capture/flush pauses; must disable routing logging workload_metricsno the complete scheduler timeline and per-request timing, uncontaminated, for the whole run no kernel detail token_corpusno the experts every accepted token routed to, layer by layer — the measured MoE routing demand its synchronization and D2H logging must never enter timing evidence; the corpus is ~150 MB per capture expert_popularityno the same run reduced to a per-layer marginal the same costs, and a marginal is only resamplable independently — DEPRECATED Use
cuda_profiler_api+ CUDA graph node tracing for thensyspass.Judging E2E (Check 3) from the
nsyspass alone is a common mistake: that
window covers a slice of the run and carries profiler pauses, so a TTFT/TPOT
distribution read off it is neither the whole population nor an undisturbed
one. Runworkload_metricsfor that, with identical model, topology,
backend, and workload settings so the two passes are comparable.Run
token_corpusfor any MoE model, not only an EP deployment. Expert
parallelism is what makes routing skew produce dispatch/combine traffic, but
a grouped GEMM is charged per expert group whatever the placement: skew
leaves the total token-expert selections unchanged while redistributing them
into fuller and emptier groups. A local/TP1 MoE arch that assumes balanced
routing will therefore mis-cost its expert GEMM, and the deviation surfaces
in Check 1 as an unexplained MoE slot gap that is easy to misattribute to the
kernel cost model.Prefer
token_corpusoverexpert_popularitywhenever the deployment
drafts. A marginal can only be resampled independently, and a verify block is
not independent: one sequence contributesdraft_tokens + 1consecutive
positions that route almost alike. Scored against recorded per-step routing,
the marginal misses the busiest rank's active-expert count by 15.15% where a
corpus sampled in groups of the verify width misses by 2.40%, against a floor
of 1.91%. At verify width 1 the two agree, which is whyexpert_popularity
still exists rather than being deleted.Neither pass needs extra flags. The runner appends what the kind cannot work
without — the routed-experts return, and EPLB's per-step expert-load log when
the deployment has expert parallelism — so onetoken_corpuscapture yields
the corpus, that per-step stream to score a sampled fold against, and the
marginal. Whenever the pass takes that stream (vLLM with--enable-expert-paralleland more than one rank) it requiresserver.expert_parallel_sizeandserver.expert_count_reduction_group_size,
the topology the marginal is reduced over. A model that does not implement
EPLB opts out with--no-enable-eplbinserver.extra_argsand the pass
records routes alone.expert_popularityalways requires both, because the
marginal is its only product.Only the
openaiworkload backend returns routes, so atoken_corpuspass
runs onopenaieven when the campaign's timed passes usevllm_tokens;
the routes are a property of the tokens routed, not of the wire protocol.Timing prediction — set the typed input builder. It reads the simulation
preset (simulation.yamlviasimulation_preset) for gpu/arch/backends, so
it runs before any completed simulation.measured_phase: forwardonly
reconstructs input shapes, it does not limit later analysis. Inspect the
emitted CostTree leaf slots before mapping. A cold profile DB may JIT-fill and
need the matching GPU.Preview with
alignment timing-predict <timing_predict.yaml> --dry-run. It
builds the measured cases in a scratch directory, checks each against the
model, and prints theprofile.dbspecs a real run would JIT. It writes
nothing into the bundle. Under--no-gpu,alignment profileis refused
before the engine starts (a--resumestill runs), and a timing-predict with
missing rows fails instead of profiling.Do not manually pre-fill the cache for an alignment campaign. Run
timing-predict or simulation on the real case inputs and let their ordinaryperf_apilookups JIT-fill the exact demanded shapes. Never invent a broad
Cartesian profiling sweep as a prerequisite. If demand-driven execution
leaves misses, useoperate-profile-existing-kernelonly to diagnose or
retry those exact missing shapes, then rerun the owning timing-predict or
simulation stage. This keeps cache population tied to production demand and
prevents speculative axes from dominating the experiment.Label + kernel-align — initialize a labeled inventory with explicit
unmapped decisions, label every stored occurrence per the rules below, and
reference only that file fromiteration.labeled_kernel_sequences_file:uv run python -m alignment label initialize \ logs/<experiment>/profile/kernel_sequences.json \ logs/<experiment>/kernel_sequences_labeled.json uv run python -c 'from pathlib import Path; from launcher.alignment_config import load_labeled_kernel_sequences; load_labeled_kernel_sequences(Path("logs/<experiment>/kernel_sequences_labeled.json"))'Use
initialize --unfoldonly when an implementation-identical repeated
layer boundary and one-off model boundary need different labels. It preserves
the same kernel-align entry point while making each occurrence addressable asliteral-v1; usebefore_nameto state the distinguishing successor.With a pack,
alignment-campaign label --pack <pack> --run-dir <case>does
the initialize-apply-check loop against the pack's stored rules. It applies
them until the label state stops changing rather than a fixed number of
passes —apply_rulescounts an overwrite as applied even when the label is
unchanged, so a fired-count loop would never terminate. Read the reportedunfiredrules: one that matches nothing in any case is dead weight from an
older inventory.The rules are a set, not a sequence: every file the manifest names is loaded
and the union applied, so no label may depend on which rule is tried first.
When you add a rule, narrow its matchers until nothing else claims the same
positions — do not rely on placing it before or after another rule.subsumptions(incheck) proves an order-dependent pair from the rule text
alone;disagreements(here, at the fixpoint) catches the overlaps it cannot
prove but a real capture exhibits.Run
analyzewith onlyiteration.enabled(the kernel-align config, nosimulation_log_dir). The strict analyzer expands the folded inventory
losslessly, validates names/categories againstparsed.jsonand mapped slots
against the CostTree, and writesrecommended_gpu_time_multiplierintoreports/alignment_iteration_report.json. This pass needs no DES simulation;
fix the labeled source on failure, never the analyzer output.Simulation — ordinary ServingStudio Sim preset with explicit worker settings:
uv run python -m launcher alignment sim logs/<experiment>/simulation.yamlKernel alignment is not a prerequisite. If adopting a reported recommendation,
setworker.gpu_time_multiplierin the preset explicitly and record its source
and any cross-workload reuse in experiment notes. Confirm the value baked into
the run'sparams.json; the launcher never reads a report to override it.
The longer wall time can
change batching, TTFT, TPOT, E2E, and throughput. Require a complete artifact
set before continuing.e2e-align, render — run
analyzeagain withworkload/e2eenabled (the
e2e-align config, pointingsimulation_log_dirat the completed sim) and then
render. These subjects consume the sim that already baked in the multiplier.The complete
workload_metricsrun is the authority for real serving
throughput and request latency. Report itsmeasured_client_completion_tps. Bothalignment-workloadandalignment-e2econsume this full run directly and must not name or inspect
the bounded NSYS capture. The profile records req-frontend's host-monotonic
replay window so workload analysis can exclude startup/preflight iterations
independently. NSYS belongs only to the earlier kernel-align phase; never
divide full-run tokens by an NSYS span or disable workload analysis merely
because the kernel capture used a smaller representative trace.
Do not use the phase NVTX envelope as GPU E2E (host submission ranges; graph
kernels run after the marker closes), and do not subtract an iteration's own busy
union from its host span and call the remainder idle or CPU overhead — it can be
queue time occupied by a prior iteration.
Inspect emitted artifacts
Use the artifact that owns the requested granularity; do not infer a
per-physical-kernel error from rows that are joined many-to-one by semantic
operation.
| Question | Artifact and field |
|---|---|
| Per-iteration total error | payloads/alignment_iteration_series.json → iterations[].delta_ms / relative_diff_pct |
| Per-iteration, per-operation error | payloads/alignment_iteration_breakdowns.<sha256>.jsonl.zst → one iteration record's operation_summary[] |
| Measured physical-kernel detail | The same breakdown record's measured_kernels[] |
| Simulated CostTree-leaf detail | The same breakdown record's simulated_kernels[] |
| Directional mapping gaps | The same breakdown record's unmapped_measured_ms / unmapped_simulated_ms |
| Per-stream intervals and reduced occurrences | payloads/alignment_timeline_iterations.<sha256>.jsonl.zst |
| Mapping decisions and unresolved measured rows | kernel_sequences_labeled.json |
| Whole-analysis aggregates and recommended multiplier | reports/alignment_iteration_report.json |
The two .jsonl.zst shards hold one zstd frame per iteration. To read one
iteration, use its byte_ranges[id] from the payload's breakdown_detail oriteration_detail and decode that frame; analyzer/python/common/layout.pyread_sharded_records does this. To read all of them, zstd -dc <shard>
streams the plain JSONL.
operation_summary[] is the direct measured-versus-simulated comparison: it
contains measured_ms, simulated_ms, delta_ms, and relative_diff_pct for
each semantic operation in that iteration. Use measured_kernels[] andsimulated_kernels[] to explain that row, but keep their different
decompositions visible rather than manufacturing a one-to-one kernel join.
The render step may sample iterations for PNG output; the JSON/JSONL payloads
remain the machine-readable authority for every emitted iteration. When an
Analyzer resource for the result is ready, user-visible numerical reporting
must instead follow operate-use-analyzer and its typed-resource citation
contract.
Preserve one raw-evidence layer
Reuse one capture-evidence parser for full serving runs and bounded repetitive
units reached through the same framework entry point. Keep profiler schema,
process/device ownership, correlation joins, interval union, ordered events, and
coarse kernel taxonomy in a shared raw layer. Keep framework scheduling
interpretation and CostTree semantics in separate consumers. Do not introduce a
second command-only capture path or an alignment-only fixed-shape input builder
merely to make a small reproduction convenient. Explicit shapes already belong
to the existing operate-run-timing-predict entry point.
The same evidence must expose both directions:
- measured operations with no simulated owner;
- simulated work with no measured counterpart.
Do not add labels merely to improve coverage. Every mapped or profiled owner
must match the measured production operation's backend specialization, layout,
page contract, numeric contract, and shape. A common operation name does not
make ragged, paged, calibration, or cache-free paths equivalent.
Treat trace-derived workload as evidence, not scheduler policy. Request lengths,
arrival times, and physical kernel inputs may be replayed. Observed DP placement,
equal-shaped adjacency, and profiler-created synthetic chunks must not become
predictive worker constraints. Implement chunked prefill as a generic lifecycle
over the original requests. If an analysis conditions on observed placement or
another realized decision, report that counterfactual separately from the
predictive alignment.
Match measured kernels to simulated slots
Matching is semantic alignment between two decompositions: measured CUDA kernel
occurrences (grouped by engine phase, folded by exact repetition) and named L1 leaf
slots in the timing-predict CostTree. The join key is a stable model operation,
not a demangled kernel name. Weigh evidence in this order:
- Phase —
preprocess,forward,postprocess, orsample. - Track — one concurrent CUDA stream. A sequence holds
tracks[]; track 0
is the one that opened the range, the rest are side streams the framework
overlapped onto the same device (vLLM runs the Qwen3-Next shared expert this
way during decode, and inline on the main stream during prefill). - Folded position — prefix/repeat-body/suffix within a track; a repeat
body matching the layer count is one transformer layer, prefix/suffix is
model-level work. - Ordered neighbors — the surrounding norm/proj/attn/act/proj pattern.
- Kernel semantics — what it computes;
suggested_categoryis a hint only. - Slot contract — the candidate
simulated_slotsjointly own the same work. - Cross-sequence consistency — the same position across prefill/mixed/decode
gets compatible labels.
One name may sit at different positions and one operation may launch several
kernels, so never map a name globally. Before editing, reason one row per folded
occurrence (phase, track, folded path, name + category, inferred operation,
evidence, slots, decision) and resolve every unresolved row.
Never reason across a track boundary. after / after_name / before_name
stop there by construction, because two tracks ran at the same time and have no
"before" between them — the neighbour on the other side of the edge is not
evidence about anything. Expect the same operation to sit on the main stream in
one sequence and a side stream in another; that is a scheduling fact about the
framework, not a reason to label it differently.
Make mapping decisions
Give every stored occurrence — including inside a repeat body, where the label
applies to every expansion — exactly one nested label: {"status": "unmapped"}, or a mapped label with operation, type, role, and a non-emptysimulated_slots (see README for the shape). Keep the measured name andsuggested_category everywhere; introduce no kernel IDs or catalog.
- Map exact ownership. Map when the occurrence computes all or a defined
component of one operation and the chosen slots jointly own that work. - Many-to-one is fine. Cache update, attention mainloop, and combine may share
one operation/slot when the CostTree models them as one leaf. - One-to-many follows CostTree attribution. The analyzer counts the measured
duration once and attributes selected leaves throughSum/Scale/Max;
parallel siblings contribute only through the critical child, while raw
folded workload remains a mapping-coverage audit. If the slots are separate
operations or need an unjustified split, leave the row unresolved. - Keep helpers explicit. Mark bookkeeping, alloc/fill/copy, launch prep, and
samplingunmapped; never attach them to a nearby op for coverage. - Keep simulator-only work visible. A simulated leaf with no measured
counterpart stays an unmapped slot; never invent a zero-duration kernel. - Stay consistent. One operation keeps the same
type,role, and orderedsimulated_slots. A slot may be shared by several operations (e.g. a fused vs.
unfused all-reduce boundary owning the sametp_allreduceslot); the analyzer
resolves the per-iteration owner from the operations actually present.
When reusing labels from an earlier capture, strip only label and provenance
from both inventories and require exact equality of the remaining
phase/sequence/fold/name/category structure before transferring; otherwise review
every changed position and never fuzzy-match.
Reduce logical occurrences, not raw rows
Identify one logical occurrence by phase and operation ordinal across folded
sequences and devices, never by a raw row ID. Preserve the ordered raw inventory
for audit, then reduce durations according to synchronization semantics:
- independent replicated compute contributes the critical-rank duration;
- a synchronizing collective uses arrival-to-completion semantics while keeping
arrival wait separate from kernel cost; - never sum the same replicated logical occurrence across ranks in a stacked
critical path.
unmapped and cross-rank synchronization are independent labels. An unmapped
collective may still require synchronizing reduction.
Produce an auditable unmapped inventory grouped by semantic family and logical
critical-path contribution. Classify with phase, folded position, ordered
neighbors, semantics, and tensor/collective ownership — never a remembered
ordinal or demangled name alone. Alignment reports facts and candidate owners;
it does not select the next framework optimization.
For multi-device, multi-stream timing, the analyzer's barrier critical path
composes each device's complete busy time per segment (the stretch between two
collectives) first: mapped work, unmapped work, and stream overlap share the
same device timeline. Each segment's winner is chosen only after that
composition, so the owner may change between segments. Never take separate
maxima for mapped, unmapped, and overlap components and splice them into a
segment no device executed. A collective enters only as its net (last arrival to
last exit); early-rank arrival wait is collective_skew_ms, reported off the
path, not kernel duration.
Measured time taxonomy
Every quantity below is per iteration and computed in exact integer nanoseconds.
Each iteration row of reports/alignment_iteration_report.json and each
timeline measured block carry these fields. The identities hold with a residual
of exactly 0, and the analyzer refuses to emit an iteration whose tiling does
not close.
measured_gpu_cycle_ms = wall_ms + inter-iteration gap (gap may be < 0)
wall_ms = measured_ms + idle_internal_ms + idle_boundary_ms
measured_ms = critical_busy_ms + collective_ms (barrier critical path)
critical_busy_ms = measured_kernel_sum_ms
- hidden_same_stream_ms - hidden_cross_stream_ms
recommended_gpu_time_multiplier = Σ measured_gpu_cycle_ms / Σ measured_msThe critical path is therefore not the busy time. It contains collective time
that no compute kernel covers, and it excludes idle time: idle_internal_ms is
outside critical_busy_ms.
Structure:
- Barrier. A maximal run of consecutive synchronizing (
cross_rank)
positions.- enter: the latest first start over ranks (the last arrival).
- exit: the latest last end over ranks.
- Segment. The stretch before, between, or after barriers, with bounds taken
over all ranks. Segments and barrier nets tile the wall window. - Winner. The rank with the most busy time in a segment, which is the same as
the least idle time; the lowest device id wins ties. The owner may change from
segment to segment, andcritical_rank_switchescounts the changes. - Gating rank. For each timeline segment,
gating_device_idis the last
arriver at the closing barrier (or, in the tail segment, the last finisher).gating_gap_nsis that rank's own non-busy time. It is a diagnostic and often
differs from the winner.
Terms of the identities:
| Field | Definition |
|---|---|
measured_ms |
The barrier critical path: critical_busy_ms + collective_ms. It is the denominator of the multiplier and the headline of Check 1. |
critical_busy_ms |
Σ over segments of the winner's busy union (its non-synchronizing launches, clipped to the segment). critical_busy_ms_by_device splits it by winner. |
collective_ms |
Σ over barriers of net = exit − max(enter, previous exit): from the last arrival to the last exit. Compute that overlaps the collective stays inside this term. |
collective_skew_ms |
Σ over barriers of (last arrival − first arrival): early ranks waiting. It is off the path and not additive, because it overlaps the slow rank's busy time. This is the "network wait" that does not lengthen the iteration. |
idle_internal_ms |
The winner's gaps between its first and last busy instant in each segment, for example host launch bubbles. |
idle_boundary_ms |
The winner's head and tail in each segment: waiting at the barrier edges. |
wall_ms |
The last kernel end minus the first kernel start, over every rank. |
measured_gpu_cycle_ms |
The next valid iteration's first-kernel start minus this iteration's first-kernel start. The final valid iteration has no cycle. cycle − wall is the inter-iteration gap, which is negative when iterations overlap. |
measured_kernel_sum_ms |
On-path kernel sum: Σ of the winners' clipped launch durations before overlap is removed. |
hidden_same_stream_ms |
Part of the on-path kernel sum covered by an earlier launch on the same stream (PDL). This can be griddepcontrol.wait residency, i.e. launch latency hidden rather than compute overlapped. |
hidden_cross_stream_ms |
Part covered by a launch on another stream (multi-stream overlap). Each hidden stretch is charged to the launch holding the running maximum end, bucketed by that launch's stream (stream_id, else track). |
hidden_under_collective_{same,cross}_stream_ms |
Non-synchronizing launch time inside barrier windows on the last-exit rank. It is already inside collective_ms, so it is reported and never subtracted. Together with the two segment buckets this forms a 2×2 grid: same/cross stream × hidden under compute/collective. |
pdl_wait_under_collective_ms |
Every rank's non-synchronizing launch time that started inside a collective on the same stream and before that collective ended: PDL griddepcontrol.wait residency behind the all-reduce. Each such launch's start is trimmed to the collective's end (a fully covered launch is dropped) before the segment races, so the wait is never busy and cannot make an early rank win a segment. Off the path and not additive, like collective_skew_ms. Cross-stream overlap is not trimmed. hidden_under_collective_* and the per-kernel overlap diagnostic still use the untrimmed launches. |
all_rank_kernel_sum_ms |
Every non-synchronizing launch on every rank. It gives scale only and is not part of any identity. |
Per operation and per kernel:
operation_measured_mssums tomeasured_msexactly. Each operation gets
the winners' sweep slices plus each barrier's net split across the barrier's
operations by per-operation extent. The integer remainder goes to the last
operation. The kernel rowduration_ms(timelinecrit_ms) is one
position's share.on_path_kernel_ms(timelinepath_ms) is its winners'
clipped durations before overlap.reduced_duration_msis the per-occurrence
cross-rank reduction, kept for audit.- The per-kernel overlap diagnostic is computed per device over all
launches, collectives included, and does not depend on barriers.- The report kernel row's
overlapgivesduration_ms,overlap_msandoverlap_pct(the intersection with the union of every other launch), plussame_stream_msandcross_stream_ms. - Timeline
ov[i], aligned toiv[i], is[overlap_ns, same_stream_ns, cross_stream_ns, stream_id, partners].partnersis[position, overlap_ns], and the position resolves to the name, operation, sync flag,
and stream. - PDL onto a collective is still reported, with the collective as the partner.
- An overlapping pair counts on both sides, so Σ
overlap_msis not the
hidden total. Againstalignment/nsys/overlap.pyon the same launches:
Σsame_stream_ns=pdl_same_stream_trace_reduction_ns+ the time the
stream is at depth ≥ 2.
- The report kernel row's
Stream plots use the same reduced logical work as the numerical report, not raw
residency intervals. Show material streams separately and aggregate small
streams into a lossless remainder whose total is explicit; aggregation must not
drop work or inflate the critical path.
Preserve routing and workload equivalence
For MoE baseline reproduction, capture logical routing outside the timed range —
the profile_kind: token_corpus pass above — and inject that same demand into
the simulator with routing: corpus and the arch's token_corpus_file, orrouting: popularity and expert_popularity_file when only a marginal exists.
Preserve original layer and step/request identity, token count, top-k, logical
expert counts, route-weight mass, and temporal variation. A single model-wide
histogram erases layer skew and bursts and cannot support per-iteration
critical-path alignment.
If the arch has neither field, it is costing a hardcoded balanced assumption.
That is a gap to report, not a reason to skip the pass: an arch whose grouped
GEMM reads a routing distribution can consume a measured one, and adding the
selector field is a small, patterned change. Seeoperate-run-simulation/references/moe-routing.md for the four routing kinds
and for hosting a corpus by repository and revision instead of in git.
Keep logical demand separate from framework realization. The reusable input is
the token-to-expert assignment and route weight before EP placement, padding,
duplicated sends, local permutation, and collective wait. Use realized rows and
bytes as validation evidence, not simulator demand; otherwise the framework's
inefficiency becomes a simulated requirement.
Communication-sensitive analysis also needs joint structure: source/destination
owner unique-token counts, owner-hit multiplicity, and expert co-occurrence.
Marginal expert counts size grouped compute but cannot determine dispatch
duplication or bottleneck message bytes.
Version the routing artifact with model/router/checkpoint revision,
token/request digest, sampling policy, expert namespace, observation window,
and schema. Store logical expert IDs independently from placement so target
exploration can remap experts. Validate end-to-end consumption: assignment and
weight totals, post-placement per-layer rank distributions, padding/drop policy,
generated kernel shapes, and simulated message sizes. Reading the artifact or
setting a config field is not proof that the CostTree consumed it.
Preserve speculative acceptance structure
Acceptance differs from request to request, and within one request it changes
with output position. Do not treat a case-wide chain as the acceptance of every
request. A pack's speculative_acceptance is one per-position conditional chain
computed from the case's aggregate counters (spec_decode_metrics.json), and
render writes that same chain into every trace row's accept_rate. Aggregated
this way, the chain is token-weighted. Totals come out right: e2e mean,
throughput, and iteration count. But short requests get the acceptance of long
ones.
GLM-5.3 DFlash2 on the enwik9 workload showed how large the spread is. Tokens
emitted per verify round rose with output position, and every output-length
class followed the same curve:
| output position | tokens per round |
|---|---|
| first 128 tokens | about 2.6 |
| up to 1024 | 3.2 |
| up to 3072 | 4.8 |
| up to 4096 | 5.7 |
The case chain gave every request about 4.0. Case 07 matched on e2e (-1.1%)
and TTFT (+1.7%), yet its request-weighted mean TPOT read -26%. The 96
short-output requests decoded 30-40% fast in the sim, while the long-output
requests matched within 5%.
Before interpreting a TPOT gap in a speculative case:
- Split measured acceptance by output-length class and by output position from
the workload pass:decode_request_progress[]carries per-stepaccepted_draft_tokens,emitted_tokens, andoutput_tokens_before. Put it
beside the sim's per-requestspeculative_progress(decode_rounds,emitted_tokensinrequest_slo.parquet). - Compare per class. Separate the rounds-per-request gap (acceptance) from the
time-per-round gap (step cost). They have different owners. - A mixed-length case whose totals agree while mean TPOT does not usually has
acceptance spread, not a cost-model error. Look for preemption, and for
step-cost gaps concentrated in mixed steps, before blaming acceptance.
The trace format already carries a chain per row. Filling each row with that
request's measured chain conditions on the realized run, so it is an
observed-conditioned counterfactual (predictive_alignment: false). Report it
next to the predictive result, never instead of it. The predictive fix is the
pack's speculative_acceptance_by_output_position: pooled per-position buckets
from the workload pass, which render folds into one chain per request from itsoutput_len. It took case 07's TPOT from -26% to -11% and case 08's from -24%
to -6%.
Match the engine's KV admission
When a case is KV-bound, TTFT and TPOT depend on who waits and who gets
preempted. That is a scheduler policy, and the sim has to use the engine's.
The worker default, full-footprint, reserves every request's whole output at
admission and never preempts. vLLM reserves nothing past the next step. It
admits a request once its prompt fits, allocates each running request's next
window first, and on a shortfall running.pop()s the most recently admitted
request and prepends it to the waiting queue. SGLang's retraction removes the
request with the fewest emitted tokens and requeues it behind the waiting ones.
Count preemptions in the workload pass before judging a TTFT or TPOT gap.
When every running request fits the token budget, a request is scheduled on
every step, so a gap in its decode_request_progress rounds is a preemption.
GLM-5.3 DFlash2 case 05 (c64, long outputs) preempted 72 of 176 requests. Under
full footprint the sim never preempted and read TTFT +38% and TPOT -48%. Withkv_admission_policy: bounded-future and decode_retraction_policy: fcfs
(kv_max_future_tokens = verify width, both ratios 0) it read +5.5% and
-11.5%. length (the SGLang policy) overshot to TTFT -55% and TPOT +93%.
Verify completion
The experiment is complete only when: analysis/alignment_manifest.json uses the
current schema and points at the snapshotted labeled inventory; no separate
mapping file exists; iteration metadata lists every captured phase; payloads
carry a phase_summary and per-kernel phase; mapped operations include
model-level suffix work, not just the repeated body; coverage keeps both unmapped
measured kernels and unmapped simulated slots; at least one prefill/mixed and one
decode plot are visually checked for phase boundaries and operation arrows; and
E2E pairing has no unexplained missing requests.
alignment-campaign extract enforces the mechanizable half of that list and
refuses to emit metrics for a case that fails it, so run it before reporting.
It also reads every number below out of the reports by a fixed formula table —
never transcribe a metric by hand, in a report or a results table. --pack
is optional here: --runs <one-off run dir> works with no pack at all.
uv run python -m launcher alignment-campaign extract --runs <dir> --out /tmp/metrics.json
uv run python -m launcher alignment-campaign compare --measured /tmp/metrics.jsonWith --pack, compare additionally judges each metric against that pack's
declared tolerances (non-zero exit on FAIL) and warns on drift from the recorded
golden; --markdown writes the human-readable matrix.
Report the experiment dir, exact commands, request/iteration counts, captured
phases, mapping coverage, total iteration error, E2E latency/throughput error,
unmapped gaps, intended consuming direction, and links to the labeled inventory,
reports, and plots.
Resume and failure policy
Resume from the last verified artifact root; never rerun the expensive GPU
profile because a later label or analyzer step failed. If an artifact disagrees
with the current schema, prefer regenerating the owning phase; migrate metadata
only when the old and new fields are provably identical, and record it. Never
alter measured timings, kernel identities, request results, or CostTree values to
make validation pass.