mlrun 1.7.0rc14__py3-none-any.whl → 1.7.0rc21__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mlrun might be problematic. Click here for more details.
- mlrun/__init__.py +10 -1
- mlrun/__main__.py +23 -111
- mlrun/alerts/__init__.py +15 -0
- mlrun/alerts/alert.py +144 -0
- mlrun/api/schemas/__init__.py +4 -3
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +36 -253
- mlrun/artifacts/dataset.py +9 -190
- mlrun/artifacts/manager.py +46 -42
- mlrun/artifacts/model.py +9 -141
- mlrun/artifacts/plots.py +14 -375
- mlrun/common/constants.py +65 -3
- mlrun/common/formatters/__init__.py +19 -0
- mlrun/{runtimes/mpijob/v1alpha1.py → common/formatters/artifact.py} +6 -14
- mlrun/common/formatters/base.py +113 -0
- mlrun/common/formatters/function.py +46 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/{runtimes → common/runtimes}/constants.py +32 -4
- mlrun/common/schemas/__init__.py +10 -5
- mlrun/common/schemas/alert.py +92 -11
- mlrun/common/schemas/api_gateway.py +56 -0
- mlrun/common/schemas/artifact.py +15 -5
- mlrun/common/schemas/auth.py +2 -0
- mlrun/common/schemas/client_spec.py +1 -0
- mlrun/common/schemas/frontend_spec.py +1 -0
- mlrun/common/schemas/function.py +4 -0
- mlrun/common/schemas/model_monitoring/__init__.py +15 -3
- mlrun/common/schemas/model_monitoring/constants.py +58 -7
- mlrun/common/schemas/model_monitoring/grafana.py +9 -5
- mlrun/common/schemas/model_monitoring/model_endpoints.py +86 -2
- mlrun/common/schemas/pipeline.py +0 -9
- mlrun/common/schemas/project.py +5 -11
- mlrun/common/types.py +1 -0
- mlrun/config.py +27 -9
- mlrun/data_types/to_pandas.py +9 -9
- mlrun/datastore/base.py +41 -9
- mlrun/datastore/datastore.py +6 -2
- mlrun/datastore/datastore_profile.py +56 -4
- mlrun/datastore/inmem.py +2 -2
- mlrun/datastore/redis.py +2 -2
- mlrun/datastore/s3.py +5 -0
- mlrun/datastore/sources.py +147 -7
- mlrun/datastore/store_resources.py +7 -7
- mlrun/datastore/targets.py +110 -42
- mlrun/datastore/utils.py +42 -0
- mlrun/db/base.py +54 -10
- mlrun/db/httpdb.py +282 -79
- mlrun/db/nopdb.py +52 -10
- mlrun/errors.py +11 -0
- mlrun/execution.py +24 -9
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +12 -47
- mlrun/feature_store/feature_set.py +9 -0
- mlrun/feature_store/feature_vector.py +8 -0
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +9 -4
- mlrun/feature_store/retrieval/conversion.py +9 -9
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +9 -3
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +16 -0
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
- mlrun/frameworks/parallel_coordinates.py +2 -1
- mlrun/frameworks/tf_keras/__init__.py +4 -1
- mlrun/k8s_utils.py +10 -11
- mlrun/launcher/base.py +4 -3
- mlrun/launcher/client.py +5 -3
- mlrun/launcher/local.py +8 -2
- mlrun/launcher/remote.py +8 -2
- mlrun/lists.py +6 -2
- mlrun/model.py +45 -21
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +41 -18
- mlrun/model_monitoring/application.py +5 -305
- mlrun/model_monitoring/applications/__init__.py +11 -0
- mlrun/model_monitoring/applications/_application_steps.py +157 -0
- mlrun/model_monitoring/applications/base.py +280 -0
- mlrun/model_monitoring/applications/context.py +214 -0
- mlrun/model_monitoring/applications/evidently_base.py +211 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +132 -91
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +3 -1
- mlrun/model_monitoring/db/__init__.py +2 -0
- mlrun/model_monitoring/db/stores/__init__.py +0 -2
- mlrun/model_monitoring/db/stores/base/store.py +22 -37
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +43 -21
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +39 -8
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +27 -7
- mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +5 -0
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +246 -224
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +232 -216
- mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
- mlrun/model_monitoring/db/tsdb/base.py +329 -0
- mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
- mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +636 -0
- mlrun/model_monitoring/evidently_application.py +6 -118
- mlrun/model_monitoring/helpers.py +46 -1
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +57 -216
- mlrun/model_monitoring/writer.py +134 -124
- mlrun/package/utils/_formatter.py +2 -2
- mlrun/platforms/__init__.py +10 -9
- mlrun/platforms/iguazio.py +21 -202
- mlrun/projects/operations.py +19 -12
- mlrun/projects/pipelines.py +79 -102
- mlrun/projects/project.py +265 -103
- mlrun/render.py +15 -14
- mlrun/run.py +16 -46
- mlrun/runtimes/__init__.py +6 -3
- mlrun/runtimes/base.py +8 -7
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +0 -28
- mlrun/runtimes/kubejob.py +2 -1
- mlrun/runtimes/local.py +5 -2
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/runtimes/nuclio/api_gateway.py +194 -84
- mlrun/runtimes/nuclio/application/application.py +170 -8
- mlrun/runtimes/nuclio/function.py +39 -49
- mlrun/runtimes/pod.py +16 -36
- mlrun/runtimes/remotesparkjob.py +9 -3
- mlrun/runtimes/sparkjob/spark3job.py +1 -1
- mlrun/runtimes/utils.py +6 -45
- mlrun/serving/server.py +2 -1
- mlrun/serving/v2_serving.py +5 -1
- mlrun/track/tracker.py +2 -1
- mlrun/utils/async_http.py +25 -5
- mlrun/utils/helpers.py +107 -75
- mlrun/utils/logger.py +39 -7
- mlrun/utils/notifications/notification/__init__.py +14 -9
- mlrun/utils/notifications/notification/base.py +1 -1
- mlrun/utils/notifications/notification/slack.py +34 -7
- mlrun/utils/notifications/notification/webhook.py +1 -1
- mlrun/utils/notifications/notification_pusher.py +147 -16
- mlrun/utils/regex.py +9 -0
- mlrun/utils/v3io_clients.py +0 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/METADATA +14 -6
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/RECORD +150 -130
- mlrun/kfpops.py +0 -865
- mlrun/platforms/other.py +0 -305
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/WHEEL +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/top_level.txt +0 -0
|
@@ -12,121 +12,9 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
from mlrun.errors import MLRunIncompatibleVersionError
|
|
23
|
-
from mlrun.model_monitoring.application import ModelMonitoringApplicationBase
|
|
24
|
-
|
|
25
|
-
SUPPORTED_EVIDENTLY_VERSION = semver.Version.parse("0.4.11")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _check_evidently_version(*, cur: semver.Version, ref: semver.Version) -> None:
|
|
29
|
-
if ref.is_compatible(cur) or (
|
|
30
|
-
cur.major == ref.major == 0 and cur.minor == ref.minor and cur.patch > ref.patch
|
|
31
|
-
):
|
|
32
|
-
return
|
|
33
|
-
if cur.major == ref.major == 0 and cur.minor > ref.minor:
|
|
34
|
-
warnings.warn(
|
|
35
|
-
f"Evidently version {cur} is not compatible with the tested "
|
|
36
|
-
f"version {ref}, use at your own risk."
|
|
37
|
-
)
|
|
38
|
-
else:
|
|
39
|
-
raise MLRunIncompatibleVersionError(
|
|
40
|
-
f"Evidently version {cur} is not supported, please change to "
|
|
41
|
-
f"{ref} (or another compatible version)."
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
_HAS_EVIDENTLY = False
|
|
46
|
-
try:
|
|
47
|
-
import evidently # noqa: F401
|
|
48
|
-
|
|
49
|
-
_check_evidently_version(
|
|
50
|
-
cur=semver.Version.parse(evidently.__version__),
|
|
51
|
-
ref=SUPPORTED_EVIDENTLY_VERSION,
|
|
52
|
-
)
|
|
53
|
-
_HAS_EVIDENTLY = True
|
|
54
|
-
except ModuleNotFoundError:
|
|
55
|
-
pass
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if _HAS_EVIDENTLY:
|
|
59
|
-
from evidently.renderers.notebook_utils import determine_template
|
|
60
|
-
from evidently.report.report import Report
|
|
61
|
-
from evidently.suite.base_suite import Suite
|
|
62
|
-
from evidently.ui.type_aliases import STR_UUID
|
|
63
|
-
from evidently.ui.workspace import Workspace
|
|
64
|
-
from evidently.utils.dashboard import TemplateParams
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class EvidentlyModelMonitoringApplicationBase(ModelMonitoringApplicationBase):
|
|
68
|
-
def __init__(
|
|
69
|
-
self, evidently_workspace_path: str, evidently_project_id: "STR_UUID"
|
|
70
|
-
) -> None:
|
|
71
|
-
"""
|
|
72
|
-
A class for integrating Evidently for mlrun model monitoring within a monitoring application.
|
|
73
|
-
Note: evidently is not installed by default in the mlrun/mlrun image.
|
|
74
|
-
It must be installed separately to use this class.
|
|
75
|
-
|
|
76
|
-
:param evidently_workspace_path: (str) The path to the Evidently workspace.
|
|
77
|
-
:param evidently_project_id: (str) The ID of the Evidently project.
|
|
78
|
-
|
|
79
|
-
"""
|
|
80
|
-
if not _HAS_EVIDENTLY:
|
|
81
|
-
raise ModuleNotFoundError("Evidently is not installed - the app cannot run")
|
|
82
|
-
self.evidently_workspace = Workspace.create(evidently_workspace_path)
|
|
83
|
-
self.evidently_project_id = evidently_project_id
|
|
84
|
-
self.evidently_project = self.evidently_workspace.get_project(
|
|
85
|
-
evidently_project_id
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
def log_evidently_object(
|
|
89
|
-
self, evidently_object: Union["Report", "Suite"], artifact_name: str
|
|
90
|
-
):
|
|
91
|
-
"""
|
|
92
|
-
Logs an Evidently report or suite as an artifact.
|
|
93
|
-
|
|
94
|
-
:param evidently_object: (Union[Report, Suite]) The Evidently report or suite object.
|
|
95
|
-
:param artifact_name: (str) The name for the logged artifact.
|
|
96
|
-
"""
|
|
97
|
-
evidently_object_html = evidently_object.get_html()
|
|
98
|
-
self.context.log_artifact(
|
|
99
|
-
artifact_name, body=evidently_object_html.encode("utf-8"), format="html"
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
def log_project_dashboard(
|
|
103
|
-
self,
|
|
104
|
-
timestamp_start: pd.Timestamp,
|
|
105
|
-
timestamp_end: pd.Timestamp,
|
|
106
|
-
artifact_name: str = "dashboard",
|
|
107
|
-
):
|
|
108
|
-
"""
|
|
109
|
-
Logs an Evidently project dashboard.
|
|
110
|
-
|
|
111
|
-
:param timestamp_start: (pd.Timestamp) The start timestamp for the dashboard data.
|
|
112
|
-
:param timestamp_end: (pd.Timestamp) The end timestamp for the dashboard data.
|
|
113
|
-
:param artifact_name: (str) The name for the logged artifact.
|
|
114
|
-
"""
|
|
115
|
-
|
|
116
|
-
dashboard_info = self.evidently_project.build_dashboard_info(
|
|
117
|
-
timestamp_start, timestamp_end
|
|
118
|
-
)
|
|
119
|
-
template_params = TemplateParams(
|
|
120
|
-
dashboard_id="pd_" + str(uuid.uuid4()).replace("-", ""),
|
|
121
|
-
dashboard_info=dashboard_info,
|
|
122
|
-
additional_graphs={},
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
dashboard_html = self._render(determine_template("inline"), template_params)
|
|
126
|
-
self.context.log_artifact(
|
|
127
|
-
artifact_name, body=dashboard_html.encode("utf-8"), format="html"
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
@staticmethod
|
|
131
|
-
def _render(temple_func, template_params: "TemplateParams"):
|
|
132
|
-
return temple_func(params=template_params)
|
|
15
|
+
# TODO : delete this file in 1.9.0
|
|
16
|
+
from mlrun.model_monitoring.applications import ( # noqa: F401
|
|
17
|
+
_HAS_EVIDENTLY,
|
|
18
|
+
SUPPORTED_EVIDENTLY_VERSION,
|
|
19
|
+
EvidentlyModelMonitoringApplicationBase,
|
|
20
|
+
)
|
|
@@ -24,6 +24,10 @@ import mlrun.common.schemas
|
|
|
24
24
|
from mlrun.common.schemas.model_monitoring import (
|
|
25
25
|
EventFieldType,
|
|
26
26
|
)
|
|
27
|
+
from mlrun.common.schemas.model_monitoring.model_endpoints import (
|
|
28
|
+
ModelEndpointMonitoringMetricType,
|
|
29
|
+
_compose_full_name,
|
|
30
|
+
)
|
|
27
31
|
from mlrun.model_monitoring.model_endpoint import ModelEndpoint
|
|
28
32
|
from mlrun.utils import logger
|
|
29
33
|
|
|
@@ -111,6 +115,24 @@ def get_connection_string(secret_provider: typing.Callable = None) -> str:
|
|
|
111
115
|
)
|
|
112
116
|
|
|
113
117
|
|
|
118
|
+
def get_tsdb_connection_string(
|
|
119
|
+
secret_provider: typing.Optional[typing.Callable] = None,
|
|
120
|
+
) -> str:
|
|
121
|
+
"""Get TSDB connection string from the project secret. If wasn't set, take it from the system
|
|
122
|
+
configurations.
|
|
123
|
+
:param secret_provider: An optional secret provider to get the connection string secret.
|
|
124
|
+
:return: Valid TSDB connection string.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
mlrun.get_secret_or_env(
|
|
129
|
+
key=mlrun.common.schemas.model_monitoring.ProjectSecretKeys.TSDB_CONNECTION,
|
|
130
|
+
secret_provider=secret_provider,
|
|
131
|
+
)
|
|
132
|
+
or mlrun.mlconf.model_endpoint_monitoring.tsdb_connection
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
114
136
|
def batch_dict2timedelta(batch_dict: _BatchDict) -> datetime.timedelta:
|
|
115
137
|
"""
|
|
116
138
|
Convert a batch dictionary to timedelta.
|
|
@@ -215,7 +237,7 @@ def update_model_endpoint_last_request(
|
|
|
215
237
|
|
|
216
238
|
def calculate_inputs_statistics(
|
|
217
239
|
sample_set_statistics: dict, inputs: pd.DataFrame
|
|
218
|
-
) ->
|
|
240
|
+
) -> mlrun.common.model_monitoring.helpers.FeatureStats:
|
|
219
241
|
"""
|
|
220
242
|
Calculate the inputs data statistics for drift monitoring purpose.
|
|
221
243
|
|
|
@@ -260,3 +282,26 @@ def get_endpoint_record(project: str, endpoint_id: str):
|
|
|
260
282
|
project=project,
|
|
261
283
|
)
|
|
262
284
|
return model_endpoint_store.get_model_endpoint(endpoint_id=endpoint_id)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def get_result_instance_fqn(
|
|
288
|
+
model_endpoint_id: str, app_name: str, result_name: str
|
|
289
|
+
) -> str:
|
|
290
|
+
return f"{model_endpoint_id}.{app_name}.result.{result_name}"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def get_default_result_instance_fqn(model_endpoint_id: str) -> str:
|
|
294
|
+
return get_result_instance_fqn(
|
|
295
|
+
model_endpoint_id,
|
|
296
|
+
mm_constants.HistogramDataDriftApplicationConstants.NAME,
|
|
297
|
+
mm_constants.HistogramDataDriftApplicationConstants.GENERAL_RESULT_NAME,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def get_invocations_fqn(project: str) -> str:
|
|
302
|
+
return _compose_full_name(
|
|
303
|
+
project=project,
|
|
304
|
+
app=mm_constants.SpecialApps.MLRUN_INFRA,
|
|
305
|
+
name=mm_constants.PredictionsQueryConstants.INVOCATIONS,
|
|
306
|
+
type=ModelEndpointMonitoringMetricType.METRIC,
|
|
307
|
+
)
|
|
@@ -17,6 +17,7 @@ from dataclasses import dataclass, field
|
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
19
19
|
import mlrun.model
|
|
20
|
+
from mlrun.common.model_monitoring.helpers import FeatureStats
|
|
20
21
|
from mlrun.common.schemas.model_monitoring.constants import (
|
|
21
22
|
EndpointType,
|
|
22
23
|
EventKeyMetrics,
|
|
@@ -42,8 +43,8 @@ class ModelEndpointSpec(mlrun.model.ModelObj):
|
|
|
42
43
|
|
|
43
44
|
@dataclass
|
|
44
45
|
class ModelEndpointStatus(mlrun.model.ModelObj):
|
|
45
|
-
feature_stats:
|
|
46
|
-
current_stats:
|
|
46
|
+
feature_stats: FeatureStats = field(default_factory=dict)
|
|
47
|
+
current_stats: FeatureStats = field(default_factory=dict)
|
|
47
48
|
first_request: str = ""
|
|
48
49
|
last_request: str = ""
|
|
49
50
|
error_count: int = 0
|
|
@@ -30,7 +30,6 @@ import mlrun.model_monitoring.db
|
|
|
30
30
|
import mlrun.model_monitoring.prometheus
|
|
31
31
|
import mlrun.serving.states
|
|
32
32
|
import mlrun.utils
|
|
33
|
-
import mlrun.utils.v3io_clients
|
|
34
33
|
from mlrun.common.schemas.model_monitoring.constants import (
|
|
35
34
|
EventFieldType,
|
|
36
35
|
EventKeyMetrics,
|
|
@@ -40,7 +39,6 @@ from mlrun.common.schemas.model_monitoring.constants import (
|
|
|
40
39
|
ProjectSecretKeys,
|
|
41
40
|
PrometheusEndpoints,
|
|
42
41
|
)
|
|
43
|
-
from mlrun.model_monitoring.helpers import get_endpoint_record
|
|
44
42
|
from mlrun.utils import logger
|
|
45
43
|
|
|
46
44
|
|
|
@@ -79,6 +77,7 @@ class EventStreamProcessor:
|
|
|
79
77
|
)
|
|
80
78
|
|
|
81
79
|
self.storage_options = None
|
|
80
|
+
self.tsdb_configurations = {}
|
|
82
81
|
if not mlrun.mlconf.is_ce_mode():
|
|
83
82
|
self._initialize_v3io_configurations(
|
|
84
83
|
model_monitoring_access_key=model_monitoring_access_key
|
|
@@ -137,33 +136,38 @@ class EventStreamProcessor:
|
|
|
137
136
|
self.tsdb_batching_max_events = tsdb_batching_max_events
|
|
138
137
|
self.tsdb_batching_timeout_secs = tsdb_batching_timeout_secs
|
|
139
138
|
|
|
140
|
-
def apply_monitoring_serving_graph(
|
|
139
|
+
def apply_monitoring_serving_graph(
|
|
140
|
+
self,
|
|
141
|
+
fn: mlrun.runtimes.ServingRuntime,
|
|
142
|
+
tsdb_service_provider: typing.Optional[typing.Callable] = None,
|
|
143
|
+
) -> None:
|
|
141
144
|
"""
|
|
142
|
-
Apply monitoring serving graph to a given serving function. The following serving graph includes about
|
|
143
|
-
of different operations that are executed on the events from
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
145
|
+
Apply monitoring serving graph to a given serving function. The following serving graph includes about 4 main
|
|
146
|
+
parts that each one them includes several steps of different operations that are executed on the events from
|
|
147
|
+
the model server.
|
|
148
|
+
Each event has metadata (function_uri, timestamp, class, etc.) but also inputs, predictions and optional
|
|
149
|
+
metrics from the model server.
|
|
150
|
+
In ths first part, the serving graph processes the event and splits it into sub-events. This part also includes
|
|
151
|
+
validation of the event data and adding important details to the event such as endpoint_id.
|
|
152
|
+
In the next parts, the serving graph stores data to 3 different targets:
|
|
153
|
+
1. KV/SQL: Metadata and basic stats about the average latency and the amount of predictions over
|
|
154
|
+
time per endpoint. for example the amount of predictions of endpoint x in the last 5 min. The model
|
|
155
|
+
endpoints table also contains data on the model endpoint from other processes, such as feature_stats that
|
|
156
|
+
represents sample statistics from the training data. If the target is from type KV, then the model endpoints
|
|
157
|
+
table can be found under v3io:///users/pipelines/project-name/model-endpoints/endpoints/. If the target is
|
|
158
|
+
SQL, then the table is stored within the database that was defined in the provided connection string.
|
|
159
|
+
2. TSDB: live data of different key metric dictionaries in tsdb target.
|
|
160
|
+
This data is being used by the monitoring dashboards in grafana. If using V3IO TSDB, results
|
|
156
161
|
can be found under v3io:///users/pipelines/project-name/model-endpoints/events/. In that case, we generate
|
|
157
162
|
3 different key metric dictionaries: base_metrics (average latency and predictions over time),
|
|
158
163
|
endpoint_features (Prediction and feature names and values), and custom_metrics (user-defined metrics).
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
mlrun.mlconf.model_endpoint_monitoring.offline. Otherwise, the default parquet path is under
|
|
164
|
-
mlrun.mlconf.model_endpoint_monitoring.user_space.
|
|
164
|
+
3. Parquet: This Parquet file includes the required data for the model monitoring applications. If defined,
|
|
165
|
+
the parquet target path can be found under mlrun.mlconf.model_endpoint_monitoring.offline. Otherwise,
|
|
166
|
+
the default parquet path is under mlrun.mlconf.model_endpoint_monitoring.user_space. Note that if you are
|
|
167
|
+
using CE, the parquet target path is based on the defined MLRun artifact path.
|
|
165
168
|
|
|
166
169
|
:param fn: A serving function.
|
|
170
|
+
:param tsdb_service_provider: An optional callable function that provides the TSDB connection string.
|
|
167
171
|
"""
|
|
168
172
|
|
|
169
173
|
graph = typing.cast(
|
|
@@ -171,7 +175,7 @@ class EventStreamProcessor:
|
|
|
171
175
|
fn.set_topology(mlrun.serving.states.StepKinds.flow),
|
|
172
176
|
)
|
|
173
177
|
|
|
174
|
-
#
|
|
178
|
+
# Event routing based on the provided path
|
|
175
179
|
def apply_event_routing():
|
|
176
180
|
typing.cast(
|
|
177
181
|
mlrun.serving.TaskStep,
|
|
@@ -184,7 +188,7 @@ class EventStreamProcessor:
|
|
|
184
188
|
|
|
185
189
|
apply_event_routing()
|
|
186
190
|
|
|
187
|
-
#
|
|
191
|
+
# Filter out events with '-' in the path basename from going forward
|
|
188
192
|
# through the next steps of the stream graph
|
|
189
193
|
def apply_storey_filter_stream_events():
|
|
190
194
|
# Filter events with Prometheus endpoints path
|
|
@@ -197,7 +201,7 @@ class EventStreamProcessor:
|
|
|
197
201
|
|
|
198
202
|
apply_storey_filter_stream_events()
|
|
199
203
|
|
|
200
|
-
#
|
|
204
|
+
# Process endpoint event: splitting into sub-events and validate event data
|
|
201
205
|
def apply_process_endpoint_event():
|
|
202
206
|
graph.add_step(
|
|
203
207
|
"ProcessEndpointEvent",
|
|
@@ -208,7 +212,7 @@ class EventStreamProcessor:
|
|
|
208
212
|
|
|
209
213
|
apply_process_endpoint_event()
|
|
210
214
|
|
|
211
|
-
#
|
|
215
|
+
# Applying Storey operations of filtering and flatten
|
|
212
216
|
def apply_storey_filter_and_flatmap():
|
|
213
217
|
# Remove none values from each event
|
|
214
218
|
graph.add_step(
|
|
@@ -225,7 +229,7 @@ class EventStreamProcessor:
|
|
|
225
229
|
|
|
226
230
|
apply_storey_filter_and_flatmap()
|
|
227
231
|
|
|
228
|
-
#
|
|
232
|
+
# Validating feature names and map each feature to its value
|
|
229
233
|
def apply_map_feature_names():
|
|
230
234
|
graph.add_step(
|
|
231
235
|
"MapFeatureNames",
|
|
@@ -237,9 +241,9 @@ class EventStreamProcessor:
|
|
|
237
241
|
|
|
238
242
|
apply_map_feature_names()
|
|
239
243
|
|
|
240
|
-
#
|
|
244
|
+
# Calculate number of predictions and average latency
|
|
241
245
|
def apply_storey_aggregations():
|
|
242
|
-
#
|
|
246
|
+
# Calculate number of predictions for each window (5 min and 1 hour by default)
|
|
243
247
|
graph.add_step(
|
|
244
248
|
class_name="storey.AggregateByKey",
|
|
245
249
|
aggregates=[
|
|
@@ -257,7 +261,7 @@ class EventStreamProcessor:
|
|
|
257
261
|
table=".",
|
|
258
262
|
key_field=EventFieldType.ENDPOINT_ID,
|
|
259
263
|
)
|
|
260
|
-
#
|
|
264
|
+
# Calculate average latency time for each window (5 min and 1 hour by default)
|
|
261
265
|
graph.add_step(
|
|
262
266
|
class_name="storey.Rename",
|
|
263
267
|
mapping={
|
|
@@ -270,8 +274,8 @@ class EventStreamProcessor:
|
|
|
270
274
|
|
|
271
275
|
apply_storey_aggregations()
|
|
272
276
|
|
|
273
|
-
#
|
|
274
|
-
#
|
|
277
|
+
# KV/SQL branch
|
|
278
|
+
# Filter relevant keys from the event before writing the data into the database table
|
|
275
279
|
def apply_process_before_endpoint_update():
|
|
276
280
|
graph.add_step(
|
|
277
281
|
"ProcessBeforeEndpointUpdate",
|
|
@@ -281,7 +285,7 @@ class EventStreamProcessor:
|
|
|
281
285
|
|
|
282
286
|
apply_process_before_endpoint_update()
|
|
283
287
|
|
|
284
|
-
#
|
|
288
|
+
# Write the filtered event to KV/SQL table. At this point, the serving graph updates the stats
|
|
285
289
|
# about average latency and the amount of predictions over time
|
|
286
290
|
def apply_update_endpoint():
|
|
287
291
|
graph.add_step(
|
|
@@ -294,7 +298,7 @@ class EventStreamProcessor:
|
|
|
294
298
|
|
|
295
299
|
apply_update_endpoint()
|
|
296
300
|
|
|
297
|
-
#
|
|
301
|
+
# (only for V3IO KV target) - Apply infer_schema on the model endpoints table for generating schema file
|
|
298
302
|
# which will be used by Grafana monitoring dashboards
|
|
299
303
|
def apply_infer_schema():
|
|
300
304
|
graph.add_step(
|
|
@@ -309,7 +313,7 @@ class EventStreamProcessor:
|
|
|
309
313
|
if self.model_endpoint_store_target == ModelEndpointTarget.V3IO_NOSQL:
|
|
310
314
|
apply_infer_schema()
|
|
311
315
|
|
|
312
|
-
#
|
|
316
|
+
# Emits the event in window size of events based on sample_window size (10 by default)
|
|
313
317
|
def apply_storey_sample_window():
|
|
314
318
|
graph.add_step(
|
|
315
319
|
"storey.steps.SampleWindow",
|
|
@@ -321,84 +325,16 @@ class EventStreamProcessor:
|
|
|
321
325
|
|
|
322
326
|
apply_storey_sample_window()
|
|
323
327
|
|
|
324
|
-
#
|
|
325
|
-
# Steps 20-21 - Prometheus branch
|
|
328
|
+
# TSDB branch (skip to Prometheus if in CE env)
|
|
326
329
|
if not mlrun.mlconf.is_ce_mode():
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
# Step 12 - Before writing data to TSDB, create dictionary of 2-3 dictionaries that contains
|
|
330
|
-
# stats and details about the events
|
|
331
|
-
def apply_process_before_tsdb():
|
|
332
|
-
graph.add_step(
|
|
333
|
-
"ProcessBeforeTSDB", name="ProcessBeforeTSDB", after="sample"
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
apply_process_before_tsdb()
|
|
337
|
-
|
|
338
|
-
# Steps 13-19: - Unpacked keys from each dictionary and write to TSDB target
|
|
339
|
-
def apply_filter_and_unpacked_keys(name, keys):
|
|
340
|
-
graph.add_step(
|
|
341
|
-
"FilterAndUnpackKeys",
|
|
342
|
-
name=name,
|
|
343
|
-
after="ProcessBeforeTSDB",
|
|
344
|
-
keys=[keys],
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
def apply_tsdb_target(name, after):
|
|
348
|
-
graph.add_step(
|
|
349
|
-
"storey.TSDBTarget",
|
|
350
|
-
name=name,
|
|
351
|
-
after=after,
|
|
352
|
-
path=self.tsdb_path,
|
|
353
|
-
rate="10/m",
|
|
354
|
-
time_col=EventFieldType.TIMESTAMP,
|
|
355
|
-
container=self.tsdb_container,
|
|
356
|
-
v3io_frames=self.v3io_framesd,
|
|
357
|
-
infer_columns_from_data=True,
|
|
358
|
-
index_cols=[
|
|
359
|
-
EventFieldType.ENDPOINT_ID,
|
|
360
|
-
EventFieldType.RECORD_TYPE,
|
|
361
|
-
EventFieldType.ENDPOINT_TYPE,
|
|
362
|
-
],
|
|
363
|
-
max_events=self.tsdb_batching_max_events,
|
|
364
|
-
flush_after_seconds=self.tsdb_batching_timeout_secs,
|
|
365
|
-
key=EventFieldType.ENDPOINT_ID,
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
# Steps 13-14 - unpacked base_metrics dictionary
|
|
369
|
-
apply_filter_and_unpacked_keys(
|
|
370
|
-
name="FilterAndUnpackKeys1",
|
|
371
|
-
keys=EventKeyMetrics.BASE_METRICS,
|
|
372
|
-
)
|
|
373
|
-
apply_tsdb_target(name="tsdb1", after="FilterAndUnpackKeys1")
|
|
374
|
-
|
|
375
|
-
# Steps 15-16 - unpacked endpoint_features dictionary
|
|
376
|
-
apply_filter_and_unpacked_keys(
|
|
377
|
-
name="FilterAndUnpackKeys2",
|
|
378
|
-
keys=EventKeyMetrics.ENDPOINT_FEATURES,
|
|
330
|
+
tsdb_connector = mlrun.model_monitoring.get_tsdb_connector(
|
|
331
|
+
project=self.project, secret_provider=tsdb_service_provider
|
|
379
332
|
)
|
|
380
|
-
|
|
333
|
+
tsdb_connector.apply_monitoring_stream_steps(graph=graph)
|
|
381
334
|
|
|
382
|
-
# Steps 17-19 - unpacked custom_metrics dictionary. In addition, use storey.Filter remove none values
|
|
383
|
-
apply_filter_and_unpacked_keys(
|
|
384
|
-
name="FilterAndUnpackKeys3",
|
|
385
|
-
keys=EventKeyMetrics.CUSTOM_METRICS,
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
def apply_storey_filter():
|
|
389
|
-
graph.add_step(
|
|
390
|
-
"storey.Filter",
|
|
391
|
-
"FilterNotNone",
|
|
392
|
-
after="FilterAndUnpackKeys3",
|
|
393
|
-
_fn="(event is not None)",
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
apply_storey_filter()
|
|
397
|
-
apply_tsdb_target(name="tsdb3", after="FilterNotNone")
|
|
398
335
|
else:
|
|
399
|
-
# Prometheus
|
|
400
|
-
|
|
401
|
-
# Step 20 - Increase the prediction counter by 1 and update the latency value
|
|
336
|
+
# Prometheus
|
|
337
|
+
# Increase the prediction counter by 1 and update the latency value
|
|
402
338
|
graph.add_step(
|
|
403
339
|
"IncCounter",
|
|
404
340
|
name="IncCounter",
|
|
@@ -406,7 +342,7 @@ class EventStreamProcessor:
|
|
|
406
342
|
project=self.project,
|
|
407
343
|
)
|
|
408
344
|
|
|
409
|
-
#
|
|
345
|
+
# Record a sample of features and labels
|
|
410
346
|
def apply_record_features_to_prometheus():
|
|
411
347
|
graph.add_step(
|
|
412
348
|
"RecordFeatures",
|
|
@@ -417,8 +353,8 @@ class EventStreamProcessor:
|
|
|
417
353
|
|
|
418
354
|
apply_record_features_to_prometheus()
|
|
419
355
|
|
|
420
|
-
#
|
|
421
|
-
#
|
|
356
|
+
# Parquet branch
|
|
357
|
+
# Filter and validate different keys before writing the data to Parquet target
|
|
422
358
|
def apply_process_before_parquet():
|
|
423
359
|
graph.add_step(
|
|
424
360
|
"ProcessBeforeParquet",
|
|
@@ -429,7 +365,7 @@ class EventStreamProcessor:
|
|
|
429
365
|
|
|
430
366
|
apply_process_before_parquet()
|
|
431
367
|
|
|
432
|
-
#
|
|
368
|
+
# Write the Parquet target file, partitioned by key (endpoint_id) and time.
|
|
433
369
|
def apply_parquet_target():
|
|
434
370
|
graph.add_step(
|
|
435
371
|
"storey.ParquetTarget",
|
|
@@ -503,76 +439,6 @@ class ProcessBeforeEndpointUpdate(mlrun.feature_store.steps.MapClass):
|
|
|
503
439
|
return e
|
|
504
440
|
|
|
505
441
|
|
|
506
|
-
class ProcessBeforeTSDB(mlrun.feature_store.steps.MapClass):
|
|
507
|
-
def __init__(self, **kwargs):
|
|
508
|
-
"""
|
|
509
|
-
Process the data before writing to TSDB. This step creates a dictionary that includes 3 different dictionaries
|
|
510
|
-
that each one of them contains important details and stats about the events:
|
|
511
|
-
1. base_metrics: stats about the average latency and the amount of predictions over time. It is based on
|
|
512
|
-
storey.AggregateByKey which was executed in step 5.
|
|
513
|
-
2. endpoint_features: feature names and values along with the prediction names and value.
|
|
514
|
-
3. custom_metric (opt): optional metrics provided by the user.
|
|
515
|
-
|
|
516
|
-
:returns: Dictionary of 2-3 dictionaries that contains stats and details about the events.
|
|
517
|
-
|
|
518
|
-
"""
|
|
519
|
-
super().__init__(**kwargs)
|
|
520
|
-
|
|
521
|
-
def do(self, event):
|
|
522
|
-
# Compute prediction per second
|
|
523
|
-
event[EventLiveStats.PREDICTIONS_PER_SECOND] = (
|
|
524
|
-
float(event[EventLiveStats.PREDICTIONS_COUNT_5M]) / 300
|
|
525
|
-
)
|
|
526
|
-
base_fields = [
|
|
527
|
-
EventFieldType.TIMESTAMP,
|
|
528
|
-
EventFieldType.ENDPOINT_ID,
|
|
529
|
-
EventFieldType.ENDPOINT_TYPE,
|
|
530
|
-
]
|
|
531
|
-
|
|
532
|
-
# Getting event timestamp and endpoint_id
|
|
533
|
-
base_event = {k: event[k] for k in base_fields}
|
|
534
|
-
|
|
535
|
-
# base_metrics includes the stats about the average latency and the amount of predictions over time
|
|
536
|
-
base_metrics = {
|
|
537
|
-
EventFieldType.RECORD_TYPE: EventKeyMetrics.BASE_METRICS,
|
|
538
|
-
EventLiveStats.PREDICTIONS_PER_SECOND: event[
|
|
539
|
-
EventLiveStats.PREDICTIONS_PER_SECOND
|
|
540
|
-
],
|
|
541
|
-
EventLiveStats.PREDICTIONS_COUNT_5M: event[
|
|
542
|
-
EventLiveStats.PREDICTIONS_COUNT_5M
|
|
543
|
-
],
|
|
544
|
-
EventLiveStats.PREDICTIONS_COUNT_1H: event[
|
|
545
|
-
EventLiveStats.PREDICTIONS_COUNT_1H
|
|
546
|
-
],
|
|
547
|
-
EventLiveStats.LATENCY_AVG_5M: event[EventLiveStats.LATENCY_AVG_5M],
|
|
548
|
-
EventLiveStats.LATENCY_AVG_1H: event[EventLiveStats.LATENCY_AVG_1H],
|
|
549
|
-
**base_event,
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
# endpoint_features includes the event values of each feature and prediction
|
|
553
|
-
endpoint_features = {
|
|
554
|
-
EventFieldType.RECORD_TYPE: EventKeyMetrics.ENDPOINT_FEATURES,
|
|
555
|
-
**event[EventFieldType.NAMED_PREDICTIONS],
|
|
556
|
-
**event[EventFieldType.NAMED_FEATURES],
|
|
557
|
-
**base_event,
|
|
558
|
-
}
|
|
559
|
-
# Create a dictionary that includes both base_metrics and endpoint_features
|
|
560
|
-
processed = {
|
|
561
|
-
EventKeyMetrics.BASE_METRICS: base_metrics,
|
|
562
|
-
EventKeyMetrics.ENDPOINT_FEATURES: endpoint_features,
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
# If metrics provided, add another dictionary if custom_metrics values
|
|
566
|
-
if event[EventFieldType.METRICS]:
|
|
567
|
-
processed[EventKeyMetrics.CUSTOM_METRICS] = {
|
|
568
|
-
EventFieldType.RECORD_TYPE: EventKeyMetrics.CUSTOM_METRICS,
|
|
569
|
-
**event[EventFieldType.METRICS],
|
|
570
|
-
**base_event,
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
return processed
|
|
574
|
-
|
|
575
|
-
|
|
576
442
|
class ProcessBeforeParquet(mlrun.feature_store.steps.MapClass):
|
|
577
443
|
def __init__(self, **kwargs):
|
|
578
444
|
"""
|
|
@@ -807,7 +673,7 @@ class ProcessEndpointEvent(mlrun.feature_store.steps.MapClass):
|
|
|
807
673
|
# left them
|
|
808
674
|
if endpoint_id not in self.endpoints:
|
|
809
675
|
logger.info("Trying to resume state", endpoint_id=endpoint_id)
|
|
810
|
-
endpoint_record = get_endpoint_record(
|
|
676
|
+
endpoint_record = mlrun.model_monitoring.helpers.get_endpoint_record(
|
|
811
677
|
project=self.project,
|
|
812
678
|
endpoint_id=endpoint_id,
|
|
813
679
|
)
|
|
@@ -853,36 +719,6 @@ def is_not_none(field: typing.Any, dict_path: list[str]):
|
|
|
853
719
|
return False
|
|
854
720
|
|
|
855
721
|
|
|
856
|
-
class FilterAndUnpackKeys(mlrun.feature_store.steps.MapClass):
|
|
857
|
-
def __init__(self, keys, **kwargs):
|
|
858
|
-
"""
|
|
859
|
-
Create unpacked event dictionary based on provided key metrics (base_metrics, endpoint_features,
|
|
860
|
-
or custom_metric). Please note that the next step of the TSDB target requires an unpacked dictionary.
|
|
861
|
-
|
|
862
|
-
:param keys: list of key metrics.
|
|
863
|
-
|
|
864
|
-
:returns: An unpacked dictionary of event filtered by the provided key metrics.
|
|
865
|
-
"""
|
|
866
|
-
super().__init__(**kwargs)
|
|
867
|
-
self.keys = keys
|
|
868
|
-
|
|
869
|
-
def do(self, event):
|
|
870
|
-
# Keep only the relevant dictionary based on the provided keys
|
|
871
|
-
new_event = {}
|
|
872
|
-
for key in self.keys:
|
|
873
|
-
if key in event:
|
|
874
|
-
new_event[key] = event[key]
|
|
875
|
-
|
|
876
|
-
# Create unpacked dictionary
|
|
877
|
-
unpacked = {}
|
|
878
|
-
for key in new_event.keys():
|
|
879
|
-
if key in self.keys:
|
|
880
|
-
unpacked = {**unpacked, **new_event[key]}
|
|
881
|
-
else:
|
|
882
|
-
unpacked[key] = new_event[key]
|
|
883
|
-
return unpacked if unpacked else None
|
|
884
|
-
|
|
885
|
-
|
|
886
722
|
class MapFeatureNames(mlrun.feature_store.steps.MapClass):
|
|
887
723
|
def __init__(
|
|
888
724
|
self,
|
|
@@ -940,7 +776,7 @@ class MapFeatureNames(mlrun.feature_store.steps.MapClass):
|
|
|
940
776
|
label_values = event[EventFieldType.PREDICTION]
|
|
941
777
|
# Get feature names and label columns
|
|
942
778
|
if endpoint_id not in self.feature_names:
|
|
943
|
-
endpoint_record = get_endpoint_record(
|
|
779
|
+
endpoint_record = mlrun.model_monitoring.helpers.get_endpoint_record(
|
|
944
780
|
project=self.project,
|
|
945
781
|
endpoint_id=endpoint_id,
|
|
946
782
|
)
|
|
@@ -1081,6 +917,9 @@ class UpdateEndpoint(mlrun.feature_store.steps.MapClass):
|
|
|
1081
917
|
self.model_endpoint_store_target = model_endpoint_store_target
|
|
1082
918
|
|
|
1083
919
|
def do(self, event: dict):
|
|
920
|
+
# Remove labels from the event
|
|
921
|
+
event.pop(EventFieldType.LABELS)
|
|
922
|
+
|
|
1084
923
|
update_endpoint_record(
|
|
1085
924
|
project=self.project,
|
|
1086
925
|
endpoint_id=event.pop(EventFieldType.ENDPOINT_ID),
|
|
@@ -1118,6 +957,8 @@ class InferSchema(mlrun.feature_store.steps.MapClass):
|
|
|
1118
957
|
def do(self, event: dict):
|
|
1119
958
|
key_set = set(event.keys())
|
|
1120
959
|
if not key_set.issubset(self.keys):
|
|
960
|
+
import mlrun.utils.v3io_clients
|
|
961
|
+
|
|
1121
962
|
self.keys.update(key_set)
|
|
1122
963
|
# Apply infer_schema on the kv table for generating the schema file
|
|
1123
964
|
mlrun.utils.v3io_clients.get_frames_client(
|