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 fromd_model / num_headsor an explicitattn_dimkv— Total KV dimension per position, computed asnum_kv_heads * head_dimper_pos— Memory per token position accounting for:- Linear KV projections (
2 * kvfor keys and values) - Quantized packing overhead (
2 * (kv // KV_GROUP) * 4) - Engram layer storage (
sites * (d + (d // KV_GROUP) * 4))
- Linear KV projections (
KV_BUDGET_BYTES— Hardcoded constant of 12,058,880 bytes (11 MiB + 512 KiB)KV_GROUP— Alignment granularity set to 32 tokensKV_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:
- Compute bytes per position (
per_pos) from architecture - Divide total budget by per-position cost:
KV_BUDGET_BYTES // per_pos - Align down to
KV_GROUPboundary:// KV_GROUP * KV_GROUP - 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 rangeseffective_kv_window()layers user intent viaconfig.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →