# Packed metadata publication

By default (`ATOM_H2D_BACKEND=packed`) the runner combines small metadata
uploads. Set `ATOM_H2D_BACKEND=direct` before starting it to copy each member
separately.
Packing is selected once for eligible groups; noncontiguous bindings use direct
copies. A single active member also uses a direct copy to avoid an extra kernel.
It neither borrows the packed arena nor releases an earlier packed read.
A producer group fully covered by an eligible larger group keeps checked
direct copies for standalone callers, without a duplicate packed arena.
Producers fill the final group's counts directly; compression plans use
`set_count(buffer, count)` without accessing buffer binding internals.

## Consumer boundaries

| Group | Data | First GPU consumer |
| --- | --- | --- |
| `token_inputs` | Sampling parameters, padded query prefix, input IDs, deferred source indices and speculative verification indices | Token assembly, then draft ID gather |
| `prefill_inputs` | Prefill attention mirrors and ordinary positions | Attention preparation |
| `mha_decode` | Slots, context lengths, block tables, KV prefix and positions | KV-index generation |
| `v41_metadata` | Compression/write plans, state slots, positions, token batch IDs, visibility and changed block tables | Step indptr generation |

V4.1 ordinary DSpark decode uses two H2Ds and two scatters: token inputs and
V4.1 metadata. All graph padding is included. Other attention builders, MRoPE
and TBO can require additional groups at their own consumer boundaries.
The shared table state records publication only after the upload succeeds.

Engram tentative preparation writes cursor candidates together with its
snapshot, preserving every accepted prefix. Committed in-place cursor updates
remain ordered after the snapshot. The sampler corrects greedy rows without
reading a GPU mask back to the CPU. Uniform top-k/top-p filters stay as CPU
scalars through ordinary and speculative sampling; only per-request filters
are uploaded. This preserves AITER scalar dispatch without a GPU readback.
The runner restores an implicit CPU default after GPU initialization to avoid
per-call DeviceContext dispatch. MRoPE decode planes use the running token
width as their axis stride before their first publication, for every backend.
V4 TBO consumers retain views of their own ubatch buffers; CPU query lengths
also supply the maximum query width without a device reduction or readback.

## Lifetime and publication contract

Each existing runner slot owns its pinned sources, packed arenas and completion
event. `begin()` waits for source reuse before any producer writes and starts
one forward epoch. `finish()` records completion after preparation; it does not
wait for GPU execution. PP rotates the existing slots. Late TBO preparation
resumes the same epoch and records completion again after its uploads.

Buffers are declared with a group and count unit (`rows`, `elements` or `bytes`)
at allocation. Initialization binds their fixed source/destination storage and
checks overlaps. Producer counts include semantic padding. `None` omits a
member; zero is an explicit empty publication. A group validates all counts,
owner thread/device/stream and duplicate publications before submitting work.
Sampling, input IDs, query prefixes, speculative indices and common attention
producers declare their source groups with `@h2d_producer("group", ...)`.
The shared decorator checks the current slot before entering the producer, including when its upload uses a
combined group. Rejected producer reentry leaves earlier sources intact.
A deliberate second publication supplies a reason and acquires its source
before rewriting it. Enqueue failures invalidate the owner and retain storage.

The packed arena contains int64 offset/count pairs and byte payloads in one DMA.
The scatter preserves bit patterns and writes only the published ranges into
the original GPU addresses. It reuses its compiled kernel and fixed destination
table. No new synchronization event or GPU scalar read is added by packing.

Publication runs before actual graph capture/replay on the consumer stream.
Bound metadata storage stays fixed; rebuilding those buffers requires the
caller's existing consumer drain and graph rebuild. This API does not manage
checkpoint pools, bulk embeddings, model weights or KV offload storage.

## Verification

Run GPU publication and producer tests with `RUN_H2D_GPU_TESTS=1`.
The suites are organized by contract and consumer, with reentry regressions
kept beside the producers they protect:

| Test file | Coverage |
| --- | --- |
| `tests/test_h2d_publication.py` | Ownership, atomic validation, stream/device identity and graph capture |
| `tests/test_packed_h2d.py` | Byte-preserving DMA/scatter, arena reuse and direct fallback |
| `tests/test_h2d_runner_publication.py` | Sampling, token inputs, query prefixes and speculative indices |
| `tests/test_h2d_attention_publication.py` | Common attention consumers, page maps and PP/TBO buffers |
| `tests/test_h2d_v4_publication.py` | V4/V4.1 plans, state, step metadata and dummy isolation |
| `tests/test_h2d_v4_indexer_publication.py` | V4 indexer, PCP, TBO and Opus |
| `tests/test_h2d_draft_publication.py` | Draft and GDN consumers |

These suites check delayed source reuse, exact values, untouched tails, changing
batch sizes, graph padding and first consumers. CPU page-table state transitions
remain in `tests/test_shared_block_tables.py`, runnable without GPU opt-in.

Use the server's torch profiler endpoints for model traces. On ROCm, correlate
copies with HIP runtime `kind=1`; displayed memcpy names can mislabel H2D as DtoD.
Measure from preparation GPU work through the first model kernel as well as
counting copies. Fewer copies alone do not establish a universal TTFT benefit.
Experiment logs and one-off profiling tools are maintained outside this PR.

## Shared block-table preparation

`block_table_state(buffer)` attaches page-map state to the existing physical
`CpuGpuBuffer`. Common attention, MHA/MLA, V4 and V4.1 share this preparation
and publication policy. Derived attention backends inherit it. Each PP slot
and each TBO buffer has its own state; this optimization is independent of TP,
DP or PP topology. The worker RPC decoder preserves versioned rows for the
same reason, rather than rebuilding page-map change information in attention.

A source `BlockTable` carries an append-lineage version. Ordered version and
length vectors identify the batch mapping without reading page IDs. Appending retains the version; modifying or deleting existing IDs draws
a new version. The decoder copies a growing row while preserving that lineage,
so older batches remain unchanged. Independent copies draw new versions.
Unversioned array/list callers compare against the pinned snapshot instead.

The pinned page table is the CPU snapshot: preparation retains no source
array views or intermediate ndarray/tuple copies. A hit reads only row versions
and lengths. An append checks and copies the added IDs; a changed mapping
updates only changed rows. Full-width rows are copied without clearing the
bytes they replace; fully replaced batches clear exposed tails in one bytewise
pass. The fixed destination view is reused, and no source view is retained.
Optional page-range validation follows each append lineage across reordering,
with the pool limit as part of the proof. Single-page suffixes use scalar bounds
checks. Request spans still check their required number of pages every step. All rows
are validated and source ownership acquired before any host write.

CPU preparation and successful GPU publication have separate revisions.
Prefill can prepare CPU slots without uploading a table. Publication reuses the
GPU table only when its destination, prepared revision and requested row count
match. Changed tables still use one bulk publication; this does not add partial
H2D transfers. A combined group omits an unchanged table but consumers retain
its GPU view. Failed publication never marks the new revision as published.
Padding, TBO slices and capture table preparation use the same state. Direct
writes to the CPU/GPU table outside these entry points invalidate this contract.

V4.1 `RequestSpan` describes only the request interval and state slot;
`begin_step(..., block_tables=rows)` receives the page mappings separately.
Runtime dummy PAGE/state storage remains private. Graph capture continues to
bind serving storage, as required by its fixed addresses. This change does not
alter either cache-allocation policy.
