BLIS Target Design

How the BLIS repositories are divided, what each one owns, the rules that keep them correct, and how we upgrade them when vLLM, SGLang, llm-d or our data change.

Status Standing design: true for every release Scope the llm-d stack, with vLLM or SGLang as the engine Companion each release has its own release plan, which schedules the work this design requires

What BLIS is. BLIS simulates an LLM serving cluster. It runs two kinds of code: the router of llm-d, a Kubernetes-based stack that routes requests to vLLM or SGLang engines, unchanged; and Go ports of the vLLM and SGLang engines. Each port covers the engine's scheduling, its KV-cache management, its KV transfer and tiering, and how it resolves its settings and chooses its kernels (§7.1). Each is tested step by step against the engine's own code. A cost model, built from published hardware facts and fitted coefficients, prices every step. This document describes the shape BLIS has, the rules that keep it correct, and how those rules are checked. It holds for every release. What a particular release does to reach it, and when, is in that release's plan; §1 explains how the two documents relate.

The main ideas.

  1. One repository, blis-substrate, holds everything that is true whatever engine runs: the shared contracts, the published facts, the fitted coefficients and the cost model. Each other repository tracks one upstream or serves one purpose, and every kind of work has exactly one owner (§2.2).
  2. Engines hand the cost model a fully resolved description of what runs, so the cost model does not depend on which engine produced it.
  3. Every upstream release gets a list of its features, generated from the upstream's own source. Each feature is modelled, refused by name, or declared out of scope, and CI fails while any feature has no status. Missing features are then counted and ranked, instead of being discovered only when someone needs one.
  4. Every coefficient the cost model needs has a value, labelled by how it was obtained: measured, derived from a roofline, taken from published work, or borrowed. Coverage can then grow without hiding how good each estimate is.
  5. The simulator follows explicit rules for a discrete-event simulation and for analysing its output, so its figures are reproducible and carry their uncertainty (§8).
  6. Every rule in this document has an identifier and a repository that runs its check. Automated checks enforce every rule that can be checked mechanically. Agents do scoped tasks that must pass those checks. People answer the yes/no questions that need judgement (§13).

1How to read this document

Words. MUST, MUST NOT and SHOULD mean what they mean in RFC 2119. An invariant is a statement about the repositories that every merged pull request and every release keeps true.

Registries. Three things in this design are called registries, and the word is never used alone. The coefficient registry is the substrate’s fitted and assumed coefficients, with their provenance (blis-substrate/registry/, the --registry flag); it is what --catalog is paired with. The invariant registry is the project’s rules, each with its statement, checks and status (blis-substrate/contracts/invariants/, §13.1). The metric registry is the definition of every metric a Result may report (contracts/metrics, §8.7). An upstream’s own tables are called registration tables, and engines are said to register a factory, not to be in a registry.

Identifiers. Every invariant has a short identifier, such as X3 or SUB-4. Pull requests cite these identifiers, and CI checks that every cited identifier exists (§13). The prefixes are:

PrefixCoversSection
Xrules that span repositories§4
SUB, VLM, SGL, RTR, SIM, CFGrules for one repository§5
FIDfidelity to upstream code§7
DEVsimulation method§8
VALhow coefficient values are obtained§9
Ddata roles§10
COVaccuracy coverage§11
LIFversions and releases§14
T-…outcomes of one trigger (for example T-A1 for accuracy ideas)§12

How a rule is enforced. A rule is one of three kinds, and the design says which (§4.2). An automated rule names a deterministic check, and the repository that runs it, which fails the pull request, the nightly run or the release when the rule is broken. A maintainer practice is a rule a person with write access keeps for a repository as a whole, such as its branch protection, who may cut a release, and signing; it is stated once, with a checklist, and no program in another repository audits it. Agent guidance is a rule an agent follows on every change it writes or reviews, and is rendered into each repository’s AGENTS.md. Where a rule depends on judgement, a review question is written for it. A review question is guidance for the review agent, which applies it to every pull request it reviews and answers it in a review comment with a reason and the lines that support it (§4, §13.2). It is never a check: it fails no CI job and waits for no person. The one reviewer with write access reads the agent’s answers as part of reading the change.

This design and the release plans. This document is the standing design: every statement in it holds for every release, and it says what must be true, not what is true at the moment. It contains no dates, milestones or status. Each release has a separate release plan, which records where the repositories stand, schedules the work that brings them in line with this design, and lists the release's open questions. A plan may schedule invariants and may relax one temporarily, naming it and the milestone at which the relaxation ends, but it may not create a rule. A new rule enters this document first, with an identifier. Each plan's status table is generated from the invariant registry (§13.1), so a plan and this design cannot disagree about which invariants exist.

Two short companions. The design is a reference, about ninety printed pages. Two summaries sit beside it for a reader who knows llm-d and vLLM and wants the shape of the simulator first: a three-page summary of the architecture and the seven-page summary of it with the detail a contributor needs before changing code. A page is a standard print page (US Letter, 0.9 inch margins, 10.5 point text). The page limits are part of the design: a summary does not grow when the design does. A design change that belongs in a summary takes the room of something that matters less to its reader, and a change that fits neither is left to the design alone. Each summary states no version, count, date or milestone, links every claim to the section that states it, and defers to the design where they differ. A section a summary cites keeps its number.

Citations. Claims about upstream code cite a file and line range at a pinned release. The releases and commits are the ones stack.json records for each engine and for the llm-d router. When an engine repository moves to a new upstream release, the citations that describe its behaviour move with it. A claim drawn from reading code that a recorded run has not confirmed is marked inferred.

2The shape of the system

2.1 Seven repositories

The target system has seven repositories. Three track an upstream project, one is the shared foundation, one is the simulator, and two are applications that use the simulator.

USES THE SIMULATOR blis-config-searchwhich configuration is best? accuracy-dashboardhow accurate is BLIS? SIMULATOR inference-simevent loop, cluster, workload, replay; picks the engine by scenario TRACKS ONE UPSTREAM blis-vllmvLLM, one release at a time blis-sglangSGLang, one release at a time llm-d-router forkllm-d, hosted unchanged FOUNDATION blis-substrate contractswhat engines use catalogfacts coefficient registrycoefficients kernelcost model wholesub-strate contracts only
Figure 1. The seven target repositories. An arrow means "depends on". The three upstream-tracking repositories use only the substrate's contracts/ packages (highlighted arrows), so a coefficient or pricing change does not require a new engine or router release (§3.2). Only inference-sim depends on the whole substrate. The two applications depend only on inference-sim.
RepositoryOwnsChanges when
blis-substratethe shared language (types, contracts, validators), published facts, fitted coefficients, and the cost modela new model or chip, a new data drop, an accuracy improvement, a new contract
blis-vllmvLLM's scheduler, KV-cache manager, connectors, defaults and lowering, in Go, for one vLLM release at a time (LIF-5)vLLM releases; a fidelity fix
blis-sglangthe same for SGLangSGLang releases; a fidelity fix
llm-d-router forkllm-d's own router code, made runnable in simulated timellm-d releases; a newly needed plugin
inference-simthe simulator itself: event loop, cluster, workload, replay, engine selectionany pin bump; a simulator feature
blis-config-searchexperiments that search configurations, and their reportsa new experiment; a simulator release
accuracy-dashboardpublic accuracy and coverage against ground trutha simulator release; a new InferenceX dump

2.2 Where a fact belongs

Every fact, rule and value has exactly one owner. To decide which, ask what the fact depends on:

To check a change to the substrate, ask whether a second engine would need the same edit. If not, the change belongs in an engine repository.

The same rule applies to work, not only to facts. Each kind of work has one owning repository, and no other repository holds a second copy of it. Another repository that needs the work calls the owner's command or imports the owner's package.

WorkOwnerOthers reach it through
Pricing a step, and the data it readsblis-substratethe cost model, through Environment (§6.3)
Validating catalog and coefficient registry datablis-substrateits validate command (X15)
Fitting coefficients, and judging a coefficient against operator-level measurementsblis-substrateits fitting scripts and operator-level gate (§13.5)
Engine behaviour and its fidelity teststhe engine repositoryengine.Engine
Router behaviour and hostingthe router forkpkg/embed.New
The documents BLIS reads and writes: the scenario, the deployment, the Result, and the per-point scoring recordblis-substrate (contracts/spec)Go types for Go repositories; for the applications, the JSON Schema each release publishes, printed by inference-sim schema
Running a simulation and writing its Resultinference-simits run and replay commands
Turning each data source's records into ledger units, for every source including InferenceXblis-substrate (catalog/ledger)inference-sim ledger, or a checkout of the ledger for seed-holding jobs
Scoring simulations against measured end-to-end benchmarks: turning a unit's recorded run into a scenario, running it, comparing, grading, and every end-to-end accuracy figureaccuracy-dashboardits scoring harness, run at a pinned release or commit with a simulator build and data paths (§11.7)
Searching configurations, and ranking themblis-config-searchits experiment specifications

So inference-sim runs simulations and holds no scorer, and a fitting script that wants an end-to-end figure runs the dashboard's scoring harness rather than writing its own. Two repositories that each compute an accuracy figure would sooner or later disagree about what the figure means.

FactDepends onLives in
H200 memory bandwidththe chipblis-substrate/catalog
GB200 NVL72's four GPUs per tray and 72 GPUs per NVLink domainthe hardware productblis-substrate/catalog
How many nodes a cluster has; the chip, fabric and storage devices they hold; which nodes sit under each switch and how oversubscribed each group's uplink is; and the GPUs per node, GPUs per rack and host memory per node where the chip's entry leaves them openthe user's configurationthe scenario
Muse-Spark's layer count and expert countthe model checkpointblis-substrate/catalog
How fast one attention kernel runs on H200the chip and the kernel libraryblis-substrate/registry
How to turn FLOPs, bytes and an efficiency curve into timearithmeticblis-substrate/kernel
Which attention backend SGLang picks on Hopperthe SGLang releaseblis-sglang
vLLM's default gpu_memory_utilization (0.92)the vLLM releaseblis-vllm
How the prefix-cache scorer ranks endpointsthe llm-d releaserouter fork
The scorer weights a deployment usesthe user's configurationthe scenario, copied verbatim
Which data points exist, and the rule that assigns their rolesthe data sourcesblis-substrate/catalog/ledger

2.3 How one simulated step flows

Figure 2 follows one engine step through the system. It shows the boundary this design changes most: the engine resolves everything about its own behaviour and hands the cost model a fully resolved description of what runs.

scenario upstream config, verbatim engine repository blis-vllm or blis-sglang resolve defaults as upstream does lower the model to kernels size the KV budget scheduler forms the step cost model prices primitives knows no engine catalog hardware and model facts coefficient registry by primitive, kernel, chip ① PoolPlan once per pool ② StepShape ③ step time Engine calls go through an Environment that inference-sim supplies (§6.3). inference-sim advances simulated time ④ step done metrics, KV events router fork routes the next request ⑤ engine metrics, KV events
Figure 2. One simulated step. ① Once per pool, the engine resolves its configuration, lowers the model, sizes its KV budget (asking the cost model for memory, not drawn) and hands the cost model a PoolPlan. Each step: ② the scheduler forms a StepShape; ③ the cost model prices it from the catalog and the coefficient registry and returns a time to the engine; ④ the engine reports the step done, with its metrics and KV events, and inference-sim advances simulated time; ⑤ inference-sim delivers the engine's metrics and KV events to the router. The engine owns everything about engine behaviour: defaults, which kernels run, how a step is formed, and which KV blocks move. With each step the engine also returns a Transfer for each KV move; inference-sim asks the cost model for the bytes each link must carry, and shares each link among the transfers that overlap in time (not drawn, §6.2). The engine reaches the cost model only through an Environment that inference-sim supplies (§6.3).

3blis-substrate

3.1 Why one repository

The substrate has four parts: the contracts and data types, the catalog of facts, the coefficient registry, and the cost model. They change together:

In one repository, such a change is one pull request, one review and one release. Each release also embeds its own data, which is small enough to compile into the program. Paths still override the embedded copy, so a data-only recalibration that the released validators accept needs no rebuild (§3.3).

3.2 Layout

blis-substrate/
├── contracts/          the packages other repositories implement or call
│   ├── *.go            package contracts: file loaders and bundle validation
│   ├── engine/         engine.Engine, Batch, Transfer, PoolPlan, StepShape …
│   ├── kernel/         the Kernel interface the cost model implements
│   ├── embed/          the router contract the llm-d fork implements
│   ├── spec/           data types: scenario, deployment, model graph, coefficient, …
│   ├── vocab/          shared names: methods, sources, roles
│   ├── check/          validation helpers shared by spec/ and validate/; fmath/, exact math functions
│   ├── conformance/    test suites implementers run (enginetest, kerneltest, embedtest)
│   ├── invariants/     the invariant registry (§13)
│   ├── toolchain       the one Go toolchain version every BLIS build uses (LIF-2)
│   └── harness/        shared checks engine repositories run in their tests
├── catalog/            published facts (YAML, vendor config.json); Python graph deriver
│   └── ledger/         data sources, units and the role rule (§10)
├── registry/           coefficient sets (YAML); Python fitting scripts
├── kernel/             the cost model (Go); "kernel" here is a historical name
├── validate/           validators for catalog and registry, and their commands (Go)
├── data.go             package substrate: go:embed of catalog/ and registry/
├── load/               the loader: reads data from a path or from the embedded copy
├── tools/              docgen, the release bot (§13.6) and other repository tools
├── docs/               one documentation site, with a section per directory above
├── .github/            workflows, filtered by path (§3.4), and the pull-request template
└── go.mod              the one Go module: github.com/inference-sim/blis-substrate

The substrate is one Go module, tagged vX.Y.Z. One commit gets one tag. Every repository that uses the substrate pins a version of it.

The module has two kinds of package:

Why a refit needs no engine or router release. Suppose blis-vllm pins substrate v1.2.0 and inference-sim pins v1.5.0, which carries new coefficients. Go builds every module in a program with the newest version any of them requires, so inference-sim's build uses v1.5.0 for blis-vllm too. The packages under contracts/ change only compatibly, except through a contract-change review set (a group of pull requests across repositories, reviewed and released as one change), which releases every implementer together (SUB-1, §12.8). So blis-vllm compiles against v1.5.0 unchanged, and no new blis-vllm release is needed. The nightly compatibility build (§13.4) confirms that every repository, at the tag the newest inference-sim release pins, still compiles and passes its tests against the newest substrate.

Engines must not move the simulator's data. The same rule works in reverse: if an engine required a newer substrate than inference-sim does, the simulator would be built with that newer substrate, and its embedded coefficients would change because of an engine release. To prevent that:

What one module costs. A repository that pins the substrate downloads all of it, mostly vendor config.json files (the release plan's evidence gives the size at the time of that plan; Go's limit for a module is 500 MB). Go compiles only the packages a program imports, so an engine build never compiles the cost model or the embedded data. A CI check fails if the module exceeds 100 MB uncompressed, well before Go's 500 MB limit (SUB-13).

3.3 Data: a path first, the embedded copy as the default

The catalog and the coefficient registry reach a running program in one of two ways. Both are always available, and every run records which one it used.

The two flags are independent. A run may, for example, use the embedded catalog with a candidate coefficient registry.

Private additions. To run a private model against released data, --catalog-overlay <dir> and --registry-overlay <dir> add entries to the data in use. An entry is identified as follows:

An overlay entry with the same identity as an existing entry replaces it; any other entry is added. The combined data are then validated in full, including the rules that each lookup has at least one entry (VAL-2) and at most one per rank (VAL-4). An overlay may not contain ledger files.

Overlays have their own flags because a recalibration candidate replaces the whole coefficient registry. If one flag could either replace or add to the data, an A/B comparison would be ambiguous.

Which files count as data

The file patterns are defined once, in the substrate's top-level package (data.go). That package must sit at the top of the repository, because Go's go:embed can only embed files at or below the package's own directory. The patterns are: hardware, devices, networks, workloads, measurement sources, each model's model.yaml and graph.yaml, every coefficient set, and acceptance and routing profiles. Most of the ledger is not runtime data: pricing never reads it, and it can be large, because it lists every unit. Seed-holding jobs (the CI jobs that hold the secret used to assign data roles, §10.2) read it from a checkout of the repository at the substrate tag they use, including those in accuracy-dashboard. The exception is the three published role lists (catalog/ledger/published/), which are embedded so that inference-sim ledger can print them offline.

The go:embed directive must list literal patterns, so a test checks that they equal the loader's. A path is read with the same patterns, and the loader refuses anything that would make the data ambiguous:

The data directories are exactly those the patterns in data.go name. Files outside them, such as scripts and documents, are ignored. Two things inside them are ignored by name: a model directory's config.json, and everything under catalog/ledger/ except published/. The check that graph.yaml's recorded derived_from hash matches config.json runs in the validators, in CI, not in the loader. So a path load and an embedded load validate the same files in the same way (SUB-9).

Vendor config.json files are not data in this sense. They stay in the repository for provenance and for the graph deriver, but the runtime never reads them. Every model value the runtime needs is a field of the graph, written by the deriver from config.json and the safetensors headers. No runtime code reads config.json, no model value is hard-coded in the cost model or an engine, and no user is asked to copy a value from a checkpoint into a setting (SUB-3).

Recording what was used

Every run computes a content hash for each of the catalog and the coefficient registry. It lists every data file as <sha256 of the file's bytes> <path relative to the catalog or registry directory, with forward slashes>, sorts the lines by path in byte order, ends every line, including the last, with a line feed, and takes the SHA-256 of the result. The loader refuses carriage returns, so the hash is the same on every machine. The Result document records, for each of the catalog and the coefficient registry:

It also records the build the simulator came from: a tagged release, or unreleased.

A run uses released data when all of these hold:

Every other run is experimental, including every run from stack CI, a local build or go run. Only released-data results are published (X8).

Checking data at load

Whatever its source, the data is validated when it is loaded, by the same validators CI runs. That includes the checks between the catalog and the coefficient registry, for example that every chip a coefficient names exists in the catalog in use. A candidate coefficient registry that does not fit the catalog it runs with is refused.

Each run also reports which coefficient registry entries were selected (a lookup returned them), and which coefficient files were never loaded. The report covers every reader behind the Environment, including acceptance and routing profiles. Without it, a candidate that adds a coefficient family no released code reads would be silently ignored and would look like a no-op. The landing gate uses this report (VAL-6).

The ledger in a candidate

A candidate may add data units, for example after a data drop. The gate and select jobs read units from the candidate's tree and compare them, by unit_id, with the ledger at the pull request's merge base:

Roles are never read from a file (D4), so a candidate cannot change which units are held out.

3.4 Directories, owners and CI

Each directory keeps its own owners, tools and checks.

DirectoryLanguageReviewed forCI on change
contracts/Gocompatibility of interfaces and typesbuild, tests, API-compatibility check of the contracts/ packages against the last substrate tag, conformance suites
catalog/YAML, Pythonsources and citations, vendor bytesvalidators; graph deriver --check; deriver tests; ledger checks
registry/YAML, Pythonheld-out evidence, provenance, fitting methodvalidators; property tests; writers --check on redistributable fixtures; landing gate
kernel/Gopricing correctnesstests; race test; conformance; engine-neutrality checks; price-change report

CI is filtered by path: a catalog-only change does not run the cost model's slow race suite. Any change also runs the cross-directory checks, because catching mismatches between directories is the reason for the merge: validators run against the data, and the cost model against the data it embeds. Python tooling keeps its own lock files under catalog/scripts/ and registry/scripts/.

4Rules that span repositories

These rules hold across the whole system. A pull request that breaks one must not merge, whatever its tests show. Each rule lists its target checks, the repository that runs each check, and the review guidance that remains.

Review guidance is reserved for questions that need judgement. A question is guidance for the review agent, never a check. When a pull request changes something a question covers, the review agent answers it in a review comment, yes or no, with a reason and the files and lines that support it. No CI job fails or waits on a question, and no person has to answer one. The question’s Expertise line names the knowledge the agent brings, not a group of people. An answer in the unwanted direction is a finding, which the one reviewer with write access reads with the rest of the review (§13.2).

4.1 Process rules

4.2 Three kinds of rule

Every invariant is kept in one of three ways. The design says which, so that a rule with no program behind it is a decision and not a gap.

KindKept byWhenExamples
AutomatedA named, deterministic check in the repository that owns the rule. It fails the pull request, the nightly run or the release.On every pull request, night or release, with the same result each time.The description check (X14), generated files matching their generators (X7), the field-list completeness check (VLM-3), the conformance suites, the lints.
Maintainer practiceA person with write access, for the repository as a whole. The rule is stated once, with a checklist item, and the hosting service enforces it where it offers a setting. No program audits it.When the repository is set up, when a setting changes, and when the maintainers review the checklist. Not on each pull request.Branch protection and its required checks (X17), protected tags (LIF-1), who may create a tag (LIF-3), the merge methods a repository allows (RTR-1), encouraged signing and sign-off by the person who takes responsibility (X20).
Agent guidanceThe agent that writes or reviews a change, from the rule’s text in its AGENTS.md. A reviewer with write access checks the result.On every change an agent writes or reviews, so it is applied pull request after pull request.What to read before editing a field list (VLM-3), how a commit message names agent help (X20), what an agent may and may not do (§13.7), the writing bar for documents, never editing another repository’s files.

The test. A rule is automated when a program can decide it from the repository’s own contents and the repositories below it, the same way release after release, and when running it needs no knowledge of a repository above it. A rule about the repository as a whole, about who holds a permission, or about an act only a person can perform is a maintainer practice. A rule that shapes how a change is written or judged, and that a program cannot decide, is agent guidance. A rule can be both: a program checks the form of a commit message while an agent follows the convention that produces it, and a person signs the commit.

Dependency direction holds for all three. An automated check reads only its own repository and the repositories it depends on (X7). A check in one repository never reads the settings, branches, issues or workflows of another, and no job exists to audit a practice from a different repository. A practice is kept in the repository it concerns, in that repository’s own settings and documentation. Agent guidance names only what the repository it is rendered into may know. A program is added for a rule only when the rule is deterministic and local; complexity, or a link between layers that the dependency direction forbids, is a reason to keep a rule a practice.

5Each repository's design

Each card gives the repository's purpose, what it owns and must never hold, its interfaces, what triggers a change, how correctness is shown, and its own invariants.

blis-substrate

Purpose
The engine-independent foundation: the shared language, published facts, coefficients and the cost model.
Owns
Contracts and data types; validators; conformance suites; the engine harness; the catalog, including the data ledger; the coefficient registry; the cost model; the invariant registry.
Never holds
Any engine's name, version or policy outside tests; third-party raw data.
Interfaces
Packages under contracts/ are imported by every Go repository. The cost model, the data loader and the embedded data are imported only by inference-sim. Its data reaches a run either from a path or from the embedded copy (§3.3). Fitting scripts reach the cost model through inference-sim with --catalog and --registry, or in-process through kernel.Open, which uses the same loader and checks (SUB-9).
Triggers
A new model, chip or network; a dataset drop; an accuracy idea; a new capability's contract.
Correct when
Validators pass on the data being released, the cost model passes its laws and engine-neutrality checks, held-out accuracy does not get worse, and contracts changes only compatibly.
  • SUB-1 Packages under contracts/ import only the Go standard library, the YAML parser and each other. Between contract-change review sets they change only compatibly: an interface never gains a method, optional abilities are separate interfaces, and new fields keep their zero-value meaning. An incompatible change is allowed only as a contract-change review set (§12.8), which releases every implementer against it together. From v1 on such a set raises the substrate's MAJOR version. While the substrate is at v0 it raises MINOR, because Go treats every v0 version as one major. contracts/harness and contracts/conformance may also use golang.org/x/tools, at the version blis-substrate's go.mod requires, for their analysis checks.Checked by: import test; API-compatibility check of the contracts/ packages against the last substrate tag. The check runs apidiff at a pinned version through go run, as the router fork’s check does, in accept-break mode for a contract-change review set, which names the set’s issue. The tool is a step of the check and not a dependency: no package under contracts/ imports it and go.mod does not require it.
  • SUB-2 Every new or changed data field ships with its validator, a failing example for each way it can be wrong, and regenerated reference documentation.Checked by: documentation-matches-types test; strict loaders; examples must validate.
  • SUB-3 The catalog holds declared facts only: figures read from a published source, each with a citation a reader can follow. Each model's graph is generated from its vendor config.json and the safetensors headers of its checkpoint, and never edited by hand. No runtime code reads config.json. No model value is hard-coded in the cost model or an engine, and no setting asks the user to copy a value from a checkpoint. A scenario's cluster names catalog entries for its chip, fabric and storage devices, and states what the user provisions: the node count, which nodes sit under each switch and how oversubscribed each group's uplink is, and the GPUs per node, GPUs per rack and host memory per node where the chip's entry leaves them open. Where the entry states them, as GB200 NVL72's does, the scenario may omit them or restate them unchanged, and any other value is refused. On a chip whose entry has no rack tier, the scenario may not state a rack size. On a chip that has one, the cluster's fabric is the network between racks. A different packaging of the same chip is a catalog entry of its own. One function in contracts/spec resolves a cluster's packaging from the scenario and the catalog, and every reader calls it (X15).Checked by: deriver --check; the validators, including the packaging check and its test pair; a test that no runtime package opens config.json; a lint for model-name literals in kernel/ and in engine lowering code. Review: that each citation supports its figure.
  • SUB-4 Every coefficient is generated by its family's writer script, never edited by hand. It records its value, units, scope, method, sources and data units (§9).Checked by: writers --check on fixtures per pull request, on full data nightly; provenance rules.
  • SUB-5 Coefficients obey physics: no compute rate above the chip's dense datasheet peak for that data type; no memory bandwidth above the datasheet peak for a working set larger than the chip's L2 cache; at a fixed algorithm, topology tier and bytes per rank, a collective never gets faster as the group grows.Checked by: property tests.
  • SUB-6 The cost model resolves nothing. Every default, backend choice, padding and capture decision arrives in the plan or the step shape.Checked by: a test that the cost model's public functions accept only plans, step shapes, transfers, and catalog and coefficient registry handles.
  • SUB-7 The cost model has a price for every primitive it can be asked about. When no fitted coefficient exists it uses the next source in a fixed order, and records which one it used (§9).Checked by: completeness test over every primitive and every catalog chip.
  • SUB-8 A change to the substrate never breaks the data it releases: validators, deriver and writers all pass on the tree being released.Checked by: the validators, the deriver and every coefficient registry writer, run on every pull request.
  • SUB-9 Data is loaded the same way from a path or from the embedded copy: the same file patterns, the same validators (including the checks between the catalog and the coefficient registry), and the same content hash. The embedded patterns equal the loader's patterns.Checked by: A test that loads the embedded copy and an exported checkout of the same commit, and gets identical data and an identical hash; a test comparing the two pattern lists.
  • SUB-10 contracts/conformance includes a set of unusual model shapes: every layer windowed, every layer recurrent, MLA only, a draft layer declared last, a single layer, and no attention at all. Every engine and the cost model either runs each one or refuses it with the upstream's reason. No size or price may be non-finite or zero where a positive value is required.Checked by: the conformance suite, run in blis-substrate, blis-vllm and blis-sglang.
  • SUB-11 Every field of PoolPlan, StepShape and Transfer, and every coefficient family, changes some price when it is changed, or is listed as not yet priced with an issue. Here Transfer means the plan.Transfer that the cost model receives; the lifecycle fields of engine.Transfer (ID, Request and Blocks) are never priced. A setting that the engine resolves but the cost model never reads is a gap, and it is visible.Checked by: a perturbation test in kernel/; a ratchet on the not-yet-priced list, which may only shrink.
  • SUB-12 Every loader of a BLIS document refuses unknown keys, duplicate keys, numbers that do not convert exactly, non-finite numbers and recursive aliases. Every declared field is either used or refused, never dropped.Checked by: a shared set of malformed documents in contracts/conformance, run against every loader; a test that every declared field is read.
  • SUB-13 blis-substrate is one Go module: no go.mod exists below the repository root, and the files in the module zip that Go fetches total under 100 MB uncompressed (Go's limit is 500 MB).Checked by: a check that fails on any nested go.mod; a check of that uncompressed total.
  • SUB-14 The memory breakdown closes. Every figure an engine's upstream reports about memory at start-up (weights, memory outside PyTorch's allocator, peak activations, the CUDA-graph pool, and the memory left for KV) has one matching term, within a stated tolerance. Buffers and workspaces the engine chooses are PoolPlan fields, sized by the engine as its upstream sizes them. Memory the engine does not choose, such as the CUDA context and communication libraries, is a measured coefficient per chip and library version. Anything left over is a named term on a list that may only shrink, never folded into another term. Each term states its basis: per rank, per node or for the group (SUB-16). Host memory closes the same way: the weights an engine keeps in host memory, its host KV tier and its pinned buffers are PoolPlan figures. Each states whether the engines on a node share it; a shared figure counts once per node. For each node, the sum fits the catalog's host memory.Checked by: in each engine repository, oracle tier T3 against the upstream's own buffer-sizing functions; in blis-substrate's error job, a comparison of the total with recorded GPU start-up logs, which are ledger units with role validate; a start-up refusal in inference-sim when a node's host-memory figures exceed its host memory.
  • SUB-15 A model graph holds only what the checkpoint states: the tensors, their shapes and stored data types, scale tensors, and the layer structure. Every graph field can be computed from config.json and the safetensors header of every checkpoint shard (the header only, no tensor data). How an engine shards, fuses, pads or repacks a tensor, which page size it uses, and which nodes it emits under a layout are lowering, and belong to the engine (X3). The engine reports the resident weights that result in its PoolPlan.Checked by: a test that rederives every graph field from those inputs; a check that graph vocabulary has no emit conditions. The deriver records each header's SHA-256 and the revision, under the same licence rule as config.json (X12).
  • SUB-16 Quantities carry units as types. Bytes, seconds, tokens, FLOPs, bytes per second and dimensionless ratios are distinct Go types in contracts/, and so are per-rank, per-node and per-group amounts, so adding a byte count to a token count, or a per-rank amount to a per-group one, does not compile. A coefficient registry entry's unit equals the unit the formula reading it requires, checked when the coefficient registry loads. An efficiency lies in (0, 1]. A quantised format is one type (payload bits, scale bits, block shape) with one function for its stored bytes.Checked by: the compiler; a load-time unit check in the cost model; validator rules that catalog peaks are dense figures and that their ratios between data types match the datasheet. The bounds on rates are SUB-5's.
  • SUB-17 A transfer or collective names its endpoints by rank, and the cost model prices it over the path between them through the catalog's links: the NVLink domain, the NIC and each switch tier. A collective's step time is set by the cost model. A collective that leaves its NVLink domain also reports the demand it places on each link during the step; that demand is reserved, and transfers share what remains.Checked by: a property test over random placements, in blis-substrate; the link-capacity test of SIM-13.
  • SUB-18 Provenance is recorded per field, not per file. A catalog field holds a datasheet figure, a published figure, or a measurement of one named device, and says which. A fitted or assumed value is a coefficient, and belongs in the coefficient registry.Checked by: validator rules on catalog provenance fields.
  • SUB-19 Every document that crosses a repository boundary has one definition, in contracts/spec: the scenario, the deployment, the Result, and the per-point record the scoring harness writes. Each substrate release publishes them as JSON Schema, generated from the Go types. blis-config-search and accuracy-dashboard validate every document they read or write with inference-sim schema validate, which runs the Go validators (X15), and define none of their own. The published JSON Schema serves editors and structural checks. The per-point record is defined there too, although the dashboard writes it, because config search and the substrate's gates read it.Checked by: a generator test in blis-substrate that the published schemas match the Go types; schema validation in the applications' CI; a lint in the applications that refuses a hand-written schema for these documents.
  • SUB-20 No price, size or engine decision depends on a free-text label. Every property a consumer branches on, such as which attention node in the model graph reads a cache the plan sizes or whether an expert group is shared or routed, is a typed field with a closed set of values, derived from the checkpoint and checked at load. Names and roles kept for people to read change no output.Checked by: a syntax-tree lint in blis-substrate and each engine repository that refuses a comparison on a documentation-only string field outside tests; a perturbation test (SUB-11) that changing such a label changes no price.

blis-vllm

Purpose
vLLM's engine in Go, in the four parts of §7.1: the scheduler, engine loop and data parallelism; the KV-cache manager, its block pool and the managers for each KV-cache spec type; the KV connectors and offload tiering; and default resolution and lowering. It models one vLLM release at a time, and registers as engine kind vllm with that version. Its tags are v<vLLM release>-blis.N (§14.1).
Owns
vLLM configuration, read verbatim; vLLM's default resolution; lowering a model graph to the kernels vLLM runs; emitting plans and step shapes; the oracle and its recorded traces; the field list and the surface list.
Never holds
Pricing arithmetic, coefficients or catalog facts; code from blis-sglang.
Interfaces
Implements engine.Engine from contracts, which includes reporting its own field list; emits PoolPlan and StepShape; publishes fields.json and surface.json. The substrate defines the method and the shape of a field list, and holds no engine’s fields: a list depends on the engine and its release, and inference-sim fields prints what the registered engine reports without reading it.
Triggers
A vLLM release; a fidelity bug; a modelling gap at the top of the backlog.
Correct when
Every oracle tier matches vLLM at the pinned commit, every changed trace was reviewed, and the field and surface lists are complete.
  • VLM-1 Decisions match vLLM at the pinned commit, step by step, on every oracle scenario.Checked by: oracle tiers T0–T6 (§7.4).
  • VLM-2 Every Go file states where it came from: derived from a vLLM path at a release and commit, moved from another repository, or original. At a tag v<release>-blis.N, every ported file names that vLLM release, and no file names another.Checked by: provenance test.
  • VLM-3 The engine’s field list classifies every configuration field and every environment variable the pinned release reads, including those read by the communication libraries it uses, as modelled, proven inert (with a passing proof), or not modelled (with an issue). No other status is allowed, except one a release plan allows for a stated time (glossary: Inert, unproven). A field is modelled when the port reads it and setting it changes a decision in a recorded trace, or a field of the emitted plan or step shape that the cost model prices (SUB-11). Its evidence is an oracle scenario that sets it and whose trace the port reproduces, or a test that sets it and finds the emitted plan or step shape equal to the upstream's own resolution. A field that reaches only a plan or step-shape field on SUB-11's not-yet-priced list is not modelled, and cites that list's issue. A field the port reads but carries to no decision, plan or step shape is proven inert or not modelled. A field marked modelled records the value classes its evidence covers: for a boolean or enumerated field, the values its evidence sets; for an ordered numeric field, the range its evidence sets, split at every threshold the upstream compares the field with, generated from the upstream's comparisons. The probe values of a proven-inert field follow the same classes. A field that can name code outside the pinned release, such as a class or module path, is marked so. A field marked not modelled cites an open issue that names the setting or its group, and says why the port does not model it, what the setting does upstream, and the milestone that models it or the reason it never will. Each field also states its estimation rule (§11.2), or that it has none.Checked by: completeness check; inertness proofs (§7.4); a perturbation test over every field marked modelled; a check that every field has an estimation rule or says it has none; a check that fails a not-modelled entry whose cited issue does not list the field or its group, or that cites no reason. A second check fails on any field whose type can hold a class or module path and is not marked as able to name code.
  • VLM-4 surface.json lists every connector, offload tier, KV-cache spec type and its manager, speculative method, scheduler policy, metric family and KV-event type the pinned release registers, each with a status (X9).Checked by: generator against vLLM's own registration tables (§7.5).
  • VLM-5 No clock reads and no goroutines. Waiting is reported to the simulator as a time value.Checked by: syntax-tree test.
  • VLM-6 A value of an enumerated setting, such as a connector, backend or policy name, runs only through the surface item that models it. Two values share code only where the upstream's own registration maps them to the same class, cited. Every value is either refused by name or produces its own trace.Checked by: dispatch generated from surface.json; a perturbation test per enumerated value.
  • VLM-7 Every KV-cache spec type and group role the pinned release registers in KVCacheSpecRegistry has a status in surface.json that X9 allows. For a modelled one, the manager that KVCacheSpecRegistry maps it to is ported, with oracle scenarios that allocate, evict and free its blocks and, where the manager supports prefix caching, share a prefix between requests. A model whose layers need a refused spec type is refused before the run starts, with a message naming the spec type. The grouping of layers into KV-cache groups at start-up, and the coordinators that run one manager per group, are ported once and serve every manager.Checked by: generator against KVCacheSpecRegistry (§7.5); a T2 scenario per ported manager; a start-up refusal test with a model that needs each refused spec type.

blis-sglang

Purpose
SGLang's engine in Go, in the four parts of §7.1: the scheduler and its event loops; the tree caches, allocators and memory pools; the hierarchical cache (HiCache) and disaggregation transfer; and default resolution and backend selection. It models one SGLang release at a time, and registers as engine kind sglang with that version. Its tags are v<SGLang release>-blis.N (§14.1).
Owns, never holds, interfaces
As blis-vllm, for SGLang. It imports nothing from blis-vllm; shared machinery comes from contracts/harness.
Triggers
An SGLang release; a fidelity bug; a gap at the top of the backlog.
Correct when
As blis-vllm. In addition, its KV events and metrics are byte-compatible with what llm-d's SGLang adapter reads.
  • SGL-1 The oracle drives SGLang's own Python classes at the pinned tag, in a locked environment, with the Python tree core of the radix cache selected.Checked by: the oracle's lock check and recorded-trace provenance check, from contracts/harness.
  • SGL-2 Every Go file states its provenance, as for VLM-2.Checked by: shared harness test.
  • SGL-3 The field list covers every configuration field and every environment variable the pinned SGLang release reads, under the rules of VLM-3. Defaults are resolved as SGLang's own resolution hooks resolve them.Checked by: the field-list completeness check against SGLang's ServerArgs and environment variables, and the T1 default-resolution tier.
  • SGL-4 surface.json lists every transfer backend, HiCache storage backend, tree cache, tree core, allocator and memory-pool type, speculative algorithm, scheduling policy, metric family and KV-event type the pinned release can select, each with a status (X9).Checked by: a generator that reads SGLang's registration tables and selection code at the pinned tag (§7.5), and a check that every item has a status.
  • SGL-5 KV events are emitted exactly as SGLang emits them: one BlockStored per page, merged in SGLang's event queue with the queue's last event when it has the same medium, LoRA adapter, block size, cache salt and session, and its parent hash continues that event's chain (mem_cache/events.py:140-170). Consecutive BlockRemoved events of the same medium are merged too. Both tree cores, Python and Rust, merge the same way (§6.4).Checked by: byte comparison with SGLang's queued events, for both tree cores; a routing test at page size 1 and at page size 16 or more.
  • SGL-6 Every tree cache, tree core, allocator and memory-pool type the pinned release can select has a status in surface.json that X9 allows. A modelled type has its Go counterpart, with oracle scenarios that insert, match, lock against eviction, evict and free through it. A configuration that selects a refused type is refused before the run starts, with a message naming the type.Checked by: a generator that parses SGLang's tree-cache, allocator and pool selection branches at the pinned tag, with an unlisted branch as its known-bad input (§7.5); a T2 scenario per ported type; a start-up refusal test for each refused type.

llm-d-router fork (inference-sim/llm-d-router, branch llm-d-r)

Purpose
llm-d's own router code, made runnable in simulated time. It serves both engines through llm-d's own adapters.
Owns
Upstream llm-d code; the runtime interface ("seams") through which the router reads time, starts work, draws random numbers and makes calls; pkg/embed; the audited plugin list; the surface list of llm-d plugin types.
Never holds
Any change to a routing decision.
Interfaces
pkg/embed.New, which implements the contracts/embed router contract and is the only identifier BLIS imports.
Triggers
An llm-d release; a plugin a BLIS user needs.
Correct when
The default runtime reproduces upstream on recorded requests, upstream's own tests pass on the fork, every simulated-runtime decision is one the default runtime can make from the same recorded state, and the change-kind, hazard and API checks pass.
  • RTR-1 Upstream releases are merged into llm-d-r, never rebased. Fork pull requests land as merge commits. Commits written for BLIS follow X20; upstream history is merged as upstream wrote it.Checked by: commit checks, and a history check that the head contains the previous upstream base and adds no upstream commit outside a release. Maintainer practice (§4.2): the repository allows merge commits and forbids squash and rebase merging, a setting a person with write access keeps.
  • RTR-2 Every change the fork makes to an upstream file is one of eleven kinds, and nothing else:
    KindRequired form
    clock read or deadlinetime.Now, time.Since, time.After or a context deadline becomes the runtime's clock (rt.Now, rt.Clock())
    timer or periodic worka goroutine looping on a ticker becomes rt.Every(ctx, name, interval, fn)
    goroutine or blocking waitgo f() becomes rt.Go(f); a blocking select becomes rt.Park(condition)
    random drawa draw from a global or unseeded source becomes a draw from a named stream, rt.Rand("<name>")
    iteration orderwhen hosted, a map or sync.Map listing that reaches a decision is sorted, or sorted and then shuffled from a named stream; when not hosted, upstream's order is kept
    network callwhen hosted, an outbound HTTP call goes through hosting.Transport, which the host serves
    work orderingwork the upstream runs in parallel or in the background runs, when hosted, one item at a time in a fixed order; when not hosted, upstream's concurrency is kept
    per-simulation statestate the upstream shares across the whole process, such as the picker's tie-break counter or random source, is kept per runtime when hosted (rt.Local), so two simulations in one process do not share it
    hosted substitutewhen hosted, a component is replaced by one with the same contract that the host can drive, such as a time-to-live cache on the runtime's clock, or a path BLIS does not host is refused by name
    test adaptationan upstream test is changed only to follow a signature another allowed kind changed
    startup assemblythe runner is split so a host can build the router without servers, Kubernetes reconcilers or leader election
    No change alters a routing, admission, flow-control or scoring decision: no condition, weight, threshold, default or plugin choice.Checked by: a classifier in hack/hosting-lint that reads the diff against the upstream tag the fork is based on and matches each changed hunk to a kind's required form; a hunk that matches none fails CI. The differential test of X4 checks that decisions are unchanged, which catches a hunk the classifier misreads. Review guidance. Does this startup-assembly change only alter how the router is built or hosted, never which plugins run or what they decide? Expertise: router fork expertise. Applies when: a hunk is classified as startup assembly.
  • RTR-3 pkg/embed.New passes the router conformance suite and changes only compatibly within an upstream major version, except through a contract-change review set (§12.8), which runs the API diff in accept-break mode and releases every implementer together (SUB-1). Once the fork imports contracts/embed from blis-substrate, the diff covers that package.Checked by: conformance suite and API diff.
  • RTR-4 A plugin type runs in BLIS only after an audit. The audit converts every baselined use inside that plugin (RTR-6). The fork's audited list and BLIS's allow-list change in the same review set.Checked by: TestSupportedTypesAreHostedByTheFork in inference-sim, against the fork's audited list.
  • RTR-5 The llm-d P/D sidecar is hosted under the same runtime interface, so P/D leg handling is llm-d's code rather than a re-implementation in inference-sim.Checked by: a test in inference-sim that P/D runs go through the hosted sidecar's code, and that the simulator holds no P/D leg logic of its own.
  • RTR-6 Every direct clock read, timer, goroutine, random draw, map-order dependence, network call and process-wide mutable state on a router path goes through the runtime interface, or is listed in the reviewed baseline. A use is on the baseline for one of three reasons, recorded with it: it cannot affect a decision (a log, a metric, or a walk whose order does not matter); it lies inside a plugin type BLIS refuses; or it lies on a path that pkg/embed.New never builds, such as the router's servers. A use of the second kind is converted when its plugin is audited and allowed (RTR-4). The baseline grows only by review, and shrinks when a use is converted or upstream removes it.Checked by: hack/hosting-lint, which type-checks the router packages, including package-level variables written after initialisation, and fails on any use not in hack/hosting-lint/baseline.txt, or on more uses than the baseline records. Review guidance. Does this baselined use stay off every decision path, lie only inside a refused plugin, or lie only on a path the embedding never builds? Expertise: router fork expertise. Applies when: an entry is added to the baseline.
  • RTR-7 Under the simulated runtime, at most one router goroutine runs at a time, and a goroutine gives way only in rt.Park. No lock is held across a call that can reach rt.Park. Channel and wait-group waits on decision paths are converted to rt.Park, as the "goroutine or blocking wait" kind of RTR-2. Decisions do not depend on the order in which goroutines runnable at one instant resume.Checked by: hack/hosting-lint, which fails on a lock acquired in a function that can reach rt.Park before its unlock, and on an unconverted channel or wait-group wait on a decision path; a seeded test that permutes resumption order and compares decisions.

inference-sim

Purpose
The simulator: a discrete-event loop that runs engines, priced by the substrate, behind the hosted router, under a workload.
Owns
The event loop, cluster and instance wiring, and workload generation and replay. Choosing between path and embedded data, and recording which was used. Engine selection, and the Environment the engines are built with (§6.3). Link sharing among transfers and the cross-domain traffic of step collectives (SIM-13). The command line, the Result document, and the byte gate.
Never holds
Engine behaviour; routing policy; pricing; resource arithmetic, except sharing links among transfers and step collectives (X3, SIM-13); catalog or coefficient registry data except through the substrate module; a scorer that compares simulations with measured benchmarks, which belongs to accuracy-dashboard (§2.2).
Interfaces
Imports blis-substrate (the cost model, the data loader and the embedded data, besides contracts/), each engine module at one build site, and the router fork. Each engine module and the router fork models one upstream release, so a build runs one release of each engine and of llm-d (LIF-6). Emits Result documents and stack.json.
Triggers
A new tag in any repository below it; a simulator feature.
Correct when
Determinism and replay tests pass, refactors are identical on the byte gate and the dashboard's corpus, validation changes carry held-out evidence, and verification changes that move outputs carry the price-change report and the scoring harness's figures on published units (X13).
  • SIM-1 The engine is chosen by the scenario's engine: {kind, version}. Any number of kinds may be registered, each at one upstream release; a scenario naming another release is refused (LIF-6). A scenario that still uses the field engine_version, from before engine: {kind, version} existed, is refused with a message that names the replacement.Checked by: tests that build two engine kinds in one binary, and that refuse any scenario using engine_version with a message naming engine: {kind, version}.
  • SIM-2 No silent degradation: unsupported or ambiguous input is refused with a message; library code returns errors and never exits the process.Checked by: refusal tests.
  • SIM-3 Every release publishes stack.json: the exact tag of every BLIS module and every upstream release it was built and tested with, the Go toolchain version, and the llm-d guides commit.Checked by: a release check that stack.json matches go list -m all, go version and guides.lock for the tagged build.
  • SIM-4 The byte gate (a fixed scenario set whose output must not change in a refactor) runs inside this repository.Checked by: the byte gate job in inference-sim's CI, on every pull request.
  • SIM-5 Each release reports how much of the llm-d stack's published configurations it can run, per engine (X9).Checked by: a release job in inference-sim that computes coverage from two inputs, the llm-d guides commit pinned in guides.lock (which stack.json records) and each engine's surface list, and publishes per-item deployment counts through inference-sim coverage. The dashboard adds its blocked-point counts to them (§7.5).
  • SIM-6 A run that names no catalog or coefficient registry uses the embedded copy. A run that names a path uses that directory instead of the embedded copy, plus any overlays the overlay flags name. A path flag never overlays.Checked by: tests for each case; the result records the data source.
  • SIM-7 Every run makes progress. Each request ends in one of the fates of §8.7 (DEV-9). An engine pauses only when it has a scheduled wake-up or nothing to do. It never has more steps in flight than PoolPlan.InFlight, which is its upstream's depth (in vLLM, max_concurrent_batches, config/vllm.py:577-587; in SGLang, one batch ahead under the overlap scheduler), and never processes a step's output before that step ends. A request that can never fit in the engine's KV cache is refused at admission by the engine, with the upstream's reason, and inference-sim records the refusal; it is never retried forever.Checked by: a progress watchdog that fails a run when no request changes state for a set number of engine steps while work is waiting; a seeded property test over random pools and prompts, including the exact-fit case and the one-block-over case. The capacity boundary cases are also oracle scenarios in each engine repository.
  • SIM-8 Requests and sessions follow one written state machine, whose end states are the fates of §8.7 (DEV-9). Intermediate P/D states, such as waiting for decode KV or transferring, are states of the machine, each with edges to eviction and to an end state. Code that ends a request sets its fate before notifying anyone. Every round a session planned has exactly one fate. A request identifier is never reused within a run.Checked by: a transition-table test; the arrival-count check of DEV-9; an oracle scenario for identifier reuse in each engine repository.
  • SIM-9 Each refusal has two tests: the smallest configuration that triggers it, and the closest one that does not. The message names the field and the value that caused it. A feature or plugin is refused only under the settings that actually reach the code BLIS does not model. A configuration the upstream refuses at start-up is refused at start-up, naming the upstream's own condition: BLIS never simulates a deployment the upstream would not start, and a search never ranks one. A refusal states what the user must change, and says that the deployment does not fit or is not supported when that is the cause; it never reports a limit of the deployment as an error in the simulator.Checked by: the refusal test pairs in inference-sim, each engine repository and the router fork; for each start-up refusal the upstream has on a modelled path, a scenario that the oracle refuses and BLIS refuses with the same condition (T1, §7.4); a message test for each refusal that wording names the field, the value and the cause.
  • SIM-10 Any option that changes a byte of the Result is recorded in the Result's inputs. A cache key includes every input the cached value depends on.Checked by: a test per command-line option that toggles it and compares the recorded inputs.
  • SIM-11 Every field the simulator itself reads (scenario keys outside the engine and router blocks, trace headers, replay descriptors) is consumed, refused or inert, and says which. Engine and router blocks are classified only by their owners (X1). A consumed field changes some byte of the Result when it changes. One validator exists per kind of value (X15), and every input that carries that kind calls it, so a value refused on the command line is refused in a trace header too.Checked by: a reflection test over every input schema that toggles each consumed field; a parity test that the same invalid value is refused through every input.
  • SIM-12 Each request's tokens are counted once, in a token record the engine emits: prompt tokens, tokens served from the local and external caches, tokens computed, and tokens emitted. Session growth, trace export, latency metrics and the Result read that record; none recomputes it. Counts per rank and per group are distinct types (SUB-16).Checked by: a test that every consumer reads the token record; known-answer tests of every capacity function at tensor-parallel and data-parallel widths above one.
  • SIM-13 The traffic that overlapping transfers and step collectives place on a link never exceeds its capacity. inference-sim shares each link among them, as the one piece of resource arithmetic it owns (X3).Checked by: a property test over random placements, transfers and collectives.
  • SIM-14 Every cluster control loop that runs beside the router, such as an autoscaler or the placement of LoRA adapters, is a component (§8.1): it acts only on published values (DEV-1, DEV-4). Each is listed in inference-sim's generated list of control loops, with the upstream controller it models, if any. A Result that used one names it and states its validation status and the evidence for it (§8.9).Checked by: a reflection test that every configurable control policy has an entry in the list of control loops; a Result test that each active loop is named with its validation status.

blis-config-search

Purpose
Find the best configuration for a goal, reproducibly, over vLLM or SGLang settings and llm-d router configurations.
Owns
Experiment specifications, deterministic sweeps, ranking, and reports.
Never holds
Copied lists of settings; invented settings; results written by hand.
Interfaces
Depends only on inference-sim, pinned by tag and commit. It reads settings lists and plugin parameters through inference-sim fields, catalog identities through inference-sim catalog resolve, metric definitions through inference-sim metrics, acceptance profiles through inference-sim acceptance, a cluster's resolved packaging through inference-sim catalog resolve, and the schemas of its inputs and outputs through inference-sim schema. Its scripts never read catalog or coefficient registry files directly.
Triggers
A new experiment; a simulator release.
Correct when
Every specification validates and runs, the same specification on the same pins gives an identical report, and every ranking follows the output analysis of §8.8.
  • CFG-1 One pin file names the simulator release, and every report states it. A committed report uses that release's embedded data; exploratory runs with other data are labelled experimental and are never committed as reports.Checked by: scripts/test_build.py; workflows that stop if the cloned commit differs from the pinned one.
  • CFG-2 Everything after the specification is deterministic and produced by a bot. Comparisons between arms follow DEV-11.Checked by: scripts/test_experiment.py; reports arrive only through the bot's pull request.
  • CFG-3 Settings are the named engine's own settings, read from the simulator. No list of settings is copied by hand.Checked by: a check that every settings list is read from inference-sim fields at the pinned release, and that the repository holds no copy.
  • CFG-4 A specification ranks and checks configurations on any metric of the metric registry, at any scope (§8.7). Examples are the p95 time to first token for the whole cluster, the largest p99 time to first token over instances, and output tokens per second per accelerator for one pool. Every metric it uses has one definition in the metric registry, shared by the search, the ranking and the report (DEV-8), and every figure states its metric, scope and aggregate. A run that is overloaded or incomplete is never ranked (DEV-10).Checked by: a specification check that every metric a specification names is a metric registry identifier and every scope is one the metric registry defines; the report schema, which requires each figure's metric, scope and aggregate; regression tests.
  • CFG-5 A search varies only settings that the engine's field list marks as modelled at the pinned release. A ranking states the least trusted coefficient method and, from the dashboard's published summary at the release its pin file names, the grades of the dashboard points in the lanes it relies on, and a recommendation in a lane with no G1 or G2 points is labelled an extrapolation, with that lane's measured error. A speculative-decoding arm states its acceptance in its scenario (D8). The value comes from the substrate's profiles, read through inference-sim acceptance, which runs D8's lookup, or from another cited measurement. The report names the source and each key field in which it differs from the arm.Checked by: a specification check against inference-sim fields; the report schema.

accuracy-dashboard

Purpose
Show publicly how accurate BLIS is against measured ground truth, and how much of that ground truth it can score, for every engine, engine version, chip, model and layout.
Owns
Fetching InferenceX runs and their recorded configurations for the units the ledger lists; the end-to-end scoring harness, which is the single scorer (COV-1) and turns a measured run into a scenario, runs a given simulator build (a release, or a commit of a branch or pull request) on it, and compares; closeness grades; coverage; the blocker backlog; and the website. The harness is a script, run from a release or from any commit a lower repository pins, and is what whole-run accuracy gates run (§13.5), so a gate and the dashboard compute a figure the same way.
Never holds
Its own list of which settings are modelled; translation of settings, except the stand-ins the engine's estimation rules state; a published figure computed on points used for fitting.
Interfaces
Depends only on inference-sim: its releases for the published dashboard, and the unreleased commits a pull request names for review and gate runs. Its seed-holding jobs also check out blis-substrate/catalog/ledger at the substrate commit the build uses, and compare it with the ledger at the merge base (§3.3), because computing select and held roles needs the full ledger. It gets everything else from seven inference-sim commands:
  • fields prints each engine's field list;
  • ledger prints the published fit and anchor unit lists;
  • catalog resolve maps an InferenceX model or chip name to a catalog identity, or reports that none exists;
  • coverage prints each surface item's deployment count in llm-d's guides;
  • invariants prints the invariant registry at the substrate version inference-sim pins;
  • metrics prints the metric registry (DEV-8);
  • schema prints the JSON Schema of every document BLIS reads and writes, and schema validate checks a document with the Go validators (SUB-19).
It imports no other BLIS module.
Triggers
A simulator release, picked up by the next nightly run (new ledger units and catalog entries arrive this way); predictions files posted by candidate jobs of lower repositories, collected by a scheduled or dispatched workflow on its own main. A new InferenceX dump is scored once the substrate release that embeds its anchor list, and the inference-sim release that pins it, exist.
Correct when
Every anchor unit has a grade, refactors leave scores identical, and coverage and the backlog are published.

Its invariants, COV-1 to COV-12, are in §11.8 with the design they enforce.

6The boundary between engines and the cost model

6.1 The rule

Only an engine repository knows its upstream, so only it works out the engine's behaviour: defaults, backend choices, padding and graph-capture sizes. It hands the cost model three plain objects, described in physical units (bytes, tokens, ranks), never in an engine's vocabulary: a PoolPlan, a StepShape, and, for each KV move, an engine.Transfer whose Move field states the move in physical units: the catalog device, the node and rack that send and receive, and the direction. The cost model never sees an engine's settings and never applies an engine's rules (SUB-6). A release plan may relax this rule for one named input while settings resolution moves into engine lowering: the cost model may read the typed engine-settings carrier (contracts/kernel/enginesettings, Inputs.Engine and the code that reads them) as a transitional input. The relaxation lets nothing new in: no engine default, spelling or backend choice is added to the substrate while it lasts. It ends when engine lowering resolves every setting and the substrate receives only the plan, the step shape, transfers and operation facts. The plan names the relaxation, the date the maintainer approved it and the milestone at which it ends (X2, SUB-6).

6.2 The plan and the step shape

Both are defined in contracts/engine. Every field added later must keep its zero value meaningful, so the contract only grows (SUB-1).

// PoolPlan is fixed for the life of one engine pool.
type PoolPlan struct {
    Hardware string         // catalog chip name
    Fabric   string         // catalog network name; "" on one node
    Layout   Layout         // tensor, pipeline, data, expert and context parallel widths, and rank placement
    Layers   []LoweredLayer // one entry per (layer, primitive)
    KV       []KVGroup      // per cache group: bytes per page, tokens per page, full or sliding window
    KVBudget []int64        // bytes per rank available for KV, one value per pipeline stage
    Tiers    []KVTier       // the KV tiers below GPU memory, as the engine resolved them
    Graphs   GraphPolicy    // CUDA-graph buckets, keyed by token count or by batch size
    Drafts   DraftPolicy    // speculative decoding: chain length, or tree (top-k × steps)
    DraftLayers []LoweredLayer // the draft model's own layers, lowered like the target's
    Host     HostPlan       // per-request and per-token host work (admission, sampling, detokenizing)
    Weights  []ResidentTensor // per rank: shape, stored data type and sharding, after weight post-processing
    Buffers  []Buffer       // engine-chosen buffers and workspaces, sized as the upstream sizes them
    InFlight int            // most steps in flight at once; 1 without asynchronous or overlap scheduling
}

type LoweredLayer struct {
    Layer      int          // index in the model graph
    Stage      int          // pipeline stage that runs it
    Primitive  string       // from a fixed list: "attention.decode", "gemm", "moe.grouped_gemm", "all2all", …
    KernelImpl KernelImpl   // what executes it; the registry looks coefficients up by this
    Shape      PrimitiveShape
    Collective *Collective  // group, bytes, and whether it crosses nodes
}

type KernelImpl struct {
    Library  string         // from a fixed list in contracts, e.g. a named attention library
    Variant  string         // the library's own exported kernel name; an engine's backend names map to it in the engine's lowering
    CodeHash string         // hash of source and target GPU architecture, or of a binary library (below)
}

type KVTier struct {
    Device   string         // catalog device name, e.g. host DRAM, local NVMe
    Bytes    int64          // capacity per rank, as the engine sized it
    Shared   bool           // one copy per node rather than one per rank
}

// StepShape is one engine step on the data-parallel ranks that run in lockstep, already padded by the engine.
type StepShape struct {
    Mode      StepMode      // Mixed, ExtendOnly, DecodeOnly, Verify, Prebuilt or Idle
    Ranks     []RankStep    // per data-parallel rank in the lockstep group: each request's scheduled and cached tokens
    Padded    []PaddedShape // per rank: the shape actually launched (graph bucket, padding)
    Drafts    DraftShape    // tokens drafted and verified this step
    Stage     int           // pipeline stage; 0 without pipeline parallelism
}

Prebuilt is SGLang's P/D decode merge: KV arrived from the prefill pool, so the step runs no forward pass. It still has scheduler and host cost, which the cost model prices.

PaddedShape also records how the step launches: a full CUDA-graph replay, a piecewise replay with some layers run eagerly, or a fully eager run. These have different costs, so the engine states which one applies.

These definitions imply five rules:

6.3 How an engine is built: two phases

An engine cannot size its KV cache without knowing how much memory the model's weights, activations and buffers take. That is the cost model's arithmetic. The cost model, in turn, cannot price a step without the engine's plan. inference-sim breaks the loop by giving the engine an environment when it builds it:

// contracts/engine: what an engine may ask of the substrate while it is built and run.
type Environment interface {
    Graph(model string) (*model.Graph, error)          // the catalog's model graph
    Chip(name string) (*hardware.Chip, error)          // catalog hardware facts
    Memory(partial PoolPlan) (kernel.MemoryBreakdown, error) // weights, activations, buffers; per stage
    Price(plan PoolPlan, step StepShape) (kernel.StepEstimate, error)
    TransferDemand(plan PoolPlan, t plan.Transfer) (kernel.TransferDemand, error) // t is an engine.Transfer's Move; plan is the engine's Plan()
    Acceptance(model, drafter, workload string) (acceptance.Profile, error)
}

// engine.New builds an engine for one pool.
func New(spec Spec, env Environment) (Engine, error)

Here plan.Transfer is the move as the cost model receives it: the tokens moved, the node and rack at each end, the direction, and the catalog device of a tier. It is defined in contracts/engine/plan, with PoolPlan. The simulator takes plan from engine.Plan() and t from the Move of each transfer the engine returns.

Construction then runs in two phases:

  1. Plan. The engine resolves its configuration and lowers the model into a partial PoolPlan, one whose KV budget is still empty. The partial plan carries the engine's own profiling step shape (in vLLM, the shape its memory profiling run uses; in SGLang, the shape behind its reserve formula) and the set of graphs it will capture. The engine states the resident weights and the buffers it chooses in the partial plan. env.Memory sums their bytes, and sizes the activations of the profiling shape and the graph pools. Choosing the profiling shape is engine policy; the cost model only sizes the shape it is given. The engine then applies its upstream's budget rule, such as vLLM's gpu_memory_utilization or SGLang's mem_fraction_static, and fills in KVBudget.
  2. Run. Each step, the engine forms a StepShape and asks env.Price for its time.

inference-sim implements Environment using the substrate's cost model and data loader. Engines therefore import only packages under contracts/, yet run against the released catalog, coefficient registry and cost model. Engine tests use a small reference implementation in contracts/conformance, which prices from fixed test values.

6.4 Step modes and engine differences the contract must allow

DifferencevLLM v0.32.0SGLang v0.5.23Where the contract allows it
Scheduling ahead of the running stepasynchronous scheduling, on by default unless an incompatible option is set (config/vllm.py:1585-1644); up to max_concurrent_batches steps in flight (config/vllm.py:577-587)the overlap scheduler, on by default; turned off by disable_overlap_schedule or by resolution hooks for some configurations (arg_groups/overrides.py, speculative_hook.py, platform_hook.py)the in-flight depth in PoolPlan; StepShape placeholders for tokens the running step has yet to produce
What one step containsprefill and decode tokens mixed in one stepa step is prefill-only or decode-only; mixing needs enable_mixed_chunk, which is off by default (arg_groups/fields/schedule.py:208-211)StepShape.Mode
Speculative verificationverify tokens inside a mixed stepa separate TARGET_VERIFY stepMode = Verify; Drafts; DraftLayers
P/D mergenonedecode merges a batch whose KV arrived from prefill, with no forward pass (PREBUILT)Mode = Prebuilt, priced at no compute
KV page size16 tokens unless the attention backend prefers another size (vllm/config/cache.py:72; vllm/platforms/interface.py:750-780)1 token on CUDA unless the attention backend forces another size (arg_groups/overrides.py:1143-1200, 1340-1363)KVGroup in bytes and tokens per page
CUDA graphsbuckets by token countdecode buckets by batch size; "breakable" prefill graphsGraphPolicy; Padded
Prefill/decode disaggregation (P/D)NIXL: decode reads KV from prefill by default; a push mode also exists (nixl/connector.py:9-12)by default decode reserves space early (disaggregation_decode_kv_reservation), a handshake gates prefill, prefill pushes KVTransfer fields for who moves the bytes, reservation and handshake
KV events for the routerone BlockStored event per newly cached run of full blocks, carrying a list of block hashes; partial-block BlockStored events for prompt tails when the hash block size differs from the block size; BlockStored events for blocks reused from the prefix cache only when the request's report mode is full (the default is incremental); one BlockRemoved per evicted hash (v1/core/block_pool.py, v1/core/kv_cache_manager.py:300-310)one event per page, merged into one event with several hashes for each run of pages that continues a parent chain (mem_cache/events.py:140-170). llm-d's index groups tokens into 16-token blocks by default. It indexes whole blocks, and skips a stored run whose parent ends part-way through a block (inferred; pkg/kvcache/kvblock/token_processor.go:35, 315-327; pkg/kvevents/pool.go:639-641)engines emit exactly as upstream (SGL-5)
Memory budget settinggpu_memory_utilization, default 0.92mem_fraction_static, derived from a reserve formulaKVBudget in bytes, resolved by the engine

7Fidelity to upstream code

7.1 What an engine port contains

An engine repository ports all of its upstream's code that decides what each step runs and what each request's KV cache costs. That code falls into four parts. Each surface-list item (§7.5) has one primary part, the one that owns most of its decisions. A feature whose code spans parts, such as speculative decoding, LoRA or HiSparse, is still one item, and it names the other parts it touches. The table names each upstream's classes at the release the engine repository pins; the names change between releases, the four parts do not.

PartWhat it decidesvLLM, at the pinned releaseSGLang, at the pinned release
Schedulingwhich requests run in each step, how many tokens each gets, and when a request is preempted or finishedScheduler and AsyncScheduler; the engine-core loop and its batch queue; data-parallel coordination; structured-output and LoRA admission; speculative proposals and their acceptancethe scheduler's event loops, normal and overlap; PrefillAdder; scheduling policies; retraction; speculative proposals and their acceptance
KV-cache managementhow a request's KV cache is laid out, shared between requests and freed: which blocks or pages it holds, which prefixes it reuses, and what is evictedgrouping the model's layers into KV-cache groups and sizing the pool at start-up; KVCacheManager and its coordinators, which run one manager per group; BlockPool; the managers KVCacheSpecRegistry maps each KV-cache spec type or group role to, one manager serving several spec types; KV eventsthe tree caches SGLang's selection code can choose: by default the unified radix cache, with its tree cores (Python and Rust, chosen through a tree-core registry) and its components for full-attention, sliding-window and Mamba layers; the other built-in tree caches; any backend registered with register_radix_cache_backend; the request-to-token pool; the token, paged, sliding-window and sparse allocators; the memory pools for each attention layout; KV events
KV transfer and tieringwhen KV moves between GPU memory, host memory, storage and other machinesthe connector framework and the connectors registered with KVConnectorFactory; the offload spec, manager and tiering layersthe HiCache controller, host pool and storage backends; the disaggregation transfer backends
Resolution and loweringthe settings the engine actually runs with, which kernel implements each operation, and the shape of each launched batchconfiguration resolution; attention-backend and kernel selection; CUDA-graph capture sizes and the padding of each launched batchServerArgs resolution hooks; backend selection; CUDA-graph capture sizes and padding

Metrics are outputs of the parts that produce them. Each part is ported framework first (§7.6). In vLLM, the KV-cache framework is the layer grouping, the coordinators and the manager base class; in SGLang, it is the tree-cache interface and the allocator and memory-pool base classes. Each further spec type, tree cache, allocator or pool is then a small change on top. The surface list has a category for each part, so a model or configuration that needs an unported manager, tree cache or pool is refused by name and counted, instead of failing inside the engine. The parts are tested by these tiers (§7.4): scheduling by T2 and T3; KV-cache management by T2; transfer and tiering by T4 and T5; resolution by T1; lowering by T0, by the comparison of priced with launched work (FID-11) and by T3's padding comparison. T6 checks the metrics and KV events of every part, and T7 covers all four.

7.2 The method

"Faithful to vLLM" means: give vLLM's own scheduler and BLIS's Go port the same inputs, and they make the same decisions at every step. To run vLLM's scheduler without GPUs, a test harness replaces only its environment. Everything else is vLLM's own code at a pinned commit. Figure 3 shows the arrangement.

scenariofixed by hash BOUNDARY (manifest of replacements, versioned) upstream Python codescheduler, KV-cache manager, connectors,tiering, metricspinned commit, locked packages GPU: tokens from scenario clock: step number transport: done at step k environment: pinned Go portblis-vllm or blis-sglang recordedtrace (reviewed) Go trace comparefirst step,first field
Figure 3. The oracle method. Inside the dashed boundary everything is upstream code, except the replacements in the manifest (GPU, clock, transport, environment, the platform, and outside services such as a tokenizer) and any code the manifest declares as transcribed. A recorded trace changes only in a commit marked as reviewed (FID-3).

7.3 The boundary for each upstream

UpstreamReal code under testReplacedCompared each step
vLLM v0.32.0Scheduler.schedule() and update_from_output(), and the AsyncScheduler used under asynchronous scheduling; KV-cache manager and block pool; NIXL and offloading connectors; offload tiering; engine-core batch queue; data-parallel load balancing and wave sync; metrics. The data-parallel busy loop itself is transcribed, because it blocks on network queues (oracle/run_dp.py:23-30). A transcription is listed in the manifest under its own kind, transcription, not as environment. Decisions made inside it are checked against the transcription, not against vLLM, and are reported separately.The kinds listed in oracle/mocks.json: GPU, clock, transport, environment, platform and transcription. The oracle runs vLLM's CPU build, whose platform changes the configuration as it resolves (vllm/platforms/cpu.py: it turns async scheduling off, empties the CUDA-graph capture sizes, changes the compilation mode, sets the default block size to 128 when none is given, and forces block sizes for MLA and DeepSeek-V4). Every such change that applies to a scenario is listed in the manifest as a platform replacement, and the oracle restores the value the CUDA platform would resolve. Each entry names the site it replaces.requests scheduled, running, preempted and finished; blocks used and free; KV usage; queue lengths; each request's tokens, finish reason and finish step
SGLang v0.5.23Scheduler.get_next_batch_to_run() and process_batch_result(); PrefillAdder and scheduling policy; the radix cache (Python tree core); token and paged allocators; the prefill budget; decode retraction; the overlap scheduler; HiCache controller; disaggregation queues with the fake transfer backend; KV-event emitter; metrics.GPU (the model worker's forward pass); clock (time.monotonic and perf_counter); transport (the fake backend completes at a scenario step); environment (pinned flags, tree-core backend); outside services (tokenizer).all of the vLLM fields, plus: step mode; extend and prefix lengths; the chunked request; retracted requests; new_token_ratio; tree-cache hits and evictions; free pages; HiCache moves; disaggregation queue states
llm-d v1.0.0 (the fork's pinned release)All router decision code reachable from pkg/embed: filters, scorers, pickers, admission, flow control, metric extractors, the KV-event indexer (including the SGLang adapter), request parsers. Also the P/D sidecar (RTR-5).Through the runtime interface: clock, timers, deadlines, goroutine start, blocking waits, random streams, map order, network calls, KV-event intake.simulated-runtime decisions equal default-runtime decisions on recorded inputs

7.4 Test tiers

Every engine repository uses the same tiers. A trigger's playbook names which tiers must run again.

TierWhat is comparedWhen
T0Model weights: the resident weights the engine reports in its PoolPlan (per-rank shape, data type and sharding) equal what the upstream allocates, found by building the upstream model on meta tensors and running each layer's weight post-processing (vLLM's process_weights_after_loading) with the quantisation method the engine selects for the chip, so the compared shapes are the resident ones. A layer whose post-processing cannot run on meta tensors, because it calls a CUDA kernel to repack weights, is listed in the manifest with the shape rule used instead, at each supported tensor-parallel width. Beside it, for every oracle step shape, the priced work is compared with the launched work (FID-11).when a substrate release changes the catalog, in the substrate's candidate job for each model the change touches; when an engine moves to a new upstream release; and on an engine pull request that changes lowering or weight post-processing, for the models it affects. Never on other pull requests, because it builds every model in the upstream
T1Configuration: fields accepted and rejected, defaults resolved. Includes the inertness proofs. A field is "proven inert" when two things hold at its default and at each probe value: the scenario set produces identical traces and identical kernel choices, and code coverage shows that every upstream function that reads the field actually ran. The second condition stops a field from passing only because no scenario reached the code that uses it. A field read only by code the manifest lists as a GPU replacement cannot run on the oracle: it is either priced through the plan, if it changes the PoolPlan or StepShape, or proven inert by a reviewed static argument. A reviewed static argument also proves a field inert when no value of it can change a decision, a kernel choice or whether a configuration is accepted on the platform the oracle restores (FID-1). Two cases qualify. In the first, every read of the field lies inside a surface-list item marked refused or out of scope, and that item is entered only through a setting BLIS refuses by name (X1). In the second, every path from a read of the field to a use of its value passes a platform check that is false on CUDA. A read in shared code without such a check is not covered, even when the variable is named for another platform; it needs the dynamic proof. The argument names every read and the item or check that makes it unreachable, and fields.json records that item or check with the field. The completeness check fails when the item becomes modelled, or when a new upstream release changes a named read or check, so that the field is classified again. The engine repository's owners review the argument.every pull request
T2Scheduler and KV cache decisionsevery pull request
T3Engine loop: asynchronous or overlap scheduling, data and pipeline parallelism, and launched-shape padding (compared with the upstream's own padding functions)every pull request
T4KV connectors and P/D protocolsevery pull request
T5KV tiering: offload, eviction and promotion across tiersevery pull request
T6Metrics and KV events, byte for byte with what llm-d reads, after the manifest's replacement of the batch timestampevery pull request
T7Mutation sweep: every deliberate one-line break of a ported function is caughton a pull request, the changed functions and those covered by any changed test, with the tests that cover them; the full sweep nightly and on release tags

7.5 The surface list: making gaps visible

The tiers test only ported code. To show what is not ported, each upstream-tracking repository generates a surface list (surface.json) from the pinned upstream's source. The generator reads the upstream's own plugin registration tables, so a new feature in an upstream release appears in the list without anyone remembering to add it.

UpstreamWhat the generator reads
vLLMconnectors registered with KVConnectorFactory (every registered name, with aliases, where one name maps to another's class, recorded as such); offload and tier types; KV-cache spec types and group roles registered with KVCacheSpecRegistry, read after its registration function runs, and the manager each maps to; speculative methods; scheduler policies; configuration classes. Metrics are not held in one registration table in vLLM; the generator finds every Prometheus metric constructor in the source.
SGLangtransfer backends (disaggregation/utils.py TransferBackend); HiCache storage backends; tree caches the selection code can choose, including those registered with register_radix_cache_backend; tree-core backends; allocator and memory-pool types; speculative algorithms; scheduling policies; ServerArgs groups; metric families; event types
llm-dregistered plugin types; P/D connector types; per-engine metric mappings; KV-event adapters

Each item has one status (X9). The aim is that every item the llm-d stack uses is modelled. The other two statuses are exceptions, and each needs evidence a reviewer can check:

"Hard to port", "rarely used" and "needed later" are not reasons for either exception; they are priorities, which the weight and the backlog already express.

Each item also has a weight: how many distinct deployments in llm-d's published guides and model recipes use it, and how many accuracy-dashboard points it blocks. surface.json holds only statuses, because an engine repository cannot depend on the guides or the dashboard (X7). The guides commit is pinned once, in inference-sim's guides.lock. Each release records it in stack.json, and inference-sim coverage publishes each item's deployment count. The accuracy dashboard, which sits above every other repository, adds its own blocked-point counts to give the weights, and opens an issue against the engine repository for any out-of-scope item whose weight is not zero. The engine then marks the item refused. The count parses each rendered manifest's engine arguments with that engine's own argument parser. The weighted share of modelled items is published with every release (SIM-5), and an engine release may not lower it (X9). The list is also the reading list for an upgrade: the difference between two releases' lists is the new work.

7.6 Port a shared framework before the features built on it

Connectors, tiers and KV-cache managers are built on shared frameworks. In vLLM these are the connector base class; the offload spec, manager and tiering layers; and the KV-cache coordinator with the manager base class, which every spec type's manager extends. In SGLang they are the HiCache controller and storage interface, and the tree-cache interface with the allocator and memory-pool base classes. Porting one framework completely, with recorded traces of its contract, makes each further connector, tier or manager a smaller change. Without it, each one is a large port of its own. Engine repositories therefore port a framework as a unit before porting the features built on it.

A framework's interface is the abstract class or classes that its features implement or call. For the connector base it is the connector base class, for offload tiering the offloading manager and spec, and for HiCache the storage interface. A framework's traces are taken at that interface and are of two kinds. A recorded trace is the sequence of interface calls that a real feature makes while the oracle runs a scenario. The port receives the same calls, and its answer to each is compared with the upstream's. A direct trace is a sequence of calls made by a driver that the engine repository writes, including orderings that no existing feature makes. Recorded traces keep the call mix realistic. Direct traces test the contract itself, not one feature's use of it. A part of a framework that reads the clock is traced with the clock replacement that FID-1 lists. When only some parts of a framework have traces, the surface list holds the parts as separate items, each with its own status.

7.7 Fidelity invariants

8Simulation method

BLIS is a discrete-event simulator: it moves simulated time from one event, such as a request arriving or a step ending, to the next. Between events, state either stays fixed or changes at a known constant rate. This section states the rules such a simulation must follow, whatever engine it runs. It takes its terms from DEVS, the Discrete Event System Specification introduced by Bernard Zeigler. The rules cover which part of the system may change state and when, what happens when two events fall at the same instant, how random numbers are drawn, and what a run measures. The table gives each term with its meaning in BLIS.

TermMeaningIn BLIS
component (atomic model)a part of the system with its own statean engine instance, the router, a link, a client (one source in the workload generator)
coupled modelcomponents connected so that one's output is another's inputa pool, the cluster
time advancehow long until the component's next event, given its statethe time of an engine's next scheduled wake-up
internal transitionthe state change when that time arrivesa step ends
external transitionthe state change when a message arrivesa request arrives at an engine
confluent transitionthe state change when both happen at the same instanta request arrives exactly as a step ends
output functionthe messages a component sends, just before an internal or confluent transitiontokens, metrics and KV events an engine emits
experimental framewhat a run feeds in, when it measures, and what it measures§8.6

8.1 Components and how they couple

Each component changes its state only through its three transitions, and sends messages only through its output function. The output function runs on the component's state just before an internal or confluent transition, and every message it sends carries that instant's time. An external transition sends nothing. A component that must answer a message at once, as the router answers a request, schedules an internal event with a time advance of zero and sends its answer then.

Between events a component's state is constant, or changes at a constant rate. A link is the one component of the second kind: its state is the set of transfers in progress and the bytes each has left, and its time advance is the earliest time a transfer finishes under the current sharing of the link. Every start or end of a transfer, and every step that starts or ends a cross-domain collective, is an event that recomputes the rate of every other transfer on the link.

Components interact only through messages. No component reads another's state directly. The router learns about an engine only through the metrics and KV events the engine sends, at the times it sends them, after the delay the deployment's scrape and event-delivery settings impose. This rule lets each component be tested alone and then together.

Within a component, nothing depends on goroutine scheduling, the system clock or the order of iteration over a map. Concurrency inside a component, such as the router's background work, runs on the simulated runtime, which serialises it (RTR-7).

8.2 One instant

Many events fall at the same simulated instant, for example a step ending as a metrics scrape is due. BLIS processes an instant in the way Parallel DEVS defines, in three phases:

  1. Every component whose internal event falls at this instant runs its output function.
  2. Each component receives the bag of every message addressed to it at this instant. BLIS sorts the bag by the key below, so a transition that reads it in order is deterministic.
  3. Each component then makes exactly one transition: confluent if it had both an internal event and messages, internal if only the first, external if only the second.

So no component sees another component's state change from the same instant before making its own. The messages a component receives at one instant are ordered by a key: (time, class, sender, the sender's own count of messages sent). The class is a fixed order of message kinds, such as "transfer complete" before "new request". Each class's position is stated with its reason, and a change to the order is a change to the model (§8.9). The key contains no global counter, so it depends only on what each sender has done, not on the order in which the simulator happened to schedule things. Two messages with the same key are a defect.

A component's confluent transition says what it does when its own event and a message coincide. For an engine, this follows from where the upstream's loop reads its input queue, not from a choice BLIS makes, except where the upstream's order is a race (below). A request that arrives while a step runs is an ordinary message: an external transition holds it until the engine next reads its input. When that is depends on the engine and its settings. Under vLLM's asynchronous scheduling, on by default, the next batch is scheduled while the current one runs, so a request arriving during step N usually first joins step N+2, because step N+1 was scheduled when step N started. Without it, the request can join step N+1. Each engine declares, for each kind of external input, the step boundary at which it takes effect, whether it arrives during a step or at the instant one ends. Where the upstream's order is a race between threads, the declaration says so and names the order BLIS uses.

A value one component publishes for others, such as an engine's metrics, carries its time and a publication sequence number, and a reader that caches it keys the cache on both.

8.3 Time and progress

Simulated time is an integer number of microseconds. A step time the cost model prices is converted to microseconds once, by one rounding rule. Two events that rounding makes simultaneous are ordered by the key (§8.2). Arithmetic whose result reaches the order of events or the Result is written so that it gives the same bits on every supported platform (X6).

Simulated time must advance: only finitely many events may happen in any finite stretch of simulated time. Every cycle of components that can pass messages around in zero time contains a component with a positive time advance, or a declared bound on how many times the cycle can repeat at one instant. An engine step that changes no state is an error unless the engine scheduled it as a wake-up, or it is an idle or dummy step the upstream itself runs, such as vLLM's dummy batches on idle data-parallel ranks of a mixture-of-experts model, which the engine declares.

A component changes its own next event only by recomputing its time advance after a transition. The event it no longer wants is marked stale, by a generation number, and dropped when it reaches the front of the queue. It cannot affect the result, wherever it sits in the queue.

8.4 Random numbers

Every random draw comes from a substream named by component, purpose and entity: for example, client 3, prompt length, request 17. The substream's seed is the SHA-256 of the scenario seed and that name, and the generator gives streams for different seeds that can be treated as independent. Its name and version are part of the experimental frame, because a new generator changes every draw. So request 17 of client 3 gets the same prompt length whatever happened to requests 1 to 16, in every configuration.

This is the basis of common random numbers: two runs that differ only in configuration give the same requests the same random inputs. Their outputs are then usually positively correlated, so the difference between them varies less from seed to seed and can be estimated with fewer runs. It does not make the difference free of noise. Some draws depend on how the system responds, for example a retry after a timeout, or a router's tie-break. They use their own substreams, and do not line up between configurations. Comparisons therefore still need replications and intervals (§8.8).

8.5 Input models

Every distribution the workload generator draws from names its family, its parameters, how they were estimated, and the trace they were estimated from, and passes a stated goodness-of-fit test against held trace data. Where a dependence matters, it is modelled, not assumed away: output length against prompt length, the burstiness of arrivals, and the structure of sessions.

A trace records the causes of each arrival, not only its time: which session a request belongs to, and how long the client waited after the previous response. Replaying a trace under a different configuration recomputes the arrivals that depend on earlier completions. A replay of fixed timestamps has no random input, so its figures have no replication interval unless the trace is resampled.

8.6 The experimental frame

A run measures something only relative to its experimental frame. In BLIS the frame is one document, recorded in every Result:

A run that fails its acceptance conditions produces no figure. Running a scenario, exporting its trace and replaying the trace under the same configuration gives an identical Result. Converting another tool's workload into BLIS's preserves, phase by phase, the offered load (requests arriving per second), how variable the gaps between arrivals are, the joint distribution of prompt and output lengths, and the session structure; each converter is checked against its source with two-sample tests.

8.7 What a metric means

Every metric that is reported, compared, ranked or used in a service-level check has one definition, with an identifier, in the metric registry in contracts/ (DEV-8): its formula, its statistic (a mean or a percentile), its population (requests, tokens or steps), its window and its unit. The metric registry includes latency (time to first token, time per output token, inter-token latency, end-to-end latency, scheduling delay and queueing delay), each with its mean and the percentiles a report needs. It also includes throughput (requests, output tokens and total tokens per second; the token rates also per accelerator), interactivity (output tokens per second per request), goodput, and service-level attainment. Time to first token, time per output token and inter-token latency follow the definitions of the benchmark clients BLIS is compared with: time per output token excludes the first token and is averaged per request; inter-token latency is pooled over tokens. Two figures are compared only when their definitions, scopes and aggregates are all the same, and a definition is never inferred from a field's name.

A figure is a metric at a scope: the whole cluster, one pool, or one instance. The scope selects which requests, tokens or steps form the population; the formula, statistic, window and unit are unchanged. A figure that aggregates over several scopes, such as the largest p99 time to first token over instances, names the scope it ranges over and the aggregate. Two figures are compared only when their metric, scope and aggregate are all the same (DEV-8). A report or ranking states the metric, scope and any aggregate of every figure it shows.

A request's fate is how it ended: completed; dropped, with a reason; timed out; in flight at the horizon; queued at the horizon; or not started. It is written once, at one place in the code, and every latency sample is derived from it. A request that emitted no token gives no token-latency sample. The generator keeps its own count of arrivals, and conservation compares that count with the sum of fates, so that a counting error in the fates cannot be hidden by the same error elsewhere.

Requests in flight or queued at the horizon have not finished, so their latency is unknown but at least what they have waited. A latency statistic states how it treats them: excluded, with their count reported beside it, or counted at their waiting time so far as a lower bound. A percentile is not reported when more requests are unfinished than the percentile leaves room for.

8.8 Output analysis

A simulation's outputs are estimates, and are analysed as estimates. A run is either terminating, measuring a fixed amount of work such as a benchmark of a set number of prompts at a set concurrency, or steady-state, measuring a system that has settled.

Replications start at five and continue until the interval is narrower than a stated fraction of the estimate, or a stated limit is reached, in which case the width reached is reported. A comparison of two configurations is made on the differences between paired replications that share common random numbers (§8.4), and has three outcomes: better, worse, or not distinguished. One configuration is better only when the whole interval of the difference lies beyond a stated margin. Not distinguished includes the case called equivalent, where the whole interval lies within the margin. A search that compares many configurations controls the chance of a false winner across all its comparisons, and re-estimates the winner on fresh seeds, because the best of many noisy estimates is biased upward.

This interval covers the randomness of the workload. The model's own uncertainty is separate: its coefficients have intervals (VAL-10) and its methods have measured errors (VAL-5). A figure states which uncertainty its interval covers. A recommendation is reported with a sensitivity run, in which the coefficients it depends on most are moved across their intervals, and states whether the ranking changes.

A deterministic run is reproducible, not accurate. Where a run has random input, one run gives an estimate but no interval for it. Byte identity shows that a refactor changed nothing; it does not show that a figure is right.

8.9 Verification and validation

BLIS checks two different things. Verification asks whether the code does what the simulation model says (here "model" means BLIS's description of the serving system, not an LLM): the oracle tiers (§7.4), the conservation of requests, determinism and the byte gate. Validation asks whether the model matches the real system: held-out accuracy against measured benchmarks, through the dashboard's scoring harness (§11.7).

A change is a verification change when it must leave the model's behaviour unchanged: a refactor, a speed-up, or a fix that makes the code match an oracle or a written rule. It is a validation change when it changes what is modelled: a pricing form, a coefficient, how links are shared, an input model, a timing rule such as the scrape delay, or the order of classes at one instant. The class follows from what the change does, not from the repository it is in. A change that is both is split. A verification change that moves a published figure, such as a fix that makes the port match its oracle, carries the evidence X13 asks of it.

The model's validity domain is the range of models, chips, layouts, concurrencies, sequence lengths and replica counts its held-out data cover. Every Result states whether its scenario lies inside it. Behaviour that appears only at cluster scale, such as router decisions across many replicas or contention on shared links, is verified against upstream code but not validated until measured cluster runs exist. Where measurements at several sizes exist, the simulated curve across sizes is compared with the measured curve, not only single points. When BLIS models a policy, such as an autoscaler or a saturation detector, its output states whether that policy has been validated, and against what.

8.10 Invariants

9Getting a value for every coefficient

9.1 The aim

The cost model needs a coefficient for every primitive on every chip: an attention rate, a GEMM efficiency curve, a collective's latency. Fitted measurements will never exist for every combination. If a missing coefficient caused a refusal, coverage would fall even where a sound estimate exists.

The aim is that every primitive gets a value, and every value records how good it is.

9.2 Sources, in order of preference

For each lookup (primitive, kernel implementation, chip, and where relevant data type and group size) the cost model uses the entry with the smallest rank number:

RankMethodWhere the value comes fromExample
1measuredfitted to microbenchmarks of this exact kernel implementation on this chip, with held-out error reportedattention decode rate for one library on H200, fitted to AISimulate sweeps
2calibrateda formula with physical structure, such as a roofline, whose one or two efficiency factors are fitted to composite data (end-to-end runs or whole forward passes) for this implementationa GEMM ramp shaped by FLOPs and bytes, with a fitted efficiency ceiling
3transferredmeasured on a closely related implementation or chip, and moved here by a stated rule, such as a ratio measured on a third chipan all-reduce coefficient measured on H100, moved to H200 by the ratio of the two chips' measured link bandwidths
4literaturea figure from published work: a paper, a vendor whitepaper, or a library's benchmark, cited exactlyan achieved bandwidth fraction reported by the GPU kernel's authors
5vendor_speca datasheet figure used directly, nominal rather than achieveda link's rated bandwidth, before any efficiency is measured
6rooflinecomputed from catalog facts alone: the larger of FLOPs over peak compute and bytes over peak bandwidth, times a default efficiency taken from the same primitive family on other chipsa new chip with datasheet figures but no measurements
7assumeda stated assumption with a reason, such as an upper bound"charge at most 5 µs of host time per request for scheduling", with the reason for the bound

A kernel whose source did not change needs no transfer: it keeps the same kernel hash, and so the same entry.

Rank 6 needs a FLOP and byte formula for the primitive and at least one measured chip in its family to borrow an efficiency from. Most compute and memory primitives have both. Some do not: host CPU overheads, collective latency floors, and the routing imbalance of expert all-to-all. For those, and for any brand-new primitive family, the substrate MUST carry a rank-7 assumed value for every catalog chip, so that the cost model can always produce a price (VAL-2). Only the trust in that price varies, and the point's grade (§11) reports it.

How to tell measured from calibrated. A value is measured when its data units are microbenchmarks of exactly this kernel implementation on this chip. It is calibrated when its data units are composite, such as end-to-end runs or whole forward passes, from which a factor of a structured formula is inferred.

Every value is stored. Every value of every rank, roofline values included, is a coefficient registry entry written by a writer script. The cost model only looks values up. That keeps every price traceable to a committed entry.

9.3 How the quality of a less trusted value is tracked

Extrapolation. A fitted entry records the range of inputs its data covered (VAL-10). A lookup outside that range is extrapolated: it keeps the entry's value, but for VAL-3 it ranks with transferred, so the price and the point's grade show that the value is used where it was never measured.

9.4 Invariants

10The data ledger

10.1 Sources

Three external sources feed BLIS:

Each source is useful for several purposes. The ledger lets each source be used for all of them, while making sure no single data point is used for two purposes that must stay apart.

10.2 Units, identities and roles

A unit is the smallest piece of data the ledger assigns a role to:

Its identifier, unit_id, is the SHA-256 of a canonical JSON object holding only the source's own identifying fields. The JSON is UTF-8, with sorted keys and no spaces. Because nothing from BLIS enters the identifier, a BLIS change can never change a unit's identity.

SourceIdentifying fields
InferenceX end-to-endsource, plus ten fields: model, hardware, precision, framework, benchmark type, configuration hash, recipe fingerprint, concurrency, measured date and benchmark run.
The configuration hash is the SHA-256 of the canonical JSON of 17 fields of the run's recorded serving configuration, in InferenceX's own names: image, isl, osl, offload_mode, spec_method, disagg, is_multinode, prefill_tp, prefill_ep, prefill_dp_attention, prefill_num_workers, num_prefill_gpu, decode_tp, decode_ep, decode_dp_attention, decode_num_workers and num_decode_gpu. All 17 keys are always present; a value the record lacks is JSON null, and integers and booleans keep their JSON types. The image is written with any suffix beginning @sha256: removed, because InferenceX records the digest on some attempts of a run and not on others; the digest is kept on the unit as a recorded field.
The recipe fingerprint is the one InferenceX records, or null where it records none.
The benchmark run is the numeric GitHub Actions run id in the record's run_url, without its attempt number, so the attempts of one run are one benchmark run, and two runs of one deployment at one concurrency on one date are two units. A record with no run id has the benchmark run null, and its measured date is its own date. Otherwise the measured date is the earliest date among all records of the benchmark run, so it is the same for all its attempts.
The dashboard takes each point's identifier from the ledger's unit list, so each point's identifier is its unit's identifier.
CollectiveX, OperatorXsource, run id, operation, configuration, sweep point
AISimulatesource, file path, git blob hash, sweep point
Forward-pass datasetsource, dataset revision, model, chip, layout, regime

Each unit has exactly one role:

RoleMay be used to
fitfit a coefficient
selectchoose between candidate methods or forms
validatemeasure a method's typical error (VAL-5); sanity checks
heldjudge an accuracy change at its landing gate
anchorreport public accuracy on the dashboard
reportcontext only, never a number

How roles are assigned

A unit's role is computed, not stored. Units that are near-copies of each other must share a role, or one could be fitted while its near-copy judges the fit. So the role is drawn per role key, not per unit. For InferenceX the role key is the source plus model, hardware, precision, framework, benchmark type and configuration hash. It leaves out the recipe fingerprint, concurrency, measured date and benchmark run, so one deployment's whole sweep and its re-measurements share one role, whether or not each record carries a fingerprint. Every field of the role key is an identifying field, so a unit's identity fixes its role key. For CollectiveX, OperatorX and AISimulate it is the run or file, so a whole sweep shares one role. For the forward-pass dataset it is the source, revision, model, chip and layout. Legacy units are the exception: they keep their legacy side (below). The rule is HMAC-SHA-256(seed, role_key) mod 100, compared with cut-points fixed per source in the ledger. For example, one source might send 0–29 to fit, 30–39 to select, 40–49 to validate, 50–79 to held and 80–99 to anchor. For a unit on a legacy side, the same value is compared with two more cut-points fixed per source, one dividing fit from select and one dividing held from anchor. The HMAC's message is the canonical JSON of the role key, and its digest is read as a big-endian unsigned integer. The seed is 32 random bytes from a cryptographic generator. It is a CI secret, and only its SHA-256 is committed; with 256 bits of entropy, the seed cannot be recovered from its hash. Who generated it, when, and who owns its rotation are recorded in the ledger.

What the ledger commits, under catalog/ledger/:

Units come from the source, not from the pull request. The unit list is generated by the ingest tool from the pinned source revision, which puts every identifying field in one canonical form. CI regenerates the list and requires it to match byte for byte. Each measurement belongs to exactly one unit. The ingest tool recognises a measurement by the SHA-256 of the canonical JSON of the record's measured values (for InferenceX, its metrics object). A record that reports an error, or whose measured values are empty, is not a measurement; it is left out and counted. The conclusion of the benchmark run, such as a failed or cancelled matrix, is recorded on the unit and does not remove it, because a run that fails in one job still writes real measurements from the others. A source sometimes stores one measurement as several records, as when each attempt of a benchmark run becomes its own row. Records with the same identifying fields and the same measured values are one unit: the ingest tool keeps the record with the smallest source row id (for InferenceX, benchmark_results.id; for a file source, the path and then the line) and lists the others as its duplicates. Because the records agree on every identifying field and every measured value, the choice cannot change the unit's identifier or role. It decides only which record's other fields, such as the image digest and the server log, the unit carries; a field only a duplicate records is taken from the duplicate with the smallest row id that records it. Records with the same identifying fields and different measured values are one unit holding each measurement, and their spread counts as the measurement's own noise (COV-10). The same measured values under two identities with one role key are one unit, under the identity of the record with the smallest row id. Under two identities whose role keys differ, they are left out, because they could let one measurement draw two roles. The ingest report counts every record merged, joined or left out. So a pull request cannot re-add a known measurement under a new unit_id to get a new role draw.

The ledger never commits the role of a select, validate or held unit. At each substrate release, a seed-holding release job writes the three published lists, catalog/ledger/published/fit.yaml, anchor.yaml and report.yaml. These three files are runtime data and are embedded with the rest of the catalog. The rest of the ledger is not.

Who may see which roles

InferenceX data are public. If every role were written down, anyone could work out which units are held out and tune a change against them. So the roles are revealed only as far as each job needs:

RoleWho learns the members
fitPublished with each substrate release, as a list of unit identifiers. Fitting scripts, people and agents read it.
anchorPublished, because the dashboard shows these points.
selectOnly the select job, which scores candidates on these units and returns aggregate results. It runs in blis-substrate for operator-level candidates and in accuracy-dashboard for whole-run candidates.
validateOnly the error job, the nightly job that measures method error (VAL-5), which returns aggregates. It runs in blis-substrate for operator-level methods and in accuracy-dashboard for whole-run comparisons.
heldOnly the gate job, which returns aggregates (D7). It too runs in blis-substrate for operator-level gates and in accuracy-dashboard for whole-run gates.
reportPublished; these units never produce a number.

For a unit whose role key holds no legacy unit, the published lists do not separate select, validate and held, so a change cannot be tuned against the held units without access to the gate job. Units on a legacy side, legacy or new (below), are different. The old FIT and HELD labels and the rules that assign a side are public, so the fit-side units not on the published fit list are exactly the select units, and the held-side units not on the anchor list are exactly the held units. The gate therefore reports every held unit on a legacy side as a separate figure, marked as not blind, for as long as such units are used.

Pull-request code never sees the seed

A candidate is code the pull request controls. If it ran in a job that holds the seed, it could read the secret, or leak which units are held through its output. So every scoring run has two jobs. A candidate job, with no secret, fetches its modules and the pinned scoring harness, then runs the candidate with no network on every unit not on the published fit, anchor or report lists, and writes one prediction per unit. The seed-holding job runs only scorer code from main, computes roles, and reduces the predictions to aggregates. Each aggregate covers at least 10 units; a smaller lane is merged with its neighbours or not reported. A candidate could still try to learn which units are held, by corrupting its predictions on chosen units and watching the aggregates move. So held-unit aggregates are returned only for a pre-registered change, or by the nightly and pre-release gate runs, which are counted and reported as such; every evaluation is counted (below). Pull-request jobs without a pre-registration may read only the published fit units, through the dashboard's scoring harness (§11.7). Their figures inform review and are never landing evidence. Anchor units are scored only for the published dashboard, so no change can be tuned against the figures it reports.

Each pre-registration commit gets one evaluation on held units. Any further evaluation of the same change is counted, and the count is shown on the pull request and in the release notes, so repeated tuning against held units is visible.

Three properties of the rule

When a partition version is retired, its seed is published. The committed hash then lets anyone check that the split was drawn as claimed.

InferenceXdump tag + hash; run id AISimulategit commit forward-pass datadataset revision ledgerunit_id from sourcefields onlyrole rule:seed + cut-points fit select validate held anchor coefficient registry writers choosing methods and forms method error (VAL-5) landing gates (VAL-6) accuracy dashboard D2: no unit has two roles
Figure 4. One role per unit. Roles are computed from the seed inside the jobs that hold it; only the fit, anchor and report lists are published. Candidate code runs without the seed; only scorer code from main runs with it. Every consumer records the units it read, and a nightly join inside the seed-holding job fails if any unit was read under two roles (D2).

10.3 Using each source fully

Sourcefitselect and validateheld and anchor
InferenceXend-to-end terms; CollectiveX and OperatorX seriesa separate share of end-to-end role keysheld: landing gates. anchor: the dashboard. Both include SGLang deployments.
AISimulateoperator coefficients per kernel implementation, including the 313 files collected under SGLang, for kernels SGLang runsheld-out sweeps (whole files); cross-lane comparisons for transferred valuesnone: operator-level only
Forward-pass datasetcalibrated composite terms, such as how much compute and communication overlap within a stepcells kept apart for choosing forms and for measuring method errornone

10.4 Invariants

11Accuracy coverage

When inference-sim releases, the accuracy dashboard scores the new release at its next nightly run, with no change to its own code. The dashboard scores every anchor unit, and grades how close each comparison is. InferenceX often runs older, nightly or custom engine builds, so a perfect comparison is not always possible.

11.1 Why settings must not be classified by the dashboard

A dashboard that keeps its own table of which engine settings are modelled will disagree with the engine. The engine changes, and the table does not. The release plan's background shows what that cost. So the dashboard never classifies a setting. The engine repository does, in its field list (VLM-3), and the dashboard reads that list.

11.2 Four steps for scoring a point

  1. State. The dashboard copies the run's recorded engine configuration and environment variables, unchanged, into the scenario's engine block. A run records its configuration in several places: the launch configuration, the arguments the engine logs as passed, the configuration it logs as resolved, and lines it prints about values it chose. The first two record the values passed; the last two record the values resolved. The dashboard reads every one, and a value any of them records is recorded by the run, so it is copied rather than estimated (X19). The engine block holds the values the run passed; a field the run records only as resolved is copied as resolved. This applies to a field the engine models or proves inert; for any other field the resolved value is the engine's own choice, not a setting the run made, so it is recorded on the point and not stated in the scenario, and the field's estimation rule applies only to a value the run passed. A resolved value that differs from a passed one is not a conflict, because the engine resolves its settings: the engine repository must reproduce that resolution. Where the run logs a resolved value that differs from the engine repository's resolution of the passed value, the point is G2, names the field, and the difference goes to the backlog as a fidelity defect. Two sources that record the same stage, such as the launch configuration and the logged passed arguments, with different values are a conflict, and the point is G5 with the reason value_conflict. The dashboard translates nothing, apart from the estimates of the next step. The one exception is engine.version: it is set to the engine release that the scoring inference-sim release runs, and the run's own release is recorded on the point (§11.5). A recorded field that the modelled release does not have would be refused (X1), so the point is G5 with the reason field_not_in_release, naming the field. A workload-dependent value the run records, such as the mean acceptance length its engine logs, is copied into the scenario as stated (D8). One it does not record is taken from the substrate's profiles, and is not an estimate under the next step. With no profile either, the point is G5 with the reason value_not_recorded.
  2. Estimate. When the run did not record a value the simulation needs, such as the maximum model length, or used a setting BLIS does not model, such as an unsupported quantisation format, the dashboard applies the engine's estimation rule for that field. Each engine repository writes these rules in its fields.json, beside each field's status: one of: leave the field unset, so the engine resolves the upstream's default (X19); a stated setting that stands in for an unmodelled one, with the reason; or none. An estimation rule may state when the stand-in is exact: preserves, a sentence naming what the upstream resolves identically, and valid_when, a list of clauses; the stand-in is exact for a recorded value when at least one clause holds, and a clause holds when every key in it holds. A value for which no clause holds is not stood in for: the point is G5 with the reason field_not_modelled. Absent valid_when, the rule applies to every value. The dashboard holds no rules of its own. A rule applies in two cases: the run did not record the value, or the run passed a setting the engine does not model. A rule never replaces the recorded value of a field the engine models or proves inert: that value is carried into the scenario as stated. For a setting the engine does not model, the rule's stand-in, or leaving the field unset, takes the place of the recorded value in the scenario, and the point names the field, the recorded value and what stood in for it. A not-modelled field that the engine reads and prices when it is stated is a status to correct, either to modelled with its evidence or to refused when stated (VLM-3); the rule does not stand in for that. Every estimate is named on the point and makes it G2; a field with no estimation rule makes the point G5, with the reason value_not_recorded for a missing value or field_not_modelled for an unmodelled setting.
  3. Decide. For each field, the engine's own fields.json says whether it is modelled, proven inert or not modelled. The dashboard keeps no table of its own.
  4. Price. The engine lowers the configuration to kernel implementations. For each implementation the cost model uses the most trusted available entry, the one with the smallest rank number (§9.2), and the point records which methods were used.

A gap at Estimate is a missing estimation rule, and a gap at Decide is porting work, both in the engine repository. A gap at Price is a value below rank 1. All three go to the backlog.

11.3 Closeness grades

Every point gets exactly one grade: the highest-numbered (least close) grade whose conditions apply. Grades are computed from data alone, so a condition the data cannot decide is never assumed to hold: a G1 is published only for a point whose every condition was evaluated.

GradeMeaningCounts toward
G1 exactThe run's engine release is the one run by the inference-sim release doing the scoring (LIF-6). Each library version the run recorded maps to the kernel hash BLIS priced. Every field the run sets is modelled or proven inert, and every modelled field the run sets has a value in a value class its evidence covers (VLM-3). Every coefficient used is measured or calibrated, and none is extrapolated or unidentified. The simulated requests are the ones the run sent (COV-12).the headline, with every graded point, and a figure of its own beside it
G2 estimatedAs G1, except that one or more of these holds, each named on the point:
  • some coefficients are transferred, literature, vendor_spec, roofline or assumed
  • a coefficient is extrapolated or unidentified
  • an execution mode is substituted
  • a recorded library's kernel hash differs from the one priced
  • a modelled field is set to a value outside every value class its evidence covers (VLM-3)
  • a recorded value was missing and replaced by the engine's estimation rule (§11.2)
  • a fact of the cluster the run's own record lacks was taken from the benchmark's later record of the same runner, such as the GPUs per node of a runner whose definition was written after the run; or, for a runner that no record lists, from the records of its label, when every record that lists the label states the same value; the point names the field and the record and its date, or the label and the records it relied on
  • a workload-dependent value the run did not record was taken from a substrate profile (D8); the point names the profile and each key field that differed from the run's
  • a resolved value differs from the engine's resolution (§11.2)
  • a field the run sets has a status allowed only during a release plan, such as "inert, unproven"
  • the simulated requests differ from the ones the run's client sent (COV-12)
  • the run was measured behind a router BLIS does not host, such as Dynamo's router or vLLM's own router, and the router table maps that router to the nearest policy llm-d's router offers; the point is simulated with that policy and names the router the run used, the policy BLIS used and the table's evidence for the match
  • a condition of G1 could not be evaluated, because the data it needs is not recorded for the point; the point names the condition as unevaluated
the headline, with every graded point (COV-11)
G3 other version, same codeThe run's engine release is a different one. The point is scored on the release inference-sim runs, and none of the upstream functions that the engine ports changed between the two releases. Each engine repository publishes the list of upstream functions it ports, and its release watcher publishes a function-level diff for every upstream release seen in the data.the headline, and a figure of its own
G4 other version, changed codeAs G3, but some ported upstream functions changed, or the build is nightly or custom. The changed functions or the build are named.the headline, and a figure of its own
G5 not scoredOnly for the reasons in §11.4.coverage only, as an ungraded point (COV-7)

The headline accuracy covers every graded point, G1 to G4, because exact matches are rare: most benchmark runs use an engine release other than the one BLIS models, or a nightly or custom build. Beside the headline, the dashboard publishes the share of points in each grade and the accuracy of each grade on its own, so a reader can see how much of the headline rests on close comparisons. Every published figure shows the number of points it covers, and a figure over fewer points than the minimum stated in the dashboard's summary schema is shown as "too few points", not as a number.

11.4 Reasons a point is not scored (G5)

11.5 One simulator release scores every point

Each inference-sim release runs one upstream release of each engine (LIF-6). The dashboard scores all points with one inference-sim release at a time (COV-9). A point measured on that engine release can be G1 or G2. A point measured on another release is scored on the release inference-sim runs, and graded G3 or G4 by how much of the ported code differs (§11.3). So every point is priced by the same substrate and engine code, and a substrate improvement reaches every point without a backport.

The previous inference-sim release's scores stay published with it, so a reader can compare a point on its own engine release across BLIS releases.

11.6 The blocker backlog

Every night the dashboard publishes a ranked list, per engine, of everything that lowers coverage or grade:

Each entry shows how many points it blocks or demotes. Engine and substrate work is chosen from the top of this list, and each entry maps to one playbook (§12).

11.7 The scoring harness, and who may run it

The dashboard's scoring harness is the one place where a simulation is compared with a measured end-to-end benchmark (§2.2). It takes three inputs: a simulator build (a released tag, or a commit of a branch or pull request), the catalog and coefficient registry it should load (embedded, or paths), and a set of ledger units. It turns each unit's recorded run into a scenario as §11.2 describes, runs the simulator, and compares. It writes the same per-point records and aggregates, whoever runs it.

The harness is released from accuracy-dashboard tags and named by its release and commit. It is run as a script on a runner that builds the simulator, not as a container image. A repository whose pull requests run it pins that release or commit in one pin file, as it pins any dependency, and the release bot moves the pin. Every figure records the harness commit that produced it (X8).

Anyone may run it, at any stage of a change: on a feature branch, on a draft pull request, or on a pull request under review. What changes with the stage is which units it may read, because only seed-holding jobs may see select, validate and held units (D7):

Who runs itUnits it readsWhat it reports
a person, an agent or CI, on any branch or pull requestthe published fit unitsper-point records and aggregates, posted on the pull request. These figures help review; they are never evidence for landing an accuracy change, because the change may have been tuned against them.
CI, for a pull request declared a refactorthe recorded configurations of every ledger unit, with no measured values readfor each point, only whether the simulation is identical to the base (X13)
the select job, for a candidateselect unitsaggregates only
the gate job, for a pre-registered changeheld unitsaggregates only; one evaluation per pre-registration, and any further one is counted (§10.2)
the nightly dashboard runanchor units, for released datathe published dashboard

A figure computed on a branch is labelled with its build and its data, as every Result is (X8), and is labelled experimental unless both are released. So it can be posted on a pull request, but never published as BLIS's accuracy.

11.8 Invariants

12Triggers

A trigger is an outside event that requires work. Each trigger has one playbook, with the same fields every time:

Figure 5 shows which repositories each trigger touches.

Triggersubstratevllmsglangroutersimsearchdash
New model (existing capabilities)●···○○◇
Data drop●···○○◇
Accuracy idea: a coefficient or method●···○○◇
Accuracy idea: a pricing form●◐◐·○○◇
Accuracy idea: engine behaviour·●◐·○○◇
vLLM release◐●··○○◇
SGLang release◐·●·○○◇
Upstream patch release·◐◐·○○◇
llm-d release·◐◐●●○◇
New chip●◐◐·○○◇
Fidelity bug·◐◐◐○○◇
Data source format change●···○·●
New engine◐···●○●
New capability◐●●◐●○◇
Figure 5. What each trigger touches. Columns: blis-substrate, blis-vllm, blis-sglang, router fork, inference-sim, blis-config-search, accuracy-dashboard. ● code or data changes; ◐ changes only if the preflight finds a gap there; ○ a version bump, and any scenario rewrite it needs, done by the release bot; ◇ picked up automatically by the next nightly run; · untouched. The first three rows change one repository. An engine release changes the substrate only when it introduces a kernel implementation that has no coefficient yet. An llm-d release changes inference-sim by hand, because its plugin allow-list moves with the fork, and it changes the engine repositories when llm-d changes the metrics or KV events it reads from them.

12.1 A new model on existing capabilities

Detected
A request; a new model appearing in InferenceX data with no catalog entry, which the dashboard's backlog lists; or a drafter that a catalog model's InferenceX deployments run and the catalog lacks, which the §12.2 poll reports.
Preflight

inference-sim onboard --plan <hf-repo@revision> <scenario>… answers, for each requested engine, layout and cluster:

  • Is there a graph handler for the architecture?
  • Does the graph vocabulary cover it?
  • Does the engine lower every primitive?
  • Which coefficients would come from which method (§9)?
  • Is the router configuration hosted?

If anything other than a catalog entry or a coefficient is missing, the request becomes a new-capability trigger (§12.7) for that part.

Steps
  1. Add the vendor config.json at a full revision hash, and model.yaml; generate graph.yaml. If the architecture is new, add a graph handler.
  2. List the model's drafters in its model.yaml: its graph's own speculator, and each separate drafter entry, which names its target. Include every drafter its InferenceX deployments run, and onboard each separate one as a model. A drafter whose method the engine does not run is a new-capability trigger (§12.7).
  3. Where the preflight shows coefficients below rank 1, fit them if fit data exists. Otherwise keep the less trusted value (the one with the larger rank number).
  4. Release blis-substrate. The release bot bumps inference-sim.
Checks
Validators; deriver --check; coefficient registry property tests; a run of each requested layout; held-out accuracy for any of the model's held units.
Done when
  • T-M1 One pull request in blis-substrate, plus the bot's version bump. No engine, router or simulator code changed.
  • T-M2 Every requested layout runs, or the preflight names why not.
  • T-M3 Every coefficient the model uses has a method, and the method mix is reported.
Size
One pull request. An agent working this trigger opens only blis-substrate.

12.2 A data drop

Detected
A scheduled job polls each source: new InferenceX dump tags, run ids and acceptance curves, new AISimulate commits, new forward-pass dataset revisions. It opens a pull request when one appears. It also reports each drafter that a catalog model's InferenceX deployments run and the catalog lacks (§12.1).
Preflight
A ledger diff: new, changed and removed units per source and engine. The seed-holding release job adds which coefficient families gain fit units.
Steps
  1. Pin the new revision in the ledger's sources file.
  2. Commit the new units. Their roles are drawn by the seed-holding jobs the first time those jobs see them, before any use (D4).
  3. Refit only the families whose fit data changed. A refit that uses new units waits for the substrate release whose release job publishes their fit list. Each refit follows §12.3.
  4. Regenerate the acceptance profiles. A new curve is carried, with its drafter, when its model and drafter resolve in the catalog; one that does not is skipped with a stated reason (D8).
Done when
  • T-D1 No existing unit changed role.
  • T-D2 Every new unit has exactly one role.
  • T-D3 The revision is pinned by hash.
  • T-D4 Coverage and grade changes per lane are reported.
Size
One ledger pull request, plus one per refitted family.

12.3 An idea to improve accuracy

Detected
A person or an agent proposes it, usually from the top of the backlog.
Preflight

A pre-registration file, committed before any held-out data is read. It states:

  • the hypothesis;
  • the layer it changes: a coefficient, a method, a pricing form, or engine behaviour;
  • the target lanes;
  • the sources and lanes whose select and held units will be used (the jobs choose the units themselves);
  • the success criteria;
  • the predicted dashboard change.
Steps
  1. Make one change per pull request.
  2. Evaluate candidates by path. A writer script puts each candidate coefficient registry in its own directory, and the select job scores each one with --registry <dir> (§13.5). A data-only candidate needs no rebuild.
  3. Choose among candidates using select units only.
  4. Evaluate once on held units, in the gate job (§13.5).
  5. Land the change if the criteria pass. Otherwise commit the negative result as a note.
Done when
  • T-A1 Results on held units exist only as outputs of the gate job, run after the pre-registration commit (D7).
  • T-A2 Held-out error and distribution distance did not get worse on any lane, judged by the paired interval of VAL-6, and improved on the target lane.
  • T-A3 After release, the dashboard change is within the predicted range, or the miss is recorded.
  • T-A4 A failed idea leaves a committed note with its evidence, so it is not tried again blindly.
Size
One change in the repository that owns the layer. A bundle of several ideas is refused.

12.4 A vLLM or SGLang release

Detected
The engine repository's release watcher, a scheduled job, watches the upstream's final release tags (release candidates are ignored, §14.1) and opens an upgrade pull request with the preflight attached.
Preflight

Three generated reports:

  • The surface diff: new, removed and changed connectors, tiers, KV-cache managers, speculative methods, policies, configuration fields, defaults, metrics and event types (§7.5).
  • The field demand: for each new or changed field, how many dashboard points set it and to which values, which the dashboard posts on the upgrade pull request from its published blocked-point counts, as it does for surface weights (§7.5). A field the data use most is modelled first, over the range of values the data use.
  • The source diff: for each ported Go file, its Derived from header names an upstream file, so the tool lists every ported function as unchanged, changed (with the upstream diff) or removed. It also lists kernel implementations whose selection changed.

An agent working the upgrade reads these reports, not the whole upstream.

Steps
  1. Cut the release branch release-<old> from main, so fixes to the old release can still ship (§14.4).
  2. On main, update the Upstream record, the oracle's pinned commit and lock file, and run all tiers.
  3. Re-port only the changed functions. Delete code for anything the new release removed.
  4. Classify every new surface item and configuration field.
  5. Regenerate the recorded traces that changed. Each one gets a golden-reviewed: line with its reason.
  6. Tag v<new>-blis.1. The release bot moves inference-sim to it in a MINOR release (§14.1). Scenarios naming the old release are then refused with the message of LIF-6.
  7. For each kernel implementation whose selection changed, the substrate gets a coefficient by the methods of §9. A transferred value states its transfer rule (§9.2).
Done when
  • T-E1 Changes are confined to three places: the engine repository; the simulator's version bump, in which the release bot rewrites engine.version in the byte-gate and example scenarios and a person reviews the byte-gate changes; and the coefficients the preflight named.
  • T-E2 Every ported file names the new release, and no code for the old release remains on main.
  • T-E3 The surface list and field list are complete for the new release.
  • T-E4 Every changed trace carries its review line.
  • T-E5 The replacement manifest is unchanged, or bumped with a reason.
  • T-E6 The new inference-sim release refuses scenarios naming the old release with the message of LIF-6. The release that message names lists the old release in its stack.json, and its tagged build reproduces its own recorded byte-gate output.
Size
Proportional to the number of changed ported functions and new surface items, not to the size of the engine.

12.5 An llm-d release

Detected
A scheduled job in the fork watches llm-d's release tags.
Preflight

hack/upstream-merge-report lists:

  • conflicts with files the fork changes;
  • new clock, goroutine, random or map-order uses on decision paths;
  • new or renamed plugin types and parameters;
  • changed per-engine metric mappings and KV-event adapters;
  • changed resource definitions.
Steps
  1. Cut release-<old> from llm-d-r (§14.4).
  2. Merge the release into llm-d-r (not a rebase).
  3. Route every new hazard site through the runtime interface, or add it to the baseline on review (RTR-6). Each change to an upstream file must be of an allowed kind (RTR-2).
  4. Audit new plugin types, or leave them refused.
  5. Tag v<new>-blis.1 once the fork's own checks pass, after re-applying any hosting change the merge conflicted with (T-R1). The fork's tag does not wait for the rest of BLIS.
  6. If the release changes the metrics or KV events llm-d reads from engines, check that each engine still emits exactly what its upstream release emits. Change an engine only where llm-d's new mapping exposes something the port omits but its upstream emits. Where llm-d reads something the modelled engine release does not emit, BLIS reproduces the mismatch, and the release notes name it.
  7. Move inference-sim's replace line to the tag, and update its plugin allow-list in the same pull request. If an upstream behaviour change breaks inference-sim tests while the fork's own checks pass, for example a change to how a picker breaks ties, that pull request waits. The release watcher opens an issue that names the upstream change and the failing tests, and tags the owners of inference-sim and the fork, who decide whether the tests or the expected results change.
Done when
  • T-R1 Every existing hosting change survives the merge unchanged, or is re-applied with a reason.
  • T-R2 Every changed hunk in an upstream file is of an allowed kind, and the hazard lint passes. The release notes report how many upstream lines the fork changes, as a measure of merge cost.
  • T-R3 No unaudited plugin type is reachable from a shipped scenario.
  • T-R4 Both engines' metric and KV-event mappings are exercised by tests, and each engine still emits exactly what its upstream release emits.
  • T-R5 Upstream's own tests pass on the fork, and the differential test of X4 passes against the new upstream release.
Size
One fork pull request and one version-bump pull request, plus engine pull requests when llm-d changes what it reads from engines.

12.6 A new engine

Steps
Create blis-<engine> from contracts/harness: the provenance check, the no-clock check, the trace and manifest checkers, and the Upstream record. Generate its surface list first, so the scope is known before porting starts. Implement engine.Engine and plan emission, then register the engine kind in inference-sim. This is how blis-sglang starts (§14.3), and how TRT-LLM would start later.
Done when
T-N1 The substrate changes only by adding to contracts. T-N2 Every surface item has a status.

12.7 A new capability

Detected
The top of the backlog, or a refused surface item that a user needs.
Steps
  1. An additive contracts change, if one is needed.
  2. Each engine either implements the capability with traces, or refuses it by name. Where the capability rests on a framework (§7.6), port the framework first.
  3. An engine-independent pricing form in the cost model, if one is needed.
  4. Coefficients by the methods of §9.
  5. Wiring in inference-sim.
Done when
T-B1 The capability is engine-independent in the substrate. T-B2 Each engine models it or refuses it by name. T-B3 It is one capability per review set, never bundled with a model.

12.8 Smaller triggers

These follow the same pattern with fewer steps. Each has its own detector, so none depends on someone remembering it.

TriggerDetected byWhat happensDone when
A new chipa request, or the dashboard's not_in_catalog countThe catalog gains the chip's datasheet facts. Values come by §9's methods, with roofline as the floor. Each engine's lowering is checked for chip-dependent choices, such as SGLang's attention backend per chip. Where a choice differs, the engine gains a test for the new chip.T-C1 Every primitive family has a value on the new chip (VAL-2), and each engine's lowering for it is tested.
An upstream patch release, such as vLLM 0.32.1the engine repository's release watcherThe same steps as §12.4, usually much smaller. The engine is tagged v0.32.1-blis.1 even when the source diff shows no ported function changed, so that every upstream release BLIS supports has its own tag.T-P1 The source diff is empty, or the changed functions are re-ported; the engine has a v0.32.1-blis.1 tag.
A fidelity bug: BLIS differs from upstreaman issue labelled fidelity, or a dashboard regressionReproduce it as an oracle scenario that fails. Fix the port. The new scenario becomes a recorded trace. The fix is released as the next -blis.N on the upstream release it fixes, and inference-sim picks it up in a PATCH release. Prices are not part of inference-sim's versioned surface (§14.1), so a fix that changes prices is still a PATCH, and its release notes carry the price-change report. If the fix is also needed on the previous upstream release, it is cherry-picked to that release branch and shipped from inference-sim's release branch (§14.4).T-F1 A new trace fails without the fix and passes with it, and the tag that carries the fix names the issue in its release notes.
A gap found after release: a feature the surface list missed, or one marked out of scope that the stack turns out to usean issue, a dashboard point the backlog cannot explain, or the nightly weight reportFix the surface generator first if it missed the item, so the next upstream release cannot miss it again. Mark the item refused, with its issue, so the backlog counts it; then model it in priority order. Each step is a new -blis.N.T-G1 The regenerated surface list contains the item, the generator has a test that fails without the fix, and the item's status is refused or modelled.
An incompatible contract changea substrate pull request whose API check of the contracts/ packages reports an incompatible changeOne review set that moves every implementer: each engine module, the router fork and inference-sim. Open release branches are not moved; they stay on their substrate (§3.2).T-K1 Every implementer passes the new conformance suites, and all are released in one review set.
llm-d's guides changea scheduled job polls the guides repositoryPin the new guides commit in inference-sim's guides.lock, and recompute coverage and surface weights (§7.5). For each out-of-scope item whose weight became non-zero, the dashboard opens an issue against the engine repository; the engine marks it refused, so it joins the backlog.T-G2 Weights are recomputed from the pinned commit; no out-of-scope item has a non-zero weight.
A data source changes its formatthe ingest code sees a new contract version from the sourceUpdate the ingest code and ship the alias table, so that each unit keeps its role (D4).T-S1 Every old unit maps to a new identity with the same role.
A run used different library versions than the engine's lock filethe dashboard maps the run's recorded library versions to kernel hashes (CodeHash, §6.2), and compares them with the hashes pricedWhere a hash differs, the point is graded G2 with the difference named. If such points are common, the new kernel key gets a coefficient (§12.3).T-L1 No point is graded G1 when a recorded library's kernel hash differs from the one priced.

13Automation: how the rules are kept

Rules are kept by three kinds of worker, used in this order of preference:

  1. Deterministic checks decide everything that can be decided mechanically. Given the same commit, they give the same answer.
  2. Agents do bounded work: porting a changed function, classifying a new field, fitting a coefficient. They work from a preflight, against the checks, inside a budget.
  3. People answer the yes/no questions in §4 and §5, approve releases, and set direction.
detectorscheduled jobdeterministic preflightscope, budgetdeterministic agentworks the listin a branch checksevery pushdeterministic personreads the review,approval release botmerge, tag, bumpdeterministic fix and retry nightly: compatibility build, full-data gates, dashboard, backlog backlog feeds the next trigger
Figure 6. A trigger, start to finish. Everything except the agent's work and the review agent's answers is deterministic. The checks, not the agent, decide when the work is done.

13.1 The invariant registry

Every invariant in this document is a record in blis-substrate/contracts/invariants/:

- id: SUB-6
  statement: The cost model resolves nothing; every default, backend choice,
             padding and capture decision arrives in the plan or step shape.
  scope: [blis-substrate/kernel]
  checks:
    - repo: blis-substrate
      test: TestKernelResolvesNothing
      runs: pull_request
  review: null                    # or {question, reviewer, trigger}
  status: new                     # ci | partial | new | review
  target: {status: ci, milestone: 4}   # a milestone of a release plan

The status partial means some of an invariant's checks run in CI and others are still new. The status review means the invariant is enforced only by its review question, with no mechanical check; X1 to X9 keep a review question beside their checks, and no invariant in this document is review alone. Each check has its own status, so the invariant registry shows which parts are enforced.

An invariant registry check's runs value is pull_request, nightly or release for an automated check, or practice for a maintainer practice (§4.2). A practice record names the repository it concerns and its checklist item, and has no test: no program in another repository reads it. A practice is confirmed by a person ticking its item on the repository’s maintainer checklist; it has no check run to pass, so the release bot does not wait on it.

A generator turns these records into this document's appendix, into a section of every repository's AGENTS.md that carries the agent guidance, into the checklist of every pull-request template, and into a maintainer checklist for each repository that lists its practices. A rendered copy that does not match its record fails CI.

What a repository’s AGENTS.md holds. It is the first file an agent reads, so it is short and complete. It holds, in this order: what the repository is and owns, and what it must never hold (§5); the rules that apply to a change here, rendered from the invariant registry; the commands to build, test and run every check, exactly as CI runs them; what a change in this repository must also update (the documentation, the generated copy, the invariant registry record, the plan’s issue); and where to look next. It states no version, count or list of supported features, and leaves the concepts to the design, which a person reads. It does not restate a rule that CI enforces and an agent cannot get wrong; it states the rules a check cannot decide. A repository adds to it only through the invariant registry or a pull request that the reviewer with write access approves.

13.2 On every pull request

  1. The template lists the invariant identifiers the change claims to keep or introduce. CI fails if any identifier does not exist. The invariant registry lives at contracts/invariants/, and every Go repository pins the substrate, so each reads the invariant registry version it pins; blis-config-search and accuracy-dashboard, which depend only on inference-sim, read it through inference-sim invariants at the release they pin. The shared workflow lives in blis-substrate/.github/workflows, and each repository calls it by commit (X21).
  2. For each cited identifier whose status is ci, CI confirms its checks passed. A check in this repository must pass on this pull request. A check in another repository must have passed at the version this pull request pins.
  3. The review agent reads the changed paths and the diff against every review question’s trigger. A few triggers depend on what changed, not where, such as “a floating-point reduction is added”; the agent applies those by reading the diff.
  4. For each question that applies, the review agent answers it in a review comment with yes or no, a reason, and the files and lines that support the answer. A question is guidance for that review and never a check: no CI job fails because a question is unanswered or answered in the unwanted direction, and no person has to answer or tick one. An answer in the unwanted direction is a finding like any other (§13.7): the reviewer with write access reads it with the rest of the review, and branch protection blocks the merge until that reviewer approves and every conversation is resolved.

13.3 On every release

  1. The release bot refuses to tag unless every ci invariant the repository owns passed at the commit being tagged.
  2. The release notes are generated, never typed. They list the price-change report (each benchmark scenario's step prices before and after the change), changed coefficient values, changed traces, contract differences, invariants added or changed, review answers, and the exact upstream versions.
  3. Once the milestone named in an invariant's target is reached, the release bot refuses to tag until that invariant has its target status.

13.4 Every night

13.5 Where accuracy gates run

An accuracy gate compares a candidate with held-out data (VAL-6). Where it runs depends on what it needs:

The gate compares two trees: the base, which is the pull request's merge base, and the candidate, which is its head. Comparing against the merge base, rather than against the last release, keeps unrelated changes already on main out of the comparison. Each tree's catalog and coefficient registry are passed with --catalog and --registry.

Before a substrate release is tagged, the release bot triggers the whole-run gate job once more, comparing the candidate (its code and its data) with the previous release (its code and its data) on held units. A lane that gets worse by the test of VAL-6 blocks the tag, unless it is recorded as a defect in another term and approved (VAL-11). That final check covers changes that were each fine alone but interact. In every case the substrate's CI imports no repository above it (X7). A substrate pull request's candidate job builds inference-sim through stack CI and runs the pinned harness. Those are the two uses of higher repositories X7 allows, and the candidate job imports neither.

The gate job runs on pull requests that carry a pre-registration, and again nightly on the full data. Any pull request, draft or not, may also run the harness on published units for review (§11.7); that run holds no seed and decides nothing.

13.6 The release bot

A review set spanning repositories is described by one file, stack.lock, which lists each repository's branch head. During review, a "stack CI" job builds and tests the heads together, through a go.work file it generates and never commits (X11). Each repository keeps its released pins until the set is approved. Then the bot:

  1. merges and tags each repository bottom-up;
  2. writes each pin once;
  3. runs the next layer's checks against the new tags.

No person or agent re-pins by hand, and no agent waits on CI.

The bot's code lives in blis-substrate/tools/release, and its tags are signed. If the bot is unavailable, a documented manual procedure, run by a person with write access, produces the same tags in the same order.

13.7 What agents may and may not do

Agents mayAgents may not
Work the items a preflight lists, in a branchStart without a preflight, or go beyond its list
Port changed functions; classify fields and surface items; write tests; fit coefficients from fit units; draft pre-registrationsEdit a recorded trace without the update flag and a review line; edit a generated file by hand
Iterate until the checks pass, within the declared budgetWeaken, skip or delete a check; change a success criterion after reading held-out data; change the ledger's cut-points or partition version
Act as the review agent: apply each review question to a pull request, answer it with the files and lines that support the answer, and raise findingsApprove a merge; cut a release; review a change it wrote itself
Read summaries produced by toolsLoad recorded traces, vendored data, vendor configs or data files into context, instead of the tool summaries

Most of the prohibitions on the right are enforced by CI or by permissions, as listed below. Two depend on the agent's instructions and on review: staying within the preflight's list, and not loading generated files into context.

13.8 Keeping agent work cheap

14Versions, releases and blis-sglang

14.1 Version schemes

KindRepositoriesTagWhat the parts mean
Enginesblis-vllm, blis-sglangv<upstream>-blis.N, for example v0.32.0-blis.2<upstream> is the one upstream release the module models, and N counts BLIS releases on it, from 1. Each module models exactly one upstream release at a time. Its Upstream record (provenance.go) names that release and its commit, and the module holds no code for any other release. When upstream releases a new version, the module's main branch moves to it, and the previous upstream release keeps its own release branch for fixes (§14.4).
Router forkthe llm-d router forkv<upstream>-blis.N, for example v1.0.0-blis.2The same scheme. <upstream> is the llm-d release the fork is based on, and N counts fork releases on it, from 1. The fork carries one llm-d release at a time, because the router is one module with upstream's import path.
Substrateblis-substratevX.Y.ZSemantic versions. PATCH: no contract, price or value changed. MINOR: a compatible contract addition, new data or pricing forms, or changed values listed in the notes. An incompatible contract change raises MAJOR from v1 on; while the substrate is at v0, it raises MINOR. Either way it needs the contract-change review set (§12.8). From v2 on, Go adds /v2 to the module path.
Simulator and applicationsinference-sim, blis-config-search, accuracy-dashboardvX.Y.ZSemantic versions over what users see: the command line, scenario format and Result document. Moving to a new upstream release of an engine or of llm-d, patch releases included, is MINOR. It stops running scenarios that name the old release, but that refusal is part of the scenario contract (LIF-6), not a breaking change; the release notes and the refusal message name the last release that runs the old one. MAJOR is kept for changes to the command line, the scenario format or the Result document.

What an engine tag guarantees

An engine release, like any software, can ship with a bug or a gap that is found later. That is expected, and it is why N exists: the fix is a new -blis.N on the same upstream release, released and pinned the same way as any other.

A tag vX.Y.Z-blis.N of blis-vllm or blis-sglang means:

The module's version number is the upstream's version number, so the module cannot use it to signal a breaking change to its Go API. That costs nothing here, because only inference-sim imports an engine module, and it pins the exact tag (LIF-2). If an upstream reaches v2, the module path gains /v2, as Go requires.

Upstream versions that are not Go versions

A detail of Go's version ordering

To Go, -blis.N is a pre-release suffix. So v0.32.0-blis.1 sorts before v0.32.0. Among suffixed tags, v0.32.0-blis.2 sorts before v0.32.0-blis.10, and both sort before v0.32.1-blis.1. go get …@latest also prefers any plain release over every pre-release. This is Go's own ordering (golang.org/x/mod/semver) and the behaviour of go get.

Three rules follow:

  • Engine modules and the router fork never tag a plain v<upstream>. Every tag has a -blis.N suffix, with N from 1.
  • Pins name exact tags. The release bot never resolves @latest, and a lint checks the version each build actually selects, not the text of go.mod (LIF-2).
  • A plain tag made before this rule stays, because tags are never moved or deleted (LIF-1). It makes @latest wrong for that module, and a module that requires it outranks every -blis.N tag in version selection. A retract line does not fix this: retractions never change version selection, and Go reads them only from the latest release version, which is the plain tag itself. An exclude line in inference-sim's go.mod does, because Go applies the main module's excludes to the whole build (X11).

14.2 Lifecycle invariants

14.3 blis-sglang

blis-sglang does for SGLang what blis-vllm does for vLLM. It models one SGLang release, pinned by tag and commit. The pin must be exact, because SGLang's main moves fast and often rewrites core components, such as the allocator, between releases. Its first release and stages are in the release plan.

Layout

PathContents
provenance.goKind = "sglang" and the Upstream record (the SGLang release and commit), using the type from contracts/harness
config/ServerArgs field for field, from srt/arg_groups/fields/; defaults resolved as SGLang's *_hook.py files resolve them; fields.json
core/sched/event loops (normal and overlap), PrefillAdder, policies, prefill budget, retraction
core/cache/the radix cache, token and paged allocators, eviction, KV events
core/hicache/the hierarchical cache controller and its storage interface (port this framework before any one backend)
core/disagg/prefill and decode queues, the five-state transfer poll, the bootstrap handshake
lower/graph to kernels: attention backend per chip and model, forced page sizes, MoE runner, all-to-all backend
sglangengine/registration, factory, plan emission
oracle/runners, manifest, lock file, scenarios, recorded traces; checkers from contracts/harness
surface.jsongenerated from SGLang's own registration tables (§7.5)

It does not port SGLang's Rust router (sgl-model-gateway). In the llm-d stack, llm-d is the router, and llm-d already has an SGLang metrics mapping, KV-event adapter, request parser and P/D connector.

Behaviour the port must reproduce

Behaviour at v0.5.23Source
A step tries prefill first. A batch is prefill or decode, mixed only with enable_mixed_chunk (off by default).managers/scheduler.py get_next_batch_to_run; arg_groups/fields/schedule.py:208-211
The overlap scheduler forms the next batch before the current result is processed, using placeholder tokens. A request that stops at end-of-sequence may run one discarded extra step (inferred).managers/scheduler.py event_loop_overlap; managers/overlap_utils.py
Admission looks ahead using new_token_ratio, which starts at 0.7 × schedule_conservativeness (default 1.0) and decays over 600 steps to 0.14 of its start. One chunked request at a time. max_prefill_tokens defaults to 16384.environ.py:646-648; arg_groups/fields/schedule.py:81, 142
Running out of memory in decode retracts requests and requeues them. Outside P/D decode, a retracted request keeps none of its decode KV; on a P/D decode server it is backed up to host memory (disaggregation_decode_retraction_backup). Its prompt prefix stays in the radix tree, unlocked, and is hit again on re-prefill unless the retraction's eviction removed it. That eviction frees the remaining requests' count times SGLANG_RETRACT_DECODE_STEPS (default 20) tokens.managers/schedule_batch.py:2360-2395 release_req; environ.py:649
The default output limit depends on the API: 128 tokens on /generate, 16 on /v1/completions, none on chat completions.sampling/sampling_params.py:149; entrypoints/openai/protocol.py:378, 1214
Speculative decoding resets max_running_requests to 48 unless it is set explicitly.arg_groups/speculative_hook.py:438-442, 852-856, 992-996, 1086-1094, 1294-1298 (one site per speculative algorithm family)
P/D: decode reserves space, a handshake gates prefill, prefill pushes KV chunk by chunk, decode merges a ready-made batch. The transfer poll has five states; the default backend is Mooncake.disaggregation/prefill.py, disaggregation/decode.py; disaggregation/base/conn.py:123-128; arg_groups/fields/disagg.py:65-71
KV events use vLLM's message framing, with a different block hash (SHA-256 truncated to 64 bits, namespaced by salt) and one event per page, merged with the previous event when it continues the same parent chain.disaggregation/kv_events.py; mem_cache/events.py:140-170; mem_cache/utils.py
Sources of nondeterminism to remove in the port: the unified radix cache uses its Rust tree core on Linux and its Python tree core elsewhere (SGLANG_UNIFIED_RADIX_TREE_CORE_BACKEND, default rust, with fallbacks to Python); a random policy; time-based timeouts.environ.py:741; managers/schedule_policy.py

14.4 Release branches

Each upstream-tracking repository has a main branch (llm-d-r in the router fork) that models the newest upstream release BLIS supports. When BLIS moves to a new upstream release, two branches are cut:

A fix needed on the old release is made first on the main branch if the main branch needs it too, then cherry-picked to the release branch; otherwise it is made on the release branch alone. The release branch then gets the next tag for its upstream release, for example v0.32.0-blis.3 after v0.32.0-blis.2, and the fix is shipped by a PATCH release from inference-sim's release branch. A tag on a release branch alone reaches no user, because every inference-sim release pins exact tags.

Only the inference-sim release before the newest one is supported this way. Both release branches close when inference-sim next moves an engine or llm-d to a new upstream release.

AInvariant index

FamilyIdentifiersApplies toDefined in
Cross-repositoryX1–9all§4
ProcessX10–28all§4.1
Per repositorySUB-1–20, VLM-1–7, SGL-1–6, RTR-1–7, SIM-1–14, CFG-1–5the named repository§5
FidelityFID-1–11engine repositories§7.7
Simulation methodDEV-1–13inference-sim, engine repositories, router fork, applications§8.10
Coefficient valuesVAL-1–11blis-substrate§9.4
DataD1–8blis-substrate, accuracy-dashboard§10.4
CoverageCOV-1–12accuracy-dashboard§11.8
VersionsLIF-1–7all§14.2
Trigger outcomesT-M1–3, T-D1–4, T-A1–4, T-E1–6, T-R1–5, T-N1–2, T-B1–3, T-C1, T-P1, T-F1, T-K1, T-G1–2, T-S1, T-L1one trigger each§12

BGlossary

AgentAn AI coding agent that does one scoped task in a branch, within a budget (§13.7).
Agent guidanceA rule an agent follows on every change it writes or reviews, rendered into each repository’s AGENTS.md (§4.2, §13.1).
AnchorA ledger unit with the role anchor: the units on which the dashboard reports public accuracy. Only anchor units are scored for publication (§10.2, COV-2).
Batch meansA way to estimate the uncertainty of a steady-state figure from one long run: cut it, after warm-up, into equal consecutive batches, and treat each batch's mean as one observation (§8.8).
Blocker backlogThe ranked list, per engine, of everything that lowers coverage or grade: refused surface items, not-modelled fields, coefficients below rank 1 and unpriced execution modes. Each entry shows how many points it blocks or demotes (§11.6).
Byte gateA fixed set of scenarios whose output must stay byte-identical across a refactor.
Candidate jobThe half of a scoring run that runs the candidate's code, with no seed and no network, and writes one prediction per unit (§10.2).
Change classWhether a pull request is a verification change or a validation change (§8.9, DEV-12), declared in its template.
Coefficient registryThe substrate’s fitted and assumed coefficients, each with its provenance, in blis-substrate/registry/ and read with --registry (§3.3, §9). Distinct from the invariant registry and the metric registry (§1).
Common random numbersUsing the same random streams in two runs that are being compared, so their difference reflects the configuration, not chance (§8.4).
ComponentA part of the simulated system with its own state, changed only by its own internal events and by messages from other components: an engine instance, the router, a link, or a client (one source in the workload generator) (§8.1).
Confluent transitionA component's change of state when its own event and a message fall at the same instant (§8.1).
Conformance suiteA test suite in contracts/conformance that any implementation of a contract must pass.
Content hashThe SHA-256 of a sorted list of every data file's own SHA-256 and relative path (§3.3). Equal hashes identify equal data.
Contract-change review setA review set that makes an incompatible change to contracts/ and releases every implementer with it (§12.8).
Cost modelThe code in blis-substrate/kernel/ that prices a step from the plan, the step shape, the catalog and the coefficient registry. It was the repository blis-latency-kernel.
CUDA graphA recorded sequence of GPU launches that replays quickly but only for fixed batch shapes, so engines pad a batch up to the next recorded shape.
Dashboard corpusEvery deployment the dashboard scores and every point it scores. A refactor must leave it identical (X13). The byte gate is a smaller fixed set in inference-sim.
Data ledgerThe committed list of external data units and the rule that gives each one a role (§10).
DCODeveloper Certificate of Origin: a Signed-off-by line by which the person who takes responsibility for a commit certifies it. Encouraged, not checked (X20).
DeploymentA serving configuration, as a scenario's deployment document states it (SUB-19). For InferenceX, one role key covers one deployment under one benchmark type (§10.2): one model on one chip, at one precision, under one framework and configuration hash.
DEVSThe Discrete Event System Specification, Bernard Zeigler's formalism for discrete-event models, whose terms §8 uses.
Distribution distanceHow far BLIS's predicted latency distribution is from the measured one, beyond the mean. The landing gate uses the largest absolute relative error across the p50, p90 and p99 of time to first token and of time per output token. The coefficient registry's fitting principles define it exactly.
DrafterWhat proposes speculative tokens for a target model: the model's own in-graph speculator, such as its MTP layers, or a separate drafter checkpoint with its own catalog entry, such as an EAGLE3, DSpark or DFlash model. It is distinct from vLLM's draft_model method, which is one way of running a separate checkpoint (D8).
Engine block, router blockThe parts of a scenario that hold the engine's own configuration and llm-d's own configuration, copied verbatim from what the upstream accepts.
Engine kindWhich engine a scenario uses: vllm or sglang.
Engine moduleThe one Go module in an engine repository (blis-vllm or blis-sglang). It models one upstream release (LIF-5).
EnvironmentThe interface through which an engine asks the substrate for model graphs, chip facts, memory sizes and prices (§6.3).
Experimental frameThe recorded description of what a run measures: its input generator, initial state, phases (warm-up, measured window, drain), stop rule, acceptance conditions and metric definitions (§8.6).
Extrapolated, unidentifiedFlags on a lookup or coefficient. Extrapolated: used outside the range of inputs its fit covered. Unidentified: its fit data do not determine its value. Both lower its rank for VAL-3 (§9.3, VAL-10).
FateHow a request ended: completed, dropped with a reason, timed out, in flight or queued at the horizon, or not started. Written once per request (DEV-9).
Field listThe generated list (fields.json) of every configuration field and environment variable an upstream release reads, each with a BLIS status (VLM-3).
FLOPsFloating-point operations.
Gate job, select job, error jobThree of the seed-holding jobs. The gate job and the select job score candidates on held and select units respectively; the error job measures each method's error on validate units. Each runs in blis-substrate for operator-level work and in accuracy-dashboard for whole-run work. All three return only aggregate results (§10.2, §13.5).
GEMMGeneral matrix multiply.
GradeG1 to G5: how closely BLIS reproduces a dashboard point, judged by its engine release, its configuration and the methods of the coefficients that price it (§11.3).
HeldA ledger unit with the role held: used only to judge an accuracy change at its landing gate, in the gate job, never listed publicly (§10.2, VAL-6).
HiSparseHierarchical sparse attention for sparse-MLA models, in vLLM and SGLang. Host memory holds each request's whole KV cache; GPU memory holds the pages the sparse indexer selects, plus the newest tokens. In vLLM it is a connector, a coordinator and three KV-cache managers (source, hot and resident).
HMACA keyed hash. Without the key (here, the seed) the output cannot be predicted.
Holm correctionA way to test many lanes at once while keeping the chance of any false failure at the stated level (VAL-6).
HorizonThe simulated time at which a run stops, if its stop rule names one (§8.6).
In-flight depthThe most engine steps that run at once, PoolPlan.InFlight: 1 without asynchronous or overlap scheduling (SIM-7).
Inert, unprovenA temporary field status allowed only by a release plan: a field believed to have no effect, whose proof is still to come.
Internal event, external eventA component's change of state at a time it scheduled for itself (internal), or on a message from another component (external) (§8.1).
Invariant registryThe records of every invariant in this document: statement, checks, review question and status, in blis-substrate/contracts/invariants/ (§13.1). The appendix, each repository’s AGENTS.md section and the pull-request checklist are generated from it.
Kernel hashCodeHash: the hash that identifies a kernel implementation, of its source and target GPU architecture, or of a binary library (§6.2).
Kernel implementationThe library, variant and kernel hash (CodeHash) that execute one primitive. The coefficient registry looks coefficients up by it.
Known-bad inputAn input that a check must reject. Each check in the invariant registry names one (X18).
KV cacheThe stored attention keys and values of each request. A KV connector moves it between machines; KV tiering moves it between GPU memory, host memory and storage.
KV-cache manager, KV-cache specIn vLLM, the code that decides which blocks a request's KV cache holds, which prefix blocks it shares and which blocks are evicted. At start-up the model's layers are grouped, and each group has a KV-cache spec, such as full attention, sliding window or Mamba state. KVCacheSpecRegistry maps each spec type, or a group's role, to a manager; one manager may serve several spec types. A coordinator runs one manager per group (§7.1).
Landing gateThe accuracy check a coefficient or pricing change must pass to merge (VAL-6).
LaneOne engine, engine release, chip, model and layout, as the dashboard groups points.
LayoutHow a model is split across GPUs: the tensor, pipeline, data, expert and context parallel widths, and where each rank is placed.
Ledger ownerA person the project team names to own the role seed and its rotation (§10.2).
llm-d stackllm-d's router in front of vLLM or SGLang engines, as llm-d's published guides deploy it.
LoweringTurning a model's architecture into the concrete kernels an engine release runs: which attention backend, which fused operations, which data types, which page size.
Maintainer practiceA rule a person with write access keeps for a repository as a whole, such as its branch protection, its tag settings, its merge methods and, encouraged but not required, the signing of its commits (§4.2). It is stated once with a checklist item, enforced by the hosting service where a setting exists, and audited by no program in another repository.
ManifestThe list of everything the oracle replaces.
Memory poolIn SGLang, a store of KV cache or recurrent state in GPU memory, or, for HiCache, in host memory. A separate allocator hands out its slots or pages. Each attention layout (full attention in its MHA and MLA forms, sliding window, Mamba, hybrid, sparse) has its own pool type (§7.1).
Message classOne of a fixed order of message kinds, used with time, sender and the sender's send count to order messages at one instant (§8.2)
Metric registryThe list in contracts/ of every metric's definition: formula, statistic, population, window and unit (§8.7, DEV-8).
MilestoneOne of the numbered steps in which a release plan delivers its work.
MLAMulti-head latent attention: attention that stores a compressed latent instead of full keys and values.
MoEMixture of experts.
MooncakeA KV transfer and storage system, used by vLLM connectors and as SGLang's default P/D transfer backend.
MSER-5A warm-up rule: batch the output series in fives and cut where the variance of the remaining mean is smallest (§8.8).
MTPMulti-token prediction: a speculative-decoding method where extra model layers draft the next few tokens.
NIXLNVIDIA's library for moving KV caches between machines. vLLM's NixlConnector, the P/D connector llm-d's guides use most, is built on it.
Not blindA held-side figure from a unit whose label predates the ledger, so that the unit may have influenced earlier choices. The gate reports each such unit as a separate figure for as long as such units are used (§10.2).
Offered loadRequests arriving per second, set by the workload rather than by the system's response.
OracleThe harness that runs the upstream's own code with its environment replaced, and records its decisions.
Output functionWhat a component sends, evaluated just before an internal or confluent transition (§8.1).
OverlayA directory given with --catalog-overlay or --registry-overlay. Each entry replaces the entry with the same identity in the data in use, or is added if none exists (§3.3).
P/DPrefill/decode disaggregation: one pool of engines processes prompts (prefill), another generates tokens (decode), and the KV cache moves between them.
Paired bootstrapA way to put an interval on the difference between a candidate's error and the base's error on the same units, by resampling the units and recomputing the difference. The landing gate fails a lane only when the whole interval lies above zero (VAL-6).
Parallel DEVSThe form of DEVS in which all outputs at an instant are collected before any component changes state (§8.2).
Partition versionA numbered version of the ledger's role assignment. A new version is needed to change any existing unit's role.
Perturbation testA test that changes one field and checks that some price, decision, or field of the emitted plan or step shape changes (SUB-11, VLM-3).
Pin, re-pinTo require an exact version of another module in go.mod; to update that requirement.
PlaybookThe fixed steps for one kind of trigger, run by CI and, where needed, by an agent (§12).
PointOne InferenceX unit as the dashboard grades it, G1 to G5 (§11.3): one benchmark run of one deployment and recipe fingerprint at one concurrency.
PoolA group of engine instances that share one configuration and layout, such as the prefill pool or the decode pool in P/D. Each pool has one PoolPlan.
PoolPlan, StepShape, TransferThe three objects an engine hands the cost model: a plan per engine pool, the shape of each step, and one transfer per KV move (§6.1, §6.2).
Pre-registrationThe preflight file of an accuracy change, committed before any held-out data is read, that states what the change will be judged on (§12.3).
PreflightThe generated report that fixes a trigger's scope before work starts.
Price-change reportA report, generated for every release and every pull request that can change prices, of each benchmark scenario's step prices before and after the change (§13.3).
PrimitiveOne kind of work the cost model prices: an attention step, a matrix multiply, a collective, a memory transfer.
Profile likelihoodA way to find a fitted coefficient's interval by refitting the others at each candidate value (VAL-10).
Progress watchdogA check that fails a run when no request changes state for a set number of engine steps while work is waiting (SIM-7).
Quarantine listThe list of tests that fail on the main branch, each with an owning issue and an expiry date; listed tests do not block merges until they expire (X25).
Radix cache, tree cache, tree coreSGLang's prefix cache: a tree of token sequences whose nodes hold KV cache, so that requests sharing a prefix reuse it. A tree cache is any implementation of SGLang's tree-cache interface; the default is the unified radix cache, and others can be registered. A tree core is the tree's own data structure inside the unified radix cache, implemented in Python and in Rust (§7.1).
RatchetA count that CI allows to fall but never to rise.
Recorded traceThe oracle's output for one scenario at one upstream commit, reviewed and committed. The commit marker golden-reviewed: keeps an older name for the same thing.
Release branchA branch release-<upstream> in an upstream-tracking repository, or release-vX.Y in inference-sim, kept for fixes to an upstream release after main moves to the next one (§14.4).
Release ownerA person with write access to a repository who cuts a release by the manual procedure when the release bot is unavailable (§13.6), with standard git or the hosting service's interface.
Release watcherThe scheduled job in each upstream-tracking repository that detects new upstream releases and opens the upgrade pull request (§12.4).
Released data, experimental dataA run uses released data when the simulator is a tagged inference-sim release, its catalog and coefficient registry hashes equal the embedded hashes of that release's substrate, and it uses no overlay. Any other run is experimental. Only released-data results are published.
ReplicationOne run of a scenario with its own scenario seed. Several replications give a confidence interval (§8.8).
ResultThe structured output of one run, written as JSON, including everything the run used (§3.3).
Review questionA yes/no question, attached to an invariant, that the review agent applies to a pull request and answers in a review comment with the files and lines that support the answer (§13.2). It is guidance and never a check: it fails no CI job and waits for no person. Its expertise line names the knowledge the agent brings, and its applies-when line says which changes it covers.
Review setThe pull requests, across repositories, that make up one change.
Role keyThe fields of a data unit that decide its role, so near-copies of one measurement share a role (§10.2).
RooflineAn estimate of a primitive's time as the larger of its compute time at peak FLOPs and its memory time at peak bandwidth.
Runtime interface (seams)The points where the router fork reads time, starts work, draws random numbers or makes calls, so BLIS can supply simulated versions.
ScenarioThe YAML file a user gives BLIS: the engine and router configuration, the cluster, the workload and the seed.
Scoring harnessThe accuracy dashboard's tool that turns measured benchmark runs into scenarios, runs a given simulator build, and compares. The only end-to-end scorer (§11.7).
Seed-holding jobOne of the four kinds of CI job that hold the role seed: the release job, the select job, the error job and the gate job. They run only code from main, never a pull request's code. Only these can compute select, validate and held roles (§10.2).
Selected-version lintA check that reads the versions Go actually chose for a build (go list -m all), not the versions written in go.mod.
Simulated runtimeThe router fork's runtime when BLIS hosts it: clock, timers, randomness and goroutines driven by simulated time (§7.3).
Stack CIThe CI job that builds and tests a review set's branch heads together, before anything is tagged (§13.6). Its file, stack.lock, lists those heads.
stack.jsonThe record, published with each inference-sim release, of the exact version of every BLIS module and upstream release it was built and tested with, its Go toolchain version and its llm-d guides commit (SIM-3).
SubstrateThe engine-independent foundation: blis-substrate.
Surface listThe generated list of every feature an upstream release offers, each with a BLIS status.
Terminating run, steady-state runA run that ends by its stop rule (terminating), or one that measures a system after it has settled (steady-state) (§8.8).
Time advanceHow long until a component's next internal event, given its state (§8.1).
Token recordThe per-request counts an engine emits: prompt tokens, tokens served from local and external caches, tokens computed and tokens emitted (SIM-12).
TriggerAn outside event that starts BLIS work, such as a new upstream release or a new model (§12).
Unit, roleA unit is the smallest piece of data the ledger tracks. Its role says what it may be used for.
UpstreamThe project BLIS reproduces: vLLM, SGLang or llm-d.
Upstream recordThe Upstream value in an engine repository's provenance.go: the one upstream release, and its commit, that the repository models (LIF-5).
Upstream-tracking repositoryblis-vllm, blis-sglang or the router fork. The first two are also called engine repositories.
Validity domainThe range of models, chips, layouts, concurrencies, sequence lengths and replica counts that held-out data cover (§8.9).
VerdictA CI result or the review agent’s review, including its answers to review questions. Each names the tree it judged (X22).
Verification, validationChecking that the simulator does what its model says (verification), and checking that the model matches the real system (validation) (§8.9).
Warm-upThe stretch at the start of a steady-state run that is discarded because the system has not settled (§8.8).
Workload identityThe key under which a workload-dependent quantity, such as speculative acceptance or expert routing balance, is stored (D8).