oracle-ads 2.13.4__py3-none-any.whl → 2.13.6__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/model/model.py CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  # Copyright (c) 2024, 2025 Oracle and/or its affiliates.
3
3
  # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
4
+ import json
4
5
  import os
5
6
  import pathlib
6
7
  from datetime import datetime, timedelta
@@ -14,6 +15,7 @@ from oci.data_science.models import JobRun, Metadata, Model, UpdateModelDetails
14
15
 
15
16
  from ads.aqua import ODSC_MODEL_COMPARTMENT_OCID, logger
16
17
  from ads.aqua.app import AquaApp
18
+ from ads.aqua.common.entities import AquaMultiModelRef
17
19
  from ads.aqua.common.enums import (
18
20
  ConfigFolder,
19
21
  CustomInferenceContainerTypeFamily,
@@ -37,12 +39,13 @@ from ads.aqua.common.utils import (
37
39
  get_artifact_path,
38
40
  get_container_config,
39
41
  get_hf_model_info,
42
+ get_preferred_compatible_family,
40
43
  list_os_files_with_extension,
41
44
  load_config,
42
45
  read_file,
43
46
  upload_folder,
44
47
  )
45
- from ads.aqua.config.container_config import AquaContainerConfig
48
+ from ads.aqua.config.container_config import AquaContainerConfig, Usage
46
49
  from ads.aqua.constants import (
47
50
  AQUA_MODEL_ARTIFACT_CONFIG,
48
51
  AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME,
@@ -77,6 +80,7 @@ from ads.aqua.model.entities import (
77
80
  ImportModelDetails,
78
81
  ModelValidationResult,
79
82
  )
83
+ from ads.aqua.model.enums import MultiModelSupportedTaskType
80
84
  from ads.common.auth import default_signer
81
85
  from ads.common.oci_resource import SEARCH_TYPE, OCIResource
82
86
  from ads.common.utils import UNKNOWN, get_console_link
@@ -91,6 +95,7 @@ from ads.config import (
91
95
  TENANCY_OCID,
92
96
  )
93
97
  from ads.model import DataScienceModel
98
+ from ads.model.common.utils import MetadataArtifactPathType
94
99
  from ads.model.model_metadata import (
95
100
  MetadataCustomCategory,
96
101
  ModelCustomMetadata,
@@ -135,40 +140,45 @@ class AquaModelApp(AquaApp):
135
140
  @telemetry(entry_point="plugin=model&action=create", name="aqua")
136
141
  def create(
137
142
  self,
138
- model_id: str,
139
- project_id: str,
140
- compartment_id: str = None,
141
- freeform_tags: Optional[dict] = None,
142
- defined_tags: Optional[dict] = None,
143
+ model_id: Union[str, AquaMultiModelRef],
144
+ project_id: Optional[str] = None,
145
+ compartment_id: Optional[str] = None,
146
+ freeform_tags: Optional[Dict] = None,
147
+ defined_tags: Optional[Dict] = None,
143
148
  **kwargs,
144
149
  ) -> DataScienceModel:
145
- """Creates custom aqua model from service model.
150
+ """
151
+ Creates a custom Aqua model from a service model.
146
152
 
147
153
  Parameters
148
154
  ----------
149
- model_id: str
150
- The service model id.
151
- project_id: str
152
- The project id for custom model.
153
- compartment_id: str
154
- The compartment id for custom model. Defaults to None.
155
- If not provided, compartment id will be fetched from environment variables.
156
- freeform_tags: dict
157
- Freeform tags for the model
158
- defined_tags: dict
159
- Defined tags for the model
155
+ model_id : Union[str, AquaMultiModelRef]
156
+ The model ID as a string or a AquaMultiModelRef instance to be deployed.
157
+ project_id : Optional[str]
158
+ The project ID for the custom model.
159
+ compartment_id : Optional[str]
160
+ The compartment ID for the custom model. Defaults to None.
161
+ If not provided, the compartment ID will be fetched from environment variables.
162
+ freeform_tags : Optional[Dict]
163
+ Freeform tags for the model.
164
+ defined_tags : Optional[Dict]
165
+ Defined tags for the model.
166
+
160
167
  Returns
161
168
  -------
162
- DataScienceModel:
169
+ DataScienceModel
163
170
  The instance of DataScienceModel.
164
171
  """
172
+ model_id = (
173
+ model_id.model_id if isinstance(model_id, AquaMultiModelRef) else model_id
174
+ )
165
175
  service_model = DataScienceModel.from_id(model_id)
166
176
  target_project = project_id or PROJECT_OCID
167
177
  target_compartment = compartment_id or COMPARTMENT_OCID
168
178
 
169
179
  if service_model.compartment_id != ODSC_MODEL_COMPARTMENT_OCID:
170
180
  logger.info(
171
- f"Aqua Model {model_id} already exists in user's compartment."
181
+ f"Aqua Model {model_id} already exists in the user's compartment."
172
182
  "Skipped copying."
173
183
  )
174
184
  return service_model
@@ -195,14 +205,13 @@ class AquaModelApp(AquaApp):
195
205
  .with_custom_metadata_list(service_model.custom_metadata_list)
196
206
  .with_defined_metadata_list(service_model.defined_metadata_list)
197
207
  .with_provenance_metadata(service_model.provenance_metadata)
198
- # TODO: decide what kwargs will be needed.
199
208
  .create(model_by_reference=True, **kwargs)
200
209
  )
201
210
  logger.info(
202
211
  f"Aqua Model {custom_model.id} created with the service model {model_id}."
203
212
  )
204
213
 
205
- # tracks unique models that were created in the user compartment
214
+ # Track unique models that were created in the user's compartment
206
215
  self.telemetry.record_event_async(
207
216
  category="aqua/service/model",
208
217
  action="create",
@@ -211,6 +220,217 @@ class AquaModelApp(AquaApp):
211
220
 
212
221
  return custom_model
213
222
 
223
+ @telemetry(entry_point="plugin=model&action=create", name="aqua")
224
+ def create_multi(
225
+ self,
226
+ models: List[AquaMultiModelRef],
227
+ project_id: Optional[str] = None,
228
+ compartment_id: Optional[str] = None,
229
+ freeform_tags: Optional[Dict] = None,
230
+ defined_tags: Optional[Dict] = None,
231
+ **kwargs, # noqa: ARG002
232
+ ) -> DataScienceModel:
233
+ """
234
+ Creates a multi-model grouping using the provided model list.
235
+
236
+ Parameters
237
+ ----------
238
+ models : List[AquaMultiModelRef]
239
+ List of AquaMultiModelRef instances for creating a multi-model group.
240
+ project_id : Optional[str]
241
+ The project ID for the multi-model group.
242
+ compartment_id : Optional[str]
243
+ The compartment ID for the multi-model group.
244
+ freeform_tags : Optional[Dict]
245
+ Freeform tags for the model.
246
+ defined_tags : Optional[Dict]
247
+ Defined tags for the model.
248
+
249
+ Returns
250
+ -------
251
+ DataScienceModel
252
+ Instance of DataScienceModel object.
253
+ """
254
+
255
+ if not models:
256
+ raise AquaValueError(
257
+ "Model list cannot be empty. Please provide at least one model for deployment."
258
+ )
259
+
260
+ artifact_list = []
261
+ display_name_list = []
262
+ model_custom_metadata = ModelCustomMetadata()
263
+
264
+ # Get container config
265
+ container_config = get_container_config()
266
+
267
+ service_inference_containers = AquaContainerConfig.from_container_index_json(
268
+ config=container_config
269
+ ).inference.values()
270
+
271
+ supported_container_families = [
272
+ container_config_item.family
273
+ for container_config_item in service_inference_containers
274
+ if any(
275
+ usage in container_config_item.usages
276
+ for usage in [Usage.MULTI_MODEL, Usage.OTHER]
277
+ )
278
+ ]
279
+
280
+ if not supported_container_families:
281
+ raise AquaValueError(
282
+ "Currently, there are no containers that support multi-model deployment."
283
+ )
284
+
285
+ selected_models_deployment_containers = set()
286
+
287
+ # Process each model
288
+ for model in models:
289
+ source_model = DataScienceModel.from_id(model.model_id)
290
+ display_name = source_model.display_name
291
+ # Update model name in user's input model
292
+ model.model_name = model.model_name or display_name
293
+
294
+ # TODO Uncomment the section below, if only service models should be allowed for multi-model deployment
295
+ # if not source_model.freeform_tags.get(Tags.AQUA_SERVICE_MODEL_TAG, UNKNOWN):
296
+ # raise AquaValueError(
297
+ # f"Invalid selected model {display_name}. "
298
+ # "Currently only service models are supported for multi model deployment."
299
+ # )
300
+
301
+ if (
302
+ source_model.freeform_tags.get(Tags.TASK, UNKNOWN).lower()
303
+ not in MultiModelSupportedTaskType
304
+ ):
305
+ raise AquaValueError(
306
+ f"Invalid or missing {Tags.TASK} tag for selected model {display_name}. "
307
+ f"Currently only `{MultiModelSupportedTaskType.values()}` models are supported for multi model deployment."
308
+ )
309
+
310
+ display_name_list.append(display_name)
311
+
312
+ # Retrieve model artifact
313
+ model_artifact_path = source_model.artifact
314
+ if not model_artifact_path:
315
+ raise AquaValueError(
316
+ f"Model '{display_name}' (ID: {model.model_id}) has no artifacts. "
317
+ "Please register the model first."
318
+ )
319
+
320
+ # Update model artifact location in user's input model
321
+ model.artifact_location = model_artifact_path
322
+
323
+ artifact_list.append(model_artifact_path)
324
+
325
+ # Validate deployment container consistency
326
+ deployment_container = source_model.custom_metadata_list.get(
327
+ ModelCustomMetadataFields.DEPLOYMENT_CONTAINER,
328
+ ModelCustomMetadataItem(
329
+ key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER
330
+ ),
331
+ ).value
332
+
333
+ if deployment_container not in supported_container_families:
334
+ raise AquaValueError(
335
+ f"Unsupported deployment container '{deployment_container}' for model '{source_model.id}'. "
336
+ f"Only '{supported_container_families}' are supported for multi-model deployments."
337
+ )
338
+
339
+ selected_models_deployment_containers.add(deployment_container)
340
+
341
+ if not selected_models_deployment_containers:
342
+ raise AquaValueError(
343
+ "None of the selected models are associated with a recognized container family. "
344
+ "Please review the selected models, or select a different group of models."
345
+ )
346
+
347
+ # Check if the all models in the group shares same container family
348
+ if len(selected_models_deployment_containers) > 1:
349
+ deployment_container = get_preferred_compatible_family(
350
+ selected_families=selected_models_deployment_containers
351
+ )
352
+ if not deployment_container:
353
+ raise AquaValueError(
354
+ "The selected models are associated with different container families: "
355
+ f"{list(selected_models_deployment_containers)}."
356
+ "For multi-model deployment, all models in the group must share the same container family."
357
+ )
358
+ else:
359
+ deployment_container = selected_models_deployment_containers.pop()
360
+
361
+ # Generate model group details
362
+ timestamp = datetime.now().strftime("%Y%m%d")
363
+ model_group_display_name = f"model_group_{timestamp}"
364
+ combined_models = ", ".join(display_name_list)
365
+ model_group_description = f"Multi-model grouping using {combined_models}."
366
+
367
+ # Add global metadata
368
+ model_custom_metadata.add(
369
+ key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER,
370
+ value=deployment_container,
371
+ description=f"Inference container mapping for {model_group_display_name}",
372
+ category="Other",
373
+ )
374
+ model_custom_metadata.add(
375
+ key=ModelCustomMetadataFields.MULTIMODEL_GROUP_COUNT,
376
+ value=str(len(models)),
377
+ description="Number of models in the group.",
378
+ category="Other",
379
+ )
380
+
381
+ # Combine tags. The `Tags.AQUA_TAG` has been excluded, because we don't want to show
382
+ # the models created for multi-model purpose in the AQUA models list.
383
+ tags = {
384
+ # Tags.AQUA_TAG: "active",
385
+ Tags.MULTIMODEL_TYPE_TAG: "true",
386
+ **(freeform_tags or {}),
387
+ }
388
+
389
+ # Create multi-model group
390
+ custom_model = (
391
+ DataScienceModel()
392
+ .with_compartment_id(compartment_id)
393
+ .with_project_id(project_id)
394
+ .with_display_name(model_group_display_name)
395
+ .with_description(model_group_description)
396
+ .with_freeform_tags(**tags)
397
+ .with_defined_tags(**(defined_tags or {}))
398
+ .with_custom_metadata_list(model_custom_metadata)
399
+ )
400
+
401
+ # Attach artifacts
402
+ for artifact in artifact_list:
403
+ custom_model.add_artifact(uri=artifact)
404
+
405
+ # Finalize creation
406
+ custom_model.create(model_by_reference=True)
407
+
408
+ logger.info(
409
+ f"Aqua Model '{custom_model.id}' created with models: {', '.join(display_name_list)}."
410
+ )
411
+
412
+ # Create custom metadata for multi model metadata
413
+ custom_model.create_custom_metadata_artifact(
414
+ metadata_key_name=ModelCustomMetadataFields.MULTIMODEL_METADATA,
415
+ artifact_path_or_content=json.dumps(
416
+ [model.model_dump() for model in models]
417
+ ).encode(),
418
+ path_type=MetadataArtifactPathType.CONTENT,
419
+ )
420
+
421
+ logger.debug(
422
+ f"Multi model metadata uploaded for Aqua model: {custom_model.id}."
423
+ )
424
+
425
+ # Track telemetry event
426
+ self.telemetry.record_event_async(
427
+ category="aqua/multimodel",
428
+ action="create",
429
+ detail=combined_models,
430
+ )
431
+
432
+ return custom_model
433
+
214
434
  @telemetry(entry_point="plugin=model&action=get", name="aqua")
215
435
  def get(self, model_id: str, load_model_card: Optional[bool] = True) -> "AquaModel":
216
436
  """Gets the information of an Aqua model.
@@ -1448,8 +1668,9 @@ class AquaModelApp(AquaApp):
1448
1668
  self, import_model_details: ImportModelDetails = None, **kwargs
1449
1669
  ) -> AquaModel:
1450
1670
  """Loads the model from object storage and registers as Model in Data Science Model catalog
1451
- The inference container and finetuning container could be of type Service Manged Container(SMC) or custom.
1452
- If it is custom, full container URI is expected. If it of type SMC, only the container family name is expected.
1671
+ The inference container and finetuning container could be of type Service Managed Container(SMC) or custom.
1672
+ If it is custom, full container URI is expected. If it of type SMC, only the container family name is expected.\n
1673
+ For detailed information about CLI flags see: https://github.com/oracle-samples/oci-data-science-ai-samples/blob/main/ai-quick-actions/cli-tips.md#register-model
1453
1674
 
1454
1675
  Args:
1455
1676
  import_model_details (ImportModelDetails): Model details for importing the model.
@@ -1609,6 +1830,8 @@ class AquaModelApp(AquaApp):
1609
1830
  filter_tag = Tags.AQUA_FINE_TUNED_MODEL_TAG
1610
1831
  elif model_type == ModelType.BASE:
1611
1832
  filter_tag = Tags.BASE_MODEL_CUSTOM
1833
+ # elif model_type == ModelType.MULTIMODEL:
1834
+ # filter_tag = Tags.MULTIMODEL_TYPE_TAG
1612
1835
  else:
1613
1836
  raise AquaValueError(
1614
1837
  f"Model of type {model_type} is unknown. The values should be in {ModelType.values()}"