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