Wnioskowanie typu

Element StableHLO został pierwotnie uruchomiony z dialektu MHLO i odziedziczył implementację MHLO wnioskowania typu. Postęp implementacji można śledzić w pliku status.md.

Proponowane poniżej wytyczne mają na celu wdrożenie wysokiej jakości weryfikatorów i funkcji kształtu w operacjach StableHLO.

Propozycja

Propozycje dotyczą zarówno ponownego sprawdzania istniejących implementacji, jak i realizowania nowych operacji aż do momentu ich kompleksowej obsługi.

(P1) Użyj specyfikacji StableHLO jako źródła danych

spec jest źródłem informacji o wszystkich weryfikatorach i funkcjach kształtu w operacjach StableHLO. Zweryfikuj ponownie istniejące weryfikatory i funkcje kształtujące każdej operacji, aby zapewnić pełną zgodność ze specyfikacją. Pamiętaj, że specyfikacja stale się rozwija. Jeśli specyfikacja operacji jest niedostępna, źródłem wiarygodnych danych powinna być implementacja XLA, w tym xla/service/shape_inference.cc i xla/service/hlo_verifier.cc. Implementacja XLA nie obejmuje nieograniczonej dynamiki, dlatego w przypadku nieograniczonej dynamiki będziemy stosować zdrowy rozsądek, dopóki nie znajdzie się RFC dynamiki.

(P2) Wykorzystaj pełnię możliwości ODS

Pliki ODS (np. StablehloOps.td) definiują operacje z wykorzystaniem cech i typów w przypadku każdego argumentu/atrybutu/wyniku i przeprowadzają weryfikację. Dlatego w przypadku usług, które już są gwarantowane przez ODS, nie trzeba stosować kodu weryfikacyjnego. Usuń kod weryfikacyjny, jeśli został on powielony w ODS, ponieważ nigdy nie zostanie wywołany.

Czy trzeba dodawać testy pod kątem ograniczeń ODS? Zapoznaj się z artykułem Ustal wytyczne dotyczące testowania.

(P3) Obsługa kodu weryfikacyjnego w weryfikatorach i kształtowanie funkcji

Obie możliwości:

  • weryfikatory: zaimplementowane przez Op::verify(),
  • funkcje kształtu: implementowane przez obiekty InferTypeOpInterface, takie jak Op::inferReturnTypes() lub Op::inferReturnTypeComponents

może mieć kod weryfikacyjny do sprawdzania operandów/atrybutów/wyników. Początkowy podział może wyglądać tak: weryfikatorzy powinni sprawdzić operandy/atrybuty, a następnie umożliwić funkcjom kształtowania tylko wywnioskowane typy wyników i sprawdzanie zgodności z typami wyników rzeczywistych. Jednak w rzeczywistości ten podział ma jednak kilka problemów:

  • Funkcja kształtu może zostać wywołana przez automatycznie wygenerowane funkcje build() bez wcześniejszego wywoływania weryfikatora. Powiązane dane wejściowe również muszą być zweryfikowane w funkcji kształtu.
  • Zduplikowany kod: na przykład w mechanizmach weryfikacyjnych wykonujemy przetwarzanie argumentów, a potem weryfikujemy wyniki pośrednie. Następnie w funkcjach kształtujących wyniki pośrednie mogą być przydatne do wywnioskowania ostatecznych wyników. Wyniki pośrednie muszą zostać obliczone dwukrotnie.
  • Obciążenia związane z konserwacją: weryfikacje operacji są realizowane w ramach 2 różnych metod.

Rozwiązanie jest następujące:

  1. W przypadku większości operacji bez regionów (np. PadOp): spróbuj umieścić cały kod weryfikacyjny w funkcjach kształtu i całkowicie odrzuć weryfikatory. Jeśli nie jest to możliwe z powodu braku możliwości określenia typów zwrotów (np. za pomocą funkcji ReshapeOp lub BroadcastInDimOp), utwórz weryfikatora, który będzie zawierał niezbędną logikę weryfikacji. Zwykle operacje możliwe do wywnioskowania, takie jak AddOp, mogą nadal wymagać weryfikatora do przeprowadzenia dodatkowej weryfikacji, ponieważ weryfikuje on ograniczenia podanego typu zwrotu, który jest niedostępny w metodach wnioskowania na podstawie typu/kształtu.

  2. W przypadku operacji obejmujących regiony (np. ReduceOp/IfOp; pełna lista znajduje się tutaj): automatycznie generowane komponenty nie przyjmują regionów jako parametrów, więc jeśli w tych konstruktorach uwzględniono wnioskowanie o typie, funkcja kształtu zostanie wywołana z pustymi regionami (zobacz ten przykład).

    1. Jeśli regiony nie są potrzebne do wnioskowania typu (np. ReduceOp), zamiast funkcji kształtu umieść w weryfikatorach logikę weryfikacji związaną z regionem. Powiel kod, jeśli jest to nieuniknione.

    2. Jeśli regiony są potrzebne do wnioskowania typu (IfOp/CaseOp/MapOp), dodatkowo funkcja kształtu musi sprawdzać, czy regiony nie są wyraźnie puste, mimo że ODS może już zagwarantować istnienie swojego działania w definicji operacji.

(P4) Opracowanie wytycznych dotyczących testowania

Czy musimy dodawać lub prowadzić testy w ramach weryfikacji objętych ODS?

My nie. Testy powinny koncentrować się na weryfikatorach i funkcjach kształtu, a zmiany w ODS wymagają ponownej weryfikacji.

Uważaj jednak na brakujące elementy. Jeśli na przykład operacja zawiera cechę SameOperandsAndResultShape, która sprawdza tylko kształty, ale nie typ elementu, weryfikacja określonych typów operandów/wyników nadal wymaga testów.

Gdzie przeprowadzamy testy weryfikatorów i wnioskowania o typach?

Plik ops_stablehlo.mlir zawiera pozytywne przypadki użycia i (co najmniej) 1 ujemny test na każdy błąd weryfikacji. Umożliwia też sprawdzenie, czy wywnioskowany zwracany typ jest zgodny z rzeczywistym typem wyniku (innym niż!).

infer_stablehlo.mlir sprawdza istnienie funkcji kształtu operacji w wierszu z hlo_test_infer.get_return_type_components"(%x):... i sprawdza, czy wywnioskowany typ pasuje dokładnie do oczekiwanego. Ogólnie na operację pozytywną.

Co możesz zrobić

Podczas implementowania lub ponownego sprawdzania weryfikatora albo funkcji kształtu:

  1. Wszystkie przypadki pozytywne i negatywne umieść w pliku ops_stablehlo.mlir.

  2. Dodaj jeden test w pliku infer_stablehlo.mlir, aby przetestować interfejs.

  3. (Opcjonalnie) Jeśli operacja jest skomplikowana i może zawierać wiele testów, rozważ dodanie osobnego pliku testowego o nazwie verify_<op_name>.mlir lub verify_<your_topic>.mlir w tym samym folderze.