1use crate::ir::*;
8use crate::load::Crate;
9use crate::verify::VerifyReport;
10use crate::{
11 dataflow::{EdgeKind, ExprGraph, GradState, NodeKind},
12 op_semantics::AbstractDtype,
13};
14
15pub fn tree(structure: &Structure, krate: &Crate, show_spans: bool) -> String {
17 let mut out = String::new();
18 let Some(root) = structure.root else {
19 return "no root instance\n".to_string();
20 };
21 render(structure, krate, root, 0, show_spans, &mut out);
22
23 let coverage = structure.coverage();
24 out.push_str(&format!(
25 "\n{} instances, {} parameters ({} certain, {} conditional, {} unknown), {} diagnostics\n",
26 coverage.instances,
27 coverage.params,
28 coverage.params_certain,
29 coverage.params_conditional,
30 coverage.params_unknown,
31 coverage.diagnostics,
32 ));
33 out
34}
35
36fn render(
37 structure: &Structure,
38 krate: &Crate,
39 id: ModuleInstanceId,
40 depth: usize,
41 show_spans: bool,
42 out: &mut String,
43) {
44 let instance = structure.instance(id);
45 let def = structure.def(instance.def);
46 let pad = " ".repeat(depth);
47
48 let label = match &instance.via_field {
49 Some(field) => format!("{field}: {}", def.name),
50 None => def.name.clone(),
51 };
52 let prefix = if instance.prefix.is_empty() {
53 "<root>".to_string()
54 } else {
55 instance.prefix.to_string()
56 };
57
58 out.push_str(&format!("{pad}{label} [{}/{prefix}]", instance.root));
59 if instance.prefix_derived {
60 out.push('~');
61 }
62 if let Some(repeat) = &instance.repeat {
63 out.push_str(&format!(" x{{{}}} over {}", repeat.var, repeat.bound));
64 }
65 if let Certainty::Conditional(reason) = &instance.certainty {
66 out.push_str(&format!(" (conditional: {reason})"));
67 }
68 if show_spans {
69 out.push_str(&format!(" @{}", krate.file_label(instance.origin)));
70 }
71 out.push('\n');
72
73 for param_id in structure
74 .params
75 .iter()
76 .filter(|p| p.owner == id)
77 .map(|p| p.id)
78 {
79 let param = &structure.params[param_id.0];
80 let site = structure.site(param.site);
81 let leaf = relative(¶m.key, &instance.prefix);
84
85 out.push_str(&format!("{pad} - {leaf}"));
86 if let Some(shape) = &site.shape {
87 out.push_str(&format!(" shape={shape}"));
88 }
89 match ¶m.checkpoint {
90 CheckpointMatch::Found { shape, dtype, .. } => {
91 out.push_str(&format!(" ckpt={shape:?} {dtype}"));
92 }
93 CheckpointMatch::FoundMany { count, .. } => {
94 out.push_str(&format!(" ckpt={count} tensors"));
95 }
96 CheckpointMatch::Missing => out.push_str(" ckpt=MISSING"),
97 CheckpointMatch::NotChecked => {}
98 }
99 if let Certainty::Conditional(reason) = ¶m.certainty {
100 out.push_str(&format!(" (conditional: {reason})"));
101 }
102 if show_spans {
103 out.push_str(&format!(" @{}", krate.file_label(site.span)));
104 }
105 out.push('\n');
106 }
107
108 for child in &instance.children {
109 render(structure, krate, *child, depth + 1, show_spans, out);
110 }
111}
112
113fn relative(key: &Key, prefix: &Key) -> String {
116 if key.segs.len() > prefix.segs.len() && key.segs[..prefix.segs.len()] == prefix.segs[..] {
117 let rest: Vec<String> = key.segs[prefix.segs.len()..]
118 .iter()
119 .map(|s| s.to_string())
120 .collect();
121 return rest.join(".");
122 }
123 key.to_string()
124}
125
126pub fn keys(structure: &Structure) -> String {
128 let mut lines: Vec<String> = structure
129 .params
130 .iter()
131 .map(|p| {
132 let marker = match &p.certainty {
133 Certainty::Certain => "",
134 Certainty::Conditional(_) => " # conditional",
135 Certainty::Unknown(_) => " # unknown",
136 };
137 format!("{}{marker}", p.key)
138 })
139 .collect();
140 lines.sort();
141 lines.dedup();
142 lines.join("\n") + "\n"
143}
144
145pub fn json(
147 structure: &Structure,
148 krate: &Crate,
149 verify: Option<&VerifyReport>,
150) -> serde_json::Value {
151 let diagnostics: Vec<serde_json::Value> = structure
152 .diagnostics
153 .iter()
154 .map(|d| {
155 serde_json::json!({
156 "at": krate.file_label(d.span),
157 "message": d.message,
158 "key": d.key.as_ref().map(|k| k.to_string()),
159 })
160 })
161 .collect();
162
163 let params: Vec<serde_json::Value> = structure
164 .params
165 .iter()
166 .map(|p| {
167 let site = structure.site(p.site);
168 let owner = structure.instance(p.owner);
169 serde_json::json!({
170 "key": p.key.to_string(),
171 "root": p.root,
172 "template": p.key.is_template(),
173 "kind": site.kind,
174 "shape": site.shape,
175 "acquired_via": site.acquisition,
176 "module": structure.def(owner.def).name,
177 "module_prefix": owner.prefix.to_string(),
178 "certainty": p.certainty,
179 "checkpoint": p.checkpoint,
180 "at": krate.file_label(site.span),
181 })
182 })
183 .collect();
184
185 let modules: Vec<serde_json::Value> = structure
186 .instances
187 .iter()
188 .map(|m| {
189 serde_json::json!({
190 "id": m.id.0,
191 "prefix": m.prefix.to_string(),
192 "root": m.root,
193 "prefix_derived": m.prefix_derived,
194 "type": structure.def(m.def).name,
195 "field": m.via_field,
196 "parent": m.parent.map(|p| structure.instance(p).prefix.to_string()),
197 "parent_id": m.parent.map(|p| p.0),
198 "repeat": m.repeat,
199 "certainty": m.certainty,
200 "at": krate.file_label(m.origin),
201 })
202 })
203 .collect();
204
205 serde_json::json!({
206 "schema": "candle-graph/structure/1",
207 "coverage": structure.coverage(),
208 "diagnostics": diagnostics,
209 "verify": verify,
210 "modules": modules,
211 "parameters": params,
212 })
213}
214
215pub fn dataflow_json(graph: &ExprGraph, krate: &Crate) -> serde_json::Value {
217 let nodes: Vec<serde_json::Value> = graph
218 .nodes
219 .iter()
220 .map(|node| {
221 let (kind, label) = node_kind(&node.kind);
222 serde_json::json!({
223 "id": format!("n{}", node.id.0),
224 "kind": kind,
225 "label": label,
226 "shape": node.shape,
227 "dtype": dtype_name(node.dtype),
228 "grad_state": grad_name(node.grad),
229 "at": krate.file_label(node.span),
230 })
231 })
232 .collect();
233 let edges: Vec<serde_json::Value> = graph
234 .edges
235 .iter()
236 .map(|edge| {
237 serde_json::json!({
238 "id": format!("e{}", edge.id.0),
239 "from": format!("n{}", edge.from.0),
240 "to": format!("n{}", edge.to.0),
241 "kind": edge_kind(edge.kind),
242 "label": edge.label,
243 })
244 })
245 .collect();
246 let conflicts: Vec<serde_json::Value> = graph
247 .dtype_conflicts
248 .iter()
249 .map(|conflict| {
250 serde_json::json!({
251 "node": format!("n{}", conflict.edge_or_node.0),
252 "op": conflict.op,
253 "left": dtype_name(conflict.left),
254 "right": dtype_name(conflict.right),
255 "at": krate.file_label(conflict.span),
256 "message": conflict.message,
257 })
258 })
259 .collect();
260 let risks: Vec<serde_json::Value> = graph
261 .dtype_risks
262 .iter()
263 .map(|risk| {
264 serde_json::json!({
265 "node": format!("n{}", risk.edge_or_node.0),
266 "op": risk.op,
267 "known": dtype_name(risk.known),
268 "at": krate.file_label(risk.span),
269 "message": risk.message,
270 })
271 })
272 .collect();
273 let diagnostics: Vec<serde_json::Value> = graph
274 .diagnostics
275 .iter()
276 .map(|diagnostic| {
277 serde_json::json!({
278 "at": krate.file_label(diagnostic.span),
279 "message": diagnostic.message,
280 })
281 })
282 .collect();
283
284 serde_json::json!({
285 "schema": "candle-graph/dataflow/1",
286 "coverage": {
287 "nodes": graph.nodes.len(),
288 "edges": graph.edges.len(),
289 "parameters": graph.param_nodes.len(),
290 "losses": graph.loss_nodes.len(),
291 "dead_parameters": graph.dead_params().len(),
292 "severing_edges": graph.severing_edges().len(),
293 "dtype_conflicts": graph.dtype_conflicts.len(),
294 "dtype_risks": graph.dtype_risks.len(),
295 "diagnostics": graph.diagnostics.len(),
296 },
297 "entry_return": graph.entry_return.map(|id| format!("n{}", id.0)),
298 "loss_nodes": graph.loss_nodes.iter().map(|id| format!("n{}", id.0)).collect::<Vec<_>>(),
299 "parameter_nodes": graph.param_nodes.iter().map(|id| format!("n{}", id.0)).collect::<Vec<_>>(),
300 "dead_parameters": graph.dead_params().iter().map(|id| format!("n{}", id.0)).collect::<Vec<_>>(),
301 "severing_edges": graph.severing_edges().iter().map(|id| format!("e{}", id.0)).collect::<Vec<_>>(),
302 "dtype_conflicts": conflicts,
303 "dtype_risks": risks,
304 "diagnostics": diagnostics,
305 "nodes": nodes,
306 "edges": edges,
307 })
308}
309
310pub fn dataflow_findings(graph: &ExprGraph, krate: &Crate) -> Vec<String> {
312 let mut findings = Vec::new();
313 for conflict in &graph.dtype_conflicts {
314 findings.push(format!(
315 "dtype-conflict\t{}\t{}\t{} vs {}\t{}",
316 krate.file_label(conflict.span),
317 conflict.op,
318 dtype_name(conflict.left),
319 dtype_name(conflict.right),
320 conflict.message
321 ));
322 }
323 for risk in &graph.dtype_risks {
324 findings.push(format!(
325 "dtype-risk\t{}\t{}\tknown {} + unknown\t{}",
326 krate.file_label(risk.span),
327 risk.op,
328 dtype_name(risk.known),
329 risk.message
330 ));
331 }
332 for id in graph.dead_params() {
333 let node = graph.node(id);
334 let (_, label) = node_kind(&node.kind);
335 findings.push(format!(
336 "dead-parameter\t{}\t{}\t{}",
337 krate.file_label(node.span),
338 label,
339 grad_name(node.grad)
340 ));
341 }
342 for edge_id in graph.severing_edges() {
343 let edge = graph.edge(edge_id);
344 let target = graph.node(edge.to);
345 let (_, label) = node_kind(&target.kind);
346 findings.push(format!(
347 "severing-edge\t{}\t{}\t{}",
348 krate.file_label(target.span),
349 label,
350 edge.label.as_deref().unwrap_or("data")
351 ));
352 }
353 findings.sort();
354 findings.dedup();
355 findings
356}
357
358pub fn dataflow_text(graph: &ExprGraph, krate: &Crate) -> String {
360 let findings = dataflow_findings(graph, krate);
361 let mut out = format!(
362 "\ndataflow: {} nodes, {} edges, {} loss sinks, {} dtype conflicts, {} dtype risks, {} dead trainable parameters, {} severing edges\n",
363 graph.nodes.len(),
364 graph.edges.len(),
365 graph.loss_nodes.len(),
366 graph.dtype_conflicts.len(),
367 graph.dtype_risks.len(),
368 graph.dead_params().len(),
369 graph.severing_edges().len(),
370 );
371 for finding in findings {
372 out.push_str(" ");
373 out.push_str(&finding.replace('\t', " "));
374 out.push('\n');
375 }
376 out
377}
378
379fn node_kind(kind: &NodeKind) -> (&'static str, String) {
380 match kind {
381 NodeKind::Param { name } => ("parameter", name.clone()),
382 NodeKind::Local { name } => ("local", name.clone()),
383 NodeKind::Call { callee } => ("operation", callee.clone()),
384 NodeKind::Literal { text } => ("literal", text.clone()),
385 NodeKind::Phi => ("phi", "branch join".to_string()),
386 NodeKind::Return => ("return", "return".to_string()),
387 NodeKind::Unknown { reason } => ("unknown", reason.clone()),
388 }
389}
390
391fn grad_name(grad: GradState) -> &'static str {
392 match grad {
393 GradState::Trainable => "Trainable",
394 GradState::Frozen => "Frozen",
395 GradState::Differentiable => "Differentiable",
396 GradState::Severed => "Severed",
397 GradState::LayoutDependent => "LayoutDependent",
398 GradState::Unknown => "Unknown",
399 }
400}
401
402fn dtype_name(dtype: AbstractDtype) -> &'static str {
403 match dtype {
404 AbstractDtype::F64 => "F64",
405 AbstractDtype::F32 => "F32",
406 AbstractDtype::F16 => "F16",
407 AbstractDtype::Bf16 => "BF16",
408 AbstractDtype::I16 => "I16",
409 AbstractDtype::I32 => "I32",
410 AbstractDtype::I64 => "I64",
411 AbstractDtype::U32 => "U32",
412 AbstractDtype::U8 => "U8",
413 AbstractDtype::F8E4M3 => "F8E4M3",
414 AbstractDtype::F6E2M3 => "F6E2M3",
415 AbstractDtype::F6E3M2 => "F6E3M2",
416 AbstractDtype::F4 => "F4",
417 AbstractDtype::F8E8M0 => "F8E8M0",
418 AbstractDtype::Unknown => "Unknown",
419 }
420}
421
422fn edge_kind(kind: EdgeKind) -> &'static str {
423 match kind {
424 EdgeKind::Data => "data",
425 EdgeKind::Severing => "severing",
426 EdgeKind::Control => "control",
427 }
428}