pytorch-backward

Backward调用链

写一个demo代码,调用torch.backward()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
x = torch.Tensor([1,2,3])
print(x.grad_fn)
x.requires_grad=True
y = x**2
print("y:",y)
y.data=torch.Tensor([3,2,1])
print("y:",y)
print("y.grad_fn:",y.grad_fn)
z = 2*y
print("z:",z)
c = torch.sum(z)
c.backward()
print("x.grad:",x.grad)

调用链

  1. demo: backward()
  2. torch/tensor.py#79: Tensor.backward()

    torch.autograd.backward()

  3. torch/autograd/__init__.py#38: backward()

    Variable._execution_engine.run_backward()

  4. torch/autograd/variable.py#14: from torch._C import _ImperativeEngine as ImperativeEngine; Variable_execution_engine=ImperativeEngine

    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#220: {(char*)"run_backward", (PyCFunction)THPEngine_run_backward, METH_VARARGS | METH_KEYWORDS, nullptr},

    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);

  5. torch/csrc/autograd/python_engine.cpp#54: PythonEngine::execute()

    Engine::execute(roots, inputs, keep_graph, create_graph, outputs); 调用基类的方法。

  6. 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)

  7. torch/csrc/autograd/engine.cpp#267: Engine::thread_main()

    L276: evaluate_function(task)

  8. torch/csrc/autograd/engine.cpp#436: Engine::evaluate_function()

    L450: auto outputs = call_function(task);

    在后半部分,还会往queue里塞数据,应该就是类似深搜/广搜方法。

  9. 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()

  10. 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部分,那么好找。目前通过两部分同步寻找线索:

  1. 基于字符串搜索
  2. 反向调用链中调用到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

官方API介绍页面

Caffe2核心代码解析系列之三:Tensor,里面有提到Tensor中存放的实际地址(storage)

intrusive_ptr源码解析

就是一个包装后的更高级的指针,可以处理一般指针与弱指针两种类型。

at::Tensor中的数据存在哪里(找到指针,与对应大小)

在StorageImpl.h(c10/core/StorageImpl.h)中,分别有几个成员变量:

  • data_type_
  • data_ptr_
  • numel_
  • received_cuda_

任务

  1. 数据存放位置,并且如何操纵之

    • 打印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()可以达到目的。
  1. 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))


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。