资讯动态

LMCache Async Loading 深度解析:异步 KV Cache 查找与预取机制

发布时间:2026/9/15 14:13:27 来源:尧图企业网站定制
LMCache Async Loading 深度解析异步 KV Cache 查找与预取机制【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache本文以 LMCache 仓库中 async_loading.rst 文档为骨架结合 v1 版本中 ZMQ 异步查找客户端/服务端、StorageManager 存储编排、EventManager 事件同步与 AsyncSerializer 并发控制等源码实现完整讲解 LMCache 在 vLLM 集成场景下如何将调度侧查找与worker 侧预取/检索解耦实现 I/O 与计算的流水线重叠。读完本文你将掌握async_loading的端到端调用链、分层回源tiered retrieval策略、防死锁并发模型以及它与 vLLM 上游 PR 19330 加载失败恢复机制的差异与适用边界。注意async_loading是 LMCache v1 与 vLLM 集成的进程内in-process模式特性该模式当前已标记为 deprecated弃用官方建议迁移至功能更完善、性能更好的 LMCache MP 模式多进程模式。本文记录的是 in-process 模式下的设计原理与实现细节供理解架构演进与迁移参考。概述什么是 Async Loadingasync_loading是 LMCache v1 集成 vLLM 时提供的一套**异步 KV Cache 查找与预取lookup prefetch**机制。它解决的核心问题是在 vLLM 调度器需要对某条请求做 prefix 命中判断时传统同步查找会把存储后端的 I/O 延迟直接暴露在调度关键路径上而async_loading将这一过程异步化让调度侧发出查找请求后可以继续推进调度与计算worker 侧则并行完成跨后端的命中查询与数据预取从而让 I/O 与计算重叠。该特性涉及的核心组件变更包括LMCache 异步查找客户端/服务端ZMQ 通信调度侧通过 ZMQ PUSH 发送查找请求worker 侧通过 ZMQ PULL 接收并在后台执行查找与预取StorageManager 存储编排负责跨多个存储后端组织异步查找、并发控制与加载任务调度Cache Engine 异步 API 入口LMCacheEngine.async_lookup_and_prefetch将 token 序列转换为 chunk 哈希与偏移并投递到存储管理器的异步事件循环vLLM adapter 集成点通过 base_service_factory.py 与 vllm_service_factory.py 将异步查找客户端接入 vLLM 的调度路径。工作原理与理论从高层视角看async_loading将调度侧的查找与worker 侧的预取/检索解耦调度器发送携带 token chunk 哈希与偏移的查找请求worker 侧服务端在可用后端上执行分层tiered的batched_async_contains对命中前缀立即发起非阻塞的批量 get 操作完成状态由EventManager追踪以安全地将加载完成的 MemoryObj 交还给请求方一个基于加权信号量weighted semaphore的AsyncSerializer依据 chunk 预算塑形并发度防止分配器死锁。完整端到端流程如下来自原文档的序列图客户端实现LMCacheAsyncLookupClient客户端类定义在 lmcache_async_lookup_client.py它实现了LookupClientInterface核心职责是建立 ZMQ 通道为每个 worker 建立一个zmq.PUSHsocket路径形如engine_id/lookup_worker/{rpc_port}/{rank}用于发送请求同时在调度侧建立一个zmq.PULLsocketengine_id/lookup_scheduler/{rpc_port}/0用于收取响应Token 分块与哈希构造时根据config.enable_blending选择 SegmentTokenDatabase 或ChunkedTokenDatabase。lookup()调用token_database.process_tokens(token_ids, make_keyFalse)得到每个 chunk 的哈希与长度偏移封装为LookupRequestMsg(lookup_id, hashes, offsets, request_configs)用msgspec.msgpack序列化后经 PUSH socket 广播给所有 worker异步轮询结果一个独立的守护线程async-lookup-client-thread在process_responses_from_workers中持续从 PULL socket 收取LookupResponseMsg。由于 TP/PP 各 rank 的命中 token 数可能不同客户端取所有 worker 返回值的 min作为最终命中数见 LMCacheAsyncLookupClient以保守保证前缀正确性超时与退避语义lookup_cache(lookup_id)的状态机为-1未找到/None进行中/0命中 token 数。当状态仍为None时会以lookup_backoff_time默认 0.01 秒可通过extra_config.lookup_backoff_time覆盖休眠重试若等待超过config.lookup_timeout_ms毫秒仍未完成则记录告警并返回 0让 vLLM 走重算路径避免调度器被无限阻塞超时处理取消与清理调度器可调用cancel_lookup(lookup_id)将请求标记为 aborted客户端在确认其加载任务结束后发送LookupCleanupMsg通知 worker 释放预取阶段占用的 MemoryObjcleanup_memory_objs。服务端实现LMCacheAsyncLookupServer服务端同样定义在 lmcache_async_lookup_client.py每个 worker 进程内运行一个守护线程async-lookup-server-thread通过 PULL socket 接收消息借助msgspec.msgpack.decode(..., typeUnion[LookupRequestMsg, LookupCleanupMsg])自动区分查找请求与清理请求收到LookupRequestMsg后调用lmcache_engine.async_lookup_and_prefetch(lookup_id, hashes, offsets, pinTrue, request_configs...)pinTrue表示在batched_async_contains中对每个命中的 key 执行 pin防止预取期间被逐出收到LookupCleanupMsg后调用lmcache_engine.cleanup_memory_objs(lookup_id)释放 aborted lookup 预留的内存缓冲加载结果通过send_response_to_scheduler(lookup_id, num_hit_tokens)以LookupResponseMsg回传调度侧。Worker 侧架构worker 侧的结构可由下图概括来自原文档架构图StorageManager分层异步查找与预取StorageManager.async_lookup_and_prefetchstorage_manager.py是整个特性的核心编排逻辑逐后端查询命中按get_active_storage_backends(search_range)返回的后端顺序逐个调用backend.batched_async_contains(lookup_id, keys, pin)获取num_hit_keys_raw。命中数会向下取整到完整 chunk 边界num_hit_chunks num_hit_keys_raw // keys_per_chunk因为 prefix 匹配模式要求整 chunk 粒度的一致性——若某 chunk 的部分 per-layer key 被逐出该 chunk 整体按 miss 处理。取整后丢弃的尾部 key 会被显式unpin避免泄漏引用计数预算相关逻辑分层回源tiered retrieval当前检索模式是严格前缀式的——后端 1 命中0~t1的 token后端 2 命中t1~t2以此类推。代码注释指出这一假设的动机是后缀 chunk 比前缀 chunk 更容易被逐出前缀式检索说明。每命中一层就为keys[:num_hit_keys]创建batched_get_non_blocking任务并把cum_chunk_lengths截断到剩余部分直至num_total_hit_chunks num_total_chunks提前退出完全未命中快速返回若所有后端命中 chunk 数为 0立即send_response_to_scheduler(lookup_id, 0)返回不阻塞调度器事件注册与回调所有层的加载任务通过asyncio.gather聚合为gather_with_keys()future先event_manager.add_event(EventType.LOADING, lookup_id, all_done)注册事件再挂prefetch_all_done_callback由它负责在全部加载完成后调用send_response_to_scheduler通知命中 token 数。注册先于回调挂载避免竞态事件注册。AsyncSerializer 与 WeightedSemaphore防死锁并发控制batched_get_non_blocking会在本地 CPU 后端并行分配 MemoryObj若并发不受控多个 get 同时抢占分配器可能死锁。StorageManager通过AsyncSerializer统一塑形并发定义见 storage_manager.py有两种实现AsyncMultiSerializer默认内部持有WeightedSemaphore。其预算由allocator_backend.calculate_chunk_budget()得出且只允许使用chunk 预算的一半作为并发预算_concurrent_budget_cap chunk_budget // 2依据是当所有 chunk 同尺寸且save_unfull_chunkFalse时碎片化不可能超过 50%因此可以安全地用一半内存支撑并发请求WeightedSemaphore 注释。acquire(n)支持两种模式n不超过并发预算时按需扣减超预算的大请求则要求独占全部并发预算等待_current_chunks回到上限防止大请求饿死AsyncSingleSerializer退化为asyncio.Lock串行执行用于无法计算 chunk 预算的场景。EventManager线程安全的异步事件追踪event_manager.py 用events[event_type][event_status][event_id] asyncio.Future的三层结构组织事件以threading.Lock保证多线程安全。EventType.LOADING事件的状态在ONGOING与DONE之间流转cache_engine.cleanup_memory_objs只对状态为DONE的事件执行 MemoryObj 释放从而安全地把 future 从 worker 线程交还给调度路径避免线程与异步循环之间的竞态。Cache Engine 入口与层叠layerwise支持LMCacheEngine.async_lookup_and_prefetchcache_engine.py是 worker 侧服务端调用的入口负责把 hash/offset 输入转换回 chunk 的CacheEngineKey列表与累计长度cum_chunk_lengthscum_chunk_lengths的语义是从 chunk 0 到 chunk i-1 的累计 token 数。例如 chunk_size256、3 个 chunk256/256/128 tokens时其值为[0, 256, 512, 640]最终命中 token 数就是cum_chunk_lengths[命中chunk数]layerwise 模式当use_layerwiseTrue时每个 chunk 被拆成num_layers个LayerCacheEngineKeychunk_hash layer_idkeys_per_chunk num_layerschunk 级计数全部由num_hit_keys // keys_per_chunk推导保证 hit 数与cum_chunk_lengths索引始终处于 chunk 单位层叠 key 拆分最终通过asyncio.run_coroutine_threadsafe(..., self.storage_manager.loop)将协程投递到 StorageManager 专属的异步事件循环线程中执行。收益性能重叠I/O–Compute Overlap查找/预取与加载解耦后KV chunk 的获取可以与 vLLM 的调度与计算并行进行隐藏存储后端的访问延迟健壮性与错误处理事件驱动同步EventManager确保 future 的安全交接避免线程与异步循环之间的竞态背压与死锁规避AsyncSerializer 加权信号量依据分配器预算封顶并发的 chunk 检索数量防止饥饿或分配器锁死优雅的 miss 路径当无可检索内容时立即以 0 命中 token 响应worker 快速返回而不拖住调度器保守的正确性保证客户端取所有 worker 命中数的最小值且命中按整 chunk 向下取整保证 prefix 语义在多 rank 环境下依然成立。与 vLLM Load Failure Recovery 特性PR 19330的对比vLLM 的 PR 19330 为 vLLM 的 KV connector 基础设施引入故障恢复机制自动检测失败的 block 加载仅将受影响的请求调度重算并从合法前缀继续。它是 vLLM 内部连接器层的容错设计。与之相对LMCache 的async_loading是外部化的缓存层拥有独立的 client/server 通信ZMQ、独立的存储后端体系与独立的并发控制StorageManager EventManager AsyncSerializer。它解决的是主动、提前地异步加载命中内容而非失败后的恢复——二者解决的问题域不同一个是前置的 I/O 重叠优化一个是后置的故障容错。限制依赖 vLLM 特定版本仅在与 vLLM 合并的 PR 23620 版本下工作后端支持约束该特性要求后端实现batched_async_contains目前仅限少数后端例如LocalCpuBackendLocalDiskBackendS3ConnectorFSConnectorRedisConnector/RedisClusterConnector从源码结构看batched_async_contains接口在 abstract_backend.py 及各 connector 基类如 base_connector.py中均有声明因此不支持该接口的后端无法参与异步查找路径弃用状态该特性所属的 in-process 模式已标记 deprecated新项目应优先评估 LMCache MP 模式。未来工作为所有后端引入默认的batched_async_contains实现使全部后端都能支持async_loading增加指标与可观测性跟踪异步查找请求数量与占用中的MemoryObj实例数量改进查找框架传入 vLLM prefix cache 的命中 token使异步查找可以跳过 vLLM 中已命中的部分避免重复加载当前实现已在客户端类注释中提示多 worker 命中 token 不同时预取可能加载多余冗余缓存这一已知开销。相关源码与测试索引文档原文docs/source/kv_cache/async_loading.rst客户端与服务端lmcache/v1/lookup_client/lmcache_async_lookup_client.py查找消息协议lmcache/v1/lookup_client/async_lookup_message.py存储编排与并发控制lmcache/v1/storage_backend/storage_manager.py事件管理lmcache/v1/event_manager.py引擎异步入口lmcache/v1/cache_engine.py集成点lmcache/integration/vllm/vllm_service_factory.py、lmcache/integration/base_service_factory.py测试覆盖tests/v1/test_cache_engine.py、tests/v1/storage_backend/test_storage_manager.py、tests/v1/test_manager.py等均包含对异步查找/预取路径的验证用例可作为行为规范的补充参考。总结async_loading展示了 LMCache v1 在 in-process 模式下如何将查找与加载异步化以 ZMQ 打通调度器与 worker、以分层batched_async_contains 非阻塞批量 get 实现跨后端前缀检索、以加权信号量塑形并发、以事件管理器安全交接 future。尽管该模式现已弃用其分层回源、保守前缀正确性与防死锁并发模型的设计思路仍是理解 LMCache 缓存架构演进、以及迁移到 MP 模式时的重要背景知识。【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价