mlrun 1.10.0rc6__py3-none-any.whl → 1.10.0rc8__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 +3 -1
- mlrun/__main__.py +47 -4
- mlrun/artifacts/base.py +0 -27
- mlrun/artifacts/dataset.py +0 -8
- mlrun/artifacts/model.py +0 -7
- mlrun/artifacts/plots.py +0 -13
- mlrun/common/schemas/background_task.py +5 -0
- mlrun/common/schemas/model_monitoring/__init__.py +2 -0
- mlrun/common/schemas/model_monitoring/constants.py +16 -0
- mlrun/common/schemas/project.py +4 -0
- mlrun/common/schemas/serving.py +2 -0
- mlrun/config.py +11 -22
- mlrun/datastore/utils.py +3 -1
- mlrun/db/base.py +0 -19
- mlrun/db/httpdb.py +73 -65
- mlrun/db/nopdb.py +0 -12
- mlrun/frameworks/tf_keras/__init__.py +4 -4
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +23 -20
- mlrun/frameworks/tf_keras/model_handler.py +69 -9
- mlrun/frameworks/tf_keras/utils.py +12 -1
- mlrun/launcher/base.py +7 -0
- mlrun/launcher/client.py +2 -21
- mlrun/launcher/local.py +4 -0
- mlrun/model_monitoring/applications/_application_steps.py +23 -39
- mlrun/model_monitoring/applications/base.py +167 -32
- mlrun/model_monitoring/helpers.py +0 -3
- mlrun/projects/operations.py +11 -24
- mlrun/projects/pipelines.py +33 -3
- mlrun/projects/project.py +45 -89
- mlrun/run.py +37 -5
- mlrun/runtimes/daskjob.py +2 -0
- mlrun/runtimes/kubejob.py +5 -8
- mlrun/runtimes/mpijob/abstract.py +2 -0
- mlrun/runtimes/mpijob/v1.py +2 -0
- mlrun/runtimes/nuclio/function.py +2 -0
- mlrun/runtimes/nuclio/serving.py +60 -5
- mlrun/runtimes/pod.py +3 -0
- mlrun/runtimes/remotesparkjob.py +2 -0
- mlrun/runtimes/sparkjob/spark3job.py +2 -0
- mlrun/serving/__init__.py +2 -0
- mlrun/serving/server.py +253 -29
- mlrun/serving/states.py +215 -18
- mlrun/serving/system_steps.py +391 -0
- mlrun/serving/v2_serving.py +9 -8
- mlrun/utils/helpers.py +18 -4
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/METADATA +9 -9
- {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/RECORD +52 -51
- {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Copyright 2023 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import random
|
|
16
|
+
from copy import copy, deepcopy
|
|
17
|
+
from datetime import timedelta
|
|
18
|
+
from typing import Any, Optional, Union
|
|
19
|
+
|
|
20
|
+
import storey
|
|
21
|
+
|
|
22
|
+
import mlrun
|
|
23
|
+
import mlrun.artifacts
|
|
24
|
+
import mlrun.common.schemas.model_monitoring as mm_schemas
|
|
25
|
+
import mlrun.serving
|
|
26
|
+
from mlrun.common.schemas import MonitoringData
|
|
27
|
+
from mlrun.utils import logger
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MonitoringPreProcessor(storey.MapClass):
|
|
31
|
+
"""preprocess step, reconstructs the serving output event body to StreamProcessingEvent schema"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
context,
|
|
36
|
+
**kwargs,
|
|
37
|
+
):
|
|
38
|
+
super().__init__(**kwargs)
|
|
39
|
+
self.context = copy(context)
|
|
40
|
+
|
|
41
|
+
def reconstruct_request_resp_fields(
|
|
42
|
+
self, event, model: str, model_monitoring_data: dict
|
|
43
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
44
|
+
result_path = model_monitoring_data.get(MonitoringData.RESULT_PATH)
|
|
45
|
+
input_path = model_monitoring_data.get(MonitoringData.INPUT_PATH)
|
|
46
|
+
|
|
47
|
+
result = self._get_data_from_path(
|
|
48
|
+
result_path, event.body.get(model, event.body)
|
|
49
|
+
)
|
|
50
|
+
output_schema = model_monitoring_data.get(MonitoringData.OUTPUTS)
|
|
51
|
+
input_schema = model_monitoring_data.get(MonitoringData.INPUTS)
|
|
52
|
+
logger.debug("output schema retrieved", output_schema=output_schema)
|
|
53
|
+
if isinstance(result, dict):
|
|
54
|
+
if len(result) > 1:
|
|
55
|
+
# transpose by key the outputs:
|
|
56
|
+
outputs = self.transpose_by_key(result, output_schema)
|
|
57
|
+
elif len(result) == 1:
|
|
58
|
+
outputs = (
|
|
59
|
+
result[output_schema[0]]
|
|
60
|
+
if output_schema
|
|
61
|
+
else list(result.values())[0]
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
outputs = []
|
|
65
|
+
if not output_schema:
|
|
66
|
+
logger.warn(
|
|
67
|
+
"Output schema was not provided using Project:log_model or by ModelRunnerStep:add_model order "
|
|
68
|
+
"may not preserved"
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
outputs = result
|
|
72
|
+
|
|
73
|
+
event_inputs = event._metadata.get("inputs", {})
|
|
74
|
+
event_inputs = self._get_data_from_path(input_path, event_inputs)
|
|
75
|
+
if isinstance(event_inputs, dict):
|
|
76
|
+
if len(event_inputs) > 1:
|
|
77
|
+
# transpose by key the inputs:
|
|
78
|
+
inputs = self.transpose_by_key(event_inputs, input_schema)
|
|
79
|
+
else:
|
|
80
|
+
inputs = (
|
|
81
|
+
event_inputs[input_schema[0]]
|
|
82
|
+
if input_schema
|
|
83
|
+
else list(result.values())[0]
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
inputs = event_inputs
|
|
87
|
+
|
|
88
|
+
if outputs and isinstance(outputs[0], list):
|
|
89
|
+
if output_schema and len(output_schema) != len(outputs[0]):
|
|
90
|
+
logger.info(
|
|
91
|
+
"The number of outputs returned by the model does not match the number of outputs "
|
|
92
|
+
"specified in the model endpoint.",
|
|
93
|
+
model_endpoint=model,
|
|
94
|
+
output_len=len(outputs[0]),
|
|
95
|
+
schema_len=len(output_schema),
|
|
96
|
+
)
|
|
97
|
+
elif outputs:
|
|
98
|
+
if output_schema and len(output_schema) != 1:
|
|
99
|
+
logger.info(
|
|
100
|
+
"The number of outputs returned by the model does not match the number of outputs "
|
|
101
|
+
"specified in the model endpoint.",
|
|
102
|
+
model_endpoint=model,
|
|
103
|
+
output_len=len(outputs),
|
|
104
|
+
schema_len=len(output_schema),
|
|
105
|
+
)
|
|
106
|
+
request = {"inputs": inputs, "id": getattr(event, "id", None)}
|
|
107
|
+
resp = {"outputs": outputs}
|
|
108
|
+
|
|
109
|
+
return request, resp
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def transpose_by_key(
|
|
113
|
+
data_to_transpose, schema: Optional[list[str]] = None
|
|
114
|
+
) -> list[list[float]]:
|
|
115
|
+
values = (
|
|
116
|
+
list(data_to_transpose.values())
|
|
117
|
+
if not schema
|
|
118
|
+
else [data_to_transpose[key] for key in schema]
|
|
119
|
+
)
|
|
120
|
+
if values and not isinstance(values[0], list):
|
|
121
|
+
values = [values]
|
|
122
|
+
transposed = (
|
|
123
|
+
list(map(list, zip(*values)))
|
|
124
|
+
if all(isinstance(v, list) for v in values) and len(values) > 1
|
|
125
|
+
else values
|
|
126
|
+
)
|
|
127
|
+
return transposed
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def _get_data_from_path(
|
|
131
|
+
path: Union[str, list[str], None], data: dict
|
|
132
|
+
) -> dict[str, Any]:
|
|
133
|
+
if isinstance(path, str):
|
|
134
|
+
output_data = data.get(path)
|
|
135
|
+
elif isinstance(path, list):
|
|
136
|
+
output_data = deepcopy(data)
|
|
137
|
+
for key in path:
|
|
138
|
+
output_data = output_data.get(key, {})
|
|
139
|
+
elif path is None:
|
|
140
|
+
output_data = data
|
|
141
|
+
else:
|
|
142
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
143
|
+
"Expected path be of type str or list of str or None"
|
|
144
|
+
)
|
|
145
|
+
if isinstance(output_data, (int, float)):
|
|
146
|
+
output_data = [output_data]
|
|
147
|
+
return output_data
|
|
148
|
+
|
|
149
|
+
def do(self, event):
|
|
150
|
+
monitoring_event_list = []
|
|
151
|
+
server: mlrun.serving.GraphServer = getattr(self.context, "server", None)
|
|
152
|
+
model_runner_name = event._metadata.get("model_runner_name", "")
|
|
153
|
+
step = server.graph.steps[model_runner_name] if server else {}
|
|
154
|
+
monitoring_data = step.monitoring_data
|
|
155
|
+
logger.debug(
|
|
156
|
+
"monitoring preprocessor started",
|
|
157
|
+
event=event,
|
|
158
|
+
model_endpoints=monitoring_data,
|
|
159
|
+
metadata=event._metadata,
|
|
160
|
+
)
|
|
161
|
+
if len(monitoring_data) > 1:
|
|
162
|
+
for model in event.body.keys():
|
|
163
|
+
if model in monitoring_data:
|
|
164
|
+
request, resp = self.reconstruct_request_resp_fields(
|
|
165
|
+
event, model, monitoring_data[model]
|
|
166
|
+
)
|
|
167
|
+
monitoring_event_list.append(
|
|
168
|
+
{
|
|
169
|
+
mm_schemas.StreamProcessingEvent.MODEL: model,
|
|
170
|
+
mm_schemas.StreamProcessingEvent.MODEL_CLASS: monitoring_data[
|
|
171
|
+
model
|
|
172
|
+
].get(mm_schemas.StreamProcessingEvent.MODEL_CLASS),
|
|
173
|
+
mm_schemas.StreamProcessingEvent.MICROSEC: event._metadata.get(
|
|
174
|
+
model, {}
|
|
175
|
+
).get(mm_schemas.StreamProcessingEvent.MICROSEC),
|
|
176
|
+
mm_schemas.StreamProcessingEvent.WHEN: event._metadata.get(
|
|
177
|
+
model, {}
|
|
178
|
+
).get(mm_schemas.StreamProcessingEvent.WHEN),
|
|
179
|
+
mm_schemas.StreamProcessingEvent.ENDPOINT_ID: monitoring_data[
|
|
180
|
+
model
|
|
181
|
+
].get(
|
|
182
|
+
mlrun.common.schemas.MonitoringData.MODEL_ENDPOINT_UID
|
|
183
|
+
),
|
|
184
|
+
mm_schemas.StreamProcessingEvent.LABELS: monitoring_data[
|
|
185
|
+
model
|
|
186
|
+
].get(mlrun.common.schemas.MonitoringData.OUTPUTS),
|
|
187
|
+
mm_schemas.StreamProcessingEvent.FUNCTION_URI: server.function_uri
|
|
188
|
+
if server
|
|
189
|
+
else None,
|
|
190
|
+
mm_schemas.StreamProcessingEvent.REQUEST: request,
|
|
191
|
+
mm_schemas.StreamProcessingEvent.RESPONSE: resp,
|
|
192
|
+
mm_schemas.StreamProcessingEvent.ERROR: event.body[model][
|
|
193
|
+
mm_schemas.StreamProcessingEvent.ERROR
|
|
194
|
+
]
|
|
195
|
+
if mm_schemas.StreamProcessingEvent.ERROR
|
|
196
|
+
in event.body[model]
|
|
197
|
+
else None,
|
|
198
|
+
mm_schemas.StreamProcessingEvent.METRICS: event.body[model][
|
|
199
|
+
mm_schemas.StreamProcessingEvent.METRICS
|
|
200
|
+
]
|
|
201
|
+
if mm_schemas.StreamProcessingEvent.METRICS
|
|
202
|
+
in event.body[model]
|
|
203
|
+
else None,
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
elif monitoring_data:
|
|
207
|
+
model = list(monitoring_data.keys())[0]
|
|
208
|
+
request, resp = self.reconstruct_request_resp_fields(
|
|
209
|
+
event, model, monitoring_data[model]
|
|
210
|
+
)
|
|
211
|
+
monitoring_event_list.append(
|
|
212
|
+
{
|
|
213
|
+
mm_schemas.StreamProcessingEvent.MODEL: model,
|
|
214
|
+
mm_schemas.StreamProcessingEvent.MODEL_CLASS: monitoring_data[
|
|
215
|
+
model
|
|
216
|
+
].get(mm_schemas.StreamProcessingEvent.MODEL_CLASS),
|
|
217
|
+
mm_schemas.StreamProcessingEvent.MICROSEC: event._metadata.get(
|
|
218
|
+
mm_schemas.StreamProcessingEvent.MICROSEC
|
|
219
|
+
),
|
|
220
|
+
mm_schemas.StreamProcessingEvent.WHEN: event._metadata.get(
|
|
221
|
+
mm_schemas.StreamProcessingEvent.WHEN
|
|
222
|
+
),
|
|
223
|
+
mm_schemas.StreamProcessingEvent.ENDPOINT_ID: monitoring_data[
|
|
224
|
+
model
|
|
225
|
+
].get(mlrun.common.schemas.MonitoringData.MODEL_ENDPOINT_UID),
|
|
226
|
+
mm_schemas.StreamProcessingEvent.LABELS: monitoring_data[model].get(
|
|
227
|
+
mlrun.common.schemas.MonitoringData.OUTPUTS
|
|
228
|
+
),
|
|
229
|
+
mm_schemas.StreamProcessingEvent.FUNCTION_URI: server.function_uri
|
|
230
|
+
if server
|
|
231
|
+
else None,
|
|
232
|
+
mm_schemas.StreamProcessingEvent.REQUEST: request,
|
|
233
|
+
mm_schemas.StreamProcessingEvent.RESPONSE: resp,
|
|
234
|
+
mm_schemas.StreamProcessingEvent.ERROR: event.body[
|
|
235
|
+
mm_schemas.StreamProcessingEvent.ERROR
|
|
236
|
+
]
|
|
237
|
+
if mm_schemas.StreamProcessingEvent.ERROR in event.body
|
|
238
|
+
else None,
|
|
239
|
+
mm_schemas.StreamProcessingEvent.METRICS: event.body[
|
|
240
|
+
mm_schemas.StreamProcessingEvent.METRICS
|
|
241
|
+
]
|
|
242
|
+
if mm_schemas.StreamProcessingEvent.METRICS in event.body
|
|
243
|
+
else None,
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
event.body = monitoring_event_list
|
|
247
|
+
return event
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class BackgroundTaskStatus(storey.MapClass):
|
|
251
|
+
"""
|
|
252
|
+
background task status checker, prevent events from pushing to the model monitoring stream target if model endpoints
|
|
253
|
+
creation failed or in progress
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
def __init__(self, context, **kwargs):
|
|
257
|
+
self.context = copy(context)
|
|
258
|
+
self.server: mlrun.serving.GraphServer = getattr(self.context, "server", None)
|
|
259
|
+
self._background_task_check_timestamp = None
|
|
260
|
+
self._background_task_state = mlrun.common.schemas.BackgroundTaskState.running
|
|
261
|
+
super().__init__(**kwargs)
|
|
262
|
+
|
|
263
|
+
def do(self, event):
|
|
264
|
+
if (self.context and self.context.is_mock) or self.context is None:
|
|
265
|
+
return event
|
|
266
|
+
if self.server is None:
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
if (
|
|
270
|
+
self._background_task_state
|
|
271
|
+
== mlrun.common.schemas.BackgroundTaskState.running
|
|
272
|
+
and (
|
|
273
|
+
self._background_task_check_timestamp is None
|
|
274
|
+
or mlrun.utils.now_date() - self._background_task_check_timestamp
|
|
275
|
+
>= timedelta(
|
|
276
|
+
seconds=mlrun.mlconf.model_endpoint_monitoring.model_endpoint_creation_check_period
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
):
|
|
280
|
+
background_task = mlrun.get_run_db().get_project_background_task(
|
|
281
|
+
self.server.project, self.server.model_endpoint_creation_task_name
|
|
282
|
+
)
|
|
283
|
+
self._background_task_check_timestamp = mlrun.utils.now_date()
|
|
284
|
+
self._log_background_task_state(background_task.status.state)
|
|
285
|
+
self._background_task_state = background_task.status.state
|
|
286
|
+
if (
|
|
287
|
+
background_task.status.state
|
|
288
|
+
== mlrun.common.schemas.BackgroundTaskState.succeeded
|
|
289
|
+
):
|
|
290
|
+
return event
|
|
291
|
+
else:
|
|
292
|
+
return None
|
|
293
|
+
elif (
|
|
294
|
+
self._background_task_state
|
|
295
|
+
== mlrun.common.schemas.BackgroundTaskState.failed
|
|
296
|
+
):
|
|
297
|
+
return None
|
|
298
|
+
return event
|
|
299
|
+
|
|
300
|
+
def _log_background_task_state(
|
|
301
|
+
self, background_task_state: mlrun.common.schemas.BackgroundTaskState
|
|
302
|
+
):
|
|
303
|
+
logger.info(
|
|
304
|
+
"Checking model endpoint creation task status",
|
|
305
|
+
task_name=self.server.model_endpoint_creation_task_name,
|
|
306
|
+
)
|
|
307
|
+
if (
|
|
308
|
+
background_task_state
|
|
309
|
+
in mlrun.common.schemas.BackgroundTaskState.terminal_states()
|
|
310
|
+
):
|
|
311
|
+
logger.info(
|
|
312
|
+
f"Model endpoint creation task completed with state {background_task_state}"
|
|
313
|
+
)
|
|
314
|
+
else: # in progress
|
|
315
|
+
logger.info(
|
|
316
|
+
f"Model endpoint creation task is still in progress with the current state: "
|
|
317
|
+
f"{background_task_state}. Events will not be monitored for the next 15 seconds",
|
|
318
|
+
name=self.name,
|
|
319
|
+
background_task_check_timestamp=self._background_task_check_timestamp.isoformat(),
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class SamplingStep(storey.MapClass):
|
|
324
|
+
"""sampling step, samples the serving outputs for the model monitoring as sampling_percentage defines"""
|
|
325
|
+
|
|
326
|
+
def __init__(
|
|
327
|
+
self,
|
|
328
|
+
sampling_percentage: Optional[float] = 100.0,
|
|
329
|
+
**kwargs,
|
|
330
|
+
):
|
|
331
|
+
super().__init__(**kwargs)
|
|
332
|
+
self.sampling_percentage = (
|
|
333
|
+
sampling_percentage if 0 < sampling_percentage <= 100 else 100
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def do(self, event):
|
|
337
|
+
logger.debug(
|
|
338
|
+
"sampling step runs",
|
|
339
|
+
event=event,
|
|
340
|
+
sampling_percentage=self.sampling_percentage,
|
|
341
|
+
)
|
|
342
|
+
if self.sampling_percentage != 100:
|
|
343
|
+
request = event[mm_schemas.StreamProcessingEvent.REQUEST]
|
|
344
|
+
num_of_inputs = len(request["inputs"])
|
|
345
|
+
sampled_requests_indices = self._pick_random_requests(
|
|
346
|
+
num_of_inputs, self.sampling_percentage
|
|
347
|
+
)
|
|
348
|
+
if not sampled_requests_indices:
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
event[mm_schemas.StreamProcessingEvent.REQUEST]["inputs"] = [
|
|
352
|
+
request["inputs"][i] for i in sampled_requests_indices
|
|
353
|
+
]
|
|
354
|
+
|
|
355
|
+
if isinstance(
|
|
356
|
+
event[mm_schemas.StreamProcessingEvent.RESPONSE]["outputs"], list
|
|
357
|
+
):
|
|
358
|
+
event[mm_schemas.StreamProcessingEvent.RESPONSE]["outputs"] = [
|
|
359
|
+
event[mm_schemas.StreamProcessingEvent.RESPONSE]["outputs"][i]
|
|
360
|
+
for i in sampled_requests_indices
|
|
361
|
+
]
|
|
362
|
+
event[mm_schemas.EventFieldType.SAMPLING_PERCENTAGE] = self.sampling_percentage
|
|
363
|
+
event[mm_schemas.EventFieldType.EFFECTIVE_SAMPLE_COUNT] = len(
|
|
364
|
+
event.get(mm_schemas.StreamProcessingEvent.REQUEST, {}).get("inputs", [])
|
|
365
|
+
)
|
|
366
|
+
return event
|
|
367
|
+
|
|
368
|
+
@staticmethod
|
|
369
|
+
def _pick_random_requests(num_of_reqs: int, percentage: float) -> list[int]:
|
|
370
|
+
"""
|
|
371
|
+
Randomly selects indices of requests to sample based on the given percentage
|
|
372
|
+
|
|
373
|
+
:param num_of_reqs: Number of requests to select from
|
|
374
|
+
:param percentage: Sample percentage for each request
|
|
375
|
+
:return: A list containing the indices of the selected requests
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
return [
|
|
379
|
+
req for req in range(num_of_reqs) if random.random() < (percentage / 100)
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class MockStreamPusher(storey.MapClass):
|
|
384
|
+
def __init__(self, context, output_stream=None, **kwargs):
|
|
385
|
+
super().__init__(**kwargs)
|
|
386
|
+
self.output_stream = output_stream or context.stream.output_stream
|
|
387
|
+
|
|
388
|
+
def do(self, event):
|
|
389
|
+
self.output_stream.push(
|
|
390
|
+
[event], partition_key=mm_schemas.StreamProcessingEvent.ENDPOINT_ID
|
|
391
|
+
)
|
mlrun/serving/v2_serving.py
CHANGED
|
@@ -384,15 +384,15 @@ class V2ModelServer(StepToDict):
|
|
|
384
384
|
return event
|
|
385
385
|
|
|
386
386
|
def logged_results(self, request: dict, response: dict, op: str):
|
|
387
|
-
"""
|
|
387
|
+
"""Hook for controlling which results are tracked by the model monitoring
|
|
388
388
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
for example in image classification calculate and track the RGB values vs the image bitmap
|
|
389
|
+
This hook allows controlling which input/output data is logged by the model monitoring.
|
|
390
|
+
It allows filtering out columns or adding custom values, and can also be used to monitor derived metrics,
|
|
391
|
+
for example in image classification to calculate and track the RGB values vs the image bitmap.
|
|
392
392
|
|
|
393
|
-
|
|
394
|
-
corresponding output values/arrays (the schema of the input/output fields is stored in the model object)
|
|
395
|
-
|
|
393
|
+
The request ["inputs"] holds a list of input values/arrays, the response ["outputs"] holds a list of
|
|
394
|
+
corresponding output values/arrays (the schema of the input/output fields is stored in the model object).
|
|
395
|
+
This method should return lists of alternative inputs and outputs which will be monitored.
|
|
396
396
|
|
|
397
397
|
:param request: predict/explain request, see model serving docs for details
|
|
398
398
|
:param response: result from the model predict/explain (after postprocess())
|
|
@@ -422,6 +422,7 @@ class V2ModelServer(StepToDict):
|
|
|
422
422
|
|
|
423
423
|
def predict(self, request: dict) -> list:
|
|
424
424
|
"""model prediction operation
|
|
425
|
+
|
|
425
426
|
:return: list with the model prediction results (can be multi-port) or list of lists for multiple predictions
|
|
426
427
|
"""
|
|
427
428
|
raise NotImplementedError()
|
|
@@ -436,7 +437,7 @@ class V2ModelServer(StepToDict):
|
|
|
436
437
|
where the internal list order is according to the ArtifactModel inputs.
|
|
437
438
|
|
|
438
439
|
:param request: event
|
|
439
|
-
:return:
|
|
440
|
+
:return: event body converting the inputs to be list of lists
|
|
440
441
|
"""
|
|
441
442
|
if self.model_spec and self.model_spec.inputs:
|
|
442
443
|
input_order = [feature.name for feature in self.model_spec.inputs]
|
mlrun/utils/helpers.py
CHANGED
|
@@ -84,10 +84,6 @@ DEFAULT_TIME_PARTITIONS = ["year", "month", "day", "hour"]
|
|
|
84
84
|
DEFAULT_TIME_PARTITIONING_GRANULARITY = "hour"
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
class OverwriteBuildParamsWarning(FutureWarning):
|
|
88
|
-
pass
|
|
89
|
-
|
|
90
|
-
|
|
91
87
|
class StorePrefix:
|
|
92
88
|
"""map mlrun store objects to prefixes"""
|
|
93
89
|
|
|
@@ -2125,6 +2121,23 @@ def join_urls(base_url: Optional[str], path: Optional[str]) -> str:
|
|
|
2125
2121
|
return f"{base_url.rstrip('/')}/{path.lstrip('/')}" if path else base_url
|
|
2126
2122
|
|
|
2127
2123
|
|
|
2124
|
+
def warn_on_deprecated_image(image: Optional[str]):
|
|
2125
|
+
"""
|
|
2126
|
+
Warn if the provided image is the deprecated 'mlrun/ml-base' image.
|
|
2127
|
+
This image is deprecated as of 1.10.0 and will be removed in 1.12.0.
|
|
2128
|
+
"""
|
|
2129
|
+
deprecated_images = ["mlrun/ml-base"]
|
|
2130
|
+
if image and any(
|
|
2131
|
+
image in deprecated_image for deprecated_image in deprecated_images
|
|
2132
|
+
):
|
|
2133
|
+
warnings.warn(
|
|
2134
|
+
"'mlrun/ml-base' image is deprecated in 1.10.0 and will be replaced by 'mlrun/mlrun'. "
|
|
2135
|
+
"This behavior will be removed in 1.12.0 ",
|
|
2136
|
+
# TODO: Remove this in 1.12.0
|
|
2137
|
+
FutureWarning,
|
|
2138
|
+
)
|
|
2139
|
+
|
|
2140
|
+
|
|
2128
2141
|
class Workflow:
|
|
2129
2142
|
@staticmethod
|
|
2130
2143
|
def get_workflow_steps(
|
|
@@ -2294,6 +2307,7 @@ class Workflow:
|
|
|
2294
2307
|
workflow_id: str,
|
|
2295
2308
|
) -> typing.Optional[mlrun_pipelines.models.PipelineManifest]:
|
|
2296
2309
|
kfp_client = mlrun_pipelines.utils.get_client(
|
|
2310
|
+
logger=logger,
|
|
2297
2311
|
url=mlrun.mlconf.kfp_url,
|
|
2298
2312
|
namespace=mlrun.mlconf.namespace,
|
|
2299
2313
|
)
|
mlrun/utils/version/version.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlrun
|
|
3
|
-
Version: 1.10.
|
|
3
|
+
Version: 1.10.0rc8
|
|
4
4
|
Summary: Tracking and config of machine learning runs
|
|
5
5
|
Home-page: https://github.com/mlrun/mlrun
|
|
6
6
|
Author: Yaron Haviv
|
|
@@ -45,14 +45,14 @@ Requires-Dist: semver~=3.0
|
|
|
45
45
|
Requires-Dist: dependency-injector~=4.41
|
|
46
46
|
Requires-Dist: fsspec<2024.7,>=2023.9.2
|
|
47
47
|
Requires-Dist: v3iofs~=0.1.17
|
|
48
|
-
Requires-Dist: storey~=1.10.
|
|
48
|
+
Requires-Dist: storey~=1.10.2
|
|
49
49
|
Requires-Dist: inflection~=0.5.0
|
|
50
50
|
Requires-Dist: python-dotenv~=1.0
|
|
51
51
|
Requires-Dist: setuptools>=75.2
|
|
52
52
|
Requires-Dist: deprecated~=1.2
|
|
53
53
|
Requires-Dist: jinja2>=3.1.6,~=3.1
|
|
54
54
|
Requires-Dist: orjson<4,>=3.9.15
|
|
55
|
-
Requires-Dist: mlrun-pipelines-kfp-common~=0.5.
|
|
55
|
+
Requires-Dist: mlrun-pipelines-kfp-common~=0.5.6
|
|
56
56
|
Requires-Dist: mlrun-pipelines-kfp-v1-8~=0.5.4
|
|
57
57
|
Requires-Dist: docstring_parser~=0.16
|
|
58
58
|
Requires-Dist: aiosmtplib~=3.0
|
|
@@ -250,7 +250,7 @@ Dynamic: summary
|
|
|
250
250
|
[](https://mlrun.readthedocs.io/en/latest/?badge=latest)
|
|
251
251
|
[](https://github.com/astral-sh/ruff)
|
|
252
252
|

|
|
253
|
-

|
|
253
|
+
[](https://github.com/mlrun/mlrun/releases)
|
|
254
254
|
[](https://mlopslive.slack.com)
|
|
255
255
|
|
|
256
256
|
<div>
|
|
@@ -298,9 +298,9 @@ Removing inappropriate data at an early stage saves resources that would otherwi
|
|
|
298
298
|
[Vector databases](https://docs.mlrun.org/en/stable/genai/data-mgmt/vector-databases.html)
|
|
299
299
|
[Guardrails for data management](https://docs.mlrun.org/en/stable/genai/data-mgmt/guardrails-data.html)
|
|
300
300
|
**Demo:**
|
|
301
|
-
[Call center demo](https://github.com/mlrun/demo-call-center
|
|
301
|
+
[Call center demo](https://github.com/mlrun/demo-call-center)
|
|
302
302
|
**Video:**
|
|
303
|
-
[Call center](https://youtu.be/YycMbxRgLBA
|
|
303
|
+
[Call center](https://youtu.be/YycMbxRgLBA)
|
|
304
304
|
|
|
305
305
|
### Development
|
|
306
306
|
Use MLRun to build an automated ML pipeline to: collect data,
|
|
@@ -321,13 +321,13 @@ inferring results using one or more models, and driving actions.
|
|
|
321
321
|
|
|
322
322
|
|
|
323
323
|
**Docs:**
|
|
324
|
-
[Serving gen AI models](https://docs.mlrun.org/en/stable/genai/deployment/genai_serving.html), GPU utilization](https://docs.mlrun.org/en/stable/genai/deployment/gpu_utilization.html), [Gen AI realtime serving graph](https://docs.mlrun.org/en/stable/genai/deployment/genai_serving_graph.html)
|
|
324
|
+
[Serving gen AI models](https://docs.mlrun.org/en/stable/genai/deployment/genai_serving.html), [GPU utilization](https://docs.mlrun.org/en/stable/genai/deployment/gpu_utilization.html), [Gen AI realtime serving graph](https://docs.mlrun.org/en/stable/genai/deployment/genai_serving_graph.html)
|
|
325
325
|
**Tutorial:**
|
|
326
326
|
[Deploy LLM using MLRun](https://docs.mlrun.org/en/stable/tutorials/genai_01_basic_tutorial.html)
|
|
327
327
|
**Demos:**
|
|
328
|
-
[Call center demo](https://github.com/mlrun/demo-call-center), [Build & deploy custom(fine-tuned)
|
|
328
|
+
[Call center demo](https://github.com/mlrun/demo-call-center), [Build & deploy custom(fine-tuned)LLM models and applications](https://github.com/mlrun/demo-llm-tuning/blob/main), [Interactive bot demo using LLMs](https://github.com/mlrun/demo-llm-bot/blob/main)
|
|
329
329
|
**Video:**
|
|
330
|
-
[Call center]
|
|
330
|
+
[Call center](https://youtu.be/YycMbxRgLBA)
|
|
331
331
|
|
|
332
332
|
|
|
333
333
|
### Live Ops
|