大模型推理场景跟训练完全不同。训练需要同步所有卡的梯度,通信模式是 AllReduce。推理需要把模型分散到多张卡上、每张卡计算一部分、结果合并------通信模式是一对一的单边读写。
HCCL 被设计来做集合通信------CPU 主动协调每张卡的收发动作。但推理场景中真正的瓶颈是延迟------NPU 从另一张 NPU 的显存里直接读数据,不需要 CPU 介入。这种模式叫单边通信或 RDMA(Remote Direct Memory Access)。
CANN 的 hixl 仓库就是做这件事的。它提供了一套零拷贝的单边通信接口,让 NPU 直接读写远端 NPU 的显存,走过 PCIe 或 HCCS 直连链路,不经过 CPU 内存。
为什么 AI 推理需要低延迟通信
LLaMA-70B 的参数有 140GB,单张 Ascend 910 的 32GB 显存放不下。推理时必须把模型切到多张卡上。切的方式有两种:
张量并行(Tensor Parallelism): 把 Attention 的头切分到不同卡上,每张卡算一部分。计算过程中 Q 投影的结果需要跨卡聚合才能得到完整的 Attention Score。这个聚合是通信热点。
流水线并行(Pipeline Parallelism): 把 Decoder Block 切到不同卡上------卡 0 算 Block 1-10,卡 1 算 Block 11-20。卡 0 输出传给卡 1 作为输入。这个传输是通信热点。
两种并行都要求 NPU 之间的通信延迟在微秒级------不能因为传数据而让 NPU 空等几十微秒。hixl 的单边通信就是为这个场景设计的。
单边通信 vs 双边通信
理解 hixl 之前先搞清楚两种通信模型的区别。
双边通信(如 HCCL 的 Send/Recv):
NPU 0: CPU 调用 HCCL_Send(buf, dest=NPU1) → DMA 出发 → 等完成
NPU 1: CPU 调用 HCCL_Recv(buf, src=NPU0) → DMA 出发 → 等完成
CPU 在两端都要参与------提交发送请求和接收请求。两端 DMA 必须配对才能完成传输。任何一端没有就绪,另一端就被阻塞。
单边通信(如 hixl 的 Read/Write):
NPU 0: hixl_read(remote_addr, local_buf, size) // NPU0 直接读 NPU1 的显存
CPU 只在一端参与------NPU 0 发起读操作,NPU 1 完全不知情。硬件 DMA 引擎直接访问远端 NPU 的显存地址,不需要远端 CPU 调度。
单边通信的优势:
- 延迟更低------省掉了远端 CPU 的调度开销和中断响应
- CPU 负担小------只有发起方需要 CPU 参与,接收方零负担
- 异步更深------hixl 的 Read 操作提交到 NPU Stream 后,发起方 NPU 可以继续做计算,不等读取完成
Zero Copy 如何减少 CPU 参与
hixl 的另一个关键是 Zero Copy------数据直接从源 NPU 显存搬到目标 NPU 显存,不经过 CPU 内存。
传统的跨卡数据传递路径:
NPU 0 显存 → PCIe DMA → CPU 内存 → DMA → NPU 1 显存
两段 DMA、一次 CPU 内存中转。CPU 内存中转引入了额外的拷贝延迟和 CPU 缓存污染。
hixl 的 Zero Copy 路径:
NPU 0 显存 → HCCS/NVLink → NPU 1 显存
一段 DMA、零 CPU 参与。数据在 NPU 之间的物理链路上直接传输。
Zero Copy 的核心依赖是远端显存映射------NPU 把自己的显存地址暴露给 DMA 引擎,其他 NPU 的 DMA 可以直接寻址到这个地址。hixl 在通信初始化阶段完成这个映射注册。
cpp
// hixl 初始化------远端显存注册
hixlRegisterMemory(npu0_buffer, size, &remote_handle);
// 推理时------NPU0 直接从 NPU1 显存读数据
hixlRead(remote_handle, offset, local_buffer, size, stream);
注册只需要做一次,后续推理运行阶段不再需要 CPU 介入。
CANN 通信链路中的 hixl
hixl 在 CANN 通信体系中的位置:
CANN 通信栈:
应用层:PyTorch DDP / AscendCL
↓
通信库层:HCCL(集合通信) / hixl(单边通信)
↓
通信算子层:hcom(Send/Recv 原语)
↓
硬件层:HCCS 互联 / RoCE 网络
hixl 和 HCCL 不是竞争关系。HCCL 负责 AllReduce 这类需要全集群协调的集合操作。hixl 负责点对点的显存直接读写------更适合推理场景中的张量并行和流水线并行。
在 PD 分离(Prefill 和 Decode 分别跑在不同卡上)场景中,Prefill 卡的输出需要通过 hixl 零拷贝传给 Decode 卡。Prefill 的 KV Cache 写进显存后,Decode 卡的 DMA 直接读取,不需要 Prefill 卡做任何额外的 Send 动作。传输延迟从微秒级降到亚微秒级。
大模型推理中的典型场景
场景 1:张量并行的跨卡 Attention 聚合。
在 8 卡张量并行场景中,每张卡持有 1/8 的 Attention Head。计算过程中需要把其他卡的 Q 投影结果读过来才能算完整的 Score。
cpp
// 每张卡把 Q 投影结果写到远端,让其他卡直接读
for each remote_card in peers:
hixlWrite(local_q_buffer, remote_card, remote_q_addr, size, stream);
// 同时读远端卡的 Q 投影结果
for each remote_card in peers:
hixlRead(remote_q_buffer, remote_card, remote_q_addr, size, stream);
8 卡场景下单步 Attention 的跨卡通信量约 4MB。hixl 的 Zero Copy 让这部分通信延迟从 15μs 降到 4μs。
场景 2:流水线并行的 Block 传输。
流水线并行中,前一个 Block 的输出是后一个 Block 的输入。使用 hixl,前卡输出 Tensor 写好后,后卡直接 Read 走,两卡之间没有同步协商开销。
场景 3:PD 分离的 KV Cache 传输。
Prefill 阶段结束后,完整的 KV Cache 在 Prefill 卡上。Decode 阶段使用 KV Cache 时通过 hixl 直接读 Prefill 卡的显存,不搬移数据。KV Cache 数据量可能很大(几十 GB),hixl 的远程读取避免了昂贵的数据迁移。
小结
hixl 解决的核心问题很简单:NPU 之间传数据不应该让 CPU 来管。单边通信 + Zero Copy 的设计让 NPU 之间的数据传输延迟降到硬件链路延迟的物理下限------接近 HCCS 或 PCIe 的带宽极限。在大规模张量并行的推理场景中,hixl 是一条关键的延迟优化路径。
hixl 的初始化与资源管理
使用 hixl 前需要初始化通信资源。hixlInit 注册远端 NPU 显存的访问权限,建立 DMA 引擎的地址映射表。
cpp
hixlInit();
// 注册本卡显存,允许其他卡直接读取
void* local_buf = aclrtMalloc(size, ACL_MEM_MALLOC_HUGE_FIRST);
hixlExportMemory(local_buf, size, &export_handle);
// 通过通信域交换 handle,让其他卡可以访问本卡显存
all_gather_handles(local_handle, remote_handles);
注册只需做一次,后续推理中直接使用 handle。hixl 的生命周期管理通过引用计数------所有指向同一块远端显存的 handle 都释放后才真正注销。
hixl vs 共享内存
hixl 的 Zero Copy 跟操作系统的共享内存(shared memory)机制不同。共享内存是一块物理内存被多个进程映射到各自的虚拟地址空间------数据不移动。hixl 的数据实际上是"从远端 NPU 显存通过 DMA 引擎拷贝到本卡显存"------数据移动了,但移动路径不经过 CPU。
两种方式的目标相同------减少 CPU 参与------但实现路径不同。共享内存适合同节点多进程通信,hixl 适合跨节点跨设备通信。