解锁远程直访的钥匙:深入解析 HIXL 单边通信库架构
CANN 组织链接: https://atomgit.com/cann
HIXL 仓库链接: https://atomgit.com/cann/hixl
在万卡集群已成常态的大规模并行计算时代,节点间的数据交换效率是衡量系统扩展性的黄金标准。传统的双边通信模型,如 MPI 的 Send/Recv,要求通信双方紧密握手,一个进程的计算节奏极易被另一个进程的通信延迟所拖累。为了打破这种强同步依赖,一种更灵活、更高效的编程范式——分区全局地址空间(PGAS)应运而生,而 HIXL (Huawei Xfer Library) 正是这一思想在专用 AI 处理器平台上的高性能实现。
HIXL 是一个面向大规模集群设计的单边通信库。它赋予了每个计算处理单元(Processing Element, PE)直接读写(Get/Put)远端 PE 设备内存的能力,而无需目标 PE 的显式参与。这种“主动出击”的通信模式,将计算与通信彻底解耦,为上层应用的性能优化打开了全新的想象空间。
1. 编程范式的革新:PGAS 与单边通信
HIXL 的根基是 PGAS 模型,它构建了一个对所有 PE 都可见的逻辑全局地址空间,但数据在物理上仍分散存储在各个 PE 的本地内存中。这种抽象使得开发者可以像操作本地内存一样,通过一个全局唯一的地址去访问集群中任意一个 PE 上的数据。
-
逻辑统一与物理分区
在 HIXL 的世界里,开发者可以像操作本地内存一样,通过一个全局唯一的地址去访问集群中任意一个 PE 上的数据。这极大地简化了分布式编程的复杂性,将程序员从繁琐的Send/Recv配对和消息标签管理中解放出来。虽然逻辑上是一个统一的地址空间,但数据在物理上依然存储在各自 PE 的设备内存(如 HBM)中,确保了数据局部性。 -
单边通信的优势
与传统的双边通信相比,单边通信的核心优势在于其非阻塞和异步的本质。这带来了多重效益:- 计算与通信解耦:当一个 PE 发起
Put(远程写入)操作时,它只需将请求提交给硬件即可继续执行后续计算,而无需等待目标 PE 准备好接收。 - 降低同步开销:在许多场景下,尤其是稀疏访问模式中,单边通信可以避免不必要的全局屏障(Barrier)同步,从而显著降低延迟。
- 天然的负载均衡:在“主-从”模式中,主节点可以主动地从多个工作节点拉取(
Get)结果,而无需协调所有工作节点同时完成并发送数据,提高了系统的灵活性。
- 计算与通信解耦:当一个 PE 发起
2. 对称内存空间:远程寻址的核心机制
为了让“指针”能够跨越设备的物理边界,HIXL 必须建立一套可靠的远程地址转换机制,其基础就是对称堆(Symmetric Heap)。这是 HIXL 性能与易用性的关键所在。
-
对称堆的创建
在程序初始化阶段,所有参与 HIXL 通信的 PE 都会在各自的设备内存中分配一块大小相同、属性一致的内存区域,这便是对称堆。HIXL 保证,一个在 PE-0 的对称堆中偏移量为0x1000的变量,在 PE-1 的对称堆中也存在一个逻辑上对应的变量,其偏移量同样为0x1000。这种内存布局的对称性是实现高效远程地址计算的基础。 -
远程地址的解析
有了对称堆,远程寻址变得异常简单。一个远程地址可以被唯一地分解为(PE_ID, local_offset)的元组。当 PE-A 想要访问 PE-B 上的某个地址时,HIXL 底层会:- 查找 PE-B 的硬件标识和其对称堆的物理基地址。
- 将逻辑偏移量
local_offset与物理基地址相加,得到目标设备上的确切物理地址。 - 配置 DMA 引擎或网络适配器,使用该物理地址发起直接内存访问。整个过程对开发者透明,无需复杂的网络地址转换。
-
静态分配的性能考量
对称堆通常在程序启动时一次性分配。这种静态策略虽然损失了一些灵活性,但换来了极高的性能确定性。它避免了运行时动态分配和地址查询的开销,确保了每次远程访问的地址计算都能在纳秒级完成,为大规模计算提供了稳定的基础。
3. 核心通信原语:Put、Get 与异步执行的艺术
HIXL 的 API 简洁而强大,其核心就是 Put 和 Get 这两个单边操作。它们是构建所有分布式数据传输的基石。
-
Put 与 Get 操作详解
hixl_putmem(远程写入): 源 PE 将本地内存中的数据直接写入到目标 PE 的指定设备内存地址。这是一个“我发起,我写入”的操作。hixl_getmem(远程读取): 源 PE 将目标 PE 指定设备内存地址的数据直接读取到本地内存。这是一个“我发起,我获取”的操作。
这两个操作都遵循“发起即返回”的异步执行模型,极大地提升了通信效率。
-
异步执行模型
当一个 PE 调用hixl_putmem或hixl_getmem时,数据传输任务被提交到硬件的命令队列后,CPU 控制权立刻返回给调用者。这意味着,在数据仍在通过高速链路传输的途中,计算单元已经可以开始执行下一条指令,从而实现了宝贵的计算-通信重叠。这种重叠效应是单边通信相对于双边通信在性能上的主要优势之一,可以有效隐藏通信延迟。
4. 数据一致性的守护者:内存屏障与同步原语
异步通信固然高效,但也带来了数据一致性的挑战。如果 PE-B 在 PE-A 的 Put 操作完成前就去读取目标内存,它可能会读到陈旧或不完整的数据。为此,HIXL 提供了精细的内存同步原语来保证分布式系统中的数据正确性。
-
hixl_fence:保证操作顺序fence操作像一道本地的栅栏。它确保所有在fence调用之前发起的远程写操作,在逻辑上都先于fence调用之后的远程访问操作被目标 PE“观察”到。它只保证操作的顺序性,不保证操作是否已经完成。这对于需要按序写入远程内存的场景至关重要。 -
hixl_quiet:等待远程完成quiet是一个更强的同步点。调用quiet会使当前 PE 阻塞,直到所有由它发起的、针对特定(或所有)PE 的单边操作(Put和Get)在硬件层面真正完成。这是确保远程数据可用的最可靠方式。在进行依赖于远程数据的新计算任务前,通常会调用quiet以确保所有前序通信已完全同步到位。 -
同步策略的选择
灵活运用fence和quiet是 HIXL 编程的艺术所在。在对数据一致性要求不高的探索性计算中,可以减少同步以换取更高性能;而在依赖远程结果的关键计算步骤前,则必须使用quiet来确保正确性。选择合适的同步粒度,是平衡性能与正确性的关键。
5. 硬件加速传输路径:HIXL 对异构链路的利用
HIXL 的高性能并非空中楼阁,它深度绑定了底层硬件的高速传输能力,能够智能地识别和利用集群中存在的各种异构通信链路。
-
单机多卡:P2P 直通车
在同一台服务器内部,不同专用 AI 处理器间的通信,HIXL 会优先利用设备间的专用高速互联总线(如 HCCS)。通过 P2P(Peer-to-Peer)技术,一块设备的 DMA 引擎可以直接读写另一块设备的内存,数据无需绕道主机 CPU 和内存,实现了最低延迟的片内传输。这对于单机内的多 NPU 协作至关重要。 -
跨机集群:RDMA 内核旁路
当通信跨越服务器节点时,HIXL 则依赖支持 RDMA(远程直接内存访问)的高性能网络(如 RoCE)。RDMA 允许网络适配器直接从用户态的应用内存中抓取数据并发送,完全绕过了操作系统内核的 TCP/IP 协议栈。这种“内核旁路”技术,消除了数据在不同内存层级间的多次拷贝和上下文切换开销,使得跨节点通信也能达到接近硬件极限的带宽和延迟。
6. 分布式同步利器:原子内存操作 (AMO) 与编程实践
除了批量的数据传输,分布式应用中还存在大量对共享状态(如计数器、标志位)的修改需求。如果使用 Get-Modify-Put 的三步走方式,不仅效率低下,还会引发竞态条件。HIXL 提供了硬件加速的原子内存操作(AMO)来解决这一问题。
-
AMO 在分布式场景的价值
AMO 允许一个 PE 对远程 PE 上的特定内存地址执行原子性的读取-修改-写入操作,而无需锁定整个内存区域或进行昂贵的全局同步。这对于以下场景特别有价值:- 分布式计数器:多个 PE 同时更新一个全局计数器。
- 锁的实现:通过原子交换(Atomic Swap)等操作构建分布式锁。
- 参数服务器:在异步更新模型中,多个工作节点向参数服务器贡献梯度时,原子操作能保证参数的正确聚合。
-
硬件加速的原子操作
像hixl_atomic_add或hixl_atomic_fetch_and_add这样的操作,能够在一个不可分割的步骤内,在目标 PE 的内存侧完成“读取-修改-写回”的整个过程。这不仅避免了数据来回传输的网络开销,更从根本上消除了软件层面的锁竞争,为实现高效的分布式锁、无锁队列、全局计数器等高级同步结构提供了坚实的基础。这些操作直接由网络适配器或内存控制器在硬件层面完成,保证了高性能和原子性。
附录:HIXL 核心 C 语言接口设计
以下代码片段展示了 HIXL 库典型的 C 风格 API 定义。这种简洁的设计易于被上层语言(如 C++, Python)通过外部函数接口(FFI)调用,体现了其作为高性能基础库的定位。
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 初始化 HIXL 运行时环境,建立通信组
*/
void hixl_init(void);
/**
* @brief 结束 HIXL 运行时,释放资源
*/
void hixl_finalize(void);
/**
* @brief 获取通信组中的 PE 总数
* @return PE 数量
*/
int hixl_n_pes(void);
/**
* @brief 获取当前 PE 的 ID (Rank)
* @return 当前 PE 的 ID,从 0 到 n_pes-1
*/
int hixl_my_pe(void);
/**
* @brief 从对称堆中分配一块内存。返回的指针在所有 PE 上逻辑等价。
* @param size 要分配的内存大小(字节)
* @return 返回在对称堆中的指针,所有 PE 上的返回值逻辑上等价
*/
void* hixl_malloc(size_t size);
/**
* @brief 将本地数据写入到远程 PE 的对称内存中(非阻塞)。
* 源 PE 仅发起操作,不等待完成。
* @param dest 目标 PE 上的目标地址(对称堆内指针)
* @param source 当前 PE 上的源地址
* @param nbytes 要传输的字节数
* @param pe 目标 PE 的 ID
*/
void hixl_putmem(void* dest, const void* source, size_t nbytes, int pe);
/**
* @brief 从远程 PE 的对称内存中读取数据到本地(非阻塞)。
* 源 PE 仅发起操作,不等待完成。
* @param dest 当前 PE 上的目标地址
* @param source 目标 PE 上的源地址(对称堆内指针)
* @param nbytes 要传输的字节数
* @param pe 目标 PE 的 ID
*/
void hixl_getmem(void* dest, const void* source, size_t nbytes, int pe);
/**
* @brief 屏障同步,等待通信组中所有 PE 到达此点。
* 这是一个全局同步操作,会阻塞直到所有 PE 都调用此函数。
*/
void hixl_barrier_all(void);
/**
* @brief 等待当前 PE 发起的所有远程内存访问操作完成。
* 此函数会阻塞,直到所有 Put/Get 等异步操作都已物理完成。
*/
void hixl_quiet(void);
/**
* @brief 对远程 PE 上的一个 word 执行原子加操作。
* @param dest 目标 PE 上的目标地址(对称堆内指针)
* @param value 要添加的值
* @param pe 目标 PE 的 ID
*/
long hixl_atomic_add(long* dest, long value, int pe);
#ifdef __cplusplus
}
#endif
更多推荐

所有评论(0)