Un package Python pour décharger un appel de fonction à un serveur HTTP fonctionnant sur localhost à l'aide d'un décorateur. Compatible avec le multiprocessement, le cornichon, le flacon, le rapide, l'async, etc.
Imaginez un cas d'une application multi-file ou multiprocesseur où 1 ou quelques fonctions sont intensives fortement des ressources (CPU ou mémoire), mais les autres fonctions peuvent s'exécuter en parallèle.
Exemple - Un appel API suivi de la tokenisation et de la classification à l'aide d'un grand modèle DL suivi par d'autres appels d'API.
Dans un tel cas, il serait logique de créer un serveur (généralement en utilisant Torchserve ou TFServing) pour servir les demandes et remplacer l'appel de la fonction par une demande de poste au serveur.
ServerHandler crée un serveur synchrone et remplace automatiquement tous les appels vers la fonction pendant l'exécution.
Les demandes sont faites à 1 instance d'un processus exécutant un http.server.httpsserver qui exécute la fonction à l'intérieur.
AsyncServerHandler est également disponible, ce qui rend les demandes de manière asynchrone.
Même les appels passés à partir de différents processus, threads, multiprocessements, flacons, rapides et boucles d'événements asynchrones sont effectués sur le même processus de serveur.
En général :
some code with a callable
Peut être remplacé par une instance de serverhandler ou asyncServerHandler qui accepte le code comme une chaîne dans son premier argument et le nom de l'appelable comme deuxième argument.
from auto_function_serving . ServerHandler import ServerHandler
callable_name = ServerHandler ( """
some independent code with a callable
""" , "callable_name" )Exemple :
import module1
import module2
def functionname ( someinput ):
a = module1 . function1 ( someinput )
return module2 . function2 ( a )peut être remplacé par
from auto_function_serving . ServerHandler import AsyncserverHandler
functionname = AsyncServerHandler ( """
import module1
import module2
def functionname(someinput):
a = module1.function1(someinput)
return module2.function2(a)
""" , "functionname" , port = "Any" )Les décorateurs (@ asyncserverhandler.decorator et @ serverhandler.decorator) et les détails d'AsyncServerHandler dans plus d'utilisation.
from auto_function_serving . ServerHandler import ServerHandler
callable_name = ServerHandler ( """
some independent code with a callable
""" , "callable_name" , port = None , backend = 'Popen' , wait = 100 , backlog = 1024 )) Exécute http.server.httpServer.
ServerHandler et AsyncServerHandler peuvent être chargés et déchargés avec du cornichon.
Utilise POPEN ou le multiprocessement pour exécuter le serveur.
Utilise une seule dépendance externe (AIOHTTP), et uniquement pour asynchrones.
http, pas https.
choisit un port basé sur le hachage d'entrée. (sauf indication contraire)
Modifications de code minimales.
Devrait être compatible avec presque toutes les fonctions dans presque tous les env. (Je ne sais pas où il pourrait échouer? Veuillez ajouter un problème si vous en trouvez un.)
Les fuites de mémoire ou les erreurs (à partir du serveur) sont extrêmement improbables car il est minimal, un seul procédé en file d'attente et un composant par défaut de Python STDLIB.
Les exceptions provoquent des erreurs 5xx sans fermer le serveur.
Même les processus séparés feront des demandes à 1 instance du même serveur, sauf indication contraire. (Parce qu'il recherche un serveur sur un port spécifique.).
Peut spécifier le contraire en définissant le port sur n'importe quel port libre afin qu'un nouvel objet server Handleur démarre un nouveau serveur.
Demandes de post HTTP: léger, peu de frais généraux MS, fiables.
L'async est une bonne fonctionnalité.
Maintenant avec des tests.
Avoir une chaîne de code comme argument à une classe n'est pas pythonique, sauf si le décorateur est utilisé.
L'importation de fonctions à l'intérieur n'est pas idéale, même lorsque le décorateur est utilisé.
Demandes de post HTTP: insécurité, peu de frais généraux MS.
Les exceptions à l'intérieur du serveur ne sont pas renvoyées.
Pas de lots.
Pas de journalisation intégrée. (Pourrait être ajouté). Délai d'initialisation de quelques secondes pour démarrer le serveur. Les fonctions asynchrones ne fonctionneront pas sur le serveur.
Aucun redémarrage de serveur automatique à caserne le serveur.
Peut laisser certaines ressources verrouillées pendant un certain temps (<1 min) si elles ne sont pas fermées correctement.
Des problèmes peuvent survenir si du papen ou du multiprocessement ne sont pas disponibles.
Éventuelles erreurs asynchrones imbriquées avec Jupyter ou autre? Veuillez consulter Nest-Asyncio et les problèmes.
Avertissements des solutions de contournement quelque peu hacky (mais légitimes et complètement fonctionnelles).
La fermeture du processus du serveur dans Del et Atexit.redister ( del ) échoue pour une raison quelconque (testée et peu probable).
Utilisez le Package Manager PIP pour installer Auto_Function_serving
pip install auto_function_serving Le code pour le serveur est stocké dans ServerHandler.base_code et un formatage de chaîne est utilisé pour remplir les blancs.
Le processus du serveur est démarré avec POPEN (ou multiprocessement s'il est spécifié). La première chose qu'il fait est d'importer une prise et de lier le port - s'il n'est pas disponible, le code s'arrête après une exception. Par conséquent, une seule instance du serveur s'exécute à la fois sur une machine.
Nous savons que la fonction est prête après que nous puissions recevoir une demande de GET valide du serveur.
Les entrées et les sorties sont envoyées sous forme d'octets, converties vers et à partir d'objets utilisant du cornichon.
Si le port n'est pas dans l'initialisation (par défaut), un port de 50000 à 60000 est choisi en hachant le code d'entrée pour le rendre indépendant de la source d'une fonction. Les collisions de différentes fonctions sont possibles, mais peu probables. La collision de la même fonction dans plusieurs processus est utilisée pour s'assurer qu'un seul processus de serveur s'exécute à la fois. Le port peut être spécifié si nécessaire.
surcharge pour les petites entrées et sorties (quelques octets) -
~ 2 ms pour les demandes avec Urllib.request
~ 4 ms pour les demandes asynchrones avec AIOHTTP.ClientSession
frais généraux pour les grandes entrées et sorties
~ 10 ms pour l'entrée et la sortie de 0,5 Mo (transfert total de 1 Mo).
~ 60 ms pour l'entrée et la sortie de 5 Mo (10 Mo de transfert total).
~ 600 ms pour une entrée et une sortie de 50 Mo (100 Mo de transfert total).
Il peut également être utilisé avec le décorateur fourni pour les fonctions sans dépendances en dehors de la fonction.
from auto_function_serving . ServerHandler import ServerHandler
@ ServerHandler . decorator
def someheavyfunction ( args , ** kwargs ):
for i in range ( big_number )
someexpensivecomputationLes importations à l'intérieur de la fonction fonctionneront
from auto_function_serving . ServerHandler import ServerHandler
@ ServerHandler . decorator
def someheavyfunction ( args , ** kwargs ):
import numpy as np from auto_function_serving . ServerHandler import ServerHandler
@ ServerHandler . decorator
def someheavyfunction ( args , ** kwargs ):
if not hasattr ( someheavyfunction , 'RunOnce' ):
global np
import numpy as np
setattr ( someheavyfunction , 'RunOnce' , None )
... etcLorsque le SomeModule n'a pas de charge mondiale coûteuse.
from auto_function_serving . ServerHandler import ServerHandler
from somemodule import someheavyfunction
someheavyfunction = ServerHandler . decorator ( someheavyfunction )L'adresse IP peut être modifiée en définissant ServerHandler.ip_address (par défaut "127.0.0.1") avant de créer une nouvelle instance.
AsyncServerHandler est également disponible qui utilise AIOHTTP pour faire les demandes de manière asynchrone, pour une utilisation avec FastAPI et d'autres cas d'utilisation asynchrammes.
AsyncServerHandler a la même utilisation que ServerHandler, sauf que les appels doivent être attendus ou utilisés avec asyncio.run () ou avec asyncio.get_event_loop (). Run_until_compte ().
Le nombre d'appels asynchronisés peut être limité en définissant AsyncServerHandler.TCPConnector_limit qui contrôle la limite TCPConnector (par défaut 100). L'utilisation du sémaphore est également quelque chose à considérer.
Bibliothèques: céleri, tfserving, torchserve, flacon
Envoi des globaux et des locaux à l'exécution
arbres ast
Les demandes de traction sont les bienvenues.
Licence Apache 2.0