Anchors and calibration#
One-pager
What: an anchor is a saved artifact from one measured run that grounds every later projection in real hardware. Why: measuring is the expensive part, so you measure once per execution regime and transport analytically from there. Cost: one short serving run on one ROCm host, reused indefinitely.
Calibration is what makes a projection trustworthy, which is why measuring is the default. The mechanism that keeps that affordable is the anchor: a measured benchmark artifact indexed by a regime signature, which the analytical projector and the DES both honour.
Harvest one per regime and every later projection in that regime is GPU-free
while still being calibrated. This is the intended steady state — not the
uncalibrated simulate path, and not a GPU per question.
Harvest an anchor#
Harvest once on any ROCm host with a serving engine. --model is the checkpoint
to serve, so it is a Hugging Face id or a local path rather than a preset name:
inferasim anchor --model openai/gpt-oss-120b --benchmark-gpus 1 --save anchor.json
--serving-backend {vllm,sglang,atom} picks the engine, which is launched
through the same adapters the platform serves with — so the anchor describes the
engine as deployed, rather than as a benchmark harness happened to start it. The
anchor covers one engine: routing across replicas is simulated, not measured, so
measuring through the router would count that layer twice.
Then project any recipe from it, with no GPU:
inferasim inference ... --load-benchmark anchor.json
Measuring and projecting in one step#
To skip saving an anchor first, ask the projection itself to measure. This is the same harness, driven from the config:
inferasim inference ... --profiling-mode benchmark \
--bench-model openai/gpt-oss-120b --save-benchmark anchor.json
Prefer the two-step form when more than one recipe is in play: measuring is by
far the expensive part, and --load-benchmark reuses one measurement across
every projection that shares its regime.
What one run actually measures#
One run sweeps more than the point it was asked for. It covers the CUDA-graph
capture ladder up to --max-concurrency, because batch is an axis projections
transport along and a lone measured point would be held flat across it. Decode
is then looked up by padding a batch up to the nearest measured size, the
way the engine pads it up to the nearest captured one. Decode latency is a
staircase, not a curve, and this is why it is looked up rather than interpolated.
The served anchor takes that ladder from vLLM’s default shape, since the engine
runs in another process and its real capture list cannot be read from here — a
default launch captures the default ladder. The offline anchor (--offline)
reads the list off the built engine instead, and also measures decode against
context so the projector can fit the attention KV term rather than assume decode
is flat in context. Naming batches explicitly with --batches overrides the
ladder on either path.
Measurement calibrates latency only
The memory projection is analytical throughout — weights, KV cache and the
activation working set are computed from the model shape and the parallel layout.
--profiling-mode benchmark does not change a single memory number.
The anchor JSON is engine-neutral: a "backend" field plus per-batch
decode/prefill measurements. A different harvester can be added without touching
the projector.
Regimes: when an anchor stops transferring#
Recipe parameters split into two kinds.
Kind |
Parameters |
Behaviour |
|---|---|---|
Regime-defining |
dtype, kernel/attention backend, graph mode, speculative decoding, the model itself |
Swap the kernel or execution path. Two recipes differing on any of them are not transportable from one another — each regime needs its own anchor. |
Transportable |
parallel shape (TP/EP/PP), batch, concurrency, sequence and context length, layer count |
Move analytically from an existing measurement. |
Reconstruction picks the nearest in-regime anchor and transports it to the target recipe by driving the same projector, so the physics lives in one place.
The anchor store#
Anchors live in a directory indexed by regime signature, named by
--anchor-store or INFERASIM_ANCHOR_STORE. Point a run at one and it looks
for a matching anchor itself, which is what lets the measured default be
satisfied without a GPU. The lookup is conservative and says what it did: an
anchor at a different dtype, attention backend, cudagraph mode or speculation
setting describes different kernels, so it is reported and refused rather
than quietly applied.
Setting this once per host is the difference between “measuring is the default” meaning one harvest and it meaning one harvest per question.
If anchor-calibrated results look wrong after you changed dtype or backend, this is why: an anchor certifies its own execution regime. Harvest another.
Harvest flag reference#
inferasim anchor forwards its flags straight to the harness, so
inferasim anchor --help is authoritative. The groups below are what the flags
are for; most harvests only touch the first two.
What to measure#
Flag |
Default |
Meaning |
|---|---|---|
|
(required) |
HF id or local path of the checkpoint to serve. |
|
|
Which engine’s kernels the anchor describes. Two engines serving one config are two measurements and never share a cache entry. Ignored under |
|
|
Target parallel shape to project to — not necessarily the shape that runs. |
|
(all of |
GPUs the run may actually use. Below |
|
(required) |
Where to write the anchor JSON. |
Shape of the measurement#
--concurrency is the usual way to set the batch axis: it derives the sweep
from the engine’s own CUDA-graph capture sizes, which is the ladder decode is
later looked up against. Name batches explicitly only when you want to override
that.
Flag |
Default |
Meaning |
|---|---|---|
|
(unset) |
Sweep the capture ladder up to this concurrency. Overrides |
|
|
Single reference batch when no sweep is requested. |
|
(unset) |
Explicit comma list, e.g. |
|
|
Prompt length the step is measured at. |
|
|
Recorded in |
|
|
K in the K-token minus 1-token difference that isolates the steady-state decode step. |
|
|
Context lengths to time decode at, so the projector fits the attention KV term instead of assuming decode is flat in context. Offline capture mode. |
|
(from config) |
Engine context limit. |
|
|
vLLM |
Reduce, measure, restore#
Depth reduction is the counterpart to parallelism reduction: build a shallow model, fit step latency against layer count, and evaluate the fit at the real depth.
Flag |
Default |
Meaning |
|---|---|---|
|
(unset) |
Comma list of reduced layer counts to measure and restore from, e.g. |
|
(HF config) |
Depth to restore to when using |
|
(unset) |
Legacy single sub-scale run with no restore. Prefer |
Anchoring prefill#
Decode escapes the analytical roofline by being measured. Prefill only escapes it if you measure prefill too, which is why the probe is on by default on the served path.
Flag |
Default |
Meaning |
|---|---|---|
|
on (served) |
Difference mean TTFT across two prompt lengths at concurrency 1 to price prefill from measurement instead of the roofline. Turning it off saves the probe runs and pays the roofline bias. |
|
|
Short probe length. The long probe is always |
|
|
Probe this many lengths and fit |
|
|
Also probe this many simultaneous sequences at fixed length, so a step that packs many sequences is measured rather than inferred. The length probe cannot supply this: at concurrency 1, token count and attention context are the same number. |
|
off |
Probe a third, interior length so pairwise slopes can be compared — a linearity check, at the cost of one more client run. |
MoE routing and expert placement#
Expert-load imbalance changes which rank is busiest, so a dummy-weight run needs
a routing distribution imposed on it. With real weights the trained router
supplies one and --routing-dist none is the constant-free choice.
Flag |
Default |
Meaning |
|---|---|---|
|
off |
Shard experts across ranks (EP=TP) instead of tensor-slicing each expert. Exposes busiest-rank and all-to-all effects. |
|
|
Token→expert distribution for the benchmark; |
|
|
Zipf skew exponent — |
|
(unset) |
Target imbalance |
|
|
|
Regime-defining engine settings#
Everything here changes which kernels run, so it changes the regime the anchor certifies. An anchor measured without speculation cannot be transported to a target that uses it.
Flag |
Default |
Meaning |
|---|---|---|
|
(from config) |
e.g. |
|
auto |
e.g. |
|
(unset) |
e.g. |
|
(unset) |
Draft tokens per step, bounded by the checkpoint’s |
|
(unset) |
Draft checkpoint for methods that need a separate one. |
|
off |
Disable graph capture — which removes the pad-up staircase decode is normally looked up against. |
|
off |
Disable AITER kernels (enabled by default on ROCm). |
|
off |
Enable vLLM prefix caching. In the offline repeated-prompt sweep this measures near-100%-hit lookup latency, not cold prefill. |
|
|
Engine flags as one string, e.g. |
|
(none) |
Repeatable environment override applied before vLLM is imported, for levers the engine reads from the environment rather than a flag. |
|
off |
Required by remote-code architectures. |
|
auto with |
Benchmark drives token ids directly. |
Sampling, determinism, and caching#
Flag |
Default |
Meaning |
|---|---|---|
|
|
Single RNG seed for random token content. |
|
|
Comma list swept inside one engine build. Each seed re-rolls token content and adds an independent sample, so the artifact carries a per-batch mean and standard deviation instead of a point with no error bar. |
|
auto with real weights |
Independent random token ids per sequence. |
|
|
Upper bound for random token ids. |
|
|
Cache keyed by run config; a hit never builds the engine, which is nearly all the wall time. Caching is off when neither is set. |
|
off |
Ignore the cache / re-run and overwrite the entry. |
|
off |
Measure through the offline |
How many GPUs a harvest should use#
A 1-GPU anchor cannot observe cross-GPU communication — TP all-reduce, EP
all-to-all — which is exactly the cost that matters at scale. So a warmup wants
more than one GPU, but only up to a point. The rule is a single line:
min(tp, 4), stepped down to a degree that divides tp. There is no sweep and
no rung to climb; one anchor is measured and the projector restores every target
from it.
Four is the operational default, not a universal accuracy guarantee. It is
large enough to observe multi-rank collectives, remains only one doubling from an
eight-GPU target, and avoids making every warmup wait on a full node. Targets
more than one doubling from their anchor are marked extrapolated. Use
--load-benchmark-scaling or a target-width confirmation when the parallelism
hop, the topology, or the risk attached to the decision warrants another
measurement.
Next steps#
Project from the anchor: Projection runs.
Simulate load with the same calibration: Simulation runs.
Understand what calibration cannot fix: Boundaries and verification.