Skip to content

Commit

Permalink
serving tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
antonymayi committed Aug 30, 2022
1 parent e3812d0 commit d9aee4d
Show file tree
Hide file tree
Showing 34 changed files with 399 additions and 151 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
show-source = true
enable-extensions=G
max-line-length = 120
ignore = B019,E731,W504,I001,W503
ignore = B019,B024,E731,W504,I001,W503
exclude = .git,__pycache__,.eggs,*.egg
min_python_version = 3.9.0
35 changes: 17 additions & 18 deletions constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ alembic==1.8.1
# via mlflow
anyio==3.6.1
# via starlette
astroid==2.11.7
astroid==2.12.4
# via pylint
attrs==22.1.0
# via
Expand All @@ -31,7 +31,7 @@ certifi==2022.6.15
# via requests
cfgv==3.3.1
# via pre-commit
charset-normalizer==2.1.0
charset-normalizer==2.1.1
# via requests
click==8.1.3
# via
Expand All @@ -50,15 +50,15 @@ cloudpickle==2.1.0
# mlflow
coverage==6.4.4
# via pytest-cov
dask==2022.8.0
dask==2022.8.1
# via forml (setup.py)
databricks-cli==0.17.1
databricks-cli==0.17.3
# via mlflow
defusedxml==0.7.1
# via nbconvert
dill==0.3.5.1
# via pylint
distlib==0.3.5
distlib==0.3.6
# via virtualenv
docker==5.0.3
# via mlflow
Expand All @@ -70,7 +70,6 @@ entrypoints==0.4
# via
# jupyter-client
# mlflow
# nbconvert
fastjsonschema==2.16.1
# via nbformat
filelock==3.8.0
Expand All @@ -80,7 +79,7 @@ flake8==5.0.4
# flake8-bugbear
# flake8-colors
# flake8-typing-imports
flake8-bugbear==22.7.1
flake8-bugbear==22.8.23
# via forml (setup.py)
flake8-colors==0.1.9
# via forml (setup.py)
Expand All @@ -100,7 +99,7 @@ gitpython==3.1.27
# via mlflow
graphviz==0.20.1
# via forml (setup.py)
greenlet==1.1.2
greenlet==1.1.3
# via sqlalchemy
gunicorn==20.1.0
# via mlflow
Expand Down Expand Up @@ -132,9 +131,9 @@ jinja2==3.1.2
# sphinx
joblib==1.1.0
# via scikit-learn
jsonschema==4.12.1
jsonschema==4.14.0
# via nbformat
jupyter-client==7.3.4
jupyter-client==7.3.5
# via nbclient
jupyter-core==4.11.1
# via
Expand Down Expand Up @@ -164,17 +163,17 @@ mccabe==0.7.0
# via
# flake8
# pylint
mistune==0.8.4
mistune==2.0.4
# via nbconvert
mlflow==1.28.0
# via forml (setup.py)
mypy-extensions==0.4.3
# via
# black
# typing-inspect
nbclient==0.6.6
nbclient==0.6.7
# via nbconvert
nbconvert==6.5.3
nbconvert==7.0.0
# via nbsphinx
nbformat==5.4.0
# via
Expand Down Expand Up @@ -258,7 +257,7 @@ pyhive==0.6.5
# via forml (setup.py)
pyjwt==2.4.0
# via databricks-cli
pylint==2.14.5
pylint==2.15.0
# via forml (setup.py)
pyparsing==3.0.9
# via packaging
Expand Down Expand Up @@ -301,7 +300,7 @@ requests==2.28.1
# sphinx
scikit-learn==1.1.2
# via forml (setup.py)
scipy==1.9.0
scipy==1.9.1
# via
# mlflow
# scikit-learn
Expand Down Expand Up @@ -402,17 +401,17 @@ typing-extensions==4.3.0
# typing-inspect
typing-inspect==0.8.0
# via libcst
urllib3==1.26.11
urllib3==1.26.12
# via requests
uvicorn==0.18.2
uvicorn==0.18.3
# via forml (setup.py)
virtualenv==20.16.3
# via pre-commit
webencodings==0.5.1
# via
# bleach
# tinycss2
websocket-client==1.3.3
websocket-client==1.4.0
# via docker
werkzeug==2.2.2
# via flask
Expand Down
18 changes: 11 additions & 7 deletions docs/application.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ ML problem, *applications* aim to expose it (by means of :ref:`gateway provider
<serving-gateway>`) in a domain-specific form suitable for integration with the *actual* decision
making process.

ForML :ref:`platform <platform>` persists :ref:`published applications <application-publishing>`
within a special :ref:`application inventory <inventory>` where they are picked from at runtime
by the :ref:`serving engine <serving>`.


.. _application-prjrelation:
.. rubric:: Project-Application Relationship

As shown in the diagram below, relationships between projects and applications can have any
possible cardinality. Projects might not be associated with any application (not exposed for
serving - e.g. *Project B*), on the other hand an application can possibly span multiple projects
Expand Down Expand Up @@ -65,10 +73,6 @@ It makes sense to manage an application (descriptor) in the scope of some partic
they form a 1:1 relationship (perhaps the most typical scenario). More complex applications might
need to be maintained separately though.

ForML :ref:`platform <platform>` persists :ref:`published applications <application-publishing>`
within a special :ref:`application inventory <inventory>` where they are picked from at runtime
by the :ref:`serving engine <serving>`.

.. _application-dispatch:

Request Dispatching
Expand Down Expand Up @@ -130,10 +134,10 @@ Another powerful way an application exerts control over the serving process is a
:meth:`selection <forml.application.Descriptor.select>` of the specific :ref:`model generation
<registry-assets>` to be used for serving each particular request.

Applications can base the selection logic on the following available details:
Applications can base the selection logic on the following available facts:

* actual content of the :ref:`model registry <registry>` (all existing model generations
to choose from)
* actual content of the :ref:`model registry <registry>` (any existing model generation to choose
from)
* custom metadata stored in the application *context* (e.g. as part of the query :meth:`receiving
<forml.application.Descriptor.receive>`)
* various serving :class:`metrics <forml.runtime.Stats>` provided by the system
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
_target_blacklist = {
'py:class': (
'_Actor',
'applications.Starlette',
r'asset\.Generation',
r'^dsl\.Operable',
r'^dsl\.Ordering\.(?:Direction|Term)',
Expand Down
2 changes: 1 addition & 1 deletion docs/io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ The two encoder/decoder matching functions bellow currently support the followin
encodings/flavours:

+-------------------------+----------------------------+------------------------------------------+
| Encoding | Example | Implementation |
| Content-type | Example | Implementation |
+=========================+============================+==========================================+
| ``application/json; | ``[{column -> value}, | |
| format=pandas-records`` | ... , {column -> value}]`` | |
Expand Down
5 changes: 3 additions & 2 deletions docs/platform.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ will try to locate and merge the :file:`config.toml` file instances in the follo

Following is the default content of the ForML platform configuration file:

.. literalinclude:: ../forml/setup/_conf/config.toml
.. literalinclude:: ../forml/setup/config.toml
:caption: config.toml (default)
:linenos:
:language: toml
Expand Down Expand Up @@ -168,9 +168,10 @@ command-line interface - see the integrated help for more details:
Lifecycle Management for Datascience Projects.
Options:
-C, --config PATH Additional config file.
-C, --config FILE Additional config file.
-L, --loglevel [debug|info|warning|error]
Global loglevel to use.
--logfile FILE Logfile path.
--help Show this message and exit.
Commands:
Expand Down
2 changes: 1 addition & 1 deletion docs/runner.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Runner API
----------

.. autoclass:: forml.runtime.Runner
:members: _run
:members: run


.. _runner-providers:
Expand Down
5 changes: 5 additions & 0 deletions docs/tutorials/titanic/lifecycle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,8 @@ free to change the directory to another location before executing the commands.
.. image:: ../../_static/images/titanic-apply.png
:target: ../../_static/images/titanic-apply.png

Now, after exploring two of the :ref:`execution mechanisms <platform-execution>` (namely the
:ref:`interactive <interactive>` mode demonstrated during the :doc:`exploratory
analysis <exploration>` and the :ref:`command-line driven <platform-cli>` batch processing shown
in this chapter), we can proceed to the final :doc:`deployment and serving <serving>`.
4 changes: 2 additions & 2 deletions docs/tutorials/titanic/pipeline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ Custom Preprocessing Operators

In addition to the ``Imputer`` operator we've created in scope of our :doc:`exploration
<exploration>`, let's improve our preprocessing with a couple more operators. We stick to the
simple ``@wrap`` technique for implementing :ref:`actors <actor-decorated>` and eventually
:ref:`operators <operator-wrapped>`.
simple ``@wrap`` technique for implementing :ref:`actors <actor-decorated>` and :ref:`operators
<operator-wrapped>` eventually.

ParseTitle
^^^^^^^^^^
Expand Down
103 changes: 102 additions & 1 deletion docs/tutorials/titanic/serving.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,108 @@
Deployment and Serving
======================

TODO
With all the :ref:`lifecycle actions <lifecycle>` simply :doc:`at our fingertips <lifecycle>`,
let's now focus on deploying the project as a :ref:`ForML application <application>` making it
available for :ref:`serving <serving>`.


Creating and Publishing the Application Descriptor
--------------------------------------------------

ForML applications are :ref:`implemented <application-implementation>` in form of a *descriptor*
instance defined within a *Python module*. Due to the potentially non-exclusive nature of the
:ref:`project-application relationship <application-prjrelation>`, this module might in general
need to be maintained out of the project scope but for the sake of this tutorial let's assume a
direct 1:1 relationship so that we can keep it along with the project sources in a module called
:file:`application.py`. Our top-level project structure is then going to look as follows:

.. code-block:: console
$ ls -1p forml-tutorial-titanic
application.py
notebooks/
setup.py
tests/
titanic/
For simplicity, we choose to :ref:`implement the application <application-implementation>` in
the most basic (yet full-featured) manner by reusing the existing :class:`application.Generic
<forml.application.Generic>` descriptor and passing its instance to :func:`application.setup()
<forml.application.setup>` for registration. Note this simplistic setup requires the application
name to match the project name (in order to select the relevant assets from the :ref:`model
registry <registry>`):

.. literalinclude:: ../../../tutorials/titanic/application.py
:caption: application.py
:linenos:
:language: python
:start-after: # under the License.

That's all it takes to implement a simple application descriptor. It can now be deployed by
the means of publishing into a :ref:`platform-configured <platform-config>` application
:ref:`inventory <inventory>`:

.. code-block:: console
$ forml application put application.py
$ forml application list
forml-example-titanic
Serving
-------

Easiest way to expose our model for serving is to spin up a particular :ref:`serving gateway
<serving-gateway>` provider linked through the :ref:`platform configuration <platform-config>` to
the same :ref:`inventory <inventory>` and :ref:`registry <registry>` holding our application and
models respectively.

The configured gateway (the :class:`rest.Gateway <forml.provider.gateway.rest.Gateway>` in our case)
can be started simply using the CLI:

.. code-block:: console
$ forml application serve
INFO: Started server process [568798]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit)
Note the gateway is capable of serving any application in the linked inventory.

Let's explore the capabilities using manual ``curl`` queries:

#. Querying a non-existent application ``foobarbaz``:

.. code-block:: console
$ curl -X POST http://127.0.0.1:8080/foobarbaz
Application foobarbaz not found in Dispatch-registry
#. Querying our ``forml-titanic-example`` application using a *JSON* encoded payload:

.. code-block:: console
$ curl -X POST -H 'Content-Type: application/json' -d '[{"Pclass":1, "Name":"Foo", "Sex": "male", "Age": 34, "SibSp": 3, "Parch": 2, "Ticket": "13", "Fare": 10.1, "Cabin": "123", "Embarked": "S"}]' http://127.0.0.1:8080/forml-example-titanic
[{"c0":0.3459976655}]
#. Making the same query but requesting the result to be encoded as CSV:

.. code-block:: console
$ curl -X POST -H 'Content-Type: application/json' -H 'Accept: text/csv' -d '[{"Pclass":1,"Name":"Foo", "Sex": "male", "Age": 34, "SibSp": 3, "Parch": 2, "Ticket": "13", "Fare": 10.1, "Cabin": "123", "Embarked": "S"}]' http://127.0.0.1:8080/forml-example-titanic
c0
0.34599766550668526
#. Making the same query but sending the payload in the *pandas-split* (JSON) format and requesting
the result as (JSON) *values*:

.. code-block:: console
$ curl -X POST -H 'Content-Type: application/json; format=pandas-split' -H 'Accept: application/json; format=pandas-values' -d '{"columns": ["Pclass", "Name", "Sex", "Age", "SibSp", "Parch", "Ticket", "Fare", "Cabin", "Embarked"], "data": [[1, "Foo", "male", 34, 3, 2, 13, 10.1, "123", "S"]]}' http://127.0.0.1:8080/forml-example-titanic
[[0.3459976655]]
That concludes this Titanic Challenge tutorial, from here you can continue to the other
:ref:`available tutorials <tutorials>` or browse the general :doc:`ForML documentation
<../../index>`.
8 changes: 5 additions & 3 deletions forml/application/_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,17 @@ def respond(
class Generic(Descriptor):
"""Generic application descriptor for basic serving scenarios.
It simply runs the directly decoded request payload through the model/generation selected using
It simply runs the directly decoded (using the :func:`available decoders
<forml.io.layout.get_decoder>`) request payload through the model/generation selected using
the provided :class:`application.Selector <forml.application.Selector>` and returns the directly
encoded outcomes as the response.
encoded (using the :func:`available encoders <forml.io.layout.get_encoder>`) outcomes as the
response.
Args:
name: The (unique) name for this application registration/lookup.
selector: Implementation of a particular model-selection strategy (defaults to
:class:`application.Latest <forml.application.Latest>` selector expecting the
project name to be matching the application name).
project name to be *matching* the application name).
Examples:
>>> APP = application.Generic('forml-example-titanic')
Expand Down
3 changes: 2 additions & 1 deletion forml/application/_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ def select(self, registry: 'asset.Directory', context: typing.Any, stats: 'runti
Args:
registry: Model registry to select the model from.
context: Optional metadata carried over from decode.
context: Optional metadata carried over from the :meth:`application.Descriptor.receive
<forml.application.Descriptor.receive>`.
stats: Application specific serving metrics.
Returns:
Expand Down
2 changes: 1 addition & 1 deletion forml/io/_input/_producer.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def __call__(self, statement: 'dsl.Statement', entry: typing.Optional['layout.En
complete, indices = self._match_entry(statement.schema, entry.schema)
if not complete:
# here we would go into augmentation mode - when implemented
raise forml.InvalidError('Augmentation not yet supported')
raise forml.MissingError('Augmentation not supported - please provide all features')
return entry.data.take_columns(indices) if indices else entry.data

LOGGER.debug('Parsing ETL query')
Expand Down
Loading

0 comments on commit d9aee4d

Please sign in to comment.