TensorFlow 计算加速(一)

TensorFlow 计算加速

  • 内容摘自《TensorFlow实战Google深度学习框架》 第二版

1. TensorFlow使用GPU

TensorFlow程序可以通过tf.device函数来通过名称指定运行每一个操作的设备,这个设备可是是本地的GPU或CPU,也可以是一台远程的服务器。

  • 在默认情况下,就算及其有多个CPU,TensorFlow也不会区分他们,所有的CPU都使用/cpu:0为名称。
  • 一台机器上不同GPU的名称是不同的,第n个GPU的名称为/gpu:n。

在生成会话(session)时,可通过log_device_placement参数来打印每一个运算的设备:

a = tf.constant([1.0, 2.0, 3.0], shape = [3], name = 'a')
b = tf.constant([1.0, 2.0, 3.0], shape = [3], name = 'b')
c = a+b

sess = tf.Session(config = tf.ConfigProto(log_device_placement=True))
print(sess.run(c))

'''
在没有GPU的机器上运行以上代码可以得到类似以下的输出:
add: (Add): /job:localhost/replica:0/task:0/cpu:0
b: (Const):  /job:localhost/replica:0/task:0/cpu:0
c: (Const):  /job:localhost/replica:0/task:0/cpu:0
[ 2. 4. 6. ]
'''
  • 在配置好GPU环境的TensorFlow中,若程序没有明确指定运行设备,那么TensorFlow会优先选GPU。
  • 若机器有多块GPU,默认会将所有运算放在/gpu:0上。
  • 若强行将无法放在GPU上的操作指定到GPU上,程序将会报错。例如下面的程序所演示的那样。
  • 为避免这类问题,TensorFlow在生成Session时可以指定allow_soft_placement = True , 若运算无法在GPU上执行,那么TensorFlow会自动将它放到CPU上执行。
a_cpu = tf.Variable(0, name = 'a_cpu')
with tf.device('/gpu:0'):
    a_gpu = tf.Variable(0, name = 'a_gpu')

sess = tf.Session(config = tf.ConfigProto(log_device_placement=True)
sess.run(tf.initialize_all_variables())

#在GPU上,`tf.Variable` 的操作值支持实数型(float32\ float64\ double)参数,
#而此处给定的参数是整数型的,因此会发报类似与以下错误。

'''
tensorflow.python.framework.errors.InvalidArgumentError:Cannot assign a device to node ' a_gpu': Could not satisfy expicit device specification '/device:GPU:0' because no supported kernel for GPU devices is available. 
'''
# 通过allow_soft_placement参数自动将无法放在GPU上的操作放回CPU上

sess = tf.Session(config = tf.ConfigProto(
       allow_soft_placement = True, log_device_placement=True)
sess.run(tf.initialize_all_variables())

#由输出的日志可知(此处未给出),a_gpu,a_gpu/read和a_gpu/Assign在CPU上执行,a_gpu/initial_value依旧由GPU执行。

虽然GPU可以加速TensorFlow的计算,但一般不会把所有操作放到GPU上。比较好的实践是将密集型的运算放到GPU上,其他的操作放在CPU上。GPU是机器中相对独立的资源,将计算放入或转出GPU都需要额外的时间,GPU的将计算时用到的数据从内存复制到GPU上也需要额外的时间。

  • TensorFlow默认会占用设备上所有GPU以及每个GPU的所有显存。
  • 可通过设置CUDA_VISIBLE_DEVICES 环境变量来控制需要使用哪几块GPU
  • TensorFlow也支持动态分配GPU的显存,使得一块GPU上可以同时运行多个任务
## 终端中:
# 只使用第二块GPU。在demo_code.py中,机器上第二块GPU的名称变为/gpu:0,
# 不过在运行是所有/gpu:0的运算将被放在第二块GPU上。
CUDA_VISIBLE_DEVICES = 1 python demo_code.py
# 使用第一块和第二块
CUDA_VISIBLE_DEVICES = 0,1 python demo_code.py

# 也可以在python中设置环境变量
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "2"



# 动态分配显存的操作:
config = tf.ConfigProto()

#按需分配显存
config.gpu_options.allow_growth = True 
# or 按固定比例分配显存
config.gpu_options.per_process_gpu_memort_fraction = 0.4 
session = tf.Session(config=config, ...)

2. 深度学习训练的并行模式

深度学习模型的训练过程是一个迭代的过程,在每一轮迭代中,前向传播算法会根据当前参数的取值计算出在一小部分训练数据上的预测值,然后反向传播算法再根据损失函数计算参数的梯度并更新。在并行训练时,不同设备(GPU, CPU)可以在不同训练数据上运行这个迭代过程,为不同并行模式的区别在于不同的参数更行方式。

2.1 异步模式

在每一轮迭代时,不同设备会读取参数最新的取值,但由于不同设备读取参数取值的时间不同,所以得到的值也有可能不同。根据当前参数的取值和随机获取的一小部分训练数据,不同设备各自运行反向传播的过程独立地更新参数。可以简单地认为异步模式就是单机模式复制多份,每一份使用不同的训练数据进行训练。不同设备之间是完全独立的。

TensorFlow 计算加速(一)_第1张图片
使用异步模式可能无法达到较优训练结果。如图12-3所示,曲线展示了模型的损失函数,黑色小球表示在 t 0 t_0 t0时刻参数所对应的损失大小。假设设备 d 0 d_0 d0 d 1 d_1 d1同时在时间 t 0 t_0 t0读取了参数的取值,那么他们计算出来的梯度都会将小黑球向左移动。假设在时间 t 1 t_1 t1 设备 d 0 d_0 d0已经完成了反向传播的计算并将参数更新了到了小灰球的位置,然而此时的设备 d 1 d_1 d1并不知道参数已被更新,所以在时间 t 2 t_2 t2时,设备 d 1 d_1 d1会继续将小球向左移动,使得其达到小白球的位置,从而无法达到最优点。

TensorFlow 计算加速(一)_第2张图片

2.2 同步模式

同步模式下,所有设备会同时读取参数的取值,并当反向传播算法完成之后同步更新参数的取值。单个设备不会单独对参数进行更新,而会等到所有设备都完成反向传播之后再统一更新参数。 每一轮迭代时, 不同设备先统一读取当前参数的取值,并随机获取一小部分数据,然后在不同设备上运行反向传播过程得到各自的梯度。虽然所有设备使用的参数一致,但因为训练数据不同,所以得到的参数的梯度就不同。待所有设备完成反向传播的计算后,需要计算出不同设备上参数梯度的平均值,根据平均值对参数进行更新。

TensorFlow 计算加速(一)_第3张图片

同步模式解决了异步模式下存在的无法达到最优问题,然而同步模式的效率却低于异步模式,设想设备运行的速度不一致,每一轮迭代都需要等待最慢的设备结束才能开始更新。

理论上虽然异步模式存在缺陷,但深度学习模型训练过程常采用的随机梯度下降本身就是梯度下降的一种近似解法,也无法保证达到全局最优最优,因此在实际应用中,在相同时间内,异步模式训练的模型不一定比同步模式差

接下来会在 《TensorFlow 计算加速(二》中总结 多GPU并行和分布式TensorFlow …

你可能感兴趣的:(DL,TensorFlow,深度学习,TensorFlow,GPU)