How the KV Window Budget Is Calculated in Needle and How to Tune It for Different Sequence Lengths

The KV window budget in Needle is calculated based on architecture constants (11.5 MiB memory budget, 32-token alignment groups) and model parameters (KV heads, hidden size, layers), then tuned via config.kv_window to cap or extend the sliding cache for different sequence lengths.

Needle uses a fixed memory budget to constrain the key-value cache during autoregressive generation. This article explains the mathematical derivation in kv_budget_window(), how effective_kv_window() applies user overrides, and practical tuning strategies for short, medium, and long sequences.

How Needle Calculates the KV Window Budget

The Core Formula

The KV budget calculation lives in needle/model/architecture.py at lines 603–617: https://github.com/cactus-compute/needle/blob/main/needle/model/architecture.py#L603-L617

def kv_budget_window(config):
    head_dim = (getattr(config, "attn_dim", 0) or config.d_model) // config.num_heads
    kv = config.num_kv_heads * head_dim
    d, L = config.d_model, config.num_layers
    sites = len(tuple(getattr(config, "engram_layers", (2, 15))))
    per_pos = (L * (2 * kv + 2 * (kv // KV_GROUP) * 4)
               + sites * (d + (d // KV_GROUP) * 4))
    window = (KV_BUDGET_BYTES // per_pos) // KV_GROUP * KV_GROUP
    return max(KV_WINDOW_MIN, min(window, config.max_seq_len))

Key components:

  • head_dim — Dimensionality per attention head, derived from d_model / num_heads or an explicit attn_dim
  • kv — Total KV dimension per position, computed as num_kv_heads * head_dim
  • per_pos — Memory per token position accounting for:
    • Linear KV projections (2 * kv for keys and values)
    • Quantized packing overhead (2 * (kv // KV_GROUP) * 4)
    • Engram layer storage (sites * (d + (d // KV_GROUP) * 4))
  • KV_BUDGET_BYTES — Hardcoded constant of 12,058,880 bytes (11 MiB + 512 KiB)
  • KV_GROUP — Alignment granularity set to 32 tokens
  • KV_WINDOW_MIN — Floor value of 160 tokens

The function returns the largest window fitting the budget, aligned to 32 tokens, bounded between 160 and max_seq_len.

Budget-to-Window Conversion

The arithmetic follows this path:

  1. Compute bytes per position (per_pos) from architecture
  2. Divide total budget by per-position cost: KV_BUDGET_BYTES // per_pos
  3. Align down to KV_GROUP boundary: // KV_GROUP * KV_GROUP
  4. Clamp with max(KV_WINDOW_MIN, min(window, config.max_seq_len))

This guarantees deterministic memory usage regardless of batch size or prompt length.

How effective_kv_window Applies User Overrides

Users control the final window via config.kv_window. The resolution logic appears at lines 614–617: https://github.com/cactus-compute/needle/blob/main/needle/model/architecture.py#L614-L617

def effective_kv_window(config):
    budget = kv_budget_window(config)
    return min(budget, config.kv_window) if config.kv_window else budget

Behavior matrix:

kv_window setting Result
0 (default) Use full budget-derived window
Positive integer < budget Cap to user-specified value
Positive integer > budget Silent cap to budget (no expansion beyond memory limit)

This design prevents accidental out-of-memory errors while allowing deliberate restriction.

Tuning the KV Window for Different Sequence Lengths

Short Sequences (≤ 512 tokens)

For brief prompts and completions, accept the default:

from needle.model.architecture import TransformerConfig, effective_kv_window

short_cfg = TransformerConfig(
    d_model=768,
    num_heads=12,
    num_kv_heads=4,  # GQA reduces KV memory

    num_layers=12,
    max_seq_len=2048,
    # kv_window defaults to 0

)

print(effective_kv_window(short_cfg))  # Typically 512-1024 tokens from budget

The budget-derived window usually exceeds short sequence needs. No tuning required.

Medium Sequences (1K–8K tokens)

Medium-length generation benefits from explicit profiling:

import torch
from needle.model.architecture import TransformerConfig, kv_budget_window

medium_cfg = TransformerConfig(
    d_model=1024,
    num_heads=16,
    num_kv_heads=8,
    num_layers=24,
    max_seq_len=8192,
)

budget_win = kv_budget_window(medium_cfg)
print(f"Budget window: {budget_win}")

# If GPU memory is tight, cap below budget

medium_cfg.kv_window = 2048
print(f"Capped window: {min(budget_win, medium_cfg.kv_window)}")

Tuning principle: Set kv_window to 50–75% of your expected maximum generation length if the budget window exceeds available VRAM after accounting for activations and weights.

Long Sequences (16K+ tokens)

Long-context models face hard memory constraints. Two tuning approaches:

Approach 1: Reduce KV dimension via architecture


# Use fewer KV heads to lower per-position cost

long_cfg = TransformerConfig(
    d_model=2048,
    num_heads=32,
    num_kv_heads=2,  # Aggressive GQA: 16:1 compression

    num_layers=32,
    max_seq_len=32768,
)

print(kv_budget_window(long_cfg))  # Larger window due to smaller kv dimension

Approach 2: Accept truncated context with explicit window

long_cfg.kv_window = 4096  # Rolling window: oldest tokens evicted

This implements sliding-window attention behavior—older KV pairs are discarded when the cache fills.

Low-Memory Hardware Tuning

On consumer GPUs, combine multiple strategies:

efficient_cfg = TransformerConfig(
    d_model=512,
    num_heads=8,
    num_kv_heads=2,      # 4:1 GQA compression

    num_layers=8,        # Shallower model

    max_seq_len=4096,
    kv_window=512,       # Hard cap for deterministic memory

)

Profile with torch.cuda.memory_stats() to verify headroom:

import torch
from needle.model.architecture import effective_kv_window

def profile_kv_memory(config, batch_size=1):
    window = effective_kv_window(config)
    head_dim = config.d_model // config.num_heads
    kv_dim = config.num_kv_heads * head_dim
    
    # KV cache: (layers, 2, batch, heads, window, head_dim) in fp16

    bytes_per_elem = 2
    kv_memory = (config.num_layers * 2 * batch_size * 
                 config.num_kv_heads * window * head_dim * bytes_per_elem)
    
    return window, kv_memory / (1024**2)  # MiB

win, mem = profile_kv_memory(efficient_cfg)
print(f"Window: {win}, KV cache: {mem:.1f} MiB")

Architecture Constants and Their Impact

Constant Value Effect on Tuning
KV_BUDGET_BYTES 12,058,880 Upper bound on total KV cache; modify only via source edit
KV_GROUP 32 Window granularity; partial windows rounded down
KV_WINDOW_MIN 160 Absolute floor; prevents degenerate caches

To adjust the global budget, edit needle/model/architecture.py and rebuild. For per-model flexibility, use kv_window override.

Key Source Files

File Relevance
needle/model/architecture.py kv_budget_window(), effective_kv_window(), TransformerConfig definition
needle/model/decode.py Consumes KV window for causal mask construction
needle/model/export.py Serializes window size to model headers
tests/test_build.py Validates window calculations across configurations

Summary

  • The KV window budget derives from fixed constants (11.5 MiB, 32-token groups) and model architecture (KV heads, layers, hidden size)
  • kv_budget_window() computes the maximum cache size fitting the memory budget, aligned and clamped to valid ranges
  • effective_kv_window() layers user intent via config.kv_window, capping rather than expanding
  • Tuning strategy depends on sequence length: accept defaults for short sequences, cap explicitly for memory constraints, reduce KV dimension for long contexts

Frequently Asked Questions

What happens if I set kv_window larger than the budget-derived window?

Needle silently caps to the budget. The min(budget, config.kv_window) logic in effective_kv_window prevents exceeding the 11.5 MiB allocation, protecting against out-of-memory errors.

Can I increase KV_BUDGET_BYTES without modifying source code?

No. The constant is hardcoded in architecture.py. To raise the budget, edit line defining KV_BUDGET_BYTES and reinstall. Consider instead reducing num_kv_heads or num_layers to fit your target window within the default budget.

Why does the window calculation include engram_layers?

Engram layers (default: layers 2 and 15) store additional per-token state for retrieval-augmented mechanisms. Their memory cost—d + (d // KV_GROUP) * 4 per site—is included in per_pos to ensure the total cache allocation stays within budget.

How does the KV window interact with Flash Attention or other kernels?

The window size determines the sequence dimension of the KV cache tensor passed to attention kernels. Needle's decode path respects this bound when constructing causal masks; underlying kernels operate on the pre-truncated tensors without additional masking logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →