Glm5NextDsaState

Struct Glm5NextDsaState 

Source
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

Source

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.

Source

pub fn len(&self) -> usize

Source

pub fn is_empty(&self) -> bool

Source

pub fn capacity(&self) -> usize

Source

pub fn row_offset(&self, pos: usize) -> usize

Byte offset of row pos in k_normed / gate.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn geometry( &self, cfg: &Glm5NextDsaConfig, q_rows: usize, ) -> Result<DsaSelectGeometry>

Source

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.

Trait Implementations§

Source§

impl LayerState for Glm5NextDsaState

Source§

fn as_any(&self) -> &dyn Any

Source§

fn as_any_mut(&mut self) -> &mut dyn Any

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more