我们在网上只发现比较少的信息与资源,最开始介绍 Colab 免费 TPU 的内容还是谷歌开发者 Sam Wittevee 最近的演讲 PPT。因此本文的测试和探索都是基于官方文档和实例所实现的,还有很多 TPU 特性没有考虑到,感兴趣的读者可查阅文末的参考资料,了解更多 Colab 免费 TPU 的特性。
本文所有的测试代码与结果都可以访问:https://colab.research.google.com/drive/1DpUCBm58fruGNRtQL_DiSVbT90spdZgm
试验 Colab 免费 TPU
首先我们需要确保 Colab 笔记本中运行时类型选择的是 TPU,同时分配了 TPU 资源。因此依次选择菜单栏中的「runtime」和「change runtime type」就能弹出以下对话框:
为了确保 Colab 给我们分配了 TPU 计算资源,我们可以运行以下测试代码。如果输出 ERROR 项,则表示目前的运行时并没有调整到 TPU,如果输出 TPU 地址及 TPU 设备列表,则表示 Colab 已经为我们分配了 TPU 计算资源。
如果查看以下测试代码的正常输出,Colab 会为「TPU 运行时」分配 CPU 和 TPU,其中分配的 TPU 工作站有八个核心,因此在后面配置的 TPU 策略会选择 8 条并行 shards。
import os
import pprint
import tensorflow as tf
if 'COLAB_TPU_ADDR' not in os.environ:
print('ERROR: Not connected to a TPU runtime')
else:
tpu_address = 'grpc://' + os.environ['COLAB_TPU_ADDR']
print ('TPU address is', tpu_address)
with tf.Session(tpu_address) as session:
devices = session.list_devices()
print('TPU devices:')
pprint.pprint(devices)
目前,Colab 一共支持三种运行时,即 CPU、GPU(K80)和 TPU(据说是 TPU v2)。但我们不太了解 Colab 中的 GPU 和 TPU 在深度模型中的表现如何,当然后面会用具体的任务去测试,不过现在我们可以先用相同的运算试试它们的效果。因此我们首先尝试用简单的卷积运算测试它们的迭代时间。
在测试不同的硬件时,需要切换到不同的运行时。如下先定义 128 张随机生成的 256×256 图像,然后定义 256 个 5×5 的卷积核后就能执行卷积运算,其中魔术函数 %timeit 会自动多次执行,以产生一个更为精确的平均执行时间。
import tensorflow as tf
import numpy as np
import timeit
tf.reset_default_graph()
img = np.random.randn(128, 256, 256, 3).astype(np.float32)
w = np.random.randn(5, 5, 3, 256).astype(np.float32)
conv = tf.nn.conv2d(img, w, [1,2,2,1], padding='SAME')
with tf.Session() as sess:
# with tf.device("/gpu:0") as dev:
%timeit sess.run(conv)
然而,是我们想当然了,使用 TPU 执行运算似乎需要特定的函数与运算,它不像 CPU 和 GPU 那样可以共用相同的代码。分别选择 CPU、GPU 和 TPU 作为运行时状态,运行上面的代码并迭代一次所需要的时间分别为:2.44 s、280 ms、2.47 s。从这里看来,仅修改运行时状态,并不会真正调用 TPU 资源,真正实现运算的还是 CPU。随后我们发现 TF 存在一个神奇的类 tf.contrib.tpu,似乎真正调用 TPU 资源必须使用它改写模型。
因此,根据文档与调用示例,我们将上面的卷积测试代码改为了以下形式,并成功地调用了 TPU。此外,因为每次都需要重新连接不同的运行时,所以这里的代码都保留了库的导入。虽然代码不太一样,但直觉上它的计算量应该和上面的代码相同,因此大致上能判断 Colab 提供的 GPU、TPU 速度对比。
import tensorflow as tf
import numpy as np
import timeit
import os
tpu_address = 'grpc://' + os.environ['COLAB_TPU_ADDR']
tf.reset_default_graph()
def conv_op():
img = np.random.randn(128, 256, 256, 3).astype(np.float32)
conv_w = np.random.randn(5, 5, 3, 256).astype(np.float32)
conv = tf.nn.conv2d(img, conv_w, [1,2,2,1], padding='SAME')
tpu_ops = tf.contrib.tpu.batch_parallel(conv_op, [], num_shards=8)
with tf.Session(tpu_address) as sess:
sess.run(tf.contrib.tpu.initialize_system())
sess.run(tpu_ops)
%timeit sess.run(tpu_ops)
sess.run(tf.contrib.tpu.shutdown_system())
运行后出现了非常意外的结果,这样的卷积运算每一次迭代只需要 1.22 ms。如下图所示,很可能存在变量缓存等其它因素造成了一定程度的缓慢,但 TPU 的速度无可置疑地快。因此如果在 Colab 上测试模型,我们就更希望使用免费的 TPU,不过使用 TPU 需要改模型代码,这又比较麻烦。
尽管简单的卷积运算 TPU 要比 K80 快很多,但这只能给我们一个大致的猜想,因此我们需要测试完整的模型。注意在 tf.contrib.tpu 类中,它还提供了两种使用 TPU 的简单方法,即直接使用 Keras 接口和使用 TPUEstimator 构建模型。
在 tf.contrib.tpu 的文档中,我们发现 tf.contrib.tpu.keras_to_tpu_model 方法可以直接将 Keras 模型与对应的 权重 复制到 TPU,并返回 TPU 模型。该方法在输入 Keras 模型和在多个 TPU 核心上的训练策略后,能输出一个 Keras TPU 模型的实例,且可分配到 TPU 进行运算。
除此之外,另外一种调用 TPU 计算资源的方法是 tf.contrib.tpu.TPUEstimator,对于修正我们原来的 Tensor Flow 模型以适用 TPU,它可能是一种更方便的方式。根据文档所示,TPUEstimator 类继承自 Estimator 类,因此它不仅支持在 TPU 上运算,同时还支持 CPU 和 GPU 的运算。TPUEstimator 隐藏了非常多在 TPU 上训练的细节,例如为多个 TPU 核心复制多个输入和模型等。
TPU 调用文档地址:https://www.tensorflow.org/api_docs/python/tf/contrib/tpu
对比 TPU 与 GPU 的计算速度
为了简单起见,这里仅使用 Fashion-MNIST 数据集与简单的 5 层 卷积 神经网络 测试不同的芯片性能。这个模型是基于 Keras 构建的,因为除了模型转换与编译,Keras 模型在 TPU 和 GPU 的训练代码都是一样的,且用 Keras 模型做展示也非常简洁。
几天前谷歌 Colab 团队发了一版使用 Keras 调用 TPU 的教程,因此我们就借助它测试 TPU 的训练速度。对于 GPU 的测试,我们可以修改该模型的编译与拟合部分,并调用 GPU 进行训练。所以整个训练的数据获取、模型结构、 超参数 都是一样的,不一样的只是硬件。
教程地址:https://colab.research.google.com/github/tensorflow/tpu/blob/master/tools/colab/fashion_mnist.ipynb
以下是整个测试的公共部分,包含了训练数据的获取和模型架构。Keras 的模型代码非常好理解,如下第一个卷积层首先采用了批归一化,然后用 64 个 5×5 的卷积核实现卷积运算,注意这里采用的 激活函数 都是 指数线性单元 (ELU)。随后对卷积结果做 2×2 的最大 池化 ,并加上一个随机丢弃率为 0.25 的 Dropout 层,最后得出的结果就是第一个卷积层的输出。
import tensorflow as tf
import numpy as np
import timeit
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
# add empty color dimension
x_train = np.expand_dims(x_train, -1)
x_test = np.expand_dims(x_test, -1)
model = tf.keras.models.Sequential()
# 以下为第一个卷积层
model.add(tf.keras.layers.BatchNormalization(input_shape=x_train.shape[1:]))
model.add(tf.keras.layers.Conv2D(64, (5, 5), padding='same', activation='elu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2,2)))
model.add(tf.keras.layers.Dropout(0.25))
model.add(tf.keras.layers.BatchNormalization(input_shape=x_train.shape[1:]))
model.add(tf.keras.layers.Conv2D(128, (5, 5), padding='same', activation='elu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Dropout(0.25))
model.add(tf.keras.layers.BatchNormalization(input_shape=x_train.shape[1:]))
model.add(tf.keras.layers.Conv2D(256, (5, 5), padding='same', activation='elu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2,2)))
model.add(tf.keras.layers.Dropout(0.25))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(256))
model.add(tf.keras.layers.Activation('elu'))
model.add(tf.keras.layers.Dropout(0.5))
model.add(tf.keras.layers.Dense(10))
model.add(tf.keras.layers.Activation('softmax'))
model.summary()
在定义模型后,TPU 需要转化模型与编译模型。如下所示,keras_to_tpu_model 方法需要输入正常 Keras 模型及其在 TPU 上的分布式策略,这可以视为「TPU 版」的模型。完成模型的转换后,只需要像一般 Keras 模型那样执行编译并拟合数据就可以了。
注意两个模型的 超参数 ,如 学习率 、批量大小和 Epoch 数量等都设置为相同的数值,且 损失函数 和最 优化器 等也采用相同的方法。
import os
tpu_model = tf.contrib.tpu.keras_to_tpu_model(
model,
strategy=tf.contrib.tpu.TPUDistributionStrategy(
tf.contrib.cluster_resolver.TPUClusterResolver(tpu='grpc://' + os.environ['COLAB_TPU_ADDR'])
tpu_model.compile(
optimizer=tf.train.AdamOptimizer(learning_rate=1e-3, ),
loss=tf.keras.losses.sparse_categorical_crossentropy,
metrics=['sparse_categorical_accuracy']
def train_gen(batch_size):
while True:
offset = np.random.randint(0, x_train.shape[0] - batch_size)
yield x_train[offset:offset+batch_size], y_train[offset:offset + batch_size]
%time tpu_model.fit_generator(train_gen(1024), epochs=5, steps_per_epoch=100, validation_data=(x_test, y_test))
最后在使用 GPU 训练模型时,我们会删除模型转换步骤,并保留相同的编译和拟合部分。训练的结果如下所示,Colab 提供的 TPU 要比 GPU 快 3 倍左右,一般 TPU 训练 5 个 Epoch 只需要 40 多秒,而 GPU 需要 2 分多钟。
Colab 使用免费 TPU 训练的信息摘要。
Colab 使用免费 GPU 训练的信息摘要。
最后,Colab 确实提供了非常强劲的免费 TPU,而且使用 Keras 或 TPUEstimator 也很容易重新搭建或转换已有的 Tensor Flow 模型。机器之心只是简单地试用了 Colab 免费 TPU,还有很多特性有待读者的测试,例如支持 TPU 的 PyTorch 1.0 或循环 神经网络 在 TPU 上的性能等。
参考资料:
-
文档:https://www.tensorflow.org/api_docs/python/tf/contrib/tpu
-
官方示例(Keras):https://colab.research.google.com/github/tensorflow/tpu/blob/master/tools/colab/shakespeare_with_tpu_and_keras.ipynb
-
官方示例(TPUEstimator):https://colab.research.google.com/github/tensorflow/tpu/blob/master/tools/colab/shakespeare_with_tpuestimator.ipynb
-
Sam Wittevee PPT:https://www.dropbox.com/s/jg7j07unw94wbom/ Tensor Flow%20Keras%20Colab%20TPUs.pdf?dl=0
-
Ceshine Lee 博客:https://medium.com/the-artificial-impostor/keras-for-tpus-on-google-colaboratory-free-7c00961fed69