Inférence de type

À l'origine, StableHLO a été amorcé à partir du dialecte MHLO et a hérité de l'implémentation MHLO de l'inférence de type. La progression de la mise en œuvre est suivie dans status.md.

Les consignes proposées ci-dessous visent à garantir l'implémentation de vérificateurs et de fonctions de forme de haute qualité pour les opérations StableHLO.

Proposition

Ces propositions s'appliquent à la fois au réaménagement des implémentations existantes et à la réalisation de nouvelles opérations jusqu'à une couverture complète.

(P1) Utiliser la spécification StableHLO comme source de référence

La spec est la source de référence pour tous les vérificateurs et les fonctions de forme des opérations StableHLO. Les vérificateurs et les fonctions de forme existants de chaque opération doivent être réexaminés afin d'être entièrement conformes à la spécification. Notez que le document de spécification ne cesse d'évoluer. Si la spécification d'une opération n'est pas disponible, l'implémentation XLA doit être utilisée comme source de référence, y compris xla/service/shape_inference.cc et xla/service/hlo_verifier.cc. L'implémentation XLA ne couvre pas le dynamisme illimité. Pour un dynamisme illimité, nous faisons donc preuve de bon sens jusqu'à ce que le RFC du dynamisme soit disponible.

(P2) Exploitez tout le potentiel d'ODS

Les fichiers ODS (comme StablehloOps.td) définissent les opérations avec des caractéristiques et des types pour chaque opérande, attribut ou résultat, et effectuent les vérifications. Ainsi, AUCUN code de vérification n'est nécessaire dans les vérificateurs ou les fonctions de forme pour les propriétés déjà garanties par l'ODS. Supprimez le code de validation s'il est dupliqué avec ODS, car ils ne seront jamais déclenchés.

Devons-nous ajouter des tests pour les contraintes de l'ODS ? Veuillez consulter Établir des consignes de test.

(P3) Gérer le code de validation dans les vérificateurs et les fonctions de forme

Les deux:

  • verifiers: implémentés par Op::verify() et
  • Fonctions de forme: implémentées par des fonctions InferTypeOpInterface telles que Op::inferReturnTypes() ou Op::inferReturnTypeComponents

peut contenir un code de validation permettant de vérifier les opérandes, les attributs ou les résultats. Un fractionnement initial peut se présenter comme suit: laissez les vérificateurs vérifier les opérandes/attributs, puis laissez les fonctions de forme calculer uniquement les types de résultats déduits et vérifier la compatibilité avec les types de résultats réels. Cependant, en réalité, cette division présente quelques problèmes:

  • La fonction de forme peut être appelée par les fonctions build() générées automatiquement, sans appeler d'abord l'outil de vérification. Les entrées associées doivent donc être vérifiées dans la fonction de forme.
  • Code en double: par exemple, dans les vérificateurs, nous effectuons un traitement sur les opérandes, puis nous vérifions des résultats intermédiaires. Dans les fonctions de forme, ces résultats intermédiaires sont ensuite utiles pour déduire les résultats finaux. Ces résultats intermédiaires doivent être calculés deux fois.
  • Charge de maintenance: les vérifications d'une opération sont contenues dans deux méthodes différentes.

La solution est la suivante:

  1. Pour la plupart des opérations sans région (comme PadOp): essayez d'intégrer tout le code de validation dans les fonctions de forme et supprimez complètement les vérificateurs. Dans les cas où cela n'est pas possible en raison de l'impossibilité de déduire les types renvoyés (par exemple, avec ReshapeOp ou BroadcastInDimOp), créez un vérificateur contenant la logique de vérification nécessaire. Les opérations généralement inférables, comme AddOp, peuvent toujours avoir besoin d'un vérificateur pour effectuer des vérifications supplémentaires, car elles vérifient les contraintes d'un type renvoyé fourni, qui n'est pas accessible dans les méthodes d'inférence de type/forme.

  2. Pour les opérations avec des régions (comme ReduceOp/IfOp ; la liste complète est disponible ici): les compilateurs générés automatiquement n'utilisent pas de régions comme paramètres. Par conséquent, si ces compilateurs impliquent l'inférence de type, la fonction de forme est appelée avec des régions vides (voir cet exemple).

    1. Si les régions ne sont pas nécessaires pour l'inférence de type (comme ReduceOp), placez la logique de vérification liée à la région dans des vérificateurs au lieu des fonctions de forme. Dupliquez un code s'il est inévitable.

    2. Si les régions sont nécessaires pour l'inférence de type (IfOp/CaseOp/MapOp), la fonction de forme doit également vérifier que les régions ne sont pas explicitement vides, même si l'ODS peut déjà garantir leur existence dans la définition de l'opération.

(P4) Établir des consignes de test

Dois-je ajouter/maintenir des tests pour les vérifications couvertes par ODS ?

Nous n'en avons pas. Les tests doivent se concentrer sur les vérificateurs et les fonctions de forme, tandis que les modifications apportées à ODS nécessitent une révision de cette opération.

Toutefois, faites attention aux éléments manquants: par exemple, si l'opération contient la caractéristique SameOperandsAndResultShape, qui ne vérifie que les formes, mais pas le type d'élément, la vérification des types d'éléments des opérandes/résultats nécessite toujours des tests.

Où plaçons-nous les tests des vérificateurs et de l'inférence de type ?

ops_stablehlo.mlir contient les cas positifs d'opérations et (au moins) un test négatif pour chaque erreur de vérification. Il peut également vérifier que le type renvoyé inféré est compatible avec le type de résultat réel (différent).

infer_stablehlo.mlir vérifie l'existence de la fonction de forme d'une opération par ligne avec hlo_test_infer.get_return_type_components"(%x):... et vérifie que le type déduit correspond exactement à ce qui était prévu. En général, un test positif par opération est requis.

Que faire ?

Lors de l'implémentation ou de la révision de la fonction de vérification et/ou de forme d'une opération:

  1. Indiquez tous les cas positifs et négatifs dans ops_stablehlo.mlir.

  2. Ajoutez un seul test positif dans infer_stablehlo.mlir pour tester l'interface.

  3. (Facultatif) Si une opération est compliquée et peut contenir de nombreux tests, envisagez d'ajouter un fichier de test distinct nommé verify_<op_name>.mlir ou verify_<your_topic>.mlir dans le même dossier.