Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

JohnScheuer/prefix-cache-sim

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

prefix-cache-sim

License: MIT C++20 Python 3.10+

Simulador de políticas de eviction para prefix KV-cache em servidores LLM.

Stack: C++20 (core) · Python (análise/plots) · CMake + Ninja · WSL/VSCode


Motivação

Servidores LLM modernos (vLLM, SGLang) reutilizam o KV-cache de prefixes repetidos entre requests. Se dois requests compartilham o mesmo prefixo (ex.: system prompt), o KV-cache desse prefixo pode ser reutilizado sem recomputação — reduzindo latência de prefill de O(n²) para O(1) por token.

Este simulador mede hit rate, prefill saved e latência para diferentes políticas de eviction, tamanhos de cache e padrões de workload.


Build

python3 -m venv .venv && source .venv/bin/activate
pip install matplotlib pandas numpy

mkdir build && cd build
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja && cd ..

# Gera workload
python3 experiments/workload_gen.py \
    --mode multi \
    --n-sessions 60 \
    --turns-per-session 5 \
    --out experiments/trace.csv

# Simula
./build/prefix_cache_sim \
    --trace experiments/trace.csv \
    --out results/run \
    --strategy LFU \
    --cache-max-pages 256

# Sweep completo
python3 experiments/sweep_cache_size.py
python3 plots/plot_metrics.py

Flags principais
Flag	            Default	         Descrição
--trace PATH	      —	             CSV de entrada (obrigatório)
--strategy STR	     LRU	         LRU · LFU · FIFO · SizeLRU
--cache-max-pages N	 512	         Máximo de páginas no prefix cache
--total-pages N	     2048	         Total de páginas físicas
--tokens-per-page N	 16	             Tokens por página de KV
--prefix-length N	 128	         Tamanho do system prompt compartilhado
--n-prefix-groups N	 5	             Número de prefixes distintos
--size-lru-alpha F	 5.0	         Peso do page_count no score do SizeLRU
--gpu-tflops F	     312	         TFLOPS da GPU (para custo de prefill)
--max-ticks N	     50000	         Duração máxima da simulação

Arquitetura
PageAllocator          — pool de páginas físicas, free-list embaralhada
RadixTree              — lookup exato e parcial de prefixes por token_id
  └── PrefixNode       — segmento de tokens + páginas KV + metadados LRU/LFU
IEvictionPolicy        — interface de eviction (opera em folhas da RadixTree)
  ├── LRUEviction
  ├── LFUEviction
  ├── FIFOEviction
  └── SizeLRUEviction  — score = last_access + alpha × page_count
Simulator (tick loop)  — arrivals → lookup → insert → decode → timeseries

Workload
# single-turn: requests independentes com prefixes compartilhados
python3 experiments/workload_gen.py --mode single \
    --shared-prefix-ratio 0.55 --repeat-ratio 0.15

# multi-turn: sessões conversacionais (histórico acumulativo)
python3 experiments/workload_gen.py --mode multi \
    --n-sessions 60 --turns-per-session 5

# mixed
python3 experiments/workload_gen.py --mode mixed \
    --multi-fraction 0.7

    Achados experimentais
1. Multi-turn vs Single-turn
Métrica         	              Multi-turn	    Single-turn	      Ganho
Partial hit rate (cache saturado)	98.1%	          76.1%	          1.29×
Prefill saved total	                211 ms	          79.8 ms	      2.64×
Cache para saturar	              ≥ 256 páginas	  ≥ 512 páginas	      2× menos


Prefix caching é 2.6× mais efetivo em workloads multi-turn.
Cada turn reutiliza 100% do histórico dos turns anteriores.

2. Ranking de políticas (cache limitado)
LFU > LRU > FIFO ≥ SizeLRU(α=100)     — regra geral
LFU > SizeLRU(α=5) > LRU              — single-turn, cache médio

Política	Melhor regime	                   Pior regime
LFU	       qualquer cache limitado	          cache grande (empata)
LRU	         cache grande	                  cache muito pequeno
FIFO	   sem overhead de tracking	          cache pequeno
SizeLRU(α=5)	single-turn + cache médio	  single-turn + cache pequeno

3. SizeLRU: onde usar
Único regime onde SizeLRU supera LRU e LFU simultaneamente:
workload:  single-turn
cache:     ~256 páginas (intermediário)
alpha:     5
resultado: partial_hit_rate = 81.0%
           vs LRU = 79.5%  (+1.5 pp)
           vs LFU = 77.2%  (+3.8 pp)

           Por que alpha=5 funciona aqui:
prompts single-turn têm tamanhos heterogêneos (8–32 páginas/nó).
Alpha=5 dá leve vantagem a nós pequenos (prefixes compartilhados curtos),
protegendo-os da eviction sem travar novas inserções.

Por que falha em multi-turn:
todos os nós crescem com o histórico — page_count fica similar entre nós.
Alpha não consegue discriminar; SizeLRU degenera para LRU.

Por que falha com cache pequeno:
alpha alto bloqueia a entrada de novos prefixes.
Hit rate cai monotonicamente com alpha crescente.

Recomendação:

Cenário	                                         Política
Workload desconhecido	                          LFU
Multi-turn (qualquer cache)         	          LFU
Single-turn, prompts variados, cache médio	      SizeLRU(α=5)
Cache grande, sem pressão de eviction	          qualquer uma

4. Sweep de alpha (SizeLRU)

Regime	              Melhor α	        PHR	       vs LRU	 vs LFU
multi-turn, c=64	    200         	86.6%	   +1.9 pp	 −5.6 pp
multi-turn, c=256	    50	            97.8%	   +0.4 pp	 −0.4 pp
multi-turn, c=2000	  qualquer	        98.1%	    0 pp	 0 pp
single-turn, c=64	    0.1         	53.4%	   −1.1 pp	−29.1 pp
single-turn, c=256	     5	            81.0%	   +1.5 pp	+3.7 pp
single-turn, c=2000   qualquer	        76.1%	   0 pp	    0 pp

Alpha muito alto (≥50) destrói o hit rate em single-turn com cache pequeno.
Não existe alpha universalmente bom — tuning é necessário por workload.

Estrutura do repositório

prefix-cache-sim/
├── include/
│   ├── allocator.hpp       PageAllocator (free-list embaralhada)
│   ├── block_table.hpp     BlockTable (mapeamento lógico→físico)
│   ├── prefix_tree.hpp     RadixTree + PrefixNode + PrefixLookupResult
│   ├── cache_policy.hpp    IEvictionPolicy + LRU/LFU/FIFO/SizeLRU
│   └── simulator.hpp       PrefixSimConfig, PrefixSimSummary, TimeSeriesRow
├── src/
│   ├── prefix_tree.cpp     RadixTree: lookup, insert, split, evict
│   ├── simulator.cpp       Tick loop: arrivals, admit, decode, timeseries
│   └── main.cpp            CLI + load_trace + build_requests
├── experiments/
│   ├── workload_gen.py     Gera traces: single/multi/mixed
│   ├── sweep_cache_size.py Sweep: 4 políticas × 7 cache sizes
│   ├── sweep_multi.py      Sweep: multi vs single, 48 runs
│   └── sweep_alpha.py      Sweep: 11 alphas × 3 caches × 2 modos
├── plots/
│   ├── plot_metrics.py     Hit rate, Pareto, latência, tabela
│   ├── plot_alpha.py       PHR vs alpha, heatmap, bar comparison
│   └── output/             PNGs gerados
├── scripts/
│   ├── analyze_cache.py    Regimes + Pareto + LaTeX
│   ├── analyze_alpha.py    Melhor alpha por regime + veredicto
│   └── compare_modes.py    Multi vs single: ganho relativo
└── results/
    ├── sweep/              Resultados sweep_cache_size
    ├── sweep_multi/        Resultados sweep_multi
    └── sweep_alpha/        Resultados sweep_alpha


    Reprodução completa

    # 1. Build
mkdir build && cd build && cmake .. -G Ninja && ninja && cd ..

# 2. Workloads
python3 experiments/workload_gen.py --mode single --n-requests 268 \
    --shared-prefix-ratio 0.55 --repeat-ratio 0.15 \
    --out experiments/trace_single.csv

python3 experiments/workload_gen.py --mode multi --n-sessions 60 \
    --turns-per-session 5 --out experiments/trace_multi.csv

# 3. Sweeps
python3 experiments/sweep_cache_size.py
python3 experiments/sweep_multi.py
python3 experiments/sweep_alpha.py

# 4. Análise
python3 scripts/analyze_cache.py
python3 scripts/compare_modes.py
python3 scripts/analyze_alpha.py

# 5. Plots
python3 plots/plot_metrics.py
python3 plots/plot_alpha.py

Autor: João Felipe De Souza

About

Event-driven simulator for prefix KV-cache eviction policies in LLM serving systems

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.