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)结果,而无需协调所有工作节点同时完成并发送数据,提高了系统的灵活性。

2. 对称内存空间:远程寻址的核心机制

为了让“指针”能够跨越设备的物理边界,HIXL 必须建立一套可靠的远程地址转换机制,其基础就是对称堆(Symmetric Heap)。这是 HIXL 性能与易用性的关键所在。

  • 对称堆的创建
    在程序初始化阶段,所有参与 HIXL 通信的 PE 都会在各自的设备内存中分配一块大小相同、属性一致的内存区域,这便是对称堆。HIXL 保证,一个在 PE-0 的对称堆中偏移量为 0x1000 的变量,在 PE-1 的对称堆中也存在一个逻辑上对应的变量,其偏移量同样为 0x1000。这种内存布局的对称性是实现高效远程地址计算的基础。

  • 远程地址的解析
    有了对称堆,远程寻址变得异常简单。一个远程地址可以被唯一地分解为 (PE_ID, local_offset) 的元组。当 PE-A 想要访问 PE-B 上的某个地址时,HIXL 底层会:

    1. 查找 PE-B 的硬件标识和其对称堆的物理基地址。
    2. 将逻辑偏移量 local_offset 与物理基地址相加,得到目标设备上的确切物理地址。
    3. 配置 DMA 引擎或网络适配器,使用该物理地址发起直接内存访问。整个过程对开发者透明,无需复杂的网络地址转换。
  • 静态分配的性能考量
    对称堆通常在程序启动时一次性分配。这种静态策略虽然损失了一些灵活性,但换来了极高的性能确定性。它避免了运行时动态分配和地址查询的开销,确保了每次远程访问的地址计算都能在纳秒级完成,为大规模计算提供了稳定的基础。

3. 核心通信原语:Put、Get 与异步执行的艺术

HIXL 的 API 简洁而强大,其核心就是 PutGet 这两个单边操作。它们是构建所有分布式数据传输的基石。

  • Put 与 Get 操作详解

    1. hixl_putmem (远程写入): 源 PE 将本地内存中的数据直接写入到目标 PE 的指定设备内存地址。这是一个“我发起,我写入”的操作。
    2. hixl_getmem (远程读取): 源 PE 将目标 PE 指定设备内存地址的数据直接读取到本地内存。这是一个“我发起,我获取”的操作。
      这两个操作都遵循“发起即返回”的异步执行模型,极大地提升了通信效率。
  • 异步执行模型
    当一个 PE 调用 hixl_putmemhixl_getmem 时,数据传输任务被提交到硬件的命令队列后,CPU 控制权立刻返回给调用者。这意味着,在数据仍在通过高速链路传输的途中,计算单元已经可以开始执行下一条指令,从而实现了宝贵的计算-通信重叠。这种重叠效应是单边通信相对于双边通信在性能上的主要优势之一,可以有效隐藏通信延迟。

4. 数据一致性的守护者:内存屏障与同步原语

异步通信固然高效,但也带来了数据一致性的挑战。如果 PE-B 在 PE-A 的 Put 操作完成前就去读取目标内存,它可能会读到陈旧或不完整的数据。为此,HIXL 提供了精细的内存同步原语来保证分布式系统中的数据正确性。

  • hixl_fence:保证操作顺序
    fence 操作像一道本地的栅栏。它确保所有在 fence 调用之前发起的远程写操作,在逻辑上都先于 fence 调用之后的远程访问操作被目标 PE“观察”到。它只保证操作的顺序性,不保证操作是否已经完成。这对于需要按序写入远程内存的场景至关重要。

  • hixl_quiet:等待远程完成
    quiet 是一个更强的同步点。调用 quiet 会使当前 PE 阻塞,直到所有由它发起的、针对特定(或所有)PE 的单边操作(PutGet)在硬件层面真正完成。这是确保远程数据可用的最可靠方式。在进行依赖于远程数据的新计算任务前,通常会调用 quiet 以确保所有前序通信已完全同步到位。

  • 同步策略的选择
    灵活运用 fencequiet 是 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_addhixl_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
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐