Skip to main content

cubecl_runtime/tune/
record.rs

1//! What a tune leaves in the environment beside its answer.
2//!
3//! The table stores a key's winner and every candidate's result, ranked. What
4//! it cannot say is how the tune went: which candidates ran and in what order,
5//! what each cost to compile and benchmark, whether the tune stopped early,
6//! and how long the key took from its miss to its answer. A [`TuneRecord`] is
7//! that account, written to [`cubecl_environment::records`] once per tune.
8
9use crate::tune::{
10    AutotuneKey, AutotuneLogContext, AutotuneLogEvent, PersistentCacheKey, Placement, TuneCache,
11    TunePlan,
12};
13use alloc::string::String;
14use alloc::vec::Vec;
15use core::time::Duration;
16use cubecl_environment::records::{Record, RecordEffect, Span};
17use serde::{Deserialize, Serialize};
18
19/// How one autotune key was decided.
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub struct TuneRecord<K> {
22    /// The namespace of the table the answer is stored in.
23    pub table: String,
24    /// The table's entry the answer is stored under: the key, and the
25    /// checksum of the candidate list it was tuned under.
26    pub entry: PersistentCacheKey<K>,
27    /// The index of the candidate the key runs.
28    pub winner: usize,
29    /// The candidates that ran, in the order they ran.
30    pub trials: Vec<Trial>,
31    /// The candidate that met the time limit and ended the tune before the
32    /// rest of the plan ran.
33    pub short_circuit: Option<String>,
34    /// From the cache miss to the answer committed.
35    pub wall: Duration,
36    /// Whether the tune ran under a process mode that discards launches, where
37    /// they compile but do not execute. Stored under its earlier name, which
38    /// environments already saved hold.
39    #[serde(rename = "dry_run")]
40    pub launches_discarded: bool,
41    /// Whether the table took the answer. One that measured nothing, or that
42    /// was tuned with the cache disabled, answers this process alone: the
43    /// table holds another answer to the key, or none.
44    pub stored: bool,
45    /// Every candidate in the order the plan releases them, whether or not
46    /// it ran: the priorities that decided the order, read when the tune
47    /// began. Empty in a record written before plans were kept.
48    #[serde(default)]
49    pub plan: Vec<PlannedCandidate>,
50}
51
52/// One candidate's place in the plan of a tune.
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub struct PlannedCandidate {
55    /// The candidate's name.
56    pub name: String,
57    /// Its index in the tunable set, the one the table's winner names.
58    pub index: usize,
59    /// The batch the plan releases it in, counting from 0. A tune stops
60    /// after the first batch with a valid result, so later batches only run
61    /// when every earlier one failed. `None` when each of its groups gave it
62    /// a negative priority for this key.
63    pub batch: Option<usize>,
64    /// Its place in each group it belongs to; empty for a candidate in no
65    /// group, which trails every batch.
66    pub groups: Vec<Placement>,
67}
68
69/// One candidate's run within a tune.
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71pub struct Trial {
72    /// The candidate's name.
73    pub name: String,
74    /// Compiling and benchmarking it: from its first launch to its samples
75    /// resolved.
76    pub wall: Duration,
77}
78
79impl<K> Record for TuneRecord<K> {
80    const KIND: &'static str = "autotune";
81}
82
83/// A tune being recorded: stamped when it began, written when it ends. Every
84/// call is a no-op when the environment records nothing.
85#[derive(Debug)]
86pub(crate) struct TuneRecording<K> {
87    open: Option<OpenRecording<K>>,
88}
89
90/// What a [`TuneRecording`] holds while the environment records.
91#[derive(Debug)]
92struct OpenRecording<K> {
93    span: Span,
94    table: String,
95    entry: PersistentCacheKey<K>,
96    launches_discarded: bool,
97    plan: Vec<PlannedCandidate>,
98}
99
100/// What [`TuneRecording::finish`] needs to know of the answer.
101pub(crate) struct Answer {
102    pub winner: usize,
103    /// Whether the table took it: see [`TuneRecord::stored`].
104    pub stored: bool,
105}
106
107impl<K: AutotuneKey> TuneRecording<K> {
108    /// Begin recording the tune of `key` in `cache`'s table. The key is
109    /// cloned only when the environment records.
110    pub(crate) fn new(cache: &TuneCache<K>, key: &K, checksum: &str) -> Self {
111        let open = Span::new().map(|span| OpenRecording {
112            span,
113            table: cache.table().into(),
114            entry: PersistentCacheKey {
115                key: key.clone(),
116                checksum: checksum.into(),
117            },
118            launches_discarded: crate::execution::ProcessMode::current().stream_mode()
119                == crate::execution::StreamMode::Discard,
120            plan: Vec::new(),
121        });
122        Self { open }
123    }
124
125    /// Whether the tune is recorded, and so has to track its steps: the
126    /// record's trials are the ones the log context collects.
127    pub(crate) fn is_open(&self) -> bool {
128        self.open.is_some()
129    }
130
131    /// Keep the plan the tune runs, its candidates named by `names`.
132    pub(crate) fn plan(&mut self, plan: &TunePlan, names: &[&str]) {
133        if let Some(open) = &mut self.open {
134            open.plan = plan.account(names);
135        }
136    }
137
138    /// Write the record of the tune that just answered.
139    pub(crate) fn finish(self, answer: Answer, log_context: Option<&AutotuneLogContext>) {
140        let Some(open) = self.open else {
141            return;
142        };
143        // A tune the environment switched away from went with its session.
144        let Some(wall) = open.span.elapsed() else {
145            return;
146        };
147        let mut trials = Vec::new();
148        let mut short_circuit = None;
149        for event in log_context.iter().flat_map(|context| &context.events) {
150            match event {
151                AutotuneLogEvent::TuningStep(name, wall) => trials.push(Trial {
152                    name: name.clone(),
153                    wall: *wall,
154                }),
155                AutotuneLogEvent::ShortCircuit(name) => short_circuit = Some(name.clone()),
156            }
157        }
158        let record = TuneRecord {
159            table: open.table,
160            entry: open.entry,
161            winner: answer.winner,
162            trials,
163            short_circuit,
164            wall,
165            launches_discarded: open.launches_discarded,
166            stored: answer.stored,
167            plan: open.plan,
168        };
169        // A stored winner is the environment changing; an answer kept in
170        // memory is not.
171        let effect = if record.stored {
172            RecordEffect::Changed
173        } else {
174            RecordEffect::Observed
175        };
176        open.span.close(effect, &record);
177    }
178}