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 (transport = cudaMemcpyPeerAsync there).

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