5.9 KiB
attention
CPU attention: scaled dot-product, multi-head, KV cache, RoPE. No fused flash-attention kernel yet.
Header: attention/attention.h
Source: attention/attention.c
Status: API Complete - Pending Verification
Overview
The attention module provides:
- Scaled dot-product attention
- Multi-head attention with projections
- KV cache for efficient inference
- Rotary Position Embedding (RoPE)
- Causal masking
Quick Start
#include "attention/attention.h"
// Configure attention
fa_config cfg = fa_config_default(8, 64); // 8 heads, 64 dim per head
cfg.causal = true;
// Attention: Q, K, V are [batch, seq, heads, head_dim]
tm_tensor* output = fa_attention(Q, K, V, &cfg);
// With KV cache for generation
fa_context* ctx = fa_context_new(batch, max_seq, 8, 64, num_layers);
for (int pos = 0; pos < seq_len; pos++) {
tm_tensor* out = fa_attention_cached(Q, K, V, ctx, layer, pos, &cfg);
// ... use output ...
}
fa_context_free(ctx);
API Reference
Configuration
typedef struct {
int num_heads;
int head_dim;
float dropout;
bool causal; // Causal masking for autoregressive
float scale; // Attention scale (default: 1/sqrt(head_dim))
} fa_config;
fa_config fa_config_default(int num_heads, int head_dim);
Standard Attention
// Scaled dot-product attention
// Q, K, V: (batch, seq_len, num_heads, head_dim)
// Returns: (batch, seq_len, num_heads, head_dim)
tm_tensor* fa_attention(tm_tensor* Q, tm_tensor* K, tm_tensor* V, fa_config* config);
// With explicit mask
// mask: (batch, 1, seq_len, seq_len) - use -inf for masked positions
tm_tensor* fa_attention_mask(tm_tensor* Q, tm_tensor* K, tm_tensor* V,
tm_tensor* mask, fa_config* config);
Multi-Head Attention
// Multi-head attention with projections
// x: (batch, seq_len, embed_dim)
// Wq, Wk, Wv: (embed_dim, num_heads * head_dim)
// Wo: (num_heads * head_dim, embed_dim)
tm_tensor* fa_multi_head(tm_tensor* x, tm_tensor* Wq, tm_tensor* Wk, tm_tensor* Wv,
tm_tensor* Wo, fa_config* config);
// Self-attention (Q=K=V from same source)
// Wqkv: (embed_dim, 3 * num_heads * head_dim)
tm_tensor* fa_self_attention(tm_tensor* x, tm_tensor* Wqkv, tm_tensor* Wo,
fa_config* config);
KV Cache
fa_context* fa_context_new(int batch_size, int max_seq_len, int num_heads,
int head_dim, int num_layers);
void fa_context_free(fa_context* ctx);
tm_tensor* fa_attention_cached(tm_tensor* Q, tm_tensor* K, tm_tensor* V,
fa_context* ctx, int layer_idx, int pos,
fa_config* config);
void fa_context_clear(fa_context* ctx);
int fa_context_seq_len(fa_context* ctx);
Rotary Position Embedding (RoPE)
// Apply RoPE to Q and K in-place
void fa_rope(tm_tensor* Q, tm_tensor* K, int start_pos, int head_dim);
// Precompute frequencies
tm_tensor* fa_rope_freqs(int max_seq_len, int head_dim);
Utility
tm_tensor* fa_causal_mask(int seq_len);
void fa_print_config(fa_config* config);
TurboQuant KV cache compression
attention/turboquant.{h,c} (prefix fa_tq_) compresses the KV cache from FP32
to 2-4 bits per channel, based on TurboQuant (Zandieh et al. 2025,
arXiv:2504.19874). Full design in attention/spec.md.
Per K or V vector the pipeline is: randomized Hadamard rotation
(D * H * x / sqrt(d), O(d log d)) to spread information across coordinates,
a per-head range map, Lloyd-Max scalar quantization against precomputed
Beta(d/2, d/2) codebooks (shipped as a static table in tq_codebooks.h, with a
Lloyd's-algorithm fallback), then bit-packing to 2/3/4 bits. Dequant reverses
each step. The module is tensor-library-free: it operates on raw float* /
uint8_t* buffers so the foundry attention loops and external engines can call
it directly. Bit allocation is asymmetric because keys route attention while
values are averaged. The default is K4/V2 (4-bit keys, 2-bit values), which is
9.14x smaller than FP32 including range metadata.
#include "attention/turboquant.h"
fa_tq_config cfg = fa_tq_config_default(); // K4 / V2
fa_tq_state* tq = fa_tq_init(head_dim, cfg, seed); // seed drives the rotation
fa_tq_cache* cache = fa_tq_cache_new(tq, num_layers, num_heads, max_seq_len);
// Per token: quantize K and V into the cache, dequantize a range back out.
fa_tq_quantize(tq, cache, layer, pos, k_vec, /*is_key=*/true);
fa_tq_quantize(tq, cache, layer, pos, v_vec, /*is_key=*/false);
fa_tq_dequantize(tq, cache, layer, /*start=*/0, count, /*is_key=*/true, out);
fa_tq_cache_free(cache);
fa_tq_free(tq);
Low-level single-head fa_tq_quantize_head / fa_tq_dequantize_head,
fa_tq_rotate / fa_tq_rotate_inverse, and the codebook/introspection helpers
are also public (see turboquant.h).
Quality: K4/V2 is lossy, validate the value bit width per model
4-bit keys are near-lossless, but 2-bit values are the error driver. Measured attention-output cosine similarity (FP vs compressed):
| Config | Synthetic (Gaussian) | Realistic (correlated) |
|---|---|---|
| K4/V2 | ~0.94 | ~0.975 |
| K4/V3 | ~0.98 | - |
| K4/V4 | ~0.99 | ~0.99 |
The default K4/V2 is a real quality hit, not lossless. Pick the value bit width
per model on real activations before relying on K4/V2; raise it to V3 or V4 where
output fidelity matters. make test-turboquant runs the 11 unit tests (all
pass); make validate-turboquant prints the attention-output quality report
(informational, not a build gate).
See Also
- tensor - Tensor operations
- lora - LoRA for attention layers
- attention/spec.md - TurboQuant design spec