iOS 14.5 中的 CoreML 内存泄漏

Posted

技术标签:

【中文标题】iOS 14.5 中的 CoreML 内存泄漏【英文标题】:CoreML Memory Leak in iOS 14.5 【发布时间】:2021-07-25 14:03:33 【问题描述】:

在我的应用程序中,我使用 VNImageRequestHandler 和自定义 MLModel 进行对象检测。

该应用在 14.5 之前的 ios 版本上运行良好。

当 iOS 14.5 到来时,它打破了一切。

    每当try handler.perform([visionRequest]) 抛出一个错误(Error Domain=com.apple.vis Code=11 "encountered unknown exception" UserInfo=NSLocalizedDescription=encountered unknown exception),pixelBuffer 内存被持有并且永远不会被释放,它使 AVCaptureOutput 的缓冲区已满,然后新帧未到来。 我必须更改代码如下,通过将pixelBuffer复制到另一个var,我解决了新帧不来的问题,但仍然发生内存泄漏问题。

由于内存泄漏,应用程序在一段时间后崩溃了。

请注意,在 iOS 14.5 版本之前,检测工作正常,try handler.perform([visionRequest]) 永远不会抛出任何错误。

这是我的代码:

private func predictWithPixelBuffer(sampleBuffer: CMSampleBuffer) 
  guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else 
    return
  
  
  // Get additional info from the camera.
  var options: [VNImageOption : Any] = [:]
  if let cameraIntrinsicMatrix = CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil) 
    options[.cameraIntrinsics] = cameraIntrinsicMatrix
  
  
  autoreleasepool 
    // Because of iOS 14.5, there is a bug that when perform vision request failed, pixel buffer memory leaked so the AVCaptureOutput buffers is full, it will not output new frame any more, this is a temporary work around to copy pixel buffer to a new buffer, this currently make the memory increased a lot also. Need to find a better way
    var clonePixelBuffer: CVPixelBuffer? = pixelBuffer.copy()
    let handler = VNImageRequestHandler(cvPixelBuffer: clonePixelBuffer!, orientation: orientation, options: options)
    print("[DEBUG] detecting...")
    
    do 
      try handler.perform([visionRequest])
     catch 
      delegate?.detector(didOutputBoundingBox: [])
      failedCount += 1
      print("[DEBUG] detect failed \(failedCount)")
      print("Failed to perform Vision request: \(error)")
    
    clonePixelBuffer = nil
  

有没有人遇到过同样的问题?如果是这样,您是如何解决的?

【问题讨论】:

Apple 回复了我的错误报告并要求我提供更多信息。我附上了源代码 + 你的 github 错误报告供他们检查。希望他们能尽快修复它。 我有 3 个自定义对象检测模型,转换代码遵循 MachineThink 博客中的@MatthijsHollemans 模板。两个模型可以识别 791 个类别,并且是 SSDLite,使用 MobileNet v2 和 v3L 作为特征提取器,它们都存在上述问题。但是,我有一个基于 SSDLIte+MobileNetv3L 识别单个类的 SSDLite 模型,它工作得很好。如果我从管道中删除 NMS 模型,所有 3 个版本都可以正常工作。问题似乎是多类非最大抑制 @James 感谢您提供的信息。请注意,可以将管道中的 NMS 模型替换为包含单个 NMS 层的神经网络模型。我很想知道这是否有效。如果没有,GitHub 上的 CoreMLHelpers 有一个 Swift 版本的 NMS,您可以调整它以使用该模型。 @RB's: 我的模型现在有 1 个班级,问题已经解决,也许你可以试试你的情况 @MatthijsHollemans 在我看来,我创建的任何 NMS 实现都有不受支持的 CoreML 操作。无论我尝试使用内置的tf.image.combined_non_max_suppression 还是创建自己的 TF 版本。我将测试 CoreMLHelpers 的快速实现(谢谢!),我唯一关心的是速度,但我可以进行基准测试 【参考方案1】:

开发者门户上提供的 iOS 14.7 Beta 似乎已解决此问题。

【讨论】:

我在我的应用程序中使用 yolov5 作为 .mlmodel 并经常收到此错误消息:Error Domain=com.apple.vis Code=11 "encountered unknown exception" UserInfo=NSLocalizedDescription=encountered unknown exception 。当我更新到 iOS 14.7.1 时,我无法重现这些错误,所以似乎可以工作【参考方案2】:

我使用@Matthijs Hollemans CoreMLHelpers 库对此进行了部分修复。

我使用的模型有 300 个类和 2363 个锚点。我使用了很多 Matthijs 提供的代码 here 将模型转换为 MLModel。

在最后一步中,使用 3 个子模型构建管道:raw_ssd_output、解码器和 nms。对于此解决方法,您需要从管道中删除 nms 模型,并输出 raw_confidenceraw_coordinates

在您的应用中,您需要添加来自CoreMLHelpers 的代码。

然后添加这个函数来解码你的 MLModel 的输出:

    func decodeResults(results:[VNCoreMLFeatureValueObservation]) -> [BoundingBox] 
        let raw_confidence: MLMultiArray = results[0].featureValue.multiArrayValue!
        let raw_coordinates: MLMultiArray = results[1].featureValue.multiArrayValue!
        print(raw_confidence.shape, raw_coordinates.shape)
        var boxes = [BoundingBox]()
        let startDecoding = Date()
        for anchor in 0..<raw_confidence.shape[0].int32Value 
            var maxInd:Int = 0
            var maxConf:Float = 0
            for score in 0..<raw_confidence.shape[1].int32Value 
                let key = [anchor, score] as [NSNumber]
                let prob = raw_confidence[key].floatValue
                if prob > maxConf 
                    maxInd = Int(score)
                    maxConf = prob
                
            
            let y0 = raw_coordinates[[anchor, 0] as [NSNumber]].doubleValue
            let x0 = raw_coordinates[[anchor, 1] as [NSNumber]].doubleValue
            let y1 = raw_coordinates[[anchor, 2] as [NSNumber]].doubleValue
            let x1 = raw_coordinates[[anchor, 3] as [NSNumber]].doubleValue
            let width = x1-x0
            let height = y1-y0
            let x = x0 + width/2
            let y = y0 + height/2
            let rect = CGRect(x: x, y: y, width: width, height: height)
            let box = BoundingBox(classIndex: maxInd, score: maxConf, rect: rect)
            boxes.append(box)
        
        let finishDecoding = Date()
        let keepIndices = nonMaxSuppressionMultiClass(numClasses: raw_confidence.shape[1].intValue, boundingBoxes: boxes, scoreThreshold: 0.5, iouThreshold: 0.6, maxPerClass: 5, maxTotal: 10)
        let finishNMS = Date()
        var keepBoxes = [BoundingBox]()
        
        for index in keepIndices 
            keepBoxes.append(boxes[index])
        
        print("Time Decoding", finishDecoding.timeIntervalSince(startDecoding))
        print("Time Performing NMS", finishNMS.timeIntervalSince(finishDecoding))
        return keepBoxes
    

然后,当您收到 Vision 的结果时,您可以像这样调用函数:

if let rawResults = vnRequest.results as? [VNCoreMLFeatureValueObservation] 
   let boxes = self.decodeResults(results: rawResults)
   print(boxes)

这个解决方案很慢,因为我移动数据和制定BoundingBox 类型列表的方式。使用底层指针处理 MLMultiArray 数据会更有效,并且可能使用 Accelerate 来找到每个锚框的最高分数和最佳类别。

【讨论】:

嘿詹姆斯!我在使用 MLModel 时也遇到了奇怪的内存峰值,甚至没有调用 predict。只需初始化他(2-3GB RAM 不做任何事情)。模型重量小于 ***.com/questions/67968988/…【参考方案3】:

在我的例子中,它通过强制 CoreML 仅在 CPU 和 GPU 上运行来帮助禁用神经引擎。这通常较慢,但不会引发异常(至少在我们的例子中)。最后,我们实施了一项政策,强制我们的一些模型不在某些 iOS 设备的神经引擎上运行。

请参阅MLModelConfiguration.computeUntis 以限制可使用的硬件 coreml 模型。

【讨论】:

谢谢@Michael。我已经尝试过了,但就我而言,它仍然会引发异常。我们正在尝试更改模型,希望它会起作用

以上是关于iOS 14.5 中的 CoreML 内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

iOS 或 MonoTouch 中的固有内存泄漏?

如何解决 iOS App 中的内存泄漏问题?

ios UIWebView中的大量内存泄漏

添加触摸事件时iOS webview中的内存泄漏

音频回调线程中的内存泄漏(iOS)

IOS性能调优系列:使用Instruments动态分析内存泄漏