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

643 lines
27 KiB
ReStructuredText

Overview
========
LMCache multiprocess (MP) mode runs LMCache as a **standalone service** that
vLLM instances connect to over ZMQ. One LMCache server per node can serve
multiple vLLM pods, providing process isolation, shared caching, and
independent resource scaling.
.. contents::
:local:
:depth: 2
Key Benefits
------------
- **Process isolation** -- LMCache and vLLM run in separate processes (or
containers), so a cache-related issue does not crash the inference engine.
- **No GIL contention or Python overhead on the inference path** -- By running
LMCache in a separate process, its Python GIL and CPU work (hashing,
memory management, L2 I/O) do not compete with vLLM's inference threads.
- **Shared caching across pods** -- Multiple vLLM instances on the same node
share a single L1 cache, maximizing KV reuse.
- **Independent resource scaling** -- Allocate CPU memory for caching
independently of GPU memory for inference.
- **Multi-tier storage (L1 + L2)** -- An L1 cache (in CPU DRAM, or an NVMe
slab via GPUDirect Storage) backed by persistent L2 storage via NIXL (GDS,
POSIX, HF3FS, and more).
- **Built-in observability** -- Prometheus metrics and a telemetry event system
out of the box.
Prerequisites
-------------
- **vLLM** latest version is recommended for best compatibility
- **LMCache** latest dev branch
Server Variants
---------------
LMCache ships two server entry points:
.. list-table::
:header-rows: 1
:widths: 30 70
* - Entry Point
- Description
* - ``lmcache server``
- **Recommended.** ZMQ + FastAPI HTTP frontend — see :doc:`http_api`.
* - ``python3 -m lmcache.v1.multiprocess.server``
- (Legacy) ZMQ-only server with no HTTP endpoints; same
``--engine-type`` / ``--supported-transfer-mode`` flags as
``lmcache server``. Prefer ``lmcache server``.
The sections below describe LMCache MP internals -- useful if you want to
understand, debug, or extend the system.
High-Level Architecture
-----------------------
.. code-block:: text
vLLM Instance(s)
|
| ZMQ (tcp)
v
MessageQueueServer (mq.py)
|
| dispatch by RequestType
v
MPCacheServer (server.py)
|
|--- TokenHasher / SessionManager
|
v
StorageManager (distributed/storage_manager.py)
|
|--- L1Manager (l1_manager.py)
| |--- L1MemoryManager (CPU DRAM) or
| | GDSL1MemoryManager (NVMe slab via cuFile / hipFile)
| |--- TTLLock per object (read/write)
|
|--- StoreController -----> L2 Adapter(s) (async L1->L2 push)
|--- PrefetchController ---> L2 Adapter(s) (async L2->L1 load)
|--- EvictionController ----> L1Manager (watermark-triggered eviction)
|
v
EventBus + OTel providers (observability)
Engine and Modules
------------------
All server entry points share the same ``MPCacheServer`` and
``StorageManager`` core. ``MPCacheServer`` is now a thin compositor:
it holds an ``MPCacheServerContext`` and a list of ``EngineModule``
instances assembled by ``_build_modules()`` (in ``server.py``)
based on ``--engine-type`` and ``--supported-transfer-mode``.
**``server.py``** -- The default ZMQ-only server. Creates an
``MPCacheServer``, assembles the engine modules
(``LookupModule`` + ``ManagementModule`` + ``LMCacheDrivenTransferModule``
and/or ``EngineDrivenTransferModule`` depending on
``--supported-transfer-mode````lmcache_driven`` or ``engine_driven`` loads
just one,
``auto`` (default) loads both — plus a CacheBlend module when
``--engine-type`` is set: ``blend`` appends ``BlendV3Module`` (the
current paged-aware implementation), and ``blend_legacy`` appends
``BlendModule`` (the original)). Starts a ``MessageQueueServer``,
registers handlers for every ``RequestType`` exposed by the loaded
modules, and blocks in a keep-alive loop.
**``modules/blend.py``** -- Defines ``BlendModule`` and ``BlendEngineV2``,
which add the original CacheBlend operations (``CB_REGISTER_KV_CACHE``,
``CB_LOOKUP_PRE_COMPUTED``, ``CB_STORE_PRE_COMPUTED``,
``CB_RETRIEVE_PRE_COMPUTED``, ``CB_STORE_FINAL`` and their V2
variants). Enables non-prefix KV cache reuse across document
paragraphs. Selected by passing ``--engine-type blend_legacy`` to
``lmcache server``.
**``modules/blend_v3.py``** -- Defines ``BlendV3Module``, the
paged-aware CacheBlend V3 pipeline that runs on the sparse-prefetch
path. Adds the V3 RPCs (``CB_REGISTER_ROPE_V3``,
``CB_UNREGISTER_ROPE_V3``, ``CB_RETRIEVE_PRE_COMPUTED_V3``,
``CB_UNIFIED_LOOKUP``) and reuses the existing
``LMCacheDrivenTransferModule`` and ``LookupModule``. Selected by
passing ``--engine-type blend`` to
``lmcache server``.
Both blend variants require ``--supported-transfer-mode`` to be
``lmcache_driven`` or ``auto`` and will refuse to load when it is
``engine_driven``.
**``http_server.py``** -- Wraps ``run_cache_server()`` (from ``server.py``)
inside a FastAPI application. Endpoints are contributed by modules under
``http_apis/`` and auto-registered via ``HTTPAPIRegistry``: ``GET /`` (basic
liveness), ``GET /healthcheck`` for Kubernetes probes, ``POST /cache/clear``
for clearing all KV cache data in L1 (CPU) memory, and ``GET /status``
for inspecting detailed internal state. The ZMQ server runs as part of the
same process, and any configured runtime plugins are spawned by
``MPRuntimePluginLauncher`` during FastAPI startup.
ZMQ Protocol
------------
Communication between vLLM and LMCache uses ZMQ (DEALER/ROUTER pattern).
**RequestType enum** (defined in ``protocols/base.py``):
.. list-table::
:header-rows: 1
:widths: 35 20 45
* - Request Type
- Handler Type
- Description
* - ``REGISTER_KV_CACHE``
- SYNC
- Register GPU KV cache tensors for a vLLM instance.
* - ``UNREGISTER_KV_CACHE``
- SYNC
- Unregister KV cache tensors.
* - ``REGISTER_KV_CACHE_ENGINE_DRIVEN_CONTEXT``
- SYNC
- Register an engine-driven KV cache context (CPU/accelerator
workers using the PREPARE/COMMIT transfer path). Loaded only when
``--supported-transfer-mode`` is ``engine_driven`` or ``auto``.
Returns a ``RegisterEngineDrivenContextResponse`` carrying the
SHM segment name and pool size when the SHM path is in use
(empty for the pickle path).
* - ``UNREGISTER_KV_CACHE_ENGINE_DRIVEN_CONTEXT``
- SYNC
- Unregister an engine-driven KV cache context.
* - ``STORE``
- BLOCKING
- Store KV cache chunks from GPU to L1 (CPU). LMCache-driven
transfer path (CUDA IPC); loaded only when
``--supported-transfer-mode`` is ``lmcache_driven`` or ``auto``.
* - ``RETRIEVE``
- BLOCKING
- Copy KV cache chunks from L1 (CPU) back to GPU. LMCache-driven
transfer path (CUDA IPC); loaded only when
``--supported-transfer-mode`` is ``lmcache_driven`` or ``auto``.
* - ``PREPARE_STORE``
- BLOCKING
- (Engine-driven path) Worker asks the server to prepare store-side
transfer state for a key. Loaded when ``--supported-transfer-mode``
is ``engine_driven`` or ``auto``.
* - ``COMMIT_STORE``
- BLOCKING
- (Engine-driven path) Worker commits the chunk's serialized bytes
(pickle path) or releases the prepared SHM slot (SHM path) so the
server can persist into L1 storage.
* - ``PREPARE_RETRIEVE``
- BLOCKING
- (Engine-driven path) Worker asks the server to prepare the
retrieval payload for a key. The pickle path returns the bytes
inline; the SHM path returns slot info so the worker can read
from shared memory.
* - ``COMMIT_RETRIEVE``
- BLOCKING
- (Engine-driven path) Worker acknowledges retrieval completion so
the server can release the underlying read locks and reclaim any
transport state.
* - ``LOOKUP``
- BLOCKING
- Submit a prefix lookup; the prefetch job is tracked server-side by
request_id.
* - ``QUERY_PREFETCH_STATUS``
- BLOCKING
- Poll a prefetch job by request_id. Returns the loaded chunk count
when done, or ``None`` while the prefetch is still in progress.
* - ``WAIT_PREFETCH_STATUS``
- BLOCKING
- (SGLang only) Block until a prefetch job completes, then return its
loaded chunk count, or ``None`` on timeout. The blocking alternative
to polling ``QUERY_PREFETCH_STATUS``.
* - ``QUERY_PREFETCH_LOOKUP_HITS``
- BLOCKING
- Query the lookup-phase hit chunk count by request_id, before the
prefetch finishes. Returns ``None`` while the lookup is still
running.
* - ``FREE_LOOKUP_LOCKS``
- BLOCKING
- Release read locks from a cancelled lookup without doing a full
RETRIEVE.
* - ``END_SESSION``
- BLOCKING
- Remove session state for a finished request.
* - ``CLEAR``
- BLOCKING
- Clear all cached data.
* - ``GET_CHUNK_SIZE``
- SYNC
- Return the server's chunk size.
* - ``PING``
- BLOCKING
- Liveness ping; the handler always returns ``True``.
* - ``REPORT_BLOCK_ALLOCATION``
- BLOCKING
- Fire-and-forget channel for the vLLM scheduler to report GPU block
allocation events to the observability subsystem.
* - ``NOOP``
- SYNC
- Debug heartbeat -- returns a confirmation string.
* - ``CB_REGISTER_KV_CACHE``
- SYNC
- (Blend) Register CacheBlend KV buffer.
* - ``CB_UNREGISTER_KV_CACHE``
- SYNC
- (Blend) Unregister CacheBlend KV buffer.
* - ``CB_STORE_PRE_COMPUTED``
- BLOCKING
- (Blend) Store pre-computed paragraph chunks.
* - ``CB_LOOKUP_PRE_COMPUTED``
- BLOCKING
- (Blend) Lookup pre-computed paragraph chunks.
* - ``CB_RETRIEVE_PRE_COMPUTED``
- BLOCKING
- (Blend) Retrieve pre-computed paragraph chunks to GPU.
* - ``CB_STORE_FINAL``
- BLOCKING
- (Blend) Store final blended chunks.
* - ``CB_LOOKUP_PRE_COMPUTED_V2``
- BLOCKING
- (Blend V2) Lookup pre-computed chunks; returns
``CBMatchResult`` entries (with old/cur ranges and per-chunk hashes)
so the retrieve step can skip re-hashing.
* - ``CB_RETRIEVE_PRE_COMPUTED_V2``
- BLOCKING
- (Blend V2) Retrieve pre-computed chunks using the
``CBMatchResult`` list returned by ``CB_LOOKUP_PRE_COMPUTED_V2``.
* - ``CB_REGISTER_ROPE_V3``
- SYNC
- (Blend V3) Share the RoPE cos/sin cache onto a context already
registered via ``REGISTER_KV_CACHE``.
* - ``CB_UNREGISTER_ROPE_V3``
- SYNC
- (Blend V3) Drop the RoPE state (paged KV cache lives on; use
``UNREGISTER_KV_CACHE`` to release that).
* - ``CB_RETRIEVE_PRE_COMPUTED_V3``
- BLOCKING
- (Blend V3) Scatter all matched chunks (prefix- and non-prefix-hit)
into paged KV by per-token block ID; re-RoPE only the shifted subset.
* - ``CB_UNIFIED_LOOKUP``
- BLOCKING
- (Blend V3) Sole live lookup path: one RPC runs prefix + non-prefix
match, reconciles, issues one sparse-coalesced prefetch, and
classifies per-TP-rank. Returns ``CBUnifiedLookupResult`` (or
``None`` while the prefetch is still in flight).
* - ``P2P_LOOKUP_AND_LOCK``
- BLOCKING
- (P2P) Look up the given keys and read-lock the locally cached
prefix. Returns a task id which the caller passes to
``P2P_QUERY_LOOKUP_RESULTS`` to poll for the transfer addresses.
Served by ``P2PController`` (loaded unconditionally by
``_build_modules()``); whether this server also acts as a P2P
client is controlled by ``--p2p-advertise-url`` -- see
:doc:`p2p`.
* - ``P2P_QUERY_LOOKUP_RESULTS``
- BLOCKING
- (P2P) Poll the transfer addresses for a lookup task. Returns a
list of ``TransferChannelAddress`` once the lookup is complete,
or ``None`` while the lookup is still in progress or its
results have already been consumed.
* - ``P2P_UNLOCK_OBJECTS``
- BLOCKING
- (P2P) Release the read locks previously taken by
``P2P_LOOKUP_AND_LOCK`` on the given keys.
**Handler types:**
- **SYNC** -- Runs directly in the ZMQ main loop (fast, non-blocking).
- **BLOCKING** -- Dispatched to a thread pool (may involve GPU copies or I/O).
Config System
-------------
Each config module exposes a composable triple:
.. code-block:: text
(DataclassConfig, add_*_args(parser), parse_args_to_*_config(args))
``server.py:parse_args()`` composes them:
.. code-block:: python
parser = argparse.ArgumentParser(...)
add_mp_server_args(parser) # from multiprocess/config.py
# includes runtime-plugin args
# (--runtime-plugin-locations,
# --runtime-plugin-config)
add_storage_manager_args(parser) # from distributed/config.py
# which internally calls add_l2_adapters_args(parser)
add_observability_args(parser) # from mp_observability/config.py
``http_server.py`` reuses this pattern, adding
``add_http_frontend_args()`` and ``add_coordinator_args()`` for the
``lmcache server`` CLI. CacheBlend is no longer a separate entry point —
it is opted into at runtime by passing ``--engine-type`` to
``server.py`` (or ``lmcache server``). ``--engine-type blend`` appends
``BlendV3Module`` (the current paged-aware implementation), while
``--engine-type blend_legacy`` appends ``BlendModule`` (the original).
Distributed Storage
-------------------
StorageManager
~~~~~~~~~~~~~~
``lmcache/v1/distributed/storage_manager.py``
The top-level manager that wires together L1, L2, and all controllers. Key
methods:
- ``reserve_write()`` / ``finish_write()`` -- Two-phase write into L1.
- ``submit_prefetch_task()`` / ``query_prefetch_status()`` -- Async lookup +
L2 prefetch. ``query_prefetch_status()`` is non-blocking and returns
``None`` while the L2 prefetch is still in flight.
- ``wait_prefetch_status()`` -- Blocking alternative to polling
``query_prefetch_status()``: waits on a controller condition variable
until the prefetch result is published (or a timeout expires).
L1-only prefetches return immediately. Used by the
``WAIT_PREFETCH_STATUS`` RPC to avoid busy-polling on the load path.
- ``read_prefetched_results()`` / ``finish_read_prefetched()`` -- Read
prefetched data from L1 with automatic lock management.
L1Manager
~~~~~~~~~
``lmcache/v1/distributed/l1_manager.py``
Manages objects in CPU memory with a state machine:
.. code-block:: text
None --> write_locked --> ready --> read_locked
(reserve_write) (finish_write) (reserve_read)
| |
v v
evictable finish_read -> ready
Each object has two ``TTLLock`` instances (read and write) with configurable
timeouts to prevent deadlocks from crashed clients.
The underlying memory allocation is handled by one of two interchangeable
tiers selected at startup (both satisfy ``L1ManagerProtocol``):
- ``L1MemoryManager`` (default) -- pinned CPU DRAM, with lazy growth up to
``--l1-size-gb``.
- ``GDSL1MemoryManager`` -- an NVMe slab file when ``--gds-l1-path`` is set.
The bytes live on disk; reads/writes DMA directly between the GPU staging
buffer and the slab, driven by the process-global ``GDSContext``
(``gpu_connector/gds_context.py``) and dispatched from ``gpu_ops``. The DMA
backend is selected by platform via ``gpu_connector/_gds_async.py`` --
cuFile (``libcufile.so``) on NVIDIA and hipFile (``libhipfile.so``) on AMD
ROCm; see the *GDS L1 Tier* section of :doc:`configuration` for the
vendor-specific requirements. The CPU tier is disabled in this mode.
L2 Adapters
~~~~~~~~~~~
``lmcache/v1/distributed/l2_adapters/``
The ``L2AdapterInterface`` (in ``base.py``) defines three async task methods:
- ``submit_store_task(key, data)`` -- Push data to L2.
- ``submit_lookup_and_lock_task(keys)`` -- Check if keys exist in L2.
- ``submit_load_task(keys, layout_desc)`` -- Load data from L2 into L1.
The factory function ``create_l2_adapter()`` (in ``__init__.py``) uses
``isinstance()`` on the config type to instantiate the correct adapter.
New adapter types are registered via ``register_l2_adapter_type()`` in
``config.py``.
Controllers
~~~~~~~~~~~
**StoreController** (``storage_controllers/store_controller.py``):
Event-driven background thread that uses ``select.poll()`` on listener eventfd
and adapter store eventfds. When new objects appear in L1 (signaled via
``StoreListener``), it submits async store tasks to each L2 adapter based on
the ``StorePolicy``.
**EvictionController** (``storage_controllers/eviction_controller.py``):
Periodically checks L1 memory usage against the watermark threshold. When
triggered, evicts objects using the configured policy (``LRU``,
``IsolatedLRU``, or ``noop``) until usage drops below the target.
``IsolatedLRU`` evicts per ``cache_salt`` against limits registered through
the ``/quota`` HTTP endpoints; see :ref:`mp-http-quota-api`.
**PrefetchController** (``storage_controllers/prefetch_controller.py``):
Handles L2 lookup and load requests submitted by ``StorageManager`` during
``LOOKUP`` RPCs. When keys are not in L1, it queries L2 adapters and loads
found data back into L1.
Request Flows
-------------
LOOKUP Flow
~~~~~~~~~~~
.. code-block:: text
vLLM MPCacheServer StorageManager L1Manager L2 (PrefetchController)
| | | | |
|---LOOKUP(key)-------->| | | |
| |--submit_prefetch------>| | |
| | |--reserve_read----->| |
| | |<--hit_count--------| |
| | |--submit_prefetch_request--------------->|
| | | (remaining keys) |
| |--query_prefetch------->| | |
| | |--query_prefetch_result----------------->|
| |<--found_count----------| | |
|<--found_count---------| | | |
STORE Flow
~~~~~~~~~~
.. code-block:: text
vLLM MPCacheServer StorageManager L1Manager
| | | |
|---STORE(key,blocks)-->| | |
| |--reserve_write-------->| |
| | |--reserve_write---->|
| | |<--memory_objs------|
| | (GPU->CPU copy) | |
| |--finish_write--------->| |
| | |--finish_write----->|
| | | |
| | | [StoreController detects new objects]
| | | [async L1->L2 push via adapters]
|<--event_handle--------| | |
RETRIEVE Flow
~~~~~~~~~~~~~
.. code-block:: text
vLLM MPCacheServer StorageManager L1Manager
| | | |
|---RETRIEVE(key)------>| | |
| |--read_prefetched------>| |
| | |--unsafe_read------>|
| | |<--memory_objs------|
| | (CPU->GPU copy) | |
| |--finish_read_prefetch->| |
| | |--finish_read------>|
|<--event_handle--------| | |
Observability Internals
-----------------------
**EventBus** (``lmcache/v1/mp_observability/event_bus.py``) is a global
singleton initialized at server startup by ``init_observability()``.
Producers (L1Manager, StorageManager, MPCacheServer) publish ``Event``
objects to a bounded queue (``--event-bus-queue-size``, default 10000,
tail-drop on overflow). A background drain thread dispatches each
event to all registered subscribers.
**Subscribers** live under ``lmcache/v1/mp_observability/subscribers/``
and are grouped by concern: ``metrics/`` (OTel counters and lifecycle
histograms), ``logging/`` (Python logging handlers, lookup-hash JSONL),
and ``tracing/`` (OTel spans built from START/END event pairs).
``init_observability()`` registers the set selected by CLI flags
(``--disable-metrics``, ``--disable-logging``, ``--enable-tracing``).
**OTel providers** are set up via ``otel_init.py`` before subscribers
are constructed, so module-level ``get_meter()`` / ``get_tracer()``
calls bind to the real provider. Metrics are exported both to an
in-process Prometheus ``/metrics`` endpoint (``--prometheus-port``,
default 9090) and, when ``--otlp-endpoint`` is set, pushed to an OTel
collector.
How to Extend
-------------
Adding a new L2 adapter
~~~~~~~~~~~~~~~~~~~~~~~~
Create a new ``*_l2_adapter.py`` module under
``lmcache/v1/distributed/l2_adapters/````__init__.py`` auto-discovers
modules matching that suffix via ``pkgutil`` and imports them lazily on
first use, so no other files need to be modified.
1. Create a config class subclassing ``L2AdapterConfigBase`` with
``from_dict()`` and ``help()`` methods.
2. Create an adapter class implementing ``L2AdapterInterface``, and
a small factory function
``(config, l1_memory_desc) -> L2AdapterInterface``.
3. At module level, self-register both the config and the factory:
.. code-block:: python
register_l2_adapter_type("my_adapter", MyAdapterConfig)
register_l2_adapter_factory("my_adapter", _create_my_adapter)
See ``mock_l2_adapter.py`` or ``s3_l2_adapter.py`` for reference
implementations.
Adding an observability subscriber
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Create a subscriber class subclassing ``EventSubscriber`` (defined
in ``lmcache/v1/mp_observability/event_bus.py``): implement
``get_subscriptions()`` to return an ``{EventType: callback}``
mapping; optionally override ``shutdown()`` for cleanup.
2. Place the class under the appropriate concern group
(``subscribers/metrics/``, ``subscribers/logging/``, or
``subscribers/tracing/``) and export it from that package's
``__init__.py``.
3. Register the subscriber in ``init_observability()``
(``lmcache/v1/mp_observability/config.py``) via
``bus.register_subscriber(...)`` inside the branch matching its
concern (metrics / logging / tracing), gated on the corresponding
CLI flag if needed.
Adding a new request type
~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Add a new member to ``RequestType`` in ``protocols/base.py``.
2. Create a ``ProtocolDefinition`` in the appropriate ``protocols/*.py`` file
(``engine``, ``controller``, ``observability``, ``debug``, ``blend``,
``blend_v2``, ``blend_v3``, or ``p2p``) and add the request name to that
module's ``REQUEST_NAMES``.
3. Implement the handler method on the appropriate ``EngineModule``
(e.g. ``LookupModule``, ``LMCacheDrivenTransferModule``, ``BlendV3Module``) and
expose it as a ``HandlerSpec`` from that module's ``get_handlers()``.
4. ``run_cache_server()`` registers every ``HandlerSpec`` returned by the
loaded modules via ``add_handler_helper()`` — no manual registration
step is needed.
Key Source Files
----------------
.. list-table::
:header-rows: 1
:widths: 40 60
* - File
- Purpose
* - ``lmcache/v1/multiprocess/server.py``
- MPCacheServer + ZMQ server entry point
* - ``lmcache/v1/multiprocess/config.py``
- MPServerConfig, HTTPFrontendConfig
* - ``lmcache/v1/multiprocess/engine_context.py``
- MPCacheServerContext (shared state passed to every EngineModule)
* - ``lmcache/v1/multiprocess/engine_module.py``
- ``EngineModule`` protocol, ``HandlerSpec``, ``ThreadPoolType``
(per-module handler registration)
* - ``lmcache/v1/multiprocess/modules/``
- Engine module implementations: ``lookup.py`` (``LookupModule``),
``management.py`` (``ManagementModule``), ``lmcache_driven_transfer.py``
(``LMCacheDrivenTransferModule``), ``engine_driven_transfer.py``
(``EngineDrivenTransferModule``), ``blend.py``
(``BlendModule`` / ``BlendEngineV2``, selected by
``--engine-type blend_legacy``), and ``blend_v3.py``
(``BlendV3Module``, the paged-aware CacheBlend V3 pipeline
selected by ``--engine-type blend``).
* - ``lmcache/v1/multiprocess/http_server.py``
- FastAPI wrapper with health check and many other useful APIs
* - ``lmcache/v1/multiprocess/http_api_registry.py``
- ``HTTPAPIRegistry`` that auto-discovers routers in ``http_apis/``
* - ``lmcache/v1/multiprocess/http_apis/``
- Extensible HTTP endpoints (``/``, ``/healthcheck``,
``/cache/clear``, ``/status``)
* - ``lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py``
- ``MPRuntimePluginLauncher`` that spawns runtime plugins with the
full server config serialized into environment variables
* - ``lmcache/v1/multiprocess/protocols/base.py``
- RequestType, HandlerType, ProtocolDefinition
* - ``lmcache/v1/distributed/storage_manager.py``
- StorageManager (top-level manager)
* - ``lmcache/v1/distributed/config.py``
- StorageManagerConfig hierarchy
* - ``lmcache/v1/distributed/l1_manager.py``
- L1Manager (object state machine)
* - ``lmcache/v1/distributed/l2_adapters/config.py``
- L2 adapter config registry
* - ``lmcache/v1/distributed/l2_adapters/base.py``
- L2AdapterInterface
* - ``lmcache/v1/distributed/storage_controllers/store_controller.py``
- StoreController (event-driven L1->L2)
* - ``lmcache/v1/distributed/storage_controllers/eviction_controller.py``
- EvictionController (watermark-triggered)
* - ``lmcache/v1/distributed/storage_controllers/prefetch_controller.py``
- PrefetchController (L2->L1 on miss)
* - ``lmcache/v1/mp_observability/config.py``
- ObservabilityConfig + ``init_observability()`` entry point
* - ``lmcache/v1/mp_observability/event_bus.py``
- EventBus singleton and ``EventSubscriber`` base class
* - ``lmcache/v1/mp_observability/event.py``
- ``Event`` / ``EventType`` definitions
* - ``lmcache/v1/mp_observability/otel_init.py``
- OTel metrics / tracing provider setup
* - ``lmcache/v1/mp_observability/subscribers/``
- Metrics, logging, and tracing subscribers
* - ``lmcache/v1/mp_observability/trace/``
- Trace recording (``--trace-level storage``) capture stack