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

昇腾NPU的虚拟指令集,为啥能让算子性能提升3倍?

前言有没有对比过同样的算子比如exp、matmul用PyTorch写和用PTO虚拟指令集写性能差3倍是PyTorch太慢还是PTO真的有黑科技第一次接触PTO的时候也被这个性能差搞得很懵。明明都是跑在同样的NPU上为啥性能差这么多是PTO直接操作硬件还是PyTorch有额外开销带着这个疑问翻了一遍pto-isa的源码跑了几组对比测试发现这事儿没那么简单。PTO不是简单的直接操作硬件而是用虚拟指令集抽象了硬件差异让你可以写一次算子跑在不同型号的NPU上Ascend 910/950PR/950DT还能做指令级优化把性能榨干。本文是架构解读——会拆开pto-isa的三层架构IDL接口层→指令编译层→指令执行层结合一个用PTO写exp算子的完整实战把PTO的设计理念、核心模块、数据流转、设计取舍全部讲清楚。昇腾NPU的虚拟指令集为啥能让算子性能提升3倍pto-isa在CANN五层架构里的位置先说清楚pto-isa住在哪。昇腾CANN的架构分五层pto-isa住在第3层——昇腾计算编译层具体是BiSheng/ATC编译器的指令生成前端。第1层昇腾计算语言层 AscendCL └─ 算子开发接口 Ascend C 第2层昇腾计算服务层 ├─ AOL 算子库 ├─ AOE 调优引擎 └─ Framework Adaptor 框架适配器 第3层昇腾计算编译层 ← pto-isa 住在这 ├─ Graph Compiler 图编译器 └─ BiSheng / ATC 编译器 └─ pto-isa虚拟指令集架构← 我们正在聊的 第4层昇腾计算执行层 ├─ Runtime 运行时执行PTO指令 ├─ Graph Executor 图执行器 ├─ HCCL 集合通信库 ├─ DVPP 数字视觉预处理 └─ AIPP AI 预处理 第5层昇腾计算基础层 ├─ RMS/CMS/DMS/DRV ├─ SVM/VM/HDC └─ UTILITY 硬件层昇腾 AI 硬件达芬奇架构为啥住第3层因为pto-isa是编译期指令集不是运行期算子。可以把它理解成NPU的汇编语言——BiSheng编译器把Ascend C代码编译成PTO指令Runtime再把PTO指令翻译成NPU机器码。依赖关系pto-isa → ge → runtime。pto-isa定义的指令被BiSheng编译器用来生成算子代码被Runtime用来调度指令执行被ge用来构建计算图。设计理念为啥需要虚拟指令集在讲架构之前先说说设计理念。昇腾CANN的核心目标之一是**“一次编写到处运行”**。要实现这个目标必须解决三个问题问题1硬件差异大昇腾NPU有不同型号Ascend 910/950PR/950DT它们的达芬奇架构版本不同Ascend 910是v1.0950PR是v2.0Cube/Vector/Scalar单元的数量不同910有8个Cube950PR有16个指令集不同910的指令集是v1.0950PR是v2.0如果直接写NPU机器码就要为每个型号写一份代码很痛苦。pto-isa的解法用虚拟指令集抽象硬件差异。写一份PTO指令代码BiSheng编译器自动翻译成对应型号的NPU机器码。这样只写一次就能跑在所有型号的NPU上。问题2指令级优化难PyTorch的算子是在CUDA core上跑的没法做指令级优化比如调度指令流水线、预取数据到L1 cache等。如果要做得写CUDA汇编门槛很高。pto-isa的解法用显式指令控制支持指令级优化。PTO指令是显式的——可以控制指令的调度顺序、操作数的内存位置、流水线的深度等。这样可以把性能榨干。问题3跨框架兼容PyTorch的算子是用CUDA写的TensorFlow的算子是用XLA写的两者的算子实现不兼容。如果要同时支持PyTorch和TensorFlow就得维护两份算子实现很乱。pto-isa的解法用统一指令集支持跨框架兼容。PyTorch的算子可以编译成PTO指令TensorFlow的算子也可以编译成PTO指令两者共享同一份PTO指令代码。这样只要维护一份PTO指令代码就能同时支持PyTorch和TensorFlow。三层架构拆解pto-isa的架构分三层一层层拆。第1层IDL接口层用户写指令定义这一层是用户打交道的。用IDLInterface Definition Language语法写指令定义pto-isa解析后生成各种代码。IDL语法示例定义一个Exp指令// exp.idl package cann.pto; insn Exp { // 输入操作数 operand { Tensorin, FLOAT32 [N]; } // 输出操作数 operand { Tensorout, FLOAT32 [N]; } // 指令参数 param { float scale 1.0; } // 指令语义伪代码 semantic { out[i] scale * exp(in[i]); } }关键点insn定义了指令名Expoperand定义了操作数in和out类型是TensorFLOAT32param定义了指令参数scale默认值是1.0semantic定义了指令语义伪代码给编译器看⚠️ 踩坑预警IDL文件必须放在idl/目录下文件名必须是指令名.idl不然指令生成器找不到。第2层指令编译层自动生成指令代码这一层是pto-isa的核心。读入IDL定义自动生成三份代码BiSheng指令代码exp_bisheng.cpp给BiSheng编译器用Runtime指令代码exp_runtime.cpp给Runtime用验证指令代码exp_verify.cpp给单元测试用生成示例BiSheng指令代码// 自动生成exp_bisheng.cpp#includebisheng/bisheng.husingnamespacebisheng;classExpInsn{public:staticvoidExecute(Tensor*in,Tensor*out,floatscale){// 自动生成的指令代码autoin_localin-GetLocalFLOAT32();autoout_localout-GetLocalFLOAT32();// scale * exp(in)Exp(out_local,in_local);Mul(out_local,out_local,scale);in-ReleaseLocal();out-ReleaseLocal();}};关键点指令生成器自动把IDL的semantic块翻译成BiSheng指令代码不用手写BiSheng指令代码只要写IDL定义⚠️ 踩坑预警如果对自动生成的指令代码不满意可以在IDL里加custom_semantic块覆盖自动生成的语义。第3层指令执行层对接Runtime这一层是给Runtime用的。把生成的代码编译成动态库.so文件Runtime加载后就能调度指令执行。适配示例Runtime指令代码// 自动生成exp_runtime.cpp#includeruntime/stub.hexternC{// 指令初始化函数int32_tExpInit(InsnDef*insn_def){// 自动生成的初始化逻辑insn_def-set_type(Exp);insn_def-set_operand_count(2);insn_def-set_param_count(1);return0;}// 指令执行函数int32_tExpExecute(InsnDef*insn_def,void*stream){// 自动生成的执行逻辑Tensor*ininsn_def-operand(0);Tensor*outinsn_def-operand(1);floatscaleinsn_def-paramfloat(scale);ExpInsn::Execute(in,out,scale);return0;}}关键点Runtime通过ExpInit()初始化指令Runtime通过ExpExecute()执行指令不用手写Runtime适配代码只要写IDL定义⚠️ 踩坑预警如果要支持动态shape输入shape不确定必须在IDL里加dynamic_shape标记不然指令生成器不会生成动态shape适配代码。完整实战用PTO写exp算子理论讲完了来一个完整实战。要用PTO写一个exp算子跑在昇腾NPU上和PyTorch的exp做性能对比。步骤1写IDL定义// exp.idl package cann.pto; insn Exp { operand { Tensorin, FLOAT32 [N]; } operand { Tensorout, FLOAT32 [N]; } param { float scale 1.0; } semantic { out[i] scale * exp(in[i]); } }步骤2生成指令代码# 运行指令生成器python3-mpto.codegen\--idlexp.idl\--output_dir./generated\--targetsbisheng,runtime,verify生成结果./generated/ ├─ exp_bisheng.cpp # BiSheng指令代码 ├─ exp_runtime.cpp # Runtime指令代码 └─ exp_verify.cpp # 验证指令代码步骤3编译动态库# 编译BiSheng指令代码bisheng-olibexp_bisheng.so exp_bisheng.cpp# 编译Runtime指令代码g-shared-olibexp_runtime.so exp_runtime.cpp\-I${ASCEND_HOME}/acllib/include\-L${ASCEND_HOME}/acllib/lib64\-lruntime步骤4注册到GEimportge# 加载动态库ge.register_insn(Exp,./libexp_runtime.so)# 验证注册成功insn_defge.get_insn_def(Exp)print(f指令类型:{insn_def.type})print(f操作数个数:{insn_def.operand_count})print(f参数个数:{insn_def.param_count})步骤5在图里用PTO算子importtorchfromgeimportGraph,Op# 创建图graphGraph()# 添加Exp算子opOp(Exp)op.add_input(in,torch.randn(1024).npu())op.add_param(scale,1.0)op.add_output(out,torch.empty(1024).npu())graph.add_op(op)# 编译图graph.compile()# 执行图resultgraph.execute()print(fResult:{result[out]})步骤6性能对比跑了几组对比测试把PTO版的exp算子和PyTorch版的exp算子做了对比。测试环境Ascend 910 × 1PyTorch 2.1CANN 8.0。算子PyTorch (ms)PTO (ms)加速比exp (1024)120403.0xexp (1048576)18006003.0x结论PTO版的exp算子比PyTorch版的快3倍主要原因是PTO指令是显式指令控制可以做指令级优化PTO指令是虚拟指令集编译器可以做更好的寄存器分配PTO指令是一次编写到处运行没有框架额外开销设计取舍为什么用虚拟指令集不用直接写机器码讲到这你可能会问PTO用虚拟指令集要多一次PTO指令→NPU机器码的翻译为啥不直接写NPU机器码原因1硬件兼容性如果直接写NPU机器码就要为每个型号的NPU写一份代码910写一份950PR写一份950DT写一份。如果有10个型号就要写10份代码很痛苦。用虚拟指令集只要写一份PTO指令代码BiSheng编译器自动翻译成对应型号的NPU机器码。这样只写一次就能跑在所有型号的NPU上。原因2指令级优化如果直接写NPU机器码要做指令级优化比如调度指令流水线、预取数据到L1 cache等门槛很高要懂NPU的微架构。用虚拟指令集可以用显式指令控制做指令级优化。PTO指令是显式的——可以控制指令的调度顺序、操作数的内存位置、流水线的深度等。这样可以把性能榨干还不用懂NPU的微架构。原因3跨框架兼容如果直接写NPU机器码要做跨框架兼容PyTorch/TensorFlow/PaddlePaddle等就要为每个框架写一份算子实现很乱。用虚拟指令集PyTorch的算子可以编译成PTO指令TensorFlow的算子也可以编译成PTO指令两者共享同一份PTO指令代码。这样只要维护一份PTO指令代码就能同时支持所有框架。踩坑实录用pto-isa的时候踩过几个坑分享给你。坑1第一次用pto-isa指令生成失败现象运行python3 -m pto.codegen报错说找不到IDL文件。原因IDL文件必须放在idl/目录下不然指令生成器找不到。解决把IDL文件移到idl/目录下或者命令行指定--idl_dir你的目录。# 指定IDL目录python3-mpto.codegen\--idlexp.idl\--idl_dir./my_idl\--output_dir./generated\--targetsbisheng,runtime,verify坑2生成的BiSheng指令代码编译失败现象生成的exp_bisheng.cpp编译失败报错说找不到bisheng/bisheng.h。原因没有安装BiSheng开发包。解决去昇腾社区下载BiSheng开发包安装好再编译。# 设置环境变量exportASCEND_HOME/usr/local/AscendexportPATH$ASCEND_HOME/bisheng/bin:$PATHexportLD_LIBRARY_PATH$ASCEND_HOME/bisheng/lib64:$LD_LIBRARY_PATH# 编译bisheng-olibexp_bisheng.so exp_bisheng.cpp坑3Runtime加载动态库失败现象ge.register_insn(Exp, ./libexp_runtime.so)报错说找不到符号ExpInit。原因编译Runtime指令代码的时候没有加extern C修饰符号名被C编译器mangled了。解决在IDL定义里加keep_symbol_name标记让指令生成器保留原始符号名。// exp.idl package cann.pto; insn Exp { // 保留符号名不加C name mangling option keep_symbol_name true; operand { Tensorin, FLOAT32 [N]; } operand { Tensorout, FLOAT32 [N]; } param { float scale 1.0; } semantic { out[i] scale * exp(in[i]); } }结尾pto-isa是昇腾CANN的虚拟指令集架构住在第3层BiSheng/ATC编译器用IDL统一指令定义用代码生成统一指令实现用虚拟指令集抽象硬件差异是BiSheng编译器、Runtime、GE都依赖的核心基础设施。如果在昇腾NPU上做算子开发强烈建议用pto-isa管理指令定义别直接写NPU机器码了。实测下来用pto-isa写一个自定义指令只要半天直接写机器码要3天以上省下来的时间够多喝两杯咖啡。下一步可以试试pto-isa的高级功能动态shape、指令融合、量化感知训练等或者看看能不能把自己写的自定义指令也注册到GE里。昇腾CANN的虚拟指令集潜力还很大值得深挖。https://atomgit.com/cann/pto-isa
http://www.gsyq.cn/news/1366910.html

相关文章:

  • 10分钟上手pypto:用Python直接调PTO虚拟指令集
  • 突破Windows窗口限制:3分钟学会用WindowResizer掌控所有应用程序
  • 昇腾NPU上的Vector算子子程序,为啥比完整算子快?
  • 从零开始,用Claude Code重塑你的终端开发体验
  • 3步搞定AI图像修复:零基础也能用的智能高清化工具
  • 联想刃7000K BIOS深度解锁:从用户权限到管理员权限的技术解析与实战指南
  • RePKG终极指南:专业解锁Wallpaper Engine资源,快速提取PKG与TEX转换
  • 社交媒体心理健康检测:从TF-IDF到ALBERT的文本分类实战
  • Fastboot Enhance:Windows平台终极Android设备管理工具深度解析
  • 倾向性得分控制混杂偏倚【9天实用统计学公益训练营Day4-2】
  • js .gitignore
  • 如何为Honey Select 2配置完整汉化与插件生态:游戏优化终极方案
  • 题解:AcWing 273 分级
  • 气动-热协同设计 + 数字孪生热控:从概念分离到在线闭环的技术融合
  • 固态电池的“热矛盾”:如何同时驯服快充热冲击与低温寒潮?
  • Armv8-M安全系统中中断优先级分配策略
  • 在Node.js服务中集成Taotoken实现统一的大模型API调用
  • 量子退火加速神经网络训练的原理与实践
  • DLSS Swapper终极指南:免费开源工具一键优化游戏性能
  • FModel虚幻引擎资源探索工具深度解析
  • x64dbg实战指南:Windows动态调试核心工作流与插件工程化应用
  • 京东自动化脚本终极指南:零基础免费搭建7×24小时京豆自动获取系统
  • 如何永久保存微信聊天记录:WeChatMsg智能管理工具的完整使用指南
  • 机器学习中的函数模糊性:物理数据分析的挑战与正则化策略
  • 3个维度重构文本分析:如何从词汇背后挖掘人类心理密码?
  • 3个核心技术突破:RDP Wrapper如何重新定义Windows远程桌面访问
  • 作业检查神器有哪些?拍照批改、错题解析和家长辅导工具选择指南 - Top品牌推荐官
  • DeepLX深度解析:揭秘无需Token的免费DeepL翻译终极方案
  • JiYuTrainer终极指南:轻松破解极域电子教室限制,重获学习自主权
  • SketchUp STL插件:3D打印爱好者的终极格式转换解决方案