metrale_circuit/hardware/
gaps.rs1use std::collections::{BTreeMap, BTreeSet};
20
21use super::HwError;
22use super::estimate::activation_of;
23use super::exec::Exec;
24use super::plan::{NOVEL_EMITTER, Planned, Resolved};
25use crate::fuser::section_of;
26use crate::ir::{Circuit, LayerKind, NodeIdx};
27use crate::rules::Repeat;
28use crate::venn::classify::{candidates, classify_node};
29use crate::venn::roofline::node_cost;
30use crate::venn::{Class, Subject, VennError};
31
32#[derive(Debug, Clone)]
34pub struct GapRow {
35 pub site: String,
37 pub op: String,
39 pub formats: Option<(String, String)>,
41 pub exec: Option<Exec>,
43 pub count: usize,
45 pub time_us: f64,
47 pub share: f64,
49 pub class: Class,
51 pub family: Option<String>,
53 pub detail: String,
55 pub diffs: Vec<String>,
57}
58
59#[derive(Debug, Clone)]
61pub struct Flag {
62 pub layer_kind: Option<LayerKind>,
64 pub sites: BTreeSet<String>,
66 pub rules: BTreeSet<String>,
68 pub added_us: f64,
70 pub share: f64,
72}
73
74#[derive(Debug, Clone)]
76pub struct GapTable {
77 pub planned: Planned,
79 pub total_us: f64,
81 pub rows: Vec<GapRow>,
83 pub flags: Vec<Flag>,
85}
86
87impl GapTable {
88 pub fn share_of(&self, classes: &[Class]) -> f64 {
90 self.rows
91 .iter()
92 .filter(|r| classes.contains(&r.class))
93 .map(|r| r.share)
94 .sum()
95 }
96}
97
98fn site_of(c: &Circuit, n: NodeIdx) -> String {
99 format!("{}.{}", c.nodes[n].block, c.nodes[n].local)
100}
101
102pub fn gap_table(
104 r: &Resolved,
105 c: &Circuit,
106 settings: &BTreeMap<String, String>,
107 recipe: &str,
108 planned: Planned,
109) -> Result<GapTable, HwError> {
110 let plan = &planned.plan;
111 let (mode, rows) = (plan.mode, plan.rows);
112 let with_plan = Subject {
113 recipe,
114 circuit: c,
115 settings,
116 plan: Some(plan),
117 };
118 let bare = Subject {
119 plan: None,
120 ..with_plan
121 };
122 let group_of: BTreeMap<NodeIdx, usize> = plan
123 .groups
124 .iter()
125 .enumerate()
126 .flat_map(|(g, grp)| grp.nodes.iter().map(move |&n| (n, g)))
127 .collect();
128 let section = section_of(mode);
129 let scope: Vec<NodeIdx> = c
130 .blocks
131 .iter()
132 .filter(|b| b.section == section)
133 .flat_map(|b| b.first..b.end)
134 .collect();
135 let mut by_site: BTreeMap<(String, String), GapRow> = BTreeMap::new();
136 let mut flags: BTreeMap<Option<LayerKind>, Flag> = BTreeMap::new();
137 let mut total = 0.0;
138 for &n in &scope {
139 let node = &c.nodes[n];
140 let g = &plan.groups[*group_of
141 .get(&n)
142 .ok_or_else(|| HwError::Plan(format!("node `{}` is in no group", node.id)))?];
143 let rf = r.roofline_of(n);
144 let one = node_cost(c, node, mode, 1, settings, &rf)?.time_us;
145 let all = node_cost(c, node, mode, rows, settings, &rf)?.time_us;
146 let kind = node.layer.map(|l| c.layer_kinds[l]);
147 let site = site_of(c, n);
148 let legacy = r.families.legacy.iter().find(|l| {
151 l.arch == c.arch
152 && l.per_sequence.contains(&mode)
153 && rows > l.rows_above
154 && Some(l.layer_kind) == kind
155 && (l.sites.is_empty() || l.sites.contains(&site))
156 });
157 let looped = matches!(g.repeat, Repeat::PerRow | Repeat::PerRowButLast) && rows > 1;
158 let per_row = looped || legacy.is_some();
159 let time = if per_row { rows as f64 * one } else { all };
160 total += time;
161 if per_row {
162 let f = flags.entry(kind).or_insert_with(|| Flag {
163 layer_kind: kind,
164 sites: BTreeSet::new(),
165 rules: BTreeSet::new(),
166 added_us: 0.0,
167 share: 0.0,
168 });
169 f.sites.insert(site.clone());
170 f.rules.insert(match legacy.filter(|_| !looped) {
171 Some(l) => format!("legacy {}", l.cite),
172 None => g.rule.clone(),
173 });
174 f.added_us += time - all;
175 }
176 let act = activation_of(c, node);
177 let formats = node.weight.zip(act).map(|(w, a)| (w.name(), a.name()));
178 let key = (site, format!("{formats:?}"));
179 if let Some(row) = by_site.get_mut(&key) {
180 row.count += 1;
181 row.time_us += time;
182 continue;
183 }
184 let novel = g.emitter == NOVEL_EMITTER;
185 let classified = if novel {
186 classify_node(&bare, n, rows, &[], &r.families)
187 } else {
188 classify_node(&with_plan, n, rows, &[], &r.families)
189 };
190 let (class, family, diffs, detail) = match classified {
191 Ok((Some(f), _)) => {
192 let detail = if novel {
193 format!(
194 "no rule of this class covers it; family `{}` implements the op",
195 f.family
196 )
197 } else {
198 kernels_of(g, &r.families)
199 };
200 let diffs = f
201 .diffs
202 .iter()
203 .map(|d| format!("{} {}->{}", d.param, d.other, d.target))
204 .collect();
205 let class = if novel && f.class == Class::Shared {
208 Class::SharedUnmeasured
209 } else {
210 f.class
211 };
212 (class, Some(f.family), diffs, detail)
213 }
214 Ok((None, _)) => {
215 let one_row: Vec<&str> = candidates(&r.families, c, node, 1)
216 .into_iter()
217 .map(|f| f.id.as_str())
218 .collect();
219 let detail = if one_row.is_empty() {
220 "no family available on this device implements it".to_string()
221 } else {
222 format!(
223 "no rule of this class covers it; {} run it one row per launch only",
224 one_row.join(", ")
225 )
226 };
227 (Class::Novel, None, Vec::new(), detail)
228 }
229 Err(VennError::UnmappedKernel { kernels, .. }) => (
230 Class::SharedUnmeasured,
231 None,
232 Vec::new(),
233 format!("{kernels} (in no kernel family)"),
234 ),
235 Err(VennError::Load(msg)) if !novel => (
236 Class::SharedUnmeasured,
237 None,
238 Vec::new(),
239 format!("{} (unclassified: {msg})", kernels_of(g, &r.families)),
240 ),
241 Err(e) => return Err(HwError::Plan(e.to_string())),
242 };
243 by_site.insert(
244 key.clone(),
245 GapRow {
246 site: key.0,
247 op: node.op.name(),
248 exec: r.exec[n],
249 formats,
250 count: 1,
251 time_us: time,
252 share: 0.0,
253 class,
254 family,
255 detail,
256 diffs,
257 },
258 );
259 }
260 let mut rows_out: Vec<GapRow> = by_site.into_values().collect();
261 for row in &mut rows_out {
262 row.share = if total > 0.0 {
263 row.time_us / total
264 } else {
265 0.0
266 };
267 }
268 rows_out.sort_by(|a, b| {
269 b.time_us
270 .total_cmp(&a.time_us)
271 .then_with(|| a.site.cmp(&b.site))
272 });
273 let mut flags: Vec<Flag> = flags.into_values().collect();
274 for f in &mut flags {
275 f.share = if total > 0.0 { f.added_us / total } else { 0.0 };
276 }
277 flags.sort_by(|a, b| b.added_us.total_cmp(&a.added_us));
278 Ok(GapTable {
279 planned,
280 total_us: total,
281 rows: rows_out,
282 flags,
283 })
284}
285
286fn kernels_of(g: &crate::fuser::Group, families: &crate::venn::families::Families) -> String {
289 if g.kernels.is_empty() {
290 format!("({} emitter) rule={}", g.emitter, g.rule)
291 } else {
292 let k: Vec<String> = g.kernels.iter().map(|k| k.to_string()).collect();
293 let unit = super::tc_policy::unit_tag(families, &g.kernels).unwrap_or_default();
294 format!("{} rule={} compute={unit}", k.join(" + "), g.rule)
295 }
296}