【CANN训练营】Qwen3-8B 推理优化实践笔记(2~3)
1 简介
见【CANN训练营】Qwen3-8B 推理优化实践食用指南-CSDN博客
2 Baseline跑通
notebook地址:gitcode.com/cann/cann-learning-hub/tutorials/llm_inference/qwen3_8b/02_baseline_inference.ipynb
2.1 环境与工作区准备
import json
import os
import subprocess
import sys
from pathlib import Path
print('[环境准备] 开始', flush=True)
os.environ.setdefault('QWEN3_8B_DOWNLOAD_BACKEND', 'modelscope')
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
os.environ.setdefault('ASCEND_RT_VISIBLE_DEVICES', '0')
def locate_repo_root():
repo_roots = []
for env_name in ('GITCODE_REPO_ROOT', 'CANN_LEARNING_HUB_ROOT', 'REPO_ROOT'):
if os.environ.get(env_name):
repo_roots.append(Path(os.environ[env_name]))
try:
cwd = Path.cwd()
lineage = [cwd, *cwd.parents]
repo_roots.extend(lineage)
repo_roots.extend(base / 'cann-learning-hub' for base in lineage)
for base in lineage:
try:
repo_roots.extend(base.glob('*/cann-learning-hub'))
except OSError:
pass
except FileNotFoundError:
pass
repo_roots.append(Path('/opt/atomgit/cann-learning-hub'))
seen = set()
for repo_root in repo_roots:
key = str(repo_root)
if key in seen:
continue
seen.add(key)
if (repo_root / 'tutorials/llm_inference/qwen3_8b/src').exists():
return repo_root
raise FileNotFoundError('Cannot locate cann-learning-hub repository root. Open this notebook inside cann-learning-hub or set CANN_LEARNING_HUB_ROOT to the repository root.')
REPO_ROOT = locate_repo_root()
os.chdir(REPO_ROOT)
TUTORIAL_DIR = REPO_ROOT / 'tutorials' / 'llm_inference' / 'qwen3_8b'
SRC_DIR = TUTORIAL_DIR / 'src'
SOURCE_DIR = REPO_ROOT / 'Sources' / 'model_inference_optimization' / 'qwen3_8b'
SOURCE_DIR.mkdir(parents=True, exist_ok=True)
if str(SRC_DIR) not in sys.path:
sys.path.insert(0, str(SRC_DIR))
from inference_scripts.environment_check import find_unmet_requirements, read_pinned_requirements
if not os.environ.get('ASCEND_TOOLKIT_HOME'):
raise EnvironmentError('ASCEND_TOOLKIT_HOME is not set. Please activate the CANN environment before running this notebook.')
cann_script = Path(os.environ['ASCEND_TOOLKIT_HOME']) / 'set_env.sh'
env_cmd = f'source {cann_script} && env'
env = subprocess.check_output(f"bash -lc '{env_cmd}'", shell=True, text=True, cwd=REPO_ROOT)
for line in env.splitlines():
if '=' in line:
os.environ.__setitem__(*line.split('=', 1))
REQUIREMENTS_FILE = TUTORIAL_DIR / 'requirements.txt'
pinned_requirements = read_pinned_requirements(REQUIREMENTS_FILE)
unmet_requirements = find_unmet_requirements(pinned_requirements)
if unmet_requirements:
print('[依赖检查] 以下依赖缺失或版本不匹配:')
for name, actual, expected in unmet_requirements:
print(f' - {name}: 当前={actual or "未安装"}, 要求={expected}')
requirements_to_install = [f'{name}=={expected}' for name, _, expected in unmet_requirements]
subprocess.run(
[
sys.executable, '-m', 'pip', 'install',
'-i', 'https://pypi.tuna.tsinghua.edu.cn/simple',
'--extra-index-url', 'https://download.pytorch.org/whl/cpu',
'--trusted-host', 'pypi.tuna.tsinghua.edu.cn',
*requirements_to_install,
],
check=True,
)
remaining = find_unmet_requirements(pinned_requirements)
if remaining:
details = ', '.join(f'{name}: {actual or "未安装"} != {expected}' for name, actual, expected in remaining)
raise RuntimeError(f'依赖安装后仍不满足统一版本:{details}')
print('[依赖检查] 安装完成,统一环境已满足')
else:
print('[依赖检查] 所有依赖版本均满足,跳过安装')
RECIPE_ROOT = SRC_DIR / 'inference_scripts' / 'recipe_qwen3_8b'
RECIPE_CONFIG_DIR = RECIPE_ROOT / 'models' / 'qwen' / 'config'
BASELINE_YAML_TEMPLATE = RECIPE_CONFIG_DIR / 'qwen3_8b_1tp.yaml'
PROFILE_YAML_TEMPLATE = RECIPE_CONFIG_DIR / 'qwen3_8b_1tp_profile.yaml'
OPTIMIZED_YAML_TEMPLATE = RECIPE_CONFIG_DIR / 'qwen3_8b_1tp_add_rmsnorm.yaml'
INFER_SH = RECIPE_ROOT / 'executor' / 'scripts' / 'infer.sh'
MODEL_ID = os.environ.get('QWEN3_8B_MODEL_PATH') or 'Qwen/Qwen3-8B'
MODEL_SOURCE_HINT = 'local_path' if os.environ.get('QWEN3_8B_MODEL_PATH') else os.environ['QWEN3_8B_DOWNLOAD_BACKEND']
print('仓库目录 =', REPO_ROOT)
print('教程目录 =', TUTORIAL_DIR)
print('运行目录 =', SOURCE_DIR)
print('recipes 目录 =', RECIPE_ROOT)
print('YAML 目录 =', RECIPE_CONFIG_DIR)
print('推理入口 =', INFER_SH)
print('可见 NPU =', os.environ.get('ASCEND_RT_VISIBLE_DEVICES'))
print('[环境准备] 完成', flush=True)
这段代码是整个教程的环境准备阶段,核心目标是:把当前 Notebook 进程配置成“可以在昇腾 NPU 上跑 Qwen3-8B 推理”的完整环境。
执行对应单元格即可。
这部分代码完成了环境准备工作:先通过多策略定位并切换到教程仓库根目录,然后设置模型下载后端、tokenizer并行开关和可见NPU卡号等基础环境变量;接着通过执行CANN的set_env.sh脚本,把昇腾工具链所需的全部环境变量导入当前Notebook进程;随后读取requirements.txt并检查当前Python环境的依赖版本,若有缺失或不匹配则自动用清华镜像源安装补齐;最后定义recipes推理框架、YAML配置文件、推理入口脚本和模型ID等路径常量,并打印环境摘要供确认,为后续在昇腾NPU上运行Qwen3-8B推理和Profiling采集做好了完整的运行环境准备。
2.2 查看YAML并执行Baseline推理
2.2.1准备Baseline YAML
把 Baseline YAML 模板复制一份到运行目录,填入真实模型路径,并打印关键配置供确认。
import os
import time
from inference_scripts.recipe_workflow import (
collect_recipe_metrics,
find_recipe_case_dir,
prepare_runtime_yaml,
print_yaml_focus,
resolve_model_path,
)
from inference_scripts.inference_qwen3_8b import ensure_recipe_log_ok
BASELINE_WORK_DIR = SOURCE_DIR / 'run_outputs' / 'baseline_demo'
BASELINE_YAML = SOURCE_DIR / 'recipe_yaml' / 'baseline_demo.yaml'
BASELINE_WORK_DIR.mkdir(parents=True, exist_ok=True)
print('[Baseline 推理] 开始', flush=True)
resolved_model_path, model_source = resolve_model_path(str(MODEL_ID), quiet_model_io=False)
prepare_runtime_yaml(BASELINE_YAML_TEMPLATE, BASELINE_YAML, model_path=resolved_model_path)
print('[YAML 准备] Baseline 配置', flush=True)
print('参考 YAML =', BASELINE_YAML_TEMPLATE)
print('运行 YAML =', BASELINE_YAML)
print_yaml_focus(BASELINE_YAML)
baseline_log_dir = BASELINE_WORK_DIR / 'recipe_logs'
baseline_log_dir.mkdir(parents=True, exist_ok=True)
os.environ['QWEN3_RECIPE_ROOT'] = str(RECIPE_ROOT)
os.environ['QWEN3_RUN_YAML'] = str(BASELINE_YAML)
os.environ['QWEN3_RUN_LOG'] = str(baseline_log_dir / 'baseline_demo_infer_stdout.log')
os.environ['QWEN3_RUN_START_TIME'] = str(time.perf_counter())
什么是YAML
YAML(YAML Ain't Markup Language)是一种人类可读的配置文件格式,用缩进和键值对来表示数据,就像“简化版的 JSON,但不用写括号和逗号”。
为什么需要 YAML
这套教程里,YAML 的作用是:用文本描述“怎么跑模型推理”,而不需要改 Python 代码。
比如需要改模型路径、是否开 Profiler、是否开 RMSNorm 融合、生成长度这些内容,只需要改 YAML 文件,不用动任何 Python 代码。
YAML 长什么样
运行YAML目录文件地址在cann-learning-hub/Sources/model_inference_optimization/qwen3_8b/recipe_yaml/baseline_demo.yaml(上一部分打印的内容)

作为 Baseline配置,这份YAML的核心特征是关闭了各类性能优化(如 Profiler、RMSNorm 融合、缓存编译等),用于作为后续优化对比的“原始基准”。
YAML文件的主要字段及解释:
(1) model_config(模型与推理核心配置)
| 字段 | 当前值 | 含义 |
|---|---|---|
|
|
| 模型名称标识 |
|
|
| 模型权重实际路径(由代码自动填入) |
|
|
| 计算精度(BF16,兼顾性能与精度) |
|
|
| 执行模式(即时执行,非图模式) |
|
| false | 关键:关闭性能剖析(Baseline 不采集数据) |
|
|
| 启用检查点 |
|
| false | 关闭缓存编译 |
|
| false | 关闭静态内核 |
|
| 子字段 | 自定义优化开关(见下) |
custom_params(自定义优化,Baseline 全部关闭):
enable_npu_rmsnorm: false(关闭 NPU 原生 RMSNorm)enable_add_rmsnorm: false(关闭 Add+RMSNorm 融合,这是第4章要优化的核心)enable_prefetch: false(关闭预取)prefetch_size:18874368(预取大小,虽关闭但保留默认值)
(2)data_config(数据配置)
dataset:default(使用默认数据集)input_truncated_len:256(输入截断长度,最多 256 token)
(3)parallel_config(并行配置)
全部为 1,表示单卡运行(无张量并行/专家并行):
world_size: 1attn_tp_size: 1(注意力张量并行)moe_tp_size: 1(MoE 张量并行)embed_tp_size: 1(嵌入层并行)lmhead_tp_size: 1(输出头并行)
(4)scheduler_config(调度与生成配置)
max_new_tokens:64(最多生成 64 个新 token)batch_size:1(单批次推理)enable_thinking: false(关闭思考模式,Qwen3 特有)
2.2.2 使用 recipes 命令启动推理
%%bash
set -euo pipefail
# 激活虚拟环境(如果有)
source /mnt/workspace/gitCode/cann/cann-learning-hub/tutorials/llm_inference/qwen3_8b/venv/bin/activate
# recipes 原生启动命令
echo 'recipes 启动命令:'
echo "cd ${QWEN3_RECIPE_ROOT}"
echo "bash executor/scripts/infer.sh --model qwen --mode offline --yaml ${QWEN3_RUN_YAML}"
cd "${QWEN3_RECIPE_ROOT}"
bash executor/scripts/infer.sh --model qwen --mode offline --yaml "${QWEN3_RUN_YAML}" 2>&1 | tee "${QWEN3_RUN_LOG}"
这部分是 Baseline 推理的实际启动命令,核心作用是:通过 recipes 框架在昇腾 NPU 上真正跑一次 Qwen3-8B 推理。
注意:这部分需要在原代码开头加一行激活虚拟环境的代码(如果有)
recipes 是什么
recipes是昇腾 CANN 生态里的一套“大模型推理样例框架”,它不是模型本身,也不是 PyTorch,而是怎么在 NPU 上把某个大模型跑起来的工程封装。
infer.sh 是什么
qwen3_8b/src/inference_scripts/recipe_qwen3_8b/executor/scripts/infer.sh 是 recipes 的统一推理入口脚本。
主要任务:解析参数 → 加载环境 → 定位 YAML → 校验配置 → 调用 launch 函数真正在 NPU 上跑推理。
2.2.3 读取recipes运行结果
case_dir = find_recipe_case_dir(RECIPE_ROOT, BASELINE_YAML)
if case_dir is None:
raise RuntimeError(f'未找到 recipes 结果目录:{BASELINE_YAML.stem}')
recipe_log_path = case_dir / 'log_0.log'
ensure_recipe_log_ok(recipe_log_path)
run_info = {
'stdout_path': os.environ['QWEN3_RUN_LOG'],
'stderr_path': os.environ['QWEN3_RUN_LOG'],
'wall_time_s': time.perf_counter() - float(os.environ['QWEN3_RUN_START_TIME']),
'recipe_case_dir': str(case_dir),
'recipe_log_path': str(recipe_log_path),
}
baseline_metrics = collect_recipe_metrics(
RECIPE_ROOT,
BASELINE_WORK_DIR,
'baseline_demo',
BASELINE_YAML,
run_info,
model_id=str(MODEL_ID),
resolved_model_path=resolved_model_path,
model_source=model_source,
optimization_mode='none',
max_new_tokens=64,
max_input_tokens=256,
enable_thinking=False,
)
print('[Baseline 推理] 完成', flush=True)
这部分代码是Baseline推理的收尾阶段,核心作用是:定位推理结果目录,检查日志是否正常, 收集并保存推理指标,为后续分析和对比做准备。
参数含义:
| 参数 | 值 | 含义 |
|---|---|---|
|
|
| 本次是 Baseline(无优化) |
|
|
| 生成长度限制 |
|
|
| 输入长度限制 |
|
|
| 关闭思考模式 |
2.3 查看结果
print('[结果查看] 开始', flush=True)
def status_cn(status):
return {'ok': '通过', 'warning': '告警', 'failed': '失败'}.get(status, status)
def yes_no(value):
if value is None:
return '未记录'
return '是' if value else '否'
def load_metrics(work_dir, tag):
metrics_path = Path(work_dir) / 'metrics' / f'{tag}_summary.json'
metrics = json.loads(metrics_path.read_text(encoding='utf-8'))
print('指标文件 =', metrics_path)
print('recipes YAML =', metrics.get('recipe_yaml_path'))
print('recipes 结果目录 =', metrics.get('recipe_case_dir'))
print('生成文件 =', metrics.get('outputs_path'))
print('生成 token 数 =', metrics.get('generated_tokens'))
print('总耗时(s) =', metrics.get('total_time_s'))
print('吞吐(tokens/s) =', metrics.get('tokens_per_second'))
rmsnorm_status = metrics.get('rmsnorm_status') or {}
print('RMSNorm 融合 =', yes_no(rmsnorm_status.get('rmsnorm_fused')))
print('Add+RMSNorm 融合 =', yes_no(rmsnorm_status.get('add_rmsnorm_fused')))
sanity = metrics.get('generation_text_sanity')
if sanity:
print('吐字检查 =', status_cn(sanity.get('status')), f"警告={sanity.get('num_with_warnings')}", f"失败={sanity.get('num_failed')}")
return metrics
baseline_metrics = load_metrics(BASELINE_WORK_DIR, 'baseline_demo')
print()
print('生成文本:')
for line in Path(baseline_metrics['outputs_path']).read_text(encoding='utf-8').splitlines():
if line.strip():
print(json.loads(line).get('generated_text', ''))
print('[结果查看] 完成', flush=True)
这段代码读取并展示刚才那次推理的完整指标和生成文本,便于直观确认 Baseline 状态。
运行结果:
[结果查看] 开始
指标文件 = /mnt/workspace/gitCode/cann/cann-learning-hub/Sources/model_inference_optimization/qwen3_8b/run_outputs/baseline_demo/metrics/baseline_demo_summary.json
recipes YAML = /mnt/workspace/gitCode/cann/cann-learning-hub/Sources/model_inference_optimization/qwen3_8b/recipe_yaml/baseline_demo.yaml
recipes 结果目录 = /mnt/workspace/gitCode/cann/cann-learning-hub/tutorials/llm_inference/qwen3_8b/src/inference_scripts/recipe_qwen3_8b/models/qwen/res/20261013/qwen3_8b_baseline_demo
生成文件 = /mnt/workspace/gitCode/cann/cann-learning-hub/Sources/model_inference_optimization/qwen3_8b/run_outputs/baseline_demo/outputs/baseline_demo_generations.jsonl
生成 token 数 = 38
总耗时(s) = 5.3463899999999995
吞吐(tokens/s) = 7.10759970746616
RMSNorm 融合 = 否
Add+RMSNorm 融合 = 否
吐字检查 = 通过 警告=0 失败=0
生成文本:
The output of an **attention function** is a **weighted sum of the value vectors**, where the weights are determined by the **similarity between the query vector and each key vector**.
[结果查看] 完成
| 打印内容 | 实际值 | 含义 |
|---|---|---|
|
| 路径 | 指标 JSON 的位置 |
|
| 运行 YAML 路径 | 用的是哪个配置 |
|
| 推理产物目录 | 日志、输出等存放位置 |
|
| outputs 路径 | 生成文本的 JSONL 文件 |
|
|
| 实际生成了 38 个 token |
|
|
| 推理总耗时 5.35 秒 |
|
|
| 每秒生成 7.11 个 token |
|
|
| 确认关闭 |
|
|
| 确认关闭 |
|
|
| 无告警、无失败 |
3 Profiling分析
这部分在Baseline的基础上打开torch_npu.profiler。
torch_npu.profiler 是 PyTorch 在昇腾 NPU 上的性能剖析器,它底层会调用 CANN / Ascend Profiling 能力,把三层数据对起来:
(1)Host 侧:Python 调用、PyTorch 算子下发、CPU 活动
(2)CANN 侧:算子编译、任务调度、内存拷贝、流同步
(3)NPU 侧:AI Core / AI CPU / Vector 核上的 kernel 执行时间、耗时、等待时间
它的目的不是“让模型更快”,而是:
先看清楚 Baseline 推理里,时间到底耗在哪些算子上了。
准备工作与运行
环境工作区准备的流程和baseline一致。准备YAML的时候准备的是Profiling YAML,在baseline的基础上打开Profiling。其余运行流程与baseline一致。
Profiling YAML中model_config.enable_profiler 设为true,其余推理参数保持和 Baseline 一致。
YAML 文件 = /mnt/workspace/gitCode/cann/cann-learning-hub/Sources/model_inference_optimization/qwen3_8b/recipe_yaml/baseline_profile.yaml
model_path = /home/developer/.cache/modelscope/models/Qwen--Qwen3-8B/snapshots/master
enable_profiler = True # 注意,这里的enable_profiler,即yaml里打开了profiler
enable_npu_rmsnorm = False
enable_add_rmsnorm = False
enable_prefetch = False
prefetch_size = None
dataset = default
input_truncated_len = 256
max_new_tokens = 64
batch_size = 1
结果分析
[Profiler 产物] 开始
指标文件 = /mnt/workspace/gitCode/cann/cann-learning-hub/Sources/model_inference_optimization/qwen3_8b/run_outputs/baseline_profile/metrics/baseline_profile_summary.json
生成 token 数 = 38
Profiler 开启 = 是
Profile 目录 = /mnt/workspace/gitCode/cann/cann-learning-hub/tutorials/llm_inference/qwen3_8b/src/inference_scripts/recipe_qwen3_8b/models/qwen/res/20261013/qwen3_8b_baseline_profile/prof
prof/prefill = /mnt/workspace/gitCode/cann/cann-learning-hub/tutorials/llm_inference/qwen3_8b/src/inference_scripts/recipe_qwen3_8b/models/qwen/res/20261013/qwen3_8b_baseline_profile/prof/prefill
一级目录 = 7f27e7db10fd4ffda8f0b031bf276588_11042_20261013190353199_ascend_pt
kernel_details.csv = 已生成
trace_view.json = 已生成
prof/decode = /mnt/workspace/gitCode/cann/cann-learning-hub/tutorials/llm_inference/qwen3_8b/src/inference_scripts/recipe_qwen3_8b/models/qwen/res/20261013/qwen3_8b_baseline_profile/prof/decode
一级目录 = 7f27e7db10fd4ffda8f0b031bf276588_11042_20261013190359433_ascend_pt
kernel_details.csv = 已生成
trace_view.json = 已生成
吐字检查 = 通过 警告=0 失败=0
Profiler 摘要文件 = /mnt/workspace/gitCode/cann/cann-learning-hub/Sources/model_inference_optimization/qwen3_8b/run_outputs/baseline_profile/profiler_summary/baseline_profile_summary.txt
Profiler summary from kernel_details
source: /mnt/workspace/gitCode/cann/cann-learning-hub/tutorials/llm_inference/qwen3_8b/src/inference_scripts/recipe_qwen3_8b/models/qwen/res/20261013/qwen3_8b_baseline_profile/prof/decode/7f27e7db10fd4ffda8f0b031bf276588_11042_20261013190359433_ascend_pt/ASCEND_PROFILER_OUTPUT/kernel_details.csv
OP Type Count Total(us) Ratio(%)
----------------------------------------------------------------------------------------
aclnnMatmulWeightNz_MatMulCommon_MatMulV2 435 36531.740 68.653
aclnnMul_MulAiCore_Mul 978 3579.760 6.727
aclnnPowTensorScalar_PowsAiCore_Pows 435 2855.780 5.367
FusedInferAttentionScore 108 2413.320 4.535
aclnnMean_ReduceMeanAiCore_ReduceMean 435 2318.040 4.356
...
aclnnInplaceOne_OnesLikeAiCore_OnesLike 3 35.100 0.066
aclnnEmbedding_GatherV2AiCore_GatherV2 3 31.100 0.058
... 仅显示前 24 行,共 35 行
[Profiler 产物] 完成
[分析结论] 开始
摘要类型 = kernel_details
生成 token 数 = 38
Baseline RMSNorm 融合 = 否
Baseline Add+RMSNorm 融合 = 否
Top 算子:
- aclnnMatmulWeightNz_MatMulCommon_MatMulV2: 次数=435,总耗时(us)=36531.740,占比=68.653%
- aclnnMul_MulAiCore_Mul: 次数=978,总耗时(us)=3579.760,占比=6.727%
- aclnnPowTensorScalar_PowsAiCore_Pows: 次数=435,总耗时(us)=2855.780,占比=5.367%
- FusedInferAttentionScore: 次数=108,总耗时(us)=2413.320,占比=4.535%
- aclnnMean_ReduceMeanAiCore_ReduceMean: 次数=435,总耗时(us)=2318.040,占比=4.356%
- aclnnInplaceCopy_CastAiCore_Cast: 次数=873,总耗时(us)=1377.980,占比=2.590%
- aclnnScatterNdUpdate_ScatterNdUpdateAiCore_ScatterNdUpdate: 次数=216,总耗时(us)=1090.060,占比=2.049%
- aclnnAdds_AddAiCore_Add: 次数=441,总耗时(us)=625.340,占比=1.175%
- aclnnRsqrt_RsqrtAiCore_Rsqrt: 次数=435,总耗时(us)=533.560,占比=1.003%
- aclnnAdd_AddAiCore_Add: 次数=219,总耗时(us)=473.720,占比=0.890%
结论:
- Baseline YAML 中 Dense RMSNorm 两个融合开关均为关闭,因此本章看到的是融合前的执行链路。
- 摘要中没有直接出现融合后的 RMSNorm 算子名,说明 Baseline 仍按小算子链路执行。
- 结合 QwenRMSNorm 的计算式,摘要中的归一化相关小算子包括:aclnnAdd_CastAiCore_Cast、aclnnArgMax_CastAiCore_Cast、aclnnGather_CastAiCore_Cast、aclnnInplaceCopy_CastAiCore_Cast、aclnnMax_ReduceMaxAiCore_ReduceMax、aclnnMean_ReduceMeanAiCore_ReduceMean、aclnnPowTensorScalar_PowsAiCore_Pows、aclnnRemainderTensorScalar_BroadcastToAiCore_BroadcastTo。
- 摘要中也可以看到残差 Add 相关算子:aclnnAdd_AddAiCore_Add、aclnnAdd_CastAiCore_Cast、aclnnAdds_AddAiCore_Add。
- 下一章保持 recipes 的 YAML + infer.sh 工作流,只切换 Dense RMSNorm NPU 融合开关,对比精度、吐字健康和吞吐变化。
分析文件 = /mnt/workspace/gitCode/cann/cann-learning-hub/Sources/model_inference_optimization/qwen3_8b/run_outputs/profiling_analysis_summary.json
[分析结论] 完成
算子耗时排名:
| 排名 | 算子 | 次数 | 耗时(us) | 占比 | 属于什么 |
|---|---|---|---|---|---|
| 1 | MatMul | 435 | 36531.74 | 68.65% | 线性层(QKV/O/FFN) |
| 2 | Mul | 978 | 3579.76 | 6.73% | RMSNorm 小算子 |
| 3 | Pow | 435 | 2855.78 | 5.37% | RMSNorm 小算子 |
| 4 | FusedInferAttentionScore | 108 | 2413.32 | 4.54% | 注意力融合算子 |
| 5 | ReduceMean | 435 | 2318.04 | 4.36% | RMSNorm 小算子 |
| 6 | Cast | 873 | 1377.98 | 2.59% | 类型转换 |
| 7 | ScatterNdUpdate | 216 | 1090.06 | 2.05% | KV Cache 更新 |
| 8 | Adds | 441 | 625.34 | 1.18% | 残差 Add |
| 9 | Rsqrt | 435 | 533.56 | 1.00% | RMSNorm 小算子 |
| 10 | Add | 219 | 473.72 | 0.89% | 残差 Add |
可以看出,MatMul(矩阵乘法)耗时最长,其余部分RMSNorm 被拆成了 8 个小算子,残差 Add 也是小算子没有融合。
Profiling 分析证实了 RMSNorm 未融合时被拆成 8 个小算子(Pow/ReduceMean/Rsqrt/Mul 等),导致launch 次数多、访存多、耗时高,是明确的性能瓶颈。下一章将打开 enable_add_rmsnorm 融合开关,用 A/B 对比验证优化效果。
trace_view.json 时间线图谱
trace_view.json 时间线图谱,呈现了 Qwen3-8B 在昇腾 NPU 上 Prefill与 Decode两个阶段的执行特征。
Prefill:

对应 prof/prefill,即处理输入提示词的阶段。
- 时间跨度与标记:整体约 150ms(50ms-200ms),标记为
ProfilerStep#0,末尾带有Dec 标记,暗示向 Decode 阶段的过渡。 - 算子形态:NPU(Stream 46/49)上的色块相对“大块”,虽然密集但多为一次性大粒度计算(如首轮的 QKV 大矩阵乘)。
- 重叠与频率:CPU(Python/CANN)与 NPU 有明显的调度交互。底部
Overlap Analysis中 Computing 与 Free 交替,由于仅执行一次,整体耗时较短,未暴露出小算子的反复累加效应。
Decode:

- 时间跨度与标记:整体约 350ms(100ms-450ms),包含
#3、#4、#5连续多步,每步对应一个(或几个)Token 的生成。 - 极度密集的细碎条纹:这是最核心的视觉特征。NPU 时间线上布满高频、细碎的彩色条纹,直观对应了前文数据中 435次 MatMul 以及伴随的 Pow/ReduceMean/Rsqrt/Mul(RMSNorm 未融合)等小算子。
- 多流并行:Stream 46 与 Stream 49 交错,CANN 线程持续调度,AI Core 频率满载(浅蓝色长条),说明计算单元持续忙碌。
| 对比维度 | Prefill | Decode | 性能含义 |
|---|---|---|---|
| 执行次数 | 单次(Step#0) | 多次迭代(Step#3/4/5) | Decode 重复放大了局部开销 |
| 算子粒度 | 相对大块(计算密集) | 极度细碎(访存/调度密集) | Decode 中小算子 Launch 开销显著 |
| 时间线密度 | 密集但无高频锯齿 | 满屏细碎条纹 | 印证 RMSNorm 拆分成 4~8 个小算子 |
| 耗时占比 | 较短(~150ms) | 较长(~350ms+) | 端到端吞吐受 Decode 阶段主导 |
图谱直观揭示了Decode阶段因 RMSNorm 未融合产生大量小算子调度(细碎条纹),是端到端性能的可优化瓶颈;Prefill 则相对健康。
后续
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐


所有评论(0)