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:143usingMutexID=uint8_t;// impl/basic_api/kernel_event.h:708-710constexprTBufId INVALID_TBUFID=255;// static_cast<uint8_t>(-1)constexprTBufId MAX_TBUFID=31;// 硬件上限constexprTBufId MAX_MUTEXID=27;// 用户可用上限(28~31 系统预留)autoid=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)
usingMutexID=uint8_t;classMutex{public:template<pipe_t pipe>static__aicore__inlinevoidLock(MutexID id);// 仅 Ascend950/5102 编译template<pipe_t pipe>static__aicore__inlinevoidUnlock(MutexID id);// 仅 Ascend950/5102 编译};实现特点:
- 运行时
ASCENDC_ASSERT(id <= MAX_MUTEXID),即id <= 27(比 C-API 更严格)
3.1 ID 分配器(AllocMutexID/ReleaseMutexID)
__aicore__inlineMutexIDAllocMutexID();// 返回最低空闲位__aicore__inlinevoidReleaseMutexID(MutexID id);实现(kernel_common.h:172-186):
__BLOCK_LOCAL__ __inline__uint32_tg_bufId;// 32 位掩码,每 bit 对应一个槽位MutexID id=static_cast<uint8_t>(sff0(Internal::g_bufId));// find-first-zeroInternal::g_bufId=sbitset1(Internal::g_bufId,id);// 置位ASCENDC_ASSERT(id<=MAX_MUTEXID,...);returnid;__BLOCK_LOCAL__:每 AI Core block 独立一份(非 per-pipe、非 per-subblock)
4. 实现约束
4.1 编号范围约束
AllocMutexIDReleaseMutexID只分配 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_tid0=AllocMutexID();uint8_tid1=AllocMutexID();for(inti=0;i<n;i++){uint8_tid=(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__inlinevoidProcess(){// 分配双缓冲 tensorLocalTensor<float>xLocal0=ubAllocator.Alloc<float,TILE_LENGTH>();LocalTensor<float>xLocal1=ubAllocator.Alloc<float,TILE_LENGTH>();// ... yLocal0/1, zLocal0/1 ...uint8_tmutexId0=AscendC::AllocMutexID();uint8_tmutexId1=AscendC::AllocMutexID();for(int32_ti=0;i<loopCount;i++){uint8_tmutexId=(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 → UBAscendC::Mutex::Lock<PIPE_MTE2>(mutexId);AscendC::DataCopy(x,src0Global[...],TILE_LENGTH);AscendC::DataCopy(y,src1Global[...],TILE_LENGTH);AscendC::Mutex::Unlock<PIPE_MTE2>(mutexId);// V: AddAscendC::Mutex::Lock<PIPE_V>(mutexId);AscendC::Add(z,x,y,TILE_LENGTH);AscendC::Mutex::Unlock<PIPE_V>(mutexId);// MTE3: UB → GMAscendC::Mutex::Lock<PIPE_MTE3>(mutexId);AscendC::DataCopy(dstGlobal[...],z,TILE_LENGTH);AscendC::Mutex::Unlock<PIPE_MTE3>(mutexId);}AscendC::ReleaseMutexID(mutexId0);AscendC::ReleaseMutexID(mutexId1);}