pyasc proposal_concat 接口详解:将连续元素合入 Region Proposal 指定字段(昇腾 AI 处理器算子开发)
【免费下载链接】pyasc本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc
asc.language.basic.proposal_concat是 pyasc 中面向目标检测 Region Proposal(区域建议)场景的基础向量算子接口,用于将一段连续排列的元素批量合入目的操作数中 Region Proposals 的指定字段位置,其语义与 Ascend C 的AscendC::ProposalConcat一一对应。本文基于当前仓库的接口文档与源码实现,完整讲解该接口的函数原型、参数取值、数据布局规则、使用约束,并结合仓库中的 IR 定义、代码生成测试与单元测试,还原从 Python 调用到 Ascend C 代码生成的完整链路,帮助开发者在 pyasc 内核中正确、安全地组织 Region Proposal 数据。
一、功能定位:Region Proposal 数据操作族的一员
在目标检测(如 Faster R-CNN 的 RPN 阶段)中,网络会产出大量 Region Proposal,每个 Proposal 通常由 6 个字段组成:左上角坐标x1、y1,右下角坐标x2、y2,置信度score与类别标签label。这类数据在 Ascend C 中的典型组织方式是"按 proposal 分组"的内存布局,而参与计算的张量往往只有某一类字段(例如只有 score),因此需要专门的接口完成两类布局之间的合入(concat)与抽取(extract)。
proposal_concat的功能是:将连续元素合入 Region Proposal 内对应位置,每次迭代会将 16 个连续元素合入到 16 个 Region Proposals 的对应位置里。在 pyasc 的asc.language.basic模块中,它与下列接口共同构成 Region Proposal / 排序类操作族:
proposal_extract:功能与proposal_concat相反,从 Region Proposals 内将相应位置的单个元素抽取后重排,每次迭代处理 16 个 Region Proposals,抽取 16 个元素后连续排列;rp_sort16:根据 Region Proposals 中的score域对其排序(score 大的排前面),每次排 16 个 Region Proposals;sort/sort32/mrg_sort/mrg_sort4:通用降序排序与多路归并排序接口;get_sort_len/get_sort_offset/get_mrg_sort_result:排序结构辅助接口。
接口总览见 basic 模块 API 索引,proposal_concat与proposal_extract分别对应其中的"合入"与"抽取"两个方向,二者配合rp_sort16等排序接口即可搭建 RPN 打分-排序-重组的完整数据通路。
二、函数原型与对应的 Ascend C 函数
Python 接口签名如下:
def proposal_concat(dst: LocalTensor, src: LocalTensor, repeat_time: int, mode_number: int) -> None参数说明(LocalTensor定义见 LocalTensor 文档):
- dst:目的操作数,
LocalTensor类型,内存中按 Region Proposal 分组存放(每个 Proposal 含 6 个字段)。 - src:源操作数,
LocalTensor类型,存放连续排列的目标字段元素;数据类型需要与 dst 保持一致。 - repeat_time:重复迭代次数。每次迭代完成 16 个元素合入到 16 个 Region Proposals 里,下次迭代跳至相邻的下一组 16 个 Region Proposals 和下一组 16 个元素。取值范围:
repeat_time ∈ [0, 255]。 - mode_number:合入位置参数,取值范围:
mode_number ∈ [0, 5],指定 16 个元素具体合入 dst 中每个 Proposal 的哪个字段:- 0:合入
x1 - 1:合入
y1 - 2:合入
x2 - 3:合入
y2 - 4:合入
score - 5:合入
label
- 0:合入
该接口与 Ascend C 的原型完全对应:
template <typename T> __aicore__ inline void ProposalConcat(const LocalTensor<T>& dst, const LocalTensor<T>& src, const int32_t repeatTime, const int32_t modeNumber)从原型可见,Ascend C 侧以模板参数T决定元素类型,而 pyasc 侧的元素类型由两个LocalTensor声明时指定的 dtype 共同决定——这也是"src 与 dst 数据类型必须一致"这一约束的来源。
2.1 数据布局与迭代次数怎么算
结合接口语义可以推断出如下布局关系:
- dst 按 proposal 连续存放,每个 proposal 占 6 个元素(x1/y1/x2/y2/score/label),因此 dst 的有效长度至少为
16 × repeat_time × 6个元素; - src 是连续字段流,有效长度至少为
16 × repeat_time个元素; - 第
i次迭代(i从 0 开始)读取 src 中第[i×16, i×16+15]个元素,分别写入 dst 中第[i×16, i×16+15]个 proposal 的mode_number对应字段位置。
因此,若需合入 N 个 proposal 的同一字段,应取repeat_time = ceil(N / 16)(且 N 向上取整到 16 的倍数后不超过 16×255=4080,超过时需分段调用)。
三、约束说明(越界与对齐)
接口文档给出的约束必须逐条遵守,否则会出现 tensor 越界错误:
- dst 容量约束:用户需保证 dst 中存储的 proposal 数目大于等于实际所需数目(即不小于
16 × repeat_time个 proposal),否则存在 tensor 越界错误; - src 容量约束:用户需保证 src 中存储的元素大于等于实际所需数目(即不小于
16 × repeat_time个元素),否则存在 tensor 越界错误; - 地址对齐约束:操作数地址对齐要求参见《Ascend C 算子开发接口》中的"通用说明和约束-通用地址对齐约束"(即 LocalTensor 地址需满足向量接口通用 32 字节对齐要求)。在 pyasc 中通过
asc.LocalTensor(..., addr=..., tile_size=...)声明时,地址与长度的取法同样受该约束限制。
四、调用示例
最小调用示例(接口文档示例):
asc.proposal_concat(dst, src, repeat_time=2, mode_number=4)即:将 src 中前 32 个连续元素分两次迭代,合入 dst 中前 32 个 Region Proposals 的score字段(mode_number=4)。
结合 pyasc 的 JIT 内核写法,仓库单元测试 test_common_api.py 给出了一个可直接运行的完整形态:
@asc.jit def kernel_proposal_concat() -> None: dst = asc.LocalTensor(dtype=asc.float16, pos=asc.TPosition.VECOUT, addr=0, tile_size=256) src = asc.LocalTensor(dtype=asc.float16, pos=asc.TPosition.VECIN, addr=0, tile_size=256) asc.proposal_concat(dst, src, repeat_time=2, mode_number=4) kernel_proposal_concat[1]() # 在 1 个 AI Core 上启动内核要点:
- 两个操作数均为
LocalTensor,dtype 一致(asc.float16),分别绑定VECOUT/VECIN位置; repeat_time=2表示合入 2 组、共 32 个 proposal 的score字段,故 dst 需容纳 32×6 个元素的 proposal 区(示例中按 256 元素分配,满足容量约束);- 该测试通过 mock launcher 断言内核被正确编译与启动,验证了接口在
@asc.jit装饰的函数体内可直接调用。
五、源码级实现链路:从 Python 调用到 Ascend C 代码生成
从源码结构看,pyasc 将proposal_concat实现为"前端包装 → IR Op → 后端代码发射"三层链路:
Python 前端:proposal.py 中定义了该接口,装饰器
@require_jit保证它只能在@asc.jit编译上下文中调用,@set_common_docstring(api_name="proposal_concat")负责把接口文档同步到 docstring(生成的 API 文档即由此产生)。函数体将repeat_time、mode_number通过materialize_ir_value物化后,调用create_asc_ProposalConcatOp创建 IR 操作:@require_jit @set_common_docstring(api_name="proposal_concat") def proposal_concat(dst: LocalTensor, src: LocalTensor, repeat_time: RuntimeInt, mode_number: RuntimeInt) -> None: global_builder.get_ir_builder().create_asc_ProposalConcatOp(dst.to_ir(), src.to_ir(), _mat(repeat_time).to_ir(), _mat(mode_number).to_ir())IR 层定义:该操作定义在 OpProposal.td,Op 名称为
proposal_concat,携带[AscFunc]接口并绑定发射名ProposalConcat,操作数依次为dst、src两个 LocalTensor 及repeatTime、modeNumber两个任意整型操作数——这与 Ascend C 原型中的const int32_t参数一一对应:def AscendC_ProposalConcatOp : VectorOp<"proposal_concat", "ProposalConcat", [AscFunc]> { let description = "Merge continuous elements into the corresponding positions within the Region Proposal"; let arguments = (ins AscendC_LocalTensor:$dst, AscendC_LocalTensor:$src, AnyType:$repeatTime, AnyType:$modeNumber); }其反向接口
proposal_extract在同一文件中以完全对称的方式定义(OpProposal.td)。后端代码发射:仓库的 lit 测试 vec_proposal.mlir 用 FileCheck 固化了最终生成结果——
ascendc.proposal_concat操作被翻译为对AscendC::ProposalConcat的直接调用,repeat_time、mode_number作为int32_t常量传参:func.func @emit_proposal_concat(%arg0: memref<?xui64, 22>) { ascendc.set_ffts_base_addr %arg0 : memref<?xui64, 22> %c256 = "emitc.constant"() <{value = 256 : ui32}> : () -> ui32 %c0 = "emitc.constant"() <{value = 0 : ui32}> : () -> ui32 %c2_i32 = arith.constant 2 : i32 %c4_i32 = arith.constant 4 : i32 %dst = ascendc.local_tensor_v2 vecout, %c0, %c256 : !ascendc.local_tensor<*xf16> %src = ascendc.local_tensor_v2 vecin, %c0, %c256 : !ascendc.local_tensor<*xf16> ascendc.proposal_concat %dst, %src, %c2_i32, %c4_i32 : !ascendc.local_tensor<*xf16>, !ascendc.local_tensor<*xf16>, i32, i32 return }对应的 CHECK 结果确认最终 C++ 代码为:
AscendC::LocalTensor<half> v4 = AscendC::LocalTensor<half>(AscendC::TPosition::VECOUT, v3, v2); AscendC::LocalTensor<half> v5 = AscendC::LocalTensor<half>(AscendC::TPosition::VECIN, v3, v2); AscendC::ProposalConcat(v4, v5, c2_i32, c4_i32);这说明 pyasc 生成的内核与直接手写
AscendC::ProposalConcat的代码在语义上完全等价,开发者可以把 pyasc 接口视作 Ascend C 模板函数的类型安全 Python 封装。
六、与 proposal_extract 的对照及典型用法
proposal_concat(合入)与proposal_extract(抽取)互为逆操作,二者参数结构完全相同(dst、src、repeat_time ∈ [0,255]、mode_number ∈ [0,5]),区别仅在于数据流方向:
| 接口 | 数据方向 | 典型场景 |
|---|---|---|
proposal_concat | src(连续字段流)→ dst(proposal 分组的指定字段) | 把网络算出的 32 个连续 score 合入 32 个 proposal 的 score 域 |
proposal_extract | src(proposal 分组的指定字段)→ dst(连续字段流) | 把 32 个 proposal 的 score 域抽出为连续张量,供rp_sort16/sort等排序接口使用 |
可以推断,在 RPN 类融合算子中,二者常与rp_sort16组合使用:先用proposal_extract取 score 域连续化 → 排序取 topN 索引 → 再按索引重组(或由sort/mrg_sort完成)。mode_number的六个取值(0~5 对应 x1/y1/x2/y2/score/label)使同一套接口能覆盖所有字段的重排需求,无需为每个字段编写单独的搬运逻辑。
七、关键文件索引
- 接口文档:asc.language.basic.proposal_concat.md
- Python 前端实现:proposal.py
- IR Op 定义:OpProposal.td
- 代码生成测试:vec_proposal.mlir
- 单元测试:test_common_api.py
- 反向接口文档:asc.language.basic.proposal_extract.md
综上,proposal_concat以"每次迭代 16 元素合入 16 个 Region Proposal 的指定字段"为基本粒度,通过repeat_time控制批量、mode_number选择字段,是 pyasc 中构建目标检测 Proposal 数据通路的核心基础接口;使用时务必同时满足 dst/src 的容量约束与向量接口通用地址对齐要求,方可避免越界错误。
【免费下载链接】pyasc本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考