Skip to main content

keyhog_scanner/
gpu_input_budget.rs

1//! VRAM-adaptive GPU batch-input sizing.
2//!
3//! This module owns the live GPU region-presence byte-budget selector used for
4//! routing and cache-key stability.
5
6use std::sync::atomic::{AtomicUsize, Ordering};
7
8// ---------------------------------------------------------------------------
9// VRAM sizing table: ONE owner for every threshold and byte budget.
10//
11// The adaptive `gpu_batch_input_limit_for_vram_mb` match arms below are the SOLE
12// readers of these; nothing is a bare magic number inline. The `_UNKNOWN` floor
13// (128 MiB) doubles as the lower clamp bound and the `_HIGH` budget (1 GiB) as
14// the upper clamp bound for any Tier-A override, so the operator can never drive
15// the buffer outside the range the table itself honors.
16// ---------------------------------------------------------------------------
17
18/// `>= 24 GiB` VRAM (RTX 4090 / 5090, A100 / H100) -> 1 GiB input.
19pub(crate) const VRAM_MB_TIER_HIGH: u64 = 24 * 1024;
20pub(crate) const GPU_BATCH_INPUT_LIMIT_HIGH: usize = 1024 * 1024 * 1024;
21/// `12 - 23 GiB` VRAM (RTX 3090, RTX 4080, M-Max) -> 512 MiB input.
22pub(crate) const VRAM_MB_TIER_MID: u64 = 12 * 1024;
23pub(crate) const GPU_BATCH_INPUT_LIMIT_MID: usize = 512 * 1024 * 1024;
24/// `8 - 11 GiB` VRAM (RTX 3080, RTX 4070, M-Pro) -> 256 MiB input.
25pub(crate) const VRAM_MB_TIER_LOW: u64 = 8 * 1024;
26pub(crate) const GPU_BATCH_INPUT_LIMIT_LOW: usize = 256 * 1024 * 1024;
27
28/// Conservative floor for hosts with low or unknown VRAM. Unknown must not
29/// inherit the 8-11 GiB tier: absence of adapter memory evidence is the same
30/// safety class as low-memory/iGPU/software adapters. Also the lower clamp bound
31/// for a Tier-A override (see [`set_gpu_batch_input_limit`]).
32pub(crate) const GPU_BATCH_INPUT_LIMIT_UNKNOWN: usize = 128 * 1024 * 1024;
33
34/// Process-wide GPU batch-input override in bytes. `0` = unset (use the
35/// VRAM-adaptive table). Set ONCE at scan startup, before the first
36/// [`gpu_batch_input_limit`] call caches the value, from resolved config (Tier-A:
37/// compiled default -> `.keyhog.toml` -> `--gpu-batch-input-limit`). Mirrors the
38/// `REGEX_DFA_LIMIT_OVERRIDE` process-global pattern so the routing/cache-key
39/// path needs no per-call plumbing.
40static GPU_BATCH_INPUT_LIMIT_OVERRIDE: AtomicUsize = AtomicUsize::new(0);
41
42/// The `[floor, cap]` the resolved GPU batch input limit is clamped into: the
43/// 128 MiB unknown-host floor and the 1 GiB pre-compile-time ceiling that bound
44/// the VRAM table. A Tier-A override is clamped into this range so no config/CLI
45/// value can request a buffer the sizing contract forbids.
46#[must_use]
47pub fn gpu_batch_input_limit_bounds() -> (usize, usize) {
48    (GPU_BATCH_INPUT_LIMIT_UNKNOWN, GPU_BATCH_INPUT_LIMIT_HIGH)
49}
50
51/// Override the GPU batch input limit for this process. Call before scanning.
52/// `0` resets to the VRAM-adaptive default; any other value is clamped into
53/// [`gpu_batch_input_limit_bounds`] at read time. Tier-A config knob
54/// (compiled default -> TOML -> CLI), the sizing analogue of
55/// [`crate::types::set_regex_dfa_limit`].
56pub fn set_gpu_batch_input_limit(bytes: usize) {
57    GPU_BATCH_INPUT_LIMIT_OVERRIDE.store(bytes, Ordering::Relaxed);
58}
59
60/// Clamp a raw Tier-A override into [`gpu_batch_input_limit_bounds`]. Pure, testable
61/// without the process-global, so the clamp contract is proven deterministically.
62pub(crate) fn clamp_gpu_batch_input_limit(bytes: usize) -> usize {
63    let (floor, cap) = gpu_batch_input_limit_bounds();
64    bytes.clamp(floor, cap)
65}
66
67/// Resolve the Tier-A override into an effective byte budget, or `None` when
68/// unset (`0`). Split out so the cached entry point stays thin. Reads the
69/// process-global; the clamp itself lives in [`clamp_gpu_batch_input_limit`].
70pub(crate) fn gpu_batch_input_limit_override() -> Option<usize> {
71    match GPU_BATCH_INPUT_LIMIT_OVERRIDE.load(Ordering::Relaxed) {
72        0 => None,
73        n => Some(clamp_gpu_batch_input_limit(n)),
74    }
75}
76
77/// VRAM-adaptive GPU batch-input limit. Bigger buffers mean fewer
78/// device dispatches per multi-TB scan; each kernel launch is a fixed
79/// ~50-300 µs cost regardless of payload, so doubling the input
80/// halves dispatch overhead. Capped by host VRAM (input + transition
81/// tables + match output must fit) and by a 1 GiB upper bound so the
82/// pre-compile time stays bounded.
83///
84/// | VRAM detected     | Input length | Adapter examples                 |
85/// |-------------------|--------------|----------------------------------|
86/// | >= 24 GiB         | 1 GiB        | RTX 4090 / 5090, A100 / H100     |
87/// | 12 - 23 GiB       | 512 MiB      | RTX 3090, RTX 4080, M-Max        |
88/// | 8 - 11 GiB        | 256 MiB      | RTX 3080, RTX 4070, M-Pro        |
89/// |  < 8 GiB / Unknown| 128 MiB      | iGPU, software, no-GPU CI runner |
90///
91/// Cached on first call; the result is stable for the process
92/// lifetime so routing and cache identities stay consistent across
93/// every batch.
94pub fn gpu_batch_input_limit() -> usize {
95    // Read the explicit Tier-A value on every call. Only the hardware-derived
96    // default is cached, so setting or clearing the override can never be
97    // silently ignored merely because another caller resolved the default first.
98    if let Some(len) = gpu_batch_input_limit_override() {
99        tracing::debug!(
100            target: "keyhog::routing",
101            gpu_batch_input_limit = len,
102            "GPU batch input limit set from Tier-A override"
103        );
104        return len;
105    }
106    use std::sync::OnceLock;
107    static CACHED: OnceLock<usize> = OnceLock::new();
108    *CACHED.get_or_init(|| {
109        let caps = crate::hw_probe::probe_hardware();
110        let len = gpu_batch_input_limit_for_vram_mb(caps.gpu_vram_mb);
111        tracing::debug!(
112            target: "keyhog::routing",
113            gpu_vram_mb = ?caps.gpu_vram_mb,
114            gpu_batch_input_limit = len,
115            "GPU batch input limit sized for VRAM"
116        );
117        len
118    })
119}
120
121pub(crate) fn gpu_batch_input_limit_for_vram_mb(gpu_vram_mb: Option<u64>) -> usize {
122    match gpu_vram_mb {
123        Some(mb) if mb >= VRAM_MB_TIER_HIGH => GPU_BATCH_INPUT_LIMIT_HIGH,
124        Some(mb) if mb >= VRAM_MB_TIER_MID => GPU_BATCH_INPUT_LIMIT_MID,
125        Some(mb) if mb >= VRAM_MB_TIER_LOW => GPU_BATCH_INPUT_LIMIT_LOW,
126        Some(_) | None => GPU_BATCH_INPUT_LIMIT_UNKNOWN,
127    }
128}