spark_model/
layer.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Composable transformer layer traits (SDD).
4//!
5//! Decouples the generic model loop (embed -> layers -> norm -> lm_head)
6//! from layer-specific logic (attention vs SSM, MoE vs dense FFN).
7//! Adding a new architecture only requires implementing [`TransformerLayer`]
8//! for each layer type, not duplicating the model loop.
9
10use std::any::Any;
11
12use atlas_core::config::ModelConfig;
13use spark_runtime::buffers::BufferArena;
14use spark_runtime::gpu::{DevicePtr, GpuBackend};
15
16mod transformer_layer;
17pub use transformer_layer::{
18    TransformerLayer, VERIFY_WY_LAYER_STRIDE_BYTES, VERIFY_WY_TABLE_SEQS,
19    VERIFY_WY_TABLE_STRIDE_BYTES, VERIFY_WY_TABLES_PER_LAYER,
20};
21
22/// Per-layer persistent state tracked across decode steps.
23///
24/// Attention layers use [`EmptyLayerState`] (KV lives in `PagedKvCache`).
25/// SSM layers use [`SsmLayerState`] (recurrent h_state + conv_state).
26/// Custom layers can implement this trait for arbitrary state.
27pub trait LayerState: Send + Sync {
28    fn as_any(&self) -> &dyn Any;
29    fn as_any_mut(&mut self) -> &mut dyn Any;
30}
31
32/// Empty state for layers that store all persistent state externally
33/// (e.g., attention layers where KV is in `PagedKvCache`).
34pub struct EmptyLayerState;
35
36/// Attention-layer per-sequence state. KV lives in `PagedKvCache`; the only
37/// resident piece is the QSA indexer carry on the 12 qwen4_exp
38/// full-attention layers (Avarok #753 item B).
39#[derive(Default)]
40pub struct AttnLayerState {
41    pub qsa: Option<crate::layers::qsa::QsaSeqState>,
42}
43
44impl LayerState for AttnLayerState {
45    fn as_any(&self) -> &dyn Any {
46        self
47    }
48    fn as_any_mut(&mut self) -> &mut dyn Any {
49        self
50    }
51}
52
53impl LayerState for EmptyLayerState {
54    fn as_any(&self) -> &dyn Any {
55        self
56    }
57    fn as_any_mut(&mut self) -> &mut dyn Any {
58        self
59    }
60}
61
62/// SSM layer state: recurrent hidden state + conv1d sliding window.
63///
64/// Used by Mamba, Gated Delta Net (GDN), and similar recurrent layers.
65pub struct SsmLayerState {
66    /// Recurrent hidden state: [num_v_heads, v_dim, k_dim] in f32.
67    pub h_state: DevicePtr,
68    /// Conv1d sliding window state: [d_inner, d_conv] in f32.
69    pub conv_state: DevicePtr,
70    /// Checkpoint buffer for h_state (allocated lazily for speculative decode).
71    pub h_state_checkpoint: Option<DevicePtr>,
72    /// Checkpoint buffer for conv_state (allocated lazily for speculative decode).
73    pub conv_state_checkpoint: Option<DevicePtr>,
74    /// Intermediate h_state snapshots during batched verification.
75    /// Element i holds h_state after processing verification token i.
76    /// Used by rollback_ssm_states to restore to the correct position.
77    pub h_state_intermediates: Vec<DevicePtr>,
78    /// Intermediate conv_state snapshots during batched verification.
79    pub conv_state_intermediates: Vec<DevicePtr>,
80    /// Storage dtype of `h_state`: `false` = FP32, `true` = FP16
81    /// (`--ssm-h-dtype f16`).
82    ///
83    /// This is the single source of truth for the h-state format. Which edge
84    /// sets it depends on the POOL width:
85    ///
86    /// * FP32-sized pool (stage 1/2, `h_prefill_stage == None`): prefill is
87    ///   the only FP32 writer and writes the slot in place, so the flag
88    ///   starts `false` and the decode mixer
89    ///   (`TransformerModel::ssm_h_to_f16_dispatch`) flips it exactly once
90    ///   per sequence, on the first decode step. No caller has to know where
91    ///   the prefill->decode edge is.
92    /// * f16-SIZED pool (stage 3, `h_prefill_stage == Some`): the slot is
93    ///   physically 2 bytes/element and can NEVER hold FP32, so the flag is
94    ///   `true` from allocation onwards and the decode mixer is a no-op.
95    ///   Prefill's FP32 kernels run over [`Self::h_prefill_stage`] instead.
96    ///
97    /// It rides through swap-out/swap-in because `state_io` mutates these
98    /// states in place rather than rebuilding them.
99    pub h_is_f16: bool,
100    /// Stage-3 f16-SIZED pool ONLY (`--ssm-h-dtype f16-pool`): the FP32
101    /// staging blob for THIS sequence's slot, which the GDN prefill widens
102    /// `h_state` into before its FP32 kernels run and narrows back after.
103    ///
104    /// `None` — every configuration before stage 3 — means "the slot IS
105    /// FP32-wide": prefill writes `h_state` in place exactly as it always
106    /// has, and not one byte moves. The same blob is shared by every layer
107    /// of the sequence (see `SsmStatePool::h_prefill_stage`).
108    pub h_prefill_stage: Option<DevicePtr>,
109    /// PLE per-sequence carry (n-gram history + dilated-conv state), present
110    /// only on the layer that hosts a `PleLayer` (Avarok #753 item B: one per
111    /// in-flight sequence, lazily created on the sequence's first pass).
112    pub ple: Option<crate::layers::ple::PleSeqState>,
113}
114
115impl LayerState for SsmLayerState {
116    fn as_any(&self) -> &dyn Any {
117        self
118    }
119    fn as_any_mut(&mut self) -> &mut dyn Any {
120        self
121    }
122}
123
124/// Pre-uploaded attention metadata device pointers.
125///
126/// Uploaded once per decode step in the model loop, reused across all
127/// 12 attention layers. Eliminates 44 redundant H2D copies per step.
128///
129/// For batched decode (num_seqs > 1), arrays are contiguous:
130/// - positions: `[N]` u32
131/// - slots: `[N]` i64
132/// - seq_lens: `[N]` i32
133/// - block_table: `[N * max_blocks_per_seq]` i32 (row-major)
134#[derive(Clone, Copy)]
135pub struct AttnMetadataDev {
136    /// Position values: `[N]` u32 at this device address. For multi-modal
137    /// MRoPE this is the temporal (T) stream; callers set
138    /// `positions_h`/`positions_w` to distinct buffers only when the token
139    /// stream contains image or video patches.
140    pub positions: DevicePtr,
141    /// Height (H) position stream for MRoPE-interleaved. When identical
142    /// to `positions` (same pointer) the rope reduces to scalar RoPE.
143    /// Default: same as `positions`.
144    pub positions_h: DevicePtr,
145    /// Width (W) position stream for MRoPE-interleaved. Same fallback as
146    /// `positions_h`.
147    pub positions_w: DevicePtr,
148    /// Slot mappings: `[N]` i64 at this device address.
149    pub slot: DevicePtr,
150    /// Sequence lengths (+1): `[N]` i32 at this device address.
151    pub seq_len: DevicePtr,
152    /// Block tables: `[N * max_blocks_per_seq]` i32 at this device address.
153    pub block_table: DevicePtr,
154    /// Number of blocks per sequence row in block_table.
155    pub max_blocks_per_seq: u32,
156    /// Number of sequences in this batch (1 for single-sequence decode).
157    pub num_seqs: u32,
158    /// M2 per-request LoRA routing: `[num_seqs]` i32 at this device address,
159    /// one adapter SLOT index per row (`< 0` = base / no delta; pad rows are
160    /// `-1`). Uploaded each decode step to a stable address (like positions /
161    /// block_table), so the batched bgmv stays inside the captured decode
162    /// graph. `DevicePtr(0)` on every non-routed path (single-seq decode,
163    /// prefill, verify, MLA, MTP) — the bgmv apply sites no-op when it is null.
164    pub seq_slot: DevicePtr,
165    /// SOLID Incr-4 (batched decode MoE fold): `[num_seqs]` i32 per-row adapter
166    /// map for the MoE expert gather-BGMV fold, at this device address. MoE
167    /// semantics (distinct from `seq_slot`): `< 0` = base / no fold (device
168    /// kernel skips the row); `>= 0` = fold the installed active adapter's
169    /// per-expert delta on that row. Built by
170    /// [`crate::lora::build_moe_row_adapter_decode`] and uploaded each decode
171    /// step to a stable address (a dedicated fixed-address buffer,
172    /// `TransformerModel::moe_row_adapter_buf`, alloc'd once at init), so the
173    /// batched fold stays inside the captured decode graph and is route-agnostic across
174    /// replays (base rows no-op individually). `DevicePtr(0)` when no adapter is
175    /// resident and on every non-batched path (the fold hooks then fall back to
176    /// the request-granularity `moe_route_gate`). NOT the `seq_slot` buffer —
177    /// that resolves `-1 → active` (attention defer-to-active), which would fold
178    /// the adapter onto base rows here.
179    pub moe_row_adapter: DevicePtr,
180}
181
182/// Q12 batched-prefill device-side metadata.
183///
184/// The single-stream `AttnMetadataDev` collapses per-stream pointers into
185/// concrete device pointers because there's only one stream. For Q12 we
186/// dispatch N concurrent prefilling streams through one batched kernel,
187/// and the kernel takes:
188///   - stacked positions / slot tables (one big buffer with all streams'
189///     data concatenated in cu_seqlens order), and
190///   - per-stream pointer arrays for block_table / seq_len / h_state.
191///
192/// Built once per `prefill_batch_chunk_dispatch` call by
193/// `stage_batched_attn_metadata`; threaded through the model-level
194/// per-layer batched dispatch (`prefill_attn_batched_layer`,
195/// `prefill_ssm_batched_layer`) — see `model/trait_impl/prefill_b/batch.rs`.
196pub struct BatchedAttnMetadata {
197    /// Stacked positions across all streams: `[total_tokens]` u32 at this
198    /// address. For MRoPE interleaved this is the temporal (T) stream.
199    pub positions_stacked: DevicePtr,
200    /// MRoPE H position stream, stacked. Equal to `positions_stacked` when
201    /// MRoPE is disabled.
202    pub positions_h_stacked: DevicePtr,
203    /// MRoPE W position stream, stacked. Equal to `positions_stacked` when
204    /// MRoPE is disabled.
205    pub positions_w_stacked: DevicePtr,
206    /// Stacked slot indices for KV writes: `[total_tokens]` i64.
207    pub slot_stacked: DevicePtr,
208    /// Per-stream block_table pointer array: `[batch_size]` of `DevicePtr`,
209    /// each element pointing to a stream's chunked-prefill block_table.
210    /// Used by `prefill_attention_paged_*_batched` kernels.
211    pub block_table_ptrs: DevicePtr,
212    /// Per-stream seq_len pointer array: `[batch_size]` of `DevicePtr`.
213    pub seq_len_ptrs: DevicePtr,
214    // Note: `h_state_ptrs` is NOT cached in BatchedAttnMetadata because
215    // it's per-layer (each SSM layer's SsmLayerState has its own h_state
216    // allocation). `prefill_ssm_batched_layer` stages h_state_ptrs JIT
217    // per-layer-call into the model's scratch buffer.
218    /// Number of batched streams.
219    pub batch_size: u32,
220    /// Per-stream chunk_len. In the legacy same-length path this is uniform; in
221    /// the VARLEN path (`cu_seqlens` populated) it is the MAX per-stream length
222    /// (retained only for buffer-bound/debug use — per-stream lengths come from
223    /// `cu_seqlens`).
224    pub chunk_len: u32,
225    /// Total tokens stacked across streams. Legacy: `batch_size * chunk_len`.
226    /// VARLEN: `Σ per-stream lengths` (= `cu_seqlens_host[batch_size]`).
227    pub total_tokens: u32,
228    /// VARLEN geometry: `[batch_size+1]` i32 prefix-sum of per-request token
229    /// counts, on device (read by the GDN kernel + FlashInfer). `DevicePtr::NULL`
230    /// in the legacy same-length path (callers fall back to `b*chunk_len`).
231    pub cu_seqlens: DevicePtr,
232    /// Host copy of `cu_seqlens` (`[batch_size+1]` i32) — FlashInfer's PrefillPlan
233    /// dereferences the indptr on the CPU, and per-request slice offsets are
234    /// computed host-side. Empty in the legacy path.
235    pub cu_seqlens_host: Vec<i32>,
236    /// VARLEN geometry: per-stream KV length `[batch_size]` i32 on device,
237    /// `kv_lens[b] = chunk_start + per-stream token count`. The batched paged
238    /// attention kernels need this per stream: a single scalar at the MAX
239    /// makes short streams index their block_table past the blocks they
240    /// actually own, and applies the wrong causal bound. `DevicePtr::NULL` in
241    /// the legacy same-length path (kernels fall back to the scalar `kv_len`).
242    pub kv_lens: DevicePtr,
243    /// Host copy of `kv_lens`. Empty in the legacy path.
244    pub kv_lens_host: Vec<i32>,
245    /// Maximum block_table length across the batch (kernel uses for
246    /// bounds checking; per-stream block_table reads via the pointer
247    /// array dereference).
248    pub max_blocks_per_seq: u32,
249    /// Exact byte footprint of this metadata block within the scratch
250    /// buffer (from `scratch_offset_bytes` to the end of `seq_len_ptrs`).
251    /// SSOT for the caller's scratch-cursor advance — the per-SSM-layer
252    /// `h_state_ptrs` slot is placed at `scratch_cursor + staged_bytes`, so
253    /// an under-estimate here would overwrite the live `slot_stacked` array
254    /// with device pointers and produce wild KV-cache slots (#110 bug #2).
255    pub staged_bytes: usize,
256}
257
258/// Device pointers to full-sequence GDN input/output buffers.
259///
260/// Used by the two-phase SSM prefill: phase 1 writes GDN inputs here,
261/// phase 2 reads them for the single-launch GDN kernel, phase 3 reads output.
262///
263/// Uses a **packed QKV layout** matching the conv1d output: each token occupies
264/// `conv_dim` contiguous BF16 elements as `[Q(key_dim) | K(key_dim) | V(value_dim)]`.
265/// This allows simple contiguous memcpy from per-chunk conv1d output buffers.
266/// The GDN kernel reads Q/K/V via stride parameters (`qk_stride = conv_dim`,
267/// `v_stride = conv_dim`) to index into the packed layout.
268pub struct GdnPrefillBuffers {
269    /// Packed Q/K/V: [total_len, conv_dim] BF16.
270    /// Layout per token: [Q(key_dim) | K(key_dim) | V(value_dim)].
271    pub qkv: DevicePtr,
272    /// Interleaved gate/beta: [total_len, 2*num_v_heads] FP32.
273    /// Layout per token: [gate(nv) | beta(nv)].
274    pub gate_beta: DevicePtr,
275    /// GDN recurrence output: [total_len, value_dim] BF16.
276    pub output: DevicePtr,
277    /// Z gate for gated RMS norm: [total_len, value_dim] BF16.
278    pub z: DevicePtr,
279    /// Total number of tokens across all chunks.
280    pub total_len: usize,
281}
282
283/// Shared context for a single forward pass step.
284///
285/// Provides access to GPU, buffers, and config without coupling
286/// layer implementations to the model struct.
287pub struct ForwardContext<'a> {
288    /// Pre-allocated scratch buffers.
289    pub buffers: &'a BufferArena,
290    /// mHC highway ROW offset for this pass (#753 item B, mixed steps):
291    /// the fused decode+prefill step gives the prefill chunk highway rows
292    /// at `padded_n` so they live disjoint from the decode rows, mirroring
293    /// the hidden/residual layout. 0 everywhere else.
294    pub hc_row_offset: usize,
295    /// GPU backend for kernel launches and memory ops.
296    pub gpu: &'a dyn GpuBackend,
297    /// Model configuration (dimensions, hyperparameters).
298    pub config: &'a ModelConfig,
299    /// Which GEMM implementation each projection takes. Carried rather than
300    /// read from a static so it cannot outlive the model whose flags it
301    /// encodes — see `layers::ops::GemmDispatch`.
302    pub dispatch: &'a crate::layers::ops::GemmDispatch,
303    /// Re-encoded copies of this model's weights, memoized for this model's
304    /// lifetime. Carried rather than kept in a static keyed by device pointer,
305    /// where a recycled address would HIT after a model swap.
306    pub derived: &'a crate::layers::ops::DerivedWeights,
307    /// Kernel-path levers for this model — the SSM/GDN variant, FFN routing,
308    /// MoE quantization, LoRA mode, diagnostics. The non-GEMM half of the
309    /// lever set; `dispatch` is the GEMM half.
310    pub levers: &'a crate::layers::ops::ModelLevers,
311    /// This model's diagnostic counters and one-shot dump latches. Carried
312    /// for the same reason as `levers`: a counter that spans a model swap
313    /// averages two models and describes neither, and a one-shot latch that
314    /// already fired swallows the next model's dump.
315    pub stats: &'a crate::layers::ops::ModelStats,
316    /// Pre-uploaded attention metadata (None if no attention layers).
317    pub attn_metadata: Option<AttnMetadataDev>,
318    /// Profile mode: sync+time per-operation within layers.
319    pub profile: bool,
320    /// Communication backend for expert parallelism (EP) all-reduce.
321    /// None when running single-GPU (no distributed communication).
322    pub comm: Option<&'a dyn spark_comm::CommBackend>,
323    /// True when inside CUDA graph capture (between begin_capture/end_capture).
324    /// MoE layers use sync all_reduce (capturable) instead of async (event-based).
325    pub graph_capture: bool,
326    /// True ONLY on the single-token decode step, where `attn_metadata`'s `positions`,
327    /// `slot`, `seq_len` and `block_table` are the step's SCALARS at stable addresses.
328    ///
329    /// 🪤 `prefill_default` drives a layer that has no `prefill` of its own by calling its
330    /// `decode` once per token — with the PREFILL context, whose `positions`/`slot` are
331    /// per-token ARRAYS and whose `block_table`/`seq_len` are NULL unless the pass is paged.
332    /// A layer that reads those pointers as decode scalars gets an illegal address on the
333    /// first prompt. Check this flag, not `attn_metadata.is_some()`.
334    pub decode_step: bool,
335    /// True when this prefill pass continues from a restored Marconi SSM
336    /// snapshot (warm prefix-cache hit). GDN layers must then take the
337    /// bit-faithful WY4 recurrence instead of the FLA chunked kernel: FLA's
338    /// chunk grid is anchored at the (arbitrary) snapshot offset and its
339    /// bf16 intermediates drift vs the pass that originally produced the
340    /// cached K/V, and the replay range [snap_tok, matched) is rewritten
341    /// into SHARED prefix-cache blocks — non-exact recompute poisons them
342    /// and the drift ratchets across turns (2026-06-10 warm-hit stutter).
343    pub gdn_exact_replay: bool,
344    /// Device `[num_tokens]` u32 token IDs for the tokens being processed this
345    /// pass, in the SAME order the per-token MoE loop visits them. Required by
346    /// DeepSeek-V4 hash-MoE layers (static `tid2eid[token_id]` routing); `None`
347    /// for models without hash routing. Must be a STABLE address across the
348    /// layer loop (and, under CUDA-graph decode, uploaded before each replay).
349    pub token_ids: Option<DevicePtr>,
350    /// HOST copy of the same token ids, when the caller had them in hand
351    /// (decode always does — it uploads `token_ids` FROM this value; chunked
352    /// prefill likewise). PLE computes its n-gram ids on the host, and
353    /// reading them back off the device costs a synchronous D2H per decode
354    /// step — pure overhead, and capture-unsupported inside a CUDA graph.
355    pub host_token_ids: Option<&'a [u32]>,
356    /// #30 (routed-prefill precision): the REQUEST slot's per-layer LoRA pairs,
357    /// GLOBAL-layer-indexed (`len == num_hidden_layers`), set ONLY at the prefill
358    /// entries and ONLY when the request routes to a NON-active slot. `Some` makes
359    /// the K/V/O prefill apply sites select the request slot's pair and fold it
360    /// through the SAME dense `apply_lora_delta` (dense_gemm_tc) the ACTIVE adapter
361    /// uses — numerically identical to serving that adapter active, instead of the
362    /// per-row bgmv (whose fp accumulation order tips razor-margin tokens). `None`
363    /// (active/base request, no LoRA, and every decode/verify/mtp/moe pass) leaves
364    /// the installed-active-pair path byte-identical. Prefill runs eager
365    /// (`graph_capture: false`) so this per-pass CPU borrow is safe.
366    pub routed_lora_layers: Option<&'a [Option<crate::lora::LoraLayerWeights>]>,
367    /// Default-ON mid-chunk SSM tail capture (opt-out `ATLAS_SSM_TAIL_MIDCHUNK=0`).
368    ///
369    /// `Some` only on the single prefill pass whose local token range spans
370    /// the block-floored matched-prefix boundary `tb`. GDN/SSM layers then
371    /// split their recurrent (h_state) and conv (conv_state) kernels at
372    /// `cap_local` and copy the @tb state into the reserved snapshot slot.
373    /// `None` (default) => no split, byte-identical to prior behavior.
374    pub midchunk_capture: Option<MidchunkCapture<'a>>,
375    /// Feature-1 MoE-LoRA per-request fold decision for this forward pass,
376    /// resolved by `TransformerModel::moe_lora_route` from the owning request's
377    /// `adapter_slot`. Governs the prefill router/expert fold hooks
378    /// (`layers/moe/lora.rs`). Ignored when no MoE adapter is installed
379    /// (`self.lora == None` short-circuits first — byte-identical off). Default
380    /// `Fold` keeps legacy single-request call sites unchanged.
381    pub moe_lora_route: MoeLoraRoute,
382}
383
384/// Per-pass descriptor for mid-chunk SSM tail capture. Points at the reserved
385/// Marconi snapshot slot's per-SSM-layer destination buffers (already offset to
386/// the slot) plus the split point in local (chunk) token coordinates.
387///
388/// `ssm_layer_counter` is a fresh per-pass counter: each SSM layer's prefill
389/// increments it once, in model order, so the value indexes `h_dsts`/`conv_dsts`
390/// (which are in the same SSM-layer order as the snapshot pool).
391pub struct MidchunkCapture<'a> {
392    /// Split point in local token coordinates: capture state AFTER this many
393    /// tokens (== `tb - proc_start`).
394    pub cap_local: usize,
395    /// Per-SSM-layer h_state snapshot destination (offset to the reserved slot).
396    pub h_dsts: &'a [DevicePtr],
397    /// Per-SSM-layer conv_state snapshot destination (offset to the reserved slot).
398    pub conv_dsts: &'a [DevicePtr],
399    /// Bytes per layer of h_state.
400    pub h_bytes: usize,
401    /// Bytes per layer of conv_state.
402    pub conv_bytes: usize,
403    /// Fresh per-pass SSM-layer ordinal counter (model order == pool order).
404    pub ssm_layer_counter: &'a std::sync::atomic::AtomicUsize,
405    /// Optional SECOND capture one KV block earlier, at `tb - block_size`
406    /// (local split point `cap_local - block_size`). `Some` only when the pass
407    /// also covers that point. On ~5/19 warm turns the next turn's block-floored
408    /// `matched_tokens` lands exactly `tb - block_size` (generation-suffix /
409    /// retokenize divergence), one block short of the tail; registering this
410    /// earlier restore point makes those turns zero-replay too.
411    pub cap_local_early: Option<usize>,
412    /// Per-SSM-layer h_state dst for the `tb - block_size` slot (offset applied).
413    pub h_dsts_early: &'a [DevicePtr],
414    /// Per-SSM-layer conv_state dst for the `tb - block_size` slot.
415    pub conv_dsts_early: &'a [DevicePtr],
416}
417
418/// Feature-1 MoE-LoRA fold decision for a single forward pass.
419///
420/// The MoE router/expert delta is a SINGLE globally-installed adapter (phase 1):
421/// this gate makes the prefill fold per-request without a device kernel, exactly
422/// mirroring the attention BGMV `seq_slot < 0` skip but at request granularity.
423/// A base request pays nothing; a packed/mixed batch refuses loudly rather than
424/// fold one adapter onto every row (the device-side per-row fold that would let
425/// a mixed batch skip base rows individually is the documented follow-up —
426/// `docs/design/lora-solid.md` Incr 1/3).
427#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
428pub enum MoeLoraRoute {
429    /// Single-request pass whose request owns the installed active MoE adapter:
430    /// FOLD. Genuine single-seq decode now folds the router + expert gate/up/down
431    /// deltas at this altitude (mirroring prefill); the remaining back-compat
432    /// paths (multi-seq / verify) still bail via `reject_decode_lora` before the
433    /// fold, so their `Fold` value is inert there. Also the default.
434    #[default]
435    Fold,
436    /// Single-request pass that is base (`adapter_slot < 0`, no adapter) or
437    /// routes to a different, non-installed adapter: SKIP the fold entirely.
438    /// Base tokens pay nothing (request-granularity mirror of the attention
439    /// BGMV `seq_slot < 0` early-return).
440    Skip,
441    /// Multi-request / packed / codispatch batch whose per-row adapter identity
442    /// cannot be honored without the device-side per-row fold (follow-up): the
443    /// fold REFUSES loudly rather than mis-apply one adapter to every row.
444    Refuse,
445}
446
447/// A single transformer layer performing the full per-layer computation.
448///
449/// Each layer encapsulates:
450/// 1. Pre-norm -> attention/SSM -> residual add
451/// 2. Post-norm -> FFN/MoE -> residual add
452///
453/// The generic model loop iterates `layers` without knowing whether
454/// each is attention, SSM, MoE, or dense FFN.
455#[cfg(test)]
456mod tests;
457
458#[cfg(test)]
459#[path = "layer/release_contract_tests.rs"]
460mod release_contract_tests;