魔法师の高塔

Metal与并行数据计算处理

很久没写博客了,最近注意力集中在iOS的人脸特征识别上。

最近注意力集中在iOS的人脸特征识别上,找了几个Demo,运行起来效果虽然不错,但无疑地十分卡顿,毕竟计算量还是有点儿大。简单地了解了人脸识别、特征点识别、神经网络之类的知识,未曾深入,对这类知识大概轮廓还是有的。特别地,装了mxnet,用本子跑了一个neural-style的demo,很是一颗赛艇,速度很慢,因为没有计算加速,只有CPU,半个小时能跑出七十张图。GPU的浮点运算能力,远超CPU,故而针对一些计算任务,使用GPU是很自然的。
我在学习Metal这个框架,Metal是Apple公司提出的一个针对Apple设备的新的图形接口,能更有效率地利用GPU,iOS上的OpenGL已经被Metal包装起来的了。
以下内容是使用Metal的并行计算处理能力。
Metal的使用更符合我组织代码的能力,在条理上比OpenGL更容易理解。
首先初始化一些对象,MTLDevice是设备引用,其他对象都从此衍出。命令队列MTLCommandQueue,Shader库MTLLibrary,命令缓存MTLCommandBuffer,计算命令编码器MTLComputeCommandEncoder。

1
2
3
4
5
6
7
8
func initMetal() -> (MTLDevice, MTLCommandQueue, MTLLibrary, MTLCommandBuffer, MTLComputeCommandEncoder) {
let device = MTLCreateSystemDefaultDevice()
let commandQueue = device?.makeCommandQueue()
let defaultLibrary = device?.newDefaultLibrary()
let commandBuffer = commandQueue?.makeCommandBuffer()
let computeCommandEncoder = commandBuffer?.makeComputeCommandEncoder()
return (device!,commandQueue!,defaultLibrary!,commandBuffer!,computeCommandEncoder!)
}

在Shader文件里,有sigmoid函数实现,这个函数在神经网络中很常见。

1
2
3
4
5
6
7
8
#include <metal_stdlib>
using namespace metal;
kernel void sigmoid(const device float *inVector [[buffer(0)]],
device float *outVector [[buffer(1)]],
uint id [[ thread_position_in_grid]]){
outVector[id] = 1.0/(1.0+exp(-inVector[id]));
}

设置函数

1
2
3
4
var (device,commandQueue,defaultLibrary,commandBuffer,computeCommandEncoder) = initMetal()
let sigmoidProgram = defaultLibrary.makeFunction(name: "sigmoid")
let computePipelineState = try device.makeComputePipelineState(function: sigmoidProgram!)
computeCommandEncoder.setComputePipelineState(computePipelineState)

接下来准备输入数组与输出数组,数据在GPU与CPU之间拷贝。

1
2
3
4
5
6
7
8
9
10
11
var myvector = [Float](repeating:0,count:1000000)
for (index, value) in myvector.enumerated() {
myvector[index] = Float(arc4random()%1000)/1000.0
}
let myvectorByteLength = MemoryLayout<Float>.size*myvector.count
let inVectorBuffer = device.makeBuffer(bytes: myvector, length: myvectorByteLength, options: .cpuCacheModeWriteCombined);
computeCommandEncoder.setBuffer(inVectorBuffer, offset: 0, at: 0)
let resultData = [Float](repeating:0,count:1000000)
let outVectorBuffer = device.makeBuffer(bytes: resultData, length: myvectorByteLength, options: .cpuCacheModeWriteCombined)
computeCommandEncoder.setBuffer(outVectorBuffer, offset: 0, at: 1)

线程组规模设置

1
2
3
let threadsPerGroup = MTLSize(width: 32, height: 1, depth: 1)
let numThreadgroups = MTLSize(width: (myvector.count+31)/32, height: 1, depth: 1)
computeCommandEncoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup)

提交运行

1
2
3
computeCommandEncoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()

运行完成后,可以outVectorBuffer读出计算结果。

1
2
3
4
5
6
7
let data = Data(bytesNoCopy: outVectorBuffer.contents(), count: myvectorByteLength, deallocator: .none)
var finalResultArray = [Float](repeating:0, count:myvector.count)
(data as NSData).getBytes(&finalResultArray, length:myvectorByteLength)
var end = Date()
for i in 0..<myvector.count{
print(finalResultArray[i])
}