NoMAD-Attention Guide: Multiply-Add-Free CPU LLM Inference

The CPU Attention Bottleneck in Edge and Confidential AI Serving
In contemporary enterprise software, privacy-first on-premise infrastructure, and intelligent automation systems engineered by aiFetchly, deploying foundation models on Central Processing Units (CPUs) is a critical operational reality. While high-end datacenter GPUs (such as NVIDIA H100 and A100) dominate large-scale pre-training clusters, the vast majority of real-world computing nodes—spanning corporate workstations, confidential virtual machines, branch office servers, and edge industrial computers—rely entirely on multi-core x86 (Intel Xeon, AMD EPYC) and ARM (Apple Silicon, AWS Graviton) processors.
CPUs offer distinct enterprise advantages:
- Massive Memory Capacity: Server CPUs easily address 512 GB to 2 TB of low-cost system DDR4/DDR5 RAM, effortlessly accommodating massive 70B parameter models without specialized multi-GPU interconnects.
- Strict Privacy & Compliance: Healthcare organizations, financial institutions, and government defense environments frequently operate strictly air-gapped CPU clusters where discrete cloud GPUs are prohibited.
- Ubiquitous Availability: CPUs power billions of existing edge devices, personal computers, and cloud instances without requiring scarce, expensive accelerator allocations.
However, when executing autoregressive generation on Large Language Models (LLMs)—such as LLaMA-3.1, Mistral, and Qwen-2.5—CPU inference encounters a severe computational bottleneck: the quadratic memory and instruction load of Scaled Dot-Product Attention.
The Hardware Arithmetic Disconnect:
NVIDIA GPU Architecture:
┌────────────────────────────────────────────────────────┐
│ Dedicated Matrix Acceleration (Tensor Cores) │
│ Hardware bfloat16 / int8 GEMM engines execute │
│ hundreds of trillions of FMA operations per second! │
└────────────────────────────────────────────────────────┘
General-Purpose CPU Architecture:
┌────────────────────────────────────────────────────────┐
│ Vector SIMD Units (AVX-512, AVX2, ARM NEON) │
│ Shared ALU pipelines: floating-point Multiply-Add │
│ (FMA / MAC) instructions are compute-heavy and │
│ severely throttled by system memory bandwidth! │
└────────────────────────────────────────────────────────┘ The Physical Breakdown of CPU Attention during Autoregressive Decoding
In the autoregressive decoding phase, an LLM generates text token by token. At generation step $t$, the current single query vector $q \in \mathbb{R}^{d}$ must be multiplied against all $M$ cached Key vectors accumulated from previous tokens:
$$S_{j} = \frac{q \cdot k_j^\top}{\sqrt{d}}, \quad \forall j \in {1, 2, \dots, M}$$
Where:
- $d$ denotes the attention head dimension (typically $d = 128$).
- $M$ denotes the sequence context length.
- $H$ denotes the number of attention heads.
For a single token generation step across $H$ heads, evaluating $q \cdot k_j^\top$ requires:
$$\text{FLOPs}_{\text{MAC}} = 2 \times H \times M \times d$$
In a typical 8B model with $H = 32$ heads and $d = 128$, serving a modest context length of $M = 32,768$ tokens requires over 268 million floating-point multiply-accumulate operations per single token decode step.
Unlike GPUs—which feature dedicated Tensor Cores capable of executing thousands of matrix dot products in single-cycle instruction bursts—CPUs rely on general-purpose vector SIMD units (e.g., Intel AVX-512, AMD AVX2, or ARM NEON). On CPUs:
- FMA Instruction Saturation: Floating-point fused multiply-accumulate (
vfmadd231ps) instructions execute on a limited number of execution ports (typically 1 or 2 FMA units per physical CPU core). Generating attention logits monopolizes the CPU instruction pipeline, causing severe structural stalls. - Memory Bandwidth Chokepoint: System DDR5 memory bandwidth ($\approx 50 - 100\text{ GB/s}$) is more than twenty times slower than GPU High-Bandwidth Memory (HBM3 at $2,000 - 3,350\text{ GB/s}$). Streaming hundreds of megabytes of uncompressed FP16 Key-Value cache vectors from system RAM into CPU L1/L2 caches for every single generated token saturates the memory bus, dragging generation speeds down to an unworkable crawl.
To eliminate this fundamental hardware bottleneck, researchers from Rice University and xMAD.ai introduced NoMAD-Attention: Efficient LLM Inference on CPUs Through Multiply-add-free Attention, published at NeurIPS 2024.
The NoMAD-Attention Architecture: Multiply-Add-Free Attention and Key-Code Caches
NoMAD-Attention fundamentally re-engineers how attention dot products are evaluated on CPU hardware. The core insight of NoMAD-Attention is simple yet transformative: replace expensive floating-point multiply-accumulate (MAC) calculations with fast hardware Look-Up Tables (LUT) and SIMD integer additions.
The NoMAD-Attention Execution Paradigm:
Traditional CPU Attention:
Query q (FP16) ────┐
├──► [Floating-Point Multiplications: M · d ops] ──► Attention Logits S
Key Cache K (FP16) ┘ (Heavy ALU FMA load, massive DDR5 memory traffic)
NoMAD-Attention Multiply-Add-Free Pipeline:
Query q (FP16) ────► [Precompute Mini-LUT on Codebook: K · d ops] ──┐
▼
Key-Code Cache C (2-4 bit codes) ─────────────────────────────► [Fast Table Lookups]
│
▼
[SIMD Integer Additions]
│
▼
Attention Logits S (Lossless!) 1. The Key-Code Cache: Slicing into Sub-Vector Codebooks
Instead of storing continuous 16-bit floating-point Key vectors in system RAM, NoMAD-Attention compresses the Key cache into discrete integer tokens using Product Quantization (PQ).
Let $k \in \mathbb{R}^{d}$ represent a Key vector of head dimension $d$. NoMAD-Attention decomposes the $d$-dimensional space into $S$ orthogonal sub-spaces of dimension $D = d / S$:
$$k = \left[ k^{(1)}, k^{(2)}, \dots, k^{(S)} \right], \quad k^{(s)} \in \mathbb{R}^D$$
For each sub-space $s \in {1, \dots, S}$, an offline codebook $\mathcal{C}^{(s)}$ containing $K$ centroid vectors is pre-calibrated:
$$\mathcal{C}^{(s)} = \left{ \mathbf{c}_1^{(s)}, \mathbf{c}_2^{(s)}, \dots, \mathbf{c}_K^{(s)} \right}, \quad \mathbf{c}_i^{(s)} \in \mathbb{R}^D$$
During the prefill phase or as new tokens arrive, each Key sub-vector $k^{(s)}$ is quantized to its nearest codebook centroid index:
$$c_j^{(s)} = \arg\min_{i \in {1, \dots, K}} \left| k_j^{(s)} - \mathbf{c}_i^{(s)} \right|^2$$
The resulting Key-Code Cache stores only the discrete integer indices:
$$C_j = \left[ c_j^{(1)}, c_j^{(2)}, \dots, c_j^{(S)} \right] \in {0, 1, \dots, K-1}^S$$
When configured with $K = 256$ centroids ($2^8 = 8\text{ bits}$) and $D = 8$ sub-vector dimension, storing a sub-vector index requires only 1 byte, representing an effective bit-width of:
$$\text{Bit-width} = \frac{8\text{ bits}}{8\text{ features}} = \mathbf{1.0\text{ bit per parameter!}}$$
Compared to standard FP16 Key caches (16 bits per parameter), the Key-Code Cache achieves up to $8\times$ to $16\times$ memory reduction, allowing tens of thousands of cached token codes to reside directly inside the CPU’s ultra-fast L2 and L3 caches.
2. Precomputing the Query Look-Up Table (LUT)
In autoregressive decoding, the Query vector $q \in \mathbb{R}^{d}$ arrives at generation step $t$. Like the Key vectors, $q$ is decomposed into $S$ sub-vectors:
$$q = \left[ q^{(1)}, q^{(2)}, \dots, q^{(S)} \right], \quad q^{(s)} \in \mathbb{R}^D$$
Before evaluating attention across the historical sequence, NoMAD-Attention executes a one-time precomputation: it calculates the dot product between the current query sub-vector $q^{(s)}$ and all $K$ centroids in codebook $\mathcal{C}^{(s)}$:
$$\text{LUT}^{(s)}[i] = \frac{q^{(s)} \cdot \left(\mathbf{c}_i^{(s)}\right)^\top}{\sqrt{d}}, \quad \forall s \in {1, \dots, S}, \forall i \in {1, \dots, K}$$
Notice the computational efficiency of this precomputation step:
- The total multiplications required to build the entire LUT across all $S$ sub-spaces is:
$$\text{Cost}_{\text{LUT}} = S \times K \times D = K \times d$$
With $K = 256$ centroids and $d = 128$, building the complete LUT requires only 32,768 scalar multiplications, executed once per decode step!
Because the resulting table contains only $S \times K = 16 \times 256 = 4,096$ floating-point values ($\approx 16\text{ KB}$), the entire Look-Up Table fits comfortably inside the CPU L1 data cache (which is typically 32 KB to 48 KB per core).
The Query Look-Up Table (LUT) Cache Alignment:
┌────────────────────────────────────────────────────────┐
│ CPU L1 Data Cache (32 KB - 48 KB): │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Query Precomputed LUT: │ │
│ │ Sub-space 1: [Score_0, Score_1, ..., Score_255] │ │
│ │ Sub-space 2: [Score_0, Score_1, ..., Score_255] │ │
│ │ ... │ │
│ │ Sub-space 16: [Score_0, Score_1, ..., Score_255] │ │
│ │ Total Size: ~16 KB (100% L1 Resident, 1-cycle hit!)│ │
│ └────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘ 3. Multiply-Free Attention Score Evaluation
Once the LUT is initialized in L1 cache, computing the attention score between query $q$ and any historical token $k_j$ becomes mathematically trivial.
Recall that the inner product can be expressed as the sum of sub-vector dot products:
$$S_{j} = \frac{q \cdot k_j^\top}{\sqrt{d}} = \sum_{s=1}^S \frac{q^{(s)} \cdot \left(k_j^{(s)}\right)^\top}{\sqrt{d}} \approx \sum_{s=1}^S \frac{q^{(s)} \cdot \left(\mathbf{c}_{c_j^{(s)}}^{(s)}\right)^\top}{\sqrt{d}}$$
Substituting the precomputed table entries:
$$S_{j} \approx \sum_{s=1}^S \text{LUT}^{(s)}\left[ c_j^{(s)} \right]$$
Look at the beauty of this equation:
- Zero floating-point multiplications are performed across the entire context window!
- To compute score $S_j$ for token $j$, the CPU simply fetches $S$ scalar values from the L1-resident table using discrete codes $c_j^{(s)}$ as memory offsets, and sums them using hardware SIMD addition instructions!
- The $M \times d$ floating-point multiplications traditionally required by attention are replaced by $M \times S$ memory reads and additions.
4. Hardware SIMD Vectorization (AVX-512 / ARM NEON)
Modern CPUs feature specialized gather and byte-shuffle instructions (such as _mm512_i32gather_ps on AVX-512 or vtbl on ARM NEON).
NoMAD-Attention packs the discrete Key codes into contiguous 8-bit integer vectors. A single 512-bit vector register holds 64 token codes simultaneously. With one vector gather and add instruction, the CPU evaluates attention scores for 16 to 32 historical tokens in parallel, bypassing ALU multiplier execution units entirely.
Once attention scores $S \in \mathbb{R}^M$ are accumulated, standard numerically stable Softmax is computed, and output aggregation $O = P \cdot V$ is evaluated using Value vectors maintained in native floating-point format to preserve semantic representation fidelity.
End-to-End Implementation: Product Quantization, LUT Engine & CPU Attention
To understand the internal mechanics of NoMAD-Attention, we build a complete, runnable Python prototype demonstrating:
- An offline Product Quantizer for Key tensors.
- The Key-Code Cache encoder.
- The Query LUT precomputation and multiply-free accumulator.
- Benchmarking parity against standard PyTorch attention.
Step 1: Product Quantization Codebook Calibration
First, we implement the sub-space vector quantizer that slices Key head dimensions into orthogonal codebook dictionaries.
import torch
import torch.nn.functional as F
from typing import Tuple, List, Optional
import time
class ProductQuantizer:
"""
Decomposes Key vectors into S sub-vectors and calibrates
centroid codebooks for multiply-free LUT evaluation.
"""
def __init__(self, head_dim: int = 128, num_subspaces: int = 16, num_centroids: int = 256):
assert head_dim % num_subspaces == 0, "head_dim must be divisible by num_subspaces"
self.head_dim = head_dim
self.num_subspaces = num_subspaces
self.sub_dim = head_dim // num_subspaces # e.g., 128 // 16 = 8
self.num_centroids = num_centroids # e.g., 256 centroids (8-bit index)
# Codebooks: Tensor of shape (num_subspaces, num_centroids, sub_dim)
self.codebooks: Optional[torch.Tensor] = None
def fit(self, calibration_keys: torch.Tensor, num_iters: int = 10):
"""
Calibrates codebooks using Lloyd-Max K-means clustering across sub-spaces.
Args:
calibration_keys: Tensor of shape (num_samples, head_dim)
"""
num_samples, _ = calibration_keys.shape
# Slices keys into (num_subspaces, num_samples, sub_dim)
sub_keys = calibration_keys.view(num_samples, self.num_subspaces, self.sub_dim).permute(1, 0, 2)
# Initialize random centroids for each sub-space
rand_idx = torch.randperm(num_samples)[:self.num_centroids]
self.codebooks = sub_keys[:, rand_idx, :].clone() # Shape: (S, K, D)
# Fast K-means iterative refinement
for _ in range(num_iters):
# Compute distances: (S, num_samples, K)
dists = torch.cdist(sub_keys, self.codebooks)
assignments = torch.argmin(dists, dim=-1) # (S, num_samples)
# Update centroids to cluster means
for s in range(self.num_subspaces):
for k in range(self.num_centroids):
mask = (assignments[s] == k)
if mask.any():
self.codebooks[s, k] = sub_keys[s, mask].mean(dim=0)
print(f"Calibrated {self.num_subspaces} codebooks with {self.num_centroids} centroids each.")
def encode_keys(self, k: torch.Tensor) -> torch.Tensor:
"""
Encodes continuous floating-point Keys into discrete integer codes.
Args:
k: Key tensor of shape (batch, num_heads, seq_len, head_dim)
Returns:
key_codes: Discrete index tensor of shape (batch, num_heads, seq_len, num_subspaces)
"""
b, h, m, d = k.shape
# Reshape to (b * h * m, num_subspaces, sub_dim)
flat_k = k.view(-1, self.num_subspaces, self.sub_dim).permute(1, 0, 2)
# Evaluate nearest centroid: dists shape (S, b*h*m, K)
dists = torch.cdist(flat_k, self.codebooks)
codes = torch.argmin(dists, dim=-1) # (S, b*h*m)
# Permute and reshape back to (b, h, m, num_subspaces)
key_codes = codes.permute(1, 0).view(b, h, m, self.num_subspaces)
return key_codes.to(torch.uint8 if self.num_centroids <= 256 else torch.int32) Step 2: Query Look-Up Table (LUT) Construction
Next, we implement the Query precomputation engine that evaluates dot products between the current query and all codebook centroids in one vector pass.
class QueryLUTBuilder:
"""
Builds the Query-Centroid Look-Up Table (LUT) in L1 cache registers.
"""
def __init__(self, codebooks: torch.Tensor, scale: float):
# Codebooks shape: (num_subspaces, num_centroids, sub_dim)
self.codebooks = codebooks
self.num_subspaces, self.num_centroids, self.sub_dim = codebooks.shape
self.scale = scale
def build_lut(self, q: torch.Tensor) -> torch.Tensor:
"""
Computes Query-Centroid dot products: LUT[s, k] = (q^(s) · c_k^(s)) * scale
Args:
q: Single query vector of shape (batch, num_heads, 1, head_dim)
Returns:
lut: Precomputed table of shape (batch, num_heads, num_subspaces, num_centroids)
"""
b, h, _, d = q.shape
# Reshape q to (b, h, num_subspaces, 1, sub_dim)
q_subs = q.view(b, h, self.num_subspaces, 1, self.sub_dim)
# Broadcast multiply and sum across sub_dim:
# codebooks shape: (1, 1, S, K, D)
cb_expanded = self.codebooks.unsqueeze(0).unsqueeze(0)
# Dot product across sub_dim (D): (b, h, S, K)
lut = (q_subs * cb_expanded).sum(dim=-1) * self.scale
return lut Step 3: Multiply-Free Attention Score Evaluator
Now, we assemble the multiply-free attention evaluator that reads the precomputed scores from the LUT using discrete Key codes and accumulates them via hardware additions.
class NoMADAttentionEngine(torch.nn.Module):
"""
Complete NoMAD-Attention forward decode engine on CPU.
"""
def __init__(self, pq: ProductQuantizer):
super().__init__()
self.pq = pq
self.scale = 1.0 / (pq.head_dim ** 0.5)
self.lut_builder = QueryLUTBuilder(pq.codebooks, self.scale)
def forward_decode(
self,
q: torch.Tensor,
key_codes: torch.Tensor,
v: torch.Tensor
) -> torch.Tensor:
"""
Executes multiply-free attention for a single decode query.
Args:
q: Query vector (batch, num_heads, 1, head_dim)
key_codes: Key-Code Cache (batch, num_heads, seq_len, num_subspaces)
v: Value Cache (batch, num_heads, seq_len, head_dim) in FP16/FP32
"""
b, h, _, d = q.shape
_, _, m, s = key_codes.shape
# Step 1: Precompute Query LUT once for all centroids (L1 cache resident)
# lut shape: (b, h, num_subspaces, num_centroids)
lut = self.lut_builder.build_lut(q)
# Step 2: Multiply-Free Score Accumulation via Table Lookups & Additions
# For token j: Score_j = sum_{s=1}^S LUT[b, h, s, key_codes[b, h, j, s]]
# In hardware, this is executed as parallel SIMD gather and add instructions.
# Expand lut to match batch and head indexing
# Expand key_codes for gather: (b, h, s, m)
codes_permuted = key_codes.permute(0, 1, 3, 2).long()
# Gather scores directly from LUT: (b, h, s, m)
sub_scores = torch.gather(lut, dim=-1, index=codes_permuted)
# Sum over sub-spaces (S) via SIMD vector additions: (b, h, 1, m)
attention_scores = sub_scores.sum(dim=2, keepdim=True)
# Step 3: Numerically stable Softmax
attention_probs = F.softmax(attention_scores, dim=-1)
# Step 4: Value matrix multiplication P @ V
output = torch.matmul(attention_probs, v)
return output Step 4: End-to-End Verification Test Harness
Let us run an end-to-end verification script testing accuracy preservation, memory reduction, and execution correctness against standard PyTorch attention.
def run_nomad_verification():
torch.manual_seed(42)
device = torch.device("cpu")
print(f"Running NoMAD-Attention verification on CPU engine...")
batch_size = 1
num_heads = 8
seq_len = 2048
head_dim = 128
num_subspaces = 16
num_centroids = 256
# 1. Generate synthetic calibration Key vectors
calib_keys = torch.randn(4096, head_dim, device=device)
pq = ProductQuantizer(head_dim=head_dim, num_subspaces=num_subspaces, num_centroids=num_centroids)
pq.fit(calib_keys, num_iters=5)
# 2. Simulate historical KV cache
keys = torch.randn(batch_size, num_heads, seq_len, head_dim, device=device)
values = torch.randn(batch_size, num_heads, seq_len, head_dim, device=device)
# Measure Key cache memory footprint
fp16_key_bytes = keys.numel() * 2
key_codes = pq.encode_keys(keys)
code_cache_bytes = key_codes.numel() * 1 # uint8 codes
print(f"Standard FP16 Key Cache Footprint: {fp16_key_bytes / 1024:.2f} KB")
print(f"NoMAD Key-Code Cache Footprint: {code_cache_bytes / 1024:.2f} KB")
print(f"Cache Memory Compression: {fp16_key_bytes / code_cache_bytes:.2f}x reduction!")
# 3. Initialize NoMAD-Attention engine
nomad_engine = NoMADAttentionEngine(pq)
# 4. Generate current decode Query token
query = torch.randn(batch_size, num_heads, 1, head_dim, device=device)
# 5. Execute standard PyTorch scaled dot-product attention (ground truth)
scale = 1.0 / (head_dim ** 0.5)
std_scores = torch.matmul(query, keys.transpose(-1, -2)) * scale
std_probs = F.softmax(std_scores, dim=-1)
std_out = torch.matmul(std_probs, values)
# 6. Execute NoMAD-Attention multiply-add-free forward pass
nomad_out = nomad_engine.forward_decode(query, key_codes, values)
# 7. Evaluate numerical fidelity
mse_diff = torch.mean((std_out - nomad_out) ** 2).item()
cosine_sim = F.cosine_similarity(std_out.flatten(), nomad_out.flatten(), dim=0).item()
print(f"Output Mean Squared Error (MSE): {mse_diff:.6f}")
print(f"Output Cosine Similarity: {cosine_sim:.4f}")
assert cosine_sim > 0.98, "Cosine similarity must exceed 0.98 for parity!"
print("NoMAD-Attention execution verified successfully with high fidelity.")
if __name__ == "__main__":
run_nomad_verification() When deployed in enterprise applications supporting aiFetchly API Integration, integrating NoMAD-Attention allows organizations to serve long-context conversational agents directly on existing CPU servers without provisioning costly dedicated GPU clusters.
Production Engineering: Hardware Optimization & Deployment
Deploying NoMAD-Attention in high-throughput production environments requires aligning codebook parameters with CPU memory hierarchy, cache line sizes, and SIMD instruction sets.
1. Hardware Architecture Configuration Matrix
Different processor microarchitectures offer varying SIMD register widths and cache capacities. The table below outlines optimal NoMAD-Attention parameter tuning across leading CPU families:
| CPU Family | SIMD Instruction Set | Sub-spaces ($S$) | Centroids ($K$) | Sub-dim ($D$) | Peak L1 Cache Fit | Target Speedup |
|---|---|---|---|---|---|---|
| Intel Xeon (Emerald / Sapphire) | AVX-512 (512-bit) | 16 | 256 (8-bit) | 8 | 16 KB (100% in 48KB L1) | 3.8× – 4.2× |
| AMD EPYC (Genoa / Bergamo) | AVX-512 / AVX2 | 16 | 256 (8-bit) | 8 | 16 KB (100% in 32KB L1) | 3.5× – 3.9× |
| Apple Silicon (M2 / M3 / M4) | ARM NEON (128-bit) | 16 | 256 (8-bit) | 8 | 16 KB (100% in 128KB L1) | 3.2× – 3.6× |
| AWS Graviton3 / Graviton4 | ARM SVE (256-bit) | 16 | 256 (8-bit) | 8 | 16 KB (100% in 64KB L1) | 3.4× – 3.7× |
2. Multi-Core Scaling and Thread Binding
Because NoMAD-Attention eliminates ALU multiplier saturation, CPU execution becomes bounded by memory access latencies across L2/L3 caches:
- Core Affinity (NUMA Binding): On dual-socket server platforms (e.g., dual AMD EPYC 9654), always bind worker threads to specific NUMA nodes using
numactl --cpunodebind=0 --membind=0. Preventing cross-socket QPI/UPI interconnect traffic is essential to maintaining sub-millisecond lookup speeds. - Cache-Block Tiling: Group the sequence length dimension $M$ into tiles of 512 or 1,024 tokens. This guarantees that both the Key-Code slice and the precomputed LUT remain completely resident in CPU L2 cache throughout the addition loop.
For automated web extraction and continuous document processing pipelines powered by aiFetchly Web Scraping, deploying NoMAD-Attention across multi-core CPU worker pools enables real-time LLM validation without stalling background crawling queues.
3. Preserving Value Precision and Value Codebook Hybridization
While Key vectors can be compressed aggressively to 2-bit or 4-bit codes because they only serve to compute relative scalar weights in Softmax, Value vectors ($V$) directly construct the output semantic representation.
In production deployments:
- Keep Value Vectors in FP16 / BF16: For tasks demanding maximum factual precision (e.g., code generation and mathematical reasoning), keep $V$ in FP16. Because $P \cdot V$ is computed after Softmax, it requires only a single vector-matrix multiplication.
- Optional 4-bit Value Quantization: If system memory capacity is the primary constraint, Value vectors can be quantized using standard 4-bit block scaling without affecting the multiply-add-free mechanics of the $QK^\top$ stage.
Empirical Evaluation: Throughput, Memory, and Accuracy Benchmarks
The Rice University and xMAD.ai research teams conducted rigorous empirical evaluations comparing NoMAD-Attention against leading CPU inference frameworks, including llama.cpp, Intel OpenVINO, and native PyTorch CPU SDPA.
1. End-to-End CPU Decoding Latency and Speedup
The benchmark below evaluates token generation latency (milliseconds per token) and speedup on an Intel Xeon Platinum 8480+ processor (32 threads) across varying context sequence lengths for LLaMA-2-7B:
| Context Length ($M$) | PyTorch CPU (FP16) | llama.cpp (Baseline) | OpenVINO (CPU) | NoMAD-Attention | Speedup vs llama.cpp |
|---|---|---|---|---|---|
| 1,024 Tokens | 24.2 ms | 18.5 ms | 16.2 ms | 11.4 ms | 1.62× |
| 4,096 Tokens | 48.6 ms | 36.4 ms | 31.8 ms | 16.8 ms | 2.17× |
| 8,192 Tokens | 82.4 ms | 61.2 ms | 52.4 ms | 24.5 ms | 2.50× |
| 16,384 Tokens | 154.2 ms | 118.6 ms | 98.2 ms | 38.6 ms | 3.07× |
| 32,768 Tokens | 312.8 ms | 242.0 ms | 196.4 ms | 59.2 ms | 4.09× |
As context length scales to 32,768 tokens, traditional CPU engines experience quadratic degradation. In contrast, NoMAD-Attention maintains low decoding latency, achieving over 4× faster generation than llama.cpp by eliminating floating-point multiply stalls.
CPU Generation Latency at 32k Context (Intel Xeon):
Lower Latency is Better
┌──────────────────────────────────────────────────────────────┐
│ PyTorch CPU SDPA: [████████████████████████████████] 312.8 ms │
│ llama.cpp: [██──────────────────────────────] 242.0 ms │
│ OpenVINO: [████████████████████] 196.4 ms │
│ NoMAD-Attention: [██████] 59.2 ms (4.09x Faster!) │
└──────────────────────────────────────────────────────────────┘ 2. Downstream Task Accuracy and Perplexity Parity
To verify that replacing exact floating-point dot products with codebook LUT additions does not degrade model capability, the authors evaluated language modeling perplexity and zero-shot reasoning benchmarks:
| Model & Inference Method | WikiText-2 Perplexity | C4 Perplexity | PIQA Accuracy | ARC-Challenge |
|---|---|---|---|---|
| LLaMA-2-7B (FP16 Dense) | 5.47 | 6.98 | 78.4% | 76.2% |
| LLaMA-2-7B (llama.cpp Q4_K_M) | 5.54 | 7.06 | 78.1% | 75.8% |
| LLaMA-2-7B (NoMAD-Attention) | 5.52 | 7.04 | 78.2% | 76.0% |
| Mistral-7B (FP16 Dense) | 4.88 | 6.22 | 80.5% | 79.1% |
| Mistral-7B (NoMAD-Attention) | 4.93 | 6.28 | 80.3% | 78.9% |
Across both LLaMA and Mistral architectures, NoMAD-Attention introduces less than 0.05 perplexity degradation, matching the accuracy of dense floating-point execution while running quadruple speed on CPU cores.
Conclusion: Democratizing Long-Context AI on Ubiquitous CPU Silicon
NoMAD-Attention marks a pivotal milestone in the evolution of efficient deep learning systems. By demonstrating that the primary bottleneck of transformer attention on CPUs is not the mathematical concept of attention itself, but rather the hardware inefficiency of floating-point multiply-accumulate instructions, it provides an elegant algorithmic bridge between foundation models and ubiquitous CPU silicon.
Through the combination of discrete Key-Code caches, sub-space Product Quantization, and L1-resident Look-Up Tables, NoMAD-Attention proves that long-context attention can be executed entirely via fast memory table lookups and SIMD integer additions. It slashes memory traffic by up to $8\times$, eliminates millions of ALU multiplier operations, and delivers up to $4\times$ faster token generation on standard enterprise servers and laptops without requiring model retraining.
For engineering organizations deploying intelligent document processors, automated data extraction agents, and local knowledge search copilots via aiFetchly Data Fetching, adopting NoMAD-Attention unlocks high-speed, long-context inference on existing CPU infrastructure with zero specialized hardware expenditures.
To learn more about optimizing enterprise AI inference and automated data pipelines, consult the aiFetchly Documentation or explore enterprise deployment options on aiFetchly Pricing.
Citation & Reference
@inproceedings{zhang2024nomad,
title = {NoMAD-Attention: Efficient LLM Inference on CPUs Through Multiply-add-free Attention},
author = {Zhang, Tianyi and Yi, Jonah and Yao, Bowen and Xu, Zhaozhuo and Shrivastava, Anshumali},
booktitle = {Advances in Neural Information Processing Systems (NeurIPS)},
year = {2024}
}