概述
Tensor dump 会记录模型推理时每个叶子节点的 输出激活值,每次Engine.step()
写一个文件。当你需要回答”某一层在运行时究竟产出了什么”时,它就是首选工具。
排查数值回归、对比两套后端(如 flashinfer vs eager,或 bf16 vs FP8 构建)、
或把新移植的模型对照参考实现做校验。
它基于 PyTorch 的 forward hook:给每个被选中且没有子模块的 nn.Module 挂一个
hook,捕获它的返回值,搬到 CPU,并以模块的点分名字
(model.expert_stack.layers.0.o_proj)累积起来。不会 dump 权重,因为
权重是静态的,本就存在 checkpoint 里;这里捕获的是随输入变化的中间Tensor。
启用方式
Tensor dump 默认关闭。可以通过 runtime 配置打开,也可以纯靠环境变量打开, 按你的工作流选择。- 环境变量
- EngineConfig
在不改调用方代码的前提下,为单次运行临时打开 dump 的最轻量方式。用一个 JSON 数组的正则(对每个算子的完整点分名字匹配)来限定捕获范围:
PHYAI_*
变量会叠加在程序传入的任意 config 之上,即便脚本自己构造了(一旦有 ENV 变量,其优先级 > EngineConfig)
EngineConfig 也照样生效:| 变量 | 含义 |
|---|---|
PHYAI_DEBUG_TENSOR_DUMP_DIR | 输出目录。设置它即启用 dump。 |
PHYAI_DEBUG_TENSOR_DUMP_FILTER | JSON 数组的正则(或单个裸 pattern)。任一 pattern 命中即记录该算子。 |
PHYAI_DEBUG_TENSOR_DUMP_FILTER_FN | "pkg.module:func" 或 "/path/file.py:func" 谓词。与 _FILTER 互斥。 |
选择 dump 什么
VLA 模型并不是单一同构的 decoder stack。仅 pi0.5 就有三个layers.<int>
stack(视觉 encoder、PaliGemma 语言模型、动作 expert),外加一批根本没有层号的
组件(heads、rope、各种 embedding / projector)。filter 就是为这个情况准备的
filter 接受三种形式:
None,记录全部(默认)
None,记录全部(默认)
捕获每一个叶子算子。pi0.5 每步约 1500 个Tensor,所以一旦你明确了要看什么,
就尽量用更窄的 filter 来自己捕获 Tensor。
正则列表,任一命中即记录
正则列表,任一命中即记录
每条 pattern 用
re.search 对算子名匹配,多条之间取并集(OR)。示例:| 目标 | 正则 |
|---|---|
| 某个 stack 的第 0 层 | r"expert_stack\.layers\.0\." |
| 两个 stack 的第 0 层 | r"expert_stack\.layers\.0\."、r"paligemma_lm\.layers\.0\." |
| 所有 output projection | r"o_proj$" |
| 动作 / 时间 heads(无层号) | r"\.heads\." |
| 整个视觉塔 | r"\.vision\." |
可调用对象,返回 True 即记录
可调用对象,返回 True 即记录
对于正则表达不了的逻辑,传一个 在 config 或环境变量里以
(name: str, module: nn.Module) -> bool
谓词。它还能拿到 module,所以可以按类型分派:"my_pkg.filters:keep"(import 路径)或
"/tmp/myfilter.py:keep"(文件路径,临时调试时不用装包,方便使用)指向它。输出布局
每个 rank 写到各自的子目录,避免并发进程之间互相覆盖;每次Engine.step() 产生一个
带编号的 pass 文件:
/tmp/dump
rank0_pid3069569
pass00000.pt
pass00001.pt
pass00002.pt
.pt 文件是一个 {算子名: cpu_tensor} 的字典。当一个算子在单步内触发多次。
视觉塔每个相机跑一次、动作 expert 每个 Euler 去噪步跑一次,每次调用都会被保留:
第一次以裸名字为 key,之后的加 ::callN 后缀。
加载 dump
用load_pass 读回一个 pass 文件:

