LMCache 运行时插件系统(Runtime Plugins)实战指南:原理、配置与最佳实践
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
本指南系统讲解 LMCache 的运行时插件(Runtime Plugin)机制——一种通过自定义脚本(目前支持 Python 与 Bash)与 LMCache 进程并行运行、从而扩展缓存系统功能的轻量级扩展方式。你将掌握插件的作用场景、lmcache.yaml与环境变量的双重配置方法、基于文件名的角色/Worker 定向执行规则,以及解释器探测、输出捕获、进程生命周期管理等底层实现原理,并可直接复用仓库中的完整示例插件搭建自己的监控上报、健康检查或自定义缓存管理任务。
一、什么是 LMCache 运行时插件
LMCache 运行时插件系统提供了一种"零侵入"的扩展能力:插件以独立子进程形式与 LMCache 主进程并行运行,由RuntimePluginLauncher类统一管理。插件不需要修改 LMCache 核心代码,只要把脚本放到指定目录并遵循命名约定,就会被自动发现、启动与回收。
从源码看,插件发现的逻辑位于 runtime_plugin_launcher.py:_launch_plugins会递归(rglob)扫描配置目录下所有*.py与*.sh文件,逐个启动;配置路径也可以是单个文件。这意味着你既可以把插件目录整体挂载,也可以精确指定某个脚本。
典型使用场景
官方文档与示例 README(examples/runtime_plugins/README.md)列出的典型场景包括:
- 指标上报:启动 metric reporter,向集中式监控系统上报指标;
- 日志采集:实现 log reporter,对接集中式日志收集与查询系统;
- 告警上报:向告警系统上报进程级自定义指标;
- 健康检查与服务发现:向健康监控系统或服务发现系统发送心跳;
- 自定义缓存管理操作:在 LMCache 生命周期内执行自定义的缓存管理逻辑。
由于插件本质上是独立进程,理论上也可以使用其他语言(只要提供可执行的 shebang),但当前官方发现机制只识别.py与.sh后缀。
二、插件配置:环境变量与 lmcache.yaml
运行时插件的配置分为两部分:环境变量(由 LMCache 注入给插件进程)与配置文件(指定插件位置及自定义参数)。
2.1 LMCache 注入的环境变量
LMCache 在启动每个插件子进程时,会通过环境变量向其传递上下文信息(见 runtime_plugin_launcher.py 的env构造逻辑):
| 环境变量 | 含义 | 说明 |
|---|---|---|
LMCACHE_RUNTIME_PLUGIN_ROLE | 当前进程角色 | 如SCHEDULER、WORKER;非 MP 模式下可能为空字符串 |
LMCACHE_RUNTIME_PLUGIN_CONFIG | 插件配置的 JSON 字符串 | 由配置对象序列化而来,含插件目录与extra_config等 |
LMCACHE_RUNTIME_PLUGIN_WORKER_ID | 当前 Worker 的 ID | 用于按 Worker 定向执行 |
LMCACHE_RUNTIME_PLUGIN_WORKER_COUNT | 集群中 Worker 总数 | 便于插件感知集群规模 |
此外,为兼容旧版本,源码中还同时注入了同义的旧环境变量LMCACHE_PLUGIN_ROLE、LMCACHE_PLUGIN_CONFIG、LMCACHE_PLUGIN_WORKER_COUNT、LMCACHE_PLUGIN_WORKER_ID。同时 LMCache 会为子进程设置PYTHONUNBUFFERED=1,强制 Python 子进程行缓冲输出,保证插件日志能被实时捕获,而不是等到进程退出才一次性刷出。
2.2 lmcache.yaml 配置文件
在lmcache.yaml中通过runtime_plugin_locations指定插件目录(也支持直接给单个文件路径),并通过extra_config向插件传递自定义参数:
runtime_plugin_locations: - "/path/to/plugins" extra_config: custom_setting: value从配置定义源码(config.py)看,runtime_plugin_locations的类型为Optional[list[str]],其环境变量转换器支持同时接受列表或单个字符串([x] if x else [])。需要注意:旧配置项plugin_locations已废弃,会被自动重定向为runtime_plugin_locations并给出警告(见 config.py)。
extra_config是一个自由的字典(Optional[dict],见 config.py),插件启动时整个配置会经config.to_json()序列化后写入LMCACHE_RUNTIME_PLUGIN_CONFIG,插件侧可以解析出自定义字段。
2.3 两种启动入口
- 单进程 / vLLM 集成模式:在 vllm_service_factory.py 中通过
maybe_create_runtime_plugin_launcher创建RuntimePluginLauncher; - 多进程(MP)模式:MP 模式下存在多个独立配置对象(
MPServerConfig、StorageManagerConfig、ObservabilityConfig等),由 mp_runtime_plugin_launcher.py 中的MPRuntimePluginLauncher将全部配置聚合为单一 JSON blob(存放于configs_dict,extra_config归入runtime_plugin_extra_config键)后注入环境变量。由于 MP 模式没有角色概念,其内部以role=None调用底层 launcher,从而跳过所有角色/Worker 过滤(见 runtime_plugin_launcher.py)。
插件进程的实际启动与回收由 manager.py 编排:launch_plugins()在主流程启动阶段被调用,stop_plugins()则通过atexit注册,保证进程退出时插件被一并清理。
三、插件命名约定:决定在哪台机器、哪个进程上运行
插件文件名决定其执行目标,格式为:
<ROLE>[_<WORKER_ID>][_<DESCRIPTION>].<EXTENSION>官方示例:
| 文件名 | 执行目标 |
|---|---|
scheduler_foo_plugin.py | 仅在角色为SCHEDULER的进程上运行 |
worker_0_test.sh | 仅在WORKER角色且worker_id = 0的进程上运行 |
all_plugin.sh | 在所有角色/Worker 上运行 |
命名规则要点(与源码_should_skip_plugin的实现一致,见 runtime_plugin_launcher.py):
- 角色不区分大小写:源码统一用
parts[0].upper()与当前角色的upper()比较; all是通配角色:plugin_role == "ALL"时不做角色过滤;- Worker ID 必须为数字:只有
parts[1].isdigit()为真时才按 Worker ID 过滤,且仅当文件名至少有三个下划线分隔的部分(如worker_0_test.sh)时才会检查第二个字段;如果文件名只有两部分(如worker_test.sh),则会匹配所有 Worker——这一点与官方文档"针对特定 Worker ID 必须至少有三个部分"的说明完全对应; - MP 模式下角色过滤整体跳过(见上文 2.3)。
四、执行模型:解释器探测、输出捕获与进程管理
4.1 解释器探测
插件使用哪个解释器,遵循"shebang 优先、扩展名兜底"的策略(见 runtime_plugin_launcher.py):
- 读取文件第一行,若以
#!开头则提取 shebang(例如#!/opt/venv/bin/python、#!/bin/bash); - 按扩展名追加兜底解释器:
.py→python、python3;.sh→bash; - 依次用
shutil.which在 PATH 中解析,返回第一个存在的解释器;全部找不到则抛出ValueError并记录日志。
这也解释了最佳实践中"必须包含 shebang"的原因:shebang 能精确指定解释器(比如虚拟环境中的 Python),提高可移植性。
4.2 输出捕获
插件进程通过subprocess.Popen启动,stdout与stderr合并重定向到管道,并由一个 daemon 线程_capture_plugin_output持续逐行读取(见 runtime_plugin_launcher.py):
- 每一行输出都会以
[插件文件名] 内容的前缀写入 LMCache 日志; - 进程退出后记录退出码(
Runtime plugin %s exited with code %d)。
也就是说,插件的print/echo输出会直接汇入 LMCache 的统一日志,无需额外的采集通道。
4.3 进程生命周期
- 插件以子进程方式启动,多个插件会记录在
self.plugin_processes列表中; - 父进程退出时,通过
atexit注册的stop_plugins对仍在运行的插件进程调用terminate()(发送 SIGTERM)实现优雅关闭(见 runtime_plugin_launcher.py); - 单个插件启动失败不会影响其他插件:
_launch_plugin将异常捕获并记录Failed to launch plugin错误日志,同时插件目录不存在时只会输出 warning 并跳过(见 runtime_plugin_launcher.py)。
五、完整示例插件
仓库 examples/runtime_plugins/ 提供了三个可直接运行、可作为模板的插件。
5.1 Python 插件:scheduler_foo_plugin.py
仅运行于SCHEDULER角色的插件(完整源码见 scheduler_foo_plugin.py):
#!/opt/venv/bin/python """Example plugin for LMCache system This plugin runs continuously and exits when parent process terminates""" # Standard import json import os import signal import time # First Party from lmcache.integration.vllm.utils import lmcache_get_or_create_config from lmcache.v1.config import LMCacheEngineConfig # Graceful exit handler def handle_exit(signum, frame): print("Received termination signal, exiting...") exit(0) signal.signal(signal.SIGTERM, handle_exit) role = os.getenv("LMCACHE_RUNTIME_PLUGIN_ROLE") worker_id = os.getenv("LMCACHE_RUNTIME_PLUGIN_WORKER_ID") worker_count = os.getenv("LMCACHE_RUNTIME_PLUGIN_WORKER_COUNT") config_str = os.getenv("LMCACHE_RUNTIME_PLUGIN_CONFIG") try: config = LMCacheEngineConfig.from_json(config_str) except json.JSONDecodeError as e: print(f"Error parsing LMCACHE_RUNTIME_PLUGIN_CONFIG: {e}") config = lmcache_get_or_create_config() print( f"Python plugin running with role: {role}, worker_id: {worker_id}, " f"worker_count: {worker_count}" ) print(f"Config: {config}") # Main loop loop_count = 0 while True: print(f"Scheduler plugin is running... (loop_count: {loop_count})") loop_count += 1 time.sleep(10)该示例示范了三个关键点:
- 优雅退出:注册
SIGTERM处理器,收到父进程的终止信号后打印提示并exit(0),避免被强制杀死留下残留状态; - 读取上下文:通过
LMCACHE_RUNTIME_PLUGIN_*环境变量获取角色、Worker ID、Worker 数与完整配置 JSON,并尝试用LMCacheEngineConfig.from_json解析;解析失败(如 JSON 损坏)时回退到lmcache_get_or_create_config()重新加载配置; - 常驻循环:以
while True + sleep(10)的形式持续运行,直到父进程退出。
5.2 Bash 插件:all_plugin.sh
在所有角色/Worker 上运行的 Bash 插件(完整源码见 all_plugin.sh):
#!/bin/bash # This plugin runs continuously and exits when parent process terminates # Handle termination signal trap "echo 'Received termination signal, exiting...'; exit 0" SIGTERM role="$LMCACHE_RUNTIME_PLUGIN_ROLE" worker_id="$LMCACHE_RUNTIME_PLUGIN_WORKER_ID" worker_count="$LMCACHE_RUNTIME_PLUGIN_WORKER_COUNT" config="$LMCACHE_RUNTIME_PLUGIN_CONFIG" echo "All plugin started for role: $role, worker ID: $worker_id, worker count: $worker_count" echo "All plugin accept LMCache Config: $config" loop_count=0 while true; do echo "All plugin is running for ${role} ${worker_id}...(loop_count: ${loop_count})" loop_count=$((loop_count + 1)) sleep 10 doneBash 版本使用trap ... SIGTERM实现与 Python 版相同的优雅退出语义,通过 shell 变量直接读取环境变量上下文。
5.3 Worker 定向插件:worker_0_test.sh
第三个示例 worker_0_test.sh 结构上与all_plugin.sh相同,仅输出文案区分;关键在于文件名worker_0_test.sh满足"三段式"约定——只有WORKER角色且worker_id == 0的进程才会启动它,可用于验证 Worker 定向执行。
5.4 运行与验证
按以下步骤即可在本地验证插件机制:
- 将上述插件放到某目录(例如
examples/runtime_plugins/自身); - 在
lmcache.yaml中配置runtime_plugin_locations指向该目录; - 正常启动 LMCache(vLLM 集成或 MP 模式均可);
- 观察 LMCache 日志,应出现
Launched runtime plugin: ...信息,以及带[插件文件名]前缀的插件输出; - 停止 LMCache 主进程,插件进程会被自动 SIGTERM 并打印退出提示。
六、最佳实践
官方文档与示例共同建议遵循以下实践:
- 保持轻量高效:插件作为常驻子进程运行,应避免不必要的计算与内存占用,避免影响主进程资源;
- 使用描述性命名:遵循
<ROLE>[_<WORKER_ID>][_<DESCRIPTION>].<EXT>约定,让文件名自解释执行目标; - 实现优雅的错误处理:解析环境变量与配置时要容错(如示例中对
JSONDecodeError的回退处理),并让SIGTERM处理器负责清理资源; - 始终包含 shebang:明确指定解释器路径,提升可移植性并避免依赖 PATH 中的默认解释器;
- 校验配置输入:对
LMCACHE_RUNTIME_PLUGIN_CONFIG传入的参数做合法性校验,防止脏数据导致异常行为; - 为长操作添加超时机制:插件内部的长任务(如网络请求、批量上报)应设置超时,避免子进程长时间卡死而无法响应终止信号。
七、总结
LMCache 运行时插件系统是一个面向运维与定制场景的轻量扩展点:通过runtime_plugin_locations声明插件目录、借助文件命名约定实现角色/Worker 定向执行、由RuntimePluginLauncher统一完成解释器探测、环境变量注入、输出捕获与生命周期管理,并以atexit保证进程退出时插件被优雅回收。结合 examples/runtime_plugins/ 下的三个完整示例与 runtime_plugin_launcher.py 的实现,你可以快速搭建自己的指标上报、日志采集、健康检查或自定义缓存管理插件,在不修改 LMCache 核心代码的前提下完成功能扩展。
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考