Creazione di un modulo che scopre nuovi percorsi utilizzabili

Questo documento spiega come estendere TensorFlow Serving per monitorare diversi sistemi di storage per scoprire nuovi (versioni di) modelli o dati da servire. In particolare, spiega come creare e utilizzare un modulo che monitora un percorso del sistema di storage per la comparsa di nuovi sottopercorsi, dove ciascun sottopercorso rappresenta una nuova versione utilizzabile da caricare. Questo tipo di modulo è chiamato Source<StoragePath> , perché emette oggetti di tipo StoragePath (typedefed in string ). Può essere composto con un SourceAdapter che crea un Loader servibile da un determinato percorso scoperto dall'origine.

Innanzitutto, una nota sulla generalità

Non è necessario utilizzare i percorsi come handle per i dati utilizzabili; illustra semplicemente un modo per inserire i servizi nel sistema. Anche se il tuo ambiente non incapsula i dati servabili in percorsi, questo documento ti aiuterà a familiarizzare con le astrazioni chiave. Hai la possibilità di creare moduli Source<T> e SourceAdapter<T1, T2> per tipi adatti al tuo ambiente (ad esempio messaggi RPC o pub/sub, record di database) o semplicemente creare un Source<std::unique_ptr<Loader>> monolitico Source<std::unique_ptr<Loader>> che emette direttamente caricatori servibili.

Naturalmente, qualunque sia il tipo di dati emessi dalla tua origine (che si tratti di percorsi POSIX, percorsi di Google Cloud Storage o handle RPC), devono esserci moduli di accompagnamento in grado di caricare i servable in base a ciò. Tali moduli sono chiamati SourceAdapters . La creazione di uno personalizzato è descritta nel documento Custom Servable . TensorFlow Serving ne viene fornito uno per creare istanze di sessioni TensorFlow in base ai percorsi nei file system supportati da TensorFlow. È possibile aggiungere il supporto per file system aggiuntivi a TensorFlow estendendo l'astrazione RandomAccessFile ( tensorflow/core/public/env.h ).

Questo documento si concentra sulla creazione di un'origine che emette percorsi in un file system supportato da TensorFlow. Si conclude con una guida dettagliata su come utilizzare il codice sorgente insieme ai moduli preesistenti per servire i modelli TensorFlow.

Creazione della tua fonte

Abbiamo un'implementazione di riferimento di Source<StoragePath> , chiamata FileSystemStoragePathSource (in sources/storage_path/file_system_storage_path_source* ). FileSystemStoragePathSource monitora un particolare percorso del file system, controlla le sottodirectory numeriche e segnala l'ultima di queste come la versione che aspira a caricare. Questo documento illustra gli aspetti salienti di FileSystemStoragePathSource . Potrebbe risultare conveniente creare una copia di FileSystemStoragePathSource e quindi modificarla in base alle proprie esigenze.

Innanzitutto, FileSystemStoragePathSource implementa l'API Source<StoragePath> , che è una specializzazione dell'API Source<T> con T associato a StoragePath . L'API è costituita da un singolo metodo SetAspiredVersionsCallback() , che fornisce una chiusura che l'origine può invocare per comunicare che desidera caricare un particolare set di versioni utilizzabili.

FileSystemStoragePathSource utilizza il callback aspired-versions in un modo molto semplice: ispeziona periodicamente il file system (eseguendo essenzialmente un ls , essenzialmente) e se trova uno o più percorsi che assomigliano a versioni servibili determina quale è la versione più recente e invoca la richiamata con un elenco di dimensione uno contenente solo quella versione (nella configurazione predefinita). Pertanto, in qualsiasi momento FileSystemStoragePathSource richiede il caricamento di al massimo un servibile e la sua implementazione sfrutta l'idempotenza del callback per mantenersi senza stato (non c'è nulla di male nell'invocare ripetutamente il callback con gli stessi argomenti).

FileSystemStoragePathSource dispone di un factory di inizializzazione statico (il metodo Create() ), che accetta un messaggio di protocollo di configurazione. Il messaggio di configurazione include dettagli come il percorso di base da monitorare e l'intervallo di monitoraggio. Include anche il nome del flusso servibile da emettere. (Approcci alternativi potrebbero estrarre il nome del flusso gestibile dal percorso di base, per generare più flussi gestibili in base all'osservazione di una gerarchia di directory più profonda; tali varianti vanno oltre l'ambito dell'implementazione di riferimento.)

La maggior parte dell'implementazione consiste in un thread che esamina periodicamente il file system, insieme ad una logica per identificare e ordinare eventuali sottopercorsi numerici che scopre. Il thread viene avviato all'interno di SetAspiredVersionsCallback() (non in Create() ) perché quello è il punto in cui l'origine dovrebbe "iniziare" e sa dove inviare le richieste della versione aspirata.

Utilizzo della tua sorgente per caricare sessioni TensorFlow

Probabilmente vorrai utilizzare il tuo nuovo modulo sorgente insieme a SavedModelBundleSourceAdapter ( servables/tensorflow/saved_model_bundle_source_adapter* ), che interpreterà ogni percorso emesso dall'origine come un'esportazione TensorFlow e convertirà ciascun percorso in un caricatore per un servable TensorFlow SavedModelBundle . Probabilmente collegherai l'adattatore SavedModelBundle a un AspiredVersionsManager , che si occupa di caricare e servire effettivamente i servable. Un buon esempio di concatenazione di questi tre tipi di moduli insieme per ottenere una libreria server funzionante si trova in servables/tensorflow/simple_servers.cc . Ecco una panoramica del flusso del codice principale (con cattiva gestione degli errori; il codice reale dovrebbe fare più attenzione):

Innanzitutto, crea un gestore:

std::unique_ptr<AspiredVersionsManager> manager = ...;

Quindi, crea un adattatore sorgente SavedModelBundle e collegalo al gestore:

std::unique_ptr<SavedModelBundleSourceAdapter> bundle_adapter;
SavedModelBundleSourceAdapterConfig config;
// ... populate 'config' with TensorFlow options.
TF_CHECK_OK(SavedModelBundleSourceAdapter::Create(config, &bundle_adapter));
ConnectSourceToTarget(bundle_adapter.get(), manager.get());

Infine, crea l'origine del percorso e collegalo all'adattatore SavedModelBundle :

auto your_source = new YourPathSource(...);
ConnectSourceToTarget(your_source, bundle_adapter.get());

La funzione ConnectSourceToTarget() (definita in core/target.h ) invoca semplicemente SetAspiredVersionsCallback() per connettere un Source<T> a un Target<T> (un Target è un modulo che cattura le richieste della versione aspirata, cioè un adattatore o un gestore ).