Skip to main content

PpNRt

Struct PpNRt 

Source
pub struct PpNRt { /* private fields */ }

Implementations§

Source§

impl PpNRt

Source

pub fn get(e: &Engine) -> Result<&'static PpNRt, Box<dyn Error>>

The process-wide transport runtime, built on first use against the primary engine. The stage count + device map freeze at first build (one config per process — gates run one placement per invocation). Build errors are sticky and loud.

Source

pub fn n_stages(&self) -> usize

Source

pub fn cross_device(&self) -> bool

True iff any boundary crosses devices.

Source

pub fn init_boundary_transport( &self, e: &Engine, n_embd: usize, ) -> Result<(), Box<dyn Error>>

Finish boot-time transport setup from the authoritative model width. This runs the production BoundarySlot ladder at 1/8/16/PRIME_CHUNK_MAX_TOKENS [n_embd] f32 rows once, then allocates host-bounce slots when selected. The loader calls it before uploading the first model weight; new_cache repeats the call as an idempotent guard before the first forward.

Source

pub fn engine<'a>(&'a self, s: usize, primary: &'a Engine) -> &'a Engine

The engine a stage’s subgraph must run through: the primary engine when the stage lives on the primary device, else the stage’s own (remote-context) engine.

Source

pub fn bind_stage(&self, s: usize) -> Result<(), Box<dyn Error>>

Bind this OS thread to stage s’s CUDA context before issuing work there.

Source

pub fn enter(&self, s: usize) -> StreamOverride

Enter stage s: until the guard drops, every engine op on this thread launches on the stage’s stream (memra_runtime ambient-stream override).

Source

pub fn prepare_overlap_slots( &self, b: usize, n: usize, ) -> Result<(), Box<dyn Error>>

Allocate/grow BOTH slots for a boundary before pipelined issue starts. tx() can grow a slot lazily, but first-use ordering requires synchronizing the RX stream after that allocation. If slot 1 first grows after stage 1 of chunk N has already been queued, that sync drains chunk N and erases the only overlap in a two-chunk prime. Prewarming both slots pays the same one-time sync before either stage starts.

Source

pub fn tx( &self, b: usize, x: &CudaSlice<f32>, n: usize, ) -> Result<usize, Box<dyn Error>>

Boundary TX at boundary b (call within the stage-b scope; x = the materialized [n] residual): wait for the slot’s previous RX (write-after-read guard), copy x into the slot’s persistent buffer via the boundary’s transport on stage-b’s stream (the owning-stream/publication law), record ev_tx. Returns the slot index for the paired rx().

n is the PAYLOAD ELEMENT COUNT, not a fixed model constant: the eager arm passes n_embd (one row), the batched arm passes b_n * n_embd (B stacked rows, the [B, n_embd] boundary). The slot buffer is GROW-ONLY and the transport moves exactly the first n elements — batched serving changes B every tick (chunk fill), and a realloc-on-every-size-change would host-sync the RX stream per width change (see the SLOT FIRST-USE ORDERING note below for why each allocation needs that sync). Growing to the high-water mark makes the syncs O(distinct widths) instead of O(width changes).

Source

pub fn tx_pipelined( &self, b: usize, x: &CudaSlice<f32>, n: usize, ) -> Result<usize, Box<dyn Error>>

Pipelined boundary TX: always alternate the shared double-buffer slots, independent of the decode-side MEMRA_PP_OVERLAP experiment flag. The boundary-local atomic keeps concurrent callers on one slot sequence rather than each restarting at A.

Source

pub fn rx( &self, b: usize, slot_idx: usize, n: usize, ) -> Result<CudaSlice<f32>, Box<dyn Error>>

Boundary RX at boundary b (call within the stage-b+1 scope): wait on the slot’s ev_tx, copy the boundary buffer into a fresh working buffer (dtod on the RX stream — local on the RX device in both transports), record ev_rx. The returned buffer is RX-stage-owned: allocated, consumed, and eventually freed on that stage’s stream.

Source

pub fn publish_to( &self, s: usize, dst: &Arc<CudaStream>, ) -> Result<(), Box<dyn Error>>

PUBLISH a DEVICE-RESIDENT result off the last stage to the caller’s stream (lane/pp2-spec 2026-08-06).

Every ppN body before this one returned HOST values — decode_step_h_ppn and decode_step_batch_ppn both dtoh inside the last-stage scope, and a dtoh on the producing stream is self-ordering. The verify trunk is the FIRST ppN body whose contract is device-resident output (decode_step_t_h_emb_dev exists precisely so the accept walk argmaxes on-device instead of moving T x n_vocab f32 per round), and device slices carry no stream affinity: the caller resumes on the PRIMARY stream and dereferences buffers whose producing kernels are still queued on the last stage’s stream. Nothing orders them.

Why this only ever failed on ONE device: with stages on separate devices the caller’s first touch is a cross-device copy that the driver orders against the source context, and the readback path syncs. Two streams on the SAME device genuinely overlap, so the primary stream reads a buffer whose matmul has not run — nondeterministic garbage (measured: NaN, 3155.677, and 2.87e-5 where the reference had -2.0048926), and it poisons the NEXT arm in the same process because the corrupted KV persists. This is the same class as the SLOT FIRST-USE ORDERING find above, one level up: there the unordered pair was alloc-memset vs TX copy, here it is stage-N compute vs the caller’s consumer.

Fix = the boundary law applied to the exit: record an event on the producing stage stream, make the caller’s stream wait on it. Event-wait, not a device sync, so the stage streams keep running for the deferred-readback arm. Call INSIDE the last-stage scope, after the last enqueue, with the caller’s (pre-enter) stream.

Source

pub fn fence_stages_behind( &self, src: &Arc<CudaStream>, ) -> Result<(), Box<dyn Error>>

REVERSE PUBLICATION (#87 root cause, lane/pp2spec-crash 2026-08-07): order every STAGE stream behind the CALLER’s stream — the mirror of publish_to.

publish_to orders caller READS behind stage COMPUTE. Nothing ordered the other direction: buffers ALLOCATED on a stage stream (the verify’s returned logits/hidden, the VerifyCkpt stashes) are CONSUMED by kernels the caller enqueues on the PRIMARY stream, and when they drop, cudarc enqueues free_async on the ALLOCATING (stage) stream. With event tracking elided (the decode-path default) the drop carries no read-guard, so the pool can hand the block to the NEXT stage-stream allocation and its writes overwrite memory the queued primary-stream consumer has not read yet. Measured (research/pp2spec-crash-20260807): the spec round-seed read 13/4096 NaN = the uninitialized-bits signature (P(NaN|random u32) ~ 1/256), clean by host re-read time — a read-before-write race, fatal via the argmax-sentinel -> embed_gather MMU fault, and gated on c>=2 because a backed-up primary stream widens the window.

Fix law: before a ppN body enqueues NEW stage-stream work (allocations that may reuse freed blocks), every stage stream waits the caller’s stream at its current point. All primary consumers of the previous round’s stage-allocated buffers are enqueued by then (single host thread), so reuse-writes land strictly after them. Call at ppN-body ENTRY with the pre-enter caller stream. Door-shut configs never build a PpNRt, so single-card behavior is untouched.

Source

pub fn record_done(&self) -> Result<CudaEvent, Box<dyn Error>>

Deferred readback: record a fresh completion event on the LAST stage’s stream (call after the step’s logits matmul has been enqueued there).

Source

pub fn readback_stream(&self) -> &Arc<CudaStream>

The dedicated readback stream (last stage’s context).

Auto Trait Implementations§

§

impl !Freeze for PpNRt

§

impl RefUnwindSafe for PpNRt

§

impl Send for PpNRt

§

impl Sync for PpNRt

§

impl Unpin for PpNRt

§

impl UnsafeUnpin for PpNRt

§

impl UnwindSafe for PpNRt

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.

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.