大数跨境

LMCache-Ascend超节点异构融合存储池

LMCache-Ascend超节点异构融合存储池 昇腾AI开发者
2026-06-18
9
导读:超节点异构融合存储池,从单机分级卸载到超节点池化


随着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 \-/usr/local/bin/npu-smi:/usr/local/bin/npu-smi \-/etc/hccn.conf:/etc/hccn.conf \-/usr/bin/hccn_tool:/usr/bin/hccn_tool \-/var/log/npu:/var/log/npu \-/usr/local/dcmi:/usr/local/dcmi \-/etc/localtime:/etc/localtime \-/etc/ascend_install.info:/etc/ascend_install.info \-/usr/local/Ascend/driver:/usr/local/Ascend/driver \-/sys/fs/cgroup:/sys/fs/cgroup:ro \-/usr/src/kernels:/usr/src/kernels:ro \-/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



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: [99609961# per TP instancep2p_lookup_ports: [99629963# 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: [99509951# 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: [76007601# 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: [73007301# Need to have 1 for each TP instancepd_peer_alloc_port: [74007401# 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

对项目感兴趣,进行更多技术交流,欢迎扫描下方二维码加入技术交流群:





【声明】内容源于网络
0
0
昇腾AI开发者
昇腾社区
内容 968
粉丝 0
昇腾AI开发者 昇腾社区
总阅读4.4k
粉丝0
内容968