Backward调用链
写一个demo代码,调用torch.backward()
1 | import torch |
调用链
demo: backward()
torch/tensor.py#79: Tensor.backward()
torch.autograd.backward()
torch/autograd/__init__.py#38: backward()
Variable._execution_engine.run_backward()
-
torch/csrc/autograd/python_engine.cpp#272 THPEngine_initModule()
PyModule_AddObject(module, "_ImperativeEngine", (PyObject *)&THPEngineType);
所以Variable._execution_engine —> torch._C._ImperativeEngine() —> THPEngineType(), 下面找run_backward()。
torch/csrc/autograd/python_engine.cpp#89: PyObject *THPEngine_run_backward()
最终backward()调用的即是THPEngine_run_backward()
L142~L161 大概是将节点的前驱的id与grad_fn纳入output_edges中(图中的前驱就是反向过程中的输出)
L169: outputs = engine.execute(roots, grads, keep_graph, create_graph, output_edges);
torch/csrc/autograd/python_engine.cpp#54: PythonEngine::execute()
Engine::execute(roots, inputs, keep_graph, create_graph, outputs);
调用基类的方法。torch/csrc/autograd/engine.cpp#561: Engine::execute()
L583:
graph_task.init_to_execute()
初始化一些参数,未深究L585: 向
ready_queues
中push一些东西,ready_queues
是成员变量,后续在反向执行过程中还会用到。L599:
thread_main(&graph_task)
torch/csrc/autograd/engine.cpp#267: Engine::thread_main()
L276:
evaluate_function(task)
torch/csrc/autograd/engine.cpp#436: Engine::evaluate_function()
L450:
auto outputs = call_function(task);
在后半部分,还会往queue里塞数据,应该就是类似深搜/广搜方法。
torch/csrc/autograd/engine.cpp#390: call_function()
L394:
call_pre_hooks()
一定存在,且被调用L417, L419:
fn()
一定存在,且被调用L431:
call_post_hooks()
不一定存在,若存在则调用&fn
的类型定义在torch/csrc/autograd/engine.cpp#53
,是指向Function
类型的指针。fn()的调用是重载了Function
的()
,位于torch/csrc/autograd/function.h#114
中,会调用apply()
torch/include/torch/csrc/autograd/functions/basic_ops.h#57: apply()
这里是基类的apply()方法,用override标明一定要重写。所以得看派生类的apply()。
torch/csrc/autograd/generated/Functions.cpp
中有很多apply,截图如下:
逻辑梳理
TODO
grad_fn中存了哪些东西
grad_fn的记录应该是从前向的时候便开始记录,但是这部分不像backward部分,那么好找。目前通过两部分同步寻找线索:
- 基于字符串搜索
- 反向调用链中调用到grad_fn的地方
基于字符串搜索
在torch/csrc/autograd/generated/VariableTypeEverything.cpp#1454
中,找到了grad_fn的行为,会记录input_
, weight
等数据,并且会通过SavedVariable
将需要的tensor记录下来(指针/深拷贝?根据demo中的例子,推测应该是深拷贝)
反向调用链中用到grad_fn的地方
在上文提到的第四步中,即torch/csrc/autograd/python_engine.cpp#142
部分,将grad_fn
信息push到output_edges
中,最后传给engine.execute
执行。
torch/csrc/autograd/python_engine.cpp#161: output_edges.emplace_back(grad_fn, output_nr)
output_edges的类型是std::vector<Edge>
, 所以相当于output_edges中push了一个Edge(grad_fn, output_nr)
。
torch/csrc/autograd/edge.h#17: Edge()
,grad_fn赋值给了Edge.function, 是一个Function类型的指针。
torch/csrc/autograd/engine.cpp#417: fn()
, 取function,并且调用了apply()
例子
以Power
为例,假设说有y=x**2
在前向中:torch/csrc/autograd/generated/VariableTypeEverything.cpp#65753
中,记录了pow求导需要的self=x
(L65760), exponent=2
(L65761)。
其中保存数据用的SavedVariable()
位于torch/csrc/autograd/saved_variable.cpp#15
中。与反向中解压对应。
在反向中,torch/csrc/autograd/generated/Functions.cpp#4267
,对self_
解压,获得了之前保存的self
数据,随后通过L4269的pow_backward()
做反向。
其中解压数据用的unpack()
位于torch/csrc/autograd/saved_variable.cpp#34
中,与前向中压缩对应。
准备知识
c10::intrusive_ptr
Caffe2核心代码解析系列之三:Tensor,里面有提到Tensor中存放的实际地址(storage)
就是一个包装后的更高级的指针,可以处理一般指针与弱指针两种类型。
at::Tensor中的数据存在哪里(找到指针,与对应大小)
在StorageImpl.h(c10/core/StorageImpl.h)中,分别有几个成员变量:
- data_type_
- data_ptr_
- numel_
- received_cuda_
任务
数据存放位置,并且如何操纵之
打印StorageImpl.h中的各个成员变量(检查是否在GPU)尝试将数据移动到CPU如何释放GPU上的内存?
- 有其他指针也指向这个地址。
- 如何获知哪些ptr指向同一个内存
对别人的ptr做修改(private类型,尽量不要破坏属性)
从这段代码中可以得出结论:grad_fn存放的tensor只有一个引用(来自grad_fn自身)。那么如果想释放tensor,则修改一下grad_fn,让grad_fn这里直接释放即可。
- 查看tensor初始化时候的GPU内存申请过程,再找一个释放过程。
- 从
torch/csrc/autograd/generated/VariableType_2.cpp#L12533
中的pow()进去,跟踪不到具体实现代码。
- 从
- 先实现将GPU上的内存拷贝一份到CPU上
- 通过遍历,
buff_CPU[address] = xxx
- Q: 在cpp代码里面无法直接调用cudaMemcpy。得看能否通过想baseType->pow()这样子调用cuda函数。
- 通过遍历,
- 通过.to()可以达到目的。
Tensor (GPU)
//backup
tensor_in_cpu = Tensor.to(CPU)
set_data(tensor_in_cpu) //check whether GPU memory is released//restore
set_data(tensor_in_cpu.to(GPU))
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。