From d32dae03e1faba5b68e7c2927a4548b1f6710ae3 Mon Sep 17 00:00:00 2001 From: Nishad Singh Date: Fri, 2 Jun 2017 02:32:24 -0700 Subject: [PATCH 1/6] Can deploy predict func out of anaconda env. Testcases --- clipper_admin/clipper_manager.py | 146 ++++ containers/python/python_container_entry.sh | 42 +- .../test_python_func_deployment.ipynb | 775 ++++++++++++++++++ 3 files changed, 956 insertions(+), 7 deletions(-) create mode 100644 examples/python_func_deployment/test_python_func_deployment.ipynb diff --git a/clipper_admin/clipper_manager.py b/clipper_admin/clipper_manager.py index cd0cfbaf6..bfe5685d6 100644 --- a/clipper_admin/clipper_manager.py +++ b/clipper_admin/clipper_manager.py @@ -818,6 +818,152 @@ def inspect_selection_policy(self, app_name, uid): r = requests.post(url, headers=headers, data=req_json) return r.text + def register_external_model(self, name, version, labels, input_type): + """Registers a model with Clipper without deploying it in any containers. + + Parameters + ---------- + name : str + The name to assign this model. + version : int + The version to assign this model. + labels : list of str + A set of strings annotating the model + input_type : str + One of "integers", "floats", "doubles", "bytes", or "strings". + """ + return self._publish_new_model(name, version, labels, input_type, + EXTERNALLY_MANAGED_MODEL, + EXTERNALLY_MANAGED_MODEL) + + def deploy_predict_function(self, + name, + version, + predict_function, + labels, + input_type, + num_containers=1): + """Deploy an arbitrary Python function to Clipper. + + The function should take a list of inputs of the type specified by `input_type` and + return a Python or numpy array of predictions. All dependencies for the function must + be installed with Anaconda or Pip and this function must be called from within an Anaconda + environment. + + Parameters + ---------- + name : str + The name to assign this model. + version : int + The version to assign this model. + predict_function : function + The prediction function. Any state associated with the function should be + captured via closure capture. + labels : list of str + A set of strings annotating the model + input_type : str + One of "integers", "floats", "doubles", "bytes", or "strings". + num_containers : int, optional + The number of replicas of the model to create. More replicas can be + created later as well. Defaults to 1. + + Example + ------- + def center(xs): + means = np.mean(xs, axis=0) + return xs - means + + centered_xs = center(xs) + model = sklearn.linear_model.LogisticRegression() + model.fit(centered_xs, ys) + + def centered_predict(inputs): + centered_inputs = center(inputs) + return model.predict(centered_inputs) + + clipper.deploy_predict_function( + "example_model", + 1, + centered_predict, + ["example"], + "doubles", + num_containers=1) + """ + + relative_base_serializations_dir = "predict_serializations" + default_python_container = "clipper/python-container" + predict_fname = "predict_func.pkl" + environment_fname = "environment.yml" + conda_dep_fname = "conda_dependencies.txt" + pip_dep_fname = "pip_dependencies.txt" + + # Serialize function + s = StringIO() + c = CloudPickler(s, 2) + c.dump(predict_function) + serialized_prediction_function = s.getvalue() + + # Set up serialization directory + serialization_dir = os.path.join( + '/tmp', relative_base_serializations_dir, name) + if not os.path.exists(serialization_dir): + os.makedirs(serialization_dir) + + # Attempt to export Anaconda environment + environment_file_abs_path = os.path.join(serialization_dir, + environment_fname) + conda_env_exported = self._export_conda_env(environment_file_abs_path) + + if conda_env_exported: + print("Anaconda environment found. Verifying packages.") + + # Confirm that packages installed through conda are solvable + # Write out conda and pip dependency files to be supplied to container + if not (self._check_and_write_dependencies( + environment_file_abs_path, serialization_dir, conda_dep_fname, + pip_dep_fname)): + return False + + print("Supplied environment details") + else: + print("Anaconda environment was either not found or failed being exported") + print( + "Your local environment details will not be supplied to and loaded in the container in which your model is deployed." + ) + + # Write out function serialization + func_file_path = os.path.join(serialization_dir, predict_fname) + with open(func_file_path, "w") as serialized_function_file: + serialized_function_file.write(serialized_prediction_function) + print("Serialized and supplied predict function") + + # Deploy function + deploy_result = self.deploy_model(name, version, serialization_dir, + default_python_container, labels, input_type, + num_containers) + # Remove temp files + shutil.rmtree(serialization_dir) + + return deploy_result + + def _export_conda_env(self, environment_file_abs_path): + """Returns true if attempt to export the current conda environment is successful + + Parameters + ---------- + environment_file_abs_path : str + The desired absolute path for the exported conda environment file + """ + + process = subprocess.Popen( + "PIP_FORMAT=legacy conda env export >> {environment_file_abs_path}". + format(environment_file_abs_path=environment_file_abs_path), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True) + process.wait() + return process.returncode == 0 + def _check_and_write_dependencies(self, environment_path, directory, conda_dep_fname, pip_dep_fname): """Returns true if the provided conda environment is compatible with the container os. diff --git a/containers/python/python_container_entry.sh b/containers/python/python_container_entry.sh index 15d672b99..d22672d95 100755 --- a/containers/python/python_container_entry.sh +++ b/containers/python/python_container_entry.sh @@ -2,12 +2,40 @@ IMPORT_ERROR_RETURN_CODE=3 -echo "Attempting to run Python container without installing dependencies" -/bin/bash -c "exec python /container/python_container.py" +CONDA_DEPS_PATH="/model/conda_dependencies.txt" +PIP_DEPS_PATH="/model/pip_dependencies.txt" +CONTAINER_SCRIPT_PATH="/container/python_container.py" + +test -f $CONDA_DEPS_PATH +dependency_check_returncode=$? + +if [ $dependency_check_returncode -eq 0 ]; then + echo "Attempting to run Python container without installing without supplied dependencies." +else + echo "No dependencies supplied. Attempting to run Python container." +fi + +/bin/bash -c "exec python $CONTAINER_SCRIPT_PATH" + if [ $? -eq $IMPORT_ERROR_RETURN_CODE ]; then - echo "Running Python container without installing dependencies fails" - echo "Will install dependencies and try again" - conda install -y --file /model/conda_dependencies.txt - pip install -r /model/pip_dependencies.txt - /bin/bash -c "exec python /container/python_container.py" + if [ $dependency_check_returncode -eq 0 ]; then + echo "Encountered an ImportError when running Python container without installing supplied dependencies." + echo "Will install supplied dependencies and try again." + conda install -y --file $CONDA_DEPS_PATH + pip install -r $PIP_DEPS_PATH + + /bin/bash -c "exec python $CONTAINER_SCRIPT_PATH" + + if [ $? -eq $IMPORT_ERROR_RETURN_CODE ]; then + echo "Encountered an ImportError even after installing supplied dependencies." + exit 1 + fi + else + echo "Encountered an ImportError when running Python container." + echo "Please supply necessary dependencies through an Anaconda environment and try again." + exit 1 + fi fi + +echo "Encountered error not related to missing packages. Please refer to the above logs to diagnose." +exit 1 diff --git a/examples/python_func_deployment/test_python_func_deployment.ipynb b/examples/python_func_deployment/test_python_func_deployment.ipynb new file mode 100644 index 000000000..4dad2a123 --- /dev/null +++ b/examples/python_func_deployment/test_python_func_deployment.ipynb @@ -0,0 +1,775 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Start Clipper" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "sys.path.append(os.path.abspath('../../clipper_admin/'))\n", + "import clipper_manager as cm\n", + "reload(cm)\n", + "user = \"\"\n", + "key = \"\"\n", + "host = \"localhost\"\n", + "clipper = cm.Clipper(host, user, key)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "clipper.start()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "app_name = \"example_app\"\n", + "model_name = \"example_model_name\"\n", + "default_output = \"0\"\n", + "input_type = \"doubles\"\n", + "\n", + "clipper.register_application(\n", + " app_name,\n", + " model_name,\n", + " input_type,\n", + " default_output,\n", + " slo_micros=20000)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Test setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "def no_packages_needed_predict(inputs):\n", + " outputs = []\n", + " for input_item in inputs:\n", + " outputs.append(str(sum(input_item)))\n", + " return outputs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "def runtime_error_predict(inputs):\n", + " 1/0\n", + " print(\"Shouldn't ever get here\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "def nonexistent_package_predict(inputs):\n", + " import packagedoesntexist\n", + " print(\"Shouldn't ever get here\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "# Make sure you run `conda install fuel` from within your local conda environment\n", + "def need_to_install_package_predict(inputs):\n", + " import fuel\n", + " print(\"Should get here after installation\")\n", + " outputs = []\n", + " for input_item in inputs:\n", + " outputs.append(str(sum(input_item)))\n", + " return outputs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "def need_to_install_package_has_runtime_error_predict(inputs):\n", + " import fuel\n", + " print(\"Should get here after installation\")\n", + " 1/0\n", + " print(\"Shouldn't ever get here\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "import json\n", + "import requests\n", + "def predict(x):\n", + " uid = 0\n", + " url = \"http://%s:1337/%s/predict\" % (host, app_name)\n", + " req_json = json.dumps({'uid': uid, 'input': list(x)})\n", + " headers = {'Content-type': 'application/json'}\n", + " r = requests.post(url, headers=headers, data=req_json)\n", + " return r.text" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "model_version = 50\n", + "datapoint = [1.0, 2.0]\n", + "labels = [\"label1\", \"label2\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "# Test cases" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Outside of conda env: deploy function that needs no additional packages" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "**Management library should print:**\n", + "\n", + "\"Anaconda environment was either not found or failed being exported\"\n", + "\n", + "\"Your local environment details will not be supplied to and loaded in the container in which your model is deployed.\"\n", + "\n", + "**After startup, logs should show:**\n", + "\n", + "\"No dependencies supplied. Attempting to run Python container.\"\n", + "\n", + "\n", + "**Model-container should work and be queryable**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "clipper.deploy_predict_function(\n", + " model_name,\n", + " model_version,\n", + " no_packages_needed_predict,\n", + " labels,\n", + " input_type,\n", + ")\n", + "model_version += 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "predict(datapoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Outside of conda env: deploy function that needs an additional package not pre-installed on the container" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "**Management library should print:**\n", + "\n", + "\"Anaconda environment was either not found or failed being exported\"\n", + "\n", + "\"Your local environment details will not be supplied to and loaded in the container in which your model is deployed.\"\n", + "\n", + "**After startup, logs should show:**\n", + "\n", + "\"No dependencies supplied. Attempting to run Python container.\"\n", + "\n", + "**Upon query, logs should show:**\n", + "\n", + "\"Encountered an ImportError when running Python container.\"\n", + "\n", + "\"Please supply necessary dependencies through an Anaconda environment and try again.\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "clipper.deploy_predict_function(\n", + " model_name,\n", + " model_version,\n", + " nonexistent_package_predict, # This should have the same behavior with `need_to_install_package_predict` or `need_to_install_package_has_runtime_error_predict`\n", + " labels,\n", + " input_type,\n", + ")\n", + "model_version += 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "predict(datapoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Outside of conda env: deploy a function that experiences a runtime error" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "**Management library should print:**\n", + "\n", + "\"Anaconda environment was either not found or failed being exported\"\n", + "\n", + "\"Your local environment details will not be supplied to and loaded in the container in which your model is deployed.\"\n", + "\n", + "**After startup, logs should show:**\n", + "\n", + "\"No dependencies supplied. Attempting to run Python container.\"\n", + "\n", + "**Upon query, logs should show:**\n", + "\n", + "{Runtime error output, including \"ZeroDivisionError: integer division or modulo by zero\"}\n", + "\n", + "\"Encountered error not related to missing packages. Please refer to the above logs to diagnose.\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "clipper.deploy_predict_function(\n", + " model_name,\n", + " model_version,\n", + " runtime_error_predict,\n", + " labels,\n", + " input_type,\n", + ")\n", + "model_version += 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "predict(datapoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Within conda env: deploy a function needs no additional dependencies. Optimistic startup should work" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "**Management library should print:**\n", + "\n", + "\"Anaconda environment found. Verifying packages.\"\n", + "\n", + "\"Supplied environment details\"\n", + "\n", + "**After startup, logs should show:**\n", + "\n", + "\"Attempting to run Python container without installing without supplied dependencies.\"\n", + "\n", + "**Model-container should work and be queryable**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "clipper.deploy_predict_function(\n", + " model_name,\n", + " model_version,\n", + " no_packages_needed_predict,\n", + " labels,\n", + " input_type,\n", + ")\n", + "model_version += 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "predict(datapoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "source": [ + "## Within conda env: Deploy a function that will experience a runtime error" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "**Management library should print:**\n", + "\n", + "\"Anaconda environment found. Verifying packages.\"\n", + "\n", + "\"Supplied environment details\"\n", + "\n", + "**After startup, logs should show:**\n", + "\n", + "\"Attempting to run Python container without installing without supplied dependencies.\"\n", + "\n", + "**Upon query, logs should show:**\n", + "\n", + "\"Encountered error not related to missing packages. Please refer to the above logs to diagnose.\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "clipper.deploy_predict_function(\n", + " model_name,\n", + " model_version,\n", + " runtime_error_predict,\n", + " labels,\n", + " input_type,\n", + ")\n", + "model_version += 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "predict(datapoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Within conda env: Deploy a function that uses a module not in the conda or pip distributions." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "**Management library should print:**\n", + "\n", + "\"Anaconda environment found. Verifying packages.\"\n", + "\n", + "\"Supplied environment details\"\n", + "\n", + "**After startup, logs should show:**\n", + "\n", + "\"Attempting to run Python container without installing without supplied dependencies.\"\n", + "\n", + "**Upon query, logs should show:**\n", + "\n", + "\"Encountered an ImportError when running Python container without installing supplied dependencies.\"\n", + "\n", + "\"Will install supplied dependencies and try again.\"\n", + "\n", + "**Upon query after the supplied dependencies has been installed, logs should show:**\n", + "\n", + "\"Encountered an ImportError even after installing supplied dependencies.\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "clipper.deploy_predict_function(\n", + " model_name,\n", + " model_version,\n", + " nonexistent_package_predict,\n", + " labels,\n", + " input_type,\n", + ")\n", + "model_version += 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "predict(datapoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "source": [ + "## Within conda env: Deploy a function that uses a module not pre-installed but within the conda distribution" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "**Management library should print:**\n", + "\n", + "\"Anaconda environment found. Verifying packages.\"\n", + "\n", + "\"Supplied environment details\"\n", + "\n", + "**After startup, logs should show:**\n", + "\n", + "\"Attempting to run Python container without installing without supplied dependencies.\"\n", + "\n", + "**Upon query, logs should show:**\n", + "\n", + "\"Encountered an ImportError when running Python container without installing supplied dependencies.\"\n", + "\n", + "\"Will install supplied dependencies and try again.\"\n", + "\n", + "**Upon query after the supplied dependencies has been installed, model-container should be queryable and functional. Logs should show upon each query:**\n", + "\n", + "\"Should get here after installation\" (from a print statement within the function)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "clipper.deploy_predict_function(\n", + " model_name,\n", + " model_version,\n", + " need_to_install_package_predict,\n", + " labels,\n", + " input_type,\n", + ")\n", + "model_version += 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "predict(datapoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "## Within conda env: Deploy a function that uses a module not pre-installed but within the conda distribution that also will experience a runtime error" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "**Management library should print:**\n", + "\n", + "\"Anaconda environment found. Verifying packages.\"\n", + "\n", + "\"Supplied environment details\"\n", + "\n", + "**After startup, logs should show:**\n", + "\n", + "\"Attempting to run Python container without installing without supplied dependencies.\"\n", + "\n", + "**Upon query, logs should show:**\n", + "\n", + "\"Encountered an ImportError when running Python container without installing supplied dependencies.\"\n", + "\n", + "\"Will install supplied dependencies and try again.\"\n", + "\n", + "**Upon query after the supplied dependencies has been installed, logs should show:**\n", + "\n", + "\"Should get here after installation\" (from a print statement within the function)\n", + "\n", + "\"Encountered error not related to missing packages. Please refer to the above logs to diagnose.\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "clipper.deploy_predict_function(\n", + " model_name,\n", + " model_version,\n", + " need_to_install_package_has_runtime_error_predict,\n", + " labels,\n", + " input_type,\n", + ")\n", + "model_version += 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "predict(datapoint)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda root]", + "language": "python", + "name": "conda-root-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 06047048f192f4b4ce45e2a458830234bd8049a9 Mon Sep 17 00:00:00 2001 From: Nishad Singh Date: Fri, 2 Jun 2017 09:27:43 -0700 Subject: [PATCH 2/6] Rebased on develop + formatting changes --- clipper_admin/clipper_manager.py | 173 +++++-------------------------- 1 file changed, 27 insertions(+), 146 deletions(-) diff --git a/clipper_admin/clipper_manager.py b/clipper_admin/clipper_manager.py index bfe5685d6..50d22b426 100644 --- a/clipper_admin/clipper_manager.py +++ b/clipper_admin/clipper_manager.py @@ -636,24 +636,29 @@ def centered_predict(inputs): if not os.path.exists(serialization_dir): os.makedirs(serialization_dir) - # Export Anaconda environment + # Attempt to export Anaconda environment environment_file_abs_path = os.path.join(serialization_dir, environment_fname) - process = subprocess.Popen( - "PIP_FORMAT=legacy conda env export >> {environment_file_abs_path}". - format(environment_file_abs_path=environment_file_abs_path), - shell=True) - process.wait() + conda_env_exported = self._export_conda_env(environment_file_abs_path) - # Confirm that packages installed through conda are solvable - # Write out conda and pip dependency files to be supplied to container - if not (self._check_and_write_dependencies( - environment_file_abs_path, serialization_dir, conda_dep_fname, - pip_dep_fname)): - return False + if conda_env_exported: + print("Anaconda environment found. Verifying packages.") - os.remove(environment_file_abs_path) - print("Supplied environment details") + # Confirm that packages installed through conda are solvable + # Write out conda and pip dependency files to be supplied to container + if not (self._check_and_write_dependencies( + environment_file_abs_path, serialization_dir, + conda_dep_fname, pip_dep_fname)): + return False + + print("Supplied environment details") + else: + print( + "Anaconda environment was either not found or failed being exported" + ) + print( + "Your local environment details will not be supplied to and loaded in the container in which your model is deployed." + ) # Write out function serialization func_file_path = os.path.join(serialization_dir, predict_fname) @@ -662,9 +667,13 @@ def centered_predict(inputs): print("Serialized and supplied predict function") # Deploy function - return self.deploy_model(name, version, serialization_dir, - default_python_container, labels, input_type, - num_containers) + deploy_result = self.deploy_model(name, version, serialization_dir, + default_python_container, labels, + input_type, num_containers) + # Remove temp files + shutil.rmtree(serialization_dir) + + return deploy_result def get_all_models(self, verbose=False): """Gets information about all models registered with Clipper. @@ -818,134 +827,6 @@ def inspect_selection_policy(self, app_name, uid): r = requests.post(url, headers=headers, data=req_json) return r.text - def register_external_model(self, name, version, labels, input_type): - """Registers a model with Clipper without deploying it in any containers. - - Parameters - ---------- - name : str - The name to assign this model. - version : int - The version to assign this model. - labels : list of str - A set of strings annotating the model - input_type : str - One of "integers", "floats", "doubles", "bytes", or "strings". - """ - return self._publish_new_model(name, version, labels, input_type, - EXTERNALLY_MANAGED_MODEL, - EXTERNALLY_MANAGED_MODEL) - - def deploy_predict_function(self, - name, - version, - predict_function, - labels, - input_type, - num_containers=1): - """Deploy an arbitrary Python function to Clipper. - - The function should take a list of inputs of the type specified by `input_type` and - return a Python or numpy array of predictions. All dependencies for the function must - be installed with Anaconda or Pip and this function must be called from within an Anaconda - environment. - - Parameters - ---------- - name : str - The name to assign this model. - version : int - The version to assign this model. - predict_function : function - The prediction function. Any state associated with the function should be - captured via closure capture. - labels : list of str - A set of strings annotating the model - input_type : str - One of "integers", "floats", "doubles", "bytes", or "strings". - num_containers : int, optional - The number of replicas of the model to create. More replicas can be - created later as well. Defaults to 1. - - Example - ------- - def center(xs): - means = np.mean(xs, axis=0) - return xs - means - - centered_xs = center(xs) - model = sklearn.linear_model.LogisticRegression() - model.fit(centered_xs, ys) - - def centered_predict(inputs): - centered_inputs = center(inputs) - return model.predict(centered_inputs) - - clipper.deploy_predict_function( - "example_model", - 1, - centered_predict, - ["example"], - "doubles", - num_containers=1) - """ - - relative_base_serializations_dir = "predict_serializations" - default_python_container = "clipper/python-container" - predict_fname = "predict_func.pkl" - environment_fname = "environment.yml" - conda_dep_fname = "conda_dependencies.txt" - pip_dep_fname = "pip_dependencies.txt" - - # Serialize function - s = StringIO() - c = CloudPickler(s, 2) - c.dump(predict_function) - serialized_prediction_function = s.getvalue() - - # Set up serialization directory - serialization_dir = os.path.join( - '/tmp', relative_base_serializations_dir, name) - if not os.path.exists(serialization_dir): - os.makedirs(serialization_dir) - - # Attempt to export Anaconda environment - environment_file_abs_path = os.path.join(serialization_dir, - environment_fname) - conda_env_exported = self._export_conda_env(environment_file_abs_path) - - if conda_env_exported: - print("Anaconda environment found. Verifying packages.") - - # Confirm that packages installed through conda are solvable - # Write out conda and pip dependency files to be supplied to container - if not (self._check_and_write_dependencies( - environment_file_abs_path, serialization_dir, conda_dep_fname, - pip_dep_fname)): - return False - - print("Supplied environment details") - else: - print("Anaconda environment was either not found or failed being exported") - print( - "Your local environment details will not be supplied to and loaded in the container in which your model is deployed." - ) - - # Write out function serialization - func_file_path = os.path.join(serialization_dir, predict_fname) - with open(func_file_path, "w") as serialized_function_file: - serialized_function_file.write(serialized_prediction_function) - print("Serialized and supplied predict function") - - # Deploy function - deploy_result = self.deploy_model(name, version, serialization_dir, - default_python_container, labels, input_type, - num_containers) - # Remove temp files - shutil.rmtree(serialization_dir) - - return deploy_result - def _export_conda_env(self, environment_file_abs_path): """Returns true if attempt to export the current conda environment is successful @@ -957,7 +838,7 @@ def _export_conda_env(self, environment_file_abs_path): process = subprocess.Popen( "PIP_FORMAT=legacy conda env export >> {environment_file_abs_path}". - format(environment_file_abs_path=environment_file_abs_path), + format(environment_file_abs_path=environment_file_abs_path), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) From dfea2173af403e45253fe15e8d982d28deaa1f4b Mon Sep 17 00:00:00 2001 From: Dan Crankshaw Date: Sat, 3 Jun 2017 09:53:53 -0700 Subject: [PATCH 3/6] fixed cloudpickle import --- PythonContainerDockerfile | 3 ++- containers/python/python_container.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/PythonContainerDockerfile b/PythonContainerDockerfile index 6d7d84095..705584d32 100644 --- a/PythonContainerDockerfile +++ b/PythonContainerDockerfile @@ -3,7 +3,8 @@ FROM clipper/py-rpc:latest MAINTAINER Dan Crankshaw COPY containers/python/python_container.py containers/python/python_container_entry.sh /container/ -COPY clipper_admin/pywrencloudpickle.py containers/python/python_container_conda_deps.txt /lib/ +COPY containers/python/python_container_conda_deps.txt /lib/ +COPY clipper_admin/ /lib/clipper_admin/ RUN conda install -y --file /lib/python_container_conda_deps.txt diff --git a/containers/python/python_container.py b/containers/python/python_container.py index 42a5fcb2b..b29525ddd 100644 --- a/containers/python/python_container.py +++ b/containers/python/python_container.py @@ -7,7 +7,7 @@ np.set_printoptions(threshold=np.nan) sys.path.append(os.path.abspath("/lib/")) -import pywrencloudpickle +from clipper_admin import pywrencloudpickle IMPORT_ERROR_RETURN_CODE = 3 @@ -118,5 +118,6 @@ def _log_incorrect_input_type(self, input_type): rpc_service = rpc.RPCService() rpc_service.start(model, ip, port, model_name, model_version, input_type) - except ImportError: + except ImportError as e: + print(e) sys.exit(IMPORT_ERROR_RETURN_CODE) From 3b124c1d0ea3f2ae02c970e7d620e2e6f9e9aa6a Mon Sep 17 00:00:00 2001 From: Dan Crankshaw Date: Sat, 3 Jun 2017 10:18:24 -0700 Subject: [PATCH 4/6] fixed cloudpickle import issues. --- clipper_admin/clipper_manager.py | 8 ++++---- containers/python/python_container_entry.sh | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clipper_admin/clipper_manager.py b/clipper_admin/clipper_manager.py index 50d22b426..ea0036776 100644 --- a/clipper_admin/clipper_manager.py +++ b/clipper_admin/clipper_manager.py @@ -654,10 +654,10 @@ def centered_predict(inputs): print("Supplied environment details") else: print( - "Anaconda environment was either not found or failed being exported" - ) - print( - "Your local environment details will not be supplied to and loaded in the container in which your model is deployed." + "Warning: Anaconda environment was either not found or exporting the environment " + "failed. Your function will still be serialized deployed, but may fail due to " + "missing dependencies. In this case, please re-run inside an Anaconda environment. " + "See http://clipper.ai/documentation/python_model_deployment/ for more information." ) # Write out function serialization diff --git a/containers/python/python_container_entry.sh b/containers/python/python_container_entry.sh index d22672d95..be368bbe8 100755 --- a/containers/python/python_container_entry.sh +++ b/containers/python/python_container_entry.sh @@ -10,7 +10,7 @@ test -f $CONDA_DEPS_PATH dependency_check_returncode=$? if [ $dependency_check_returncode -eq 0 ]; then - echo "Attempting to run Python container without installing without supplied dependencies." + echo "First attempting to run Python container without installing supplied dependencies." else echo "No dependencies supplied. Attempting to run Python container." fi @@ -37,5 +37,5 @@ if [ $? -eq $IMPORT_ERROR_RETURN_CODE ]; then fi fi -echo "Encountered error not related to missing packages. Please refer to the above logs to diagnose." +echo "Encountered error not related to missing packages. Please refer to the container log to diagnose." exit 1 From bc28ae54bc59a5bca74818ffaea643683ea9b6a0 Mon Sep 17 00:00:00 2001 From: Dan Crankshaw Date: Sat, 3 Jun 2017 10:22:32 -0700 Subject: [PATCH 5/6] removed python function deployment example notebook --- .../test_python_func_deployment.ipynb | 775 ------------------ 1 file changed, 775 deletions(-) delete mode 100644 examples/python_func_deployment/test_python_func_deployment.ipynb diff --git a/examples/python_func_deployment/test_python_func_deployment.ipynb b/examples/python_func_deployment/test_python_func_deployment.ipynb deleted file mode 100644 index 4dad2a123..000000000 --- a/examples/python_func_deployment/test_python_func_deployment.ipynb +++ /dev/null @@ -1,775 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "## Start Clipper" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "import sys\n", - "import os\n", - "sys.path.append(os.path.abspath('../../clipper_admin/'))\n", - "import clipper_manager as cm\n", - "reload(cm)\n", - "user = \"\"\n", - "key = \"\"\n", - "host = \"localhost\"\n", - "clipper = cm.Clipper(host, user, key)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "clipper.start()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "app_name = \"example_app\"\n", - "model_name = \"example_model_name\"\n", - "default_output = \"0\"\n", - "input_type = \"doubles\"\n", - "\n", - "clipper.register_application(\n", - " app_name,\n", - " model_name,\n", - " input_type,\n", - " default_output,\n", - " slo_micros=20000)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "## Test setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "def no_packages_needed_predict(inputs):\n", - " outputs = []\n", - " for input_item in inputs:\n", - " outputs.append(str(sum(input_item)))\n", - " return outputs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "def runtime_error_predict(inputs):\n", - " 1/0\n", - " print(\"Shouldn't ever get here\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "def nonexistent_package_predict(inputs):\n", - " import packagedoesntexist\n", - " print(\"Shouldn't ever get here\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "# Make sure you run `conda install fuel` from within your local conda environment\n", - "def need_to_install_package_predict(inputs):\n", - " import fuel\n", - " print(\"Should get here after installation\")\n", - " outputs = []\n", - " for input_item in inputs:\n", - " outputs.append(str(sum(input_item)))\n", - " return outputs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "def need_to_install_package_has_runtime_error_predict(inputs):\n", - " import fuel\n", - " print(\"Should get here after installation\")\n", - " 1/0\n", - " print(\"Shouldn't ever get here\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "import json\n", - "import requests\n", - "def predict(x):\n", - " uid = 0\n", - " url = \"http://%s:1337/%s/predict\" % (host, app_name)\n", - " req_json = json.dumps({'uid': uid, 'input': list(x)})\n", - " headers = {'Content-type': 'application/json'}\n", - " r = requests.post(url, headers=headers, data=req_json)\n", - " return r.text" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "model_version = 50\n", - "datapoint = [1.0, 2.0]\n", - "labels = [\"label1\", \"label2\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "# Test cases" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "## Outside of conda env: deploy function that needs no additional packages" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "**Management library should print:**\n", - "\n", - "\"Anaconda environment was either not found or failed being exported\"\n", - "\n", - "\"Your local environment details will not be supplied to and loaded in the container in which your model is deployed.\"\n", - "\n", - "**After startup, logs should show:**\n", - "\n", - "\"No dependencies supplied. Attempting to run Python container.\"\n", - "\n", - "\n", - "**Model-container should work and be queryable**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "clipper.deploy_predict_function(\n", - " model_name,\n", - " model_version,\n", - " no_packages_needed_predict,\n", - " labels,\n", - " input_type,\n", - ")\n", - "model_version += 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "predict(datapoint)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "## Outside of conda env: deploy function that needs an additional package not pre-installed on the container" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "**Management library should print:**\n", - "\n", - "\"Anaconda environment was either not found or failed being exported\"\n", - "\n", - "\"Your local environment details will not be supplied to and loaded in the container in which your model is deployed.\"\n", - "\n", - "**After startup, logs should show:**\n", - "\n", - "\"No dependencies supplied. Attempting to run Python container.\"\n", - "\n", - "**Upon query, logs should show:**\n", - "\n", - "\"Encountered an ImportError when running Python container.\"\n", - "\n", - "\"Please supply necessary dependencies through an Anaconda environment and try again.\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "clipper.deploy_predict_function(\n", - " model_name,\n", - " model_version,\n", - " nonexistent_package_predict, # This should have the same behavior with `need_to_install_package_predict` or `need_to_install_package_has_runtime_error_predict`\n", - " labels,\n", - " input_type,\n", - ")\n", - "model_version += 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "predict(datapoint)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "## Outside of conda env: deploy a function that experiences a runtime error" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "**Management library should print:**\n", - "\n", - "\"Anaconda environment was either not found or failed being exported\"\n", - "\n", - "\"Your local environment details will not be supplied to and loaded in the container in which your model is deployed.\"\n", - "\n", - "**After startup, logs should show:**\n", - "\n", - "\"No dependencies supplied. Attempting to run Python container.\"\n", - "\n", - "**Upon query, logs should show:**\n", - "\n", - "{Runtime error output, including \"ZeroDivisionError: integer division or modulo by zero\"}\n", - "\n", - "\"Encountered error not related to missing packages. Please refer to the above logs to diagnose.\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "clipper.deploy_predict_function(\n", - " model_name,\n", - " model_version,\n", - " runtime_error_predict,\n", - " labels,\n", - " input_type,\n", - ")\n", - "model_version += 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "predict(datapoint)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "## Within conda env: deploy a function needs no additional dependencies. Optimistic startup should work" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "**Management library should print:**\n", - "\n", - "\"Anaconda environment found. Verifying packages.\"\n", - "\n", - "\"Supplied environment details\"\n", - "\n", - "**After startup, logs should show:**\n", - "\n", - "\"Attempting to run Python container without installing without supplied dependencies.\"\n", - "\n", - "**Model-container should work and be queryable**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "clipper.deploy_predict_function(\n", - " model_name,\n", - " model_version,\n", - " no_packages_needed_predict,\n", - " labels,\n", - " input_type,\n", - ")\n", - "model_version += 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "predict(datapoint)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "source": [ - "## Within conda env: Deploy a function that will experience a runtime error" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "**Management library should print:**\n", - "\n", - "\"Anaconda environment found. Verifying packages.\"\n", - "\n", - "\"Supplied environment details\"\n", - "\n", - "**After startup, logs should show:**\n", - "\n", - "\"Attempting to run Python container without installing without supplied dependencies.\"\n", - "\n", - "**Upon query, logs should show:**\n", - "\n", - "\"Encountered error not related to missing packages. Please refer to the above logs to diagnose.\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "clipper.deploy_predict_function(\n", - " model_name,\n", - " model_version,\n", - " runtime_error_predict,\n", - " labels,\n", - " input_type,\n", - ")\n", - "model_version += 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "predict(datapoint)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "## Within conda env: Deploy a function that uses a module not in the conda or pip distributions." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "**Management library should print:**\n", - "\n", - "\"Anaconda environment found. Verifying packages.\"\n", - "\n", - "\"Supplied environment details\"\n", - "\n", - "**After startup, logs should show:**\n", - "\n", - "\"Attempting to run Python container without installing without supplied dependencies.\"\n", - "\n", - "**Upon query, logs should show:**\n", - "\n", - "\"Encountered an ImportError when running Python container without installing supplied dependencies.\"\n", - "\n", - "\"Will install supplied dependencies and try again.\"\n", - "\n", - "**Upon query after the supplied dependencies has been installed, logs should show:**\n", - "\n", - "\"Encountered an ImportError even after installing supplied dependencies.\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "clipper.deploy_predict_function(\n", - " model_name,\n", - " model_version,\n", - " nonexistent_package_predict,\n", - " labels,\n", - " input_type,\n", - ")\n", - "model_version += 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "predict(datapoint)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "source": [ - "## Within conda env: Deploy a function that uses a module not pre-installed but within the conda distribution" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "**Management library should print:**\n", - "\n", - "\"Anaconda environment found. Verifying packages.\"\n", - "\n", - "\"Supplied environment details\"\n", - "\n", - "**After startup, logs should show:**\n", - "\n", - "\"Attempting to run Python container without installing without supplied dependencies.\"\n", - "\n", - "**Upon query, logs should show:**\n", - "\n", - "\"Encountered an ImportError when running Python container without installing supplied dependencies.\"\n", - "\n", - "\"Will install supplied dependencies and try again.\"\n", - "\n", - "**Upon query after the supplied dependencies has been installed, model-container should be queryable and functional. Logs should show upon each query:**\n", - "\n", - "\"Should get here after installation\" (from a print statement within the function)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "clipper.deploy_predict_function(\n", - " model_name,\n", - " model_version,\n", - " need_to_install_package_predict,\n", - " labels,\n", - " input_type,\n", - ")\n", - "model_version += 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "predict(datapoint)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "## Within conda env: Deploy a function that uses a module not pre-installed but within the conda distribution that also will experience a runtime error" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, - "source": [ - "**Management library should print:**\n", - "\n", - "\"Anaconda environment found. Verifying packages.\"\n", - "\n", - "\"Supplied environment details\"\n", - "\n", - "**After startup, logs should show:**\n", - "\n", - "\"Attempting to run Python container without installing without supplied dependencies.\"\n", - "\n", - "**Upon query, logs should show:**\n", - "\n", - "\"Encountered an ImportError when running Python container without installing supplied dependencies.\"\n", - "\n", - "\"Will install supplied dependencies and try again.\"\n", - "\n", - "**Upon query after the supplied dependencies has been installed, logs should show:**\n", - "\n", - "\"Should get here after installation\" (from a print statement within the function)\n", - "\n", - "\"Encountered error not related to missing packages. Please refer to the above logs to diagnose.\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "clipper.deploy_predict_function(\n", - " model_name,\n", - " model_version,\n", - " need_to_install_package_has_runtime_error_predict,\n", - " labels,\n", - " input_type,\n", - ")\n", - "model_version += 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "deletable": true, - "editable": true - }, - "outputs": [], - "source": [ - "predict(datapoint)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda root]", - "language": "python", - "name": "conda-root-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From c7649650b3298f9a41a895bf4746158f8a637349 Mon Sep 17 00:00:00 2001 From: Dan Crankshaw Date: Sat, 3 Jun 2017 11:23:33 -0700 Subject: [PATCH 6/6] uncomment deploy_predict_function tests after rebase --- clipper_admin/tests/clipper_manager_test.py | 88 ++++++++++----------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/clipper_admin/tests/clipper_manager_test.py b/clipper_admin/tests/clipper_manager_test.py index 01ccecd46..da0cb4493 100644 --- a/clipper_admin/tests/clipper_manager_test.py +++ b/clipper_admin/tests/clipper_manager_test.py @@ -152,22 +152,22 @@ def test_add_container_for_deployed_model_succeeds(self): split_output = running_containers_output.split("\n") self.assertGreaterEqual(len(split_output), 2) - # def test_predict_function_deploys_successfully(self): - # model_name = "m2" - # model_version = 1 - # predict_func = lambda inputs: ["0" for x in inputs] - # labels = ["test"] - # input_type = "doubles" - # result = self.clipper_inst.deploy_predict_function( - # model_name, model_version, predict_func, labels, input_type) - # self.assertTrue(result) - # model_info = self.clipper_inst.get_model_info(model_name, - # model_version) - # self.assertIsNotNone(model_info) - # running_containers_output = self.clipper_inst._execute_standard( - # "docker ps -q --filter \"ancestor=clipper/python-container\"") - # self.assertIsNotNone(running_containers_output) - # self.assertGreaterEqual(len(running_containers_output), 1) + def test_predict_function_deploys_successfully(self): + model_name = "m2" + model_version = 1 + predict_func = lambda inputs: ["0" for x in inputs] + labels = ["test"] + input_type = "doubles" + result = self.clipper_inst.deploy_predict_function( + model_name, model_version, predict_func, labels, input_type) + self.assertTrue(result) + model_info = self.clipper_inst.get_model_info(model_name, + model_version) + self.assertIsNotNone(model_info) + running_containers_output = self.clipper_inst._execute_standard( + "docker ps -q --filter \"ancestor=clipper/python-container\"") + self.assertIsNotNone(running_containers_output) + self.assertGreaterEqual(len(running_containers_output), 1) class ClipperManagerTestCaseLong(unittest.TestCase): @@ -217,34 +217,34 @@ def test_deployed_model_queried_successfully(self): self.assertNotEqual(parsed_response["output"], self.default_output) self.assertFalse(parsed_response["default"]) - # def test_deployed_predict_function_queried_successfully(self): - # model_version = 1 - # predict_func = lambda inputs: [str(len(x)) for x in inputs] - # labels = ["test"] - # input_type = "doubles" - # result = self.clipper_inst.deploy_predict_function( - # self.model_name_1, model_version, predict_func, labels, input_type) - # self.assertTrue(result) - # - # time.sleep(60) - # - # received_non_default_prediction = False - # url = "http://localhost:1337/{}/predict".format(self.app_name_1) - # test_input = [101.1, 99.5, 107.2] - # req_json = json.dumps({'uid': 0, 'input': test_input}) - # headers = {'Content-type': 'application/json'} - # for i in range(0, 40): - # response = requests.post(url, headers=headers, data=req_json) - # parsed_response = json.loads(response.text) - # output = parsed_response["output"] - # if output == self.default_output: - # time.sleep(20) - # else: - # received_non_default_prediction = True - # self.assertEqual(int(output), len(test_input)) - # break - # - # self.assertTrue(received_non_default_prediction) + def test_deployed_predict_function_queried_successfully(self): + model_version = 1 + predict_func = lambda inputs: [str(len(x)) for x in inputs] + labels = ["test"] + input_type = "doubles" + result = self.clipper_inst.deploy_predict_function( + self.model_name_1, model_version, predict_func, labels, input_type) + self.assertTrue(result) + + time.sleep(60) + + received_non_default_prediction = False + url = "http://localhost:1337/{}/predict".format(self.app_name_1) + test_input = [101.1, 99.5, 107.2] + req_json = json.dumps({'uid': 0, 'input': test_input}) + headers = {'Content-type': 'application/json'} + for i in range(0, 40): + response = requests.post(url, headers=headers, data=req_json) + parsed_response = json.loads(response.text) + output = parsed_response["output"] + if output == self.default_output: + time.sleep(20) + else: + received_non_default_prediction = True + self.assertEqual(int(output), len(test_input)) + break + + self.assertTrue(received_non_default_prediction) SHORT_TEST_ORDERING = [