Skip to main content

HybridModel

Struct HybridModel 

Source
pub struct HybridModel {
    pub cfg: ModelConfig,
    pub embd: EmbedHost,
    pub output_norm: GpuTensor,
    pub output: GpuTensor,
    pub layers: Vec<HybridLayer>,
    pub mtp: Option<MtpHead>,
    pub embd_gpu: OnceLock<CudaSlice<u8>>,
    pub gemma4_aux: Option<GemmaAux>,
    pub prime_slabs: Mutex<Option<PrimeSlabs>>,
}

Fields§

§cfg: ModelConfig§embd: EmbedHost§output_norm: GpuTensor§output: GpuTensor§layers: Vec<HybridLayer>§mtp: Option<MtpHead>§embd_gpu: OnceLock<CudaSlice<u8>>

Lazily-uploaded DEVICE copy of the raw embed table (spec/graph hot loops gather rows on-device instead of host-dequant + htod). ~0.5GB; uploaded once on first use.

§gemma4_aux: Option<GemmaAux>§prime_slabs: Mutex<Option<PrimeSlabs>>

PRIME ACTIVATION SLABS (piecewise-graph foundation, 2026-07-26): the layer loop’s seven trunk transients live in RESIDENT per-model buffers instead of per-call pool allocs — kills ~224 alloc/free API calls per prime AND freezes the Lt GEMM operand addresses (nvjet’s alignment-variant kernels become run-to-run stable once their pointers stop moving). Sized on first prime to the largest T seen; Mutex = lazy init only (single GPU worker).

Implementations§

Source§

impl HybridModel

Source

pub fn load(e: &Engine, g: &GgufFile) -> Result<Self, Box<dyn Error>>

Load a hybrid (qwen35) model from GGUF. Thin byte-identical wrapper over load_from_source.

Source

pub fn load_without_mtp( e: &Engine, g: &GgufFile, ) -> Result<Self, Box<dyn Error>>

Plain-generation loader. run-gen never calls the optional draft head, so avoid loading its weights and expert bank while preserving the model config and all trunk semantics.

Source

pub fn load_from_source( e: &Engine, src: &dyn TensorSource, ) -> Result<Self, Box<dyn Error>>

Load a hybrid model from any TensorSource (GGUF or a safetensors HF checkpoint). The whole loop speaks ggml names; the source maps them (and, for safetensors, applies the SSM value transforms via the owned-buffer seam). The forward graph is untouched.

Source

pub fn load_from_source_without_mtp( e: &Engine, src: &dyn TensorSource, ) -> Result<Self, Box<dyn Error>>

Source-backed twin of load_without_mtp, used by the safetensors/repack run-gen path.

Source

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

Force the device embed table resident, FALLIBLY (F5 right-size ladder, 2026-08-05). The lazy embd_gpu.get_or_init(.. expect ..) sites panic the GPU worker on OOM; on a VRAM-tight rig a right-sized spec session that “fits” can leave too little for this ~hundreds-of-MB upload and die on its first prefill (observed: research/specpool-20260804/server-ladder-miss.log). The server calls this after each ladder landing so the biggest lazy transient surfaces as a catchable Err (shrink further / fall back) instead of a panic. No-op when the host-gather door (MEMRA_EMBED_DEV=0) is open or the table is already resident.

Source

pub fn embed( &self, e: &Engine, tokens: &[u32], ) -> Result<CudaSlice<f32>, Box<dyn Error>>

Source§

impl HybridModel

Source

pub fn forward( &self, e: &Engine, tokens: &[u32], ) -> Result<Vec<f32>, Box<dyn Error>>

Prefill forward over tokens; returns logits [T, n_vocab] (host f32).

Source

pub fn forward_last( &self, e: &Engine, tokens: &[u32], ) -> Result<Vec<f32>, Box<dyn Error>>

Prefill that returns ONLY the last token’s logits — the common case (greedy/sample needs just the final position to start decode). Runs the trunk over all T, then the lm_head (output.weight, the largest matrix — 248320 rows) on the LAST hidden row ONLY, not all T. On a 512-token prompt this turns a [512,248320] GEMM into [1,248320] — the dominant prefill cost (nsys: ~99ms when done for all T). Bit-identical last-row logits to forward()[last].

Source

pub fn prime_cache( &self, e: &Engine, tokens: &[u32], cache: &mut Cache, ) -> Result<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn Error>>

BATCHED PROMPT PRIME (the measured #1 e2e gap, e2e-image-1): forward_last’s batched prefill body EXTENDED to leave a DECODE-READY cache behind — vs the tokenwise prime’s ~102/38 tok/s (9B/27B) decode_step loop, this runs the whole prompt at prefill throughput. (a) full-attn layers append their T post-RoPE K/V rows into cache.kv[il] via the SAME per-row quantize kernel as the decode append (bit-identical cache bytes per row); (b) linear layers run STATEFULLY from the cache’s current recurrent state (zero at a fresh prime): carried-ring conv (ssm_conv1d_tm_state) + ONE gdn_scan(state_in, state_out) whose internal sequential t-loop equals T chained T=1 steps — but with the NORMAL prefill matmul dispatch (GEMM at m>=16), NOT the decode-exact MMVQ the spec verify uses (prime is a prefill-regime pass; the run-gen prefill==decode argmax gate is the accuracy authority, exactly as for forward_last); (c) cache.pos/KV len/len_d advance by T. Returns (last-row logits host, h_seed = last-row PRE-output_norm hidden [n_embd], hiddens = the full pre-output_norm hidden stack [T, n_embd] — generate_spec’s prompt_h). FRESH-PROMPT ONLY (cache.pos == 0): the fa_prefill tiles attend within tokens alone. forward_last itself stays untouched (kernel-check / run-gen gate on it).

Source

pub fn prime_slabs_get( &self, e: &Engine, t: usize, n_embd: usize, n_ff_max: usize, ) -> Result<MutexGuard<'_, Option<PrimeSlabs>>, Box<dyn Error>>

(cache.pos + i). Returns (last-row logits, h_seed, this chunk’s hidden stack [T, n_embd]). See HybridModel::prime_slabs — the eager prime’s resident trunk transients.

Source

pub fn prime_chunk_captured( &self, e: &Engine, x_in: &CudaSlice<f32>, pos_d: &CudaSlice<i32>, t: usize, cache: &mut Cache, len_d: &CudaSlice<i32>, logits_out: &mut CudaSlice<f32>, h_seed_out: &mut CudaSlice<f32>, ) -> Result<(), Box<dyn Error>>

CAPTURE-SAFE prime trunk (task #14 increment 1, ARCHITECTURE-H100.md design v2): prime_chunk’s layer stack with every capture hazard hoisted — x is the PRE-EMBEDDED input in a STABLE graph-input buffer (embed + its 8MB htod stay eager, one launch), pos_d is a baked device param (fresh prime = 0..T, constant per bucket), logits/h_seed/hidden stay DEVICE-resident (no dtoh), and cache.pos is NOT advanced (host state — the replay wrapper owns it). Body mirrors prime_chunk (the prime-graph-gate pins them together). KNOWN smoke-scope gap: append host-len bookkeeping still runs on the host per call — the real replay path moves the write slot to the len_d device counter (increment 3). GRAPH-OUTPUT CONTRACT: results are COPIED into caller-provided stable buffers (logits_out [n_vocab], h_seed_out [n_embd]) — every internal allocation drops INSIDE the capture region (alloc+free node pairs). Retaining an in-capture allocation across end_capture makes instantiate throw INVALID_VALUE (smoke finding 2026-07-26), and under AUTO_FREE_ON_LAUNCH its address wouldn’t survive a launch anyway — the decode GraphSession’s pre-allocated-output pattern is the law here.

Source

pub fn prime_cache_batch( &self, e: &Engine, prompts: &[&[u32]], caches: &mut [&mut Cache], ) -> Result<Vec<(Vec<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn Error>>

Cross-request BATCHED fresh prime (task #13, design in ARCHITECTURE-H100.md): the trunk’s token-parallel ops (embed, norms, adds, ffn, projection GROUPS) run once on the CONCATENATION of B sequences — GEMMs at m = sum_T, the continuous-batching win the serving-lane bench attributed the remaining vLLM gap to. The stateful mixer CORES (QK-norm/RoPE/FA/append, conv/GDN scans) run per sequence on split projection buffers (D2D row copies; the mixers’ own out-projections stay per-seq this increment). CONTINUATION primes (increment (b), 2026-07-30): cache.pos > 0 seqs batch the projections/FFN/lm_head exactly like fresh; the mixer cores take the per-seq CONTINUATION arms (Full: core_inner with carried pos_d + fa_prefill_view over the quantized past; Linear: the stateful pad_view twin — the same state carry the chunked single-seq prime rides). The fresh-only favl/gdn-vl fast paths stay byte-identical (gated on !carried). gemma4 models have no continuation prime (v0 monolithic fresh) — carried gemma4 batches return Err (caller falls back to single-chunk serving). NUMERIC CONFIG: a concat GEMM tiles K differently than per-seq GEMMs — same class as every prefill GEMM change; prime_batch_gate arbitrates (argmax + stream battery).

Source

pub fn full_attn( &self, e: &Engine, fa: &FullAttnLayer, h: &CudaSlice<f32>, pos_d: &CudaSlice<i32>, t: usize, ) -> Result<CudaSlice<f32>, Box<dyn Error>>

Full-attention mixer with QK-norm, partial RoPE, sigmoid output gate (qwen35 :257-336).

Source

pub fn linear_attn( &self, e: &Engine, la: &LinearAttnLayer, h: &CudaSlice<f32>, t: usize, ) -> Result<CudaSlice<f32>, Box<dyn Error>>

Linear-attention (Gated DeltaNet) mixer (qwen35 :338-470).

Source§

impl HybridModel

Source

pub fn moe_ffn_il( &self, e: &Engine, m: &MoeWeights, z: &CudaSlice<f32>, t: usize, il: u16, ) -> Result<CudaSlice<f32>, Box<dyn Error>>

MoE FFN (EDGE-1). z: [T, n_embd] (already post-attention-normed). Returns moe_out [T, n_embd]. Node-for-node vs llama.cpp build_moe_ffn + qwen35moe::build_layer_ffn.

il is the trunk layer index — the residency-cache key prefix (a gate-expert of layer 3 is a different 860160-byte block than the same expert of layer 7).

Routing: host softmax+sort (default) OR the fused router kernel (MEMRA_FUSED_ROUTER). Dispatch: stage-every-token into 3 scratch slots (default) OR the SLRU residency cache (MEMRA_MOE_CACHE). The cache-HIT weight path is bit-identical to stage-every-token (§B.3). Convenience wrapper used by the hybrid trunk/MTP loops: pulls dims + max-block from self.

Source

pub fn moe_ffn_il_zq8( &self, e: &Engine, m: &MoeWeights, z: &CudaSlice<f32>, zq8: Option<&(CudaSlice<i8>, CudaSlice<f32>)>, t: usize, il: u16, ) -> Result<CudaSlice<f32>, Box<dyn Error>>

Decode-path twin with a PRE-QUANTIZED z (from add_rms_norm_zq8): threads (zq, zd) into the t=1 dev arm so the per-layer standalone quantize_q8_1 launch folds away. Identical bytes (the fused kernel reproduces quantize_q8_1 exactly); every other path ignores the pair.

Source

pub fn stage1_h2d_per_token(&self) -> u64

Stage-1 (no-cache) per-DECODE-TOKEN H2D bytes: every routed block re-staged every layer every token = sum over MoE layers of n_used * (gate+up+down expert_stride). The §D.4 PCIe baseline.

Source

pub fn save_cpu_expert_residency_profile( &self, e: &Engine, path: &Path, ) -> Result<(), Box<dyn Error>>

Persist the frozen residency set so a later process can restage it directly and skip the profiling warmup. Plain text: a versioned header binding slot geometry, then one layer proj ex triple per line. A mismatched or stale profile is rejected at load (header check) or degrades to fewer restaged blocks (per-id checks); either way the post-freeze argmax gate still validates the serving assignment.

Source

pub fn restore_cpu_expert_residency_profile( &self, e: &Engine, path: &Path, ) -> Result<bool, Box<dyn Error>>

Restage a saved freeze profile and freeze immediately, skipping the profiling warmup. Returns false (leaving the cache untouched for a normal warmup) when the profile is missing or its header does not match this model’s slot geometry.

Source

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

Freeze the heterogeneous CPU/GPU split after the caller’s discarded profile warmup.

Source

pub fn ffn_act( e: &Engine, cfg: &ModelConfig, gate: &CudaSlice<f32>, up: &CudaSlice<f32>, act: &mut CudaSlice<f32>, n: usize, ) -> Result<(), Box<dyn Error>>

FFN activation dispatch: swigluoai (clamped, alpha/limit) when cfg.m3 says so, else the standard SiLU*up. One seam so every FFN site (dense, routed expert, shared expert) follows the model’s activation exactly.

Source

pub fn start_moe_prefetch_predictor( &self, e: &Engine, cfg: &ModelConfig, ) -> Result<(), Box<dyn Error>>

Start the prediction-guided prefetch worker (MEMRA_MOE_PREFETCH=depth). Call after residency freeze: the worker filters against a static snapshot of the frozen HBM set. Builds a fully-owned per-layer table (host router copies via one-time DtoH, bias, active mask, prebuilt projection descriptors) so no model reference escapes.

Source§

impl HybridModel

Source

pub fn gemma4_decode_step_dc( &self, e: &Engine, token_d: &CudaSlice<u32>, pos_d: &mut CudaSlice<i32>, embd_gpu: &CudaSlice<u8>, embd_qt: i32, embd_rb: usize, cache: &mut Cache, n_vocab: usize, cap_bucket_max: Option<(usize, usize)>, ) -> Result<CudaSlice<u32>, Box<dyn Error>>

gemma4 DEVICE-COUNTER decode step (graph arc): token id + rope pos + KV lengths live in device counters; ZERO varying host kernel args. cap_bucket_max = Some(bucket) for graph capture (host mirrors untouched, n_splits from bucket, full-buffer KV views) / None for the eager-dc gate path (host mirrors advanced, live geometry — bit-identical target = gemma4_decode_step_h’s token stream). V1 scope: t_kv <= sliding_window (no window views in-graph; the driver gates).

Source

pub fn gemma4_decode_step_dc_into( &self, e: &Engine, token_d: &CudaSlice<u32>, pos_d: &mut CudaSlice<i32>, embd_gpu: &CudaSlice<u8>, embd_qt: i32, embd_rb: usize, cache: &mut Cache, n_vocab: usize, cap_bucket_max: Option<(usize, usize)>, tok_out: &mut CudaSlice<u32>, ) -> Result<(), Box<dyn Error>>

CAPTURE body: argmax lands in the PERSISTENT tok_out (same buffer = same address on every replay; pass token_d itself for the self-feeding graph loop).

Source

pub fn g4_dc_slots(&self, e: &Engine) -> Result<G4DcSlots, Box<dyn Error>>

Persistent transient slots for the ALLOC-FREE captured dc step (the graph door): every buffer the step produces per token lives here, allocated ONCE pre-capture, so the captured graph carries zero cuMemAllocAsync/Free nodes (the 226us/launch tax, osrt 2026-07-23). Sized for the model’s max per-layer shapes. Build the slot set (call OUTSIDE any capture).

Source

pub fn gemma4_decode_step_dc_slotted( &self, e: &Engine, token_d: &CudaSlice<u32>, pos_d: &mut CudaSlice<i32>, embd_gpu: &CudaSlice<u8>, embd_qt: i32, embd_rb: usize, cache: &mut Cache, n_vocab: usize, cap_bucket_max: Option<(usize, usize)>, sl: &mut G4DcSlots, tok_out: &mut CudaSlice<u32>, ring: Option<(&mut CudaSlice<u32>, usize)>, ) -> Result<(), Box<dyn Error>>

ALLOC-FREE dc step (capture body): kernel-for-kernel mirror of gemma4_decode_step_dc_into at t=1 with every transient slot-fed. Dense gemma4 only (12B/31B; uniform q4_0 trunk guarantees the fused2/3 arms).

Source

pub fn gemma4_generate_graph( &self, e: &Engine, prompt_pos: usize, first_token: u32, cache: &mut Cache, max_new: usize, eos: &[u32], on_token: impl FnMut(u32) -> bool, ) -> Result<(Vec<u32>, StopReason), Box<dyn Error>>

gemma4 GRAPH-REPLAY greedy loop: per (swa-key, global-key) fa bucket, capture ONE full dc step (self-feeding: argmax writes token_d in-graph) and replay it — one graph launch per token, one 4B dtoh. V1 scope: whole generation under the sliding window (no window views in-graph); caller gates and falls back to the dc-eager loop.

Source§

impl HybridModel

Source

pub fn is_gemma4_e4b(&self) -> bool

Source

pub fn gemma4_e4b_decode_step_t_am_dev( &self, e: &Engine, tok_d: &CudaSlice<u32>, t: usize, pos0: usize, cache: &mut Cache, ) -> Result<(CudaSlice<u32>, CudaSlice<f32>), Box<dyn Error>>

E4B batched VERIFY (device tokens, the spec round’s t=K+1 step): t rows through the e4b trunk (per-row causal attention; own-KV layers append t rows host-len, KV-shared layers ride their targets), per-row device argmax + the POST-output_norm hidden stack (the drafter’s h convention). Advances cache.pos/kvl.len by t — the spec round rolls back rejected rows (shared layers have no KvLayer, so the plain rewind covers exactly the layers that appended).

Source

pub fn gemma4_e4b_decode_step_dcg( &self, e: &Engine, token_d: &mut CudaSlice<u32>, pos_d: &mut CudaSlice<i32>, embd_gpu: &CudaSlice<u8>, embd_qt: i32, embd_rb: usize, cache: &mut Cache, n_vocab: usize, bucket: usize, ) -> Result<(), Box<dyn Error>>

E4B GRAPH-CAPTURABLE dc step: same trunk as the dc step but token_d is updated IN PLACE (self-feeding replay) and every launch arg is a device counter — pos from pos_d (inc’d in-stream), KV slots from len_d (advanced in-stream), attention from fa_decode_dc at bucket. Host mirrors (cache.pos / kvl.len) advance in the caller’s replay loop. UNDER-WINDOW regime only (the caller gates pos + budget < window).

Source

pub fn gemma4_e4b_decode_step_dc( &self, e: &Engine, token_d: &CudaSlice<u32>, pos_d: &mut CudaSlice<i32>, embd_gpu: &CudaSlice<u8>, embd_qt: i32, embd_rb: usize, cache: &mut Cache, n_vocab: usize, ) -> Result<CudaSlice<u32>, Box<dyn Error>>

E4B DEVICE-COUNTER decode step (the dc serving arm): token id rides token_d, the greedy argmax lands in the returned device buffer — 4B/token host traffic. The layer stack is gemma4_e4b_trunk_core, i.e. the SAME functions the eager chain runs (stream identity by construction, not by twin-kernel parity). Host KV mirrors advance like the 26B dc-eager arm (window views are host math); len_d stays synced by the caller’s entry sync + the appends here don’t read it. Graph capture is NOT wired (no cap_bucket_max) — the E4B graph arc comes after the perf gates.

Source§

impl HybridModel

Source

pub fn decode_step( &self, e: &Engine, token: u32, cache: &mut Cache, ) -> Result<Vec<f32>, Box<dyn Error>>

One decode step for token at cache.pos; returns logits [n_vocab] (host f32). Advances cache.

Source

pub fn decode_step_aux( &self, e: &Engine, token: u32, cache: &mut Cache, aux_layers: &[usize], ) -> Result<(Vec<f32>, Vec<CudaSlice<f32>>), Box<dyn Error>>

EAGLE3 aux-hidden capture (EAGLE-PLAN N1): one decode step that ALSO returns the trunk residual-stream x taken AFTER each of the blocks in aux_layers (the EAGLE3 encoder feeds these 3 layer hiddens through fc). Returns (logits[n_vocab] host, aux: Vec<[n_embd] dev>), one device buffer per requested aux layer, in aux_layers order. The captured tensor is the residual x produced by that block (x2 at the loop tail), cloned before the next block overwrites it — cheap (one clone_dtod of [n_embd] per aux layer). T=1 decode regime.

Source

pub fn decode_step_hy3_layer0_stages( &self, e: &Engine, token: u32, cache: &mut Cache, ) -> Result<(Vec<f32>, Hy3Layer0Stages), Box<dyn Error>>

Diagnostic-only Hy3 layer-0 trace through the real eager T=1 serving path. Besides the final block residual, this captures the attention output before its residual add, the after-attention residual, and the dense-MLP output before the final residual add.

Source

pub fn decode_step_h( &self, e: &Engine, token: u32, cache: &mut Cache, ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn Error>>

Like decode_step, but ALSO returns the trunk’s hidden state x taken BEFORE the final output_norm (MTP-PLAN §A: this is h_seed for the NextN head). Device buffer [n_embd].

Source

pub fn decode_step_h_ppn_deferred( &self, e: &Engine, token: u32, cache: &mut Cache, ) -> Result<PendingLogits, Box<dyn Error>>

M2 increment 3 (DEFERRED READBACK — the pipelining seed): the ppN step WITHOUT the terminal logits D2H. Returns PendingLogits (device logits + completion event + the runtime’s dedicated readback stream); the caller keeps 2+ tokens in flight by enqueueing step t+1 BEFORE waiting step t (with MEMRA_PP_OVERLAP=1 the double-buffered boundary slots actually alternate, so stage 0 of t+1 runs under stage 1..N-1 of t; the slot ev_tx/ev_rx chain keeps each token’s math fully event-ordered either way — enqueueing deeper than 2 is CORRECT, the slots simply serialize device-side).

EXACTNESS CONTRACT: per-token logits are BIT-IDENTICAL to the serial arm — same kernels, same per-token event order; only the host-side wait moves (scheduling change, never math). The pipelined replay arm of ppn-gate proves it per step.

NOT produced here (both are trunk COPIES — no math feeding the logits changes): h_seed and the MEMRA_DUMP_HN diagnostic tap. The serving loop decides their deferred form when it adopts this API.

The caller advances the token stream, so cache.pos advances at ENQUEUE (host state; device work is event-ordered regardless).

Source

pub fn decode_step_lockstep( &self, e: &Engine, tokens: &[u32], caches: &mut [Cache], ) -> Result<Vec<Vec<f32>>, Box<dyn Error>>

LOCKSTEP MULTI-STREAM decode (lane-3 M1): m independent streams advance one token each through a single per-layer walk. Per-stream math is identical to decode_step_h (same fusion chain, same mixer and FFN calls against that stream’s own Cache), so each stream’s token sequence is bit-identical to its single-stream run. The lockstep order puts the m streams’ layer-il MoE calls adjacent in time, so one stream’s expert-cache fill serves its siblings within the step — the measured cross-stream io amortization (1.12x/1.32x/1.66x at m=2/4/8) lands without batching attention or the CPU ABI.

Source

pub fn decode_step_dc( &self, e: &Engine, token_d: &CudaSlice<u32>, pos_d: &mut CudaSlice<i32>, embd_gpu: &CudaSlice<u8>, embd_qt: i32, embd_row_bytes: usize, cache: &mut Cache, n_vocab: usize, ) -> Result<CudaSlice<u32>, Box<dyn Error>>

DEVICE-COUNTER decode step (CUDA-GRAPH-PLAN Phase 2). A clone of decode_step_h that removes the two per-step VARYING host kernel-args by reading them from device counters:

  1. the KV-append write slot -> per-layer kvl.len_d (device i32[1])
  2. the fa_decode t_kv bound -> the same kvl.len_d after inc_seqlen plus it keeps the token id + rope pos DEVICE-RESIDENT (embed_gather_device, device rope pos, argmax_token_device). NO graph capture yet — runs the kernels eagerly through the counter path. Must be BIT-IDENTICAL to decode_step_h’s token stream (the gate).

Args: token_d = resident device token id [1] (this step’s input token); pos_d = resident device rope pos i32[1] (== cache.pos at entry; INCREMENTED in-path); embd_gpu = resident embed table; (qt,row_bytes) from EmbedHost::qt_and_row_bytes. Returns the NEXT token id device buffer. cache.pos and each kvl.len/kvl.len_d are advanced to match decode_step_h.

Source

pub fn decode_step_dc_cap( &self, e: &Engine, token_d: &mut CudaSlice<u32>, pos_d: &mut CudaSlice<i32>, embd_gpu: &CudaSlice<u8>, embd_qt: i32, embd_row_bytes: usize, cache: &mut Cache, n_vocab: usize, bucket_max: usize, ) -> Result<(), Box<dyn Error>>

CAPTURE body for CUDA-graph replay (CUDA-GRAPH-PLAN Phase 3). One full decode step enqueued entirely on e.stream() with ZERO host sync and ZERO per-step varying host kernel-args:

  • embed reads the PERSISTENT device token_d (last step’s argmax), writes scratch x.
  • full-attn layers size n_splits from bucket_max (fixed for this capture); the kernel reads the ACTUAL t_kv from the device counter kvl.len_d. KV append + device-counter inc happen in-graph. The host kvl.len/cache.pos are NOT advanced here (the driver advances the host mirrors once per replay; only the DEVICE counters advance inside the graph).
  • linear-attn layers use the persistent-state variant (copy-back, stable pointers).
  • lm_head -> parallel 2-pass argmax (argmax_partial_f32+argmax_final_f32) writes the next id into the PERSISTENT token_d.
  • inc_seqlen(pos_d) advances the rope-pos device counter in-graph. Captured ONCE per bucket_max; replayed for every t_kv in that bucket. Bit-identical to eager when bucket_max reproduces eager’s n_splits for the replayed t_kv (the bucket-key contract).
Source

pub fn decode_step_dc_cap_masked( &self, e: &Engine, token_d: &mut CudaSlice<u32>, pos_d: &mut CudaSlice<i32>, embd_gpu: &CudaSlice<u8>, embd_qt: i32, embd_row_bytes: usize, cache: &mut Cache, n_vocab: usize, bucket_max: usize, mask: Option<(&CudaSlice<u32>, usize)>, ) -> Result<(), Box<dyn Error>>

decode_step_dc_cap + GRAMMAR MASK (constrained decoding): with mask = Some((buf, words)), mask_logits_f32 bans the packed bitset’s unset ids IN the captured graph — a stable-pointer read between lm_head and the in-graph argmax (the KV-pointer pattern: contents change per step, address is baked). None is bit-for-bit the unmasked capture.

Source

pub fn generate_graph( &self, e: &Engine, gs: &mut GraphDecodeState, prompt: &[u32], max_new: usize, ) -> Result<Vec<u32>, Box<dyn Error>>

CUDA-GRAPH decode driver (CUDA-GRAPH-PLAN Phase 3). Primes the prompt EAGERLY (device-counter decode_step_dc, advancing host + device counters together), then generates max_new tokens by CUDA-graph REPLAY: per step it picks the t_kv bucket key, captures a graph on first sight of that key (re-using the SAME persistent counters/cache so replays continue the sequence), and replays. The argmax-written next token stays device-resident in gs.token_d; we read back only the [1] u32 after each launch (the gate compares it; a real server can defer this). Returns the generated token ids. Greedy. Bit-identical to eager decode_step (the gate).

CAPTURE STATE HYGIENE: capture_graph runs the step body 3x (2 warmup + 1 capture), each of which mutates the device KV/conv/ssm/counter state. We SNAPSHOT the cache + device counters + token id before capturing and RESTORE them after, so the 3 throwaway runs leave zero residue and replay resumes from the true pre-capture state.

Source

pub fn graph_session_new( &self, e: &Engine, prompt: &[u32], max_new: usize, ) -> Result<(GraphSession, u32), Box<dyn Error>>

Step-wise CUDA-graph decode session (ARCHITECTURE-H100.md graph-serving lane, 2026-07-26): generate_graph’s prime+capture lifted into a long-lived session so a SERVING scheduler can replay ONE step per tick instead of blocking a whole generation. Serving policy (measured): graphs win only at B=1 (214 solo vs 425 aggregate batched-eager at B=4) — this is the single-interactive-session path. Capture discipline is generate_graph’s verbatim: event tracking must be OFF for every buffer the graph references (new() toggles it), capture at bucket_max = pos + max_new + 1, fa geometry retuned per step (fa_apply, FP lockstep with eager).

Source

pub fn graph_session_from_cache( &self, e: &Engine, cache: Cache, first_token: u32, max_new: usize, ) -> Result<(GraphSession, u32), Box<dyn Error>>

GraphSession over an ALREADY-PRIMED cache (round 35): keeps the chunked-prefill TTFT. graph_session_new’s token-wise re-prime made solo long-prompt promotion a net ~3x END-TO-END LOSS (measured live: 871-tok prompt + 400 gen = 6.4s vs ~2.2s eager). Device counters sync from host state; capture recipe unchanged. Requires event tracking OFF (engine default; MEMRA_EVT=1 callers must not use this — the primed cache’s buffers would carry events, illegal inside capture).

Source

pub fn graph_session_from_cache_masked( &self, e: &Engine, cache: Cache, first_token: u32, max_new: usize, mask_init: Option<&[u32]>, ) -> Result<(GraphSession, u32), Box<dyn Error>>

graph_session_from_cache + GRAMMAR MASK (constrained decoding, 2026-08-03): mask_init = Some(packed bitset) allocates the session’s stable mask buffer (tracking is OFF here — capture-legal), seeds it with the FIRST step’s mask, and captures mask_logits_f32 into the graphed step. The caller re-uploads contents per step via GraphSession::upload_mask — same stable-pointer discipline as the KV len_d counters. None = the unmasked session, byte-identical.

Source

pub fn graph_session_recapture_pub( &self, e: &Engine, sess: &mut GraphSession, ) -> Result<(), Box<dyn Error>>

Measurement door for graph_session_recapture (graph-allocfree-probe): the capture path timed WITHOUT the prompt prime. Same call the live step() makes at a kernel-class crossing.

Source

pub fn generate( &self, e: &Engine, prompt: &[u32], max_new: usize, ) -> Result<Vec<u32>, Box<dyn Error>>

Greedy generation: prime with prompt tokens (decode them in sequence to build state), then generate max_new tokens. Returns the generated token ids. (Back-compat: greedy, no EOS/stop — used by the decode==prefill validation gate. New code uses generate_with.)

Source

pub fn generate_with<F: FnMut(u32) -> bool>( &self, e: &Engine, prompt: &[u32], params: &GenParams, sampler: &mut Sampler, on_token: F, ) -> Result<GenOutput, Box<dyn Error>>

The reusable serving generation API (BASE-3). Primes the prompt, then samples up to params.max_new tokens, stopping on EOS, any stop-token, or the context-length guard. Calls on_token(id) after each emitted token (for streaming; return false to stop early). Returns GenOutput { tokens, stop_reason }. Does NOT detokenize — the caller (which owns the tokenizer) handles text + stop-STRING matching on the detokenized tail.

Source

pub fn linear_attn_decode( &self, e: &Engine, la: &LinearAttnLayer, h: &CudaSlice<f32>, cache: &mut Cache, il: usize, ) -> Result<CudaSlice<f32>, Box<dyn Error>>

Linear-attention decode: conv with ring-buffer state, GDN scan carrying SSM state.

Source

pub fn linear_attn_decode_pre( &self, e: &Engine, la: &LinearAttnLayer, h: &CudaSlice<f32>, hq: &CudaSlice<i8>, hd: &CudaSlice<f32>, cache: &mut Cache, il: usize, persistent: bool, ) -> Result<CudaSlice<f32>, Box<dyn Error>>

PRE-QUANTIZED-INPUT variant (DECODE attn-input NORM-FUSION lever): the caller passes the post-attn-norm activation ALREADY q8_1-quantized (hq,hd) (produced by rms_norm_q8_1, fusing the attn_norm + the mixer’s internal quantize_q8_1). Skips the internal quantize. Caller GUARANTEES the projections are q8_1-fast. persistent selects the capture-safe state plumbing. BIT-IDENTICAL to linear_attn_decode(h) when (hq,hd)==quantize_q8_1(rms_norm(x)*w).

Source§

impl HybridModel

Source

pub fn decode_batch_cap() -> usize

Batched-decode width cap. 8 = the exactness-tier default (see the assert below); MEMRA_DECODE_BATCH_CAP overrides for tier-probe measurement, clamped to 32.

Source

pub fn decode_batch_exact16_ok(&self) -> bool

EXACT-16 TIER admission (increment 3a, 2026-08-01, 5090 receipts research/batched-tick-inc3-20260801): true iff EVERY matmul the batched decode step runs has a per-(token,row) bit-exact kernel class at m=9..16 under the verify_exact scope — i.e. the batched-mmvq b16 family (32-thread warp reduce, the exact m=1 mmvq program per column) or the e4m3 grid.y=m mmvq catch-all. Q8_0 qualifies only with the split-plane mirror (rp4, MEMRA_Q8RP): its b16 kernel exists only as the _rp twin. Float matmuls (cuBLASLt, n-dependent reductions) and MoE FFNs disqualify the model. Measured attribution for WHY the naked m=16 tier is not exact: the m>=16 arms (MMQ int8-MMA mul_mat_q — MEMRA_PP_Q8MMQ default-on — and qmatvec_gemm, both block-scale f32) and the m=9..15 dp4a tail (128-thread two-level reduce) all break per-row bit-identity vs isolated decode (gate2 step-0 bit-diffs, maxdiff ~1.3-2.3e-1).

Source

pub fn b1_fast_on() -> bool

H3 rollback/A-B seam (serve-path phase 2): MEMRA_SERVE_B1FAST=0 sends B=1 back through the batched body (the pre-change tick, bit-for-bit). Default ON.

EXACTNESS, stated precisely (measured on-box 2026-08-05, sm_120 q9 NVFP4-MTP): the fast path is BIT-IDENTICAL TO decode_step_h — decode-batch-gate’s STRICT gate1 (--mode strict) PASSes with it ON and FAILs with it OFF at maxdiff 1.591e-1. It is deliberately NOT bit-identical to the batched body: the two carry the long-accepted decode-config FP-composition gap (same class gate1’s config mode tolerates), and this lever moves solo sessions onto the NAKED side of it. That is the desired direction — a c=1 serve request now computes exactly what run-gen computes for the same prompt. Token-stream receipts: research/servepath-p2-20260805 (greedy 150 ids + seeded-sampled identical to the run-gen oracle AND cross-arm, so the gap is sub-token here as designed).

Read fresh (an AtomicU8 memo, not a OnceLock): decode-batch-gate flips this seam BETWEEN gates in-process — gate1 needs the fast path ON to prove bit-identity, gate2 needs it pinned OFF to keep testing the batched body. A latch-once read would bake whichever gate ran first, so the gate could never test both sides. The memo caches the parse but set_b1_fast invalidates it.

Source

pub fn set_b1_fast(on: bool)

Test/gate seam: force the B=1 fast path on or off for the rest of the process, overriding the env. Used by decode-batch-gate to pin gate2’s reference arm.

Source

pub fn decode_step_batch( &self, e: &Engine, tokens: &[u32], caches: &mut [&mut Cache], ) -> Result<Vec<Vec<f32>>, Box<dyn Error>>

One batched greedy-decode step over B independent sequences. tokens[b] is sequence b’s input token; caches[b] its private cache (position, quantized KV, GDN/conv state). Returns the B logits rows (host, [n_vocab] each). Each cache’s pos/len advance exactly as decode_step_h would.

Source

pub fn decode_step_batch_sampled( &self, e: &Engine, tokens: &[u32], caches: &mut [&mut Cache], samp: &[Option<(f32, u64, u32)>], ) -> Result<(Vec<Vec<f32>>, Vec<Option<u32>>), Box<dyn Error>>

decode_step_batch + DEVICE-SIDE SAMPLING for eligible rows (the batched-tick lever, 2026-08-01): the host sampler’s temp-path is O(n_vocab) with a full-vocab exp per row (measured 1.36 ms/row at the 9B’s 248320 vocab = 10.9 ms/tick at B=8 — the single largest component of the serving tick). Here each requested row samples ON DEVICE between the lm_head matmul and the logits D2H: temp <= 0 (greedy): the 2-pass device argmax — bit-identical to host argmax (argmax-gate contract, same kernels as the dc serving path). temp > 0: gumbel_perturb(seed, ctr, temp) + the same argmax = ONE categorical draw from softmax(logits/temp) — the sampled-spec Philox machinery. Deterministic per (seed, ctr) and INDEPENDENT of batch composition (the isolation contract; decode-batch-gate gate3). NOTE: the draw stream differs from the host sampler’s SplitMix64 (distribution-equal, seed-deterministic, NOT byte-equal to the old host draws) — greedy rows are unchanged bit-exact. samp[bi] = Some((temp, seed, ctr)) requests a device sample for row bi; the full logits rows are still returned (worker keeps last_logits semantics + fallback rows).

Source

pub fn decode_step_batch_sampled_lean( &self, e: &Engine, tokens: &[u32], caches: &mut [&mut Cache], samp: &[Option<(f32, u64, u32)>], lean: bool, ) -> Result<(Vec<Vec<f32>>, Vec<Option<u32>>), Box<dyn Error>>

decode_step_batch_sampled + LEAN LOGITS (increment 2 component 3, 2026-08-01): with lean, device-sampled rows SKIP the [n_vocab] logits D2H (9.4%/32.5% of the pre-/post-inc2 tick profile) — their returned row is EMPTY. The audit-mapped consumers: (a) the next tick’s host sample — never fires, device_next carries the token; (b) the graph-promotion argmax — reads only prefill logits (generated empty); (c) the KV-reuse pool park at retire — the REAL consumer, served by a per-cache device park: the row is dtod-copied into cache.last_logits_dev (device bandwidth) and D2H’d ONCE at retire by the worker. Rows without a device sample keep a per-row D2H. lean=false is bit-for-bit the previous method (gates + non-serving callers).

Source

pub fn decode_step_batch_sampled_lean_masked( &self, e: &Engine, tokens: &[u32], caches: &mut [&mut Cache], samp: &[Option<(f32, u64, u32)>], masks: &[Option<(&CudaSlice<u32>, usize)>], lean: bool, ) -> Result<(Vec<Vec<f32>>, Vec<Option<u32>>), Box<dyn Error>>

decode_step_batch_sampled_lean + GRAMMAR MASKS (constrained decoding, 2026-08-03): masks[bi] = Some((packed_bitset, words)) bans every unset-bit vocab id on row bi (mask_logits_f32, -FLT_MAX) BETWEEN the lm_head matmul and the device sampler, so a constrained row rides the SAME device-sample/lean-logits tick as everyone else — no full-row D2H, no host O(n_vocab) sample. Contract: a masked row must also request a device sample. The row’s PRISTINE logits are preserved for their consumers before the in-place ban: lean rows park the unmasked row into cache.last_logits_dev (the retire-time reuse-pool park stays unmasked — continuations resume grammar-free, the v1 host-path contract), non-lean rows D2H the unmasked row. masks = &[] is bit-for-bit the unmasked method.

Source§

impl HybridModel

Source

pub fn decode_step_t( &self, e: &Engine, tokens: &[u32], pos0: usize, cache: &mut Cache, ) -> Result<Vec<f32>, Box<dyn Error>>

Batched target verify forward over tokens at positions pos0..pos0+T (§D.3, T=K+1). Returns ALL T logit columns (host f32, [T*n_vocab]); appends T cols to every full-attn KV and advances every linear-attn recur state by T steps (the recur steps are SEQUENTIAL T=1). Advances cache.pos by T.

Source

pub fn decode_step_t_h( &self, e: &Engine, tokens: &[u32], pos0: usize, cache: &mut Cache, ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn Error>>

Like decode_step_t but ALSO returns the LAST column’s pre-output_norm hidden (h_seed for the next draft round). This lets partial-accept replay run as ONE batched T=(n_acc+1) forward (single weight read) instead of n_acc+1 separate T=1 decode_steps (n_acc+1 weight reads). At batch=1 decode is bandwidth-bound, so batching the replay is THE MTP profitability lever.

Source

pub fn decode_step_t_h_emb( &self, e: &Engine, tokens: &[u32], pos0: usize, cache: &mut Cache, embd_dev: Option<(&CudaSlice<u8>, i32, usize)>, ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn Error>>

Like decode_step_t_h with an optional RESIDENT embed table (spec hot loop): device gather instead of host dequant + [T, n_embd] f32 htod. Bit-identical rows.

Source

pub fn decode_step_t_h_emb_dev( &self, e: &Engine, tokens: &[u32], pos0: usize, cache: &mut Cache, embd_dev: Option<(&CudaSlice<u8>, i32, usize)>, ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn Error>>

DEVICE-LOGITS verify forward (spec device-argmax lever): identical kernel chain to decode_step_t_h_emb but returns the [T, n_vocab] logits ON DEVICE — the accept walk argmaxes each column on-device and reads back ONE [T] u32 instead of dtoh’ing the full T x n_vocab f32 block (~1-4 MB + T host argmaxes, every round). Kernel dispatch is UNCHANGED (same decode-exact kernels); only the post-logits transfer moves.

Source

pub fn decode_step_t_aux2( &self, e: &Engine, tokens: &[u32], pos0: usize, cache: &mut Cache, aux_layers: &[usize], pred_col: Option<usize>, ) -> Result<(Vec<f32>, Vec<CudaSlice<f32>>, Option<Vec<CudaSlice<f32>>>), Box<dyn Error>>

EAGLE3 aux-capturing verify forward over tokens (T) — mirrors decode_step_t_h exactly (same KV append, same causal verify, same recur advance) but ALSO clones the aux residual- stream hiddens (blocks in aux_layers) for TWO columns: the LAST column (always) and the optional pred_col (the EAGLE seed = bonus’s predecessor). Returns (all_T_logits host, last_col_aux, pred_col_aux?). Used by the EAGLE3 orchestrator’s commit.

Source

pub fn new_session( &self, e: &Engine, max_ctx: usize, ) -> Result<SpecSession, Box<dyn Error>>

Greedy MTP speculative decode (§B). Token-identical to generate(prompt, max_new) but uses the NextN head to draft K tokens then verifies them in one batched target forward. Returns (generated tokens, total_drafted, total_accepted) so the caller can report acceptance rate. k = draft length per round.

GRAPH DRAFT (stage 2 of graph-grade spec): when the model is all-Dense and the MTP head is Dense (no MoE host readbacks), the fixed-shape T=1 MTP forward is CUDA-graph-captured ONCE and replayed per draft step — the ~40 eager launches per drafted token collapse into one graph dispatch; only the 4-byte token id (and 4-byte p-min confidence) round-trip per step. Event tracking is disabled for the whole call (generate_graph pattern) so every buffer the captured graph references is event-free; the spec loop is strictly single-stream. MEMRA_SPEC_NOGRAPH=1 forces the eager draft chain. SAMPLED mode (MEMRA_SPEC_TEMP>0) has its OWN capture (gumbel-perturbed in-graph argmax, device Philox event counter, persistent q retention) — graph-vs-eager sampled streams are bit-identical for the same (seed, prompt, K, temp); see the sampled-graph setup in generate_spec_inner2. Multi-turn session: trunk cache + MTP draft scratch persist across generate calls, so turn N+1 primes ONLY its new suffix (the 124k-conversation daily pattern — re-priming a 32k history costs ~54s; a suffix prime costs seconds). APPEND-ONLY by construction: the hybrid linear-attn states are in-place (no position index), so a session can extend but never rewind — committed is the exact token list whose state the caches hold (includes any overshoot tokens past max_new; the caller renders from committed, not its own echo).

Source

pub fn spec_rewind_to_checkpoint( &self, e: &Engine, sess: &mut SpecSession, ) -> Result<Option<usize>, Box<dyn Error>>

SESSION-AFFINITY REWIND (lane/session-affinity, 2026-08-05): roll sess back to its retained prompt-end checkpoint, so a request whose prompt matches committed[..rewind_pos()] exactly can resume there and prime only its own delta.

EXACTNESS. After this returns, the session is byte-for-byte the state it was in AT that boundary: full-attn KV truncated to it (append-only, position-addressed), GDN conv/ssm restored from the device copy taken there, draft scratch length reset, committed truncated, last_h = the boundary’s predecessor anchor. That is precisely the state a fresh prime of committed[..pos] would have produced, so the following suffix prime and every burst after it are identical to a cold run of the same token stream — the committed-tokens-authoritative contract.

next_pred and pending_tok are CLEARED: both describe generation past the boundary, which the rewind discards. The caller therefore must supply a non-empty suffix (a rewound session cannot serve an empty-suffix continuation burst — there is nothing to continue). The persistent draft graph survives: it bakes only session-stable pointers (the scratch KV, the resident embedding), none of which the rewind moves.

The checkpoint is CONSUMED (turn_ckpt taken): its snapshot buffers are freed here, and this turn’s own prime installs a fresh one at the new prompt end. Returns the position rewound to, or None when the session holds no checkpoint (caller: full re-prime).

Source

pub fn spec_flush_pending( &self, e: &Engine, sess: &mut SpecSession, ) -> Result<(), Box<dyn Error>>

Commit a carried pending bonus (see SpecSession::pending_tok): one T=1 trunk pass (its logits’ argmax becomes next_pred) + the draft-KV fill at the carried anchor — byte-identical to the pre-carry session tail. Required before a non-empty-suffix prime, a sampled turn, or parking a session for pool reuse. No-op without a pending.

Source

pub fn generate_spec_session( &self, e: &Engine, sess: &mut SpecSession, suffix: &[u32], max_new: usize, k: usize, ) -> Result<(Vec<u32>, usize, usize), Box<dyn Error>>

One spec-decode turn on a live session. suffix = the NEW tokens only (turn N+1’s user message rendered through the chat template continuation). Returns (new tokens emitted, drafted, accepted); session.committed grows by suffix + emitted.

Source

pub fn generate_spec_session_sampled( &self, e: &Engine, sess: &mut SpecSession, suffix: &[u32], max_new: usize, k: usize, sampling: Option<SpecSampling>, on_commit: Option<&mut dyn FnMut(&[u32]) -> bool>, ) -> Result<(Vec<u32>, usize, usize), Box<dyn Error>>

Serve-path sampled spec: routes the burst through the rejection-sampling verify with per-SESSION Philox continuity (sess.sctr/uctr). None = env-driven (CLI) or greedy. Filters (top-k/p/min-p) apply SYMMETRICALLY to draft q and verify p — distribution-exact for the filtered target (feat/filtered-spec).

on_commit (sse-cadence, 2026-08-05): called with each newly-emitted slice of the output — once right after the prime’s first token, then once per round commit — so a streaming caller can flush text at round cadence instead of once per burst. The slices are disjoint, in order, and concatenate to exactly the returned token vec. Emission- timing only: token bytes, session state, and exactness are untouched.

The returned bool is a CONTINUE-VERDICT (admission yield, 2026-08-06): false ends the burst at the current round boundary, exactly as if max_new had been reached — the caller’s scheduler regains control without waiting the burst out. Burst size is content-neutral (spec-levers battery), so an early exit moves WHEN the burst returns, never what tokens say. The slice may be EMPTY (a poll-only boundary — round-stream drains and the defensive tail flush can land with nothing new committed).

Source

pub fn generate_spec_session_constrained( &self, e: &Engine, sess: &mut SpecSession, suffix: &[u32], max_new: usize, k: usize, sampling: Option<SpecSampling>, constraint: Option<&mut dyn SpecConstraint>, on_commit: Option<&mut dyn FnMut(&[u32]) -> bool>, ) -> Result<(Vec<u32>, usize, usize), Box<dyn Error>>

generate_spec_session_sampled + GRAMMAR (constrained decoding, 2026-08-03): the hook truncates acceptance at the first grammar-illegal token AFTER the exactness verify (grammar is an extra rejection rule, ordering like the batched-verify twins) and replaces an illegal bonus with the MASKED argmax of the target’s own verify column — token-identical to constrained plain greedy decode. GREEDY only (the worker routes sampled constrained to plain decode). Acceptance under tight grammars may drop (drafter is unconstrained); that is measured, not hidden.

Source

pub fn generate_spec( &self, e: &Engine, prompt: &[u32], max_new: usize, k: usize, ) -> Result<(Vec<u32>, usize, usize), Box<dyn Error>>

Source

pub fn replay_acceptance( &self, e: &Engine, tokens: &[u32], k: usize, stride: usize, chunk: usize, hdump: Option<&mut File>, ) -> Result<(Vec<(usize, Vec<u32>, Vec<u32>)>, Vec<u32>), Box<dyn Error>>

TEACHER-FORCED REPLAY ACCEPTANCE (hqmtp MTP-heal protocol): walk a FIXED token sequence and, at sampled positions, compare the MTP head’s K-token draft chain against the trunk’s own teacher-forced greedy predictions. Nothing is generated — the context is the corpus text itself, so (a) degenerate self-generated loops cannot inflate acceptance and (b) two arms (bf16 ceiling vs NVFP4) score on IDENTICAL contexts, isolating the quant-induced head/hidden-state mismatch from text drift.

Per eval position p (context = tokens[0..=p], predecessor pairing as in spec decode): draft_j = chain token j from (tokens[p], h_{p-1}), then its own drafts — the exact eager spec-decode chain (same mtp_head_forward_dev, same rope positions). target_j = teacher-forced greedy pick for position p+1+j (argmax of the trunk logits at forced context tokens[0..p+j]). For j==0 this equals live spec acceptance; for j>=1 live verify would condition on the drafts, here it conditions on the corpus — deterministic and arm-comparable by design.

Returns (rows, bg): one (p, drafts[k], targets[k]) row per eval position (ascending p), plus the full teacher-forced greedy track bg (bg[i] = greedy pick for position i, i>=1) so harnesses can cross-check runs (e.g. different chunk sizes must give identical bg).

hdump: when Some, every position’s pre-output_norm trunk hidden (the exact rows the draft-KV fill pairs from) streams to the file as little-endian f32 [t_total, n_embd] — the head-distillation extraction (hqmtp): the ENGINE is the source of truth for trunk hiddens (HF torch reproductions of the hybrid trunk measured only ~0.5 greedy agreement vs this path — not usable as a training-data source).

Source§

impl HybridModel

Source

pub fn gemma4_draft_step( &self, e: &Engine, d: &GemmaDraft, token: u32, h: &CudaSlice<f32>, pos: usize, cache: &Cache, ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn Error>>

One drafter step: (token, h[2816 device]) at absolute position pos over the FROZEN main cache. Returns (draft logits host [n_vocab], h_next [2816 device]).

Source

pub fn gemma4_draft_step_greedy( &self, e: &Engine, d: &GemmaDraft, token: u32, h: &CudaSlice<f32>, pos: usize, cache: &Cache, ) -> Result<(u32, CudaSlice<f32>), Box<dyn Error>>

Greedy draft step: like gemma4_draft_step but the token argmax stays on device — host sees 4 bytes (no 1MB logits dtoh per draft). Returns (token, h_next).

Source§

impl HybridModel

Source

pub fn generate_spec_gemma( &self, e: &Engine, d: &mut GemmaDraft, prompt: &[u32], max_new: usize, k: usize, eos: &[u32], ) -> Result<Vec<u32>, Box<dyn Error>>

gemma4 MTP greedy spec loop: prime the prompt, then rounds of (chained K-token draft over the frozen main cache) + (ONE batched verify) + longest-prefix accept + KV rollback. Returns generated tokens; prints acceptance stats.

Source§

impl HybridModel

Source

pub fn gemma4_generate_plain_graph( &self, e: &Engine, cache: &mut Cache, last: u32, max_new: usize, eos: &[u32], ) -> Result<Vec<u32>, Box<dyn Error>>

PLAIN-DECODE CUDA-GRAPH loop (gemma4, greedy): one captured verify-trunk step (t=1, device tokens/pos/lens) replayed per token — the launch-gap eraser the decode decomposition demanded (2026-07-23: ~2.3ms/token idle at 128 launches). Self-feeding: argmax -> tok_d -> next embed; counters advance in-graph via spec_rollback_stream(base=1, acc=0). Tokens land in a device ring; ONE host sync per drain window. Captures are keyed on the (rung, window-side, f512-side) regime (the round-graph hint law); regime-crossing stretches run the same body eagerly. Caller guarantees: gemma4, greedy, shared_kv_layers == 0, prompt already primed (cache.pos = prompt len, host kvl.len mirrors set).

Source§

impl HybridModel

Source

pub fn generate_spec_dflash( &self, e: &Engine, draft: &DflashDraft, prompt: &[u32], max_new: usize, eos: &[u32], ) -> Result<Vec<u32>, Box<dyn Error>>

Source§

impl HybridModel

Source

pub fn generate_spec_eagle( &self, e: &Engine, draft: &Eagle3Draft, prompt: &[u32], max_new: usize, k: usize, ) -> Result<(Vec<u32>, usize, usize), Box<dyn Error>>

Greedy EAGLE3 speculative decode (EAGLE-PLAN N6). Token-identical to generate(prompt,n) but drafts K tokens with the separate EAGLE3 draft, then verifies them in ONE batched target forward. Verify/accept/snapshot/rollback are REUSED from the MTP path (decode_step_t, cache.snapshot/rollback). Returns (tokens, total_drafted, total_accepted).

Source§

impl HybridModel

Source

pub fn prime_graph_new( &self, e: &Engine, bucket: usize, ) -> Result<PrimeGraph, Box<dyn Error>>

Capture the fresh-prime graph for bucket tokens (13-15ms measured). Manual staged capture — capture_graph_retained’s keeper path trips on the prime (smoke finding 4).

Source

pub fn prime_graph_run( &self, e: &Engine, pg: &mut PrimeGraph, tokens: &[u32], session: &mut Cache, ) -> Result<(Vec<f32>, CudaSlice<f32>), Box<dyn Error>>

Replay the graph for tokens (len <= bucket) and copy the outputs into session (a FRESH cache: pos == 0). Returns host logits (the prefill_tick contract).

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.

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.