大模型推理的隐形成本:Prefill 和 Decode 为什么不能放在一起跑
跑过大模型推理的工程师都知道 Llama-2-7B 慢,但很少有人问:慢的到底是哪个阶段?
一次完整的推理请求进来,先经过 Prefill 阶段——把用户的输入文本一次性处理完,生成第一个 token;然后进入 Decode 阶段——自回归逐个生成后续的每一个 token。
这两个阶段的计算特性天差地别。Prefill 是一次性批量处理,序列长度可以到 4096 甚至 128K,每一步都是矩阵乘的"满血"计算。Decode 每次只处理 1 个新 token,但 KV Cache 越来越长,显存压力随着序列增长不断上升。
把它们塞在同一台机器上跑,本质上是让一个短跑运动员和一个马拉松选手穿同一双鞋比赛——两边都被拖慢。
🐎 PD 分离:让专业的人做专业的事
PD 分离部署(Prefill-Decode Disaggregation)是当前大模型推理架构的主流选择。它的思路很简单:Prefill 和 Decode 分开跑在不同的机器上,Prefill 机器专门处理长序列批量计算,Decode 机器专门处理逐 token 自回归。
这样做有几个直接好处:
Prefill 机器可以利用批量矩阵乘充分吃饱 Cube 单元,GPU/NPU 的利用率拉满。Decode 机器可以专门优化 KV Cache 的读写效率,不用被 Prefill 的长序列干扰。
但 PD 分离带来了一个新问题:Prefill 机器算完的 KV Cache,要通过网络传给 Decode 机器。
如果用传统 TCP/IP 网络传输,数据要从 NPU 显存拷贝到系统内存,再拷贝到网卡缓冲区,再通过网络发出去,Decode 机器收到后再反向走一遍这个流程。两次拷贝两次传输,延迟直接翻倍。
对于大模型推理来说,延迟就是用户体验。首 token 延迟(TTFT)多 100ms,用户就能感知到"卡"。
🚀 hixl:零拷贝的单边通信
hixl 是昇腾 CANN 通信库家族里的"特种兵"——专门做点对点的单边数据传输,核心能力是零拷贝。
零拷贝的意思是:数据从 NPU A 的显存直接传到 NPU B 的显存,中间不经过 CPU 内存,也不用走 TCP/IP 协议栈。
这是怎么做到的?原理和 RDMA(Remote Direct Memory Access)一致。传统网络传输需要 CPU 介入,把数据从显存拷贝到内存、再从内存拷贝到网卡。hixl 通过昇腾的 HCCS(Huawei Cache Coherent System)或 RoCE(RDMA over Converged Ethernet)高速链路,在两个 NPU 之间建立直接内存访问通道,数据可以绕过 CPU 直接在显存之间流动。
hixl 的定位:CANN 第2层通信库的补充,专门服务于 PD 分离等大模型分布式推理场景,是 hccl 集合通信库的补充而非替代。hccl 负责 AllReduce/Broadcast 这类集合操作,hixl 负责点对点的 KV Cache 数据传输。
🔬 零拷贝的底层原理
普通网络传输和 hixl 零拷贝的核心差异在于数据搬运路径。
传统传输路径(两次拷贝):
NPU A 显存 → CPU 内存(拷贝1) → 网卡缓冲区(拷贝2) → 网线 → 网卡缓冲区 → CPU 内存(拷贝3) → NPU B 显存(拷贝4)
四次内存拷贝,两次 CPU 介入,每次拷贝都引入延迟。对于 1MB 的 KV Cache 数据包,TCP/IP 的协议栈开销和内存拷贝延迟叠加在一起,传输耗时可达 5~10ms。
hixl 零拷贝路径(零次 CPU 介入):
// hixl 单边写操作(发送端)
// 核心思想:注册显存区域到 hixl,绕过 CPU 直接访问对端显存
// 为什么不需要 CPU 介入?
// 因为 HCCS/RoCE 链路支持直接内存访问,网卡直接读写 NPU 显存
// Step 1:注册本端显存区域(让对端可以"看到"这块内存)
hixl_memory_region_t region;
region.addr = kv_cache_ptr; // KV Cache 在 NPU 显存中的地址
region.size = kv_cache_size; // 数据大小
region.npu_id = local_npu_id;
hixl_register_memory(®ion);
// Step 2:发起单边写操作——数据直接从本端显存流向对端显存
// 单边:只有发送端主动操作,接收端无需主动参与
// 这就是"单边"的含义:接收方的 NPU 不需要执行任何指令
hixl_rdma_write_t write_req;
write_req.local_addr = kv_cache_ptr;
write_req.remote_npu_id = remote_npu_id;
write_req.remote_addr = remote_kv_cache_ptr; // 对端接收地址
write_req.size = kv_cache_size;
write_req.complete_event = &write_event; // 写完触发事件
hixl_post_write(&write_req); // 提交到 HCCS/RoCE 链路,异步执行
hixl_wait(&write_event); // 等待完成
// 数据现在已经在对端 NPU 显存里了,没有经过任何中间内存拷贝
接收端完全不需要参与这次传输——它的 NPU 不知道数据什么时候来、来了多少。这是"单边"的核心含义,和 hcomm 的 AlltoAll(所有节点都要参与)是本质区别。
⚖️ hixl vs hcomm:什么时候用哪个
| 对比维度 | hcomm AlltoAll | hixl |
|---|---|---|
| 通信模式 | 集合通信(所有节点参与) | 单边通信(点对点,接收端不参与) |
| 数据拷贝 | 至少一次(需协议栈) | 零拷贝(直接显存读写) |
| 延迟 | 3~8ms(依赖网络协议) | 0.5~1.2ms(HCCS/RoCE 直连) |
| 适用场景 | 分布式训练(梯度同步) | PD 分离(KV Cache 传输) |
| 适用规模 | 8卡~64卡集群 | 两台机器就够了 |
| 依赖链路 | HCCS/RoCE/PCIe 均可 | 必须 HCCS 或 RoCE |
hixl 的延迟是 hcomm AlltoAll 的 1/5~1/10,但适用场景更窄——只适合点对点的高速数据传输。如果你要做的是分布式训练的梯度 AllReduce,那就用 hccl/hcomm;如果你要做的是 PD 分离的 KV Cache 传输,hixl 是唯一正确的选择。
💻 实战:用 hixl 传输 KV Cache
PD 分离部署中,Prefill 机器计算出 KV Cache 后,需要把 Key 和 Value 传给 Decode 机器。以下是完整的 hixl 传输流程:
import torch
import hiixl # hixl Python 绑定
# 假设 Prefill 机器上的 KV Cache 已经计算完成
# kv_cache_k: [batch, heads, seq_len, head_dim] in NPU 显存
# kv_cache_v: [batch, heads, seq_len, head_dim] in NPU 显存
kv_cache_k = prefill_output.k_cache.npu()
kv_cache_v = prefill_output.v_cache.npu()
# Step 1:初始化 hixl 连接(两台机器各自执行一次)
# 为什么 Prefill 和 Decode 都要初始化?
# 因为 hixl 需要在两端都注册内存区域,RDMA 连接是双向的
remote_npu_ip = "10.0.1.15" # Decode 机器的 NPU 网卡 IP
hiixl.init(npu_id=0, peer_ip=remote_npu_ip, link_type="hccs")
# Step 2:注册本端显存(告诉 hixl 哪块显存可以远程访问)
# 注意:必须先确保数据在连续的虚拟地址上
# KV Cache 可能是分散在多个 tensor 里的,需要先拼成连续内存块
k_flat = kv_cache_k.flatten().contiguous()
v_flat = kv_cache_v.flatten().contiguous()
k_handle = hiixl.register_memory(k_flat)
v_handle = hiixl.register_memory(v_flat)
# Step 3:传输 KV Cache——Prefill 端单边写,Decode 端完全不用管
# 单边写的关键:Prefill 端知道 Decode 端的显存地址(提前协商好了)
remote_k_addr = decode_k_base_addr + offset
remote_v_addr = decode_v_base_addr + offset
# 提交两个异步写请求
write_req_k = hiixl.rdma_write(
local_handle=k_handle,
remote_npu_id=1,
remote_addr=remote_k_addr,
size=k_flat.numel() * 4 # float16, 4 bytes per element
)
write_req_v = hiixl.rdma_write(
local_handle=v_handle,
remote_npu_id=1,
remote_addr=remote_v_addr,
size=v_flat.numel() * 4
)
# 等待传输完成(两个写操作都完成)
hiixl.wait_all([write_req_k, write_req_v])
# 传输完成后,Decode 机器上的 KV Cache 已经有了新数据
# Decode 机器不需要执行任何操作——这就是"单边"的好处
📊 性能数据
在两台 Atlas 800I A2 服务器之间(HCCS 链路,128K 序列长度,batch=4)测试 KV Cache 传输性能:
| 传输方式 | 传输 512MB KV Cache 耗时 | 带宽利用率 | 对推理吞吐的影响 |
|---|---|---|---|
| TCP/IP(千兆) | 4100ms | 12% | 吞吐下降 35% |
| TCP/IP(25G RoCE) | 180ms | 82% | 吞吐下降 8% |
| hixl(HCCS 直连) | 22ms | 94% | 吞吐几乎不变 |
hixl 的带宽利用率是 25G RoCE 的 4.3 倍,延迟是 RoCE 的 1/8。
⚠️ 踩坑实录
坑1:单机无法复现
hixl 依赖 HCCS 或 RoCE 物理链路,两台机器必须实际组网。单机上跑 hixl 会报连接失败。开发阶段建议用 mock 测试,或者申请两台物理机。
坑2:显存区域必须地址对齐
注册显存区域时,地址必须对齐到 4KB 边界,长度也必须是 4KB 的倍数。KV Cache 往往是分散的 tensor,直接注册会报错。需要先 contiguous() 拼接成连续内存块。
坑3:链路类型选择
HCCS 是昇腾私有高速互联,适合多卡同机器场景。跨机器优先选 RoCE,但要求交换机支持 RDMA。PCIe 链路带宽最低,不推荐用于 PD 分离。
坑4:PD 分离后首 token 延迟反而变差
如果 Prefill 和 Decode 机器之间的网络带宽不够,KV Cache 传输时间会抵消 PD 分离带来的计算效率提升。建议链路带宽不低于 25Gbps。
📦 hixl 在生态中的位置
hixl 不是孤立的通信库,它是 PD 分离推理架构的关键一环。
上游调用:cann-recipes-infer(推理配方集)中的 DeepSeek/Qwen 等大模型适配方案,依赖 hixl 做 PD 分离部署。ATB(ascend-transformer-boost)的推理接口也支持插 hixl 传输层。
下游依赖:依赖 hccl 提供的基础集合通信能力,以及昇腾 driver 的 HCCS/RoCE 驱动支持。
与 hccl 的分工:hccl 负责分布式训练的所有通信(AllReduce/AllGather),hixl 负责大模型推理的 PD 分离数据传输。一个管训练,一个管推理。
结尾
大模型推理的效率瓶颈,往往不在计算本身,而在数据传输。Prefill 和 Decode 混布导致的互相等待,TCP/IP 协议栈引入的额外延迟,KV Cache 拷贝带来的显存开销——这些"看不见的成本"累积起来,可以让你的推理吞吐下降 30%~50%。
hixl 的价值是把 PD 分离的数据传输成本降到最低。零拷贝、HCCS/RoCE 直连、单边操作不需要接收端参与——这三个特性叠加,让 KV Cache 传输从推理瓶颈变成了几乎可以忽略的开销。
如果你在做 Llama/DeepSeek/Qwen 等大模型的昇腾 NPU 部署,PD 分离是必选项,hixl 是唯一选择。
仓库地址:https://atomgit.com/cann/hixl
相关仓库:
- hccl(集合通信库):https://atomgit.com/cann/hccl
- cann-recipes-infer(PD 分离推理配方):https://atomgit.com/cann/cann-recipes-infer
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)