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}