Live Model Server Testing#

Test model server via HTTP calls

# nuclio: ignore
import nuclio
%nuclio config spec.image = "mlrun/mlrun"
%nuclio: setting spec.image to 'mlrun/mlrun'
import os
import pandas as pd
import requests
import json
import numpy as np

from datetime import datetime
from mlrun.datastore import DataItem
from mlrun.artifacts import get_model, ChartArtifact

def model_server_tester(context,
                        table: DataItem,
                        addr: str, 
                        label_column: str = "label",
                        model: str = '',
                        match_err: bool = False,
                        rows: int = 20):
    """ Test a model server 
    
    :param table:         csv/parquet table with test data
    :param addr:          function address/url
    :param label_column:  name of the label column in table
    :param model:         tested model name 
    :param match_err:     raise error on validation (require proper test set)
    :param rows:          number of rows to use from test set
    """
        
    table = table.as_df()

    y_list = table.pop(label_column).values.tolist()
    context.logger.info(f'testing with dataset against {addr}, model: {model}')
    if rows and rows < table.shape[0]:
        table = table.sample(rows)
    
    count = err_count = match = 0
    times = []
    for x, y in zip(table.values, y_list):
        count += 1
        event_data = json.dumps({"instances":[x.tolist()]})
        had_err = False
        try:
            start = datetime.now()
            resp = requests.put(f'{addr}/{model}/predict', json=event_data)
            if not resp.ok:
                context.logger.error(f'bad function resp!!\n{resp.text}')
                err_count += 1
                continue
            times.append((datetime.now()-start).microseconds)
                
        except OSError as err:
            context.logger.error(f'error in request, data:{event_data}, error: {err}')
            err_count += 1
            continue
            
        y_resp = resp.json()[0]
        if y == y_resp:
            match += 1
        
    context.log_result('total_tests', count)
    context.log_result('errors', err_count)
    context.log_result('match', match)
    if count - err_count > 0:
        times_arr = np.array(times)
        context.log_result('avg_latency', int(np.mean(times_arr)))
        context.log_result('min_latency', int(np.amin(times_arr)))
        context.log_result('max_latency', int(np.amax(times_arr)))
        
        chart = ChartArtifact('latency', header=['Test', 'Latency (microsec)'])
        for i in range(len(times)):
            chart.add_row([i+1, int(times[i])])
        context.log_artifact(chart)

    context.logger.info(f'run {count} tests, {err_count} errors and {match} match expected value')
    
    if err_count:
        raise ValueError(f'failed on {err_count} tests of {count}')
    
    if match_err and match != count:
        raise ValueError(f'only {match} results match out of {count}')
# nuclio: end-code
# marks the end of a code section

Deploy model server for testing#

import mlrun
project_name = 'sk-project'
MODEL_PATH = 'https://s3.wasabisys.com/iguazio/models/iris/model.pkl'

artifact_path = mlrun.set_environment(api_path = 'http://mlrun-api:8080',
                                      artifact_path = os.path.abspath('./'))

# import model server function from hub
fn = mlrun.import_function('hub://model_server')
fn.add_model("mymodel", MODEL_PATH)
address = fn.deploy()
> 2020-10-28 16:43:54,679 [warning] warning!, server (0.5.3-rc1) and client (0.5.2) ver dont match
> 2020-10-28 16:43:55,002 [info] deploy started
[nuclio] 2020-10-28 16:45:17,274 (info) Build complete
[nuclio] 2020-10-28 16:45:22,363 done updating default-model-server, function address: default-tenant.app.dsteam.iguazio-cd1.com:30150
> 2020-10-28 16:45:22,369 [warning] warning!, server (0.5.3-rc1) and client (0.5.2) ver dont match
user_name = os.getenv('V3IO_USERNAME')

fn.apply(mlrun.mount_v3io())
fn.set_envs({'SERVING_MODEL_iris_dataset_v1': MODEL_PATH,
             'INFERENCE_STREAM': 'users/{}/tststream'.format(user_name)})

address = fn.deploy()
> 2020-10-28 16:45:22,418 [info] deploy started
[nuclio] 2020-10-28 16:45:25,272 (info) Build complete
[nuclio] 2020-10-28 16:45:34,868 done updating default-model-server, function address: default-tenant.app.dsteam.iguazio-cd1.com:30150

Run model server tester locally#

# run the function locally
DATA_PATH  = 'https://s3.wasabisys.com/iguazio/data/iris/iris_dataset.csv'

gen = mlrun.run_local(name='model_server_tester', 
                      handler=model_server_tester, 
                      params={'addr': address, 'model': 'mymodel'},
                      inputs={'table': DATA_PATH},
                      project=project_name, 
                      artifact_path=os.path.join(artifact_path, 'data')) 
> 2020-10-28 16:45:34,916 [warning] warning!, server (0.5.3-rc1) and client (0.5.2) ver dont match
> 2020-10-28 16:45:34,916 [info] starting run model_server_tester uid=c84fdd4dfacd447dbe417d709f5983f0  -> http://mlrun-api:8080
> 2020-10-28 16:45:34,972 [warning] warning!, server (0.5.3-rc1) and client (0.5.2) ver dont match
> 2020-10-28 16:45:35,264 [info] testing with dataset against http://default-tenant.app.dsteam.iguazio-cd1.com:30150, model: mymodel
> 2020-10-28 16:45:35,967 [info] run 20 tests, 0 errors and 6 match expected value
project uid iter start state name labels inputs parameters results artifacts
sk-project 0 Oct 28 16:45:34 completed model_server_tester
v3io_user=admin
kind=handler
owner=admin
host=jupyter-d87678b84-n4lcf
table
addr=http://default-tenant.app.dsteam.iguazio-cd1.com:30150
model=mymodel
total_tests=20
errors=0
match=6
avg_latency=32619
min_latency=26263
max_latency=110524
latency
to track results use .show() or .logs() or in CLI: 
!mlrun get run c84fdd4dfacd447dbe417d709f5983f0 --project sk-project , !mlrun logs c84fdd4dfacd447dbe417d709f5983f0 --project sk-project
> 2020-10-28 16:45:36,042 [info] run executed, status=completed

Save#

test_func = mlrun.code_to_function(name='model_server_tester', 
                                   kind='job',
                                   handler='model_server_tester',
                                   description='test model servers',
                                   categories=["ml": "test"],
                                   labels={"author": "yaronh"})

test_func.export('function.yaml')
> 2020-10-28 16:45:43,591 [info] function spec saved to path: function.yaml
<mlrun.runtimes.kubejob.KubejobRuntime at 0x7fde772f2f50>

Run remotely#

test_func.run(mlrun.NewTask(name='model_server_tester', 
                            handler=model_server_tester, 
                            params={'addr': address, 'model': 'mymodel'},
                            inputs={'table': DATA_PATH},
                            project=project_name, 
                            artifact_path=os.path.join(artifact_path, 'data')))
> 2020-10-28 16:45:43,605 [warning] warning!, server (0.5.3-rc1) and client (0.5.2) ver dont match
> 2020-10-28 16:45:43,606 [info] starting run model_server_tester uid=73cb7da1bfeb4b50afb29bce40a9c861  -> http://mlrun-api:8080
> 2020-10-28 16:45:43,738 [info] Job is running in the background, pod: model-server-tester-btgvj
project uid iter start state name labels inputs parameters results artifacts
sk-project 0 Oct 28 16:45:43 running model_server_tester
v3io_user=admin
kind=job
owner=admin
table
addr=http://default-tenant.app.dsteam.iguazio-cd1.com:30150
model=mymodel
to track results use .show() or .logs() or in CLI: 
!mlrun get run 73cb7da1bfeb4b50afb29bce40a9c861 --project sk-project , !mlrun logs 73cb7da1bfeb4b50afb29bce40a9c861 --project sk-project
> 2020-10-28 16:45:43,790 [info] run executed, status=running
<mlrun.model.RunObject at 0x7fdeb84930d0>