hixl:昇腾单边通信库,PD分离推理的隐藏拼图
前言
PD分离推理是2024-2025年大模型推理的新范式——把Prefill(首token计算,计算密集)和Decode(后续token生成,访存密集)拆到不同节点上跑,各取所长。但Prefill算完的KV Cache要传给Decode节点,如果用hccl做AllGather,延迟高、带宽浪费大。
hixl就是解决这个问题的——单边通信,Prefill节点直接RDMA写Decode节点的显存,零拷贝,KV Cache传输延迟从12ms降到1.8ms。
这篇文章不是hixl的API手册,是我实际做PD分离推理时对"为什么需要hixl"这个问题的理解。
你需要了解hixl吗?
先做个角色分层:
- 模型推理工程师:需要。你在做PD分离方案,hixl是核心通信组件
- 算子开发者:不一定。除非你要写需要跨节点零拷贝的算子
- 应用开发者:不需要。hccl已经够用了
- 分布式训练工程师:不需要。训练用hccl的AllReduce/AllGather
一句话总结:搞PD分离才需要hixl,搞数据并行用hccl就行。
PD分离推理:为什么传统通信方案不行?
先说清楚PD分离的场景。大模型推理(比如Llama-3-70B)有两个阶段:
- Prefill阶段:输入一个长prompt(比如4096个token),一次性算出所有token的KV Cache。这个阶段是计算密集的,GPU/NPU利用率高。
- Decode阶段:基于KV Cache逐个生成后续token。这个阶段是访存密集的,GPU/NPU利用率低(每次只算1个token,但要读全部KV Cache)。
PD分离的核心思路:让Prefill节点和Decode节点各干自己擅长的事。但问题是——Prefill节点算出来的KV Cache(可能有好几GB),怎么传给Decode节点?
方案一:hccl AllGather(传统方案)
Prefill节点算完KV Cache → hccl AllGather(所有节点拿到所有KV Cache) → 每个Decode节点都有完整KV Cache → 开始Decode问题:
- AllGather是集合通信,所有节点参与,即使某些节点不需要这些数据
- KV Cache要经过CPU中转(hccl内部实现),三次拷贝:NPU HBM → CPU内存 → 网卡 → 对端CPU内存 → 对端NPU HBM
- 延迟高:4卡AllGather传2GB KV Cache,约12ms
方案二:hixl单边通信(优化方案)
Prefill节点算完KV Cache → hixl RDMA Write(直接写Decode节点的NPU HBM) → Decode节点收到KV Cache → 开始Decode优势:
- RDMA Write是点对点单边通信,只有发送方和接收方参与
- 零拷贝:NPU HBM → RDMA网卡 → 对端NPU HBM,不经过CPU
- 延迟低:4卡RDMA Write传2GB KV Cache,约1.8ms
hixl vs hccl:选型指南
| 维度 | hccl | hixl |
|---|---|---|
| 通信模式 | 集合通信(AllReduce/AllGather/Broadcast) | 单边通信(RDMA Write/Read) |
| 适用场景 | 数据并行训练、张量并行 | PD分离推理、零拷贝跨节点访问 |
| CPU参与 | 是(内部需要CPU中转) | 否(RDMA旁路CPU) |
| 延迟 | 高(毫秒级) | 低(亚毫秒级) |
| 带宽利用率 | 中(集合通信有额外开销) | 高(点对点,利用率接近物理带宽) |
| API复杂度 | 低(一个AllReduce调用搞定) | 中(需要手动管理内存注册和地址交换) |
| 依赖 | CANN Runtime | CANN Runtime + RDMA网卡 |
选择原则:如果你是"一人发,多人收"的广播式通信,用hccl;如果你是"A直接写B的显存"的点对点通信,用hixl。
hixl在PD分离中的完整工作流
┌─────────────────┐ ┌─────────────────┐ │ Prefill 节点 │ │ Decode 节点 │ │ │ │ │ │ 1. 注册HBM区域 │ │ 1. 注册HBM区域 │ │ (ibv_reg_mr) │ │ (ibv_reg_mr) │ │ │ │ │ │ 2. 交换地址信息 │◄──── RDMA ────►│ 2. 交换地址信息 │ │ (rkey+addr) │ │ (rkey+addr) │ │ │ │ │ │ 3. 算KV Cache │ │ │ │ │ │ │ │ 4. RDMA Write │──────────────────►│ 3. 收到KV Cache │ │ 零拷贝传输 │ │ 直接在HBM中 │ │ │ │ │ │ │ │ 4. 开始Decode │ └─────────────────┘ └─────────────────┘步骤说明:
- 注册HBM区域:两端的NPU HBM都要注册为RDMA可见(
ibv_reg_mr),这样RDMA网卡才能直接读写 - 交换地址信息:两端交换注册后的
rkey(远程密钥)和addr(远程地址),这样发送方才知道往哪写 - 算KV Cache:Prefill节点正常计算
- RDMA Write:Prefill节点直接把KV Cache写到Decode节点的HBM中,零拷贝
代码示例:用hixl做零拷贝KV Cache传输
importtorchimporthixl# ============ Decode 节点(先启动,等待KV Cache) ============# 1. 分配接收KV Cache的HBM区域kv_buffer=torch.empty(num_layers*2*batch*seq_len*head_dim,dtype=torch.float16,device="npu:0")# 2. 注册HBM区域为RDMA可见mr=hixl.register_memory(kv_buffer)# 3. 把mr.rkey和mr.addr发给Prefill节点(通过hccl或其他方式)decode_addr_info={"rkey":mr.rkey,"addr":mr.addr}# send to prefill node...# 4. 等待KV Cache到达(hixl提供了通知机制)hixl.wait(mr)# 阻塞直到RDMA Write完成# 5. KV Cache已经在kv_buffer里了,直接用# 开始Decode...# ============ Prefill 节点(算完KV Cache后发送) ============# 1. 算KV Cachekv_cache=model.prefill(input_ids)# shape: [num_layers, 2, batch, seq_len, head_dim]# 2. 注册KV Cache的HBM区域mr=hixl.register_memory(kv_cache)# 3. 从Decode节点获取地址信息# decode_addr_info = receive from decode node...# 4. RDMA Write:零拷贝传输KV Cache到Decode节点hixl.rdma_write(src_mr=mr,# 本地内存区域dst_rkey=decode_addr_info["rkey"],# 远程密钥dst_addr=decode_addr_info["addr"],# 远程地址length=kv_cache.numel()*2,# 传输字节数(FP16=2字节))# 5. 等待RDMA Write完成hixl.flush(mr)关键点:
hixl.register_memory()把NPU HBM注册为RDMA可见,这一步有一次性开销(~10ms)hixl.rdma_write()是零拷贝的,数据从NPU HBM直接到RDMA网卡,不经过CPUhixl.wait()和hixl.flush()是同步机制,确保数据传输完成
性能数据
PD分离场景下,KV Cache传输性能对比(2GB KV Cache,4个Decode节点):
| 方案 | 传输延迟 (ms) | CPU占用 | 带宽利用率 |
|---|---|---|---|
| hccl AllGather | 12.3 | 15%(CPU中转) | 65% |
| Socket + NCCL Send | 8.7 | 10%(CPU拷贝) | 72% |
| hixl RDMA Write | 1.8 | <1%(CPU不参与) | 94% |
hixl比hccl快6.8倍,CPU占用几乎为零。
对端到端推理延迟的影响(Llama-3-70B,seq_len=4096):
| 方案 | Prefill (ms) | KV传输 (ms) | Decode首token (ms) | 总延迟 (ms) |
|---|---|---|---|---|
| 无PD分离 | 45 | 0 | 45 | 90 |
| PD分离 + hccl | 45 | 12.3 | 28 | 85.3 |
| PD分离 + hixl | 45 | 1.8 | 28 | 74.8 |
PD分离 + hixl比无PD分离快17%,比PD分离 + hccl快12%。差距会随模型规模和序列长度增大而增大。
踩坑实录
坑1:hixl依赖RDMA网卡
问题:在只有以太网的环境下运行hixl,报hixl::RdmaInit failed: no IB device found。
原因:hixl依赖RDMA(InfiniBand或RoCEv2),普通以太网卡不支持RDMA。
解决方案:
- 方案A:加装Mellanox/Broadcom RDMA网卡(支持RoCEv2)
- 方案B:用SoftRoCE(软件模拟RDMA,性能差但能跑通测试)
- 方案C:不用hixl,退回到hccl AllGather
坑2:内存必须注册为RDMA可见
问题:直接传未注册的tensor,报hixl::RdmaWrite failed: memory not registered。
原因:RDMA网卡只能读写已注册的内存区域(ibv_reg_mr),未注册的内存RDMA网卡没有访问权限。
解决方案:所有参与RDMA传输的tensor都要先hixl.register_memory():
# ❌ 错误写法(未注册)kv=torch.empty(...,device="npu:0")hixl.rdma_write(kv,...)# 报错!# ✅ 正确写法(先注册)kv=torch.empty(...,device="npu:0")mr=hixl.register_memory(kv)# 注册hixl.rdma_write(mr,...)# 正常坑3:RDMA Write是异步的,不等待就看不到数据
问题:Prefill节点调了hixl.rdma_write(),但Decode节点读到的KV Cache全是0。
原因:RDMA Write是异步的,调用返回不代表数据已经写完。Decode节点要等通知。
解决方案:Prefill端用hixl.flush(),Decode端用hixl.wait():
# Prefill端:发完数据后flushhixl.rdma_write(mr,...)hixl.flush(mr)# 等待所有RDMA Write完成# Decode端:等通知hixl.wait(mr)# 阻塞直到数据写完hixl在CANN架构中的位置
hixl位于CANN五层架构的第4层(昇腾计算执行层),跟hccl和hcomm并列:
第4层:昇腾计算执行层 ├─ Runtime 运行时 ├─ Graph Executor ├─ HCCL 集合通信库(数据并行) ├─ hcomm 通信基础库 ├─ hixl 单边通信库(PD分离)← 你在这里 └─ DVPP / AIPP结尾
hixl是PD分离推理的最后一公里。PD分离的思路很自然——Prefill和Decode特性不同,分开跑效率更高。但分离之后,KV Cache怎么传?hccl太重,Socket太慢,hixl的零拷贝RDMA Write是最优解。
不过hixl也有门槛——需要RDMA网卡,需要手动管理内存注册和地址交换。如果你只是想快速验证PD分离的可行性,可以先用hccl AllGather跑通,确认有收益后再引入hixl优化延迟。hixl是锦上添花,不是雪中送炭。
https://atomgit.com/cann/hixl
