Profiling性能数据采集
基本原理
该章节下的接口用于Profiling采集性能数据,实现方式支持以下三种:
Profiling AscendCL API(通过Profiling AscendCL API采集并落盘性能数据):实现将采集到的Profiling数据写入文件,再使用Profiling工具解析该文件(请参见《CANN 开发工具指南》中的“Profiling工具使用指南”章节下的“解析Profiling数据”),并展示性能分析数据。
- aclprofInit接口、aclprofStart接口、aclprofStop接口、aclprofFinalize接口配合使用,实现该方式的性能数据采集。该方式可获取AscendCL的接口性能数据、AI Core上算子的执行时间、AI Core性能指标数据等。目前这些接口为进程级控制,表示在进程内任意线程调用该接口,其它线程都会生效。
一个进程内,可以根据需求多次调用这些接口,基于不同的Profiling采集配置,采集数据。
- 调用aclInit接口,在AscendCL初始化阶段,通过*.json 文件传入要采集的Profiling数据。该方式可获取AscendCL的接口性能数据、AI Core上算子的执行时间、AI Core性能指标数据等。
一个进程内,只能调用一次aclInit接口,如果要修改Profiling采集配置,需修改*.json文件中的配置。详细使用说明请参见aclInit接口处的说明,不在本章节描述。
Profiling AscendCL API for Extension(Profiling AscendCL API扩展接口):当用户需要定位应用程序或上层框架程序的性能瓶颈时,可在Profiling采集进程内(aclprofStart接口、aclprofStop接口之间)调用Profiling AscendCL API扩展接口(统称为msproftx功能),开启记录应用程序执行期间特定事件发生的时间跨度,并将数据写入Profiling数据文件,再使用Profiling工具解析该文件,并导出展示性能分析数据。
msproftx:msprof tool extension,MindStudio系统调优工具扩展。
Profiling工具解析导出操作请参见《CANN 开发工具指南》中的“Profiling工具使用指南”章节下的“解析Profiling数据”和“导出Profiling数据”。
接口调用方式:在aclprofStart和aclprofStop接口之间调用aclprofCreateStamp、aclprofSetStampTraceMessage、aclprofMark、aclprofPush、aclprofPop、aclprofRangeStart、aclprofRangeStop、aclprofDestroyStamp接口。该方式可获取应用程序执行期间特定时间发生的事件并记录事件发生的时间跨度。
一个进程内,可以根据需求多次调用这些接口。
Profiling AscendCL API for Subscription(订阅算子信息的Profiling AscendCL API):实现将采集到的Profiling数据解析后写入管道,由用户读入内存,再由用户调用AscendCL的接口获取性能数据。
接口调用方式:aclprofModelSubscribe接口、aclprofGet*接口、aclprofModelUnSubscribe接口配合使用,实现该方式的性能数据采集,当前支持获取网络模型中算子的性能数据,包括算子名称、算子类型名称、算子执行时间等。
Profiling AscendCL API示例代码
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
//1.AscendCL初始化 //2.申请运行管理资源,包括设置用于计算的Device、创建Context、创建Stream //3.profiling初始化 //设置数据落盘路径 const char *aclProfPath = "..."; aclprofInit(aclProfPath, strlen(aclProfPath)); //4.进行profiling配置 uint32_t deviceIdList[1] = {0}; //创建配置结构体 aclprofConfig *config = aclprofCreateConfig(deviceIdList, 1, ACL_AICORE_ARITHMETIC_UTILIZATION, nullptr,ACL_PROF_ACL_API | ACL_PROF_TASK_TIME | ACL_PROF_AICORE_METRICS | ACL_PROF_AICPU | ACL_PROF_L2CACHE | ACL_PROF_HCCL_TRACE | ACL_PROF_TRAINING_TRACE | ACL_PROF_MSPROFTX | ACL_PROF_RUNTIME_API); aclprofStart(config); //5.模型加载,加载成功后,返回标识模型的modelId //6.创建aclmdlDataset类型的数据,用于描述模型的输入数据input、输出数据output //7.执行模型 ret = aclmdlExecute(modelId, input, output); //8.处理模型推理结果 //9.释放描述模型输入/输出信息、内存等资源,卸载模型 //10.关闭profiling配置, 释放配置资源, 释放profiling组件资源 aclprofStop(config); aclprofDestroyConfig(config); aclprofFinalize(); //11.释放运行管理资源 //12. AscendCL去初始化 //...... |
Profiling AscendCL API for Extension示例代码
调用接口后,需增加异常处理的分支,示例代码中不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
示例中,运行管理资源申请与释放请参见运行管理资源申请和运行管理资源释放,模型加载的接口调用流程请参见模型加载,模型推理的接口调用流程、准备模型推理的输入/输出数据的接口调用流程请参见模型执行。
for (int i = 0; i < 200000; ++i) { g_stamp = aclprofCreateStamp(); if (g_stamp == nullptr) { ERROR_LOG("stamp is nullptr"); return FAILED; } std::string mesg = "test msprof tx"; aclprofSetStampTraceMessage(g_stamp, mesg.c_str(), mesg.size()); aclprofMark(g_stamp); // aclprofPush和aclprofPop接口配对使用,完成单线程采集 aclprofPush(g_stamp); aclprofPop(); // aclprofRangeStart和aclprofRangeStop接口配对使用,可以完成多线程采集 uint32_t rangeId = 0; aclprofRangeStart(stamp, &rangeId); aclprofRangeStop(rangeId); aclprofDestroyStamp(g_stamp); }
Profiling AscendCL API for Subscription示例代码
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
//1.AscendCL初始化 //2.申请运行管理资源,包括设置用于计算的Device、创建Context、创建Stream //3.模型加载,加载成功后,返回标识模型的modelId //4.创建aclmdlDataset类型的数据,用于描述模型的输入数据input、输出数据output //5.创建管道(UNIX操作系统下需要引用C++标准库头文件unistd.h),用于读取以及写入模型订阅的数据 int subFd[2]; // 读管道指针指向subFd[0],写管道指针指向subFd[1] pipe(subFd); //6.创建模型订阅的配置并且进行模型订阅 aclprofSubscribeConfig *config = aclprofCreateSubscribeConfig(1, ACL_AICORE_NONE, &subFd[1]); //模型订阅需要传入模型的modelId aclprofModelSubscribe(modelId, config); //7.实现管道读取订阅数据的函数 //7.1 自定义函数,实现从用户内存中读取订阅数据的函数 void getModelInfo(void *data, uint32_t len) { uint32_t opNumber = 0; uint32_t dataLen = 0; //通过AscendCL接口读取算子信息个数 aclprofGetOpNum(data, len, &opNumber); //遍历用户内存的算子信息 for (int32_t i = 0; i < opNumber; i++){ //获取算子的模型id uint32_t modelId = aclprofGetModelId(data,len, i); //获取算子的类型名称长度 size_t opTypeLen = 0; aclprofGetOpTypeLen(data, len, i, &opTypeLen); //获取算子的类型名称 char opType[opTypeLen]; aclprofGetOpType(data, len, i, opType, opTypeLen); //获取算子的详细名称长度 size_t opNameLen = 0; aclprofGetOpNameLen(data, len, i, &opNameLen); //获取算子的详细名称 char opName[opNameLen]; aclprofGetOpName(data, len, i, opName, opNameLen); //获取算子的执行开始时间 uint64_t opStart = aclprofGetOpStart(data, len, i); //获取算子的执行结束时间 uint64_t opEnd = aclprofGetOpEnd(data, len, i); uint64_t opDuration = aclprofGetOpDuration(data, len, i); } } //7.2 自定义函数,实现从管道中读取数据到用户内存的函数 void *profDataRead(void *fd) { //设置每次从管道中读取的算子信息个数 uint64_t N = 10; //获取单位算子信息的大小(Byte) uint64_t bufferSize = 0; aclprofGetOpDescSize(&bufferSize); //计算存储算子信息的内存的大小,并且申请内存 uint64_t readbufLen = bufferSize * N; char *readbuf = new char[readbufLen]; //从管道中读取数据到申请的内存中,读取到的实际数据大小dataLen可能小于bufferSize * N,如果管道中没有数据,默认会阻塞直到读取到数据为止 auto dataLen = read(*(int*)fd, readbuf, readbufLen); //读取数据到readbuf成功 while (dataLen > 0) { //调用5.1实现的函数解析内存中的数据 getModelInfo(readbuf, dataLen); memset(readbuf, 0, bufferSize); dataLen = read(*(int*)fd, readbuf, readbufLen); } delete []readbuf; } //8. 启动线程读取管道数据并解析 pthread_t subTid = 0; pthread_create(&subTid, NULL, profDataRead, &subFd[0]); //9.执行模型 ret = aclmdlExecute(modelId, input, output); //10.处理模型推理结果 //11.释放描述模型输入/输出信息、内存等资源,卸载模型 //12.取消订阅,释放订阅相关资源 aclprofModelUnSubscribe(modelId); pthread_join(subTid, NULL); //关闭读管道指针 close(subFd[0]); //释放config指针 aclprofDestroySubscribeConfig(config); //13.释放运行管理资源 //14. AscendCL去初始化 //......