After stepping through these development iterations and achieving sufficient performance and successful test results, we can proceed to releasing the particular version of the solution model and deploying it for inference.
Let's release the first version of our model. As per the version
field in the pyproject.toml, the version number is going to be 0.1
.
Following the best practices, we first commit all the recent changes and tag it with the version number:
! git commit -m "Released 0.1"
! git tag "v0.1"
[main (root-commit) 096ff6a] Released 0.1 9 files changed, 200 insertions(+) create mode 100644 .gitignore create mode 100644 avazuctr/__init__.py create mode 100644 avazuctr/evaluation.py create mode 100644 avazuctr/pipeline.py create mode 100644 avazuctr/source.py create mode 100644 pyproject.toml create mode 100644 tests/__init__.py create mode 100644 tests/test_pipeline.py create mode 100644 tests/test_source.py
Now we can trigger the actual packaging of the artifact and publish it to the model registry:
! forml project release
running bdist_4ml Collecting category-encoders==2.6.0 Downloading category_encoders-2.6.0-py2.py3-none-any.whl (81 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 81.2/81.2 kB 910.0 kB/s eta 0:00:00a 0:00:01 Collecting forml==0.93 Using cached forml-0.93-py3-none-any.whl (283 kB) Collecting imbalanced-learn==0.10.1 Using cached imbalanced_learn-0.10.1-py3-none-any.whl (226 kB) Collecting openschema==0.7 Downloading openschema-0.7-py3-none-any.whl (14 kB) Collecting pandas==2.0.1 Downloading pandas-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.3 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.3/12.3 MB 869.3 kB/s eta 0:00:0000:0100:01 Collecting scikit-learn==1.2.2 Using cached scikit_learn-1.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (9.6 MB) Collecting numpy>=1.14.0 (from category-encoders==2.6.0) Using cached numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.3 MB) Collecting scipy>=1.0.0 (from category-encoders==2.6.0) Using cached scipy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (34.4 MB) Collecting statsmodels>=0.9.0 (from category-encoders==2.6.0) Downloading statsmodels-0.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (10.1 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 10.1/10.1 MB 880.9 kB/s eta 0:00:0000:0100:01 Collecting patsy>=0.5.1 (from category-encoders==2.6.0) Downloading patsy-0.5.3-py2.py3-none-any.whl (233 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 233.8/233.8 kB 974.1 kB/s eta 0:00:00a 0:00:01 Collecting click (from forml==0.93) Using cached click-8.1.3-py3-none-any.whl (96 kB) Collecting cloudpickle (from forml==0.93) Using cached cloudpickle-2.2.1-py3-none-any.whl (25 kB) Collecting jinja2 (from forml==0.93) Using cached Jinja2-3.1.2-py3-none-any.whl (133 kB) Collecting packaging>=20.0 (from forml==0.93) Using cached packaging-23.1-py3-none-any.whl (48 kB) Collecting pip (from forml==0.93) Using cached pip-23.1.2-py3-none-any.whl (2.1 MB) Collecting setuptools (from forml==0.93) Using cached setuptools-67.8.0-py3-none-any.whl (1.1 MB) Collecting toml (from forml==0.93) Using cached toml-0.10.2-py2.py3-none-any.whl (16 kB) Collecting tomli (from forml==0.93) Using cached tomli-2.0.1-py3-none-any.whl (12 kB) Collecting joblib>=1.1.1 (from imbalanced-learn==0.10.1) Using cached joblib-1.2.0-py3-none-any.whl (297 kB) Collecting threadpoolctl>=2.0.0 (from imbalanced-learn==0.10.1) Using cached threadpoolctl-3.1.0-py3-none-any.whl (14 kB) Collecting python-dateutil>=2.8.2 (from pandas==2.0.1) Using cached python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB) Collecting pytz>=2020.1 (from pandas==2.0.1) Using cached pytz-2023.3-py2.py3-none-any.whl (502 kB) Collecting tzdata>=2022.1 (from pandas==2.0.1) Using cached tzdata-2023.3-py2.py3-none-any.whl (341 kB) Collecting six (from patsy>=0.5.1->category-encoders==2.6.0) Using cached six-1.16.0-py2.py3-none-any.whl (11 kB) Collecting MarkupSafe>=2.0 (from jinja2->forml==0.93) Using cached MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (25 kB) Installing collected packages: pytz, tzdata, tomli, toml, threadpoolctl, six, setuptools, pip, packaging, numpy, MarkupSafe, joblib, cloudpickle, click, scipy, python-dateutil, patsy, jinja2, scikit-learn, pandas, statsmodels, imbalanced-learn, forml, openschema, category-encoders Successfully installed MarkupSafe-2.1.2 category-encoders-2.6.0 click-8.1.3 cloudpickle-2.2.1 forml-0.93 imbalanced-learn-0.10.1 jinja2-3.1.2 joblib-1.2.0 numpy-1.24.3 openschema-0.7 packaging-23.1 pandas-2.0.1 patsy-0.5.3 pip-23.1.2 python-dateutil-2.8.2 pytz-2023.3 scikit-learn-1.2.2 scipy-1.10.1 setuptools-67.8.0 six-1.16.0 statsmodels-0.14.0 threadpoolctl-3.1.0 toml-0.10.2 tomli-2.0.1 tzdata-2023.3 running upload
We should now see the project in the model registry next to the previously published dummy
:
! forml model list
dummy forml-solution-avazuctr
! forml model list forml-solution-avazuctr
0.1
! forml model list forml-solution-avazuctr 0.1
The release 0.1
is now in the model registry but it has no trained generations yet.
! tree /opt/forml/assets/registry/forml-solution-avazuctr
/opt/forml/assets/registry/forml-solution-avazuctr └── 0.1 └── package.4ml 1 directory, 1 file
Let's pretend the current time is 2014-10-21 03:00:00
and train a model with data up to that now point:
! forml model train forml-solution-avazuctr \
--upper '2014-10-21 03:00:00'
! forml model list forml-solution-avazuctr 0.1
1
! tree /opt/forml/assets/registry/forml-solution-avazuctr
/opt/forml/assets/registry/forml-solution-avazuctr └── 0.1 ├── 1 │ ├── 0444977f-94e0-41af-95cc-bc73c7d4190e.bin │ ├── 13459cdb-6c85-4899-a516-f814d5b2bda2.bin │ ├── 32e05554-5eed-44dd-b53d-23b8807562a3.bin │ ├── 38442d05-75ce-4a00-954e-aef27cacd4e7.bin │ ├── 4cb2c4c6-7cd2-47b6-a37d-daf15119e46f.bin │ ├── 66ab3016-2baa-4864-915f-13ab9163ce60.bin │ ├── 83ed1abf-4086-4d05-9ad4-4e9f086c5d68.bin │ ├── 8b94be64-ea35-4642-b26a-f98a6317be20.bin │ ├── 8c736b7e-6fc6-4918-815a-4badc0d63af8.bin │ └── tag.toml └── package.4ml 2 directories, 11 files
Serving our published model means exposing its task graph in the apply mode for prediction requests.
To quickly remind ourselves what this task graph looks like:
! forml model -R graphviz apply forml-solution-avazuctr
On top of the published models, ForML adds an additional layer for controlling all sorts of model serving aspects. This brings forth the concept of applications and serving gateways.
Let's start the default serving gateway. Since it is a server process, launch it in a separate terminal using the following command:
$ forml application serve
We can now issue a request to the gateway targetting the (not-yet-deployed) app forml-solution-avazuctr
:
! curl -X POST http://127.0.0.1:8000/forml-solution-avazuctr
Application forml-solution-avazuctr not found in Dispatch-registry
As expected, this results in 404
until the application is actually deployed.
Applications are defined through an application descriptor responsible for:
Let's implement a simple application descriptor for our scenario:
! touch application.py
Now, add the generic application descriptor code to the application.py:
from forml import application
application.setup(application.Generic('forml-solution-avazuctr'))
! git add application.py
The application gets deployed by pushing its descriptor to the application inventory:
! forml application put application.py
! forml application list
forml-solution-avazuctr
Now we can issue a prediction request:
! curl -H 'Content-Type: application/json' -d '[{ \
"hour": "2014-10-21 03:00:00", \
"banner_pos": "0", \
"site_id": "887a4754", "site_domain": "e3d9ca35", \
"site_category": "50e219e0", \
"app_id": "ecad2386", "app_domain": "7801e8d9", \
"app_category": "07d7df22", \
"device_id": "0e79d423", "device_ip": "9f423918", \
"device_model": "fc10a0d3", \
"device_type": "0", "device_conn_type": "0", \
"C1": "1002", "C14": "22701", "C15": "320", "C16": "50", \
"C17": "2624", "C18": "0", "C19": "35", "C20": "-1", "C21": "221" \
}]' http://127.0.0.1:8000/forml-solution-avazuctr
[{"c0":0.1618960089}]