pub struct Glm5NextDsaState {
pub k_normed: DevicePtr,
pub gate: DevicePtr,
pub valid: DevicePtr,
/* private fields */
}Expand description
One sequence’s indexer cache for one DSA layer.
Allocated once at sequence creation and never grown: max_dsa_context is a hard cap,
so a fixed reservation is correct. At index_head_dim = 128 that is 513 B a token a
layer — 8 MiB per layer (~92 MiB over the 11 text DSA layers) at a 16,384-token context,
and 64 MiB per layer (~736 MiB) at 131,072.
Fields§
§k_normed: DevicePtr[capacity, index_head_dim] BF16 — LayerNorm’d indexer keys.
🪤 indexer.k_norm is an nn.LayerNorm with a bias, not an RMSNorm. The bias
is applied when this is written; a .weight-only binder silently drops both the
mean subtraction and the bias.
gate: DevicePtr[capacity, index_head_dim] BF16 — the compress-gate projection.
valid: DevicePtr[capacity] u8 — per-position validity.
Implementations§
Source§impl Glm5NextDsaState
impl Glm5NextDsaState
Sourcepub fn alloc(gpu: &dyn GpuBackend, cfg: &Glm5NextDsaConfig) -> Result<Self>
pub fn alloc(gpu: &dyn GpuBackend, cfg: &Glm5NextDsaConfig) -> Result<Self>
Reserve for the whole addressable context. alloc_state has no length argument, so
the cap — not the prompt — sizes this.
pub fn len(&self) -> usize
pub fn is_empty(&self) -> bool
pub fn capacity(&self) -> usize
Sourcepub fn row_offset(&self, pos: usize) -> usize
pub fn row_offset(&self, pos: usize) -> usize
Byte offset of row pos in k_normed / gate.
Sourcepub fn ensure_room(&self, n: usize) -> Result<()>
pub fn ensure_room(&self, n: usize) -> Result<()>
Would n more rows fit? Ask BEFORE writing them, not after.
🔴 advance is too late on its own. indexer_forward GEMMs k_normed and gate
straight into row len() and only then advances, so at len == capacity the write
lands on row capacity — 256 B past k_normed/gate and 1 B past valid. CUDA
reports that asynchronously as CUDA_ERROR_ILLEGAL_ADDRESS (700) at the next
synchronize, and a 700 is sticky: every later CUDA call in the context fails, so
one over-length prompt takes the serve down for every subsequent request while
/v1/models, /health and /health/live all keep answering 200. Checking first
turns that into a plain per-request error. ANOMALIES A62 (the overrun) and
A60 (the non-recovering serve it explains).
Sourcepub fn ensure_room_through(&self, end: usize) -> Result<()>
pub fn ensure_room_through(&self, end: usize) -> Result<()>
The same refusal for an ABSOLUTE end position.
🔴 The graph-replay path knows where the sequence will END (seq_len + k) but not
where this counter currently sits: a rejected draft leaves it AHEAD, and sync_to
rewinds it only after the replay has already written. Asking in absolute terms is
what makes the check answerable before launch_graph. A62.
Sourcepub fn advance(&mut self, n: usize) -> Result<()>
pub fn advance(&mut self, n: usize) -> Result<()>
Advance after writing n rows at [len, len + n).
Refuses rather than wrapping or truncating: past the reservation there is no row to write, and a silently clamped length would select over a prefix while the MLA cache held the full context — a wrong answer, not a crash.
Sourcepub fn rewind_to(&mut self, n: usize) -> Result<()>
pub fn rewind_to(&mut self, n: usize) -> Result<()>
Plan a selection over everything cached so far.
Rewind to n rows after a rejected speculative draft.
The rows in [n, len) are left in the cache but become unreachable: the selector reads
[0, len) and the next write starts at n, so they are overwritten before anything
can select over them. Only shrinks — growing is advance’s job, and a request to
“rewind” forward would mean the caller lost track of where the sequence is.
Sourcepub fn sync_to(&mut self, seq_len: usize, k: usize) -> Result<()>
pub fn sync_to(&mut self, seq_len: usize, k: usize) -> Result<()>
Put the counter where a RUN step would have left it, for a step served by a replayed
CUDA graph. seq_len is the sequence length before this step’s k rows.
🔴 The same lockstep reconcile decode_k does on the eager path, and for the same
reason: a K-row verify writes K rows and the scheduler keeps only the accepted prefix,
so the counter is AHEAD by (k - accepted) whenever a draft was rejected. decode_k
rewinds on entry; a replay never calls it, so a plain advance(k) compounds that drift
every step. See ANOMALIES A56 — the drafter writes its indexer rows at len(), so the
drift moves those rows on top of ones the target selects over.
pub fn geometry( &self, cfg: &Glm5NextDsaConfig, q_rows: usize, ) -> Result<DsaSelectGeometry>
Sourcepub fn free(&mut self, gpu: &dyn GpuBackend) -> Result<()>
pub fn free(&mut self, gpu: &dyn GpuBackend) -> Result<()>
Release the per-sequence device buffers.
Takes &mut self rather than self because the only caller reaches the
state through &mut dyn ProposerState and cannot move out of it. The
by-value signature this replaces was inherently call-once; the caller
(Glm5NextMtpHead::free_state) now owns that guard via
Glm5NextMtpProposerState::released.
🔴 Invariant L2 (slot reuse): every buffer freed here can be baked into a captured
CUDA graph, so before a slot is re-occupied its graphs must be destroyed AND these
pointers freed and nulled. free_sequence does both. ANOMALIES A56 put that teardown
in place; the invariant is slot reuse, not the order of the two blocks.
Idempotent: two owners can now reach a DSA state — the drafter’s
free_state and, since ANOMALIES A76, the target layer’s — so a second
call is a no-op rather than a double gpu.free. The pointers are nulled
so a released state cannot be mistaken for a live one.