Jak skompilować TensorFlow i nie zwariować

10.02.2020 | Robert Obłój

Wstęp

AI jest obecnie jednym z najbardziej obiecujących kierunków rozwoju w świecie IT. Rozwój AI jest na tyle dynamiczny, że praktycznie co chwilę możemy usłyszeć o nowym frameworku, który jest „nowszy”, „lepszy”, „fajniejszy”. Oczywiście nie ma jednoznacznej odpowiedzi na pytanie, który framework wybrać, ponieważ wiele zależy od problemu, który chcemy za pomocą AI rozwiązać.

Jakiś czas temu postanowiliśmy się zmierzyć z tematem wykrywania anomalii za pomocą AI. Przy okazji zdecydowaliśmy się na zastosowanie TensorFlow, udostępnionego przez Google. TensorFlow to framework, który umożliwia zbudowanie dowolnego modelu AI za pomocą niskopoziomowego API. Takie podejście ma pewną wadę, ponieważ framework nie jest łatwy do użycia dla początkujących. Z drugiej strony bogate API umożliwia zbudowanie praktycznie dowolnego modelu, dzięki czemu (mając odpowiednio dużą wiedzę) możemy stworzyć „coś”, co będzie unikalne i innowacyjne.

W naszym przypadku zdecydowaliśmy się na zbudowanie modelu autoencodera, co samo w sobie nie jest może unikalne i specjalnie innowacyjne ;-), ale dzięki temu mogliśmy się skupić na poznaniu możliwości TensorFlow. Po kilku miesiącach pracy udało nam się zbudować model, który miał dobrą skuteczność. Postanowiliśmy model zainstalować na produkcji… i tutaj zaczęły się nasze problemy…

Problem

W trakcie instalacji na środowisku private cloud okazało się, że nasz model nie tyle nie działa, co po prostu nie potrafił się uruchomić. Lokalnie na stacjach developerskich wszystko było OK, a na serwerze mieliśmy niewiele wyjaśniający błąd:

Illegal instruction

W logach mieliśmy dokładnie dwa słowa, nic więcej. Googlowanie niewiele wniosło (dziś już drugi link daje wskazówkę, co jest przyczyną), ale po małym śledztwie okazało się, że TensorFlow w wersji 1.6 włączył domyślnie instrukcję AVX dla CPU.

Czyli mamy pewną wskazówkę, trzeba to tylko potwierdzić:

cat /proc/cpuinfo | grep flags
flags:  fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse 
sse2 ss syscall nx lm constant_tsc rep_good nopl eagerfpu pni cx16 hypervisor lahf_lm

Faktycznie, procesor na docelowym serwerze nie wspiera instrukcji AVX.

Rozwiązanie

Aby nasz model działał w środowisku private cloud, musimy zbudować „własną” wersję TensorFlow ze źródeł (z wyłączoną instrukcją AVX). Będziemy do tego potrzebować:

Binaria

Wersja

Bazel

0.24.1

Gcc

4.8.5

Python

3.7

Tensorflow

r.1.14

 

Słowem wyjaśnienia:

  • Bazel to narzędzie Google’a umożliwiające budowanie i testowanie oprogramowania. Jest to narzędzie podobne do Gradle, Maven, itp.
  • Gcc czyli GNU Compiler Collection, czyli zestaw kompilatorów o otwartym kodzie źródłowym rozwijany w ramach projektu GNU.
  • Python, czyli język programowania oraz TensorFlow, czyli framework, który chcemy skompilować ze żródeł.

UWAGA: W naszym rozwiązaniu skompilujemy Tensorflow dla CPU. Pomijamy w tym artykule wszystkie kwestie związane z wykorzystaniem GPU.

Dockerfile dla narzędzi bazel, gcc i python

W pierwszym pliku Dockerfile skonfigurujemy wszystkie narzędzia niezbędne do skompilowania tensorflow. W tym celu instalujemy:

  • Kompilator GCC w wersji 4.8.5
  • Framework Bazel w wersji 0.24.1
  • Interpreter Python w wersji 3.7

Dockerfile dla źródeł TensorFlow

W przypadku źródeł TensorFlow wystarczy je pobrać za pomocą komendy wget oraz wypakować do katalogu tymczasowego:

Dockerfile dla procesu kompilacji

Aby skompilować TensorFlow ze źródeł, musimy wiedzieć jakie instrukcje chcemy włączyć, a jakie wyłączyć. Przypomnijmy, że w naszym przypadku mamy dostępne następujące instrukcje:

cat /proc/cpuinfo | grep flags
flags:  fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse 
sse2 ss syscall nx lm constant_tsc rep_good nopl eagerfpu pni cx16 hypervisor lahf_lm

Wszystko OK, ale jak zmapować instrukcje CPU na nasz proces kompilacji TensorFlow? Najprościej możemy to zrobić za pomocą dokumentacji gcc (przełącznik –march=cpu-type).

Kompilacja

Rozpatrzymy następujące scenariusze:

  • kompilacja na docelowym środowisku,
  • kompilacja z predefiniowaną architekturą CPU,
  • kompilacja custom.

Kompilacja na docelowym środowisku

Kompilacja na docelowym środowisku jest najbezpieczniejszą opcją. W tym celu wybieramy opcję:

-march=cpu-native

Zauważmy, że dla TensorFlow powyższą opcję musimy przemapować na:

ENV CC_OPT_FLAGS "-mtune=native"

Dzięki czemu kompilacja TensorFlow wygląda następująco:

Kompilacja z predefiniowaną architekturą CPU

W przypadku, gdy nie możemy przeprowadzić kompilacji na docelowym serwerze, ale lista wspieranych instrukcji CPU mapuje się dokładnie na parametr –march, proces kompilacji wygląda bardzo podobnie. Załóżmy na chwilę, że nasz procesor wspiera jedynie instrukcje MMX, SSE, SSE2, SSE3 oraz SSSE3. Na postawie dokumentacji wybieramy opcję:

-march=cpu-core2

Przemapowaną na:

ENV CC_OPT_FLAGS "-mtune=core2"

Dockerfile wygląda bardzo podobnie:

Kompilacja custom

W przypadku, gdy chcemy sami zdefiniować, które instrukcje mamy włączyć, a które wyłączyć, możemy to zrobić poprzez wybranie opcji generic oraz poprzez specjalne przełączniki w kompilacji TensorFlow, które pewne instrukcje włączą lub wyłączą:

Jak to działa?

Uruchamiamy proces kompilacji, np.:

docker run --name tf-native -it robloj/tensorflow-native

UWAGA: Proces kompilacji tensorflow jest bardzo skomplikowany i może zająć nawet kilka godzin!

Czekamy, aż proces kompilacji się zakończy, w logach powinniśmy mieć wpisy podobne do:

bazel-out/k8-opt/genfiles/external/protobuf_archive/src: warning: directory does not exist.
Target //tensorflow/tools/pip_package:build_pip_package up-to-date:
  bazel-bin/tensorflow/tools/pip_package/build_pip_package
INFO: Elapsed time: 9026.270s, Critical Path: 95.38s
INFO: 6801 processes: 6801 local.
INFO: Build completed successfully, 7502 total actions
Sun Aug 19 09:23:50 UTC 2018 : === Preparing sources in dir: /tmp/tmp.jbIBSPuSwb
/tensorflow-r1.14 /tensorflow-r1.14
/tensorflow-r1.14
Sun Aug 19 09:24:02 UTC 2018 : === Building wheel
warning: no files found matching '*.dll' under directory '*'
warning: no files found matching '*.lib' under directory '*'
warning: no files found matching '*.h' under directory 'tensorflow/include/tensorflow'
warning: no files found matching '*' under directory 'tensorflow/include/Eigen'
warning: no files found matching '*.h' under directory 'tensorflow/include/google'
warning: no files found matching '*' under directory 'tensorflow/include/third_party'
warning: no files found matching '*' under directory 'tensorflow/include/unsupported'
Sun Aug 19 09:24:21 UTC 2018 : === Output wheel file is in: /tmp/tensorflow_pkg

Dockerfile dla kompilacji został przygotowany tak, aby po zakończonym procesie kompilacji uruchomił komendę:

sleep infinity

Dzięki temu możemy pobrać skompilowane binaria TensorFlow. W tym celu sprawdzamy ID kontenera:

docker ps

Przykładowo, nasz kontener ma następujący identyfikator:

CONTAINER ID   IMAGE               COMMAND       CREATED        STATUS       PORTS  NAMES
b4fef7c3adfd   tf-native  "/bin/sh..."  5 seconds ago  Up 4 seconds        tf-native

Mając ID kontenera, możemy skopiować binaria TensorFlow do lokalnego filesystemu:

CONTAINER_ID=b4fef7c3adfd
DEST_DIR=/tmp/output

docker cp $CONTAINER_ID:/tmp/tensorflow_pkg $DEST_DIR

Sprawdźmy dla pewności, co się skopiowało do katalogu DEST_DIR:

ls $DEST_DIR

tensorflow-1.14.1-cp37-cp37m-linux_x86_64.whl

Udało nam się skompilować TensorFlow ze źrodeł. Pozostaje jedynie tą binarkę zainstalować:

cd $DEST_DIR
python -m pip install tensorflow-1.14.1-cp37-cp37m-linux_x86_64.whl

Podsumowanie

Projekty związane z rozwojem AI to nie tylko analiza danych, big data czy sieci neuronowe. Czasem przychodzi nam się zmierzyć z problemami, które są niskopoziomowe. Zmierzenie się z nimi (zwłaszcza, że dokumentacja TensorFlow była wówczas bardzo uboga na temat kompilacji ze źródeł) pokazuje, że zespół data science oprócz ról takich jak data engineer, czy data scientist, powinien mieć również kompetencje, które pozwolą rozwiązywać różne, często niskopoziomowe, problemy.