使用两个输入实现 CoreML 自定义层

Posted

技术标签:

【中文标题】使用两个输入实现 CoreML 自定义层【英文标题】:Implement CoreML Custom Layer With Two Inputs 【发布时间】:2019-02-22 22:40:36 【问题描述】:

我有一个要转换为 CoreML 的 tensorflow 图,但它使用了一些缺少的操作,我必须将其实现为自定义层。

我现在关注的两个操作是SinFloorDiv

Sin 非常简单,我可以关注this tutorial,并且我有一个可以工作的 Swift 类和 Metal 内核来完成这项工作,我用一个玩具 coreml 文件对其进行了测试:

import Foundation
import CoreML
import Accelerate

@objc(Sin) class Sin: NSObject, MLCustomLayer 

    let sinPipeline: MTLComputePipelineState

    required init(parameters: [String : Any]) throws 
        print(#function, parameters)

        let sinFunction = GPUDispatch.sharedInstance.library.makeFunction(name: "sin")!
        sinPipeline = try! GPUDispatch.sharedInstance.device.makeComputePipelineState(
            function: sinFunction)


        super.init()
    

    func setWeightData(_ weights: [Data]) throws 
        print(#function, weights)
    


    func outputShapes(forInputShapes inputShapes: [[NSNumber]]) throws
        -> [[NSNumber]] 
            print(#function, inputShapes)
            return inputShapes
    

    func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws 

        for i in 0..<inputs.count 
            let input = inputs[i]
            let output = outputs[i]

            var count = Int32(input.count)
            let iptr = UnsafeMutablePointer<Float>(OpaquePointer(input.dataPointer))
            let optr = UnsafeMutablePointer<Float>(OpaquePointer(output.dataPointer))

            vvsinf(optr, iptr, &count)
        

    

    func encode(commandBuffer: MTLCommandBuffer,
                inputs: [MTLTexture], outputs: [MTLTexture]) throws 
        if let encoder = commandBuffer.makeComputeCommandEncoder() 
            for i in 0..<inputs.count 
                encoder.setTexture(inputs[i], index: 0)
                encoder.setTexture(outputs[i], index: 1)
                encoder.dispatch(pipeline: sinPipeline, texture: inputs[i])
                encoder.endEncoding()
            
        
    


Sin.metal:

kernel void sin(
                  texture2d_array<half, access::read> inTexture [[texture(0)]],
                  texture2d_array<half, access::write> outTexture [[texture(1)]],
                  ushort3 gid [[thread_position_in_grid]])

    if (gid.x >= outTexture.get_width() ||
        gid.y >= outTexture.get_height()) 
        return;
    

    const float4 x = float4(inTexture.read(gid.xy, gid.z));
    const float4 y = sin(x);
    outTexture.write(half4(y), gid.xy, gid.z);

我不明白的是,如果自定义层有两个输入,这将如何工作,例如我需要 FloorDiv,它返回 floor(x / y)

我将如何调整我提供的Sin 类来生成类似sin(x*y) 的东西,即使它只是在CPU 上?这类事情还有其他好的教程吗?

【问题讨论】:

【参考方案1】:

模式与我预期的不同,但现在我已经对代码进行了更多尝试,这一点非常明显。

这是一个实现FloorDiv的类:

import Foundation
import CoreML
import Accelerate

@objc(FloorDiv) class FloorDiv: NSObject, MLCustomLayer 

    let floorDivPipeline: MTLComputePipelineState

    required init(parameters: [String : Any]) throws 
        print(#function, parameters)

        let floorDivFunction = GPUDispatch.sharedInstance.library.makeFunction(name: "floordiv")!
        floorDivPipeline = try! GPUDispatch.sharedInstance.device.makeComputePipelineState(
            function: floorDivFunction)

        super.init()
    

    func setWeightData(_ weights: [Data]) throws 
        print(#function, weights)
    

    func outputShapes(forInputShapes inputShapes: [[NSNumber]]) throws
        -> [[NSNumber]] 
            print(#function, inputShapes)
            return inputShapes
    

    func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws 

        let numerator = inputs[0]
        let denominator = inputs[1]
        var output = outputs[0]


        assert(numerator.count == denominator.count)

        var count = Int32(numerator.count)
        let numerator_ptr = UnsafeMutablePointer<Float>(OpaquePointer(numerator.dataPointer))
        let denominator_ptr = UnsafeMutablePointer<Float>(OpaquePointer(denominator.dataPointer))

        let output_ptr = UnsafeMutablePointer<Float>(OpaquePointer(output.dataPointer))

        vvdivf(output_ptr, numerator_ptr, denominator_ptr, &count)
        vvfloorf(output_ptr, output_ptr, &count)

    


    func encode(commandBuffer: MTLCommandBuffer,
                inputs: [MTLTexture], outputs: [MTLTexture]) throws 

        if let encoder = commandBuffer.makeComputeCommandEncoder() 

                encoder.setTexture(inputs[0], index: 0)
                encoder.setTexture(inputs[1], index: 1)
                encoder.setTexture(outputs[0], index: 2)

                encoder.dispatch(pipeline: floorDivPipeline, texture: inputs[0])
                encoder.endEncoding()

        
    


这是金属内核:

#include <metal_stdlib>
using namespace metal;

kernel void floordiv(
                 texture2d_array<half, access::read> inTexture [[texture(0)]],
                 texture2d_array<half, access::read> inTexture2 [[texture(1)]],
                 texture2d_array<half, access::write> outTexture [[texture(2)]],
                 ushort3 gid [[thread_position_in_grid]])

    if (gid.x >= outTexture.get_width() ||
        gid.y >= outTexture.get_height()) 
        return;
    

    const float4 x = float4(inTexture.read(gid.xy, gid.z));
    const float4 x2 = float4(inTexture2.read(gid.xy, gid.z));
    const float4 y = floor(x / x2);
    outTexture.write(half4(y), gid.xy, gid.z);

【讨论】:

以上是关于使用两个输入实现 CoreML 自定义层的主要内容,如果未能解决你的问题,请参考以下文章

带有自定义层的 CoreML 在带有 Apple 神经引擎的设备上存在错误

在 CoreML 中为循环网络定义可选输入

Core ML 上具有两个参数功能的自定义层

CoreML:为 ONNX RandomNormal 创建自定义层

用于 AudioKit DSP 的 CoreML

如何将使用 Mask Rcnn 在自定义对象检测上创建蒙版图像的 Keras 模型转换为 CoreML 模型以在 iOS 应用程序中使用?