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_confidence
和 raw_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 内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章