沉思语录

取次花丛懒回顾,半缘修道半缘君


  • 首页

  • 归档

  • 标签

  • 搜索

pytorch

发表于 2021-01-04 |

Build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
git clone --recursive *.git
如果要修改已有的checkin
rm -rf third_party
git submodule sync
git submodule update --init --recursive
## Build
python setup.py build
python setup.py install
python setup.py clean --all
生成wheel包
python setup.py bdist_wheel

quantized::linear python 接口定义在
torch/nn/quantized/modules/linear.py

Pytorch 运行单元测试

https://github.com/pytorch/pytorch/tree/master/benchmarks/operator_benchmark
numactl -c 0 -m 0 python -m pt.qlinear_test –omp_num_threads 28 –mkl_num_threads 28

运行FBGEMM的benchmark
FBGEMM的run to run 变化很大,因为它会自动cflush去刷新内存

Python 和C++之间的接口

基本介绍

pytorch里面有两种方式:

  • 使用pybind11
  • 直接使用CPython,python.h 以及使用pyobject*,ATEN相关的函数映射(如下面提高的conv2d)是用这种方式实现的

conv2d的例子

通过conv2d的例子来看下python和C++之间的接口层:

  • torch/nn/modules/conv.py -> nn.Conv2d
    调用了F.conv2d
  • torch/nn/functional.py -> conv2d
    这个函数里面调用到了 torch.conv2d, 这个实现在C++里面
  • torch/csrc/autograd/generated/python_torch_functions.cpp -> THPVariable_conv2d
    generated 目录下面的文件都是在编译时生成的,python_torch_functions.cpp文件生成过程的细节参考下面的section
    这里简单看一下这个文件的做函数映射的内容:
    python_torch_functions.cpp的PyMethodDef torch_functions[]里面定义了映射关系,从而将torch.conv2d 映射到了函数THPVariable_conv2d
    1
    {"conv2d", (PyCFunction)(void(*)(void))THPVariable_conv2d, METH_VARARGS | METH_KEYWORDS | METH_STATIC, NULL},

THPVariable_conv2d 调用lambda函数dispatch_conv2d,进一步调用到函数at::conv2d

  • aten/src/ATen/native/Convolution.cpp -> at::conv2d
  • aten/src/ATen/native/Convolution.cpp -> at::convolution
  • aten/src/ATen/native/Convolution.cpp -> at::_convolution
    这个函数会将卷积派发到mkldnn或者cuda去直接计算,下面先看mkldnn的部分
  • aten/src/ATen/native/mkldnn/Conv.cpp -> at::mkldnn_convolution
  • aten/src/ATen/native/mkldnn/Conv.cpp -> at::_mkldnn_conv2d
  • ideep/computations.hpp -> compute_impl
    compute_impl 是convolution_forward 这个结构体的成员函数
    看一下compute_impl这个函数的细节,如何创建mkldnn的Primitive和执行计算:
    首先是计算一些mkldnn创建PD需要的参数,然后将这些参数传入这个重点函数
    1
    2
    fetch_or_create_m(comp, key, src_desc, weights_desc, bias_desc, dst_desc_in, strides, dilates,
    padding_l, padding_r, op_attr, std::forward<Ts>(args)...);

fetch_or_create_m 定义在ideep/include/ideep/lru_cache.hpp 里面

1
2
3
4
#define fetch_or_create_m(op, key, ...) \
auto it = find(key); \
if (it == end()) { it = create(key, __VA_ARGS__); } \
auto op = fetch(it);

进一步跟进fetch_or_create_m函数,发现调用到了computation_cache::create 方法

1
auto it = t_store().insert(std::make_pair(key,value_t(std::forward<Ts>(args)...)));

value_t(std::forward(args)…)) 就是进入了创建primitive的函数
value_t是通过computation_cache的模板传入的

1
2
template <class value_t, size_t capacity = 1024, class key_t = std::string>
class computation_cache {

同时注意到,compute_impl是convolution_forward结构体的成员函数,convolution_forward结构体继承了computation 以及computation_cache 结构体

1
2
struct convolution_forward: public computation,
public utils::computation_cache<convolution_forward>

所以这里的value_t就是convolution_forward
这里通过value_t 去创建了一个convolution_forward的结构体的对象,然后存入到primitive cache里面,方便后面再次调用,避免重复创建

  • ideep/computations.hpp::convolution_forward 的构造函数 convolution_forward
    convolution_forward 结构体继承自computation结构体,继承自primitive_group结构体,继承自c_wrapper
    这个构造函数,做了两件事情
    1
    2
    descriptor forward_descriptor(src_desc, std::forward<Ts>(args)...); #descriptor 继承自primitive descriptor,这里创建了对应的pd
    computation::init(forward_descriptor); #这里通过PD去创建primitive

细节python_torch_functions.cpp

来具体看下python_torch_functions.cpp 这个文件是如何生成的:
主要做的事情就是读取yaml文件的内容,去替换template文件对应的部分,生成新的文件

build的时候会去执行这个脚本tools/setup_helpers/generate_code.py

1
build/build.ninja:41358: COMMAND = cd /home/lesliefang/pytorch/pytorch && /home/lesliefang/pytorch/anaconda3/envs/pytorch_leslie/bin/python tools/setup_helpers/generate_code.py --declarations-path /home/lesliefang/pytorch/pytorch/build/aten/src/ATen/Declarations.yaml --nn-path aten/src

/home/lesliefang/pytorch/pytorch/build/aten/src/ATen/Declarations.yaml 这个文件和torch/share/ATen/Declarations.yaml 是一个文件,例如conv2d的函数就是申明在这个文件里面,通过查这个yaml文件,我们就可以知道Python函数名和C++函数名之间的映射关系

  • tools/setup_helpers/generate_code.py:main ->
  • tools/setup_helpers/generate_code.py:generate_code ->
  • tools/autograd/gen_autograd.py:gen_autograd_python ->
    这个函数首先通过去载入Declarations.yaml文件去解析所有的函数
    1
    aten_decls = load_aten_declarations(aten_path)

然后调用gen_py_torch_functions

  • tools/autograd/gen_python_functions.py:gen_py_torch_functions
    在这个函数里面去创建了python_torch_functions.cpp 文件
    第一步:这个函数首先读取templates目录下面python_torch_functions.cpp的同名模板文件
    第二步:然后通过get_py_torch_functions函数解析yaml文件里面的声明函数,创建py_torch_functions列表
    第三步:调用create_python_bindings 函数进一步格式化yaml文件中读到的内容
    第四步:调用wirte函数去创建torch/csrc/autograd/generated/python_torch_functions.cpp 文件,用yaml文件里面解析到的内容去替换template文件对应的位置
阅读全文 »

tensorflow编译系统

发表于 2020-12-03 |

介绍

本文以一组tensorflow的常用编译命令为例,去介绍tensorflow的编译系统

1
2
3
4
export build_dir=$path_var #定义编译输出目录
yes "" | python configure.py
bazel --output_user_root=$build_dir build --config=mkl --config=opt --copt=-O3 -s //tensorflow/tools/pip_package:build_pip_package
bazel-bin/tensorflow/tools/pip_package/build_pip_package $build_dir

Step1

命令:

1
yes "" | python configure.py

执行configure.py脚本,主要作用是把编译选项写入.tf_configure.bazelrc 文件
.tf_configure.bazelrc文件会被.bazelrc 文件import

1
try-import %workspace%/.tf_configure.bazelrc

我的理解.bazelrc 文件里面定义的就是hardcode的编译选项
.tf_configure.bazelrc文件通过configure.py暴露了一些可配置的选项给用户去配置

Step2

命令:

1
bazel --output_user_root=$build_dir build --config=mkl --config=opt --copt=-O3 -s //tensorflow/tools/pip_package:build_pip_package

解析编译命令

  • 根据–config的定义,解析.tf_configure.bazelrc 和 .bazelrc文件生成各个定义–define值

解析WORKSPACE文件

  • WORKSPACE 里面加载了tensorflow/workspace.bzl 的tf_repositories函数
    tf_repositories函数里面定义了各种外部依赖,一旦被调用到就会执行tf_http_archive 函数去下载并解压各种依赖库(包括mkldnn)
    tf_http_archive 是一个repository_rule(详见下面术语的解释),函数定义在//third_party:repo.bzl 文件里面
    注意 这里只是定义,不会立刻去下载,解压,只是定义了target,这个target一旦在某个地方呗调用到了,就会去执行下载,解压的操作

  • WORKSPACE 里面加载了load(“@bazel_tools//tools/build_defs/repo:http.bzl”, “http_archive”)
    http_archive 函数也定义一些用于下载依赖的压缩包的target

调用target目标

build_pip_package 这个target

//tensorflow/tools/pip_package:build_pip_package 找到 //tensorflow/tools/pip_package目录下面BUILD文件,找到build_pip_package name

1
2
3
4
5
6
7
8
9
10
11
12
13
sh_binary(
name = "build_pip_package",
srcs = ["build_pip_package.sh"],
data = COMMON_PIP_DEPS +
select({
"//tensorflow:windows": [
":simple_console_for_windows",
],
"//conditions:default": [
":simple_console",
],
}) + if_mkl_ml(["//third_party/mkl:intel_binary_blob"]),
)

这里最主要的依赖是COMMON_PIP_DEPS,从这里会延伸到各个需要build的target上面
一条我认为比较重要的编译依赖链路:

  • //tensorflow/tools/pip_package:build_pip_package
  • COMMON_PIP_DEPS
  • //tensorflow/python:while_v2
  • //tensorflow/python:framework_ops
  • //tensorflow/python:platform
  • //tensorflow/python:lib
  • //tensorflow/python:pywrap_tensorflow
  • //tensorflow/python:pywrap_tensorflow_internal
    从pywrap_tensorflow_internal会调用到各个C/C++ 实现的依赖(core,kernel,op)上面

接上面看下怎么调用到下载编译mkldnn

  • //tensorflow/c:c_api
  • //tensorflow/c:c_api_internal
  • //tensorflow/core:core_cpu
  • //tensorflow/core/common_runtime:core_cpu
  • mkl_deps()
    如下面的解释,mkl_deps函数定义在third_party/mkl/build_defs.bzl 会去下载和编译MKKLDNN
阅读全文 »

图计算

发表于 2020-12-01 |

综述和必读论文

https://zhuanlan.zhihu.com/p/89503068
https://mp.weixin.qq.com/s/3CYkXj2dnehyJSPLBTVSDg

框架实现

  • Deep Graph Library (DGL)
  • PyTorch Geometric
  • TensorFlow primitives for “gather” and “math.unsorted_segment” ops

应用

生成每个节点的向量表达式

得到每个节点的嵌入向量表达
这个应用和Word2Vec(https://zhuanlan.zhihu.com/p/27234078)很像,每个单词的低维稠密嵌入向量

算法

DeepWalk

这还是图嵌入算法,主要用于生成每个节点的低维稠密向量表达,提供给后续算法去使用

https://zhuanlan.zhihu.com/p/56380812
以当前节点为起始节点,随机游走(随机选择一个邻近节点),直到选择到的节点数量满足条件,然后利用skip-gram进行训练(和Word2Vec)相似,得到节点的低维稠密向量表达

官方实现:

https://sites.google.com/site/bryanperozzi/projects/deepwalk
https://github.com/phanein/deepwalk

举一个例子:

有512个节点的图,节点之间具有一定的连接关系

  1. 对每个节点编号,(然后就可以对每个节点进行one-hot编码, 如果使用gensim的Word2Vec函数,不需要进行显示的one-hot编码)
  2. 生成一个字典,字典的key为每个节点的编号,value为一个list,这个list里面的值就是和当前节点相连的节点的编号
  3. 确定每个节点需要随机游走的次数,比如这里我们设置10,这样一共需要进行512*10=5120次随机游走
  4. 确定随机游走的深度,比如是40,所以每一次随机游走都会生成一个长度为40的list,表示这次随机游走经过的节点的编号。这样会生成一个5120*40的矩阵,作为输入矩阵
  5. 调用gensim的Word2vec的API,输入这个512040的矩阵,设置特征嵌入的矩阵维度。比如为1024. 最后的权重矩阵是一个512 \1024的,每一行就对应了一个节点的特征向量

tips:

  • 在第四步随机游走的过程中,存在可能性回到原始节点重新游走
  • 第五步调用了gensim的Word2vec API,如果矩阵过大,无法一次性放在内存中,需要写入硬盘分块载入处理,参考官方代码的实现
  • 上面提到的=5120次随机游走,每一次都是独立的,所以可以多线程并行来做

GCN(Graph Convolution Network)

利用嵌入后的向量进行计算的后续算法,关键的需要解决的问题是如何去设计卷积核

资料

  • 通用介绍 https://zhuanlan.zhihu.com/p/78624225
  • 详细介绍了度矩阵,邻接矩阵和拉普拉斯矩阵的定义 以及数学推导 https://www.zhihu.com/question/54504471/answer/332657604

图卷积步骤

  1. 首先计算图的度矩阵D,和邻接矩阵A,从而可以计算得到图的Laplace矩阵
  2. 对Laplace矩阵进行矩阵分解,得到N个特征向量和N个特征值 (N为图的顶点的个数)
  3. 对图进行离散傅里叶变换,平时说的傅里叶变换是从时域向频域的转换,这里说的是从顶点的编号组成的域 向 图特征值组成的域的变换
  4. 将第三步说的对N个特征值分别进行的离散傅里叶变换,写成矩阵的形式(N个离散傅里叶变换拼在一起)
  5. 同理,可以推导傅里叶逆变换,得到从特征值到顶点编号的变换矩阵
  6. 定理:两个函数的卷积的傅里叶变换,等于,两个函数的傅里叶变换的乘积
    所以两个函数的卷积,等于,两个函数的傅里叶变换的乘积的逆变换

图卷积神经网络的计算步骤

参考这个实现的源代码,有很多变种的实现,可以看论文综述的文章
Numpy实现:https://mp.weixin.qq.com/s/sg9O761F0KHAmCPOfMW_kQ
Pytorch-Geometric实现:https://pytorch-geometric.readthedocs.io/en/latest/notes/installation.html

GraphSAGE

阅读全文 »
12…28
Leslie

Leslie

记录心情与能力的成长

82 日志
15 标签
© 2021 Leslie
由 Hexo 强力驱动
主题 - NexT.Pisces
本站访客数 本站总访问量