[AI][昇腾950]同步BufID 方案
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_id的Mutex::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);
}
更多推荐




所有评论(0)