spark_model/layers/deepseek_v41_layer.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2// provenance-id: 526f6e616c6420522e205374657369616b
3
4//! DeepSeek-V4.1 Flash: one transformer block on the generic model.
5//!
6//! The generic `TransformerModel` owns the embedding, the FP32 mHC highway
7//! (`BufferArena::hc_streams`, `[T, hc, H]`), the final norm and the LM head;
8//! each layer here does everything in between, in the reference's order
9//! (`deepseek_v41_ref::model::forward`, 39/39 against DeepSeek's golden):
10//!
11//! ```text
12//! layer 0 only: hc_expand(embedding -> streams)
13//! engram layers only: streams += engram(token n-grams) (EngramV41)
14//! attention: (pre_a, post_a, comb_a) = hc_mixes(streams, attn site)
15//! x = rmsnorm(collapse(streams, pre_prev)) <- DELAYED pre
16//! streams = hc_post(attn(x), streams, post_a, comb_a)
17//! ffn: (pre_f, post_f, comb_f) = hc_mixes(streams, ffn site)
18//! x = rmsnorm(collapse(streams, pre_a))
19//! streams = hc_post(moe(x), streams, post_f, comb_f)
20//! pre_prev = pre_f
21//! last layer only: hidden = collapse(streams, pre_prev) (no learned head)
22//! ```
23//!
24//! `pre_prev` starts as the one-hot on stream 0 (the reference's initial
25//! pre-mix) and travels with the sequence through [`V41Runtime`]. So do the
26//! shared attention slots, the engram hasher and the expert cache: V4.1's
27//! attention is shared ACROSS layers (four kv sources write, forty read), which
28//! no per-layer state can express, so one runtime object is shared by all
29//! forty layers through an `Arc` and guarded by mutexes. One sequence at a
30//! time; multi-sequence decode and the MODEL-level CUDA graph are declined
31//! through the layer hooks. The layer captures its own single-token step
32//! instead (`ATLAS_DS41_GRAPH=1`, see [`GraphMode`]): the host work in the
33//! middle of every layer (the routing download and the expert fetch; on the
34//! twelve kv/index source layers also the compressor group and the index
35//! top-k) splits the step into graph segments with the host spans between.
36//!
37//! Routed experts never sit in HBM: the cache is a page-locked, device-visible
38//! arena the GPU reads in place (`ExpertLru` + `ExpertSliceMap`), filled by
39//! pread from the seven shards; the engram tables are read by row (`EngramRowReader`).
40
41use std::sync::atomic::AtomicBool;
42use std::sync::{Arc, Mutex, OnceLock};
43
44use anyhow::Result;
45
46use spark_runtime::gpu::{DevicePtr, GpuBackend, GraphHandle, KernelHandle};
47use spark_runtime::weights::expert_stream::{
48 EngramRowReader, ExpertArena, ExpertLru, ExpertSliceMap,
49};
50
51use crate::layer::LayerState;
52use crate::layers::attn_v41::{
53 AttnV41, AttnV41Cfg, AttnV41LayerState, AttnV41LayerWeights, LayerRole, SharedV41,
54};
55use crate::layers::engram_v41::{EngramHashTables, EngramHasher, EngramV41};
56use crate::layers::moe_v41::{MoeV41, MoeV41Cfg, MoeV41LayerWeights, RouterWeights};
57use crate::layers::qwen3_attention::HcSiteWeights;
58use crate::weight_map::DenseWeight;
59
60mod graph;
61mod hc_launch;
62mod step;
63mod step_ffn;
64mod step_seg;
65mod step_seg_run;
66mod trait_impl;
67
68pub use step_seg::{SegState, step_graph_on};
69
70/// Everything the forty layers share for the ONE sequence in flight.
71pub struct V41Runtime {
72 pub attn_cfg: AttnV41Cfg,
73 pub moe_cfg: MoeV41Cfg,
74 pub attn: Mutex<AttnV41>,
75 pub moe: Mutex<MoeV41>,
76 pub engram: Mutex<EngramV41>,
77 pub lru: Mutex<ExpertLru>,
78 /// Kept alive for the cache's lifetime; freed with the runtime.
79 pub arena: ExpertArena,
80 pub slices: Arc<ExpertSliceMap>,
81 pub rows: EngramRowReader,
82 pub tables: Arc<EngramHashTables>,
83 pub hasher: Mutex<EngramHasher>,
84 pub shared: Mutex<SharedV41>,
85 /// The step's engram hashes `[tokens, n_engram_layers, cols]`, computed by
86 /// the first engram layer and reused by the second.
87 pub step_hashes: Mutex<Option<Vec<i64>>>,
88 /// The delayed `pre` mix `[max_tokens, hc]` f32 on the device.
89 pub pre_prev: DevicePtr,
90 /// `[max_tokens, (2 + hc) * hc]` f32 scratch for the hc mixes (dot -> finish)
91 pub mixes_s: DevicePtr,
92 pub reader_threads: usize,
93 pub n_layers: usize,
94 pub hc_mult: usize,
95 pub hidden: usize,
96 pub sinkhorn_iters: usize,
97 pub hc_eps: f32,
98 pub norm_eps: f32,
99 // per-step scratch, guarded by the same one-sequence contract
100 pub pre_a: DevicePtr,
101 pub pre_f: DevicePtr,
102 pub post_s: DevicePtr,
103 pub comb_s: DevicePtr,
104 pub attn_in: DevicePtr,
105 pub max_tokens: usize,
106 /// per-step totals across layers, printed by the last layer when ATLAS_DS41_DIAG=1
107 pub step_moe: Mutex<crate::layers::moe_v41::MoeV41Timing>,
108 pub step_attn_ms: Mutex<f64>,
109 pub step_engram_ms: Mutex<f64>,
110 pub step_start: Mutex<Option<std::time::Instant>>,
111 /// Set when a capture failed: every segment from then on runs eagerly
112 /// (graphs already captured keep replaying; they are valid).
113 pub graph_disabled: AtomicBool,
114 /// The whole-step segment graphs (`ATLAS_DS41_STEP_GRAPH=1`, `step_seg.rs`).
115 pub seg: Mutex<SegState>,
116 /// Every layer's role, filled as the layers are built (the segment owner
117 /// reads the roles of the layers its graph spans).
118 pub roles: Mutex<Vec<Option<LayerRole>>>,
119 /// `(layer, hash index)` of the engram layers.
120 pub engram_layers: Mutex<Vec<(usize, usize)>>,
121 /// `ATLAS_DS41_PREDICT_TRACE`: the last three layers' MoE inputs
122 /// (`[3, hidden]` bf16, slot `layer % 3`) and the trace file.
123 pub pred_x: DevicePtr,
124 pub pred_trace: Mutex<Option<std::io::BufWriter<std::fs::File>>>,
125}
126
127/// How the single-token step runs. `ATLAS_DS41_GRAPH_ORACLE=1` runs every
128/// layer BOTH ways and compares the highway bit for bit; `ATLAS_DS41_GRAPH=1`
129/// replays the captured segments; anything else is the eager step. Read once.
130#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131pub enum GraphMode {
132 Off,
133 On,
134 Oracle,
135}
136
137pub fn graph_mode() -> GraphMode {
138 static MODE: OnceLock<GraphMode> = OnceLock::new();
139 *MODE.get_or_init(|| {
140 let is_one = |k: &str| std::env::var(k).is_ok_and(|v| v == "1");
141 if is_one("ATLAS_DS41_GRAPH_ORACLE") {
142 GraphMode::Oracle
143 } else if is_one("ATLAS_DS41_GRAPH") {
144 GraphMode::On
145 } else {
146 GraphMode::Off
147 }
148 })
149}
150
151/// The pointers a layer's captured segments bake that are not the runtime's
152/// own (those live as long as the runtime). Checked on every replay: a
153/// different set means the graphs describe other buffers and are recaptured.
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155struct Baked {
156 hidden: DevicePtr,
157 streams: DevicePtr,
158 normed: DevicePtr,
159 window: DevicePtr,
160 /// the kv source's latent cache the captured `sparse_attn` reads
161 rows_b: Option<DevicePtr>,
162 attn_out: DevicePtr,
163 moe_out: DevicePtr,
164}
165
166/// One layer's captured single-token step, per sequence (it bakes the
167/// sequence's window ring and the kv source's cache).
168///
169/// Segment A = the attention site's mixes, collapse and norm, the attention
170/// (capturable layers only), `hc_post`, the ffn site's mixes, collapse and
171/// norm, the router GEMV. On the twelve kv/index source layers the attention
172/// runs eagerly between `a[0]` (through the norm) and `a[1]` (from `hc_post`).
173/// Host span: the routing download, the expert fetch, the plan uploads.
174/// Segment B = the expert compute, `hc_post`, the delayed-mix copy, and on
175/// the last layer the final collapse.
176struct LayerGraphs {
177 a: [Option<GraphHandle>; 2],
178 b: Option<GraphHandle>,
179 baked: Baked,
180}
181
182impl LayerGraphs {
183 fn destroy(self, gpu: &dyn GpuBackend) -> Result<()> {
184 for g in self.a.into_iter().chain([self.b]).flatten() {
185 gpu.destroy_graph(g)?;
186 }
187 Ok(())
188 }
189}
190
191// SAFETY: every raw device/host pointer here names memory the runtime owns
192// for its whole life; access is serialised by the mutexes and the
193// one-sequence contract.
194unsafe impl Send for V41Runtime {}
195unsafe impl Sync for V41Runtime {}
196
197pub struct V41LayerState {
198 pub attn: AttnV41LayerState,
199 graphs: Option<LayerGraphs>,
200}
201
202impl LayerState for V41LayerState {
203 fn as_any(&self) -> &dyn std::any::Any {
204 self
205 }
206 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
207 self
208 }
209}
210
211pub struct DeepSeekV41Layer {
212 pub idx: usize,
213 pub role: LayerRole,
214 pub rt: Arc<V41Runtime>,
215 pub attn_w: AttnV41LayerWeights,
216 pub moe_w: MoeV41LayerWeights,
217 /// The next layer's router, run on this layer's MoE input to predict
218 /// (and start reading) the experts the next layer will ask for.
219 pub next_router: Option<RouterWeights>,
220 /// `Some(index into engram_layer_ids)` on engram layers.
221 pub engram_index: Option<usize>,
222 pub hc_attn: HcSiteWeights,
223 pub hc_ffn: HcSiteWeights,
224 pub attn_norm: DenseWeight,
225 pub ffn_norm: DenseWeight,
226 pub k_hc_expand: KernelHandle,
227 pub k_hc_post: KernelHandle,
228 pub k_mixes_dot: KernelHandle,
229 pub k_mixes_finish: KernelHandle,
230 pub k_collapse: KernelHandle,
231 /// the site's finish and the block's collapse in one wide launch
232 pub k_finish_collapse: KernelHandle,
233 /// hc_v41_collapse / hc_post over ceil(H/256) blocks a token
234 pub k_collapse_wide: KernelHandle,
235 pub k_post_wide: KernelHandle,
236 pub k_rms_norm: KernelHandle,
237}