【问题标题】:newComputePipelineStateWithFunction failednewComputePipelineStateWithFunction 失败
【发布时间】:2015-11-18 13:13:30
【问题描述】:

我试图让神经网络在金属上运行。 基本思想是数据复制。每个 gpu 线程为随机数据点运行一个网络版本。

我编写了其他可以正常工作的着色器。

我还在 C++ 命令行应用程序中尝试了我的代码。那里没有错误。 也没有编译错误。

我使用苹果文档转换为金属 c++,因为并非所有来自 c++11 的内容都受支持。

它在加载内核函数并尝试将newComputePipelineStateWithFunction 分配给金属设备时崩溃。这意味着在编译时未捕获的代码存在问题。

MCVE:

kernel void net(const device float *inputsVector [[ buffer(0) ]], // layout of net *
                uint id [[ thread_position_in_grid ]]) {

    uint floatSize = sizeof(tempFloat);
    uint inputsVectorSize = sizeof(inputsVector) / floatSize;

    float newArray[inputsVectorSize];


    float test = inputsVector[id];

    newArray[id] = test;

}

更新

它与动态数组有关。

由于它无法创建管道状态并且在运行实际着色器时不会崩溃,这一定是编码问题。不是输入问题。

将动态数组中的值分配给缓冲区会导致失败。

【问题讨论】:

  • 您问题中的文字数量令人恐惧。我确实阅读了所有内容,但仍然无法回答非常基本的问题:代码的最小可重现示例在哪里,以及您对“不工作”的理解是什么。
  • 您应该将所有内容替换为重现错误的最少代码,并准确描述错误是什么。
  • 你熟悉金属吗?然后您知道目前无法查明导致错误的代码行,或获取详细状态并找到破坏程序的值。无论如何,我在解决这个问题一天后问了这个问题。我不断添加我尝试并发现的内容。对文字数量感到抱歉,但由于这是全新的领域,最好添加更多信息。
  • 标题中清楚地说明了确切的错误。在示例中可以找到攻击性代码。添加了完整但大大简化的着色器,以便其他人可以快速尝试并重现我的崩溃。代码中添加了注释以显示我在哪里找到了导致崩溃的部分。
  • 从您的个人资料中,我猜您不会为 mac 编写代码。所以你不知道金属会带来什么痛苦。如果我们能保持主题而不是形式,我将不胜感激。当苹果公司能够理解某条管道失败的原因时,将会有很多很好的问题。在那之前,它会很混乱,令人沮丧,而且没有很多答案。也许我很幸运,有人已经遇到过类似的问题。

标签: c++ metal


【解决方案1】:

真正的问题: 这是内存问题!

对于所有说这是内存问题的人,你是对的! 这是一些伪代码来说明它。抱歉,它在“Swift”中,但更易于阅读。金属着色器有一种时髦的方式来实现。它们首先在没有值的情况下初始化以获取内存。正是这一步失败了,因为它依赖于后面的步骤:设置缓冲区。

这一切都取决于什么时候可以使用哪些值。我对newComputePipelineStateWithFunction 的理解是错误的。它不仅仅是获取着色器功能。这也是初始化过程中的一小步。

class MetalShader {

    // buffers
    var aBuffer : [Float]
    var aBufferCount : Int

    // step One : newComputePipelineStateWithFunction
    memory init() {
        // assign shader memory

        // create memory for one int
        let aStaticValue : Int
        // create memory for one int
        var aNotSoStaticValue : Int // this wil succeed, assigns memory for one int

        // create memory for 10 floats
        var aStaticArray : [Float] = [Float](count: aStaticValue, repeatedValue: y) // this will succeed

        // create memory for x floats
        var aDynamicArray : [Float] = [Float](count: aBuffer.count, repeatedValue: y) // this will fail
        var aDynamicArray : [Float] = [Float](count: aBufferCount, repeatedValue: y) // this will fail

        let tempValue : Float // one float from a loop

    }

    // step Two : commandEncoder.setBuffer()
    assign buffers (buffers) {

        aBuffer = cpuMemoryBuffer

    }

    // step Three : commandEncoder.endEncoding()
    actual init() {
        // set shader values

        let aStaticValue : Int = 0

        var aNotSoStaticValue : Int = aBuffer.count

        var aDynamicArray : [Float] = [Float](count: aBuffer.count, repeatedValue: 1) // this could work, but the app already crashed before getting to this point.

    }

    // step Four : commandBuffer.commit()
    func shaderFunction() {
        // do stuff
        for i in 0..<aBuffer.count {

            let tempValue = aBuffer[i]

        }
    }
}

修复:

我终于意识到缓冲区在技术上是动态数组,而不是在着色器中创建数组,我还可以添加更多缓冲区。这显然有效。

【讨论】:

    【解决方案2】:

    我认为你的问题在于这一行:

    uint schemeVectorSize = sizeof(schemeVector) / uintSize;
    

    这里schemeVector动态 所以as in classic C++ 你不能在动态数组上使用sizeof 来获取元素的数量。 sizeof 仅适用于您在金属着色器代码中本地/静态定义的数组。

    想象一下它在内部是如何工作的:在编译时,Metal 编译器应该将sizeof 调用转换为一个常量......但他不能,因为schemeVector 是你的着色器的一个参数,因此可以有任何大小...

    所以对我来说,解决方案是在代码的 C++/ObjectiveC/Swift 部分计算 schemeVectorSize,并将其作为参数传递给着色器(作为 OpenGLES 术语中的统一...)。

    【讨论】:

    • 我对编译器的工作原理以及 Apple 如何决定让它不知道许多容易捕获的错误进行了一些研究。我相信代码不会被视为错误,因为您可以静态声明 uint 并使用它来设置多个数组的大小。但我认为newComputePipelineStateWithFunction 也确实为着色器中声明的所有内容保留了内存。因此,将 schemeVectorSize 作为参数传递不会(也不会)起作用。它仍然不知道要为代码的非缓冲部分保留多少内存。
    • 我的意思是uint schemeVectorSize = sizeof(schemeVector) / uintSize;等于uint schemeVectorSize = sizeof(const device uint*) / uintSize;,这可能会给你schemeVectorSize等于1或2......所以这可能根本不是你的期待……你有没有试过用离线Metal编译器来编译shader,看看有没有错误?
    • 另见here
    • 正确答案应该是“是的,你可以”。您可以获取缓冲区的大小并将其用于循环或 if 语句。您不能使用它来构建动态数组。因为,据我所知,着色器中不是缓冲区的每个实例都必须在分配缓冲区之前创建。
    • Menke,你不能在着色器代码中调用 sizeof(pointer) 并期望它工作。您必须将常量缓冲区大小作为常量空间值传递给着色器调用,通常包装在结构中。着色器代码不知道创建后稍后将传递给着色器的缓冲区的长度。
    猜你喜欢
    • 2016-10-02
    • 1970-01-01
    • 1970-01-01
    • 2014-10-20
    • 2016-01-22
    • 1970-01-01
    • 2013-02-24
    • 2011-01-17
    • 1970-01-01
    相关资源
    最近更新 更多