hrx/loom/report/
guidance.rs1use super::CompileReport;
2use crate::{Error, Result};
3use serde::Serialize;
4use serde_json::Value;
5use std::collections::{BTreeMap, BTreeSet};
6
7#[derive(Clone, Debug, Serialize)]
9pub struct ReportGuidance {
10 pub bank_service_available: bool,
12 pub residency_available: bool,
14 pub suggestions: Vec<ReportSuggestion>,
16}
17
18#[derive(Clone, Debug, Serialize)]
20pub struct ReportSuggestion {
21 pub kind: &'static str,
23 pub function: String,
25 pub action: String,
27 pub evidence: BTreeMap<String, Value>,
29}
30
31impl CompileReport {
32 pub fn guidance(&self) -> Result<ReportGuidance> {
36 self.validate()?;
37 let constraints = self.rows("residency_constraints")?;
38 let groups = self
39 .document
40 .pointer("/source_low/memory/bank_service_groups");
41 let mut guidance = ReportGuidance {
42 bank_service_available: groups.is_some_and(|v| !v.is_null()),
43 residency_available: constraints.is_some(),
44 suggestions: Vec::new(),
45 };
46 if self
47 .document
48 .pointer("/status/code")
49 .and_then(Value::as_u64)
50 != Some(0)
51 || self.document["target_family"] != "amdgpu"
52 {
53 return Ok(guidance);
54 }
55 let entries = self.rows("entries")?.unwrap_or_default();
56 let mut names = BTreeSet::new();
57 for entry in entries {
58 if let Some(name) = entry["function"].as_str()
59 && (!names.insert(name) || name.is_empty())
60 {
61 return Err(Error::Message(
62 "report has ambiguous entry identities".into(),
63 ));
64 }
65 }
66 if let Some(constraints) = constraints {
67 for (index, entry) in entries.iter().enumerate() {
68 let Some(function) = entry["function"].as_str() else {
69 continue;
70 };
71 let Some(summary) = entry.pointer("/target_resources/residency") else {
72 continue;
73 };
74 let (Some(current), Some(next), Some(limiters)) = (
75 summary["current_tier"].as_u64(),
76 summary["next_better_tier"].as_u64(),
77 summary["limiting_resource_count"].as_u64(),
78 ) else {
79 continue;
80 };
81 if next <= current || limiters == 0 {
82 continue;
83 }
84 let limiting: Vec<_> = constraints
85 .iter()
86 .enumerate()
87 .filter(|(_, row)| row["function"] == function && row["limiting"] == true)
88 .collect();
89 if limiting.len() as u64 != limiters {
90 continue;
91 }
92 let mut reductions = Vec::new();
93 let mut resources = BTreeSet::new();
94 let mut evidence = BTreeMap::new();
95 for (position, row) in limiting {
96 let (Some(name), Some(unit), Some(scope), Some(units), Some(reduction)) = (
97 row["name"].as_str(),
98 row["unit"].as_str(),
99 row["allocation_scope"].as_str(),
100 row["units"].as_u64(),
101 row["reduction_units_to_next_better_tier"].as_u64(),
102 ) else {
103 break;
104 };
105 let Some(maximum) = units.checked_sub(reduction) else {
106 return Err(Error::Message(
107 "residency reduction exceeds resource usage".into(),
108 ));
109 };
110 if !resources.insert(name) || name.is_empty() || reduction == 0 {
111 return Err(Error::Message(
112 "report has invalid residency requirements".into(),
113 ));
114 }
115 reductions.push(format!(
116 "{name} by {reduction} {unit}/{scope} to at most {maximum}"
117 ));
118 cite(
119 &mut evidence,
120 &format!("/residency_constraints/rows/{position}"),
121 row,
122 );
123 }
124 if reductions.len() as u64 != limiters {
125 continue;
126 }
127 cite(
128 &mut evidence,
129 &format!("/entries/rows/{index}/target_resources/residency"),
130 summary,
131 );
132 guidance.suggestions.push(ReportSuggestion {
133 kind: "amdgpu.residency_cliff", function: function.into(), evidence,
134 action: format!("Reduce {} together to target modeled residency {current} -> {next} subgroups/SIMD. Recompile and benchmark; higher residency does not guarantee higher throughput.", reductions.join(" and ")),
135 });
136 }
137 }
138 if guidance.bank_service_available {
139 let groups = groups
140 .and_then(Value::as_array)
141 .ok_or_else(|| Error::Message("report bank groups must be an array".into()))?;
142 if self
143 .document
144 .pointer("/source_low/memory/bank_service_group_count")
145 .and_then(Value::as_u64)
146 != Some(groups.len() as u64)
147 {
148 return Err(Error::Message(
149 "report bank group count does not match its rows".into(),
150 ));
151 }
152 for (index, group) in groups.iter().enumerate() {
153 let Some(function) = group["function"]
154 .as_str()
155 .filter(|name| names.contains(name))
156 else {
157 continue;
158 };
159 if !matches!(
160 group.pointer("/model/evidence").and_then(Value::as_str),
161 Some("public-vendor-documentation" | "silicon-calibrated-vendor-model")
162 ) {
163 continue;
164 }
165 let summary = &group["summary"];
166 let (Some(exact), Some(conflicts), Some(extra), Some(required), Some(uncontended)) = (
167 summary["exact_packet_count"].as_u64(),
168 summary
169 .pointer("/structural/conflicted_packet_count")
170 .and_then(Value::as_u64),
171 summary
172 .pointer("/structural/extra_round_count")
173 .and_then(Value::as_u64),
174 summary
175 .pointer("/structural/required_round_count")
176 .and_then(Value::as_u64),
177 summary
178 .pointer("/structural/uncontended_round_count")
179 .and_then(Value::as_u64),
180 ) else {
181 continue;
182 };
183 if exact == 0 || conflicts == 0 || extra == 0 {
184 continue;
185 }
186 if conflicts > exact
187 || uncontended == 0
188 || uncontended.checked_add(extra) != Some(required)
189 {
190 return Err(Error::Message(
191 "report bank service counts are inconsistent".into(),
192 ));
193 }
194 let mut evidence = BTreeMap::new();
195 cite(
196 &mut evidence,
197 &format!("/source_low/memory/bank_service_groups/{index}"),
198 group,
199 );
200 let location = group["source_root"].as_str().unwrap_or("this access group");
201 guidance.suggestions.push(ReportSuggestion {
202 kind: "amdgpu.lds_bank_service", function: function.into(), evidence,
203 action: format!("Test a pitch or padding change for {location}: {conflicts} proven packet sites require {extra} extra bank-service rounds. These are structural counts, not measured cycles; unknown and unmodeled accesses remain unqualified. Check the resulting LDS residency and benchmark before adopting the layout."),
204 });
205 }
206 }
207 Ok(guidance)
208 }
209}
210
211fn cite(evidence: &mut BTreeMap<String, Value>, pointer: &str, value: &Value) {
212 evidence.insert(pointer.into(), value.clone());
213}