动态图调试
概述
在MindSpore中,动态图模式又被称为PyNative模式,该模式下支持按照Python的语法去执行。在网络开发中,更容易进行调试,支持单算子执行,逐行语句执行,查看中间变量等等调试手段。本教程主要介绍使用MindSpore动态图模式调试的基本方法。
基本方法
在动态图下执行出错时,往往会有Python调用栈,但是由于MindSpore存在算子下发的多级流水以及host和device的异步执行,有时该调用栈并不准确,当Python捕获到异常时,往往已经跑过了真正出错的地方。此外,在正常开发调试脚本时,往往需要查看传递的变量是否准确,函数的调用是否准确。因此在动态图下调试时,我们需要进行一些常用的调试步骤,例如打断点、查看日志、查看变量值、单步执行等等。
同步模式定位问题
由于MindSpore动态图下框架存在多线程异步行为,因此当Python调用栈存在不准确的场景时,我们需要先开启同步模式,来找到精准的报错调用栈。
设置同步模式
当出现错误时,首先设置context,将动态图设置成同步执行模式:
import mindspore as ms
ms.set_context(pynative_synchronize=True)
设置完成后,重新执行脚本。此时脚本出错的时候就会准确的出错在正确的调用栈了,可以根据调用栈信息区分不同的错误类型。
调用栈出错的位置为与MindSpore框架API无关的地方,需要检查一下是否为Python语法问题。
调用栈出错在MindSpore框架API,常见的错误位置如下:
出错在前向API上,调用栈最后在如下两种Python函数上:
self._executor.run_op_async(*args)
或者
pyboost_xxx
其中针对
pyboost_xxx
情况,xxx
为具体的算子接口名称。针对该类报错,需要检查输入数据的shape和dtype类型,类型是否符合该API的要求。
出现在反向传播
self._executor.new_graph(obj, *args, *(kwargs.values())) self._executor.end_graph(obj, output, *args, *(kwargs.values())) self._executor.grad(grad, obj, weights, grad_position, *args)
此时可以根据报错检查一下是否是输入类型,输出类型以及使用的求导接口输入有错。
单步调试定位问题
设置断点
在实际开发过程中,可以通过PyCharm,VsCode等IDE在图形化界面中进行断点设置,也可以通过直接在Python脚本中插入pdb,添加断点。例如:
def some_function():
x = 10
import pdb; pdb.set_trace() # 在这里设置断点
y = x + 1
return y
单步执行
在IDE中,可以使用调试工具栏中的“Step Over”、“Step Into”、“Step Out”按钮来单步执行代码,在Python命令行中可以使用交互命令 n(next), s(step)
命令来进行逐行执行。此外,可以通过在断点处观察和打印变量值,来判断脚本的执行结果是否准确。
日志记录
在调试过程中,往往需要通过查看日志来定位问题,在MindSpore中,可以通过GLOG_v来进行日志级别的控制,默认值:2,具体级别如下:
0-DEBUG
1-INFO
2-WARNING
3-ERROR,表示程序执行出现报错,输出错误日志,程序可能不会终止
4-CRITICAL,表示程序执行出现异常,将会终止执行程序
详细的日志控制方法见环境变量
常见PDB调试命令
c (continue): 继续执行直到下一个断点。
n (next): 执行下一行代码。
s (step): 执行下一行代码,如果下一行代码是函数调用,则进入函数内部。
l (list): 显示当前执行的代码行周围的源代码。
p (print): 打印表达式的值。
q (quit): 退出pdb调试器。
h (help): 显示帮助信息。
反向问题定位
在动态图下需要查看反向精度是否准确,往往可以利用动态图的反向hook功能,来查看在反向传播中的梯度是否符合预期。
查看Parameter的梯度是否符合预期,可以通过对Parameter注册hook
通过
register_hook(hook_fn)
来注册hook,例如:
import mindspore as ms from mindspore import Tensor ms.set_context(mode=ms.PYNATIVE_MODE) def hook_fn(grad): return grad * 2 def hook_test(x, y): z = x * y z.register_hook(hook_fn) z = z * y return z ms_grad = ms.grad(hook_test, grad_position=(0,1)) output = ms_grad(Tensor(1, ms.float32), Tensor(2, ms.float32)) print(output)
详细API使用说明可以参考
查看执行过程中的梯度,可以通过
mindspore.ops.HookBackward
,例如:import mindspore as ms from mindspore import ops from mindspore import Tensor from mindspore.ops import GradOperation ms.set_context(mode=ms.PYNATIVE_MODE) def hook_fn(grad): print(grad) hook = ops.HookBackward(hook_fn) def hook_test(x, y): z = x * y z = hook(z) z = z * y return z grad_all = GradOperation(get_all=True) def backward(x, y): return grad_all(hook_test)(x, y) output = backward(Tensor(1, ms.float32), Tensor(2, ms.float32)) print(output)
详细API使用说明可以参考
查看某个Cell的梯度,可以通过
mindspore.nn.Cell.register_backward_hook
,例如:import numpy as np import mindspore as ms from mindspore import Tensor, nn, ops ms.set_context(mode=ms.PYNATIVE_MODE) def backward_hook_fn(cell_id, grad_input, grad_output): print("backward input: ", grad_input) print("backward output: ", grad_output) class Net(nn.Cell): def __init__(self): super(Net, self).__init__() self.relu = nn.ReLU() self.handle = self.relu.register_backward_hook(backward_hook_fn) def construct(self, x): x = x + x x = self.relu(x) return x grad = ops.GradOperation(get_all=True) net = Net() output = grad(net)(Tensor(np.ones([1]).astype(np.float32))) print(output)
详细API使用说明可以参考
更多实际案例
参考调试案例