Zaplecze akceleratora

Opisanie obliczeń Tensor jest dość proste, ale kiedy i jak te obliczenia zostaną przeprowadzone, będzie zależeć od tego, który backend jest używany dla Tensor i kiedy wyniki będą potrzebne na procesorze hosta.

Za kulisami operacje na Tensor są wysyłane do akceleratorów, takich jak procesory graficzne lub TPU , lub uruchamiane na procesorze, gdy nie jest dostępny akcelerator. Dzieje się to automatycznie i ułatwia wykonywanie złożonych obliczeń równoległych przy użyciu interfejsu wysokiego poziomu. Jednak przydatne może być zrozumienie, w jaki sposób następuje ta wysyłka i możliwość dostosowania jej w celu uzyskania optymalnej wydajności.

Swift dla TensorFlow ma dwa backendy do wykonywania przyspieszonych obliczeń: tryb chętny TensorFlow i X10. Domyślnym backendem jest tryb chętny TensorFlow, ale można go zastąpić. Dostępny jest interaktywny samouczek , który przeprowadzi Cię przez korzystanie z tych różnych backendów.

Tryb chętny TensorFlow

Zaplecze trybu chętnego TensorFlow wykorzystuje interfejs API TensorFlow C do wysyłania każdej operacji Tensor do procesora graficznego lub procesora, gdy tylko zostanie ona napotkana. Wynik tej operacji jest następnie pobierany i przekazywany do następnej operacji.

To wysyłanie operacji po operacji jest łatwe do zrozumienia i nie wymaga jawnej konfiguracji w kodzie. Jednak w wielu przypadkach nie skutkuje to optymalną wydajnością ze względu na obciążenie związane z wysyłaniem wielu małych operacji w połączeniu z brakiem fuzji i optymalizacji operacji, które mogą wystąpić, gdy obecne są wykresy operacji. Wreszcie tryb chętny TensorFlow jest niekompatybilny z TPU i można go używać tylko z procesorami CPU i GPU.

X10 (śledzenie oparte na XLA)

X10 to nazwa backendu Swift dla TensorFlow, który wykorzystuje leniwe śledzenie tensora i kompilator optymalizujący XLA , aby w wielu przypadkach znacznie poprawić wydajność w porównaniu z wysyłaniem operacji po operacji. Dodatkowo dodaje kompatybilność z TPU , akceleratorami specjalnie zoptymalizowanymi pod kątem rodzajów obliczeń występujących w modelach uczenia maszynowego.

Użycie X10 do obliczeń Tensor nie jest ustawieniem domyślnym, dlatego należy włączyć tę opcję. Odbywa się to poprzez określenie, że Tensor jest umieszczony na urządzeniu XLA:

let tensor1 = Tensor<Float>([0.0, 1.0, 2.0], on: Device.defaultXLA)
let tensor2 = Tensor<Float>([1.5, 2.5, 3.5], on: Device.defaultXLA)

Po tym punkcie opisywanie obliczeń przebiega dokładnie tak samo, jak w przypadku trybu chętnego TensorFlow:

let tensor3 = tensor1 + tensor2

Dalsze szczegóły można podać podczas tworzenia Tensor , na przykład jakiego rodzaju akceleratora użyć, a nawet jakiego, jeśli jest ich kilka. Na przykład Tensor można utworzyć na drugim urządzeniu TPU (zakładając, że jest on widoczny dla hosta, na którym działa program), korzystając z następujących poleceń:

let tpuTensor = Tensor<Float>([0.0, 1.0, 2.0], on: Device(kind: .TPU, ordinal: 1, backend: .XLA))

Nie jest wykonywany żaden ukryty ruch Tensor między urządzeniami, więc jeśli w operacji zostaną użyte razem dwa Tensor na różnych urządzeniach, wystąpi błąd w czasie wykonywania. Aby ręcznie skopiować zawartość Tensor na nowe urządzenie, możesz użyć inicjatora Tensor(copying:to:) . Niektóre struktury o większej skali, zawierające w sobie elementy Tensor , takie jak modele i optymalizatory, posiadają funkcje pomocnicze umożliwiające przeniesienie wszystkich wewnętrznych elementów Tensor na nowe urządzenie w jednym kroku.

W przeciwieństwie do trybu chętnego TensorFlow, operacje korzystające z zaplecza X10 nie są wysyłane indywidualnie po ich napotkaniu. Zamiast tego wysłanie do akceleratora jest wyzwalane jedynie przez odczytanie obliczonych wartości z powrotem do hosta lub przez umieszczenie wyraźnej bariery. Działa to w ten sposób, że środowisko wykonawcze rozpoczyna się od wartości odczytanej hostowi (lub ostatniego obliczenia przed barierą ręczną) i śledzi wykres obliczeń, w wyniku których uzyskano tę wartość.

Ten prześledzony wykres jest następnie konwertowany do pośredniej reprezentacji XLA HLO i przekazywany do kompilatora XLA w celu optymalizacji i skompilowania do wykonania w akceleratorze. Stamtąd całe obliczenie jest przesyłane do akceleratora i uzyskiwany jest wynik końcowy.

Obliczenia są procesem czasochłonnym, dlatego X10 najlepiej stosować w przypadku masowo równoległych obliczeń, które są wyrażane za pomocą wykresu i które są wykonywane wielokrotnie. Stosowane są wartości skrótu i ​​buforowanie, dzięki czemu identyczne wykresy są kompilowane tylko raz dla każdej unikalnej konfiguracji.

W przypadku modeli uczenia maszynowego proces uczenia często obejmuje pętlę, w której model jest poddawany w kółko tej samej serii obliczeń. Będziesz chciał, aby każde z tych przebiegów było postrzegane jako powtórzenie tego samego śladu, a nie jako jeden długi wykres z powtarzającymi się jednostkami w środku. Jest to możliwe poprzez ręczne wstawienie wywołania funkcji LazyTensorBarrier() w miejscach w kodzie, w których ma się zakończyć śledzenie.

Obsługa mieszanej precyzji w X10

Obsługiwane jest szkolenie z mieszaną precyzją za pośrednictwem X10, a do jego kontrolowania dostępne są interfejsy API zarówno niskiego, jak i wysokiego poziomu. Interfejs API niskiego poziomu oferuje dwie obliczone właściwości: toReducedPrecision i toFullPrecision , które konwertują między pełną i zmniejszoną precyzją, wraz z isReducedPrecision do sprawdzania precyzji. Oprócz Tensor za pomocą tego interfejsu API można konwertować modele i optymalizatory między pełną i zmniejszoną precyzją.

Należy zauważyć, że konwersja na zmniejszoną precyzję nie zmienia typu logicznego Tensor . Jeśli t jest Tensor<Float> , t.toReducedPrecision jest również Tensor<Float> z podstawową reprezentacją o zmniejszonej precyzji.

Podobnie jak w przypadku urządzeń, operacje pomiędzy tensorami o różnej precyzji nie są dozwolone. Pozwala to uniknąć cichej i niechcianej promocji do wersji 32-bitowej, która byłaby trudna do wykrycia przez użytkownika.