Серьезная утечка оперативной памяти при запуске вывода OpenVINO на Raspberry Pi 5 (ARM) — даже с помощью infer().Python

Программы на Python
Ответить
Anonymous
 Серьезная утечка оперативной памяти при запуске вывода OpenVINO на Raspberry Pi 5 (ARM) — даже с помощью infer().

Сообщение Anonymous »

Я разрабатываю проект обнаружения объектов, работающий на Raspberry Pi 5.

Со стороны камеры (Picamera2/libcamera) все работает отлично: когда камера работает отдельно, использование оперативной памяти полностью стабильно.
Однако, как только я перехожу к этапу обнаружения объектов с помощью YOLO, появляется серьезная проблема. Даже после полного удаления Ultralytics YOLO и выполнения вывода непосредственно с помощью OpenVINO Runtime проблема сохраняется.
Важное наблюдение заключается в следующем:
  • Даже когда предварительная обработка изображения не выполняется
  • Даже когда кадры камеры не используются
  • Даже когда я неоднократно вызываю only infer() для статического ввода
  • Использование ОЗУ увеличивается на десятки мегабайт в секунду
Это ясно указывает на то, что проблема не вызвана:
  • цветом преобразование
  • изменение размера изображения
  • буферы камеры
  • Распределение NumPy
  • Предварительная обработка OpenCV
Вместо этого проблема, похоже, возникает из-за OpenVINO на ARM (Raspberry Pi) не освобождает память должным образом.
Такое поведение особенно воспроизводится с:
  • OpenVINO 2025.4.1
  • Python 3.13
  • Raspberry Pi 5 (ARM64)
На настольном ПК та же логика работает «нормально», но проблема, вероятно, маскируется большим объемом доступной оперативной памяти.
Чтобы изолировать проблему, я написал минимальный тест, в котором:
  • модель загружается один раз
  • тензоры используются повторно
  • новые массивы NumPy не выделяются
  • вывод выполняется в узком цикле
Даже в этом режиме изоляции память RSS постоянно увеличивается.
На этом этапе я рассматриваю возможность:
  • понижения OpenVINO
  • понижения Python
  • или полного отказа от OpenVINO и перехода на TFLite или NCNN
Прежде чем сделать это, я хотел бы понять:
  • Известна ли это утечка памяти OpenVINO на ARM?
  • Связано ли это с привязками Python 3.13?
  • Существует ли какой-либо рекомендуемый обходной путь или конфигурация для принудительного повторного использования памяти?
Ниже приведен минимальный вариант воспроизводимый пример, демонстрирующий проблему.
Пример кода утечки ОЗУ:
import sys
import os
import glob
import time
import argparse
import gc
import psutil
import cv2
import numpy as np
import openvino.runtime as ov

# --- CONFIGURATION ---
MODEL_DIR = "yolo11n_openvino_model"
CONF_THRESHOLD = 0.50
INPUT_W, INPUT_H = 640, 640 # Model Input Dimensions
CAM_W, CAM_H = 640, 480 # Camera Dimensions

def get_rss_mb():
process = psutil.Process(os.getpid())
return process.memory_info().rss / 1024 / 1024

class YoloZeroAlloc:
def __init__(self, model_dir):
self.core = ov.Core()

# Load Model
xml_files = glob.glob(os.path.join(model_dir, "*.xml"))
if not xml_files: raise FileNotFoundError(f"No .xml in {model_dir}")

print(f"Loading: {xml_files[0]}")
model = self.core.read_model(xml_files[0])

# Force Static Shape [1, 3, 640, 640]
print(f"Forcing Shape: [1, 3, {INPUT_H}, {INPUT_W}]")
model.reshape([1, 3, INPUT_H, INPUT_W])

self.compiled_model = self.core.compile_model(model, "CPU")
self.infer_request = self.compiled_model.create_infer_request()

# --- MEMORY POOLS (The Fix) ---
# 1. Input Tensor (Float32, NCHW)
self.input_tensor = self.infer_request.get_input_tensor()
self.input_data_buffer = self.input_tensor.data

# 2. Resize Buffer (Uint8, HWC)
# We calculate the target size once based on aspect ratio
scale = min(INPUT_W / CAM_W, INPUT_H / CAM_H)
self.new_w = int(CAM_W * scale)
self.new_h = int(CAM_H * scale)
self.resize_buffer = np.zeros((self.new_h, self.new_w, 3), dtype=np.uint8)

# 3. Canvas Buffer (Uint8, HWC) - Full 640x640
self.canvas_buffer = np.full((INPUT_H, INPUT_W, 3), 114, dtype=np.uint8)

# Calculate padding offsets once
self.dw = (INPUT_W - self.new_w) // 2
self.dh = (INPUT_H - self.new_h) // 2

print("Buffers Allocated. Memory Pools Ready.")

def preprocess_zero_alloc(self, img_rgb):
"""
Resizes and pads WITHOUT allocating new numpy arrays.
Uses cv2.resize(dst=...) and in-place assignments.
"""
# 1. Resize directly into pre-allocated buffer
# This prevents creating a new 1.2MB array
cv2.resize(img_rgb, (self.new_w, self.new_h), dst=self.resize_buffer)

# 2. Reset Canvas (Fill with gray 114)
# Faster than np.full, we just assign the value
self.canvas_buffer[:] = 114

# 3. Copy resized image into canvas
# Numpy handles this heavily optimized
self.canvas_buffer[self.dh:self.dh+self.new_h, self.dw:self.dw+self.new_w] = self.resize_buffer

# 4. Normalize and Transpose directly to Tensor
# HWC -> CHW happens via transpose view (cheap)
# np.divide writes result directly to OpenVINO memory (no intermediate float array)

# Create a temporary view of the canvas for transposing
# (Views do not allocate data memory)
canvas_chw = self.canvas_buffer.transpose((2, 0, 1))

# Normalize 0-255 -> 0-1 directly into input_data_buffer
np.divide(canvas_chw, 255.0, out=self.input_data_buffer[0], casting='unsafe')

def infer_isolation(self):
"""Run inference ONLY. No preprocessing. Just math."""
self.infer_request.infer()
# Retrieve result to ensure pipeline completes
_ = self.infer_request.get_output_tensor().data[0, 0, 0]

def infer_pipeline(self, img_rgb):
"""Run full zero-alloc pipeline."""
self.preprocess_zero_alloc(img_rgb)
self.infer_request.infer()
return self.infer_request.get_output_tensor().data

# --- TEST MODES ---

def run_isolation_test():
"""
MODE 1: Isolation
If this leaks, the OpenVINO driver is broken.
If this is stable, the leak was in the Python Preprocessing.
"""
print("\n--- MODE: ISOLATION (No Preprocessing) ---")
yolo = YoloZeroAlloc(MODEL_DIR)

print("Starting Inference Loop on Static Data...")
frames = 0
start = time.time()

while True:
try:
# PURE INFERENCE
yolo.infer_isolation()

frames += 1
if frames % 30 == 0:
rss = get_rss_mb()
elapsed = time.time() - start
fps = frames / elapsed
print(f"ISO | T:{elapsed:.0f}s | FPS:{fps:.1f} | RAM:{rss:.1f}MB")

# Manual GC every 10s just to be sure
if frames % 100 == 0: gc.collect()

except KeyboardInterrupt:
break

def run_fixed_test():
"""
MODE 2: Production Fix
Uses strict buffer reuse to stop the 19MB/s leak.
"""
print("\n--- MODE: FIXED ZERO-ALLOC PIPELINE ---")
yolo = YoloZeroAlloc(MODEL_DIR)

# Static dummy frame
frame_rgb = np.zeros((CAM_H, CAM_W, 3), dtype=np.uint8)
cv2.randu(frame_rgb, 0, 255)

print("Starting Optimized Pipeline...")
frames = 0
start = time.time()

while True:
try:
# Full Pipeline with Zero Alloc Preprocess
_ = yolo.infer_pipeline(frame_rgb)

frames += 1
if frames % 30 == 0:
rss = get_rss_mb()
elapsed = time.time() - start
fps = frames / elapsed
print(f"FIX | T:{elapsed:.0f}s | FPS:{fps:.1f} | RAM:{rss:.1f}MB")

if frames % 60 == 0:
gc.collect() # Helper sweep

except KeyboardInterrupt:
break

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--mode", choices=["isolation", "fixed"], required=True)
args = parser.parse_args()

if args.mode == "isolation":
run_isolation_test()
elif args.mode == "fixed":
run_fixed_test()


Подробнее здесь: https://stackoverflow.com/questions/798 ... arm-even-w
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»