1use serde::{Deserialize, Serialize};
4
5use super::memory::{MemoryAction, MemoryCategory};
6use super::schema::{GradientState, RunOutcome, SpanKind, TraceRunMeta, SCHEMA};
7use crate::phase::ExecutionStep;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(tag = "kind", rename_all = "snake_case")]
12pub enum TraceEvent {
13 Meta {
15 schema: String,
16 #[serde(flatten)]
17 run: Box<TraceRunMeta>,
18 },
19 SpanStart(SpanStartEvent),
20 SpanEnd(SpanEndEvent),
21 Op(OpEvent),
22 Tensor(TensorEvent),
23 TensorStats(TensorStatsEvent),
24 Memory(MemoryEvent),
25 DeviceMemory(DeviceMemoryEvent),
26 DeviceInterval(DeviceIntervalEvent),
27 Gradient(GradientEvent),
28 Edge(EdgeEvent),
29 Terminal(TerminalEvent),
30}
31
32impl TraceEvent {
33 pub fn meta(run: TraceRunMeta) -> Self {
35 Self::Meta {
36 schema: SCHEMA.to_string(),
37 run: Box::new(run),
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct SpanStartEvent {
45 pub id: String,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub parent_id: Option<String>,
48 pub name: String,
49 pub start_ns: u64,
51 #[serde(rename = "span_kind")]
54 pub kind: SpanKind,
55 #[serde(default)]
56 pub measured: bool,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub step: Option<ExecutionStep>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct SpanEndEvent {
65 pub id: String,
66 #[serde(default)]
68 pub duration_ns: u64,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub struct OpEvent {
74 pub span_id: String,
75 pub op_name: String,
76 #[serde(default)]
77 pub inputs: Vec<String>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub output: Option<String>,
80 pub shape: Vec<usize>,
81 pub dtype: String,
82 pub device: String,
83 pub duration_ns: u64,
84 #[serde(default)]
86 pub timestamp_ns: u64,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub output_dense_bytes: Option<u64>,
90 pub input_dense_bytes: u64,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96pub struct TensorEvent {
97 pub span_id: String,
98 pub tensor_id: String,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub label: Option<String>,
103 pub shape: Vec<usize>,
104 pub dtype: String,
105 pub device: String,
106 #[serde(default)]
107 pub requires_grad: bool,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub dense_bytes: Option<u64>,
110 #[serde(default)]
111 pub category: MemoryCategory,
112}
113
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct TensorStatsEvent {
117 pub span_id: String,
118 pub label: String,
120 pub shape: Vec<usize>,
121 pub dtype: String,
122 pub elements: u64,
123 pub non_finite: u64,
125 pub rms: f64,
126 pub abs_max: f64,
127 pub mean: f64,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct MemoryEvent {
133 pub timestamp_ns: u64,
134 pub storage_id: String,
136 pub tensor_id: String,
137 pub span_id: String,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub op_name: Option<String>,
140 pub device: String,
141 pub bytes: u64,
142 pub action: MemoryAction,
143 #[serde(default)]
144 pub shape: Vec<usize>,
145 pub dtype: String,
146 #[serde(default)]
147 pub category: MemoryCategory,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152pub struct DeviceMemoryEvent {
153 pub timestamp_ns: u64,
154 pub device: String,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub used_bytes: Option<u64>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 pub free_bytes: Option<u64>,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub reserved_bytes: Option<u64>,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub capacity_bytes: Option<u64>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
169pub struct DeviceIntervalEvent {
170 pub span_id: String,
171 pub device: String,
172 pub stream_id: String,
173 pub clock_id: String,
174 pub backend: String,
175 pub start_ns: u64,
176 pub duration_ns: u64,
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct GradientEvent {
182 pub event_id: String,
183 pub root: String,
184 pub key: String,
186 pub state: GradientState,
187 #[serde(default, skip_serializing_if = "Option::is_none")]
188 pub norm: Option<f64>,
189}
190
191impl GradientEvent {
192 pub fn param_key(&self) -> &str {
193 &self.key
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
199#[serde(tag = "edge_kind", rename_all = "snake_case")]
200pub enum EdgeEvent {
201 Call {
202 from_span: String,
203 to_span: String,
204 host_duration_ns: u64,
205 },
206 Data {
207 from_tensor: String,
208 to_tensor: String,
209 },
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
213pub struct TerminalEvent {
214 pub outcome: RunOutcome,
215 pub timestamp_ns: u64,
216 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub reason: Option<String>,
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn tensor_event_without_label_remains_readable() {
226 let event: TraceEvent = serde_json::from_str(
227 r#"{"kind":"tensor","span_id":"root","tensor_id":"backend:1","shape":[1],"dtype":"f32","device":"cpu"}"#,
228 )
229 .unwrap();
230 let TraceEvent::Tensor(tensor) = event else {
231 panic!("expected tensor event");
232 };
233 assert_eq!(tensor.tensor_id, "backend:1");
234 assert_eq!(tensor.label, None);
235 }
236}