Help us shape the future of Go! Take the 2026 Go Developer Survey

latencykernel

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 20, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

README

blis-latency-kernel

The cost model of BLIS. Given a model, a GPU, a set of coefficients for that GPU and a deployment, it answers the two questions a serving simulator asks: how long one forward pass over a batch of requests takes, and how much GPU memory the deployment holds.

Documentation: https://inference-sim.github.io/blis-latency-kernel/

It is a Go library implementing the Kernel interface from blis-schemas. It reads the model and the hardware from blis-catalog and the coefficients from blis-registry, and holds no data of its own. inference-sim calls it once per simulated step in pull request #1851. It prices the engine behavior of vLLM v0.31.0.

Use

go get github.com/inference-sim/blis-latency-kernel@latest
k, err := latencykernel.Open("my-deployment.yaml", latencykernel.Repos{
	Scenarios: "scenarios", Catalog: "blis-catalog", Registry: "blis-registry",
})
if err != nil {
	panic(err)
}
e := k.StepTime(batch) // e.Overlap, e.NoOverlap, e.Bottleneck, e.PerResource

Price a batch from Go walks through it with a real deployment, and Pricing a step explains what the estimate means.

Develop

scripts/fetch-testdata.sh   # the catalog and registry, at the commits testdata/upstream.lock pins
go test ./...

The tests are behavioral: they check what the kernel does for an input, most often against a transcription of the vLLM source or an exact relation the answer must satisfy. testdata/README.md describes the test inputs, including the measurement corpora the scoring commands use, which are third-party data and not distributed here. Contributing describes the layout of the repository and the conventions a change is reviewed against.

License

Apache 2.0, matching the other BLIS repositories.

Documentation

Overview

Package latencykernel implements the blis-schemas Kernel interface: it prices GPU work for one resolved deployment.

Construction resolves everything — catalog facts, registry coefficients, the engine settings a layout overrides, and the model graph flattened to a per-kind plan. After that every method is a pure function of its arguments, so a discrete-event simulator can call StepTime at any simulated instant, from any goroutine, and get the same answer.

Two constructors, for the two shapes a caller comes in. Open takes a scenario filename and the roots its names resolve against, which is what a simulator or a scoring command has. New takes the documents already parsed, which is what a configuration search producing deployment variants that were never written to disk has. Open ends in New, so both validate identically and neither can price a deployment the other would refuse.

Speed is a requirement rather than a nicety: a simulator calls StepTime once per simulated step, millions of times over a run. So the hot path does arithmetic over a short pre-aggregated slice and allocates little: the per-resource breakdown map, which StepTimeInto lets a caller reuse, and a few scratch slices that grow with the number of requests. The plan holds one entry per distinct layer kind rather than one per layer, which for the deepest model in the catalog is three entries instead of 108.

Index

Examples

Constants

View Source
const EngineBehaviourCommit = "db9527a46873454610df6dbedf79a36d6bf1a7f6"

EngineBehaviourCommit is the commit of vLLM's EngineBehaviourVersion tag. A comment citing vllm/<path>:<lines> means those lines at this commit.

View Source
const EngineBehaviourVersion = "0.31.0"

EngineBehaviourVersion is the vLLM release whose engine behaviour this kernel encodes.

View Source
const KernelAssumptionSet = "blis-latency-kernel"

KernelAssumptionSet is the Set a Provenance entry carries when the kernel supplied the value itself rather than reading it from a registry set or the deployment. No registry set can take this name, since registry sets are named for their coefficient family, so a consumer can tell the two apart without parsing anything else.

Variables

This section is empty.

Functions

func LoadBundle

func LoadBundle(path string) (*scenario.Scenario, *deployment.Deployment, error)

LoadBundle reads a scenario and the deployment applied to it from one file, as two YAML documents separated by `---`.

blis-schemas keeps the two apart: a Scenario is the immutable problem (model, cluster inventory, coefficient and engine-version references) and a Deployment is the tunable configuration chosen against it (pools, each a parallelism layout with its own engine settings). One `blis run` is one of each.

They share a file here, rather than sitting in a scenario and a sibling deployment file, because a measurement row addresses a deployment by a single filename — `{"scenario": "glm-5-h200-fp8-sglang-tp8.yaml", ...}` — and testdata/measurements holds over three thousand such rows. A sibling-file layout would rewrite every one of them to say nothing it does not already say.

blis-schemas' own LoadScenario and LoadDeployment each open a path and read one document, so neither can reach the second half of a pair; this reproduces their strict decoding over a single stream instead. Strictness is the point: a misspelled key that parsed silently would leave a document that validates while omitting the setting its author intended, which is the failure mode hardest to notice.

Types

type Inputs

type Inputs struct {
	Scenario *scenario.Scenario
	// Deployment is the tunable configuration applied to the scenario: the pools that lay
	// the model out. It is a separate document because a scenario fixes the immutable
	// problem and an optimizer sweeps deployments against it.
	Deployment *deployment.Deployment
	// PoolIndex selects which pool of a disaggregated deployment this kernel prices. Each
	// pool runs its own engine with its own settings, so one kernel per pool.
	PoolIndex    int
	Model        *model.Graph
	Chip         *hardware.Chip
	Fabric       *hardware.Fabric
	Devices      []*hardware.StorageDevice
	Coefficients []*coefficient.Set
	// Rules carry the engine-version behaviour the resolver needs.
	Rules resolve.EngineRules
	// Routing is the run's resolved expert-parallel routing imbalance (blis-schemas
	// routing.Resolve), which depends on the model and the traffic and so is resolved by
	// the caller, as speculative acceptance is. Nil prices an expert-parallel group as
	// evenly loaded and records that as an assumption.
	Routing *routing.Resolution
}

Inputs are the documents a kernel is built from. They arrive already parsed: loading belongs to blis-schemas, and New takes documents rather than paths so a caller holding structs — a config search producing deployment variants never written to disk, a test, a generator — does not have to serialize them first. Open is the path-based half of the pair.

New DOES validate them, at the field layer, which is the half of blis-schemas' two-layer validation that is version-independent and always the author's to fix. See New.

func OpenInputs

func OpenInputs(scenario string, r Repos, poolIndex int) (Inputs, error)

OpenInputs resolves one pool of a scenario file into the documents New prices it from, without building the kernel.

It is OpenPool's loading half, exported for a caller that needs the documents as well as the kernel. A scoring harness that configures a rival backend for the same deployment needs the model's name, the chip's memory and the model's expert geometry -- deployment identity, which the kernel deliberately does not re-export (blis-schemas#36 draws that line: identity for comparison is the harness's concern, not the cost model's). Reading them from these Inputs, and building the kernel with New(in), means the harness holds the SAME chip and graph the kernel priced, not a second load of files that could resolve differently.

What it must not be used for is reaching configuration the kernel resolved: widths and backends are Resolved's to report, and the pool's settings are Deployment's. Reading those from Inputs instead would rebuild the second answer to "which pool is this" that the interface exists to remove.

type Kernel

type Kernel struct {
	// contains filtered or unexported fields
}

Kernel prices one deployment. Obtain one from New; the zero value is not usable.

func New

func New(in Inputs) (*Kernel, error)

New resolves the inputs into a kernel.

Everything that can be decided once is decided here: which coefficients apply, which requested settings the layout overrides, and the model graph flattened to a per-kind plan. The result holds no mutable state, so its methods are pure and safe to call concurrently.

Construction is deliberately strict. A missing coefficient, an unresolvable condition or a non-positive rate is an error rather than a default, because each would otherwise produce a step time that looks plausible and is wrong by whatever the term contributes. validateDocuments extends that strictness to the documents themselves.

Example

A deployment variant that was never written to disk: load the documents once, change one setting, and build a kernel from the result.

package main

import (
	"fmt"

	latencykernel "github.com/inference-sim/blis-latency-kernel"
)

// --8<-- [start:repos]
// The roots a scenario's names resolve against: its model, chip, fabric and coefficient sets.
var repos = latencykernel.Repos{
	Scenarios: "testdata",
	Catalog:   "testdata/catalog",
	Registry:  "testdata/registry",
}

func main() {
	// --8<-- [start:new]
	in, err := latencykernel.OpenInputs("aisimulate/deepseek-v3-h200-fp8-sglang-tp8.yaml",
		repos, 0)
	if err != nil {
		panic(err)
	}
	base, err := latencykernel.New(in)
	if err != nil {
		panic(err)
	}

	// The same deployment with a 16-bit KV cache instead of fp8. New copied what it needed,
	// so changing the documents now leaves base as it was.
	in.Deployment.Pools[0].Engine.CacheDType = "bfloat16"
	wide, err := latencykernel.New(in)
	if err != nil {
		panic(err)
	}
	fmt.Printf("fp8:  %d bytes of KV per token per rank\n", base.SequenceVariableBytes(64)/64)
	fmt.Printf("bf16: %d bytes of KV per token per rank\n", wide.SequenceVariableBytes(64)/64)
	// --8<-- [end:new]

}
Output:
fp8:  35136 bytes of KV per token per rank
bf16: 70272 bytes of KV per token per rank

func Open

func Open(scenario string, r Repos) (*Kernel, error)

Open resolves one scenario file into a kernel.

The path-based half of the constructor pair. New takes documents a caller already holds; Open takes a filename and the roots its names resolve against, which is what a simulator or a scoring command has. Both end in New, so both inherit its validation and neither can price a deployment the other would reject.

It is exported because the alternative is every consumer reimplementing it. Go forbids importing another module's internal packages, so while this lived in internal/harness, inference-sim carried a copy of the sequence and said so in a comment, and blis-config-search and every future backend repo would have needed their own. The duplication was tolerable only because it was confined to loading — but "which files a scenario implies" is this package's answer to give, not each caller's to guess.

Every artifact is loaded through blis-schemas' own loaders, and the engine rules come from that package's version registry rather than from a caller, so a scenario pinned to an older release cannot silently get current behaviour.

It prices the deployment's FIRST pool, which is what a colocated deployment has. Use OpenPool to price one pool of a disaggregated one.

Example

Pricing a decode step: open a scenario, describe the batch, read the estimate.

package main

import (
	"fmt"
	"time"

	"github.com/inference-sim/blis-schemas/kernel"

	latencykernel "github.com/inference-sim/blis-latency-kernel"
)

// --8<-- [start:repos]
// The roots a scenario's names resolve against: its model, chip, fabric and coefficient sets.
var repos = latencykernel.Repos{
	Scenarios: "testdata",
	Catalog:   "testdata/catalog",
	Registry:  "testdata/registry",
}

func ms(d time.Duration) float64 { return d.Seconds() * 1e3 }

func main() {
	// --8<-- [start:open]
	// DeepSeek-V3, served in fp8 on eight H200s at tensor-parallel width 8.
	const scenario = "aisimulate/deepseek-v3-h200-fp8-sglang-tp8.yaml"
	k, err := latencykernel.Open(scenario, repos)
	if err != nil {
		panic(err)
	}

	// 32 requests, each decoding one token against a context of 4,096 tokens.
	batch := kernel.Batch{DecodeThreshold: 8}
	for range 32 {
		batch.Reqs = append(batch.Reqs, kernel.ReqShape{
			Scheduled: 1, Computed: 4095, PromptLen: 4096,
		})
	}

	e := k.StepTime(batch)
	fmt.Printf("between %.1f and %.1f ms, bottleneck %s\n",
		ms(e.Overlap), ms(e.NoOverlap), e.Bottleneck)
	for _, r := range []kernel.Resource{kernel.ResourceSM, kernel.ResourceHBM,
		kernel.ResourceNVLink, kernel.ResourceHost} {
		fmt.Printf("%-7s %5.1f ms\n", r, ms(e.PerResource[r]))
	}
	// --8<-- [end:open]

}
Output:
between 24.1 and 25.7 ms, bottleneck hbm
sm        4.5 ms
hbm      16.5 ms
nvlink    1.2 ms
host      3.5 ms

func OpenPool

func OpenPool(scenario string, r Repos, poolIndex int) (*Kernel, error)

OpenPool resolves ONE pool of a scenario file into a kernel.

A disaggregated deployment states a prefill pool and a decode pool, and the two differ in the quantities that set step time: tensor-parallel width, whether expert parallelism is on, the engine's token budget. One kernel prices one pool — each runs its own engine with its own settings — so a caller serving the roles separately opens one kernel per pool. Pricing both from pool 0 would charge the decode pool the prefill pool's parallelism, and the simulation would still run.

Open is this function at pool 0. Both exist because the common case should not have to name an index, and the disaggregated case cannot be served without one.

func (*Kernel) AdmissionOverhead

func (k *Kernel) AdmissionOverhead(promptTokens int) time.Duration

AdmissionOverhead returns host time before a request can be scheduled.

func (*Kernel) CaptureTokens added in v0.3.0

func (k *Kernel) CaptureTokens() int

CaptureTokens returns the largest step, in tokens, a captured CUDA graph covers: the deployment's max_cudagraph_capture_size or cudagraph_capture_sizes as vLLM v0.31.0 resolves them, or its default where neither is stated (see Provenance). A step of more tokens runs with no graph whatever the mode. Zero when the resolved mode captures nothing.

func (*Kernel) CompletionOverhead

func (k *Kernel) CompletionOverhead() time.Duration

CompletionOverhead returns fixed host time at request completion.

func (*Kernel) DecodeContextParallelWidth

func (k *Kernel) DecodeContextParallelWidth() int

DecodeContextParallelWidth returns how many ranks shard the decode KV cache.

A consumer sizing its own KV budget needs it and cannot derive it from anything else this kernel exposes: it is the only axis that shards a LATENT cache, because KVBytesPerToken floors at one KV head and a latent cache has exactly one, so no tensor-parallel width reduces it. A simulator that read Resolution's TensorParallelWidth alone would size an MLA deployment's cache as if DCP did nothing.

From the resolved layout rather than the deployment document: a Parallelism states a request and resolution settles it.

A method on this type rather than a field of kernel.Resolution only because blis-schemas v0.3.0's Resolution carries the tensor-, data- and expert-parallel widths and not the two context-parallel ones. The argument that put those three in Resolution -- a consumer that can read one width and not the others has to re-derive the rest -- applies to these two as well, so this method and its companion are interim, pending the same fields upstream.

func (*Kernel) Deployment

func (k *Kernel) Deployment() deployment.Pool

Deployment returns the pool this kernel prices, as the document stated it.

The request, not the resolution: a caller wanting a width or a backend reads Resolved, and reads this for the settings resolution does not touch -- the admission settings a scheduler sizes itself from. It is the pool New was given at PoolIndex, so a caller holding several kernels of one disaggregated deployment needs no index of its own to know which pool each prices.

A deep copy, so a caller writing through one of the engine block's pointer fields changes its copy and not the kernel. blis-schemas permits returning a shallow copy and asking callers not to write; this is not a hot path, and a promise the type enforces is worth more than one a reader has to keep.

func (*Kernel) Evidence

func (k *Kernel) Evidence() (measured, total int, assumed []string)

Evidence reports how much of the resolved coefficient set rests on measurement, so a caller can state a prediction's footing.

Example

Evidence: how much of what a kernel used was measured, and what it assumed.

package main

import (
	"fmt"
	"maps"
	"slices"

	latencykernel "github.com/inference-sim/blis-latency-kernel"
)

// --8<-- [start:repos]
// The roots a scenario's names resolve against: its model, chip, fabric and coefficient sets.
var repos = latencykernel.Repos{
	Scenarios: "testdata",
	Catalog:   "testdata/catalog",
	Registry:  "testdata/registry",
}

func main() {
	k, err := latencykernel.Open("aisimulate/deepseek-v3-h200-fp8-sglang-tp8.yaml", repos)
	if err != nil {
		panic(err)
	}
	// --8<-- [start:evidence]
	measured, total, assumed := k.Evidence()
	fmt.Printf("%d of %d values rest on evidence; %d were assumed\n",
		measured, total, len(assumed))

	// The same entries, counted by the method that produced each.
	byMethod := map[string]int{}
	for _, o := range k.Provenance() {
		byMethod[o.Method]++
	}
	for _, m := range slices.Sorted(maps.Keys(byMethod)) {
		fmt.Printf("  %-12s %3d\n", m, byMethod[m])
	}

	// The kernel's own assumptions, as distinct from registry values whose method is
	// "assumed", carry the set name KernelAssumptionSet.
	for _, o := range k.Provenance() {
		if o.Set == latencykernel.KernelAssumptionSet {
			fmt.Println("the kernel assumed", o.Name)
		}
	}
	// --8<-- [end:evidence]

}
Output:
113 of 155 values rest on evidence; 41 were assumed
  assumed       41
  measured      99
  not_charged    1
  vendor_spec   14
the kernel assumed max_cudagraph_capture_size
the kernel assumed engine_version

func (*Kernel) FixedBytes

func (k *Kernel) FixedBytes() kernel.MemoryBreakdown

FixedBytes returns occupancy independent of the request set, per rank.

Example

Memory: what one rank holds before any request arrives, and what each request adds.

package main

import (
	"fmt"

	latencykernel "github.com/inference-sim/blis-latency-kernel"
)

// --8<-- [start:repos]
// The roots a scenario's names resolve against: its model, chip, fabric and coefficient sets.
var repos = latencykernel.Repos{
	Scenarios: "testdata",
	Catalog:   "testdata/catalog",
	Registry:  "testdata/registry",
}

func main() {
	k, err := latencykernel.Open("aisimulate/deepseek-v3-h200-fp8-sglang-tp8.yaml", repos)
	if err != nil {
		panic(err)
	}
	// --8<-- [start:memory]
	const gib = 1 << 30
	m := k.FixedBytes()
	fmt.Printf("weights      %5.1f GiB\n", float64(m.Weights)/gib)
	fmt.Printf("activations  %5.1f GiB\n", float64(m.ActivationPeak)/gib)
	fmt.Printf("CUDA graphs  %5.1f GiB\n", float64(m.CUDAGraph)/gib)
	fmt.Printf("comm buffers %5.1f GiB\n", float64(m.CommBuffers)/gib)
	fmt.Printf("EPLB         %5.1f GiB\n", float64(m.EPLBRedundant)/gib)
	fmt.Printf("total        %5.1f GiB per rank\n", float64(m.Total())/gib)
	// The communicator and engine workspace, held before the budget is measured.
	fmt.Printf("before the budget %4.1f GiB\n", float64(k.PreSnapshotBytes())/gib)

	// KV cache for one request, rounded up to whole pages.
	fmt.Printf("a 4,096-token request: %.0f MiB of KV cache per rank\n",
		float64(k.SequenceVariableBytes(4096))/(1<<20))
	// --8<-- [end:memory]

}
Output:
weights       78.2 GiB
activations    1.1 GiB
CUDA graphs    0.8 GiB
comm buffers   0.0 GiB
EPLB           0.0 GiB
total         80.1 GiB per rank
before the budget  3.9 GiB
a 4,096-token request: 137 MiB of KV cache per rank

func (*Kernel) GraphModes added in v0.3.0

func (k *Kernel) GraphModes() (decode, other string)

GraphModes returns the CUDA graph mode the engine runs a uniform decode step under and the one it runs every other step under, as vLLM names them -- "FULL", "PIECEWISE" or "NONE" -- after the overrides Resolved reports. A step above CaptureTokens runs with no graph under either.

func (*Kernel) OutputTokenOverhead

func (k *Kernel) OutputTokenOverhead() time.Duration

OutputTokenOverhead returns host time per emitted token.

func (*Kernel) PDTransferDemand added in v0.2.0

func (k *Kernel) PDTransferDemand(tokens int, from, to kernel.Placement) kernel.TransferDemand

PDTransferDemand is the handoff PDTransferTime prices, separated into payload, floor and the links it crosses, so a simulator running several handoffs at once can arbitrate the links they share. The kernel prices one transfer; dividing a shared link among several is the simulator's work.

Decode-context parallelism deliberately does NOT divide the payload. A prefill pool holds the whole cache for a request -- DCP shards the DECODE cache, and vLLM's own wording is "Number of ranks that shard the decode KV cache" (vllm/config/parallel.py:359-362 at v0.31.0) -- so the bytes that cross between pools are the whole request's, whatever the decode pool's dcp width is.

The links mirror the three placements a handoff can have:

same node                          that node's NVLink domain, at the NVLink rate
same rack, one-domain NVLink rack  that rack's NVLink domain, at the NVLink rate
otherwise                          the sender's NIC egress and the receiver's NIC
                                   ingress, each at the NIC rate

Link IDs are derived from the placement alone, so every kernel of one deployment names a physical link the same way: a prefill pool's kernel and a decode pool's kernel describe one handoff with the same IDs, and two handoffs that cross one NIC name it alike.

The floor is the expert-group all-to-all floor, which is what PDTransferTime has always charged. Whether that is the right floor for a KV handoff is a separate question: changing it would move every P/D price, so it is not changed here.

func (*Kernel) PDTransferTime

func (k *Kernel) PDTransferTime(tokens int, from, to kernel.Placement) time.Duration

PDTransferTime prices moving one request's KV between pools: its demand, run alone.

Computed from PDTransferDemand rather than beside it, so the two cannot drift: the blis-schemas contract is that a demand alone costs exactly this price, for every argument.

func (*Kernel) PreSnapshotBytes added in v0.3.0

func (k *Kernel) PreSnapshotBytes() int64

PreSnapshotBytes returns what a rank holds before vLLM measures the memory its gpu_memory_utilization budget is drawn from: the collective library's communicator reservation and the engine workspace -- NVIDIA's lumped allowance for the CUDA context, cuBLAS and allocator slack (blis-registry nccl_communicator_bytes_<N>rank, engine_workspace_bytes). vLLM v0.31.0 initializes the device and the distributed environment, NCCL included, and only then takes the snapshot both its requested memory (total x gpu_memory_utilization) and its non-KV figure are measured against (Worker.init_device, vllm/v1/worker/gpu_worker.py:466-493; request_memory, vllm/v1/worker/utils.py:539-557; memory_profiling, vllm/utils/mem_utils.py:300-330). So these take their room from the part's memory beyond the budget, not from the KV cache, and FixedBytes leaves them out of its Total. A consumer checking that the deployment starts at all compares gpu_memory_utilization x memory + PreSnapshotBytes with the part.

KNOWN DIVERGENCE: the workspace allowance is one lumped figure, and the part of it that arises after the snapshot -- cuBLAS and other library workspaces made at the first forward -- is charged against the KV cache by vLLM and is left out here with the rest; the registry states no split.

func (*Kernel) PrefillContextParallelWidth

func (k *Kernel) PrefillContextParallelWidth() int

PrefillContextParallelWidth returns how many ranks split a prefill sequence.

The companion to DecodeContextParallelWidth, and the two are genuinely independent: PCP splits prefill computation and expands the process world size while leaving the KV cache replicated, where DCP shards the cache without expanding the world size -- it reuses the tensor-parallel ranks when pcp is 1, and spans the PCP axis or the whole TP x PCP block otherwise (vllm/config/parallel.py:359-362 at v0.31.0). A consumer sizing a deployment needs both, and neither can be derived from the other.

Interim for the same reason as DecodeContextParallelWidth.

func (*Kernel) Provenance

func (k *Kernel) Provenance() []kernel.CoefficientOrigin

Provenance reports every coefficient this kernel used and its evidence.

func (*Kernel) Resolved

func (k *Kernel) Resolved() kernel.Resolution

Resolved reports the configuration after resolution, including overridden requests.

func (*Kernel) SequenceAdmissionBytes added in v0.3.0

func (k *Kernel) SequenceAdmissionBytes() int64

SequenceAdmissionBytes returns the per-sequence bytes the engine reserves for a request's bounded state at admission: SequenceFixedBytes with each window ring at vLLM's admission cap rather than its steady decode hold -- cdiv(window - 1 + extra_retained + max_in_flight_tokens, block) + 1 pages, max_in_flight_tokens being the concurrent batches times max_num_batched_tokens (vllm/v1/kv_cache_interface.py:840-866; vllm/config/vllm.py:592-613) -- in whole blocks of the shared pool.

It is what vLLM sizes a request at when it reports its KV capacity: the engine's "GPU KV cache size" is the pool's blocks over SequenceAdmissionBytes plus SequenceVariableBytes(max_model_len), in blocks, times max_model_len (get_kv_cache_capacity, vllm/v1/core/kv_cache_utils.py:2444-2471), and its runtime admission gate clamps a request's window reservation to the same cap (vllm/v1/core/single_type_kv_cache_manager.py:216-229). It grows with max_num_batched_tokens: on DeepSeek-V4.1-Flash at 8,192 tokens, 517 pages in each of eight window groups against the six SequenceFixedBytes holds. A caller charging every resident sequence the engine's reservation charges this; one charging the steady decode hold charges SequenceFixedBytes. It equals SequenceFixedBytes for a model with no window cache.

func (*Kernel) SequenceBoundedBytes added in v0.3.0

func (k *Kernel) SequenceBoundedBytes(inFlightTokens int) int64

SequenceBoundedBytes returns the per-sequence bytes of a request's bounded state with inFlightTokens scheduled but not yet settled: each window ring at cdiv(window - 1 + extra_retained + inFlightTokens, block) + 1 pages in whole pool blocks (SlidingWindowSpec.max_admission_blocks_per_request, vllm/v1/kv_cache_interface.py:840-866 at v0.31.0), the state caches and the recurrent state as SequenceFixedBytes holds them. At 1 + num_spec_tokens it is SequenceFixedBytes, the steady decode hold, wherever the drafter retains no extra tokens (all but multi-module MTP); at the engine's max_in_flight_tokens it is SequenceAdmissionBytes. A simulator that grows a sequence's window reservation with the tokens it actually has in flight, as vLLM's scheduler does, charges this per step instead of holding the admission cap for the sequence's life.

func (*Kernel) SequenceBoundedBytesAt added in v0.3.0

func (k *Kernel) SequenceBoundedBytesAt(computedTokens, inFlightTokens int) int64

SequenceBoundedBytesAt is SequenceBoundedBytes for a request that has computed only computedTokens: a window ring holds no page for a token it has not seen, so each ring pages min(computedTokens + inFlightTokens, window - 1 + extra_retained + inFlightTokens) tokens, plus the rules pack's page for a window that does not start on a page edge. That is the hold vLLM's allocate_slots reaches after remove_skipped_blocks: blocks wholly before the window's trailing edge are freed (get_num_skipped_tokens, SlidingWindowManager, vllm/v1/core/single_type_kv_cache_manager.py:1153-1175, applied by remove_skipped_blocks, :687-725), and new ones are allocated only for the tokens scheduled (get_num_blocks_to_allocate, :178-238). Past the window it equals SequenceBoundedBytes.

KNOWN over-charge: a ring that has not yet outgrown its window starts on a page edge, so its trailing page is charged though vLLM holds none, at most one page a ring.

func (*Kernel) SequenceFixedBytes

func (k *Kernel) SequenceFixedBytes() int64

SequenceFixedBytes returns per-sequence bytes that do not vary with prompt length: recurrent state, where the model has it and the cache mode keeps it bounded, and the bounded caches a graph states -- window rings and fixed state (cacheFixedBytes).

func (*Kernel) SequenceVariableBytes

func (k *Kernel) SequenceVariableBytes(tokens int) int64

SequenceVariableBytes returns per-sequence KV bytes for a token count, quantized to the engine's page size.

func (*Kernel) StepTime

func (k *Kernel) StepTime(b kernel.Batch) kernel.StepEstimate

StepTime prices one forward pass. This is the hot path.

The returned StepEstimate carries a freshly allocated PerResource map. A simulator that calls this per simulated step, millions of times, can use StepTimeInto instead and reuse one map across calls, which removes that allocation.

func (*Kernel) StepTimeInto

func (k *Kernel) StepTimeInto(b kernel.Batch,
	per map[kernel.Resource]time.Duration) kernel.StepEstimate

StepTimeInto prices one forward pass, writing the per-resource breakdown into per instead of allocating a map.

per is cleared first, so a caller reuses one map for every step. Passing nil is the same as calling StepTime: a new map is allocated.

The map is the allocation a caller can remove. The step still allocates a few scratch slices that grow with the number of requests -- the prefill chunks, the decode contexts, the prefills a context gather packs -- and BenchmarkStepTimeIntoDecode reports how many.

func (*Kernel) TierTime

func (k *Kernel) TierTime(tier string, dir kernel.Direction, bytes int64,
	queueDepth int) time.Duration

TierTime prices one transfer between GPU and an offload tier.

type Repos

type Repos struct {
	Scenarios string // directory holding scenario files
	Catalog   string // a blis-catalog checkout, or a pinned copy of one
	Registry  string // a blis-registry checkout, or a pinned copy of one
}

Repos locates the roots a scenario's names resolve against.

A scenario names its model, hardware, fabric and coefficient sets without saying where they live — it describes a deployment, not a filesystem — so a caller supplies the roots.

Directories

Path Synopsis
cmd
bandprobe command
Command bandprobe prices one step per line of a CSV and prints both band edges.
Command bandprobe prices one step per line of a CSV and prints both band edges.
overlap-probe command
Command overlap-probe answers one question: does the per-layer overlap term change the SHAPE of the concurrency response, or only its level?
Command overlap-probe answers one question: does the per-layer overlap term change the SHAPE of the concurrency response, or only its level?
score command
Command score reports how well the kernel predicts published measurements.
Command score reports how well the kernel predicts published measurements.
shape command
Command shape scores this kernel's concurrency response against NVIDIA's end-to-end accuracy snapshot, along AISimulate's own published error on the same points.
Command shape scores this kernel's concurrency response against NVIDIA's end-to-end accuracy snapshot, along AISimulate's own published error on the same points.
worked-table command
Command worked-table prints a worked step-time table, computed by the kernel itself from the committed catalog and registry.
Command worked-table prints a worked step-time table, computed by the kernel itself from the committed catalog and registry.
internal
artifacttest
Package artifacttest holds the rule every test package uses to decide whether a failure to load a catalog or registry artifact is a skip or a failure.
Package artifacttest holds the rule every test package uses to decide whether a failure to load a catalog or registry artifact is a skip or a failure.
docgen command
Command docgen writes the generated parts of the documentation into docs/generated/:
Command docgen writes the generated parts of the documentation into docs/generated/:
harness
Package harness holds what this repository's scoring and reporting commands share: opening a kernel from a scenario file, and building the batch shapes a comparison prices.
Package harness holds what this repository's scoring and reporting commands share: opening a kernel from a scenario file, and building the batch shapes a comparison prices.
price
Package price holds the cost laws the kernel composes: the GEMM efficiency ramp, the cross-node collective scaling, and the resource maximum.
Package price holds the cost laws the kernel composes: the GEMM efficiency ramp, the cross-node collective scaling, and the resource maximum.
resolve
Package resolve turns a scenario, the catalog facts it names and the registry sets it lists into one frozen configuration a kernel prices against.
Package resolve turns a scenario, the catalog facts it names and the registry sets it lists into one frozen configuration a kernel prices against.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL