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;