Mac上使用GPU进行机器学习加速

2017年时,初次接触机器学习,当时不记得tensorflow是什么版本了,是不支持AMD GPU进行加速的,对于一个是Macbook Pro 15寸带AMD独显的人来说,入门训练很不友好。

我相信只要试过GPU加速训练的感觉,是绝对不会再想使用CPU进行训练了,即使是验证想法,CPU训练也太慢了。想起了多年前为了让本机的Tensorflow支持Intel的向量指令集,还要本地编译源码好久,然后比起原来也没多大提升,甚至浮点运算精度还下降了,都是泪。

使用Mac的GPU进行ML训练的意义

相信很多人会说,有谁会用Mac进行训练?Mac写代码,服务器多个卡一起跑不好吗?

的确,对于有这个多卡服务器资源的人来说,Mac训练毫无意义,装了科学计算卡的服务器显存大得多,浮点计算能力也强。但是对于刚刚入门的新手,很少的实验室老板不会爽快地给你服务器资源进行训练,很多学生都是在自己的电脑上折腾入门的,这时只有Mac的同学就欲哭无泪的。

我之前入门的一些ML课,服务资源有这样几个来源 1. Google Cloud Platform 提供300刀的免费额度,可以开带科学计算卡GPU的云服务器,这个很爽。 2. 一些课提供GPU平台,这个是良心的。 3. 厚脸皮地问实验室老师蹭服务器,但是很少会爽快地给资源来提升你个人

对于很多人来说,有自己的电脑的一个GPU可以加速训练,无论是入门学习,还是验证想法都是有益的。

支持Mac的GPU的机器学习后端 PlaidML 前端Keras

PlaidML 是Intel开发的机器学习后端,2018年发布。目前支持Keras、nGraph等前端平台。github网址为https://github.com/plaidml/plaidml 这意味着,使用Keras的项目几乎可以无成本地将后端从tensorflow切换到PlaidML。 使用Tesorflow的Keras前端的项目,迁移也是较容易的。2019年末看到了PlaidML社区中关于对tensorflow支持的讨论,但是目前还没有发布成果。 PlaidML 支持Metal、OpenCL等并行计算框架,这意味着Mac上的显卡(包括核显、A卡、N卡)都能得到很好的支持。

PlaidML安装流程

macOS官方安装教程https://plaidml.github.io/plaidml/docs/install.html#macos 十分简单,就四五行shell命令,我的Macbook Pro安装时没有碰到bug。 首先创建一个python虚拟环境,如果还不知道什么是python虚拟环境的建议先去搜索virtualenv是什么,虚拟环境十分有用,可以避免把你电脑上的python环境搞乱,我见过很多入门的人各种包版本出错(包括使用conda的),因此了解python虚拟环境的使用是必要的,也很简单。 下面的代码做了两件事,第一行创建虚拟环境,环境文件在名为plaidml-venv的文件目录中,第二行是激活该环境,激活之后的python调用都是使用的该目录下的python环境,pip安装也都是安装到该目录下。

1
2
python3 -m venv plaidml-venv
source plaidml-venv/bin/activate
然后安装带PlaidML的Keras
1
pip install -U plaidml-keras
然后设置PlaidML
1
plaidml-setup
然后会有三个选择, 第一个是问你要不要Dev模式,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Default Config Devices:
llvm_cpu.0 : CPU (via LLVM)
metal_amd_radeon_pro_560.0 : AMD Radeon Pro 560 (Metal)
metal_intel(r)_hd_graphics_630.0 : Intel(R) HD Graphics 630 (Metal)

Experimental Config Devices:
llvm_cpu.0 : CPU (via LLVM)
opencl_amd_radeon_pro_560_compute_engine.0 : AMD AMD Radeon Pro 560 Compute Engine (OpenCL)
opencl_intel_hd_graphics_630.0 : Intel Inc. Intel(R) HD Graphics 630 (OpenCL)
metal_amd_radeon_pro_560.0 : AMD Radeon Pro 560 (Metal)
metal_intel(r)_hd_graphics_630.0 : Intel(R) HD Graphics 630 (Metal)

Using experimental devices can cause poor performance, crashes, and other nastiness.

Enable experimental device support? (y,n)[n]:
为了速度与稳定性,当然是选择n

第二个问题问你用什么设备进行默认计算,如下

1
2
3
4
5
6
7
8
Multiple devices detected (You can override by setting PLAIDML_DEVICE_IDS).
Please choose a default device:

1 : llvm_cpu.0
2 : metal_amd_radeon_pro_560.0
3 : metal_intel(r)_hd_graphics_630.0

Default device? (1,2,3)[1]:
为了速度,选择amd显卡 最后一个问题问你是不是保存设置到用户文件夹,保存就是了
1
Save settings to /Users/***/.plaidml? (y,n)[y]:y
然后就安装好了,可以用下面的命令测试一下MobileNet的运行效果
1
2
pip install plaidml-keras plaidbench
plaidbench keras mobilenet

测试训练神经网络

当然要用一个简单的神经网络训练一下测试 就使用mnist数据机跑个简单的cnn测试一下,测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import numpy as np
import os
import time

os.environ["KERAS_BACKEND"] = "plaidml.keras.backend"

import keras

from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K

batch_size = 256
num_classes = 10
epochs = 10

# input image dimensions
img_rows, img_cols = 28, 28

# the data, split between train and test sets
(x_train, y_train), (x_test, y_test) = mnist.load_data()

if K.image_data_format() == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
input_shape = (1, img_rows, img_cols)
else:
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

# convert class vectors to binary class matrices
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model = Sequential()
model.add(Conv2D(16, kernel_size=(3, 3), activation='relu', input_shape=input_shape))
model.add(Dropout(0.5))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(32, kernel_size=(3, 3), activation='relu'))
model.add(Dropout(0.5))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.Adadelta(),
metrics=['accuracy'])
train_start_t = time.time()
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
train_end_t = time.time()
train_dur_t = train_end_t - train_start_t
print("train use time %f s" % train_dur_t)
test_start_t = time.time()
score = model.evaluate(x_test, y_test, verbose=0)
test_end_t = time.time()
test_dur_t = test_end_t - test_start_t
print("test use time %f s" % test_dur_t)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

训练过程输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Using plaidml.keras.backend backend.
x_train shape: (60000, 28, 28, 1)
60000 train samples
10000 test samples
INFO:plaidml:Opening device "metal_amd_radeon_pro_560.0"
Train on 60000 samples, validate on 10000 samples
Epoch 1/10
60000/60000 [==============================] - 9s 145us/step - loss: 0.4366 - acc: 0.8646 - val_loss: 0.4369 - val_acc: 0.9487
Epoch 2/10
60000/60000 [==============================] - 7s 122us/step - loss: 0.1542 - acc: 0.9539 - val_loss: 0.2412 - val_acc: 0.9733
Epoch 3/10
60000/60000 [==============================] - 7s 124us/step - loss: 0.1192 - acc: 0.9636 - val_loss: 0.2171 - val_acc: 0.9778
Epoch 4/10
60000/60000 [==============================] - 7s 121us/step - loss: 0.1019 - acc: 0.9683 - val_loss: 0.1783 - val_acc: 0.9803
Epoch 5/10
60000/60000 [==============================] - 7s 119us/step - loss: 0.0904 - acc: 0.9722 - val_loss: 0.1661 - val_acc: 0.9831
Epoch 6/10
60000/60000 [==============================] - 7s 119us/step - loss: 0.0822 - acc: 0.9749 - val_loss: 0.1371 - val_acc: 0.9839
Epoch 7/10
60000/60000 [==============================] - 7s 119us/step - loss: 0.0756 - acc: 0.9766 - val_loss: 0.1437 - val_acc: 0.9829
Epoch 8/10
60000/60000 [==============================] - 7s 121us/step - loss: 0.0722 - acc: 0.9776 - val_loss: 0.1240 - val_acc: 0.9859
Epoch 9/10
60000/60000 [==============================] - 7s 119us/step - loss: 0.0667 - acc: 0.9794 - val_loss: 0.1159 - val_acc: 0.9870
Epoch 10/10
60000/60000 [==============================] - 7s 119us/step - loss: 0.0641 - acc: 0.9799 - val_loss: 0.1299 - val_acc: 0.9877
train use time 73.713526 s
test use time 1.666734 s
Test loss: 0.1298656805753708
Test accuracy: 0.9877
跑下来一个epoch用7s作用,虽然比核弹卡要慢一些,但是还是很快的。

我之前用nvidia-tesla-p100,使用tensorflow.keras跑这个网络,100个epoch花了227s,现在使用AMD Radeon Pro 560,100个epoch 734s。

这样看小数据的训练也只是核弹卡3倍时间,尚能接受,当然由于显存原因,大的网络和数据就不太合适,新的Macbook Pro有8G显存的Radeon 5500M Pro,应该会好些,iMac、iMac Pro和Mac Pro就更不在话下了,但是我想应该没有什么人买个iMac Pro来做机器学习的。

使用CPU的tensorflow与使用CPU、核显、独立GPU的PlaidML进行对比,1个epoch训练时间如下

tf CPU Intel CPU 2.9GHz 6Thread 4Core Intel hd graphics 630 AMD Radeon Pro 560
21.39 111.47s 28.69s 7.34s

首先可以发现,tensorflow的CPU向量加速做的还是比PlaidML做的好的,可能PlaidML没有过多地投注精力在CPU加速上。独立GPU的训练速度是使用CPU的tesorflow的3倍,加速效果明显。另外使用核显训练时loss有些问题,可能对Intel 核显的支持还不够完善。 另外,这里的网络比较小,当我扩大网络规模时,gpu加速的效果更加明显。

总结

使用Mac上的GPU训练优点师能够帮助ML初学者快速入门、炼丹师方便地验证想法,对Keras、nGraph框架支持较好。 缺点是目前对tensorflow、pytorch的支持还不够完善。