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.
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.
- 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). - Engines hand the cost model a fully resolved description of what runs, so the cost model does not depend on which engine produced it.
- 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.
- 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.
- 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).
- 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:
| Prefix | Covers | Section |
|---|---|---|
| X | rules that span repositories | §4 |
| SUB, VLM, SGL, RTR, SIM, CFG | rules for one repository | §5 |
| FID | fidelity to upstream code | §7 |
| DEV | simulation method | §8 |
| VAL | how coefficient values are obtained | §9 |
| D | data roles | §10 |
| COV | accuracy coverage | §11 |
| LIF | versions 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.
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.| Repository | Owns | Changes when |
|---|---|---|
blis-substrate | the shared language (types, contracts, validators), published facts, fitted coefficients, and the cost model | a new model or chip, a new data drop, an accuracy improvement, a new contract |
blis-vllm | vLLM'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-sglang | the same for SGLang | SGLang releases; a fidelity fix |
llm-d-router fork | llm-d's own router code, made runnable in simulated time | llm-d releases; a newly needed plugin |
inference-sim | the simulator itself: event loop, cluster, workload, replay, engine selection | any pin bump; a simulator feature |
blis-config-search | experiments that search configurations, and their reports | a new experiment; a simulator release |
accuracy-dashboard | public accuracy and coverage against ground truth | a 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:
- If its value changes when an engine releases a new version, it belongs in that engine's repository, on the branch that models that release (§14.4).
- If it changes when llm-d releases a new version, it belongs in the router fork.
- If it changes with the simulator's own design, it belongs in
inference-sim. - Otherwise it belongs in
blis-substrate: it is true of the hardware, the model, the physics or the data, whatever engine runs.
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.
| Work | Owner | Others reach it through |
|---|---|---|
| Pricing a step, and the data it reads | blis-substrate | the cost model, through Environment (§6.3) |
| Validating catalog and coefficient registry data | blis-substrate | its validate command (X15) |
| Fitting coefficients, and judging a coefficient against operator-level measurements | blis-substrate | its fitting scripts and operator-level gate (§13.5) |
| Engine behaviour and its fidelity tests | the engine repository | engine.Engine |
| Router behaviour and hosting | the router fork | pkg/embed.New |
The documents BLIS reads and writes: the scenario, the deployment, the Result, and the per-point scoring record | blis-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 Result | inference-sim | its run and replay commands |
| Turning each data source's records into ledger units, for every source including InferenceX | blis-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 figure | accuracy-dashboard | its scoring harness, run at a pinned release or commit with a simulator build and data paths (§11.7) |
| Searching configurations, and ranking them | blis-config-search | its 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.
| Fact | Depends on | Lives in |
|---|---|---|
| H200 memory bandwidth | the chip | blis-substrate/catalog |
| GB200 NVL72's four GPUs per tray and 72 GPUs per NVLink domain | the hardware product | blis-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 open | the user's configuration | the scenario |
| Muse-Spark's layer count and expert count | the model checkpoint | blis-substrate/catalog |
| How fast one attention kernel runs on H200 | the chip and the kernel library | blis-substrate/registry |
| How to turn FLOPs, bytes and an efficiency curve into time | arithmetic | blis-substrate/kernel |
| Which attention backend SGLang picks on Hopper | the SGLang release | blis-sglang |
vLLM's default gpu_memory_utilization (0.92) | the vLLM release | blis-vllm |
| How the prefix-cache scorer ranks endpoints | the llm-d release | router fork |
| The scorer weights a deployment uses | the user's configuration | the scenario, copied verbatim |
| Which data points exist, and the rule that assigns their roles | the data sources | blis-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.
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:
- A model whose architecture is already supported needs only a catalog entry. A model with something new in its architecture usually needs a new data field, a catalog entry, new coefficients and a change to the cost model, all at once.
- The data are validated by the contracts' validators, and the cost model is tested against the data. If the parts lived in separate repositories, each change would be released in order, each release would force the next repository to update its pin, and consistency would rest on vendored copies and lock files. The release plan's background describes what that cost when the parts were separate.
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:
- Packages under
contracts/hold interfaces, data types, validators of those types, conformance suites, the engine harness and the invariant registry. They import only the Go standard library, the YAML parser and each other. The engine repositories and the router fork import only these packages, and an import test in each of those repositories enforces it. - All other packages hold the cost model, the catalog and coefficient registry validators, the data loader, and the embedded data. Only
inference-simimports them.
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:
- An engine or the router fork requires the oldest substrate version whose
contracts/packages it uses. It never requires a version newer thaninference-sim's current requirement; the release bot refuses such a release. When an engine needs a newer contract, the release bot movesinference-simfirst, in the same review set. On a release branch (§14.4) the bound is the substrate requirement of theinference-simrelease branch the fix ships in. A fix that needs a newer contract moves that branch to the newer substrate first, in a PATCH release whose notes carry its price-change report. A fix that would need a substrate past an incompatible contract change is not backported. - Before every
inference-simrelease,go testruns every engine and router package in a scratch copy ofinference-sim's module, with test dependencies added, so each is tested against the substrate the simulator actually selects (LIF-2). inference-sim's own requirement on the substrate must equal the version its build selects. If a dependency raises it, the build fails until the release bot re-pinsinference-sim, which produces the usual price-change report (LIF-2).
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.
- A path.
--catalog <dir>and--registry <dir>(orBLIS_CATALOGandBLIS_REGISTRY) name directories with the same layout asblis-substrate/catalog/andblis-substrate/registry/, such as a checkout, a working tree or an exported revision. When a path is given, the run uses that directory instead of the embedded copy. Recalibration uses this. A fitting script writes candidate coefficients to a directory, and the released simulator scores them with no rebuild. Comparing two coefficient registries means running one binary twice with two paths. - The embedded copy. When no path is given, the run uses the data compiled into the substrate with
go:embed. A release therefore carries the exact data it was tested with, and a user of a released binary needs no checkout.inference-simcannot be installed withgo install …@version, because Go refuses that for a module whosego.modhas areplacedirective, andinference-simcarries one (X11). So each release ships signed binaries for the three supported platforms, with the provenance and bill of materials of X21. A source build needs a checkout at the tag. - What stays fixed. A model runs only if it is in the catalog in use, and it is resolved from its
graph.yaml. A missing model is refused with the catalog's source (the embedded release, or a path) and the relative path its entry belongs at. No run creates or changes catalog or coefficient registry files, no run fetches anything from the network, and the deployment always comes from a required scenario.
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:
- In the catalog, a whole model directory (
models/<name>/), or a whole hardware, device, network or workload file. - In the coefficient registry, one coefficient, identified by its set, its name, its scope and its method.
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:
- a file under a data directory that matches no pattern, such as
coefficients/x.ymlwhere.yamlis expected. Ignoring it would silently drop a candidate's change. - a symbolic link;
- a data file containing a carriage return;
- two paths that differ only in letter case.
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:
- its source:
embeddedorpath; - its content hash;
- the hash of each overlay, and the hash of the combined data. With no overlay the combined hash is the base hash. Otherwise it is the SHA-256 of the line
<base hash> basefollowed by one line<overlay hash> overlayfor each overlay in the order applied, each line ending in a line feed, so it names the base and every overlay and their order.
It also records the build the simulator came from: a tagged release, or unreleased.
A run uses released data when all of these hold:
- the simulator is a tagged
inference-simrelease, whosestack.jsonnames a tagged substrate release; - the catalog and coefficient registry hashes equal that substrate release's embedded hashes;
- no overlay is used.
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:
- a new unit has its role drawn by the job;
- a
unit_idpresent at the merge base but absent from the candidate is refused, unless an alias table or a new partition version covers it and a person has approved it. A unit whose identifying fields changed shows up exactly this way, because itsunit_idis a hash of those fields.
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.
| Directory | Language | Reviewed for | CI on change |
|---|---|---|---|
contracts/ | Go | compatibility of interfaces and types | build, tests, API-compatibility check of the contracts/ packages against the last substrate tag, conformance suites |
catalog/ | YAML, Python | sources and citations, vendor bytes | validators; graph deriver --check; deriver tests; ledger checks |
registry/ | YAML, Python | held-out evidence, provenance, fitting method | validators; property tests; writers --check on redistributable fixtures; landing gate |
kernel/ | Go | pricing correctness | tests; 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).
- X1 Users configure BLIS with the upstream's own configuration.
What a user would hand llm-d, vLLM or SGLang is what they hand BLIS, in the upstream's own field names, types, defaults and errors. BLIS adds only what the real system learns from its environment: hardware, cluster topology and workload. Engine repositories resolve defaults exactly as the upstream does. The router needs no port of its defaults: the fork runs llm-d's own flag parsing, configuration loader and Kubernetes decoding unchanged, so a value the real router rejects is rejected with the router's own message. An unknown or unsupported field is refused by its upstream name, never ignored. The one exception is the router's flags that only change how a deployed router is served or observed (transport, TLS, leader election, ports, telemetry). Those are accepted, have no effect, and are named in a warning.
Review guidance. Does this new scenario field describe something the real system would observe, rather than a setting the upstream does not have? Expertise: contracts expertise. Applies when: a key is added to the environment list.
Check Runs in Every configuration field of the pinned release is classified as modelled, proven inert, or not modelled (VLM-3, SGL-3). blis-vllm, blis-sglang An unknown field, or a not-modelled field set away from its default, is refused with its upstream name. blis-vllm, blis-sglang Defaults match: the upstream's own configuration code resolves each oracle scenario, and the port's values are compared field by field. blis-vllm, blis-sglang Every configuration the upstream rejects is rejected. This is tested on a generated set: each field's boundary values, taken from the upstream's own argument and validation metadata, and one case for each branch of the upstream's validation code, with branch coverage required. blis-vllm, blis-sglang The scenario's router flags, configuration file and manifests reach the router unchanged, and the router parses them with its own code. inference-sim A set of malformed router flags, configuration files and manifests is refused, each with llm-d's own error message. router fork Every flag in the router's flag set either is on the fork's list of flags that only matter in a cluster, or can change a decision. Every plugin type llm-d registers either is on the fork's list of audited types, or is refused by name. Both lists are regenerated against each new llm-d release and reviewed as a diff. router fork Every scenario key outside the engine and router blocks is on a fixed list of environment facts. blis-substrate ( contracts)
Review guidance. Does this router flag only change how a deployed router is served or observed, with no effect on any routing, admission or flow-control decision? Expertise: router fork expertise. Applies when: a flag is added to the fork's list of flags that only matter in a cluster. - X2 The substrate knows no engine.
Outside its tests,
blis-substratecontains no engine name, no engine version and no engine's policy. Engine behaviour reaches it only as a resolvedPoolPlan,StepShapeorTransfer.Review guidance. Does this change hide one engine's policy (a default, threshold, backend choice or padding rule) behind a neutral name? Would the other engine need the same edit? Expertise: expertise in the changed directory. Applies when: any non-test change underCheck Runs in No engine identifier in non-test Go identifiers, string literals or coefficient registry keys: engine names, engine version strings, upstream source-path citations, and engine-only setting names. The word list is committed in the substrate ( invariants/engine-words.txt). A bot regenerates it from each engine release's field list in a pull request, leaving out words the architecture vocabulary also uses, such asdtypeorquantization. The count per directory may only go down, measured against the word list of the same version, until it reaches zero.blis-substrate The cost model's public functions accept only plans, step shapes, transfers, and catalog and coefficient registry handles. blis-substrate ( kernel/)PoolPlan,StepShapeandTransfercontain no field whose values come from an engine's vocabulary, and a zero value means "absent" or the field's documented neutral value (such as stage 0), never an engine default (a reflection test). Separately, a scenario both engines support is priced from each engine's plan, and every price difference is traced to a named difference between the plans.inference-sim (it imports both engines) Coefficient scope keys come from a fixed set with no engine key. blis-substrate ( validate/)Model graphs use only architecture vocabulary. blis-substrate ( validate/,catalog/)catalog/,registry/orkernel/. - X3 Engine behaviour lives only in its engine repository.
Scheduling, KV management, KV connectors and tiering decisions, default resolution, and lowering a model to kernel implementations belong to
blis-vllmorblis-sglang. Each engine repository models one upstream release at a time (LIF-5).Review guidance. Is engine behaviour, such as scheduling, admission or KV accounting, being added to the simulator instead of an engine repository? Expertise: inference-sim expertise. Applies when: a change to the simulator's request-lifecycle or capacity code.Check Runs in The cost model imports no engine module. blis-substrate No engine module imports another. blis-vllm, blis-sglang The simulator reaches engines only through engine.Engine, at one build site.inference-sim Scheduler and KV types exist only in engine modules. inference-sim, blis-substrate The simulator does no resource arithmetic (KV blocks, bytes, tier capacities); it asks the engine or the cost model. The one exception is dividing a link's bandwidth among transfers and the cross-domain traffic of step collectives that overlap in time (SIM-13) (§6.2). inference-sim Every ported file names the release that the repository's Upstreamrecord names.blis-vllm, blis-sglang - X4 The router is llm-d's own router code, unchanged.
The fork changes only how the router is hosted: how it reads time, starts work, draws random numbers and makes calls. It changes no routing decision. Engine-specific router behaviour comes from llm-d's own code for that engine.
Review guidance. Does this change to how the router is hosted keep upstream behaviour on paths the recorded streams do not cover, such as errors, timeouts and cancellation? Expertise: router fork expertise. Applies when: a fork pull request converts a call site or updates the lint baseline.
Check Runs in No new clock, timer, goroutine, randomness, map-order or network call on a decision path, except through the fork's runtime interface; every change to an upstream file is of an allowed kind (RTR-2), and every hosting hazard is converted or reviewed (RTR-6). router fork The fork, run under its default runtime, and the upstream release it is based on make the same decisions on recorded request streams. Because the fork keeps upstream's module path, one program cannot load both, so the upstream release is built separately and each build replays the same recorded requests. A decision that depends on a random draw or on map order is compared as a set: the fork's choice must be one the upstream can make. Upstream's own tests also run on the fork, changed only as RTR-2 allows. router fork Under BLIS's simulated runtime, every decision is one the default runtime can make from the same recorded state. Each decision is replayed from the recorded state just before it, not from the simulation's own history, so one different tie-break cannot make every later decision incomparable. Ties and random draws are compared as sets of the choices the upstream could make. inference-sim BLIS imports only the fork's pkg/embed.New, and only audited plugin types are reachable.inference-sim Every branch the fork takes only when hosted is exercised by a test that compares it with the upstream code it replaces. router fork - X5 Fidelity is proven by comparing against the upstream's own code.
Each engine repository runs the upstream's own Python code on the same inputs and compares its decisions with the Go port's, step by step. Only the environment is replaced (GPU, clock, transport, environment settings, the hardware platform and outside services), and every replacement is listed in a manifest (§7).
Review guidance. Does this new replacement stand in only for the GPU, clock, transport, environment settings, the hardware platform or an outside service, and not for a decision? If it reproduces a decision, is it declared as a transcription? Expertise: engine repository expertise. Applies when: the manifest changes.
Check Runs in Every replacement the test harness makes is listed in the manifest; a changed manifest carries a new version. blis-vllm, blis-sglang Decisions match on every recorded scenario. blis-vllm, blis-sglang A recorded trace's decisions change only in a commit marked as reviewed. blis-vllm, blis-sglang Every ported function is exercised by some recorded scenario, and deliberately broken copies of it are caught. blis-vllm, blis-sglang - X6 Same inputs, same output.
For fixed scenario bytes, seed, module versions, Go toolchain version and catalog and coefficient registry content hashes, the simulator's standard output is identical byte for byte across runs, thread counts and supported platforms. The data source (embedded or path) is recorded in the
Result, but is left out of the output the byte gate compares. Simulated time is passed in as a number, and no code reads the system clock. Randomness comes from named substreams seeded from the scenario (DEV-6).Review guidance. Could this floating-point reduction give a different result on another platform? Expertise: expertise in the changed repository. Applies when: a parallel or floating-point reduction is added, or the cross-platform check fails.Check Runs in Identical output across runs and thread counts; a recorded trace replays identically. inference-sim Identical output on linux/amd64, linux/arm64 and darwin/arm64: nightly, and on every pull request that changes kernel/or an engine's lowering. Two things differ by platform. First, the Go compiler may fusex*y+zinto one fused multiply-add on some architectures and not others, which changes the last bits; so cost-model and engine code writesfloat64(x*y) + zwhere fusion must not happen, or callsmath.FMAwhere it must. Second, some functions in Go'smathpackage, such asExpandLog, use assembly or CPU-dependent code on some platforms, so their last bits can differ between machines; cost-model and engine code calls the pure-Go copies incontracts/check/fmathinstead, which use no fused multiply-add. A lint enforces both rules.stack.jsonrecords the toolchain version (SIM-3).inference-sim No clock reads and no goroutines in engine or cost-model code. blis-vllm, blis-sglang, blis-substrate No unsorted map iteration on an output path, and no unnamed randomness. In the router fork this lint covers files the fork wrote; upstream code is covered by the differential test in X4. every Go repository, using lint settings from contracts/harness/golangci.yml. A generator copies them from the pinned substrate version, and a check confirms the copy matches (X7). - X7 One owner for each fact; dependencies point one way.
Contracts live in
blis-substrate/contracts. A repository never imports one above it in Figure 1. A generated file is written only by its generator. Every copy of a fact is generated from its owner or checked against it. The dashboard's scoring harness is the one thing a lower repository uses from a repository above it: it runs the harness at a pinned release or commit, never imports its code. A lower repository's candidate job may also buildinference-simthrough stack CI to run that harness (§13.5).Review guidance. Does this change give a second home to a fact another repository owns? Expertise: expertise in the changed repository. Applies when: a pull request adds a literal list or table, or a constant that cites another repository.Check Runs in A lower repository refers to accuracy-dashboardonly through the harness pin file.every repository The module graph across all repositories follows Figure 1. blis-substrate (nightly compatibility build) The engine repositories and the router fork import only substrate packages under contracts/.blis-vllm, blis-sglang, router fork Every generated file matches its generator's output. every repository with a generator No hand-copied list of another repository's facts; field lists, knobs and plugin parameters are read from their owner at the pinned version. blis-config-search, accuracy-dashboard - X8 Every number is reproducible, and no data point both fits and judges.
Every number records the exact versions and data it came from, and can be regenerated from them. A published number, meaning one on the dashboard or in a committed report, is computed from released data only (§3.3). No data point used to fit or choose a value is used to evaluate it (§10).
Review guidance. Was this fitting method, gate or threshold chosen after seeing evaluation results? Expertise: expertise in the repository landing the change. Applies when: a pull request changes a fitting method, gate or threshold.
Check Runs in No ledger unit is cited under two roles by any fit, gate or dashboard point. the nightly run of blis-substrate's gate job, over every consumer's recorded unit reads (D2)Every data source is pinned to an exact revision. blis-substrate Every Resultrecords the module versions, the source and content hash of the catalog and the coefficient registry, any overlay hashes, and whether the run used released data.inference-sim The dashboard and committed config-search reports accept only released-data results; an experimental result is refused. accuracy-dashboard, blis-config-search Every published number carries its data units. accuracy-dashboard, blis-config-search Published numbers regenerate: on redistributable fixtures on every pull request, on the full data nightly. blis-substrate, blis-config-search, accuracy-dashboard An accuracy change's success criteria are committed before any commit that reads its held-out data. any repository landing an accuracy change - X9 The upstream feature surface is fully accounted for.
For every pinned upstream release, each feature the upstream offers has exactly one status in a generated list. Features include connectors, KV tiers, KV-cache managers, speculative methods, scheduler policies, configuration fields, metrics and event types. The statuses are: modelled, with evidence; refused by name, with an issue; or out of scope, for one of three listed reasons (§7.5). Modelled is the goal; the other two are exceptions, each with evidence.
Review guidance. Is this "out of scope" reason one of the three allowed, with a citation that supports it, and does the llm-d stack never use the feature on the hardware BLIS targets? Expertise: engine repository expertise. Applies when: an item is marked out of scope.
Check Runs in The surface list is regenerated from the pinned upstream source and matches the committed list. blis-vllm, blis-sglang, router fork Every item has a status, and every modelled item names its evidence. blis-vllm, blis-sglang, router fork Coverage of the llm-d stack's published configurations is computed and published per release. inference-sim Every out-of-scope item names one of the three allowed reasons and cites upstream. An engine release does not lower the weighted share of modelled items, and does not move an item from modelled to refused without a fidelity issue that explains why. blis-vllm, blis-sglang, router fork; the release bot
4.1 Process rules
- X10 Repositories are released bottom-up. A change spanning repositories is one review set. Each repository's part is tagged before the repository above it pins that tag.Checked by: release bot (§13.6).
- X11 Pins are exact release tags. The only committed
replacedirective is the one ininference-sim'sgo.modthat substitutes the router fork for upstream llm-d. It exists because the fork keeps upstream's module path (github.com/llm-d/llm-d-router), so Go cannot fetch the fork by its own path; a replace is Go's only way to substitute it. It names the fork's exact-blis.Ntag, never a branch or a local path, so it pins as exactly as arequiredoes, andgo.sumrecords its hash. It may appear only ininference-sim, because Go ignores replace directives in every module but the one being built. No other module requires the fork; engines and other repositories use only the router contract incontracts/embed. The only committedexcludedirectives are those ininference-simthat skip a plain tag made before the rule of §14.1, and no repository commits ago.workfile.Checked by: selected-version lint (LIF-2), in every repository: noreplaceoutsideinference-sim; ininference-sim, exactly one, fromgithub.com/llm-d/llm-d-routertogithub.com/inference-sim/llm-d-routerat a-blis.Ntag; no other module requiringgithub.com/llm-d/llm-d-router;excludedirectives only ininference-sim, each naming a plain tag of an engine module; no committedgo.work. - X12 Only derived values are committed, never third-party data, with one exception: vendor
config.jsonfiles whose licence is on the allow-list. Each such model records the licence identifier in itsmodel.yaml; a model whose licence is not on the allow-list commits only the file's hash and revision, and the deriver fetches it in a nightly job that holds the access token; pull-request jobs check only the committed hash and revision and the graph'sderived_fromhash, so no pull-request job needs a secret. Each derived value cites its source by URL and commit or hash. No private names appear in files, commits, pull-request text or issue text, and reports and documents name internal data neutrally, as internal experiments.Checked by: name scan in every repository, which also reads the text of every open issue each night; provenance schema rules; a check that every committedconfig.jsonhas an allow-listed licence in itsmodel.yaml. These are checks on the project’s own repositories, run in CI. No simulation path reads, requires or enforces a licence: the loader checks that data is well formed, and a catalog or coefficient registry a user supplies is not refused for lacking a licence record. - X13 Every behaviour change has a test that fails without it. A validation change (§8.9) to a published number carries success criteria fixed in advance, plus held-out evidence. A verification change that moves published numbers, such as a fidelity fix, carries the price-change report and the scoring harness's figures on published units, and is never rejected for exposing an error elsewhere (VAL-11). A refactor shows identical output on the byte gate and on the dashboard's corpus: the recorded configuration of every ledger unit the dashboard can simulate, re-run through the scoring harness, with no measured value read. That corpus covers shapes the byte gate does not. Each nightly run of the whole corpus records which functions each point reaches. A pull request then runs every point that reaches a function it changes, in parallel; with no such record for its base, it runs the whole corpus. The whole corpus also runs before every release. A pull request that is both a refactor and a behaviour change is split.Checked by: byte gate in inference-sim; the corpus comparison, run by the scoring harness at the pinned commit from each repository's CI, for pull requests declared as refactors; landing gate (VAL-6); mutation checks: on a pull request, the functions it changes and the functions covered by any test it changes are mutated, and only the tests that cover them run; and the full sweep runs nightly and on release tags.
- X14 A pull request's description is accurate: the commit hashes it lists are the branch heads, it merges cleanly, and every invariant it cites exists.Checked by: the description check, a shared workflow run in every repository.
- X15 Each validation rule exists once, in Go, in
blis-substrate. Python tools validate data by calling the substrate'svalidatecommand. A Python check that restates a Go rule must pass a test that runs both on the same set of failing examples.Checked by: the parity test inblis-substrate; a lint that flags schema-like rule code in Python scripts. - X16 A skipped test in a required or gate job is a failure, not a pass, unless it is on the quarantine list of X25. A gate's workflow triggers are among the paths it declares, so removing a trigger needs review. A gate whose last real run is more than seven days old is shown as failing.Checked by: each repository's CI configuration; the nightly status page (§13.4).
- X17 Every repository's protected branch requires its named checks, including formatting and lint. No change merges without a successful run of them, on the tree being merged (X22).Maintainer practice (§4.2): the repository’s own branch protection, set by a person with write access and enforced by the hosting service on every merge; its required checks are the repository’s own jobs. No other repository audits it. The settings are on the maintainer checklist (§13.1).
- X18 Every check in the invariant registry names a known-bad input that must make it fail. A check that cannot fail is not a check.Checked by: a nightly job in
blis-substratethat runs each check on its known-bad input and fails if the check passes; the nightly mutation sweep (T7), which covers substrate validators and the simulator's own checks as well as engine code. - X19 Scripts and generators, in every language, never write an engine default. They leave an unstated setting unstated, or read the default with
inference-sim fields. A rendered scenario or report shows each resolved value with its source: stated by the user, recorded by the measured run (including a value it logs as resolved), the engine's default, or, for a workload-dependent value, a cited measurement (D8).Checked by: the engine-name lint (X2), run on*.pyand*.shin every repository that has scripts; in every scenario generator, a test that a generated scenario contains only stated or recorded values, and workload-dependent values with their citation. The name lint cannot see a bare number under a neutral key, so the second test is the one that catches it. - X20 A commit written with an agent's help says so in one
Assisted-by: <tool>line, and carries noCo-authored-byline naming the tool. An agent never signs off in its own name, since only a person can certify the DCO. Signing a commit and adding a DCOSigned-off-byline are encouraged for the person who takes responsibility for it, and an agent that pushes commits for a person, on that person’s instruction and with that person’s key, may sign and sign off in that person’s name; no check requires either. Release-bot commits are signed with the bot's identity and carry no sign-off. In the router fork, this applies to every commit not reachable from the upstream tag being merged; upstream history is kept as upstream wrote it.Checked by: a commit-policy check, required on every pull request in every repository, over the commits the pull request adds. It checks the form an agent can get wrong: oneAssisted-byline, noCo-authored-bynaming a tool, noSigned-off-bynaming a tool. It does not check for a signature or a sign-off. Maintainer practice (§4.2): the person who takes responsibility signs the commits and signs them off, or instructs an agent to push them in that person’s name. It is encouraged and never fails a pull request. Agent guidance (§4.2): how a commit message names agent help. - X21 Everything a build or a release runs is pinned and traceable. GitHub Actions are pinned by commit, not by tag. Python environments used by oracles and fitting scripts are locked with hashes. Each
inference-simrelease publishes build provenance and a software bill of materials. Each repository keeps the licence and notice files its code requires, includingblis-vllm'sNOTICEfor code derived from vLLM; by policyblis-sglangkeeps one too for code derived from SGLang. Each derived file keeps upstream's licence header (vLLM's SPDX line, SGLang's Apache notice) and states that it was changed, as Apache-2.0 section 4(b) requires.Checked by: a workflow lint that refuses an action not pinned by commit;pip install --require-hashesin every Python CI job; the release job, which fails without provenance and a bill of materials; a check thatLICENSEand any requiredNOTICEexist; the provenance test checks each derived file's licence header. - X22 Every verdict (a CI result or the review agent's review, including its answers to review questions) names the git tree hash of the exact tree it judged. A CI verdict is on the head tree of the pull request. The review agent’s verdict is on the head tree, and is given again after a push. BLIS repositories do not use a merge queue. Every job that scores, fits or checks data states the minimum number of items it must process, and fails below it. No step in a required job ignores an exit code.Checked by: the shared pull-request check in
blis-substrate/.github/workflows, called by every repository, which records each review-agent answer with the head tree; branch protection, which dismisses an approval when a push changes the tree; a workflow lint that refuses ignored exit codes; each job's item count. - X23 A version is written in one place.
stack.json, each repository'sgo.mod,contracts/toolchain, and each application's or harness pin file are the only files that state a version; every other file that mentions one (Dockerfiles, workflow files, documentation) is generated from them. Documentation does not repeat a version those files state: it says “the pinned release” and links to the file that states it. A page names an upstream release only when the page is about that release, or where a citation needs it (vllm/<path>:<lines>@<commit8>), because a line number means nothing without its release. A workflow reads the Go version fromgo.mod. A draft specification underspecs/is a record of what was planned and is exempt.Checked by: a lint in every repository, over every file type except records underspecs/, for version literals of BLIS modules or Go that are not generated. In the router fork the lint covers the files the fork wrote; upstream’s files are kept as upstream wrote them (X4). - X24 Every review finding ends in one of three outcomes before merge: fixed, filed as a linked issue, or rejected with a reason. An issue closes only through a commit or pull request that names it, or as not planned, by a reviewer with write access, with a reason.Checked by: branch protection, which refuses a merge while a review conversation is unresolved; a pull-request check that refuses a thread with no outcome; a nightly job in each repository that reopens an issue closed any other way. Agent guidance (§4.2): answer each thread with its commit or issue, and resolve it once the reviewer is satisfied.
- X25 Tests read only their declared inputs, never sibling checkouts, ignored directories or the user's environment. A test failing on the main branch blocks every merge but its fix, unless it is quarantined with an owning issue and an expiry date.Checked by: CI in a clean container with no sibling checkouts; the quarantine list, which fails CI when an entry expires; a required check that fails while
mainis failing and the pull request does not name the failing test's issue. - X26 In documentation, every source path, symbol, issue or pull-request reference, and every statement that a feature is supported, is either generated (from
surface.json,stack.jsonor the code) or checked against the pinned tree. A pull request carries the documentation change that its own change needs: a path it removes or renames, a behaviour it changes, a setting it adds or drops. A repository that adopts the check clears the findings it already has in the pull request that first requires the check, so the check is green from then on and every later finding is a pull request’s own. No finding is hidden from the check by a list of known ones. Documentation has two kinds of reader and is written for each. A person needs the concept: the design, the invariants, the process and why they are so, with no version, count or other detail that changes without the idea changing. An agent needs pointers: where a rule, a file or a command lives, so it can change the code correctly. Each page is for one of them, says which, and is no longer than that reader needs. A page for people holds no path, symbol, version or count, so there is nothing in it for a check to fail on; the reference check reads the pages for agents, and skips records such asspecs/and RFCs, which say what was true when written. A check that fails a pull request for something it did not touch, or for a detail that was never needed, is a defect in the check, and the fix is to narrow the check or delete the detail, never to add a list of known findings. Documentation is short, it says where a rule, a file or a command lives and links to it, and it leaves out anything that can drift without a check noticing, such as a version, a count, a line number or a list of what is supported at the moment of writing.Checked by: a documentation reference check in every repository, which fails on any reference that does not resolve in the tree the pull request produces. In the router fork the check covers the documents the fork wrote; upstream’s documents are kept as upstream wrote them (X4). Review guidance (§4.2): the review agent asks whether the change leaves a document describing something it removed or changed, and raises a finding when it does. - X27 An open bug labelled as breaking an invariant blocks a release of the repository where the bug lives, and of each repository that ships that code, unless the release fixes only that bug or a security issue, or a release owner records a waiver in the release notes.Checked by: the release bot.
- X28 An issue in a BLIS repository that is cited as the reason for a refusal, for a refused or not-modelled item in a field or surface list, or in a code comment that marks a gap or a divergence, stays open. It closes only in the change that removes or updates every citation of it.Checked by: a nightly job in every repository that resolves each cited issue and fails on a closed one; a pull-request check that refuses a change closing an issue still cited elsewhere; the nightly job also reports an issue closed outside a merge.
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.
| Kind | Kept by | When | Examples |
|---|---|---|---|
| Automated | A 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 practice | A 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 guidance | The 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 byinference-sim. Its data reaches a run either from a path or from the embedded copy (§3.3). Fitting scripts reach the cost model throughinference-simwith--catalogand--registry, or in-process throughkernel.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
contractschanges 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/harnessandcontracts/conformancemay also usegolang.org/x/tools, at the versionblis-substrate'sgo.modrequires, for their analysis checks.Checked by: import test; API-compatibility check of thecontracts/packages against the last substrate tag. The check runsapidiffat a pinned version throughgo 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 undercontracts/imports it andgo.moddoes 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.jsonand the safetensors headers of its checkpoint, and never edited by hand. No runtime code readsconfig.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 incontracts/specresolves 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 opensconfig.json; a lint for model-name literals inkernel/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
--checkon 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/conformanceincludes 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 inblis-substrate,blis-vllmandblis-sglang. - SUB-11 Every field of
PoolPlan,StepShapeandTransfer, and every coefficient family, changes some price when it is changed, or is listed as not yet priced with an issue. HereTransfermeans theplan.Transferthat the cost model receives; the lifecycle fields ofengine.Transfer(ID,RequestandBlocks) 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 inkernel/; 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-substrateis one Go module: nogo.modexists 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 nestedgo.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
PoolPlanfields, 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 arePoolPlanfigures. 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; inblis-substrate's error job, a comparison of the total with recorded GPU start-up logs, which are ledger units with rolevalidate; a start-up refusal ininference-simwhen 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.jsonand 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 itsPoolPlan.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 asconfig.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, theResult, and the per-point record the scoring harness writes. Each substrate release publishes them as JSON Schema, generated from the Go types.blis-config-searchandaccuracy-dashboardvalidate every document they read or write withinference-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 inblis-substratethat 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-substrateand 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
vllmwith that version. Its tags arev<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.Enginefromcontracts, which includes reporting its own field list; emitsPoolPlanandStepShape; publishesfields.jsonandsurface.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, andinference-sim fieldsprints 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.jsonlists 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
KVCacheSpecRegistryhas a status insurface.jsonthat X9 allows. For a modelled one, the manager thatKVCacheSpecRegistrymaps 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 againstKVCacheSpecRegistry(§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
sglangwith that version. Its tags arev<SGLang release>-blis.N(§14.1). - Owns, never holds, interfaces
- As
blis-vllm, for SGLang. It imports nothing fromblis-vllm; shared machinery comes fromcontracts/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
ServerArgsand environment variables, and the T1 default-resolution tier. - SGL-4
surface.jsonlists 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
BlockStoredper 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). ConsecutiveBlockRemovedevents 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.jsonthat 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 thecontracts/embedrouter 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:
No change alters a routing, admission, flow-control or scoring decision: no condition, weight, threshold, default or plugin choice.Checked by: a classifier in
Kind Required form clock read or deadline time.Now,time.Since,time.Afteror a context deadline becomes the runtime's clock (rt.Now,rt.Clock())timer or periodic work a goroutine looping on a ticker becomes rt.Every(ctx, name, interval, fn)goroutine or blocking wait go f()becomesrt.Go(f); a blockingselectbecomesrt.Park(condition)random draw a draw from a global or unseeded source becomes a draw from a named stream, rt.Rand("<name>")iteration order when hosted, a map or sync.Maplisting that reaches a decision is sorted, or sorted and then shuffled from a named stream; when not hosted, upstream's order is keptnetwork call when hosted, an outbound HTTP call goes through hosting.Transport, which the host serveswork ordering work 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 state state 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 ithosted substitute when 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 adaptation an upstream test is changed only to follow a signature another allowed kind changed startup assembly the runner is split so a host can build the router without servers, Kubernetes reconcilers or leader election hack/hosting-lintthat 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.Newpasses 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 importscontracts/embedfromblis-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:
TestSupportedTypesAreHostedByTheForkininference-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 ininference-simthat 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.Newnever 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 inhack/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 reachrt.Park. Channel and wait-group waits on decision paths are converted tort.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 reachrt.Parkbefore 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
Environmentthe engines are built with (§6.3). Link sharing among transfers and the cross-domain traffic of step collectives (SIM-13). The command line, theResultdocument, 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, besidescontracts/), 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). EmitsResultdocuments andstack.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 fieldengine_version, from beforeengine: {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 usingengine_versionwith a message namingengine: {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 thatstack.jsonmatchesgo list -m all,go versionandguides.lockfor 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-simthat computes coverage from two inputs, the llm-d guides commit pinned inguides.lock(whichstack.jsonrecords) and each engine's surface list, and publishes per-item deployment counts throughinference-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, andinference-simrecords 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
Resultis recorded in theResult'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
Resultwhen 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
Resultread 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-simshares 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 throughinference-sim fields, catalog identities throughinference-sim catalog resolve, metric definitions throughinference-sim metrics, acceptance profiles throughinference-sim acceptance, a cluster's resolved packaging throughinference-sim catalog resolve, and the schemas of its inputs and outputs throughinference-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 fieldsat 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 againstinference-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 outblis-substrate/catalog/ledgerat the substrate commit the build uses, and compare it with the ledger at the merge base (§3.3), because computingselectandheldroles needs the full ledger. It gets everything else from seveninference-simcommands:fieldsprints each engine's field list;ledgerprints the publishedfitandanchorunit lists;catalog resolvemaps an InferenceX model or chip name to a catalog identity, or reports that none exists;coverageprints each surface item's deployment count in llm-d's guides;invariantsprints the invariant registry at the substrate versioninference-simpins;metricsprints the metric registry (DEV-8);schemaprints the JSON Schema of every document BLIS reads and writes, andschema validatechecks a document with the Go validators (SUB-19).
- 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 theinference-simrelease 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:
- The engine pads the batch, and the cost model prices the padded shape. vLLM pads in its model runner, to token-sized graph buckets and data-parallel padding rules. SGLang captures decode graphs by batch size. Each engine reports the shape it launches in
Padded. When data-parallel ranks run in lockstep, padding depends on every rank's tokens in the same step, so aStepShapedescribes all the ranks that run in lockstep. The engine decides which ranks those are. In vLLM v0.32.0, data-parallel ranks run in lockstep only for mixture-of-experts models; for a dense model each rank is an independent engine (v1/engine/core.py:1473-1481), so itsStepShapehas one rank. The engine names the exact kernel implementation. SGLang, for example, picks an attention backend per chip and model, and forces a page size of 64 for FlashMLA (
arg_groups/overrides.py:1146-1154). The engine's lowering records that choice inKernelImpl, and the coefficient registry prices that exact implementation.The coefficient registry's lookup key uses a hash of the kernel's source (the kernel hash,
CodeHash), not a package version. Engines pin library versions in their lock files (vLLM v0.32.0 pinsflashinfer-python==0.7.0.post1), and those versions move often without changing a given kernel. A coefficient lists the kernel hashes it is valid for. A version bump that leaves a kernel unchanged therefore needs no substrate change. A kernel whose source changed gets a new key, and the substrate must supply a value for it (§9). Libraries shipped only as binaries, such as cuBLAS, cuDNN and FlashInfer's precompiled kernels, are keyed by the SHA-256 of the shipped library file together with the target GPU architecture. For a kernel built from source, the hash covers the source and the target GPU architecture. A compiler or CUDA toolkit change can change a kernel's speed without changing its source; the engine records the toolkit version with each kernel implementation, and the coefficient registry treats a toolkit change like a library change that leaves the source alone: the entry stays, and the landing gate (VAL-6) shows whether the old value still holds. Each engine release ships a generated table,kernel_impls.json, of every kernel implementation it can emit, for its pinned library versions and for every library version seen in InferenceX runs. Runtime tuning (autotuned launch configurations, library heuristics, and tuned-configuration files such as vLLM's per-GPU fused-MoE configurations) is part of the implementation's measured behaviour, not a separate key. If a lookup has no entry for an exact implementation, the cost model takes the most trusted entry §9.2 offers for that chip, at worst the primitive family'srooflineorassumedentry, and the price records that method (VAL-3). Kernels that ship inside an engine, such as vLLM's custom all-reduce, use the library namesvllm-csrcandsglang-kernel. These are library names, and the engine-name check (X2) allows them as such.- The engine decides which KV blocks move between tiers, and the substrate prices each move. Which blocks move between GPU memory, host memory and storage, when, and under which policy, is engine behaviour: vLLM's offloading connector and tiering factory, SGLang's HiCache. The engine reports each move as an
engine.Transfer. The cost model turns each transfer into a demand: bytes per link, from catalog device facts and coefficient registry tier coefficients. The engine returns its transfers to the simulator with each step. The simulator asksenv.TransferDemand(engine.Plan(), t.Move)for each one, throughenv.DemandFor, which returns the bytes each link must carry.env.DemandForfirst refuses a transfer whose report andMovedisagree, or that its plan cannot make. An engine that starts a transfer implementsengine.Planned, whosePlan()returns thePoolPlanthe transfer is priced against. The engine states a peer's node and rack from the request (engine.Request.Peer), which a host must set on a request that pulls KV from a peer. The simulator fills in nothing. Two limits are deferred to M3. Aplan.Transferhas one sender and one receiver, so for a pool that spans nodes the engine states the node that holds the tier, and the cost model prices that node's link (blis-substrate#48). Aplan.Transferhas one token count, so the blocks each KV cache group moves, which differ in a hybrid model, are not stated (blis-substrate#49). - Sharing a link over time is the simulator's job. A transfer can span many steps, and several transfers can share one link. Dividing a link's bandwidth among concurrent transfers depends on when they happen, so it belongs to the simulator's event loop. This is the one place the simulator does resource arithmetic, and X3 states it as the only exception. Everything else about tiers comes from the engine or the cost model: capacity, bytes per block, and which blocks move.
- The engine combines pipeline stages. With pipeline parallelism the engine emits one
StepShapeper stage. The cost model prices each stage alone. How stages overlap, and where bubbles occur, is the engine's batch-queue behaviour, so the engine combines the stage times and reports them to the simulator.
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:
- 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.Memorysums 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'sgpu_memory_utilizationor SGLang'smem_fraction_static, and fills inKVBudget. - Run. Each step, the engine forms a
StepShapeand asksenv.Pricefor 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
| Difference | vLLM v0.32.0 | SGLang v0.5.23 | Where the contract allows it |
|---|---|---|---|
| Scheduling ahead of the running step | asynchronous 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 contains | prefill and decode tokens mixed in one step | a 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 verification | verify tokens inside a mixed step | a separate TARGET_VERIFY step | Mode = Verify; Drafts; DraftLayers |
| P/D merge | none | decode merges a batch whose KV arrived from prefill, with no forward pass (PREBUILT) | Mode = Prebuilt, priced at no compute |
| KV page size | 16 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 graphs | buckets by token count | decode buckets by batch size; "breakable" prefill graphs | GraphPolicy; 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 KV | Transfer fields for who moves the bytes, reservation and handshake |
| KV events for the router | one 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 setting | gpu_memory_utilization, default 0.92 | mem_fraction_static, derived from a reserve formula | KVBudget 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.
| Part | What it decides | vLLM, at the pinned release | SGLang, at the pinned release |
|---|---|---|---|
| Scheduling | which requests run in each step, how many tokens each gets, and when a request is preempted or finished | Scheduler and AsyncScheduler; the engine-core loop and its batch queue; data-parallel coordination; structured-output and LoRA admission; speculative proposals and their acceptance | the scheduler's event loops, normal and overlap; PrefillAdder; scheduling policies; retraction; speculative proposals and their acceptance |
| KV-cache management | how 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 evicted | grouping 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 events | the 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 tiering | when KV moves between GPU memory, host memory, storage and other machines | the connector framework and the connectors registered with KVConnectorFactory; the offload spec, manager and tiering layers | the HiCache controller, host pool and storage backends; the disaggregation transfer backends |
| Resolution and lowering | the settings the engine actually runs with, which kernel implements each operation, and the shape of each launched batch | configuration resolution; attention-backend and kernel selection; CUDA-graph capture sizes and the padding of each launched batch | ServerArgs 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.
7.3 The boundary for each upstream
| Upstream | Real code under test | Replaced | Compared each step |
|---|---|---|---|
| vLLM v0.32.0 | Scheduler.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.23 | Scheduler.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.
| Tier | What is compared | When |
|---|---|---|
| T0 | Model 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 |
| T1 | Configuration: 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 |
| T2 | Scheduler and KV cache decisions | every pull request |
| T3 | Engine loop: asynchronous or overlap scheduling, data and pipeline parallelism, and launched-shape padding (compared with the upstream's own padding functions) | every pull request |
| T4 | KV connectors and P/D protocols | every pull request |
| T5 | KV tiering: offload, eviction and promotion across tiers | every pull request |
| T6 | Metrics and KV events, byte for byte with what llm-d reads, after the manifest's replacement of the batch timestamp | every pull request |
| T7 | Mutation sweep: every deliberate one-line break of a ported function is caught | on 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.
| Upstream | What the generator reads |
|---|---|
| vLLM | connectors 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. |
| SGLang | transfer 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-d | registered 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:
- modelled, naming the oracle scenarios that cover it. This is the default goal for every item.
- refused, meaning the engine refuses it by name until it is modelled. The item names its issue and the backlog counts what it blocks (§11.6). A refused item waits in the backlog, and the item that blocks the most dashboard points or published deployments is the next one modelled.
- out of scope, for an item BLIS will not model. Only three reasons are allowed: the item runs only on hardware BLIS does not simulate (for example AMD-only or TPU-only); it does no work the simulator prices (for example a multimodal encoder when BLIS simulates text serving); or it is deprecated upstream and slated for removal. The reason names which of the three it is, with a citation to the upstream code or announcement. An item may be out of scope only while its weight (below) is zero. Otherwise it is refused, and appears in the backlog.
"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
- FID-1 The manifest lists every replacement. Any write the harness makes outside a listed site fails CI, and a changed list carries a new version. The configuration the oracle resolves equals, outside the fields the manifest lists, what the upstream's CUDA platform would resolve for the chip the scenario names. Restoring a CUDA value that depends on choosing a CUDA attention backend is a transcription (FID-8), checked against recorded CUDA start-up logs. Every read of the current platform reachable from ported code is listed in the manifest.Checked by:
oracle/check_mocks.py, fromcontracts/harness, in each engine repository. - FID-2 The oracle runs in a locked Python environment at the pinned upstream commit, and every recorded trace records that commit, the lock file's hash, the scenario's hash, the seed and the manifest version.Checked by:
oracle/check_lock.pyand the trace provenance check. - FID-3 A recorded trace's decisions change only in a commit carrying a
golden-reviewed:line that names the trace and gives the reason. Tools refuse to rewrite traces unless an explicit update flag is set.Checked by:oracle/check_goldens.py; the oracle runners refuse to write traces without the update flag. - FID-4 Every ported function is exercised by at least one recorded scenario.Checked by: coverage gate.
- FID-5 A framework (connector base, offload tiering, HiCache) is marked modelled in the surface list only when it has recorded and direct traces at its own interface (§7.6). A feature built on the framework does not supply them. A part of a framework without those traces is a separate item with its own status. It is not marked modelled, whatever the status of the other parts.Checked by: a surface-list check that a framework marked modelled names recorded and direct traces taken at its interface, and that each part without them is a separate item that is not marked modelled.
- FID-6 A property asserted about an engine port, such as a monotonicity law, is first run against the upstream oracle. It is adopted only if the upstream satisfies it on the oracle scenarios. A law about the cost model names the upstream behaviour it rests on.Checked by: the property tests run in the oracle tier before they run against the port.
- FID-7 For every catalog model and each supported layout, the resident weights an engine reports in its
PoolPlan(per-rank shape, stored data type and sharding, after the engine's weight post-processing) equal what the pinned upstream allocates. The graph's tensors equal the checkpoint's safetensors headers.Checked by: oracle tier T0 in each engine repository, run whenever a substrate release changes the catalog or the engine moves to a new upstream release. - FID-8 The harness may not contain a copy of an upstream decision function. A harness file with a
Derived fromheader, or a function named like an upstream function, is refused unless the manifest declares it as a transcription.Checked by:oracle/check_mocks.py. - FID-9 The engine is tested as the simulator drives it, not only on its own. Each recorded trace also records the timed inputs the engine received: arrivals, completions, transfer ends and aborts. Replaying those inputs through
inference-sim's driver intoengine.Enginereproduces the trace's decisions step by step.Checked by: the coupled replay test ininference-sim's CI, over every engine repository's recorded traces. - FID-10 Every compared field and metric series takes its neutral value and at least one other value somewhere in the scenario set, so a comparison of it can fail. The comparison is two-sided: a field or series that appears on only one side is a mismatch.Checked by: a value-coverage report in each engine repository's CI, which fails on any field or series seen only at one value.
- FID-11 The work a step shape asks the cost model to price equals what the pinned upstream launches for the same step, for each layer: the rows of every matrix multiply, each operation's FLOPs and bytes, the token count of every draft pass, and the context entries each context-dependent operation reads, bounded by its window where it has one. Rows and token counts match exactly; FLOPs and bytes match within a stated tolerance. FID-7 compares what is resident; this compares what runs.Checked by: an oracle comparison, run with T0 and on engine pull requests that change how step shapes are emitted. It executes the upstream forward pass on meta tensors, at the padded batch the upstream launches, under a PyTorch dispatch mode that counts each operation's rows, FLOPs and bytes, with the ranks under a fake process group. Each opaque custom operation, such as attention or a fused mixture of experts, is counted by a rule the manifest lists. Routed-expert rows are compared in total (tokens times experts per token), since routing depends on data. Context reads are counted from the upstream's own attention metadata: sequence lengths, block tables and windows. Known-bad inputs: a matrix multiply priced at fewer rows per token than the upstream runs, and a windowed gather priced over the whole context.
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.
| Term | Meaning | In BLIS |
|---|---|---|
| component (atomic model) | a part of the system with its own state | an engine instance, the router, a link, a client (one source in the workload generator) |
| coupled model | components connected so that one's output is another's input | a pool, the cluster |
| time advance | how long until the component's next event, given its state | the time of an engine's next scheduled wake-up |
| internal transition | the state change when that time arrives | a step ends |
| external transition | the state change when a message arrives | a request arrives at an engine |
| confluent transition | the state change when both happen at the same instant | a request arrives exactly as a step ends |
| output function | the messages a component sends, just before an internal or confluent transition | tokens, metrics and KV events an engine emits |
| experimental frame | what 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:
- Every component whose internal event falls at this instant runs its output function.
- 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.
- 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:
- the generator: the workload and its input models (§8.5), or a trace to replay, with its seeds;
- the initial state: empty caches, or caches warmed by a stated rule;
- the phases: warm-up, measured window and drain, each defined by a rule;
- the stop rule: a request count, a horizon (a simulated time at which the run stops), or both;
- the acceptance conditions under which the run counts for this frame, such as achieved concurrency within a tolerance of the target, and few enough requests unfinished at the horizon;
- the metric definitions (§8.7).
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.
- A terminating run discards nothing at its start, unless the measured run did. Where the run has random input, its estimate is the mean over several independent replications (runs with different scenario seeds), with a confidence interval from the spread between them. InferenceX benchmarks are terminating runs. Their frame copies the measured run's start state, stop rule and requests (COV-12), so seeds vary only what the requests do not fix.
- A steady-state run discards a warm-up, the stretch at the start where the system is still filling. The warm-up length is found by a stated method, such as MSER-5 (which picks the cut that minimises the variance of the remaining mean), applied to each output series, including the prefix-cache hit rate, which often settles later than queue lengths. Its estimate comes from replications, or from batch means: one long run, after warm-up, split into 10 to 30 equal batches, long enough that neighbouring batch means are not correlated. A percentile gets its interval from replications, each holding enough samples for that percentile, never from batch means.
- A run whose measured window shows a trend has not settled. If the backlog grows, the system is overloaded: its latency figures describe a transient and are not reported, and its departure rate is reported as saturation throughput only if the departure rate itself shows no trend.
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
- DEV-1 Components interact only through timestamped messages. No component reads another component's state.Checked by: in the router fork and
inference-sim, unexported constructors for the router's view types, so only the message-delivery code can build them; an import-graph test that finds no import of engine-internal packages from router-side packages; the coupled replay test (FID-9). - DEV-2 Each instant is processed in the three phases of §8.2, and messages are ordered by (time, class, sender, sender's send count). No global counter enters the order. Each class's position has a stated reason.Checked by: in
inference-sim, a test that permutes the scheduling order of same-instant events from different components and compares theResult; an assertion that no two queued messages share a key; a run of the corpus under a second priority-queue implementation. - DEV-3 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, and whether the upstream's order there is fixed or a race between threads. A declared race is a listed divergence, with the order BLIS uses.Checked by: the oracle, which records where the upstream's loop reads its input queue (each read site is listed in the manifest; where that loop is transcribed, as vLLM's data-parallel busy loop is, the check runs against the transcription, FID-8) and checks that reads happen only at the declared boundaries; a property test in
inference-simthat, under the cost model's timing, every external input takes effect at its declared boundary (FID-9). - DEV-4 No component observes a message before its timestamp. A published value carries its time and a publication sequence number, every cache of it is keyed on both, and the delay between publication and observation follows the deployment's declared scrape and delivery settings.Checked by: in
inference-sim, a trace check that every observation is the latest value published at or before its time minus the declared delay; a lint on cache keys of published values. - DEV-5 Only finitely many events happen in any finite stretch of simulated time. Every zero-time cycle between components has a declared bound. An engine step that changes no state is an error unless the engine scheduled it as a wake-up or declared it as an upstream idle step. Events per request, and wall time per event, stay bounded as runs grow.Checked by: in
inference-sim, a watchdog on events per instant across the whole model; a nightly scaling run at 104, 105 and 106 requests that fails on growth in events per request or wall time per event. - DEV-6 Every random draw comes from a substream named by component, purpose and entity, seeded from the SHA-256 of the scenario seed and the name, by a generator named in the frame. A request gets the same random inputs whatever happened to earlier requests, in every configuration.Checked by: in
inference-sim, a test that changes one server setting under a closed-loop session workload and compares every request's prompt and output lengths byte for byte; the lint against unnamed randomness (X6). - DEV-7 Every run records its experimental frame (§8.6). Run, export and replay under the same configuration give an identical
Result. A converted or composed workload keeps, phase by phase, the offered load, the variability of gaps between arrivals, the joint length distribution and the session structure. A run that fails its frame's acceptance conditions produces no figure.Checked by: ininference-sim, a round-trip property test over every workload feature (closed loop, sessions, accumulated context, P/D, deadlines); two-sample tests for each converter against its source. - DEV-8 Every reported metric names its definition and scope (§8.7), and a comparison is refused unless both sides have the same definition, scope and aggregate.Checked by: the metric registry in
contracts/, which the simulator reads directly and the dashboard and config search read throughinference-sim metrics; a test that they compare only figures with matching definitions, scopes and aggregates. - DEV-9 Each request's fate is written once, at one site, and every latency sample is derived from it. The number of requests the generator created equals the number of fates written. A latency statistic states how it treats requests unfinished at the horizon.Checked by: in
inference-sim, the fate type, which fails on a second write; the arrival-count check at the end of every byte-gate scenario; a known-bad scenario for each fate (X18). - DEV-10 A steady-state run is classified as settled, overloaded or incomplete by one detector, and only a settled run gives a latency figure for steady state. The detector states its false-alarm rate and its power for each window length.Checked by: the detector in
inference-sim, reached by the applications throughinference-sim metrics, and its reference tests against single-server queues whose behaviour is known exactly: open M/M/1 (exponential service times) and M/G/1 (general service times) at loads from 0.5 to 1.5 of capacity, including 0.9 to 0.99 where settling is slow, closed queues for fixed-concurrency benchmarks, and heavy-tailed service, which must be classed incomplete at practical window lengths. - DEV-11 A figure that compares configurations, or that is published as a property of BLIS, carries an interval from replications or batch means and states which uncertainty that interval covers, or states that the run has no random input and which uncertainty it therefore leaves out. Comparisons are paired under common random numbers, have three outcomes, and control the chance of a false winner across all comparisons made.Checked by: the report schemas of
blis-config-searchandaccuracy-dashboard; a coverage test in which the full procedure, repeated 1,000 times on a model with a known difference, covers that difference at its stated rate. - DEV-12 Each change is a verification change or a validation change by what it does (§8.9), and is judged as such. A change that is both is split.Checked by: in every repository, the change class in the pull-request template, and a check that the outputs that moved match the declared class. Review guidance. Does this change alter what is modelled? Expertise: expertise in the changed directory. Applies when: a pull request is declared a verification change but moves a
Resultoutside the byte gate. - DEV-13 A request's sampled output length is written by the workload generator and read only by the trace writer, by replay, and by the engine's stop check, where it acts as an end-of-sequence token at that position. The request's maximum token count stays what the client stated. Every other part of BLIS, including the engine's own scheduler, the router, admission and flow control, sees only what its counterpart in the real system sees: the maximum token count and the tokens emitted so far. The oracle delivers the length the same way, as a forced end-of-sequence in the sampler that the manifest lists.Checked by: a field-access lint in
inference-simand each engine repository that refuses any read of the sampled length outside these declared sites and tests, with a known-bad input (X18).
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:
| Rank | Method | Where the value comes from | Example |
|---|---|---|---|
| 1 | measured | fitted to microbenchmarks of this exact kernel implementation on this chip, with held-out error reported | attention decode rate for one library on H200, fitted to AISimulate sweeps |
| 2 | calibrated | a 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 implementation | a GEMM ramp shaped by FLOPs and bytes, with a fitted efficiency ceiling |
| 3 | transferred | measured on a closely related implementation or chip, and moved here by a stated rule, such as a ratio measured on a third chip | an all-reduce coefficient measured on H100, moved to H200 by the ratio of the two chips' measured link bandwidths |
| 4 | literature | a figure from published work: a paper, a vendor whitepaper, or a library's benchmark, cited exactly | an achieved bandwidth fraction reported by the GPU kernel's authors |
| 5 | vendor_spec | a datasheet figure used directly, nominal rather than achieved | a link's rated bandwidth, before any efficiency is measured |
| 6 | roofline | computed 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 chips | a new chip with datasheet figures but no measurements |
| 7 | assumed | a 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
- Labelling. Every coefficient records its method, and every price records the methods of the coefficients it used.
- Error measurement. Each method's typical error is measured by leaving one chip out: for chips that have a measured value, compute each less trusted method's estimate as if the measurement did not exist, and compare. That error is published per method and per primitive family.
- Replacement. For each lookup there is at most one entry per rank, and the cost model uses the most trusted one, the entry with the smallest rank number. Removing a more trusted entry (one with a smaller rank number) needs a
supersedes:record that cites a defect issue, such as a measurement found to be wrong. That record triggers a review question. Replacing a measured value with a newly measured one is a normal accuracy change, with held-out evidence. - Backlog. Every value below rank 1 appears in the blocker backlog (§11.6), weighted by how many points it prices.
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
- VAL-1 Every coefficient records exactly one method from the list in §9.2, or
not_charged, with the sources that method needs:Checked by: schema rules inMethod Required sources measured,calibratedthe data units it was fitted to transferredthe source coefficient and the transfer rule literaturean exact citation vendor_speca datasheet citation rooflinethe catalog facts it was computed from assumed,not_chargeda written reason blis-substrate/validate. - VAL-2 For every primitive family and every catalog chip, some rank has a value. A missing measured value never causes a refusal.Checked by: completeness test over every primitive and catalog chip (SUB-7).
- VAL-3 Each price records, among the coefficients it used, the least trusted effective rank, and names its method and any flag. An extrapolated lookup counts as
transferred, and an unidentified entry asassumed(VAL-10).Checked by: step-time result field; checked in kernel tests. - VAL-4 Each lookup resolves to one entry per rank, and the entry with the smallest rank number is used; flags do not change which entry is chosen, only how the price reports it. Where several entries of one rank match a lookup at different scopes, one written order of precedence over every scope dimension (chip, model, layout widths, node) picks one, whatever order the entries were loaded in. Two matching entries that the precedence does not order are refused at load, and a price's provenance names every matching entry it displaced. A more trusted entry is removed only with a
supersedes:record citing a defect issue, in a pull request that the reviewer with write access approves.Checked by: validator check against the previous release, plus review guidance for the review agent; a validator rule that refuses unordered matches; a cost-model test that resolves every pair of scope kinds in both load orders and agrees with the validator (X15). - VAL-5 The typical error of each method is measured by leaving one chip out, and published per primitive family, using data units with role
validate(§10).Checked by: nightly job. - VAL-6 A change to a measured or calibrated value passes the landing gate. The fit and held-out split and the success criteria are committed first. Held-out error and distribution distance must not get worse on any lane, and must improve on the target lane. For each lane the gate computes, on that lane's held units, the paired bootstrap interval of the candidate's error minus the base's error. A lane fails only when that whole interval lies above zero, with the Holm correction for the number of lanes compared. The target lane passes only when its whole interval lies below zero. Every changed
measuredorcalibratedentry is selected by a lookup in at least one operator-level or whole-run gate run. An entry no gate run selects, such as a coefficient for a chip with no held data, is reported as unjudged and lands only with a person's approval. Figures come from a script. A whole-run lane that gets worse does not by itself fail a change whose operator-level held-out check passed: the gate records the regression as a defect in another term (VAL-11), and the change needs a person's approval.Checked by: on fixtures per pull request, full data nightly; operator-level gates inblis-substrate, whole-run gates inaccuracy-dashboard's gate job (§13.5). - VAL-7 A coefficient scoped to a single model marks a cost that the cost model's general forms do not yet explain. It names the missing form. The number of such coefficients only goes down.Checked by: ratchet test.
- VAL-8 A lookup matches a coefficient only for the exact kernel implementation that runs. A value taken from a different implementation is a
transferredentry (D5). Every coefficient registry entry is selected by a lookup on the conformance scenarios, or its kernel hash appears in thekernel_impls.jsonof an engine release that some supportedinference-simrelease pins.Checked by: the cost model's lookup, which has no fallback to a sibling implementation; a test that lists entries matching neither condition, which are removed in the next substrate release. - VAL-9 A coefficient states what its measurement includes: device time only or host issue time as well, captured or eager execution, and which harness collected it. The formula that reads it states the same, and the two must agree. A formula that needs both device and host time reads two coefficients, never one that mixes them.Checked by: a validator rule matching each coefficient's measurement boundary with the boundary its reading formula declares.
- VAL-10 A measured or calibrated entry records the interval its fit supports (95%, by bootstrap or profile likelihood) and the range of inputs its fit data covered: shapes, token counts, ranks, chip and kernel. A price that reads an entry outside that range records the lookup as extrapolated, which ranks it with
transferredfor VAL-3 (§9.3). An entry whose interval is unbounded, or wider than half its value, is flagged unidentified: its data do not determine it, and it ranks withassumedfor VAL-3, while keeping its method and its citations (D1).Checked by: validator rules on the interval and range fields; a test that extrapolated lookups report it; the fitting scripts' identifiability check. - VAL-11 An operator-level check judges a pricing form; an end-to-end check judges how forms combine. A form that passes its operator-level held-out check but makes end-to-end error worse shows that another term is wrong. The disagreement is recorded as one defect, in the other term. The end-to-end figure is not used to reject the form that passed, or to keep a known error. No coefficient or form is kept because it cancels another error.Checked by: review guidance for the review agent; a lint that refuses comments or entries marked as a known over-charge or under-charge without a linked defect. Review guidance. Is this change kept or rejected because it offsets an error elsewhere? Expertise: expertise in the changed directory. Applies when: a pricing change lands or is rejected with end-to-end evidence that contradicts its operator-level evidence.
10The data ledger
10.1 Sources
Three external sources feed BLIS:
- SemiAnalysis InferenceX: end-to-end serving benchmarks for vLLM, SGLang and other engines, plus collective benchmarks (CollectiveX), operator benchmarks (OperatorX), agent traces and speculative acceptance curves.
- NVIDIA AISimulate: operator sweeps per chip, collected under vLLM, SGLang and TRT-LLM.
- NVIDIA's forward-pass dataset on Hugging Face (
nvidia/aisimulate-fpm-dataset, Apache-2.0): whole forward-pass timings per model, chip and layout.
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:
- one InferenceX benchmark run of one deployment and recipe fingerprint at one concurrency, with the measurement of each attempt that produced one;
- one point of a CollectiveX or OperatorX run, such as one token count;
- one point of an AISimulate sweep, such as one matrix shape;
- a forward-pass cell.
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.
| Source | Identifying fields |
|---|---|
| InferenceX end-to-end | source, 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, OperatorX | source, run id, operation, configuration, sweep point |
| AISimulate | source, file path, git blob hash, sweep point |
| Forward-pass dataset | source, dataset revision, model, chip, layout, regime |
Each unit has exactly one role:
| Role | May be used to |
|---|---|
fit | fit a coefficient |
select | choose between candidate methods or forms |
validate | measure a method's typical error (VAL-5); sanity checks |
held | judge an accuracy change at its landing gate |
anchor | report public accuracy on the dashboard |
report | context 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/:
- the sources, each pinned to an exact revision;
- the units, by
unit_id, identifying fields and role key; - the ingest report: for each source revision, the records read, those merged as duplicates, those joined into one unit, and those left out, each with its reason;
- the cut-points and the partition version;
- the legacy table and any alias tables (below);
- the hash of the seed.
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:
| Role | Who learns the members |
|---|---|
fit | Published with each substrate release, as a list of unit identifiers. Fitting scripts, people and agents read it. |
anchor | Published, because the dashboard shows these points. |
select | Only 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. |
validate | Only 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. |
held | Only 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. |
report | Published; 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
- Append-only. Roles for new units are drawn inside the jobs that hold the seed, the first time a unit appears, and before any job uses it. An existing unit keeps its role unless a new partition version, approved in a pull request by the reviewer with write access, says otherwise.
- Legacy units keep their old side. A partition made before the ledger existed is carried over through
ledger/legacy-map.yaml. Each legacy unit keeps its old label, fit side or held side, and the keyed rule chooses only within that side:fitorselect,heldoranchor. A unit whose legacy records carry both labels takes the fit side, because some of its records may already have been fitted on, and data that was fitted on cannot judge a fit. This happens when the new identity joins records the old partition split, for example by removing the image digest. A new unit whose role key holds legacy units takes their side, and the fit side if they hold both. Legacy units of one deployment can still be on both sides, because the old partition split deployments by concurrency; this is one more reason legacy held units are not blind. A coefficient fitted from legacy records before the ledger existed cites their units. A substrate release refuses such a coefficient while any unit it cites is inselect; it is refitted from thefitlist first (D1). The ingest tool generates the map from legacy point identifiers to units and commits it with the legacy table, listing each unit that takes a side by these rules. Because the old split is public, legacy held units are not blind (above). - Stable when identities are recomputed. If InferenceX renames a field, or a change to the ingest tool changes how identifying fields or role keys are computed, the affected
unit_ids or role keys change, and units would be redrawn: a held unit could become a fit unit. So such a change must ship an alias table from old to newunit_idand role key, and an aliased unit keeps its role. The alias table is generated, not written: CI runs the merge-base and candidate ingest tools on the same pinned source revision and pairs the identities each gives the same records. A change whose aliases would give one new unit or role key two different roles needs a new partition version, which a person approves. The ingest code detects such a change from the source's contract version (§12.8).
When a partition version is retired, its seed is published. The committed hash then lets anyone check that the split was drawn as claimed.
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
| Source | fit | select and validate | held and anchor |
|---|---|---|---|
| InferenceX | end-to-end terms; CollectiveX and OperatorX series | a separate share of end-to-end role keys | held: landing gates. anchor: the dashboard. Both include SGLang deployments. |
| AISimulate | operator coefficients per kernel implementation, including the 313 files collected under SGLang, for kernels SGLang runs | held-out sweeps (whole files); cross-lane comparisons for transferred values | none: operator-level only |
| Forward-pass dataset | calibrated composite terms, such as how much compute and communication overlap within a step | cells kept apart for choosing forms and for measuring method error | none |
10.4 Invariants
- D1 Every value computed from ledger units cites them, whatever its method. A fitted coefficient cites only units whose role is
fit.assumedmeans no data unit influenced the value.Checked by: a validator rule; the fitting scripts, which write the units they read. Review guidance. Was this value read from, or tuned against, any measurement? Expertise: coefficient registry expertise. Applies when: anassumedentry is added or changed. - D2 No unit is read under two roles across all consumers.Checked by: a nightly join over every consumer's record of the units it read, run inside the nightly run of
blis-substrate's gate job. - D3 Every source revision is pinned by commit, hash or run id. "latest", "main" and "unknown" are refused.Checked by: a ledger check that refuses
latest,mainandunknownas revisions. - D4 Roles are drawn by the keyed rule, per role key, so every unit sharing a role key has the same role, except a legacy unit, which keeps its legacy side even where units of its role key fall on both sides. For a unit in the legacy table, the rule chooses only within its legacy label: FIT gives
fitorselect, HELD givesheldoranchor. A legacy unit whose records carry both labels, and a new unit whose role key holds legacy units of both labels, are on the FIT side; a new unit whose role key holds legacy units of one label takes that label. Roles are append-only, and the ledger stores no role except in the three published lists. A change that alters a source's identifying fields, or how the ingest tool computes them or role keys, ships an alias table, and aliased units keep their roles.Checked by: a check against the ledger at the pull request's merge base; a check that no committed file lists roles, other than the three published lists that the release job writes and the legacy labels inlegacy-map.yaml; a check that the committed unit list equals the ingest tool's output for the pinned source; a check that every unit at the merge base keeps itsunit_idand role key, or is covered by an alias table or by a new partition version a person approved; a check that each alias table equals the one generated from the merge-base and candidate ingest tools on the same source revision. - D5 A coefficient's fit data exercised the kernel implementation the coefficient is keyed by, meaning the same library, variant and kernel hash (
CodeHash). Otherwise it is atransferredvalue with a stated rule.Checked by: a validator rule that a coefficient's fit units were collected on the kernel implementation it is keyed by. - D6 Only redistributable artefacts are committed. CI gates run on redistributable fixtures, and a nightly job checks the fixtures agree with the full data.Checked by: a check that every committed data file is listed in the ledger as redistributable; a nightly job comparing fixtures with the full data.
- D7 The role seed is 256 random bits and a CI secret until its partition version is retired. Only the
fit,anchorandreportlists are published, by the release job. Results onselect,validateandheldunits come only from the select job, the error job and the gate job, as aggregates over at least 10 units. No job that holds the seed runs code from a pull request. A candidate job runs with no CI environment and a read-only token, and its only output is a predictions file with a fixed schema and size limit, which the seed-holding job reads with a strict loader (SUB-12) as untrusted input. A seed-holding job is started only by a workflow on its own repository's default branch, never by a token a pull request controls.Checked by: the seed is stored in one CI environment per repository (blis-substrateandaccuracy-dashboard) whose branch policy allows onlymain, available only to the release, select, error and gate jobs; a workflow lint that those jobs check out onlymain; their outputs are checked to contain no per-unit rows and no aggregate over fewer than 10 units. Review guidance. Was the new seed generated as the ledger records? Expertise: ledger expertise. Applies when: a seed is created or rotated. - D8 A quantity BLIS simulates, such as prefix-cache hits or queueing, is never stored. A quantity that depends on the request stream but that BLIS does not simulate, such as speculative acceptance or expert routing balance, is a property of the workload. It is keyed by workload identity and never stored as a coefficient scoped only to a chip or model. Its value is taken from the command line if stated there, else from the scenario, else derived from the trace, else taken from the substrate's profile for that workload identity, and the Result's inputs record which supplied it (SIM-10). Speculative acceptance is keyed by the target model, the drafter, the speculative method, the number of speculative tokens K, and the workload's category and thinking mode. The drafter is the model's own in-graph speculator, such as its MTP layers, or a separate drafter checkpoint with its own catalog entry. A value stated on the command line, in the scenario or by the workload is used as stated. A profile lookup, in the substrate's profiles or in a profile set the scenario names, may borrow when no measured profile has the run's exact key. It takes the closest measured one, in this order. First, the same target and drafter at another K. Between measured values of K it interpolates linearly. Above the largest it takes the largest's value. Below the smallest it takes that profile's stated per-position rates for the first K positions. A profile that states none gives 1 + K(AL − 1)/K₀, where K₀ is its K. That figure cannot exceed what the profile's own rates would give, so it does not overstate drafting's gain. The lookup never extrapolates. Second, another drafter of the same target, preferring the same method and then the lowest value at the run's K, found by the first step's rules, so that drafting's gain is not overstated. Third, the same method on another target whose graph records the same architecture. Every step keeps the category and thinking mode, apart from a fallback category the user requested. Two profiles that share a key but record different sampling or rejection settings are not interchangeable, and an exact key found in two profile sets is ambiguous; the lookup refuses both. Any remaining tie breaks in an order the substrate's acceptance reference states, so the choice is deterministic. With no profile on any step, the run is refused. The Result records the profile used, the step, and each key field that differed. A scenario or command-line option limits the lookup to exact matches.Checked by: a validator rule refusing such families in the coefficient registry without a workload key; a test per borrowing step, for a tie, for each bound of K with and without stated per-position rates, for an ambiguous key, for the exact-only option and for the refusal.
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
- 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 isengine.version: it is set to the engine release that the scoringinference-simrelease 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 reasonfield_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 reasonvalue_not_recorded. - 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, andvalid_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 reasonfield_not_modelled. Absentvalid_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 reasonvalue_not_recordedfor a missing value orfield_not_modelledfor an unmodelled setting. - Decide. For each field, the engine's own
fields.jsonsays whether it is modelled, proven inert or not modelled. The dashboard keeps no table of its own. - 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.
| Grade | Meaning | Counts toward |
|---|---|---|
| G1 exact | The 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 estimated | As G1, except that one or more of these holds, each named on the point:
| the headline, with every graded point (COV-11) |
| G3 other version, same code | The 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 code | As 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 scored | Only 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)
code_not_visible: the run loaded code BLIS does not have. That is a class, module or plugin that a configuration field or plugin entry point names and the pinned upstream release does not ship, such as an out-of-tree connector, scheduler, worker or logits processor, or a patched private build. This reason takes precedence overfield_not_modelled.router_unsupported: the run was measured behind a router BLIS does not host, and the router table has no entry for it or says that no llm-d policy is close enough. BLIS hosts only llm-d's router (X4) and never simulates another, so such a point stays in the coverage counts as not scored. The router table (catalog/routersinblis-substrate) lists each router BLIS does not host, by the mode and session-affinity setting a run used, with the llm-d policy it maps to, or an explicit statement that no llm-d policy is close enough, and the evidence for the match or the mismatch: a citation of both routers' code or documentation at the revisions the runs used. An entry maps a router to an llm-d policy only with that evidence; a router and mode with no such entry staysrouter_unsupported. A point behind a router the table maps is G2 (§11.3), never G1, and it is never presented as if llm-d had routed the run. Hosting another router would add an upstream to this design.value_not_recorded: the result depends on a value the run did not record. It becomes G2 when an estimation rule in the engine'sfields.jsoncovers the value (§11.2).field_not_modelled: the run sets a field the engine classifies as not modelled, and the field has no estimation rule, or its rule does not cover the recorded value. It clears when the field is modelled or gains a rule.field_unclassified: the engine has not classified a field the run sets, or its repository publishes no field list yet. It is a blocker-backlog entry, cleared when an engine release classifies the field or publishes the list.engine_unsupported: no BLIS engine repository exists for the run's engine, as with TRT-LLM.topology_unsupported: the run was measured on a deployment the scoring harness cannot write as a scenario: several engines, as in a prefill and decode split or a run across nodes, where it builds one colocated pool. It is a blocker-backlog entry, cleared when the harness can write that topology.not_in_catalog: the run's model or chip has no catalog entry yet. This is a model-onboarding trigger (§12.1), and the backlog counts it.field_not_in_release: the run sets a configuration field that the engine releaseinference-simruns does not have. It clears wheninference-simmoves to a release that has the field.value_conflict: two sources that record the same stage of one field, such as the launch configuration and the logged passed arguments, give different values (§11.2). This is a data issue, reported to the source.build_unparseable: the run's recorded engine build cannot be read. This is a data issue, reported to the source.
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:
- refused surface items;
- not-modelled fields;
- coefficients below rank 1;
- unpriced execution modes.
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 it | Units it reads | What it reports |
|---|---|---|
| a person, an agent or CI, on any branch or pull request | the published fit units | per-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 refactor | the recorded configurations of every ledger unit, with no measured values read | for each point, only whether the simulation is identical to the base (X13) |
| the select job, for a candidate | select units | aggregates only |
| the gate job, for a pre-registered change | held units | aggregates only; one evaluation per pre-registration, and any further one is counted (§10.2) |
| the nightly dashboard run | anchor units, for released data | the 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
- COV-1 One scorer produces every number, and it lives only in
accuracy-dashboard: no other repository compares a simulation with a measured end-to-end benchmark. Every anchor unit is either graded G1 to G4 or listed as G5 with a reason, and the counts sum to the total.Checked by: summary tests in the dashboard; a lint that refuses code outsideaccuracy-dashboardthat reads end-to-end benchmark units to compute an error. - COV-2 Only ledger
anchorunits are scored, using the simulator release's embedded data and the anchor list embedded in its substrate release (inference-sim ledger).Checked by: a scorer check that every scored point is an anchor unit from the release's embedded anchor list. - COV-3 The dashboard copies recorded configuration verbatim, and reads field status and estimation rules only from the engine's
fields.json.Checked by: a check that the dashboard holds no settings table; a check that every scenario's engine block equals, field for field, the values the run passed, parsed with the engine's own argument parser, or, for a field the engine models or proves inert, the resolved value where only that is recorded (§11.2). The exceptions areengine.version, which equals the release instack.json, and each field an estimation rule set, which the point names. - COV-4 Every point has one grade, computed from data, with every substitution named.Checked by: a scorer check that every point has exactly one grade and every substitution is named.
- COV-5 G5 is used only for the reasons in §11.4. Any other reason fails CI.Checked by: a scorer check that every G5 reason is one of the listed reasons.
- COV-6 Every point in a release's scores is scored by that one
inference-simrelease. Each point shows its recorded engine build, and is graded G3 or G4 when that build is not the engine releaseinference-simruns.Checked by: a scorer check that one simulator release produced every score, and that each point's grade agrees with its recorded build andstack.json. - COV-7 Coverage (graded anchor units over all anchor units, by engine, engine release, chip and grade, with the share in each grade) is published beside accuracy, with the number of source records the ledger left out (§10.2), and the backlog is published every night.Checked by: the nightly publish job, which fails if coverage or the backlog is missing.
- COV-8 A failed or partial night publishes nothing; published snapshots never change.Checked by: publish guards in
api-nightly.yml. - COV-9 Each simulator release is scored exactly once. The nightly run scores, in the order they were tagged, every release that has no scores. Every provenance field is recomputed at every publish.Checked by: the nightly scoring job; a test that each published pull date is no earlier than the newest measurement it covers.
- COV-10 The dashboard copies the run's whole experimental frame (§8.6), not only its engine block: client concurrency, warm-up, drain, duration and the measured-window rule. The requests are the ones the run sent (COV-12), so BLIS's own substreams (DEV-6) draw only what the run's requests do not fix, such as a router's tie-breaks, and replications (DEV-11) cover those draws. A frame field the run did not record makes the point G5 with the reason
value_not_recorded(§11.4); no default is substituted. The achieved concurrency in the simulation matches the measured one within a stated tolerance. Each lane is scored on its percentiles and a distribution distance, not only on its mean. A simulated figure compared with a measurement carries its replication interval, or states that the run has no random input (§8.4); its uncertainty is then the measurement's own noise (below) and the model's (§8.8). Where InferenceX has several units whose identifying fields differ only in measured date or benchmark run, and whose recorded image digests agree, or one such unit holding several measurements (§10.2), their spread is reported as the measurement's own noise, and no point's error is counted for a difference smaller than that noise. Units whose image digests differ or are unrecorded are not pooled for noise, because their builds may differ. A unit holding several measurements is scored against their mean.Checked by: a check that every scored scenario's frame equals the recorded one; a concurrency check per point; the summary schema. - COV-11 The headline accuracy covers every graded point, G1 to G4. Beside it, the dashboard publishes the share of points in each grade and each grade's own accuracy. Every published figure shows its point count, and a figure over fewer points than the minimum stated in the dashboard's summary schema is shown as "too few points".Checked by: the summary schema, which requires a count with every figure; a publish check that refuses a figure under the minimum count.
- COV-12 The scored simulation receives the requests the measured run sent: the same requests, in the same order, with the same prompt lengths, output lengths and shared prefixes. Their arrival times follow the simulated completions by the client's own rules (§8.5). Where the client draws its requests from a seeded generator, the dashboard recomputes them with the client's own algorithm and seed, at the client release the run records; where the run records none, at the release its image ships, and the point names it. Prompt lengths are counted after the client's tokenisation and verified against the run's tokenizer at a pinned revision. An output length is the length the client forced or, where it forced none, the length the run's response records (DEV-13); where neither is recorded, the length is drawn from the frame's input model, and the point names it. The simulator runs the client's warm-up, so the measured phase starts from the engine state the warm-up left, such as cached prefixes. Every difference between the run's client and the simulated one, including an unrecorded client release or an unverified length, is named on the point and makes it G2.Checked by: a test that recomputes every scored point's requests with an independent implementation of the client's generator and compares them with the replayed trace; a test per client release that its pinned source still draws as the dashboard recomputes; a check that every scored point's trace has the run's request count and order; a scorer check that every point naming a client difference is G2; a test that the replayed warm-up leaves the recorded prefix structure.
12Triggers
A trigger is an outside event that requires work. Each trigger has one playbook, with the same fields every time:
- how the trigger is detected;
- a preflight that computes the scope before any work starts;
- the steps;
- the checks that must pass;
- what done means;
- the expected size of the work.
Figure 5 shows which repositories each trigger touches.
| Trigger | substrate | vllm | sglang | router | sim | search | dash |
|---|---|---|---|---|---|---|---|
| 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 | ◐ | ● | ● | ◐ | ● | ○ | ◇ |
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
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.
- Add the vendor
config.jsonat a full revision hash, andmodel.yaml; generategraph.yaml. If the architecture is new, add a graph handler. - 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). - Where the preflight shows coefficients below rank 1, fit them if
fitdata exists. Otherwise keep the less trusted value (the one with the larger rank number). - Release
blis-substrate. The release bot bumpsinference-sim.
--check; coefficient registry property tests; a run of each requested layout; held-out accuracy for any of the model's held units.- 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.
blis-substrate.12.2 A data drop
fit units.- Pin the new revision in the ledger's sources file.
- Commit the new units. Their roles are drawn by the seed-holding jobs the first time those jobs see them, before any use (D4).
- Refit only the families whose fit data changed. A refit that uses new units waits for the substrate release whose release job publishes their
fitlist. Each refit follows §12.3. - 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).
- 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.
12.3 An idea to improve accuracy
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
selectandheldunits will be used (the jobs choose the units themselves); - the success criteria;
- the predicted dashboard change.
- Make one change per pull request.
- 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. - Choose among candidates using
selectunits only. - Evaluate once on
heldunits, in the gate job (§13.5). - Land the change if the criteria pass. Otherwise commit the negative result as a note.
- 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.
12.4 A vLLM or SGLang release
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 fromheader 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.
- Cut the release branch
release-<old>frommain, so fixes to the old release can still ship (§14.4). - On
main, update theUpstreamrecord, the oracle's pinned commit and lock file, and run all tiers. - Re-port only the changed functions. Delete code for anything the new release removed.
- Classify every new surface item and configuration field.
- Regenerate the recorded traces that changed. Each one gets a
golden-reviewed:line with its reason. - Tag
v<new>-blis.1. The release bot movesinference-simto it in a MINOR release (§14.1). Scenarios naming the old release are then refused with the message of LIF-6. - For each kernel implementation whose selection changed, the substrate gets a coefficient by the methods of §9. A
transferredvalue states its transfer rule (§9.2).
- T-E1 Changes are confined to three places: the engine repository; the simulator's version bump, in which the release bot rewrites
engine.versionin 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-simrelease refuses scenarios naming the old release with the message of LIF-6. The release that message names lists the old release in itsstack.json, and its tagged build reproduces its own recorded byte-gate output.
12.5 An llm-d release
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.
- Cut
release-<old>fromllm-d-r(§14.4). - Merge the release into
llm-d-r(not a rebase). - 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).
- Audit new plugin types, or leave them refused.
- Tag
v<new>-blis.1once 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. - 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.
- Move
inference-sim'sreplaceline to the tag, and update its plugin allow-list in the same pull request. If an upstream behaviour change breaksinference-simtests 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 ofinference-simand the fork, who decide whether the tests or the expected results change.
- 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.
12.6 A new engine
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.contracts. T-N2 Every surface item has a status.12.7 A new capability
- An additive
contractschange, if one is needed. - 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.
- An engine-independent pricing form in the cost model, if one is needed.
- Coefficients by the methods of §9.
- Wiring in
inference-sim.
12.8 Smaller triggers
These follow the same pattern with fewer steps. Each has its own detector, so none depends on someone remembering it.
| Trigger | Detected by | What happens | Done when |
|---|---|---|---|
| A new chip | a request, or the dashboard's not_in_catalog count | The 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.1 | the engine repository's release watcher | The 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 upstream | an issue labelled fidelity, or a dashboard regression | Reproduce 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 use | an issue, a dashboard point the backlog cannot explain, or the nightly weight report | Fix 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 change | a substrate pull request whose API check of the contracts/ packages reports an incompatible change | One 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 change | a scheduled job polls the guides repository | Pin 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 format | the ingest code sees a new contract version from the source | Update 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 file | the dashboard maps the run's recorded library versions to kernel hashes (CodeHash, §6.2), and compares them with the hashes priced | Where 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:
- Deterministic checks decide everything that can be decided mechanically. Given the same commit, they give the same answer.
- 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.
- People answer the yes/no questions in §4 and §5, approve releases, and set direction.
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
- 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-searchandaccuracy-dashboard, which depend only oninference-sim, read it throughinference-sim invariantsat the release they pin. The shared workflow lives inblis-substrate/.github/workflows, and each repository calls it by commit (X21). - 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. - 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.
- 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
- The release bot refuses to tag unless every
ciinvariant the repository owns passed at the commit being tagged. - 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.
- 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
- The nightly compatibility build builds every repository at the tag the newest
inference-simrelease pins, and at the newest tag on each open release branch, with the substrate raised to its newest tag (skipping a release branch whose contracts the newest substrate no longer fits), on three platforms. It runs each repository's tests and checks the module graph against Figure 1. - Full-data gates rerun coefficient registry writers, the landing gates and fixture-agreement checks on the full data, which CI cannot hold.
- Detectors poll upstream releases and data sources, and open trigger pull requests.
- The dashboard scores, publishes coverage and grades, and updates the backlog.
- A status page shows every invariant by repository with its latest result.
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:
- Operator-level gates need only the cost model and the coefficient registry, for example "does this attention coefficient predict the held-out sweep points?" They run in
blis-substrate's gate job, which holds the role seed in that repository's protected environment (D7). - Whole-run gates need engines and the simulator, for example "does this change improve end-to-end latency on held-out InferenceX deployments?" They run the dashboard's scoring harness (§11.7), the only end-to-end scorer, at the release or commit the repository pins. A candidate job in the pull request's own repository builds the candidate and runs the harness with no secret and a read-only token, on every unit not on the published
fit,anchororreportlists, and writes one prediction per unit.accuracy-dashboard's gate job holds the role seed (D7), runs only its ownmain, refuses predictions made by a harness commit other than one released from itsmain, reads them as untrusted input, reduces them to aggregates and reports the result on the pull request.
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.
- If the pull request changes only files under
catalog/andregistry/, and both trees load under the released simulator's validators, and the code outside those directories at the merge base is identical to the substrate release that simulator embeds, the gate runs the released simulator twice, once per tree. No rebuild is needed. Ifmaincarries unreleased code changes, the next case applies, so the data are judged with the code they will ship with. - Otherwise, for example when the pull request changes the cost model or an engine, or uses a data field an older simulator cannot read, stack CI (§13.6) builds one simulator from the base and one from the candidate, and each runs on its own tree.
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:
- merges and tags each repository bottom-up;
- writes each pin once;
- 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 may | Agents may not |
|---|---|
| Work the items a preflight lists, in a branch | Start 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-registrations | Edit 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 budget | Weaken, 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 findings | Approve a merge; cut a release; review a change it wrote itself |
| Read summaries produced by tools | Load 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.
- Every check declares the paths it consists of: its test files, thresholds, fixtures and workflow file. Those paths, and
.github/, are protected. A change to any of them is a trigger for the review agent’s guidance. - A CI step fails on any new test skip, lint suppression or loosened threshold, unless the pull-request description carries a
Waiver:line that names the file and the reason. The line is a fact the step reads; the review agent judges whether the reason holds, and the reviewer with write access approves the pull request that carries it. - A change to a success criterion, or to the ledger’s cut-points or partition version, is a trigger for the review agent’s guidance. Held-out data is never read before the criterion is committed (§9, §10).
- Trace and generated-file guards are CI checks.
- Agent credentials have no permission to merge or tag.
13.8 Keeping agent work cheap
- The preflight sets the scope. An agent works only on the preflight's list, within its budget. Work outside the list is a new trigger.
- Tools do mechanical work. Re-pinning, re-vendoring, regenerating traces, diffing releases and updating descriptions are commands.
- Agents do not wait. When CI or review is pending, the session ends and resumes on an event.
- Reviews by agents are narrow. An audit agent receives one diff and one trigger's done-when list.
14Versions, releases and blis-sglang
14.1 Version schemes
| Kind | Repositories | Tag | What the parts mean |
|---|---|---|---|
| Engines | blis-vllm, blis-sglang | v<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 fork | the llm-d router fork | v<upstream>-blis.N, for example v1.0.0-blis.2 | The 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. |
| Substrate | blis-substrate | vX.Y.Z | Semantic 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 applications | inference-sim, blis-config-search, accuracy-dashboard | vX.Y.Z | Semantic 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:
- It models upstream release vX.Y.Z, at the commit its
Upstreamrecord names, and no other release. - It passed that release's oracle tiers, its field list and surface list checks, and every
ciinvariant (LIF-3). - A higher
Non the same upstream release is a BLIS-side change only: a bug fix, a newly modelled feature or plugin, a contract or substrate update. It may change prices or decisions. Its release notes say what kind of change it is and whether prices or decisions changed, and include the price-change report (§13.3). - Every file is either ported from vX.Y.Z or original to BLIS. Nothing is kept for an older release.
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
- Release candidates (vLLM's
v0.33.0rc0, SGLang'sv0.5.3rc0) are not modelled. An engine module models final releases only. - Post releases, of vLLM or SGLang (for example SGLang's
v0.5.6.post1or vLLM'sv0.6.1.post1), are writtenv0.5.6-post.1.blis.N. The dot afterpostmakes Go compare the number numerically, sopost.2sorts beforepost.10. Every-post.K.blis.Nsorts after everyv0.5.6-blis.Nand beforev0.5.7-blis.1, which is the upstream order. These orderings are Go's own (golang.org/x/mod/semver). - Four-part versions, such as vLLM's
v0.10.1.1, are writtenv0.10.1-rev.1.blis.N. Go compares these words as text, and bothpostandrevsort afterblis, so a post release or a four-part release sorts after every-blis.Ntag of its three-part base and before the next three-part version. If an upstream ever publishes both on one base, therevform is used for the four-part release.
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.Nsuffix, withNfrom 1. - Pins name exact tags. The release bot never resolves
@latest, and a lint checks the version each build actually selects, not the text ofgo.mod(LIF-2). - A plain tag made before this rule stays, because tags are never moved or deleted (LIF-1). It makes
@latestwrong for that module, and a module that requires it outranks every-blis.Ntag in version selection. Aretractline 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. Anexcludeline ininference-sim'sgo.moddoes, because Go applies the main module's excludes to the whole build (X11).
14.2 Lifecycle invariants
- LIF-1 A tag names one commit forever. Tags are never moved or deleted; a mistake gets a new tag.Checked by: a nightly check, in the repository, that every tag resolves to its recorded commit. Maintainer practice (§4.2): the repository protects its release tags from update and deletion.
- LIF-2 In every build, each BLIS module resolves to the exact version the release records in
stack.json, and the router fork resolves to a-blis.Ntag throughinference-sim's onereplacedirective (X11).inference-sim's own substrate requirement equals the version its build selects, so no engine or router release can change the simulator's data. One Go toolchain version is named incontracts/toolchain, at the substrate versioninference-simselects. No module BLIS writes declares agoversion above it. Every CI job setsGOTOOLCHAIN=localand installs exactly that version, so no build downloads a toolchain. The router fork'sgoline follows upstream llm-d; when upstream raises it, the release bot raises the toolchain file first, in the same review set. Documented version strings are generated (X23).Checked by: selected-version lint (go list -m all), which for the router reads the replace target thatgo list -m -jsonreports, not the placeholder version on therequireline; a check that every module ingo list -m -json alldeclares aGoVersionno higher thancontracts/toolchain; a CI step that fails ifgo versiondiffers from it; the pre-release test of every engine and router package against the simulator's selected versions (§3.2). - LIF-3 Only the release bot cuts releases, and only from a commit where every
ciinvariant passed. If the bot is unavailable, a person with write access to the repository may follow the documented manual procedure (§13.6); a manual tag is an annotated tag on the checked commit, recorded in the release notes.Checked by: the bot’s own check of everyciinvariant; a nightly check, in the repository, that every tag made by a person is annotated and listed in its release notes. Maintainer practice (§4.2): the repository’s settings let only the release bot and people with write access create tags. - LIF-4 Release notes are generated (§13.3).Checked by: the release bot, which writes the notes from the release's changes.
- LIF-5 An engine module, or the router fork, models one upstream release. Its tags are
v<upstream>-blis.N,v<upstream>-post.K.blis.Nfor a post release, orv<upstream>-rev.W.blis.Nfor a four-part version, withNfrom 1. No new plain upstream tag is made; older tags stay (LIF-1). In an engine module, theUpstreamrecord names that release and commit, every ported file names that same release, and no code for another release remains. Release candidates are not modelled. The package that runs the modelled release depends on no package that exists only for another release.Checked by: a test that lists the dependencies of the engine's entry package withgo list -depsand fails on any package kept for another release; a tag check in the release bot (format,None above the previous tag on the same upstream release, no plain tag); in engine modules, the provenance test, which refuses a ported file that names another release. - LIF-6 Each
inference-simrelease runs exactly one upstream release of each engine and of llm-d, and lists them instack.json. A scenario whoseengine.versionnames a different upstream release is refused, with a message naming the release it asked for, the one this build runs, and the newestinference-simrelease that runs the one it asked for, or says that none does. The llm-d release is not stated by scenarios; it is the onestack.jsonlists.Checked by: a refusal test per engine kind; a check that the message's release names matchstack.jsonhistory. - LIF-7 A deprecated feature is removed by the first release after its deprecation that may break compatibility: a MAJOR release, or a MINOR release while the repository is at v0. In an engine module, which has no such releases of its own, deprecated code is removed when
mainmoves to a new upstream release. Removal takes with it every code path, flag, field, test and document that existed only for that feature. Each deprecation is recorded where the feature is defined (a command's or flag's deprecation notice, or a// Deprecated:comment) with the release that deprecated it. Nothing in this design depends on a deprecated feature, and no repository keeps code that none of the binaries BLIS ships can reach.Checked by: the release bot, which refuses a MAJOR release, or a MINOR release at v0, that still contains a feature deprecated in an earlier release; a whole-program dead-code check (Go'sdeadcode), run in stack CI's workspace from the main packages of every binary BLIS ships, with test files not counted as reaching code. Upstream files in the router fork, and the interfaces incontracts/, are exempt.
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
| Path | Contents |
|---|---|
provenance.go | Kind = "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.json | generated 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.23 | Source |
|---|---|
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:
- in the upstream-tracking repository,
release-<upstream>, for examplerelease-0.32.0, from the last commit that modelled the old release; - in
inference-sim,release-vX.Y, from its last release that ran the old upstream release.
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
| Family | Identifiers | Applies to | Defined in |
|---|---|---|---|
| Cross-repository | X1–9 | all | §4 |
| Process | X10–28 | all | §4.1 |
| Per repository | SUB-1–20, VLM-1–7, SGL-1–6, RTR-1–7, SIM-1–14, CFG-1–5 | the named repository | §5 |
| Fidelity | FID-1–11 | engine repositories | §7.7 |
| Simulation method | DEV-1–13 | inference-sim, engine repositories, router fork, applications | §8.10 |
| Coefficient values | VAL-1–11 | blis-substrate | §9.4 |
| Data | D1–8 | blis-substrate, accuracy-dashboard | §10.4 |
| Coverage | COV-1–12 | accuracy-dashboard | §11.8 |
| Versions | LIF-1–7 | all | §14.2 |
| Trigger outcomes | T-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-L1 | one trigger each | §12 |
BGlossary
| Agent | An AI coding agent that does one scoped task in a branch, within a budget (§13.7). |
| Agent guidance | A rule an agent follows on every change it writes or reviews, rendered into each repository’s AGENTS.md (§4.2, §13.1). |
| Anchor | A 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 means | A 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 backlog | The 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 gate | A fixed set of scenarios whose output must stay byte-identical across a refactor. |
| Candidate job | The 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 class | Whether a pull request is a verification change or a validation change (§8.9, DEV-12), declared in its template. |
| Coefficient registry | The 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 numbers | Using the same random streams in two runs that are being compared, so their difference reflects the configuration, not chance (§8.4). |
| Component | A 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 transition | A component's change of state when its own event and a message fall at the same instant (§8.1). |
| Conformance suite | A test suite in contracts/conformance that any implementation of a contract must pass. |
| Content hash | The 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 set | A review set that makes an incompatible change to contracts/ and releases every implementer with it (§12.8). |
| Cost model | The 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 graph | A 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 corpus | Every 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 ledger | The committed list of external data units and the rule that gives each one a role (§10). |
| DCO | Developer Certificate of Origin: a Signed-off-by line by which the person who takes responsibility for a commit certifies it. Encouraged, not checked (X20). |
| Deployment | A 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. |
| DEVS | The Discrete Event System Specification, Bernard Zeigler's formalism for discrete-event models, whose terms §8 uses. |
| Distribution distance | How 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. |
| Drafter | What 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 block | The 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 kind | Which engine a scenario uses: vllm or sglang. |
| Engine module | The one Go module in an engine repository (blis-vllm or blis-sglang). It models one upstream release (LIF-5). |
| Environment | The interface through which an engine asks the substrate for model graphs, chip facts, memory sizes and prices (§6.3). |
| Experimental frame | The 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, unidentified | Flags 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). |
| Fate | How 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 list | The generated list (fields.json) of every configuration field and environment variable an upstream release reads, each with a BLIS status (VLM-3). |
| FLOPs | Floating-point operations. |
| Gate job, select job, error job | Three 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). |
| GEMM | General matrix multiply. |
| Grade | G1 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). |
| Held | A 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). |
| HiSparse | Hierarchical 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). |
| HMAC | A keyed hash. Without the key (here, the seed) the output cannot be predicted. |
| Holm correction | A way to test many lanes at once while keeping the chance of any false failure at the stated level (VAL-6). |
| Horizon | The simulated time at which a run stops, if its stop rule names one (§8.6). |
| In-flight depth | The most engine steps that run at once, PoolPlan.InFlight: 1 without asynchronous or overlap scheduling (SIM-7). |
| Inert, unproven | A 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 event | A component's change of state at a time it scheduled for itself (internal), or on a message from another component (external) (§8.1). |
| Invariant registry | The 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 hash | CodeHash: the hash that identifies a kernel implementation, of its source and target GPU architecture, or of a binary library (§6.2). |
| Kernel implementation | The library, variant and kernel hash (CodeHash) that execute one primitive. The coefficient registry looks coefficients up by it. |
| Known-bad input | An input that a check must reject. Each check in the invariant registry names one (X18). |
| KV cache | The 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 spec | In 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 gate | The accuracy check a coefficient or pricing change must pass to merge (VAL-6). |
| Lane | One engine, engine release, chip, model and layout, as the dashboard groups points. |
| Layout | How a model is split across GPUs: the tensor, pipeline, data, expert and context parallel widths, and where each rank is placed. |
| Ledger owner | A person the project team names to own the role seed and its rotation (§10.2). |
| llm-d stack | llm-d's router in front of vLLM or SGLang engines, as llm-d's published guides deploy it. |
| Lowering | Turning 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 practice | A 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. |
| Manifest | The list of everything the oracle replaces. |
| Memory pool | In 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 class | One 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 registry | The list in contracts/ of every metric's definition: formula, statistic, population, window and unit (§8.7, DEV-8). |
| Milestone | One of the numbered steps in which a release plan delivers its work. |
| MLA | Multi-head latent attention: attention that stores a compressed latent instead of full keys and values. |
| MoE | Mixture of experts. |
| Mooncake | A KV transfer and storage system, used by vLLM connectors and as SGLang's default P/D transfer backend. |
| MSER-5 | A warm-up rule: batch the output series in fives and cut where the variance of the remaining mean is smallest (§8.8). |
| MTP | Multi-token prediction: a speculative-decoding method where extra model layers draft the next few tokens. |
| NIXL | NVIDIA'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 blind | A 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 load | Requests arriving per second, set by the workload rather than by the system's response. |
| Oracle | The harness that runs the upstream's own code with its environment replaced, and records its decisions. |
| Output function | What a component sends, evaluated just before an internal or confluent transition (§8.1). |
| Overlay | A 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/D | Prefill/decode disaggregation: one pool of engines processes prompts (prefill), another generates tokens (decode), and the KV cache moves between them. |
| Paired bootstrap | A 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 DEVS | The form of DEVS in which all outputs at an instant are collected before any component changes state (§8.2). |
| Partition version | A numbered version of the ledger's role assignment. A new version is needed to change any existing unit's role. |
| Perturbation test | A 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-pin | To require an exact version of another module in go.mod; to update that requirement. |
| Playbook | The fixed steps for one kind of trigger, run by CI and, where needed, by an agent (§12). |
| Point | One InferenceX unit as the dashboard grades it, G1 to G5 (§11.3): one benchmark run of one deployment and recipe fingerprint at one concurrency. |
| Pool | A 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, Transfer | The 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-registration | The 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). |
| Preflight | The generated report that fixes a trigger's scope before work starts. |
| Price-change report | A 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). |
| Primitive | One kind of work the cost model prices: an attention step, a matrix multiply, a collective, a memory transfer. |
| Profile likelihood | A way to find a fitted coefficient's interval by refitting the others at each candidate value (VAL-10). |
| Progress watchdog | A check that fails a run when no request changes state for a set number of engine steps while work is waiting (SIM-7). |
| Quarantine list | The 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 core | SGLang'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). |
| Ratchet | A count that CI allows to fall but never to rise. |
| Recorded trace | The 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 branch | A 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 owner | A 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 watcher | The scheduled job in each upstream-tracking repository that detects new upstream releases and opens the upgrade pull request (§12.4). |
| Released data, experimental data | A 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. |
| Replication | One run of a scenario with its own scenario seed. Several replications give a confidence interval (§8.8). |
| Result | The structured output of one run, written as JSON, including everything the run used (§3.3). |
| Review question | A 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 set | The pull requests, across repositories, that make up one change. |
| Role key | The fields of a data unit that decide its role, so near-copies of one measurement share a role (§10.2). |
| Roofline | An 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. |
| Scenario | The YAML file a user gives BLIS: the engine and router configuration, the cluster, the workload and the seed. |
| Scoring harness | The 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 job | One 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 lint | A check that reads the versions Go actually chose for a build (go list -m all), not the versions written in go.mod. |
| Simulated runtime | The router fork's runtime when BLIS hosts it: clock, timers, randomness and goroutines driven by simulated time (§7.3). |
| Stack CI | The 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.json | The 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). |
| Substrate | The engine-independent foundation: blis-substrate. |
| Surface list | The generated list of every feature an upstream release offers, each with a BLIS status. |
| Terminating run, steady-state run | A run that ends by its stop rule (terminating), or one that measures a system after it has settled (steady-state) (§8.8). |
| Time advance | How long until a component's next internal event, given its state (§8.1). |
| Token record | The per-request counts an engine emits: prompt tokens, tokens served from local and external caches, tokens computed and tokens emitted (SIM-12). |
| Trigger | An outside event that starts BLIS work, such as a new upstream release or a new model (§12). |
| Unit, role | A unit is the smallest piece of data the ledger tracks. Its role says what it may be used for. |
| Upstream | The project BLIS reproduces: vLLM, SGLang or llm-d. |
| Upstream record | The Upstream value in an engine repository's provenance.go: the one upstream release, and its commit, that the repository models (LIF-5). |
| Upstream-tracking repository | blis-vllm, blis-sglang or the router fork. The first two are also called engine repositories. |
| Validity domain | The range of models, chips, layouts, concurrencies, sequence lengths and replica counts that held-out data cover (§8.9). |
| Verdict | A CI result or the review agent’s review, including its answers to review questions. Each names the tree it judged (X22). |
| Verification, validation | Checking that the simulator does what its model says (verification), and checking that the model matches the real system (validation) (§8.9). |
| Warm-up | The stretch at the start of a steady-state run that is discarded because the system has not settled (§8.8). |
| Workload identity | The key under which a workload-dependent quantity, such as speculative acceptance or expert routing balance, is stored (D8). |