Each media item's features are encoded lazily when a prefill chunk
first overlaps its expansion and stay pinned until the expansion is
fully evaluated. A chunk never ends strictly inside an atomic
expansion: a bidirectional run's early rows attend its later keys, so
its first evaluation must cover the whole run in one forward. Items
marked Causal are exempt and split at any boundary.
Draft models need the same request state — reference MTP drafters
embed prompt tokens with the image features merged in, and an M-RoPE
drafter cannot compute positions without the request's layout — so the
layout is stamped on every forward, target and draft alike, and the
MTP session holds feature rows across its deferred flush. The dflash
drafter ignores media: its context rows are target hiddens.
A draft model conditions on state that the target produces during its
own forward pass. For an MTP head or an assistant model that state is
the final hidden state; for a block draft it is the concatenated
outputs of several layers. The choice belongs to the model, so Forward
now returns the conditioning state along with the hidden state to
unembed. Models without a special conditioning state return the final
hidden state for both, and the decode paths hand the value to the
drafter without looking at it.
Generalize the draft path so a head that maintains a KV cache (EAGLE-style)
and Gemma's read-only single-position assistant both fit one drafter
interface with no per-model branches, and make the committed stream the
drafter's maintenance mechanism — every committed run is reported, the
drafter pairs each draft slot with its look-ahead token and flushes completed
pairs to the draft caches. The draft KV thus stays prefix-cached alongside
the target in every session, drafting or not.
Models build their own attention masks and read K/V directly from
the cache's buffers, which ties them to the cache's storage layout.
That blocks multi-sequence batching — right-padded rows need a
query-padding mask composed onto every model — and rules out
variants like paged attention where K/V isn't one contiguous tensor.
Caches now hand back a per-layer KVHistory holding post-update K, V,
and a MaskApplier that merges the cache's storage restrictions into
the model's logical mask. Models describe their mask in logical
terms; SDPA composes model, padding, and applier contributions and
dispatches to the kernel's causal or no-mask fast path when it can.
KVHistory still exposes K, V, and the composed mask for manual
attention paths (e.g. CUDA prefill at head_dim > 128).
Performance for single-sequence inference is unchanged.
Switch RoPE from the scalar-offset kernel (mlx_fast_rope) to the
array-offset one (mlx_fast_rope_dynamic) so each batch row can start
at its own position. The pipeline tracks the current position locally
and passes it to the model through Batch.SeqOffsets; each model
materializes that slice into an int32 array for the RoPE call.
Single-sequence behavior is unchanged; this is the wiring needed
before the runner can batch independent sequences.
Gives a single extension point for per-call context (positions,
sequence IDs, masks) as multi-sequence batching grows, without having
to churn every model's Forward signature again.