metrale_model_engine/model/types.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: `TransformerModel`, the state one loaded model owns, and its teardown (`release_pools`).
4//!
5//! Owner: model-engine.
6//! Invariants:
7//! - `release_pools` attempts every release even after an earlier one fails, and returns the first error.
8
9#![allow(unused_imports, dead_code)]
10
11use parking_lot::Mutex;
12use std::collections::HashMap;
13use std::sync::Arc;
14
15use anyhow::{Result, bail};
16use metrale_cache::kv_cache::PagedKvCache;
17use metrale_config::{LayerType, ModelConfig};
18use metrale_gpu_runtime::buffers::BufferArena;
19use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, GraphHandle, KernelHandle};
20
21use super::ssm_pool::SsmStatePool;
22use super::ssm_snapshot::SsmSnapshotPool;
23use crate::traits::{ChunkedPrefillPageMetadata, Model, SequenceState};
24use metrale_model_layers::layer::{
25 AttnMetadataDev, ForwardContext, GdnPrefillBuffers, LayerState, SsmLayerState, TransformerLayer,
26};
27use metrale_model_layers::layers::ops;
28use metrale_model_layers::speculative::DraftProposer;
29use metrale_model_layers::weight_map::{DenseWeight, Fp8DenseWeight, MtpWeights, QuantizedWeight};
30
31mod pinned_meta_staging;
32mod staging;
33mod teardown;
34
35pub(crate) use pinned_meta_staging::PinnedMetaStaging;
36
37#[allow(dead_code)]
38/// 2026-09-25: Rows in `mtp_catchup_ring`. Position `p` is stored in ring row
39/// `p % MTP_CATCHUP_RING_ROWS`.
40pub(super) const MTP_CATCHUP_RING_ROWS: usize = 512;
41
42/// 2026-09-25: One loaded model: its layers, weights, kernel handles, KV and
43/// SSM pools and CUDA-graph caches. `trait_impl` implements `Model` for it.
44pub struct TransformerModel {
45 pub(super) config: ModelConfig,
46 /// 2026-09-25: Which GEMM implementation each projection takes, resolved
47 /// from the environment when this model was built
48 /// (`GemmDispatch::from_env`). Every `ForwardContext` this model creates
49 /// borrows it.
50 pub(super) dispatch: metrale_model_layers::layers::ops::GemmDispatch,
51 /// 2026-09-25: Per-model memo of derived weight encodings, released by
52 /// `release_pools`.
53 pub(super) derived: metrale_model_layers::layers::ops::DerivedWeights,
54 /// 2026-09-25: The weight ledger this model was built from, installed by
55 /// `adopt_weight_store` after construction and held for teardown:
56 /// `release_pools` releases it after every other pool. `None` before
57 /// adoption and after release.
58 pub(super) weight_store: Option<metrale_model_weights::weights::WeightStore>,
59 pub(super) levers: metrale_model_layers::layers::ops::ModelLevers,
60 /// 2026-09-25: Diagnostic counters and one-shot dump latches for this model.
61 pub(super) stats: metrale_model_layers::layers::ops::ModelStats,
62 pub(super) embed_tokens: DenseWeight,
63 /// 2026-09-25: Fused n-gram input embedding, installed by
64 /// `set_ngram_embedding`. A `Mutex` because the forward path has `&self`
65 /// while `NgramEmbedding::embed` takes `&mut self`.
66 pub(super) ngram_embed:
67 Option<std::sync::Mutex<metrale_model_layers::layers::ngram_embed::NgramEmbedding>>,
68 pub(super) final_norm: DenseWeight,
69 pub(super) lm_head_weight: DenseWeight,
70 pub(super) lm_head_nvfp4: Option<QuantizedWeight>,
71 /// 2026-09-25: Transposed `[K/2, ldb]` copy of `lm_head_nvfp4` for the tile
72 /// GEMM, and its row stride.
73 ///
74 /// The stride is the vocab rounded up to a multiple of 128
75 /// (`transpose_concat_for_gemm_padded(.., 16, 128)`) so that every row
76 /// starts 16-byte aligned for the GEMM's B loads, including for an odd
77 /// vocab. It is an additional buffer: `lm_head_nvfp4` keeps its row-major
78 /// layout. `None` when there is no NVFP4 head or `METRALE_NO_LMHEAD_TGEMM=1`.
79 pub(super) lm_head_nvfp4_t: Option<(QuantizedWeight, u32)>,
80 /// 2026-09-25: Runtime FP8 E4M3 LM head with per-row scales, read by the
81 /// `dense_gemv_fp8w` kernels. `Some` only on the `--lm-head-dtype fp8`
82 /// path of `setup_lm_heads`, which returns `lm_head_nvfp4 = None`.
83 pub(super) lm_head_fp8: Option<Fp8DenseWeight>,
84 /// 2026-09-25: The output head as raw Q6_K blocks, run by the K-quant GEMV.
85 /// Installed by `set_lm_head_q6k` when the store's `lm_head.weight` is
86 /// Q6_K; `None` otherwise.
87 pub(super) lm_head_q6k: Option<super::lm_head_q6k::LmHeadQ6k>,
88 /// 2026-09-28: FP8 head multi-row/W8A8 kernels; 2026-10-02 the NVFP4 W4A16 row-tile head.
89 pub(super) lm_head_rows: super::lm_head_fp8_rows::LmHeadRows,
90 pub(super) layers: Vec<Box<dyn TransformerLayer>>,
91 /// 2026-09-25: `true` when any layer's `decode_graph_unsupported()` is true,
92 /// so decode stays eager. Computed once at construction from `layers`.
93 pub(super) decode_graph_veto: bool,
94 pub(super) buffers: BufferArena,
95 /// 2026-09-25: The LoRA adapter pool and pointer tables, installed by
96 /// `set_lora_weights`, which also installs the active adapter's per-layer
97 /// pairs into the layers. `None` = no adapter.
98 pub(super) lora: Option<metrale_model_layers::lora::LoraWeights>,
99 /// 2026-09-25: Runtime adapter rotation is armed: `METRALE_LORA_ROTATE` is
100 /// on, or `METRALE_LORA_PEER` is non-empty. Set by `set_lora_weights`; the
101 /// rotate and swap entry points refuse while it is false.
102 pub(super) lora_rotatable: bool,
103 pub(super) kv_cache: Mutex<PagedKvCache>,
104 pub(super) gpu: Box<dyn GpuBackend>,
105 /// 2026-09-25: TQ+ InnerQ calibration driver, when `TURBO_INNERQ` is a
106 /// positive integer. The scheduler reaches it through `Model::poll_innerq`.
107 #[cfg(feature = "cuda")]
108 pub(super) innerq: Option<metrale_model_layers::layers::qwen3_attention::InnerQDriver>,
109 pub(super) rms_norm_kernel: KernelHandle,
110 pub(super) dense_gemv_kernel: KernelHandle,
111 /// 2026-09-25: Always `KernelHandle(0)`: `use_fp32_logits` is always false.
112 pub(super) dense_gemv_fp32out_kernel: KernelHandle,
113 pub(super) w4a16_gemv_kernel: KernelHandle,
114 pub(super) w4a16_gemv_logits_kernel: KernelHandle,
115 /// 2026-09-25: Tile GEMM over `lm_head_nvfp4_t`. 0 when `tgemm_probe_ok`
116 /// rejects the model type.
117 pub(super) w4a16_gemm_t_kernel: KernelHandle,
118 /// 2026-09-25: BF16-MMA tile GEMM over `lm_head_nvfp4_t`
119 /// (`w4a16_gemm_t_m128_bf16_v2`). 0 unless `METRALE_LMHEAD_LOSSLESS` is set
120 /// (any value) and the target has the kernel.
121 pub(super) w4a16_gemm_t_bf16_kernel: KernelHandle,
122 pub(super) w4a16_gemm_kernel: KernelHandle,
123 pub(super) w4a16_gemv_batch2_kernel: KernelHandle,
124 /// 2026-09-25: The narrow batched-GEMV tiers; a tier the target lacks is a
125 /// 0 handle.
126 pub(super) w4a16_batchm: metrale_model_layers::layers::w4a16_gemv_tiers::W4a16BatchmTiers,
127 pub(super) w4a16_gemv_batch16_kernel: KernelHandle,
128 /// 2026-09-25: FP8 E4M3 GEMV for `lm_head_fp8`. Loaded unconditionally;
129 /// launched only on the FP8-head path.
130 pub(super) dense_gemv_fp8w_kernel: KernelHandle,
131 /// 2026-09-25: FP8-weight GEMV over two rows in one weight pass. 0 when the
132 /// target lacks the kernel.
133 pub(super) dense_gemv_fp8w_batch2_kernel: KernelHandle,
134 pub(super) dense_gemm_kernel: KernelHandle,
135 /// 2026-09-25: Batched BF16 GEMV (M rows, one weight pass). 0 = absent.
136 pub(super) dense_gemv_batchm_kernel: KernelHandle,
137 /// 2026-09-25: Tensor-core BF16 GEMM with a 16-row M tile
138 /// (`dense_gemm_m16_bf16`), the BF16 LM-head arm that
139 /// `trait_impl/lm_head_batched.rs::lm_head_m16_tc_route` selects when the
140 /// `lm_head_m16_tc` lever is on. 0 when the target lacks the kernel.
141 pub(super) lm_head_m16_tc_kernel: KernelHandle,
142 /// 2026-09-25: The `N_TILE=64` variant (`dense_gemm_m16_bf16_n64`), selected
143 /// by `METRALE_LM_HEAD_M16_TC_NTILE=64`. 0 when the target lacks it.
144 pub(super) lm_head_m16_tc_n64_kernel: KernelHandle,
145 pub(super) argmax_kernel: KernelHandle,
146 /// 2026-09-25: Batched argmax (one block per row). 0 when the kernel set lacks it.
147 pub(super) argmax_batch_kernel: KernelHandle,
148 pub(super) argmax_logits_kernel: KernelHandle,
149 pub(super) batched_embed_kernel: KernelHandle,
150 pub(super) fill_slots_kernel: KernelHandle,
151 /// 2026-09-25: The device token feed (`trait_impl/feed.rs`): the masked feed
152 /// argmax and the source resolver of the `argmax_feed` module. Both are 0
153 /// when the target lacks the module, and then the feed is not used.
154 pub(super) argmax_feed_kernel: KernelHandle,
155 pub(super) feed_resolve_kernel: KernelHandle,
156 /// 2026-09-25: Feed buffers, `[feed_rows]` u32 each: the cells a step's
157 /// argmax writes and the next step reads, the resolved input ids, and the
158 /// per-row source words. The masks are `[feed_rows * 2]` u32. All four are
159 /// NULL when either feed kernel is 0.
160 pub(super) feed_cells: DevicePtr,
161 pub(super) feed_ids: DevicePtr,
162 pub(super) feed_sources: DevicePtr,
163 pub(super) feed_masks: DevicePtr,
164 pub(super) feed_rows: usize,
165 /// 2026-09-25: Batched-graph keys of fed steps still in flight, in launch
166 /// order (`pin_fed_graph_key` / `fed_step_settled_dispatch`). The batched
167 /// decode graph LRU never evicts these keys.
168 pub(super) inflight_fed_graph_keys: Mutex<std::collections::VecDeque<Vec<u32>>>,
169 /// 2026-09-25: CUDA graphs for n=1 decode, keyed by `seq.slot_idx`. A
170 /// captured graph has the slot's SSM `h_state`/`conv_state` pointers baked
171 /// in as kernel arguments, so it is only replayed for the same slot.
172 pub(super) decode_graph:
173 Mutex<std::collections::HashMap<super::trait_impl::SlotGraphKey, GraphHandle>>,
174 /// 2026-09-25: CUDA graphs for batched decode, keyed by the per-row SSM pool
175 /// slot vector (`trait_impl/decode_graph_key.rs`). Value =
176 /// `(graph, last_use_tick)`; the `u64` beside the map is the tick counter.
177 /// At `batch_decode_graph_cap` entries the least-recently-used graph is
178 /// destroyed.
179 pub(super) batch_decode_graphs: Mutex<(HashMap<Vec<u32>, (GraphHandle, u64)>, u64)>,
180 /// 2026-09-25: Pre-allocated SSM state pool, so state addresses stay fixed
181 /// across graph replays. `Arc` so each `SequenceState` can hold the
182 /// `SlotGuard` from `SsmStatePool::claim_guarded`, which returns the slot
183 /// to the free list when the sequence is dropped.
184 pub(super) ssm_pool: Arc<SsmStatePool>,
185 /// 2026-09-25: SSM state snapshots for Marconi prefix caching.
186 pub(super) ssm_snapshots: SsmSnapshotPool,
187 /// 2026-09-25: SSM snapshot spill tier. `Some` only when `METRALE_SSM_TIER`
188 /// is set and the model has SSM layers (`build_ssm_tier_store`).
189 pub(super) ssm_tier_store: Option<Arc<dyn super::ssm_tier::SnapshotBlobStore>>,
190 /// 2026-09-25: `max_seq_len / block_size + 1`, the fixed block-table stride
191 /// in attention metadata, so a captured graph's stride stays valid.
192 pub(super) max_blocks_per_seq: u32,
193 /// 2026-09-25: A KV block allocated and zeroed at construction, never freed,
194 /// used for padding rows in batched decode and verify.
195 pub(super) dummy_kv_block: u32,
196 pub(super) profile: bool,
197 /// 2026-09-25: One-shot profile of the next prefill, armed by
198 /// `METRALE_PROFILE_FIRST` (any value). The prefill layer loop
199 /// (`prefill_b/forward_layers.rs`) swaps it to false when it consumes it.
200 pub(super) profile_first_pending: std::sync::atomic::AtomicBool,
201 /// 2026-09-25: When true, CUDA graph capture and replay are skipped. Set at
202 /// construction during FP8 KV calibration or under a graph-disabling
203 /// diagnostic env, and around the per-sequence decode loop in `decode_a2`.
204 pub(super) suppress_graphs: std::sync::atomic::AtomicBool,
205 /// 2026-09-25: Draft proposer: built in `new` from the MTP weights, or
206 /// installed later by `set_dflash_proposer`.
207 pub(super) proposer: Option<Arc<dyn DraftProposer>>,
208 /// 2026-09-25: One FP32 hidden row (`hidden_size * 4` bytes): the target
209 /// hidden that `save_hidden_for_mtp` copies out before the MTP head runs.
210 pub(super) mtp_hidden_save: DevicePtr,
211 /// 2026-09-25: Batched-verify hidden stash, `[VERIFY_WY_TABLE_SEQS,
212 /// hidden_size]` BF16, one row per sequence of a batched verify.
213 /// `stash_verify_hidden_rows` copies each sequence's accepted-row hidden
214 /// here and `save_hidden_for_mtp_from_stash` reads it back for the drafter.
215 /// NULL without a proposer at construction.
216 pub(super) verify_hidden_stash: DevicePtr,
217 /// 2026-09-25: `ModelLevers::mtp_kv_exact`: verify-forward rows of accepted
218 /// drafts, `[VERIFY_WY_TABLE_SEQS * MTP_CATCHUP_MAX, hidden_size]` BF16.
219 /// NULL unless the lever is on and a proposer exists at construction.
220 pub(super) verify_catchup_stash: DevicePtr,
221 /// 2026-09-25: `METRALE_MTP_CATCHUP`: ring of per-position final hiddens
222 /// captured during serial decode, BF16 rows, row = position %
223 /// `MTP_CATCHUP_RING_ROWS`. Feeds the drafter catch-up on the next propose.
224 /// NULL when `mtp_catchup_enabled()` is false at construction.
225 pub(super) mtp_catchup_ring: DevicePtr,
226 /// 2026-09-25: `(first_position, count)` of the contiguous position range in
227 /// the ring. A write that does not extend the range restarts it at that
228 /// position; `count` never exceeds `MTP_CATCHUP_RING_ROWS`.
229 pub(super) mtp_catchup_meta: parking_lot::Mutex<(usize, usize)>,
230 /// 2026-09-25: Whole-prompt final-layer hidden capture, `[rows,
231 /// hidden_size]` BF16 with `rows = mtp_prefill_capacity`. Written per
232 /// prefill chunk and read by the drafter prefill and the drafter carry
233 /// (`drafter_prefill.rs`). NULL unless an MTP proposer exists, its head supports the
234 /// drafter prefill, and the drafter-prefill lever is on.
235 pub(super) mtp_prefill_hidden: DevicePtr,
236 /// 2026-09-25: Row capacity of `mtp_prefill_hidden`: `max_seq_len` capped by
237 /// the proposer's `prefill_hidden_rows` and, with DFlash, by the DFlash
238 /// context cap. 0 when the buffer is NULL. The capture bounds check
239 /// compares against this value.
240 pub(super) mtp_prefill_capacity: usize,
241 /// 2026-09-25: Rows of `mtp_prefill_hidden` captured contiguously from
242 /// position 0 for the sequence that owns the capture. Reset to 0 by
243 /// `alloc_sequence`. A chunk that does not extend the contiguous range
244 /// leaves it short, and the propose-site coverage check then skips the
245 /// drafter prefill.
246 pub(super) mtp_prefill_capture_len: std::sync::atomic::AtomicUsize,
247 /// 2026-09-25: Generation of the single-slot capture above. A chunk-0
248 /// prefill bumps it and stamps the new value on its sequence
249 /// (`SequenceState::mtp_capture_gen`). Appends and the drafter-prefill
250 /// consume require the sequence's stamp to equal the current value and to
251 /// be non-zero, so a sequence whose capture another sequence's prefill has
252 /// restarted skips the drafter prefill.
253 pub(super) mtp_prefill_capture_gen: std::sync::atomic::AtomicU64,
254 /// 2026-09-25: Ticket counter for `mtp_store_range` ownership
255 /// (`SequenceState::mtp_store_gen`), drawn once per `alloc_sequence`.
256 ///
257 /// It is separate from `mtp_prefill_capture_gen` because a draw on every
258 /// admission would advance that counter, and `owns_capture` requires the
259 /// capture generation to be unchanged between a sequence's capture and its
260 /// propose.
261 pub(super) mtp_store_gen_seq: std::sync::atomic::AtomicU64,
262 /// 2026-09-25: The previous turn's drafter KV, held so the next turn of the
263 /// same session can adopt it. Single slot: `mtp_carry::carry_armed_with`
264 /// turns the carry off in multi-sequence MTP mode. `None` when the carry is
265 /// off or nothing has been carried.
266 pub(super) mtp_carry:
267 parking_lot::Mutex<Option<metrale_model_layers::mtp_carry::CarriedDrafter>>,
268 /// 2026-09-25: Absolute position interval of `mtp_prefill_hidden` rows and
269 /// the sequence ticket that wrote them. Updated only while the drafter
270 /// carry is armed.
271 ///
272 /// The ticket (`owner`) is what ties the interval to one sequence:
273 /// `mtp_carry::stamped_merge` replaces rather than extends the interval
274 /// when a different owner writes. `alloc_sequence` also resets it.
275 pub(super) mtp_store_range: parking_lot::Mutex<metrale_model_layers::mtp_carry::StoreRange>,
276 /// 2026-09-25: DFlash per-layer hidden capture, allocated when the config
277 /// names DFlash capture layers. Row-major: `dflash_hidden_save_rows` rows,
278 /// each `dflash_capture_layers.len() * hidden_size` BF16, with capture
279 /// layer `s` at element offset `s * hidden_size` inside a row. `None` for
280 /// non-DFlash runs.
281 pub(super) dflash_hidden_save: Option<DevicePtr>,
282 /// 2026-09-25: Lazily allocated 16 KiB metadata staging for the
283 /// sliding-attention per-token verify loop (`verify_attention_per_token`).
284 /// The loop cannot stage at `scratch + 32768`: the verify bodies'
285 /// multi-seq metadata overlay lives there, and the interleaved
286 /// FullAttention layers still read it between the per-token calls. Layout:
287 /// position at +0, slot at +8, seq_len at +16, seq_slot at +128, block
288 /// table from +256; the loop refuses a block table that would not fit.
289 pub(super) verify_ptok_meta: std::sync::OnceLock<DevicePtr>,
290 /// 2026-09-25: Layer indices to capture for DFlash, from
291 /// `config.dflash_capture_layers`. Empty when DFlash is off.
292 pub(super) dflash_capture_layers: Vec<usize>,
293 /// 2026-09-25: Row capacity of `dflash_hidden_save`,
294 /// `dflash_kgamma.max(2) * max_batch_size.max(1)`, or 0 without DFlash.
295 /// `try_dflash_capture_all_at` clamps its writes to it.
296 pub(super) dflash_hidden_save_rows: usize,
297 /// 2026-09-25: Rows per per-sequence capture band in `dflash_hidden_save`:
298 /// the drafter γ + 1, or 17 when γ is unknown, or 0 without DFlash.
299 /// Sequence `i` of a batched verify writes rows from `i * dflash_kgamma`;
300 /// single-sequence paths use band 0. The scheduler passes this stride to
301 /// `commit_ctx` as `scratch_row`.
302 pub(super) dflash_kgamma: usize,
303 /// 2026-09-25: CUDA graphs for K=2 verify, keyed by `seq.slot_idx` for the
304 /// same reason as `decode_graph`.
305 pub(super) verify2_graph:
306 Mutex<std::collections::HashMap<super::trait_impl::SlotGraphKey, GraphHandle>>,
307 /// 2026-09-25: CUDA graphs for K=3 verify, keyed by `seq.slot_idx`.
308 pub(super) verify3_graph:
309 Mutex<std::collections::HashMap<super::trait_impl::SlotGraphKey, GraphHandle>>,
310 /// 2026-09-25: CUDA graphs for K=4 verify, keyed by `seq.slot_idx`.
311 pub(super) verify4_graph:
312 Mutex<std::collections::HashMap<super::trait_impl::SlotGraphKey, GraphHandle>>,
313 /// 2026-09-25: CUDA graphs for the batched verify (`verify_e.rs`), keyed by
314 /// `verify_key::verify_graph_key`: each sequence's `(ssm slot, row count)`
315 /// in dispatch order, then a sentinel word for the WY-tables-present and
316 /// write-on-accept bits. A graph bakes every sequence's state pointers and
317 /// the row counts, so it replays only for an equal key. Value =
318 /// `(graph, last_use_tick)`; at `VERIFY_BATCHED_GRAPH_CAP` entries the
319 /// least-recently-used graph is destroyed.
320 pub(super) verify_batched_graphs:
321 Mutex<(std::collections::HashMap<Vec<u32>, (GraphHandle, u64)>, u64)>,
322 /// 2026-09-25: Batched-verify WY pointer tables at a fixed device address:
323 /// one `VERIFY_WY_LAYER_STRIDE_BYTES` slice per GDN layer, holding
324 /// `VERIFY_WY_TABLES_PER_LAYER` tables of `VERIFY_WY_TABLE_SEQS` u64
325 /// entries. Zeroed at allocation and written by `upload_verify_wy_tables`.
326 /// NULL without a proposer at construction or without SSM layers.
327 pub(super) verify_wy_tables: DevicePtr,
328 /// 2026-09-25: Write-on-accept: device `u32[VERIFY_WY_TABLE_SEQS]` of
329 /// accepted row counts for the post-verdict fold, and the SSM slots the
330 /// most recent fold committed (`async_chkpt.rs` consumes an entry to skip
331 /// that slot's h restore). The table is NULL when `verify_wy_tables` is.
332 pub(super) gdn_woa_na_tab: DevicePtr,
333 pub(super) gdn_woa_folded_slots: Mutex<Vec<usize>>,
334 /// 2026-09-25: Cleared at the start of every batched verify, set at the end
335 /// of one that ran under a write-on-accept request with its tables staged,
336 /// and consumed by the fold (`gdn_fold_accepted_dispatch`), which declines
337 /// unless it was set.
338 pub(super) gdn_woa_eligible: std::sync::atomic::AtomicBool,
339 /// 2026-09-25: Write-on-accept `(flags, stash, seqs)` for every GDN layer,
340 /// allocated and bound by `gdn_woa_bind` on the first write-on-accept
341 /// request, before any capture, and never moved. `(NULL, NULL, 0)` until
342 /// then.
343 pub(super) gdn_woa_bound: Mutex<(DevicePtr, DevicePtr, usize)>,
344 /// 2026-09-26: Carried-state GDN verify buffers and pending rows (`gdn_carry.rs`).
345 pub(super) gdn_carry: super::gdn_carry::GdnCarry,
346 /// 2026-09-25: Key of the bytes currently staged in `verify_wy_tables`
347 /// (`verify_wy_cache_key`), or `None` when nothing has been staged. A step
348 /// whose key matches skips the host build and the H2D. Presence of
349 /// `METRALE_NO_VERIFY_WY_CACHE` disables the cache.
350 pub(super) verify_wy_cache: Mutex<Option<Vec<u64>>>,
351 /// 2026-09-25: CUDA graphs for the DFlash K=γ verify, keyed by
352 /// `(seq.slot_idx, tokens.len())`.
353 pub(super) verify_kgamma_graph: Mutex<std::collections::HashMap<(usize, usize), GraphHandle>>,
354 /// 2026-09-25: CUDA graphs for the DFlash decode+verify fused pass
355 /// (`verify_fused.rs`), keyed by `(seq.slot_idx, M)` with
356 /// `M = tokens.len()`.
357 pub(super) fused_graph: Mutex<std::collections::HashMap<(usize, usize), GraphHandle>>,
358 pub(super) prefix_cache: Box<dyn metrale_telemetry::prefix_cache::PrefixCache>,
359 /// 2026-09-25: Second CUDA stream and its event, used by the asynchronous
360 /// SSM checkpoint copies (`async_chkpt.rs`).
361 pub(super) secondary_stream: u64,
362 pub(super) secondary_event: u64,
363 /// 2026-09-25: Orders SSM-snapshot saves before a later warm Marconi
364 /// restore that runs on another stream. A save records it after enqueueing
365 /// its D2D copies (`record_snapshot_save_dispatch`), and a restore waits on
366 /// it before reading the snapshot region (`wait_snapshot_saves_dispatch`),
367 /// so a restore never reads a slot whose save copy is still in flight.
368 pub(super) snapshot_event: u64,
369 /// 2026-09-25: Communication backend for multi-rank (EP/TP) collectives.
370 /// `None` on a single GPU.
371 pub(super) comm: Option<std::sync::Arc<dyn metrale_comm::CommBackend>>,
372 /// 2026-09-25: 4-byte device buffer for the EP token broadcast.
373 pub(super) ep_cmd_buf: DevicePtr,
374 /// 2026-09-25: EP wire protocol: true when `METRALE_EP_PROTOCOL=v2`, which
375 /// precedes every command broadcast with a `seq_id` broadcast so the
376 /// worker can route slot-bound work to the right `SequenceState`. With a
377 /// comm backend, `new` checks that the ranks agree on it
378 /// (`rank_agree::assert_ranks_agree`).
379 pub(super) ep_protocol_v2: bool,
380 pub(super) self_speculative: bool,
381 /// 2026-09-25: Last token index `save_hidden_for_mtp` copied from, broadcast
382 /// to the other EP rank before an MTP propose.
383 pub(super) last_mtp_hidden_idx: std::sync::atomic::AtomicUsize,
384 pub(super) vision_encoder: Option<metrale_model_layers::layers::VisionTower>,
385 /// 2026-09-25: Number of patches encoded by the last `prepare_vision_embed`
386 /// call. 0 means no vision embeddings are pending.
387 pub(super) vision_embed_patches: Mutex<usize>,
388 /// 2026-09-25: Per-item `(t_len, grid_h_post_merge, grid_w_post_merge)` from
389 /// the most recent vision prepare, used by MRoPE prefill to assign
390 /// `(t, h, w)` position ids to each vision pad token. Empty when no vision
391 /// input is pending. `t_len` is the number of temporal groups the item
392 /// spans (1 for a still image), because a video occupies one contiguous
393 /// pad run whose T advances.
394 pub(super) vision_image_grids: Mutex<Vec<(usize, usize, usize)>>,
395 /// 2026-09-25: Where the next prefill's vision slice starts inside a batched
396 /// vision prepare: the first row of the packed output, the first
397 /// `vision_image_grids` index, and how many items it owns. Set by
398 /// `Model::set_vision_slice_base`; all 0 reads from row 0 and grid 0.
399 pub(super) vision_row_base: Mutex<usize>,
400 pub(super) vision_grid_base: Mutex<usize>,
401 pub(super) vision_owned_images: Mutex<usize>,
402 /// 2026-09-25: Page-locked host staging for batched metadata H2D transfers,
403 /// allocated in `new` and freed on drop (`drop_pinned_staging`).
404 ///
405 /// An `UnsafeCell`, not a `Mutex`, because the model is used only from the
406 /// scheduler thread after construction (see the `Sync` impl below).
407 pub(super) pinned_staging: std::cell::UnsafeCell<PinnedMetaStaging>,
408 /// 2026-09-25: Save an SSM snapshot every N blocks during chunked prefill;
409 /// 0 disables the intermediate checkpoints (`prefill_b/save_checkpoint.rs`).
410 pub(super) ssm_checkpoint_interval: usize,
411 /// 2026-09-25: `ssm_state_clamp_norm_fused`, 0 when the target lacks it.
412 pub(super) ssm_state_norm_kernel: KernelHandle,
413 /// 2026-09-25: The FP16 h-state variant (`ssm_state_clamp_norm_fused_f16`),
414 /// for sequences whose `SsmLayerState::h_is_f16` is set.
415 pub(super) ssm_state_norm_f16_kernel: KernelHandle,
416 /// 2026-09-25: Device pointer table `[num_ssm_layers]` for
417 /// `ssm_state_clamp_norm_fused`.
418 pub(super) ssm_norm_ptrs_buf: DevicePtr,
419 /// 2026-09-25: FP32 → FP16 h-state converter (`METRALE_SSM_H_FP16`).
420 pub(super) ssm_h_f32_to_f16_kernel: KernelHandle,
421 /// 2026-09-25: FP16 → FP32 h-state converter, used by the batched prefill
422 /// path under the FP16-sized pool (`--ssm-h-dtype f16-pool`).
423 pub(super) ssm_h_f16_to_f32_kernel: KernelHandle,
424 /// 2026-09-25: Staging for the FP32 → FP16 conversion
425 /// (`ssm_h_to_f16_dispatch`), one layer wide (`h_bytes / 2`), allocated on
426 /// first use. The narrowing cannot run in place: output element `i` is
427 /// written into the bytes that hold input element `i / 2`.
428 pub(super) ssm_h_f16_scratch: std::sync::OnceLock<DevicePtr>,
429
430 /// 2026-09-25: Fixed-address device buffer for the batched-decode MoE
431 /// per-row adapter map, `[max_batch_size]` i32 (`< 0` = base row, `>= 0` =
432 /// fold that adapter). Allocated in `new` and refreshed per decode step by
433 /// `upload_moe_row_adapter`, which does not touch it when `lora` is `None`.
434 pub(super) moe_row_adapter_buf: DevicePtr,
435
436 // 2026-09-25: Two-phase SSM prefill buffers, `gdn_buf_max_len` rows each,
437 // shared by every layer and sequence. NULL when the model has no GDN
438 // layers (`build_gdn_prefill_buffers`).
439 /// 2026-09-25: Packed QKV, `[rows, conv_dim]` BF16, per token
440 /// `[Q(key_dim) | K(key_dim) | V(value_dim)]`.
441 pub(super) gdn_buf_qkv: DevicePtr,
442 /// 2026-09-25: Gate and beta, `[rows, 2 * num_v_heads]` FP32.
443 pub(super) gdn_buf_gate_beta: DevicePtr,
444 /// 2026-09-25: GDN output, `[rows, value_dim]` BF16.
445 pub(super) gdn_buf_out: DevicePtr,
446 /// 2026-09-25: Z gate for the gated RMS norm, `[rows, value_dim]` BF16.
447 pub(super) gdn_buf_z: DevicePtr,
448 /// 2026-09-25: Row count of the buffers above:
449 /// `min(max_batch_tokens, max_seq_len)`.
450 pub(super) gdn_buf_max_len: usize,
451
452 /// 2026-09-25: Logit softcapping, `logits = cap * tanh(logits / cap)`.
453 /// `KernelHandle(0)` when the model has no softcapping.
454 pub(super) logit_softcap_kernel: KernelHandle,
455 /// 2026-09-25: Always `KernelHandle(0)`: `use_fp32_logits` is always false.
456 pub(super) logit_softcap_fp32_kernel: KernelHandle,
457 /// 2026-09-25: Always false: `new` sets it so, and the BF16 logits path is
458 /// always taken.
459 pub(super) use_fp32_logits: bool,
460 /// 2026-09-25: FP32 logits scratch, allocated only when `use_fp32_logits`;
461 /// NULL otherwise.
462 pub(super) logits_fp32_buf: DevicePtr,
463 /// 2026-09-25: Embedding scale by `config.embed_scale`.
464 /// `KernelHandle(0)` when the model does not scale embeddings.
465 pub(super) embed_scale_kernel: KernelHandle,
466 /// 2026-09-25: Per-adapter-slot token overlay tables (embed and lm_head row
467 /// overrides), built by `set_lora_weights` only when some adapter
468 /// overrides a row. `None` makes every overlay hook return early.
469 pub(super) overlays: Option<metrale_model_layers::lora::TokenOverlaySet>,
470 /// 2026-09-25: Token overlay kernels, resolved in `new`.
471 pub(super) overlay_kernels: metrale_model_layers::layers::ops::token_overlay::OverlayKernels,
472 /// 2026-09-25: The overlay route of the current forward: the request's
473 /// `adapter_slot`, stamped at each `Model` entry
474 /// (`stamp_overlay_route` / `stamp_overlay_route_batch`). `i32::MIN` marks
475 /// a batch that mixes adapters; the hooks then skip.
476 pub(super) overlay_route_slot: std::sync::atomic::AtomicI32,
477 /// 2026-09-25: MoE LoRA route of the current decode batch, read through
478 /// `decode_moe_route()`: 0 = Skip, 1 = Fold, 2 = Refuse (any other value
479 /// reads as Fold). Initialised to 1.
480 pub(super) decode_moe_route: std::sync::atomic::AtomicI32,
481 /// 2026-09-28: The circuit executor decode runs (`ModelCircuit::set_forward`); `None` runs
482 /// the legacy layer loops.
483 pub(super) circuit:
484 parking_lot::RwLock<Option<metrale_model_layers::circuit_exec::CircuitExec>>,
485}