Skip to main content

qualia_core_db/inference/
inference_modes.rs

1//! Multi-mode inference — keep parallel approaches, do not dump one pipeline.
2//!
3//! Timothy (2026-07-09): different *modes* rather than replacing the portable
4//! resident path. Modes are selected by env / API; each has a honest role:
5//!
6//! | Mode | Role |
7//! |------|------|
8//! | **Portable** | wgpu resident decode/prefill (DX12/Vulkan/Metal) — default product path |
9//! | **CudaTc** | Prefer forge CUDA WMMA / TC GEMM when dims allow; fall back to Portable |
10//! | **QuantGraph** | Aggressive INT4/INT8 + **mid** hybrid hints + post graph repair |
11//! | **FastVerify** | Ollama-like full-speed decode (no mid-token Sentinel tax) → **post-turn** CML/graph self-heal + HTML |
12//!
13//! Modes compose toggles already in `inference_bench`; they do not invent a
14//! second engine. Graph-hybrid quality recovery is neuro-symbolic: LLM proposes,
15//! graph + Logic VM grounds, domain engines compute exact subproblems.
16
17use std::sync::atomic::{AtomicU8, Ordering};
18
19/// Active inference approach for this process.
20#[repr(u8)]
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum InferenceMode {
23    /// wgpu single-fence resident decode/prefill — default, portable.
24    Portable = 0,
25    /// Prefer CUDA tensor-core dense GEMM via forge when eligible; else Portable.
26    CudaTc = 1,
27    /// Quantized front-end + graph/Webizen grounding for quality recovery.
28    QuantGraph = 2,
29    /// Full-speed decode (skip mid-token Sentinel rings) then post-turn verify/heal.
30    FastVerify = 3,
31}
32
33impl InferenceMode {
34    pub const ALL: [InferenceMode; 4] = [
35        InferenceMode::Portable,
36        InferenceMode::CudaTc,
37        InferenceMode::QuantGraph,
38        InferenceMode::FastVerify,
39    ];
40
41    pub fn as_str(self) -> &'static str {
42        match self {
43            InferenceMode::Portable => "portable",
44            InferenceMode::CudaTc => "cuda",
45            InferenceMode::QuantGraph => "quant-graph",
46            InferenceMode::FastVerify => "fast-verify",
47        }
48    }
49
50    pub fn parse(s: &str) -> Option<Self> {
51        match s.trim().to_ascii_lowercase().as_str() {
52            "portable" | "wgpu" | "default" | "0" => Some(Self::Portable),
53            "cuda" | "cuda-tc" | "cudatc" | "tc" | "1" => Some(Self::CudaTc),
54            "quant-graph" | "quant_graph" | "graph" | "hybrid" | "neuro-symbolic" | "2" => {
55                Some(Self::QuantGraph)
56            }
57            "fast-verify" | "fast_verify" | "fastverify" | "ollama-like" | "post-verify"
58            | "verify" | "3" => Some(Self::FastVerify),
59            _ => None,
60        }
61    }
62
63    pub fn description(self) -> &'static str {
64        match self {
65            InferenceMode::Portable => {
66                "wgpu resident decode/prefill (DX12/Vulkan/Metal); Q4_K SoA INT4 coop GEMV; INT8 KV default; no second DirectML device unless QUALIA_DIRECTML=1"
67            }
68            InferenceMode::CudaTc => {
69                "CUDA-capable lane: resident wgpu decode by default (coherent); Q4_K SoA device GEMV when applicable; dense densify+TC decode GEMV is opt-in QUALIA_LLM_CUDA_TC_DECODE=1 (lab — was incoherent when default-on)"
70            }
71            InferenceMode::QuantGraph => {
72                "INT4 (Q4_K SoA) front-end — A2000 bandwidth sweet spot — + graph/Webizen verify to recover quality; INT2 experimental with graph net"
73            }
74            InferenceMode::FastVerify => {
75                "Ollama-like full-speed decode (no mid-token Sentinel) → post-turn quant-graph + CML/HTML self-heal before turn finalises"
76            }
77        }
78    }
79}
80
81static MODE: AtomicU8 = AtomicU8::new(InferenceMode::Portable as u8);
82
83#[inline]
84fn atomic_inference_mode() -> InferenceMode {
85    match MODE.load(Ordering::Relaxed) {
86        1 => InferenceMode::CudaTc,
87        2 => InferenceMode::QuantGraph,
88        3 => InferenceMode::FastVerify,
89        _ => InferenceMode::Portable,
90    }
91}
92
93/// Resolve mode from `QUALIA_INFERENCE_MODE` at the cold configuration boundary.
94///
95/// A successful environment parse is published to [`MODE`]. Per-token predicates must read
96/// that atomic via [`atomic_inference_mode`] instead of allocating a fresh environment string.
97pub fn active_inference_mode() -> InferenceMode {
98    // Env can override the configured atomic when this cold-boundary API is invoked.
99    if let Ok(s) = std::env::var("QUALIA_INFERENCE_MODE") {
100        if let Some(m) = InferenceMode::parse(&s) {
101            MODE.store(m as u8, Ordering::Relaxed);
102            return m;
103        }
104    }
105    atomic_inference_mode()
106}
107
108/// Set process mode and apply associated toggles. Env still wins on next read if set.
109pub fn set_inference_mode(mode: InferenceMode) {
110    MODE.store(mode as u8, Ordering::Relaxed);
111    apply_mode_toggles(mode);
112    log::info!("LLM_MODE|active|{}|{}", mode.as_str(), mode.description());
113}
114
115/// Apply mode-specific defaults without changing the mode atom (used at first infer).
116pub fn apply_mode_toggles(mode: InferenceMode) {
117    #[cfg(not(target_arch = "wasm32"))]
118    {
119        use crate::llm_bench::{
120            set_coop_gemv, set_resident_decode, set_resident_prefill, set_resident_weights,
121        };
122        // All modes keep resident paths ON by default; they differ in accel / grounding.
123        set_resident_decode(true);
124        set_resident_prefill(true);
125        set_resident_weights(true);
126        set_coop_gemv(true);
127        match mode {
128            InferenceMode::Portable => {
129                // Explicit: do not force CUDA TC for dense forge calls.
130            }
131            InferenceMode::CudaTc => {
132                crate::wgsl_forge::dispatch::ensure_cuda_runtime_path();
133                // Default: resident mega-pass ON (measured ~6.5–7 tok/s on 3B).
134                // QUALIA_LLM_CUDA_DECODE=1 opts into the layer-by-layer CUDA SoA path
135                // (P4: device RoPE/KV/SDPA + sticky Q4_K_SOA). Lab / A-B only until
136                // it beats resident. Device SDPA requires f32 KV (int8 indices differ).
137                match std::env::var("QUALIA_LLM_CUDA_DECODE").ok().as_deref() {
138                    Some("1") | Some("true") => {
139                        // Keep resident_decode ON — the wgpu resident path handles mixed
140                        // quant types (Q4_K_M has Q8_0 V, Q6_K down) and serves as fallback
141                        // when the CUDA mega-pass can't run (requires all-Q4_K_SOA weights).
142                        // The mega-pass is only attempted when the resident path returns None.
143                        // Force f32 KV so device SDPA index formula matches host layout.
144                        crate::llm_bench::set_kv_int8(false);
145                        // SAFETY: process-local lab toggle; decode-proxy is single-threaded measure.
146                        std::env::set_var("QUALIA_LLM_KV_INT8", "0");
147                        log::info!(
148                            "LLM_MODE|cuda|resident_decode=on|cuda_mega_pass_fallback|f32_kv|device_sdpa|lab_path"
149                        );
150                    }
151                    _ => {
152                        // Default excellence path: resident mega-pass + wgpu GEMV.
153                        // Dense densify+TC for decode GEMV is OFF unless QUALIA_LLM_CUDA_TC_DECODE=1
154                        // (that path was measured incoherent — garbage tokens — 2026-07-24).
155                        log::info!(
156                            "LLM_MODE|cuda|resident_decode=on|dense_tc_decode=off|use_q4k_soa_device_when_present"
157                        );
158                    }
159                }
160            }
161            InferenceMode::QuantGraph => {
162                // Prefer bandwidth-friendly layouts; graph verify is opt-in at agent layer.
163                // FFN f16 promote stays off (measured slower on A2000 for Q4).
164                // Refresh fact graph from bundled TSV / QUALIA_GROUNDING_FACTS.
165                let n = crate::quant_graph_grounding::seed_facts_from_bundled();
166                log::info!("LLM_MODE|quant-graph|facts_seeded|{n}");
167            }
168            InferenceMode::FastVerify => {
169                // Same weight path as portable; quality is *post-turn* only.
170                let n = crate::quant_graph_grounding::seed_facts_from_bundled();
171                log::info!("LLM_MODE|fast-verify|post_turn_only|facts_seeded|{n}");
172            }
173        }
174    }
175    let _ = mode;
176}
177
178/// True when dense forge GEMM should try tensor-core path first.
179#[inline]
180pub fn prefer_tensor_core_gemm() -> bool {
181    matches!(atomic_inference_mode(), InferenceMode::CudaTc)
182}
183
184/// True when agent should run graph / Webizen grounding after proposals.
185/// FastVerify also grounds — but only post-turn (see `post_turn_verify_enabled`).
186#[inline]
187pub fn quant_graph_grounding_enabled() -> bool {
188    matches!(
189        active_inference_mode(),
190        InferenceMode::QuantGraph | InferenceMode::FastVerify
191    )
192}
193
194/// Mid-decode Webizen Sentinel / logit-ring governance active?
195/// Off in FastVerify so decode matches Ollama-style uninterrupted generation.
196#[inline]
197pub fn sentinel_mid_decode_enabled() -> bool {
198    // Explicit override.
199    match std::env::var("QUALIA_SENTINEL_MID").ok().as_deref() {
200        Some("1") | Some("true") | Some("on") => return true,
201        Some("0") | Some("false") | Some("off") => return false,
202        _ => {}
203    }
204    !matches!(active_inference_mode(), InferenceMode::FastVerify)
205}
206
207/// Post-turn verify + self-heal (graph/CML/HTML) before finalising the turn.
208#[inline]
209pub fn post_turn_verify_enabled() -> bool {
210    matches!(
211        active_inference_mode(),
212        InferenceMode::FastVerify | InferenceMode::QuantGraph
213    ) || matches!(
214        std::env::var("QUALIA_POST_VERIFY").ok().as_deref(),
215        Some("1") | Some("true") | Some("on")
216    )
217}
218
219/// FastVerify defaults to returning plain healed text; HTML when this is true.
220#[inline]
221pub fn fast_verify_html_default() -> bool {
222    matches!(
223        std::env::var("QUALIA_RETURN_VERIFY_HTML").ok().as_deref(),
224        Some("1") | Some("true") | Some("on")
225    )
226}
227
228/// One-shot: resolve env, apply toggles, return mode (call from agent entry).
229///
230/// Order:
231/// 1. Device path selector (passport-ranked backend + lane + quant) when `QUALIA_PATH_AUTO`
232/// 2. Rights-mode quant-graph if still unset
233/// 3. Active mode toggles (resident, coop GEMV, INT8 KV, …)
234pub fn bootstrap_inference_mode() -> InferenceMode {
235    #[cfg(not(target_arch = "wasm32"))]
236    {
237        let _plan = crate::inference_path_selector::bootstrap_optimal_inference_path();
238        // Application profile (interactive / live-fast / batch overnight).
239        let _app = crate::application_profile::bootstrap_application_profile();
240    }
241    // Rights-grade consumer default: FastVerify (speed + post-heal) unless pinned.
242    if matches!(
243        std::env::var("QUALIA_RIGHTS_MODE").ok().as_deref(),
244        Some("1") | Some("true") | Some("on")
245    ) {
246        if std::env::var("QUALIA_INFERENCE_MODE").is_err()
247            && std::env::var("QUALIA_APP_PROFILE").is_err()
248        {
249            set_inference_mode(InferenceMode::FastVerify);
250        }
251        log::info!("LLM_MODE|rights|fast-verify|post_turn_heal");
252    }
253    // Unpinned default on NVIDIA: FastVerify (smol ~60+ tok/s). Operator can pin
254    // QUALIA_INFERENCE_MODE=cuda for large SoA models (3B ~7 tok/s measured).
255    if std::env::var("QUALIA_INFERENCE_MODE").is_err()
256        && std::env::var("QUALIA_APP_PROFILE").is_err()
257        && std::env::var("QUALIA_RIGHTS_MODE").is_err()
258    {
259        set_inference_mode(InferenceMode::FastVerify);
260        log::info!("LLM_MODE|default|fast-verify|consumer_speed");
261    }
262    let m = active_inference_mode();
263    apply_mode_toggles(m);
264    m
265}
266
267/// True when operator asked for rights-grade defaults (`QUALIA_RIGHTS_MODE`).
268#[inline]
269pub fn rights_mode_enabled() -> bool {
270    matches!(
271        std::env::var("QUALIA_RIGHTS_MODE").ok().as_deref(),
272        Some("1") | Some("true") | Some("on")
273    )
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn parse_mode_names() {
282        assert_eq!(
283            InferenceMode::parse("portable"),
284            Some(InferenceMode::Portable)
285        );
286        assert_eq!(InferenceMode::parse("CUDA"), Some(InferenceMode::CudaTc));
287        assert_eq!(
288            InferenceMode::parse("quant-graph"),
289            Some(InferenceMode::QuantGraph)
290        );
291        assert_eq!(
292            InferenceMode::parse("hybrid"),
293            Some(InferenceMode::QuantGraph)
294        );
295        assert_eq!(
296            InferenceMode::parse("fast-verify"),
297            Some(InferenceMode::FastVerify)
298        );
299        assert_eq!(
300            InferenceMode::parse("ollama-like"),
301            Some(InferenceMode::FastVerify)
302        );
303        assert!(InferenceMode::parse("nope").is_none());
304    }
305
306    #[test]
307    fn fast_verify_disables_mid_sentinel() {
308        if std::env::var("QUALIA_INFERENCE_MODE").is_ok()
309            || std::env::var("QUALIA_SENTINEL_MID").is_ok()
310        {
311            return;
312        }
313        set_inference_mode(InferenceMode::FastVerify);
314        assert!(!sentinel_mid_decode_enabled());
315        assert!(post_turn_verify_enabled());
316        set_inference_mode(InferenceMode::Portable);
317        assert!(sentinel_mid_decode_enabled());
318    }
319
320    #[test]
321    fn set_and_read_without_env() {
322        // Do not assert env-free if the machine has QUALIA_INFERENCE_MODE set.
323        if std::env::var("QUALIA_INFERENCE_MODE").is_ok() {
324            return;
325        }
326        set_inference_mode(InferenceMode::CudaTc);
327        assert_eq!(active_inference_mode(), InferenceMode::CudaTc);
328        set_inference_mode(InferenceMode::Portable);
329        assert_eq!(active_inference_mode(), InferenceMode::Portable);
330    }
331
332    #[test]
333    #[serial_test::serial]
334    fn cold_env_publish_makes_hot_cuda_guard_zero_allocation() {
335        let previous = std::env::var("QUALIA_INFERENCE_MODE").ok();
336        std::env::set_var("QUALIA_INFERENCE_MODE", "cuda");
337        set_inference_mode(InferenceMode::Portable);
338        assert_eq!(active_inference_mode(), InferenceMode::CudaTc);
339        crate::specialized_libs::computational_geometry::allocation_counter::assert_zero_alloc(
340            "atomic_cuda_mode_guard",
341            || assert!(prefer_tensor_core_gemm()),
342        );
343        match previous {
344            Some(value) => {
345                std::env::set_var("QUALIA_INFERENCE_MODE", value);
346                let _ = active_inference_mode();
347            }
348            None => {
349                std::env::remove_var("QUALIA_INFERENCE_MODE");
350                set_inference_mode(InferenceMode::Portable);
351            }
352        }
353    }
354}