cd /news/machine-learning/pivot-explained-from-paper-to-workin… · home topics machine-learning article
[ARTICLE · art-81195] src=dev.to ↗ pub= topic=machine-learning verified=true sentiment=· neutral

PIVOT Explained — From Paper to Working Code in 10 Minutes

A developer introduced PIVOT, an algorithm that reduces the indexer cost of dynamic sparse attention from O(L²) to O(L²/g) by grouping queries and using a proxy query for a single scan per group. The algorithm exploits the observation that adjacent queries share ~90% of their top-k token selections, enabling efficient long-context inference without modifying model weights.

read3 min views1 publishedJul 31, 2026

You enabled sparse attention. Your model still chokes at 128K tokens. The indexer is why — and PIVOT fixes it without touching your weights.

Dynamic Sparse Attention (DSA) should make long-context inference fast. Score all tokens → pick top-k → attend only to those k. Complexity drops from O(L²) to O(L·k).

Except scoring all tokens is itself O(L²). The "indexer" does a full O(L) scan per query position. With L queries, you're back to O(L²). At 100K tokens, the indexer dominates latency. Sparse attention becomes a lie.

Observation 1: Adjacent queries share ~90% of their top-k token selections — they process nearly identical context.

Observation 2: Indexer scores are long-tailed — a proxy query produces a reliable candidate set.

PIVOT's algorithm:

group = [q_i, q_{i+1}, ..., q_{i+g-1}]
proxy_q = mean(group)

scores = proxy_q · K[:i]        # O(L)
C = top-K(scores)               # candidate set, K = 2 × top_k

for q in group:
    refine_scores = q · K[C]    # O(K), not O(L)
    final_indices[q] = top-k(refine_scores)

Indexer cost: O(L²) → O(L²/g). With g=8 that's 8× fewer full scans.

import torch
import torch.nn.functional as F

def pivot_refine(
    queries: torch.Tensor,   # (seq_len, num_heads, head_dim)
    keys: torch.Tensor,
    top_k: int = 64,
    group_size: int = 8,
    candidate_ratio: float = 2.0,
) -> torch.Tensor:
    """
    Returns sparse-attention token indices.
    Shape: (seq_len, num_heads, top_k)
    """
    seq_len, num_heads, head_dim = queries.shape
    K = int(top_k * candidate_ratio)
    indices = torch.zeros(seq_len, num_heads, top_k,
                          dtype=torch.long, device=queries.device)

    for h in range(num_heads):
        q_h = queries[:, h, :]
        k_h = keys[:, h, :]

        for g_start in range(0, seq_len, group_size):
            g_end = min(g_start + group_size, seq_len)

            proxy = q_h[g_start:g_end].mean(dim=0)

            for pos in range(g_start, g_end):
                if pos == 0:
                    continue
                proxy_scores = proxy @ k_h[:pos].T
                actual_K = min(K, pos)
                _, C = torch.topk(proxy_scores, actual_K)

                refine_scores = q_h[pos] @ k_h[C].T
                actual_k = min(top_k, actual_K)
                _, best = torch.topk(refine_scores, actual_k)
                indices[pos, h, :actual_k] = C[best]

    return indices

def pivot_reuse(
    queries: torch.Tensor,
    keys: torch.Tensor,
    top_k: int = 64,
    group_size: int = 8,
    candidate_ratio: float = 2.0,
) -> torch.Tensor:
    """Max-speed variant — all queries in group share indices."""
    seq_len, num_heads, head_dim = queries.shape
    K = int(top_k * candidate_ratio)
    indices = torch.zeros(seq_len, num_heads, top_k,
                          dtype=torch.long, device=queries.device)

    for h in range(num_heads):
        q_h = queries[:, h, :]
        k_h = keys[:, h, :]

        for g_start in range(0, seq_len, group_size):
            g_end = min(g_start + group_size, seq_len)
            ref_pos = max(g_start, 1)
            proxy = q_h[g_start:g_end].mean(dim=0)
            proxy_scores = proxy @ k_h[:ref_pos].T
            actual_K = min(K, ref_pos)
            _, C = torch.topk(proxy_scores, actual_K)

            for pos in range(g_start, g_end):
                actual_k = min(top_k, actual_K)
                indices[pos, h, :actual_k] = C[:actual_k]

    return indices

if __name__ == "__main__":
    S, H, D = 1024, 8, 64
    q = torch.randn(S, H, D)
    k = torch.randn(S, H, D)
    v = torch.randn(S, H, D)

    idx = pivot_refine(q, k, top_k=64, group_size=8)
    print(f"Index shape: {idx.shape}")  # (1024, 8, 64)

    out = torch.zeros_like(q)
    scale = D ** -0.5
    for pos in range(1, S):
        for h in range(H):
            sel = idx[pos, h]
            w = F.softmax(q[pos, h] @ k[sel, h].T * scale, dim=-1)
            out[pos, h] = w @ v[sel, h]
    print(f"Output shape: {out.shape}")  # (1024, 8, 64)

⚠️ Reference implementation only. Production speedups need Triton/CUDA kernels. Official code not yet released (July 2026).

Tested on DeepSeek-V3.2 and GLM-5.1 — LongBench + RULER:

Method Indexer Speed E2E Latency Accuracy
Dense DSA (baseline) ✅ full
PIVOT-Refine ~3× faster −28% ✅ ≈ baseline
PIVOT-Reuse
4× faster
−37.5%
⚠️ minor drop

PIVOT-Refine = dense-indexer accuracy + 3× speed. Zero retraining.

DSA-only: Works out of the box on DeepSeek-V3.2 / GLM-5.1. Standard full-attention models need DSA fine-tuning first.

Group size is manual: No adaptive strategy in the paper — tune g per model/sequence length.

Batch inference untested: Single-sequence results only.

Prefill vs. decode split missing: The 1.6× E2E number doesn't break down by phase.

What's your experience with sparse attention in production? Drop a comment.

── more in #machine-learning 4 stories · sorted by recency
── more on @pivot 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/pivot-explained-from…] indexed:0 read:3min 2026-07-31 ·