metrale_model_arch/deepseek_v41_layer.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// provenance-id: 526f6e616c6420522e205374657369616b
3
4//! 2026-09-25: DeepSeek-V4.1 Flash: one transformer block on the generic model.
5//!
6//! The layer runs on the f32 mHC highway the generic model provides
7//! (`ctx.buffers.hc_streams()`, `[T, hc, H]`), in the order of the CPU
8//! reference `deepseek_v41_ref::model::forward`:
9//!
10//! ```text
11//! layer 0 only: hc_expand(embedding -> streams)
12//! engram layers only: streams += engram(token n-grams) (EngramV41)
13//! attention: (pre_a, post_a, comb_a) = hc_mixes(streams, attn site)
14//! x = rmsnorm(collapse(streams, pre_prev)) <- delayed pre
15//! streams = hc_post(attn(x), streams, post_a, comb_a)
16//! ffn: (pre_f, post_f, comb_f) = hc_mixes(streams, ffn site)
17//! x = rmsnorm(collapse(streams, pre_a))
18//! streams = hc_post(moe(x), streams, post_f, comb_f)
19//! pre_prev = pre_f
20//! last layer only: hidden = collapse(streams, pre_prev)
21//! ```
22//!
23//! `pre_prev` is set by layer 0 to the one-hot on stream 0 (the reference's
24//! initial pre-mix) and lives in [`V41Runtime`], with the shared attention
25//! slots, the engram hasher and the expert cache: attention state is shared
26//! across layers, so one runtime is shared by every layer through an `Arc`,
27//! its mutable parts behind mutexes. One sequence at a time: multi-sequence
28//! decode and the model-level CUDA graph are declined through the layer
29//! hooks. The layer can capture its own single-token step instead
30//! (`METRALE_DS41_GRAPH=1`, see [`GraphMode`], or the multi-layer segments of
31//! `METRALE_DS41_STEP_GRAPH=1`): the host work in the middle of every layer
32//! (the routing download and the expert fetch; on kv and index source layers
33//! also the compressor group and the index top-k) splits the step into graph
34//! segments with host spans between them.
35//!
36//! Routed experts are read on demand into an expert cache (`ExpertLru` over
37//! the `ExpertArena` slots, `ExpertSliceMap` locating each expert in the GGUF
38//! shards) with positional file reads. The arena is device memory behind a
39//! page-locked staging ring, or with `METRALE_DS41_ARENA_DEVICE=0` a
40//! page-locked arena the GPU reads in place. The engram tables are read by
41//! row (`EngramRowReader`).
42//!
43//! Owner: model-arch, DeepSeek-V4.1.
44//! Invariants:
45//! - Layer 0's `step` at position 0 resets the shared attention slots, the
46//! engram hasher and the cached step hashes after its argument checks and
47//! before any engram, attention or MoE work.
48
49use std::sync::atomic::AtomicBool;
50use std::sync::{Arc, Mutex, OnceLock};
51
52use anyhow::Result;
53
54use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, GraphHandle, KernelHandle};
55use metrale_model_weights::weights::expert_stream::{
56 EngramRowReader, ExpertArena, ExpertLru, ExpertSliceMap,
57};
58
59use crate::attn_v41::{
60 AttnV41, AttnV41Cfg, AttnV41LayerState, AttnV41LayerWeights, LayerRole, SharedV41,
61};
62use crate::engram_v41::{EngramHashTables, EngramHasher, EngramV41};
63use crate::moe_v41::{MoeV41, MoeV41Cfg, MoeV41LayerWeights, RouterWeights};
64use metrale_model_layers::layer::LayerState;
65use metrale_model_layers::layers::qwen3_attention::HcSiteWeights;
66use metrale_model_layers::weight_map::DenseWeight;
67
68mod graph;
69mod hc_launch;
70mod step;
71mod step_ffn;
72mod step_seg;
73mod step_seg_run;
74mod trait_impl;
75
76pub use step_seg::{SegState, step_graph_on};
77
78/// 2026-09-25: Everything the layers share for the one sequence in flight.
79pub struct V41Runtime {
80 pub attn_cfg: AttnV41Cfg,
81 pub moe_cfg: MoeV41Cfg,
82 pub attn: Mutex<AttnV41>,
83 pub moe: Mutex<MoeV41>,
84 pub engram: Mutex<EngramV41>,
85 pub lru: Mutex<ExpertLru>,
86 pub arena: ExpertArena,
87 pub slices: Arc<ExpertSliceMap>,
88 pub rows: EngramRowReader,
89 pub tables: Arc<EngramHashTables>,
90 pub hasher: Mutex<EngramHasher>,
91 pub shared: Mutex<SharedV41>,
92 /// 2026-09-25: The step's engram hashes, computed by the first engram layer
93 /// (hash index 0) or the segment-graph preload, and reused by the others.
94 pub step_hashes: Mutex<Option<Vec<i64>>>,
95 /// 2026-09-25: The delayed `pre` mix, f32 `[max_tokens, hc]`.
96 pub pre_prev: DevicePtr,
97 /// 2026-09-25: f32 `[max_tokens, (2 + hc) * hc]` scratch between
98 /// `hc_v41_mixes_dot` and `hc_v41_finish_collapse`.
99 pub mixes_s: DevicePtr,
100 pub reader_threads: usize,
101 pub n_layers: usize,
102 pub hc_mult: usize,
103 pub hidden: usize,
104 pub sinkhorn_iters: usize,
105 pub hc_eps: f32,
106 pub norm_eps: f32,
107 pub pre_a: DevicePtr,
108 pub pre_f: DevicePtr,
109 pub post_s: DevicePtr,
110 pub comb_s: DevicePtr,
111 pub attn_in: DevicePtr,
112 pub max_tokens: usize,
113 /// 2026-09-25: Per-step totals across layers, logged by the last layer when
114 /// `METRALE_DS41_DIAG=1`.
115 pub step_moe: Mutex<crate::moe_v41::MoeV41Timing>,
116 pub step_attn_ms: Mutex<f64>,
117 pub step_engram_ms: Mutex<f64>,
118 pub step_start: Mutex<Option<std::time::Instant>>,
119 /// 2026-09-25: Set when a `METRALE_DS41_GRAPH` capture failed: segments
120 /// without a graph run eagerly from then on; captured graphs keep replaying.
121 pub graph_disabled: AtomicBool,
122 /// 2026-09-25: The multi-layer segment graphs (`METRALE_DS41_STEP_GRAPH=1`,
123 /// `step_seg.rs`).
124 pub seg: Mutex<SegState>,
125 /// 2026-09-25: Every layer's role, filled as the layers are built; a segment
126 /// owner reads the roles of the layers its graph spans.
127 pub roles: Mutex<Vec<Option<LayerRole>>>,
128 /// 2026-09-25: `(layer, hash index)` of the engram layers.
129 pub engram_layers: Mutex<Vec<(usize, usize)>>,
130 /// 2026-09-25: `METRALE_DS41_PREDICT_TRACE`: the last three layers' MoE
131 /// inputs (bf16 `[3, hidden]`, slot `layer % 3`) and the trace file.
132 pub pred_x: DevicePtr,
133 pub pred_trace: Mutex<Option<std::io::BufWriter<std::fs::File>>>,
134}
135
136/// 2026-09-25: How the single-token step runs, read once.
137/// `METRALE_DS41_GRAPH_ORACLE=1` runs every layer both ways and logs whether
138/// the highway, the delayed mix and the hidden match bit for bit;
139/// `METRALE_DS41_GRAPH=1` replays the captured segments; anything else is the
140/// eager step.
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
142pub enum GraphMode {
143 Off,
144 On,
145 Oracle,
146}
147
148pub fn graph_mode() -> GraphMode {
149 static MODE: OnceLock<GraphMode> = OnceLock::new();
150 *MODE.get_or_init(|| {
151 let is_one = |k: &str| std::env::var(k).is_ok_and(|v| v == "1");
152 if is_one("METRALE_DS41_GRAPH_ORACLE") {
153 GraphMode::Oracle
154 } else if is_one("METRALE_DS41_GRAPH") {
155 GraphMode::On
156 } else {
157 GraphMode::Off
158 }
159 })
160}
161
162/// 2026-09-25: The pointers a layer's captured segments bake that are not the
163/// runtime's own. Compared on every step: a different set destroys the
164/// graphs, which are then captured again.
165#[derive(Clone, Copy, Debug, PartialEq, Eq)]
166struct Baked {
167 hidden: DevicePtr,
168 streams: DevicePtr,
169 normed: DevicePtr,
170 window: DevicePtr,
171 rows_b: Option<DevicePtr>,
172 attn_out: DevicePtr,
173 moe_out: DevicePtr,
174}
175
176/// 2026-09-25: One layer's captured single-token step, held in the sequence's
177/// layer state (it bakes the sequence's window ring and the kv source's cache).
178///
179/// Segment A = the attention site's mixes, collapse and norm, the attention
180/// (capturable layers only), `hc_post`, the ffn site's mixes, collapse and
181/// norm, the router GEMV. On kv and index source layers the attention runs
182/// eagerly between `a[0]` (through the norm) and `a[1]` (from `hc_post`).
183/// Host span: `MoeV41::stage_m1`. Segment B = the expert compute, `hc_post`,
184/// the delayed-mix copy, and on the last layer the final collapse.
185struct LayerGraphs {
186 a: [Option<GraphHandle>; 2],
187 b: Option<GraphHandle>,
188 baked: Baked,
189}
190
191impl LayerGraphs {
192 fn destroy(self, gpu: &dyn GpuBackend) -> Result<()> {
193 for g in self.a.into_iter().chain([self.b]).flatten() {
194 gpu.destroy_graph(g)?;
195 }
196 Ok(())
197 }
198}
199
200// 2026-09-25: SAFETY: the raw device pointers are allocated at load and not
201// reassigned behind the `Arc`; the mutable state is behind the mutexes, and
202// one sequence runs at a time.
203unsafe impl Send for V41Runtime {}
204unsafe impl Sync for V41Runtime {}
205
206pub struct V41LayerState {
207 pub attn: AttnV41LayerState,
208 graphs: Option<LayerGraphs>,
209}
210
211impl LayerState for V41LayerState {
212 fn as_any(&self) -> &dyn std::any::Any {
213 self
214 }
215 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
216 self
217 }
218}
219
220pub struct DeepSeekV41Layer {
221 pub idx: usize,
222 pub role: LayerRole,
223 pub rt: Arc<V41Runtime>,
224 pub attn_w: AttnV41LayerWeights,
225 pub moe_w: MoeV41LayerWeights,
226 /// 2026-09-25: The next layer's router. With expert prefetch armed
227 /// (`prefetch_k() > 0` and the reader pool), `MoeV41::forward` /
228 /// `stage_m1` run it on this layer's MoE input to start reading the
229 /// experts the next layer is predicted to ask for.
230 pub next_router: Option<RouterWeights>,
231 /// 2026-09-25: `Some(hash index)` on engram layers (`EngramHashTables::hash_index`).
232 pub engram_index: Option<usize>,
233 pub hc_attn: HcSiteWeights,
234 pub hc_ffn: HcSiteWeights,
235 pub attn_norm: DenseWeight,
236 pub ffn_norm: DenseWeight,
237 pub k_hc_expand: KernelHandle,
238 pub k_hc_post: KernelHandle,
239 pub k_mixes_dot: KernelHandle,
240 pub k_mixes_finish: KernelHandle,
241 pub k_collapse: KernelHandle,
242 /// 2026-09-25: `hc_v41_finish_collapse`: a site's mix finish and the
243 /// block's collapse in one launch over ceil(H / 256) blocks a token.
244 pub k_finish_collapse: KernelHandle,
245 /// 2026-09-25: `hc_v41_collapse_wide` and `hc_v41_post_wide`: the collapse
246 /// and `hc_post` over ceil(H / 256) blocks a token.
247 pub k_collapse_wide: KernelHandle,
248 pub k_post_wide: KernelHandle,
249 pub k_rms_norm: KernelHandle,
250}