use crate::args::BackendArgs;
use crate::exit_codes::{EXIT_BACKEND_SELF_TEST_FAILED, EXIT_HEALTH_FAILURE, EXIT_SUCCESS};
use crate::format::format_bytes;
use crate::style::{self, Palette};
use anyhow::Result;
use keyhog_scanner::hw_probe::{
gpu_routing_profile, gpu_routing_profiles, probe_hardware, select_backend_verdict, simd_label,
HardwareCaps,
};
use serde::Serialize;
use std::process::ExitCode;
use std::sync::LazyLock;
const KEYHOG_GPU_MAX_BUFFER_CAP_MB: u64 = 256 * 1024;
#[derive(serde::Deserialize)]
pub(crate) struct GpuLoweringGapRules {
pub(crate) lowering_gap_markers: Vec<String>,
pub(crate) moe_parity_degrade_markers: Vec<String>,
}
fn parse_gpu_lowering_gap_rules(raw: &str) -> Result<GpuLoweringGapRules, String> {
toml::from_str::<GpuLoweringGapRules>(raw).map_err(|error| error.to_string())
}
pub(crate) static GPU_LOWERING_GAP_RULES: LazyLock<GpuLoweringGapRules> = LazyLock::new(|| {
match parse_gpu_lowering_gap_rules(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/rules/gpu-lowering-gaps.toml"
))) {
Ok(rules) => {
assert!(
!rules.lowering_gap_markers.is_empty()
&& !rules.moe_parity_degrade_markers.is_empty(),
"rules/gpu-lowering-gaps.toml must define non-empty lowering_gap_markers and \
moe_parity_degrade_markers; an empty set would misclassify every GPU self-test \
error as a hard FAIL"
);
rules
}
Err(error) => panic!(
"rules/gpu-lowering-gaps.toml is invalid: {error}. \
Fix the bundled Tier-B GPU-lowering-gap classification data."
),
}
});
pub(crate) fn is_known_vyre_lowering_gap(error: &str) -> bool {
GPU_LOWERING_GAP_RULES
.lowering_gap_markers
.iter()
.any(|marker| error.contains(marker))
}
pub(crate) fn is_moe_parity_degrade(error: &str) -> bool {
GPU_LOWERING_GAP_RULES
.moe_parity_degrade_markers
.iter()
.any(|marker| error.contains(marker))
}
pub(crate) fn run(args: BackendArgs) -> Result<ExitCode> {
let gpu_policy = if args.require_gpu {
keyhog_scanner::gpu::GpuRuntimePolicy::Required
} else if args.no_gpu {
keyhog_scanner::gpu::GpuRuntimePolicy::Disabled
} else {
keyhog_scanner::gpu::GpuRuntimePolicy::Auto
};
keyhog_scanner::gpu::set_gpu_runtime_policy(gpu_policy);
if args.self_test {
return run_self_test(args.json, args.require_gpu);
}
if args.autoroute {
return run_autoroute_inspection(args.json, args.autoroute_cache.as_deref(), args.verbose);
}
print_backend_report(&args)?;
Ok(ExitCode::SUCCESS)
}
fn run_autoroute_inspection(
json: bool,
autoroute_cache: Option<&str>,
verbose: bool,
) -> Result<ExitCode> {
let path = crate::autoroute_cache_path::resolve_autoroute_cache_path(autoroute_cache)
.map_err(|message| anyhow::anyhow!(message))?;
let inspection = crate::orchestrator::inspect_autoroute_cache(path.as_deref());
let health = inspection.readiness();
let exit = autoroute_inspection_exit_code(health);
if json {
let mut value = serde_json::to_value(&inspection)?;
value["health"] = serde_json::Value::String(health.as_str().to_string());
value["repair_command"] = health
.repair_command()
.map(|command| serde_json::Value::String(command.to_string()))
.unwrap_or(serde_json::Value::Null);
println!("{}", serde_json::to_string_pretty(&value)?);
return Ok(exit);
}
let p = style::for_stdout();
println!("{}## autoroute calibration cache{}", p.bold, p.reset);
println!(
" health: {}{}{}",
p.cyan,
health.as_str(),
p.reset
);
match &inspection.path {
Some(path) => println!(" path: {path}"),
None => println!(" path: (disabled)"),
}
if let Some(error) = &inspection.error {
println!(" status: {}{}{}", p.yellow, error, p.reset);
println!();
if !inspection.calibration_required {
let direct_backend = direct_backend_or_error(inspection.direct_backend)?;
println!(
"This cache artifact is not used by automatic scans in a single-backend build. \
Automatic scans resolve {direct_backend} directly."
);
return Ok(exit);
}
println!(
"Repair: `{}`.",
health
.required_repair_command()
.map_err(anyhow::Error::msg)?
);
println!("An explicit `--backend` is a diagnostic override, not autoroute evidence.");
return Ok(exit);
}
if !inspection.present {
if !inspection.calibration_required {
let direct_backend = direct_backend_or_error(inspection.direct_backend)?;
println!(
" status: {}calibration not required{} (single compiled backend)",
p.green, p.reset
);
println!();
println!(
"Automatic scans resolve {direct_backend} directly. No autoroute cache is needed \
for this build."
);
return Ok(exit);
}
println!(
" status: {}not calibrated yet{}",
p.yellow, p.reset
);
println!();
println!(
"No autoroute cache exists here yet, so automatic scans warn and complete through \
scalar correctness recovery rather than claim an unproved route. Repair: `{}`. \
An explicit `--backend` is a diagnostic override, not autoroute evidence.",
health
.required_repair_command()
.map_err(anyhow::Error::msg)?
);
return Ok(exit);
}
if let Some(version) = inspection.version {
println!(" schema version: {version}");
}
if let (Some(binary), Some(git)) = (&inspection.binary_version, &inspection.git_hash) {
println!(" built for: keyhog {binary} ({git})");
}
if let Some(digest) = &inspection.executable_sha256 {
println!(" executable hash: sha256:{digest}");
}
match inspection.identity_matches_build {
Some(true) => println!(
" identity: {}matches this build{} (host/detector/rules verified at scan time)",
p.green, p.reset
),
Some(false) => {
println!(
" identity: {}STALE (real scans will reject this cache){}",
p.red, p.reset
);
if let Some(reason) = &inspection.identity_mismatch_reason {
println!(" {reason}");
}
println!(
" repair: `{}`",
health
.required_repair_command()
.map_err(anyhow::Error::msg)?
);
}
None => {}
}
if let Some(detector) = &inspection.detector_digest {
println!(" detector digest: {detector}");
}
if let Some(rules) = &inspection.rules_digest {
println!(" rules digest: {rules}");
}
println!();
let total_decisions: usize = inspection.configs.iter().map(|c| c.decision_count).sum();
println!(
"{}{} calibrated config(s), {} workload decision(s){}",
p.bold,
inspection.configs.len(),
total_decisions,
p.reset
);
let mut one_shot_gpu = 0usize;
let mut one_shot_cuda = 0usize;
let mut one_shot_wgpu = 0usize;
let mut daemon_gpu = 0usize;
let mut daemon_cuda = 0usize;
let mut daemon_wgpu = 0usize;
let mut vyre_gpu_receipts = 0usize;
let mut first_gpu_workload = None;
for config in &inspection.configs {
for decision in &config.decisions {
if let Some(backend) = keyhog_scanner::hw_probe::parse_backend_str(&decision.backend) {
if backend.is_gpu() {
one_shot_gpu += 1;
first_gpu_workload.get_or_insert(decision.workload.clone());
match backend {
keyhog_scanner::ScanBackend::GpuCuda => one_shot_cuda += 1,
keyhog_scanner::ScanBackend::GpuWgpu => one_shot_wgpu += 1,
_ => {}
}
}
}
if let Some(backend) =
keyhog_scanner::hw_probe::parse_backend_str(&decision.daemon_backend)
{
if backend.is_gpu() {
daemon_gpu += 1;
match backend {
keyhog_scanner::ScanBackend::GpuCuda => daemon_cuda += 1,
keyhog_scanner::ScanBackend::GpuWgpu => daemon_wgpu += 1,
_ => {}
}
}
}
vyre_gpu_receipts += decision
.candidate_receipts
.iter()
.filter(|receipt| {
keyhog_scanner::hw_probe::parse_backend_str(&receipt.backend)
.is_some_and(|backend| backend.is_gpu())
})
.count();
}
}
println!(
" route summary: one-shot GPU {one_shot_gpu}/{total_decisions} (CUDA {one_shot_cuda}, WGPU {one_shot_wgpu}); daemon GPU {daemon_gpu}/{total_decisions} (CUDA {daemon_cuda}, WGPU {daemon_wgpu}); VYRE candidate receipts {vyre_gpu_receipts}"
);
if inspection.runtime_fault_count > 0 {
println!(
" runtime health: {}{} quarantined workload decision(s){}; repair: `keyhog calibrate-autoroute`",
p.yellow, inspection.runtime_fault_count, p.reset
);
} else {
println!(
" runtime health: {}no quarantined routes{}",
p.green, p.reset
);
}
if let Some(workload) = first_gpu_workload {
println!(" first calibrated GPU bucket: {workload}");
} else {
println!(
" GPU route: no calibrated workload currently selects GPU; run `keyhog calibrate-autoroute` after fixing GPU health"
);
}
println!(" recalibrate: `keyhog calibrate-autoroute` (measures all eligible GPU peers)");
if !verbose {
println!(" details: omitted; add `--verbose` for every workload receipt");
return Ok(exit);
}
for config in &inspection.configs {
println!();
println!(
" {}config {}{} - {} decision(s), {} quarantined",
p.cyan,
config.config_digest,
p.reset,
config.decision_count,
config.quarantined_decision_count,
);
println!(" host: {}", config.host);
for decision in &config.decisions {
let measurement_receipts = decision
.measured_points
.iter()
.map(|point| {
format!(
"{}B/{}chunk(s):generator={}:payload={}:shape={}",
point.sample_bytes,
point.sample_chunks,
point.measurement_generator,
point.payload_digest,
point.measurement_shape_digest,
)
})
.collect::<Vec<_>>()
.join(", ");
let parity_receipts = decision
.candidate_receipts
.iter()
.map(|receipt| {
format!(
"{}+plain-localizer={}+keyword-localizer={}:result={}/trials={}/receipt={}",
receipt.backend,
receipt.phase2_plain_localizer,
receipt.phase2_keyword_localizer,
receipt.correctness_digest,
receipt.completed_trials,
receipt.evidence_digest
)
})
.collect::<Vec<_>>()
.join(", ");
let route_timings = decision
.route_timings
.iter()
.map(|timing| {
let warm = timing
.warm_ms
.map(|ms| format!("/warm={ms}ms"))
.unwrap_or_default();
format!(
"{}[plain={},keyword={}]={}ms{warm}",
timing.backend,
timing.phase2_plain_localizer,
timing.phase2_keyword_localizer,
timing.one_shot_ms
)
})
.collect::<Vec<_>>()
.join(" ");
let margin = decision
.selected_margin_ns
.map(|ns| format!(" margin={}µs", ns / 1_000))
.unwrap_or_default(); let daemon_margin = decision
.daemon_selected_margin_ns
.map(|ns| format!(" margin={}µs", ns / 1_000))
.unwrap_or_default(); println!(" {}", decision.workload);
if decision.runtime_quarantined {
println!(
" runtime: {}QUARANTINED{} backend={} fault={}",
p.yellow,
p.reset,
decision
.runtime_fault_backend
.as_deref()
.unwrap_or("unknown"),
decision
.runtime_fault_reason
.as_deref()
.unwrap_or("not recorded"),
);
}
println!(
" evidence age: {} (calibrated_at_unix_ms={})",
render_age_ms(decision.calibration_age_ms),
decision.calibrated_at_unix_ms
);
println!(" measurements: {measurement_receipts}");
println!(" parity: {parity_receipts}");
println!(
" one-shot -> {}+plain-localizer={}+keyword-localizer={} {}[{} B / {} chunk(s);{} basis={}]{}",
decision.backend,
decision.phase2_plain_localizer,
decision.phase2_keyword_localizer,
p.dim,
decision.sample_bytes,
decision.sample_chunks,
margin,
decision.selection_basis,
p.reset
);
println!(
" daemon -> {}+plain-localizer={}+keyword-localizer={} {}[warm evidence{}; basis={}]{}",
decision.daemon_backend,
decision.daemon_phase2_plain_localizer,
decision.daemon_phase2_keyword_localizer,
p.dim,
daemon_margin,
decision.daemon_selection_basis,
p.reset
);
println!(" route timings: {route_timings}");
}
}
Ok(exit)
}
fn autoroute_inspection_exit_code(health: crate::orchestrator::AutorouteReadiness) -> ExitCode {
use crate::orchestrator::AutorouteReadiness;
match health {
AutorouteReadiness::Direct | AutorouteReadiness::Ready => ExitCode::SUCCESS,
AutorouteReadiness::Quarantined
| AutorouteReadiness::CalibrationRequired
| AutorouteReadiness::Disabled
| AutorouteReadiness::Stale
| AutorouteReadiness::Invalid => ExitCode::from(EXIT_HEALTH_FAILURE),
}
}
fn direct_backend_or_error(direct_backend: Option<&'static str>) -> Result<&'static str> {
direct_backend.ok_or_else(|| {
anyhow::anyhow!(
"autoroute inspection omitted the direct backend for a single-backend build"
)
})
}
fn render_age_ms(age_ms: u128) -> String {
const SECOND_MS: u128 = 1_000;
const MINUTE_MS: u128 = 60 * SECOND_MS;
const HOUR_MS: u128 = 60 * MINUTE_MS;
const DAY_MS: u128 = 24 * HOUR_MS;
if age_ms < SECOND_MS {
format!("{age_ms}ms")
} else if age_ms < MINUTE_MS {
format!("{}s", age_ms / SECOND_MS)
} else if age_ms < HOUR_MS {
format!("{}m", age_ms / MINUTE_MS)
} else if age_ms < DAY_MS {
format!("{}h", age_ms / HOUR_MS)
} else {
format!("{}d", age_ms / DAY_MS)
}
}
fn print_backend_report(args: &BackendArgs) -> Result<()> {
let hw = probe_hardware();
println!("## hardware");
println!(" physical_cores: {}", hw.physical_cores);
println!(" logical_cores: {}", hw.logical_cores);
println!(
" simd: {}",
simd_label(hw.has_avx512, hw.has_avx2, hw.has_neon)
);
println!(
" gpu: {} {}",
if hw.gpu_available {
hw.gpu_name.as_deref().unwrap_or("yes") } else {
"not detected"
},
if hw.gpu_is_software {
"(software renderer: disabled)"
} else {
""
}
);
if let Some(buf) = hw.gpu_vram_mb {
println!(" gpu_max_buffer: {}", format_gpu_max_buffer(buf));
}
if let Some(mem) = hw.total_memory_mb {
println!(" total_memory: {mem} MB");
}
println!(
" hyperscan: {}",
if hw.hyperscan_available {
"compiled-in"
} else {
"absent"
}
);
println!(
" io_uring: {}",
if hw.io_uring_available {
"available"
} else {
"n/a"
}
);
let pat = effective_pattern_count(args)?;
println!();
println!("## routing decision matrix (pattern_count = {pat})");
{
let p = style::for_stdout();
println!(
" {}note: heuristic reference only. `scan --backend auto` routes from the\n \
persisted autoroute calibration cache (see `keyhog backend --autoroute`),\n \
never from this table.{}",
p.dim, p.reset
);
}
let active_profile = gpu_routing_profile(hw.gpu_name.as_deref());
let active_min = active_profile.min_bytes;
let active_solo = active_profile.solo_bytes;
let scenarios: &[(u64, &str)] = &[
(0, "idle (size=0)"),
(4 * 1024, "4 KiB single chunk"),
(1024 * 1024, "1 MiB chunk"),
(8 * 1024 * 1024, "8 MiB required GPU target"),
(64 * 1024 * 1024, "64 MiB measured no-win boundary"),
(active_min.saturating_sub(1), "just under tier min_bytes"),
(active_min, "tier min_bytes exactly"),
(active_solo.saturating_sub(1), "just under tier solo cap"),
(active_solo, "tier solo cap exactly"),
(1024 * 1024 * 1024, "1 GiB single chunk"),
];
for (bytes, label) in scenarios {
let verdict = select_backend_verdict(hw, *bytes, pat);
println!(
" {:<42} {} reason={} ({})",
label,
verdict.backend.label(),
verdict.reason.label(),
verdict.reason_detail()
);
}
if let Some(bytes) = args.probe_bytes {
println!();
let verdict = select_backend_verdict(hw, bytes, pat);
println!("## --probe-bytes {bytes}");
println!(" backend: {}", verdict.backend.label());
println!(
" reason: {} ({})",
verdict.reason.label(),
verdict.reason_detail()
);
}
println!();
println!("## gpu tier (heuristic from adapter name)");
let tier = gpu_routing_profile(hw.gpu_name.as_deref());
let tier_label = format!("{} ({})", tier.tier, tier.description);
println!(" classified: {tier_label}");
println!(
" effective min bytes: {} (tier {})",
format_bytes(tier.min_bytes),
tier.tier
);
println!(
" effective solo cap: {}",
format_bytes(tier.solo_bytes)
);
println!();
println!("## thresholds (per-tier table)");
for profile in gpu_routing_profiles() {
println!(
" {:<4} tier min/solo/pattern = {} / {} / {}",
profile.tier,
format_bytes(profile.min_bytes),
format_bytes(profile.solo_bytes),
profile.pattern_breakeven
);
}
println!();
println!(
"Force a scan backend with: keyhog scan --backend <auto|gpu-cuda|gpu-wgpu|simd|cpu> ..."
);
Ok(())
}
fn effective_pattern_count(args: &BackendArgs) -> Result<usize> {
if let Some(patterns) = args.patterns {
return Ok(patterns);
}
let detectors = keyhog_core::load_embedded_detectors_or_fail()
.map_err(|error| anyhow::anyhow!("backend: load embedded detectors: {error}"))?;
let scanner = keyhog_scanner::CompiledScanner::compile(detectors)
.map_err(|error| anyhow::anyhow!("backend: compile embedded scanner: {error}"))?;
Ok(scanner.runtime_status().pattern_count)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum BackendSelfTestStatus {
Pass,
Fail,
Warning,
Known,
Skip,
}
#[derive(Debug, Serialize)]
pub(crate) struct BackendSelfTestProbe {
pub(crate) name: &'static str,
pub(crate) status: BackendSelfTestStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) adapter_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) scores: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) max_buffer_mb: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) direct_matches: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) coalesced_matches: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) matches: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) backend_id: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) backend_route: Option<&'static str>,
}
impl BackendSelfTestProbe {
fn pass(name: &'static str) -> Self {
Self {
name,
status: BackendSelfTestStatus::Pass,
message: None,
adapter_name: None,
scores: None,
max_buffer_mb: None,
direct_matches: None,
coalesced_matches: None,
matches: None,
backend_id: None,
backend_route: None,
}
}
fn fail(name: &'static str, message: String) -> Self {
Self {
status: BackendSelfTestStatus::Fail,
message: Some(message),
..Self::pass(name)
}
}
fn known(name: &'static str, message: impl Into<String>) -> Self {
Self {
status: BackendSelfTestStatus::Known,
message: Some(message.into()),
..Self::pass(name)
}
}
fn warning(name: &'static str, message: impl Into<String>) -> Self {
Self {
status: BackendSelfTestStatus::Warning,
message: Some(message.into()),
..Self::pass(name)
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum BackendSelfTestRouteSelection {
NotMeasured,
}
#[derive(Debug, Serialize)]
pub(crate) struct BackendSelfTestReport {
pub(crate) ok: bool,
pub(crate) status: BackendSelfTestStatus,
pub(crate) exit_code: u8,
pub(crate) gpu_available: bool,
pub(crate) gpu_is_software: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) gpu_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) gpu_max_buffer_mb: Option<u64>,
pub(crate) healthy_gpu_backends: Vec<&'static str>,
pub(crate) route_selection: BackendSelfTestRouteSelection,
pub(crate) probes: Vec<BackendSelfTestProbe>,
}
impl BackendSelfTestReport {
fn exit_code(&self) -> ExitCode {
ExitCode::from(self.exit_code)
}
}
fn run_self_test(json: bool, require_gpu: bool) -> Result<ExitCode> {
let report = collect_self_test_report(require_gpu);
if json {
println!("{}", serde_json::to_string_pretty(&report)?);
} else {
print_self_test_report(&report);
}
Ok(report.exit_code())
}
fn collect_self_test_report(require_gpu: bool) -> BackendSelfTestReport {
let hw = probe_hardware();
let region_presence = keyhog_scanner::gpu::gpu_region_presence_self_test();
let acquired_backends: Vec<_> = match ®ion_presence {
Ok(report) => report.peers.iter().map(|peer| peer.backend).collect(),
Err(error) => error.acquired_backends.clone(),
};
if (!hw.gpu_available || hw.gpu_is_software) && acquired_backends.is_empty() {
return unavailable_gpu_self_test_report(hw, require_gpu);
}
let mut all_ok = true;
let mut probes = Vec::with_capacity(2 + acquired_backends.len());
let has_wgpu = acquired_backends.contains(&keyhog_scanner::ScanBackend::GpuWgpu);
let healthy_gpu_backends = region_presence
.as_ref()
.ok()
.map(|report| {
report
.peers
.iter()
.map(|peer| crate::orchestrator_config::backend_override_cli_value(peer.backend))
.collect()
})
.unwrap_or_default();
if !has_wgpu {
probes.push(BackendSelfTestProbe::warning(
"moe_kernel",
"WGPU peer was not acquired; the WGPU MoE diagnostic is not applicable to this CUDA-only runtime",
));
} else {
match keyhog_scanner::gpu::gpu_self_test() {
Ok(report) => {
let mut probe = BackendSelfTestProbe::pass("moe_kernel");
probe.adapter_name = Some(report.adapter_name);
probe.scores = Some(report.scores);
probe.max_buffer_mb = report.vram_mb;
probes.push(probe);
}
Err(error) => {
let parity_degrade = is_moe_parity_degrade(&error);
if parity_degrade {
probes.push(BackendSelfTestProbe::known("moe_kernel", &error));
} else {
probes.push(BackendSelfTestProbe::fail("moe_kernel", error));
all_ok = false;
}
}
}
}
if !has_wgpu {
probes.push(BackendSelfTestProbe::warning(
"vyre_literal_set",
"WGPU peer was not acquired; direct WGPU match-triple diagnostics are not applicable",
));
} else {
match keyhog_scanner::gpu::vyre_gpu_self_test() {
Ok(report) => {
let mut probe = BackendSelfTestProbe::pass("vyre_literal_set");
probe.direct_matches = Some(report.direct_matches);
probe.coalesced_matches = Some(report.coalesced_matches);
probes.push(probe);
}
Err(error) => {
let known_lowering_gap = is_known_vyre_lowering_gap(&error);
if known_lowering_gap {
probes.push(BackendSelfTestProbe::known(
"vyre_literal_set",
"VYRE IR lowering rejects the direct match-triple form; the production region-presence path is checked separately below",
));
} else {
probes.push(BackendSelfTestProbe::warning(
"vyre_literal_set",
format!(
"VYRE direct match-triple diagnostic failed ({error}); production scan eligibility is determined by gpu_region_presence"
),
));
}
}
}
}
match region_presence {
Ok(report) => {
for peer in report.peers {
let mut probe = BackendSelfTestProbe::pass("gpu_region_presence");
probe.matches = Some(peer.matches);
probe.backend_id = Some(peer.backend_id);
probe.backend_route = Some(crate::orchestrator_config::backend_override_cli_value(
peer.backend,
));
probes.push(probe);
}
}
Err(error) => {
probes.push(BackendSelfTestProbe::fail(
"gpu_region_presence",
error.to_string(),
));
all_ok = false;
}
}
BackendSelfTestReport {
ok: all_ok,
status: if all_ok {
BackendSelfTestStatus::Pass
} else {
BackendSelfTestStatus::Fail
},
exit_code: if all_ok {
0
} else {
EXIT_BACKEND_SELF_TEST_FAILED
},
gpu_available: hw.gpu_available || !acquired_backends.is_empty(),
gpu_is_software: hw.gpu_is_software && acquired_backends.is_empty(),
gpu_name: hw.gpu_name.clone(),
gpu_max_buffer_mb: hw.gpu_vram_mb,
healthy_gpu_backends,
route_selection: BackendSelfTestRouteSelection::NotMeasured,
probes,
}
}
fn unavailable_gpu_self_test_report(hw: &HardwareCaps, require_gpu: bool) -> BackendSelfTestReport {
let reason = if !hw.gpu_available {
"no GPU adapter detected"
} else {
"only software adapter (llvmpipe/lavapipe/swiftshader): won't be used for scans"
};
let status = if require_gpu {
BackendSelfTestStatus::Fail
} else {
BackendSelfTestStatus::Skip
};
let message = if require_gpu {
format!("--require-gpu requested but {reason}")
} else {
reason.to_string()
};
BackendSelfTestReport {
ok: !require_gpu,
status,
exit_code: if require_gpu {
EXIT_BACKEND_SELF_TEST_FAILED
} else {
EXIT_SUCCESS
},
gpu_available: hw.gpu_available,
gpu_is_software: hw.gpu_is_software,
gpu_name: hw.gpu_name.clone(),
gpu_max_buffer_mb: hw.gpu_vram_mb,
healthy_gpu_backends: Vec::new(),
route_selection: BackendSelfTestRouteSelection::NotMeasured,
probes: vec![BackendSelfTestProbe {
name: "gpu_adapter",
status,
message: Some(message),
adapter_name: None,
scores: None,
max_buffer_mb: None,
direct_matches: None,
coalesced_matches: None,
matches: None,
backend_id: None,
backend_route: None,
}],
}
}
fn print_self_test_report(report: &BackendSelfTestReport) {
let palette = style::for_stdout();
println!("## GPU self-test");
if report.status == BackendSelfTestStatus::Skip {
let message = report
.probes
.first()
.and_then(|probe| probe.message.as_deref())
.unwrap_or("GPU self-test skipped"); println!(" {}: {message}", style::warn("SKIP", &palette));
return;
}
for probe in &report.probes {
print!(" {:<17} ... ", probe.name);
match probe.status {
BackendSelfTestStatus::Pass => print_pass_probe(probe, &palette),
BackendSelfTestStatus::Fail => {
let message = probe.message.as_deref().unwrap_or("probe failed"); println!("{} {message}", style::fail("FAIL", &palette));
}
BackendSelfTestStatus::Warning => {
let message = probe.message.as_deref().unwrap_or("diagnostic warning"); println!("{} {message}", style::warn("WARN", &palette));
}
BackendSelfTestStatus::Known => {
let message = probe.message.as_deref().unwrap_or("known limitation"); println!("{} {message}.", style::warn("KNOWN", &palette));
}
BackendSelfTestStatus::Skip => {
let message = probe.message.as_deref().unwrap_or("probe skipped"); println!("{} {message}", style::warn("SKIP", &palette));
}
}
}
println!();
if report.ok {
println!(
"{} GPU self-test passed, scans on this box can route to GPU.",
style::pass("PASS", &palette)
);
println!(
" Self-test proves backend health only. `keyhog backend --autoroute` shows the measured route."
);
} else {
let stderr_palette = style::for_stderr();
eprintln!(
"{} GPU self-test failed; GPU routes are unavailable until fixed. \
Use --backend simd/cpu or --no-gpu for an explicit CPU-only scan.",
style::fail("FAIL", &stderr_palette)
);
}
}
fn print_pass_probe(probe: &BackendSelfTestProbe, palette: &Palette) {
let pass = style::pass("PASS", palette);
match probe.name {
"moe_kernel" => println!(
"{pass} ({}, scores={}, max_buffer={} MB)",
probe.adapter_name.as_deref().unwrap_or("unknown adapter"), format_probe_metric(probe.scores),
format_probe_metric(probe.max_buffer_mb)
),
"vyre_literal_set" => println!(
"{pass} (direct={}, coalesced={})",
format_probe_metric(probe.direct_matches),
format_probe_metric(probe.coalesced_matches)
),
"gpu_region_presence" => println!(
"{pass} (matches={}, route={}, backend={})",
format_probe_metric(probe.matches),
probe.backend_route.unwrap_or("unknown"), probe.backend_id.unwrap_or("unknown") ),
_ => println!("{pass}"),
}
}
fn format_probe_metric<T: std::fmt::Display>(value: Option<T>) -> String {
value.map_or_else(|| "unknown".to_string(), |value| value.to_string())
}
fn render_self_test_json_for_contract(report: &BackendSelfTestReport) -> Result<String> {
serde_json::to_string_pretty(report).map_err(Into::into)
}
fn format_gpu_max_buffer(max_buffer_mb: u64) -> String {
let base = if max_buffer_mb >= 1024 {
format!("{} GB", max_buffer_mb / 1024)
} else {
format!("{max_buffer_mb} MB")
};
if max_buffer_mb >= KEYHOG_GPU_MAX_BUFFER_CAP_MB {
format!(">={base} (keyhog cap; wgpu max_buffer_size)")
} else {
format!("{base} (wgpu max_buffer_size)")
}
}
#[doc(hidden)]
pub(crate) mod testing {
use anyhow::Result;
pub(crate) fn render_failing_region_presence_probe_json() -> Result<String> {
let report = super::BackendSelfTestReport {
ok: false,
status: super::BackendSelfTestStatus::Fail,
exit_code: super::EXIT_BACKEND_SELF_TEST_FAILED,
gpu_available: true,
gpu_is_software: false,
gpu_name: Some("NVIDIA GeForce RTX 5090".to_string()),
gpu_max_buffer_mb: Some(262_144),
healthy_gpu_backends: vec!["gpu-wgpu"],
route_selection: super::BackendSelfTestRouteSelection::NotMeasured,
probes: vec![
super::BackendSelfTestProbe {
name: "moe_kernel",
status: super::BackendSelfTestStatus::Pass,
message: None,
adapter_name: Some("NVIDIA GeForce RTX 5090".to_string()),
scores: Some(64),
max_buffer_mb: Some(262_144),
direct_matches: None,
coalesced_matches: None,
matches: None,
backend_id: None,
backend_route: None,
},
super::BackendSelfTestProbe {
name: "vyre_literal_set",
status: super::BackendSelfTestStatus::Known,
message: Some(
"vyre IR lowering rejects literal_set's subgroup form".to_string(),
),
adapter_name: None,
scores: None,
max_buffer_mb: None,
direct_matches: None,
coalesced_matches: None,
matches: None,
backend_id: None,
backend_route: None,
},
super::BackendSelfTestProbe {
name: "gpu_region_presence",
status: super::BackendSelfTestStatus::Fail,
message: Some("GPU region-presence dispatch failed".to_string()),
adapter_name: None,
scores: None,
max_buffer_mb: None,
direct_matches: None,
coalesced_matches: None,
matches: None,
backend_id: Some("cuda"),
backend_route: Some("gpu-cuda"),
},
super::BackendSelfTestProbe {
name: "gpu_region_presence",
status: super::BackendSelfTestStatus::Pass,
message: None,
adapter_name: None,
scores: None,
max_buffer_mb: None,
direct_matches: None,
coalesced_matches: None,
matches: Some(1),
backend_id: Some("wgpu"),
backend_route: Some("gpu-wgpu"),
},
],
};
super::render_self_test_json_for_contract(&report)
}
pub(crate) fn format_gpu_max_buffer(max_buffer_mb: u64) -> String {
super::format_gpu_max_buffer(max_buffer_mb)
}
pub(crate) fn format_probe_count_metric(value: Option<usize>) -> String {
super::format_probe_metric(value)
}
pub(crate) fn format_probe_mb_metric(value: Option<u64>) -> String {
super::format_probe_metric(value)
}
}
#[cfg(test)]
mod tests;