oracle-ads 2.13.12__py3-none-any.whl → 2.13.13__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.
- ads/aqua/app.py +60 -8
- ads/aqua/cli.py +2 -0
- ads/aqua/client/client.py +38 -21
- ads/aqua/client/openai_client.py +20 -10
- ads/aqua/common/entities.py +58 -18
- ads/aqua/constants.py +2 -0
- ads/aqua/extension/common_handler.py +47 -2
- ads/aqua/extension/model_handler.py +50 -8
- ads/aqua/model/constants.py +1 -0
- ads/aqua/model/model.py +115 -56
- ads/aqua/modeldeployment/deployment.py +93 -20
- ads/aqua/modeldeployment/entities.py +146 -0
- ads/aqua/modeldeployment/model_group_config.py +7 -14
- ads/aqua/verify_policies/__init__.py +8 -0
- ads/aqua/verify_policies/constants.py +13 -0
- ads/aqua/verify_policies/entities.py +29 -0
- ads/aqua/verify_policies/messages.py +101 -0
- ads/aqua/verify_policies/utils.py +432 -0
- ads/aqua/verify_policies/verify.py +345 -0
- ads/aqua/version.json +3 -0
- ads/common/oci_logging.py +4 -7
- ads/model/deployment/model_deployment.py +51 -38
- {oracle_ads-2.13.12.dist-info → oracle_ads-2.13.13.dist-info}/METADATA +2 -1
- {oracle_ads-2.13.12.dist-info → oracle_ads-2.13.13.dist-info}/RECORD +27 -20
- {oracle_ads-2.13.12.dist-info → oracle_ads-2.13.13.dist-info}/WHEEL +0 -0
- {oracle_ads-2.13.12.dist-info → oracle_ads-2.13.13.dist-info}/entry_points.txt +0 -0
- {oracle_ads-2.13.12.dist-info → oracle_ads-2.13.13.dist-info}/licenses/LICENSE.txt +0 -0
ads/aqua/model/model.py
CHANGED
@@ -16,7 +16,7 @@ from oci.data_science.models import JobRun, Metadata, Model, UpdateModelDetails
|
|
16
16
|
|
17
17
|
from ads.aqua import logger
|
18
18
|
from ads.aqua.app import AquaApp
|
19
|
-
from ads.aqua.common.entities import AquaMultiModelRef
|
19
|
+
from ads.aqua.common.entities import AquaMultiModelRef
|
20
20
|
from ads.aqua.common.enums import (
|
21
21
|
ConfigFolder,
|
22
22
|
CustomInferenceContainerTypeFamily,
|
@@ -83,10 +83,7 @@ from ads.aqua.model.entities import (
|
|
83
83
|
ModelValidationResult,
|
84
84
|
)
|
85
85
|
from ads.aqua.model.enums import MultiModelSupportedTaskType
|
86
|
-
from ads.aqua.model.utils import
|
87
|
-
extract_base_model_from_ft,
|
88
|
-
extract_fine_tune_artifacts_path,
|
89
|
-
)
|
86
|
+
from ads.aqua.model.utils import extract_fine_tune_artifacts_path
|
90
87
|
from ads.common.auth import default_signer
|
91
88
|
from ads.common.oci_resource import SEARCH_TYPE, OCIResource
|
92
89
|
from ads.common.utils import UNKNOWN, get_console_link, is_path_exists, read_file
|
@@ -242,6 +239,7 @@ class AquaModelApp(AquaApp):
|
|
242
239
|
compartment_id: Optional[str] = None,
|
243
240
|
freeform_tags: Optional[Dict] = None,
|
244
241
|
defined_tags: Optional[Dict] = None,
|
242
|
+
source_models: Optional[Dict[str, DataScienceModel]] = None,
|
245
243
|
**kwargs, # noqa: ARG002
|
246
244
|
) -> DataScienceModel:
|
247
245
|
"""
|
@@ -259,6 +257,10 @@ class AquaModelApp(AquaApp):
|
|
259
257
|
Freeform tags for the model.
|
260
258
|
defined_tags : Optional[Dict]
|
261
259
|
Defined tags for the model.
|
260
|
+
source_models: Optional[Dict[str, DataScienceModel]]
|
261
|
+
A mapping of model OCIDs to their corresponding `DataScienceModel` objects.
|
262
|
+
This dictionary contains metadata for all models involved in the multi-model deployment,
|
263
|
+
including both base models and fine-tuned weights.
|
262
264
|
|
263
265
|
Returns
|
264
266
|
-------
|
@@ -295,76 +297,127 @@ class AquaModelApp(AquaApp):
|
|
295
297
|
|
296
298
|
selected_models_deployment_containers = set()
|
297
299
|
|
300
|
+
if not source_models:
|
301
|
+
# Collect all unique model IDs (including fine-tuned models)
|
302
|
+
source_model_ids = list(
|
303
|
+
{model_id for model in models for model_id in model.all_model_ids()}
|
304
|
+
)
|
305
|
+
logger.debug(
|
306
|
+
"Fetching source model metadata for model IDs: %s", source_model_ids
|
307
|
+
)
|
308
|
+
|
309
|
+
# Fetch source model metadata
|
310
|
+
source_models = self.get_multi_source(source_model_ids) or {}
|
311
|
+
|
298
312
|
# Process each model in the input list
|
299
313
|
for model in models:
|
300
|
-
# Retrieve model metadata
|
301
|
-
source_model =
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
314
|
+
# Retrieve base model metadata
|
315
|
+
source_model: DataScienceModel = source_models.get(model.model_id)
|
316
|
+
if not source_model:
|
317
|
+
logger.error(
|
318
|
+
"Failed to fetch metadata for base model ID: %s", model.model_id
|
319
|
+
)
|
320
|
+
raise AquaValueError(
|
321
|
+
f"Unable to retrieve metadata for base model ID: {model.model_id}."
|
322
|
+
)
|
323
|
+
|
324
|
+
# Use display name as fallback if model name not provided
|
325
|
+
model.model_name = model.model_name or source_model.display_name
|
306
326
|
|
327
|
+
# Validate model file description
|
328
|
+
model_file_description = source_model.model_file_description
|
307
329
|
if not model_file_description:
|
330
|
+
logger.error(
|
331
|
+
"Model '%s' (%s) has no file description.",
|
332
|
+
source_model.display_name,
|
333
|
+
model.model_id,
|
334
|
+
)
|
308
335
|
raise AquaValueError(
|
309
336
|
f"Model '{source_model.display_name}' (ID: {model.model_id}) has no file description. "
|
310
|
-
"Please register the model
|
337
|
+
"Please register the model with a file description."
|
311
338
|
)
|
312
339
|
|
313
|
-
#
|
314
|
-
|
315
|
-
|
340
|
+
# Track model file description in a validated structure
|
341
|
+
model_file_description_list.append(
|
342
|
+
ModelFileDescription(**model_file_description)
|
316
343
|
)
|
317
344
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
345
|
+
# Ensure base model has a valid artifact
|
346
|
+
if not source_model.artifact:
|
347
|
+
logger.error(
|
348
|
+
"Base model '%s' (%s) has no artifact.",
|
349
|
+
model.model_name,
|
350
|
+
model.model_id,
|
351
|
+
)
|
352
|
+
raise AquaValueError(
|
353
|
+
f"Model '{model.model_name}' (ID: {model.model_id}) has no registered artifacts. "
|
354
|
+
"Please register the model before deployment."
|
325
355
|
)
|
326
356
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
)
|
335
|
-
]
|
357
|
+
# Set base model artifact path
|
358
|
+
model.artifact_location = source_model.artifact
|
359
|
+
logger.debug(
|
360
|
+
"Model '%s' artifact path set to: %s",
|
361
|
+
model.model_name,
|
362
|
+
model.artifact_location,
|
363
|
+
)
|
336
364
|
|
337
|
-
|
338
|
-
display_name = model.model_name
|
365
|
+
display_name_list.append(model.model_name)
|
339
366
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
367
|
+
# Extract model task metadata from source model
|
368
|
+
self._extract_model_task(model, source_model)
|
369
|
+
|
370
|
+
# Process fine-tuned weights if provided
|
371
|
+
for ft_model in model.fine_tune_weights or []:
|
372
|
+
fine_tune_source_model: DataScienceModel = source_models.get(
|
373
|
+
ft_model.model_id
|
344
374
|
)
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
375
|
+
if not fine_tune_source_model:
|
376
|
+
logger.error(
|
377
|
+
"Failed to fetch metadata for fine-tuned model ID: %s",
|
378
|
+
ft_model.model_id,
|
379
|
+
)
|
380
|
+
raise AquaValueError(
|
381
|
+
f"Unable to retrieve metadata for fine-tuned model ID: {ft_model.model_id}."
|
382
|
+
)
|
349
383
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
f"Model '{model.model_name}' (ID: {model.model_id}) has no artifacts. "
|
354
|
-
"Please register the model first."
|
384
|
+
# Validate model file description
|
385
|
+
ft_model_file_description = (
|
386
|
+
fine_tune_source_model.model_file_description
|
355
387
|
)
|
388
|
+
if not ft_model_file_description:
|
389
|
+
logger.error(
|
390
|
+
"Model '%s' (%s) has no file description.",
|
391
|
+
fine_tune_source_model.display_name,
|
392
|
+
ft_model.model_id,
|
393
|
+
)
|
394
|
+
raise AquaValueError(
|
395
|
+
f"Model '{fine_tune_source_model.display_name}' (ID: {ft_model.model_id}) has no file description. "
|
396
|
+
"Please register the model with a file description."
|
397
|
+
)
|
356
398
|
|
357
|
-
|
358
|
-
|
359
|
-
|
399
|
+
# Track model file description in a validated structure
|
400
|
+
model_file_description_list.append(
|
401
|
+
ModelFileDescription(**ft_model_file_description)
|
402
|
+
)
|
360
403
|
|
361
|
-
|
362
|
-
|
404
|
+
# Extract fine-tuned model path
|
405
|
+
_, fine_tune_path = extract_fine_tune_artifacts_path(
|
406
|
+
fine_tune_source_model
|
407
|
+
)
|
408
|
+
logger.debug(
|
409
|
+
"Resolved fine-tuned model path for '%s': %s",
|
410
|
+
ft_model.model_id,
|
411
|
+
fine_tune_path,
|
412
|
+
)
|
413
|
+
ft_model.model_path = fine_tune_path
|
363
414
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
415
|
+
# Use fallback name if needed
|
416
|
+
ft_model.model_name = (
|
417
|
+
ft_model.model_name or fine_tune_source_model.display_name
|
418
|
+
)
|
419
|
+
|
420
|
+
display_name_list.append(ft_model.model_name)
|
368
421
|
|
369
422
|
# Validate deployment container consistency
|
370
423
|
deployment_container = source_model.custom_metadata_list.get(
|
@@ -375,9 +428,15 @@ class AquaModelApp(AquaApp):
|
|
375
428
|
).value
|
376
429
|
|
377
430
|
if deployment_container not in supported_container_families:
|
431
|
+
logger.error(
|
432
|
+
"Unsupported deployment container '%s' for model '%s'. Supported: %s",
|
433
|
+
deployment_container,
|
434
|
+
source_model.id,
|
435
|
+
supported_container_families,
|
436
|
+
)
|
378
437
|
raise AquaValueError(
|
379
438
|
f"Unsupported deployment container '{deployment_container}' for model '{source_model.id}'. "
|
380
|
-
f"Only
|
439
|
+
f"Only {supported_container_families} are supported for multi-model deployments."
|
381
440
|
)
|
382
441
|
|
383
442
|
selected_models_deployment_containers.add(deployment_container)
|
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
|
6
6
|
import json
|
7
|
+
import re
|
7
8
|
import shlex
|
8
9
|
import threading
|
9
10
|
from datetime import datetime, timedelta
|
@@ -47,7 +48,11 @@ from ads.aqua.constants import (
|
|
47
48
|
)
|
48
49
|
from ads.aqua.data import AquaResourceIdentifier
|
49
50
|
from ads.aqua.model import AquaModelApp
|
50
|
-
from ads.aqua.model.constants import
|
51
|
+
from ads.aqua.model.constants import (
|
52
|
+
AquaModelMetadataKeys,
|
53
|
+
ModelCustomMetadataFields,
|
54
|
+
ModelTask,
|
55
|
+
)
|
51
56
|
from ads.aqua.model.utils import (
|
52
57
|
extract_base_model_from_ft,
|
53
58
|
extract_fine_tune_artifacts_path,
|
@@ -214,20 +219,52 @@ class AquaDeploymentApp(AquaApp):
|
|
214
219
|
freeform_tags=freeform_tags,
|
215
220
|
defined_tags=defined_tags,
|
216
221
|
)
|
222
|
+
task_tag = aqua_model.freeform_tags.get(Tags.TASK, UNKNOWN)
|
223
|
+
if (
|
224
|
+
task_tag == ModelTask.TIME_SERIES_FORECASTING
|
225
|
+
or task_tag == ModelTask.TIME_SERIES_FORECASTING.replace("-", "_")
|
226
|
+
):
|
227
|
+
create_deployment_details.env_var.update(
|
228
|
+
{Tags.TASK.upper(): ModelTask.TIME_SERIES_FORECASTING}
|
229
|
+
)
|
217
230
|
return self._create(
|
218
231
|
aqua_model=aqua_model,
|
219
232
|
create_deployment_details=create_deployment_details,
|
220
233
|
container_config=container_config,
|
221
234
|
)
|
222
235
|
else:
|
223
|
-
|
236
|
+
# Collect all unique model IDs (including fine-tuned models)
|
237
|
+
source_model_ids = list(
|
238
|
+
{
|
239
|
+
model_id
|
240
|
+
for model in create_deployment_details.models
|
241
|
+
for model_id in model.all_model_ids()
|
242
|
+
}
|
243
|
+
)
|
244
|
+
logger.debug(
|
245
|
+
"Fetching source model metadata for model IDs: %s", source_model_ids
|
246
|
+
)
|
247
|
+
# Fetch source model metadata
|
248
|
+
source_models = self.get_multi_source(source_model_ids) or {}
|
249
|
+
|
250
|
+
try:
|
251
|
+
create_deployment_details.validate_input_models(
|
252
|
+
model_details=source_models
|
253
|
+
)
|
254
|
+
except ConfigValidationError as err:
|
255
|
+
raise AquaValueError(f"{err}") from err
|
256
|
+
|
257
|
+
base_model_ids = [
|
258
|
+
model.model_id for model in create_deployment_details.models
|
259
|
+
]
|
224
260
|
|
225
261
|
try:
|
226
262
|
model_config_summary = self.get_multimodel_deployment_config(
|
227
|
-
model_ids=
|
263
|
+
model_ids=base_model_ids, compartment_id=compartment_id
|
228
264
|
)
|
229
265
|
if not model_config_summary.gpu_allocation:
|
230
266
|
raise AquaValueError(model_config_summary.error_message)
|
267
|
+
|
231
268
|
create_deployment_details.validate_multimodel_deployment_feasibility(
|
232
269
|
models_config_summary=model_config_summary
|
233
270
|
)
|
@@ -298,7 +335,7 @@ class AquaDeploymentApp(AquaApp):
|
|
298
335
|
)
|
299
336
|
|
300
337
|
logger.debug(
|
301
|
-
f"Multi models ({
|
338
|
+
f"Multi models ({source_model_ids}) provided. Delegating to multi model creation method."
|
302
339
|
)
|
303
340
|
|
304
341
|
aqua_model = model_app.create_multi(
|
@@ -307,6 +344,7 @@ class AquaDeploymentApp(AquaApp):
|
|
307
344
|
project_id=project_id,
|
308
345
|
freeform_tags=freeform_tags,
|
309
346
|
defined_tags=defined_tags,
|
347
|
+
source_models=source_models,
|
310
348
|
)
|
311
349
|
return self._create_multi(
|
312
350
|
aqua_model=aqua_model,
|
@@ -727,14 +765,16 @@ class AquaDeploymentApp(AquaApp):
|
|
727
765
|
).deploy(wait_for_completion=False)
|
728
766
|
|
729
767
|
deployment_id = deployment.id
|
768
|
+
|
730
769
|
logger.info(
|
731
770
|
f"Aqua model deployment {deployment_id} created for model {aqua_model_id}. Work request Id is {deployment.dsc_model_deployment.workflow_req_id}"
|
732
771
|
)
|
772
|
+
status_list = []
|
733
773
|
|
734
774
|
progress_thread = threading.Thread(
|
735
775
|
target=self.get_deployment_status,
|
736
776
|
args=(
|
737
|
-
|
777
|
+
deployment,
|
738
778
|
deployment.dsc_model_deployment.workflow_req_id,
|
739
779
|
model_type,
|
740
780
|
model_name,
|
@@ -822,12 +862,22 @@ class AquaDeploymentApp(AquaApp):
|
|
822
862
|
)
|
823
863
|
|
824
864
|
if oci_aqua:
|
865
|
+
# skipping the AQUA model deployments that are created from model group
|
866
|
+
# TODO: remove this checker after AQUA deployment is integrated with model group
|
867
|
+
aqua_model_id = model_deployment.freeform_tags.get(
|
868
|
+
Tags.AQUA_MODEL_ID_TAG, UNKNOWN
|
869
|
+
)
|
870
|
+
if (
|
871
|
+
"datasciencemodelgroup" in aqua_model_id
|
872
|
+
or model_deployment.model_deployment_configuration_details.deployment_type
|
873
|
+
== "UNKNOWN_ENUM_VALUE"
|
874
|
+
):
|
875
|
+
continue
|
825
876
|
results.append(
|
826
877
|
AquaDeployment.from_oci_model_deployment(
|
827
878
|
model_deployment, self.region
|
828
879
|
)
|
829
880
|
)
|
830
|
-
|
831
881
|
# log telemetry if MD is in active or failed state
|
832
882
|
deployment_id = model_deployment.id
|
833
883
|
state = model_deployment.lifecycle_state.upper()
|
@@ -1233,7 +1283,7 @@ class AquaDeploymentApp(AquaApp):
|
|
1233
1283
|
|
1234
1284
|
def get_deployment_status(
|
1235
1285
|
self,
|
1236
|
-
|
1286
|
+
deployment: ModelDeployment,
|
1237
1287
|
work_request_id: str,
|
1238
1288
|
model_type: str,
|
1239
1289
|
model_name: str,
|
@@ -1255,13 +1305,10 @@ class AquaDeploymentApp(AquaApp):
|
|
1255
1305
|
AquaDeployment
|
1256
1306
|
An Aqua deployment instance.
|
1257
1307
|
"""
|
1258
|
-
ocid = get_ocid_substring(
|
1259
|
-
telemetry_kwargs = {"ocid": ocid}
|
1260
|
-
|
1308
|
+
ocid = get_ocid_substring(deployment.id, key_len=8)
|
1261
1309
|
data_science_work_request: DataScienceWorkRequest = DataScienceWorkRequest(
|
1262
1310
|
work_request_id
|
1263
1311
|
)
|
1264
|
-
|
1265
1312
|
try:
|
1266
1313
|
data_science_work_request.wait_work_request(
|
1267
1314
|
progress_bar_description="Creating model deployment",
|
@@ -1269,23 +1316,49 @@ class AquaDeploymentApp(AquaApp):
|
|
1269
1316
|
poll_interval=DEFAULT_POLL_INTERVAL,
|
1270
1317
|
)
|
1271
1318
|
except Exception:
|
1319
|
+
status = ""
|
1320
|
+
logs = deployment.show_logs().sort_values(by="time", ascending=False)
|
1321
|
+
|
1322
|
+
if logs and len(logs) > 0:
|
1323
|
+
status = logs.iloc[0]["message"]
|
1324
|
+
|
1325
|
+
status = re.sub(r"[^a-zA-Z0-9]", " ", status)
|
1326
|
+
|
1272
1327
|
if data_science_work_request._error_message:
|
1273
1328
|
error_str = ""
|
1274
1329
|
for error in data_science_work_request._error_message:
|
1275
1330
|
error_str = error_str + " " + error.message
|
1276
1331
|
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1332
|
+
error_str = re.sub(r"[^a-zA-Z0-9]", " ", error_str)
|
1333
|
+
telemetry_kwargs = {
|
1334
|
+
"ocid": ocid,
|
1335
|
+
"model_name": model_name,
|
1336
|
+
"work_request_error": error_str,
|
1337
|
+
"status": status,
|
1338
|
+
}
|
1339
|
+
|
1340
|
+
self.telemetry.record_event(
|
1341
|
+
category=f"aqua/{model_type}/deployment/status",
|
1342
|
+
action="FAILED",
|
1343
|
+
**telemetry_kwargs,
|
1344
|
+
)
|
1345
|
+
else:
|
1346
|
+
telemetry_kwargs = {
|
1347
|
+
"ocid": ocid,
|
1348
|
+
"model_name": model_name,
|
1349
|
+
"status": status,
|
1350
|
+
}
|
1351
|
+
|
1352
|
+
self.telemetry.record_event(
|
1353
|
+
category=f"aqua/{model_type}/deployment/status",
|
1354
|
+
action="FAILED",
|
1355
|
+
**telemetry_kwargs,
|
1356
|
+
)
|
1284
1357
|
|
1285
1358
|
else:
|
1286
|
-
|
1359
|
+
telemetry_kwargs = {"ocid": ocid, "model_name": model_name}
|
1360
|
+
self.telemetry.record_event(
|
1287
1361
|
category=f"aqua/{model_type}/deployment/status",
|
1288
1362
|
action="SUCCEEDED",
|
1289
|
-
value=model_name,
|
1290
1363
|
**telemetry_kwargs,
|
1291
1364
|
)
|
@@ -13,12 +13,15 @@ from ads.aqua.common.enums import Tags
|
|
13
13
|
from ads.aqua.config.utils.serializer import Serializable
|
14
14
|
from ads.aqua.constants import UNKNOWN_DICT
|
15
15
|
from ads.aqua.data import AquaResourceIdentifier
|
16
|
+
from ads.aqua.finetuning.constants import FineTuneCustomMetadata
|
16
17
|
from ads.aqua.modeldeployment.config_loader import (
|
17
18
|
ConfigurationItem,
|
18
19
|
ModelDeploymentConfigSummary,
|
19
20
|
)
|
20
21
|
from ads.common.serializer import DataClassSerializable
|
21
22
|
from ads.common.utils import UNKNOWN, get_console_link
|
23
|
+
from ads.model.datascience_model import DataScienceModel
|
24
|
+
from ads.model.model_metadata import ModelCustomMetadataItem
|
22
25
|
|
23
26
|
|
24
27
|
class ConfigValidationError(Exception):
|
@@ -474,6 +477,149 @@ class CreateModelDeploymentDetails(BaseModel):
|
|
474
477
|
logger.error(error_message)
|
475
478
|
raise ConfigValidationError(error_message)
|
476
479
|
|
480
|
+
def validate_input_models(self, model_details: Dict[str, DataScienceModel]) -> None:
|
481
|
+
"""
|
482
|
+
Validates the input models for a multi-model deployment configuration.
|
483
|
+
|
484
|
+
Validation Criteria:
|
485
|
+
- The base model must be explicitly provided.
|
486
|
+
- The base model must be in 'ACTIVE' state.
|
487
|
+
- Fine-tuned model IDs must refer to valid, tagged fine-tuned models.
|
488
|
+
- Fine-tuned models must refer back to the same base model.
|
489
|
+
- All model names (including fine-tuned variants) must be unique.
|
490
|
+
|
491
|
+
Parameters
|
492
|
+
----------
|
493
|
+
model_details : Dict[str, DataScienceModel]
|
494
|
+
Dictionary mapping model OCIDs to DataScienceModel instances.
|
495
|
+
Includes the all models to validate including fine-tuned models.
|
496
|
+
|
497
|
+
Raises
|
498
|
+
------
|
499
|
+
ConfigValidationError
|
500
|
+
If any of the above conditions are violated.
|
501
|
+
"""
|
502
|
+
if not self.models:
|
503
|
+
logger.error("Validation failed: No models specified in the model group.")
|
504
|
+
raise ConfigValidationError(
|
505
|
+
"Multi-model deployment requires at least one model entry. "
|
506
|
+
"Please provide a base model in the `models` list."
|
507
|
+
)
|
508
|
+
|
509
|
+
seen_names = set()
|
510
|
+
duplicate_names = set()
|
511
|
+
|
512
|
+
for model in self.models:
|
513
|
+
base_model_id = model.model_id
|
514
|
+
base_model = model_details.get(base_model_id)
|
515
|
+
|
516
|
+
if not base_model:
|
517
|
+
logger.error(
|
518
|
+
"Validation failed: Base model ID '%s' not found.", base_model_id
|
519
|
+
)
|
520
|
+
raise ConfigValidationError(f"Model not found: '{base_model_id}'.")
|
521
|
+
|
522
|
+
if Tags.AQUA_FINE_TUNED_MODEL_TAG in (base_model.freeform_tags or {}):
|
523
|
+
logger.error(
|
524
|
+
"Validation failed: Base model ID '%s' is a fine-tuned model.",
|
525
|
+
base_model_id,
|
526
|
+
)
|
527
|
+
raise ConfigValidationError(
|
528
|
+
f"Invalid base model ID '{base_model_id}'. "
|
529
|
+
"Specify a base model OCID in the `models` input, not a fine-tuned model."
|
530
|
+
)
|
531
|
+
|
532
|
+
if base_model.lifecycle_state != "ACTIVE":
|
533
|
+
logger.error(
|
534
|
+
"Validation failed: Base model '%s' is in state '%s'.",
|
535
|
+
base_model_id,
|
536
|
+
base_model.lifecycle_state,
|
537
|
+
)
|
538
|
+
raise ConfigValidationError(
|
539
|
+
f"Invalid base model ID '{base_model_id}': must be in ACTIVE state."
|
540
|
+
)
|
541
|
+
|
542
|
+
# Normalize and validate model name uniqueness
|
543
|
+
model_name = model.model_name or base_model.display_name
|
544
|
+
if model_name in seen_names:
|
545
|
+
duplicate_names.add(model_name)
|
546
|
+
else:
|
547
|
+
seen_names.add(model_name)
|
548
|
+
|
549
|
+
for lora_module in model.fine_tune_weights or []:
|
550
|
+
ft_model_id = lora_module.model_id
|
551
|
+
ft_model = model_details.get(ft_model_id)
|
552
|
+
|
553
|
+
if not ft_model:
|
554
|
+
logger.error(
|
555
|
+
"Validation failed: Fine-tuned model ID '%s' not found.",
|
556
|
+
ft_model_id,
|
557
|
+
)
|
558
|
+
raise ConfigValidationError(
|
559
|
+
f"Fine-tuned model not found: '{ft_model_id}'."
|
560
|
+
)
|
561
|
+
|
562
|
+
if ft_model.lifecycle_state != "ACTIVE":
|
563
|
+
logger.error(
|
564
|
+
"Validation failed: Fine-tuned model '%s' is in state '%s'.",
|
565
|
+
ft_model_id,
|
566
|
+
ft_model.lifecycle_state,
|
567
|
+
)
|
568
|
+
raise ConfigValidationError(
|
569
|
+
f"Invalid Fine-tuned model ID '{ft_model_id}': must be in ACTIVE state."
|
570
|
+
)
|
571
|
+
|
572
|
+
if Tags.AQUA_FINE_TUNED_MODEL_TAG not in (ft_model.freeform_tags or {}):
|
573
|
+
logger.error(
|
574
|
+
"Validation failed: Model ID '%s' is missing tag '%s'.",
|
575
|
+
ft_model_id,
|
576
|
+
Tags.AQUA_FINE_TUNED_MODEL_TAG,
|
577
|
+
)
|
578
|
+
raise ConfigValidationError(
|
579
|
+
f"Invalid fine-tuned model ID '{ft_model_id}': missing tag '{Tags.AQUA_FINE_TUNED_MODEL_TAG}'."
|
580
|
+
)
|
581
|
+
|
582
|
+
ft_base_model_id = ft_model.custom_metadata_list.get(
|
583
|
+
FineTuneCustomMetadata.FINE_TUNE_SOURCE,
|
584
|
+
ModelCustomMetadataItem(
|
585
|
+
key=FineTuneCustomMetadata.FINE_TUNE_SOURCE
|
586
|
+
),
|
587
|
+
).value
|
588
|
+
|
589
|
+
if ft_base_model_id != base_model_id:
|
590
|
+
logger.error(
|
591
|
+
"Validation failed: Fine-tuned model '%s' is linked to base model '%s' (expected '%s').",
|
592
|
+
ft_model_id,
|
593
|
+
ft_base_model_id,
|
594
|
+
base_model_id,
|
595
|
+
)
|
596
|
+
raise ConfigValidationError(
|
597
|
+
f"Fine-tuned model '{ft_model_id}' belongs to base model '{ft_base_model_id}', "
|
598
|
+
f"but was included under base model '{base_model_id}'."
|
599
|
+
)
|
600
|
+
|
601
|
+
# Validate fine-tuned model name uniqueness
|
602
|
+
lora_model_name = lora_module.model_name or ft_model.display_name
|
603
|
+
if lora_model_name in seen_names:
|
604
|
+
duplicate_names.add(lora_model_name)
|
605
|
+
else:
|
606
|
+
seen_names.add(lora_model_name)
|
607
|
+
|
608
|
+
logger.debug(
|
609
|
+
"Validated fine-tuned model '%s' under base model '%s'.",
|
610
|
+
ft_model_id,
|
611
|
+
base_model_id,
|
612
|
+
)
|
613
|
+
|
614
|
+
if duplicate_names:
|
615
|
+
logger.error(
|
616
|
+
"Duplicate model names detected: %s", ", ".join(sorted(duplicate_names))
|
617
|
+
)
|
618
|
+
raise ConfigValidationError(
|
619
|
+
f"The following model names are duplicated across base and fine-tuned models: "
|
620
|
+
f"{', '.join(sorted(duplicate_names))}. Model names must be unique for proper routing in multi-model deployments."
|
621
|
+
)
|
622
|
+
|
477
623
|
class Config:
|
478
624
|
extra = "allow"
|
479
625
|
protected_namespaces = ()
|
@@ -81,14 +81,12 @@ class BaseModelSpec(BaseModel):
|
|
81
81
|
unique_modules: List[LoraModuleSpec] = []
|
82
82
|
|
83
83
|
for module in fine_tune_weights or []:
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
if name in seen:
|
89
|
-
logger.warning(f"Duplicate LoRA Module detected: {name!r} (skipping duplicate).")
|
84
|
+
if module.model_name and module.model_name in seen:
|
85
|
+
logger.warning(
|
86
|
+
f"Duplicate LoRA Module detected: {module.model_name!r} (skipping duplicate)."
|
87
|
+
)
|
90
88
|
continue
|
91
|
-
seen.add(
|
89
|
+
seen.add(module.model_name)
|
92
90
|
unique_modules.append(module)
|
93
91
|
|
94
92
|
return unique_modules
|
@@ -169,17 +167,12 @@ class ModelGroupConfig(Serializable):
|
|
169
167
|
model, container_params, container_type_key
|
170
168
|
)
|
171
169
|
|
172
|
-
model_id = (
|
173
|
-
model.fine_tune_weights[0].model_id
|
174
|
-
if model.fine_tune_weights
|
175
|
-
else model.model_id
|
176
|
-
)
|
177
|
-
|
178
170
|
deployment_config = model_config_summary.deployment_config.get(
|
179
|
-
model_id, AquaDeploymentConfig()
|
171
|
+
model.model_id, AquaDeploymentConfig()
|
180
172
|
).configuration.get(
|
181
173
|
create_deployment_details.instance_shape, ConfigurationItem()
|
182
174
|
)
|
175
|
+
|
183
176
|
params_found = False
|
184
177
|
for item in deployment_config.multi_model_deployment:
|
185
178
|
if model.gpu_count and item.gpu_count and item.gpu_count == model.gpu_count:
|
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*--
|
3
|
+
|
4
|
+
# Copyright (c) 2024 Oracle and/or its affiliates.
|
5
|
+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
|
6
|
+
from ads.aqua.verify_policies.verify import AquaVerifyPoliciesApp
|
7
|
+
|
8
|
+
__all__ = ["AquaVerifyPoliciesApp"]
|