metrale_model_arch/deepseek_v41_layer/step_seg.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// provenance-id: 526f6e616c6420522e205374657369616b
3
4//! 2026-09-25: The single-token step as a few CUDA graphs spanning layers
5//! (`METRALE_DS41_STEP_GRAPH=1`, which needs `METRALE_DS41_DEVICE_ROUTE=1`).
6//!
7//! With the routing on the device and every engram layer's rows uploaded up
8//! front, the host work left in a decode step is the attention of the kv and
9//! index source layers (the index score read-back and the host top-k, plus
10//! the compressor's position parity). The step is captured as segments
11//! between those layers: layer 0's segment runs to the first source layer's
12//! attention input; a source layer's segment runs from its ffn to the next
13//! source layer's attention input; the last runs to the final collapse. Each
14//! launch is followed by one read-back of the routing headers of the
15//! segment's layers.
16//!
17//! The protocol. A segment's graph copies a snapshot before every layer's
18//! ffn (`step_seg_run::SAVE_N` buffers), and a segment is launched only at
19//! its owner's step, never at its close. The owner reads its layers' routing
20//! headers once after the launch. Inside a replay nothing fetches: the
21//! selection kernel points an absent expert at slot 0 and flags the layer,
22//! so every layer after it routed on a wrong input. The first flagged layer
23//! k (or the first whose fetch moved a cache slot) has real picks: its
24//! misses are fetched, its snapshot restored, layer k runs from its ffn on
25//! with the eager step's code (`ffn_eager`), the layers after it to the
26//! segment's end run the whole eager step, and the layers before k stand.
27//! The capture token records the bodies on a second stream (the capture is
28//! relaxed) while every layer runs eagerly on the compute stream, so the
29//! capture token's result is the eager path's and the first replay is the
30//! next token. An owner runs its own attention input eagerly whenever the
31//! segment before it did not replay through to it. A failed capture turns
32//! the mode off for the process; the token in flight finishes eagerly.
33//!
34//! In this mode every layer's `step` lands here; the owner of a segment
35//! (layer 0, or a kv/index source layer) prepares, launches and runs the
36//! protocol, and the layers a replayed segment covered return at once
37//! (`skip_until`).
38//!
39//! Owner: model-arch, DeepSeek-V4.1.
40//! Invariants: none beyond the types.
41
42use std::sync::OnceLock;
43
44use anyhow::{Context, Result, ensure};
45use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, GraphHandle};
46
47use super::step::{trace_hash, trace_on};
48use super::step_seg_run::SAVE_N;
49use super::{DeepSeekV41Layer, V41LayerState};
50use crate::attn_v41::LayerRole;
51use crate::moe_v41::MoeV41;
52use metrale_model_layers::layer::ForwardContext;
53
54/// 2026-09-25: `METRALE_DS41_STEP_GRAPH=1`, read once.
55pub fn step_graph_on() -> bool {
56 static V: OnceLock<bool> = OnceLock::new();
57 *V.get_or_init(|| std::env::var("METRALE_DS41_STEP_GRAPH").is_ok_and(|v| v == "1"))
58}
59
60/// 2026-09-25: One captured segment. `end` is the layer whose attention input
61/// closes it (`n_layers` for the last one); layers below it are covered.
62/// `moe_layers` ran their MoE inside it (their headers are read after a
63/// launch). `classes` are the selection classes (ratio > 0, ratio 0) its
64/// attention bodies read, and `rows_b` the compressed rows they bake.
65pub struct SegGraph {
66 pub(super) g: GraphHandle,
67 pub(super) end: usize,
68 pub(super) moe_layers: Vec<u32>,
69 pub(super) classes: (bool, bool),
70 pub(super) rows_b: Option<DevicePtr>,
71}
72
73/// 2026-09-25: An open capture: the owner and the MoE layers appended so far.
74pub struct SegCapture {
75 pub(super) owner: usize,
76 pub(super) moe_layers: Vec<u32>,
77 pub(super) classes: (bool, bool),
78 pub(super) rows_b: Option<DevicePtr>,
79}
80
81/// 2026-09-25: The segment-graph state shared by every layer.
82/// - `disabled`: a capture failed; the mode is off for the process.
83/// - `skip_until`: layers below it were covered by the segment just launched.
84/// - `eager_until`: layers below it run the whole eager step this token.
85/// - `ffn_only_at`: the layer whose snapshot the owner restored; it runs from
86/// its ffn on.
87/// - `graphs`: by owner layer.
88/// - `baked`: the buffers every graph bakes (hidden, streams, normed).
89/// - `cap_stream`, `cap_side`, `ev_fork`, `ev_join`: the stream a capture
90/// token records on, the side stream the recorded shared expert forks
91/// onto, and the fork / join events.
92/// - `save`: per layer, the snapshot before its ffn (`step_seg_run::SAVE_N`
93/// buffers: streams, the attention output, `pre_a`, `post_s`, `comb_s`).
94/// - `windows`: every layer's window ring as last seen (`note_window`), which
95/// the captured attention bodies bake.
96/// - `stale`: a window ring changed since the graphs were captured; layer 0
97/// destroys them all.
98/// - `launches`, `reruns`: graph launches and miss falls this process, for
99/// the log.
100#[derive(Default)]
101pub struct SegState {
102 pub disabled: bool,
103 pub skip_until: usize,
104 pub eager_until: usize,
105 pub ffn_only_at: Option<usize>,
106 pub capturing: Option<SegCapture>,
107 pub graphs: Vec<Option<SegGraph>>,
108 pub baked: Option<[DevicePtr; 3]>,
109 pub cap_stream: Option<u64>,
110 pub cap_side: u64,
111 pub ev_fork: u64,
112 pub ev_join: u64,
113 pub save: Vec<[DevicePtr; SAVE_N]>,
114 pub windows: Vec<DevicePtr>,
115 pub stale: bool,
116 pub launches: u64,
117 pub reruns: u64,
118}
119
120impl DeepSeekV41Layer {
121 fn role_of(&self, l: usize) -> Result<LayerRole> {
122 self.rt.roles.lock().unwrap()[l].context("step graph: a layer without a role")
123 }
124
125 fn capturable(r: &LayerRole) -> bool {
126 !r.is_kv_source && !r.is_index_source
127 }
128
129 /// 2026-09-25: The segment an owner at `s` spans: its end and the selection
130 /// classes of the capturable layers in it.
131 fn geometry(&self, s: usize) -> Result<(usize, (bool, bool))> {
132 let n = self.rt.n_layers;
133 let mut classes = (false, false);
134 let mut end = n;
135 for l in s..n {
136 let r = self.role_of(l)?;
137 if l != s && !Self::capturable(&r) {
138 end = l;
139 break;
140 }
141 if Self::capturable(&r) {
142 if r.ratio > 0 {
143 classes.0 = true;
144 } else {
145 classes.1 = true;
146 }
147 }
148 }
149 Ok((end, classes))
150 }
151
152 /// 2026-09-25: With `METRALE_DS41_STEP_GRAPH=1` every step (prefill too)
153 /// notes this layer's window ring: the captured attention bodies bake it,
154 /// and a new sequence brings new rings, so a change marks the graphs stale.
155 pub(super) fn note_window(&self, window: DevicePtr) {
156 let mut seg = self.rt.seg.lock().unwrap();
157 let n = self.rt.n_layers;
158 if seg.windows.len() != n {
159 seg.windows = vec![DevicePtr(0); n];
160 }
161 if seg.windows[self.idx] != window {
162 seg.windows[self.idx] = window;
163 seg.stale = true;
164 }
165 }
166
167 /// 2026-09-25: The step of one layer in segment-graph mode.
168 pub(super) fn step_seg(
169 &self,
170 hidden: DevicePtr,
171 start_pos: usize,
172 st: &mut V41LayerState,
173 ctx: &ForwardContext,
174 stream: u64,
175 ) -> Result<()> {
176 let rt = &self.rt;
177 let gpu = ctx.gpu;
178 let streams = ctx.buffers.hc_streams();
179 let normed = ctx.buffers.norm_output();
180 let n = rt.n_layers;
181 let mut seg = rt.seg.lock().unwrap();
182 if seg.graphs.len() != n {
183 seg.graphs = (0..n).map(|_| None).collect();
184 }
185 if self.idx == 0 {
186 // 2026-09-25: A new step: the baked buffers, the skip and fallback marks.
187 seg.skip_until = 0;
188 seg.eager_until = 0;
189 seg.ffn_only_at = None;
190 let baked = [hidden, streams, normed];
191 if seg.baked != Some(baked) || seg.stale {
192 if seg.baked.is_some() && seg.graphs.iter().any(Option::is_some) {
193 tracing::info!(
194 "DS41 step graph: {}; recapturing every segment",
195 if seg.stale {
196 "a sequence's window rings changed"
197 } else {
198 "buffers changed"
199 }
200 );
201 }
202 for g in seg.graphs.iter_mut().flat_map(Option::take) {
203 gpu.destroy_graph(g.g)?;
204 }
205 seg.baked = Some(baked);
206 seg.stale = false;
207 }
208 if seg.cap_stream.is_none() {
209 seg.cap_stream = Some(
210 gpu.create_stream()
211 .context("step graph: the recording stream")?,
212 );
213 seg.cap_side = gpu.create_stream().context("step graph: the side stream")?;
214 seg.ev_fork = gpu.create_event()?;
215 seg.ev_join = gpu.create_event()?;
216 }
217 if seg.save.len() != n {
218 self.alloc_save(gpu, &mut seg)?;
219 }
220 }
221 if self.idx < seg.skip_until {
222 return Ok(());
223 }
224 if seg.ffn_only_at == Some(self.idx) {
225 // 2026-09-25: The owner restored this layer's snapshot: from the ffn on.
226 seg.ffn_only_at = None;
227 drop(seg);
228 return self.ffn_eager(hidden, 1, start_pos, ctx, stream);
229 }
230 if self.idx < seg.eager_until {
231 drop(seg);
232 return self.eager_layer(hidden, start_pos, st, ctx, stream);
233 }
234 let role = self.role_of(self.idx)?;
235 let owner = self.idx == 0 || !Self::capturable(&role);
236 let cs = seg.cap_stream.context("step graph: no recording stream")?;
237
238 // 2026-09-25: A layer inside an open capture: its body is recorded on
239 // the recording stream while the layer runs eagerly on the compute stream.
240 if let Some(mut cap) = seg.capturing.take() {
241 ensure!(
242 cap.owner < self.idx,
243 "step graph: capture owned by a later layer"
244 );
245 if owner {
246 // 2026-09-25: The closer: its attention input ends the segment;
247 // it runs that input eagerly below, as any owner a replay did
248 // not reach.
249 let r = (|| -> Result<()> {
250 self.engram_apply(gpu, streams, cs)?;
251 self.seg_attn_in(gpu, streams, hidden, normed, cs)
252 })();
253 self.close_or_disable(gpu, &mut seg, cap, self.idx, cs, r);
254 } else {
255 let arena = MoeV41::arena_of(&rt.lru.lock().unwrap());
256 let save = seg.save[self.idx];
257 let fork = (seg.cap_side, seg.ev_fork, seg.ev_join);
258 let r = (|| -> Result<()> {
259 let moe = rt.moe.lock().unwrap();
260 self.body_attn(gpu, st, hidden, streams, normed, cap.rows_b, cs)?;
261 self.save_nodes(gpu, &save, hidden, streams, cs)?;
262 self.body_ffn(gpu, &moe, arena, fork, hidden, streams, normed, cs)
263 })();
264 cap.moe_layers.push(self.idx as u32);
265 if r.is_err() || self.idx + 1 == n {
266 self.close_or_disable(gpu, &mut seg, cap, n, cs, r);
267 } else {
268 seg.capturing = Some(cap);
269 }
270 drop(seg);
271 return self.eager_layer(hidden, start_pos, st, ctx, stream);
272 }
273 }
274 if seg.disabled {
275 drop(seg);
276 return self.eager_layer(hidden, start_pos, st, ctx, stream);
277 }
278 ensure!(
279 owner,
280 "step graph: layer {} reached outside any segment",
281 self.idx
282 );
283
284 // 2026-09-25: The owner: its attention input if no replay delivered
285 // it, then prepare, then replay or capture the segment from here.
286 if self.idx > 0 && seg.skip_until != self.idx {
287 self.engram_apply(gpu, streams, stream)?;
288 self.seg_attn_in(gpu, streams, hidden, normed, stream)?;
289 }
290 let (end, classes) = self.geometry(self.idx)?;
291 let rows_b = self.prepare(gpu, st, ctx, start_pos, classes, normed, stream)?;
292 if let Some(sg) = seg.graphs[self.idx].as_ref()
293 && (sg.end != end || sg.classes != classes || sg.rows_b != rows_b)
294 {
295 tracing::warn!(
296 "DS41 step graph: segment {} bakes other rows; recapturing",
297 self.idx
298 );
299 if let Some(sg) = seg.graphs[self.idx].take() {
300 gpu.destroy_graph(sg.g)?;
301 }
302 }
303 if let Some(sg) = seg.graphs[self.idx].as_ref() {
304 let (gh, layers) = (sg.g, sg.moe_layers.clone());
305 let first_miss = self.launch_once(gpu, gh, &layers, stream)?;
306 seg.launches += 1;
307 let Some(k) = first_miss else {
308 seg.skip_until = end;
309 if trace_on() {
310 // 2026-09-25: `METRALE_DS41_TRACE=1`: the highway after the
311 // segment, to diff against the eager trace's line for layer end-1.
312 gpu.synchronize(stream)?;
313 tracing::info!(
314 "DS41 trace pos={start_pos} seg={}..{end} hwy={:016x} hidden={:016x}",
315 self.idx,
316 trace_hash(gpu, streams, rt.hc_mult * rt.hidden * 4),
317 trace_hash(gpu, hidden, rt.hidden * 2)
318 );
319 }
320 if end == n {
321 self.log_step(&seg, start_pos);
322 }
323 return Ok(());
324 };
325 // 2026-09-25: Layer k's picks were real and are resident now: its
326 // snapshot back, k from its ffn on and k+1..end whole, eagerly.
327 seg.reruns += 1;
328 let save = seg.save[k];
329 self.restore(gpu, &save, hidden, streams, stream)?;
330 seg.eager_until = end;
331 if k == self.idx {
332 drop(seg);
333 return self.ffn_eager(hidden, 1, start_pos, ctx, stream);
334 }
335 seg.skip_until = k;
336 seg.ffn_only_at = Some(k);
337 return Ok(());
338 }
339 // 2026-09-25: No graph yet: record from here on the recording stream
340 // while this token runs eagerly on the compute stream.
341 let arena = MoeV41::arena_of(&rt.lru.lock().unwrap());
342 let save = seg.save[self.idx];
343 let fork = (seg.cap_side, seg.ev_fork, seg.ev_join);
344 let r = gpu
345 .begin_capture(cs)
346 .context("step graph: begin_capture")
347 .and_then(|_| {
348 let moe = rt.moe.lock().unwrap();
349 if self.idx == 0 {
350 self.body_attn(gpu, st, hidden, streams, normed, rows_b, cs)?;
351 }
352 self.save_nodes(gpu, &save, hidden, streams, cs)?;
353 self.body_ffn(gpu, &moe, arena, fork, hidden, streams, normed, cs)
354 });
355 let cap = SegCapture {
356 owner: self.idx,
357 moe_layers: vec![self.idx as u32],
358 classes,
359 rows_b,
360 };
361 if r.is_err() || self.idx + 1 == n {
362 self.close_or_disable(gpu, &mut seg, cap, n, cs, r);
363 } else {
364 seg.capturing = Some(cap);
365 }
366 drop(seg);
367 if self.idx == 0 {
368 self.eager_layer(hidden, start_pos, st, ctx, stream)
369 } else {
370 // 2026-09-25: A source-layer owner: its attention ran eagerly in
371 // `prepare`.
372 self.ffn_eager(hidden, 1, start_pos, ctx, stream)
373 }
374 }
375
376 /// 2026-09-25: End the open capture as the segment of `cap.owner` ending at
377 /// `end`, or, when the recording failed (`r`) or the capture cannot end,
378 /// turn the mode off: the token in flight finishes eagerly.
379 fn close_or_disable(
380 &self,
381 gpu: &dyn GpuBackend,
382 seg: &mut SegState,
383 cap: SegCapture,
384 end: usize,
385 cs: u64,
386 r: Result<()>,
387 ) {
388 match r.and_then(|_| gpu.end_capture(cs).context("step graph: end_capture")) {
389 Ok(g) => {
390 tracing::info!(
391 "DS41 step graph: segment {} = layers {}..{} captured ({} MoE layers)",
392 cap.owner,
393 cap.owner,
394 end,
395 cap.moe_layers.len()
396 );
397 seg.graphs[cap.owner] = Some(SegGraph {
398 g,
399 end,
400 moe_layers: cap.moe_layers,
401 classes: cap.classes,
402 rows_b: cap.rows_b,
403 });
404 }
405 Err(e) => {
406 gpu.abort_capture_if_active(cs);
407 seg.disabled = true;
408 seg.capturing = None;
409 seg.eager_until = self.rt.n_layers;
410 tracing::warn!(
411 "DS41 L{}: step graph capture failed ({e:#}); the mode is off, the step runs eagerly",
412 self.idx
413 );
414 }
415 }
416 }
417
418 fn log_step(&self, seg: &SegState, start_pos: usize) {
419 if step_log_on() {
420 tracing::info!(
421 "DS41 step graph pos {start_pos}: {} launches, {} miss falls so far",
422 seg.launches,
423 seg.reruns
424 );
425 }
426 }
427}
428
429/// 2026-09-25: `METRALE_DS41_STEP_GRAPH_LOG=1`, read once: the running counts
430/// whenever the last segment replays without a miss, and a line per segment miss.
431pub(super) fn step_log_on() -> bool {
432 static V: OnceLock<bool> = OnceLock::new();
433 *V.get_or_init(|| std::env::var("METRALE_DS41_STEP_GRAPH_LOG").is_ok_and(|v| v == "1"))
434}