Ascend 950PR (Ascend950) bufid 原理、实现约束与使用约束

目标芯片:Ascend 950PR / DT(Atlas A5),__NPU_ARCH__ == Ascend950
核心结论bufid(buffer ID)与 mutex_id(互斥锁编号)是同一个硬件资源——一个 5 位编号 [0, 31],命名 32 个硬件"取缓冲/释放缓冲"信号量。
底层指令get_buf(pipe, bufId, mode) / rls_buf(pipe, bufId, mode)
2201 差异:2201 无 mutex 能力,使用 set_flag/wait_flag 替代


1. bufid 是什么

1.1 定义

在 Ascend950 上,每个 AI Core 内置 32 个硬件缓冲锁槽位(buffer-lock slots),每个槽位由一个 5 位编号 bufid ∈ [0, 31] 标识。每个槽位是一个二值信号量,可被核内异步流水线(MTE2/MTE1/M/V/MTE3/FIX/S)通过 get_buf/rls_buf 指令获取与释放。

1.2 类型与常量


// include/basic_api/kernel_common.h:143
using MutexID = uint8_t;

// impl/basic_api/kernel_event.h:708-710
constexpr TBufId INVALID_TBUFID = 255;   // static_cast<uint8_t>(-1)
constexpr TBufId MAX_TBUFID     = 31;    // 硬件上限
constexpr TBufId MAX_MUTEXID    = 27;    // 用户可用上限(28~31 系统预留)

auto id = AllocMutexID();
Mutex::Lock<pipe>(id);
xxx
Mutex::UnLock<pipe>(id);

三种用途共享同一 32 槽位池,由 AllocMutexID/ReleaseMutexID 统一分配。

2. 硬件原理

2.1 信号量机制

每个 bufid 槽位是一个硬件二值信号量。get_buf(pipe, id, mode)pipe 流水线上尝试获取槽位 id

  • 若槽位空闲:获取成功,pipe 流水线继续执行后续指令
  • 若槽位已被占用(前序 get_buf 未配对 rls_buf):阻塞该 pipe 流水线(BLOCK 模式)或继续执行(NON_BLOCK 模式)

rls_buf(pipe, id, mode) 释放槽位 id,使等待该槽位的后续 get_buf 解除阻塞。

2.2 跨流水线同步语义

bufid 的核心价值在于跨异步流水线建立依赖。典型场景:

MTE2 流水:  DataCopy(GM→UB) → rls_buf(MTE2, id)     // 数据就绪,释放锁
V   流水:                              get_buf(V, id) → Add → rls_buf(V, id)  // 等数据就绪后计算
MTE3流水:                                              get_buf(MTE3, id) → DataCopy(UB→GM)  // 等计算完成后搬出

get_buf/rls_buf 配对在不同流水线上形成 happens-before 链,无需 set_flag/wait_flag

3. Basic-API(Mutex::Lock / Mutex::Unlock

using MutexID = uint8_t;

class Mutex {
public:
    template <pipe_t pipe>
    static __aicore__ inline void Lock(MutexID id);     // 仅 Ascend950/5102 编译

    template <pipe_t pipe>
    static __aicore__ inline void Unlock(MutexID id);   // 仅 Ascend950/5102 编译
};

实现特点

  • 运行时 ASCENDC_ASSERT(id <= MAX_MUTEXID),即 id <= 27(比 C-API 更严格)

3.1 ID 分配器(AllocMutexID / ReleaseMutexID

__aicore__ inline MutexID AllocMutexID();     // 返回最低空闲位
__aicore__ inline void ReleaseMutexID(MutexID id);

实现kernel_common.h:172-186):

__BLOCK_LOCAL__ __inline__ uint32_t g_bufId;    // 32 位掩码,每 bit 对应一个槽位

MutexID id = static_cast<uint8_t>(sff0(Internal::g_bufId));    // find-first-zero
Internal::g_bufId = sbitset1(Internal::g_bufId, id);           // 置位
ASCENDC_ASSERT(id <= MAX_MUTEXID, ...);
return id;

  • __BLOCK_LOCAL__每 AI Core block 独立一份(非 per-pipe、非 per-subblock)

4. 实现约束

4.1 编号范围约束

AllocMutexID ReleaseMutexID 只分配 0~27, 超过范围运行时 assert
文档明确:“28-31为系统内部规划预留,不建议使用。”

4.2 模式约束

  • Basic-API Mutex::Lock/Unlock
  • 配对的 lock/unlock 必须使用相同 mode,否则硬件行为不可预测

4.3 配对规则

  • 同一 mutex_idMutex::Lock 必须在 unlock 之前(程序序)
  • lock/unlock 必须严格配对lock, unlock, lock, unlock(不可 lock, lock, unlock, unlock
  • 配对的 lock/unlock 必须使用相同 mutex_id相同 mode
  • 配对的 lock/unlock 必须使用相同 pipe

4.4 禁止嵌套

相同 mutex_id 的 lock/unlock 对不可嵌套(无论 pipe/mode 是否相同),否则硬件行为不可预测。

错误:  lock(id=1) → lock(id=1) → unlock(id=1) → unlock(id=1)   // 嵌套
正确:  lock(id=1) → unlock(id=1) → lock(id=1) → unlock(id=1)   // 顺序

4.5 同流水同 ID 的失效陷阱

同一 pipe + 同一 mutex_id 连续出现两次 lock/unlock 对时,第二次 lock 不会阻塞流水,无法建立同步。同流水线内部依赖应使用 PipeBarrier<pipe>()

4.6 双缓冲交替模式(推荐用法)

为允许流水重叠,分配两个 MutexID,按迭代交替使用:

uint8_t id0 = AllocMutexID();
uint8_t id1 = AllocMutexID();
for (int i = 0; i < n; i++) {
    uint8_t id = (i % 2 == 0) ? id0 : id1;
    // 用 id 同步 MTE2→V→MTE3
}
ReleaseMutexID(id0);
ReleaseMutexID(id1);

这样第 N+1 次的 MTE2 可与第 N 次的 V/MTE3 并行(不同 id 不互斥)。

4.7 多锁联合(join 依赖)

同一流水可同时持有多个锁,表达多源依赖的 join:

Mutex::Lock(PIPE_V, input_mutex);    // 等输入就绪
Mutex::Lock(PIPE_V, output_mutex);   // 等上次输出缓冲释放
// ... 计算 ...
Mutex::Unlock(PIPE_V, input_mutex);
Mutex::Unlock(PIPE_V, output_mutex);

4.8 跨核同步不适用

bufid/mutex 是核内机制。跨核(cross-core)同步使用 ffts_cross_core_sync / wait_flag_dev + flag ID,不使用 bufid。core_mng/roc/ 的 cube_group/group_barrier 也用 event ID,不涉及 bufid。

5. 实战示例

5.1 Basic-API 双缓冲流水(官方示例)

__aicore__ inline void Process() {
    // 分配双缓冲 tensor
    LocalTensor<float> xLocal0 = ubAllocator.Alloc<float, TILE_LENGTH>();
    LocalTensor<float> xLocal1 = ubAllocator.Alloc<float, TILE_LENGTH>();
    // ... yLocal0/1, zLocal0/1 ...

    uint8_t mutexId0 = AscendC::AllocMutexID();
    uint8_t mutexId1 = AscendC::AllocMutexID();

    for (int32_t i = 0; i < loopCount; i++) {
        uint8_t mutexId = (i % 2 == 0) ? mutexId0 : mutexId1;
        auto& x = (i % 2 == 0) ? xLocal0 : xLocal1;
        auto& y = (i % 2 == 0) ? yLocal0 : yLocal1;
        auto& z = (i % 2 == 0) ? zLocal0 : zLocal1;

        // MTE2: GM → UB
        AscendC::Mutex::Lock<PIPE_MTE2>(mutexId);
        AscendC::DataCopy(x, src0Global[...], TILE_LENGTH);
        AscendC::DataCopy(y, src1Global[...], TILE_LENGTH);
        AscendC::Mutex::Unlock<PIPE_MTE2>(mutexId);

        // V: Add
        AscendC::Mutex::Lock<PIPE_V>(mutexId);
        AscendC::Add(z, x, y, TILE_LENGTH);
        AscendC::Mutex::Unlock<PIPE_V>(mutexId);

        // MTE3: UB → GM
        AscendC::Mutex::Lock<PIPE_MTE3>(mutexId);
        AscendC::DataCopy(dstGlobal[...], z, TILE_LENGTH);
        AscendC::Mutex::Unlock<PIPE_MTE3>(mutexId);
    }
    AscendC::ReleaseMutexID(mutexId0);
    AscendC::ReleaseMutexID(mutexId1);
}
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐