pyasc TPipe.init_buffer 详解:为 TQue 队列与 TBuf 临时变量分配 Device 端内存
【免费下载链接】pyasc本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc
asc.language.fwk.TPipe.init_buffer是 CANN pyasc 算子编程接口中负责 Device 端(如 Unified Buffer、L1 Buffer 等)内存分配的核心 API,它为一类 Kernel 中必须且只能存在一个的TPipe资源管理器提供了两种内存分配入口:为流水队列TQue分配多块内存(支持 double buffer 双缓冲),以及为临时变量容器TBuf分配单块内存。读完本文,你将掌握init_buffer两种重载的完整语义、参数取值与 32 字节对齐规则、double buffer 的开启方式、内存生命周期约束,并能结合仓库示例(examples/02_add_framework/add_framework.py)与源码(python/asc/language/fwk/tpipe.py)写出可直接运行的流水化算子。
一、init_buffer 在 pyasc 内存管理中的定位
在 pyasc 中,一个 Kernel 函数必须且只能初始化一个TPipe对象(对应源码中TPipeManager的全局唯一实例管理,见 python/asc/language/fwk/tpipe.py 的TPipeManager.set/get实现)。TPipe统一管理 Device 端内存与同步事件,其主要能力包括:
- 内存资源管理:通过
init_buffer接口为TQue(队列)和TBuf(临时变量缓冲区)分配内存; - 同步事件管理:通过
alloc_event_id、release_event_id等接口申请和释放事件 ID,用于流水线同步控制。
其中init_buffer是内存管理的人口,后续所有基于TQue的alloc_tensor/deque/enque,以及基于TBuf的get/get_with_offset,都建立在其分配出的内存块之上。对应地,在 docs/python-api/language/fwk.md 的TPipe章节中,init_buffer被明确描述为"用于为 TQue 等队列和 TBuf 分配内存"。
二、函数签名与两种重载形式
TPipe.init_buffer提供两个重载,分别面向队列对象与临时缓冲区对象:
# 重载 1:为 TQue 等队列分配内存 TPipe.init_buffer(que: TQue, num: int = 0, len: int = 0) -> None # 重载 2:为 TBuf 分配内存 TPipe.init_buffer(buf: TBuf, len: int = 0) -> None其对应的 Ascend C 函数原型分别为:
template <class T> __aicore__ inline bool InitBuffer(T& que, uint8_t num, uint32_t len) template <TPosition bufPos> __aicore__ inline bool InitBuffer(TBuf<bufPos>& buf, uint32_t len)在 pyasc 的 Python 侧,这两种重载通过OverloadDispatcher分发实现(python/asc/language/fwk/tpipe.py):
@dispatcher.register(que=TQue, num=RuntimeInt, len=RuntimeInt) def _(que: TQue, num: RuntimeInt = 0, len: RuntimeInt = 0): global_builder.get_ir_builder().create_asc_TPipeInitQueueOp(self.to_ir(), que.to_ir(), _mat(num, KnownTypes.int_).to_ir(), _mat(len, KnownTypes.int_).to_ir()) @dispatcher.register(buf=TBuf, len=RuntimeInt) def _(buf: TBuf, len: RuntimeInt = 0): global_builder.get_ir_builder().create_asc_TPipeInitBufferOp(self.to_ir(), buf.to_ir(), _mat(len, KnownTypes.int_).to_ir())可以看到,init_buffer最终会分别生成asc_TPipeInitQueueOp(队列内存初始化)与asc_TPipeInitBufferOp(缓冲区内存初始化)两种 IR 算子,将TPipe、TQue/TBuf及内存参数一起下发给编译后端,进而映射为昇腾指令侧的缓冲区初始化逻辑。
三、参数说明与取值细节
3.1 que + num + len(队列重载)
| 参数 | 类型 | 说明 |
|---|---|---|
que | TQue | 需要分配内存的 TQue 等队列对象,通常以asc.TQue(asc.TPosition.VECIN, depth)形式创建 |
num | int | 分配内存块的个数。double buffer 功能通过该参数开启:num设置为 1 表示不开启 double buffer;设置为 2 表示开启 double buffer |
len | int | 每个内存块的大小,单位为字节。当传入的len不满足 32 字节对齐时,API 内部会自动向上补齐至 32 字节对齐,后续的数据搬运过程会涉及非对齐处理 |
que的类型可以是TQue,也可以是TQueBind(TQue继承自TQueBind,TQue是TQueBind的简化模式)。TQue构造时传入的TPosition逻辑位置决定了内存分配的位置(如VECIN、VECOUT、VECCALC等),TPosition枚举定义见 python/asc/language/core/enums.py,常用取值包括:
GM(全局内存)、A1/A2/B1/B2/C1/C2(Cube 侧逻辑位置)、CO1/CO2;VECIN、VECOUT、VECCALC(向量计算侧逻辑位置)。
3.2 buf + len(缓冲区重载)
| 参数 | 类型 | 说明 |
|---|---|---|
buf | TBuf | 需要分配内存的 TBuf 对象,用于管理临时变量占用的内存,存储位置通过TBuf(pos)构造时的TPosition指定 |
len | int | 为 TBuf 分配的内存大小,单位为字节。与队列重载相同,非 32 字节对齐的len会被 API 内部自动向上补齐至 32 字节对齐 |
在 pyasc 中TBuf同样继承自TQueBind,其占用的存储空间由TPipe管理。init_buffer完成内存初始化后,即可通过TBuf.get(dtype, len)或TBuf.get_with_offset(size, buf_offset, dtype)获取指定长度的LocalTensor参与计算。值得注意的是,TBuf.get_with_offset在源码中对偏移量做了显式的 32 字节对齐校验(python/asc/language/fwk/tpipe.py 中if buf_offset % 32 != 0: raise ValueError("buf_offset must be align to 32B.")),与init_buffer内部的 32 字节对齐规则保持一致。
四、约束说明
使用init_buffer时需要遵守以下约束:
- 自动释放:
init_buffer申请的内存会在TPipe对象销毁时通过析构函数自动释放,无需手动释放。 - 重新分配需先 reset:如果需要重新分配
init_buffer申请的内存,应先调用TPipe.reset(),再调用init_buffer。reset完成资源的释放与 eventId 等变量的初始化操作,使TPipe恢复到初始化状态(详见 docs/python-api/language/generated/asc.language.fwk.TPipe.reset.md)。 - Buffer 总数上限:一个 Kernel 中所有使用的 Buffer 数量之和不能超过 64。
五、调用示例:为 TQue 与 TBuf 分配内存
以下示例完整继承自 docs/python-api/language/generated/asc.language.fwk.TPipe.init_buffer.md,展示了两种重载的标准用法:
# 为TQue分配内存,分配内存块数为2,每块大小为128字节(num=2,开启double buffer) pipe = asc.Tpipe() que = asc.TQue(asc.TPosition.VECOUT, 2) num = 2 len = 128 pipe.init_buffer(que=que, num=num, len=len) # 为TBuf分配内存,分配长度为128字节 pipe = asc.Tpipe() buf = asc.TBuf(asc.TPosition.A1) len = 128 pipe.init_buffer(buf=buf, len=len)六、实战:结合 double buffer 编写流水化算子
num参数对 double buffer 的控制是init_buffer最重要的实战价值。以仓库示例 examples/02_add_framework/add_framework.py 为例,一个采用 2 级缓冲(BUFFER_NUM = 2)的向量加法 Kernel 完整展示了init_buffer的典型用法:
BUFFER_NUM = 2 # BUFFER_NUM should be 1 or 2 @asc.jit def vadd_kernel(x: asc.GlobalAddress, y: asc.GlobalAddress, z: asc.GlobalAddress, block_length: int, tile_length: asc.ConstExpr[int]): offset = asc.get_block_idx() * block_length x_gm = asc.GlobalTensor() y_gm = asc.GlobalTensor() z_gm = asc.GlobalTensor() x_gm.set_global_buffer(x + offset) y_gm.set_global_buffer(y + offset) z_gm.set_global_buffer(z + offset) pipe = asc.TPipe() in_queue_x = asc.TQue(asc.TPosition.VECIN, BUFFER_NUM) in_queue_y = asc.TQue(asc.TPosition.VECIN, BUFFER_NUM) out_queue_z = asc.TQue(asc.TPosition.VECOUT, BUFFER_NUM) pipe.init_buffer(in_queue_x, BUFFER_NUM, tile_length * x.dtype.sizeof()) pipe.init_buffer(in_queue_y, BUFFER_NUM, tile_length * y.dtype.sizeof()) pipe.init_buffer(out_queue_z, BUFFER_NUM, tile_length * z.dtype.sizeof()) for i in range(TILE_NUM * BUFFER_NUM): copy_in(i, x_gm, y_gm, in_queue_x, in_queue_y, tile_length) compute(z_gm, in_queue_x, in_queue_y, out_queue_z, tile_length) copy_out(i, z_gm, out_queue_z, tile_length)该示例中的三个要点:
- 队列深度与内存块数对应:
TQue(asc.TPosition.VECIN, BUFFER_NUM)的 depth 与init_buffer的num保持一致(均为BUFFER_NUM),即每个队列可容纳两块缓冲,实现搬入(copy_in)与计算(compute)、搬出(copy_out)的流水重叠。 - len 按数据类型换算:
len = tile_length * dtype.sizeof(),其中dtype.sizeof()返回单个元素的字节数,保证每块内存恰好容纳一个 tile 的数据。 alloc_tensor与init_buffer的联动:TQue.alloc_tensor分配的 Tensor 大小即为init_buffer时设置的每块内存长度(docs/python-api/language/fwk.md 中TQue.alloc_tensor的描述),因此len的设置直接决定后续data_copy、add等算子可操作的数据量。
类似的双缓冲写法在仓库测试中大量出现,例如 python/test/generalization/basic/test_vadd.py 同样以buffer_num同时作为队列 depth 与init_buffer的num,并在copy_in/compute/copy_out三段流水间通过队列完成同步。
七、实战:TBuf 临时缓冲区的分配与使用
TBuf用于管理 Kernel 内临时变量占用的内存,适用于向量计算中需要中间缓冲区的场景。结合 docs/python-api/language/generated/asc.language.fwk.TBuf.get.md 中的示例与仓库测试 python/test/generalization/adv/test_quant.py 的用法,完整链路如下:
# 为TBuf初始化分配内存,分配内存长度为1024字节 pipe = asc.Tpipe() calc_buf = asc.TBuf(asc.TPosition.VECCALC) byte_len = 1024 pipe.init_buffer(calc_buf, byte_len) # 从calc_buf获取Tensor,Tensor为pipe分配的所有内存大小,为1024字节 temp_tensor1 = calc_buf.get(asc.int32) # 从calc_buf获取Tensor,Tensor为128个int32_t类型元素的内存大小,为512字节 temp_tensor1 = calc_buf.get(asc.int32, 128)仓库测试 python/test/generalization/adv/test_quant.py 中的实际使用模式与之完全一致:
tmp_buf = asc.TBuf(asc.TPosition.VECCALC) pipe.init_buffer(buf=tmp_buf, len=tmp_min_bytes) tmp_local = tmp_buf.get(asc.uint8)使用TBuf.get时需要注意:len的数值是 Tensor 中元素的个数,len * sizeof(T)不能超过init_buffer时设置的 TBuf 初始化长度;若要按字节偏移取子块,则使用get_with_offset,且偏移量必须 32 字节对齐。
八、与 TPipe 生命周期管理的配合
init_buffer分配的内存生命周期由TPipe统一管理,在实际 Kernel 中通常与以下接口配合使用:
TPipe.reset():释放资源并初始化 eventId,使TPipe恢复到初始化状态,之后可再次调用init_buffer重新分配。典型的多轮复用模式(来自 docs/python-api/language/generated/asc.language.fwk.TPipe.reset.md):
pipe = asc.Tpipe() que = asc.TQue(asc.TPosition.VECOUT, 1) num = 1 len = 192 * 1024 for i in range(2): pipe.init_buffer(que=que, num=num, len=len) ... # process pipe.reset()TPipe.init_buf_pool()/TBufPool.init_buffer():在内存资源有限、需要手动指定 UB/L1 内存资源复用的场景下,先用TPipe.init_buf_pool划分整块资源池,再用TBufPool.init_buffer为其中的TQue/TBuf分配内存(详见 docs/python-api/language/fwk.md 的TBufPool章节)。此时TBufPool声明时通过buf_id_size指定可分配 Buffer 的最大数量,默认上限为 4,最大为 16。TPipe.destroy():显式释放资源;而普通场景下不调用destroy时,init_buffer申请的内存也会在TPipe对象销毁时自动释放。
九、小结
TPipe.init_buffer是 pyasc 算子编写中内存初始化的起点:
- 两种重载分别面向流水队列
TQue(que + num + len)与临时缓冲TBuf(buf + len); num参数同时承担"内存块个数"与"double buffer 开关"两个角色(1 关闭、2 开启);len参数以字节为单位,非 32 字节对齐时会自动向上补齐,与TBuf.get_with_offset的 32 字节对齐校验、data_copy等算子的对齐要求保持一致;- 生命周期约束包括 TPipe 析构自动释放、重新分配前需
reset、单 Kernel Buffer 总数不超过 64; - 源码层面,该接口通过
OverloadDispatcher分发为asc_TPipeInitQueueOp与asc_TPipeInitBufferOp两种 IR 算子(python/asc/language/fwk/tpipe.py),是理解 pyasc 内存分配链路的重要入口。
掌握init_buffer后,可进一步阅读 docs/python-api/language/fwk.md 中的TQue(alloc_tensor/deque/enque)、TBuf(get/get_with_offset)、TQueBind与TBufPool系列接口,并结合 examples/02_add_framework/add_framework.py、examples/01_add/add.py 等示例,构建完整的流水化算子。
【免费下载链接】pyasc本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考