Skip to main content

Profile LLM gateway logs for prefix-cache reuse potential, and export anonymized replayable traces

Project description

llmtrafficlens

PyPI Python License

Analyze an LLM gateway's request log for prefix-cache reuse, then export the log as an anonymized trace that standard benchmark tools can replay.

On the public Mooncake conversation trace, which the Try it section below reproduces verbatim:

$ llmtrafficlens profile conversation_trace.jsonl --format mooncake -o report
12,031 requests · unknown
input p50 6,909 tok · output p50 350 tok · stream 0% · 3.4 req/s

hit rate by cache size
      16K tokens    3.3%
      64K tokens    4.2%
     256K tokens    4.3%
       1M tokens    5.6%
       4M tokens   18.5%
      16M tokens   34.2%
       unbounded   37.4%  <- ceiling

session identifiers  0.0% of requests  (affinity routing cannot reach this reuse)
top prefix by volume 512 tok x 12,031 reqs = 5.3% of reusable volume
top prefix by count  512 tok x 12,031 reqs = 5.3% of reusable volume

wrote report.json, report.html

report.html carries the full leaderboards and distributions; report.json is the same content for scripts.

Two commands:

  • profile — how much a prefix cache could save on this traffic, and how much cache memory that takes.
  • export — the same log as a Mooncake- or bailian-format trace, with all text removed.

Background

An engine serves a request in two phases: prefill reads the whole prompt at once, then decode emits output tokens one by one. Prefill cost grows with prompt length, and it is pure recomputation whenever two requests begin with the same text — the same system prompt, the same few-shot examples, the same attached document.

A prefix cache avoids that: the intermediate state a prompt produces (its KV cache) stays in memory, and the next request starting with the same text reuses it instead of recomputing. Whether that pays off depends on three things, which are exactly what this tool reports:

  • The traffic. If requests share no leading text, nothing can be reused. This is a property of your workload, not of your setup.
  • Cache memory. KV state is bulky, so a cache holds a bounded number of tokens and evicts the rest. More memory, more hits — with diminishing returns you can measure instead of guess.
  • Routing. Across several workers, a request only hits if it reaches the worker that already holds its prefix. Sending it there is what prefix-aware routing does; the alternative, session affinity, only works when requests carry a session identifier.

Reuse is tracked in fixed-size blocks (16 tokens by default) because engines cache at block granularity, not per character.

Install

pip install llmtrafficlens

No runtime dependencies. Python ≥ 3.10.

profile

llmtrafficlens profile gateway.csv -o report

Input. A CSV with a request_json column holding the OpenAI-format request body. These columns are used when present: response_json, model_name, status_code, and a timestamp column (timestamp, ts, created_at, ...; epoch or ISO-8601, or set --ts-column). Mooncake and qwen-bailian traces are accepted as input too (--format mooncake|bailian).

Output. A summary on stdout (shown above), plus report.html and report.json containing:

  • hit rate at each cache size, ending with the unbounded case — the ceiling;
  • top prefixes by reuse count, and separately by reusable token volume;
  • share of requests carrying a session identifier;
  • input/output token distributions, streaming ratio, model mix, QPS.

How to read it.

Ceiling — the share of input tokens that could be served from cache if memory were unlimited and every request reached the right worker. It bounds everything else: at a few percent, no amount of engineering makes prefix caching worthwhile on this traffic.

Capacity curve — the hit rate at each cache size, so you can see what the ceiling costs. Where it flattens is the point past which buying memory stops helping.

Session-identifier share — whether the cheap option is enough. When most requests carry an identifier, pinning each session to a worker captures the reuse. When none do (a common case for API traffic), the reuse sits in prefixes shared between unrelated requests, and only prefix-aware routing reaches it.

The two leaderboards — which prefixes to keep resident. They rank differently, and the token-volume one is what determines savings: a short prefix reused very often contributes almost no reusable volume, while a long prefix reused a few dozen times can account for most of it.

export

llmtrafficlens export gateway.csv --to mooncake -o trace.jsonl   # or --to bailian

A benchmark written by hand, with equally popular prompt groups, reports a higher hit rate than production reaches, because every group stays warm. Replaying the real log avoids that, and the export can be shared, because each request is reduced to one line of structure:

{"timestamp": 1753340000123, "input_length": 1994, "output_length": 117,
 "hash_ids": [4251731047194047120, 8125214104179782736, ...]}

hash_ids is a salted chained block hash: two requests share their first N hashes exactly when they share their first N blocks. Replay tools generate one synthetic block per hash, so the prefix-sharing structure is preserved while the content is not real.

# replay at the recorded pace
aiperf profile --custom-dataset-type mooncake_trace --input-file trace.jsonl --fixed-schedule ...
# replay at 2x the pace, for rate sweeps
aiperf profile --custom-dataset-type mooncake_trace --input-file trace.jsonl --synthesis-speedup-ratio 2.0 ...
# or SGLang
python -m sglang.bench_serving --dataset-name mooncake --dataset-path trace.jsonl ...

--to bailian adds chat_id, parent_chat_id and turn for AIPerf's bailian_trace mode; use it when the log carries session identifiers. Tell the consumer which block size the export used — AIPerf defaults to 512 for mooncake and 16 for bailian (--prompt-input-tokens-block-size).

Try it on a public trace

No data of your own needed:

curl -LO https://raw.githubusercontent.com/kvcache-ai/Mooncake/main/FAST25-release/traces/conversation_trace.jsonl
llmtrafficlens profile conversation_trace.jsonl --format mooncake -o mooncake-report

That prints the summary at the top of this README. Reading the curve: this workload gains almost nothing below 1M tokens, and most of its ceiling needs a cache past 16M — which for GLM-5.2 at bf16 (93 KiB/token) is 1.5 TiB, more than device memory alone holds, and the case tiered KV stores (host memory, SSD) are built for.

Cache size is reported in tokens because bytes are model-specific: 93 KiB/token for GLM-5.2, 68.6 for Kimi K2.5, 128 for a 32-layer fp16 GQA model. Some architectures also carry a DSA indexer cost on top of attention. Look yours up with kvcache-simulator list-models.

The repository also contains a synthetic sample log (examples/, not shipped in the pip package): 360 requests, three shared system prompts with skewed popularity, sparse session identifiers, real timestamps. It reports a 66.4% ceiling, and it shows the two leaderboards disagreeing — 16 tokens × 180 requests is 2.3% of the reusable volume, while 2400 tokens × 30 requests is 55.6% of it.

Methodology and validation

Metric definitions and their sources, the per-model KV byte accounting, and what has been checked are in METHODOLOGY.md. In short: our block matching reproduces NVIDIA AIPerf's analyze-trace to 5e-15 on the same trace, and the official kvcache-simulator's ceiling once its warm-up window is applied. Not yet verified: an export actually replaying through AIPerf or SGLang.

Privacy

Processing is local. Reports contain aggregates only. An export contains lengths, timestamps, anonymized session identifiers and salted block hashes; prompts cannot be reconstructed from it. The hash key is written to .ltl-salt on first run — keep it unchanged so runs stay comparable, and treat it as a secret.

Scope

We do not replay traffic, control request rate, or search deployment configurations. AIPerf, SGLang bench_serving and simulators such as Vidur do that, and all of them take a trace as input. Observability platforms record token counts and cost per request, not prefix structure; trace analyzers require an already-hashed trace. This tool covers the step between: raw gateway log to hashed trace.

License

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

llmtrafficlens-0.6.0.tar.gz (20.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

llmtrafficlens-0.6.0-py3-none-any.whl (22.5 kB view details)

Uploaded Python 3

File details

Details for the file llmtrafficlens-0.6.0.tar.gz.

File metadata

  • Download URL: llmtrafficlens-0.6.0.tar.gz
  • Upload date:
  • Size: 20.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for llmtrafficlens-0.6.0.tar.gz
Algorithm Hash digest
SHA256 037d0ca1899542b54540efd91a73f14d38cf435823b7b07c7494d906a6ae25b4
MD5 6468a38c9b5acafa530ad9e35fbf3f26
BLAKE2b-256 d39a3b7d8d18c4e4c2bfd5736cbdf2079d846dd12cfe7e4d2b9bbc97f03f29e2

See more details on using hashes here.

File details

Details for the file llmtrafficlens-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: llmtrafficlens-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 22.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for llmtrafficlens-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 625102ef23c80c2d0fbbdeaf09f9a956938aaf94e9def3508d742bf5558c348e
MD5 ee9cd29651f35c5e88b0bc3b4610ea92
BLAKE2b-256 d7d35c32e5197ae18f6d8d12110f7dbe9057ee8b80416b4c3dd5d27dc2d1bea7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page