PyTorch内部2-The Build System

翻译:PyTorch Internals Part II - The Build System

PyTorch​pytorch.org/blog/a-tour-of-pytorch-internals-2/正在上传…重新上传取消

第一篇博文解释了我们如何生成torch.Tensor这个可以在Python解释器中使用的object。接下来研究一下pytorch的构建(build)系统。pytorch的代码库包含多个部分:

  • 核心的Torch库:TH、THC、THNN、THCUNN
  • 供应商库:CuDNN、NCCL
  • python扩展库
  • 另外的第三方库:NumPy、MKL、LAPACK

只调用python setup.py install,就能使你们调用import torch、并在代码中使用pytoch库,这是如何做到的呢?

这篇文档的第一部分将会从最终用户的角度解释构建过程(build process),将会解释如何使用上面的部件来构建库。文档的第二个部分将会对pytorch的开发者比较重要,它将描述一些方法,通过只构建你修改的那部分代码来提升迭代开发的速度。

Setuptools和PyTorch的 setup( )函数

Python使用Setuptools来构建lib库(library)。Setuptools是对来自python核心库的原始系统的扩展,它的核心部件是setup.py,这个文件包含了构建工程的所有需要的信息。最为重要的函数是setup函数,它是主要的入口,让我们看一下PyTorch的这个函数:

setup(name="torch", version=version,
      description="Tensors and Dynamic neural networks in Python with strong GPU acceleration",
      ext_modules=extensions,
      cmdclass={
          'build': build,
          'build_py': build_py,
          'build_ext': build_ext,
          'build_deps': build_deps,
          'build_module': build_module,
          'develop': develop,
          'install': install,
          'clean': clean,
      },
      packages=packages,
      package_data={'torch': [
          'lib/*.so*', 'lib/*.dylib*',
          'lib/torch_shm_manager',
          'lib/*.h',
          'lib/include/TH/*.h', 'lib/include/TH/generic/*.h',
          'lib/include/THC/*.h', 'lib/include/THC/generic/*.h']},
      install_requires=['pyyaml'],
      )

这个函数完全由关键字参数构成,实现两个目的:

  • 元信息(Metadata)(也就是名字、描述、版本)
  • 包(package)的内容

我们关心第二个,将这些部件拆开看一下:

  • ext_modules:python的模块要么是纯粹的模块,只包含python代码,要么是扩展的、使用开发python的底层语言写成的模块。这里我们列出构建的扩展模块,包括主要的包含Tensor的torch._C库。
  • cmdclass:当从命令行使用setup.py脚本的时候,需要指定一个或多个命令,这些命令片段执行具体的动作。例如,install命令构建并安装package。这个映射将具体的命令导入到使用它们的函数中。
  • packages:这个工程中package的列表。如果它们仅仅包含python的代码,这里就是“纯净的”,这些package被定义在setup.py的其他地方。
  • package_data:需要被安装到package中的一些额外的文件:这种情况下,构建生成的头文件和共享的lib库需要纳入到安装过程中。
  • install_requires:为了构建pytorch,我们需要pyyaml。setuptools将会确保pyymal可用,需要的时候会下载和安装它。

我们将会更详细考虑这些部分,但是现在,更有指导性的做法是去看一下安装最后生成的东西,也就是,看一下在构建代码之后setuptools做了什么。

site_packages

第三方的package缺省情况下被安装在lib//site_packages目录下,这个目录和python执行文件有着关联。例如,因为我使用miniconda的环境,我的python的执行文件在:

(p3) killeent@devgpu047:pytorch (master)$ which python
~/local/miniconda2/envs/p3/bin/python

因此packages被安装在了:

/home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages

我已经安装了pytorch,看一下site-package下troch的文件夹:

(p3) killeent@devgpu047:site-packages$ cd torch
(p3) killeent@devgpu047:torch$ ls
autograd  backends  _C.cpython-36m-x86_64-linux-gnu.so  cuda  distributed  _dl.cpython-36m-x86_64-linux-gnu.so  functional.py  __init__.py  legacy  lib  multiprocessing  nn  optim  __pycache__  serialization.py  _six.py  sparse  storage.py  _tensor_docs.py  tensor.py  _tensor_str.py  _thnn  _torch_docs.py  utils  _utils.py  version.py

注意到我们期望的在这里的都可以看到:

  • 所有纯净的package在这里
  • 扩展的lib库在这里-._C*和._dl*共享的库
  • package_data在这里:lib的内容和我们在setup函数中描述的完全匹配:
(p3) killeent@devgpu047:torch$ ls lib/
include     libnccl.so.1  libTHC.so.1   libTHCUNN.so.1  libTHNN.so.1  libTH.so.1   THCUNN.h  torch_shm_manager libnccl.so  libshm.so     libTHCS.so.1  libTHD.so.1     libTHPP.so.1  libTHS.so.1  THNN.h

在import的时候,python解释器会查看site_package。如果我们在python代码中调用import torch,它就会在这里寻找module,并进行初始化和导入。你可以从这里了解更多关于import系统的事情。6. Modules - Python 3.7.2 documentation

构建单独的部件

接下来,看一下各种部件,从开始到结束的构建过程。这将解释如何将前面提到的代码组合到一起。

Torch的后端和供应商的库

看一下在pytorch的setup.py中重载的install指令

class install(setuptools.command.install.install):

 def run(self):
 if not self.skip_build:
            self.run_command('build_deps')
        setuptools.command.install.install.run(self)

注意到它做的第一件事情是运行叫做build_deps的指令,让我们看一下它的run()方法。

def run(self):
 from tools.nnwrap import generate_wrappers as generate_nn_wrappers
        build_all_cmd = ['bash', 'torch/lib/build_all.sh']
 if WITH_CUDA:
            build_all_cmd += ['--with-cuda']
 if WITH_NCCL and not SYSTEM_NCCL:
            build_all_cmd += ['--with-nccl']
 if WITH_DISTRIBUTED:
            build_all_cmd += ['--with-distributed']
 if subprocess.call(build_all_cmd) != 0:
            sys.exit(1)
        generate_nn_wrappers()

这里注意到有个shell脚本在build_all.sh在torch/lib/目录下。这个脚本是可以配置的,配置是否在

一个可以启用CUDA的系统上、是否启用NCCL库,以及pytorch的分布式库是否开启。

看一下torch/lib目录:

(p3) killeent@devgpu047:lib (master)$ ls
build_all.sh  libshm  nccl  README.md  TH  THC  THCS  THCUNN  THD  THNN  THPP  THS

这里可以看到所有后端lib库的目录。TH、THC、THNN、THCUNN和nccl这些是git的子树,时与torch(github.com/torch)库保持同步的一些库。THS、THCS、THD、THPP和libshm这些是pytorch专有的库。所有的库都包含CMakeList.txt,表示它们是使用CMake构建的。

build_all.sh本质上是一个脚本,它在这些库上运行CMake的配置文件,然后make install。运行一下./build_all.sh,看看得到了什么:

(p3) killeent@devgpu047:lib (master)$ ./build_all.sh --with-cuda --with-nccl --with-distributed
[various CMake output logs]
(p3) killeent@devgpu047:lib (master)$ ls
build  build_all.sh  include  libnccl.so  libnccl.so.1  libshm  libshm.so  libTHC.so.1  libTHCS.so.1  libTHCUNN.so.1  libTHD.so.1  libTHNN.so.1  libTHPP.so.1  libTH.so.1  libTHS.so.1  nccl  README.md  TH  THC  THCS  THCUNN  THCUNN.h  THD  THNN  THNN.h  THPP  THS  tmp_install  torch_shm_manager

现在这个目录下有一些增加的东西:

  • 每个库的共享lib库文件
  • THNN和THCUNN的头文件
  • build和tmp_install目录
  • torch_shm_manager可执行文件

进一步研究一下,在这个shell脚本中,我们创建了build的目录,为了构建,并为每个lib创建了一个子目录:

# We create a build directory for the library, which will
# contain the cmake output. $1 is the library to be built
  mkdir -p build/$1
  cd build/$1

这样,比如build/TH包含了CMake配置文件的输出,包括为了构建TH的Makefile文件,以及在这个目录下运行make install的结果。

看一下tmp_install:

(p3) killeent@devgpu047:lib (master)$ ls tmp_install/
bin  include  lib  share

tmp_install像一个标准的安装文件夹,包括二值文件、头文件和lib文件。例如tmp_install/include/TH包括所有的TH的头文件,相应的tmp_install/lib文件夹包括libTH.so.1文件。

那么为什么需要这个目录呢?它用来编译那些互相依赖的lib库。比如THC库依赖TH的库和头文件,这些在构建的shell脚本中,作为cmake命令的参数。

# install_dir is tmp_install
cmake ...
	-DTH_INCLUDE_PATH="$INSTALL_DIR/include" \
	-DTH_LIB_PATH="$INSTALL_DIR/lib" \

事实上如果我们看一下编译的THC库:

(p3) killeent@devgpu047:lib (master)$ ldd libTHC.so.1
	...
	libTH.so.1 => /home/killeent/github/pytorch/torch/lib/tmp_install/lib/./libTH.so.1 (0x00007f84478b7000)

build_all.sh指定include和lib路径的方式有点乱,但是这表达了上面的所有思想。最后,在这个脚本的结束处:

# If all the builds succeed we copy the libraries, headers,
# binaries to torch/lib
cp $INSTALL_DIR/lib/* .
cp THNN/generic/THNN.h .
cp THCUNN/generic/THCUNN.h .
cp -r $INSTALL_DIR/include .
cp $INSTALL_DIR/bin/* .

可以看到,最后拷贝所有的东西,到顶层的torch/lib目录中-解释了我们前面看到的内容。接下来看一下为什么要这样做。

NN封装

简单地,我们接触一下build_deps这个命令的最后部分:generate_nn_wrappers()。使用pytorch的定制的cwrap工具,绑定后端lib,这个工具在上一章博文中我们已经接触到了。为了绑定TH和THC,为每一个函数,人工写了YAML声明。实际上,由于THNN和THCUNN库相对简单,我们自动生成cwrap声明并得到c++代码。

我们拷贝THNN.h和THCUNN.h头文件到torch/lib的原因是,generate_nn_wrappers期望这些文件在那里。generate_nn_wrappers做了这些事情:

  • 解析头文件,生成cwrap YAML的声明,并将它们写到输出文件.cwrap中
  • 调用cwrap,并在.cwrap文件上使用合适的插件,为每一个生成源代码
  • 第二次解析这些头文件,来生成THNN_generic.h-这个lib库接收THPP的Tensor、pytorch的通用C++Tensor库,并基于Tensor的动态类型调用合适的THNN/THCUNN库函数

在运行generate_nn_wrappers()之后,看一下torch/csrc/nn,可以看到这些输出:

(p3) killeent@devgpu047:nn (master)$ ls
THCUNN.cpp  THCUNN.cwrap  THNN.cpp  THNN.cwrap  THNN_generic.cpp  THNN_generic.cwrap  THNN_generic.h  THNN_generic.inc.h

例如,生成cwrap文件的代码是:

[[
  name: FloatBatchNormalization_updateOutput
  return: void
  cname: THNN_FloatBatchNormalization_updateOutput
  arguments:
    - void* state
    - THFloatTensor* input
    - THFloatTensor* output
    - type: THFloatTensor*
      name: weight
      nullable: True
    - type: THFloatTensor*
      name: bias
      nullable: True
    - THFloatTensor* running_mean
    - THFloatTensor* running_var
    - THFloatTensor* save_mean
    - THFloatTensor* save_std
    - bool train
    - double momentum
    - double eps
]]

相应的.cpp文件是:

extern "C" void THNN_FloatBatchNormalization_updateOutput(void*, THFloatTensor*, THFloatTensor*, THFloatTensor*, THFloatTensor*, THFloatTensor*, THFloatTensor*, THFloatTensor*, THFloatTensor*, bool, double, double);

PyObject * FloatBatchNormalization_updateOutput(PyObject *_unused, PyObject *args) {
 // argument checking, unpacking
	 PyThreadState *_save = NULL;
 try {
        Py_UNBLOCK_THREADS;
        THNN_FloatBatchNormalization_updateOutput(arg_state, arg_input, arg_output, arg_weight, arg_bias, arg_running_mean, arg_running_var, arg_save_mean, arg_save_std, arg_train, arg_momentum, arg_eps);
        Py_BLOCK_THREADS;
        Py_RETURN_NONE;
      } catch (...) {
 if (_save) {
          Py_BLOCK_THREADS;
        }
 throw;
      }

    ...
}

在生成的THPP代码中,函数看起来是这样的:

void BatchNormalization_updateOutput(thpp::Tensor* input, thpp::Tensor* output, thpp::Tensor* weight, thpp::Tensor* bias, thpp::Tensor* running_mean, thpp::Tensor* running_var, thpp::Tensor* save_mean, thpp::Tensor* save_std, bool train, double momentum, double eps) {
 // Call appropriate THNN function based on tensor type, whether its on CUDA, etc.
}

我们后面会再看一下如何使用这些源文件。

构建纯粹的python模块

现在已经构建了后端库(依赖项),我们可以更进一步,来构建实际的pytorch代码。下一个运行的setuptools命令是build_py,这个被用来在库中构建纯粹的python模块。这些是传给setup.py的包。

这些包是使用setuptools的单元函数find_packages找到的:

packages = find_packages(exclude=('tools.*',))
['torch', 'torch._thnn', 'torch.autograd', 'torch.backends', 'torch.cuda', 'torch.distributed', 'torch.legacy', 'torch.multiprocessing', 'torch.nn', 'torch.optim', 'torch.sparse', 'torch.utils', 'torch.autograd._functions', 'torch.backends.cudnn', 'torch.legacy.nn', 'torch.legacy.optim', 'torch.nn._functions', 'torch.nn.backends', 'torch.nn.modules', 'torch.nn.parallel', 'torch.nn.utils', 'torch.nn._functions.thnn', 'torch.utils.data', 'torch.utils.ffi', 'torch.utils.serialization', 'torch.utils.trainer', 'torch.utils.backcompat', 'torch.utils.trainer.plugins']

可以看到,find_package会递归地遍历torch的目录,找到所有的有着__init__.py文件的目录路径。

当使用setuptools构建的时候,这个工具在工程根目录下,也就是和setup.py文件相同的位置,创建build文件夹。因为pytorch是由纯粹的python模块,和扩展模块构成的,当进行构建的时候,我们需要保存关于操作系统和python版本的信息。看一下build目录,可以看到:

(p3) killeent@devgpu047:pytorch (master)$ ls build
lib.linux-x86_64-3.6  temp.linux-x86_64-3.6

这说明我们使用python3.6,在linux-x86-64上构建了工程。lib文件夹包含了lib文件,而临时的文件夹包含了在构建过程中生成的文件,这些文件在最终安装时是不需要的。

因为纯粹的python模块仅仅是python的代码,不需要编译,这个build_py只从find_packages找到的目录中,复制文件,并拷贝到build/.下的对应位置。因此build输出可以用这几行来说明:

copying torch/autograd/_functions/blas.py -> build/lib.linux-x86_64-3.6/torch/autograd/_functions

前面我们也注意到在主要的setup()函数中,可以传递文件和目录到package_data关键字参数,这样setuptools就可以拷贝这些文件到安装目录中。在build_py中,这些文件被拷贝到build/目录下,因此我们也可以看到类似的代码:

copying torch/lib/libTH.so.1 -> build/lib.linux-x86_64-3.6/torch/lib
...
copying torch/lib/include/THC/generic/THCTensor.h -> build/lib.linux-x86_64-3.6/torch/lib/include/THC/generic

构建扩展的模块

最后我们需要构建扩展的模块,也就是pytorch的用CPython作为后端、c++写成的模块。这也构成了setup.py的主要的代码逻辑。在扩展被构建之前,重载的build_ext命令有一些特殊的逻辑:

from tools.cwrap import cwrap
from tools.cwrap.plugins.THPPlugin import THPPlugin
from tools.cwrap.plugins.ArgcountSortPlugin import ArgcountSortPlugin
from tools.cwrap.plugins.AutoGPU import AutoGPU
from tools.cwrap.plugins.BoolOption import BoolOption
from tools.cwrap.plugins.KwargsPlugin import KwargsPlugin
from tools.cwrap.plugins.NullableArguments import NullableArguments
from tools.cwrap.plugins.CuDNNPlugin import CuDNNPlugin
from tools.cwrap.plugins.WrapDim import WrapDim
from tools.cwrap.plugins.AssertNDim import AssertNDim
from tools.cwrap.plugins.Broadcast import Broadcast
from tools.cwrap.plugins.ProcessorSpecificPlugin import ProcessorSpecificPlugin
        thp_plugin = THPPlugin()
        cwrap('torch/csrc/generic/TensorMethods.cwrap', plugins=[
            ProcessorSpecificPlugin(), BoolOption(), thp_plugin,
            AutoGPU(condition='IS_CUDA'), ArgcountSortPlugin(), KwargsPlugin(),
            AssertNDim(), WrapDim(), Broadcast()
        ])
        cwrap('torch/csrc/cudnn/cuDNN.cwrap', plugins=[
            CuDNNPlugin(), NullableArguments()
        ])

回想一下前面描述的,为了调用THNN等库,而自动生成的c++代码,在那里绑定TH, THC和CuDNN。我们提取TensorMethods.cwrap中YAML的声明,使用它们生成c++代码文件,这些源文件包含了,可在pytorch的c++生态系统中运行的实现。例如,zero_这样的简单声明:

[[
  name: zero_
  cname: zero
  return: self
  arguments:
    - THTensor* self
]]

生成的代码是:

PyObject * THPTensor_(zero_)(PyObject *self, PyObject *args, PyObject *kwargs) {
	...
	THTensor_(zero)(LIBRARY_STATE arg_self);
	...
}

在上一个博文中,我们已经说明了,这些函数是如何绑定到具体的Tensor类型,这里不再对它们详细展开。对于build过程,知道这些c++文件是在扩展被构建之前生成的就足够了,因为这些源文件在扩展编译的过程中会被用到。

指定扩展

不同于纯粹的模块,仅仅列出模块或者包,就期望setuptools运行并找到正确的文件是不够的,你需要指定扩展的名字,源文件和任何编译/链接需要的信息(包括目录,需要链接的库等等)。

在setup.py的这些代码块(200行的位置),说明了如何构建这些扩展。这里,在build_all.sh中给出的一些选择会起作用。例如我们看到构建脚本指定了tmp_install目录,在那里安装了后端库。在setup.py代码中,将一系列包含头文件的目录加入到include中时,我们引用了这个目录:

# tmp_install_path is torch/lib/tmp_install
include_dirs += [
    cwd,
    os.path.join(cwd, "torch", "csrc"),
    tmp_install_path + "/include",
    tmp_install_path + "/include/TH",
    tmp_install_path + "/include/THPP",
    tmp_install_path + "/include/THNN",

类似地,在build_all.sh的最后,拷贝这些共享的目标库到torch/csrc中。在setup.py的代码中,当需要指定链接库时,我们直接引用了这些位置:

# lib_path is torch/lib
TH_LIB = os.path.join(lib_path, 'libTH.so.1')
THS_LIB = os.path.join(lib_path, 'libTHS.so.1')
THC_LIB = os.path.join(lib_path, 'libTHC.so.1')
THCS_LIB = os.path.join(lib_path, 'libTHCS.so.1')
THNN_LIB = os.path.join(lib_path, 'libTHNN.so.1')
# ...

让我们看一下,如何构建这个主要的torch._C扩展模块:

 = Extension("torch._C",
              libraries=main_libraries,
              sources=main_sources,
              language='c++',
              extra_compile_args=main_compile_args + extra_compile_args,
              include_dirs=include_dirs,
              library_dirs=library_dirs,
              extra_link_args=extra_link_args + main_link_args + [make_relative_rpath('lib')],
              )
  • main libraries是我们需要link的所有库。包括shm、pytorch的共享内存管理库,也包括系统库比如cudart和cudnn。注意到TH库并没有列在这里。
  • 主要的源文件是c++的,构成了pytorch的c++后端
  • 编译参数是各种各样的标志和编译配置。比如,我们可能想在编译debug模式时,加入debug的标志
  • include dirs是所有的包含头文件的目录路径。这也是另一个例子说明了build_all.sh脚本的哪些是很重要的。比如,我们在torch/lib/tmp_install/include/TH中查看头文件,这是为CMake的配置文件指定的安装位置
  • library dirs是链接时,共享库的搜索文件夹。比如,包含torch/lib--在build_all.sh的最后将.so文件拷贝到的位置,也是指向CUDA和CuDNN目录的路径。

6,link arguments在将object文件链接到一起,生成扩展的时候要用到。在pytorch中,包括更常规的选项,比如静态地链接libstdc++。然而,这里有一个重要的部分:也就是,我们在哪里链接后端的库,比如TH。注意到有这几行:

# The explicit paths to .so files we described above
main_link_args = [TH_LIB, THS_LIB, THPP_LIB, THNN_LIB]

你可能会疑惑为什么要这样做,而不是将这些库,加入到libraries关键字参数的列表中。毕竟这是一个需要被链接的库列表。问题是Lua Torch在安装的时,往往会设置LD_LIBRARY_PATH这个变量,因此可能会错误地链接到,为Lua Torch编译的一个TH库上,而不是局部地编译的这个。这就会产生问题,因为代码可能会过时,而且为了编译Lua Torch的TH库,而设定的种种配置选项,不一定能很好地兼容PyTorch。

这样,我们为链接器,直接人工指定这些共享库的路径。

也有其他的增强的Pytorch需要的库,也是使用相似的方式构建的。setuptools库唤醒c++的编译器和链接器,来构建这些扩展。如果构建成功,我们已经成功地构建了pytorch库,可以进行安装了。

安装

在构建完成之后,安装就很简单了,我们只需要拷贝build/lib.linux-x86_64-3.6目录下所有东西,到合适的安装位置。回忆一下,前面注意到这个目录,是与python执行文件关联的site_package目录。因此我们可以看到类似几行:

running install_lib
creating /home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages/torch
copying build/lib.linux-x86_64-3.6/torch/_C.cpython-36m-x86_64-linux-gnu.so -> /home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages/torch
copying build/lib.linux-x86_64-3.6/torch/_dl.cpython-36m-x86_64-linux-gnu.so -> /home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages/torch
creating /home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages/torch/_thnn
copying build/lib.linux-x86_64-3.6/torch/_thnn/_THNN.cpython-36m-x86_64-linux-gnu.so -> /home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages/torch/_thnn
copying build/lib.linux-x86_64-3.6/torch/_thnn/_THCUNN.cpython-36m-x86_64-linux-gnu.so -> /home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages/torch/_thnn

最后让python解释器变强大了。当python解释器执行一个import的声明时,它会沿着搜索路径,寻找python的代码和扩展模块。当解释器被构建时,一个缺省的路径,被配置到python执行文件中。

# note we are now in my home directory
(p3) killeent@devgpu047:~$ python
Python 3.6.1 |Continuum Analytics, Inc.| (default, Mar 22 2017, 19:54:23)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/home/killeent/local/miniconda2/envs/p3/lib/python36.zip', '/home/killeent/local/miniconda2/envs/p3/lib/python3.6', '/home/killeent/local/miniconda2/envs/p3/lib/python3.6/lib-dynload', '/home/killeent/.local/lib/python3.6/site-packages', '/home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages', '/home/killeent/github/pytorch', '/home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages/setuptools-27.2.0-py3.6.egg']

可以看到,拷贝了pytorch安装文件的site-packages目录,是搜索路径的一部分。现在加载一个torch的模块,并看一下它的位置:

>>> import torch
>>> import inspect
>>> inspect.getfile(torch)
'/home/killeent/local/miniconda2/envs/p3/lib/python3.6/site-packages/torch/__init__.py'

可以看到,与期望的一样,我们已经从site-packages中加载了模块,构建和安装都成功了!

注意:python将一个空的字符串,前缀到sys.path中。来表示当前的工作目录--使它成为模块搜索的第一个地方。因此,如果在pytorch的目录下运行python,可能加载的是pytorch的局部版本,而不是安装的版本。这有时候需要注意一下。

补充-开发高效的第三方的库,前面并没有讲到。

整个安装pytorch是很耗时的。在我测试的时候,源码安装大概花费了5分钟。更多的时候,当开发pytorch时,我们只在整个工程的一个子集上修改,只想对那个子集进行重新构建,来测试修改。幸运的是构建系统允许这样做。

setuptools开发模式

主要支持上述做法的工具,是setuptools的develop命令。文档中是这样说明的:

This command allows you to deploy your project’s source for use in one or more “staging areas” where it will be available for importing. This deployment is done in such a way that changes to the project source are immediately available in the staging area(s), without needing to run a build or install step after each change.

但是它是如何工作的呢?假设我们在pytorch的目录下,运行python setup.py build develop。build指令被运行,构建了依赖项(TH, THPP等)和扩展库。然而如果我们看一下site-packages的内部:

(p3) killeent@devgpu047:site-packages$ ls -la torch*
-rw-r--r--. 1 killeent users 31 Jun 27 08:02 torch.egg-link

看一下torch.egg-link文件的内容,它仅仅引入了pytorch的工作目录:

(p3) killeent@devgpu047:site-packages$ cat torch.egg-link
/home/killeent/github/pytorch

如果重新回到pytorch的目录下,可以看到一个新的torch.egg-info的目录:

(p3) killeent@devgpu047:pytorch (master)$ ls -la torch.egg-info/
total 28
drwxr-xr-x.  2 killeent users  4096 Jun 27 08:09 .
drwxr-xr-x. 10 killeent users  4096 Jun 27 08:01 ..
-rw-r--r--.  1 killeent users     1 Jun 27 08:01 dependency_links.txt
-rw-r--r--.  1 killeent users   255 Jun 27 08:01 PKG-INFO
-rw-r--r--.  1 killeent users     7 Jun 27 08:01 requires.txt
-rw-r--r--.  1 killeent users 16080 Jun 27 08:01 SOURCES.txt
-rw-r--r--.  1 killeent users    12 Jun 27 08:01 top_level.txt

这个文件包含pytorch工程的metadata。比如,requirements.txt列出了,建立pytorch的所有依赖项:

(p3) killeent@devgpu047:pytorch (master)$ cat torch.egg-info/requires.txt
pyyaml

不需要深入到更多的细节,develop本质上,允许我们将pytorch repo本身,看成是在site-packages中,所以导入模块就可以工作:

(p3) killeent@devgpu047:~$ python
Python 3.6.1 |Continuum Analytics, Inc.| (default, Mar 22 2017, 19:54:23)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> torch.__file__
'/home/killeent/github/pytorch/torch/__init__.py'

作为这样的一个结果,会有如下的情况:

  • 如果改变了一个pytroch的源文件会自动被接收到,不需要运行任何命令使python的解释器,看到这些改变。
  • 如果改变了扩展库的一个c++源文件,可以重新运行develop命令,它会重新构建这个扩展

因此我们可以无缝地开发pytorch的代码,并以一种方便的方式,测试这些改变。

修改依赖库

如果修改了依赖(也就是TH,THPP等),可以只运行build_eps命令,直接对这些改变进行快速地重新构建。这会自动调用build_all.sh,来重构我们的库,并适时地拷贝这些生成的库。如果使用develop模式,我们正在使用的是,在pytorch目录下,编译的局部扩展库。因为当编译扩展库的时候,已经在共享库中,指定了这些路径,这些改变会被接收到:

# we are using the local extension
(p3) killeent@devgpu047:~$ python
Python 3.6.1 |Continuum Analytics, Inc.| (default, Mar 22 2017, 19:54:23)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> torch._C.__file__
'/home/killeent/github/pytorch/torch/_C.cpython-36m-x86_64-linux-gnu.so'

# it references the local shared object library we just re-built
(p3) killeent@devgpu047:~$ ldd /home/killeent/github/pytorch/torch/_C.cpython-36m-x86_64-linux-gnu.so
# ...
libTH.so.1 => /home/killeent/github/pytorch/torch/lib/libTH.so.1 (0x00007f543d0e2000)
# ...

因此,可以测试任何改变,而不需要进行完整的重构。

第三方库

pytroch依赖一些第三方的库。通常的方案是,通过Anaconda安装并使用它们,然后对它们建立链接。比如,通过这样做,让pytorch使用mkl库:

# installed to miniconda2/envs/p3/lib/libmkl_intel_lp64.so
conda install mkl

然后只要在$CMAKE_PREFIX_PATH中,设定了这个lib库的路径,编译时,就能成功找到这个库:

# in the site-packages dir
(p3) killeent@devgpu047:torch$ ldd _C.cpython-36m-x86_64-linux-gnu.so
# ...
libmkl_intel_lp64.so => /home/killeent/local/miniconda2/envs/p3/lib/libmkl_intel_lp64.so (0x00007f3450bba000)
# ...

没有提到的,但是相关的一些:

  • 如何使用ccache来提升构建速度
  • pytroch顶层的__init__.py文件,如何处理初始模型的导入,并载入各种各样的模块和扩展库
  • CMake构建系统,后端的lib库是如何配置并被CMake构建的。

 

你可能感兴趣的:(pytorch,python,深度学习)