#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GpuSelfTest {
pub adapter_name: String,
pub vram_mb: Option<u64>,
pub scores: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VyreGpuSelfTest {
pub direct_matches: usize,
pub coalesced_matches: usize,
}
#[cfg(feature = "gpu")]
static GPU_SELF_TEST_CACHE: std::sync::OnceLock<std::result::Result<GpuSelfTest, String>> =
std::sync::OnceLock::new();
pub fn gpu_self_test() -> Result<GpuSelfTest, String> {
#[cfg(not(feature = "gpu"))]
{
Err(
"GPU support not compiled in (lean ci build). Rebuild with `--features gpu` \
(or the default profile) to exercise the wgpu/CUDA path."
.to_string(),
)
}
#[cfg(feature = "gpu")]
{
GPU_SELF_TEST_CACHE
.get_or_init(|| {
let gpu = super::backend::get_gpu().ok_or_else(|| {
"GPU adapter unavailable; install or enable a non-software GPU adapter and driver"
.to_string()
})?;
let max_abs = super::backend::gpu_moe_parity_max_divergence(
std::time::Duration::from_millis(
crate::scanner_config::ScannerTuningConfig::GPU_MOE_TIMEOUT_MS_DEFAULT,
),
)?;
if max_abs > super::backend::GPU_MOE_PARITY_TOLERANCE {
return Err(format!(
"GPU MoE compute shader diverges from the CPU MoE reference by {max_abs:.4} \
(tolerance {:.4}); the GPU would score findings differently from the \
CPU/SIMD path. Indicates a shader miscompile, weights-packing mismatch, \
or driver bug. Scans record the GPU MoE degrade and use the CPU MoE \
(correct + deterministic), \
so detection is unaffected, but GPU ML acceleration is OFF on this host.",
super::backend::GPU_MOE_PARITY_TOLERANCE
));
}
Ok(GpuSelfTest {
adapter_name: gpu.gpu_name().to_string(),
vram_mb: gpu.vram_mb(),
scores: crate::ml_scorer::GPU_BATCH_THRESHOLD,
})
})
.clone()
}
}
pub fn vyre_gpu_self_test() -> Result<VyreGpuSelfTest, String> {
#[cfg(not(feature = "gpu"))]
{
Err(
"VYRE GPU self-test not available in the lean CI build (no WGPU driver compiled in). \
Rebuild with `--features gpu`."
.to_string(),
)
}
#[cfg(feature = "gpu")]
{
vyre_gpu_self_test_impl()
}
}
#[cfg(feature = "gpu")]
fn vyre_gpu_self_test_impl() -> Result<VyreGpuSelfTest, String> {
use vyre_driver_wgpu::WgpuBackend;
use vyre_libs::scan::GpuLiteralSet;
let patterns: Vec<Vec<u8>> = vec![b"needle".to_vec()];
let pattern_refs: Vec<&[u8]> = patterns.iter().map(Vec::as_slice).collect();
let backend = WgpuBackend::shared().map_err(|e| format!("failed to init wgpu backend: {e}"))?;
let scanner = GpuLiteralSet::compile(&pattern_refs);
let direct = scanner
.scan(backend.as_ref(), b"needle", 100)
.map_err(|error| format!("vyre direct GPU scan failed: {error}"))?;
if direct.len() != 1 || direct[0].pattern_id != 0 || direct[0].start != 0 {
return Err(format!(
"vyre direct GPU scan returned unexpected matches: {direct:?}"
));
}
const COALESCED_ITEMS: usize = 100;
let items: Vec<Vec<u8>> = (0..COALESCED_ITEMS)
.map(|index| format!("id-{index:03}-needle").into_bytes())
.collect();
let mut buffer = Vec::with_capacity(items.iter().map(Vec::len).sum());
for item in &items {
buffer.extend_from_slice(item);
}
let coalesced = scanner
.scan(backend.as_ref(), &buffer, 10_000)
.map_err(|error| format!("vyre coalesced GPU scan failed: {error}"))?;
if coalesced.len() != COALESCED_ITEMS {
return Err(format!(
"vyre coalesced GPU scan returned {} matches, expected {COALESCED_ITEMS}",
coalesced.len()
));
}
Ok(VyreGpuSelfTest {
direct_matches: direct.len(),
coalesced_matches: coalesced.len(),
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GpuRegionPresencePeerSelfTest {
pub backend: crate::hw_probe::ScanBackend,
pub backend_id: &'static str,
pub matches: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GpuRegionPresenceSelfTest {
pub peers: Vec<GpuRegionPresencePeerSelfTest>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GpuRegionPresenceSelfTestFailure {
pub acquired_backends: Vec<crate::hw_probe::ScanBackend>,
pub message: String,
}
impl std::fmt::Display for GpuRegionPresenceSelfTestFailure {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.message)
}
}
impl std::error::Error for GpuRegionPresenceSelfTestFailure {}
pub fn gpu_region_presence_self_test(
) -> Result<GpuRegionPresenceSelfTest, GpuRegionPresenceSelfTestFailure> {
#[cfg(not(feature = "gpu"))]
{
Err(GpuRegionPresenceSelfTestFailure {
acquired_backends: Vec::new(),
message: "GPU region-presence self-test not available in the lean ci build. Rebuild with `--features gpu` to exercise the production GPU trigger path.".to_string(),
})
}
#[cfg(feature = "gpu")]
{
gpu_region_presence_self_test_impl()
}
}
#[cfg(feature = "gpu")]
fn gpu_region_presence_self_test_impl(
) -> Result<GpuRegionPresenceSelfTest, GpuRegionPresenceSelfTestFailure> {
use crate::engine::CompiledScanner;
use crate::hw_probe::ScanBackend;
use keyhog_core::{Chunk, ChunkMetadata, DetectorFile};
const PLANTED: &str = "KHGPUSELFTEST_A1b2C3d4E5f6";
let detector =
toml::from_str::<DetectorFile>(include_str!("../../data/gpu-self-test-detector.toml"))
.map(|file| file.detector)
.map_err(|error| GpuRegionPresenceSelfTestFailure {
acquired_backends: Vec::new(),
message: format!("bundled GPU self-test detector TOML is invalid: {error}"),
})?;
let scanner = CompiledScanner::compile(vec![detector]).map_err(|error| {
GpuRegionPresenceSelfTestFailure {
acquired_backends: Vec::new(),
message: format!("CompiledScanner::compile failed during self-test: {error}"),
}
})?;
let candidates = scanner.gpu_backend_candidates();
let acquired_backends: Vec<_> = candidates
.iter()
.filter(|candidate| candidate.is_eligible())
.map(|candidate| candidate.backend)
.collect();
if acquired_backends.is_empty() {
let diagnostics = candidates
.iter()
.map(|candidate| {
let diagnostic = match candidate.acquisition_error.as_deref() {
Some(reason) => reason,
None => "driver was not acquired and returned no diagnostic",
};
format!("{}: {diagnostic}", candidate.backend.label())
})
.collect::<Vec<_>>()
.join("; ");
return Err(GpuRegionPresenceSelfTestFailure {
acquired_backends,
message: format!("no GPU region-presence peer was acquired ({diagnostics})"),
});
}
let make_chunk = || Chunk {
data: format!("gpu_secret = {PLANTED}").into(),
metadata: ChunkMetadata::default(),
};
let cpu_results = scanner.scan_chunks_with_backend(&[make_chunk()], ScanBackend::CpuFallback);
let cpu_total: usize = cpu_results.iter().map(Vec::len).sum();
if cpu_total == 0 {
return Err(GpuRegionPresenceSelfTestFailure {
acquired_backends,
message: "GPU self-test probe matched on no backend (CPU baseline is zero); fix the self-test probe so it survives suppression.".to_string(),
});
}
let mut peers = Vec::with_capacity(acquired_backends.len());
let mut failures = Vec::new();
for candidate in candidates
.into_iter()
.filter(|candidate| candidate.is_eligible())
{
let route = candidate.backend;
let Some(backend_id) = candidate.driver_id else {
failures.push(format!(
"{}: acquired driver returned no identity",
route.label()
));
continue;
};
let degrade_before = scanner.runtime_status().gpu_degrade_count;
let results = match scanner.try_scan_coalesced_gpu_region_presence(
&[make_chunk()],
route,
scanner.execution_route_for_backend(route),
) {
Ok(results) => results,
Err(error) => {
failures.push(format!(
"{} ({backend_id}): dispatch failed: {error}",
route.label()
));
continue;
}
};
if scanner.runtime_status().gpu_degrade_count > degrade_before {
let diagnostic = match scanner.last_gpu_degrade_reason() {
Some(reason) => reason,
None => "runtime degrade recorded without a diagnostic".to_owned(),
};
failures.push(format!("{} ({backend_id}): {diagnostic}", route.label()));
continue;
}
let total: usize = results.iter().map(Vec::len).sum();
if total != cpu_total {
failures.push(format!(
"{} ({backend_id}): found {total} match(es), CPU found {cpu_total}",
route.label()
));
continue;
}
peers.push(GpuRegionPresencePeerSelfTest {
backend: route,
backend_id,
matches: total,
});
}
if !failures.is_empty() {
let passed = peers
.iter()
.map(|peer| format!("{} ({})", peer.backend.label(), peer.backend_id))
.collect::<Vec<_>>()
.join(", ");
let passed = if passed.is_empty() {
"none".to_string()
} else {
passed
};
return Err(GpuRegionPresenceSelfTestFailure {
acquired_backends,
message: format!(
"GPU region-presence peer parity failed: {}; passed peers: {passed}",
failures.join("; ")
),
});
}
Ok(GpuRegionPresenceSelfTest { peers })
}