1383 lines
44 KiB
ReStructuredText
1383 lines
44 KiB
ReStructuredText
.. _lmcache-bench:
|
|
|
|
lmcache bench
|
|
=============
|
|
|
|
The ``lmcache bench`` command runs sustained performance benchmarks. It has
|
|
three sub-commands, each targeting a different layer of the stack:
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 20 80
|
|
|
|
* - Sub-command
|
|
- Description
|
|
* - ``engine``
|
|
- Benchmark an inference engine (e.g. vLLM) with workloads that
|
|
exercise different KV-cache reuse patterns.
|
|
* - ``server``
|
|
- End-to-end sanity test against a running LMCache MP cache server
|
|
(ZMQ + HTTP). Requires the full ``lmcache`` install and a GPU.
|
|
* - ``l2``
|
|
- Throughput / latency benchmark against an L2 cache adapter
|
|
(store / lookup / load).
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench {engine,server,l2} [options]
|
|
|
|
|
|
.. _lmcache-bench-engine:
|
|
|
|
engine
|
|
------
|
|
|
|
The ``lmcache bench engine`` command runs sustained performance benchmarks
|
|
against an inference engine (e.g., vLLM). It supports multiple workload types
|
|
that exercise different caching patterns and reports TTFT, decoding speed, and
|
|
throughput metrics.
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine [options]
|
|
|
|
There are three ways to configure the benchmark:
|
|
|
|
1. **CLI arguments** -- pass all options on the command line.
|
|
2. **Interactive mode** -- run ``lmcache bench engine`` without required args
|
|
and follow the step-by-step prompts.
|
|
3. **Config file** -- save a configuration to JSON and replay it with
|
|
``--config``.
|
|
|
|
|
|
Quick Start
|
|
~~~~~~~~~~~
|
|
|
|
**Minimal (with all required arguments):**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine \
|
|
--engine-url http://localhost:8000 \
|
|
--workload long-doc-qa \
|
|
--lmcache-url http://localhost:8080
|
|
|
|
**Interactive mode (guided setup):**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine
|
|
|
|
The interactive mode walks you through each required setting, then asks
|
|
whether you want to configure general and workload-specific options or use
|
|
defaults.
|
|
|
|
**From a saved config file:**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine --engine-url http://localhost:8000 \
|
|
--config my_bench.json
|
|
|
|
Config files contain benchmark parameters (workload, KV cache settings, etc.)
|
|
but not the engine URL, so you can reuse the same config against different
|
|
engines.
|
|
|
|
**Export a config without running the benchmark:**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine \
|
|
--engine-url http://localhost:8000 \
|
|
--workload long-doc-qa \
|
|
--lmcache-url http://localhost:8080 \
|
|
--export-config my_bench.json
|
|
|
|
This resolves all auto-detected values (model name, tokens per GB) and saves
|
|
them to a portable JSON file that works without an LMCache server.
|
|
|
|
**Non-interactive mode (for scripts and CI):**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine \
|
|
--engine-url http://localhost:8000 \
|
|
--workload long-doc-qa \
|
|
--lmcache-url http://localhost:8080 \
|
|
--no-interactive
|
|
|
|
Errors immediately if any required argument is missing, instead of entering
|
|
interactive mode. Useful in automated pipelines.
|
|
|
|
If you don't have an LMCache server, you can pass ``--tokens-per-gb-kvcache``
|
|
directly instead of ``--lmcache-url``
|
|
(see :ref:`bench-tokens-per-gb` for how to find this value).
|
|
|
|
|
|
General Options
|
|
~~~~~~~~~~~~~~~
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 30 10 60
|
|
|
|
* - Flag
|
|
- Required
|
|
- Description
|
|
* - ``--config FILE``
|
|
- No
|
|
- Load configuration from a JSON file. Skips interactive mode.
|
|
CLI flags override values in the file. The engine URL is not
|
|
stored in config files and must be provided separately.
|
|
* - ``--export-config FILE``
|
|
- No
|
|
- Export resolved configuration to a JSON file and exit. Does not
|
|
run the benchmark. Auto-detected values (model, tokens per GB)
|
|
are resolved and saved so the config is portable. Environment-
|
|
specific values (engine URL, LMCache URL) are excluded.
|
|
* - ``--no-interactive``
|
|
- No
|
|
- Disable interactive mode. Errors if required arguments are
|
|
missing instead of prompting. Useful for scripts and CI.
|
|
* - ``--engine-url URL``
|
|
- Yes
|
|
- Inference engine URL (e.g., ``http://localhost:8000``).
|
|
Set ``OPENAI_API_KEY`` env var if authentication is needed.
|
|
* - ``--workload TYPE``
|
|
- Yes
|
|
- Workload type: ``long-doc-qa``, ``multi-round-chat``,
|
|
``long-doc-permutator``, ``prefix-suffix-tuner``, or
|
|
``random-prefill``.
|
|
* - ``--tokens-per-gb-kvcache N``
|
|
- \*
|
|
- Tokens per GB of KV cache. Required unless ``--lmcache-url`` is set.
|
|
See :ref:`bench-tokens-per-gb` for how to find this value.
|
|
* - ``--lmcache-url URL``
|
|
- No
|
|
- LMCache HTTP server URL. When provided, ``--tokens-per-gb-kvcache``
|
|
is auto-detected from the server.
|
|
* - ``--model NAME``
|
|
- No
|
|
- Model name. Auto-detected from the engine if omitted.
|
|
* - ``--kv-cache-volume GB``
|
|
- No
|
|
- Target active KV cache volume in GB (default: 100).
|
|
* - ``--seed N``
|
|
- No
|
|
- Random seed (default: 42).
|
|
* - ``--output-dir DIR``
|
|
- No
|
|
- Directory for CSV and JSON output files (default: current directory).
|
|
* - ``--no-csv``
|
|
- No
|
|
- Skip CSV export.
|
|
* - ``--json``
|
|
- No
|
|
- Export a JSON summary file.
|
|
* - ``-q`` / ``--quiet``
|
|
- No
|
|
- Suppress the real-time progress display.
|
|
|
|
|
|
.. _bench-tokens-per-gb:
|
|
|
|
Finding ``--tokens-per-gb-kvcache``
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
If you have an LMCache server running, the easiest approach is to pass
|
|
``--lmcache-url`` and let the tool auto-detect the value.
|
|
|
|
If you are using **vLLM without LMCache**, look for these lines in vLLM's
|
|
startup log:
|
|
|
|
.. code-block:: text
|
|
|
|
INFO: Available KV cache memory: 12.34 GiB
|
|
INFO: GPU KV cache size: 567,890 tokens
|
|
|
|
Then compute::
|
|
|
|
tokens_per_gb = 567890 / 12.34 = 46,020
|
|
|
|
|
|
Workloads
|
|
~~~~~~~~~
|
|
|
|
long-doc-qa
|
|
^^^^^^^^^^^
|
|
|
|
Simulates repeated Q&A over long documents. Warmup sends each document once
|
|
to populate the KV cache, then benchmark queries are dispatched with
|
|
semaphore-controlled concurrency.
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 35 10 55
|
|
|
|
* - Flag
|
|
- Default
|
|
- Description
|
|
* - ``--ldqa-document-length``
|
|
- 10000
|
|
- Token length of each synthetic document.
|
|
* - ``--ldqa-query-per-document``
|
|
- 2
|
|
- Number of questions asked per document.
|
|
* - ``--ldqa-shuffle-policy``
|
|
- random
|
|
- Request ordering: ``random`` (shuffled) or ``tile`` (round-by-round).
|
|
* - ``--ldqa-num-inflight-requests``
|
|
- 3
|
|
- Maximum concurrent in-flight requests.
|
|
|
|
**Example:**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine \
|
|
--engine-url http://localhost:8000 \
|
|
--workload long-doc-qa \
|
|
--lmcache-url http://localhost:8080 \
|
|
--kv-cache-volume 50 \
|
|
--ldqa-document-length 8000 \
|
|
--ldqa-query-per-document 4 \
|
|
--ldqa-shuffle-policy tile
|
|
|
|
|
|
multi-round-chat
|
|
^^^^^^^^^^^^^^^^
|
|
|
|
Simulates multi-round chat with stateful sessions. Creates concurrent user
|
|
sessions, dispatches requests at a fixed QPS rate, and records responses in
|
|
session history so each subsequent query includes prior context.
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 35 10 55
|
|
|
|
* - Flag
|
|
- Default
|
|
- Description
|
|
* - ``--mrc-shared-prompt-length``
|
|
- 2000
|
|
- System prompt token length per session.
|
|
* - ``--mrc-chat-history-length``
|
|
- 10000
|
|
- Pre-filled chat history token length.
|
|
* - ``--mrc-user-input-length``
|
|
- 50
|
|
- Tokens per user query.
|
|
* - ``--mrc-output-length``
|
|
- 200
|
|
- Max tokens to generate per response.
|
|
* - ``--mrc-qps``
|
|
- 1.0
|
|
- Target queries per second.
|
|
* - ``--mrc-duration``
|
|
- 60.0
|
|
- Benchmark duration in seconds.
|
|
|
|
**Example:**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine \
|
|
--engine-url http://localhost:8000 \
|
|
--workload multi-round-chat \
|
|
--lmcache-url http://localhost:8080 \
|
|
--mrc-qps 2.0 \
|
|
--mrc-duration 120
|
|
|
|
|
|
long-doc-permutator
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
Stress-tests blended KV cache reuse by sending permutations of a set of context
|
|
documents. Each request concatenates all context documents in a different order:
|
|
|
|
.. code-block:: text
|
|
|
|
[System Prompt] + [Doc_i1] + [Doc_i2] + ... + [Doc_iN]
|
|
|
|
A single dummy warmup request is sent before the benchmark phase. Requests are
|
|
dispatched with semaphore-controlled concurrency.
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 35 10 55
|
|
|
|
* - Flag
|
|
- Default
|
|
- Description
|
|
* - ``--ldp-num-contexts``
|
|
- 5
|
|
- Number of unique context documents.
|
|
* - ``--ldp-context-length``
|
|
- 5000
|
|
- Token length of each context document.
|
|
* - ``--ldp-system-prompt-length``
|
|
- 1000
|
|
- Token length of the shared system prompt. Use ``0`` for no system prompt.
|
|
* - ``--ldp-num-permutations``
|
|
- 10
|
|
- Number of distinct permutations to send. Capped at N! where
|
|
N = ``--ldp-num-contexts``.
|
|
* - ``--ldp-num-inflight-requests``
|
|
- 1
|
|
- Maximum concurrent in-flight requests.
|
|
|
|
**Example:**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine \
|
|
--engine-url http://localhost:8000 \
|
|
--workload long-doc-permutator \
|
|
--lmcache-url http://localhost:8080 \
|
|
--ldp-num-contexts 4 \
|
|
--ldp-context-length 8000 \
|
|
--ldp-num-permutations 24 \
|
|
--ldp-num-inflight-requests 2
|
|
|
|
|
|
prefix-suffix-tuner
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
A two-pass sequential workload designed to be run **unchanged** across
|
|
three LMCache configurations to demonstrate the value of each cache tier
|
|
(L0 HBM, L1 DRAM, L2 disk):
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 15 25 30 30
|
|
|
|
* - Baseline
|
|
- LMCache config
|
|
- Targeted overflow
|
|
- Expected pass-2 hits
|
|
* - 1
|
|
- vanilla vLLM (L0 only)
|
|
- L0 (HBM)
|
|
- none -- every request a cold prefill
|
|
* - 2
|
|
- vLLM + LMCache L1 + L2
|
|
- L1 (DRAM)
|
|
- L2 prefix hits (suffix recomputed)
|
|
* - 3
|
|
- vLLM + LMCache L1 + L2 + CacheBlend
|
|
- L1 (DRAM)
|
|
- L2 prefix hits + CacheBlend suffix hits
|
|
|
|
Set ``--kv-cache-volume`` to the size in GB of the tier you want to overflow
|
|
(L0 size for Baseline 1, L1 size for Baselines 2 and 3). The workload itself
|
|
is identical across baselines.
|
|
|
|
Each request has the layout::
|
|
|
|
[prefix_i with unique-ID][random breaker][shared suffix]
|
|
|
|
- ``num_prefixes`` distinct prefixes, each starting with ``PREFIX_<8-hex>``
|
|
so the prefix's tokenized hash differs across the pool.
|
|
- A fresh random 32-token breaker per request, defeating ordinary prefix
|
|
caching past the prefix boundary.
|
|
- A single shared suffix used by every request -- the only entry CacheBlend
|
|
can reuse.
|
|
|
|
Pass 1 (warmup) sends each prefix once to populate the cache; its stats are
|
|
discarded. Pass 2 sends them again in identical order. Because LRU evicts
|
|
the next-needed prefix on each pass-2 access, even a 1.05x overflow of the
|
|
targeted tier is enough to make every pass-2 request miss that tier and
|
|
fall through to the next one.
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 35 10 55
|
|
|
|
* - Flag
|
|
- Default
|
|
- Description
|
|
* - ``--psf-context-length``
|
|
- 8000
|
|
- Total tokens per request (prefix + breaker + suffix).
|
|
* - ``--psf-prefix-ratio``
|
|
- 0.8
|
|
- Fraction of context-length used by the prefix. Must be in (0.0, 1.0).
|
|
The remainder (minus a 32-token breaker) is the shared suffix.
|
|
* - ``--psf-thrash``
|
|
- 20.0
|
|
- **Size in GB of the KV-cache tier to overflow.** Use the L0 (HBM)
|
|
size for vanilla vLLM, or the L1 (LMCache DRAM) size for tiered
|
|
baselines. The workload sizes its prefix pool to slightly more than
|
|
this (5% overflow internally), enough to drive every pass-2 request
|
|
to a miss of that tier under sequential dispatch + LRU.
|
|
|
|
The number of pass-2 (measured) requests equals the prefix pool size,
|
|
computed as
|
|
``floor(psf_thrash * 1.05 * tokens_per_gb / prefix_tokens)``.
|
|
``--kv-cache-volume`` is unused by this workload — sizing is driven solely
|
|
by ``--psf-thrash``.
|
|
|
|
**Example:**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine \
|
|
--engine-url http://localhost:8000 \
|
|
--workload prefix-suffix-tuner \
|
|
--lmcache-url http://localhost:8080 \
|
|
--psf-context-length 8000 \
|
|
--psf-prefix-ratio 0.8 \
|
|
--psf-thrash 100
|
|
|
|
.. note::
|
|
For the analytical-model claim "thrash ≈ L1 size → ~0% LMCache hit rate"
|
|
to hold empirically, the LMCache server must be started with
|
|
``--eviction-ratio 0.99`` (default ``0.20`` only clears 20% per cycle,
|
|
leaving ~60% of pass-1 content in cache through pass 2):
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache server --l1-size-gb <SIZE> --eviction-policy LRU \
|
|
--eviction-trigger-watermark 0.80 \
|
|
--eviction-ratio 0.99
|
|
|
|
The workload itself sleeps 5 seconds between pass 1 (warmup) and pass 2
|
|
(measured), so LMCache's 1Hz batched-eviction polling thread has time
|
|
to actually run. Without that sleep, fast benchmarks complete before
|
|
any eviction fires.
|
|
|
|
|
|
random-prefill
|
|
^^^^^^^^^^^^^^
|
|
|
|
Fires all requests simultaneously with ``max_tokens=1`` to measure pure
|
|
prefill performance. No warmup phase.
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 35 10 55
|
|
|
|
* - Flag
|
|
- Default
|
|
- Description
|
|
* - ``--rp-request-length``
|
|
- 10000
|
|
- Token length per prefill request.
|
|
* - ``--rp-num-requests``
|
|
- 50
|
|
- Number of requests to fire.
|
|
|
|
**Example:**
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine \
|
|
--engine-url http://localhost:8000 \
|
|
--workload random-prefill \
|
|
--lmcache-url http://localhost:8080 \
|
|
--rp-request-length 15000 \
|
|
--rp-num-requests 100
|
|
|
|
|
|
Interactive Mode
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
.. image:: /_static/bench_interactive_demo.gif
|
|
:alt: Interactive mode demo
|
|
:width: 100%
|
|
|
|
When ``--engine-url`` or ``--workload`` is not provided (and
|
|
``--no-interactive`` is not set), the tool enters interactive mode. It guides
|
|
you through four phases:
|
|
|
|
1. **Required settings** -- engine URL, workload type, LMCache server
|
|
(or tokens per GB).
|
|
2. **General settings** (optional gate) -- model name, KV cache volume.
|
|
3. **Workload settings** (optional gate) -- workload-specific parameters.
|
|
4. **Summary and action** -- review configuration, then start the benchmark
|
|
or export to a JSON file.
|
|
|
|
Each prompt focuses on a single setting. Selection prompts use arrow keys;
|
|
text and number prompts accept typed input with defaults shown in brackets.
|
|
|
|
.. code-block:: text
|
|
|
|
══════════════════════════════════════════════════
|
|
lmcache bench engine -- Interactive Setup
|
|
══════════════════════════════════════════════════
|
|
|
|
Engine URL
|
|
URL of the inference engine.
|
|
[default: http://localhost:8000] >
|
|
|
|
Workload
|
|
The type of benchmark workload to run.
|
|
Use up/down to navigate, Enter to select.
|
|
|
|
* long-doc-qa Repeated Q&A over long documents
|
|
multi-round-chat Multi-turn chat with stateful sessions
|
|
long-doc-permutator Permutations of context documents
|
|
prefix-suffix-tuner Two-pass tiered KV-cache demonstrator
|
|
random-prefill Prefill-only requests fired simultaneously
|
|
|
|
LMCache Server
|
|
Do you have a running LMCache server?
|
|
It can auto-detect KV cache size information.
|
|
[default: Y] (Y/n) >
|
|
|
|
...
|
|
|
|
──────────────────────────────────────────────────
|
|
Configuration Summary
|
|
──────────────────────────────────────────────────
|
|
Workload: long-doc-qa
|
|
Model: Qwen/Qwen3-14B
|
|
Tokens per GB: 6553
|
|
...
|
|
──────────────────────────────────────────────────
|
|
|
|
What would you like to do?
|
|
* Start benchmark
|
|
Export configuration for later use and exit
|
|
|
|
When you choose "Export configuration", all auto-detected values (model name,
|
|
tokens per GB) are resolved and saved to a portable JSON file.
|
|
|
|
|
|
Config File
|
|
~~~~~~~~~~~
|
|
|
|
Config files store benchmark parameters but **not** environment-specific
|
|
values like engine URL or LMCache URL. This lets you reuse the same config
|
|
across different environments.
|
|
|
|
You can create a config file in three ways:
|
|
|
|
1. **Interactive mode** -- choose "Export configuration" at the summary step.
|
|
2. **``--export-config``** -- resolve and export from CLI without running.
|
|
3. **Manually** -- write JSON with keys matching CLI arg names (dashes
|
|
replaced by underscores).
|
|
|
|
Example config file:
|
|
|
|
.. code-block:: json
|
|
|
|
{
|
|
"model": "Qwen/Qwen3-14B",
|
|
"workload": "long-doc-qa",
|
|
"tokens_per_gb_kvcache": 6553,
|
|
"kv_cache_volume": 100.0,
|
|
"ldqa_document_length": 10000,
|
|
"ldqa_query_per_document": 2,
|
|
"ldqa_shuffle_policy": "random",
|
|
"ldqa_num_inflight_requests": 3
|
|
}
|
|
|
|
Load it with ``--config`` (engine URL must be provided separately):
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench engine --engine-url http://localhost:8000 \
|
|
--config my_bench.json
|
|
|
|
CLI arguments override config file values, so you can use a base config and
|
|
tweak individual settings:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Use saved config but override KV cache volume
|
|
lmcache bench engine --engine-url http://localhost:8000 \
|
|
--config my_bench.json --kv-cache-volume 200
|
|
|
|
|
|
Output
|
|
~~~~~~
|
|
|
|
Terminal (real-time progress)
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
During the benchmark, a live progress display shows in-flight requests,
|
|
average TTFT, decode speed, and throughput. Suppress it with ``-q``.
|
|
|
|
Terminal (final summary)
|
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
After completion, a summary table is printed:
|
|
|
|
.. code-block:: text
|
|
|
|
======= Engine Benchmark Result (long-doc-qa) ========
|
|
---------------------- Configuration ------------------
|
|
Engine URL: http://localhost:8000
|
|
Model: Qwen/Qwen3-14B
|
|
Workload: long-doc-qa
|
|
------------------------- Results ---------------------
|
|
Successful requests: 20
|
|
Failed requests: 0
|
|
Benchmark duration (s): 31.34
|
|
Total input tokens: 200000
|
|
Total output tokens: 2560
|
|
Input throughput (tok/s): 6381.62
|
|
Output throughput (tok/s): 81.69
|
|
--------------- Time to First Token -------------------
|
|
Mean TTFT (ms): 313.41
|
|
P50 TTFT (ms): 272.83
|
|
P90 TTFT (ms): 587.21
|
|
P99 TTFT (ms): 837.32
|
|
------------------ Decoding Speed ---------------------
|
|
Mean decode (tok/s): 48.23
|
|
P99 decode (tok/s): 38.55
|
|
======================================================
|
|
|
|
CSV and JSON
|
|
^^^^^^^^^^^^
|
|
|
|
- ``bench_results.csv`` -- per-request metrics (TTFT, latency, decode speed,
|
|
token counts). Written by default; skip with ``--no-csv``.
|
|
- ``bench_summary.json`` -- aggregate statistics with percentiles and config
|
|
metadata. Opt-in with ``--json``.
|
|
|
|
Both files are written to ``--output-dir`` (default: current directory).
|
|
|
|
|
|
Exit Codes
|
|
~~~~~~~~~~
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 15 85
|
|
|
|
* - Code
|
|
- Meaning
|
|
* - ``0``
|
|
- All requests succeeded.
|
|
* - ``1``
|
|
- One or more requests failed.
|
|
|
|
|
|
.. _lmcache-bench-server:
|
|
|
|
server
|
|
------
|
|
|
|
The ``lmcache bench server`` command is an end-to-end sanity test for the
|
|
LMCache Multi-Process (MP) cache server. It connects to a running server
|
|
over ZMQ and exercises the full KV-cache data path for a sequence of
|
|
synthetic requests, then optionally verifies per-chunk checksums through
|
|
the HTTP API.
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench server [options]
|
|
|
|
Unlike :ref:`lmcache bench engine <lmcache-bench-engine>`, this command does
|
|
**not** require an inference engine. It only needs a running LMCache MP
|
|
server (ZMQ + HTTP). GPU mode additionally requires a CUDA-capable device.
|
|
It also requires the full ``lmcache`` install (not the lightweight
|
|
``lmcache-cli`` package).
|
|
|
|
|
|
What it does
|
|
~~~~~~~~~~~~
|
|
|
|
For each sequence in ``[--start, --end)``, the tool runs two passes:
|
|
|
|
1. **Cold pass** -- ``LOOKUP`` is expected to miss, so the generated KV
|
|
tensors are ``STORE``\ d on the server.
|
|
2. **Warm pass** -- ``LOOKUP`` is expected to hit; the tool issues
|
|
``RETRIEVE`` and compares the retrieved KV chunks' checksums to the
|
|
originals.
|
|
|
|
The full RPC path exercised is::
|
|
|
|
REGISTER_KV_CACHE → GET_CHUNK_SIZE → LOOKUP
|
|
→ QUERY_PREFETCH_STATUS → RETRIEVE → STORE
|
|
→ END_SESSION
|
|
|
|
When ``--url`` points to the server's HTTP endpoint, per-chunk checksums
|
|
are additionally cross-checked against the server-side computation, so a
|
|
mismatch between producer and consumer surfaces as a loud
|
|
``CHECKSUM MISMATCH`` log line.
|
|
|
|
|
|
Quick start
|
|
~~~~~~~~~~~
|
|
|
|
Start the MP server in one terminal:
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache server \
|
|
--host localhost --port 15556 \
|
|
--chunk-size 256 --l1-size-gb 5 \
|
|
--eviction-policy LRU --max-workers 1
|
|
|
|
Then in another terminal:
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench server \
|
|
--rpc-url tcp://localhost:15556 \
|
|
--url http://localhost:8080
|
|
|
|
By default the tool runs forever (``--end`` unset); stop it with
|
|
``Ctrl-C`` at any time. Pass ``--end N`` for a bounded run.
|
|
|
|
|
|
Options
|
|
~~~~~~~
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 30 15 55
|
|
|
|
* - Flag
|
|
- Default
|
|
- Description
|
|
* - ``--rpc-url URL``
|
|
- ``tcp://localhost:5555``
|
|
- ZMQ endpoint of the MP cache server.
|
|
* - ``--url URL``
|
|
- ``http://localhost:8080``
|
|
- HTTP base URL of the server's checksum API. Used to
|
|
verify per-chunk checksums end-to-end.
|
|
* - ``--mode {gpu,cpu}``
|
|
- ``gpu``
|
|
- Run mode. ``gpu`` allocates real CUDA tensors and uses CUDA IPC
|
|
(lmcache-driven handle path). ``cpu`` allocates POSIX-SHM-backed tensors
|
|
and uses the engine-driven (worker-side gather/scatter) path by default.
|
|
* - ``--transfer-mode {auto,engine_driven,lmcache_driven}``
|
|
- ``auto``
|
|
- Transport routing for STORE/RETRIEVE. ``lmcache_driven`` forces the
|
|
single-shot handle path (``REGISTER_KV_CACHE`` + ``STORE``/``RETRIEVE``),
|
|
which supports both CUDA IPC and CPU SHM zero-copy transfers.
|
|
``engine_driven`` forces the worker-side gather/scatter path
|
|
(``REGISTER_KV_CACHE_ENGINE_DRIVEN_CONTEXT`` + ``PREPARE``/``COMMIT``).
|
|
``auto`` maps gpu→lmcache_driven and cpu→engine_driven.
|
|
* - ``--num-tokens N``
|
|
- ``512``
|
|
- Tokens per synthetic request.
|
|
* - ``--num-blocks N``
|
|
- ``1024``
|
|
- Number of paged blocks allocated on the GPU.
|
|
* - ``--block-size N``
|
|
- ``16``
|
|
- Tokens per paged block.
|
|
* - ``--start N``
|
|
- ``0``
|
|
- First sequence number to run.
|
|
* - ``--end N``
|
|
- *(unset)*
|
|
- Exclusive upper bound on sequence numbers. When omitted the
|
|
loop runs forever.
|
|
* - ``--interval SECS``
|
|
- ``0.5``
|
|
- Delay between successive sub-passes.
|
|
* - ``--kvcache-shape-spec SPEC``
|
|
- ``(2,1024,16,8,128):float16:32``
|
|
- KV cache shape spec (see below).
|
|
* - ``--format FORMAT``
|
|
- ``terminal``
|
|
- Stdout output format for the final metrics summary. Available:
|
|
``terminal``, ``json``.
|
|
* - ``--output PATH``
|
|
- *(unset)*
|
|
- Save the final metrics summary to a file at PATH (format chosen
|
|
by ``--format``).
|
|
* - ``-q`` / ``--quiet``
|
|
- *(unset)*
|
|
- Suppress all progress messages during the run. Only the final
|
|
structured metrics summary is emitted (unless also redirected
|
|
via ``--output``).
|
|
|
|
|
|
CPU mode (no GPU)
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
``--mode cpu`` runs the same end-to-end path without a GPU. The server
|
|
runs on a CPU-only host (``StubCPUDevice``); the bench tool allocates
|
|
POSIX-SHM-backed KV tensors and exercises the full RPC path.
|
|
|
|
By default ``--mode cpu`` uses the engine-driven gather/scatter path
|
|
(``auto`` → ``cpu→engine_driven``). To use the zero-copy SHM
|
|
handle path instead, pass ``--transfer-mode lmcache_driven``:
|
|
|
|
.. code-block:: bash
|
|
|
|
# Terminal 1 -- start the LMCache server (no GPU required)
|
|
lmcache server \
|
|
--host localhost --port 5555 \
|
|
--l1-size-gb 2 --eviction-policy LRU
|
|
|
|
# Terminal 2 -- run bench in CPU + lmcache_driven mode
|
|
lmcache bench server \
|
|
--rpc-url tcp://localhost:5555 \
|
|
--url http://localhost:8080 \
|
|
--mode cpu --transfer-mode lmcache_driven \
|
|
--start 0 --end 2
|
|
|
|
|
|
KV cache shape spec
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
The ``--kvcache-shape-spec`` flag describes how KV tensors are laid out on
|
|
the GPU. A spec is one or more groups separated by ``;``:
|
|
|
|
.. code-block:: text
|
|
|
|
(kv_size,NB,BS,NH,HS):dtype:layers[;(...):dtype:layers...]
|
|
|
|
Fields:
|
|
|
|
* ``kv_size`` -- 2 for classical attention (separate K/V), 1 for MLA.
|
|
* ``NB`` -- number of paged blocks.
|
|
* ``BS`` -- block size (tokens per block).
|
|
* ``NH`` -- number of attention heads per layer.
|
|
* ``HS`` -- head size (in elements).
|
|
* ``dtype`` -- element dtype (e.g. ``float16``, ``bfloat16``, ``float32``,
|
|
``uint8``). The full set matches the keys of ``DTYPE_MAP`` in
|
|
``lmcache/v1/kv_layer_groups.py``.
|
|
* ``layers`` -- number of layers in this group.
|
|
|
|
Multi-group specs let you model heterogeneous layers (for example, MLA
|
|
layers + classical attention layers in the same model):
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench server \
|
|
--rpc-url tcp://localhost:15556 \
|
|
--kvcache-shape-spec "(1,1024,16,1,128):float16:4;(2,1024,16,8,128):float16:28"
|
|
|
|
All groups must share the same ``NB`` and ``BS`` (this is a physical
|
|
constraint of paged KV). Layer counts across groups sum to the total
|
|
layer count registered with the server.
|
|
|
|
See ``parse_kvcache_shape_spec`` in ``lmcache/v1/kv_layer_groups.py``
|
|
for the authoritative parsing rules and validation errors.
|
|
|
|
|
|
Output
|
|
~~~~~~
|
|
|
|
After the run completes (or is interrupted with ``Ctrl-C``), a structured
|
|
metrics summary is printed. The summary includes:
|
|
|
|
* **Configuration** -- RPC URL, mode, transfer mode, tokens per request,
|
|
interval.
|
|
* **Results** -- total requests, checksum OK / FAIL counts, pass rate.
|
|
* **Latency sections** -- per-operation latency statistics (count, mean,
|
|
min, max, p50, p99) for cold lookup, cold store, warm lookup, and warm
|
|
retrieve.
|
|
|
|
Use ``--format json`` to get machine-readable output, or ``--output FILE``
|
|
to save the summary to a file.
|
|
|
|
.. code-block:: text
|
|
|
|
================ Server Bench Result =================
|
|
---------------------- Configuration -----------------
|
|
RPC URL: tcp://localhost:15556
|
|
Mode: gpu
|
|
Transfer mode: auto
|
|
Tokens / request: 512
|
|
Interval (s): 0.5
|
|
------------------------- Results --------------------
|
|
Total requests: 3
|
|
Checksum OK: 3
|
|
Checksum FAIL: 0
|
|
Pass rate (%): 100.0
|
|
-------------------- Cold Lookup (ms) ---------------
|
|
count: 3
|
|
mean: 1.647
|
|
min: 1.312
|
|
max: 1.823
|
|
p50: 1.647
|
|
p99: 1.823
|
|
--------------------- Cold Store (ms) ---------------
|
|
count: 3
|
|
mean: 1.740
|
|
min: 1.521
|
|
max: 1.982
|
|
p50: 1.740
|
|
p99: 1.982
|
|
-------------------- Warm Lookup (ms) ---------------
|
|
count: 3
|
|
mean: 1.310
|
|
min: 1.102
|
|
max: 1.512
|
|
p50: 1.310
|
|
p99: 1.512
|
|
------------------- Warm Retrieve (ms) --------------
|
|
count: 3
|
|
mean: 1.480
|
|
min: 1.321
|
|
max: 1.612
|
|
p50: 1.480
|
|
p99: 1.612
|
|
=====================================================
|
|
|
|
|
|
Example output (progress)
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
During the run, progress messages are printed to stdout (suppressed by
|
|
``-q`` / ``--quiet``):
|
|
|
|
.. code-block:: text
|
|
|
|
Connecting to LMCache MP Server at tcp://localhost:15556 (mode=gpu) ...
|
|
Server chunk_size = 256
|
|
Resolved KV shape spec: (2,1024,16,8,128):float16:32
|
|
[seq=0] LOOKUP cold: 0/2 chunks hit (1.82 ms)
|
|
[seq=0] STORE: 2 chunks stored (1.74 ms)
|
|
[seq=0] LOOKUP warm: 2/2 chunks hit (1.31 ms)
|
|
[seq=0] RETRIEVE: 2 chunks retrieved (1.48 ms)
|
|
[seq=0] CHECKSUM MATCH OK
|
|
[seq=1] ...
|
|
|
|
Any ``CHECKSUM MISMATCH``, ``ERROR``, or Python traceback in the log
|
|
indicates a real problem worth investigating.
|
|
|
|
|
|
Exit codes
|
|
~~~~~~~~~~
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 15 85
|
|
|
|
* - Code
|
|
- Meaning
|
|
* - ``0``
|
|
- Test loop completed (or was interrupted cleanly with Ctrl-C)
|
|
with no checksum mismatches.
|
|
* - ``1``
|
|
- Fatal error (for example, CUDA unavailable in ``--mode gpu``,
|
|
server unreachable, or a checksum mismatch).
|
|
|
|
.. _lmcache-bench-l2:
|
|
|
|
l2
|
|
--
|
|
|
|
The ``lmcache bench l2`` command benchmarks an L2 cache adapter
|
|
(e.g. the local-filesystem adapter) end-to-end through the same
|
|
``parse_args_to_l2_adapters_config`` + ``create_l2_adapter`` pipeline that
|
|
LMCache uses in production. Any registered adapter type can be tested
|
|
without code changes: you describe the adapter with a single JSON spec
|
|
and pick the operations to exercise.
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench l2 [options]
|
|
|
|
Unlike :ref:`lmcache bench engine <lmcache-bench-engine>`, this command
|
|
does **not** require an inference engine or an LMCache MP server. It
|
|
only needs the adapter's own backing storage to be reachable (for the
|
|
``fs`` adapter, that simply means a writable directory).
|
|
|
|
|
|
What it does
|
|
~~~~~~~~~~~~
|
|
|
|
For each measured operation the tool drives the adapter directly via
|
|
its public submit/wait API:
|
|
|
|
* ``Store`` -- ``submit_store_task`` writes ``num_keys`` MemoryObjs per
|
|
submit and waits for the store eventfd.
|
|
* ``Lookup`` -- ``submit_lookup_and_lock_task`` checks key existence
|
|
(no payload transfer) and waits for the lookup eventfd.
|
|
* ``Load`` -- ``submit_load_task`` reads ``num_keys`` MemoryObjs per
|
|
submit and waits for the load eventfd.
|
|
|
|
Each measured **round** issues ``--in-flight`` submits sequentially from
|
|
a single producer thread and then waits for all of them to complete; the
|
|
round duration is the wall-clock time from the first submit until the
|
|
last completion. Warmup rounds run before measurement and their results
|
|
are discarded from the final summary.
|
|
|
|
All three operations share the same key idx universe, so running
|
|
``--only store`` followed by ``--only load`` (or ``--only lookup``) with
|
|
identical other flags hits exactly the same keys. This makes the
|
|
benchmark useful as a quick regression test for adapters that should
|
|
support a clean store -> load round-trip.
|
|
|
|
.. note::
|
|
|
|
When ``--only`` is not given, the three operations are run **in a
|
|
single process in the order** ``store -> lookup -> load``. For
|
|
adapters whose backing storage sits behind an OS-level cache --
|
|
most notably the local-filesystem (``fs``) adapter, which is
|
|
subject to the Linux **page cache** -- this means ``lookup`` and
|
|
``load`` will almost always observe the data that ``store`` just
|
|
wrote still hot in RAM, and the reported numbers reflect
|
|
page-cache throughput rather than the underlying device.
|
|
|
|
To benchmark each operation against a cold cache, run them
|
|
separately with ``--only`` and drop the OS caches in between, for
|
|
example::
|
|
|
|
lmcache bench l2 --l2-adapter '...' --only store
|
|
sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
|
|
lmcache bench l2 --l2-adapter '...' --only lookup
|
|
sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
|
|
lmcache bench l2 --l2-adapter '...' --only load
|
|
|
|
For adapters that bypass the page cache (e.g. ``fs`` with
|
|
``"use_odirect": true``) or that talk to a remote service without
|
|
a local cache, the default combined run is usually fine.
|
|
|
|
O_DIRECT adapters may also require the benchmark L1 buffer to
|
|
satisfy the adapter's block alignment. Use ``--l1-align-bytes`` to
|
|
set that alignment, commonly ``4096`` for local block devices. The
|
|
payload size (``--data-size-kb * 1024``) must be a multiple of the
|
|
selected alignment.
|
|
|
|
|
|
Quick start
|
|
~~~~~~~~~~~
|
|
|
|
Benchmark the local filesystem adapter with default parameters:
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench l2 \
|
|
--l2-adapter '{"type":"fs","base_path":"/tmp/lmcache-bench"}'
|
|
|
|
This runs all three operations (store, lookup, load) with one warmup
|
|
round and one measurement round.
|
|
|
|
Stress the adapter with more in-flight submits and larger payloads:
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench l2 \
|
|
--l2-adapter '{"type":"fs","base_path":"/data/lmcache-bench","relative_tmp_dir":"tmp"}' \
|
|
--num-keys 32 --in-flight 4 \
|
|
--data-size-kb 512 \
|
|
--rounds 5 --warmup-rounds 1
|
|
|
|
Benchmark an O_DIRECT adapter with aligned L1 buffers:
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench l2 \
|
|
--l2-adapter '{"type":"raw_block","device_path":"/dev/nvme0n1","slot_bytes":4194304,"use_odirect":true,"block_align":4096}' \
|
|
--data-size-kb 1024 \
|
|
--l1-align-bytes 4096
|
|
|
|
Run only one operation (useful to isolate store vs. load throughput):
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench l2 \
|
|
--l2-adapter '{"type":"fs","base_path":"/tmp/lmcache-bench"}' \
|
|
--only store
|
|
|
|
Lookup with a controlled hit rate (the benchmark splits the lookup keys
|
|
between a potentially-existing range and a guaranteed-non-existent
|
|
range):
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench l2 \
|
|
--l2-adapter '{"type":"fs","base_path":"/tmp/lmcache-bench"}' \
|
|
--only lookup --lookup-max-hit-rate 0.5
|
|
|
|
Enable a store -> load round-trip data integrity check on the last
|
|
measured round:
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench l2 \
|
|
--l2-adapter '{"type":"fs","base_path":"/tmp/lmcache-bench"}' \
|
|
--no-skip-verify
|
|
|
|
If you prefer to keep the JSON spec out of the command line, set the
|
|
``L2_ADAPTER_JSON`` environment variable instead of passing
|
|
``--l2-adapter``:
|
|
|
|
.. code-block:: bash
|
|
|
|
export L2_ADAPTER_JSON='{"type":"fs","base_path":"/tmp/lmcache-bench"}'
|
|
lmcache bench l2 --num-keys 32 --in-flight 2
|
|
|
|
|
|
Options
|
|
~~~~~~~
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 30 15 55
|
|
|
|
* - Flag
|
|
- Default
|
|
- Description
|
|
* - ``--l2-adapter JSON``
|
|
- *(unset)*
|
|
- L2 adapter spec as JSON with a ``"type"`` field plus
|
|
adapter-specific configs, e.g.
|
|
``'{"type":"fs","base_path":"/tmp/bench"}'``. May be passed
|
|
multiple times; only the first spec is benchmarked. If not
|
|
provided, falls back to the ``L2_ADAPTER_JSON`` environment
|
|
variable. Either the flag or the env var is **required**.
|
|
* - ``--num-keys N``
|
|
- ``32``
|
|
- Number of keys per submit.
|
|
* - ``--in-flight N``
|
|
- ``1``
|
|
- In-flight submits per round. Each round issues this many
|
|
submits sequentially from a single producer thread, then waits
|
|
for all of them.
|
|
* - ``--data-size-kb N``
|
|
- ``256``
|
|
- Data size per key, in KiB.
|
|
* - ``--l1-align-bytes N``
|
|
- ``1``
|
|
- Alignment in bytes for benchmark L1 buffers. Use a value
|
|
at least as large as the adapter's block alignment when
|
|
benchmarking O_DIRECT backends, for example ``4096`` for local
|
|
block devices. ``--data-size-kb * 1024`` must be a multiple of
|
|
this value.
|
|
* - ``--rounds N``
|
|
- ``1``
|
|
- Measurement rounds per operation.
|
|
* - ``--warmup-rounds N``
|
|
- ``1``
|
|
- Warmup rounds run before measurement; their results are
|
|
discarded.
|
|
* - ``--lookup-max-hit-rate F``
|
|
- ``0.0``
|
|
- Upper bound on the lookup hit rate, in ``[0, 1]``. The benchmark
|
|
requests ``floor(N * rate)`` keys from the
|
|
potentially-existing range and ``N - hit`` keys from a
|
|
guaranteed-non-existent range, where ``N`` is the total number
|
|
of lookup keys. The actual hit rate may be lower if those keys
|
|
were never stored in this run.
|
|
* - ``--skip-verify`` / ``--no-skip-verify``
|
|
- ``--skip-verify``
|
|
- Skip the store -> load round-trip data integrity check (the
|
|
default). Pass ``--no-skip-verify`` to enable verification on
|
|
the last measured round; this requires both ``store`` and
|
|
``load`` to be exercised.
|
|
* - ``--only {lookup,store,load}``
|
|
- *(unset)*
|
|
- Run only the specified operation. When omitted, all three
|
|
operations are run in the order ``store -> lookup -> load``.
|
|
* - ``--flamegraph {on,off}``
|
|
- ``off``
|
|
- Capture a flame graph of the measured phases (``on``) or run the
|
|
benchmark normally (``off``). When ``on``, the benchmark profiles
|
|
itself and renders an SVG. Default ``off`` leaves benchmark
|
|
behavior unchanged. See
|
|
:ref:`Profiling / flame charts <lmcache-bench-l2-profiling>`.
|
|
* - ``--flamegraph-mode {on-cpu,off-cpu}``
|
|
- ``on-cpu``
|
|
- Flame-graph mode for ``--flamegraph on``. ``on-cpu`` shows
|
|
where CPU time goes; ``off-cpu`` shows time blocked on I/O /
|
|
locks (best for I/O-bound adapters).
|
|
* - ``--flamegraph-output PATH``
|
|
- *(auto)*
|
|
- SVG output path. Default:
|
|
``/tmp/lmcache_bench_flames/<adapter>.<mode>.svg``.
|
|
* - ``--flamegraph-dir DIR``
|
|
- *($FLAMEGRAPH_DIR or ~/FlameGraph)*
|
|
- Directory with the FlameGraph scripts (``flamegraph.pl``,
|
|
``stackcollapse-perf.pl``).
|
|
|
|
|
|
Adapter JSON spec
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
The ``--l2-adapter`` JSON is parsed by
|
|
``lmcache.v1.distributed.l2_adapters.config.parse_args_to_l2_adapters_config``,
|
|
the same entry point LMCache uses everywhere else. The minimum required
|
|
field is ``type``; all remaining fields are forwarded to the adapter
|
|
implementation as keyword arguments.
|
|
|
|
Example for the local-filesystem adapter:
|
|
|
|
.. code-block:: json
|
|
|
|
{
|
|
"type": "fs",
|
|
"base_path": "/data/lmcache-bench",
|
|
"relative_tmp_dir": "tmp",
|
|
"read_ahead_size": null,
|
|
"use_odirect": false
|
|
}
|
|
|
|
See the source under ``lmcache/v1/distributed/l2_adapters/`` for the
|
|
full list of adapter types and their accepted fields.
|
|
|
|
|
|
Example output
|
|
~~~~~~~~~~~~~~
|
|
|
|
Per-round progress (suppressed by ``-q``):
|
|
|
|
.. code-block:: text
|
|
|
|
============================================================
|
|
L2 Adapter Benchmark
|
|
============================================================
|
|
Adapter config : FSL2AdapterConfig
|
|
L2 adapter JSON : {"type":"fs","base_path":"/data/lmcache-bench","relative_tmp_dir":"tmp"}
|
|
Keys / submit : 32
|
|
In-flight / round : 3
|
|
Keys / round : 96
|
|
Data size / key : 256 KB
|
|
Data / round : 24.00 MB
|
|
Rounds : 1 (+ 1 warmup)
|
|
Lookup max hit rate : 0.00%
|
|
============================================================
|
|
|
|
[Init] Creating adapter...
|
|
[Init] Adapter created successfully (FSL2Adapter).
|
|
|
|
[Store] Running 1 warmup + 1 measurement rounds...
|
|
[Store] Round 1: 47.83 ms, success_keys=96/96
|
|
[Store] Round 2: 46.19 ms, success_keys=96/96
|
|
|
|
[Lookup] Running 1 warmup + 1 measurement rounds...
|
|
[Lookup] Round 1: 5.36 ms, found=96/96
|
|
[Lookup] Round 2: 5.03 ms, found=96/96
|
|
|
|
[Load] Running 1 warmup + 1 measurement rounds...
|
|
[Load] Round 1: 18.15 ms, loaded=96/96
|
|
[Load] Round 2: 17.63 ms, loaded=96/96
|
|
|
|
Final summary (one section per exercised operation):
|
|
|
|
.. code-block:: text
|
|
|
|
====== L2 Adapter Benchmark Result (FSL2Adapter) =======
|
|
----------------------- Configuration -------------------
|
|
Adapter: FSL2Adapter
|
|
Keys / submit: 32
|
|
In-flight / round: 3
|
|
Data size / key (KB): 256
|
|
Measurement rounds: 1
|
|
Warmup rounds: 1
|
|
Lookup max hit rate: 0.0
|
|
--------------------------- Store -----------------------
|
|
Operation: Store
|
|
Rounds: 1
|
|
Keys / round: 96
|
|
Total keys: 96
|
|
Total success: 96
|
|
Duration avg (ms): 46.19
|
|
...
|
|
Throughput avg (MB/s): 519.62
|
|
Avg ops/s: 2078.50
|
|
Avg latency / key (ms): 0.481
|
|
--------------------------- Lookup ----------------------
|
|
...
|
|
---------------------------- Load -----------------------
|
|
...
|
|
=========================================================
|
|
|
|
Each operation section reports per-round duration statistics
|
|
(avg / min / max / p50 / p99 / std), aggregate throughput
|
|
(``avg_throughput_mbps`` -- 0 for ``Lookup`` since it has no payload),
|
|
average key-rate (``avg_ops_per_sec``), and a per-key latency.
|
|
|
|
For ``Lookup``, three additional fields are reported when
|
|
``--lookup-max-hit-rate`` is non-zero or some keys were found:
|
|
|
|
* ``Expected max hit rate`` -- the configured upper bound.
|
|
* ``Expected hit keys`` -- ``floor(total_keys * rate)``, scaled for
|
|
the measured rounds only.
|
|
* ``Actual hit rate`` -- the measured hit rate over the kept rounds.
|
|
|
|
|
|
Round-trip verification
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When ``--no-skip-verify`` is passed and both ``store`` and ``load`` were
|
|
run, the benchmark compares the load buffers from the last measured
|
|
round against the byte pattern that ``store`` wrote (see
|
|
``make_memory_objects`` in
|
|
``lmcache/cli/commands/bench/l2_adapter_bench/data.py``):
|
|
|
|
.. code-block:: text
|
|
|
|
[Verify] Checking store -> load data integrity for last measured round...
|
|
[Verify] OK
|
|
|
|
Verification is **off** by default because the stricter byte pattern
|
|
requires both the store and load object batches to stay resident so the
|
|
loaded data can be compared against the original store pattern.
|
|
|
|
|
|
.. _lmcache-bench-l2-profiling:
|
|
|
|
Profiling / flame charts
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When ``--flamegraph on`` is passed, the benchmark profiles the L2
|
|
adapter's performance and renders a flame graph of the measured phases:
|
|
|
|
.. code-block:: bash
|
|
|
|
lmcache bench l2 \
|
|
--l2-adapter '{"type":"fs","base_path":"/data/lmcache-bench"}' \
|
|
--rounds 300 --flamegraph on --flamegraph-mode on-cpu
|
|
# [Profile] on-cpu recording started (pid=12345) -> .../FSL2Adapter.oncpu.svg
|
|
# [Profile] wrote /tmp/lmcache_bench_flames/FSL2Adapter.oncpu.svg
|
|
|
|
The recorder samples every thread of the process (including native
|
|
worker threads). Two modes are available via ``--flamegraph-mode``:
|
|
|
|
* **on-cpu** (default) -- ``perf record``; where CPU cycles go
|
|
(serialization, copies, hashing).
|
|
* **off-cpu** -- ``offcputime-bpfcc`` (bcc); time spent blocked
|
|
(waiting on I/O, locks, eventfds). Often the more informative view
|
|
for I/O-bound adapters such as ``fs`` and ``s3``.
|
|
|
|
Recording covers only the measured store/lookup/load work, so make the
|
|
run long enough to collect samples (a few seconds is plenty -- use a
|
|
large ``--rounds``). The SVG is written to ``--flamegraph-output``
|
|
(default ``/tmp/lmcache_bench_flames/<adapter>.<mode>.svg``).
|
|
|
|
**Requirements.** Rendering needs Brendan Gregg's
|
|
`FlameGraph <https://github.com/brendangregg/FlameGraph>`__ scripts
|
|
(``--flamegraph-dir`` or ``FLAMEGRAPH_DIR``, default ``~/FlameGraph``;
|
|
auto-cloned to a temp directory when absent). ``on-cpu`` needs ``perf``
|
|
(and ``kernel.perf_event_paranoid`` low enough to profile non-root, or
|
|
run as root); ``off-cpu`` needs ``bcc`` (``offcputime-bpfcc``) and
|
|
``sudo``. The required tools are checked up front: when ``--flamegraph
|
|
on`` is requested but a tool is missing, the benchmark exits with a
|
|
non-zero status and an actionable message naming the missing tool
|
|
instead of running unprofiled. The default ``--flamegraph off`` skips
|
|
all of this and leaves benchmark behavior unchanged.
|
|
|
|
.. note::
|
|
|
|
When comparing a change, profile each variant with the same
|
|
``--rounds`` and ``--flamegraph-mode`` and distinct
|
|
``--flamegraph-output`` paths (e.g. ``baseline.svg`` vs
|
|
``after.svg``) so the two flame graphs are directly comparable.
|
|
|
|
|
|
Exit codes
|
|
~~~~~~~~~~
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 15 85
|
|
|
|
* - Code
|
|
- Meaning
|
|
* - ``0``
|
|
- All requested operations completed and (when enabled) the
|
|
round-trip verification passed.
|
|
* - ``1``
|
|
- Adapter creation failed, round-trip verification failed, or
|
|
an operation hit a fatal error (e.g. all rounds timed out).
|
|
* - ``2``
|
|
- Invalid invocation: the ``--l2-adapter`` JSON / ``L2_ADAPTER_JSON``
|
|
env var was missing or could not be parsed, an option value was
|
|
invalid, or ``--flamegraph on`` was requested but the profiling
|
|
toolchain is unavailable.
|