Run simulations

Simuler les charges de travail d’apprentissage fédéré est utile pour une multitude d’utilisation : vous pouvez vouloir exécuter votre charge de travail sur un grand nombre de clients sans avoir à s’attaquer, configurer et gérer un grand nombre de dispositifs physiques ; vous pouvez vouloir exécuter vos charges FL aussi rapidement que possible sur les systèmes de calcul dont vous avez accès sans passer par un processus de mise en place complexe ; vous pouvez vouloir valider votre algorithme dans différents scénarios à des niveaux variables de données et d’hétérogénéité système, disponibilité client, budgets de confidentialité, etc. Ces sont quelques-unes des utilisations où simuler les charges FL a du sens.

Note

La Simulation Runtime de Flower est construite sur top de Ray, un cadre open-source pour des workloads Python scalables. Flower prend en charge pleinement Linux et macOS. Sur Windows, le support Ray reste expérimental, et même si vous pouvez exécuter les simulations directement à partir du PowerShell, nous recommandons d’utiliser WSL2.

Note

Si vous êtes sous Windows et que vous voyez une sortie de terminal inattendue (par exemple : □[32m□[1m), consultez cette entrée de FAQ.

La Simulation Runtime de Flower planifie, lance et gère des instances ClientApp. Cela se fait par le biais d’un Backend, qui contient plusieurs travailleurs (c’est-à-dire des processus Python) pouvant exécuter un ClientApp en passant un Context et un Message. Ces objets ClientApp sont identiques à ceux utilisés par la Deployment Runtime de Flower, ce qui rend l’alternance entre simulation et déploiement un processus facile. L’exécution des objets ClientApp via la Simulation Runtime de Flower est :

  • Ressources-aware : Chaque worker backend exécutant ClientApps reçoit une portion des ressources de calcul et de mémoire de votre système. Vous pouvez définir ces à la base de la simulation, vous permettant de contrôler le niveau de parallélisme de votre simulation. Pour un total fixe de pool de ressources, moins il y a de ressources par worker backend, plus ClientApps peuvent être exécutés concurremment sur le même matériel.

  • Batchable : Lorsqu’il existe plus de ClientApps à exécuter que des workers backend, les ClientApps sont en attente et exécutés dès que des ressources sont libérées. Cela signifie que les ClientApps sont généralement exécutés en lots de N, où N est le nombre de workers backend.

  • Auto-géré : Cela signifie que vous, en tant qu’utilisateur, n’avez pas besoin de lancer manuellement la ClientApps; au lieu de cela, l’orchestrateur Simulation Runtime gère l’exécution de toutes les ClientApps.

  • Éphémère : Cela signifie que la ClientApp est matérialisée uniquement lorsque celle-ci est requise par l’application (par exemple, pour faire @app.train()). L’objet est détruit ensuite, libérant les ressources qu’il a été assignées et permettant à d’autres clients de participer.

Note

Vous pouvez conserver l’état (par exemple, variables internes, parties d’un modèle ML, résultats intermédiaires) d’une ClientApp en la sauvegardant dans son Context. Consultez le guide Designing Stateful Clients pour une walkthrough complète.

La Simulation Runtime délègue à un Backend le rôle de lancer et gérer les ClientApps. Le backend par défaut est le RayBackend, qui utilise Ray, un cadre open-source pour des charges de travail Python scalables. En particulier, chaque worker est un Actor capable de lancer un ClientApp donné ses Context et un Message à traiter.

Lancer votre simulation Flower

Astuce

Si vous n’avez pas déjà fait cela, installez Flower via pip install -U "flwr[simulation]" dans un environnement Python.

Exécuter une simulation est simple ; en fait, c’est le mode par défaut d’opération pour la flwr run. Par conséquent, l’unique exigence pour exécuter les simulations de Flower est d’avoir une application Flower. Une façon pratique de générer une application Flower minimale mais pleinement fonctionnelle est à travers le commande flwr new. Il existe plusieurs applications à choisir. L’exemple ci-dessous utilise l’applicatif quickstart PyTorch.

# or simply execute `flwr new` for a list of recommended apps to choose from
flwr new @flwrlabs/quickstart-pytorch

Ensuite, suivez les instructions affichées après avoir terminé le commande flwr new. Lorsque vous exécutez flwr run, l’exécution sera exécutée avec les Simulation Runtime.

Pour les profils de simulation locales, la flwr run soumet l’exécution à un SuperLink local géré via l’API de contrôle. Si le profil utilise address = ":local:", Flower démarre automatiquement un SuperLink local lorsque nécessaire, le garde en cours d’exécution en arrière-plan et le réutilise pour flwr list, flwr log et flwr stop. Voir Exécuter Flower Localement avec un SuperLink Géré pour la trajectoire complète de workflow et du cycle de vie de runtime.

Astuce

Si vous exécutez vos simulations sur un serveur à l’aide d’un système de fichiers réseau (par exemple, NFS-monté répertoire personnel), vous pouvez rencontrer des erreurs de base de données SQL si votre réseau est lent. Si tel est le cas, consultez this FAQ entry pour apprendre comment exécuter les simulations avec un SuperLink à l’aide d’une base de données en mémoire.

Simulation examples

En plus des tutoriels quickstart dans la documentation (par exemple, quickstart PyTorch Tutorial, quickstart JAX Tutorial), la plupart des exemples dans le dépôt Flower sont prêts pour simulation.

La liste complète d’exemples peut être trouvée dans the Flower GitHub.

Personnaliser le Simulation Runtime

Par défaut, le Simulation Runtime simule un lot de 10 SuperNodes et affecte deux cœurs CPU à chaque worker backend. Cela signifie que si votre système a 12 cœurs CPU, six workers backend peuvent être en cours d’exécution parallèlement, chacun exécutant une instance différente de ClientApp.

La plupart du temps, vous aimeriez probablement ajuster les ressources que votre ClientApp reçoit en fonction de la complexité (c’est-à-dire, empreinte de calcul et mémoire) de votre Flower application. Vous pouvez le faire en ajustant les ressources back-end pour votre fédération.

Prudence

Notez que les ressources attribuées par l’arrière-plan à chaque travailleur (et donc à chaque ClientApp exécuté) sont attribuées d’une manière souple. Cela signifie que les ressources sont principalement prises en compte pour contrôler le degré de parallélisme auquel les instances ClientApp devraient être exécutées. L’attribution des ressources n’est pas stricte, ce qui signifie que si vous avez spécifié que votre ClientApp utilise 25% de la mémoire VRAM disponible, mais qu’il finit par en utiliser 50%, cela pourrait causer d’autres instances ClientApp à crasher avec une erreur de mémoire insuffisante (OOM).

Personnaliser les ressources peut être fait de deux manières : soit en changeant la configuration de simulation par défaut utilisée par votre SuperLink local ; ou en surchargeant la configuration de simulation sur une base par run. Voyons comment faire les deux.

Fixer définitivement la configuration du Simulation Runtime

La commande flwr federation simulation-config vous permet de fixer définitivement la configuration de simulation par défaut pour votre SuperLink local. Cela est utile lorsque vous souhaitez avoir une configuration par défaut différente de celle fournie par Flower par défaut. Par exemple, si vous voulez fixer la configuration à 100 SuperNodes, où chaque ClientApp est affecté de 4 processeurs et 25% d’un GPU, vous feriez :

flwr federation simulation-config \
        --num-supernodes 100 \
        --client-resources-num-cpus 4 \
        --client-resources-num-gpus 0.25

Ensuite, pour chaque exécution ultérieure, le SuperLink utilisera la configuration ci-dessus par défaut. Utilisez flwr federation simulation-config --help pour voir toutes les options que vous pouvez configurer.

Survolage de la configuration de Simulation Runtime

Parfois, vous pourriez vouloir surcharger la configuration de simulation par défaut pour une exécution spécifique. Vous pouvez le faire en passant les mêmes options que ci-dessus à flwr run mais en utilisant la balise –federation-config et exprimés sous forme de chaîne unique. Par exemple, supposons que vous voulez exécuter une seule simulation avec 256 SuperNodes au lieu des 100 par défaut, réduire le nombre de processeurs par ClientApp à 1 et laisser inchangée l’allocation GPU. Vous feriez :

flwr run . --federation-config="num-supernodes=256 client-resources-num-cpus=1"

Astuce

La balise –federation-config accepte n’importe quelle option qui peut être configurée avec flwr federation simulation-config en utilisant la même syntaxe mais exprimée sous forme de chaîne unique et sans le préfixe --.

Compréhension de l’affectation de ressources de Simulation Runtime

Voyons comment la configuration ci-dessus, c’est-à-dire 1x CPU et 25% d’un GPU par ClientApp, aboutit à un nombre différent de ClientApps exécutés en parallèle selon les ressources disponibles dans votre système. Si votre système possède :

  • 10x processeurs et 1x GPU : au maximum, 4 ClientApps peuvent s’exécuter en parallèle car chacun nécessite 25% de la mémoire VRAM disponible.

  • 10x processeurs et 2x GPUs : au maximum, 8 ClientApps peuvent s’exécuter en parallèle (limité par la mémoire VRAM).

  • 6x processeurs et 4x GPUs : au maximum, 6 ClientApps peuvent s’exécuter en parallèle (limités par les processeurs).

  • 10x processeurs mais 0x GPUs : vous ne pourrez pas exécuter la simulation car même les ressources nécessaires pour une seule ClientApp ne peuvent être satisfaits.

Une généralisation de cela est donnée par l’équation suivante. Elle donne le nombre maximum de ClientApps qui peuvent être exécutés en parallèle sur les cœurs CPU disponibles (SYS_CPUS) et la mémoire VRAM (SYS_GPUS).

\[N = \min\left(\left\lfloor \frac{\text{SYS_CPUS}}{\text{num_cpus}} \right\rfloor, \left\lfloor \frac{\text{SYS_GPUS}}{\text{num_gpus}} \right\rfloor\right)\]

Les deux num_cpus (un entier supérieur à 1) et num_gpus (un nombre réel non négatif) doivent être définis sur une base par ClientApp. Si, par exemple, vous voulez que seule une ClientApp s’exécute sur chaque GPU, alors fixez num_gpus=1.0. Si, par exemple, une ClientApp nécessite accès à deux GPUs entières, vous feriez mieux de fixer num_gpus=2.

Même si le client-resources-{num-cpus,num-gpus} peut être utilisé pour contrôler le niveau de concurrence dans vos simulations, cela ne vous empêche pas d’exécuter des centaines ou même des milliers de clients dans la même ronde et avoir des ordres de grandeur plus élevés de clients dormants (c’est-à-dire non participant à une ronde). Supposons que vous voulez avoir 100 clients par ronde mais que votre système ne peut accueillir que 8 clients en concurrence. Le Simulation Runtime planifiera 100 ClientApps pour s’exécuter et les exécutera ensuite de manière consciente des ressources en lots de 8.

Ressources de Simulation Runtime

Par défaut, le Simulation Runtime a accès à toutes les ressources système (c’est-à-dire tous les processeurs, toutes les GPUs). Cependant, dans certains contextes, vous pourriez vouloir limiter la quantité de vos ressources système utilisées pour la simulation. Vous pouvez le faire en passant une valeur à l’attribut init-args des flags Flower Configuration.

flwr federation simulation-config --init-args-num-cpus 1 --init-args-num-gpus 0

Avec cette configuration, l’arrière-plan sera initialisé avec un seul processeur et sans GPU. Par conséquent, même si plus de processeurs et de GPUs sont disponibles dans votre système, ils ne seront pas utilisés pour la simulation. L’exemple ci-dessus donne lieu à une seule instance ClientApp s’exécutant en tout point.

Pour une liste complète des paramètres que vous pouvez configurer, consultez la documentation du ray.init.

Pour obtenir le meilleur rendement, n’installez pas les flags --init-args-{...}.

Simulations multi-nœuds Flower

La fonction Simulation Runtime de Flower vous permet d’exécuter des simulations FL sur plusieurs nœuds de calcul, afin que vous ne soyez pas limités à exécuter les simulations sur une seule machine. Avant de démarrer votre simulation multi-nœud, assurez-vous que :

  1. Vous avez le même environnement Python sur tous les nœuds.

  2. Vous avez une copie de votre code sur tous les nœuds.

  3. Vous avez une copie de votre jeu de données sur tous les nœuds. Si vous utilisez des partitions à partir de Flower Datasets, assurez-vous que la stratégie de partitionnement et sa paramétrisation sont identiques. L’attente est que la ième partition de jeu de données soit identique sur tous les nœuds.

  4. Lancez Ray sur votre nœud de tête : dans la console, tapez ray start --head. Cette commande affichera quelques lignes, dont une indiquera comment attacher d’autres nœuds à la tête du nœud.

  5. Attachez d’autres nœuds au nœud principal : copiez la commande affichée après avoir démarré le nœud principal et exécutez-la dans l’invite de commandes d’un nouveau nœud (avant d’exécuter flwr run). Par exemple : ray start --address='192.168.1.132:6379'. Notez que pour pouvoir attacher des nœuds au nœud principal, ils doivent être découvrables les uns par les autres.

Une fois que vous avez terminé toutes ces étapes, vous pouvez exécuter votre code depuis le nœud principal comme si la simulation était en cours sur une seule machine. En d’autres termes :

# From your head node, launch the simulation
flwr run

Une fois que votre simulation est terminée, si vous souhaitez démonter votre cluster, il suffit de lancer la commande ray stop dans l’invite de commandes de chaque nœud (y compris le nœud principal).

Note

Lorsque vous attachez un nouveau nœud à la tête, toutes ses ressources (c’est-à-dire tous les processeurs, toutes les GPUs) seront visibles par le nœud de tête. Cela signifie que le Simulation Runtime peut planifier autant d’instances ClientApp qu’il est possible de lancer sur ce nœud. Dans certains contextes, vous pourriez vouloir exclure certaines ressources de la simulation. Vous pouvez le faire en ajoutant --num-cpus=<NUM_CPUS_FROM_NODE> et/ou --num-gpus=<NUM_GPUS_FROM_NODE> à tout commande ray start (y compris lors du démarrage de la tête).

FAQ pour les simulations

Puis-je rendre mes instances ClientApp étatiques ?

Oui. Utilisez l’attribut state de l’objet Context qui est passé à la fonction ClientApp pour sauvegarder des variables, des paramètres ou des résultats dans elle. Lisez le guide Designing Stateful Clients pour une walkthrough complète.

Puis-je exécuter plusieurs simulations sur la même machine ?

Oui, mais tenez compte que chaque simulation n’est pas conscient de l’utilisation des ressources des autres. Si vos simulations utilisent des GPUs, considérez définir la variable d’environnement CUDA_VISIBLE_DEVICES pour faire en sorte que chaque simulation utilise un ensemble différent des GPU disponibles. Exportez une telle variable d’environnement avant de démarrer flwr run.

Les ressources CPU/GPU définies pour chaque ClientApp limitent-elles l’utilisation de calcul/mémoire par ces dernières ?

Non. Ces ressources sont exclusivement utilisées par le backend de simulation pour contrôler combien d’instances peuvent être créées à l’ouverture. Supposons que N travailleurs backend soient lancés, alors au maximum N ClientApp instances seront exécutées en parallèle. Il vous revient de vous assurer que les instances ClientApp disposent des ressources suffisantes pour exécuter leur charge de travail (par exemple, affiner un modèle transformer).

Mon ClientApp déclenche une erreur OOM sur mon GPU. Qu’est-ce que je dois faire ?

Il est probable que votre réglage num_gpus, qui contrôle le nombre d’instances ClientApp pouvant partager un GPU, soit trop bas (ce qui signifie que trop d’instances ClientApps partagent le même GPU). Essayez les choses suivantes :

  1. Fixez votre num_gpus=1. Cela fera s’exécuter une seule instance ClientApp sur un GPU.

  2. Inspectez combien de mémoire VRAM est utilisée (utilisez nvidia-smi pour cela).

  3. Sur la base de la mémoire VRAM que vous voyez votre seule instance ClientApp utiliser, calculez combien d’autres instances ClientApps pourraient s’adapter dans les ressources restantes. Un divisé par le nombre total d’instances ClientApps est la valeur num_gpus que vous devriez configurer.

Référez-vous à Personnaliser le Simulation Runtime pour plus de détails.

Si votre ClientApp utilise TensorFlow, assurez-vous d’avoir exporté TF_FORCE_GPU_ALLOW_GROWTH="1" avant de démarrer votre simulation. Pour plus de détails, consultez.

Comment puis-je savoir quel est le bon num_cpus et num_gpus pour mon ClientApp ?

Il est recommandé de commencer par lancer la simulation pendant quelques tours avec des valeurs plus élevées pour num_cpus et num_gpus que ce qui est vraiment nécessaire (par exemple, num_cpus=8 et, si vous avez un GPU, num_gpus=1). Ensuite, surveillez votre utilisation du CPU et du GPU. Pour cela, vous pouvez utiliser des outils tels que htop et nvidia-smi. Si vous voyez que l’utilisation globale des ressources reste faible, essayez de réduire num_cpus et num_gpus (rappelez-vous que cela fera plus d’instances ClientApp s’exécuter en parallèle) jusqu’à ce que vous voyiez une utilisation satisfaisante des ressources système.

Notez que si la charge de travail sur vos instances ClientApp n’est pas homogène (c’est-à-dire qu’il y a des différences dans l’utilisation de calcul ou de mémoire), vous devriez probablement vous concentrer sur celles-ci lorsqu’il s’agit de déterminer une bonne valeur pour num_gpus et num_cpus.

Puis-je affecter des ressources différentes à chaque instance ClientApp ?

Non. Tous les objets ClientApp sont supposés faire usage du même num_cpus et num_gpus. Lorsque vous définissez ces valeurs (référez-vous à Personnaliser le Simulation Runtime pour plus de détails), assurez-vous que le ClientApp avec la plus grande empreinte mémoire (soit RAM ou VRAM) peut s’exécuter dans votre système avec d’autres comme lui en parallèle.

Puis-je exécuter une simulation unique sur plusieurs nœuds de calcul (par exemple, serveurs GPU) ?

Oui. Si vous utilisez le backend par défaut (RayBackend), vous pouvez d’abord interconnecter vos nœuds à l’aide de Ray’s cli et puis lancer la simulation. Référez-vous à Simulations multi-nœuds Flower pour une guide étape par étape.

Mon ServerApp a également besoin d’utiliser le GPU (par exemple, pour effectuer l’évaluation du modèle global après agrégation). Est-ce que cette utilisation de GPU est prise en compte par le backend Simulation Runtime ?

Non. Le Simulation Runtime ne gère que les ClientApps et n’est donc pas conscient des ressources système qu’ils requièrent. Si votre ServerApp utilise des ressources de calcul ou mémoire importantes, prenez cela en compte lors du définissement de num_cpus et num_gpus.

Puis-je indiquer sur quel ressource une instance spécifique de ClientApp devrait s’exécuter ? Puis-je faire la mise en place des ressources ?

Actuellement, la mise en place des instances ClientApp est gérée par le RayBackend (le seul backend disponible à partir de flwr==1.13.0) et ne peut pas être personnalisée. La mise en œuvre d’un backend personnalisé serait une façon d’atteindre la mise en place des ressources.