前言
大语言模型推理系统正在经历从单体部署向分布式解耦架构的演进。Prefill-Decode(PD)分离架构将推理过程拆分为预填充和解码两个阶段,分别由不同计算节点承担,从而提升整体吞吐和资源利用率。这种架构的核心挑战在于跨节点KV Cache的高效传输——Prefill节点生成的KV Cache需要低延迟、高带宽地传输到Decode节点,任何通信瓶颈都会直接拖累端到端推理性能。
昇腾CANN提供了HiXL(Huawei Xfer Library)单边通信库,通过零拷贝(Zero-Copy)机制和单边通信(One-Sided Communication)能力,为PD分离架构提供了高效的跨节点数据传输方案。HiXL在昇腾NPU之间建立直接内存访问通道,避免传统通信模式中多次数据拷贝和CPU介入带来的延迟开销,充分释放底层高速互联链路(HCCS、RDMA)的带宽潜力。
PD分离架构的通信痛点
传统通信模式的开销来源
在典型的PD分离部署中,Prefill节点负责处理用户输入的Prompt,计算并缓存Key-Value向量;Decode节点负责逐Token生成输出,需要从Prefill节点拉取对应的KV Cache。这一过程涉及跨节点的内存数据传输,传统实现方式存在多个性能瓶颈。
基于Socket或gRPC的通信方案需要在发送端和接收端各进行一次内存拷贝:应用层Buffer到内核Socket Buffer,再从内核Socket Buffer到应用层Buffer。每次拷贝不仅消耗内存带宽,还引入额外的延迟。当KV Cache规模达到百MB级别时,这种开销变得不可忽视。
即使使用RDMA的send/recv语义,仍然需要在目标端准备接收Buffer并显式发起接收操作。这意味着Decode节点的CPU必须主动参与每次传输的生命周期管理,无法与NPU的计算任务形成有效的流水线重叠。
单边通信的技术优势
单边通信(One-Sided Communication)允许源端在无需对端CPU介入的情况下完成数据传输。发送端准备好数据后,直接写入接收端已预注册的内存区域,接收端无需执行任何recv类操作即可在本地内存中看到完整数据。这种模式将通信的主导权完全交给源端,目标端的计算任务可以持续运行,仅在需要时直接读取已就绪的远程写入数据。
零拷贝(Zero-Copy)进一步消除传输路径上的冗余数据移动。HiXL通过内存注册(Memory Registration)机制,使发送端NPU能够直接访问接收端NPU或Host的物理内存,数据从源Buffer经过网络链路直接写入目标Buffer,全程不经过中间临时Buffer。这不仅降低了内存带宽消耗,也减少了内存容量压力——在KV Cache规模持续增长的背景下,每一分内存节省都具有实际价值。
HiXL核心架构与关键概念
三层设计
HiXL采用分层架构设计,从底层到上层依次为:HiXL Engine、HiXL CS(Communication Service)、LLM-DataDist。每一层面向不同的使用场景,提供逐步抽象的接口。
HiXL Engine是底层传输引擎,提供最基础的点对点数据传输接口。它直接操作各类内存类型(Device内存、Host内存)和传输链路(HCCS、RDMA),支持D2D(Device-to-Device)、D2H(Device-to-Host)、H2D(Host-to-Device)等传输方向。Engine层接口最为灵活,适合需要精细控制传输行为的场景。
HiXL CS在Engine之上提供了面向通信服务的抽象,支持更复杂的通信模式和多链路聚合。该接口目前仅支持Ascend 950PR和Ascend 950DT超节点场景,利用UB(Unified Bus)协议实现超节点内高带宽传输。
LLM-DataDist是最上层的语义接口,携带KV Cache的业务语义,直接与vLLM、SGLang等推理引擎对接。它屏蔽了底层传输的细节,推理引擎通过简洁的接口完成KV Cache的发布和订阅,无需关心数据传输的具体实现。
内存模型与传输语义
HiXL的内存模型区分本地内存和远程内存。本地内存是发起传输的进程所拥有的内存区域,远程内存是目标进程预注册可供远程访问的内存区域。所有参与传输的内存区域都必须通过HiXL的内存注册接口进行注册,以获取全局唯一的内存句柄(Memory Handle)。
传输语义分为同步和异步两种模式。同步传输在数据写入远程内存后返回,调用线程阻塞直至传输完成。异步传输立即返回传输句柄,调用线程可以继续执行其他任务,通过轮询或回调方式获知传输完成事件。异步模式是实现通信-计算重叠的关键——Prefill节点可以在启动KV Cache传输的同时开始处理下一个请求,从而隐藏通信延迟。
FabricMem是HiXL在Atlas A3超节点上提供的增强模式。它基于CANN的Virtual Memory Manager机制,将超节点内所有计算节点的DRAM统一编址,NPU可以通过HCCS高速链路直接访问远程节点的内存,无需CPU介入。在128M数据传输场景下,HCCS链路的带宽可达119GB/s,RDMA链路可达22GB/s。
实战:基于HiXL构建PD分离通信层
环境准备与编译安装
在开始编写代码之前,需要确保HiXL库已正确编译并安装到CANN软件路径下。HiXL的编译依赖HDK、CANN和灵衢计算网络组件,版本要求如下:HDK 25.5以上,CANN 9.0以上,灵衢计算网络1.5.0以上。
# 克隆HiXL仓库gitclone https://atomgit.com/cann/hixl.gitcdhixl# 配置CANN安装路径(根据实际安装位置调整)exportASCEND_HOME=/usr/local/Ascend/cann# 执行编译脚本bashbuild.sh# 编译成功后,库文件位于 build/output 目录# 头文件路径:include/hixl/hixl.h# 库文件路径:build/output/libcann_hixl.soHiXL使用CMake构建系统,build.sh封装了完整的编译流程,包括依赖检查、编译选项配置和安装包生成。编译完成后,头文件hixl.h包含所有Engine层接口的声明,动态库libcann_hixl.so在运行时被链接。将ASCEND_HOME指向正确的CANN安装路径是确保编译找到正确依赖的关键步骤。
初始化HiXL传输引擎
任何HiXL程序的起点是初始化传输引擎。引擎初始化时需要配置本端和远端的通信地址、使用的传输链路类型,以及可选的高级参数(如FabricMem开关、内存池容量等)。
#include"hixl/hixl.h"#include<iostream>intmain(){// 创建HiXL引擎配置HixlConfig config=nullptr;hixlCreateConfig(&config);// 设置本端IP和端口hixlConfigSetString(config,HIXL_OPTION_LOCAL_IP,"192.168.1.10");hixlConfigSetInt(config,HIXL_OPTION_LOCAL_PORT,18000);// 设置远端IP和端口(Prefill节点连接Decode节点)hixlConfigSetString(config,HIXL_OPTION_REMOTE_IP,"192.168.1.20");hixlConfigSetInt(config,HIXL_OPTION_REMOTE_PORT,18000);// 使用HCCS链路(超节点内);跨节点使用RDMAhixlConfigSetString(config,HIXL_OPTION_LINK_TYPE,"HCCS");// 启用FabricMem模式(仅Atlas A3)hixlConfigSetString(config,HIXL_OPTION_ENABLE_USE_FABRIC_MEM,"1");// 创建引擎实例HixlEngine engine=nullptr;HixlStatus status=hixlCreateEngine(config,&engine);if(status!=HIXL_SUCCESS){std::cerr<<"Failed to create engine: "<<hixlGetErrorString(status)<<std::endl;return-1;}std::cout<<"HiXL engine initialized successfully"<<std::endl;// ... 传输操作 ...// 销毁引擎hixlDestroyEngine(engine);hixlDestroyConfig(config);return0;}hixlCreateConfig创建一个配置对象,通过hixlConfigSetString和hixlConfigSetInt设置引擎参数。LOCAL_IP/PORT和REMOTE_IP/PORT定义了通信端点的网络地址。LINK_TYPE选择传输链路:HCCS用于超节点内高速互联,RDMA用于跨节点标准网络。ENABLE_USE_FABRIC_MEM开启FabricMem模式后,引擎将使用统一内存编址,允许直接访问远端Host内存,这对PD分离场景中Decode节点直接读取Prefill节点的KV Cache至关重要。
内存注册与数据传输
PD分离的核心数据流是Prefill节点将KV Cache写入Decode节点的预注册内存区域。以下代码展示完整的数据发送流程。
// Prefill节点:发送KV Cache到Decode节点// 1. 在Prefill节点本地申请Device内存(KV Cache所在位置)void*local_kv_cache=nullptr;size_t kv_cache_size=128*1024*1024;// 128MBaclrtMalloc(&local_kv_cache,kv_cache_size,ACL_MEM_MALLOC_HUGE_FIRST);// 2. 获取Decode节点远程内存的句柄(通过控制面协商得到)// 实际部署中,Decode节点调用hixlRegisterMemory获取handle,通过控制面传给Prefill节点HixlMemHandle remote_mem_handle=/* 从控制面获取 */;// 3. 将本地KV Cache传输到远程内存(单边零拷贝写入)HixlRequest request=nullptr;status=hixlWriteRemote(engine,local_kv_cache,// 本地源地址kv_cache_size,// 传输大小remote_mem_handle,// 远程内存句柄0,// 远程偏移量&request// 返回的请求句柄);if(status!=HIXL_SUCCESS){std::cerr<<"hixlWriteRemote failed: "<<hixlGetErrorString(status)<<std::endl;return-1;}// 4. 异步模式下,立即返回可做其他工作,需要时查询完成状态// hixlWaitRequest阻塞等待完成;hixlPollRequest非阻塞查询hixlWaitRequest(engine,request,HIXL_WAIT_FOREVER);std::cout<<"KV Cache transferred successfully via zero-copy write"<<std::endl;// 5. 释放资源aclrtFree(local_kv_cache);hixlDestroyRequest(request);aclrtMalloc在昇腾NPU的Device内存上分配KV Cache缓冲区。HiXL的零拷贝写入通过hixlWriteRemote实现:发送端NPU直接将数据写入接收端预注册的内存区域,全过程无需接收端CPU参与。HixlMemHandle是内存区域的全局标识,包含目标内存的物理地址信息,由接收端通过hixlRegisterMemory注册后获得,并通过控制面(如Redis、etcd或gRPC)分发给发送端。hixlWriteRemote支持异步执行,返回HixlRequest句柄用于后续完成查询,这使得Prefill节点可以在传输进行的同时继续处理其他请求,实现通信与计算的流水线重叠。
Decode节点:内存注册与数据消费
Decode节点需要预注册一块内存区域供Prefill节点写入,并在写入完成后直接读取该区域获取KV Cache。
// Decode节点:注册远程可写内存区,接收Prefill节点的KV Cache// 1. 在Decode节点申请Host或Device内存作为KV Cache接收缓冲区void*recv_buffer=nullptr;size_t recv_size=128*1024*1024;// 使用Host内存(DRAM)作为接收缓冲区,支持更大的缓存容量// Atlas A3上FabricMem模式支持D2RH(Device-to-Remote-Host)aclrtMallocHost(&recv_buffer,recv_size);// 2. 注册内存区域,获取内存句柄供Prefill节点使用HixlMemHandle mem_handle=nullptr;status=hixlRegisterMemory(engine,recv_buffer,recv_size,HIXL_MEM_PERM_WRITE,// 允许远程写入&mem_handle);if(status!=HIXL_SUCCESS){std::cerr<<"hixlRegisterMemory failed: "<<hixlGetErrorString(status)<<std::endl;return-1;}// 3. 将mem_handle通过控制面发送给Prefill节点// 实际代码中通过gRPC/Redis等控制面组件分发// std::string handle_str = hixlMemHandleSerialize(mem_handle);// control_plane->distributeHandle(handle_str);std::cout<<"Memory registered, handle distributed to Prefill nodes"<<std::endl;// 4. 等待Prefill节点写入完成(通过通知机制或轮询完成标记)// 实际部署中可使用HiXL的完成通知机制wait_for_transfer_complete();// 5. 读取接收到的KV Cache(无需拷贝,直接访问)// recv_buffer中已包含Prefill节点写入的KV Cache数据KVCacheHeader*header=static_cast<KVCacheHeader*>(recv_buffer);std::cout<<"Received KV Cache: seq_len="<<header->seq_len<<", num_layers="<<header->num_layers<<std::endl;// 6. 将KV Cache注入本地推理引擎的解码过程inject_kv_cache_to_decoder(recv_buffer,recv_size);// 7. 使用完成后注销内存注册并释放hixlUnregisterMemory(engine,mem_handle);aclrtFreeHost(recv_buffer);Decode节点调用aclrtMallocHost在Host侧分配接收缓冲区,这是因为Host DRAM容量通常远大于Device显存,可以缓存更多的KV Cache。hixlRegisterMemory将这块内存注册到HiXL引擎,生成HixlMemHandle,该句柄编码了内存的物理地址和访问权限(WRITE/READ)。HIXL_MEM_PERM_WRITE标志允许远程节点向该区域写入数据。注册完成后,内存句柄需要通过控制面分发给所有潜在的发送节点。Decode节点在消费数据时直接读取recv_buffer,无需额外的接收拷贝操作——这是零拷贝通信的第二层含义:接收端消费数据时也无需从内核Buffer或网络Buffer拷贝到应用Buffer。
异步流水线:通信计算重叠
PD分离架构的性能关键在于隐藏通信延迟。以下示例展示如何利用HiXL的异步接口实现Prefill阶段与KV Cache传输的流水线执行。
// 异步传输实现通信-计算重叠classPDPrefillWorker{private:HixlEngine engine_;std::vector<HixlRequest>inflight_requests_;public:voidprocess_request(constInferenceRequest&req){// 阶段1:Prefill计算(生成KV Cache)KVCache kv_cache=run_prefill(req.input_tokens);// 阶段2:启动异步传输(不等待完成)HixlRequest req_handle=nullptr;HixlMemHandle remote_handle=get_remote_handle_for_request(req.request_id);HixlStatus status=hixlWriteRemoteAsync(engine_,kv_cache.data(),kv_cache.size(),remote_handle,0,&req_handle);if(status==HIXL_SUCCESS){inflight_requests_.push_back(req_handle);}// 阶段3:立即继续处理下一个请求的Prefill// 传输在后台进行,与下一个请求的计算并行}voidpoll_completions(){// 周期性轮询完成状态,释放已完成请求的资源autoit=inflight_requests_.begin();while(it!=inflight_requests_.end()){HixlStatus poll_status=hixlPollRequest(engine_,*it);if(poll_status==HIXL_SUCCESS){// 传输完成,通知Decode节点可以开始Decodenotify_decode_complete((*it)->request_id);hixlDestroyRequest(*it);it=inflight_requests_.erase(it);}else{++it;}}}};hixlWriteRemoteAsync是非阻塞版本的数据传输接口,调用后立即返回,传输在后台继续执行。通过将传输过程与后续请求的计算过程并行化,有效隐藏了跨节点通信延迟。在实际部署中,这通常可以将通信开销从关键路径上完全移除——当第N个请求还在传输KV Cache时,第N+1个请求的Prefill计算已经在进行。poll_completions周期性检查传输完成状态,完成后通过通知机制告知Decode节点启动对应请求的解码过程。这种生产者-消费者模式是PD分离架构中的标准范式。
对接推理引擎:LLM-DataDist接口
vLLM集成路径
HiXL通过LLM-DataDist层提供与vLLM的直接集成。vLLM在Prefill节点生成KV Cache后,通过LLM-DataDist接口将Cache发布到HiXL传输层;Decode节点的vLLM通过对应接口订阅并获取KV Cache,注入本地解码过程。
LLM-DataDist接口的设计目标是让推理引擎开发者无需理解底层传输细节。接口核心概念包括:KVCachePublisher(发布端)、KVCacheSubscriber(订阅端)、CacheDescriptor(Cache描述符,包含形状、数据类型、存储位置等元数据)。
# Decode节点:使用LLM-DataDist Python接口订阅KV Cacheimportllm_datadistasldd# 初始化订阅端subscriber=ldd.KVCacheSubscriber()subscriber.init(rank_id=0,local_ip="192.168.1.20",local_port=18000)# 注册本地接收缓冲区recv_buffer=ldd.allocate_host_memory(128*1024*1024)# 128MBsubscriber.register_buffer(recv_buffer)# 订阅来自特定Prefill节点的KV Cache# 通过Cache ID建立发布-订阅映射cache_id=ldd.CacheId(prompt_hash="abc123",seq_len=512)result=subscriber.subscribe(cache_id,timeout_ms=5000)ifresult.status==ldd.SUBSCRIBE_STATUS_SUCCESS:# 直接访问接收到的KV Cache,无需拷贝kv_cache_ptr=subscriber.get_cache_data(cache_id)print(f"Received KV Cache with{result.seq_len}tokens")# 注入vLLM的解码器model.executor.add_kv_cache(cache_id,kv_cache_ptr)Python接口降低了与vLLM等Python原生推理引擎的集成门槛。KVCacheSubscriber封装了HiXL Engine的初始化、内存注册、传输等待等底层操作。CacheId通过Prompt的哈希值和序列长度唯一标识一份KV Cache,使Decode节点能够准确匹配需要订阅的Cache。allocate_host_memory内部调用aclrtMallocHost,确保分配的内存可被HiXL用于远程写入。整个流程对vLLM透明——vLLM只需要处理标准的KV Cache数据结构,传输细节完全由LLM-DataDist处理。
使用前vs使用后
使用前的通信路径
在未使用HiXL的传统PD分离方案中,KV Cache的跨节点传输通常依赖以下路径之一:
基于gRPC/socket的传输:Prefill节点将KV Cache从Device内存拷贝到Host内存,序列化为字节流,通过TCP/gRPC发送到Decode节点,Decode节点接收后反序列化,再拷贝到Device内存。整个路径涉及至少3次数据拷贝(D2H、H2N、N2H、H2D中的多个阶段),每次拷贝消耗内存带宽和延迟。在128MB KV Cache场景下,仅拷贝开销就可能达到毫秒级。
基于传统RDMA的传输:虽然RDMA可以减少拷贝次数,但标准的send/recv语义仍然需要Decode节点的CPU主动参与接收操作。此外,RDMA内存注册的区域通常较小,大规模KV Cache需要分片传输,增加了调度复杂度。CPU介入导致Decode节点的计算资源被通信任务占用,降低了有效算力。
基于NCCL等集合通信库的传输:NCCL设计用于多GPU协同计算场景,不适合PD分离这种非对称的Producer-Consumer模式。NCCL需要所有参与节点协同调用,无法由Prefill节点独立主导传输。
使用后的通信路径
引入HiXL后,通信路径得到显著简化:
零拷贝单边写入:Prefill节点的NPU直接将数据写入Decode节点预注册的Device或Host内存,全程无需Decode节点CPU参与。数据从发送端Device内存经网络链路直接写入接收端内存,中间无任何临时Buffer。对于Decode节点,接收到的数据已经位于可直接用于解码计算的内存位置,无需额外整理或拷贝。
异步流水线与细粒度重叠:HiXL的异步传输接口允许Prefill节点在启动传输后立即切换至下一个请求的处理,通信延迟完全被后续请求的计算掩盖。实测表明,在Atlas A3超节点内部署时,这种重叠可以将端到端推理延迟降低20%以上。
统一内存编址(FabricMem):在Atlas A3超节点内,FabricMem模式将各节点的Host DRAM统一编址,Prefill节点的NPU可以直接访问Decode节点的Host内存(D2RH),无需经过Decode节点NPU的转发。这种直连路径的带宽达到119GB/s(HCCS链路),相比之下传统RoCE方案的带宽仅为20GB/s左右。
与推理引擎的无缝集成:通过LLM-DataDist接口,vLLM、SGLang等推理引擎可以以最小化代码改动接入HiXL传输层。开发者无需重写通信模块,只需在引擎初始化时配置KV Cache的发布/订阅策略即可。
性能优化实践要点
传输链路选择策略
HiXL支持多种传输链路,正确选择链路类型对性能至关重要。超节点内的节点间通信应优先使用HCCS链路,其带宽显著高于RDMA。跨机架或跨服务器的通信则必须使用RDMA链路。在混合部署场景中,可以通过HiXL的多链路支持同时配置多种链路,由HiXL引擎根据目标地址自动选择最优路径。
FabricMem模式仅适用于Atlas A3系列,开启后自动启用统一内存编址。如果部署环境包含A2和A3混合节点,需要关闭FabricMem以确保兼容性。HiXL支持A2/A3/A5异构互联,但部分高级特性仅在同代际节点间可用。
内存注册粒度
内存注册的粒度影响传输效率和管理开销。过小的注册粒度导致每个KV Cache传输都需要多次注册/注销操作,增加控制面开销;过大的注册粒度则造成内存浪费。推荐的实践是为每个Decode节点预注册若干固定大小的内存池(如每个池128MB),通过内存池内的偏移量管理多个KV Cache的存储位置。HiXL的OPTION_GLOBAL_RESOURCE_CONFIG参数可以配置Fabric虚拟内存池的容量和起始地址,建议在部署规划阶段根据典型KV Cache大小进行合理配置。
异步传输的完成通知
大规模部署中,Decode节点需要同时处理来自多个Prefill节点的KV Cache传输请求。使用轮询方式检查传输完成状态会消耗CPU资源,推荐的做法是结合HiXL的完成通知机制(Completion Notification),在传输完成后由HiXL主动通知目标节点。这需要在初始化引擎时配置通知通道,并在Decode节点注册回调函数处理传输完成事件。
总结
HiXL为昇腾CANN生态提供了生产级的单边零拷贝通信能力,其在PD分离架构中的价值可以归纳为三个维度:性能维度上,零拷贝和单边通信消除了传统传输路径上的冗余拷贝和CPU介入,FabricMem模式进一步将超节点内传输带宽提升至百GB/s级别;工程维度上,分层接口设计允许开发者根据场景选择合适的抽象层级,LLM-DataDist接口使主流推理引擎的集成成本降至最低;生态维度上,HiXL已对接Mooncake、DeepLink、vLLM、SGLang等开源项目,形成了从底层传输到上层应用的完整技术栈。
仓库地址:https://atomgit.com/cann/hixl