Last updated: 2026-07-19
Inference Runtime
Forward Cuda
CUDA forward pass for the dense `qwen35` hybrid-SSM model (Qwen 3.5 9B).
M2 bring-up — incremental greedy decode of a single token on NVIDIA. The CUDA backend modules (device/buffer/pipeline/command) and the kernel library (`src/shaders/cuda/kernels.cu`, NVRTC-compiled) are orchestrated here into a real forward pass. Weights upload VERBATIM-quantized (Q4_K/Q5_K/Q6_K/Q8_0 blocks) via loader_cuda and are dequantized inside the DMMV kernels.
Layer schedule (qwen35, full_attention_interval=4): layer L is full attention when ((L+1) % 4 == 0) → L in {3,7,11,15,19,23,27,31}; the other 24 layers are gated-delta-net SSM. Every layer is followed by a SwiGLU FFN block.
1 exports shown
struct
ForwardCuda
pub const ForwardCuda = struct Per-token GPU forward state for qwen35 greedy decode.
Methods
20method
ForwardCuda.init
pub fn init(allocator: std.mem.Allocator, model: *loader.Model, max_ctx: u32) !ForwardCuda Compile kernels, allocate every device buffer, upload inv_freq + sinks, zero the KV cache and SSM state.
method
ForwardCuda.deinit
pub fn deinit(self: *ForwardCuda) void No public doc comment yet.
method
ForwardCuda.allocSlotState
pub fn allocSlotState(self: *ForwardCuda, n_slots: u32, slot_ctx: u32) !void Allocate per-sequence slot state for `n_slots` concurrent sequences, each with up to `slot_ctx` positions.
Attn layers get slot KV; ssm layers get per-slot conv + recurrent state (zero-initialised). Additive — production single-sequence buffers are untouched. Re-callable (frees first).
method
ForwardCuda.freeSlotState
pub fn freeSlotState(self: *ForwardCuda) void No public doc comment yet.
method
ForwardCuda.slotKvOffsetBytes
pub fn slotKvOffsetBytes(self: *const ForwardCuda, slot: u32, pos: u32) usize Byte offset of slot `slot`'s K/V for position `pos` in attn layer `L`'s slot KV: (slot*slot_ctx + pos)*kv_dim*sizeof(f32).
The future per-seq kv-write + slot attention kernels (4b/4c) will use this exact indexing.
method
ForwardCuda.slotConvOffsetBytes
pub fn slotConvOffsetBytes(self: *const ForwardCuda, slot: u32) usize Byte offset of slot `slot`'s conv ring (ssm layer): slot*conv_state_len*f4.
method
ForwardCuda.slotStateOffsetBytes
pub fn slotStateOffsetBytes(self: *const ForwardCuda, slot: u32) usize Byte offset of slot `slot`'s recurrent state (ssm layer): slot*ssm_state_len*f4.
method
ForwardCuda.resetSlot
pub fn resetSlot(self: *ForwardCuda, slot: u32) !void Effort 28 increment 4 (qwen serving): zero a REUSED slot's SSM conv ring + recurrent state so a new request prefilling from pos=0 into this slot starts from clean state.
The serving engine reuses slots across requests (nslots < concurrent clients) and qwen's SSM state is ACCUMULATED (not position-indexed), so without this a reused slot inherits the prior request's recurrent state → corruption. The attention slot KV needs NO reset (it is position-indexed and overwritten on prefill; reads never reach beyond the current sequence's positions — same reason gemma's slot KV reuses cleanly). No-op if slot state is unallocated. Call on ADMIT, before per-token prefill.
method
ForwardCuda.slotStateSmoke
pub fn slotStateSmoke(self: *ForwardCuda) !bool Sub-step 4a plumbing smoke: prove the slot-offset arithmetic round-trips and that distinct slots map to NON-overlapping device regions for BOTH the attention KV and the SSM conv + recurrent state.
Requires allocSlotState with n_slots >= 2 (so slot 0 and slot n_slots-1 differ). Returns true on success.
method
ForwardCuda.resetState
pub fn resetState(self: *ForwardCuda) !void Reset the production single-sequence recurrent state to a fresh-process start: zero every ssm layer's conv ring + recurrent state and clear the per-layer conv offset.
The attention KV cache needs no reset — it is position-indexed and each sequence overwrites from pos 0, reading only what it wrote. Used by the batch harness to make the per-sequence SERIAL reference truly single-sequence (without this, the unindexed SSM recurrent state leaks from one reference sequence into the next). Additive — never called on the real decode/prefill path; that path runs one sequence/process.
method
ForwardCuda.decodeStep
pub fn decodeStep(self: *ForwardCuda, token: u32, pos: u32, run_layers: bool) !u32 Run one greedy decode step for `token` at sequence position `pos`, returning the argmax token id.
v0: embed → final rms_norm → LM head → argmax (layers are gated by `run_layers`).
method
ForwardCuda.prefillStep
pub fn prefillStep(self: *ForwardCuda, token: u32, pos: u32) !void Prefill helper: run every layer (build the KV cache) but SKIP the tail rms_norm + LM head + argmax.
A prompt-internal token's logits are never used — only the final prompt token's argmax seeds generation — so the (vocab x n_embd) head matvec is pure waste on T-1 of the T prompt tokens. It is a large share for the MoE models, whose active per-token forward is small next to the full-vocab head. The async layer ops are drained here (waitPending) so the ring is freed each token; bit-identical generation.
method
ForwardCuda.prefillBatched
pub fn prefillBatched(self: *ForwardCuda, tokens: []const u32) !u32 No public doc comment yet.
method
ForwardCuda.prefillBatchedSlot
pub fn prefillBatchedSlot(self: *ForwardCuda, tokens: []const u32, slot: u32) !u32 Batched prefill that writes KV cache + SSM state into the given slot's per-sequence region (instead of the single-seq scratch).
Used by the serve engine so server-mode prefill gets the 5× batched-GEMM speedup instead of falling back to per-token decodeBatch (B=1).
method
ForwardCuda.decodeBatch
pub fn decodeBatch(self: *ForwardCuda, tokens: []const u32, positions: []const u32, slots: []const u32, out: []u32) !void No public doc comment yet.
method
ForwardCuda.attentionLayerPub
pub fn attentionLayerPub(self: *ForwardCuda, L: u32, pos: u32) !void No public doc comment yet.
method
ForwardCuda.ssmLayerPub
pub fn ssmLayerPub(self: *ForwardCuda, L: u32) !void No public doc comment yet.
method
ForwardCuda.ffnBlockPub
pub fn ffnBlockPub(self: *ForwardCuda, L: u32) !void No public doc comment yet.
method
ForwardCuda.readLogits
pub fn readLogits(self: *ForwardCuda, out: []f32) void Download the top of the logits buffer (for top-k reporting).