第 2 章 TensorFlow环境准备

第 2 章 TensorFlow环境准备

对TensorFlow有了基本认识之后,读者也许会迫不及待地想在自己的计算机上部署一套TensorFlow开发和运行环境。安装TensorFlow并对其软件整体结构与关键组件有所了解是学习TensorFlow基础用法和算法模型开发方法的前提。本章首先向读者介绍在Linux操作系统上安装TensorFlow的多种方法,然后介绍TensorFlow的主要软件依赖项,以及TensorFlow的源代码和安装目录组织结构。在指导读者构建TensorFlow开发和运行环境的同时,我们构建了一幅学习TensorFlow的宏观视图。

2.1 安装

TensorFlow提供多种语言的API,具体包括Python、Java、Go、C和C++ 等。本节重点介绍TensorFlow Python发布包的安装和开发环境搭建方法。选择Python的原因有二:其一,Python是目前机器学习和深度学习领域使用最为广泛和最受欢迎的程序设计语言;其二,Python语言本身简单易学,适合新手快速入门。TensorFlow团队为Linux、macOS和Windows这3大主流操作系统分别适配了CPU和GPU版本的Python发布包(从TensorFlow 1.2开始,TensorFlow团队不再为macOS提供GPU版本的官方支持)。本节将详细介绍TensorFlow的多种安装方法,具体包括使用Anaconda、原生pip和virtualenv安装,以及使用Docker容器安装和从源代码编译安装。本节以Linux发行版——Ubuntu为例进行说明和展示。

2.1.1 TensorFlow安装概述

TensorFlow Python发布包支持使用多种Python包管理工具进行安装。常见的Python包管理工具有Anaconda、原生pip和virtualenv。同时,用户也可以自己下载TensorFlow的源代码进行编译,然后得到对应的whl包。whl是Python软件发布的新格式,用于取代曾经风靡一时的egg包格式。除此以外,我们还可以使用Docker运行包含TensorFlow二进制包的容器,实现在容器中使用TensorFlow。到目前为止,TensorFlow官方主要提供了以上5种安装方法。根据它们各自在Linux、macOS和Windows上的支持情况,我们整理出如表2-1所示的TensorFlow Python发布包的安装适配情况。为了简化说明,后面的所有TensorFlow发布包均特指TensorFlow Python发布包。

表2-1 TensorFlow安装适配表

 

Anaconda

原生pip

virtualenv

Docker

源代码

Linux

支持

支持

支持

支持

支持

macOS

支持

支持

支持

支持

支持

Windows

支持

支持

支持

不支持

实验性支持

在加速器硬件方面,TensorFlow官方目前主要支持NVIDIA公司的GPU设备(后面的GPU特指NVIDIA GPU)。如果用户想要使用GPU版的TensorFlow,那么必须安装相应的显卡驱动程序和配套软件,具体包括统一计算工具包CUDA、深度神经网络GPU加速库cuDNN(CUDA® Deep Neural Network library)和CUDA分析工具接口——libcupti-dev开发库。其中,CUDA囊括了NVIDIA显卡驱动程序和基于GPU的通用计算程序,我们将在2.2.4节中详细介绍它。cuDNN是NVIDIA为深度学习设计的GPU加速库,它对常见的深度神经网络都做了针对性的优化。随着GPU技术的不断发展,CUDA和cuDNN也在不断更新,表2-2给出了TensorFlow各版本对CUDA和cuDNN的官方支持情况。

表2-2 TensorFlow各版本对CUDA和cuDNN的支持情况(最低版本要求)

TensorFlow

CUDA

cuDNN

0.8~0.11

7.0

4.0

0.12~1.2

8.0

5.1

1.3~1.4

8.0

6.0

因为CUDA的开发环境对系统软硬件存在较强依赖和约束,所以读者应该检查自己的机器是否满足以下3项指标:

  • GPU是否支持CUDA;
  • 系统的编译器和工具链是否支持CUDA;
  • 系统内核是否满足特定CUDA版本的安装要求。

根据NVIDIA官网的介绍,我们整理出x86_64架构下,CUDA 8.0当前支持的Linux发行版以及对应的内核版本和编译器,如表2-3所示。

表2-3 CUDA 8.0支持的Linux发行版

Linux发行版

系统内核版本

GCC

glibc

ICC

PGI

Clang

RHEL 7.x

3.10

4.8.2

2.17

15/16

16.3+

3.8+

RHEL 6.x

2.6.32

4.4.7

2.12

15/16

16.3+

3.8+

CentOS 7.x

3.10

4.8.2

2.17

15/16

16.3+

3.8+

CentOS 6.x

2.6.32

4.4.7

2.12

15/16

16.3+

3.8+

Fedora 23

4.2.3

5.3.1

2.22

15/16

16.3+

3.8+

openSUSE 13.2

3.16.6

4.8.3

2.19

15/16

16.3+

3.8+

SLES 12

3.12.28

4.8.6

2.19

15/16

16.3+

3.8+

SLES 11 SP4

3.0.101

4.3.4

2.11

15/16

16.3+

3.8+

Ubuntu 16.04

4.4.0

5.3.1

2.23

15/16

16.3+

3.8+

Ubuntu 14.04

3.13

4.8.2

2.19

15/16

16.3+

3.8+

关于CUDA 8.0和cuDNN 5.1的安装和配置方法,读者可以参考NVIDIA官网的详细步骤说明(http://docs.nvidia.com/cuda/cuda-installation-guide-linux),这里不再赘述。下面我们介绍一下TensorFlow开发环境的安装方法。

2.1.2 使用Anaconda安装

Anaconda是目前很受欢迎的Python数据科学平台,它内置超过1000个Python数据科学软件包,并同时提供社区版和官方发布版的软件包。作为Python软件包管理工具,它也同时支持Python 2.7和Python 3.6的软件包管理。Anaconda在主流的操作系统上都能正常运行,如Linux、macOS和Windows。读者可以在Anaconda官网上(https://www.continuum.io/downloads)查看和下载相应的版本。

在使用Python做开发时,时常出现依赖软件包版本冲突的情况,而Anaconda提供的虚拟开发环境有效解决了该问题。它支持为项目创建特定的虚拟环境,允许每个虚拟环境拥有一套独立的Python软件包。于是,我们就可以在不同的虚拟环境中开发可能存在冲突的Python项目。

使用Anaconda安装TensorFlow主要分为以下4个步骤。

(1) 下载并安装Anaconda。安装结束前,脚本会询问我们是否将Anaconda路径添加到 $PATH环境变量,并将更新的 $PATH保存到 /home//.bashrc文件中。这里建议选择Yes。这样一来,再次打开终端时,shell程序便能自动加载 /home//.bashrc文件中更新的 $PATH。此外,用户亦可使用source命令,在当前shell环境中引入该环境变量:

$ bash Anaconda2-4.4.0.1-Linux-x86_64.sh
$ source ~/.bashrc

(2) 使用Anaconda的命令行工具conda创建虚拟开发环境。其中-n参数表示环境名称,我们将其命名为tensorflow。同时,我们指定虚拟环境使用的Python版本为2.7:

$ conda create -n tensorflow python=2.7

(3) 使用source activate命令激活并进入Anaconda创建的虚拟开发环境:

$ source activate tensorflow
(tensorflow)$  # 进入tensorflow虚拟开发环境

(4) 在虚拟开发环境中安装TensorFlow,这里以CPU版的TensorFlow 1.2.1为例:

(tensorflow)$ pip install \
https://storage.googleapis.com/tensorflow/linux/cpu/ \
tensorflow-1.2.1-cp34-cp34m-linux_x86_64.whl

至此,TensorFlow安装完成。关于如何验证TensorFlow是否安装成功,我们将在2.1.7节中统一介绍。下面我们继续介绍如何使用原生pip安装TensorFlow。

2.1.3 使用原生pip安装

原生pip是Python社区推荐的软件包安装和维护工具。针对Python 2.x 和Python 3.x 版本,该工具分别提供pippip3命令。PyPA(Python Packaging Authority)工作组维护着pip的软件包下载源。在开始使用原生pip安装TensorFlow前,我们应该首先检查本地安装的Python版本:

$ python -v
Python 2.7.12

这里强烈推荐使用pip 8.1以上版本安装TensorFlow。更老版本的pip存在一些bug,有可能影响TensorFlow的安装和使用。下面我们以Ubuntu系统为例,说明Python 2.x 和Python 3.x 升级pip版本的方法:

$ sudo apt-get install python-pip python-dev    # 用于Python 2.x
$ sudo apt-get install python3-pip python3-dev  # 用于Python 3.x

因为TensorFlow已经加入了PyPA维护的软件包集合,所以可以使用原生pip直接安装TensorFlow。针对不同Python版本和计算设备的安装命令如下所示,用户可以依据自己的环境选择其一:

$ pip install tensorflow      # 用于Python 2.x,CPU版(不支持GPU)
$ pip3 install tensorflow     # 用于Python 3.x,CPU版(不支持GPU)
$ pip install tensorflow-gpu  # 用于Python 2.x,GPU版(支持CPU)
$ pip3 install tensorflow-gpu # 用于Python 3.x,GPU版(支持CPU)

命令执行后,TensorFlow软件包将被安装到操作系统全局或者当前用户本地的Python库目录(这取决于使用root用户还是一般用户安装)。事实上,原生pip安装方式与Anaconda安装方式并不冲突,二者可以配合使用。因此,用户也可以在Anaconda虚拟环境中执行上述命令,以便将TensorFlow安装到虚拟环境。

2.1.4 使用virtualenv安装

virtualenv与Anaconda类似,也是一种Python软件包管理工具,同样支持创建虚拟开发环境。使用virtualenv安装TensorFlow的步骤简述如下。

(1) 安装pip和virtualenv:

$ sudo apt-get install python-pip python-dev python-virtualenv # 用于Python 2.x
$ sudo apt-get install python3-pip python3-dev python-virtualenv # 用于Python 3.x

(2) 使用virtualenv创建Python虚拟开发环境。virtualenv命令的最后一个参数为虚拟环境的目录名:

$ virtualenv --system-site-packages ~/tensorflow # 用于Python 2.x
$ virtualenv --system-site-packages -p python3 ~/tensorflow # 用于Python 3.x

(3) 激活并进入虚拟开发环境:

$ source ~/tensorflow/bin/activate
(tensorflow)$  # 进入tensorflow虚拟开发环境

(4) 在虚拟开发环境中安装TensorFlow:

(tensorflow)$ pip install tensorflow      # 用于Python 2.x,CPU版(不支持GPU)
(tensorflow)$ pip3 install tensorflow     # 用于Python 3.x,CPU版(不支持GPU)
(tensorflow)$ pip install tensorflow-gpu  # 用于Python 2.x,GPU版(支持CPU)
(tensorflow)$ pip3 install tensorflow-gpu # 用于Python 3.x,GPU版(支持CPU)

2.1.5 使用Docker安装

Docker是目前最为流行的Linux容器技术,它分为社区版(Community Edition,CE)和企业版(Enterprise Edition,EE)。前者主要为个人开发者和小型研发团队提供设计容器应用的基本开发环境,后者主要为企业开发者提供更加安全和可靠的商用生产环境。Docker官网的文档(https://docs.docker.com/engine/installation/)介绍了在各种操作系统上安装Docker的详细步骤。现在,我们假设读者的机器上已经安装好Docker。下面的代码展示了使用Docker运行TensorFlow容器应用的命令格式和常用输入参数:

$ docker run -it -p hostPort:containerPort TensorFlowCPUImage

其中,hostPort:containerPort是可选参数。hostPort表示容器映射到宿主机的端口号,containerPort表示容器应用的端口号。如果读者想要在命令行中使用TensorFlow,那么无需输入此参数。如果读者想要在Jupyter Notebook中使用TensorFlow,那么需要将hostPortcontainerPort设置为相同的端口,如8888。TensorFlowCPUImage是指使用TensorFlow CPU版镜像。Google为TensorFlow CPU版创建了4种不同的镜像文件,并保存在Google的镜像仓库中,具体如下所示。

  • gcr.io/tensorflow/tensorflow:二进制镜像。
  • gcr.io/tensorflow/tensorflow:latest-devel:二进制和源代码镜像。
  • gcr.io/tensorflow/tensorflow:version:指定版本的二进制镜像。
  • gcr.io/tensorflow/tensorflow:version-devel:指定版本的二进制和源代码镜像。

我们使用TensorFlow CPU版二进制镜像,并在启动容器时设置相应的端口号。下面的命令成功运行后,会输出Jupyter Notebook服务运行的地址,本例中便是本地IP的8888端口对应的地址(http://localhost:8888):

$ docker run -it -p 8888:8888 gcr.io/tensorflow/tensorflow

如果想要使用GPU版本的TensorFlow容器应用,那么还需要安装NVIDIA开发的nvidia-docker(https://github.com/NVIDIA/nvidia-docker)工具。下面的代码展示了如何在Ubuntu上安装nvidia-docker:

$ wget -P /tmp \
https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.1/ \
nvidia-docker_1.0.1-1_amd64.deb
$ sudo dpkg -i /tmp/nvidia-docker*.deb && rm /tmp/nvidia-docker*.deb

# 安装完成后,测试nvidia-smi是否可用
$ nvidia-docker run --rm nvidia/cuda nvidia-smi

TensorFlow GPU版的镜像文件同样分为4种,如下所示。

  • gcr.io/tensorflow/tensorflow:latest-gpu:二进制镜像。
  • gcr.io/tensorflow/tensorflow:latest-devel-gpu:二进制和源代码镜像。
  • gcr.io/tensorflow/tensorflow:version-gpu:指定版本的二进制镜像。
  • gcr.io/tensorflow/tensorflow:version-devel-gpu:指定版本的二进制和源代码镜像。

启动TensorFlow GPU版容器应用的输入参数与CPU版类似,只是启动命令需要换为nvidia-docker,如下所示:

$ nvidia-docker run -it -p 8888:8888 gcr.io/tensorflow/tensorflow:latest-gpu

下面我们介绍如何使用源代码编译安装TensorFlow。

2.1.6 使用源代码编译安装

为了保证软件对操作系统和硬件平台的通用性,Google官方发布的TensorFlow whl包没有使用过多的编译优化选项,如XLA、AVX、SSE等。如果读者想要打开这些编译优化选项来提升TensorFlow的计算性能,那么必须使用源代码编译安装的方式。TensorFlow官方支持在Linux和macOS操作系统上进行源代码编译。目前,对于Windows,只是提供了实验性的源代码编译方案。

TensorFlow源代码编译的目标输出是TensorFlow whl包,构建工具是Google开源的一套软件构建工具——Bazel。在开始源代码编译前,用户应该确保开发环境上已经安装好Bazel以及TensorFlow依赖的第三方Python软件包。如果想要编译GPU版的TensorFlow whl软件包,还需要安装配套的NVIDIA CUDA和cuDNN。下面我们仍然以Ubuntu为例,介绍源代码编译TensorFlow的流程。

(1) 安装Bazel软件构建工具(在其他操作系统上安装Bazel的方法可参考其官方网站的文档):

# 1.安装Bazel依赖的JDK 8
$ sudo apt-get install openjdk-8-jdk
# 2.添加Bazel发布包URL到apt的下载源
$ echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | \
sudo tee /etc/apt/sources.list.d/bazel.list
$ curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
# 3.使用apt安装Bazel
$ sudo apt-get update && sudo apt-get install bazel
# 4.(可选步骤)当Bazel版本较低时,使用apt更新Bazel
$ sudo apt-get upgrade bazel

(2) 安装TensorFlow依赖的第三方Python软件包:

$ sudo apt-get install python-numpy python-dev python-pip python-wheel # 用于Python 2.x
$ sudo apt-get install python3-numpy python3-dev python3-pip python3-wheel # 用于Python 3.x

这里主要包括下面4个包。

  • numpy:经典的数据处理软件包。
  • dev:Python扩展开发工具包。
  • pip:安装和管理Python软件包的工具包。
  • wheel:Python软件打包和发布工具包。

(3) (可选步骤)安装适配当前操作系统的NVIDIA CUDA和cuDNN,以及CUDA分析工具接口libcupti-dev开发库。

(4) 从GitHub上克隆TensorFlow源代码,并将其切换到稳定的发布分支,如TensorFlow 1.2:

$ git clone https://github.com/tensorflow/tensorflow.git
$ cd tensorflow
$ git checkout r1.2

(5) 运行configure脚本,配置TensorFlow编译选项。该脚本位于TensorFlow项目源代码的根目录。下面的命令行输出展示了用户可配置的编译选项,包括Python路径、是否支持Google云平台、是否支持HDFS、是否使用XLA编译优化、是否使用OpenCL、是否使用CUDA等。对于期望开启的选项,用户可以根据提示,输入“y”或相应软件安装目录:

$ ./configure
Please specify the location of python. [Default is /usr/bin/python]: /usr/bin/python2.7
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]
jemalloc enabled
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 JIT support will be enabled for TensorFlow
Found possible Python library paths:
  /usr/local/lib/python2.7/dist-packages
  /usr/lib/python2.7/dist-packages
Please input the desired Python library path to use.  Default is [/usr/local/lib/python2.7/dist-packages]
Using python library path: /usr/local/lib/python2.7/dist-packages
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
Please specify which gcc should be used by nvcc as the host compiler. [Default is /usr/bin/gcc]:
Please specify the Cuda SDK version you want to use, e.g. 7.0. [Leave empty to use system default]: 8.0
Please specify the location where CUDA 8.0 toolkit is installed. Refer to README.md for more details. [Default is /usr/local/cuda]:
Please specify the cuDNN version you want to use. [Leave empty to use system default]: 5
Please specify the location where cuDNN 5 library is installed. Refer to README.md for more details. [Default is /usr/local/cuda]:
Please specify a list of comma-separated Cuda compute capabilities you want to build 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 increases your build time and binary size.
[Default is: "3.5,5.2"]: 3.0
Setting up Cuda include
Setting up Cuda lib
Setting up Cuda bin
Setting up Cuda nvvm
Setting up CUPTI include
Setting up CUPTI lib64
Configuration finished

当用户选择编译生成GPU版的TensorFlow whl包时,configure脚本将会为CUDA库文件创建软链接。因此,如果用户改变了操作系统的CUDA库文件路径,就必须重新运行configure脚本配置TensorFlow编译选项,以确保使用Bazel编译源代码时软链接可用。

(6) 编译TensorFlow whl软件包。

首先,我们需要编译TensorFlow源代码,生成对应的二进制文件。命令如下:

# CPU版
$ bazel build --config=opt //tensorflow/tools/pip_package:build_pip_package
# GPU版
$ bazel build --config=opt --config=cuda //tensorflow/tools/pip_package:build_pip_package

编译TensorFlow源代码需要使用大量内存。如果用户没有特别充足的内存资源,那么可以手动设置内存限制,如 --local_resources 2048,.5,1.0。TensorFlow官方发布的whl包均使用GCC 4编译。如果用户使用GCC 5或更高版本编译,为了兼容老版本的ABI,建议手动指定参数 --cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"。以上两个手动设置项均是bazel build命令的输入参数。

接下来,构建TensorFlow的whl包。bazel build命令执行成功后,会生成build_pip_package脚本,用于构建whl包。下面展示执行该脚本生成TensorFlow whl包的方法:

# /tmp/tensorflow_pkg指定了生成的TensorFlow whl包的路径,用户可以替换
$ bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg

(7) 安装TensorFlow,具体的安装方法与安装TensorFlow官方提供的whl包类似:

$ sudo pip install /tmp/tensorflow_pkg/tensorflow-1.2.0-py2-none-any.whl

2.1.7 Hello TensorFlow

使用前面介绍的方法安装TensorFlow后,我们需要验证TensorFlow是否安装成功。只要拥有编程背景的人,一定都知道Hello World!。不出俗套,这里我们也使用TensorFlow打印Hello TensorFlow!,来验证安装的正确性:

$ python
>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
>>> print(sess.run(hello))
Hello TensorFlow!

如果程序成功打印Hello TensorFlow!,则说明安装成功。如果出现导入TensorFlow软件包错误,则说明安装失败。

一般而言,在Python解释器中编写程序并不是一种高效的做法。我们推荐普通用户使用一些成熟的Python IDE或Jupyter Notebook来开发。Jupyter Notebook是一套Python交互式编程环境,它支持同时编辑多个文件,并以Web形式提供服务。下面我们简单介绍Jupyter Notebook的安装和使用方法。

(1) 安装并启动Jupyter Notebook。服务默认运行在本地的8888端口,用户在浏览器中输入对应的URL(http://localhost:8888)即可打开服务:

$ pip install jupyter
$ jupyter notebook
[I 23:28:44.100 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/
[I 23:28:44.100 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).

(2) 创建Python交互式环境,图2-1给出了相应操作菜单和命令的位置。

{%}

图2-1 在Jupyter Notebook中创建Python交互式环境

(3) 编写Python应用程序,图2-2给出了打印Hello TensorFlow! 的示例。

{%}

图2-2 在Jupyter Notebook中编写Python应用程序

Jupyter Notebook是所见即所得的交互式编程环境。用户可以指定执行某个输入的代码块,输出结果将直接打印在该代码块下方。

现在,我们已经完成了TensorFlow开发环境的搭建。在上述多种安装方法中,用户应该根据自身情况选择合适的方法。当出现安装问题时,可以查看TensorFlow官网整理的常见问题和解答(https://www.tensorflow.org/install/install_linux#common_installation_problems)。

2.2 依赖项

TensorFlow的设计实现引入了很多工具软件和开发库,这些软件和库有的来自Google公司,有的来自第三方开发者;有些以源代码或二进制形式集成到了TensorFlow的源代码发布包或二进制安装包,有些则作为编译时或运行时的外部依赖项。审视这些依赖项,可以看出开放的生态系统对于TensorFlow发展的重要性。本节挑选几个相对重要的、具有代表性的外部依赖项加以介绍。

2.2.1 Bazel软件构建工具

Bazel(http://bazel.build)是Google开源的一套软件构建工具。多数开发人员可能比较熟悉GNU Autotools、CMake和Apache Ant等经典的构建工具。Bazel的功能定位与这些软件类似,但具有一些独特的优势,具体如下。

  • 多语言支持:为C++、Java、Python等语言提供原生的构建规则,并允许以简易的方式开发针对新语言的扩展。
  • 高级构建语言:构建规则描述语言具有语法简洁、高度抽象,却不失功能丰富的特点,有助于用户快速写出正确的构建规则。
  • 多平台支持:除支持类Unix操作系统外,也支持Android等移动平台,并在新版本中提供对Windows平台的支持。
  • 可重现性:以严格的依赖关系为依据,在提供并行编译和增量编译能力的同时,保证每次编译的结果相同。
  • 可伸缩性:具有高效的增量代码分析能力,支持数十万行代码量级的大规模应用程序构建。

这些优势恰恰满足了TensorFlow的技术特征与构建需求,因此Bazel成为TensorFlow默认的软件构建工具。

Bazel使用工作空间(workspace)、包(package)和目标(target)三层抽象组织待构建的对象。

  • 工作空间:代表待构建的软件整体,它映射到文件系统中的一个目录,一般是软件源代码包的顶层目录。工作空间使用名为WORKSPACE的文件描述元信息,主要的元信息包括工作空间名称与软件的外部依赖项。Bazel支持在构建时通过本地复制或网络下载等多种**方式获取依赖项。
  • :代表一组具有逻辑关联的待构建实体,通常映射到软件内部的特定功能模块,并使用独立的目录组织该模块的所有源文件。包具有名为BUILD的描述文件,该文件除了描述包的元信息外,其主体内容是一系列目标构建规则的集合。包可以具有层次嵌套关系,典型的嵌套用法是将单元测试包嵌入到对应的功能模块包中。
  • 目标:指包内部管理的、具有独立构建规则的待构建实体,它通常映射到一条或一组编译器命令,用于生成软件模块中独立的目标代码文件(如 .o文件)或可执行文件。目标的完整名称可以用标签(label)描述,其格式一般为//[包名]:[目标名]。例如,TensorFlow核心层数据流图操作模块的完整目标名(标签)为//tensorflow/core:ops。在定义依赖项等场合指定同一包中的目标时,//[包名]可以省略。

Bazel目标构建规则的描述语法比较直观,它由一个规则名称和一组属性构成。规则名称取决于待构建实体所使用的编程语言及期待的输出文件类型。TensorFlow常用的规则包括用于构建C++ 库文件的cc_library、用于构建Python库文件的py_library,以及用于编译CUDA库文件的tf_cuda_library自定义规则等。规则的属性一般包括构建过程所需的源文件名、依赖项名、编译器选项等。下面给出一组构建规则描述语言的示例:

cc_library(
    name = "gradients",
    srcs = ["framework/gradients.cc"],
    hdrs = ["framework/gradients.h"],
    deps = [
        ":cc_ops",
        ":grad_op_registry",
        ":ops",
        ":scope",
        "//tensorflow/core:core_cpu",
        "//tensorflow/core:framework",
        "//tensorflow/core:lib",
        "//tensorflow/core:lib_internal",
    ],
)

该规则用于构建TensorFlow C++ API中的梯度计算接口,以便生成对应的目标文件。namesrcshdrsdeps属性分别定义了目标名称,以及构建过程所需的源文件、头文件和依赖项集合。其中,cc_ops等依赖项来自当前包(tensorflow/cc),core_cpu等依赖项来自核心层包(tensorflow/core)。开发者无须关心编译器的命令行参数,也无须考虑操作系统的目标文件扩展名。Bazel会自动处理这些细节,生成正确的编译器命令,进而编译出目标文件。Bazel甚至能够分析C++ 源代码,检查其实际依赖项与声明依赖项的差异,反馈给开发者,从而避免潜在错误的发生。

使用Bazel构建已经定义好目标规则的软件时,只需要运行bazel命令,该命令的一般参数格式为bazel [子命令] [选项] [目标名(标签)]。常用的子命令包括build(构建)、test(测试)和clean(清理)等,常用的选项包括编译器优化开关和详细日志开关等。我们已在2.1.6节中看到了使用Bazel构建TensorFlow安装包的命令。除此之外,在对TensorFlow进行二次开发时,开发者常常需要使用Bazel构建并运行测试用例,这时bazel test命令将有用武之地。

2.2.2 Protocol Buffers数据结构序列化工具

Protocol Buffers(https://developers.google.com/protocol-buffers/)是Google开发的一套数据结构序列化工具。自2008年开源以来,该工具已被互联网、大数据等领域的大量软件广泛使用。Protocol Buffers提供一套与操作系统和编程语言无关的数据结构定义语法及序列化表示方法。用户基于Protocol Buffers语法编写的数据结构定义文件(.proto文件)可以通过Protocol Buffers编译器(如protoc)生成C++、Java、Python等多种语言的数据结构定义代码。这些代码不但包含基本数据的成员定义,也包含用于访问成员、复制对象,以及对数据结构进行序列化和反序列化操作的辅助方法或类型。将这些自动生成的代码集成到应用程序中,可以实现跨平台、跨语言的结构化数据交换,同时减轻开发者处理语言差异和编写重复逻辑的开销,有助于提升软件开发时和运行时两方面的效率。

这里我们以TensorFlow核心数据结构之一的TensorProto为例,说明Protocol Buffers的基本用法。TensorProto是TensorFlow内部对张量数据的一种抽象,在C++、Python API层及C++核心层均有使用,多用于张量的进程间通信和持久化存储等场合。因此,它对于序列化功能和多语言支持具有明确的需求,很适合使用Protocol Buffers定义。TensorProto的数据结构定义文件是tensorflow/core/framework/tensor.proto,其关键内容摘录如下:

syntax = "proto3";

package tensorflow;

import "tensorflow/core/framework/tensor_shape.proto";
import "tensorflow/core/framework/types.proto";
...

message TensorProto {
    DataType dtype = 1;
    TensorShapeProto tensor_shape = 2;
    int32 version_number = 3;
    bytes tensor_content = 4;
    repeated float float_val = 5 [packed = true];
    ...
};

可以看出,.proto文件主要由语法声明、包名定义、文件引用和消息(即数据结构)定义等部分构成,其中核心内容是消息定义。消息内部可以包含int32bytes等基本类型字段,也可以包含DataTypeTensorShapeProto等自定义类型字段。repeated关键字修饰的字段称为重复型字段,类似于编程语言中的数组或向量。所有抽象数据类型均会被映射到目标语言中的实际数据类型。当构建TensorFlow源代码包时,Bazel会调用Protocol Buffers编译器,创建名为tensor.pb.h和tensor_pb2.py的C++ 与Python数据结构定义文件。尽管这些自动生成的文件在实现上相对冗长且晦涩,但其功能和效率一般不亚于普通开发者自行编写的数据结构代码,使用也非常简单。以C++ 语言为例,protoc能够生成TensorProto类,为之添加构造、析构函数和赋值操作符等辅助函数。对于其中的基本类型字段,用户可以使用与字段同名的方法读取其值,使用set_[字段名]方法修改其值。基类Message提供的SerializeToStringParseFromString方法则可以用于TensorProto对象的序列化和反序列化。

为了实现跨平台、跨语言的远程过程调用(RPC),同时简化分布式系统组件间协同机制的设计,Google在Protocol Buffers基础上开发并开源了gRPC通信库(http://www.grpc.io)。gRPC使用Protocol Buffers语法描述RPC的请求、响应消息类型和函数原型,Protocol Buffers能够为之生成多种编程语言的客户端与服务端的接口代码——这得益于Protocol Buffers的插件机制,该机制可以扩展.proto文件的语法和代码生成器的功能。gRPC能够将函数调用方与实现方在空间上解耦,允许双方以异步的时序协同工作。gRPC底层使用HTTP/2作为通信协议,其内部实现了支持非阻塞异步调用的并发调度引擎。TensorFlow的进程间通信机制,以及Serving等周边组件的交互机制均基于gRPC框架开发。下面的示例代码给出了TensorFlow的进程间通信框架中,有关数据流图注册操作的gRPC服务定义:

...
message RegisterGraphRequest {
    string session_handle = 1;
    GraphDef graph_def = 2;
    bool has_control_flow = 3 [deprecated = true];
    GraphOptions graph_options = 4;
    DebugOptions debug_options = 5;
}

message RegisterGraphResponse {
    string graph_handle = 1;
}
...

rpc RegisterGraph(RegisterGraphRequest) returns (RegisterGraphResponse);
...

这段代码来自tensorflow/core/protobuf目录下的worker.proto和worker_service.proto文件。Protocol Buffers及其gRPC插件以这些文件作为输入,为TensorFlow生成worker服务的C++ 接口代码。

Protocol Buffers和gRPC均是TensorFlow编译时集成的依赖项。用户在使用TensorFlow二进制包时,无须独立安装这两个软件。Bazel在构建TensorFlow源代码包时,能够自动下载并编译这两个软件。

2.2.3 Eigen线性代数计算库

Eigen(http://eigen.tuxfamily.org)是一套基于C++ 模板技术开发的线性代数计算库。该库由众多开源贡献者共同维护,托管于TuxFamily社区,现已被包括TensorFlow在内的大量科学和工程计算类软件使用。作为一款基础的数学库,Eigen具有通用性、高效性、易用性和可靠性等特征。它支持复数、向量、矩阵等多种数据类型,提供大量经典矩阵算法和几何算法的实现,在算法实现中充分利用了主流CPU指令集和编译器的高级优化特性。Eigen仅依赖于标准C++ 库,这使得它能够兼容多种编译器和操作系统。基于C++ 模板的技术方案一方面可以为数据类型的扩展和算法的特化实现提供可能,有助于增强库的灵活性;另一方面也可以通过元编程(metaprogramming)技巧将部分计算量由运行时迁移到编译时,有助于提升软件效率。尽管Eigen的功能是自包含的,然而它同样支持使用其他计算加速技术提升自身的性能。例如,可以使用OpenMP实现多线程并行计算,也可以使用Intel MKL实现算法向量化加速。

Eigen社区对于开源贡献者非常开放。项目源代码包的unsupported目录包含了一系列第三方贡献的、尚未由官方提供支持的组件。这些第三方组件既包括对数据结构和算法的扩展,也包括服务于计算逻辑的功能支撑组件,例如用于实现线程调度的线程池组件以及用于接入加速器硬件的适配层组件等。

TensorFlow使用了Eigen提供的多种组件,这些组件大多用于在CPU和OpenCL GPU设备上实现TensorFlow的计算类操作。为了在精度要求不高的场景下节约计算和存储开销,TensorFlow通常使用16位的Eigen::half浮点类型保存参数。针对矩阵存储和计算需求,TensorFlow引入了Eigen::Matrix类型以及Eigen::PartialPivLUEigen::HouseholderQR等多种分解算法。由于TensorFlow以张量作为核心数据结构,它的许多算法实现借助了Eigen第三方组件中的Eigen::Tensor类型及其相关函数。Eigen::Tensor类型提供张量的高效存储和访问能力,以及降维、缩并和卷积等常用算法的实现,这些算法实现已针对多种计算设备分别进行了优化。Eigen::Tensor组件基于C++ 11的特性开发,这一点不同于仅依赖C++ 98特性的Eigen官方组件。

此外,TensorFlow也使用Eigen第三方组件中的线程池模块——Eigen::ThreadPool。事实上,这个模块正是来自Google公司的贡献。Eigen::ThreadPool能够以线程池抽象有效管理多核CPU的计算资源,将计算任务有序地安排在不同线程上并发执行,并在任务量超过资源量的情况下对任务进行排队。为了以较低的开发成本接入OpenCL GPU设备,TensorFlow还引入了Codeplay公司贡献的Eigen::SyclDevice设备抽象及其相关组件。这些组件使得TensorFlow能够简单地以模板参数替换方式,将CPU上的算法迁移到OpenCL GPU。

Eigen也是TensorFlow编译时集成的依赖项,不需要用户独立安装。

2.2.4 CUDA统一计算设备架构

CUDA(https://developer.nvidia.com/cuda-zone)是NVIDIA公司推出的一种用于并行计算的软硬件架构,发布于2007年。该架构以通用计算图形处理器(GPGPU)作为主要的硬件平台,提供一组用于编写和执行通用计算任务的开发库与运行时环境。CUDA架构能够充分利用原本为图形渲染而设计的众核GPU,发挥其并行处理、浮点计算和可编程流水线的技术优势,从而为计算密集型和数据密集型的任务提供高效的算力支持。CUDA架构最初在高性能计算领域崭露头角,随后也被引入了互联网和大数据生态系统。自从深度学习技术在工业界流行之后,CUDA架构因能够很好地适配神经网络算法的并行加速需求,已成为深度学习领域,特别是模型训练过程首选的计算架构。包括TensorFlow在内的绝大多数机器学习、深度学习平台原生地提供了对CUDA架构及NVIDIA GPU的支持。

当把CUDA一词作为软件依赖项提及时,我们指的往往是CUDA架构中的软件组件,即NVIDIA驱动程序和CUDA工具包(CUDA Toolkit)。用户可以在NVIDIA官方网站下载集成了NVIDIA驱动程序、CUDA开发库和编译器的安装包。除了基本的CUDA开发库和编译器外,CUDA工具包还包括cuBLAS、cuFFT、cuSOLVER、cuDNN等高级算法库,以及IDE、调试器、可视化分析器等开发工具,其中部分组件需要独立安装。CUDA工具包中的一部分软件组件是开源的,例如基于LLVM的CUDA编译器。其余非开源的组件大多以免费软件的形式提供给开发者和最终用户。

在CUDA架构中,不同层次的软件组件均为开发者提供编程接口,以适应不同类型软件的开发需求。NVIDIA驱动层的开发接口(即cu开头的函数,也称为CUDA Driver API)较为底层,暴露了GPU的若干种内部实现抽象。这种接口能够对GPU的运行时行为进行细粒度控制,有助于提升程序的运行时效率,但缺点在于开发过程烦琐。一般的GPU应用程序不会直接使用这一层接口,然而TensorFlow内部的GPU计算引擎——StreamExecutor为了追求性能,选择使用这一层接口实现GPU任务调度和内存管理等功能。CUDA开发库的API(即cuda开头的函数,也称为CUDA Runtime API)是CUDA架构中使用最为广泛的接口,功能涵盖GPU设备管理、内存管理、事件管理以及与图形处理相关的逻辑。不过,既然有了StreamExecutor,TensorFlow就没有大面积地使用这个层次的接口。cuBLAS、cuDNN等高级算法库提供面向通用计算(如线性代数)或领域专用计算(如神经网络)需求的高层次接口。在这个层次,GPU设备的很多技术细节已被屏蔽,开发者可以专注于算法逻辑的设计和实现。TensorFlow面向NVIDIA GPU的计算类操作大多基于cuBLAS和cuDNN接口实现。

对于TensorFlow而言,CUDA工具包是不受Bazel管理的外部依赖项。用户想要使用NVIDIA GPU加速深度学习时,无论部署TensorFlow GPU版本的二进制包还是编译通用的源代码包,都需要事先安装带有NVIDIA驱动程序的CUDA工具包及cuDNN库。

2.3 源代码结构

安装了TensorFlow,并对其外部依赖项有了基本认识之后,读者或许会好奇TensorFlow的内部组件结构与实现原理。分析TensorFlow的源代码结构是理解其功能构成和模块组织的良好途径,也是对TensorFlow进行二次开发的必要条件。即使不打算从事二次开发,学习TensorFlow的源代码结构,了解其基本组件的层级关系,同样有助于建立对软件架构的整体印象,从而为编写正确而高效的算法模型奠定基础。此外,源代码包中提供的某些第三方组件、辅助工具、示例和测试代码在二进制安装包中并不存在。如果需要使用它们,就必须从源代码包中提取。有兴趣深入探索这些“隐藏物件”的读者也应当关注TensorFlow的源代码结构。

2.3.1 根目录

TensorFlow源代码的组织符合Bazel构建工具要求的规范。其根目录是一个Bazel项目的工作空间,包含了TensorFlow的所有源代码、Bazel构建规则文件,以及一些辅助脚本。根目录下的主要子目录和文件介绍如下(以方括号表示目录名,下同)。

[tensorflow]:TensorFlow项目自身的源代码。

[third_party]:部分第三方源代码以及针对第三方项目的Bazel构建规则文件。

[tools]:Bazel构建过程所需的环境配置脚本。

[util]:Bazel构建过程所需的辅助构建规则文件。

configure:TensorFlow源代码包配置脚本,用于在构建源代码包之前设置软件的可选特性。

WORKSPACE:Bazel工作空间描述文件,包含项目元信息和部分外部依赖项的下载规则。

除此之外,根目录下还包含一系列纯文本的说明文件,用于向用户介绍TensorFlow项目的基本情况。

这里有必要特别说明的是third_party目录。尽管Bazel会通过外部依赖项下载规则,从网络上获取TensorFlow所需的绝大多数第三方源代码,然而很多第三方软件自身不包含Bazel构建规则,TensorFlow需要为它们准备相应的规则文件。这些规则文件不但解决了一些项目中特有的技术问题(例如OpenCL GPU相关代码所需的专用工具链),而且解决了集成某些项目涉及的非技术问题(例如需要排除Eigen库中的部分GPL授权代码)。另外,third_party目录还包括一些第三方项目的头文件,例如hdfs.h。这是因为TensorFlow构建时只需要这些项目的头文件,不需要相应的源文件或库文件,没有必要下载该依赖项的整个软件包。

2.3.2 tensorflow目录

TensorFlow项目的源代码主体位于tensorflow目录。该目录下的源文件几乎实现了TensorFlow的全部功能,同时体现了TensorFlow的整体模块布局。它的主要子目录和文件介绍如下。

[c]:C语言应用层API,亦作为C、C++ 以外的其他语言应用层API的实现基础。

[cc]:C++ 语言应用层API。

[compiler]:XLA(Accelerated Linear Algebra)编译优化组件的源代码。

[contrib]:社区托管的第三方贡献组件(主要组件见15.1节)。

[core]:TensorFlow核心运行时库的源代码,主要使用C++ 语言实现。

[docs_src]:TensorFlow软件文档(即TensorFlow官方网站文档)的Markdown源代码。

[examples]:TensorFlow应用开发示例代码。

[g3doc]:旧的文档目录,已弃用。

[go]:Go语言应用层API。

[java]:Java语言应用层API。

[python]:Python语言应用层API。

[stream_executor]:StreamExecutor库的源代码,主要使用C++ 语言实现,用于管理CUDA GPU上的计算。

[tensorboard]:TensorBoard组件的源代码,主要使用Python语言实现,用于深度学习过程可视化。

[tools]:TensorFlow构建时和运行时使用的工具程序或脚本。

[user_ops]:用于存放用户自行开发的数据流图操作,包含一组示例代码。

BUILD:Bazel构建规则文件,用于构建TensorFlow核心运行时库等组件。

tensorflow.bzl:Bazel构建过程所需的辅助脚本,主要用于定义TensorFlow特有的构建规则。

workspace.bzl:Bazel构建过程所需的辅助脚本,主要用于定义外部依赖项的下载规则。

深度学习算法模型的开发者可以对Python等应用层API目录进行简单浏览,以便了解TensorFlow提供的抽象和接口。TensorFlow核心层的二次开发者则需要聚焦core目录,从而理解其内部设计与实现原理。docs_src目录提供的软件文档适用于所有用户,它是学习TensorFlow基础知识的第一手资料。相比这些常用的目录,tools目录有时会被用户忽略。该目录包含不少实用工具,涉及搭建TensorFlow持续集成和基准测试环境所需的框架代码、在Docker容器或Google云环境中运行TensorFlow所需的脚本、用于编辑和压缩模型文件的数据流图转换器,以及自动升级Python应用代码中TensorFlow API版本的工具等。

2.3.3 tensorflow/core目录

TensorFlow核心运行时库的源代码位于tensorflow/core目录,其主要子目录介绍如下。

[common_runtime]:核心库的公共运行时源代码,实现了TensorFlow数据流图计算的主要逻辑。

[debug]:用于核心库调试的组件。

[distributed_runtime]:核心库的分布式运行时源代码,实现了TensorFlow分布式运行模式的主要逻辑。

[example]:使用Protocol Buffers创建自定义数据结构并访问序列化文件的示例代码。

[framework]:核心库的框架性组件,包含TensorFlow编程框架中主要抽象的C++ 或Protocol Buffers定义。

[graph]:数据流图相关抽象和工具类的源代码。

[grappler]:Grappler优化器(一种基于硬件使用成本分析的数据流图优化器)的源代码。

[kernels]:数据流图操作(Op)针对各类计算设备实现的核函数源代码。

[lib]:公共基础库,涉及通用数据结构、常用算法的实现,以及多种图形、音频格式的访问接口类。

[ops]:数据流图操作的接口定义源代码。

[platform]:用于访问特定操作系统或云服务接口的平台相关代码。

[protobuf]:数据流图基本抽象以外的序列化数据结构的Protocol Buffers源代码,例如gRPC接口定义。

[public]:对应用层可见的公开接口的头文件。

[user_ops]:用于存放用户自行开发的数据流图操作,包含一组示例代码。

[util]:核心库内部使用的多种实用工具类或函数的集合,例如用于解析命令行参数和访问环境变量的工具。

随着TensorFlow版本的演进,核心运行时的源代码组织也有可能发生变化。这些对最终用户不可见的变化往往不会体现在TensorFlow的版本发布说明(release note)中,有兴趣的读者可以自行分析目录结构和文件的变化。

读者可能会注意到,tensorflow和tensorflow/core目录下各有一个user_ops目录。官方对二者给出了不同的定位:前者适用于用户以二进制包形式安装TensorFlow之后,通过Bazel的tf_custom_op_library规则构建自定义操作的情况;后者适用于用户获取TensorFlow源代码包之后,在核心运行时库构建过程中编译自定义操作的情况。

2.3.4 tensorflow/python目录

TensorFlow Python API的源代码位于tensorflow/python目录,其主要子目录和文件介绍如下。

[client]:TensorFlow主-从模型中的客户端组件,主要包括会话抽象,用于维护数据流图计算的生命周期。

[debug]:用于Python应用程序调试的组件。

[estimator]:各类模型评价器(estimator)。

[feature_column]:特征列(feature column)组件。

[framework]:Python API的框架型组件,包含TensorFlow编程框架中主要抽象的Python语言定义。

[grappler]:Grappler优化器的Python语言接口。

[kernel_tests]:数据流图操作的单元测试代码,有助于用户学习各种操作的使用方法。

[layers]:预置的神经网络模型层(layer)组件。

[lib]:公共基础库,涉及专用数据结构访问和文件系统I/O等。

[ops]:数据流图操作的Python语言接口。

[platform]:用于访问特定操作系统或云服务接口的平台相关代码。

[saved_model]:用于访问TensorFlow通用模型序列化格式(SavedModel)的组件。

[summary]:用于生成TensorFlow事件汇总文件(summary)的组件,以便在TensorBoard中可视化计算过程。

[tools]:若干可独立运行的Python脚本工具,涉及访问和优化模型文件等功能。

[training]:与模型训练过程相关的组件,例如各类优化器(optimizer)、模型保存器(saver)等。

[user_ops]:用于存放用户自行开发的数据流图操作的Python语言接口。

[util]:Python API内部使用的多种实用工具类或函数的集合,例如用于处理Python 2/3文本兼容性的函数。

build_defs.bzl:Bazel构建过程所需的辅助脚本,主要用于定义Python API特有的构建规则。

pywrap_tensorflow.py:间接封装核心库通过C API导出的函数,以便在Python API内部调用核心库的功能。

tensorflow.i:对接C API的SWIG接口描述文件,用于在软件构建时为Python API生成核心层的动态链接库。

Python API的多数模块内部通过SWIG工具生成的胶合层代码调用C API,进而使用C++ 核心库的功能。另有少量API的内部实现代码出于提升执行效率或调用核心库未导出接口的目的,直接使用C++ 语言开发。Python API在TensorFlow源代码中属于更新相对频繁的部分,读者可以通过TensorFlow的版本发布说明追踪API的新变化。

2.3.5 安装目录

使用官方二进制包或基于源代码包自行构建出来的二进制包安装TensorFlow之时,pip命令会将TensorFlow运行时所需的Python文件、动态链接库以及必要的依赖项复制到当前Python环境的site-packages或dist-packages目录中。其中,TensorFlow软件本身的运行时代码会被部署到tensorflow子目录。这一目录具有和源代码包的tensorflow目录相似的组织结构。对于一般用户而言,二者的主要不同点在于以下几点。

  • 安装目录中只包含每个模块的Python语言接口文件,不再包含C++ 源代码。所有使用到的C++ 源代码已被编译到了python子目录下的动态链接库文件中(在Linux下为_pywrap_tensorflow_internal.so)。如果某个模块未提供Python API,那么相应的子目录不会在安装目录中出现。
  • 安装目录中的python/ops子目录比同名的源代码子目录增加了一系列名称由gen_ 开头的Python接口文件。这些文件是TensorFlow编译脚本自动创建的,它们旨在为C++ 核心库的一部分数据流图操作提供Python编程接口。
  • 安装目录比源代码目录多出一个include子目录。这个目录包含了TensorFlow本身以及Protocol Buffers、Eigen等依赖库的C++ 头文件,允许用户通过编程方式使用核心库的功能。需要注意的是,这些头文件提供的是原core目录下的TensorFlow核心层API,而非原cc目录下的C++ 应用层API。

2.4 小结

本章介绍了TensorFlow的发布形式和多种安装方法。我们看到,原生pip方式适用于简单的Python应用程序开发场景,Anaconda和virtualenv方式适用于需要维护多套Python虚拟环境的场合,而源代码编译安装方式适合于对性能有较高要求、需要使用可选特性,或者希望对TensorFlow做二次开发的情况。读者可以根据自身的需求,选择合适的TensorFlow版本和安装方法,从而构建属于自己的深度学习开发环境。本章亦介绍了TensorFlow的4个主要外部依赖项——Bazel、Protocol Buffers、Eigen和CUDA的功能与用法,以及TensorFlow源代码包的目录结构,这为读者进一步学习TensorFlow应用层API和核心层原理奠定了基础、理顺了思路。

目录