metrale_scheduler/plan.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: What the core asks the device to run ([`StepPlan`]) and what comes back
4//! ([`StepResult`]). Generic over the device's logits handle `P`.
5//!
6//! Owner: scheduler.
7//! Invariants: none beyond the types.
8
9/// 2026-09-25: A short label for traces.
10pub trait Label {
11 fn label(&self) -> String;
12}
13
14/// 2026-09-25: Where a fed decode row's input token comes from.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum FeedSource {
17 /// 2026-09-25: The previous step's argmax for row `from_row`, read from the device
18 /// feed cell that step wrote; the host does not know the id at launch.
19 Feed { from_row: u32 },
20 /// 2026-09-25: A token id the host supplies.
21 Host(u32),
22}
23
24/// 2026-09-25: The two token ids the host would re-pick around for a row on the
25/// argmax fast path (`u32::MAX` = none): the closed-thinking mask.
26pub type RowMask = [u32; 2];
27
28/// 2026-09-25: How the logits of a decode step come back to the host.
29pub enum Readback<'a> {
30 /// 2026-09-25: One argmax id per row, computed on the device.
31 Argmax,
32 /// 2026-09-25: The device argmax with the per-row two-id mask applied exactly as
33 /// the host re-pick would (see `argmax_feed.cu`); the ids also land
34 /// in the device feed cells for a following fed step.
35 ArgmaxMasked { masks: Vec<RowMask> },
36 /// 2026-09-25: The whole logits block, copied into `into` (resized by the router to
37 /// `rows * vocab * elem_bytes`; the core samples over it).
38 HostLogits { into: &'a mut Vec<u8> },
39}
40
41impl std::fmt::Debug for Readback<'_> {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 Self::Argmax => f.write_str("Argmax"),
45 Self::ArgmaxMasked { masks } => write!(f, "ArgmaxMasked{{n={}}}", masks.len()),
46 Self::HostLogits { .. } => f.write_str("HostLogits"),
47 }
48 }
49}
50
51/// 2026-09-25: The DFlash context commit a decode step performs between its forward and
52/// its readback (the drafter's view of the serial tokens).
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum DecodeCtxCommit {
55 None,
56 /// 2026-09-25: `commit_ctx(row, 1, seq_len - 1, row)` for every row.
57 Unified,
58 /// 2026-09-25: `dflash_serial_ctx_append(row 0)` (single-row batches only).
59 SerialAppend,
60}
61
62/// 2026-09-25: One device step. The sequence rows it advances travel beside it as
63/// `&mut S`.
64#[derive(Debug)]
65pub enum StepPlan<'a> {
66 /// 2026-09-25: Batched decode of one token per row, the context commit, then the
67 /// readback.
68 Decode {
69 /// 2026-09-25: The pending decode input of each row, in row order.
70 tokens: Vec<u32>,
71 ctx: DecodeCtxCommit,
72 readback: Readback<'a>,
73 },
74 /// 2026-09-25: A decode step launched while the previous one is still in flight:
75 /// each row's input is resolved on the device from `sources`, the
76 /// readback is the masked feed argmax, and the host bookkeeping the
77 /// plain step performs at launch (`tokens.push`, `seq_len += 1`) is
78 /// deferred to the core, which applies it once the previous step is
79 /// committed. The context commit runs between the forward and the
80 /// readback, as it does for the plain step.
81 DecodeFed {
82 sources: Vec<FeedSource>,
83 masks: Vec<RowMask>,
84 ctx: DecodeCtxCommit,
85 },
86}
87
88impl Label for StepPlan<'_> {
89 fn label(&self) -> String {
90 match self {
91 Self::Decode {
92 tokens,
93 ctx,
94 readback,
95 } => format!(
96 "Decode{{n={}, ctx={ctx:?}, readback={readback:?}}}",
97 tokens.len()
98 ),
99 Self::DecodeFed { sources, ctx, .. } => {
100 let fed = sources
101 .iter()
102 .filter(|s| matches!(s, FeedSource::Feed { .. }))
103 .count();
104 format!("DecodeFed{{n={}, fed={fed}, ctx={ctx:?}}}", sources.len())
105 }
106 }
107 }
108}
109
110/// 2026-09-25: The rows of a decode step after the readback.
111#[derive(Debug)]
112pub enum DecodeRows {
113 /// 2026-09-25: The device argmax per row.
114 Tokens(Vec<u32>),
115 /// 2026-09-25: The logits block was copied into the plan's buffer; `elem_bytes` is
116 /// the element width the model writes (2 = BF16, 4 = FP32).
117 HostLogits { elem_bytes: usize },
118}
119
120#[derive(Debug)]
121pub enum StepOutcome<P> {
122 Decode {
123 /// 2026-09-25: The device logits the step produced, for a later
124 /// [`crate::Effect::ReadLogits`].
125 logits: P,
126 rows: DecodeRows,
127 },
128}
129
130#[derive(Debug)]
131pub struct StepResult<P> {
132 pub ticket: crate::Ticket,
133 pub outcome: StepOutcome<P>,
134}