背景

大家都知道,在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的編譯系統,讀者就可以更加熟悉這個領域。


推薦閱讀:
相关文章