随着AI Agent的爆发式增长,推理场景正在被推向全新的复杂度:从单轮问答到多轮对话,再到如今的Agent工作流,大模型的交互模式已经截然不同。一个典型的Agent任务(如代码生成、科研分析、自动化办公)往往包含数十次甚至上百次的工具调用、思考回溯和上下文延续。这也给KVCache管理后端带来了三个挑战:
单请求KVCache量级质变:Agent要维持完整对话历史、检索片段和中间推理,上下文动辄128K-1M,单请求 KVCache从MB级飙到GB级,HBM放不下,必须分级卸载到DDR/SSD/超节点池化——后端不再只是“搬运工”,而是要管理一个跨越多级存储的KVCache生命周期,搬运调度本身成为瓶颈。
前缀可复用性急剧上升:多Agent实例共享相同的system prompt、角色设定、知识库前缀,KVCache命中率极高——后端需要从精确匹配走向全局近似匹配,毫秒级定位最长前缀命中的缓存块,以存代算跳过重复prefill。
KVCache生命周期从“被动逐出”变成“主动管理”:Agent上下文内容发生变更时(如subagent执行完成、上下文压缩、工具结果清理等),KVCache需要主动管理——后端无法感知KVCache对应的用户意图及语义结构,简单的 LRU驱逐策略在Agent场景下容易误逐相关性高的 KVCache带来额外KVCache搬运消耗。
关键技术
为了应对上述挑战,昇腾生态联合OpenAtom openEuler(简称 “openEuler”或“开源欧拉”)团队构建了面向超节点的异构融合存储池解决方案 —— LMCache-Ascend。当前LMCache-Ascend已从最初的单节点分级卸载升级为面向超节点的多级存储池,不仅增强了底层池化分离底座的传输性能,还具备了协同记忆体感知调度、CacheGen传输压缩、CacheBlend非前缀复用等稀疏加速能力;
LMCache-Ascend通过多级异构存储池技术,深度释放昇腾全栈算力。在Agent时代,让128K-1M超长上下文推理告别显存瓶颈,TTFT降低30%以上。
Figure.1 整体技术框架:从单机到超节点
KVCache池化能力
我们以KVCache的传输路径作为划分,如下图所示以三级存储为例,展示了KVCache可能的流动方向:
Figure.2 双节点三级存储池数据传输拓扑
AscendP2PBackend是LMCache-Ascend项目中用于实现NPU 间点对点KVCache传输的后端类,继承自基类P2PBackend,主要针对上图中的路径5进行了优化。它支持基于NPU内存的高性能传输,并提供推拉两种传输模式。
传输模式
Push模式
发送方主动将数据推送到接收方预分配的内存中:
接收方预先分配内存并通过buffer_uuids和 mem_indexes告知发送方
发送方通过transfer_channel.async_batched_write直接写入数据
适用于延迟敏感的场景
Pull模式
接收方按需从发送方拉取数据:
发送方在lookup阶段返回remote_buffer_uuids和 remote_mem_indexes,而非实际数据
发送方将内存对象保存在pending_pull_resources中防止过早释放
接收方通过async_batched_read主动读取数据
传输完成后发送Done信号释放发送方资源
Delay Pull模式
Pull模式的优化变体,进一步延迟内存分配:
在lookup阶段不预分配内存,而是创建轻量级的 ProxyMemoryObj
实际数据传输在NPU connector的ping-pong管道中按需进行
通过P2PTransferContext管理传输生命周期和Done信号发送
资源管理与容错
Done信号机制
为防止资源泄漏,系统实现了专门的Done信号处理:
每个节点运行独立的Done信号处理器,绑定到OS分配的端口
接收方在传输完成后发送 AscendBatchedLookupAndGetDoneMsg
发送方收到Done信号后释放pending_pull_resources中的内存对象
TTL清理机制
为处理接收方崩溃等异常情况,实现了TTL清理:
每个pending资源都有时间戳
后台定期扫描过期条目并释放资源
TTL通过p2p_pull_pending_ttl配置,默认360秒
消息处理流程
Lookup请求处理
|
Figure.3 超节点存储池数据请求处理流程
关键消息类型
AscendBatchedLookupAndGetMsg:查找并获取数据的请求消息
AscendBatchedLookupAndGetRetMsg:返回命中数量和远程buffer引用
AscendBatchedLookupAndGetDoneMsg:传输完成信号
AscendQueryDonePortMsg:查询Done端口的请求
配置参数
PD分离传输优化
预填充(Prefill)和解码(Decode)阶段具有不同的计算特征:Prefill是计算密集型,Decode是访存密集型。PD分离将两个阶段部署在不同实例上,可分别优化资源配比。
整体架构:多节点 HBM↔DDR传输拓扑
|
Figure.4 超节点存储池数据传输拓扑图
图例说明:
HBM:昇腾NPU高带宽显存,KVCache生产/消费主存储
DDR:CPU系统内存,用于缓存卸载、异步预取、CPU offloading
HCCL P2P:昇腾集合通信库,支持NPU间直接内存访问(需 HCCS/PCIe 拓扑支持)
虚线:备选/降级传输路径(当P2P不可用时)
四种PD传输模式数据流详解
模式1️⃣:Push Mode(主动推送)
Prefill节点计算完成后主动将KVCache推送给Decode节点,适合确定性调度场景。
|
Figure.5 超节点存储池 Push Mode 时序图
数据流向:Prefill-HBM → HCCL → Decode-HBM(主路径)
触发时机:Prefill 计算完成立即触发
适用场景:批处理任务、确定性调度、低延迟要求
模式2️⃣:Pull-eager Mode(按需立即拉取)
Decode节点在需要KVCache时立即从Prefill节点拉取,适合交互式低延迟场景。
Figure.6 超节点存储池 Pull-eager Mode 时序图
数据流向:Decode-CPU → HCCL → Prefill-HBM → HCCL → Decode-HBM
触发时机:Decode首次需要某层KV时立即拉取
适用场景:在线对话、低延迟交互、动态批处理
模式3️⃣:Pull-delay Mode(延迟批量拉取)
Decode节点延迟批量拉取KVCache,允许Prefill实例聚合多个请求,提升系统吞吐。
|
Figure.7 超节点存储池 Pull-delay Mode 时序图
数据流向:Prefill-HBM(Batch) → HCCL(Batch) → Decode-HBM(Batch)
触发时机:Decode队列达到阈值或超时时批量触发
适用场景:高吞吐批处理、离线推理、资源聚合调度
模式4️⃣:CPU Offloading Pull-delay Mode(DDR中转延迟拉取)
结合CPU内存卸载,KVCache先暂存Prefill节点DDR,再由Decode按需加载,适用于显存受限场景。
Figure.8 超节点存储池 CPU 卸载 Pull-delay Mode 时序图
数据流向:Prefill-HBM → Prefill-DDR → CPU Network → Decode-DDR → Decode-HBM
触发时机:Prefill 完成后异步卸载,Decode需要时按需加载
适用场景:显存受限、边缘部署、大模型长序列推理
四种模式对比总结
不同模式可根据业务场景在延迟和吞吐之间取得平衡,灵活适配各类部署需求。
Agent记忆体协同
Agent场景下,记忆系统需要主动管控KVCache的生命周期——哪些该留、哪些该清、哪些该提前拉回来。LMCache-Ascend通过Internal API Server对外暴露这些管控能力。Internal API Server提供了一条独立于vLLM请求链路,基于FastAPI的轻量级微服务,承载所有KVCache管控接口。主要接口如下,具体接口及功能可以参考:
https://mp.weixin.qq.com/s/T2koq4PjRMARyKx5yQ7yZg
/memory/evict:语义驱动的精细化驱逐
/memory/prefetch:工作流感知预取加载
/memory/L2Cache:L2 Cache 预取和持久化管理(实现中)
测试数据
效果总结:在多租场景,相比传统KVCache调度方式TTFT 时间节省20%~40%
执行任务:terminal-bench2.0评测集任务
评测方式:对比实验基于相同的上下文内容(通过加载同一个session快照实现),由于多租造成请求之间存在其他用户/Agent/Session的推理交互,导致前一轮请求的 KVCache从DDR被驱逐到SSD,并继续完成下一轮请求交互
数据说明:平均TTFT(首Token延迟)时间如下(单位:ms),数据取五轮测试平均值
KVCache的传输压缩:CacheGen
概述
CacheGen是一种KV缓存压缩技术,通过量化 + 算术编码将KV 缓存压缩为比特流,以减少远端存储(Redis、NFS 等)和传输开销。LMCache-Ascend通过monkey-patch替换了上游的编解码函数,使用Ascend专用的PAC(Power-of-2 Aligned Coding)实现。
Patch方式
patchcachegen()将上游的encode_function和 decode_function_gpu替换为 Ascend 实现:
编码流程(pac_encode_function)
使用上游的torch_quant_vectorized对K/V进行量化(这部分沿用LMCache的Python实现)
调用lmc_ops.pac_prepare_enc_metadata在NPU上构建CDF元数据
按256 token为一个chunk,循环调用 lmc_ops.pac_encode生成比特流
解码流程(pac_decode_function)
在独立的NPU stream上,按chunk调用lmc_ops.pac_decode解码,最后reshape还原为 (2, layers, n_tokens, channels)的K/V张量。
与CUDA实现的关键差异
原始CacheGen使用纯算术编码(状态递推,难以向量化)。Ascend实现将CDF边界对齐到2的幂次,使每个符号总是编码为固定比特序列,本质上是Huffman编码的变体。这样牺牲了少量压缩率,换取了NPU向量化友好性。
性能与局限
Cache hit场景:相比重计算快2x,但比naive serde慢约 20%,存储占用仅为naive的1/3.5
Cache miss场景:编码开销显著,瓶颈在上游调用的 RepeatInterleave kernel
解码目前仍是标量核心瓶颈,尚未完全向量化;部分模型下精度损失较大,仍在优化近似算法。
性能数据:Qwen3-30B-A3B模型上,测试32k序列长度,在精度下降0.23%的情况下(84.23%->84.00%,gsm8k flexible-extract 基准测试),完全命中时TTFT降低约40%(4.5s->2.5s)。
Figure.9 CacheGen 特性开启性能优化数据表
精度测试结果:
Figure.10 CacheGen特性开启精度变化数据表
KVCache的非前缀复用:CacheBlend
CacheBlend 是一种KV缓存混合技术:当缓存命中时,不直接使用缓存的KV,而是将其与当前 context 下重新计算的KV进行混合,只对“重要 token”(差异最大的 token)重新计算attention,从而在精度和效率之间取得平衡。
Patch方式
patchcacheblend()替换 LMCBlenderBuilder.get_or_create,使其创建Ascend专用的 LMCBlender实例。
核心类:LMCBlender
LMCBlender在lmcache_ascend/v1/blend/blender.py中实现,核心方法是process_qkv:
从 gpu_connector.get_kv(layer_id)获取缓存的旧KV
对Q/K施加RoPE位置编码
在check_layers上计算diff_k = ||k_new - k_old||²,选出 top-k差异最大的token作为重要token
只对重要token重新计算attention(缩减 attn_metadata)
将重要token的新KV写回旧KV,其余token直接复用缓存
位置编码(positional_encoding.py)
CacheBlend需要对缓存的K做位置重编码(从旧位置变换到新位置)。Ascend实现了两个关键类:
BasicReverseRope:先shuffle再RoPE,实现逆向位置编码(去除旧位置信息)
FusedRope:调用 lmc_ops.rotary_embedding_k_fused一步完成从旧位置到新位置的K变换
注意力后端(attention/attention.py)
提供三种NPU注意力实现:
支持的模型
lmcache_ascend/v1/blend/models/目录下有针对LLaMA和 Qwen3的专用实现:
限制
不支持MLA(Multi-head Latent Attention),与layerwise模式下的MLA互斥
不支持use_layerwise=False时的blending(需要layerwise模式)
精度测试结果:在musique数据集上,llama-3.1-8B模型F1 Score从0.56(全量计算)下降到0.50(开启CacheBlend)。
Figure.11 CacheBlend 特性开启精度变化数据表
分别构造完全不命中、前缀命中3个上下文chunk和乱序命中9个上下文chunk的情况,开启CacheBlend场景下TTFT下降 22.2%(2.02s->1.57)。
Figure.12 CacheBlend 特性开启性能优化数据表
CacheBlend测试指导可至github LMCache-Ascend主仓获取,路径:
LMCache/LMCache-Ascend/blob/main/examples/blending/README.md
其他性能优化
Store Async异步存储机制
概述
Store Async是LMCache-Ascend在AscendLMCacheEngine中实现的异步KV缓存存储路径。核心思想是:将KV缓存从NPU 卸载到CPU/远端存储的操作放到后台线程执行,不阻塞vLLM 的推理主循环。
通过两个配置项控制:
store_async(默认False):是否启用异步存储
store_async_max_queue_size(默认0即无界):任务队列最大深度,>0时启用背压
核心架构
AscendLMCacheEngine继承自上游LMCacheEngine,在其基础上增加了异步存储路径:
关键状态:
存储流程
store()方法:同步/异步分支
store_async=False → 直接调用 _run_store_pipeline()(阻塞)store_async=True → 将任务 put 进 _store_queue(非阻塞)
异步路径下,store()在入队前先将 pendingstore_reqs[req_id]计数 +1,入队失败时回滚计数:
storeworker_loop():后台工作线程
后台线程持续从队列取任务,调用runstore_pipeline()执行实际存储,完成后将 pendingstore_reqs计数 -1 并通知等待方:
runstore_pipeline():实际存储逻辑
同步和异步路径共用,核心步骤:
token_database.process_tokens()计算chunk keys
storage_manager.allocate()分配CPU内存对象
gpu_connector.batched_from_gpu()将KV从NPU拷贝到CPU
storage_manager.batched_put()写入存储后端
与vLLM调度器的集成
LMCacheAscendConnectorV1Impl在 vllm_v1_adapter.py中处理与vLLM的交互:
delay_free机制
request_finished()在异步存储模式下返回delay_free=True,告知vLLM调度器不要立即释放KV blocks,等存储完成后再释放:
get_finished() / get_finished_stores()
调度器周期性调用get_finished()查询哪些请求的存储已完成,完成后才真正释放blocks。get_finished_stores()实现了去重逻辑,防止同一req_id被重复上报:
抢占保护(store-after-free 防护)
当vLLM抢占某个请求时,其KV blocks可能被立即复用。若后台线程仍在读取这些blocks,会发生store-after-free。handle_preemptions()通过wait_for_pending_stores()阻塞等待相关存储完成:
wait_for_pending_stores()使用 Condition变量等待,直到所有相关 req_id 从 pendingstore_reqs中移除。
线程安全设计
enginestate_lock(RLock):lookup()和 lookup_unpin()都持有此锁,与后台 runstore_pipeline()中的batched_put()互斥,防止 lookup读到半写入状态
ThreadSafeEventList:当kv_events_enabled且 store_async同时开启时,用queue.Queue替换普通list作为KV事件缓冲区,避免主线程drain与后台线程append 的数据竞争
整体数据流
Figure.13 Store Async 异步存储机制数据传输时序图
快速上手
环境要求
硬件:Atlas 800I A2 Inference系列。(A3 Inference/Training和300I Duo都是实验性测试。)
操作系统:基于Linux。
软件:
Python: >= 3.10
CANN 工具包:>= 8.2.RC1
昇腾驱动:>= 24.1.0
PyTorch:>= 2.7.1
vLLM:>=v0.11.0&vLLM-Ascend: >=v0.11.0
快速开始
准备基准环境
建议以官方的vLLM-Ascend镜像为基础:
# Pull and run the official vLLM-Ascend imagedocker pull quay.io/ascend/vllm-ascend:v0.18.0-openeulerdocker run -it \--shm-size=200g --privileged --net=host \--cap-add=SYS_RESOURCE \--cap-add=IPC_LOCK \--device=/dev/davinci0 --device=/dev/davinci1 --device=/dev/davinci2 --device=/dev/davinci3 \--device=/dev/davinci4 --device=/dev/davinci5 --device=/dev/davinci6 --device=/dev/davinci7 \--device=/dev/davinci_manager --device=/dev/devmm_svm --device=/dev/hisi_hdc \-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \-v /etc/hccn.conf:/etc/hccn.conf \-v /usr/bin/hccn_tool:/usr/bin/hccn_tool \-v /var/log/npu:/var/log/npu \-v /usr/local/dcmi:/usr/local/dcmi \-v /etc/localtime:/etc/localtime \-v /etc/ascend_install.info:/etc/ascend_install.info \-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \-v /sys/fs/cgroup:/sys/fs/cgroup:ro \-v /usr/src/kernels:/usr/src/kernels:ro \-v /data:/data \--name lmcache-ascend-test \--entrypoint /bin/bash \quay.io/ascend/vllm-ascend:v0.18.0-openeuler
安装LMCache仓库
NO_CUDA_EXT=1 pip install lmcache==0.4.3
编译安装LMCache-Ascend
git clone --recurse-submodules -b v0.4.3 https://github.com/LMCache/LMCache-Ascend.gitcd LMCache-Ascendpip install -v --no-build-isolation -e .
启动vLLM服务时配置connector为LMCache-Ascend
--kv-transfer-config '{"kv_connector":"LMCacheAscendConnector","kv_role":"kv_both"}'
多节点池化部署
使用mooncake作为后端池化
启动mooncake master管理mooncake后端中的kvcache
mooncake_master --port 50088 --eviction_high_watermark_ratio 0.9 --eviction_ratio 0.1
每个节点的lmcache配置文件增加对mooncake的额外配置:
#LMCache Configurationlocal_cpu: Trueremote_url: "mooncakestore://192.0.2.10:50051/"max_local_cpu_size: 50 chunk_size: 256use_layerwise: Falseenable_async_loading: False#Mooncake Configuration (via extra_config)extra_config: use_exists_async: True save_chunk_meta: False local_hostname: "192.0.2.10" metadata_server: "P2PHANDSHAKE" protocol: "ascend" global_segment_size: 60000000000 # 50 GiB per worker master_server_address: "192.0.2.10:50051" local_buffer_size: 0 #10737418240 10G 53687091200 # 50GB mooncake_prefer_local_alloc: true use_ascend_direct: true
#LMCacheConfigurationlocal_cpu: Trueremote_url: "mooncakestore://192.0.2.10:50051/"max_local_cpu_size: 50chunk_size: 256use_layerwise: Falseenable_async_loading: False#MooncakeConfiguration (via extra_config)extra_config:use_exists_async: Truesave_chunk_meta: Falselocal_hostname: "192.0.2.10"metadata_server: "P2PHANDSHAKE"protocol: "ascend"global_segment_size: 60000000000 # 50 GiB per workermaster_server_address: "192.0.2.10:50051"local_buffer_size: 0#1073741824010G 53687091200 # 50GBmooncake_prefer_local_alloc: trueuse_ascend_direct: true
LMCache P2P池化
lmcache使用hccl实现多实例间的KVCache共享,并实现了三种模式:push、Pull-delay和Pull-eager,配置文件示例如下:
chunk_size: 256local_cpu: Truemax_local_cpu_size: 5enable_async_loading: True# P2P configurationsenable_p2p: Truep2p_host: "localhost" # replace with the host ipp2p_init_ports: [9960, 9961] # per TP instancep2p_lookup_ports: [9962, 9963] # per TP instancetransfer_channel: "hccl"p2p_use_npu: Truep2p_pull_mode: Truep2p_delay_pull: True# 128MBp2p_npu_buffer_size: 134217728# Controller configurationsenable_controller: Truelmcache_instance_id: "lmcache_instance_1"controller_pull_url: "localhost:9800" # replace with the controller pull urlcontroller_reply_url: "localhost:9900" # replace with the controller reply urllmcache_worker_ports: [9950, 9951] # per TP instanceextra_config:lookup_backoff_time: 0.001
PD分离部署
lmcache通过hccl实现prefill decoder阶段分离,并同样支持 push、Pull-eager和Pull-delay三种传输模式
P节点配置:
local_cpu: False # Enable local CPU RAM cache for fast accessenable_pd: Truetransfer_channel: "hccl"pd_role: "sender"pd_pull_mode: Falsepd_delay_pull: Falsepd_pull_done_port: [7600, 7601] # per TP instancepd_use_cpu_offload: Falsepd_cpu_buffer_size: 21474836480 # 20GBpd_peer_host: "localhost" # replace with the host ippd_proxy_host: "localhost" # replace with the proxy ippd_proxy_port: 7500 # replace with the proxy portpd_buffer_size: 2415919104 # Size of the buffer for fetching data from the peer (in bytes)pd_buffer_device: "npu" # Use NPU memory for buffer# For LMCache 0.3.12, we see that for small prompt# prefill would not send the unfull chunk to the decoder.# setting this to true will ensure the pd transfer occurs even for small prompts.save_unfull_chunk: True # Save unfull chunk to disk,
D节点配置:
local_cpu: False # Enable local CPU RAM cache for fast accessenable_pd: Truetransfer_channel: "hcomm_onesided"pd_role: "receiver"pd_peer_host: "localhost" # replace with the host ippd_pull_mode: Falsepd_delay_pull: False # Enable delayed pulling of data from the peerpd_peer_init_port: [7300, 7301] # Need to have 1 for each TP instancepd_peer_alloc_port: [7400, 7401] # Need to have 1 for each TP instancepd_buffer_size: 134217728 # Size of the buffer for fetching data from the peer (in bytes)pd_buffer_device: "npu" # Use npu memory for buffering
主流模型支持
性能数据
我们在内部基于910B部署的推理集群上结合SSD直通已达成一定的性能优化:
Qwen3.5上TTFT降低30%(68.325s->46.492s,平均 28k序列,60并发,A2单机);
GLM5上TTFT也降低约30%(195.8s->122.0s,平均25k 序列,60并发,A2双机)。
下一步展望
回顾过去一年的技术演进,我们可以清晰地看到一条主线:LMCache-Ascend正从被动搬运KVCache的“存储卸载工具”,进化为主动理解数据、智能调度资源的“语义感知缓存系统”。
池化打破了单机边界,让大规模集群受益。
SSD直通颠覆了成本公式,让TB级缓存成为可能。
语义感知赋予了缓存“智能”,让有限的存储承载最值得保留的数据。
稀疏与压缩双管齐下,同时减轻了计算和传输的压力。
这些能力并非孤立叠加,而是协同作用。例如:语义感知识别出的高频前缀,会被优先保存在 HBM 中并跳过稀疏化;低频的长尾内容则被压缩后存入 SSD,并通过 P2P 传输共享给需要的节点。
未来,我们将继续沿着以下方向深化:
进一步探索昇腾亲和的底层传输加速手段,支持PD分离和 P2P池化方案共同部署。
针对DSA等原生稀疏模型,将不使用的KVCache卸载到 DDR等外部存储,提升推理整体吞吐。
结合网关的KVCache亲和调度,在满足SLO及负载均衡的约束下尽可能调度到KVCache所在的节点,减少底层KVCache传输。
跨请求复用DiTCache,拓展LMCache在多模态场景上的应用。
支持LMCache社区的MP Mode,按照独立进程运行 LMCache,提升多实例场景下性能及可靠性。
探索更激进的KVCache量化压缩:包括int8、int4 甚至更加极致的量化,减少传输量。
欢迎广大开发者移步LMCache-Ascend开源社区,一同参与生态共建:
算子仓(KV OPS):
https://gitcode.com/openeuler/kvcache-ops
LMCache-Ascend:
https://github.com/LMCache/LMCache-Ascend
对项目感兴趣,进行更多技术交流,欢迎扫描下方二维码加入技术交流群:


