// SPDX-License-Identifier: Apache-2.0
//
// Archived: 2026-07-12
// Reason: Replaced by C kernel FFI (c-kernels/attention.c) for the inner
// Gram-table-lookup loop and softmax. The Rust orchestration
// (compressed_attention_logits, compressed_attention_topk) remains in
// kv/compressed_attention.rs — only the inner numerical loops were
// moved to C.
//
// This file preserves the original Rust inner loops from
// kv/compressed_attention.rs for reference. The functions below are
// NOT compiled or used — they exist purely as historical records.
#![allow(dead_code)]
use crate::{FibQuantError, Result};
/// Original Rust softmax from kv/compressed_attention.rs (line ~173).
///
/// Numerically stable softmax with max-subtraction (f64 accumulator).
/// The C replacement (`fq_softmax` in c-kernels/attention.c) implements
/// the same algorithm.
fn softmax(logits: &[f32]) -> Result<Vec<f32>> {
if logits.is_empty() {
return Err(FibQuantError::ZeroDimension);
}
check_finite(logits)?;
let max = logits
.iter()
.copied()
.fold(f32::NEG_INFINITY, |acc, v| acc.max(v));
let mut sum = 0.0f64;
let mut exps = Vec::with_capacity(logits.len());
for &v in logits {
let exp = f64::from(v - max).exp();
sum += exp;
exps.push(exp);
}
if !sum.is_finite() || sum <= 0.0 {
return Err(FibQuantError::NumericalFailure(
"compressed attention softmax underflow".into(),
));
}
Ok(exps.into_iter().map(|e| (e / sum) as f32).collect())
}
/// Original Rust compressed_attention_logits inner loop from
/// kv/compressed_attention.rs (line ~54).
///
/// Uses FibScorer::prepare_query + FibScorer::score_prepared for
/// efficient batch scoring. The inner loop of score_prepared performs:
/// 1. Unpack stored code's indices.
/// 2. For each block: Gram table lookup G[query_idx, stored_idx].
/// 3. Sum and scale by query_norm * stored_norm.
///
/// The C replacement (`fq_compressed_attention_logits` in
/// c-kernels/attention.c) handles steps 2-3 only. Step 1 (unpacking)
/// remains in Rust.
fn compressed_attention_logits_inner_loop(
prepared_indices: &[u32],
prepared_norm: f64,
stored_indices: &[u32],
gram_table: &[f32],
gram_size: usize,
stored_norm: f64,
) -> f32 {
let mut total = 0.0f32;
for (block_idx, &stored_idx) in stored_indices.iter().enumerate() {
let stored_idx = stored_idx as usize;
let query_idx = prepared_indices[block_idx] as usize;
total += gram_table[query_idx * gram_size + stored_idx];
}
total * (prepared_norm as f32) * (stored_norm as f32)
}
/// Check that all values are finite.
fn check_finite(values: &[f32]) -> Result<()> {
if values.iter().any(|v| !v.is_finite()) {
return Err(FibQuantError::CorruptPayload(
"compressed attention input contains non-finite value".into(),
));
}
Ok(())
}