Zaakceptowałem wyzwanie: zbudować w czasie rzeczywistym kanał wideo YOLOv8 przy użyciu Vanilla ONNX Runtime. Brak opuchniętych ram. Brak python bottlenecks. Po prostu surowy C++ grit. Bądźmy szczerzy: Python jest niekwestionowanym królem laboratorium badawczego. ale jeśli próbujesz przesyłać wideo H.264 na żywo za pośrednictwem sieci neuronowej w skali na sprzęcie krawędziowym? Niedawno otrzymałem zadanie z prostym celem: tworzenie szybkich wniosków dla strumienia wideo przy użyciu vanilli ONNX runtime i modelu segmentacji YOLOv8. Oto, jak przeciągnąłem powolny prototyp 10 FPS w rock-solid 29 FPS bestie, a "ostatni szef" błędy, które musiałem zabić w drodze. In reality, it was a journey through engineering hell. (pełny kod źródłowy dla masochistów: video-yolo-dash-procesor) Procesor wideo YOLO-DASH FogAI Sandbox: walidacja przed integracją Ten repozytorium nie jest samodzielną zabawką – jest Używam tego środowiska do rygorystycznego testowania określonych modeli wizji komputerowej, konstrukcji silnika i wzorców optymalizacji, zanim zostaną one promowane do . dedicated testbed FogAI core Jeśli strategia (takie jak mapowanie sprzętu Zero-Copy) nie może przetrwać tutaj przy 29 fps, nie ma żadnego biznesu będącego wewnątrz przemysłowego autonomicznego układu nerwowego. Previous Chapters in the FogAI Saga: Manifest: Prompts są przeceniane. oto jak zbudowałem węzeł AI o mgle zero-copy bez Pythona Historia kariery: Prompts są przeceniane: zbudowałem węzeł AI o zerowej kopii mgły bez Pythona (i to boli) Źródło cierpienia: GitHub: NickZt/FogAi Podatek od kopii pamięci (Memory Copy Tax) Większość prototypów wizji komputerowej jest powolna, ponieważ traktuje pamięć jak grę z gorącym ziemniakiem. Moja początkowa architektura była "standardowym" bałaganem: FFmpeg odszyfrował H.264 do formatów sprzętowych YUV, przekonwertował go na OpenCV (BGR) do zasilania modelu, nałożone maski na obraz RGB, przekonwertowane do YUV, i wreszcie trafić na kodera. cv::Mat Powrót Na procesorze ARM przetwarzającym ramy 4K, który spala się do Wystarczy poruszać się po okolicy. That's three unnecessary memory copies and two heavy pixel-format conversions. 30% of your cycles Ustaliliśmy to poprzez wdrożenie Zamiast konwertować ramy, skopiowałem Hardware Y-plane (Luminance) bezpośrednio do OpenCV i wrapperem. Zero-Copy Hardware Mapping AVFrame cv::Mat C++ // Mapping the hardware Y-plane natively - zero memcpy, zero overhead. cv::Mat y_plane(yuvFrame->height, yuvFrame->width, CV_8UC1, yuvFrame->data, yuvFrame->linesize); // YOLO segmentation masks now inject binary modifications directly // onto the hardware Y sequence. y_plane(bbox).setTo(0, valid_mask); Oszczędzając konwersję, całkowicie pominąłem blokadę procesora, ale wciąż byłem ograniczony do 23 FPS. Why? Mutabilność i asynchroniczne uporządkowanie Profilowanie pokazało, że moje pasma były zamknięte w kolejnym uchwycie śmierci. Abstrakcja opiera się na mutacji wspólnych buforów wewnętrznych.Jeśli po prostu zrodziłem więcej przewodów na jednym modelu, one zanieczyszczyły się nawzajem, a system się rozpadł. YOLO Udało mi się zorganizować konkurencję na Modele - jedna unikalna instancja modelu ONNX na wątek pracownika. The Fix: std::unique_ptr<YOLO_Segment> Było jednak jedno złapanie: Ponieważ pracownicy kończą w różnych momentach, Frame 2 może zakończyć się przed Frame 1, powodując, że wideo wstrząsnie jak skok z lat 90. Zapewnia bezproblemową synchronizację H.264. DASH video requires strict frame order. std::map C++ // Reorder buffer logic to keep the stream sequential std::map<int64_t, FramePayload> reorderBuffer; int64_t expected_pts = 0; while (true) { auto payload = inferenceQueue.pop(); // Workers drop processed frames here reorderBuffer[payload.pts] = payload; // Emit frames only when the sequential timestamp flags align while (!reorderBuffer.empty() && reorderBuffer.begin()->first == expected_pts) { auto it = reorderBuffer.begin(); encoder.writeFrame(it->second.yuvFrame, it->second.pts); reorderBuffer.erase(it); expected_pts++; } } Recenzja The Final Boss: Thread Cache Thrashing Na papierze, logika była doskonała. W praktyce, mój FPS spadł do Moje opóźnienia Time-To-Inference (TTI) wzrosły z 43 ms do strasznych 890 ms. 10 FPS I was a victim of CPU Cache Thrashing. Pomimo tego, że odłączyłem zamki, podstawowe biblioteki ML (OpenCV i ONNX) „pomagają” mi, wytwarzając własne wewnętrzne pasma. ONNX Runtime: Defaults to hardware_concurrency() / 2 nić na sesję. Z 10 pracowników, zrodził 100+ wewnętrznych nić na moim 20-rdzeniowym procesorze. OpenCV: automatycznie wdraża pracowników do operacji takich jak .setTo(). Moi wyznaczeni pracownicy walczyli z pasmami ONNX, które walczyły z pasmami OpenCV. Thousands of context switches were destroying my L1/L2 caches every second. Naprawa była brutalnym „nie” do domniemanej konkurencji. pozbawiłem biblioteki prawa do rodzenia przewodów: C++ int main() { // Globally disable implicit OpenCV threading cv::setNumThreads(1); // Cap ONNX Runtime to a single thread per op Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); session_options.SetInterOpNumThreads(1); } Hałas przełączania kontekstu zniknął. Cache instrukcji CPU jest zsynchronizowane. Rura natychmiast uderzyła w bezproblemowy Z prędkością wynoszącą ~329 ms. 29 FPS Utrzymanie nad ego: strategia wanilii Częstym pytaniem, które dostaję, jest: "Jeśli jesteś tak skoncentrowany na wydajności, dlaczego nie skręcić silnika i samemu zoptymalizować rdzenia?" Odpowiedzią jest Technical Debt avoidance. Jeśli hakujesz wewnętrzne strony silnika, rejestrujesz się do niekończącego się cyklu konserwacji. Za każdym razem, gdy nowa wersja spada z obsługą świeżego sprzętu---np. (57% wzrost wstępny) lub --- musisz ponownie przenieść swoje niestandardowe optymalizacje ręcznie. , Mogę "złapać" te aktualizacje sprzętowe za darmo, po prostu biorąc numer wersji. ARM KleidiAI Intel DL Boost (VNNI) Vanilla Inference Engine Podobnie, zdecydowałem się nie optymalizować rurociągu kodowania / dekodowania. Dlaczego? Ponieważ dostawcy sprzętu już to zrobili. lub The , te żetony mają przyspieszenie na poziomie krzemu dla H.264. Zostaw kodek do metalu, dla którego został zbudowany. Intel QuickSync Rockchip VPU Zero-Copy Bridge Konkluzja: przestań zgadywać, zacznij profilować Skalowanie sztucznej inteligencji dla świata rzeczywistego wymaga odkręcania warstw abstrakcji, z którymi jesteśmy zbyt wygodni.Python ukrywa te opłaty za opóźnienie, dopóki nie wprowadzisz systemu do produkcji. Jeśli masz do czynienia z ciężkimi obciążeniami napięcia wideo: Zabij konwersje pikseli - pracuj bezpośrednio na płaszczyznach sprzętowych. Wyizoluj swoje modele – jeden przykład na pracownika. Reorder sequential outputs---nie pozwól, aby asynchroniczne czasy zakończenia przerwały twój strumień. Nigdy nie pozwól, aby Twoje biblioteki rodziły własne wątki. Stay Vanilla - optymalizuj swoją architekturę, a nie silnik, aby utrzymać niskie zadłużenie technologiczne. Następny Następny wpis: Jesteśmy gotowi? Dla zero-copy run... Grounding DINO