Files
2026-07-13 12:24:33 +08:00

404 lines
17 KiB
Markdown

# LMCache CLI Design
**Status:** Proposal | **Date:** 2026-03-11
## Why
Today users must remember `python3 -m lmcache.v1.multiprocess.http_server ...` and
similar module paths. We need a single `lmcache` command as the front door to all
LMCache functionality.
## Command Overview
```
lmcache
├── server # Launch LMCache server (ZMQ + HTTP)
├── coordinator # Launch the mp coordinator (HTTP)
├── describe {kvcache,engine} # Rich status view of a running endpoint
├── ping {kvcache,engine} # Pure liveness check (OK/FAIL)
├── query {kvcache,engine} # Single-shot query with metrics
├── bench {engine,server,l2} # Sustained performance benchmarking
└── kvcache {clear,end-session} # KV cache management actions
```
| Verb | Question it answers | Weight |
|------|-------------------|--------|
| `ping` | Is it alive? | Single-shot, instant (OK/FAIL) |
| `query` | What happens when I send one request? | Single-shot, with metrics |
| `describe` | What is this thing? | Rich status dashboard |
| `bench` | How fast is it? | Multi-iteration, metrics-heavy |
| `kvcache` | Mutate cache state | Clear, end-session, evict (future) |
All client commands use a `--url` flag pointing to the **LMCache HTTP server**
(e.g. `--url http://localhost:8000`).
---
## Commands in Detail
### `lmcache server`
Replaces `python3 -m lmcache.v1.multiprocess.http_server`. Runs in foreground,
Ctrl-C to stop. HTTP frontend is enabled by default; use `--no-http` to run
ZMQ-only.
```bash
lmcache server \
--engine-type blend --host 0.0.0.0 --port 5555 \
--max-gpu-workers 2 \
--l1-size-gb 60 --eviction-policy LRU \
--no-http # opt out of HTTP frontend
```
Server args are composed from existing helpers: `add_mp_server_args()`,
`add_storage_manager_args()`, `add_prometheus_args()`, `add_telemetry_args()`,
`add_http_frontend_args()`.
### `lmcache coordinator`
Replaces `python3 -m lmcache.v1.mp_coordinator`. Runs the mp coordinator's
FastAPI/HTTP app in the foreground (Ctrl-C to stop). The coordinator tracks mp
server instances in a registry and evicts those whose heartbeats lapse.
```bash
lmcache coordinator \
--host 0.0.0.0 --port 9300 \
--instance-timeout 30 \
--health-check-interval 10
```
Config resolves from `MPCoordinatorConfig.from_env()` (the
`LMCACHE_MP_COORDINATOR_*` environment variables); any CLI flag that is supplied
overrides the corresponding field. Each flag defaults to unset so env-only
deployments keep working. See
[../v1/mp_coordinator/README.md](../v1/mp_coordinator/README.md).
### `lmcache describe`
```bash
$ lmcache describe kvcache --url localhost:5555
============ LMCache KV Cache Service ============
Health: OK
ZMQ endpoint: tcp://localhost:5555
HTTP endpoint: http://localhost:8000
Engine type: blend
Chunk size: 256
L1 capacity (GB): 60.0
L1 used (GB): 42.3 (70.5%)
Eviction policy: LRU
Cached objects: 1024
Uptime: 2h 14m 32s
==================================================
$ lmcache describe engine --url http://localhost:8000
================ Inference Engine ================
Model: meta-llama/Llama-3.1-70B-Instruct
Max context (tokens): 131072
Status: healthy
Running requests: 3
==================================================
```
`describe kvcache` gathers data from multiple ZMQ request types (`NOOP` for debug
info, `GET_CHUNK_SIZE` for chunk size) and `/status` (HTTP) to build a
consolidated view.
### `lmcache ping`
Pure liveness check for both targets. Returns OK/FAIL with round-trip time,
measuring only the network round-trip excluding local Python overhead.
**`ping kvcache`** -- pings the LMCache server process via HTTP `/healthcheck`:
```bash
$ lmcache ping kvcache --url http://localhost:8080
======= Ping KV Cache =======
Status: OK
Round trip time (ms): 0.42
==============================
```
**`ping engine`** -- pings the vLLM server process via HTTP `/health`:
```bash
$ lmcache ping engine --url http://localhost:8000
======== Ping Engine =========
Status: OK
Round trip time (ms): 12.3
==============================
```
### `lmcache query`
Single-shot query with detailed metrics. Use this to test a specific request
and see what happened.
**`query engine`** -- single inference request with TTFT/TPOT. Supports `{corpus}`
templates for realistic long-context prompts:
```bash
$ lmcache query engine --url http://localhost:8000 \
--prompt "{ffmpeg} What is the example usage of ffmpeg?" --max-tokens 128
========== Query Engine Result ==========
Prompt tokens: 8192
Corpus 'ffmpeg': 8186
Query: 6
Output tokens: 128
-----------Latency Metrics---------------
TTFT (ms): 892.3
TPOT (ms/token): 11.8
Total latency (ms): 2403.7
Throughput (tokens/s): 53.2
=========================================
```
**`query kvcache`** -- query KV cache state for specific keys or tokens:
```bash
# Check if a specific token sequence is cached (lookup)
$ lmcache query kvcache --url localhost:5555 \
--prompt "{ffmpeg} What is the example usage of ffmpeg?" \
--model meta-llama/Llama-3.1-8B-Instruct
======== Query KV Cache Result ==========
Prompt tokens: 8192
Cached chunks: 30/32 (93.8%)
Cached tokens: 7680/8192
Cache status: HIT (partial)
=========================================
# Store-retrieve round-trip with latency and correctness
$ lmcache query kvcache --url localhost:5555 --round-trip
==== Query KV Cache Result (round-trip) ====
Store latency (ms): 1.23
Retrieve latency (ms): 0.87
Checksum: OK
============================================
```
### `lmcache bench`
**`bench server`** -- end-to-end sanity test for a running LMCache MP cache
server (ZMQ + HTTP). For each sequence in ``[--start, --end)`` the tool runs a
cold pass (``LOOKUP`` miss → ``STORE``) and a warm pass (``LOOKUP`` hit →
``RETRIEVE``), then cross-checks per-chunk checksums against the server's HTTP
API. Exercises the full RPC path
(``REGISTER_KV_CACHE → GET_CHUNK_SIZE → LOOKUP → QUERY_PREFETCH_STATUS →
RETRIEVE → STORE → END_SESSION``).
Supports two run modes via ``--mode``:
- **``gpu``** (default) -- allocates real CUDA tensors and uses CUDA IPC
(LMCache-driven handle transfer path).
- **``cpu``** -- allocates POSIX-SHM-backed tensors; the server maps the same
physical pages for zero-copy STORE/RETRIEVE (engine-driven transfer path by
default). To use the zero-copy SHM handle path, add
``--transfer-mode lmcache_driven``.
The transfer path can be overridden explicitly with ``--transfer-mode
{auto,engine_driven,lmcache_driven}``. ``auto`` keeps the historical mapping:
gpu→lmcache_driven, cpu→engine_driven.
```bash
$ lmcache bench server \
--rpc-url tcp://localhost:5555 \
--url http://localhost:8080 \
--start 0 --end 2
Connecting to LMCache MP Server at tcp://localhost:5555 (mode=gpu, transfer=auto) ...
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] ...
```
With ``--end`` unset, the loop runs forever; stop with ``Ctrl-C``. The KV
tensor layout is controlled by ``--kvcache-shape-spec`` (see
``lmcache/v1/kv_layer_groups.py``); see :doc:`bench_server` in the user guide
for the full flag list.
**`bench l2`** -- store / lookup / load throughput benchmark against an
``L2AdapterInterface`` implementation (no MP server required). Implemented at
``lmcache/cli/commands/bench/l2_adapter_bench/``; see the
``docs/source/cli/bench_l2.rst`` user guide for full options.
**`bench engine`** -- **superset of `vllm bench serve`**. Same CLI args, same output
format, plus an extra LMCache KV cache metrics section:
```bash
# vllm bench serve compatible -- just swap the command name
$ lmcache bench engine \
--url http://localhost:8000 \
--model meta-llama/Llama-3.1-8B-Instruct \
--dataset-name random --random-input-len 7500 --random-output-len 200 \
--num-prompts 30 --request-rate 1 --ignore-eos
============ Serving Benchmark Result ============
Successful requests: 30
Benchmark duration (s): 31.34
Total input tokens: 224970
Total generated tokens: 6000
Request throughput (req/s): 0.96
Output token throughput (tok/s): 191.44
Total Token throughput (tok/s): 7369.36
---------------Time to First Token----------------
Mean TTFT (ms): 313.41
Median TTFT (ms): 272.83
P99 TTFT (ms): 837.32
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms): 8.84
Median TPOT (ms): 8.72
P99 TPOT (ms): 11.35
----------LMCache KV Cache Performance------------
KV cache hit rate (L1): 92.3%
KV cache hit rate (L2): 67.8%
L1 read bandwidth: 12.4 GB/s
L1 write bandwidth: 8.7 GB/s
Avg tokens saved by cache (per req): 6420
Cache-assisted TTFT savings (est.): 58.2%
==================================================
```
LMCache-specific additions on top of vLLM args: `--url` (replaces `--port`),
`--prompt` with `{corpus}` templates, `--corpus name=path` for custom corpora.
### `lmcache kvcache`
```bash
$ lmcache kvcache clear --url localhost:5555
========== KV Cache Clear ==========
Status: OK
Objects removed: 1024
====================================
$ lmcache kvcache end-session --url localhost:5555 <request_id>
======== KV Cache End Session ========
Status: OK
Request ID: <request_id>
======================================
```
---
## Prompt Corpora
`query engine`, `bench engine`, and `query kvcache` support `{name}` in `--prompt`
to expand built-in text corpora (e.g., `{paul_graham}` ~12k tokens, `{ffmpeg}`
~8k tokens). Custom corpora: `--corpus my_doc=./file.txt`. Built-in corpora ship
in `lmcache/cli/corpora/`.
## Implementation Notes
### Architecture
- **Auto-discovery (N-level):** Commands at all levels are discovered
automatically via `discover_subclasses()` (in
`lmcache/v1/utils/subclass_discovery.py`). No manual registration is needed
— adding a new command at any depth is a single-file change.
- **Leaf commands:** Inherit from `BaseCommand` directly.
- **Command groups:** Inherit from `CompositeCommand(BaseCommand)`. Its
`register()` scans the package where the concrete subclass is defined for
nested `BaseCommand` subclasses and registers each one automatically.
- **Recursive nesting:** A discovered subcommand can itself be a
`CompositeCommand`, enabling arbitrary depth (e.g.
`tool → cache-simulator → simulate`).
- **Class hierarchy:**
- `BaseCommand` — abstract base class for all CLI commands (leaf or composite).
- `CompositeCommand(BaseCommand)` — base class for commands that contain
auto-discovered sub-subcommands (e.g. `query`, `bench`, `quota`, `trace`,
`tool`). Subclasses only need to implement `name()` and `help()`.
- **Adding a new command:**
- *Top-level:* Create a new `.py` file (or sub-package with `__init__.py`)
under `commands/` with a concrete `BaseCommand` subclass. Done.
- *Second-level:* Create a new `.py` file (or sub-package with `__init__.py`)
under the parent command's package with a concrete `BaseCommand` subclass.
Done. No edits to the parent's `__init__.py` required.
- **`send_request()` helper:** Creates a temporary `MessageQueueClient`, submits
a ZMQ request, waits with timeout (default 5s), tears down. All ZMQ commands
use this. Extended to handle HTTP targets alongside ZMQ.
- **Framework:** `argparse` with subparsers (no new deps). Reuses existing
`add_*_args()` helpers.
- **`--url` flag:** Configured per-subcommand (ZMQ vs HTTP semantics vary).
### File layout
```
lmcache/cli/
├── __init__.py
├── main.py # main() entry point
├── metrics/ # Metrics system (see framework-and-metrics.md)
├── commands/
│ ├── __init__.py # Auto-discovers ALL_COMMANDS (no manual edits)
│ ├── base.py # BaseCommand ABC + CompositeCommand
│ ├── mock.py # lmcache mock (example/test command)
│ ├── server.py # lmcache server
│ ├── coordinator.py # lmcache coordinator
│ ├── describe.py # lmcache describe {kvcache}
│ ├── ping.py # lmcache ping {kvcache,engine}
│ ├── kvcache.py # lmcache kvcache {clear,end-session}
│ ├── query/ # lmcache query (CompositeCommand)
│ │ ├── __init__.py # QueryCommand(CompositeCommand)
│ │ ├── engine_command.py # Auto-discovered: lmcache query engine
│ │ └── kvcache_command.py # Auto-discovered: lmcache query kvcache
│ ├── bench/ # lmcache bench (CompositeCommand)
│ │ ├── __init__.py # BenchCommand(CompositeCommand)
│ │ ├── engine_bench/ # Auto-discovered: lmcache bench engine
│ │ ├── server_bench/ # Auto-discovered: lmcache bench server
│ │ └── l2_adapter_bench/ # Auto-discovered: lmcache bench l2
│ ├── quota/ # lmcache quota (CompositeCommand)
│ │ ├── __init__.py # QuotaCommand(CompositeCommand)
│ │ ├── set_command.py # Auto-discovered: lmcache quota set
│ │ ├── get_command.py # Auto-discovered: lmcache quota get
│ │ ├── list_command.py # Auto-discovered: lmcache quota list
│ │ └── delete_command.py # Auto-discovered: lmcache quota delete
│ ├── trace/ # lmcache trace (CompositeCommand)
│ │ ├── __init__.py # TraceCommand(CompositeCommand)
│ │ ├── info_command.py # Auto-discovered: lmcache trace info
│ │ └── replay_command.py # Auto-discovered: lmcache trace replay
│ └── tool/ # lmcache tool (CompositeCommand)
│ ├── __init__.py # ToolCommand(CompositeCommand)
│ └── cache_simulator/ # Auto-discovered: lmcache tool cache-simulator
│ ├── __init__.py # CacheSimulatorCommand(CompositeCommand)
│ ├── simulate_command.py # Auto-discovered: simulate
│ ├── sweep_command.py # Auto-discovered: sweep
│ └── gen_dataset_command.py # Auto-discovered: gen-dataset
├── config.py # CLIConfig (centralized config system)
└── corpora/ # Built-in prompt corpora
```
### Other notes
- **Entry point:** `lmcache = "lmcache.cli.main:main"` in `pyproject.toml`.
- **Auto-discovery mechanism:** Powered by `discover_subclasses()` in
`lmcache/v1/utils/subclass_discovery.py`. Uses `pkgutil.iter_modules` to
scan direct submodules, then `inspect.getmembers` to find concrete
`BaseCommand` subclasses. Each subclass is yielded at most once.
- **`CompositeCommand` pattern:** A `CompositeCommand` scans its own package
for `BaseCommand` subclasses (excluding itself and abstract classes).
Sub-packages with `__init__.py` defining a `BaseCommand` are also discovered,
enabling nested command groups (e.g. `tool cache-simulator simulate`).
- **`bench engine`:** Wraps `vllm.benchmarks`, then queries `/status` for
cache metrics.
- **`query kvcache`:** Tokenizes `--prompt` using the model's tokenizer, then
performs a lookup over ZMQ to check which chunks are cached.
## Phasing
| Phase | Scope |
|-------|-------|
| **0** | CLI framework (explicit registration, `Metrics`), `mock` example command, entry point — see [framework-and-metrics.md](framework-and-metrics.md) |
| **1** | **`server`** (done), `ping kvcache`, `kvcache clear`, `kvcache end-session`, `describe kvcache` |
| **2** | `ping engine`, `query engine`, `query kvcache`, `bench engine`, `bench server`, `bench l2`, `describe engine`, corpora |
| **3** | `kvcache evict` (future) |
Existing `lmcache_server` entry point kept as a deprecated alias for 2 minor releases.