Creazione di TensorFlow ModelServer standard

Questo tutorial mostra come utilizzare i componenti TensorFlow Serving per creare il TensorFlow ModelServer standard che rileva e serve dinamicamente nuove versioni di un modello TensorFlow addestrato. Se desideri utilizzare solo il server standard per servire i tuoi modelli, consulta il tutorial di base su TensorFlow Serving .

Questo tutorial utilizza il semplice modello di regressione Softmax introdotto nel tutorial TensorFlow per la classificazione di immagini scritte a mano (dati MNIST). Se non sai cos'è TensorFlow o MNIST, consulta il tutorial MNIST per principianti ML .

Il codice per questo tutorial è composto da due parti:

  • Un file Python mnist_saved_model.py che addestra ed esporta più versioni del modello.

  • Un file C++ main.cc che è il TensorFlow ModelServer standard che rileva nuovi modelli esportati ed esegue un servizio gRPC per servirli.

Questo tutorial illustra le seguenti attività:

  1. Addestra ed esporta un modello TensorFlow.
  2. Gestisci il controllo delle versioni del modello con TensorFlow Serving ServerCore .
  3. Configurare il batch utilizzando SavedModelBundleSourceAdapterConfig .
  4. Servire la richiesta con TensorFlow Serving ServerCore .
  5. Esegui e testa il servizio.

Prima di iniziare, installa Docker

Addestra ed esporta il modello TensorFlow

Innanzitutto, se non l'hai ancora fatto, clona questo repository sul tuo computer locale:

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

Cancella la directory di esportazione se esiste già:

rm -rf /tmp/models

Addestra (con 100 iterazioni) ed esporta la prima versione del modello:

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

Addestra (con 2000 iterazioni) ed esporta la seconda versione del modello:

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

Come puoi vedere in mnist_saved_model.py , l'addestramento e l'esportazione vengono eseguiti nello stesso modo del tutorial di base di TensorFlow Serving . A scopo dimostrativo, stai intenzionalmente riducendo le iterazioni di addestramento per la prima esecuzione ed esportandolo come v1, mentre lo stai addestrando normalmente per la seconda esecuzione ed esportandolo come v2 nella stessa directory principale, come ci aspettiamo che quest'ultima ottenga migliore precisione di classificazione grazie ad una formazione più intensiva. Dovresti vedere i dati di addestramento per ogni esecuzione di addestramento nella directory /tmp/mnist :

$ ls /tmp/mnist
1  2

ServerCore

Ora immagina che v1 e v2 del modello vengano generati dinamicamente in fase di esecuzione, mentre vengono sperimentati nuovi algoritmi o mentre il modello viene addestrato con un nuovo set di dati. In un ambiente di produzione, potresti voler creare un server in grado di supportare l'implementazione graduale, in cui la v2 può essere scoperta, caricata, sperimentata, monitorata o ripristinata mentre si serve la v1. In alternativa, potresti voler eliminare la v1 prima di aprire la v2. TensorFlow Serving supporta entrambe le opzioni: mentre una è utile per mantenere la disponibilità durante la transizione, l'altra è utile per ridurre al minimo l'utilizzo delle risorse (ad esempio la RAM).

TensorFlow Serving Manager fa esattamente questo. Gestisce l'intero ciclo di vita dei modelli TensorFlow, inclusi il caricamento, la fornitura e lo scaricamento, nonché le transizioni di versione. In questo tutorial, costruirai il tuo server su un TensorFlow Serving ServerCore , che avvolge internamente un 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() accetta un parametro ServerCore::Options. Ecco alcune opzioni comunemente utilizzate:

  • ModelServerConfig che specifica i modelli da caricare. I modelli vengono dichiarati tramite model_config_list , che dichiara un elenco statico di modelli, o tramite custom_model_config , che definisce un modo personalizzato per dichiarare un elenco di modelli che potrebbero essere aggiornati in fase di esecuzione.
  • PlatformConfigMap che mappa dal nome della piattaforma (come tensorflow ) a PlatformConfig , che viene utilizzato per creare SourceAdapter . SourceAdapter adatta StoragePath (il percorso in cui viene rilevata una versione del modello) al modello Loader (carica la versione del modello dal percorso di archiviazione e fornisce interfacce di transizione di stato a Manager ). Se PlatformConfig contiene SavedModelBundleSourceAdapterConfig , verrà creato un SavedModelBundleSourceAdapter , che spiegheremo in seguito.

SavedModelBundle è un componente chiave di TensorFlow Serving. Rappresenta un modello TensorFlow caricato da un determinato percorso e fornisce la stessa Session::Run di TensorFlow per eseguire l'inferenza. SavedModelBundleSourceAdapter adatta il percorso di archiviazione a Loader<SavedModelBundle> in modo che la durata del modello possa essere gestita da Manager . Tieni presente che SavedModelBundle è il successore del deprecato SessionBundle . Gli utenti sono incoraggiati a utilizzare SavedModelBundle poiché il supporto per SessionBundle verrà presto rimosso.

Con tutto ciò, ServerCore esegue internamente quanto segue:

  • Crea un'istanza di FileSystemStoragePathSource che monitora i percorsi di esportazione del modello dichiarati in model_config_list .
  • Crea un'istanza di un SourceAdapter utilizzando PlatformConfigMap con la piattaforma del modello dichiarata in model_config_list e connette FileSystemStoragePathSource ad esso. In questo modo, ogni volta che viene rilevata una nuova versione del modello nel percorso di esportazione, SavedModelBundleSourceAdapter la adatta a un Loader<SavedModelBundle> .
  • Crea un'istanza di un'implementazione specifica di Manager denominata AspiredVersionsManager che gestisce tutte le istanze Loader create da SavedModelBundleSourceAdapter . ServerCore esporta l'interfaccia Manager delegando le chiamate ad AspiredVersionsManager .

Ogni volta che è disponibile una nuova versione, questo AspiredVersionsManager carica la nuova versione e, con il suo comportamento predefinito, scarica quella vecchia. Se vuoi iniziare a personalizzare, sei incoraggiato a comprendere i componenti che crea internamente e come configurarli.

Vale la pena ricordare che TensorFlow Serving è progettato da zero per essere molto flessibile ed estensibile. Puoi creare vari plugin per personalizzare il comportamento del sistema, sfruttando al tempo stesso componenti core generici come ServerCore e AspiredVersionsManager . Ad esempio, potresti creare un plug-in dell'origine dati che monitora l'archiviazione nel cloud anziché l'archiviazione locale oppure potresti creare un plug-in per i criteri di versione che esegua la transizione della versione in un modo diverso: in effetti, potresti persino creare un plug-in del modello personalizzato che serva modelli non TensorFlow. Questi argomenti non rientrano nell'ambito di questo tutorial. Tuttavia, puoi fare riferimento ai tutorial sull'origine personalizzata e sui servizi personalizzati per ulteriori informazioni.

Batch

Un'altra caratteristica tipica del server che desideriamo in un ambiente di produzione è il batch. I moderni acceleratori hardware (GPU, ecc.) utilizzati per eseguire l'inferenza dell'apprendimento automatico di solito raggiungono la migliore efficienza di calcolo quando le richieste di inferenza vengono eseguite in batch di grandi dimensioni.

Il batch può essere attivato fornendo SessionBundleConfig appropriato durante la creazione di SavedModelBundleSourceAdapter . In questo caso impostiamo BatchingParameters con valori praticamente predefiniti. Il batching può essere ottimizzato impostando valori personalizzati di timeout, batch_size, ecc. Per i dettagli, fare riferimento a 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;

Una volta raggiunto il batch completo, le richieste di inferenza vengono unite internamente in un'unica grande richiesta (tensore) e viene richiamato tensorflow::Session::Run() (da cui deriva l'effettivo guadagno di efficienza sulle GPU).

Servire con il manager

Come accennato in precedenza, TensorFlow Serving Manager è progettato per essere un componente generico in grado di gestire caricamento, elaborazione, scaricamento e transizione di versione di modelli generati da sistemi di machine learning arbitrari. Le sue API si basano sui seguenti concetti chiave:

  • Servable : Servable è qualsiasi oggetto opaco che può essere utilizzato per servire le richieste del client. Le dimensioni e la granularità di un serverable sono flessibili, in modo che un singolo serverable possa includere qualsiasi cosa, da un singolo frammento di una tabella di ricerca a un singolo modello di apprendimento automatico a una tupla di modelli. Un servibile può essere di qualsiasi tipo e interfaccia.

  • Versione servabile : i servable hanno una versione e TensorFlow Serving Manager può gestire una o più versioni di un servable. Il controllo delle versioni consente di caricare contemporaneamente più di una versione di un server, supportando l'implementazione e la sperimentazione graduali.

  • Flusso servibile : un flusso servibile è la sequenza di versioni di un servibile, con numeri di versione crescenti.

  • Modello : un modello di apprendimento automatico è rappresentato da uno o più servizi. Esempi di servibili sono:

    • Sessione TensorFlow o wrapper attorno ad essi, come SavedModelBundle .
    • Altri tipi di modelli di machine learning.
    • Tabelle di ricerca del vocabolario.
    • Incorporamento di tabelle di ricerca.

    Un modello composito può essere rappresentato come più servizi indipendenti o come un singolo servizio composito. Un servibile può anche corrispondere a una frazione di un modello, ad esempio con una tabella di ricerca di grandi dimensioni suddivisa in molte istanze Manager .

Per inserire tutto ciò nel contesto di questo tutorial:

  • I modelli TensorFlow sono rappresentati da un tipo di servizio: SavedModelBundle . SavedModelBundle è costituito internamente da un tensorflow:Session abbinato ad alcuni metadati su quale grafico viene caricato nella sessione e come eseguirlo per l'inferenza.

  • Esiste una directory del file system contenente un flusso di esportazioni TensorFlow, ciascuna nella propria sottodirectory il cui nome è un numero di versione. La directory esterna può essere considerata come la rappresentazione serializzata del flusso gestibile per il modello TensorFlow servito. Ogni esportazione corrisponde a un servable che può essere caricato.

  • AspiredVersionsManager monitora il flusso di esportazione e gestisce il ciclo di vita di tutti i servable SavedModelBundle in modo dinamico.

TensorflowPredictImpl::Predict quindi solo:

  • Richiede SavedModelBundle dal gestore (tramite ServerCore).
  • Utilizza le generic signatures per mappare i nomi dei tensori logici in PredictRequest ai nomi dei tensori reali e associare i valori ai tensori.
  • Esegue l'inferenza.

Testare ed eseguire il server

Copia la prima versione dell'esportazione nella cartella monitorata:

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

Quindi avvia il server:

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 &

Il server emetterà messaggi di registro ogni secondo che dicono "Aspirante versione per servable...", il che significa che ha trovato l'esportazione e ne sta monitorando l'esistenza.

Eseguiamo il client con --concurrency=10 . Ciò invierà richieste simultanee al server e quindi attiverà la logica di batching.

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

Il che si traduce in un output simile a:

...
Inference error rate: 13.1%

Quindi copiamo la seconda versione dell'esportazione nella cartella monitorata ed eseguiamo nuovamente il 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

Il risultato è un output simile a:

...
Inference error rate: 9.5%

Ciò conferma che il tuo server rileva automaticamente la nuova versione e la utilizza per la pubblicazione!