在 Paddle 代码库中定位问题并输出高质量调试报告的专用技能。当遇到以下场景时优先使用:(1) Paddle 框架 bug 调试,(2) 算子实现问题排查,(3) 训练脚本异常诊断,(4) 分布式训练故障定位,(5) CUDA/GPU 相关错误处理,(6) 需要生成结构化调试报告。
调试遵循以下步骤:
用简洁的自然语言说明:
先确认 bug 能被稳定复现。若无法复现:
抽取独立的 Python 脚本承载问题:
numpy / random / paddle.seed 等)目标:一条命令即可复现 python reproduce_xxx.py。
阅读报错栈和相关代码时,先列出多个可能原因假设(数据异常、shape 错误、数值不稳定、环境不一致、算子实现问题等),不要立刻改代码。
围绕假设在关键路径上加入观测点:
| 观测方式 | 用途 |
|---|---|
| 打印与断言 | 在关键算子调用前后,打印 Tensor 的 shape、dtype、device、数值范围(min/max/mean) |
| 对比法 | 对同一逻辑分别在 CPU / GPU 上运行,比较中间结果差异 |
| 版本与环境信息 | 记录 paddle.__version__、CUDA/CUDNN 版本、驱动信息等 |
每完成一次带观测点的复现:
将调试日志保存到 .paddle-agent/debug-logs/ 目录。
基于已有观测和对比结果,先完成问题分析报告:
# [问题标题]
## 复现方式
- 命令:
- 环境:
- 最小脚本路径:
## 现象描述
[错误信息或异常行为]
## 根因分析
[配置 / 数据 / 框架 / 算子 / 环境中的哪一处有问题]
## 关键证据
[日志片段、对比结果、重要观测点输出]
报告存放在 .paddle-agent/debug-analysis/ 目录,没有该目录请创建。
归因时考虑以下维度:
只有在分析结论较为充分时,才进入最小修复阶段:
当判断问题可能由近期提交引入时:
git bisect 对可疑提交范围做二分定位对已定位的问题:
.paddle-agent/debug-analysis/详细的 CUDA 调试技巧:见 references/cuda-debug.md
快速参考:
# 启用错误检查环境变量复现问题
export PYTHONPATH=$(pwd)/Paddle/build/python
FLAGS_check_cuda_error=1 FLAGS_use_system_allocator=1 python reproduce.py
关键点:
FLAGS_check_cuda_error=1 让错误立即暴露PADDLE_ENFORCE_GPU_SUCCESS 不会调用 cudaGetLastError(),在错误路径上需手动清除cudaErrorInitializationError(3)——详见 references/cuda-debug.md 中的 CUDA Fork Safety 章节.h 后需重新编译所有引用它的 .cu,并重新链接 .socudaEventSynchronize、cudaStreamSynchronize 等,忽略返回值不仅丢失错误信息,还会导致 CUDA runtime 中残留 sticky errorPADDLE_ENFORCE_GPU_SUCCESS 抛出异常之前,手动调用 cudaGetLastError() 清除残留错误,否则 Python try/except 捕获异常后 CUDA 状态仍被污染cudaErrorInitializationError(3)num_workers > 0 fork 子进程;子进程继承了父进程中 GPU tensor 的引用,GC 回收时触发 cudaFreecudaGetDevice() 返回 cudaErrorInitializationError(3)、cudaErrorNoDevice(100)、cudaErrorInsufficientDriver(35) 均表示 CUDA 不可用cudaFree 是安全的,因为 fork 后子进程中的 GPU 内存不属于该进程,进程退出时由 OS/driver 回收修改 kernel 头文件后的增量编译:
cd build
# 编译修改的 kernel
ninja paddle/phi/CMakeFiles/phi_gpu.dir/kernels/gpu/<kernel_name>.cu.o -j512
# 重新链接 phi_gpu
ninja phi_gpu -j512
# 重新链接 libpaddle.so
ninja paddle/fluid/pybind/libpaddle.so -j512
# 如果 Python 库未自动更新,手动复制
cp paddle/fluid/pybind/libpaddle.so python/paddle/base/libpaddle.so
Paddle 构建产物存在两套路径,增量编译后 Python 加载的可能仍是旧版本:
| 构建产物路径 | Python 加载路径 | 说明 |
|---|---|---|
build/paddle/phi/libphi_core.so | build/python/paddle/libs/libphi_core.so | phi core 库 |
build/paddle/phi/libphi_gpu.so | build/python/paddle/libs/libphi_gpu.so | phi GPU 库 |
build/paddle/fluid/pybind/libpaddle.so | build/python/paddle/base/libpaddle.so | 主绑定库 |
增量编译后务必检查:
# 确认 Python 实际加载了哪个 .so
python -c "import paddle; import os; print(os.path.realpath(paddle.__file__))"
# 比较构建时间戳
stat build/paddle/phi/libphi_core.so
stat build/python/paddle/libs/libphi_core.so
# 如果时间戳不一致,手动同步
cp build/paddle/phi/libphi_core.so build/python/paddle/libs/libphi_core.so
cp build/paddle/phi/libphi_gpu.so build/python/paddle/libs/libphi_gpu.so
典型症状:修改了源码并重新编译,但运行时错误信息中的行号不变——这说明 Python 加载的仍是旧 .so。
当崩溃发生在公共底层函数(如 GetCurrentDeviceId、cudaFree)时,需穷举所有调用路径来定位真正的入口:
FLAGS_use_system_allocator)确定实际使用的分配器链路DenseTensor::~DenseTensor -> shared_ptr<Allocation> -> AllocationDeleter -> 具体分配器的 FreeImplbacktrace_symbols_fd 打印调用栈,确认实际触发路径PADDLE_ENFORCE_GPU_SUCCESS 是宏,行号由 __LINE__ 决定;FreeImpl 是虚函数,实际调用取决于运行时类型见 references/case-studies.md 了解实际调试案例。