Минимальные размеры матрицы, чтобы получить выгоду от умножения матриц на GPU - PullRequest
0 голосов
/ 06 августа 2020

Меня особенно интересует умножение матриц с использованием шейдеров Metal Performance, но ответы о других фреймворках тоже хороши.

Умножение матриц - теоретически очень распараллеливаемая операция. Мне нужно умножить много матриц на себя, например A 'A (где апостроф означает транспонирование). Размер матриц A составляет около 4000 x 300. Мне было интересно, стоит ли переносить код умножения на GPU, учитывая размер этих матриц. Насколько я понимаю, умножение на GPU также будет включать копирование данных из основной памяти в память GPU (я использую eGPU, поэтому память не используется совместно). Затем необходимо найти компромисс между дополнительными усилиями для копирования данных туда и обратно и ускорением вычислений. Итак, мой вопрос: при каких размерах матриц (приблизительно) я мог бы начать видеть преимущества выполнения этого на GPU? помогите, кое-что о медленном кешировании памяти (как правило, на всех GPU): https://graphics.stanford.edu/papers/gpumatrixmult/gpumatrixmult.pdf

Ответы [ 2 ]

0 голосов
/ 12 августа 2020

Я провел тест, и он значительно быстрее (x 8-9) на GPU для моего случая, даже включая копирование всей памяти с CPU на GPU и обратно. Я сравниваю производительность умножения матрицы float32, поскольку Metal не поддерживает float64.

let count = 100

let N = 7005
let K = 700

let DIV = 8
let K2 = (K / DIV) * DIV + (K % DIV > 0 ? 1 : 0) * DIV
let N2 = (N / DIV) * DIV + (N % DIV > 0 ? 1 : 0) * DIV

print(N2)
print(K2)

printTimeElapsedWhenRunningCode(title: "vDSP(f)") {
    
    let ATf = [Float].init(repeating: Float(1), count: N*K)
    let Af = [Float].init(repeating: Float(1), count: N*K)
    var C = Array(repeating: Float(0), count: K*K)

    for _ in 0..<count {

        vDSP_mmul(ATf, 1,
                  Af, 1,
                  &C, 1,
                  vDSP_Length(K),
                  vDSP_Length(K),
                  vDSP_Length(N))
    }
}

guard let bufferA = device.makeBuffer(length: K2 * N2 * MemoryLayout<Float>.stride,
                                      options: [.storageModeManaged]) else {
    fatalError("Could not make buffer A")
}

guard let bufferC = device.makeBuffer(length: K2 * K2 * MemoryLayout<Float>.stride,
                                      options: [.storageModeManaged]) else {
    fatalError("Could not make buffer C")
}

let descA = MPSMatrixDescriptor(dimensions: N2,
                                columns: K2,
                                rowBytes: K2 * MemoryLayout<Float>.stride,
                                dataType: .float32)

let descC = MPSMatrixDescriptor(dimensions: K2,
                                columns: K2,
                                rowBytes: K2 * MemoryLayout<Float>.stride,
                                dataType: .float32)

let matrixA = MPSMatrix(buffer: bufferA, descriptor: descA)
let matrixC = MPSMatrix(buffer: bufferC, descriptor: descC)

let matrixMultiplication = MPSMatrixMultiplication(device: device,
                                                   transposeLeft: true,
                                                   transposeRight: false,
                                                   resultRows: K2,
                                                   resultColumns: K2,
                                                   interiorColumns: N2,
                                                   alpha: 1,
                                                   beta: 0)

guard let commandQueue = device.makeCommandQueue() else {
    fatalError("Could not make command queue")
}

printTimeElapsedWhenRunningCode(title: "Metal") {
    
    let Af = [Float].init(repeating: Float(1), count: N*K)
    let zeros = [Float].init(repeating: Float(0), count: K2)

    for i in 0..<count {

        var dest = bufferA.contents()
        Af.withUnsafeBufferPointer { pA in
            var from = pA.baseAddress!
            for _ in 0..<N {
                dest.copyMemory(from: from, byteCount: K)
                dest += K
                if K2 > K {
                    dest.copyMemory(from: zeros, byteCount: K2 - K)
                    dest += K2 - K
                }
                from += K
            }
        }
        
        for _ in 0..<(N2-N) {
            dest.copyMemory(from: zeros, byteCount: K2)
        }
        
        bufferA.didModifyRange(0..<N2*K2)
        
        let commandBuffer = commandQueue.makeCommandBuffer()!

        matrixMultiplication.encode(commandBuffer: commandBuffer,
                                    leftMatrix: matrixA,
                                    rightMatrix: matrixA,
                                    resultMatrix: matrixC)

        let blitEncoder = commandBuffer.makeBlitCommandEncoder()!
        blitEncoder.synchronize(resource: bufferC)
        blitEncoder.endEncoding()
        
        commandBuffer.commit()

        if i == count - 1 {
            commandBuffer.waitUntilCompleted()
        }
    }
}

Вывод:

AMD Radeon RX 5700 XT
7008
704
Time elapsed for vDSP(f): 5.156805992126465 s.
Time elapsed for Metal: 0.6834449768066406 s.
DONE.
0 голосов
/ 06 августа 2020

Я рекомендую вам проверить раздел vDSP в платформе Apple Accelerate. У них есть очень быстрые функции SIMD для матрицы умножения и транспонирования .

Недавно они также добавили несколько Swift-friendly API .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...