fib-quant 0.1.0-beta.4

Experimental Rust implementation of the FibQuant radial-angular vector quantization core
Documentation
// 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(())
}