Synchronisation

Mécanisme de planification

Dans un graphe MediaPipe, les données sont traitées dans des nœuds de traitement définis en tant que sous-classes CalculatorBase. Le système de planification décide quand chaque calculatrice doit s’exécuter.

Chaque graphique comporte au moins une file d'attente du planificateur. Chaque file d'attente du programmeur ne comporte qu'un seul executor. Les nœuds sont attribués de manière statique à une file d'attente (et donc à un exécuteur). Par défaut, il existe une file d'attente, dont l'exécuteur est un pool de threads avec un certain nombre de threads en fonction des capacités du système.

Chaque nœud a un état de planification qui peut être pas prêt, prêt ou en cours d'exécution. Une fonction d'aptitude détermine si un nœud est prêt à être exécuté. Cette fonction est appelée lors de l'initialisation du graphe, à chaque fois qu'un nœud termine son exécution et chaque fois que l'état des entrées d'un nœud change.

La fonction d'aptitude utilisée dépend du type de nœud. Un nœud sans entrée de flux est appelé nœud source. Les nœuds sources sont toujours prêts à être exécutés, jusqu'à ce qu'ils indiquent au framework qu'ils n'ont plus de données à générer, auquel cas ils sont fermés.

Les nœuds non sources sont prêts s'ils ont des entrées à traiter et si ces entrées constituent un ensemble d'entrées valide selon les conditions définies par la règle d'entrée du nœud (décrite ci-dessous). La plupart des nœuds utilisent la règle d'entrée par défaut, mais certains nœuds en spécifient une autre.

Lorsqu'un nœud est prêt, une tâche est ajoutée à la file d'attente du planificateur correspondante, qui est une file d'attente prioritaire. La fonction de priorité est actuellement fixe. Elle prend en compte les propriétés statiques des nœuds et leur tri topologique dans le graphe. Par exemple, les nœuds plus proches du côté de sortie du graphique ont la priorité la plus élevée, tandis que les nœuds sources ont la priorité la plus faible.

Chaque file d'attente est diffusée par un exécuteur, chargé d'exécuter la tâche en appelant le code du simulateur. Différents exécuteurs peuvent être fournis et configurés. Cela permet de personnaliser l'utilisation des ressources d'exécution, par exemple en exécutant certains nœuds sur des threads de priorité inférieure.

Synchronisation de l'horodatage

L'exécution du graphe MediaPipe est décentralisée: il n'y a pas d'horloge globale, et différents nœuds peuvent traiter simultanément des données provenant de différents horodatages. Cela permet un débit plus élevé via le pipeline.

Cependant, les informations temporelles sont très importantes pour de nombreux processus de perception. Les nœuds qui reçoivent plusieurs flux d'entrée doivent généralement les coordonner d'une manière ou d'une autre. Par exemple, un détecteur d'objets peut générer une liste de rectangles de délimitation à partir d'une image. Ces informations peuvent être transmises à un nœud de rendu, qui doit les traiter avec l'image d'origine.

Par conséquent, l'une des principales responsabilités du framework MediaPipe est de fournir la synchronisation des entrées pour les nœuds. En termes de mécanismes de framework, le rôle principal d'un horodatage est de servir de clé de synchronisation.

De plus, MediaPipe est conçu pour prendre en charge les opérations déterministes, ce qui est important dans de nombreux scénarios (test, simulation, traitement par lot, etc.), tout en permettant aux auteurs de graphiques d'assouplir le déterminisme si nécessaire pour respecter des contraintes en temps réel.

Les deux objectifs de synchronisation et de déterminisme sous-tendent plusieurs choix de conception. En particulier, les paquets envoyés dans un flux donné doivent comporter des horodatages augmentant de manière monotone. Il ne s'agit pas seulement d'une hypothèse utile pour de nombreux nœuds, mais elle est également utilisée par la logique de synchronisation. Chaque flux est limité par l'horodatage, qui correspond au code temporel le plus bas possible pour un nouveau paquet dans le flux. Lorsqu'un paquet avec le code temporel T arrive, la limite passe automatiquement à T+1, ce qui reflète l'exigence monotone. Cela permet au framework de s'assurer que plus aucun paquet avec un horodatage inférieur à T n'arrivera.

Règles de saisie

La synchronisation est gérée localement sur chaque nœud, à l'aide de la règle d'entrée spécifiée par le nœud.

La règle d'entrée par défaut, définie par DefaultInputStreamHandler, fournit une synchronisation déterministe des entrées avec les garanties suivantes:

  • Si des paquets avec le même horodatage sont fournis sur plusieurs flux d'entrée, ils seront toujours traités ensemble, quel que soit leur ordre d'arrivée en temps réel.

  • Les ensembles d'entrées sont traités dans l'ordre strictement croissant des codes temporels.

  • Aucun paquet n'est supprimé, et le traitement est entièrement déterministe.

  • Le nœud est prêt à traiter les données dès que possible, conformément aux garanties ci-dessus.

Pour expliquer son fonctionnement, nous devons présenter la définition d'un horodatage réglé. Un horodatage dans un flux est settled s'il est inférieur à la limite d'horodatage. En d'autres termes, un horodatage est défini pour un flux une fois que l'état de l'entrée à cet horodatage est connu irrévocablement: soit il y a un paquet, soit il est probable qu'un paquet avec cet horodatage n'arrivera pas.

Un horodatage est appliqué à plusieurs flux s'il l'est sur chacun d'entre eux. De plus, si un horodatage est établi, cela implique que tous les horodatages précédents le sont également. Les horodatages réglés peuvent être traités de manière déterministe dans l'ordre croissant.

Selon cette définition, un calculateur avec la règle d'entrée par défaut est prêt s'il existe un horodatage réglé sur tous les flux d'entrée et contenant un paquet sur au moins un flux d'entrée. La règle d'entrée fournit tous les paquets disponibles pour un horodatage réglé en tant qu'ensemble d'entrée unique dans le simulateur.

L'une des conséquences de ce comportement déterministe est que, pour les nœuds avec plusieurs flux d'entrée, il peut y avoir une attente théoriquement illimitée pour qu'un horodatage soit réglé, et un nombre illimité de paquets peuvent être mis en mémoire tampon en attendant. (Prenons l'exemple d'un nœud avec deux flux d'entrée, dont l'un continue à envoyer des paquets tandis que l'autre n'envoie rien et n'avance pas la limite.)

Par conséquent, nous fournissons également des règles d'entrée personnalisées, par exemple en divisant les entrées dans différents ensembles de synchronisation définis par SyncSetInputStreamHandler, ou en évitant la synchronisation et en traitant immédiatement les entrées dès qu'elles arrivent (définies par ImmediateInputStreamHandler).

Contrôle de flux

Il existe deux principaux mécanismes de contrôle de flux. Un mécanisme de contre-pression limite l'exécution des nœuds en amont lorsque les paquets mis en mémoire tampon sur un flux atteignent une limite (configurable) définie par CalculatorGraphConfig::max_queue_size. Ce mécanisme maintient un comportement déterministe et inclut un système d'évitement des interblocages qui assouplit les limites configurées si nécessaire.

Le second système consiste à insérer des nœuds spéciaux qui peuvent supprimer des paquets en fonction de contraintes en temps réel (généralement à l'aide de règles d'entrée personnalisées) définies par FlowLimiterCalculator. Par exemple, un modèle courant place un nœud de contrôle de flux à l'entrée d'un sous-graphe, avec une connexion de bouclage entre la sortie finale et le nœud de contrôle de flux. Le nœud de contrôle de flux est ainsi capable de suivre le nombre d'horodatages traités dans le graphe en aval et de supprimer des paquets si ce nombre atteint une limite (configurable). De plus, comme les paquets sont supprimés en amont, nous évitons le gaspillage de travail qui résulterait du traitement partiel d'un horodatage, puis de la suppression de paquets entre les étapes intermédiaires.

Cette approche basée sur un calculateur permet à l'auteur du graphe de contrôler où les paquets peuvent être supprimés et offre une certaine flexibilité pour adapter et personnaliser le comportement du graphe en fonction des contraintes de ressources.