公司前一阵搞了几台 GPU 服务器,终于有“玩具”可以玩了~用的卡是最新的 P100 (好吧,真正最新的是 V100,不过还没铺货)。本着爱折腾的精神,自然就开始了折腾它们的征程(结果是我被折腾了。。。)
以前自己也搭建过 TensorFlow 的开发环境,所以一开始以为这次也不会难,结果…咳咳,还是要正视自己的水平的。因为服务器上装的是系统是 CentOS,以前自己捣鼓的时候用的是 Ubuntu,差别还是不少的,所以特此记录自己踩过的坑,也给其他人一些经验帮助。
这次折腾总共分为两个阶段:最初打算直接通过 pip 下载 TensorFlow 安装包安装,结果完全失败,不过在此期间摸清了许多限制条件,也为第二个阶段——通过编译的方式安装 TensorFlow 打下了基础。
此次安装过程均是在以下系统环境进行:
系统版本:CentOSrelease 6.8 (Final) 64 位
废话不多说,开正题~
追根溯源
首先,目标是安装 TensorFlow,所以查阅 TensorFlow 官网的源码安装教程。
发现需要使用 Bazel 来进行编译 TensorFlow 源码,进而生成 pip 安装包,通过 pip 来安装。
那下一步就是安装 Bazel。去 Bazel 官网查看,发现主要提供了 Ubuntu、MacOS、Windows 的安装方式,对于其他平台来说,需要通过源码编译的方式来安装。所需要的环境条件主要是 JDK8、Python、跟 C 相关的编译工具等。
CentOS6.8 自带的 gcc 版本为 4.4.7,不支持 C++11,而 TensorFlow 和 Bazel 的编译需要 C++11 的支持,所以要将 gcc 升级为支持 C++11 的版本,经过网上查找,gcc 版本跨度不是很大的情况下,可以使用低版本的来编译安装高版本的 gcc。gcc4.9.4 可以通过 4.4.7 编译安装。
CentOS6.8 自带的 Python 版本为 2.6.6,而 TensorFlow 和 Bazel 的编译所需要的 Python 版本最低为 2.7。
因为要使用 GPU,所以关于 TensorFlow 部分还需要一些额外的条件,在编译之前需要具备显卡驱动、CUDA Toolkit 以及 cuDNN。
所以整个安装流程如下:
Java8、gcc4.9.4、Python2.7、pip、Bazel、NVIDIADriver、CUDA、cuDNN、TensorFlow
安装过程
1、Java-8
去 Oracle 官网下载最新的 Java8 版本即可(我下载时为 8u144)
http:// www.oracle. com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
下载.tar.gz 格式的压缩包后,解压缩,将 bin 目录所在的路径添加到 PATH 当中即可
2、gcc-4.9.4
准备工作
先检查系统中是否安装了 g++,如果没有的话在编译安装 gcc 时会报错
make[1]:*** [stage1-bubble] Error 2
复制代码
使用 yum 安装 g++
下载 gcc 源码
http:// ftp.gnu.org/gnu/gcc/gcc-4.9.4/gcc-4.9.4.tar.bz2
解压
tar-jvf gcc-4.9.4.tar.bz2
复制代码
进入目录,接下来的操作都在这个目录下进行
下载所需要的依赖库
正常只需要执行
./contrib/download_prerequisites
复制代码
但是因为服务器无法访问外网(后来就算可以访问了,但是连接依旧有限,比如这步自动下载一些依赖包的操作,就无法正常进行),所以我们采用先把所需要的依赖包下载下来后,放到所需要的目录(最后发现是 ftp 服务器无法请求= =)
查看下载操作的脚本
vimcontrib/download_prerequisites
复制代码
可以发现脚本通过 wget 下载了五个依赖包,分别是
mpfr-2.4.2
gmp-4.3.2
mpc-0.8.1
isl-0.12.2
cloog-0.18.1
也可以去 ftp:// gcc.gnu.org/pub/gcc/infrastructure/ 下载
下载完成后将它们放到 gcc-4.9.4 目录下即可,然后再执行
./contrib/download_prerequisites
配置编译安装
官方建议新建一个目录用于编译,所以
配置
../gcc-4.9.4/configure--prefix=/opt/gcc-4.9.4/ --enable-checking=release --enable-languages=c,c++--disable-multilib
复制代码
关于具体的参数设置可以参照 https: //gcc.gnu.org/install/configure.html
编译
-j4 指的是使用四个线程,服务器的 CPU 大约使用了不到 20 分钟。不过有人不建议使用多线程编译,说是可能会失败。
安装
添加路径,打开 .bashrc,添加如下内容
exportPATH=/opt/gcc-4.9.4/bin:$PATH
export CXX=/opt/gcc-4.9.4/bin/c++
export CC=/opt/gcc-4.9.4/bin/gcc
export LDFLAGS="-L/opt/gcc-4.9.4/lib -L/opt/gcc-4.9.4/lib64"
export CXXFLAGS="-L/opt/gcc-4.9.4/lib -L/opt/gcc-4.9.4/lib64"
export C_INCLUDE_PATH=/opt/gcc-4.9.4/include
export CXX_INCLUDE_PATH=$C_INCLUDE_PATH
export LD_RUN_PATH=/opt/gcc-4.9.4/lib/:/opt/gcc-4.9.4/lib64/
exportLD_LIBRARY_PATH=/opt/gcc-4.9.4/lib/:/opt/gcc-4.9.4/lib64/:$LD_LIBRARY_PATH
复制代码
一般添加第一行就可以,但是在 Bazel 的编译过程中会出现一些错误,加上后面的部分可以解决这些错误
最后在 source 一下 .bashrc 就可以了~
通过 gcc -v 和 g++ -v 可以看到版本已经变成了 4.9.4:gccversion 4.9.4 (GCC)
3、Python-2.7
Python 也需要通过编译源码的方式安装,使用刚才升级过的 gcc 来编译。之所以这样做是因为可以在后续操作中避免一些错误。
准备工作
安装一些系统依赖
yumgroupinstall -y 'development tools'
yum install -y zlib-devel bzip2-devel openssl-devel xz-libs wget
复制代码
下载 Python2.7 源码
https:// www.python. org/ftp/python/2.7.14/Python-2.7.14.tar.xz
解压
xz-d Python-2.7.8.tar.xz
tar -xvf Python-2.7.8.tar
复制代码
进入目录,接下来的操作都在这个目录下进行
配置编译安装
配置
./configure--prefix=/usr/local
复制代码
编译,用时几分钟
make
安装
使新版本生效
两种方式:加入 PATH、软连接
加入 PATH
exportPATH=/usr/local/bin:$PATH
复制代码
软连接
mv/usr/bin/python /usr/bin/python2.6
ln -s /usr/local/bin/python2.7 /usr/bin/python
复制代码
此时通过 python-V 可以查看 Python 版本已经为 2.7
解决 yum 失效问题
因为 yum 依赖的是原来 Python2.6 版本,所以做以下修改
将第一行 #!/usr/bin/python 改为 #!/usr/bin/python2.6
更新 setuptools 和 pip
分别下载 setuptools (https:// pypi.python.org/pypi/setuptools)和 pip (https:// pypi.python.org/pypi/pip)的安装包
解压后进到相应的目录,执行
此时通过 pip -V 可以查看 pip 的版本,已经是对应于 Python2.7 的了
顺手更新 pip 源,毕竟还是国内的快
然后写入如下内容并保存
[global]
trusted-host = mirrors.aliyun.com
index-url = http:// mirrors.aliyun. com/pypi/simple
复制代码
4、Bazel-0.5.3
接下来是编译安装 Bazel,主要参照 官方教程 (https:// docs.bazel.build/versions/master/install-compile-source.html)即可。
下载 Bazel 源码
去 GitHub 上下载 bazel--dist.zip 格式的源码,下载最新版或者特定版本均可。此处下载的是 0.5.3 版本(最新的为 0.5.4)
https:// github. com/bazelbuild/bazel/releases/download/0.5.3/bazel-0.5.3-dist.zip
解压,最好指定目录,因为 Bazel 所有文件都放在根目录
unzipbazel-0.5.3-dist.zip -d bazel-0.5.3
复制代码
进入目录,接下来的操作都在这个目录下进行
编译
运行
编译出的结果放在 output/bazel 当中,将其复制到 PATH 路径下即可
cpoutput/bazel /usr/local/bin
复制代码
或者
可以执行 bazel 验证是否安装成功
一些错误
ERROR:/root/gg/bazel/third_party/BUILD:106:1: Extracting interface //third_party:apache_commons_collectionsfailed (Exit 1): ijar failed: error executing command
复制代码
参照这个 issue (https://github.com/bazelbuild/bazel/issues/760)解决:
升级 gcc 后,需要添加 CXX, CC, LDFLAGS and CXXFLAGS 等环境变量
Executing genrule//src:embedded_tools failed (Exit 1): bash failed: error executing command
复制代码
参照这两个 issue(https://github.com/bazelbuild/bazel/issues/2738)、issue(https://github.com/bazelbuild/bazel/issues/673) 解决:
mkdir/root/tmp
export TMPDIR=/root/tmp
AttributeError: ZipFileinstance has no attribute '__exit__'
复制代码
当初升级 gcc 后直接编译 Bazel 报的错误,经查找是 Python 版本的问题(当时 Python 还是 2.6),使用 gcc4.9.4 编译安装 Python2.7 后问题解决。
5、NVIDIADriver
准备工作
安装一些系统依赖
下载驱动程序
去 http:// www.nvidia.cn/Download/index.aspx 这里寻找对应的显卡驱动即可,这里选择:
这里下载的文件名是:NVIDIA-Linux-x86_64-384.66.run
安装
添加可执行权限,安装
chmod+x NVIDIA-Linux-x86_64-384.66.run
./NVIDIA-Linux-x86_64-384.66.run
复制代码
进入安装界面后一路同意就可以,是否安装 32 位的库,我选择的同意。对于最后出现的 warning 我选择了忽略。
安装完成后需要重启服务器,驱动才会生效
验证
执行
后可以看到一些显卡信息,包括驱动版本、风扇转速、温度、显卡型号、已用功率/总功率、已用显存/总显存、GPU 计算力利用率等
一些错误
ERROR: Unable to find thekernel source tree for the currently running kernel. Please make sure you haveinstalled the kernel source files for your kernel and that they are properlyconfigured; on Red Hat Linux systems, for example, be sure you have the'kernel-source' or 'kernel-devel' RPM installed. If you know the correct kernelsource files are installed, you may specify the kernel source path with the'--kernel-source-path' command line option.
复制代码
如果没做最一开始的准备工作,可能会出现这个错误。
WARNING: nvidia-installerwas forced to guess the X library path '/usr/lib64' and X module path'/usr/lib64/xorg/modules'; these paths were not queryable from the system. If Xfails to find the NVIDIA X driver module, please install the pkg-config utilityand the X.Org SDK/development package for your distribution and reinstall thedriver.
复制代码
安装完成后的 warning,目前没发现有什么问题。
6、CUDA-8.0
CUDA 是英伟达开发的一款针对于使用 GPU 来加速计算的工具包,所以机器学习或者深度学习想要使用 GPU 来加速计算的话,就必须使用 CUDA。
下载
去 https:// developer.nvidia.com/cuda-downloads 这里寻找对应平台的文件下载即可。这里有一份详尽官方的 说明文档(http:// docs.nvidia.com/cuda/cuda-installation-guide-linux/)。
目前 CUDA 已经出了 9.0,但是 TenforFlow 官方推荐的是 8.0,所以我们安装的还是 8.0 版本。
这里一些选项的选择为:
下面会显示两个安装文件,一个 Base Installer ,一个 Patch。安装完 Base 后再安装 Patch 即可。
安装
添加可执行权限,安装
chmod+x cuda_8.0.61_375.26_linux.run
./cuda_8.0.61_375.26_linux.run
复制代码
接下来会有一系列提示需要确认,其中在询问是否要安装显卡驱动时选 n ,因为我们之前已经安装了最新版本的驱动。其他的一路同意即可
安装完成后会出现以下内容:
Installing the CUDA Toolkit in/usr/local/cuda-8.0 ...
Missing recommended library: libGLU.so
Missing recommended library: libX11.so
Missing recommended library: libXi.so
Missing recommended library: libXmu.so
Installingthe CUDA Samples in /home/gaixindong ...
Copying samples to /home/gaixindong/NVIDIA_CUDA-8.0_Samples now...
Finished copying samples.
=Summary =
Driver:Not Selected
Toolkit: Installed in /usr/local/cuda-8.0
Samples: Installed in /home/gaixindong, but missing recommended libraries
Pleasemake sure that
- PATH includes /usr/local/cuda-8.0/bin
- LD_LIBRARY_PATH includes /usr/local/cuda-8.0/lib64, or, add/usr/local/cuda-8.0/lib64 to /etc ld.so.conf and run ldconfig as root
Touninstall the CUDA Toolkit, run the uninstall script in/usr/local/cuda-8.0/bin
Pleasesee CUDAInstallationGuide_Linux.pdf in /usr/local/cuda-8.0/doc/pdf fordetailed information on setting up CUDA.
***WARNING:Incomplete installation! This installation did not install the CUDA Driver. Adriver of version at least 361.00 is required for CUDA 8.0 functionality towork.
To install the driver using this installer, run the following command,replacing with the name of this run file:
sudo .run -silent -driver
Logfile is /tmp/cudainstall132117.log
复制代码
根据以上提示内容发现,缺少了一些推荐的库,但是这些库可能是跟运行它提供的 samples 有关,所以现在并没有安装。日后如果有问题,可以依照这些提示去安装一下。
并且 samples 也可以选择不安装,因为在 cuda 的目录下有一份 samples。
最后再打一下补丁即可
chmod+x cuda_8.0.61.2_linux.run
./cuda_8.0.61.2_linux.run
复制代码
测试
进入 samples 目录,选择第一个例子进行测试
cd/usr/local/cuda/samples/1_Utilities/deviceQuery
make
复制代码
编译完成后执行
会看到一系列显卡参数信息,只要最后显示 Result = PASS 即说明 CUDA 安装成功
7、cuDNN-6.0
在 CUDA 之外,还有个库叫做 cuDNN(CUDA Deep Neural Network library),是专门给深度神经网络针对 GPU 调优的,也是 TensorFlow 官方要求必须安装的。
下载
去 https://developer.nvidia.com/rdp/cudnn-download 选择对应的版本下载即可。不过需要先注册开发者账号后才可以下载。
目前 cuDNN 最新版本是 7.0,因为担心兼容性问题所以没有选择最新版本。因为之前安装的 CUDA 是 8.0,所以我们选择
DownloadcuDNN v6.0 (April 27, 2017), for CUDA 8.0
cuDNNv6.0 Library for Linux
安装
执行解压操作
tar-zxvf cudnn-8.0-linux-x64-v6.0-tgz
解压后的文件夹是 cuda。执行以下操作把文件复制到相应的位置
cpcuda/include/cudnn.h /usr/local/cuda/include/
cp cuda/lib64/libcudnn* /usr/local/cuda/lib64/
chmod a+r /usr/local/cuda/include/cudnn.h
chmod a+r /usr/local/cuda/lib64/libcudnn*
复制代码
8、TensorFlow-1.3.0
终于到最后一步了,整个过程主要参照 官方教程 (https://www.tensorflow.org/install/install_sources)。
准备工作
安装一些系统依赖
yuminstall python-devel
pip install numpy wheel
复制代码
下载 TensorFlow 源码
最一开始是直接通过 pip 下载官方编译好的安装包进行安装,但因为 CentOS 的原因,并不能通过这种方式安装(在网上找到的 CentOS 安装 TensorFlow 大部分都是通过编译源码来安装的)。所以这也是导致这次安装如此繁琐的原因。
目前 TensorFlow 的最新版本为 1.3.0,源码可以通过 gitclone 下来,也可以直接去 releases 界面下载
gitclone https:// github.com/tensorflow/tensorflow
git checkout r1.3
TensorFlow 1.3.0 (https:// github.com/tensorflow/tensorflow/archive/v1.3.0.tar.gz)
配置
进入 TensorFlow 源码根目录后,执行
接下来会有一系列提示需要确认,其中
Do youwish to use jemalloc as the malloc implementation? [Y/n] 选择 n,因为选择y后编译不通过
Do youwish to build TensorFlow with CUDA support? [y/N] 选择 y,因为我们要使用GPU
复制代码
其他的默认就可以(大写字母是默认选择)
以下是具体的配置选项
.........
You have bazel 0.5.3- installed.
Please specify the location of python. [Default is /usr/local/bin/python]:
Found possible Python library paths:
/usr/local/lib/python2.7/site-packages
Please input the desired Python library path to use. Default is[/usr/local/lib/python2.7/site-packages]
Using python library path:/usr/local/lib/python2.7/site-packages
Do you wish to build TensorFlow with MKL support? [y/N]
No MKL support will be enabled for TensorFlow
Please specify optimization flags to use during compilation when bazel option"--config=opt" is specified [Default is -march=native]:
Do you wish to use jemalloc as the malloc implementation? [Y/n] n
jemalloc disabled
Do you wish to build TensorFlow with Google Cloud Platform support? [y/N]
No Google Cloud Platform support will be enabled for TensorFlow
Do you wish to build TensorFlow with Hadoop File System support? [y/N]
No Hadoop File System support will be enabled for TensorFlow
Do you wish to build TensorFlow with the XLA just-in-time compiler(experimental)? [y/N]
No XLA support will be enabled for TensorFlow
Do you wish to build TensorFlow with VERBS support? [y/N]
No VERBS support will be enabled for TensorFlow
Do you wish to build TensorFlow with OpenCL support? [y/N]
No OpenCL support will be enabled for TensorFlow
Do you wish to build TensorFlow with CUDA support? [y/N] y
CUDA support will be enabled for TensorFlow
Do you want to use clang as CUDA compiler? [y/N]
nvcc will be used as CUDA compiler
Please specify the CUDA SDK version you want to use, e.g. 7.0. [Leave empty todefault to CUDA 8.0]:
Please specify the location where CUDA 8.0 toolkit is installed. Refer toREADME.md for more details. [Default is /usr/local/cuda]:
Please specify which gcc should be used by nvcc as the host compiler. [Defaultis /opt/gcc-4.9.4/bin/gcc]:
Please specify the cuDNN version you want to use. [Leave empty to default tocuDNN 6.0]:
Please specify the location where cuDNN 6 library is installed. Refer toREADME.md for more details. [Default is /usr/local/cuda]:
Please specify a list of comma-separated Cuda compute capabilities you want tobuild with.
You can find the compute capability of your device at: https://developer.nvidia.com/cuda-gpus.
Please note that each additional compute capability significantly increasesyour build time and binary size.
[Default is: "6.0"]:
Do you wish to build TensorFlow with MPI support? [y/N]
MPI support will not be enabled for TensorFlow
Configuration finished
复制代码
编译打包安装
接下来进行编译操作
bazelbuild --config=opt--config=cuda//tensorflow/tools/pip_package:build_pip_package
复制代码
如果顺利的话就不会出现什么 ERROR 提示,整个编译过程大约十分钟。如果出现错误可以加入 --verbose_failures 参数,会提供更丰富的出错信息。
编译完成后,接下来就是打包成 .whl 文件供 pip 安装使用,文件会放到/tmp/tensorflow_pkg 目录下
bazel-bin/tensorflow/tools/pip_package/build_pip_package/tmp/tensorflow_pkg
复制代码
安装
pipinstall /tmp/tensorflow_pkg/tensorflow-1.3.0-py2-none-any.whl
复制代码
安装过程会自动联网下载一些依赖的包,如果无法连外网的话,那就需要自己下载所需要的包,然后上传到服务器后用 pip 离线安装
可以使用 官方 pip 源 或者 阿里 pip 源 来搜索安装,推荐后者,国内快
依赖的包有(可能不全):
· backports.weakref-1.0
· bleach-1.5.0
· funcsigs-1.0.2
· html5lib-0.9999999
· Markdown-2.6.9
· mock-2.0.0
· numpy-1.13.1
· pbr-3.1.1
· protobuf-3.4.0
· six-1.11.0
· tensorflow_tensorboard-0.1.6
· webencodings-0.5.1
· Werkzeug-0.12.2
· wheel-0.30.0
验证
进入 python,进行 importtensorflow,没有错误那就代表大功告成了!
一些错误
整个编译过程还是比较坎坷的
1、第一个错误
ERROR: /root/gg/tensorflow/tensorflow/tensorflow/tools/pip_package/BUILD:100:1:no such package '@boringssl//': Traceback (most recent call last):
File "/root/gg/tensorflow/tensorflow/tensorflow/workspace.bzl", line116
_apply_patch(repo_ctx, repo_ctx.attr.patch_file)
File "/root/gg/tensorflow/tensorflow/tensorflow/workspace.bzl", line107, in _apply_patch
_execute_and_check_ret_code(repo_ctx, cmd)
File "/root/gg/tensorflow/tensorflow/tensorflow/workspace.bzl", line91, in _execute_and_check_ret_code
fail("Non-zero return code({1}) when ...))
Non-zero return code(256) when executing 'patch -p1 -d/root/.cache/bazel/_bazel_root/9abfd3cc56b23f8500d978612da34f89/external/boringssl-i/root/gg/tensorflow/tensorflow/third_party/boringssl/add_boringssl_s390x.patch':
Stdout:
Stderr: java.io.IOException: Cannot run program "patch" (in directory"/root/.cache/bazel/_bazel_root/9abfd3cc56b23f8500d978612da34f89/external/boringssl"):error=2, No such file or directory and referenced by '//tensorflow/tools/pip_package:licenses'.
ERROR: Analysis of target '//tensorflow/tools/pip_package:build_pip_package'failed; build aborted.
INFO: Elapsed time: 1.401s
复制代码
这个错误出现的时候 @boringssl 和 @protobuf 是交替出现,no such package '@boringssl。一开始以为是这两个包没有下载成功,因为在 info 信息中有连接下载链接失败的提示,并且错误信息中是说没有找到对应的包,所以去 tensorflow/workspace.bzl 中查找对应的下载链接,下载到具体的包后在服务器上做了个微型本地服务器,将 workspace.bzl 中的下载链接改成本地的下载链接。然而这个错误继续出现,后来经过白银指点,发现是 Cannot run program “patch” 这里的问题,遂去 yum install patch 后该错误不再出现。原来的思路一直局限在下载不成功上,没有仔细观察后面的错误信息。
2、第二个错误
C++ compilation of rule'@nanopb_git//: nanopb' failed: crosstool_wrapper_driver_is_not_gcc failed:error executing command
复制代码
后续内容还有一堆,其中 @nanopb_git 会变成其他包的名字,所以查找的重点就放在了后面
crosstool_wrapper_driver_is_not_gccfailed: error executing command
复制代码
3、经过了一顿 google,查阅了无数 github 上的 issus,都没有发现类似的问题(有这部分相同的报错,但是后续错误内容不同),问题没有解决,我决定先编译 TensorFlow 提供的 example,看看小范围编译是否存在问题
bazelbuild --config=opt --config=cuda //tensorflow/cc:tutorials_example_trainer
复制代码
此时报错:
error: 'MADV_NOHUGEPAGE'undeclared
复制代码
参照这个 issus (https://github.com/tensorflow/tensorflow/issues/7572) 解决:
在进行 configure 配置时,将 Do you wish to use jemalloc as the mallocimplementation? [Y/n] 选择 n
4、在对 example 编译成功后,又继续回来尝试编译源码,但是依旧出问题。不过,有时候命运就是捉弄人,尝到了‘踏破铁鞋无觅处,得来全不费功夫’的滋味。
看到白银在看 这篇文章 (http://www.jianshu.com/p/fdb7b54b616e),突然发现在文章的前部有这样一句话:
“CentOS6 上 glibc 最多到 2.12,强行使用高版本的 glibc 会导致程序意外崩溃。”
这篇文章之前看过,但是注意力从来没有留意这句话。突然想到会不会真的是 glibc 的问题。因为在最初的尝试过程中因为某些报错需要安装 glibc2.14 版本,而系统里自带的版本只到 2.12,所以就去下载编译了 glibc2.14 版本,并且加入 LD_LIBRARY_PATH 中。
那么在 .bashrc 中将 glibc 添加到 LD_LIBRARY_PATH 中这一行注释掉,结果竟然顺利编译成功!真是惊喜啊!
感谢白银同学,最后两个关键问题都是在他的帮助下解决的~
写在最后
当初所有的操作都是在 root 权限下操作的,所以所有的环境变量都写在了/root/.bashrc 当中,但是这个样子的话非 root 用户就没法使用安装好的 TensorFlow 了。后来把安装过程中添加的环境变量都添加到了/etc/profile.d/path.sh 当中,这样所有的用户就都可以使用了。
整个折腾过程就这样完成了,虽然折腾很闹心,但是折腾完还是很开心的,也感谢在此过程中提供帮助的同学们~最后希望这篇文章能帮助其他的小伙伴少走一些弯路,早日走上人生巅峰^_^
本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。
原文链接:
https://mp.weixin.qq.com/s/D0H_iK6cKKzCWrUkYZJ5Uw
评论