背景
大家都知道,在2018年4月份的时候,Facebook宣布将Caffe2的仓库合并到了PyTorch的仓库里,到现在马上就一整年了。那么这个合并到底做了什么呢?
另外,Gemfield最近fix了一个PyTorch Android编译方面的问题,然后顺便写了一篇文章发表在专栏里,简单介绍了下Android编译的上下文:Gemfield:PyTorch的Android编译。在这篇文章里,gemfield提到过“要弄懂PyTorch Android library的编译原理,我们最好要弄懂PyTorch在服务端的编译与安卓版library的编译有什么区别。”那区别是什么呢?其实我们能够想象,这个编译过程无非就是产生了一些库及可执行文件什么的,事实上,Server端的编译大约会产生2663个目标或临时ELF文件(包含.o、.a、.so、可执行程序等)。但是,在Server端编译和Android端编译肯定是有所不同的,这些不同的地方究竟是什么呢?
这些问题正是本文的来由,在这篇文章里,Gemfield将要描述PyTorch在服务端是如何编译的、产生的库文件之间有什么联系、PyTorch仓库里的caffe2和PyTorch究竟是什么关系,等等。
PyTorch中caffe2和Torch的关系
为什么要合并代码呢?肯定是为了复用啊。从用户层面来看,这个复用包含了代码、CI、部署、使用、各种管理维护等;而在代码层面,这个复用栈看起来是这样的(:
1,C10: 核心Tensor实现,手机端、服务端都用;
2,ATen + TH:Tensor算法的实现,由ATen和legacy的TH组成这一层面;这一层依赖上一层。目前在将ATen core往C10 porting,并且将TH往ATen上porting;
3,Caffe2,caffe2中network、operators等的实现,会生成libcaffe2.so、libcaffe2_gpu.so、caffe2_pybind11_state.cpython-37m-x86_64-linux-gnu.so(caffe2 CPU Python 绑定)、caffe2_pybind11_state_gpu.cpython-37m-x86_64-linux-gnu.so(caffe2 CUDA Python 绑定);基本来自旧的caffe2项目,这一层依赖上一层;
4,Torch,PyTorch的实现,这一层会生成libtorch.so和libtorch_python.so(Python绑定),依赖ATen+TH,不过因为ATen+TH的逻辑被封装在了libcaffe2.so,因此这一层要直接依赖上一层。
就这样实现了之前两个项目的代码复用。
Server端的编译及产生的库文件
按照先后顺序,PyTorch在Server端编译的时候会先后编译生成以下45个库文件(包含7个主角library):
libprotobuf-lite.a
libprotobuf.a
libprotoc.a
libclog.a
libcpuinfo.a
libqnnpack.a
libcpuinfo_internals.a
libpthreadpool.a
libnnpack_reference_layers.a
libnnpack.a
libgtest.a
libgtest_main.a
libbenchmark.a
libbenchmark_main.a
libasmjit.a
libfbgemm.a
libgloo.a
libgloo_cuda.a
libgloo_builder.a
libonnxifi_loader.a
libonnx_proto.a
libonnx.a
libonnxifi.so
libonnxifi_dummy.so
libmkldnn.a
libc10.so
libc10_cuda.so
libCaffe2_perfkernels_avx2.a
libcaffe2_protos.a
libsleef.a
libCaffe2_perfkernels_avx.a
libCaffe2_perfkernels_avx512.a
libcaffe2.so (主角1)
libcaffe2_gpu.so (主角2)
caffe2_pybind11_state.cpython-37m-x86_64-linux-gnu.so (主角3)
caffe2_pybind11_state_gpu.cpython-37m-x86_64-linux-gnu.so(主角4)
libshm.so
libtorch.so(主角5)
libTHD.a
libc10d.a
libtorch_python.so(主角6)
libc10d_cuda_test.so
libcaffe2_detectron_ops_gpu.so
libcaffe2_module_test_dynamic.so
libcaffe2_observers.so
_C.cpython-37m-x86_64-linux-gnu.so(主角7)
1,libcaffe2.so
这时候该第一个主角出场了,这就是libcaffe2.so,这个库会把以下的目标文件和.a、.so库链接进来:
#会链接下面的.o文件
aten/src/ATen/
aten/src/TH/
aten/src/THNN/
caffe2/,大部分是core和operators目录
#会链接下面的库文件
libqnnpack.a
libnnpack.a
libcpuinfo.a
libfbgemm.a
libgloo.a
libonnxifi_loader.a
libpthreadpool.a
libmkldnn.a
libcpuinfo.a
libsleef.a
libcaffe2_protos.a
libclog.a
libasmjit.a
libonnx.a
libonnx_proto.a
libprotobuf.a
libCaffe2_perfkernels_avx.a
libCaffe2_perfkernels_avx2.a
libc10.so
libCaffe2_perfkernels_avx512.a
libcaffe2.so就是caffe2的实现。在Android版本的PyTorch编译时,只会编译这一个主角library。
在Server端编译和使用时,它会被caffe2 python绑定库(主角3)、caffe2 cuda库(主角2)、caffe2 CUDA python绑定库(主角4)、Torch库(主角5)、Torch Python绑定库(主角6)所依赖和使用。
2,libcaffe2_gpu.so
第二个主角就是cuda版的libcaffe2.so:libcaffe2_gpu.so 。相比libcaffe2.so,把CPU相关的换成了CUDA相关的。比如会把以下的目标文件和.a和.so库链接进来:
aten/src/ATen/cudnn/
aten/src/ATen/SparseCUDA*
operators/*_gpu.cc
#以及一些cuda相关的library(不限于以下)
libc10_cuda.so
libcusparse.so
libcurand.so
libcaffe2.so
libcurand.so
libcudart.so
libc10.so
libcufft.so
libcublas.so
在Server端编译和使用时,它会被caffe2 CUDA python绑定库(主角4)、Torch库(主角5)、Torch python绑定库(主角6)、Torch python接口库(主角7)所依赖和使用。
3,caffe2_pybind11_state.cpython-37m-x86_64-linux-gnu.so
第三个主角,从名字就能看出来这是caffe2的python绑定,这个库编译的时候会链接
pybind_state.cc.o
pybind_state_dlpack.cc.o
pybind_state_nomni.cc.o
pybind_state_registry.cc.o
pybind_state_int8.cc.o
pybind_state_ideep.cc.o
libcaffe2.so (主角1)
libprotobuf.a
libc10.so
libdl.so
libmkldnn.a
可以看到,主角3链接的就是一些python接口代码和libcaffe2.so(主角1)。当你在python语言中import caffe2的时候,caffe2会进行下面的import尝试:
try:
from caffe2.python.caffe2_pybind11_state_gpu import * # noqa
if num_cuda_devices(): # noqa
has_gpu_support = True
except ImportError as gpu_e:
logging.info('Failed to import cuda module: {}'.format(gpu_e))
try:
RTLD_LAZY = 1
with extension_loader.DlopenGuard(RTLD_LAZY):
from caffe2.python.caffe2_pybind11_state_hip import * # noqa
if num_hip_devices():
has_hip_support = True
logging.info('This caffe2 python run has AMD GPU support!')
except ImportError as hip_e:
logging.info('Failed to import AMD hip module: {}'.format(hip_e))
logging.warning(
'This caffe2 python run does not have GPU support. '
'Will run in CPU only mode.')
try:
from caffe2.python.caffe2_pybind11_state import * # noqa
except ImportError as cpu_e:
logging.critical(
'Cannot load caffe2.python. Error: {0}'.format(str(cpu_e)))
sys.exit(1)
也就是先import caffe2_pybind11_state_gpu.cpython-37m-x86_64-linux-gnu.so(主角4),如果失败就import AMD GPU的实现(本文未提及,因为还没人用过),如果失败再import 当前的主角3。
4,caffe2_pybind11_state_gpu.cpython-37m-x86_64-linux-gnu.so
第四个主角就是主角3的cuda版本。它会链接以下文件:
pybind_state.cc.o
pybind_state_dlpack.cc.o
pybind_state_nomni.cc.o
pybind_state_registry.cc.o
pybind_state_int8.cc.o
pybind_state_ideep.cc.o
pybind_state_gpu.cc.o
libcaffe2.so
libcaffe2_gpu.so
libc10_cuda.so
libcudart_static.a
librt.so
libprotobuf.a
libc10.so
libdl.so
libmkldnn.a
libcufft.so
libcurand.so
libcudnn.so.7
libcublas.so
libcublas_device.a
可见同它的CPU版本(也就是主角3)相比的话,多链接了以下文件:
pybind_state_gpu.cc.o
libcaffe2_gpu.so
libc10_cuda.so
libcudart_static.a
librt.so
libcufft.so
libcurand.so
libcudnn.so.7
libcublas.so
libcublas_device.a
当你在python语言中import caffe2的时候,Caffe2会先import 当前主角4,如果失败就import AMD GPU的实现(本文未提及,因为还没人用过),如果失败再import caffe2_pybind11_state.cpython-37m-x86_64-linux-gnu.so(主角3)。
5,libtorch.so;
第5个主角就是那个PyTorch之所以是PyTorch的库。这个库在编译的时候会链接以下文件(其中.o文件都来自torch/csrc目录):
anomaly_mode.cpp.o
engine.cpp.o
function.cpp.o
accumulate_grad.cpp.o
basic_ops.cpp.o
tensor.cpp.o
utils.cpp.o
Functions.cpp.o
VariableType_0.cpp.o
VariableType_1.cpp.o
VariableType_2.cpp.o
VariableType_3.cpp.o
VariableType_4.cpp.o
grad_mode.cpp.o
input_buffer.cpp.o
profiler.cpp.o
saved_variable.cpp.o
variable.cpp.o
VariableTypeManual.cpp.o
autodiff.cpp.o
attributes.cpp.o
export.cpp.o
register_aten_ops_0.cpp.o
register_aten_ops_1.cpp.o
register_aten_ops_2.cpp.o
graph_executor.cpp.o
import_method.cpp.o
import.cpp.o
interpreter.cpp.o
constants.cpp.o
node_hashing.cpp.o
ir.cpp.o
operator.cpp.o
caffe2_operator.cpp.o
register_c10_ops.cpp.o
symbolic_script.cpp.o
alias_analysis.cpp.o
batch_mm.cpp.o
canonicalize.cpp.o
constant_propagation.cpp.o
constant_pooling.cpp.o
common_subexpression_elimination.cpp.o
create_autodiff_subgraphs.cpp.o
inline_autodiff_subgraphs.cpp.o
dead_code_elimination.cpp.o
canonicalize_ops.cpp.o
erase_number_types.cpp.o
inline_fork_wait.cpp.o
graph_fuser.cpp.o
inplace_check.cpp.o
loop_unrolling.cpp.o
lower_grad_of.cpp.o
lower_tuples.cpp.o
peephole.cpp.o
remove_expands.cpp.o
remove_inplace_ops.cpp.o
shape_analysis.cpp.o
requires_grad_analysis.cpp.o
specialize_undef.cpp.o
python_print.cpp.o
subgraph_utils.cpp.o
check_alias_annotation.cpp.o
alias_tracker.cpp.o
interface.cpp.o
register_prim_ops.cpp.o
register_special_ops.cpp.o
scope.cpp.o
compiler.cpp.o
final_returns.cpp.o
schema_matching.cpp.o
type_parser.cpp.o
sugared_value.cpp.o
parser.cpp.o
builtin_functions.cpp.o
edit_distance.cpp.o
lexer.cpp.o
module.cpp.o
tracer.cpp.o
hooks_for_testing.cpp.o
tensor_flatten.cpp.o
variadic.cpp.o
kernel_cache.cpp.o
compiler.cpp.o
executor.cpp.o
codegen.cpp.o
fallback.cpp.o
no-gtest.cpp.o
register_caffe2_ops.cpp.o
dynamic_library_unix.cpp.o
fused_kernel.cpp.o
fused_kernel.cpp.o
profiler_cuda.cpp.o
comm.cpp.o
comm.cpp.o
cuda.cpp.o
mnist.cpp.o
random.cpp.o
sequential.cpp.o
stream.cpp.o
jit.cpp.o
init.cpp.o
module.cpp.o
batchnorm.cpp.o
conv.cpp.o
dropout.cpp.o
embedding.cpp.o
functional.cpp.o
linear.cpp.o
rnn.cpp.o
adagrad.cpp.o
adam.cpp.o
lbfgs.cpp.o
optimizer.cpp.o
rmsprop.cpp.o
serialize.cpp.o
sgd.cpp.o
input-archive.cpp.o
output-archive.cpp.o
libgomp.so
libnvToolsExt.so
libcudart_static.a
librt.so
libcaffe2.so
libcaffe2_gpu.so
libc10_cuda.so
libcudart_static.a
librt.so
libprotobuf.a
libc10.so
libdl.so
libmkldnn.a
libcufft.so
libcurand.so
libcudnn.so.7
libcublas.so
libcublas_device.a
这就是PyTorch之所以为PyTorch的那部分,它链接了主角1、2,并且被主角6所依赖。
6,libtorch_python.so
第6个主角就是PyTorch库的python绑定,这个库会链接以下的.o文件和库文件(其中.o文件来自torch/csrc目录):
DataLoader.cpp.o
Device.cpp.o
Dtype.cpp.o
DynamicTypes.cpp.o
Exceptions.cpp.o
TypeInfo.cpp.o
Generator.cpp.o
Layout.cpp.o
Module.cpp.o
PtrWrapper.cpp.o
Size.cpp.o
Storage.cpp.o
init.cpp.o
init.cpp.o
python_functions.cpp.o
python_nn_functions.cpp.o
python_torch_functions.cpp.o
python_variable_methods.cpp.o
init.cpp.o
python_anomaly_mode.cpp.o
python_cpp_function.cpp.o
python_engine.cpp.o
python_function.cpp.o
python_hook.cpp.o
python_legacy_variable.cpp.o
python_variable.cpp.o
python_variable_indexing.cpp.o
byte_order.cpp.o
BatchTensor.cpp.o
init.cpp.o
onnx.cpp.o
fixup_onnx_loop.cpp.o
prepare_division_for_onnx.cpp.o
peephole.cpp.o
to_batch.cpp.o
python_arg_flatten.cpp.o
python_interpreter.cpp.o
python_ir.cpp.o
python_tracer.cpp.o
init.cpp.o
lexer.cpp.o
module.cpp.o
python_tree_views.cpp.o
init.cpp.o
THNN.cpp.o
init.cpp.o
serialization.cpp.o
python_tensor.cpp.o
utils.cpp.o
cuda_lazy_init.cpp.o
invalid_arguments.cpp.o
object_ptr.cpp.o
python_arg_parser.cpp.o
tensor_apply.cpp.o
tensor_dtypes.cpp.o
tensor_flatten.cpp.o
tensor_layouts.cpp.o
tensor_list.cpp.o
tensor_new.cpp.o
tensor_numpy.cpp.o
tensor_types.cpp.o
tuple_parser.cpp.o
Module.cpp.o
Storage.cpp.o
Stream.cpp.o
Event.cpp.o
utils.cpp.o
comm.cpp.o
python_comm.cpp.o
serialization.cpp.o
THCUNN.cpp.o
Module.cpp.o
init.cpp.o
ddp.cpp.o
nccl.cpp.o
python_nccl.cpp.o
libtorch.so.1
libshm.so
libnvToolsExt.so
libTHD.a
libc10d.a
libcaffe2.so
libgomp.so
libnvToolsExt.so
libcaffe2_gpu.so
libgloo_cuda.a
libgloo.a
libcudart.so
libnccl.so.2.3.7
libprotobuf.a
libdl.so
libmkldnn.a
libc10_cuda.so
libc10.so
libcudart_static.a
librt.so
libcufft.so
libcurand.so
libcudnn.so.7
libcublas.so
libcublas_device.a
libmpi_cxx.so
libmpi.so
libtorch_python.so库会被_C.cpython-37m-x86_64-linux-gnu.so(主角7)所依赖,后者是一个接口/stub库,在import torch的时候被直接使用。
7,_C.cpython-37m-x86_64-linux-gnu.so
当在python语言中import torch的时候,主角7就会被调用。主角7链接了stub.o和以下的库:
stub.o
libshm.so
libtorch_python.so
libcaffe2_gpu.so
在import torch的时候,在__init__.py中会调用:
from torch._C import *
其中torch._C就是_C.cpython-37m-x86_64-linux-gnu.so。
Android编译的不同
首先编译流程的不同是由CMakeLists.txt里的配置以及命令行设置的参数不同导致的,除却build tools使用的是NDK里的交叉编译组件之外(一个是x86,一个是ARM),最大的不同就是,手机端编译的时候不会编译PyTorch的Torch。是的,只会编译PyTorch的caffe2。
总结
在本文,gemfield描述了PyTorch在服务端编译产生的文件,并简单介绍了这些文件之间的关系,并且解释了caffe2和PyTorch之间的联系。另外,结合专栏文章Gemfield:PyTorch的编译系统,读者就可以更加熟悉这个领域。