指标#

指标通过 OpenTelemetry 计数器收集,并通过进程内的 Prometheus /metrics HTTP 端点导出(默认端口 9090)。当设置 --otlp-endpoint 时,指标也会推送到 OTel 收集器。

所有指标使用 lmcache_mp. 前缀(多进程)。在 Prometheus 中,点被转换为下划线,计数器会添加 _total 后缀(例如 lmcache_mp_l1_read_chunks_total)。

全局资源属性#

每个由 MP 服务器导出的指标和跨度都携带在启动时构建的资源级属性。这些属性标识生成遥测的进程,并与每个指标的属性(如 cache_salt)正交。

属性

命令行标志 / 配置

未设置时的默认值

service.instance.id

--instance-id / MPServerConfig.instance_id

在启动时生成的随机 UUID v4。

资源属性附加到 MeterProvider / TracerProvider 并通过 OTLP 传播到每个导出的数据点。在 Prometheus 中,SDK 资源属性出现在 target_info 系列上,而不是每个时间序列上——这是标准的 OTel 行为。

L1 指标#

指标

类型

描述

lmcache_mp.l1_read

计数器(属性:cache_salt

按租户分组的从 L1 读取的块数。

lmcache_mp.l1_write

计数器(属性:cache_salt

按租户分组写入 L1 的块数。

lmcache_mp.l1_evicted

计数器(属性:cache_salt

按租户分组的被 EvictionController 逐出的块数。

lmcache_mp.l1_eviction_loop_ticks

计数器

L1 逐出循环迭代(每个周期,无论水位线是否被跨越)。由 L1_EVICTION_LOOP_TICK 驱动。

lmcache_mp.l1_eviction_loop_triggered

计数器

L1 逐出循环迭代,其中 usage >= watermark 且逐出策略实际运行。这两个计数器区分“循环仍在运行”和“逐出已触发”——在调试完成速度快于 1 Hz 轮询周期的短期基准测试时,这一点非常重要。

L1 块生命周期直方图#

通过 L1LifecycleSubscriber 进行采样的(默认 1%)块级生命周期跟踪。只有采样的块会对直方图产生贡献;上述计数器始终计算所有事件。采样是确定性的(基于哈希),因此相同的键总是会得到相同的决策,且没有内存开销。

指标

类型

描述

lmcache_mp.l1_chunk_lifetime

直方图

每个采样块从分配到逐出的时间。

lmcache_mp.l1_chunk_idle_before_evict

直方图

每个采样块从最后访问到逐出的时间。

lmcache_mp.l1_chunk_reuse_gap

直方图

同一块的连续访问(读取或写入)之间的时间间隔。

lmcache_mp.l1_chunk_evict_reuse_gap

直方图

逐出到下次重用的时间(上限为 300 秒)。

存储管理器真实重用指标#

SMLifecycleSubscriber 发出的工作负载级重用直方图,由面向调用者的 StorageManager 事件(SM_READ_PREFETCHED_FINISHEDSM_WRITE_FINISHED)驱动。存储/预取控制器的内部读取锁释放被排除在外,因此信号仅反映用户驱动的访问。

这两个直方图都带有 cache_salt 标签,以实现租户隔离。每个块的每次读取和写入都会使对应 cache_salt 的访问计数器递增(无论是否采样),因此块间隔反映了真实的存储量;直方图本身仅记录通过(确定性、基于哈希的)采样门控的块的间隔。

指标

类型

描述

lmcache_mp.real_reuse_gap

直方图(标签:cache_salt

块的最后一次访问(读取或写入)与下一次读取之间的时间间隔。捕获存储成本——一个存储块在访问之间静止了多久。仅在读取事件中发出。

lmcache_mp.real_reuse_gap_objects

直方图(标签:cache_salt

同一块两次读取之间每 cache_salt 访问计数器的增量。反映存储量——在该块等待下次读取期间发生了多少次块访问。仅对采样块的读取事件发出。

L2 指标#

指标

类型

描述

lmcache_mp.l2_store_submitted

计数器

提交的 L2 存储请求数量。

lmcache_mp.l2_store_submitted_objects

计数器(属性:cache_salt

按租户分组的提交到 L2 存储的块数量。

lmcache_mp.l2_store_completed

计数器(属性:l2_name

按适配器类型标记的完成的 L2 存储请求数量。

lmcache_mp.l2_store_completed_objects

计数器(属性:cache_salt

按租户分组成功存储到 L2 的块数。

lmcache_mp.l2_prefetch_lookup

计数器

L2 预取查找请求的数量。

lmcache_mp.l2_prefetch_lookup_objects

计数器(属性:cache_salt

按租户分组的提交用于 L2 预取查找的块数量。

lmcache_mp.l2_prefetch_hit

计数器

在 L2 查找中找到的前缀块数量。

lmcache_mp.l2_prefetch_load_submitted

计数器

提交的 L2 预取加载请求数量。

lmcache_mp.l2_prefetch_load_submitted_objects

计数器(属性:cache_salt

按租户分组的提交用于 L2 加载的块数量。

lmcache_mp.l2_prefetch_load_completed

计数器(属性:cache_salt

按租户分组成功从 L2 加载的块数。

lmcache_mp.l2_load_completed

计数器(属性:l2_name

按适配器类型标记的每个适配器完成的 L2 加载请求数量。

lmcache_mp.l2_evicted_objects

计数器(属性:cache_salt

按租户分组的从 L2 逐出的块数量。

l2_name 标签的计数器 (l2_store_completedl2_load_completed) 存在的目的是为了让仪表板能够通过 rate(lmcache_mp_l2_store_completed_requests_total{l2_name=\"...\"}[1m]) 按需计算每个后端的 IOPS(加载的等效项也是如此)。没有单独导出 *_iops 指标;保留原始计数器让仪表板用户可以选择自己的时间窗口。

失败与健康计数器#

在专用的 lmcache_mp.health OTel 计量器上发出的健康监测计数器。由 L1FailureMetricsSubscriberL2FailureMetricsSubscriber 驱动,这两个订阅者在启用指标时会自动注册。所有三个计数器都携带 model_name(从每个 ObjectKey 中提取),以便操作员可以在 Prometheus /metrics 端点上按模型进行切片。

指标

类型

描述

lmcache_mp.l1_allocation_failure

计数器

reserve_write 期间的 L1 内存分配失败(OOM)。通过 during ∈ {l1_store, l2_prefetch} 标记,以区分用户发起的存储与预取触发的分配,以及 model_name

lmcache_mp.l1_read_failure

计数器

L1 reserve_read 失败。标记为 during ∈ {l2_store, l1_retrieve},reason ∈ {not_found, write_locked},加上 model_name后查找异常计数器,而非缓存未命中计数器——在 MP 模式下,reserve_read 仅在查找成功后调用,因此任何非零值均表明存在查找/预留竞争或意外逐出,健康运行时该值应接近零。

lmcache_mp.l2_prefetch_failure

计数器

L2 报告在查找时存在但未能进入 L1 的块。标记为 reason ∈ {l1_oom, not_found} 以及 model_namel1_oom 表示 L1 没有空间接收预取的对象;not_found 表示适配器在查找成功的情况下未返回数据(例如并发删除)。

一旦 L2 适配器区分反序列化错误和缺失对象,将会将 reason=serde_failure 值作为附加的、非破坏性的扩展添加到 l2_prefetch_failure 中——当这项功能上线时,无需进行仪表板迁移。

有关完整的设计原理(包括哪些事件类型驱动每个计数器以及为何推迟 lmcache_instance_id),请参阅源树中的 docs/design/v1/mp_observability/METRICS.md

查找命中率指标#

令牌级计数器,其比值表示通过查找请求的令牌中有多少比例是从 L1 或 L2 提供的。L0(GPU 前缀缓存)被有意排除在外——它由 vLLM 管理,无法从 LMCache 中观察到。

指标

类型

描述

lmcache_mp.lookup_requested

计数器(属性:model_name, cache_salt

提交查找的总令牌数(L1+L2 令牌级命中率的分母)。仅计算与块对齐的令牌。

lmcache_mp.lookup_hit

计数器(属性:model_name, cache_salt

在查找过程中在 L1 或 L2 中找到的总令牌数(L1+L2 令牌级命中率的分子)。仅计算连续前缀命中。

这两个计数器由同一事件(MP_LOOKUP_PREFETCH_END)驱动,因此它们在每次完成查找时总是一起增加。提前退出的查找对两者都贡献 0,而放弃的查找则不对两者贡献。

model_namecache_salt 属性在查找时从 IPCCacheServerKey 捕获,以便仪表板计算每个模型或每个租户的命中率。cache_salt 可能具有高基数(每个租户或隔离域一个条目);若存储成本敏感,请在抓取时通过 metric_relabel_configs 丢弃它。

PromQL 查询命中率:

# Aggregate (all models, all salts):
rate(lmcache_mp_lookup_hit_tokens_total[5m])
/ rate(lmcache_mp_lookup_requested_tokens_total[5m])

# Per-model:
sum(rate(lmcache_mp_lookup_hit_tokens_total[5m])) by (model_name)
/ sum(rate(lmcache_mp_lookup_requested_tokens_total[5m])) by (model_name)

L0 (GPU) 块生命周期直方图#

通过 L0LifecycleSubscriber 采样(默认 1%)GPU KV Cache 块生命周期跟踪。在重新分配时检测逐出(当块被分配不同的令牌时)。采样使用带有 _skipped 集的随机选择(受限于有限数量的物理 GPU 块)。

所有 L0 直方图都带有 instance_idmodel_name OTel 属性,从而在 Prometheus 中实现按实例和按模型的指标切片(例如 lmcache_mp_l0_block_lifetime_seconds{instance_id=\"12345\",model_name=\"llama-7b\"})。

指标

类型

描述

lmcache_mp.l0_block_lifetime

直方图

每个采样的 GPU 块从分配到逐出的时间。

lmcache_mp.l0_block_idle_before_evict

直方图

每个采样的 GPU 块从最后访问到逐出的时间。

lmcache_mp.l0_block_reuse_gap

直方图

同一 GPU 块连续访问之间的时间间隔。

L0 ↔ L1 吞吐量直方图#

每个请求的 GPU↔CPU 复制吞吐量通过 L0L1ThroughputSubscriber 进行统计。每个存储/检索请求为相应的直方图贡献一个样本:total_bytes / (end_ts - start_ts) 以 GB/s 为单位。时间戳来自在 GPU cupy 流上发布的 MP_{STORE,RETRIEVE}_{START,END} 事件,因此它们反映了真实的 GPU 流复制时间——而不是 Python/锁的开销。

所有吞吐量直方图都带有 engine_id(vLLM 工作实例 ID)、device(例如 "cuda:3")和 model_name OTel 属性,从而支持在 Prometheus 中按工作者、设备和模型进行切片(例如 lmcache_mp_l0_l1_store_throughput_GB_per_second{engine_id="0",device="cuda:3",model_name="meta-llama/Llama-3.1-8B"})。

指标

类型

描述

lmcache_mp.l0_l1_store_throughput

直方图

每个请求的 GPU→CPU (L0→L1) 存储吞吐量(单位:GB/s)。

lmcache_mp.l0_l1_load_throughput

直方图

每个请求的 CPU→GPU (L1→L0) 加载吞吐量(GB/s)。

L1 ↔ L2 吞吐量直方图#

每个请求的 L1↔L2 传输吞吐量通过 L2ThroughputSubscriber 进行统计。存储路径通过 (adapter_index, task_id) 关联 L2_STORE_SUBMITTEDL2_STORE_COMPLETED。加载路径通过 (request_id, adapter_index) 关联每个适配器的 L2_LOAD_TASK_SUBMITTEDL2_LOAD_TASK_COMPLETED 事件;请求级别的 L2_PREFETCH_LOAD_* 事件用于块计数器在适配器之间进行聚合,无法归因于特定的 l2_name

时间戳跨度为 提交 → 完成,因此持续时间包括适配器队列、网络和磁盘 I/O — 该值为 字节 / 端到端延迟,而非原始传输速率。使用这些直方图比较适配器类型并捕捉回归;当您需要纯粹的复制时间吞吐量时,请使用 L0↔L1 直方图。

所有 L1↔L2 吞吐量直方图都携带一个 l2_name OTel 属性——注册的适配器类型(例如 \"fs\", \"nixl_store\", \"mooncake_store\")——使得在 Prometheus 中能够按后端进行切片(例如 lmcache_mp_l2_store_throughput_GB_per_second{l2_name=\"nixl_store\"})。

指标

类型

描述

lmcache_mp.l2_store_throughput

直方图

每个请求的 L1→L2 存储吞吐量(GB/s)。

lmcache_mp.l2_load_throughput

直方图

每对(请求,适配器)的 L2→L1 加载吞吐量(单位:GB/s)。

引擎计数器#

与 MP 服务器通过 retrieve() 返回给每个 vLLM 工作线程相关的工作线程范围计数器。以 worker_id(vLLM 工作线程实例 ID)标记——与其他指标中可能出现的任何调度器范围 ID 不同。

指标

类型

描述

lmcache_mp.num_chunks_loaded

计数器(属性:worker_id, model_name, cache_salt

加载到引擎的 LMCache 块总数,汇总自所有 retrieve() 完成结果。可按工作者、模型及租户/隔离域(cache_salt)进行切片。cache_salt 可能具有高基数;若存储成本敏感,请在抓取时通过 metric_relabel_configs 丢弃它。

可观察仪表#

通过 register_gauge 注册的即时状态快照(基于拉取的 OTel 可观察仪表)。

这三个正在进行的指标携带两个属性,即使在与相同后端类型注册多个适配器时也能区分它们——与 lmcache_mp.l2_store_completed 具有相同形状:

  • l2_name — 注册的适配器类型(例如 "fs", "nixl_store", "mooncake_store")。

  • adapter_index — 控制器适配器列表中的位置。

没有正在进行的工作的适配器不会为该抓取发出数据点。

指标

类型

描述

lmcache_mp.active_prefetch_jobs

可观察仪表

当前正在进行的预取作业数量。持续的高值可能表示 L2 后端缓慢或轮询延迟。

lmcache_mp.l1_memory_usage_bytes

可观察仪表

当前在 L1 中占用的字节数。持续上升而没有平台期通常表示存在泄漏;在配置的 --l1-size-gb 达到饱和时表示工作集超过了容量。

lmcache_mp.l1_usage_ratio

可观察仪表

L1 使用/总比率 (0.01.0),在抓取时从 L1Manager.get_memory_usage() 采样。当仪表目标尚未连接或 total_bytes 为零时返回 0.0,因此在抓取期间回调不会触发。与逐出水位线 (默认 0.8) 进行比较,以判断逐出循环是否低于或高于触发阈值。

lmcache_mp.l2_usage_bytes

可观察仪表 (属性: l2_name)

每个 L2 适配器当前持有的字节数,在抓取时从 adapter.get_usage() 采样。每个已配置的适配器对应一个观测值,通过 l2_name 标记(适配器类型,例如 "fs""nixl_store""mooncake_store")。它是 L2 层中与 l1_memory_usage_bytes 对应的指标——用于查看每个 L2 后端当前持有多少数据。get_usage() 抛出异常的适配器会被静默跳过,而不会污染观测结果,因此某个 l2_name 缺失数据点可能意味着“未配置”或“该次抓取时适配器发生错误”——请与 L2 存储/加载计数器交叉核对。

lmcache_mp.num_inflight_l2_stores

ObservableGauge (attrs: l2_name, adapter_index)

每个适配器当前正在执行的 L2 存储任务。 持续的非零值表明适配器无法跟上 L1 → L2 写入速率。

lmcache_mp.num_inflight_l2_loads

ObservableGauge (attrs: l2_name, adapter_index)

每个适配器当前正在执行的 L2 → L1 预取加载任务。与 num_inflight_l2_stores 配对,以查看给定后端是读流量还是写流量占主导。

lmcache_mp.inflight_load_memory_usage_bytes

ObservableGauge (attrs: l2_name, adapter_index)

每个适配器由正在进行的 L2 → L1 预取加载所预留的 L1 字节数。若正在进行的字节数与 l1_memory_usage_bytes 同步上升,则表明预取预留正在挤占可缓存数据的空间。每个适配器的字节归属遵循每个请求的 load_plan 位图,因此跨适配器求和时不会重复计数。

事件总线自我监控#

EventBus 本身的健康指标,由 EventBusSelfMetricsSubscriberlmcache.event_bus OTel 计量器上注册。这些指标通过 EventBus 访问器直接观察总线状态,并在每次 OTel 抓取时报告——它们不是由事件驱动的,因此丢弃或失败的订阅者无法使其静默。

使用它们来回答:EventBus 是否能跟上发布者的速度,是否有事件被丢弃,是否有订阅者回调抛出异常?非零的 dropped_events_total 或持续非零的 drain_lag_seconds 表明总线队列已达 --event-bus-queue-size 上限并正在进行尾部丢弃;请调大该参数值或排查慢速订阅者。

指标

类型

描述

lmcache_mp.event_bus.queue_depth

可观察仪表

事件总线中当前排队的事件(在抓取时的 len(_queue))。

lmcache_mp.event_bus.drain_lag_seconds

可观察仪表

自最旧的排队事件发布以来的秒数;队列为空时为 0.0。值持续上升意味着排空线程处理速度跟不上。

lmcache_mp.event_bus.dropped_events_total

可观察计数器

因 EventBus 队列达到 --event-bus-queue-size 而累计丢弃的事件。

lmcache_mp.event_bus.subscriber_exceptions

可观察计数器 (属性: subscriber_name)

由事件总线分发期间由订阅者回调引发的累计异常,按 subscriber_name 标记(对于绑定方法为失败回调的拥有类,对于自由函数为 __qualname__)。

有关完整的设计原理以及支持每个指标的进程内访问器,请参阅源代码树中的 docs/design/v1/mp_observability/METRICS.mddocs/design/v1/mp_observability/event-bus.md

Prometheus 抓取配置#

将 LMCache 服务器添加为 Prometheus 抓取目标:

scrape_configs:
  - job_name: "lmcache-mp"
    static_configs:
      - targets: ["<lmcache-host>:9090"]