metrale_model_layers/circuit_exec/program.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-28: A compiled circuit program: the straight-line launches of one plan, with every
4//! host decision (kernel, grid, sizes, pointers into the model's fixed buffers and the
5//! workspace) made at compile time.
6//!
7//! Owner: model-layers circuit executor.
8//! Invariants:
9//! - A step reads only [`StepEnv`]: the stream, each row's GDN state pointers and the
10//! block-table width uploaded for the step. Everything else a launch uses was fixed when it
11//! was compiled, so a CUDA-graph capture of [`Program::run`] replays correctly for any step
12//! with the same state pointers (the graph caches key on the sequence's slot).
13//! - `run` issues no synchronize and no device-to-host copy, so it may be captured.
14//! - A program holds exactly `FusionPlan::launches` kernel launches and `FusionPlan::copies`
15//! copies.
16
17use anyhow::{Context, Result};
18use metrale_circuit::Mode;
19use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend};
20
21/// 2026-09-29: The most rollback points an MTP verify writes: one per row but the last, at
22/// K = 4.
23pub const MAX_VERIFY_STEPS: usize = 3;
24
25/// 2026-09-28: One sequence's recurrent state in one GatedDeltaNet layer.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct GdnState {
28 /// 2026-09-28: The h state (FP32).
29 pub h: DevicePtr,
30 /// 2026-09-28: The conv1d window.
31 pub conv: DevicePtr,
32 /// 2026-09-29: The h state after verify row `t` (`SsmLayerState::h_state_intermediates`);
33 /// NULL where the sequence has none. Only a verify reads them.
34 pub h_steps: [DevicePtr; MAX_VERIFY_STEPS],
35 /// 2026-09-29: The conv window after verify row `t`, indexed like `h_steps`.
36 pub conv_steps: [DevicePtr; MAX_VERIFY_STEPS],
37}
38
39/// 2026-09-28: What varies between two runs of one program.
40pub struct StepEnv<'a> {
41 /// 2026-09-28: The backend.
42 pub gpu: &'a dyn GpuBackend,
43 /// 2026-09-28: The stream the step runs (and is captured) on.
44 pub stream: u64,
45 /// 2026-09-28: Per layer, each row's GDN state (row `i` = sequence `i`, padding rows
46 /// included); empty for a layer without one.
47 pub gdn: &'a [Vec<GdnState>],
48 /// 2026-09-28: `AttnMetadataDev::max_blocks_per_seq` of this step.
49 pub max_blocks_per_seq: u32,
50}
51
52impl StepEnv<'_> {
53 /// 2026-09-28: Row `row`'s GDN state in layer `layer`; an error when there is none.
54 pub fn gdn_state(&self, layer: usize, row: usize) -> Result<GdnState> {
55 self.gdn
56 .get(layer)
57 .and_then(|l| l.get(row))
58 .copied()
59 .with_context(|| format!("layer {layer} has no GDN state for row {row}"))
60 }
61}
62
63/// 2026-09-28: The body of one launch.
64pub(crate) type RunFn = Box<dyn Fn(&StepEnv<'_>) -> Result<()> + Send + Sync>;
65
66/// 2026-09-29: What a launch puts on the stream.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum LaunchKind {
69 /// 2026-09-29: A kernel.
70 Kernel,
71 /// 2026-09-29: A copy-engine transfer.
72 Copy,
73}
74
75/// 2026-09-28: One kernel launch or copy.
76pub struct Launch {
77 /// 2026-09-28: Index of the plan group it belongs to.
78 pub group: usize,
79 /// 2026-09-28: `module::func` it launches, or `copy` for a transfer.
80 pub kernel: String,
81 /// 2026-09-29: Kernel or copy.
82 pub kind: LaunchKind,
83 pub(crate) run: RunFn,
84}
85
86/// 2026-09-28: The compiled launches of one plan.
87pub struct Program {
88 /// 2026-09-28: The plan's mode.
89 pub mode: Mode,
90 /// 2026-09-28: The plan's padded rows.
91 pub rows: u64,
92 /// 2026-09-28: The plan's digest (`metrale_circuit::digest::plan_digest`).
93 pub plan_digest: String,
94 /// 2026-09-28: The launches, in order.
95 pub launches: Vec<Launch>,
96}
97
98impl Program {
99 /// 2026-09-28: Issue every launch on `env.stream`.
100 pub fn run(&self, env: &StepEnv<'_>) -> Result<()> {
101 for l in &self.launches {
102 (l.run)(env).with_context(|| format!("circuit group {} ({})", l.group, l.kernel))?;
103 }
104 Ok(())
105 }
106}
107
108/// 2026-09-29: A program another component runs in place of its own forward (the MTP draft
109/// head's step), without GDN state. 2026-09-30: One program per row count: one row for a
110/// single sequence's draft, n rows for the batched propose's.
111pub trait DraftRunner: Send + Sync {
112 /// 2026-09-30: Whether a program for `rows` draft rows was compiled.
113 fn serves(&self, rows: u64) -> bool;
114 /// 2026-09-29: Issue the `rows`-row program on `stream` for a step whose block table is
115 /// `max_blocks_per_seq` wide; an error when none was compiled for `rows`.
116 fn run_draft(
117 &self,
118 gpu: &dyn GpuBackend,
119 stream: u64,
120 rows: u64,
121 max_blocks_per_seq: u32,
122 ) -> Result<()>;
123}
124
125/// 2026-09-30: The draft head's compiled programs, one per row count, ascending (one row
126/// first).
127pub struct DraftPrograms {
128 pub programs: Vec<(Program, metrale_circuit::FusionPlan)>,
129}
130
131impl DraftRunner for DraftPrograms {
132 fn serves(&self, rows: u64) -> bool {
133 self.programs.iter().any(|(p, _)| p.rows == rows)
134 }
135
136 fn run_draft(
137 &self,
138 gpu: &dyn GpuBackend,
139 stream: u64,
140 rows: u64,
141 max_blocks_per_seq: u32,
142 ) -> Result<()> {
143 let (p, _) = self
144 .programs
145 .iter()
146 .find(|(p, _)| p.rows == rows)
147 .with_context(|| format!("no draft program was compiled for {rows} rows"))?;
148 p.run(&StepEnv {
149 gpu,
150 stream,
151 gdn: &[],
152 max_blocks_per_seq,
153 })
154 }
155}