tt-hous's picture
Add files using upload-large-folder tool
e649a04 verified
|
Raw History Blame Contribute Delete
13.2 kB

Prefill Runtime

models/common/llm_runtime/prefill implements the common host-side prefill pipeline for TTTv2 language models. It plans logical requests, stages TT inputs, runs eager or traced prefill, postprocesses device outputs, restores the caller's row order, and releases invocation resources.

This document describes the internals of the prefill package. See the common LLM runtime README for model-executor composition, program and trace registries, paged-KV allocation, warmup, serving lifecycle, and cleanup order.

Scope

The package owns:

  • immutable prefill request and chunk planning;
  • regular-single, regular-batched, and chunked invocation geometry;
  • eager program and trace identity construction;
  • host and device input staging;
  • trace capture inputs, replay refresh, and replay ownership;
  • prefill postprocessing and device sampling state;
  • streaming synchronized result collection; and
  • retryable cleanup of prefill invocation transients.

The package does not choose a model, allocate the paged KV cache, select eager versus traced execution, own the program or trace registries, or adapt an inference-server API. Those decisions remain with the model executor and the common runtime components above this package.

Composition and construction

A model executor resolves one immutable config and constructs one prefill runtime per execution lane. PrefillRuntime then composes the focused package collaborators around the exact model, mesh, output reader, and page-table layout held by that config:

model + mesh + OutputReader + PageTableLayout + static prefill policy
  β”‚
  └─> PrefillRuntimeConfig.resolve(...)
        β”‚
        └─> PrefillRuntime(config)
              β”‚
              β”œβ”€> PrefillInputStager(model, mesh)
              β”‚
              β”œβ”€> PrefillPostprocessor(config)
              β”‚
              β”œβ”€> PrefillResultAssembler(
              β”‚     config,
              β”‚     postprocessor,
              β”‚     transient-release callback)
              β”‚
              β”œβ”€> PrefillSequenceRunner(
              β”‚     input stager,
              β”‚     postprocessor,
              β”‚     model-body callbacks,
              β”‚     transient-release callback)
              β”‚
              └─> PrefillTraceLifecycle(
                    input stager,
                    postprocessor,
                    model-body callbacks,
                    transient-release callback)

The collaborators share identities rather than constructing replacements:

  • the input stager, postprocessor, sequence runner, and trace lifecycle use the exact objects composed by PrefillRuntime;
  • eager and traced paths call the same runtime-owned model-body boundaries;
  • eager invocation and trace replay finish through the same postprocessor;
  • eager and traced results enter the same streaming result collector; and
  • all transient release callbacks return to the runtime's retryable orphan owner.

configure_page_table_layout(...) is the one intentional late configuration step. Before allocation or execution, it installs a bounded immutable config replacement and forwards that exact replacement to config-dependent collaborators. It does not reconstruct the runtime or its execution helpers.

Data flow

One public prefill call follows four phases:

host request
  -> plan and classify                  [runtime.py, plan.py, signatures.py,
                                         postprocess.py]
  -> execute each prepared request      [sequence_runner.py or trace.py,
                                         inputs.py, postprocess.py]
  -> collect each invocation result     [result_collector.py]
  -> public logits or sampled tokens

PrefillRuntime is the package facade. Callers use its public methods rather than invoking the planner, input stager, sequence runner, postprocessor, or result collector directly.

Phase Primary participants
Plan and classify PrefillRuntime.prepare() coordinates _plan_prefill_requests(), PrefillPostprocessor.classify_sampling_path(), and the pure signature builders.
Stage inputs PrefillInputStager prepares host values, allocates eager device inputs, or refreshes persistent trace inputs.
Execute eager PrefillSequenceRunner stages and runs each PrefillChunk, releasing non-final outputs as it advances.
Execute traced PrefillTraceLifecycle defines capture, refreshes each replay step, and records replay ownership.
Postprocess PrefillPostprocessor turns the final hidden output into logits or device-sampled result values.
Collect PrefillResultAssembler synchronizes readback, restores source rows, aggregates outputs, and releases invocation ownership.

1. Plan and classify once

PrefillRuntime.prepare(...) validates the request and produces one or more immutable PreparedPrefill values:

tokens + page table + lengths + cached positions + slots
  -> PrefillRequest
       -> one or more PrefillChunk values
  -> sampling path
  -> eager program signatures
  -> optional trace signature
  -> PreparedPrefill

The prepared value is the shared input to eager invocation, compilation, trace capture, and traced dispatch. Those paths do not re-plan the request or reclassify sampling.

The central immutable values are:

Value Meaning
PrefillRequest Logical rows, physical geometry, page tables, cached offsets, and ordered chunks for one invocation sequence
PrefillChunk One model-body step, including its token slice, absolute position, page-table view, and final-token marker
PreparedPrefill One request plus its sampling classification and program/trace identities
InvocationResult A result value together with the transient resources that remain owned until collection

2. Execute eager or traced prefill

Eager execution runs the complete prepared sequence through PrefillSequenceRunner:

PreparedPrefill
  -> stage one chunk
  -> run the regular or chunked model body
  -> release a non-final chunk output
  -> repeat until the final-token chunk
  -> finish prefill postprocessing
  -> InvocationResult

A regular request is the one-step case. A cached or long request can contain multiple chunks, but it retains one ordered request-level sequence and one final result.

Traced execution captures a sampling-free hidden body and keeps program-alias-local postprocessing state separate:

PreparedPrefill
  -> capture plan
       -> persistent model inputs
       -> hidden-body trace
       -> alias-local replay workspace
  -> refresh inputs for each replay step
  -> replay each planned chunk
  -> finish only the final hidden output
  -> InvocationResult

Cached offsets and chunk starts are refreshed inputs. A fixed chunk trace can therefore serve multiple positions through a host-side replay loop without changing trace identity.

3. Postprocess device outputs

PrefillPostprocessor owns the boundary between model-body output and the result that will be read on the host. Its responsibilities include:

  • selecting logits, forced-argmax, or top-k behavior;
  • creating and refreshing K/P/T device values;
  • selecting the logical last-token row or tile;
  • allocating sampling and log-probability outputs; and
  • preserving which tensors are borrowed, persistent, or invocation-owned.

Postprocessing does not restore public source-row order and does not perform the final synchronized host read. Those belong to result collection.

4. Collect results in source-row order

PrefillResultAssembler.assemble(...) consumes prepared invocation results as a stream. For each result it:

  1. completes synchronized device-to-host readback;
  2. normalizes logits or sampled tokens;
  3. restores the original source-row mapping;
  4. records per-row log probabilities when requested; and
  5. releases that invocation's owned resources before advancing the stream.

Streaming is a correctness requirement for trace replay. Multiple replays can reuse one persistent output buffer, so a result must be read before the caller advances the generator and overwrites it.

Planning invariants

Logical rows and physical rows are distinct

source_rows preserves caller-visible order. active_batch_size records real rows, while padded_batch_size records the physical wave. Compatible active counts round up to a supported wave in {1, 2, 4, 8, 16, 32} without changing the logical result shape.

Program and trace identities use padded geometry rather than active row count. Requests with the same physical geometry can therefore share compiled and captured work.

Padding rows must not write KV

Regular paged-prefill tables use -1 as the skip sentinel for unallocated tails and padding rows. Token padding rows are zero. This makes unused physical rows inert while retaining one shared padded program identity.

Chunked full-request page tables retain the nonnegative filler required by the attention path. Fill-only chunk tables use -1 beyond their mapped prefix. These table roles are intentionally different.

Cached and chunk positions stay absolute

Planning preserves the cached-token offset and each chunk's absolute start. Last-token extraction is derived relative to the current invocation only at the staging or postprocessing boundary. Cached prefill is not a separate service or execution mode.

Planning fallback is not execution fallback

The planner can represent an ineligible batch as sequential single requests. Once a prepared request selects traced execution, missing program-to-trace coverage is an error; the traced path does not silently reinvoke eager execution.

Identity boundaries

Eager programs and traces have related but different identities:

  • PrefillProgramSignature includes every material program choice, including operation variant, padded invocation geometry, page-table widths, sampling path, and any material last-token tile.
  • PrefillTraceSignature identifies the static hidden body and persistent input geometry. Sampling state is deliberately excluded.
  • capture-schema and workspace fingerprints allow one hidden trace family to keep distinct postprocessing workspaces for its program aliases.

Identity builders are pure. They consume an already-planned request and do not inspect mutable runtime state.

Ownership and failures

The runtime borrows the model, mesh device, output reader, and bound paged-KV context. Eager staging and postprocessing create invocation transients. Successful invocation transfers those resources in InvocationResult.owned to the result collector.

The sequence runner releases non-final chunk outputs immediately. On a primary failure it attempts to release everything acquired so far and attaches cleanup failures without replacing the primary error. Failed releases are retained by PrefillRuntime as retryable tensor-resource orphans and are retried during runtime cleanup.

Trace-owned persistent inputs and hidden outputs are not treated as eager invocation transients. PrefillReplayOwnership records the boundary between trace-owned, nested persistent, newly allocated, and replay-local values.

Module map

Module Main responsibility
config.py Resolve and validate static collaborators, capabilities, and geometry ceilings
plan.py Build immutable logical requests, padded waves, page tables, and chunks
signatures.py Classify prepared requests and construct pure eager/trace identities
inputs.py Prepare host values, stage device inputs, and refresh replay inputs in place
sequence_runner.py Run eager chunk sequences with failure-safe ownership transfer
trace.py Define capture plans, persistent inputs, replay workspaces, refresh, and replay ownership
postprocess.py Classify sampling and transform final hidden outputs into readable result values
result_collector.py Read results synchronously, restore source rows, aggregate outputs, and release ownership
sampling_helpers.py Provide stateless sampling normalization, slicing, and log-probability helpers
runtime.py Expose the stable facade, compose collaborators, call model bodies, and retain cleanup orphans

The intended dependency direction is from the facade toward focused collaborators and from execution mechanics toward immutable plan/signature values. Focused modules must not import the model executor, execution-target selection, compiler registries, warmup coordination, or serving adapters.

Tests

The main contract suite is models/common/tests/llm_runtime/test_prefill_runtime.py. It covers planning, padding, page-table semantics, signatures, eager sequences, trace refresh, sampling, result collection, and cleanup failures.

models/common/tests/llm_runtime/test_execution.py covers the boundary between prepared prefill values and eager/traced execution, including strict trace coverage and streaming replay consumption. Warmup-specific prefill coverage is tested in models/common/tests/llm_runtime/test_warmup.py.