MSProbe 调试指南#
在推理或训练过程中,我们常会遇到精度异常问题,例如输出偏离预期、出现数值不稳定(NaN/Inf)现象,或预测结果与标签不再匹配。要定位根本原因,必须监控并捕获模型执行过程中产生的中间数据——包括特征图、权重、激活值和各层输出。通过在特定阶段捕获关键张量、记录核心层的输入输出对,并保留上下文元数据(提示词、张量数据类型、硬件配置等),我们可以系统性地追踪精度退化或数值误差的源头。本指南描述了诊断 AI 模型精度问题的端到端工作流(重点针对 vllm-ascend 服务):准备工作、数据采集以及分析与验证。
更多详情请参阅 Ascend/msprobe。
0.背景概念#
msprobe 支持三种精度级别:
L0:在模块级别保存张量,并生成
construct.json以便可视化工具重建网络结构。需要传入模型或子模块句柄。L1:仅采集算子级统计信息,适用于轻量级故障排查。
mix:同时捕获结构信息和算子统计,适用于需要同时进行图重构和数值比较的场景。
1.前提条件#
1.1 安装 msprobe#
使用 pip 安装 msprobe:
pip install mindstudio-probe
1.2 图模式 dump(可选)#
如果需要采集 cudagraph 图,请从源码安装:
从源码安装
aclgraph_dump:git clone https://gitcode.com/Ascend/msprobe.git cd msprobe python3 setup.py bdist_wheel --include-mod=aclgraph_dump --no-check pip install dist/*.whl
2.使用 msprobe 采集数据#
我们通常采用由粗到细的策略采集数据。首先确定问题出现的 token,然后决定围绕该 token 需要采样的范围。典型工作流程如下所述。
2.1 准备 dump 配置内容#
准备可被 PrecisionDebugger 解析的配置内容。你可以使用以下任一方式:
通过
--additional-config.dump_config直接传入配置对象。通过
--additional-config.dump_config_path传入配置文件路径。
常见字段如下:
字段 |
说明 |
必填 |
Eager 模式 |
图模式 |
|---|---|---|---|---|
|
采集任务类型。PyTorch 常用取值包括 |
是 |
✅ |
✅ |
|
采集结果存储目录。如未指定, |
否 |
✅ |
✅ |
|
指定需要采样的设备 rank。空列表表示采集所有 rank。单卡任务必须将此字段设置为 |
否 |
✅ |
✅ |
|
指定需要采样的 token 迭代次数。空列表表示所有迭代。 |
否 |
✅ |
❌ |
|
采集级别字符串( |
是 |
✅ |
✅ |
|
是否启用异步采集(PyTorch |
否 |
✅ |
❌ |
|
指定需要采样的模块范围。空列表表示所有模块。 |
否 |
✅ |
❌ |
|
在同一次训练/推理任务运行过程中,通过 |
否 |
✅ |
❌ |
|
指定需要采样的算子范围。空列表表示所有算子。 |
否 |
✅ |
✅ |
如需限制捕获的算子范围,可配置 list 块:
scope(list[str]):在 PyTorch 动态图场景下,此字段用于限制采集范围。提供两个遵循工具命名约定的模块或 API 名称以锁定范围;仅会采集这两个名称之间的数据。示例:"scope": ["Module.conv1.Conv2d.forward.0", "Module.fc2.Linear.forward.0"] "scope": ["Cell.conv1.Conv2d.forward.0", "Cell.fc2.Dense.forward.0"] "scope": ["Tensor.add.0.forward", "Functional.square.2.forward"]
level设置决定了可提供的内容——level=L0时使用模块名,level=L1时使用 API 名,level=mix时两者皆可。list(list[str]):自定义算子列表。选项包括:在 PyTorch 动态图场景中,提供特定 API 的全称以仅采集这些 API。示例:
"list": ["Tensor.permute.1.forward", "Tensor.transpose.2.forward", "Torch.relu.3.forward"]。当
level=mix时,可提供模块名称,采集将扩展至该模块运行时产生的所有内容。示例:"list": ["Module.module.language_model.encoder.layers.0.mlp.ParallelMlp.forward.0"]。提供子字符串(例如
"list": ["relu"])以采集名称包含该子字符串的所有 API。当level=mix时,名称包含该子字符串的模块也会被展开采集。
配置示例:eager 模式:
{
"task": "statistics",
"dump_path": "/home/data_dump",
"rank": [],
"step": [],
"level": "L1",
"async_dump": false,
"statistics": {
"scope": [],
"list": [],
"tensor_list": [],
"data_mode": ["all"],
"summary_mode": "statistics"
}
}
图模式:
{
"task": "statistics",
"level": "L1",
"dump_path": "/home/data_dump",
"statistics": {
"list": []
}
}
3.在 vllm-ascend 中启用 msprobe#
启动 vLLM,并通过
--additional-config传递 dump 配置内容:vllm serve Qwen/Qwen2.5-0.5B-Instruct \ --dtype bfloat16 \ --host 0.0.0.0 \ --port 8000 \ --additional-config '{ "dump_config": { "task": "statistics", "level": "L1", "dump_path": "/data/msprobe_dump", "statistics": { "list": [] } } }' &
兼容模式(legacy)仍然支持:
vllm serve Qwen/Qwen2.5-0.5B-Instruct \ --dtype bfloat16 \ --host 0.0.0.0 \ --port 8000 \ --additional-config '{"dump_config_path": "/data/msprobe_config.json"}' &
4.发送请求并采集数据#
按常规方式发送推理请求,例如:
curl http://localhost:8000/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen/Qwen2.5-0.5B-Instruct", "prompt": "Explain gravity in one sentence.", "max_completion_tokens": 32, "temperature": 0 }' | python -m json.tool
每个请求都会驱动序列
msprobe: start -> forward -> stop -> step。运行器在每个代码路径都会调用step(),因此即使推理提前返回,您也能获得完整的数据集。采集文件将写入
dump_path。通常包含以下内容:按算子或模块分组的张量文件。
记录 dtype、形状、最小值/最大值以及
requires_grad等元数据的dump.json。当
level为L0或mix时生成的construct.json(可视化必需)。
目录结构示例:eager 模式:
├── dump_path │ ├── step0 │ │ ├── rank0 │ │ │ ├── dump_tensor_data │ │ │ │ ├── Tensor.permute.1.forward.pt # Format: {api_type}.{api_name}.{call_count}.forward.{input/output}.{arg_index}. │ │ │ │ │ # arg_index is the nth input or output of the API. If an input is a list, keep numbering with decimals (e.g., 1.1 is the first element of the first argument). │ │ │ │ ├── Module.conv1.Conv2d.forward.0.input.0.pt # Format: {Module}.{module_name}.{class_name}.forward.{call_count}.{input/output}.{arg_index}. │ │ │ │ └── Module.conv1.Conv2d.forward.0.parameters.bias.pt # Module parameter data: {Module}.{module_name}.{class_name}.forward.{call_count}.parameters.{parameter_name}. │ │ │ │ # When the `model` argument passed to dump is a List[torch.nn.Module] or Tuple[torch.nn.Module], module-level data names also include the index inside the list ({Module}.{index}.*), e.g., Module.0.conv1.Conv2d.forward.0.input.0.pt. │ │ │ ├── dump.json │ │ │ ├── stack.json │ │ │ ├── dump_error_info.log │ │ │ └── construct.json │ │ ├── rank1 │ │ │ ├── dump_tensor_data │ │ │ │ └── ... │ │ │ ├── dump.json │ │ │ ├── stack.json │ │ │ ├── dump_error_info.log │ │ │ └── construct.json │ │ ├── ... │ │ │ │ │ └── rank7 │ ├── step1 │ │ ├── ... │ ├── step2rank:设备 ID。每张卡将其数据写入对应的rank{ID}目录。在非分布式场景中,目录仅命名为rank。dump_tensor_data:采集到的张量数据。dump.json:每个 API 或模块前向数据的统计信息,包括名称、dtype、形状、最大值、最小值、均值、L2 范数(L2 方差的平方根),以及在summary_mode="md5"时的 CRC-32。详情请参阅 dump.json 文件说明。dump_error_info.log:仅在采集工具遇到错误时生成,记录失败日志。stack.json:API/模块的调用栈信息。construct.json:分层结构描述。level=L1时为空。
图模式:
L0_dump ├── step0 │ └── rank0 │ └── dump.json ├── step1 │ └── rank0 │ └── dump.json ├── step2 │ └── rank0 │ └── dump.json ├── step3 │ └── rank0 │ └── dump.json ├── step4 │ └── rank0 │ └── dump.json └── step5 └── rank0 └── dump.jsondump.json:每个 API 或模块前向数据的统计信息,包括名称、dtype、形状、最大值、最小值、均值、L2 范数(L2 方差的平方根),以及在summary_mode="md5"时的 CRC-32。详情请参阅 dump.json 文件说明。
5.分析结果#
5.1 前提条件#
通常需要两个采集数据集:一个来自“问题侧”(暴露精度或数值错误的运行),另一个来自“基准侧”(良好的基线)。这些数据集不必完全相同——它们可以来自不同的分支、框架版本,甚至不同的实现(算子替换、不同的图优化开关等)。只要它们使用相同或相似的输入、硬件拓扑和采样点(步数/token),msprobe 就能进行比较并定位差异节点。如果找不到完全干净的基准,可以先采集问题侧数据,手动构建最小可复现案例,然后进行自比较。下文中,我们假设问题采集目录为 problem_dump,基准采集目录为 bench_dump。
5.2 可视化#
使用 msprobe graph_visualize 构建或对比图,然后通过 TensorBoard(tb_graph_ascend 插件)打开生成的 *.vis.db 文件。
确保 dump 数据满足可视化要求:
Dump level 必须为
L0或mix,以保证construct.json非空。每个 rank 目录应包含
dump.json、stack.json和construct.json。
选择命令模式:
单图构建:
msprobe graph_visualize -tp <target_path> -o <output_path>
图对比:
msprobe graph_visualize -tp <target_path> -gp <golden_path> -o <output_path>
常用可选参数:
-oc/--overflow_check:启用溢出标记-fm/--fuzzy_match:为节点映射启用模糊匹配-lm/--layer_mapping [mapping.yaml]:跨框架/层映射对比-tensor_log:打印逐节点对比日志(tensor dump 场景)-progress_log:打印详细进度日志
graph_visualize会自动识别路径粒度:单 rank:
.../step0/rank0多 rank(批量):
.../step0多 step(批量):包含
step*的 dump 根路径
输出文件:
单图构建:
build_{timestamp}.vis.db图比较:
compare_{timestamp}.vis.db
使用输出目录启动 TensorBoard:
tensorboard --logdir <output_path> --bind_all --port <optional_port>
在可视化 UI 中检查结构与数值差异:
切换 rank/step,快速定位不稳定节点。
使用搜索/过滤聚焦目标算子/模块。
在对比模式下,优先分析高亮的高差异节点,并追踪其周边 I/O/参数。
6.故障排除#
RuntimeError: Please enforce eager mode:重启 vLLM 并添加--enforce-eager标志。无采集文件:确认 JSON 路径正确且每个节点都有写权限。在分布式场景中,设置
keep_all_ranks以便每个 rank 写入自己的采集数据。采集数据过大:从
statistics任务开始以定位异常张量,然后使用scope/list/tensor_list、filters、token_range等缩小范围。
附录#
dump.json 文件说明#
L0 级别#
L0 级别的 dump.json 包含模块的前向输入输出以及参数。以 PyTorch 的 Conv2d 为例,网络代码如下:
output = self.conv2(input) # self.conv2 = torch.nn.Conv2d(64, 128, 5, padding=2, bias=True)
dump.json 包含以下条目:
Module.conv2.Conv2d.forward.0:模块的前向数据。input_args表示位置输入,input_kwargs表示关键字输入,output存储前向输出,parameters存储权重/偏置。
注意:当传递给采集 API 的 model 参数为 List[torch.nn.Module] 或 Tuple[torch.nn.Module] 时,模块级名称会包含其在列表中的索引({Module}.{index}.*)。例如:Module.0.conv1.Conv2d.forward.0。
{
"task": "tensor",
"level": "L0",
"framework": "pytorch",
"dump_data_dir": "/dump/path",
"data": {
"Module.conv2.Conv2d.forward.0": {
"input_args": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
8,
16,
14,
14
],
"Max": 1.638758659362793,
"Min": 0.0,
"Mean": 0.2544615864753723,
"Norm": 70.50277709960938,
"requires_grad": true,
"data_name": "Module.conv2.Conv2d.forward.0.input.0.pt"
}
],
"input_kwargs": {},
"output": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
8,
32,
10,
10
],
"Max": 1.6815717220306396,
"Min": -1.5120246410369873,
"Mean": -0.025344856083393097,
"Norm": 149.65576171875,
"requires_grad": true,
"data_name": "Module.conv2.Conv2d.forward.0.output.0.pt"
}
],
"parameters": {
"weight": {
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32,
16,
5,
5
],
"Max": 0.05992485210299492,
"Min": -0.05999220535159111,
"Mean": -0.0006165213999338448,
"Norm": 3.421217441558838,
"requires_grad": true,
"data_name": "Module.conv2.Conv2d.forward.0.parameters.weight.pt"
},
"bias": {
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32
],
"Max": 0.05744686722755432,
"Min": -0.04894155263900757,
"Mean": 0.006410328671336174,
"Norm": 0.17263513803482056,
"requires_grad": true,
"data_name": "Module.conv2.Conv2d.forward.0.parameters.bias.pt"
}
}
}
}
}
L1 级别#
L1 级别的 dump.json 记录 API 的前向输入输出。以 PyTorch 的 relu 函数为例(output = torch.nn.functional.relu(input)),文件包含:
Functional.relu.0.forward:API 的前向数据。input_args是位置输入,input_kwargs是关键字输入,output存储前向输出。
{
"task": "tensor",
"level": "L1",
"framework": "pytorch",
"dump_data_dir":"/dump/path",
"data": {
"Functional.relu.0.forward": {
"input_args": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32,
16,
28,
28
],
"Max": 1.3864083290100098,
"Min": -1.3364859819412231,
"Mean": 0.03711778670549393,
"Norm": 236.20692443847656,
"requires_grad": true,
"data_name": "Functional.relu.0.forward.input.0.pt"
}
],
"input_kwargs": {},
"output": [
{
"type": "torch.Tensor",
"dtype": "torch.float32",
"shape": [
32,
16,
28,
28
],
"Max": 1.3864083290100098,
"Min": 0.0,
"Mean": 0.16849493980407715,
"Norm": 175.23345947265625,
"requires_grad": true,
"data_name": "Functional.relu.0.forward.output.0.pt"
}
]
}
}
}
mix 级别#
mix 级别的 dump.json 同时包含 L0 和 L1 级别的数据;文件格式与上述示例相同。