{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# **算子仿真调优**\n",
    "\n",
    "本节为算子仿真调优章节,完成本章节内容的学习可以掌握如何使用msProf工具采集仿真性能数据,并分析算子仿真性能瓶颈。我们将按照以下结构,带你学习算子仿真调优流程:\n",
    "- 环境准备\n",
    "- 如何使用msProf工具采集仿真性能数据\n",
    "- 如何分析仿真性能数据\n",
    "- 针对性优化\n",
    "- 课后实践\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## **1环境准备**\n",
    "本文所有内容均存放于Sources文件夹。\n",
    "在开始创建算子工程前,先要对jupyter环境进行初始化。以下代码完成了初始化并将环境中的变量导入jupyter环境,并完成代码目录的创建。保证能正常导入代码以及正常使用工具。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!mkdir -p Sources/04.04\n",
    "\n",
    "\n",
    "import os\n",
    "import subprocess\n",
    "from pathlib import Path\n",
    "\n",
    "set_env = os.environ.get(\"ASCEND_TOOLKIT_HOME\", \"/usr/local/Ascend/cann\") + \"/set_env.sh\"\n",
    "\n",
    "result = subprocess.run(\n",
    "    [\"bash\", \"-lc\", f\"source {set_env} && env\"],\n",
    "    capture_output=True,\n",
    "    text=True,\n",
    "    check=True,\n",
    ")\n",
    "for line in result.stdout.strip().split(\"\\n\"):\n",
    "    if \"=\" in line and not line.startswith((\"#\", \" \")):\n",
    "        key, value = line.split(\"=\", 1)\n",
    "        os.environ[key] = value\n",
    "\n",
    "print(\"Environment initialization process completed successfully.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## **2如何使用msProf工具采集仿真性能数据**\n",
    "\n",
    "上一节已经介绍了如何使用msProf采集上板性能数据。相对于上板模式,仿真模式多了 `simulator` 参数,并且需要指定仿真的产品型号。对于本节使用的自包含Add算子样例,可在CMake配置阶段通过 `CMAKE_ASC_RUN_MODE=sim` 开启仿真编译,并通过 `CMAKE_ASC_ARCHITECTURES=dav-2201` 指定NPU架构版本。同时,为了让仿真trace能够正常显示源码代码行,需要在运行目录的 `CMakeLists.txt` 中为Ascend C编译添加 `-g`。\n",
    "\n",
    "所以仿真性能采集分为3步:  \n",
    "\n",
    "1. 准备测试程序并添加源码行信息编译选项\n",
    "2. 使用仿真编译参数编译测试程序\n",
    "3. 采集仿真数据\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里继续使用 `./src/04.03` 中的自包含Add算子测试程序,复制到 `Sources/04.04` 后用于仿真采集。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 清理并准备测试程序目录\n",
    "!rm -rf Sources/04.04\n",
    "!mkdir -p Sources/04.04\n",
    "\n",
    "# 复制已准备好的自包含 Add 算子样例\n",
    "!cp ./src/04.03/add_custom.asc Sources/04.04/add_custom.asc\n",
    "!cp ./src/04.03/CMakeLists.txt Sources/04.04/CMakeLists.txt\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "测试程序目录结构为:\n",
    "\n",
    "```\n",
    "Sources/04.04/\n",
    "├── add_custom.asc    // 包含Kernel实现、Host侧调用代码和main函数\n",
    "└── CMakeLists.txt    // 使用Ascend C CMake工具链编译demo可执行程序\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "复制得到的 `CMakeLists.txt` 来自共享测试程序目录。为了让仿真trace能够显示源码代码行,本节只在运行目录 `Sources/04.04/CMakeLists.txt` 中增加 `-g` 编译选项;仿真编译所需配置仍通过CMake配置命令传入:\n",
    "\n",
    "```shell\n",
    "cmake -B build -DCMAKE_ASC_RUN_MODE=sim -DCMAKE_ASC_ARCHITECTURES=dav-2201\n",
    "```\n",
    "\n",
    "其中 `CMAKE_ASC_RUN_MODE=sim` 表示开启仿真编译,`CMAKE_ASC_ARCHITECTURES=dav-2201` 表示本节样例使用的NPU架构版本。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile Sources/04.04/CMakeLists.txt\n",
    "\n",
    "cmake_minimum_required(VERSION 3.16)\n",
    "\n",
    "find_package(ASC REQUIRED)\n",
    "\n",
    "project(kernel_samples LANGUAGES ASC CXX)\n",
    "\n",
    "add_executable(demo\n",
    "    add_custom.asc\n",
    ")\n",
    "\n",
    "target_compile_options(demo PRIVATE\n",
    "    $<$<COMPILE_LANGUAGE:ASC>:--npu-arch=dav-2201>\n",
    "    $<$<COMPILE_LANGUAGE:ASC>:-g>\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然后根据仿真产品型号设置仿真相关的环境变量,这里以仿真Atlas A2训练产品为例,应设置环境变量为:\n",
    "\n",
    "```shell\n",
    "export LD_LIBRARY_PATH=${ASCEND_TOOLKIT_HOME}/tools/simulator/Ascend910B1/lib:$LD_LIBRARY_PATH\n",
    "```\n",
    "\n",
    "设置环境变量后,即可使用 `msprof op simulator` 抓取可执行程序执行的仿真性能,命令为:\n",
    "\n",
    "```shell\n",
    "msprof op simulator --output=./output_data ./xxxx\n",
    "```\n",
    "\n",
    "如果不设置 `LD_LIBRARY_PATH`,也可通过增加 `--soc-version` 指定要仿真的产品型号,命令为:\n",
    "\n",
    "```shell\n",
    "msprof op simulator --soc-version=Ascend910B1 --output=./output_data ./xxxx\n",
    "```\n",
    "\n",
    "如果确认核间数据为均匀分布,或者只想获取指定核的仿真数据,可以通过 `--core-id` 来指定核。以采集id为0的核的仿真性能为例,命令为:\n",
    "\n",
    "```shell\n",
    "msprof op simulator --soc-version=Ascend910B1 --output=./output_data --core-id=0 ./xxxx\n",
    "```\n",
    "\n",
    "让我们执行以下命令编译测试程序,尝试采集id为0的仿真性能数据。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 清除可能存在的性能文件\n",
    "!rm -rf Sources/04.04/prof\n",
    "# 创建性能文件存放目录\n",
    "!mkdir -p Sources/04.04/prof\n",
    "# 编译测试程序\n",
    "!cd Sources/04.04 && cmake -B build -DCMAKE_ASC_RUN_MODE=sim -DCMAKE_ASC_ARCHITECTURES=dav-2201 && cmake --build build\n",
    "# 采集仿真性能数据\n",
    "!msprof op simulator --soc-version=Ascend910B1 --output=./Sources/04.04/prof --core-id=0 ./Sources/04.04/build/demo\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "命令执行后,Sources/04.04/prof目录下会生成性能数据文件,目录结构如下:\n",
    "\n",
    "```\n",
    "OPPROF_20260310020803_BJNTOKUSUZMIESZL/\n",
    "├── simulator/\n",
    "│   ├── core0.veccore0/                   // 按照core*.veccore*或core*.cubecore*目录存放各核的数据文件\n",
    "│   ├── trace.json                       // 全部核或指定核的仿真指令流水图文件\n",
    "│   └── visualize_data.bin               // 全部核或指定核的仿真指令流水图文件\n",
    "└── dump/                                  // 存放过程件的文件夹,无需关注\n",
    "    ├── aicore_binary.o\n",
    "    ├── object_dump.txt\n",
    "    └── pc_start_addr.txt\n",
    "```\n",
    "\n",
    "\n",
    "执行以下命令查看Sources/04.04/prof查看到采集的性能数据文件。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!cd Sources/04.04/prof; find . -maxdepth 3 -print | sed -e 's;[^/]*/;|____;g;s;____|;    |;g'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## **3如何分析仿真性能数据**\n",
    "\n",
    "已抓取的仿真数据文件trace.json、visualize_data.bin通常不能直接分析性能,这里我们需要借助Chrome浏览器的chrome://tracing打开trace.json或者使用[MindStudio Insight工具](https://www.hiascend.com/document/detail/zh/mindstudio/840/GUI_baseddevelopmenttool/msascendinsightug/Insight_userguide_0027.html)打开trace.json、visualize_data.bin。 \n",
    "\n",
    "### **3.1 Chrome浏览器打开**\n",
    "在Chrome浏览器中输入“chrome://tracing”地址,并将通过msprof op simulator生成指令流水图文件(trace.json)拖到空白处打开,键盘上输入快捷键(**W:放大,S:缩小,A:左移,D:右移**)可进行查看看各个流水任务耗时的信息。例如刚刚我们抓取的Add算子样例性能文件打开会如下:  \n",
    "\n",
    "<img src=\"./images/trace_info.png\"  alt=\"trace\"  width=\"1920px\">    \n",
    "\n",
    "我们可以比较直观的看到算子整体耗时中MTE2、VECTOR、MET3耗时均很短,而SCALAR运算耗时很长,点击右侧SCALAR可在下方看到该流水任务对应的具体代码行数。\n",
    "执行以下代码下载我们抓取到的trace.json文件,尝试使用Chrome浏览器查看一下吧。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import glob,base64;from IPython.display import display,HTML\n",
    "f=glob.glob(\"Sources/04.04/prof/OPPROF_*/simulator/trace.json\")\n",
    "if f:\n",
    "    b64=base64.b64encode(open(f[0],'rb').read()).decode()\n",
    "    display(HTML(f'<a href=\"data:application/json;base64,{b64}\" download=\"trace.json\" style=\"color:white;background:#007bff;padding:5px 10px;text-decoration:none;border-radius:3px;\">📥 下载</a>'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "也可以直接执行下面代码查看性能文件,该代码简单生成了一个图表模拟了chrome://tracing的展示效果,但是不可以缩放和拖动。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt, warnings, json, pandas as pd, os, glob\n",
    "plt.set_loglevel(\"error\"); warnings.filterwarnings('ignore', category=UserWarning)\n",
    "\n",
    "BASE_DIR = \"Sources/04.04/prof\"\n",
    "try:\n",
    "    trace_path = f\"{sorted(glob.glob(f'{BASE_DIR}/OPPROF*'), key=os.path.getctime)[-1]}/simulator/trace.json\"\n",
    "except:\n",
    "    trace_path = \"Sources/04.04/prof/OPPROF_20260310110753_XXSVYUAHKSEHAZXS/simulator/trace.json\"\n",
    "\n",
    "df = pd.DataFrame([{\"name\":e.get(\"name\",\"unknown\"),\"start\":e[\"ts\"]/1e6,\"dur\":e[\"dur\"]/1e6,\"thread\":f\"Thread-{e.get('tid',0)}\"} \n",
    "                   for e in json.load(open(trace_path))[\"traceEvents\"] if \"ts\" in e and \"dur\" in e])\n",
    "plt.figure(figsize=(12, 5))\n",
    "\n",
    "for thread in df[\"thread\"].unique():\n",
    "    t_df = df[df[\"thread\"] == thread]\n",
    "    plt.barh(thread, t_df[\"dur\"], left=t_df[\"start\"], label=t_df[\"name\"].iloc[0], alpha=0.7)\n",
    "\n",
    "thread_names = [th.replace(\"Thread-\", \"\") for th in df[\"thread\"].unique()]\n",
    "plt.yticks(ticks=df[\"thread\"].unique(), labels=thread_names)\n",
    "\n",
    "plt.xlabel(\"Time (ms)\"); plt.title(\"Trace.json\")\n",
    "plt.legend(loc=\"upper right\", bbox_to_anchor=(1.2, 1)); plt.grid(axis='x', alpha=0.3)\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **3.2使用MindStudio Insight打开**\n",
    "若要使用MindStudio Insight进行查看时,需要单独安装MindStudio Insight软件包,具体下载链接请参见[安装与卸载](https://www.hiascend.com/document/detail/zh/mindstudio/840/GUI_baseddevelopmenttool/msascendinsightug/Insight_userguide_0006.html)。  \n",
    "安装好MindStudio Insight软件包后,我们可以在工具中导入刚刚抓取到的性能数据文件。   \n",
    "\n",
    "<img src=\"./images/chose_file.png\" alt=\"chose_file\"  width=\"700px\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "导入后,时间线界面与Chrome浏览器中打开效果一致,如图:   \n",
    "\n",
    "<img src=\"./images/timeline.png\" alt=\"timeline\"  width=\"700px\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "切换到源码界面,我们在设置好对应源码后,即可查看每行代码对应的时钟周期,可以更直观的看到耗时较多的代码或代码块。  \n",
    "\n",
    "<img src=\"./images/code_info.png\" alt=\"code_info\"  width=\"1920px\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## **4针对性优化**\n",
    "\n",
    "根据trace图或者源码时钟周期我们可以看出耗时主要集中在`printf`打印,所以推测将`printf`打印屏蔽后,算子的性能会有较大提升。这里通过CMake编译定义`ASCENDC_DUMP=0`屏蔽打印,并重新采集仿真性能数据进行对比。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile Sources/04.04/CMakeLists.txt\n",
    "\n",
    "cmake_minimum_required(VERSION 3.16)\n",
    "\n",
    "find_package(ASC REQUIRED)\n",
    "\n",
    "project(kernel_samples LANGUAGES ASC CXX)\n",
    "\n",
    "add_executable(demo\n",
    "    add_custom.asc\n",
    ")\n",
    "\n",
    "target_compile_options(demo PRIVATE\n",
    "    $<$<COMPILE_LANGUAGE:ASC>:--npu-arch=dav-2201>\n",
    "    $<$<COMPILE_LANGUAGE:ASC>:-g>\n",
    ")\n",
    "\n",
    "target_compile_definitions(demo PRIVATE ASCENDC_DUMP=0)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "修改完成后重新部署算子并抓取性能:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 清理旧构建目录并重新编译\n",
    "!rm -rf Sources/04.04/build\n",
    "!cd Sources/04.04 && cmake -B build -DCMAKE_ASC_RUN_MODE=sim -DCMAKE_ASC_ARCHITECTURES=dav-2201 && cmake --build build\n",
    "\n",
    "# 清除可能存在的性能文件\n",
    "!rm -rf Sources/04.04/prof2\n",
    "# 创建性能文件存放目录\n",
    "!mkdir -p Sources/04.04/prof2\n",
    "# 采集屏蔽打印后的仿真性能数据\n",
    "!msprof op simulator --soc-version=Ascend910B1 --output=./Sources/04.04/prof2 --core-id=0 ./Sources/04.04/build/demo\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "抓取仿真性能后,使用Chrome浏览器读取性能数据与之前对比,观察是否符合预期,减少了scalar运算耗时。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import glob,base64;from IPython.display import display,HTML\n",
    "f=glob.glob(\"Sources/04.04/prof2/OPPROF_*/simulator/trace.json\")\n",
    "if f:\n",
    "    b64=base64.b64encode(open(f[0],'rb').read()).decode()\n",
    "    display(HTML(f'<a href=\"data:application/json;base64,{b64}\" download=\"trace.json\" style=\"color:white;background:#007bff;padding:5px 10px;text-decoration:none;border-radius:3px;\">📥 下载</a>'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "预期如图:  \n",
    "\n",
    "<img src=\"./images/no_print_trace.png\" alt=\"no_print_trace\"  width=\"1920px\">\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "或执行下面代码简单查看"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt, warnings, json, pandas as pd, os, glob\n",
    "plt.set_loglevel(\"error\"); warnings.filterwarnings('ignore', category=UserWarning)\n",
    "\n",
    "BASE_DIR = \"Sources/04.04/prof2\"\n",
    "try:\n",
    "    trace_path = f\"{sorted(glob.glob(f'{BASE_DIR}/OPPROF*'), key=os.path.getctime)[-1]}/simulator/trace.json\"\n",
    "except:\n",
    "    trace_path = \"Sources/04.04/prof2/OPPROF_20260310110753_XXSVYUAHKSEHAZXS/simulator/trace.json\"\n",
    "\n",
    "df = pd.DataFrame([{\"name\":e.get(\"name\",\"unknown\"),\"start\":e[\"ts\"]/1e6,\"dur\":e[\"dur\"]/1e6,\"thread\":f\"Thread-{e.get('tid',0)}\"} \n",
    "                   for e in json.load(open(trace_path))[\"traceEvents\"] if \"ts\" in e and \"dur\" in e])\n",
    "plt.figure(figsize=(12, 5))\n",
    "\n",
    "for thread in df[\"thread\"].unique():\n",
    "    t_df = df[df[\"thread\"] == thread]\n",
    "    plt.barh(thread, t_df[\"dur\"], left=t_df[\"start\"], label=t_df[\"name\"].iloc[0], alpha=0.7)\n",
    "\n",
    "thread_names = [th.replace(\"Thread-\", \"\") for th in df[\"thread\"].unique()]\n",
    "plt.yticks(ticks=df[\"thread\"].unique(), labels=thread_names)\n",
    "\n",
    "plt.xlabel(\"Time (ms)\"); plt.title(\"Trace.json\")\n",
    "plt.legend(loc=\"upper right\", bbox_to_anchor=(1.2, 1)); plt.grid(axis='x', alpha=0.3)\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## **课后实践**\n",
    "请根据本节课程学习内容完成以下题目进行自测。\n",
    "\n",
    "1. (单选题)在AscendC算子仿真调优环节,原文档中提到,开展算子性能仿真验证、获取性能数据的前置条件是?   \n",
    "    A. 完成算子开发并能正常调用   \n",
    "    B. 完成算子Kerne实现逻辑即可采集性能数据  \n",
    "    C. 仅通过编译器静态分析代码逻辑,即可分析出性能数据,不需要借助工具   \n",
    "    D. 手动计算理论性能值,不需要借助工具  \n",
    "\n",
    "2. (单选题)Ascend C算子仿真数据抓取完成后,主要通过查看哪类核心文件来获取详细的性能耗时、单元利用率等关键性能数据?  \n",
    "    A. 源码编译生成的.o目标文件  \n",
    "    B. 仿真运行生成的性能统计trace.json和visualize_data.bin文件  \n",
    "    C. 算子定义头文件  \n",
    "    D. 系统通用日志文件  \n",
    "\n",
    "3. (单选题)AscendC算子仿真性能分析时,重点关注的核心性能指标不包含以下哪一项?    \n",
    "    A. 算子整体执行耗时  \n",
    "    B. AI Core计算单元利用率  \n",
    "    C. 数据搬运带宽与搬运耗时占比  \n",
    "    D. 操作系统版本号  \n",
    "\n",
    "4. (单选题)Ascend C算子仿真调优中的性能瓶颈定位,可行的方法是?  \n",
    "    A. 随机修改代码尝试优化  \n",
    "    B. 对比仿真报告中计算模块、搬运模块、同步等待的耗时占比,定位核心瓶颈  \n",
    "    C. 仅查看代码行数判断效率  \n",
    "    D. 依赖硬件自带的自动瓶颈提示  \n",
    "\n",
    "5. (单选题)Ascend C算子仿真调优相比硬件实测,在算子性能优化阶段的核心优势是?  \n",
    "    A. 无需依赖算子实际运行的实体昇腾硬件,借助仿真模拟实际硬件行为完成性能验证与瓶颈定位,缩短优化周期  \n",
    "    B. 性能数据比硬件实测更精准  \n",
    "    C. 可以直接修改硬件底层参数  \n",
    "    D. 不需要编写任何算子代码  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "执行以下代码查看答案:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!cat ./answer/04.04_answer.txt"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py312",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}