Skip to main content

qualia_core_db/inference/inference_agent/
decode.rs

1// Phase 8 bifurcated-compute decode path for `LocalLlmAgent`.
2//
3// This is the LIVE LLM decode agent: it loads the GGUF/P64 model, tokenises,
4// runs the autoregressive loop through `QTensorEngine`, and streams logit
5// summaries from the LLM engine thread to the Webizen Sentinel over wait-free
6// SPSC ring buffers. Moved verbatim from the `inference_agent.rs` monolith —
7// no logic, control-flow, or signature changes.
8
9#[cfg(not(target_arch = "wasm32"))]
10use super::config::effective_inference_timeout_ms;
11use super::config::{DECODE_TOKEN_BUDGET, TEST_TRANSFORMER_LAYER_CAP, TEST_VOCAB_CHUNK_CAP};
12#[cfg(not(target_arch = "wasm32"))]
13use super::decode_helpers::get_prefix_cache;
14use super::decode_helpers::{
15    apply_model_helper_stops, build_sieve, drain_tensor_context_inject, embedding_fallback_logits,
16    try_accept_topology_draft, TopologyDraftStep,
17};
18use super::local_agent::LocalLlmAgent;
19#[cfg(not(target_arch = "wasm32"))]
20use super::sticky_infer;
21use super::types::AgentBackend;
22use crate::{q_hash, NQuin};
23
24impl LocalLlmAgent {
25    /// Phase 8: Bifurcated Compute — SPSC Wait-Free Intercept.
26    ///
27    /// On native targets: loads the GGUF model, tokenises the prompt, and runs an
28    /// autoregressive decode loop via `QTensorEngine::dispatch_fused_transformer_block`.
29    /// Logit summaries flow from the LLM engine thread to the Webizen Sentinel (this
30    /// thread) over a wait-free SPSC ring. The Sentinel may inject `DenyRollback`
31    /// for real governance signals; the old IEEE-754 `0x99` mantissa check was
32    /// removed (it fired randomly ~1/256 tokens and corrupted the stream).
33    ///
34    /// On WASM / non-local backends: falls through to the original mock path.
35    /// Run local inference, optionally streaming decoded text deltas to `on_token`.
36    pub fn infer_local_model_streaming<F: FnMut(String) + Send + 'static>(
37        &self,
38        prompt: &str,
39        graph_context: &str,
40        on_token: Option<F>,
41    ) -> (String, Vec<u64>, u32, Option<NQuin>) {
42        self.infer_local_model_inner(prompt, graph_context, on_token)
43    }
44
45    pub(super) fn infer_local_model(
46        &self,
47        prompt: &str,
48        graph_context: &str,
49    ) -> (String, Vec<u64>, u32, Option<NQuin>) {
50        self.infer_local_model_inner::<fn(String)>(prompt, graph_context, None)
51    }
52
53    #[cfg_attr(target_arch = "wasm32", allow(unused_variables, unused_mut))]
54    fn infer_local_model_inner<F: FnMut(String) + Send + 'static>(
55        &self,
56        prompt: &str,
57        graph_context: &str,
58        mut on_token: Option<F>,
59    ) -> (String, Vec<u64>, u32, Option<NQuin>) {
60        let prov_hash = graph_context
61            .bytes()
62            .take(8)
63            .fold(0u64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64));
64        let use_sieve = self
65            .use_sieve_output
66            .load(std::sync::atomic::Ordering::Relaxed);
67        let sieve_spec = if use_sieve {
68            Some(*self.sieve_spec.lock().unwrap_or_else(|e| e.into_inner()))
69        } else {
70            None
71        };
72        let sieve_lex_path = if use_sieve {
73            self.sieve_lex_path
74                .lock()
75                .unwrap_or_else(|e| e.into_inner())
76                .clone()
77        } else {
78            None
79        };
80
81        // ── Native GPU path ─────────────────────────────────────────────────
82        #[cfg(not(target_arch = "wasm32"))]
83        {
84            use crate::gguf_bridge::{QTensor, QTensorEngine};
85            use crate::gguf_sharder::GgufTokenizer;
86            use rtrb::RingBuffer;
87
88            let model_path = match &self.backend {
89                AgentBackend::Local { model_path, .. } => model_path.clone(),
90                _ => {
91                    return (
92                        String::from("[no local model configured]"),
93                        vec![prov_hash],
94                        0,
95                        None,
96                    );
97                }
98            };
99            let prompt_owned = prompt.to_string();
100
101            // Multi-mode: portable | cuda | quant-graph (`QUALIA_INFERENCE_MODE`).
102            let _mode = crate::inference_modes::bootstrap_inference_mode();
103
104            // ── LoRA context detection (before thread spawn) ─────────────────
105            // Detect the prompt domain and pre-load the matching LoRA adapter.
106            // The pre-computed delta vectors are cloned into the inference thread
107            // as fixed-size heap data — one allocation per infer call, not per token.
108            #[allow(unused_variables)]
109            let lora_active_adapter: Option<crate::lora::LoRAAdapter> = {
110                let mut guard = self.lora_manager.lock().unwrap_or_else(|e| e.into_inner());
111                if let Some(ref mut mgr) = *guard {
112                    let (ctx, conf, _switched) =
113                        mgr.auto_switch(&prompt_owned, mgr.detector.confidence_threshold);
114                    log::debug!("LoRA|context-detect|domain={ctx}|conf={conf:.3}");
115                    mgr.active().cloned()
116                } else {
117                    None
118                }
119            };
120
121            // Fixed-size types keep the hot-path allocation-free in the ring buffer.
122            #[derive(Clone, Copy)]
123            struct LogitSummary {
124                _top_id: u32,
125                anomaly: u8,
126            }
127            #[derive(Clone)]
128            enum LlmMsg {
129                Logit(LogitSummary),
130                Eos,
131            }
132            #[derive(Clone)]
133            enum SentMsg {
134                DenyRollback,
135            }
136
137            // LogitStream: LLM engine → Webizen Sentinel
138            let (mut lp, mut lc) = RingBuffer::<LlmMsg>::new(1024);
139            // ControlStream: Webizen Sentinel → LLM engine
140            let (mut cp, mut cc) = RingBuffer::<SentMsg>::new(16);
141
142            let stream_pair = if on_token.is_some() {
143                Some(std::sync::mpsc::sync_channel::<String>(512))
144            } else {
145                None
146            };
147            let stream_tx_thread = stream_pair.as_ref().map(|(tx, _)| tx.clone());
148
149            // Move the (optional) LoRA adapter into the inference thread.
150            let lora_for_thread = lora_active_adapter;
151
152            // Sticky pool thread owns the engine (thread_local); caller runs Sentinel.
153            let (done_tx, done_rx) =
154                std::sync::mpsc::sync_channel::<(String, u32, Option<NQuin>, bool)>(1);
155
156            // ── LLM engine job (sticky 1-thread pool) ────────────────────────
157            sticky_infer::pool().spawn(move || {
158                let result = sticky_infer::with_engine(
159                    model_path.as_str(),
160                    |engine| {
161                        // Load only on cache miss / path change.
162                        if let Some(mmap) =
163                            crate::resident_model::resident_mmap_for_path(model_path.as_str())
164                        {
165                            let is_p64 = crate::p64_weight::has_p64_magic(&mmap[..]);
166                            let adopted = if is_p64 {
167                                engine.adopt_resident_p64_mmap(mmap).is_ok()
168                            } else {
169                                engine.adopt_resident_mmap(mmap).is_ok()
170                            };
171                            if !adopted {
172                                engine.load_model(&model_path);
173                            }
174                        } else {
175                            engine.load_model(&model_path);
176                        }
177                    },
178                    |engine: &mut QTensorEngine| {
179                // Initialize Tokio runtime for the sticky thread (once per job; cheap if already warm).
180                let rt = tokio::runtime::Builder::new_current_thread()
181                    .enable_all()
182                    .build()
183                    .unwrap_or_else(|e| {
184                        panic!("Failed to create Tokio runtime for LLM thread: {}", e)
185                    });
186                let _rt_guard = rt.enter();
187
188                // A0 phase timing (D17/D22): once-per-phase, off the per-token hot path.
189                let t_phase = std::time::Instant::now();
190
191                // Thermal-eviction WAL is a native-only file mmap (`memmap2`); on
192                // wasm there is no mmap'd-file WAL, so the telemetry is simply absent.
193                #[cfg(not(target_arch = "wasm32"))]
194                let mut thermal_wal_opt = {
195                    let wal_path = std::env::var("QUALIA_DATA_DIR")
196                        .map(|p| std::path::PathBuf::from(p).join("thermal_eviction.wal"))
197                        .unwrap_or_else(|_| std::env::temp_dir().join("thermal_eviction.wal"));
198                    crate::inference::thermal_wal::ThermalWal::open(&wal_path, 1024).ok()
199                };
200
201                let lora_adapter = lora_for_thread;
202                let sieve_spec = sieve_spec;
203                let sieve_lex_path = sieve_lex_path;
204
205                // Tokenizer + tensor index come from the matching on-disk
206                // format. P64 carries a Q42T tokenizer section and a manifest;
207                // GGUF carries both in its own metadata.
208                let is_p64_mmap = engine
209                    .gguf_mmap
210                    .as_ref()
211                    .map(|m| crate::p64_weight::has_p64_magic(&m[..]))
212                    .unwrap_or(false);
213                // Prefer engine-cached index from adopt (zero re-CRC); else parse once.
214                let p64_index = engine.p64_index.clone().or_else(|| {
215                    if is_p64_mmap {
216                        engine
217                            .gguf_mmap
218                            .as_ref()
219                            .and_then(|m| crate::p64_weight::P64TensorIndex::from_p64(m).ok())
220                    } else {
221                        None
222                    }
223                });
224                let mut tok = if let (Some(qi), Some(m)) =
225                    (p64_index.as_ref(), engine.gguf_mmap.as_ref())
226                {
227                    GgufTokenizer::from_p64_section(qi.tokenizer_bytes(m)).unwrap_or_default()
228                } else {
229                    engine
230                        .gguf_mmap
231                        .as_ref()
232                        .map(|m| GgufTokenizer::from_gguf(m))
233                        .unwrap_or_default()
234                };
235                // Sibling canonical `.q42` metadata (convert-time stop set / chat metadata).
236                apply_model_helper_stops(&model_path, &mut tok);
237
238                let tensor_idx = engine.tensor_index_cache.clone().or_else(|| {
239                    if let Some(qi) = p64_index {
240                        Some(qi.to_gguf_index())
241                    } else {
242                        engine
243                            .gguf_mmap
244                            .as_ref()
245                            .map(|m| crate::gguf_sharder::GgufTensorIndex::from_gguf(m))
246                    }
247                });
248
249                let mut ctx = tok.encode_chat_prompt(&prompt_owned);
250                // Keep `eos` for draft/topology APIs that still take a single id; decode
251                // termination uses the full stop set (eos + chat end-of-turn specials).
252                let eos = tok.eos_token_id;
253                let vlen = tok.vocab_len().max(1);
254
255                // Use the real embedding dimension if the tensor was found; fall back to 4096.
256                let emb_dim = tensor_idx
257                    .as_ref()
258                    .map(|idx| idx.emb_dim())
259                    .filter(|&d| d > 0)
260                    .unwrap_or(4096);
261
262                // Stack buffers — zero-heap path (512MB floor safe).
263                use crate::gguf_bridge::{PREFILL_CHUNK_SIZE, PREFILL_CHUNK_STACK_FLOATS};
264
265                const MAX_EMB_DIM: usize = 8192;
266                const MAX_FFN_DIM: usize = 10240;
267                let mut emb_buf = [0f32; MAX_EMB_DIM];
268                let mut scratch_a = [0f32; MAX_FFN_DIM];
269                let mut scratch_b = [0f32; MAX_FFN_DIM];
270                let mut prefill_chunk = [0f32; PREFILL_CHUNK_STACK_FLOATS];
271                let emb_dim = emb_dim.min(MAX_EMB_DIM);
272                let mut prefix_cached = false;
273                if prov_hash != 0 {
274                    if let Ok(cache) = get_prefix_cache().lock() {
275                        if let Some(cached_kv) = cache.get(&prov_hash) {
276                            engine.set_kv_cache_cpu(cached_kv);
277                            prefix_cached = true;
278                        }
279                    }
280                }
281
282                if !prefix_cached {
283                    engine.reset_kv_cache();
284                }
285
286                // Phase boundary: load (mmap/adopt + tokenizer + tensor index + setup) done.
287                crate::llm_bench::record_load_ns(t_phase.elapsed().as_nanos() as u64);
288                let t_prefill = std::time::Instant::now();
289
290                // Chunked prefill: populate KV for prompt tokens [0, prompt_len-1).
291                let prompt_len = ctx.len();
292                crate::tensor::kv_provenance::rebuild_prompt_provenance(
293                    prompt_len as u32,
294                    crate::tensor::resident_substrate::global_resident_substrate().node_count(),
295                    0,
296                );
297                let draft_mapper = crate::topology_draft::TopologyDraftMapper::new(&tok);
298                if !prefix_cached {
299                    if prompt_len > 1 {
300                        if let Some(idx) = tensor_idx.as_ref() {
301                            let prefill_tokens = prompt_len - 1;
302                            let chunk_cap = (PREFILL_CHUNK_STACK_FLOATS / emb_dim)
303                                .min(PREFILL_CHUNK_SIZE)
304                                .max(1);
305                            let mut pos = 0usize;
306                            while pos < prefill_tokens {
307                                let n = (prefill_tokens - pos).min(chunk_cap);
308                                let batch_elems = n * emb_dim;
309                                {
310                                    let mmap = match engine.gguf_mmap.as_deref() {
311                                        Some(m) => m,
312                                        None => break,
313                                    };
314                                    for t in 0..n {
315                                        let _ = idx.dequantize_token_embedding_into(
316                                            mmap,
317                                            ctx[pos + t],
318                                            &mut prefill_chunk[t * emb_dim..(t + 1) * emb_dim],
319                                        );
320                                    }
321                                }
322                                if !engine.dispatch_prefill_chunk(
323                                    idx,
324                                    &mut prefill_chunk[..batch_elems],
325                                    emb_dim,
326                                    n as u32,
327                                    pos as u32,
328                                    &mut scratch_a,
329                                    &mut scratch_b,
330                                    TEST_TRANSFORMER_LAYER_CAP,
331                                ) {
332                                    crate::gguf_bridge::wlog(&format!(
333                                        "[llm] PREFILL chunk FAILED pos={pos} n={n}"
334                                    ));
335                                }
336                                pos += n;
337                            }
338                        }
339                    }
340
341                    if prov_hash != 0 {
342                        if let Some(cpu_kv) = engine.get_kv_cache_cpu() {
343                            if let Ok(mut cache) = get_prefix_cache().lock() {
344                                cache.insert(prov_hash, cpu_kv.into());
345                            }
346                        }
347                    }
348                }
349
350                // Phase boundary: prefill done. Decode phase begins below.
351                crate::llm_bench::record_prefill(
352                    t_prefill.elapsed().as_nanos() as u64,
353                    prompt_len.saturating_sub(1) as u64,
354                );
355
356                let mut out_ids: Vec<u32> = Vec::new();
357                let mut streamed_len = 0usize;
358                let mut sieve = if use_sieve {
359                    build_sieve(&tok, sieve_spec.as_ref(), sieve_lex_path.as_deref())
360                } else {
361                    None
362                };
363                let mut semantic_quin: Option<NQuin> = None;
364                let mut sieve_failed = false;
365                let gen_budget = if sieve.is_some() {
366                    3usize
367                } else {
368                    // Benchmark override (A0): a fixed decode count for stable tok/s; 0 = default.
369                    let ov = crate::llm_bench::decode_budget_override();
370                    if ov > 0 {
371                        ov as usize
372                    } else {
373                        DECODE_TOKEN_BUDGET as usize
374                    }
375                };
376
377                #[cfg(not(target_arch = "wasm32"))]
378                crate::compute_universe::start_tensor_search_producer();
379                crate::compute_universe::publish_query_tensor(
380                    crate::tensor::Tensor10D::default(),
381                    0,
382                );
383                // Qualia-unique hybrid: graph route mask + 10D query + deontic obligation.
384                // Must run *after* the default query publish so it is not wiped.
385                crate::qualia_hybrid::prepare_hybrid_decode(&prompt_owned);
386
387                let t_decode = std::time::Instant::now();
388                // A1a: GPU top-1 decode path toggle (default-on; QUALIA_LLM_GPU_TOPK / set_gpu_topk).
389                let gpu_topk_enabled = crate::llm_bench::gpu_topk_enabled();
390                // W2: exact CPU sampler. `None` ⇒ greedy argmax (pre-W2 byte-identical path). When
391                // active, decode uses the legacy forward (leaves the normed hidden in `emb_buf`),
392                // reads back the FULL logit vector, and runs the penalty/temp/top-k/top-p chain.
393                #[cfg(not(target_arch = "wasm32"))]
394                let mut sampler =
395                    crate::llm_bench::sampler_config().map(crate::sampler::SamplerState::new);
396                #[cfg(target_arch = "wasm32")]
397                let mut sampler: Option<crate::sampler::SamplerState> = None;
398                let mut sampler_logits: Vec<f32> = Vec::new();
399                // Decode-profiler (gated): one-shot empty submit→wait baseline on the SAME device, so
400                // the bench can separate per-token fence latency from real kernel compute time.
401                #[cfg(not(target_arch = "wasm32"))]
402                if std::env::var("QUALIA_LLM_PROFILE_DECODE").is_ok() {
403                    let n = 64u32;
404                    crate::llm_bench::record_empty_rt(
405                        engine.bench_empty_submit_roundtrip(n),
406                        n as u64,
407                    );
408                }
409
410                // Phase 6: Initialize Semantic Chunking State
411                let mut current_page_id = 100u64; // Starting mock page ID
412                let chunk_policy = crate::q42::q42_kvp::Q42ChunkPolicy {
413                    max_tokens: 128,
414                    semantic_shift_threshold: 0.0,
415                    discourse_boundary_weight: 0.0,
416                    attention_phase_weight: 0.0,
417                    max_entropy_drop: -2.0, // A threshold that will trigger when top1 and top2 are close
418                    thermal_pressure_bias: 0.0,
419                    reserved: [0; 40],
420                };
421
422                // QUALIA_GRAPH_FORCE=1: emit grounded repair tokens without model decode.
423                let mut graph_force_emitted = false;
424                #[cfg(not(target_arch = "wasm32"))]
425                if let Some(forced) = crate::qualia_hybrid::force_fact_tokens(&prompt_owned, &|s| {
426                    tok.encode(s)
427                }) {
428                    for &tid in &forced {
429                        let next = tid % vlen.max(1);
430                        out_ids.push(next);
431                        ctx.push(next);
432                        if let Some(ref tx) = stream_tx_thread {
433                            let full = tok.decode(&out_ids);
434                            if full.len() > streamed_len {
435                                let delta = full[streamed_len..].to_string();
436                                streamed_len = full.len();
437                                let _ = tx.send(delta);
438                            }
439                        }
440                        let fixed = crate::llm_bench::decode_budget_fixed_tokens();
441                        if out_ids.len() >= gen_budget
442                            || (!fixed && tok.is_stop_token(next))
443                        {
444                            break;
445                        }
446                    }
447                    graph_force_emitted = true;
448                }
449
450                if !graph_force_emitted {
451                for step in 0..gen_budget {
452                    crate::gpu_context::record_llm_decode_step();
453
454                    // Codex P0 — cooperative deadline: break BEFORE the wall-clock timeout instead of
455                    // the old post-hoc check in infer() that let a no-EOS run continue for minutes.
456                    // t_decode starts at decode entry (post-prefill); INFERENCE_TIMEOUT_MS bounds the
457                    // generation phase so the call never appears frozen.
458                    if t_decode.elapsed().as_millis() as u64 >= effective_inference_timeout_ms() {
459                        break;
460                    }
461
462                    // W7 — periodic GPU thermal check during sustained decode: recommends a TDP cap,
463                    // and (when the auto-cap user option is on + a real NVML governor is present)
464                    // applies it under sustained Critical, restoring on cool-down. Cheap NVML read
465                    // every 32 tokens; no-op without the `nvml` feature or an NVIDIA card.
466                    #[cfg(not(target_arch = "wasm32"))]
467                    if step > 0 && step % 32 == 0 {
468                        crate::inference::thermal_telemetry::thermal_tick();
469                    }
470
471                    // W6a — prompt-lookup / graph fact speculative decode (default OFF, exact-output).
472                    // Prefer quant-graph fact draft when mode=quant-graph; else n-gram prompt-lookup.
473                    // FastVerify: skip mid-decode fact draft (post-turn heal only) for Ollama-like speed.
474                    // Verify drafts in ONE batched forward. Bit-identical to greedy when accepted.
475                    #[cfg(not(target_arch = "wasm32"))]
476                    if (crate::llm_bench::spec_decode_enabled()
477                        || (crate::inference_modes::quant_graph_grounding_enabled()
478                            && crate::inference_modes::sentinel_mid_decode_enabled()))
479                        && sieve.is_none()
480                        && sampler.is_none()
481                        && TEST_TRANSFORMER_LAYER_CAP == 0
482                    {
483                        if let Some(idx) = tensor_idx.as_ref() {
484                            let cur = *ctx.last().unwrap_or(&tok.bos_token_id);
485                            let draft = crate::qualia_hybrid::propose_best_draft(
486                                &prompt_owned,
487                                &ctx,
488                                &|s| tok.encode(s),
489                            );
490                            if draft.len > 0 {
491                                // inputs = [cur, d0..d_{m-1}] at positions [pos, pos+m], pos = ctx.len()-1.
492                                let mut inputs = Vec::with_capacity(draft.len + 1);
493                                inputs.push(cur);
494                                inputs.extend_from_slice(draft.as_slice());
495                                let pos = ctx.len().saturating_sub(1) as u32;
496                                let mut amax: Vec<u32> = Vec::new();
497                                let mut alog: Vec<f32> = Vec::new();
498                                if engine
499                                    .verify_draft_batch(idx, &inputs, pos, &mut amax, &mut alog)
500                                    .is_some()
501                                    && amax.len() == inputs.len()
502                                {
503                                    // Accept the longest prefix where argmax[i] == draft[i]; then emit
504                                    // d0..d_{k-1} (accepted) + argmax[k] (correction/bonus) = k+1 tokens.
505                                    let m = draft.len;
506                                    let mut k = 0usize;
507                                    while k < m && amax[k] == draft.tokens[k] {
508                                        k += 1;
509                                    }
510                                    crate::llm_bench::record_spec_step(m as u64, k as u64);
511                                    let mut stop = false;
512                                    for i in 0..=k {
513                                        let (tokn, _logv) = if i < k {
514                                            (draft.tokens[i], alog[i])
515                                        } else {
516                                            (amax[k], alog[k])
517                                        };
518                                        // anomaly 0x01 = normal. Do not use random mantissa bytes.
519                                        let _ = lp.push(LlmMsg::Logit(LogitSummary {
520                                            _top_id: tokn,
521                                            anomaly: 0x01u8,
522                                        }));
523                                        let next = tokn % vlen;
524                                        out_ids.push(next);
525                                        ctx.push(next);
526                                        if let Some(ref tx) = stream_tx_thread {
527                                            let full = tok.decode(&out_ids);
528                                            if full.len() > streamed_len {
529                                                let delta = full[streamed_len..].to_string();
530                                                streamed_len = full.len();
531                                                let _ = tx.send(delta);
532                                            }
533                                        }
534                                        let fixed = crate::llm_bench::decode_budget_fixed_tokens();
535                                        if out_ids.len() >= gen_budget
536                                            || (!fixed && tok.is_stop_token(next))
537                                        {
538                                            stop = true;
539                                            break;
540                                        }
541                                    }
542                                    if stop {
543                                        break;
544                                    }
545                                    continue; // skip the normal single-token path this step
546                                }
547                            }
548                        }
549                    }
550
551                    let draft_step = try_accept_topology_draft(
552                        engine,
553                        tensor_idx.as_ref(),
554                        &draft_mapper,
555                        &mut ctx,
556                        emb_dim,
557                        &mut emb_buf,
558                        &mut scratch_a,
559                        &mut scratch_b,
560                        &mut out_ids,
561                        &mut sieve,
562                        prov_hash,
563                        eos,
564                        &tok,
565                        &mut streamed_len,
566                        stream_tx_thread.as_ref(),
567                        None,
568                    );
569                    match draft_step {
570                        TopologyDraftStep::AcceptedFull => continue,
571                        TopologyDraftStep::Stop {
572                            sieve_failed: sf,
573                            semantic_quin: sq,
574                        } => {
575                            sieve_failed = sf;
576                            semantic_quin = sq;
577                            break;
578                        }
579                        _ => {}
580                    }
581
582                    drain_tensor_context_inject();
583                    let _attention_mask = crate::compute_universe::attention_route_mask();
584                    // FastVerify: skip ControlStream — no mid-decode DenyRollback tax.
585                    let mut rollback = if crate::inference_modes::sentinel_mid_decode_enabled() {
586                        cc.pop().is_ok()
587                    } else {
588                        false
589                    };
590                    if matches!(draft_step, TopologyDraftStep::Denied) {
591                        rollback = true;
592                    }
593
594                    let cur = *ctx.last().unwrap_or(&tok.bos_token_id);
595                    crate::compute_universe::publish_decode_hint(cur, step as u32);
596
597                    // 1) Embedding lookup → hidden state (stack dequant).
598                    let hidden_ok = tensor_idx
599                        .as_ref()
600                        .and_then(|idx| {
601                            engine.gguf_mmap.as_deref().map(|m| {
602                                idx.dequantize_token_embedding_into(m, cur, &mut emb_buf[..emb_dim])
603                            })
604                        })
605                        .unwrap_or(0);
606
607                    // 1b) LoRA delta — additive correction to the embedding vector.
608                    // Applied after dequantize so the base model is unmodified.
609                    // Silently skipped if dimensions don't match (wrong adapter for model).
610                    if hidden_ok > 0 {
611                        if let Some(ref adapter) = lora_adapter {
612                            if adapter.meta.n_in == hidden_ok && adapter.meta.n_out == hidden_ok {
613                                let snap: Vec<f32> = emb_buf[..hidden_ok].to_vec();
614                                let _ = adapter.apply_cpu(&snap, &mut emb_buf[..hidden_ok]);
615                            }
616                        }
617                    }
618
619                    let (top_i, top_v) = if hidden_ok > 0 {
620                        if let Some(idx) = tensor_idx.as_ref() {
621                            let token_idx = ctx.len().saturating_sub(1) as u32;
622                            let sieve_mask = sieve.as_ref().map(|s| s.current_mask());
623                            // Resident-token fast path: the WHOLE forward (32 layers + output norm
624                            // + logits top-1) in ONE submit with ONE fence. `Some` means the token
625                            // was produced and the KV cache was written; `None` falls through to
626                            // the legacy per-layer path unchanged (non-sieve, full-depth only —
627                            // the unit-test 2-layer cap keeps its per-layer semantics).
628                            // Resident single-fence forward:
629                            //   • greedy → GPU top-1 inside the encoder
630                            //   • sampler → same layer stack, read back post-norm hidden, then
631                            //     full logits + CPU sample (chat no longer pays ~107 fences/token)
632                            #[cfg(not(target_arch = "wasm32"))]
633                            let (resident_hit, resident_hidden_ok) = if sieve_mask.is_none()
634                                && TEST_TRANSFORMER_LAYER_CAP == 0
635                            {
636                                let t_res = std::time::Instant::now();
637                                if sampler.is_some() {
638                                    // Sampling does not need GPU top-1; resident still wins.
639                                    // Copy embedding input aside so out_hidden can reuse emb_buf.
640                                    scratch_a[..emb_dim].copy_from_slice(&emb_buf[..emb_dim]);
641                                    let ok = engine.dispatch_token_forward_resident_hidden(
642                                        idx,
643                                        &scratch_a[..emb_dim],
644                                        token_idx,
645                                        &mut emb_buf[..emb_dim],
646                                    );
647                                    if ok {
648                                        crate::llm_bench::add_decode_forward_ns(
649                                            t_res.elapsed().as_nanos() as u64,
650                                        );
651                                        crate::llm_bench::record_resident_hit();
652                                        (None, true)
653                                    } else {
654                                        crate::llm_bench::record_resident_fallback();
655                                        (None, false)
656                                    }
657                                } else if gpu_topk_enabled {
658                                    let hit = engine.dispatch_token_forward_resident(
659                                        idx,
660                                        &emb_buf[..emb_dim],
661                                        token_idx,
662                                    );
663                                    if hit.is_some() {
664                                        crate::llm_bench::add_decode_forward_ns(
665                                            t_res.elapsed().as_nanos() as u64,
666                                        );
667                                        crate::llm_bench::record_resident_hit();
668                                    } else {
669                                        crate::llm_bench::record_resident_fallback();
670                                    }
671                                    (hit, false)
672                                } else {
673                                    (None, false)
674                                }
675                            } else {
676                                (None, false)
677                            };
678                            #[cfg(target_arch = "wasm32")]
679                            let (resident_hit, resident_hidden_ok): (
680                                Option<crate::gguf_bridge::StreamingArgmaxResult>,
681                                bool,
682                            ) = (None, false);
683
684                            // CUDA mega-pass: attempt single-fence all-layer forward.
685                            // Returns Some(token_id) when fully done (including logits).
686                            // Returns Some(u32::MAX) when forward is done but logits projection
687                            // is still needed (hidden state has been read back into emb_buf).
688                            // Returns None to fall back to per-layer path.
689                            #[cfg(all(not(target_arch = "wasm32"), feature = "cuda"))]
690                            let mega_token: Option<u32> = if resident_hit.is_none() && !resident_hidden_ok {
691                                engine.try_cuda_mega_pass_decode(
692                                    idx,
693                                    &mut emb_buf[..emb_dim],
694                                    emb_dim,
695                                    token_idx,
696                                )
697                            } else {
698                                None
699                            };
700                            #[cfg(any(target_arch = "wasm32", not(feature = "cuda")))]
701                            let mega_token: Option<u32> = None;
702
703                            // mega_forward_done = mega-pass completed the 32-layer forward
704                            // (either fully or just the forward — sentinel u32::MAX means
705                            // forward done but logits still needed).
706                            let mega_forward_done = mega_token.is_some();
707                            let mega_full_token = mega_token.filter(|&t| t != u32::MAX);
708                            #[cfg(all(not(target_arch = "wasm32"), feature = "cuda"))]
709                            {
710                                if mega_forward_done {
711                                    crate::llm_bench::record_cuda_mega_hit();
712                                } else if matches!(
713                                    std::env::var("QUALIA_LLM_CUDA_DECODE").ok().as_deref(),
714                                    Some("1") | Some("true") | Some("on")
715                                ) {
716                                    crate::llm_bench::record_cuda_mega_fallback();
717                                }
718                            }
719
720                            if resident_hit.is_none() && !resident_hidden_ok {
721                                if mega_forward_done {
722                                    crate::llm_bench::add_decode_forward_ns(0);
723                                    // Skip forward — mega-pass did it. If mega_full_token is
724                                    // Some, logits are also done. If None (sentinel), logits
725                                    // projection still runs below.
726                                } else {
727                                    // Decode-profiler: time the 32-layer forward (legacy path).
728                                    let t_fwd = std::time::Instant::now();
729                                    let _layers = engine.dispatch_transformer_forward(
730                                        idx,
731                                        &mut emb_buf[..emb_dim],
732                                        emb_dim,
733                                        &mut scratch_a,
734                                        &mut scratch_b,
735                                        token_idx,
736                                        TEST_TRANSFORMER_LAYER_CAP,
737                                    );
738                                    let _ = engine.apply_output_norm_inplace(
739                                        idx,
740                                        &mut emb_buf[..emb_dim],
741                                        emb_dim,
742                                    );
743                                    crate::llm_bench::add_decode_forward_ns(
744                                        t_fwd.elapsed().as_nanos() as u64
745                                    );
746                                }
747                            }
748                            // If mega-pass produced a full token, use it directly and skip projection.
749                            // mega_full_token is None when the sentinel was returned (forward done,
750                            // logits still needed) — in that case the output projection runs normally.
751                            let mega_pass_done = mega_full_token.is_some();
752                            let mega_pass_tok = mega_full_token.unwrap_or(0) as usize;
753                            // Decode-profiler: time the output projection (argmax / top-k).
754                            let t_out = std::time::Instant::now();
755                            // W2: exact sampling — read back the FULL logit vector for this token and
756                            // run the CPU chain. Only when a non-greedy sampler is installed; on any
757                            // readback failure, `sampled` stays None and the greedy paths below run
758                            // (never a silent hang). The legacy forward above left the normed hidden
759                            // in `emb_buf`, so the projection input is correct.
760                            #[cfg(not(target_arch = "wasm32"))]
761                            let sampled: Option<(usize, f32)> = if let Some(s) = sampler.as_mut() {
762                                let vocab = idx
763                                    .logits_projection_info()
764                                    .map(|i| QTensorEngine::matmul_dims(i).1)
765                                    .unwrap_or(0);
766                                if vocab > 0 {
767                                    if sampler_logits.len() < vocab {
768                                        sampler_logits.resize(vocab, 0.0);
769                                    }
770                                    // Existing chunked projection; `written == vocab` iff it produced
771                                    // REAL logits (else it degraded to copying hidden → not sampleable,
772                                    // so we fall through to the greedy paths rather than sample garbage).
773                                    let written = engine.dispatch_output_logits_into(
774                                        idx,
775                                        &emb_buf[..emb_dim],
776                                        emb_dim,
777                                        &mut sampler_logits[..vocab],
778                                    );
779                                    if written == vocab {
780                                        // Neuro-symbolic: soft-boost graph answer tokens before sample.
781                                        let _ = crate::qualia_hybrid::apply_graph_logit_bias(
782                                            &prompt_owned,
783                                            &mut sampler_logits[..vocab],
784                                            &|s| {
785                                                let ids = tok.encode(s);
786                                                ids.first().copied()
787                                            },
788                                        );
789                                        let tid = s.sample(&mut sampler_logits[..vocab], &ctx);
790                                        Some((tid as usize, sampler_logits[tid as usize]))
791                                    } else {
792                                        None
793                                    }
794                                } else {
795                                    None
796                                }
797                            } else {
798                                None
799                            };
800                            #[cfg(target_arch = "wasm32")]
801                            let sampled: Option<(usize, f32)> = None;
802
803                            // A1a: GPU top-1 path (additive, default-on; non-sieve only in v1).
804                            // Returns the argmax token via the on-GPU block reduction; falls
805                            // through to the existing argmax path if disabled or on any failure — the
806                            // working path is never bypassed. Skipped when sampling produced a token.
807                            let topk_hit = if sampled.is_some() {
808                                None
809                            } else if resident_hit.is_some() {
810                                resident_hit
811                            } else if gpu_topk_enabled && sieve_mask.is_none() {
812                                engine.dispatch_output_top1_chunked(
813                                    idx,
814                                    &emb_buf[..emb_dim],
815                                    emb_dim,
816                                )
817                            } else {
818                                None
819                            };
820                            let out_sel = if mega_pass_done {
821                                crate::llm_bench::add_decode_output_ns(0);
822                                (mega_pass_tok, 0.0f32)
823                            } else if let Some(sel) = sampled {
824                                crate::llm_bench::record_sampled_token();
825                                sel
826                            } else if let Some(item) = topk_hit {
827                                crate::llm_bench::record_topk_hit();
828                                (item.best_token_id as usize, item.max_logit)
829                            } else if let Some(argmax) = engine.dispatch_output_argmax_chunked(
830                                idx,
831                                &emb_buf[..emb_dim],
832                                emb_dim,
833                                &mut scratch_a[..],
834                                TEST_VOCAB_CHUNK_CAP,
835                                sieve_mask,
836                            ) {
837                                crate::llm_bench::record_argmax_fallback();
838                                if argmax.max_logit > f32::NEG_INFINITY {
839                                    (argmax.best_token_id as usize, argmax.max_logit)
840                                } else {
841                                    sieve_failed = true;
842                                    (0usize, f32::NEG_INFINITY)
843                                }
844                            } else {
845                                let mut top1_v = f32::NEG_INFINITY;
846                                let mut top1_i = 0usize;
847                                let mut top2_v = f32::NEG_INFINITY;
848                                for (i, &v) in emb_buf[..emb_dim].iter().enumerate() {
849                                    if v > top1_v {
850                                        top2_v = top1_v;
851                                        top1_v = v;
852                                        top1_i = i;
853                                    } else if v > top2_v {
854                                        top2_v = v;
855                                    }
856                                }
857
858                                // Phase 6: Semantic Chunking Entropy Calculation
859                                let fast_entropy = -(top1_v - top2_v);
860                                if fast_entropy > chunk_policy.max_entropy_drop {
861                                    current_page_id += 1;
862
863                                    #[cfg(not(target_arch = "wasm32"))]
864                                    if let Some(ref mut wal) = thermal_wal_opt {
865                                        let record =
866                                            crate::inference::thermal_wal::ThermalEvictionRecord {
867                                                timestamp_ms: std::time::SystemTime::now()
868                                                    .duration_since(std::time::UNIX_EPOCH)
869                                                    .unwrap_or_default()
870                                                    .as_millis()
871                                                    as u64,
872                                                page_id: (current_page_id - 1) as u32,
873                                                fast_entropy,
874                                                top1_v,
875                                                top2_v,
876                                                reserved: [0; 8],
877                                            };
878                                        wal.append(record);
879                                    }
880
881                                    if std::env::var("QUALIA_LLM_DEBUG_DECODE").is_ok() {
882                                        eprintln!(
883                                            "[NEW CHUNK] page_id={} entropy={:.3}",
884                                            current_page_id, fast_entropy
885                                        );
886                                        eprintln!(
887                                            "[THERMAL EVICT] Hard eviction logged for page_id={}",
888                                            current_page_id - 1
889                                        );
890                                    }
891                                }
892
893                                // Update KV provenance for this new token
894                                let token_idx = ctx.len() as u32;
895                                crate::tensor::kv_provenance::record_kv_provenance(
896                                    token_idx,
897                                    token_idx,
898                                    current_page_id,
899                                );
900
901                                (top1_i, top1_v)
902                            };
903                            crate::llm_bench::add_decode_output_ns(
904                                t_out.elapsed().as_nanos() as u64
905                            );
906                            out_sel
907                        } else {
908                            (0usize, 0.0)
909                        }
910                    } else {
911                        let wt = QTensor::new(vec![emb_dim, emb_dim], 0, true);
912                        let logits = embedding_fallback_logits(
913                            &engine,
914                            tensor_idx.as_ref(),
915                            lora_adapter.as_ref(),
916                            cur,
917                            emb_dim,
918                            &mut emb_buf[..],
919                            &wt,
920                        );
921                        logits.iter().enumerate().fold(
922                            (0usize, f32::NEG_INFINITY),
923                            |(bi, bv), (i, &v)| if v > bv { (i, v) } else { (bi, bv) },
924                        )
925                    };
926
927                    // #48 diagnostic: reveal eos vs argmax for the first step (gated, native).
928                    if step == 0 && std::env::var("QUALIA_LLM_DEBUG_DECODE").is_ok() {
929                        eprintln!(
930                            "[decode-dbg] step0 eos={} vlen={} prompt_last={} top_i={} top_v={} decoded={:?}",
931                            eos,
932                            vlen,
933                            cur,
934                            top_i,
935                            top_v,
936                            tok.decode(&[top_i as u32])
937                        );
938                        if let Some(idx) = tensor_idx.as_ref() {
939                            if let Some(top5) = engine.dispatch_output_topk_chunked(
940                                idx,
941                                &emb_buf[..emb_dim],
942                                emb_dim,
943                                5,
944                            ) {
945                                for it in &top5 {
946                                    eprintln!(
947                                        "[top5] id={} logit={:.3} dec={:?}",
948                                        it.token_id,
949                                        it.logit,
950                                        tok.decode(&[it.token_id])
951                                    );
952                                }
953                            }
954                        }
955                    }
956
957                    // FastVerify: skip per-token Logit ring push (only Eos ends the turn).
958                    if crate::inference_modes::sentinel_mid_decode_enabled() {
959                        // anomaly 0x01 = normal. Removed IEEE mantissa 0x99 check (random ~1/256 fire).
960                        let _ = lp.push(LlmMsg::Logit(LogitSummary {
961                            _top_id: top_i as u32,
962                            anomaly: 0x01u8,
963                        }));
964                    }
965
966                    if sieve_failed {
967                        break;
968                    }
969
970                    // DenyRollback must never inject sequential garbage (cur+1).
971                    if rollback {
972                        log::warn!(
973                            "LLM_DECODE|sentinel-deny-rollback|keeping argmax token {} (no cur+1)",
974                            top_i
975                        );
976                    }
977                    let next = (top_i as u32) % vlen;
978
979                    if let Some(ref mut s) = sieve {
980                        match s.apply_token(next) {
981                            Ok(()) => {
982                                out_ids.push(next);
983                                ctx.push(next);
984                                if s.is_complete() {
985                                    semantic_quin = Some(s.assemble_quin(prov_hash));
986                                    break;
987                                }
988                            }
989                            Err(_) => {
990                                sieve_failed = true;
991                                break;
992                            }
993                        }
994                    } else {
995                        out_ids.push(next);
996                        ctx.push(next);
997                        if let Some(ref tx) = stream_tx_thread {
998                            let full = tok.decode(&out_ids);
999                            if full.len() > streamed_len {
1000                                let delta = full[streamed_len..].to_string();
1001                                streamed_len = full.len();
1002                                let _ = tx.send(delta);
1003                            }
1004                        }
1005                        // Stop on eos AND chat end-of-turn — unless fixed-token bench override.
1006                        let fixed = crate::llm_bench::decode_budget_fixed_tokens();
1007                        if out_ids.len() >= gen_budget
1008                            || (!fixed && tok.is_stop_token(next))
1009                        {
1010                            break;
1011                        }
1012                    }
1013                }
1014                } // end else: normal decode (not QUALIA_GRAPH_FORCE)
1015
1016                // Phase boundary: decode loop complete.
1017                crate::llm_bench::record_decode(
1018                    t_decode.elapsed().as_nanos() as u64,
1019                    out_ids.len() as u64,
1020                );
1021
1022                let _ = lp.push(LlmMsg::Eos);
1023                let text = if semantic_quin.is_some() {
1024                    String::new()
1025                } else if sieve_failed {
1026                    String::from("[sieve-misaligned]")
1027                } else {
1028                    let raw = tok.decode(&out_ids);
1029                    // Post-turn path (FastVerify / QuantGraph): generate full draft at
1030                    // full speed, then graph/CML self-heal + optional HTML surface.
1031                    if crate::inference_modes::post_turn_verify_enabled() {
1032                        let v = crate::post_turn_verify::verify_and_heal_turn(&prompt_owned, &raw);
1033                        if crate::post_turn_verify::return_html_as_text() {
1034                            v.display_html
1035                        } else {
1036                            v.final_text
1037                        }
1038                    } else {
1039                        crate::quant_graph_grounding::maybe_ground_generation(&prompt_owned, &raw)
1040                            .text
1041                    }
1042                };
1043                (text, out_ids.len() as u32, semantic_quin, sieve_failed)
1044                    }, // sticky_infer::with_engine f
1045                ); // sticky_infer::with_engine
1046                let _ = done_tx.send(result);
1047            }); // sticky pool spawn
1048
1049            // ── Webizen Sentinel (calling thread) ────────────────────────────
1050            // FastVerify: still drain stream + wait for Eos, but ignore anomaly mid-decode
1051            // (no DenyRollback) so generation is uninterrupted like Ollama.
1052            let mid_sentinel = crate::inference_modes::sentinel_mid_decode_enabled();
1053            let mut drain_tokens = || {
1054                if let (Some((_, ref rx)), Some(cb)) = (&stream_pair, on_token.as_mut()) {
1055                    while let Ok(delta) = rx.try_recv() {
1056                        cb(delta);
1057                    }
1058                }
1059            };
1060
1061            loop {
1062                drain_tokens();
1063                match lc.pop() {
1064                    Ok(LlmMsg::Eos) => break,
1065                    Ok(LlmMsg::Logit(s)) => {
1066                        if mid_sentinel && s.anomaly == 0x99 {
1067                            let _ = cp.push(SentMsg::DenyRollback);
1068                        }
1069                    }
1070                    Err(_) => std::hint::spin_loop(),
1071                }
1072            }
1073
1074            drain_tokens();
1075
1076            let (text, tokens, semantic_quin, sieve_failed) = done_rx
1077                .recv()
1078                .unwrap_or_else(|_| (String::new(), 0, None, false));
1079            let mut prov = vec![prov_hash];
1080            if prov_hash == 0 {
1081                prov.push(q_hash("qualia:grounded"));
1082            }
1083            if let Some(q) = semantic_quin {
1084                prov.push(q.subject);
1085                prov.push(q.predicate);
1086                prov.push(q.object);
1087            }
1088            if sieve_failed && semantic_quin.is_none() {
1089                return (text, prov, tokens, None);
1090            }
1091            return (text, prov, tokens, semantic_quin);
1092        }
1093
1094        // ── Native GPU path ─────────────────────────────────────────────────
1095        #[cfg(target_arch = "wasm32")]
1096        {
1097            use crate::gguf_bridge::QTensor;
1098            use crate::gguf_sharder::GgufTokenizer;
1099
1100            let model_path = match &self.backend {
1101                AgentBackend::Local { model_path, .. } => model_path.clone(),
1102                _ => {
1103                    return (
1104                        String::from("[no local model configured]"),
1105                        vec![prov_hash],
1106                        0,
1107                        None,
1108                    );
1109                }
1110            };
1111
1112            // ── WASM Extension Bus Offloading ────────────────────────────────
1113            if crate::extension_bus::wasm_bus::is_connected() {
1114                if let Some(cb) = on_token {
1115                    let _ = crate::extension_bus::wasm_bus::send_intent(prompt, graph_context, cb);
1116                } else {
1117                    let _ =
1118                        crate::extension_bus::wasm_bus::send_intent(prompt, graph_context, |_| {});
1119                }
1120                return (String::new(), vec![prov_hash], 0, None);
1121            }
1122            let prompt_owned = prompt.to_string();
1123
1124            // Multi-mode: portable | cuda | quant-graph (`QUALIA_INFERENCE_MODE`).
1125            let _mode = crate::inference_modes::bootstrap_inference_mode();
1126
1127            // ── LoRA context detection (before thread spawn) ─────────────────
1128            // Detect the prompt domain and pre-load the matching LoRA adapter.
1129            // The pre-computed delta vectors are cloned into the inference thread
1130            // as fixed-size heap data — one allocation per infer call, not per token.
1131            #[allow(unused_variables)]
1132            let lora_active_adapter: Option<crate::lora::LoRAAdapter> = {
1133                let mut guard = self.lora_manager.lock().unwrap_or_else(|e| e.into_inner());
1134                if let Some(ref mut mgr) = *guard {
1135                    let (ctx, conf, _switched) =
1136                        mgr.auto_switch(&prompt_owned, mgr.detector.confidence_threshold);
1137                    log::debug!("LoRA|context-detect|domain={ctx}|conf={conf:.3}");
1138                    mgr.active().cloned()
1139                } else {
1140                    None
1141                }
1142            };
1143
1144            // Move the (optional) LoRA adapter into the inference thread.
1145            let lora_for_thread = lora_active_adapter;
1146
1147            // ── LLM engine synchronous execution ─────────────────────────────
1148            let (text, tokens, semantic_quin, sieve_failed) = {
1149                let mut rollback = false;
1150
1151                let lora_adapter = lora_for_thread;
1152                let sieve_spec = sieve_spec;
1153                let sieve_lex_path = sieve_lex_path;
1154                // Build the GPU engine and memory-map the GGUF inside the thread to
1155                // avoid Send constraints on the DirectML / wgpu device handles.
1156                let mut engine = {
1157                    let engine_guard =
1158                        crate::gguf_bridge::WASM_ENGINE_INSTANCE.with(|g| g.borrow_mut().take());
1159                    engine_guard.expect(
1160                        "WASM WebGPU engine not initialized. Call initialize_webgpu_engine first.",
1161                    )
1162                };
1163
1164                let is_p64_mmap = engine
1165                    .gguf_mmap
1166                    .as_ref()
1167                    .map(|m| crate::p64_weight::has_p64_magic(&m[..]))
1168                    .unwrap_or(false);
1169                let p64_index = if is_p64_mmap {
1170                    engine
1171                        .gguf_mmap
1172                        .as_ref()
1173                        .and_then(|m| crate::p64_weight::P64TensorIndex::from_p64(m).ok())
1174                } else {
1175                    None
1176                };
1177                let mut tok =
1178                    if let (Some(qi), Some(m)) = (p64_index.as_ref(), engine.gguf_mmap.as_ref()) {
1179                        GgufTokenizer::from_p64_section(qi.tokenizer_bytes(m)).unwrap_or_default()
1180                    } else {
1181                        engine
1182                            .gguf_mmap
1183                            .as_ref()
1184                            .map(|m| GgufTokenizer::from_gguf(m))
1185                            .unwrap_or_default()
1186                    };
1187                apply_model_helper_stops(&model_path, &mut tok);
1188
1189                let tensor_idx = if let Some(qi) = p64_index {
1190                    Some(qi.to_gguf_index())
1191                } else {
1192                    engine
1193                        .gguf_mmap
1194                        .as_ref()
1195                        .map(|m| crate::gguf_sharder::GgufTensorIndex::from_gguf(m))
1196                };
1197
1198                let mut ctx = tok.encode_chat_prompt(&prompt_owned);
1199                let eos = tok.eos_token_id;
1200                let vlen = tok.vocab_len().max(1);
1201
1202                // Use the real embedding dimension if the tensor was found; fall back to 4096.
1203                let emb_dim = tensor_idx
1204                    .as_ref()
1205                    .map(|idx| idx.emb_dim())
1206                    .filter(|&d| d > 0)
1207                    .unwrap_or(4096);
1208
1209                // Stack buffers — zero-heap path (512MB floor safe).
1210                use crate::gguf_bridge::{PREFILL_CHUNK_SIZE, PREFILL_CHUNK_STACK_FLOATS};
1211
1212                const MAX_EMB_DIM: usize = 8192;
1213                const MAX_FFN_DIM: usize = 10240;
1214                let mut emb_buf = [0f32; MAX_EMB_DIM];
1215                let mut scratch_a = [0f32; MAX_FFN_DIM];
1216                let mut scratch_b = [0f32; MAX_FFN_DIM];
1217                let mut prefill_chunk = [0f32; PREFILL_CHUNK_STACK_FLOATS];
1218                let emb_dim = emb_dim.min(MAX_EMB_DIM);
1219                engine.reset_kv_cache();
1220
1221                // Chunked prefill: populate KV for prompt tokens [0, prompt_len-1).
1222                let prompt_len = ctx.len();
1223                crate::tensor::kv_provenance::rebuild_prompt_provenance(
1224                    prompt_len as u32,
1225                    crate::tensor::resident_substrate::global_resident_substrate().node_count(),
1226                    0,
1227                );
1228                let draft_mapper = crate::topology_draft::TopologyDraftMapper::new(&tok);
1229                if prompt_len > 1 {
1230                    if let Some(idx) = tensor_idx.as_ref() {
1231                        let prefill_tokens = prompt_len - 1;
1232                        let chunk_cap = (PREFILL_CHUNK_STACK_FLOATS / emb_dim)
1233                            .min(PREFILL_CHUNK_SIZE)
1234                            .max(1);
1235                        let mut pos = 0usize;
1236                        while pos < prefill_tokens {
1237                            let n = (prefill_tokens - pos).min(chunk_cap);
1238                            let batch_elems = n * emb_dim;
1239                            {
1240                                let mmap = match engine.gguf_mmap.as_deref() {
1241                                    Some(m) => m,
1242                                    None => break,
1243                                };
1244                                for t in 0..n {
1245                                    let _ = idx.dequantize_token_embedding_into(
1246                                        mmap,
1247                                        ctx[pos + t],
1248                                        &mut prefill_chunk[t * emb_dim..(t + 1) * emb_dim],
1249                                    );
1250                                }
1251                            }
1252                            if !engine.dispatch_prefill_chunk(
1253                                idx,
1254                                &mut prefill_chunk[..batch_elems],
1255                                emb_dim,
1256                                n as u32,
1257                                pos as u32,
1258                                &mut scratch_a,
1259                                &mut scratch_b,
1260                                TEST_TRANSFORMER_LAYER_CAP,
1261                            ) {
1262                                crate::gguf_bridge::wlog(&format!(
1263                                    "[llm] PREFILL chunk FAILED pos={pos} n={n}"
1264                                ));
1265                            }
1266                            pos += n;
1267                        }
1268                    }
1269                }
1270
1271                let mut out_ids: Vec<u32> = Vec::new();
1272                let mut streamed_len = 0usize;
1273                let mut sieve = if use_sieve {
1274                    build_sieve(&tok, sieve_spec.as_ref(), sieve_lex_path.as_deref())
1275                } else {
1276                    None
1277                };
1278                let mut semantic_quin: Option<NQuin> = None;
1279                let mut sieve_failed = false;
1280                let gen_budget = if sieve.is_some() {
1281                    3usize
1282                } else {
1283                    DECODE_TOKEN_BUDGET as usize
1284                };
1285
1286                crate::compute_universe::start_tensor_search_producer();
1287                crate::compute_universe::publish_query_tensor(
1288                    crate::tensor::Tensor10D::default(),
1289                    0,
1290                );
1291                crate::qualia_hybrid::prepare_hybrid_decode(&prompt_owned);
1292
1293                for step in 0..gen_budget {
1294                    crate::gpu_context::record_llm_decode_step();
1295
1296                    let on_token_sink = on_token.as_mut().map(|cb| cb as &mut dyn FnMut(String));
1297                    let draft_step = try_accept_topology_draft(
1298                        &mut engine,
1299                        tensor_idx.as_ref(),
1300                        &draft_mapper,
1301                        &mut ctx,
1302                        emb_dim,
1303                        &mut emb_buf,
1304                        &mut scratch_a,
1305                        &mut scratch_b,
1306                        &mut out_ids,
1307                        &mut sieve,
1308                        prov_hash,
1309                        eos,
1310                        &tok,
1311                        &mut streamed_len,
1312                        None,
1313                        on_token_sink,
1314                    );
1315                    match draft_step {
1316                        TopologyDraftStep::AcceptedFull => continue,
1317                        TopologyDraftStep::Stop {
1318                            sieve_failed: sf,
1319                            semantic_quin: sq,
1320                        } => {
1321                            sieve_failed = sf;
1322                            semantic_quin = sq;
1323                            break;
1324                        }
1325                        _ => {}
1326                    }
1327
1328                    drain_tensor_context_inject();
1329                    let _attention_mask = crate::compute_universe::attention_route_mask();
1330
1331                    let rollback_val = rollback;
1332                    rollback = false;
1333                    let mut rollback = rollback_val;
1334                    if matches!(draft_step, TopologyDraftStep::Denied) {
1335                        rollback = true;
1336                    }
1337
1338                    let cur = *ctx.last().unwrap_or(&tok.bos_token_id);
1339                    crate::compute_universe::publish_decode_hint(cur, step as u32);
1340
1341                    // 1) Embedding lookup → hidden state (stack dequant).
1342                    let hidden_ok = tensor_idx
1343                        .as_ref()
1344                        .and_then(|idx| {
1345                            engine.gguf_mmap.as_deref().map(|m| {
1346                                idx.dequantize_token_embedding_into(m, cur, &mut emb_buf[..emb_dim])
1347                            })
1348                        })
1349                        .unwrap_or(0);
1350
1351                    // 1b) LoRA delta — additive correction to the embedding vector.
1352                    // Applied after dequantize so the base model is unmodified.
1353                    // Silently skipped if dimensions don't match (wrong adapter for model).
1354                    if hidden_ok > 0 {
1355                        if let Some(ref adapter) = lora_adapter {
1356                            if adapter.meta.n_in == hidden_ok && adapter.meta.n_out == hidden_ok {
1357                                let snap: Vec<f32> = emb_buf[..hidden_ok].to_vec();
1358                                let _ = adapter.apply_cpu(&snap, &mut emb_buf[..hidden_ok]);
1359                            }
1360                        }
1361                    }
1362
1363                    let (top_i, top_v) = if hidden_ok > 0 {
1364                        if let Some(idx) = tensor_idx.as_ref() {
1365                            let token_idx = ctx.len().saturating_sub(1) as u32;
1366                            // CUDA mega-pass: attempt single-fence all-layer forward.
1367                            // Returns Some(u32::MAX) sentinel when forward is done but logits
1368                            // still needed (hidden state read back into emb_buf).
1369                            #[cfg(all(not(target_arch = "wasm32"), feature = "cuda"))]
1370                            let mega_token: Option<u32> = engine.try_cuda_mega_pass_decode(
1371                                idx,
1372                                &mut emb_buf[..emb_dim],
1373                                emb_dim,
1374                                token_idx,
1375                            );
1376                            #[cfg(any(target_arch = "wasm32", not(feature = "cuda")))]
1377                            let mega_token: Option<u32> = None;
1378
1379                            if let Some(mt) = mega_token.filter(|&t| t != u32::MAX) {
1380                                (mt as usize, 0.0f32)
1381                            } else if mega_token.is_some() {
1382                                // Sentinel: forward done, logits needed.
1383                                let sieve_mask = sieve.as_ref().map(|s| s.current_mask());
1384                                if let Some(argmax) = engine.dispatch_output_argmax_chunked(
1385                                    idx,
1386                                    &emb_buf[..emb_dim],
1387                                    emb_dim,
1388                                    &mut scratch_a[..],
1389                                    TEST_VOCAB_CHUNK_CAP,
1390                                    sieve_mask,
1391                                ) {
1392                                    if argmax.max_logit > f32::NEG_INFINITY {
1393                                        (argmax.best_token_id as usize, argmax.max_logit)
1394                                    } else {
1395                                        sieve_failed = true;
1396                                        (0usize, f32::NEG_INFINITY)
1397                                    }
1398                                } else {
1399                                    emb_buf[..emb_dim].iter().enumerate().fold(
1400                                        (0usize, f32::NEG_INFINITY),
1401                                        |(bi, bv), (i, &v)| {
1402                                            if v > bv {
1403                                                (i, v)
1404                                            } else {
1405                                                (bi, bv)
1406                                            }
1407                                        },
1408                                    )
1409                                }
1410                            } else {
1411                                let _layers = engine.dispatch_transformer_forward(
1412                                    idx,
1413                                    &mut emb_buf[..emb_dim],
1414                                    emb_dim,
1415                                    &mut scratch_a,
1416                                    &mut scratch_b,
1417                                    token_idx,
1418                                    TEST_TRANSFORMER_LAYER_CAP,
1419                                );
1420                                let _ = engine.apply_output_norm_inplace(
1421                                    idx,
1422                                    &mut emb_buf[..emb_dim],
1423                                    emb_dim,
1424                                );
1425                                let sieve_mask = sieve.as_ref().map(|s| s.current_mask());
1426                                if let Some(argmax) = engine.dispatch_output_argmax_chunked(
1427                                    idx,
1428                                    &emb_buf[..emb_dim],
1429                                    emb_dim,
1430                                    &mut scratch_a[..],
1431                                    TEST_VOCAB_CHUNK_CAP,
1432                                    sieve_mask,
1433                                ) {
1434                                    if argmax.max_logit > f32::NEG_INFINITY {
1435                                        (argmax.best_token_id as usize, argmax.max_logit)
1436                                    } else {
1437                                        sieve_failed = true;
1438                                        (0usize, f32::NEG_INFINITY)
1439                                    }
1440                                } else {
1441                                    emb_buf[..emb_dim].iter().enumerate().fold(
1442                                        (0usize, f32::NEG_INFINITY),
1443                                        |(bi, bv), (i, &v)| {
1444                                            if v > bv {
1445                                                (i, v)
1446                                            } else {
1447                                                (bi, bv)
1448                                            }
1449                                        },
1450                                    )
1451                                }
1452                            }
1453                        } else {
1454                            (0usize, 0.0)
1455                        }
1456                    } else {
1457                        let wt = QTensor::new(vec![emb_dim, emb_dim], 0, true);
1458                        let logits = embedding_fallback_logits(
1459                            &engine,
1460                            tensor_idx.as_ref(),
1461                            lora_adapter.as_ref(),
1462                            cur,
1463                            emb_dim,
1464                            &mut emb_buf[..],
1465                            &wt,
1466                        );
1467                        logits.iter().enumerate().fold(
1468                            (0usize, f32::NEG_INFINITY),
1469                            |(bi, bv), (i, &v)| {
1470                                if v > bv {
1471                                    (i, v)
1472                                } else {
1473                                    (bi, bv)
1474                                }
1475                            },
1476                        )
1477                    };
1478
1479                    // anomaly 0x01 = normal. Removed IEEE mantissa 0x99 check (random ~1/256 fire).
1480                    let anomaly = 0x01u8;
1481                    let _ = anomaly; // reserved for real governance signals
1482
1483                    if sieve_failed {
1484                        break;
1485                    }
1486
1487                    // DenyRollback must never inject sequential garbage (cur+1).
1488                    if rollback {
1489                        log::warn!(
1490                            "LLM_DECODE|sentinel-deny-rollback|keeping argmax token {} (no cur+1)",
1491                            top_i
1492                        );
1493                    }
1494                    let next = (top_i as u32) % vlen;
1495
1496                    if let Some(ref mut s) = sieve {
1497                        match s.apply_token(next) {
1498                            Ok(()) => {
1499                                out_ids.push(next);
1500                                ctx.push(next);
1501                                if s.is_complete() {
1502                                    semantic_quin = Some(s.assemble_quin(prov_hash));
1503                                    break;
1504                                }
1505                            }
1506                            Err(_) => {
1507                                sieve_failed = true;
1508                                break;
1509                            }
1510                        }
1511                    } else {
1512                        out_ids.push(next);
1513                        ctx.push(next);
1514                        if let Some(ref mut cb) = on_token {
1515                            let full = tok.decode(&out_ids);
1516                            if full.len() > streamed_len {
1517                                let delta = full[streamed_len..].to_string();
1518                                streamed_len = full.len();
1519                                cb(delta);
1520                            }
1521                        }
1522                        let fixed = crate::llm_bench::decode_budget_fixed_tokens();
1523                        if out_ids.len() >= gen_budget || (!fixed && tok.is_stop_token(next)) {
1524                            break;
1525                        }
1526                    }
1527                }
1528
1529                let text = if semantic_quin.is_some() {
1530                    String::new()
1531                } else if sieve_failed {
1532                    String::from("[sieve-misaligned]")
1533                } else {
1534                    let raw = tok.decode(&out_ids);
1535                    if crate::inference_modes::post_turn_verify_enabled() {
1536                        let v = crate::post_turn_verify::verify_and_heal_turn(&prompt_owned, &raw);
1537                        if crate::post_turn_verify::return_html_as_text() {
1538                            v.display_html
1539                        } else {
1540                            v.final_text
1541                        }
1542                    } else {
1543                        crate::quant_graph_grounding::maybe_ground_generation(&prompt_owned, &raw)
1544                            .text
1545                    }
1546                };
1547
1548                // Return engine to global instance
1549                {
1550                    crate::gguf_bridge::WASM_ENGINE_INSTANCE.with(|g| {
1551                        *g.borrow_mut() = Some(engine);
1552                    });
1553                }
1554
1555                (text, out_ids.len() as u32, semantic_quin, sieve_failed)
1556            };
1557            let mut prov = vec![prov_hash];
1558            if prov_hash == 0 {
1559                prov.push(q_hash("qualia:grounded"));
1560            }
1561            if let Some(q) = semantic_quin {
1562                prov.push(q.subject);
1563                prov.push(q.predicate);
1564                prov.push(q.object);
1565            }
1566            if sieve_failed && semantic_quin.is_none() {
1567                return (text, prov, tokens, None);
1568            }
1569            return (text, prov, tokens, semantic_quin);
1570        }
1571    }
1572}