Утечка памяти CoreML в iOS 14.5

В своем приложении я использовал VNImageRequestHandler с настраиваемой моделью MLModel для обнаружения объектов.

Приложение отлично работает с версиями iOS до 14.5.

Когда вышла iOS 14.5, все сломалось.

  1. Каждый раз при возникновении ошибки (Error Domain = com.apple.vis Code = 11 «обнаружено неизвестное исключение» UserInfo = {NSLocalizedDescription = обнаружено неизвестное исключение}) pixelBuffer память удерживается и никогда не освобождается, буферы AVCaptureOutput были заполнены, а новый кадр не пришел.
  2. Мне нужно изменить код, как показано ниже, скопировав pixelBuffer в другую переменную, я решил проблему, заключающуюся в том, что новый кадр не поступает, но проблема с утечкой памяти все еще возникает.

Из-за утечки памяти приложение через некоторое время вылетало.

Обратите внимание, что до 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
  }
}

Кто-нибудь испытывал такую ​​же проблему, как вы, ребята, ее исправляете?

3 ответа

Бета-версия iOS 14.7, доступная на портале для разработчиков, похоже, устранила эту проблему.

У меня есть частичное исправление с помощью библиотеки @Matthijs Hollemans CoreMLHelpers.

В моей модели 300 классов и 2363 якоря. Я использовал много кода Matthijs предоставленного здесь , чтобы преобразовать модель в MLModel.

На последнем этапе конвейер строится с использованием 3 подмоделей: raw_ssd_output, декодера и nms. Для этого обходного пути вам необходимо удалить nms модель из конвейера, а вывод raw_confidence а также raw_coordinates.

В свое приложение вам нужно добавить код из CoreMLHelpers.

Затем добавьте эту функцию для декодирования вывода из вашей модели ML:

          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, чтобы найти максимальную оценку и лучший класс для каждого блока привязки.

В моем случае это помогло отключить нейронный движок, заставив CoreML работать только на CPU и GPU. Часто это происходит медленнее, но не вызывает исключения (по крайней мере, в нашем случае). В конце мы внедрили политику, запрещающую запускать некоторые из наших моделей на нейронном движке для определенных устройств iOS.

См. MLModelConfiguration.computeUntis, чтобы ограничить аппаратную модель, которую может использовать coreml.

Другие вопросы по тегам