Budowanie standardowego serwera modeli TensorFlow

W tym samouczku pokazano, jak używać składników TensorFlow Serving do kompilowania standardowego TensorFlow ModelServer, który dynamicznie odnajduje i udostępnia nowe wersje przeszkolonego modelu TensorFlow. Jeśli chcesz używać standardowego serwera do obsługi swoich modeli, zobacz podstawowy samouczek dotyczący obsługi TensorFlow .

W tym samouczku zastosowano prosty model regresji Softmax wprowadzony w samouczku TensorFlow do klasyfikacji obrazów pisanych odręcznie (dane MNIST). Jeśli nie wiesz, czym jest TensorFlow lub MNIST, zapoznaj się z samouczkiem MNIST dla początkujących ML .

Kod tego samouczka składa się z dwóch części:

  • Plik Pythona mnist_saved_model.py , który szkoli i eksportuje wiele wersji modelu.

  • Plik C++ main.cc będący standardowym serwerem TensorFlow ModelServer, który wykrywa nowe wyeksportowane modele i uruchamia usługę gRPC w celu ich obsługi.

W tym samouczku opisano następujące zadania:

  1. Trenuj i eksportuj model TensorFlow.
  2. Zarządzaj wersjonowaniem modelu za pomocą TensorFlow Serving ServerCore .
  3. Skonfiguruj przetwarzanie wsadowe przy użyciu SavedModelBundleSourceAdapterConfig .
  4. Obsługuj żądanie za pomocą TensorFlow obsługującego ServerCore .
  5. Uruchom i przetestuj usługę.

Zanim zaczniesz, najpierw zainstaluj Docker

Trenuj i eksportuj model TensorFlow

Po pierwsze, jeśli jeszcze tego nie zrobiłeś, sklonuj to repozytorium na swój komputer lokalny:

git clone https://github.com/tensorflow/serving.git
cd serving

Wyczyść katalog eksportu, jeśli już istnieje:

rm -rf /tmp/models

Trenuj (ze 100 iteracjami) i eksportuj pierwszą wersję modelu:

tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
  --training_iteration=100 --model_version=1 /tmp/mnist

Trenuj (z 2000 iteracjami) i eksportuj drugą wersję modelu:

tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
  --training_iteration=2000 --model_version=2 /tmp/mnist

Jak widać w mnist_saved_model.py , trenowanie i eksportowanie odbywa się w taki sam sposób, jak w podstawowym samouczku dotyczącym obsługi TensorFlow . W celach demonstracyjnych celowo wybierasz iteracje szkoleniowe dla pierwszego uruchomienia i eksportujesz je jako v1, podczas gdy trenujesz normalnie w drugim uruchomieniu i eksportujesz je jako v2 do tego samego katalogu nadrzędnego - tak jak oczekujemy, że ten ostatni osiągnie lepsza dokładność klasyfikacji dzięki intensywniejszemu szkoleniu. Powinieneś zobaczyć dane treningowe dla każdego przebiegu treningowego w katalogu /tmp/mnist :

$ ls /tmp/mnist
1  2

Rdzeń Serwera

Teraz wyobraź sobie, że wersje 1 i 2 modelu są generowane dynamicznie w czasie wykonywania, gdy eksperymentowane są nowe algorytmy lub gdy model jest szkolony przy użyciu nowego zestawu danych. W środowisku produkcyjnym możesz chcieć zbudować serwer obsługujący stopniowe wdrażanie, w którym można wykrywać, ładować, eksperymentować, monitorować lub przywracać wersję 2 podczas udostępniania wersji 1. Alternatywnie możesz chcieć usunąć wersję 1 przed przywołaniem wersji 2. TensorFlow Serving obsługuje obie opcje — jedna jest dobra do utrzymywania dostępności podczas przejścia, druga jest dobra do minimalizowania zużycia zasobów (np. pamięci RAM).

Dokładnie to robi TensorFlow Serving Manager . Obsługuje pełny cykl życia modeli TensorFlow, w tym ich ładowanie, udostępnianie i rozładowywanie, a także przejścia wersji. W tym samouczku zbudujesz swój serwer na bazie TensorFlow Serving ServerCore , który wewnętrznie otacza AspiredVersionsManager .

int main(int argc, char** argv) {
  ...

  ServerCore::Options options;
  options.model_server_config = model_server_config;
  options.servable_state_monitor_creator = &CreateServableStateMonitor;
  options.custom_model_config_loader = &LoadCustomModelConfig;

  ::google::protobuf::Any source_adapter_config;
  SavedModelBundleSourceAdapterConfig
      saved_model_bundle_source_adapter_config;
  source_adapter_config.PackFrom(saved_model_bundle_source_adapter_config);
  (*(*options.platform_config_map.mutable_platform_configs())
      [kTensorFlowModelPlatform].mutable_source_adapter_config()) =
      source_adapter_config;

  std::unique_ptr<ServerCore> core;
  TF_CHECK_OK(ServerCore::Create(options, &core));
  RunServer(port, std::move(core));

  return 0;
}

ServerCore::Create() przyjmuje parametr ServerCore::Options. Oto kilka często używanych opcji:

  • ModelServerConfig , który określa modele do załadowania. Modele są deklarowane albo poprzez model_config_list , który deklaruje statyczną listę modeli, albo poprzez custom_model_config , który definiuje niestandardowy sposób deklarowania listy modeli, które mogą zostać zaktualizowane w czasie wykonywania.
  • PlatformConfigMap , która odwzorowuje nazwę platformy (taką jak tensorflow ) na PlatformConfig , która służy do tworzenia SourceAdapter . SourceAdapter dostosowuje StoragePath (ścieżkę, w której wykryta jest wersja modelu) do Loader model (ładowuje wersję modelu ze ścieżki magazynu i udostępnia interfejsy przejścia stanu do Manager ). Jeśli PlatformConfig zawiera SavedModelBundleSourceAdapterConfig , zostanie utworzony SavedModelBundleSourceAdapter , co wyjaśnimy później.

SavedModelBundle to kluczowy element udostępniania TensorFlow. Reprezentuje model TensorFlow załadowany z danej ścieżki i zapewnia ten sam interfejs Session::Run co TensorFlow do uruchamiania wnioskowania. SavedModelBundleSourceAdapter dostosowuje ścieżkę przechowywania do Loader<SavedModelBundle> dzięki czemu okres życia modelu może być zarządzany przez Manager . Należy pamiętać, że SavedModelBundle jest następcą przestarzałego SessionBundle . Zachęcamy użytkowników do korzystania z SavedModelBundle , ponieważ obsługa SessionBundle zostanie wkrótce usunięta.

Mając to wszystko na uwadze, ServerCore wewnętrznie wykonuje następujące czynności:

  • Tworzy instancję FileSystemStoragePathSource , która monitoruje ścieżki eksportu modelu zadeklarowane w model_config_list .
  • Tworzy instancję SourceAdapter przy użyciu PlatformConfigMap z platformą modelową zadeklarowaną w model_config_list i łączy z nią FileSystemStoragePathSource . W ten sposób za każdym razem, gdy w ścieżce eksportu zostanie wykryta nowa wersja modelu, SavedModelBundleSourceAdapter dostosowuje ją do Loader<SavedModelBundle> .
  • Tworzy instancję określonej implementacji Manager o nazwie AspiredVersionsManager , która zarządza wszystkimi takimi instancjami Loader utworzonymi przez SavedModelBundleSourceAdapter . ServerCore eksportuje interfejs Manager , delegując wywołania do AspiredVersionsManager .

Ilekroć dostępna jest nowa wersja, ten AspiredVersionsManager ładuje nową wersję i zgodnie z domyślnym zachowaniem usuwa starą. Jeśli chcesz rozpocząć dostosowywanie, zachęcamy do zrozumienia komponentów tworzonych wewnętrznie i sposobu ich konfiguracji.

Warto wspomnieć, że TensorFlow Serving został zaprojektowany od podstaw tak, aby był bardzo elastyczny i rozszerzalny. Możesz tworzyć różne wtyczki, aby dostosować zachowanie systemu, korzystając jednocześnie z ogólnych podstawowych komponentów, takich jak ServerCore i AspiredVersionsManager . Można na przykład zbudować wtyczkę źródła danych, która monitoruje pamięć w chmurze zamiast pamięci lokalnej, lub można zbudować wtyczkę zasad wersji, która przeprowadza zmianę wersji w inny sposób — można nawet zbudować niestandardową wtyczkę modelu, która obsługuje modele inne niż TensorFlow. Te tematy wykraczają poza zakres tego samouczka. Więcej informacji można jednak znaleźć w niestandardowych źródłach i niestandardowych samouczkach, które można udostępnić.

Dozowanie

Inną typową funkcją serwera, której potrzebujemy w środowisku produkcyjnym, jest przetwarzanie wsadowe. Nowoczesne akceleratory sprzętowe (procesory graficzne itp.) używane do wnioskowania w ramach uczenia maszynowego zwykle osiągają najlepszą wydajność obliczeniową, gdy żądania wnioskowania są uruchamiane w dużych partiach.

Przetwarzanie wsadowe można włączyć, podając odpowiednią SessionBundleConfig podczas tworzenia SavedModelBundleSourceAdapter . W tym przypadku ustawiliśmy BatchingParameters na prawie wartości domyślne. Dzielenie wsadowe można dostosować, ustawiając niestandardowe wartości limitu czasu, rozmiaru_wsadu itp. Aby uzyskać szczegółowe informacje, zobacz BatchingParameters .

SessionBundleConfig session_bundle_config;
// Batching config
if (enable_batching) {
  BatchingParameters* batching_parameters =
      session_bundle_config.mutable_batching_parameters();
  batching_parameters->mutable_thread_pool_name()->set_value(
      "model_server_batch_threads");
}
*saved_model_bundle_source_adapter_config.mutable_legacy_config() =
    session_bundle_config;

Po osiągnięciu pełnej partii żądania wnioskowania są wewnętrznie łączone w jedno duże żądanie (tensor) i wywoływany jest tensorflow::Session::Run() (stąd pochodzi rzeczywisty wzrost wydajności procesorów graficznych).

Podawaj z menadżerem

Jak wspomniano powyżej, TensorFlow Serving Manager został zaprojektowany jako ogólny komponent, który może obsługiwać ładowanie, udostępnianie, rozładowywanie i przenoszenie wersji modeli generowanych przez dowolne systemy uczenia maszynowego. Jego interfejsy API są zbudowane wokół następujących kluczowych koncepcji:

  • Servable : Servable to dowolny nieprzezroczysty obiekt, którego można użyć do obsługi żądań klientów. Rozmiar i szczegółowość serwowalnego obiektu jest elastyczny, tak że pojedynczy serwable może zawierać wszystko, od pojedynczego fragmentu tabeli przeglądowej, przez pojedynczy model uczony maszynowo, aż po krotkę modeli. Obiekt serwowalny może być dowolnego typu i interfejsu.

  • Wersja obsługiwana : Elementy serwowalne są wersjonowane, a Manager obsługi TensorFlow może zarządzać jedną lub większą liczbą wersji serwowalnych. Wersjonowanie umożliwia jednoczesne ładowanie więcej niż jednej wersji obiektu serwowalnego, co ułatwia stopniowe wdrażanie i eksperymentowanie.

  • Strumień możliwy do serwowania : Strumień możliwy do serwowania to sekwencja wersji strumienia udostępnianego, z rosnącymi numerami wersji.

  • Model : model uczony maszynowo jest reprezentowany przez jeden lub więcej obiektów serwable. Przykładowe serwable to:

    • Sesja TensorFlow lub opakowania wokół nich, takie jak SavedModelBundle .
    • Inne rodzaje modeli uczących się maszynowo.
    • Tabele wyszukiwania słownictwa.
    • Osadzanie tabel przeglądowych.

    Model złożony może być reprezentowany jako wiele niezależnych obiektów serwable lub jako pojedynczy obiekt złożony. Element serwowalny może również odpowiadać części Modelu, na przykład z dużą tabelą przeglądową podzieloną na wiele instancji Manager .

Aby umieścić to wszystko w kontekście tego samouczka:

  • Modele TensorFlow są reprezentowane przez jeden rodzaj obsługiwanego elementu — SavedModelBundle . SavedModelBundle wewnętrznie składa się z tensorflow:Session w połączeniu z pewnymi metadanymi dotyczącymi tego, jaki wykres jest ładowany do sesji i jak go uruchomić w celu wyciągnięcia wniosków.

  • Istnieje katalog systemu plików zawierający strumień eksportów TensorFlow, każdy w swoim własnym podkatalogu, którego nazwa jest numerem wersji. Katalog zewnętrzny można traktować jako serializowaną reprezentację obsługiwanego strumienia dla obsługiwanego modelu TensorFlow. Każdy eksport odpowiada elementom serwowalnym, które można załadować.

  • AspiredVersionsManager monitoruje strumień eksportu i dynamicznie zarządza cyklem życia wszystkich obiektów SavedModelBundle .

TensorflowPredictImpl::Predict a następnie po prostu:

  • Żąda SavedModelBundle od menedżera (poprzez ServerCore).
  • Używa generic signatures do mapowania logicznych nazw tensorów w PredictRequest na rzeczywiste nazwy tensorów i wiązania wartości z tensorami.
  • Uruchamia wnioskowanie.

Przetestuj i uruchom serwer

Skopiuj pierwszą wersję eksportu do monitorowanego folderu:

mkdir /tmp/monitored
cp -r /tmp/mnist/1 /tmp/monitored

Następnie uruchom serwer:

docker run -p 8500:8500 \
  --mount type=bind,source=/tmp/monitored,target=/models/mnist \
  -t --entrypoint=tensorflow_model_server tensorflow/serving --enable_batching \
  --port=8500 --model_name=mnist --model_base_path=/models/mnist &

Serwer będzie co sekundę emitował komunikaty dziennika o treści „Wersja aspirująca do obsłużenia…”, co oznacza, że ​​znalazł eksport i śledzi jego dalsze istnienie.

Uruchommy klienta za pomocą --concurrency=10 . Spowoduje to wysłanie równoczesnych żądań do serwera i w ten sposób uruchomi logikę przetwarzania wsadowego.

tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
  --num_tests=1000 --server=127.0.0.1:8500 --concurrency=10

Co daje wynik, który wygląda następująco:

...
Inference error rate: 13.1%

Następnie kopiujemy drugą wersję eksportu do monitorowanego folderu i ponownie uruchamiamy test:

cp -r /tmp/mnist/2 /tmp/monitored
tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
  --num_tests=1000 --server=127.0.0.1:8500 --concurrency=10

Co daje wynik, który wygląda następująco:

...
Inference error rate: 9.5%

To potwierdza, że ​​Twój serwer automatycznie wykryje nową wersję i użyje jej do serwowania!