metrale_telemetry/kernel_audit/
report.rs1use std::collections::BTreeMap;
14
15use super::{FailureSplit, split_failures};
16
17fn ptx_hash(bytes: &[u8]) -> String {
20 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
21 for &b in bytes {
22 h ^= b as u64;
23 h = h.wrapping_mul(0x0000_0100_0000_01b3);
24 }
25 format!("{:012x}", h & 0xffff_ffff_ffff)
26}
27
28pub fn render_kernel_table(
34 embedded: &[(&str, &[u8])],
35 set_hash: &str,
36 shadowed_dropped: &[(&str, &str)],
37 expected_absent: &[(&str, &str)],
38) -> String {
39 let rows = super::audit_rows();
40 let mut mod_resolved: BTreeMap<&str, bool> = BTreeMap::new();
43 for r in &rows {
44 let e = mod_resolved.entry(r.module.as_str()).or_insert(false);
45 *e = *e || r.loaded;
46 }
47
48 let mut out = String::new();
49 out.push_str(&format!(
50 "\n\u{250c}\u{2500} Kernel load audit \u{2500} {} kernels embedded \u{b7} \
51 set-hash {} \u{2500}\n",
52 embedded.len(),
53 set_hash
54 ));
55 out.push_str(&format!(
56 "\u{2502} {:<34} {:<14} {:<20} {}\n",
57 "MODULE (operation)", "PTX-HASH", "RESOLUTION", "SHADOWED"
58 ));
59 out.push_str(&format!("\u{2502} {}\n", "\u{2500}".repeat(84)));
60 let mut sorted: Vec<&(&str, &[u8])> = embedded.iter().collect();
61 sorted.sort_by_key(|(m, _)| *m);
62 for (m, blob) in sorted {
63 let h = ptx_hash(blob);
64 let res = match mod_resolved.get(m) {
65 Some(true) => "used",
66 Some(false) => "** lookup FAILED **",
67 None => "-", };
69 let n_dropped = shadowed_dropped.iter().filter(|(sm, _)| sm == m).count();
72 let shadow = if n_dropped > 0 {
73 format!("Y ({n_dropped} dropped)")
74 } else {
75 "N".to_string()
76 };
77 out.push_str(&format!("\u{2502} {m:<34} {h:<14} {res:<20} {shadow}\n"));
78 }
79 out.push_str("\u{2514}\u{2500}\n");
80
81 let split = split_failures(&rows, expected_absent);
82 if !split.expected.is_empty() {
83 out.push_str(&format!(
84 "\n{} kernel(s) declared expected-absent in this target's MODEL.toml \
85 [expected_absent] (no action):\n {}\n",
86 split.expected.len(),
87 split
88 .expected
89 .iter()
90 .map(|r| r.name())
91 .collect::<Vec<_>>()
92 .join(", ")
93 ));
94 }
95 out
96}
97
98pub fn unresolved_report(
103 split: &FailureSplit,
104 shadowed_dropped: &[(&str, &str)],
105 model: &str,
106 arch: &str,
107 quant: &str,
108 allowed: bool,
109) -> String {
110 let mut out = format!(
111 "{} unresolved kernel lookup(s) for ({model}, {arch}, {quant}). Each one resolved to \
112 handle 0, so its dispatch site is on a silent fallback path:\n",
113 split.required.len()
114 );
115 for (i, r) in split.required.iter().enumerate() {
116 let n = i + 1;
117 let name = r.name();
118 let dropped = shadowed_dropped
119 .iter()
120 .any(|(sm, sf)| *sm == r.module.as_str() && *sf == r.func.as_str());
121 let note = if dropped {
123 " [SHADOW-DROPPED: common/ defines it, this target's kernel file shadows common/ \
124 without it - port it in as an exact piecewise copy]"
125 } else {
126 ""
127 };
128 out.push_str(&format!(
129 " {n}. {name} at {}:{} ({model}, {arch}, {quant}){note}\n",
130 r.site.file(),
131 r.site.line(),
132 ));
133 }
134 out.push_str(
135 "\nEither gate the lookup on this model's config so it is never issued (see \
136 `qwen3_attention::init`), fix the build so the kernel is compiled, or declare it in \
137 this target's MODEL.toml [expected_absent] with a stated reason.\n",
138 );
139 if allowed {
140 out.push_str(
141 "\nPerformance may be seriously degraded. We recommend you open a GitHub issue \
142 and/or open a PR to solve this issue.\n",
143 );
144 } else {
145 out.push_str(
146 "\nIf you wish to allow this model to be served, you can pass\n\
147 --dangerously-allow-unresolved-kernel-lookups. But note that performance may be\n\
148 seriously degraded. We recommend you open a GitHub issue and/or open a PR to\n\
149 solve this issue.\n",
150 );
151 }
152 out
153}
154
155#[cfg(test)]
156mod tests {
157 use super::super::AuditRow;
158 use super::*;
159
160 fn row(module: &str, func: &str) -> AuditRow {
161 AuditRow {
162 module: module.to_string(),
163 func: func.to_string(),
164 loaded: false,
165 site: std::panic::Location::caller(),
166 }
167 }
168
169 #[test]
172 fn the_report_enumerates_then_remediates_once() {
173 let split = FailureSplit {
174 required: vec![row("gdn", "gdn_decode_multi_seq")],
175 expected: vec![],
176 };
177 let deny = unresolved_report(&split, &[], "qwen3.6-27b", "sm_121", "nvfp4", false);
178 assert!(deny.starts_with("1 unresolved kernel lookup(s)"));
179 assert!(deny.contains(" 1. gdn::gdn_decode_multi_seq"));
180 assert!(deny.contains("--dangerously-allow-unresolved-kernel-lookups"));
181 assert_eq!(deny.matches("open a GitHub issue").count(), 1);
182
183 let allow = unresolved_report(&split, &[], "qwen3.6-27b", "sm_121", "nvfp4", true);
184 assert!(allow.contains(" 1. gdn::gdn_decode_multi_seq"));
185 assert!(
186 !allow.contains("If you wish to allow"),
187 "the flag is already set; do not offer it again"
188 );
189 assert_eq!(allow.matches("open a GitHub issue").count(), 1);
190 }
191
192 #[test]
194 fn the_report_is_plain_ascii() {
195 let split = FailureSplit {
196 required: vec![row("gdn", "gdn_decode_multi_seq")],
197 expected: vec![],
198 };
199 let dropped = [("gdn", "gdn_decode_multi_seq")];
200 let text = unresolved_report(&split, &dropped, "m", "a", "q", false);
201 assert!(text.is_ascii(), "report must survive a non-TTY pipe");
202 assert!(text.contains("SHADOW-DROPPED"));
203 }
204
205 #[test]
206 fn expected_absent_never_lands_in_the_required_list() {
207 let rows = vec![row("mla_absorbed", "mla_batched_gemv"), row("gdn", "x")];
208 let split = split_failures(&rows, &[("mla_absorbed", "mla_batched_gemv")]);
209 assert_eq!(split.required.len(), 1);
210 assert_eq!(split.required[0].module, "gdn");
211 assert_eq!(split.expected.len(), 1);
212 }
213 #[test]
217 fn the_remediation_wording_is_the_one_that_was_asked_for() {
218 let split = FailureSplit {
219 required: vec![row("gdn", "gdn_decode_multi_seq")],
220 expected: vec![],
221 };
222 let deny = unresolved_report(&split, &[], "qwen3.6-27b", "sm_121", "nvfp4", false);
223 let flat = deny.split_whitespace().collect::<Vec<_>>().join(" ");
224 let required = "If you wish to allow this model to be served, you can pass \
225 --dangerously-allow-unresolved-kernel-lookups. But note that \
226 performance may be seriously degraded. We recommend you open a \
227 GitHub issue and/or open a PR to solve this issue.";
228 let required = required.split_whitespace().collect::<Vec<_>>().join(" ");
229 assert!(
230 flat.contains(&required),
231 "the remediation sentence has drifted from the specified wording.\n\
232 wanted: {required}\n\
233 got: {flat}"
234 );
235 }
236}