自动并行计算

“异步计算” 一节里我们提到 MXNet 后端会自动构建计算图。通过计算图,系统可以知道所有计算的依赖关系,并可以选择将没有依赖关系的多个任务并行执行来获得性能的提升。以 “异步计算” 一节中的计算图(图 8.1)为例。其中 a = nd.ones((1, 2))b = nd.ones((1, 2)) 这两步计算之间并没有依赖关系。因此,系统可以选择并行执行它们。

通常一个运算符会用掉一个 CPU/GPU 上所有计算资源。例如,dot 操作符会用到所有 CPU(即使是有多个 CPU)或单个 GPU 上所有线程。因此在单 CPU/GPU 上并行运行多个运算符可能效果并不明显。本节中探讨的自动并行计算主要关注 CPU 和 GPU 的并行计算,以及计算和通讯的并行。

首先导入本节中实验所需的包或模块。注意,我们需要至少一个 GPU 才能运行本节实验。

In [1]:
import mxnet as mx
from mxnet import nd
from time import time

CPU 和 GPU 的并行计算

我们先介绍 CPU 和 GPU 的并行计算,例如程序中的计算既发生在 CPU,又发生在 GPU 之上。

先定义一个函数,令它做 10 次矩阵乘法。

In [2]:
def run(x):
    return [nd.dot(x, x) for _ in range(10)]

接下来,分别在 CPU 和 GPU 上创建 NDArray。

In [3]:
x_cpu = nd.random.uniform(shape=(2000, 2000))
x_gpu = nd.random.uniform(shape=(6000, 6000), ctx=mx.gpu(0))

然后,分别使用它们在 CPU 和 GPU 上运行 run 函数并打印所需时间。

In [4]:
run(x_cpu) # 预热开始。
run(x_gpu)
nd.waitall() # 预热结束。

start = time()
run(x_cpu)
nd.waitall()
print('run on CPU: %f sec' % (time()-start))

start = time()
run(x_gpu)
nd.waitall()
print('run on GPU: %f sec' % (time()-start))
run on CPU: 1.226887 sec
run on GPU: 1.186589 sec

我们去掉 run(x_cpu)run(x_gpu) 两个计算任务之间的 nd.waitall(),希望系统能自动并行这两个任务。

In [5]:
start = time()
run(x_cpu)
run(x_gpu)
nd.waitall()
print('run on both CPU and GPU: %f sec' % (time()-start))
run on both CPU and GPU: 1.245450 sec

可以看到,当两个计算任务一起执行时,执行总时间小于它们分开执行的总和。这表示,MXNet 能有效地在 CPU 和 GPU 上自动并行计算。

计算和通讯的并行计算

在多 CPU/GPU 计算中,我们经常需要在 CPU/GPU 之间复制数据,造成数据的通讯。举个例子,在下面例子中,我们在 GPU 上计算,然后将结果复制回 CPU。我们分别打印 GPU 上计算时间和 GPU 到 CPU 的通讯时间。

In [6]:
def copy_to_cpu(x):
    return [y.copyto(mx.cpu()) for y in x]

start = time()
y = run(x_gpu)
nd.waitall()
print('run on GPU: %f sec' % (time() - start))

start = time()
copy_to_cpu(y)
nd.waitall()
print('copy to CPU: %f sec' % (time() - start))
run on GPU: 1.192017 sec
copy to CPU: 0.525574 sec

我们去掉计算和通讯之间的 waitall 函数,打印这两个任务完成的总时间。

In [7]:
start = time()
y = run(x_gpu)
copy_to_cpu(y)
nd.waitall()
t = time() - start
print('run on GPU then copy to CPU: %f sec' % (time() - start))
run on GPU then copy to CPU: 1.236424 sec

可以看到,执行计算和通讯的总时间小于两者分别执行的耗时之和。需要注意的是,这个计算并通讯的任务不同于前面多 CPU/GPU 的并行计算中的任务。这里的运行和通讯之间有依赖关系:y[i] 必须先计算好才能复制到 CPU。所幸的是,在计算 y[i] 的时候系统可以复制 y[i-1],从而减少计算和通讯的总运行时间。

小结

  • MXNet 能够通过自动并行计算提升计算性能,例如 CPU 和 GPU 的并行以及计算和通讯的并行。

练习

  • 本节中定义的 run 函数里做了 10 次运算。它们之间也没有依赖关系。看看 MXNet 有没有自动并行执行它们。
  • 试试包含更加复杂的数据依赖的计算任务。MXNet 能不能得到正确结果并提升计算性能?

扫码直达 讨论区