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}