Αποδέχτηκα μια πρόκληση: να δημιουργήσω έναν αγωγό βίντεο YOLOv8 σε πραγματικό χρόνο χρησιμοποιώντας το Vanilla ONNX Runtime. Ας είμαστε ειλικρινείς: Η Python είναι ο αδιαμφισβήτητος βασιλιάς του ερευνητικού εργαστηρίου. αλλά αν προσπαθείτε να μεταδώσετε ζωντανό βίντεο H.264 μέσω ενός νευρικού δικτύου σε κλίμακα στο εξάρτημα υλικού; Το Παγκόσμιο Κλειδί Ερμηνευτή (GIL) της Python και η παθολογική της εμμονή με την αντιγραφή μνήμης είναι φανερά ευθύνες. Πρόσφατα μου ανατέθηκε ένας απλός στόχος: να δημιουργήσω γρήγορο συμπέρασμα για μια ροή βίντεο χρησιμοποιώντας ένα runtime ONNX βανίλιας και ένα μοντέλο κατακερματισμού YOLOv8. Εδώ είναι πώς έσυρα ένα αργό πρωτότυπο 10 FPS σε ένα rock-solid 29 FPS τέρας, και τα "τελικά αφεντικά" σφάλματα που έπρεπε να σκοτώσω κατά μήκος του δρόμου. In reality, it was a journey through engineering hell. (Ο πλήρης πηγαίος κώδικας για τους μαζοχιστές: video-yolo-dash-processor) Επεξεργαστής video-yolo-dash The FogAI Sandbox: επικύρωση πριν από την ολοκλήρωση Αυτό το αποθετήριο δεν είναι ένα αυτόνομο παιχνίδι - είναι ένα Χρησιμοποιώ αυτό το περιβάλλον για να δοκιμάσω αυστηρά συγκεκριμένα μοντέλα όρασης υπολογιστή, κατασκευές κινητήρων και πρότυπα βελτιστοποίησης πριν προωθηθούν στο . dedicated testbed FogAI core Εάν μια στρατηγική (όπως η χαρτογράφηση υλικού Zero-Copy) δεν μπορεί να επιβιώσει εδώ σε 29 FPS, δεν έχει καμία επιχείρηση που βρίσκεται μέσα σε ένα βιομηχανικό αυτόνομο νευρικό σύστημα. Previous Chapters in the FogAI Saga: Το Μανιφέστο: Οι πιθανότητες είναι υπερεκτιμημένες. εδώ είναι πώς δημιούργησα έναν κόμβο μηδενικής αντιγραφής ομίχλης AI χωρίς Python Η ιστορία της σταδιοδρομίας: Οι πιθανότητες είναι υπερεκτιμημένες: Κατασκεύασα έναν κόμβο τεχνητής νοημοσύνης μηδενικής αντιγραφής χωρίς Python (και πονάει) Η πηγή του πόνου: GitHub: NickZt/FogAi Η παγίδα του φόρου «Memory Copy Tax» Τα περισσότερα πρωτότυπα όρασης υπολογιστή είναι αργά επειδή αντιμετωπίζουν τη μνήμη σαν ένα παιχνίδι ζεστής πατάτας. Η αρχική μου αρχιτεκτονική ήταν το "τυποποιημένο" χάος: το FFmpeg αποκωδικοποίησε το H.264 σε μορφές υλικού YUV, το μετατράπηκε σε OpenCV (BGR) για να τροφοδοτήσει το μοντέλο, εφαρμόστηκε μάσκες στην εικόνα RGB, μετατράπηκε του YUV, και τελικά χτύπησε τον κωδικοποιητή. cv::Mat Πίσω Σε μια CPU ARM που επεξεργάζεται 4K πλαίσια, αυτό το overhead καίει μέχρι Αρκεί να μετακινηθούν τα κομμάτια. That's three unnecessary memory copies and two heavy pixel-format conversions. 30% of your cycles Αυτό επιτεύχθηκε με την εφαρμογή Αντί να μετατρέψω το πλαίσιο, χαρτογράφησα το Hardware Y-plane (Luminance) απευθείας σε ένα OpenCV Ραπέρ . 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); Με την παράκαμψη της μετατροπής πάνω από το κεφάλι, παράκαμψα εντελώς το κενό της CPU. αλλά ήμουν ακόμα περιορισμένος σε 23 FPS. Why? Μεταβλητότητα και ασύγχρονη αναδιοργάνωση Το προφίλ έδειξε ότι τα νήματα μου ήταν κλειδωμένα σε μια αλληλουχία διαδοχικού θανάτου. Η αφαίρεση βασίζεται στην μετάλλαξη των κοινών εσωτερικών απορροφητήρων.Αν απλά γεννήσω περισσότερα νήματα σε ένα μόνο μοντέλο, μολύνουν το ένα το άλλο και το σύστημα αποσυνδέεται. YOLO Δημιούργησε μια ανταγωνιστική ομάδα από μοντέλα---ένα μοναδικό έντυπο μοντέλου ONNX ανά σύνδεσμο εργάτη. The Fix: std::unique_ptr<YOLO_Segment> Υπήρχε όμως ένα κυνήγι: Δεδομένου ότι οι εργαζόμενοι τελειώνουν σε διαφορετικές χρονικές στιγμές, το Frame 2 μπορεί να τελειώσει πριν από το Frame 1, προκαλώντας το βίντεο να χτυπήσει σαν ένα άλμα της δεκαετίας του '90. Για να διασφαλιστεί ο απόλυτος συγχρονισμός 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++; } } Το τελευταίο αφεντικό: Thread Cache Thrashing Στο χαρτί, η λογική ήταν τέλεια. στην πράξη, το FPS μου κατέρρευσε σε Οι καθυστερήσεις Time-To-Inference (TTI) μου αυξήθηκαν από 43ms σε τρομερά 890ms. 10 FPS I was a victim of CPU Cache Thrashing. Παρόλο που είχα αποσυνδέσει τις κλειδαριές μου, οι υποκείμενες βιβλιοθήκες ML (OpenCV και ONNX) με «βοήθησαν» δημιουργώντας τα δικά τους εσωτερικά νήματα. ONNX Runtime: Από προεπιλογή σε hardware_concurrency() / 2 νήματα ανά συνεδρία. Με 10 εργαζόμενους, γεννήθηκε 100+ εσωτερικά νήματα στην 20-πυρήνα CPU μου. OpenCV: Αυτόματη ανάπτυξη εργαζομένων για λειτουργίες όπως το .setTo(). Οι εντεταλμένοι εργαζόμενοι μου πολεμούσαν τα νήματα του ONNX, τα οποία πολεμούσαν τα νήματα του OpenCV. Thousands of context switches were destroying my L1/L2 caches every second. Η επιδιόρθωση ήταν ένα βίαιο «όχι» στην σιωπηλή συναλλαγή. άρπαξα τις βιβλιοθήκες από το δικαίωμά τους να γεννούν νήματα: 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); } Ο θόρυβος που αλλάζει το πλαίσιο εξαφανίστηκε. Η προσωρινή μνήμη εντολών της CPU μου συγχρονίζεται. Ο αγωγός χτύπησε αμέσως ένα άψογο με ανώτατο όριο TTI ~329ms. 29 FPS Συντήρηση πάνω από το εγώ: Η στρατηγική της βανίλιας Μια κοινή ερώτηση που λαμβάνω είναι: "Αν είστε τόσο επικεντρωμένοι στην απόδοση, γιατί να μην σφυρηλατήσετε τον κινητήρα και να βελτιστοποιήσετε τους πυρήνες μόνοι σας;" Η απάντηση είναι Technical Debt avoidance. Αν χάσετε τα εσωτερικά του κινητήρα, εγγραφείτε σε έναν ατελείωτο κύκλο συντήρησης. Κάθε φορά που μια νέα έκδοση πέφτει με υποστήριξη για φρέσκο υλικό---όπως (57% προεπιλεγμένη ώθηση) ή --- θα πρέπει να επαναφορτώσετε τις προσαρμοσμένες βελτιστοποιήσεις σας με μη αυτόματο τρόπο. , Μπορώ να "παίξω" αυτές τις ενημερώσεις υλικού δωρεάν απλά χτυπώντας έναν αριθμό έκδοσης. ARM KleidiAI Intel DL Boost (VNNI) Vanilla Inference Engine Ομοίως, επέλεξα να μην βελτιστοποιήσω τον αγωγό κωδικοποίησης / αποκωδικοποίησης. Γιατί; Επειδή οι πωλητές υλικού το έκαναν ήδη. Ή το , αυτά τα τσιπ έχουν επιτάχυνση σε επίπεδο πυριτίου για το H.264. Αφήστε τους κωδικοποιητές στο μέταλλο για το οποίο κατασκευάστηκαν. Intel QuickSync Rockchip VPU Zero-Copy Bridge Συμπέρασμα: Σταματήστε να μαντέψετε, ξεκινήστε το προφίλ Η κλιμάκωση της τεχνητής νοημοσύνης για τον πραγματικό κόσμο απαιτεί το ξεφλούδισμα των στρωμάτων αφαίρεσης με τα οποία αισθανόμαστε πολύ άνετα.Η Python κρύβει αυτούς τους φόρους καθυστέρησης μέχρι να θέσετε το σύστημα σε παραγωγή. Εάν είστε επιφορτισμένοι με βαριά φορτία τεντωτή στο βίντεο: Σκοτώστε τις μετατροπές pixel - εργαστείτε απευθείας στα επίπεδα υλικού. Απομονώστε τα μοντέλα σας - μία περίπτωση ανά εργαζόμενο. Αναδιατάξτε τις διαδοχικές εξόδους---μην αφήνετε τους χρόνους λήξης async να σπάσουν το ρεύμα σας. Ποτέ μην αφήνετε τις βιβλιοθήκες σας να γεννούν τα δικά τους νήματα. Μείνετε Βανίλα - βελτιστοποιήστε την αρχιτεκτονική σας, όχι τον κινητήρα, για να κρατήσετε το χρέος της τεχνολογίας χαμηλό. Επόμενο ΆρθροΠώς να προετοιμαστούμε; Για ένα μηδενικό copy run... Grounding DINO