PROJET AUTOBLOG


blog.fevrierdorian.com

source: blog.fevrierdorian.com

⇐ retour index

La ferme de rendu (première partie) : Les jobs

samedi 18 août 2018 à 23:49

L’exécution de tâches (jobs) sur des gestionnaires de tâche (job manger, render manager, farm manager) sont courant dans énormément de secteurs liés à l’informatique. Que ce soit la compilation de code, l’exécution et le rapport de tests unitaires, le calcul financier, ça pullule. :popcorn:

Internet regorge d’informations et de bonnes pratiques liées à l’exécution et la gestion de tâches sur des fermes de calcul. Pourtant, beaucoup de ses informations sont trop génériques et passe à côté de certains problèmes de fond, propre à notre industrie. :triste:

Je voudrais, modestement et de façon totalement subjective, faire un retour d’expérience autour de l’organisation et la structure du code de ferme de calcul. Notez que je n’affirme pas avoir LA bonne méthode pour gérer les fermes de la façon la plus efficace qui soit, mais j’aborde et commente différentes problématiques que j’ai pu rencontrer.

Je vais passer en revu et commenter différentes choses. Je tenterais, dans les parties suivantes, d’établir quelques règles et guides à suivre puis je finirais sur une explication de l’organisation du code. :banaeyouhou:

Terminologie

Avant de commencer je dois expliciter certains termes que je vais employer. :redface:

Job manager

C’est le cerveau. Il s’agit du « serveur » :

Le job manager embarque souvent une base de donnée (PostgreSQL, MySQL, etc.) et ne doit jamais être indisponible pour les workers. :siffle:

Worker

« Travailleur » en français. Il s’agit d’un petit programme qui tourne et communique avec le job manager pour recevoir et exécuter des jobs. On exécute souvent un worker par machine de calcul, mais il n’est pas rare d’exécuter plusieurs workers sur une seule machine. Parfois pour des raisons de licences liées à la machine (une machine Yeti, plusieurs exports de .fur), parfois pour des raisons d’équilibre de charge. Ainsi, plusieurs petits jobs qui demandent peu de ressources peuvent n’exécuter en parallèle sur une seule machine. Notez qu’un gestionnaire de ferme évolué permet de gérer intelligemment les ressources d’une machine et de lancer plus ou moins de jobs suivant les exigences du job (licence, mémoire, etc.).

Job

Bien que le terme français « tâche » existe, il fait souvent spécifiquement référence, en production, à la tâche du graphiste (traduction de « task », qu’on peut retrouver dans Shotgun). J’utilise donc le terme de « job » pour faire référence à ce que doit exécuter un worker.

Exigence des jobs

Parfois appelé job requirement. C’est un concept qui est présent sous différentes formes dans la plupart des job managers du marché. Le principe est que chaque job contient des informations sur ce qui est nécessaire à son exécution. Plus ces informations sont complètes, plus le job manager pourra gérer au mieux la farm. Le cas le plus courant est le type et le nombre de licences nécessaire à l’exécution du job.

Voici un exemple d’exigence de job :

{'requirement': {'license': {'nuke': 1},
                            {'sapphire': 1}
                 'cpu_count': '12+',
                 'memory_min’: '8GB',
                 'memory_max': '64GB'}

Le dictionnaire suivant spécifie que le job :

L’aspect intéressant c’est que certaines informations peuvent aider le job manager à mieux organiser la soumission des jobs. Ainsi, en spécifiant memory_max, vous « garantissez » au job manger que le job ne fera jamais plus de 64 GB. Si, par exemple, vous avez des petits jobs qui n’utilise qu’un seul CPU vous pourriez spécifier cpu_count à 1 et le job manager saurait que votre job ne va utiliser qu’un seul CPU.

C’est une sorte de « contrat » que vous passez avec le job manager. C’est donc à double tranchant. Si vous lui dite que votre job utilisera 16 GB et qu’il monte à 64 GB, le worker aura des soucis pour l’exécuter. :nannan: Bien entendu, c’est celui qui génère le job à soumettre qui explicite ses exigences. Comme ce sont souvent les TD qui codent les chaînes de job, les exigences sont dans le code. :gniarkgniark:

Job de génération des fichiers de rendu

Le job de rendu est le type de job le plus courant. C’est souvent celui pour lequel vous souhaitez mettre une ferme de rendu en place. :baffed:

Scène de travail -> images rendues
  .mb/.gproject  |      .exr

Comme ce sont souvent les jobs les plus longs, il est préférable de séparer la génération des fichiers de rendu (.rib, .ass, etc.) et le rendu de ces derniers en deux jobs distincts :

Scène de travail -> fichiers de rendu -> images rendues
  .mb/.gproject  |     .rib/.ass      |      .exr

La génération des fichiers de rendu consiste à ouvrir une scène (Maya/Guerilla/Katana/Poalobra) et à faire les exports des fichiers de rendu (.rib, .ass, etc.), sur un range d’image donné.

Dans le cas de Guerilla, on ouvre le .gprojet, on exporte un range d’image (paquet de 10 par exemple : 101-110, 111-120, etc.) en .rib.

Pourquoi ne pas exporter toutes les images plutôt que par petits paquets ? Tout simplement parce que le but est de garder la durée de vos jobs aussi consistante et contrôlable que possible. :laClasse: Si on exportait « toutes les images », on aurait des temps très aléatoires suivant les plans du fait de la disparité de leur nombre d’images (certains plans font une trentaine d’image, d’autre plusieurs centaines). Si vous avez un bug sur la dernière image sur votre plan de 900 frames (oui parce qu’on a toujours un problème sur la PUTAIN de dernière image du plan le plus long du projet ! :injures: ), il vous faudrait tout relancer. Et si l’export dure 30 minutes, vous allez passer des heures à le déboguer. :casseTeteMur:

Diviser en paquet permet de rendre le temps humainement gérable.

Dans un monde idéal et instantané, on n’exportera qu’une seule image. Ceci permettrait d’avoir le job de rendu en dépendance directe sur le job de génération de fichier de rendu. Mais ce n’est pas possible, il y a un temps minimum nécessaire à l’ouverture de l’application et de la scène. :triste:

La taille des paquets dépendent de la lenteur générale de l’ouverture de la scène (Les scènes Maya peuvent être lente si elles ne sont pas « vides »). Plus la scène est lente à ouvrir, plus il est intéressant d’augmenter la taille du paquet.

J’utilise, en général, des paquets de 10 images pour Guerilla et 20 pour Maya. Mais c’est vraiment suivant la taille du projet. :redface:

Notez qu’il n’est pas toujours possible et/ou faisable d’avoir des jobs de génération de fichiers de rendu sur votre ferme de calcul. Par exemple, on peut vouloir rendre des ranges d’images directement depuis son logiciel favori (Maya à une époque et je suppose Blender, Cinema 4D, etc.). Cette méthode, plus « direct » peut se révéler chaotique dès lors que des problèmes apparaissent. Il faut être vigilant. :papi:

Job de rendu

C’est sûrement le type de job le plus long de la ferme de calcul. Je conseille fortement de n’avoir qu’une image par job.

Les informations importantes de tels jobs se situent dans le log. Certains job manager permettent de parser le log à la volée pour remonter des informations de façon claire (temps de rendu, fichiers manquants, etc.) voir, arrêter le job, faisant ainsi gagner un temps précieux.

Si les temps de rendu sont importants (12 heures et plus) et si votre moteur de rendu le permet, il peut être intéressant d’avoir accès à l’image « en l’état » avant la fin du rendu (au bout de 10 minutes par exemples) ceci permet de pouvoir s’assurer qu’aucun truc grossier n’est présent/absent du rendu et d’éviter de perdre des heures inutiles. :reflexionIntense:

La technique du pauvre consisterait à lancer deux jobs de rendu utilisant les mêmes fichiers de rendu mais avec des arguments de la ligne de commande différent :

Mais cela reste très imparfait, car quand le « vrai » rendu peut rarement partir dans les mêmes conditions que le premier. Il peut ainsi se retrouver sur une autre machine. Le meilleur reste donc d’utiliser l’image du rendu en cours. Si le moteur ne le permet pas, vous pouvez lancer une seule frame, au milieu du plan (ou une frame toutes les 5 frame par exemple) avec une priorité plus élevée. Vous n’attendez ainsi pas la fin complète des images pour en vérifier quelques-unes. Mais cette technique aussi est imparfaite, car vous ne voyez pas l’ensemble du plan, juste quelques images.

Dans les deux cas, cela vous permet de générer des vidéos de « pré-rendu », mais forcement, avec tout cela, le suivi du rendu est alourdi. :septic:

Job d’export Alembic

C’est sûrement le second job le plus courant : L’ouverture de scène puis l’export de son contenu au format Alembic.

La méthode bourrine consiste à ouvrir la scène et tout exporter dans un gros fichier .abc.

Ouverture de la scène Maya -> Export Alembic
         .mb               |       .abc

C’est lourd et complètement con. :vomit:

En pratique, il est plus pertinent de séparer les exports suivant leur nature. Ainsi, s’il y a 3 personnages dans la scène, l’animation de chaque personnage est exporté dans son Alembic respectif. Il est ainsi plus rapide de réexporter l’animation d’un seul personnage lors d’une retake.

Le plus intéressant étant encore de savoir ce que contient le plan à l’avance en vu de générer un job par personnage. :dentcasse:

Job d’export ATOM

À l’instar du rendu, plutôt que de passer directement par un job d’export Alembic, on pourrait passer par une étape intermédiaire consistant à exporter l’ATOM des contrôleurs d’animation, puis un second job, celui exportant l’Alembic, partirait d’une scène vide, amènerait une version du rig (la dernière par exemple) appliquerait l’ATOM sur les contrôleurs d’animation puis ferait l’export.

                                     Rig
                                      |
                                      v
​Scène de travail -> fichiers ATOM -> Maya -> Export anim
      .mb        |     .atom      |       |     .abc

Mais on pourrait aller encore plus loin ! :onSeFendLaPoire:

Pourquoi ne pas en profiter pour faire un preroll automatique pour nos amis du cloth et du FX ?

Pensez à garder les paramètres de numéro de frame modifiable afin que les départements puisse régénérer leur Alembic avec preroll si la version automatique n’est pas optimale.

Job de génération de vidéo

Ce job s’occupe de générer des vidéos, souvent .mp4 depuis une séquence d’image (.jpeg, .exr, etc.). Ce dernier est loin d’être simple.

On peut utiliser Nuke pour bénéficier de tous les outils dont il dispose (texte, option d’export, etc.), mais Nuke n’est pas donné et nécessite une licence. :cayMal:

Le module Python Pillow est vraiment très bon. Je le recommande chaudement. :sauteJoie:

Pour les cartouches (bandes noir en haut et en bas, affichant des informations) je déconseille de calculer dynamiquement leur taille via un pourcentage. Gardez une taille fixe et connu et simple à retenir pour tout le monde (100 pixels, 150 pixels, 200 pixels, etc.).

+--------------------------------+
|text1        text4        text7 |
|text2        text5        text8 |
|text3        text6        text9 |
|--------------------------------|
|                                |
|                                |
|           *picture*            |
|                                |
|                                |
|--------------------------------|
|text10       text13       text16|
|text11       text14       text17|
|text12       text15       text18|
+--------------------------------+

Neuf infos en haut et neuf infos en bas, soit 18 petits textes d’informations ! :sourit:

Liste non exhaustive d’informations intéressantes à mettre dans les cartouches :

Vous pouvez également ajouter une ou plusieurs images en début de vidéo avec plus d’informations (commentaire du graphiste, date des fichiers utilisés, mire colorimétrique, etc.).

Job de synchro

Que l’on travaille dans un studio multi-site ou qu’on utilise les ferme de rendu dans le cloud, les jobs de synchro sont monnaies courante. Ces jobs ne prennent quasiment aucune ressource et ne font bien souvent rien d’autre qu’attendre. En effet, ils sont rarement chargés de faire la synchro eux-mêmes mais passe par un service dédié auquel ils envoient des informations des fichiers à synchroniser. Ensuite, ils attendent que le service de synchro leur dise que les fichiers demandés ont été synchronise. D’où l’importance de s’assurer qu’aucun worker dédié à une tâche lourde (rendu, export, etc.) n’exécute un job de synchro. Ce serait une perte de temps énorme. Pour cela il peut être pratique d’avoir une machine compose de dizaines de worker qui ne font qu’exécuter des jobs de synchro (c’est-à-dire attendre… :seSentCon: ).

Une fois ces jobs exécutés ils « débloquent » les jobs en dépendance qui peuvent s’exécuter sur leur worker avec la garantie que les fichiers nécessaires sont présents.

Job de nettoyage

Souvent appelé cleanup jobs, leur mécanisme est parfois pris en charge par le job manager qui ne s’exécute qu’au moment de la suppression des jobs. On ne peut donc pas directement parler de « job » mais plutôt de cleanup list (le job contient la liste des fichiers/dossier à supprimer) :

{'cleanup':['/path/to/file.rib',
            '/path/to/folder']}

Mais tous ne le gèrent pas comme ça et un job dédié reste la méthode traditionnelle.

Ce job sert tout simplement à supprimer les fichiers qui ne sont plus nécessaires. Ça peut être les .rib ou des .vrmesh généré à la volée par la chaîne de job mais qui ne servaient qu’au calcul d’une image.

Il fut un temps, ces jobs supprimaient les shadow map, les point-clouds, les brick maps, etc. qui pesaient très lourd.

C’est la fin de cette première partie ! J’espère que ça vous aura intéressé. La suite, un jour… :aupoil:

:marioCours: