前言

在昇腾CANN软件栈的分层架构中,HICANN 处于驱动层与上层运行时之间的关键位置,承担着对昇腾 NPU 硬件能力的抽象封装与调度转发职责。当上层框架(如 PyTorch)通过 Ascend CL 接口下发计算任务时,HICANN 负责将逻辑设备映射到物理 NPU,并在多卡、多芯片场景下完成亲和性调度决策。本文从 HICANN 的架构定位出发,深入剖析其硬件接口抽象机制与 NPU 亲和性调度的实现原理,并结合实际部署场景分析其对计算性能与资源利用率的影响。

HICANN 在昇腾CANN架构中的定位

昇腾CANN采用分层解耦设计,自底向上依次为:驱动层(Driver)、HICANN 层、运行时层(Runtime/ACL)以及上层的算子库与框架适配层。HICANN 的核心职责是屏蔽底层硬件差异,向上提供统一的设备管理接口。在 Ascend 910 等芯片平台上,HICANN 需要处理不同芯片版本之间的寄存器差异、内存布局差异以及通信拓扑差异,使得上层运行时无需感知硬件的具体实现细节。

这种分层抽象带来了两方面的收益:其一,上层框架适配层可以专注于算子逻辑与计算图优化,而不必关心 NPU 底层的寄存器配置与数据搬运方式;其二,当新一代芯片发布时,只需在 HICANN 层扩展对应的硬件适配模块,即可复用整个上层软件栈,大幅缩短新硬件的适配周期。

从代码组织角度看,HICANN 模块内部可划分为设备管理子模块、内存管理子模块、任务调度子模块以及通信管理子模块。各子模块之间通过定义清晰的接口契约进行交互,避免跨层调用带来的耦合问题。

硬件接口抽象机制

HICANN 对硬件接口的抽象遵循"能力描述驱动"的设计思想。每颗 NPU 在初始化阶段会向 HICANN 上报自身的硬件能力描述信息,包括可用的 AI Core 数量、AI CPU 数量、片上存储容量、通信带宽参数等。HICANN 将这些信息解析为统一的能力描述结构体,供上层调度决策使用。

// 设备能力描述结构体:HICANN 通过此结构统一不同芯片的能力信息
// WHY: 不同代际芯片的核心数、存储大小差异大,必须抽象为统一结构才能实现跨代调度
typedef struct {
    uint32_t ai_core_count;       // AI Core 数量,影响算子并行度上限
    uint32_t ai_cpu_count;        // AI CPU 数量,控制逻辑类算子的吞吐
    uint64_t hbm_size_bytes;      // HBM 总容量,决定单卡可承载的模型规模
    uint32_t bus_bandwidth_gbps;  // 片间互联带宽,影响多卡通信效率
    uint32_t die_count;           // Die 数量,多 Die 场景需感知 Die 间拓扑
} HcclDevCapability;

在设备初始化流程中,HICANN 通过驱动层提供的查询接口获取原始硬件信息,再经过格式转换与校验后填充到上述结构体中。该流程的关键设计考量在于:驱动层返回的原始信息格式可能随芯片版本迭代而变化,因此 HICANN 在解析阶段需要进行版本协商,确保能力描述的语义一致性。

对于内存管理接口,HICANN 提供了基于段式管理的抽象。NPU 的片上存储被划分为多个功能段,包括用于算子计算的 Workspace 段、用于权重驻留的 Weight 段以及用于中间数据缓存的 Buffer 段。HICANN 将这些段的分配与回收封装为统一的内存池接口,上层运行时通过该接口申请和释放设备内存,而无需关心底层是否涉及跨 Die 内存访问或缓存一致性维护。

// 内存段分配接口:上层通过统一接口申请设备内存
// WHY: 不同芯片的内存拓扑(单Die/多Die)差异大,统一接口让运行时无需感知底层布局
HcResult HcMalloc(int32_t device_id, HcMemType type, size_t size, HcMemHandle* handle);

// 内存类型枚举,覆盖 NPU 上各类存储资源
typedef enum {
    HC_MEM_HBM = 0,        // HBM 大容量存储,存放权重与激活值
    HC_MEM_L1    = 1,        // AI Core L1 缓存,存放算子临时数据
    HC_MEM_L2    = 2,        // AI Core L2 缓存,存放频繁访问的只读数据
} HcMemType;

任务下发接口是 HICANN 另一核心抽象。上层运行时将计算图编译后的执行计划以任务流(Task Stream)的形式提交给 HICANN,HICANN 负责将任务流中的每个任务节点映射到具体的硬件执行单元。该映射过程需要综合考虑任务类型(计算/通信/同步)、数据局部性以及执行单元的负载状态。

NPU 亲和性调度的核心原理

亲和性调度(Affinity Scheduling)是指将计算任务优先分配到与数据距离最近的执行单元上执行,以减少数据搬运开销。在昇腾CANN场景中,亲和性调度的内涵更为丰富,涵盖了进程-设备亲和、线程-Stream 亲和、数据-存储亲和三个层次。

进程-设备亲和

在多卡训练场景下,每个训练进程需要绑定到特定的 NPU 设备上执行。HICANN 提供了设备亲和性查询接口,允许运行时根据进程的 NUMA 拓扑信息选择最优的 NPU 设备。当服务器上插有多张昇腾 910 加速卡时,CPU 与 NPU 之间的 PCIe 拓扑关系决定了数据传输的延迟与带宽。将进程绑定到 NUMA 亲和的 NPU 上,可以有效避免跨 NUMA 节点访问带来的带宽损耗与延迟增加。

# 基于 NUMA 拓扑的设备亲和性选择
# WHY: 多卡服务器中 CPU-NPU 的 PCIe 拓扑不对称,跨 NUMA 访问会导致 HBM 拷贝带宽下降 30% 以上
import numa

def select_affinity_device(rank_id, total_devices):
    """根据 rank 选择 NUMA 亲和的 NPU 设备"""
    numa_nodes = numa.get_topology()  # 获取 NUMA 拓扑
    npu_per_numa = total_devices // len(numa_nodes)
    target_numa = rank_id // npu_per_numa
    # 绑定当前进程到目标 NUMA 节点
    numa.bind(target_numa)
    # 返回该 NUMA 节点下的 NPU 设备 ID
    local_device = rank_id % npu_per_numa
    return target_numa * npu_per_numa + local_device

HICANN 在内部维护了一份 CPU-NPU 拓扑映射表,该映射表在驱动初始化阶段通过遍历 PCIe 总线拓扑自动构建。运行时层可以通过 HICANN 提供的查询接口获取该拓扑信息,并据此做出调度决策。实测数据表明,在 8 卡昇腾 910 服务器上,采用 NUMA 亲和绑定的训练任务相较于随机绑定,端到端训练吞吐量可提升约 10%-15%(数据仅供参考,实际提升取决于模型结构与通信模式)。

线程-Stream 亲和

昇腾CANN运行时中的 Stream 是任务下发的逻辑通道,不同 Stream 上的任务可以并行执行。HICANN 在 Stream 调度层面同样引入了亲和性机制。当多个 Stream 同时向同一颗 NPU 提交任务时,HICANN 的任务调度器会根据 Stream 的创建上下文(绑定线程、优先级、任务类型)进行仲裁,优先调度与当前正在执行任务具有数据依赖关系的 Stream,以减少流水线气泡。

线程-Stream 亲和的另一个应用场景是计算-通信重叠。在大模型分布式训练中,前向计算与梯度 AllReduce 通信可以分属不同的 Stream 并行执行。HICANN 调度器在检测到计算 Stream 与通信 Stream 之间无数据依赖时,会自动将两者分发到不同的硬件执行通道,实现真正的流水线并行。这种调度的前提是 HICANN 需要精确追踪 Stream 间的依赖关系图,确保仅在依赖满足时才触发并行调度。

数据-存储亲和

数据-存储亲和关注的是计算数据与存储位置之间的距离优化。在多 Die 架构的芯片上,同一颗芯片内的不同 Die 之间存在互联带宽与延迟差异。当计算任务所需的数据位于远端 Die 的存储上时,需要通过片间互联进行数据搬移,这会引入额外的延迟。

HICANN 的内存管理子模块在分配内存时,会参考任务的调度位置信息,优先在与执行 Die 亲和的存储区域上分配内存。当内存容量不足需要跨 Die 分配时,HICANN 会在任务执行前自动插入数据预取指令,将远端数据搬运到执行 Die 的本地存储中。该预取策略的核心参数包括预取距离(提前多少个任务节点触发预取)与预取粒度(单次预取的数据块大小),这些参数由 HICANN 的调优模块根据历史执行信息动态调整。

亲和性调度的实现细节

HICANN 亲和性调度的实现涉及拓扑感知、调度决策与动态调优三个阶段。

在拓扑感知阶段,HICANN 在系统启动时通过驱动层获取完整的硬件拓扑信息,包括 CPU-NPU 拓扑、NPU 间互联拓扑(HCCL 通信域)以及芯片内 Die 级拓扑。这些拓扑信息被组织为加权无向图,边的权重反映了两节点之间的通信开销。

在调度决策阶段,HICANN 采用基于代价模型的贪心策略。对于每个待调度的任务,调度器枚举所有候选执行位置,计算每个位置的预估执行代价,选择代价最小的位置。代价模型考虑三个因素:数据搬运开销(与数据量和搬运距离成正比)、执行单元负载(避免热点)以及通信开销(多卡场景下任务间的数据交换量)。

// 简化的调度代价估算函数
// WHY: 代价模型需要在微秒级完成计算,不能使用复杂的迭代优化算法
float estimate_task_cost(HcTask* task, HcExecUnit* candidate,
                        HcTopology* topo) {
    float data_cost = 0.0f;
    // 遍历任务的所有输入张量
    for (int i = 0; i < task->input_count; i++) {
        HcMemLoc src = get_mem_location(task->inputs[i]);
        HcMemLoc dst = get_exec_local_mem(candidate);
        float distance = topo_get_distance(topo, src, dst);
        data_cost += task->input_sizes[i] * distance;
    }
    float load_cost = candidate->pending_tasks * LOAD_WEIGHT;
    float comm_cost = estimate_comm_overhead(task, candidate, topo);
    return data_cost + load_cost + comm_cost;
}

在动态调优阶段,HICANN 收集任务执行的实际耗时与数据搬运耗时,将偏差反馈到代价模型的参数中。例如,如果某类任务在 Die-0 上的实际数据搬运耗时持续高于预估值,调优模块会相应增大 Die-0 的距离权重系数,使后续调度决策倾向于选择其他执行位置。这种闭环反馈机制使得亲和性调度能够在不同工作负载下持续收敛到较优状态。

典型部署场景分析

在单机多卡场景下,亲和性调度的核心矛盾在于 CPU-NPU 的 NUMA 拓扑匹配。采用 HICANN 的 NUMA 亲和绑定后,数据从 CPU 内存拷贝到 NPU HBM 的带宽可以接近 PCIe 理论峰值,而跨 NUMA 绑定时带宽损失可达 20%-30%(数据仅供参考)。对于参数服务器架构的训练任务,这种带宽差异会直接影响参数拉取的效率,进而拖慢整体训练速度。

在多机多卡场景下,除了 NUMA 亲和性外,还需关注 NPU 间的通信拓扑。昇腾 910 支持基于 RoCE 的 RDMA 通信,多机之间通过交换机互联。HICANN 在构建通信域时会感知物理网络拓扑,优先将网络跳数少的 NPU 划分到同一通信子域内,减少跨交换机通信的比例。这种拓扑感知的通信域划分对于 AllReduce 等集合通信操作的效率影响显著。

在推理部署场景下,亲和性调度的关注点有所不同。推理通常以低延迟为目标,单次请求的计算量较小,调度开销在总延迟中的占比相对较高。HICANN 针对推理场景提供了轻量级调度模式,该模式跳过代价模型计算,直接根据预设的亲和性规则(如固定绑定到当前 NUMA 节点的 NPU)进行调度,将调度开销控制在微秒级。

结尾

HICANN 作为昇腾CANN软件栈中承上启下的关键模块,其硬件接口抽象能力与 NPU 亲和性调度机制直接影响着上层应用对底层算力的利用效率。通过统一的设备能力描述、NUMA 感知的进程-设备绑定、基于代价模型的动态调度以及闭环反馈的参数调优,HICANN 为昇腾NPU 提供了高效的硬件资源管理能力。在多卡训练、大规模分布式推理等场景中,合理利用 HICANN 的亲和性调度策略,能够显著降低数据搬运开销与通信延迟,提升端到端性能。

仓库地址:https://gitee.com/ascend/hicann

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐