pyasc proposal_concat 接口详解:将连续元素合入 Region Proposal 指定字段(昇腾 AI 处理器算子开发)
2026/9/18 13:22:24 网站建设 项目流程

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 个字段组成:左上角坐标x1y1,右下角坐标x2y2,置信度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_concatproposal_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

该接口与 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 越界错误:

  1. dst 容量约束:用户需保证 dst 中存储的 proposal 数目大于等于实际所需数目(即不小于16 × repeat_time个 proposal),否则存在 tensor 越界错误;
  2. src 容量约束:用户需保证 src 中存储的元素大于等于实际所需数目(即不小于16 × repeat_time个元素),否则存在 tensor 越界错误;
  3. 地址对齐约束:操作数地址对齐要求参见《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 → 后端代码发射"三层链路:

  1. Python 前端:proposal.py 中定义了该接口,装饰器@require_jit保证它只能在@asc.jit编译上下文中调用,@set_common_docstring(api_name="proposal_concat")负责把接口文档同步到 docstring(生成的 API 文档即由此产生)。函数体将repeat_timemode_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())
  2. IR 层定义:该操作定义在 OpProposal.td,Op 名称为proposal_concat,携带[AscFunc]接口并绑定发射名ProposalConcat,操作数依次为dstsrc两个 LocalTensor 及repeatTimemodeNumber两个任意整型操作数——这与 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)。

  3. 后端代码发射:仓库的 lit 测试 vec_proposal.mlir 用 FileCheck 固化了最终生成结果——ascendc.proposal_concat操作被翻译为对AscendC::ProposalConcat的直接调用,repeat_timemode_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(抽取)互为逆操作,二者参数结构完全相同(dstsrcrepeat_time ∈ [0,255]mode_number ∈ [0,5]),区别仅在于数据流方向:

接口数据方向典型场景
proposal_concatsrc(连续字段流)→ dst(proposal 分组的指定字段)把网络算出的 32 个连续 score 合入 32 个 proposal 的 score 域
proposal_extractsrc(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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询