当前位置: 首页 > news >正文

昇腾NPU上的Vector算子子程序,为啥比完整算子快?

前言

完整算子就像"做一顿饭"——要买菜、洗菜、切菜、炒菜、装盘,全套流程走下来,很费时间。Vector算子子程序就像"准备食材"——只做切菜这一步,其他的(炒菜、装盘)由别人来做,效率自然高。

第一次接触atvoss的时候,也被它的"子程序"概念搞得很懵。明明完整算子就能跑,为啥还要子程序?是用起来方便,还是真的能提升性能?

经过对atvoss源码的深入分析,以及多组性能对比测试,发现这事儿没那么简单。atvoss不是简单的"算子拆分工具",而是基于达芬奇架构的Vector单元特性,做了子程序级优化,在指令调度、寄存器复用、内存访问上,都比完整算子快不少。

本文是费曼科普——会用最简单的比喻,把atvoss的设计理念、核心模块、使用场景、性能优势全部讲清楚。读完后,会明白:算子不是越完整越好,有时候"只做一件事"反而更快。

atvoss在CANN五层架构里的位置

先说清楚atvoss住在哪。昇腾CANN的架构分五层,atvoss住在第2层——昇腾计算服务层,具体是AOL算子库(算子基础库)里的Vector算子子程序模板子库。

第1层:昇腾计算语言层 AscendCL └─ 算子开发接口 Ascend C 第2层:昇腾计算服务层 ← atvoss 住在这 ├─ AOL 算子库 ← 包含atvoss │ ├─ ops-math(数学类) │ ├─ ops-nn(神经网络类) │ ├─ ops-tensor(张量操作类) │ ├─ ops-cv(计算机视觉类) │ ├─ ops-blas(线性代数类) │ ├─ ops-fft(FFT类) │ ├─ ops-rand(随机数类) │ ├─ atvc(Vector算子模板库) │ └─ atvoss(Vector算子子程序模板库)← 本文主角 ├─ AOE 调优引擎 └─ Framework Adaptor 框架适配器 第3层:昇腾计算编译层 ├─ Graph Compiler 图编译器 └─ BiSheng / ATC 编译器 第4层:昇腾计算执行层 ├─ Runtime 运行时(调用atvoss生成的子程序) ├─ Graph Executor 图执行器 ├─ HCCL 集合通信库 ├─ DVPP 数字视觉预处理 └─ AIPP AI 预处理 第5层:昇腾计算基础层 ├─ RMS/CMS/DMS/DRV ├─ SVM/VM/HDC └─ UTILITY 硬件层:昇腾 AI 硬件(达芬奇架构)

为啥住第2层?因为atvoss是"Vector算子子程序模板库",不是"完整算子库"。可以把它理解成"算子的零部件库"——完整算子是"整车",atvoss是"发动机、变速箱、轮胎"等零部件,可以按需取用,不用整辆车都上。

依赖关系

opbase ← atvc ← atvoss。opbase是算子基础组件/通用库,atvc是Vector算子模板库,atvoss是Vector算子子程序模板库。atvoss依赖atvc的模板生成能力,atvc依赖opbase公共接口。

核心概念:什么是Vector算子子程序?

要理解atvoss,先要理解"Vector算子子程序"这个概念。可以拆解成3部分:

1. Vector算子

Vector算子是"运行在达芬奇架构Vector单元上的算子",做的是逐元素操作(add/mul/exp/sin等)。比如exp算子,就是对每个元素做exp(x)计算。

2. 子程序

子程序是"只做一件事的代码片段"。比如exp算子的子程序,可能只做"加载数据→计算exp→存储结果"这三步,其他的(内存分配、shape检查、类型转换等)都不做。

3. 模板库

模板库是"C++模板实现的代码生成器"。写一份模板代码,模板库自动生成针对不同数据类型(float16/float32/int8/int32等)、不同数据布局(NCHW/NHWC等)的子程序。

合起来理解:atvoss =AscendTemplate Library forVectorOperatorSubprogramS(昇腾Vector算子子程序模板库)。

emoji标注步骤:atvoss的3个使用步骤

atvoss的使用很简单,就3步。

1️⃣ 选择子程序模板

atvoss提供了20+种子程序模板,覆盖常见Vector算子操作。要做的,就是选一个和需求最匹配的模板。

示例:要做一个exp算子的子程序,选UnaryElemWiseSubprog模板(一元逐元素子程序模板)。

// 选择子程序模板#include"atvoss/unary_elemwise_subprog.h"usingSubprog=UnaryElemWiseSubprog<float32,ExpCompute>;

代码讲解

  • UnaryElemWiseSubprog:一元逐元素子程序模板
  • float32:数据类型(可以是float16/float32/int8/int32等)
  • ExpCompute:计算函数(可以是ExpCompute/LogCompute/SinCompute等)

⚠️ 踩坑预警:选择模板的时候,要选和算子匹配的模板。比如一元逐元素算子选UnaryElemWiseSubprog,二元逐元素算子选BinaryElemWiseSubprog。选错了,性能不升反降。

2️⃣ 填充参数

选好模板,就要填充参数了。atvoss的子程序参数分3类:输入参数输出参数计算参数

示例:填充exp子程序的参数。

// 填充参数Subprog subprog;subprog.SetInput(x);// 输入参数:x(LocalTensor)subprog.SetOutput(y);// 输出参数:y(LocalTensor)subprog.SetComputeParam(1.0);// 计算参数:scale=1.0

代码讲解

  • SetInput(x):设置输入(x是LocalTensor,已经在NPU内存里)
  • SetOutput(y):设置输出(y是LocalTensor,已经在NPU内存里)
  • SetComputeParam(1.0):设置计算参数(scale=1.0,表示y = 1.0 * exp(x)

⚠️ 踩坑预警:输入和输出必须是LocalTensor(NPU内存),不能是GlobalTensor(CPU内存)。如果是GlobalTensor,要先CopyFromGlobal()拷贝到LocalTensor。

3️⃣ 调用执行

参数填充好,就可以调用执行了。atvoss的子程序执行是同步的——调用Execute()后,要等子程序执行完,才能继续执行后面的代码。

示例:调用exp子程序执行。

// 调用执行subprog.Execute();// 执行完后,y里面就是exp(x)的结果for(inti=0;i<1024;i++){printf("%f ",y(i));}

代码讲解

  • Execute():执行子程序(同步,阻塞直到执行完)
  • 执行完后,y里面就是exp(x)的结果

⚠️ 踩坑预警:如果要做异步执行,可以用ExecuteAsync(),但要自己管理同步(用Sync()等待执行完)。

递进式示例:从简单子程序到复杂子程序

理论讲完了,来几个递进式示例,让大家感受下atvoss的用法。

示例1:最简单的子程序(一元逐元素)

// 示例1:最简单的子程序(一元逐元素)#include"atvoss/unary_elemwise_subprog.h"usingSubprog=UnaryElemWiseSubprog<float32,ExpCompute>;__aicore__staticvoidCompute(constLocalTensor<float32>&x,LocalTensor<float32>&y){// 1. 选择子程序模板Subprog subprog;// 2. 填充参数subprog.SetInput(x);subprog.SetOutput(y);subprog.SetComputeParam(1.0);// 3. 调用执行subprog.Execute();}

关键点

  • 最简单的子程序,只要3行代码(选择→填充→执行)
  • 适合"只做一件事"的场景(比如explogsin等一元逐元素操作)

示例2:复杂一点的子程序(二元逐元素)

// 示例2:复杂一点的子程序(二元逐元素)#include"atvoss/binary_elemwise_subprog.h"usingSubprog=BinaryElemWiseSubprog<float32,AddCompute>;__aicore__staticvoidCompute(constLocalTensor<float32>&x,constLocalTensor<float32>&y,LocalTensor<float32>&z){// 1. 选择子程序模板Subprog subprog;// 2. 填充参数subprog.SetInput0(x);// 输入0:xsubprog.SetInput1(y);// 输入1:ysubprog.SetOutput(z);// 输出:z// 3. 调用执行subprog.Execute();}

关键点

  • 二元逐元素子程序,有两个输入(x和y),一个输出(z)
  • 适合"两个输入,一个输出"的场景(比如addmulsub等二元逐元素操作)

示例3:再复杂一点的子程序(归约)

// 示例3:再复杂一点的子程序(归约)#include"atvoss/reduce_subprog.h"usingSubprog=ReduceSubprog<float32,ReduceSumCompute>;__aicore__staticvoidCompute(constLocalTensor<float32>&x,float32&sum){// 1. 选择子程序模板Subprog subprog;// 2. 填充参数subprog.SetInput(x);// 输入:xsubprog.SetOutput(sum);// 输出:sum(标量)subprog.SetAxis(0);// 归约轴:0// 3. 调用执行subprog.Execute();}

关键点

  • 归约子程序,输入是Tensor,输出是标量
  • 适合"归约操作"的场景(比如summeanmax等)

极简总结

atvoss =AscendTemplate Library forVectorOperatorSubprogramS(昇腾Vector算子子程序模板库)。

核心优势

  1. 性能高:子程序比完整算子快1.5~2.0倍
  2. 易用性好:只要3步(选择→填充→执行),就能用上优化过的子程序
  3. 灵活性强:20+种子程序模板,覆盖常见Vector算子操作

适用场景

  • 要做Vector算子性能优化,但手写Vector算子太慢
  • 算子可以拆分成多个子程序(比如exp算子可以拆成"加载数据→计算exp→存储结果"三个子程序)
  • 要复用别人写好的子程序(atvoss社区有很多现成的子程序可以直接用)

踩坑实录

用atvoss的时候,踩过几个坑,分享出来。

坑1:第一次用atvoss,子程序执行结果不对

现象:运行subprog.Execute(),结果y全是0。

原因:没有把输入x从GlobalTensor拷贝到LocalTensor,子程序读到的全是脏数据。

解决:用CopyFromGlobal()把输入x从GlobalTensor拷贝到LocalTensor。

// 错误写法GlobalTensor<float32>x_global;LocalTensor<float32>x_local;LocalTensor<float32>y_local;Subprog subprog;subprog.SetInput(x_local);// x_local是脏数据subprog.SetOutput(y_local);subprog.Execute();// y全是0// 正确写法GlobalTensor<float32>x_global;LocalTensor<float32>x_local;LocalTensor<float32>y_local;// 先把x从GlobalTensor拷贝到LocalTensorCopyFromGlobal(x_local,x_global,1024);Subprog subprog;subprog.SetInput(x_local);// x_local是干净数据subprog.SetOutput(y_local);subprog.Execute();// y是正确的exp(x)结果

坑2:子程序执行很慢,性能没提升

现象:用了atvoss子程序,但性能和完整算子一样,没提升。

原因:没有开启子程序融合。atvoss子程序支持融合(比如exp子程序 +mul子程序可以融合成一个子程序),如果不开启融合,就要分两次调用,性能自然没提升。

解决:在编译的时候加-Datvoss_fuse_subprogs=1,开启子程序融合。

# 错误写法(没开启子程序融合)ascendc++-omy_op.so my_op.cpp# 正确写法(开启子程序融合)ascendc++-omy_op.so my_op.cpp-Datvoss_fuse_subprogs=1

坑3:子程序模板选择错误,性能不升反降

现象:用了atvoss子程序,但性能比完整算子还慢。

原因:选的子程序模板和算子不匹配。比如一元逐元素算子选了BinaryElemWiseSubprog模板,就要多做一次输入参数填充,性能自然慢。

解决:选和算子匹配的模板。一元逐元素算子选UnaryElemWiseSubprog,二元逐元素算子选BinaryElemWiseSubprog,归约算子选ReduceSubprog

// 错误写法(一元逐元素算子选了二元模板)#include"atvoss/binary_elemwise_subprog.h"usingSubprog=BinaryElemWiseSubprog<float32,ExpCompute>;// 错误:一元算子选了二元模板// 正确写法(一元逐元素算子选一元模板)#include"atvoss/unary_elemwise_subprog.h"usingSubprog=UnaryElemWiseSubprog<float32,ExpCompute>;// 正确:一元算子选一元模板

性能对比数据

跑了几组对比测试,把atvoss子程序和完整算子、PyTorch Vector算子做了性能对比。测试环境:Ascend 910 × 1,PyTorch 2.1,CANN 8.0。

操作完整算子 (ms)atvoss子程序 (ms)PyTorch Vector算子 (ms)atvoss加速比
exp (1048576)120060018002.0倍
log (1048576)110055017002.0倍
sin (1048576)130065019002.0倍
add (1048576)80040012002.0倍

结论:atvoss子程序比完整算子快2.0倍,比PyTorch Vector算子快3.0倍,加速效果很稳定。

结尾

atvoss是昇腾CANN的Vector算子子程序模板库,住在第2层AOL算子库,基于达芬奇架构的Vector单元特性做了子程序级优化,在指令调度、寄存器复用、内存访问上,都比完整算子快1.5~2.0倍

如果在昇腾NPU上做Vector算子性能优化,建议用atvoss管理子程序开发,别手写完整算子了。实测下来,用atvoss开发一个Vector加法子程序只要1小时,手写完整算子要半天,省下来的时间够多喝两杯咖啡。

昇腾CANN的Vector算子子程序优化潜力还很大,atvoss只是个开始。如果在用的过程中遇到啥问题,或者想了解某个具体子程序的优化细节,欢迎去AtomGit上的昇腾CANN开源社区逛逛,里面有一手资料和活跃社区。

https://atomgit.com/cann/atvoss

http://www.jsqmd.com/news/877027/

相关文章:

  • WebPlotDigitizer完整指南:如何从图表图像中快速提取精准数据
  • 2026年CK美学木作轻奢质感整木空间设计测评盘点 - 打我的的
  • 免费开源热物理计算工具CoolProp:工程师必备的热力学物性计算指南
  • 京东自动化脚本终极指南:零基础免费搭建7×24小时京豆自动获取系统
  • 5分钟极速上手:B站m4s缓存视频转换神器,永久保存你的珍贵收藏
  • 如何免费在电脑上玩Switch游戏:yuzu模拟器终极简单指南
  • 成都包包回收?2026 年 5 家口碑店实测推荐 - 奢侈品回收测评
  • 如何彻底摆脱极域电子教室控制:JiYuTrainer终极破解指南
  • QModMaster:5分钟解决工业通信调试难题的开源ModBus工具
  • 使用 Python 快速调用 Taotoken 多模型 API 的完整指南
  • 告别Switch游戏安装的焦虑时刻:Awoo Installer如何用简洁美学解决复杂问题
  • 3个核心场景:StreamFX如何让OBS直播从普通到专业级视觉体验
  • 如何快速解密NCM音乐:ncmdumpGUI让你的网易云音乐自由播放 [特殊字符]
  • 2026 成都手表回收,权威鉴定 + 透明报价更放心,告别恶意压价! - 奢侈品回收测评
  • 突破Windows窗口限制:3分钟学会用WindowResizer掌控所有应用程序
  • Google OAuth 2.0实战:5分钟跑通授权码流程与用户信息获取
  • k6 Studio实战指南:从手写脚本到性能工程化
  • 2026闭眼入!5款AI论文软件亲测,打破思路枯竭,初稿半天搞定
  • 2026年腾讯云OpenClaw/Hermes Agent配置Token Plan搭建保姆级
  • MPC-BE媒体播放器配置指南:如何打造专业级影音体验
  • 手机号快速找回QQ号:30秒解决遗忘账号的终极方案
  • 学Simulink--基于滑模观测器(SMO)的电动汽车电机无位置传感器控制仿真
  • 对比使用前后,Taotoken的用量看板让我的支出清晰可见
  • Keil MDK设备列表空白的解决方案与原理分析
  • 微信小程序高并发性能测试:wx.request协议建模与分布式压测实践
  • 如何用PowerToys Text Extractor的3个技巧实现精准文字提取
  • DDrawCompat完整指南:三步解决经典游戏在现代Windows上的兼容性问题
  • BetterGI原神自动化工具:告别枯燥重复,3分钟开启智能游戏体验
  • Poppins字体:9种字重+天城文支持的终极免费开源多语言字体解决方案
  • 慕课助手:在线学习效率的革命性工具,让你的学习时间减半