【问题标题】:Swift, macOS, mac with 2 GPUs, matrix operations work on one GPU, not on the otherSwift、macOS、带有 2 个 GPU 的 mac,矩阵运算在一个 GPU 上工作,而不是在另一个 GPU 上工作
【发布时间】:2020-07-20 07:04:00
【问题描述】:

根据 Apple 菜单中的“关于本机”,我在我的 MacBook Pro(Retina,15 英寸,2015 年中)上运行 macOS,其中有两个 GPU。一个 GPU 是 AMD Radeon R9 M370X 2 GB,另一个是 Intel Iris Pro 1536 MB——标准芯片,我猜?它们是我买的时候里面的芯片,我自己没有添加。

我正在使用 Swift MPS 库进行矩阵计算;它在 Intel GPU 上运行良好,但是当我选择 Radeon 时,我只会从每个操作中返回零,没有报告错误。我四处寻找有关它的文档,但找不到任何东西。到目前为止,我唯一的线索是 Radeon 报告“未集成”(或者至少,我认为它确实如此,基于 Finding GPUs on macOS 的示例代码,这与 Apple 的文档一样有用,这意味着 不是很)。如果我没看错那页,这就是我的两个 GPU 告诉我的。

Device Intel Iris Pro Graphics; caps: headful, not discrete, integrated, not external

Device AMD Radeon R9 M370X; caps: headful, discrete, not integrated, not external

我找不到任何可以表明我做错了什么的文档。我翻遍了 Apple 的 MPS 文档,但无济于事。正如我所说,代码在 Intel GPU 上运行良好,所以我认为它也可以在 Radeon 上运行。我已经运行了一些可下载的诊断工具来检查 Radeon,但它没有出现在这些工具的菜单中。所以我什至不知道这是我在代码中做错了什么,还是芯片本身坏了。

下面是代码,您可以通过粘贴到main.swift 将其构建为控制台应用程序。找到以下行:

let device = MTLCopyAllDevices()[1]

我用[0]代表Intel,[1]代表Radeon,你可以看到输出不同,即Radeon全为零。我想您的里程可能会因您的机器而异。我欢迎任何意见,干杯

import MetalPerformanceShaders

typealias MPSNumber = Float32

let MPSNumberSize = MemoryLayout<MPSNumber>.size
let MPSNumberTypeInGPU = MPSDataType.float32

class MPSNet {
    let commandBuffer: MTLCommandBuffer
    let commandQueue: MTLCommandQueue
    let device = MTLCopyAllDevices()[1]
    var neuronsInMatrix1: MPSMatrix?
    var neuronsInMatrix2: MPSMatrix?
    var neuronsOutMatrix: MPSMatrix?

    init() {
        guard let cq = device.makeCommandQueue() else { fatalError() }
        guard let cb = cq.makeCommandBuffer() else { fatalError() }

        commandQueue = cq
        commandBuffer = cb

        let cMatrices = 2
        let cRows = 1
        let cColumns = 3

        let sensoryInputs1: [MPSNumber] = [1, 2, 3]
        let sensoryInputs2: [MPSNumber] = [4, 5, 6]

        neuronsInMatrix1 = makeMatrix(device, sensoryInputs1)
        neuronsInMatrix2 = makeMatrix(device, sensoryInputs2)

        let rowStride = MPSMatrixDescriptor.rowBytes(fromColumns: cColumns, dataType: MPSNumberTypeInGPU)
        neuronsOutMatrix = makeMatrix(device, cRows, cColumnsOut: cColumns, rowStride: rowStride)

        let adder = MPSMatrixSum(
            device: device, count: cMatrices, rows: cRows, columns: cColumns, transpose: false
        )

        adder.encode(
            to: commandBuffer,
            sourceMatrices: [neuronsInMatrix1!, neuronsInMatrix2!],
            resultMatrix: neuronsOutMatrix!, scale: nil, offsetVector: nil,
            biasVector: nil, start: 0
        )

        commandBuffer.addCompletedHandler { _ in
            let motorOutputs = self.getComputeOutput(self.neuronsOutMatrix!)

            let discrete = !self.device.isLowPower && !self.device.isRemovable
            let caps = "\(self.device.isHeadless ? " headless" : " headful")" +
                       "\(discrete ? ", discrete" : ", not discrete")" +
                       "\(self.device.isLowPower ? ", integrated" : ", not integrated")" +
                       "\(self.device.isRemovable ? ", external" : ", not external")"

            print("Device \(self.device.name); caps:\(caps); motor outputs \(motorOutputs)")
        }
    }

    func compute() {
        commandBuffer.commit()
        commandBuffer.waitUntilCompleted()
    }
}

extension MPSNet {
    func getComputeOutput(_ matrix: MPSMatrix) -> [Double] {
        let rc = matrix.data.contents()
        return stride(from: 0, to: matrix.columns * MPSNumberSize, by: MPSNumberSize).map {
            offset in

            let rr = rc.load(fromByteOffset: offset, as: MPSNumber.self)

            return Double(rr)
        }
    }

    func loadMatrix(_ data: MTLBuffer, _ rawValues: [MPSNumber]) {
        let dContents = data.contents()

        zip(stride(from: 0, to: rawValues.count * MPSNumberSize, by: MPSNumberSize), rawValues).forEach { z in
            let (byteOffset, rawValue) = (z.0, MPSNumber(z.1))

            dContents.storeBytes(of: rawValue, toByteOffset: byteOffset, as: MPSNumber.self)
        }
    }

    func makeMatrix(_ device: MTLDevice, _ rawValues: [MPSNumber]) -> MPSMatrix {
        let rowStride = MPSMatrixDescriptor.rowBytes(
            fromColumns: rawValues.count, dataType: MPSNumberTypeInGPU
        )

        let descriptor = MPSMatrixDescriptor(
            dimensions: 1, columns: rawValues.count, rowBytes: rowStride,
            dataType: MPSNumberTypeInGPU
        )

        guard let inputBuffer = device.makeBuffer(
            length: descriptor.matrixBytes, options: MTLResourceOptions.storageModeManaged
        ) else { fatalError() }

        loadMatrix(inputBuffer, rawValues)

        return MPSMatrix(buffer: inputBuffer, descriptor: descriptor)
    }

    func makeMatrix(_ device: MTLDevice, _ cRowsOut: Int, cColumnsOut: Int, rowStride: Int) -> MPSMatrix {
        let matrixDescriptor = MPSMatrixDescriptor(
            dimensions: cRowsOut, columns: cColumnsOut,
            rowBytes: rowStride, dataType: MPSNumberTypeInGPU
        )

        return MPSMatrix(device: device, descriptor: matrixDescriptor)
    }
}

let net = MPSNet()
net.compute()

【问题讨论】:

  • 问题:在 Windows 上,当有多个可用 gpu 时,您必须分配一个应用程序/程序以在特定 gpu 上运行 - 或者您可以根据您的电源状态让所有内容在其中一个上运行(即 - 插入或电池)。从我在我的 Mac 上可以看出 - 我可以做出基于电源状态的选择。也许您可以尝试一下,告诉我当您在插入 AMD 时让所有东西都运行时会发生什么,然后我们可以检查是否可以进行基于应用程序的设置(如果您对此感兴趣)
  • 嘿,非常感谢!仅仅通过跟进你的领导,我学到的知识比我以前知道的要多一百倍。我尝试了该电源设置,并阅读了一些与之相关的文章,现在我开始认为芯片坏了。如果你愿意,我会及时通知你,这样我们就可以整理出一个答案,当我最终弄清楚一些事情时,你可以得到分数。干杯
  • 啊,芯片坏了?太糟糕了,MacBook Pro 上的一切都那么紧凑。我通常可以通过将热风枪对准主板上的 gpu 轻松验证芯片是否已损坏或芯片座是否由于过热而退化。如果它在接下来的 10 分钟内起作用并且失败了 - 是微球融化并再次移位和移位。但在你走这条疯狂的科学家/破产玩家路线之前,请询问具有类似工作环境的人,看看代码是否能在他们身上工作。
  • 我猜芯片没问题;我从 Apple 下载了一些用 Objective-C 编写的东西,我真的不知道,但我设法编译它并使用两个 GPU 很好地看到它。今天我会试着做更多的工作,以找出细节。干杯
  • @SamThomas 问题是我使用的是MTLResourceOptions.storageModeManaged 而不是MTLResourceOptions.storageModeShared。我改变了这一点,即使没有调用MPSMatrix.synchronize(on:),一切正常,如下面的答案之一所述。祝你好运

标签: swift macos gpu matrix-multiplication


【解决方案1】:

您似乎未能使用 -[MPSMatrix synchronizeOnCommandBuffer:]。在离散设备上,数据从 GPU 返回之前需要进行一些显式同步。

【讨论】:

    【解决方案2】:

    问题在于矩阵缓冲区的存储模式。您正在使用MTLResourceOptions.storageModeManaged,它告诉 Metal 您想要管理 CPU 和 GPU 之间共享的内存的同步。正如此处另一个答案中提到的,您必须在 GPU 操作之后使用MPSMatrix.synchronize(on: MTLCommandBuffer),然后才能尝试使用 CPU 读取数据。但是您还必须在另一个方向上进行同步,即在 CPU 操作之后,然后再将命令提交给 GPU,使用 MTLBuffer.didModifyRange(_: Range)

    或者,您可以使用共享存储模式MTLResourceOptions.storageModeShared,它会为您处理同步。

    有关详细信息,请参阅 Apple 文档中的 Synchronizing a Managed Resource

    下面是使用托管存储模式的示例的工作版本。注意函数MPSNet.compute() 的区别。如果您的应用可以使用共享存储模式,您可以在为您的矩阵创建 MTLBuffers 时将这些内容保留并更改存储模式。

    import MetalPerformanceShaders
    
    typealias MPSNumber = Float32
    
    let MPSNumberSize = MemoryLayout<MPSNumber>.size
    let MPSNumberTypeInGPU = MPSDataType.float32
    
    class MPSNet {
        let commandBuffer: MTLCommandBuffer
        let commandQueue: MTLCommandQueue
        let device = MTLCopyAllDevices()[1]
        var neuronsInMatrix1: MPSMatrix?
        var neuronsInMatrix2: MPSMatrix?
        var neuronsOutMatrix: MPSMatrix?
    
        init() {
            guard let cq = device.makeCommandQueue() else { fatalError() }
            guard let cb = cq.makeCommandBuffer() else { fatalError() }
    
            commandQueue = cq
            commandBuffer = cb
    
            let cMatrices = 2
            let cRows = 1
            let cColumns = 3
    
            let sensoryInputs1: [MPSNumber] = [1, 2, 3]
            let sensoryInputs2: [MPSNumber] = [4, 5, 6]
    
            neuronsInMatrix1 = makeMatrix(device, sensoryInputs1)
            neuronsInMatrix2 = makeMatrix(device, sensoryInputs2)
    
            let rowStride = MPSMatrixDescriptor.rowBytes(fromColumns: cColumns, dataType: MPSNumberTypeInGPU)
            neuronsOutMatrix = makeMatrix(device, cRows, cColumnsOut: cColumns, rowStride: rowStride)
    
            let adder = MPSMatrixSum(
                device: device, count: cMatrices, rows: cRows, columns: cColumns, transpose: false
            )
    
            adder.encode(
                to: commandBuffer,
                sourceMatrices: [neuronsInMatrix1!, neuronsInMatrix2!],
                resultMatrix: neuronsOutMatrix!, scale: nil, offsetVector: nil,
                biasVector: nil, start: 0
            )
    
            commandBuffer.addCompletedHandler { _ in
                let motorOutputs = self.getComputeOutput(self.neuronsOutMatrix!)
    
                let discrete = !self.device.isLowPower && !self.device.isRemovable
                let caps = "\(self.device.isHeadless ? " headless" : " headful")" +
                           "\(discrete ? ", discrete" : ", not discrete")" +
                           "\(self.device.isLowPower ? ", integrated" : ", not integrated")" +
                           "\(self.device.isRemovable ? ", external" : ", not external")"
    
                print("Device \(self.device.name); caps:\(caps); motor outputs \(motorOutputs)")
            }
        }
    
        func compute() {
            for matrix in [neuronsInMatrix1!, neuronsInMatrix2!, neuronsOutMatrix!] {
                let matrixData = matrix.data
                matrixData.didModifyRange(0..<matrixData.length)
    
                matrix.synchronize(on: commandBuffer)
            }
    
            commandBuffer.commit()
        }
    }
    
    extension MPSNet {
        func getComputeOutput(_ matrix: MPSMatrix) -> [Double] {
            let rc = matrix.data.contents()
            return stride(from: 0, to: matrix.columns * MPSNumberSize, by: MPSNumberSize).map {
                offset in
    
                let rr = rc.load(fromByteOffset: offset, as: MPSNumber.self)
    
                return Double(rr)
            }
        }
    
        func loadMatrix(_ data: MTLBuffer, _ rawValues: [MPSNumber]) {
            let dContents = data.contents()
    
            zip(stride(from: 0, to: rawValues.count * MPSNumberSize, by: MPSNumberSize), rawValues).forEach { z in
                let (byteOffset, rawValue) = (z.0, MPSNumber(z.1))
    
                dContents.storeBytes(of: rawValue, toByteOffset: byteOffset, as: MPSNumber.self)
            }
        }
    
        func makeMatrix(_ device: MTLDevice, _ rawValues: [MPSNumber]) -> MPSMatrix {
            let rowStride = MPSMatrixDescriptor.rowBytes(
                fromColumns: rawValues.count, dataType: MPSNumberTypeInGPU
            )
    
            let descriptor = MPSMatrixDescriptor(
                dimensions: 1, columns: rawValues.count, rowBytes: rowStride,
                dataType: MPSNumberTypeInGPU
            )
    
            guard let inputBuffer = device.makeBuffer(
                length: descriptor.matrixBytes, options: MTLResourceOptions.storageModeManaged
            ) else { fatalError() }
    
            loadMatrix(inputBuffer, rawValues)
    
            return MPSMatrix(buffer: inputBuffer, descriptor: descriptor)
        }
    
        func makeMatrix(_ device: MTLDevice, _ cRowsOut: Int, cColumnsOut: Int, rowStride: Int) -> MPSMatrix {
            let matrixDescriptor = MPSMatrixDescriptor(
                dimensions: cRowsOut, columns: cColumnsOut,
                rowBytes: rowStride, dataType: MPSNumberTypeInGPU
            )
    
            return MPSMatrix(device: device, descriptor: matrixDescriptor)
        }
    }
    
    let net = MPSNet()
    net.compute()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-23
      • 2014-06-03
      • 1970-01-01
      • 2020-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-27
      相关资源
      最近更新 更多