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